Skip to content

Commit 7ff91c1

Browse files
committed
Parse jsdoc type literals in params
Now Typescript supports the creation of anonymous types using successive `@param` lines in JSDoc: ```js /** * @param {object} o - has a string and a number * @param {string} o.s - the string * @param {number} o.n - the number */ function f(o) { return o.s.length + o.n; } ``` This is equivalent to the Typescript syntax `{ s: string, n: number }`, but it allows per-property documentation, even for types that only need to be used in one place. (`@typedef` can be used for reusable types.) If the type of the initial `@param` is `{object[]}`, then the resulting type is an array of the specified anonymous type: ```js /** * @param {Object[]} os - has a string and a number * @param {string} os[].s - the string * @param {number} os[].n - the number */ function f(os) { return os[0].s; } ``` Finally, nested anonymous types can be created by nesting the pattern: ```js /** * @param {Object[]} os - has a string and a number * @param {string} os[].s - the string * @param {object} os[].nested - it's nested because of the object type * @param {number} os[].nested.length - it's a number */ function f(os) { return os[0].nested.length; } ``` Implementation notes: 1. I refactored JSDocParameterTag and JSDocPropertyTag to JSDocPropertyLikeTag and modified its parsing to be more succinct. These changes make the overall change easier to read but are not strictly required. 2. parseJSDocEntityName accepts postfix[] as in `os[].nested.length`, but it doesn't check that usages are correct. Such checking would be easy to add but tedious and low-value. 3. `@typedef` doesn't support nested `@property` tags, but does support `object[]` types. This is mostly a practical decision, backed up by the fact that usejsdoc.org doesn't document nested types for `@typedef`.
1 parent 441daa4 commit 7ff91c1

8 files changed

Lines changed: 228 additions & 150 deletions

File tree

src/compiler/binder.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,9 +1504,9 @@ namespace ts {
15041504
return declareSymbol(container.symbol.exports, container.symbol, node, symbolFlags, symbolExcludes);
15051505

15061506
case SyntaxKind.TypeLiteral:
1507+
case SyntaxKind.JSDocTypeLiteral:
15071508
case SyntaxKind.ObjectLiteralExpression:
15081509
case SyntaxKind.InterfaceDeclaration:
1509-
case SyntaxKind.JSDocTypeLiteral:
15101510
case SyntaxKind.JsxAttributes:
15111511
// Interface/Object-types always have their children added to the 'members' of
15121512
// their container. They are only accessible through an instance of their
@@ -2104,8 +2104,9 @@ namespace ts {
21042104
case SyntaxKind.ConstructorType:
21052105
return bindFunctionOrConstructorType(<SignatureDeclaration>node);
21062106
case SyntaxKind.TypeLiteral:
2107+
case SyntaxKind.JSDocTypeLiteral:
21072108
case SyntaxKind.MappedType:
2108-
return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode);
2109+
return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral);
21092110
case SyntaxKind.ObjectLiteralExpression:
21102111
return bindObjectLiteralExpression(<ObjectLiteralExpression>node);
21112112
case SyntaxKind.FunctionExpression:
@@ -2163,13 +2164,16 @@ namespace ts {
21632164
case SyntaxKind.ModuleBlock:
21642165
return updateStrictModeStatementList((<Block | ModuleBlock>node).statements);
21652166

2167+
case SyntaxKind.JSDocParameterTag:
2168+
if (node.parent.kind !== SyntaxKind.JSDocTypeLiteral) {
2169+
break;
2170+
}
2171+
// falls through
21662172
case SyntaxKind.JSDocPropertyTag:
2167-
return declareSymbolAndAddToSymbolTable(node as JSDocPropertyTag,
2168-
(node as JSDocPropertyTag).isBracketed || ((node as JSDocPropertyTag).typeExpression && (node as JSDocPropertyTag).typeExpression.type.kind === SyntaxKind.JSDocOptionalType) ?
2173+
return declareSymbolAndAddToSymbolTable(node as JSDocPropertyLikeTag,
2174+
(node as JSDocPropertyLikeTag).isBracketed || ((node as JSDocPropertyLikeTag).typeExpression && (node as JSDocPropertyLikeTag).typeExpression.type.kind === SyntaxKind.JSDocOptionalType) ?
21692175
SymbolFlags.Property | SymbolFlags.Optional : SymbolFlags.Property,
21702176
SymbolFlags.PropertyExcludes);
2171-
case SyntaxKind.JSDocTypeLiteral:
2172-
return bindAnonymousTypeWorker(node as JSDocTypeLiteral);
21732177
case SyntaxKind.JSDocTypedefTag: {
21742178
const { fullName } = node as JSDocTypedefTag;
21752179
if (!fullName || fullName.kind === SyntaxKind.Identifier) {

src/compiler/checker.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5074,20 +5074,9 @@ namespace ts {
50745074
return unknownType;
50755075
}
50765076

5077-
let declaration: JSDocTypedefTag | TypeAliasDeclaration = getDeclarationOfKind<JSDocTypedefTag>(symbol, SyntaxKind.JSDocTypedefTag);
5078-
let type: Type;
5079-
if (declaration) {
5080-
if (declaration.jsDocTypeLiteral) {
5081-
type = getTypeFromTypeNode(declaration.jsDocTypeLiteral);
5082-
}
5083-
else {
5084-
type = getTypeFromTypeNode(declaration.typeExpression.type);
5085-
}
5086-
}
5087-
else {
5088-
declaration = getDeclarationOfKind<TypeAliasDeclaration>(symbol, SyntaxKind.TypeAliasDeclaration);
5089-
type = getTypeFromTypeNode(declaration.type);
5090-
}
5077+
const declaration = getDeclarationOfKind<JSDocTypedefTag>(symbol, SyntaxKind.JSDocTypedefTag) ||
5078+
getDeclarationOfKind<TypeAliasDeclaration>(symbol, SyntaxKind.TypeAliasDeclaration);
5079+
let type = getTypeFromTypeNode(declaration.kind === SyntaxKind.JSDocTypedefTag ? declaration.typeExpression : declaration.type);
50915080

50925081
if (popTypeResolution()) {
50935082
const typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
@@ -7654,9 +7643,12 @@ namespace ts {
76547643
links.resolvedType = emptyTypeLiteralType;
76557644
}
76567645
else {
7657-
const type = createObjectType(ObjectFlags.Anonymous, node.symbol);
7646+
let type = createObjectType(ObjectFlags.Anonymous, node.symbol);
76587647
type.aliasSymbol = aliasSymbol;
76597648
type.aliasTypeArguments = getAliasTypeArgumentsForTypeNode(node);
7649+
if (isJSDocTypeLiteral(node) && node.isArrayType) {
7650+
type = createArrayType(type);
7651+
}
76607652
links.resolvedType = type;
76617653
}
76627654
}
@@ -7890,7 +7882,8 @@ namespace ts {
78907882
case SyntaxKind.ParenthesizedType:
78917883
case SyntaxKind.JSDocNonNullableType:
78927884
case SyntaxKind.JSDocOptionalType:
7893-
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode>node).type);
7885+
case SyntaxKind.JSDocTypeExpression:
7886+
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode | JSDocTypeExpression>node).type);
78947887
case SyntaxKind.FunctionType:
78957888
case SyntaxKind.ConstructorType:
78967889
case SyntaxKind.TypeLiteral:

0 commit comments

Comments
 (0)