Skip to content

Commit c425ac9

Browse files
committed
Make automatic function conversion more conservative
Ref: #2881
1 parent 627dec7 commit c425ac9

File tree

9 files changed

+86
-15
lines changed

9 files changed

+86
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ title: Changelog
1313
- The `source-order` sort ordering now considers package names / package relative paths instead of using the absolute paths to a file.
1414
- TypeDoc will only check for a project README file next to the discovered `package.json` file if `--readme` is not set
1515
this change improves handling of monorepo setups where some packages have readme files and others do not, #2875.
16-
- Function-like variable declarations with a type annotation will now be converted as variables unless annotated with `@function`, #2881.
16+
- Function-like variable exports will now only be automatically converted as function types if
17+
they are initialized with a function expression. TypeDoc can be instructed to convert them as functions
18+
with the `@function` tag, #2881.
1719

1820
### API Breaking Changes
1921

site/tags/function.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ title: "@function"
66

77
**Tag Kind:** [Modifier](../tags.md#modifier-tags)
88

9-
If a variable declaration is callable and does not have a type declaration,
10-
TypeDoc may convert it as a function. Sometimes, even if a type declaration
11-
is specified, it is desirable to convert a variable as if it was a function.
9+
If a variable declaration is callable (but not constructable), TypeDoc can
10+
convert it as a function. TypeDoc will only automatically convert it as a function
11+
if the variable's initializer is a function expression and the variable is not
12+
explicitly typed.
1213

13-
This tag will cause TypeDoc to convert the specified variable as a function
14-
even if it has a type declaration. TypeDoc will still refuse to convert the
15-
variable as a function if it contains construct signatures or does not contain
16-
any call signatures.
14+
TypeDoc can be instructed to convert a callable variable declaration as a function
15+
with the `@function` tag. The `@function` tag will have no effect if the variable
16+
it is placed on is not callable or is constructable.
1717

1818
## Example
1919

@@ -32,6 +32,21 @@ export const Callable2: MultiCallSignature = () => "";
3232

3333
// Documented as if it was a function
3434
export const Callable3 = () => "";
35+
36+
// Documented as a variable
37+
export const Callable4 = Object.assign(function () {
38+
return "";
39+
}, {
40+
fnProp: "",
41+
});
42+
43+
// Documented as if it was a function
44+
/** @function */
45+
export const Callable5 = Object.assign(function () {
46+
return "";
47+
}, {
48+
fnProp: "",
49+
});
3550
```
3651

3752
## See Also

src/lib/converter/symbols.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ function convertVariable(
995995
) {
996996
if (
997997
comment?.hasModifier("@function") ||
998-
!(declaration && ts.isVariableDeclaration(declaration) && declaration.type)
998+
(declaration && shouldAutomaticallyConvertAsFunction(declaration))
999999
) {
10001000
return convertVariableAsFunction(context, symbol, exportSymbol);
10011001
}
@@ -1418,3 +1418,48 @@ function setSymbolModifiers(symbol: ts.Symbol, reflection: Reflection) {
14181418
hasAllFlags(symbol.flags, ts.SymbolFlags.Optional),
14191419
);
14201420
}
1421+
1422+
function shouldAutomaticallyConvertAsFunction(node: ts.Declaration): boolean {
1423+
// const fn = () => {}
1424+
if (ts.isVariableDeclaration(node)) {
1425+
if (node.type || !node.initializer) return false;
1426+
1427+
return isFunctionLikeInitializer(node.initializer);
1428+
}
1429+
1430+
// { fn: () => {} }
1431+
if (ts.isPropertyAssignment(node)) {
1432+
return isFunctionLikeInitializer(node.initializer);
1433+
}
1434+
1435+
// exports.fn = () => {}
1436+
// exports.fn ||= () => {}
1437+
// exports.fn ??= () => {}
1438+
if (ts.isPropertyAccessExpression(node)) {
1439+
if (
1440+
ts.isBinaryExpression(node.parent) &&
1441+
[ts.SyntaxKind.EqualsToken, ts.SyntaxKind.BarBarEqualsToken, ts.SyntaxKind.QuestionQuestionEqualsToken]
1442+
.includes(node.parent.operatorToken.kind)
1443+
) {
1444+
return isFunctionLikeInitializer(node.parent.right);
1445+
}
1446+
}
1447+
1448+
return false;
1449+
}
1450+
1451+
function isFunctionLikeInitializer(node: ts.Expression): boolean {
1452+
if (ts.isFunctionExpression(node) || ts.isArrowFunction(node)) {
1453+
return true;
1454+
}
1455+
1456+
if (ts.isSatisfiesExpression(node)) {
1457+
return isFunctionLikeInitializer(node.expression);
1458+
}
1459+
1460+
if (ts.isAsExpression(node)) {
1461+
return isFunctionLikeInitializer(node.expression);
1462+
}
1463+
1464+
return false;
1465+
}

src/test/converter2/issues/gh1462.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ const sideEffects = {
55
prop: 1,
66
};
77

8-
export const { method: METHOD, prop: PROP } = sideEffects;
8+
export const {
9+
/** @function */
10+
method: METHOD,
11+
prop: PROP,
12+
} = sideEffects;

src/test/converter2/issues/gh1770.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const api = (): {
88
export const {
99
/**
1010
* Docs for Sym1
11+
* @function
1112
*/
1213
sym1,
1314

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
const makeFn = () => () => {};
22

3-
/** Docs */
3+
/** Docs @function */
44
export const myFn = makeFn();

src/test/converter2/issues/gh2042.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ function factory() {
44
return fn;
55
}
66

7+
/** @function */
78
export const built = factory();
89

9-
/** outer docs */
10+
/** outer docs @function */
1011
export const built2 = factory();
1112

1213
const obj = {
1314
/** inner docs */
1415
fn(x: unknown) {},
1516
};
1617

18+
/** @function */
1719
export const fn = obj.fn;
1820

1921
/**
2022
* outer docs
2123
* @param x param-docs
24+
* @function
2225
*/
2326
export const fn2 = obj.fn;

src/test/converter2/issues/gh2307.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const times = (b: number) => (a: number) => a * b;
22

3+
/** @function */
34
export const double = times(2);
45

56
export const foo = () => 123;

src/test/issues.c2.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,9 +1121,9 @@ describe("Issue Tests", () => {
11211121
return refl.signatures?.flatMap((sig) => sig.sources!.map((src) => src.line));
11221122
};
11231123

1124-
equal(getLines("double"), [3]);
1125-
equal(getLines("foo"), [5]);
1126-
equal(getLines("all"), [9, 10]);
1124+
equal(getLines("double"), [4]);
1125+
equal(getLines("foo"), [6]);
1126+
equal(getLines("all"), [10, 11]);
11271127
});
11281128

11291129
it("#2320 Uses type parameters from parent class in arrow-methods, ", () => {

0 commit comments

Comments
 (0)