Skip to content

Commit 24d98f2

Browse files
author
Andy
authored
Merge pull request #15856 from Microsoft/jsdoc
Support for JSDoc in services
2 parents 2068192 + d646c72 commit 24d98f2

36 files changed

+297
-140
lines changed

src/compiler/binder.ts

+40-28
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ namespace ts {
439439
// during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation
440440
// and this case is specially handled. Module augmentations should only be merged with original module definition
441441
// and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed.
442+
if (node.kind === SyntaxKind.JSDocTypedefTag) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file.
442443
const isJSDocTypedefInJSDocNamespace = node.kind === SyntaxKind.JSDocTypedefTag &&
443444
(node as JSDocTypedefTag).name &&
444445
(node as JSDocTypedefTag).name.kind === SyntaxKind.Identifier &&
@@ -603,9 +604,7 @@ namespace ts {
603604
// Binding of JsDocComment should be done before the current block scope container changes.
604605
// because the scope of JsDocComment should not be affected by whether the current node is a
605606
// container or not.
606-
if (isInJavaScriptFile(node) && node.jsDoc) {
607-
forEach(node.jsDoc, bind);
608-
}
607+
forEach(node.jsDoc, bind);
609608
if (checkUnreachable(node)) {
610609
bindEachChild(node);
611610
return;
@@ -1913,9 +1912,7 @@ namespace ts {
19131912
// Here the current node is "foo", which is a container, but the scope of "MyType" should
19141913
// not be inside "foo". Therefore we always bind @typedef before bind the parent node,
19151914
// and skip binding this tag later when binding all the other jsdoc tags.
1916-
if (isInJavaScriptFile(node)) {
1917-
bindJSDocTypedefTagIfAny(node);
1918-
}
1915+
bindJSDocTypedefTagIfAny(node);
19191916

19201917
// First we bind declaration nodes to a symbol if possible. We'll both create a symbol
19211918
// and then potentially add the symbol to an appropriate symbol table. Possible
@@ -2003,7 +2000,7 @@ namespace ts {
20032000
// for typedef type names with namespaces, bind the new jsdoc type symbol here
20042001
// because it requires all containing namespaces to be in effect, namely the
20052002
// current "blockScopeContainer" needs to be set to its immediate namespace parent.
2006-
if ((<Identifier>node).isInJSDocNamespace) {
2003+
if (isInJavaScriptFile(node) && (<Identifier>node).isInJSDocNamespace) {
20072004
let parentNode = node.parent;
20082005
while (parentNode && parentNode.kind !== SyntaxKind.JSDocTypedefTag) {
20092006
parentNode = parentNode.parent;
@@ -2073,10 +2070,7 @@ namespace ts {
20732070
return bindVariableDeclarationOrBindingElement(<VariableDeclaration | BindingElement>node);
20742071
case SyntaxKind.PropertyDeclaration:
20752072
case SyntaxKind.PropertySignature:
2076-
case SyntaxKind.JSDocRecordMember:
2077-
return bindPropertyOrMethodOrAccessor(<Declaration>node, SymbolFlags.Property | ((<PropertyDeclaration>node).questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes);
2078-
case SyntaxKind.JSDocPropertyTag:
2079-
return bindJSDocProperty(<JSDocPropertyTag>node);
2073+
return bindPropertyWorker(node as PropertyDeclaration | PropertySignature);
20802074
case SyntaxKind.PropertyAssignment:
20812075
case SyntaxKind.ShorthandPropertyAssignment:
20822076
return bindPropertyOrMethodOrAccessor(<Declaration>node, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
@@ -2121,13 +2115,10 @@ namespace ts {
21212115
return bindPropertyOrMethodOrAccessor(<Declaration>node, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes);
21222116
case SyntaxKind.FunctionType:
21232117
case SyntaxKind.ConstructorType:
2124-
case SyntaxKind.JSDocFunctionType:
21252118
return bindFunctionOrConstructorType(<SignatureDeclaration>node);
21262119
case SyntaxKind.TypeLiteral:
21272120
case SyntaxKind.MappedType:
2128-
case SyntaxKind.JSDocTypeLiteral:
2129-
case SyntaxKind.JSDocRecordType:
2130-
return bindAnonymousDeclaration(<Declaration>node, SymbolFlags.TypeLiteral, "__type");
2121+
return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode);
21312122
case SyntaxKind.ObjectLiteralExpression:
21322123
return bindObjectLiteralExpression(<ObjectLiteralExpression>node);
21332124
case SyntaxKind.FunctionExpression:
@@ -2148,11 +2139,6 @@ namespace ts {
21482139
return bindClassLikeDeclaration(<ClassLikeDeclaration>node);
21492140
case SyntaxKind.InterfaceDeclaration:
21502141
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes);
2151-
case SyntaxKind.JSDocTypedefTag:
2152-
if (!(<JSDocTypedefTag>node).fullName || (<JSDocTypedefTag>node).fullName.kind === SyntaxKind.Identifier) {
2153-
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
2154-
}
2155-
break;
21562142
case SyntaxKind.TypeAliasDeclaration:
21572143
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
21582144
case SyntaxKind.EnumDeclaration:
@@ -2190,9 +2176,41 @@ namespace ts {
21902176
// falls through
21912177
case SyntaxKind.ModuleBlock:
21922178
return updateStrictModeStatementList((<Block | ModuleBlock>node).statements);
2179+
2180+
default:
2181+
if (isInJavaScriptFile(node)) return bindJSDocWorker(node);
21932182
}
21942183
}
21952184

2185+
function bindJSDocWorker(node: Node) {
2186+
switch (node.kind) {
2187+
case SyntaxKind.JSDocRecordMember:
2188+
return bindPropertyWorker(node as JSDocRecordMember);
2189+
case SyntaxKind.JSDocPropertyTag:
2190+
return declareSymbolAndAddToSymbolTable(node as JSDocPropertyTag, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
2191+
case SyntaxKind.JSDocFunctionType:
2192+
return bindFunctionOrConstructorType(<SignatureDeclaration>node);
2193+
case SyntaxKind.JSDocTypeLiteral:
2194+
case SyntaxKind.JSDocRecordType:
2195+
return bindAnonymousTypeWorker(node as JSDocTypeLiteral | JSDocRecordType);
2196+
case SyntaxKind.JSDocTypedefTag: {
2197+
const { fullName } = node as JSDocTypedefTag;
2198+
if (!fullName || fullName.kind === SyntaxKind.Identifier) {
2199+
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
2200+
}
2201+
break;
2202+
}
2203+
}
2204+
}
2205+
2206+
function bindPropertyWorker(node: PropertyDeclaration | PropertySignature) {
2207+
return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes);
2208+
}
2209+
2210+
function bindAnonymousTypeWorker(node: TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral | JSDocRecordType) {
2211+
return bindAnonymousDeclaration(<Declaration>node, SymbolFlags.TypeLiteral, "__type");
2212+
}
2213+
21962214
function checkTypePredicate(node: TypePredicateNode) {
21972215
const { parameterName, type } = node;
21982216
if (parameterName && parameterName.kind === SyntaxKind.Identifier) {
@@ -2558,10 +2576,8 @@ namespace ts {
25582576
}
25592577

25602578
function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) {
2561-
if (!file.isDeclarationFile && !isInAmbientContext(node)) {
2562-
if (isAsyncFunction(node)) {
2563-
emitFlags |= NodeFlags.HasAsyncFunctions;
2564-
}
2579+
if (!file.isDeclarationFile && !isInAmbientContext(node) && isAsyncFunction(node)) {
2580+
emitFlags |= NodeFlags.HasAsyncFunctions;
25652581
}
25662582

25672583
if (currentFlow && isObjectLiteralOrClassExpressionMethod(node)) {
@@ -2573,10 +2589,6 @@ namespace ts {
25732589
: declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes);
25742590
}
25752591

2576-
function bindJSDocProperty(node: JSDocPropertyTag) {
2577-
return declareSymbolAndAddToSymbolTable(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
2578-
}
2579-
25802592
// reachability checks
25812593

25822594
function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean {

src/compiler/checker.ts

+5
Original file line numberDiff line numberDiff line change
@@ -22327,6 +22327,11 @@ namespace ts {
2232722327
}
2232822328
}
2232922329

22330+
if (entityName.parent!.kind === SyntaxKind.JSDocParameterTag) {
22331+
const parameter = ts.getParameterFromJSDoc(entityName.parent as JSDocParameterTag);
22332+
return parameter && parameter.symbol;
22333+
}
22334+
2233022335
if (isPartOfExpression(entityName)) {
2233122336
if (nodeIsMissing(entityName)) {
2233222337
// Missing entity name.

src/compiler/parser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ namespace ts {
5050
// stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise,
5151
// embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns
5252
// a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned.
53-
export function forEachChild<T>(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T | undefined {
53+
export function forEachChild<T>(node: Node, cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
5454
if (!node) {
5555
return;
5656
}

src/compiler/utilities.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -1599,7 +1599,7 @@ namespace ts {
15991599

16001600
// Pull parameter comments from declaring function as well
16011601
if (node.kind === SyntaxKind.Parameter) {
1602-
cache = concatenate(cache, getJSDocParameterTags(node));
1602+
cache = concatenate(cache, getJSDocParameterTags(node as ParameterDeclaration));
16031603
}
16041604

16051605
if (isVariableLike(node) && node.initializer) {
@@ -1610,11 +1610,8 @@ namespace ts {
16101610
}
16111611
}
16121612

1613-
export function getJSDocParameterTags(param: Node): JSDocParameterTag[] {
1614-
if (!isParameter(param)) {
1615-
return undefined;
1616-
}
1617-
const func = param.parent as FunctionLikeDeclaration;
1613+
export function getJSDocParameterTags(param: ParameterDeclaration): JSDocParameterTag[] {
1614+
const func = param.parent;
16181615
const tags = getJSDocTags(func, SyntaxKind.JSDocParameterTag) as JSDocParameterTag[];
16191616
if (!param.name) {
16201617
// this is an anonymous jsdoc param from a `function(type1, type2): type3` specification
@@ -1635,10 +1632,22 @@ namespace ts {
16351632
}
16361633
}
16371634

1635+
/** Does the opposite of `getJSDocParameterTags`: given a JSDoc parameter, finds the parameter corresponding to it. */
1636+
export function getParameterFromJSDoc(node: JSDocParameterTag): ParameterDeclaration | undefined {
1637+
const name = node.parameterName.text;
1638+
const grandParent = node.parent!.parent!;
1639+
Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment);
1640+
if (!isFunctionLike(grandParent)) {
1641+
return undefined;
1642+
}
1643+
return find(grandParent.parameters, p =>
1644+
p.name.kind === SyntaxKind.Identifier && p.name.text === name);
1645+
}
1646+
16381647
export function getJSDocType(node: Node): JSDocType {
16391648
let tag: JSDocTypeTag | JSDocParameterTag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag;
16401649
if (!tag && node.kind === SyntaxKind.Parameter) {
1641-
const paramTags = getJSDocParameterTags(node);
1650+
const paramTags = getJSDocParameterTags(node as ParameterDeclaration);
16421651
if (paramTags) {
16431652
tag = find(paramTags, tag => !!tag.typeExpression);
16441653
}

src/harness/fourslash.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,7 @@ namespace FourSlash {
916916
}
917917

918918
private getNode(): ts.Node {
919-
return ts.getTouchingPropertyName(this.getSourceFile(), this.currentCaretPosition);
919+
return ts.getTouchingPropertyName(this.getSourceFile(), this.currentCaretPosition, /*includeJsDocComment*/ false);
920920
}
921921

922922
private goToAndGetNode(range: Range): ts.Node {
@@ -994,12 +994,11 @@ namespace FourSlash {
994994
}
995995

996996
public verifyReferenceGroups(startRanges: Range | Range[], parts: Array<{ definition: string, ranges: Range[] }>): void {
997-
interface ReferenceJson { definition: string; ranges: ts.ReferenceEntry[]; }
998997
const fullExpected = ts.map(parts, ({ definition, ranges }) => ({ definition, ranges: ranges.map(rangeToReferenceEntry) }));
999998

1000999
for (const startRange of toArray(startRanges)) {
10011000
this.goToRangeStart(startRange);
1002-
const fullActual = ts.map<ts.ReferencedSymbol, ReferenceJson>(this.findReferencesAtCaret(), ({ definition, references }) => ({
1001+
const fullActual = ts.map(this.findReferencesAtCaret(), ({ definition, references }) => ({
10031002
definition: definition.displayParts.map(d => d.text).join(""),
10041003
ranges: references
10051004
}));
@@ -1046,6 +1045,10 @@ namespace FourSlash {
10461045
this.raiseError(`${msgPrefix}At ${path}: ${msg}`);
10471046
};
10481047

1048+
if ((actual === undefined) !== (expected === undefined)) {
1049+
fail(`Expected ${expected}, got ${actual}`);
1050+
}
1051+
10491052
for (const key in actual) if (ts.hasProperty(actual as any, key)) {
10501053
const ak = actual[key], ek = expected[key];
10511054
if (typeof ak === "object" && typeof ek === "object") {

src/harness/harness.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ namespace Utils {
172172
assert.isFalse(child.pos < currentPos, "child.pos < currentPos");
173173
currentPos = child.end;
174174
},
175-
(array: ts.NodeArray<ts.Node>) => {
175+
array => {
176176
assert.isFalse(array.pos < node.pos, "array.pos < node.pos");
177177
assert.isFalse(array.end > node.end, "array.end > node.end");
178178
assert.isFalse(array.pos < currentPos, "array.pos < currentPos");
@@ -383,7 +383,7 @@ namespace Utils {
383383

384384
assertStructuralEquals(child1, child2);
385385
},
386-
(array1: ts.NodeArray<ts.Node>) => {
386+
array1 => {
387387
const childName = findChildName(node1, array1);
388388
const array2: ts.NodeArray<ts.Node> = (<any>node2)[childName];
389389

src/services/breakpoints.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace ts.BreakpointResolver {
1414
return undefined;
1515
}
1616

17-
let tokenAtLocation = getTokenAtPosition(sourceFile, position);
17+
let tokenAtLocation = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false);
1818
const lineOfPosition = sourceFile.getLineAndCharacterOfPosition(position).line;
1919
if (sourceFile.getLineAndCharacterOfPosition(tokenAtLocation.getStart(sourceFile)).line > lineOfPosition) {
2020
// Get previous token if the token is returned starts on new line

src/services/codefixes/disableJsDiagnostics.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace ts.codefix {
2222
// We also want to check if the previous line holds a comment for a node on the next line
2323
// if so, we do not want to separate the node from its comment if we can.
2424
if (!isInComment(sourceFile, startPosition) && !isInString(sourceFile, startPosition) && !isInTemplateString(sourceFile, startPosition)) {
25-
const token = getTouchingToken(sourceFile, startPosition);
25+
const token = getTouchingToken(sourceFile, startPosition, /*includeJsDocComment*/ false);
2626
const tokenLeadingCommnets = getLeadingCommentRangesOfNode(token, sourceFile);
2727
if (!tokenLeadingCommnets || !tokenLeadingCommnets.length || tokenLeadingCommnets[0].pos >= startPosition) {
2828
return {

src/services/codefixes/fixAddMissingMember.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace ts.codefix {
1313
// This is the identifier of the missing property. eg:
1414
// this.missing = 1;
1515
// ^^^^^^^
16-
const token = getTokenAtPosition(sourceFile, start);
16+
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
1717

1818
if (token.kind !== SyntaxKind.Identifier) {
1919
return undefined;

src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace ts.codefix {
1515
const start = context.span.start;
1616
// This is the identifier in the case of a class declaration
1717
// or the class keyword token in the case of a class expression.
18-
const token = getTokenAtPosition(sourceFile, start);
18+
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
1919
const checker = context.program.getTypeChecker();
2020

2121
if (isClassLike(token.parent)) {

src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace ts.codefix {
88
function getActionForClassLikeIncorrectImplementsInterface(context: CodeFixContext): CodeAction[] | undefined {
99
const sourceFile = context.sourceFile;
1010
const start = context.span.start;
11-
const token = getTokenAtPosition(sourceFile, start);
11+
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
1212
const checker = context.program.getTypeChecker();
1313

1414
const classDeclaration = getContainingClass(token);

src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace ts.codefix {
55
getCodeActions: (context: CodeFixContext) => {
66
const sourceFile = context.sourceFile;
77

8-
const token = getTokenAtPosition(sourceFile, context.span.start);
8+
const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
99
if (token.kind !== SyntaxKind.ThisKeyword) {
1010
return undefined;
1111
}

src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace ts.codefix {
44
errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code],
55
getCodeActions: (context: CodeFixContext) => {
66
const sourceFile = context.sourceFile;
7-
const token = getTokenAtPosition(sourceFile, context.span.start);
7+
const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
88

99
if (token.kind !== SyntaxKind.ConstructorKeyword) {
1010
return undefined;

src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace ts.codefix {
55
getCodeActions: (context: CodeFixContext) => {
66
const sourceFile = context.sourceFile;
77
const start = context.span.start;
8-
const token = getTokenAtPosition(sourceFile, start);
8+
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
99
const classDeclNode = getContainingClass(token);
1010
if (!(token.kind === SyntaxKind.Identifier && isClassLike(classDeclNode))) {
1111
return undefined;

src/services/codefixes/fixForgottenThisPropertyAccess.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace ts.codefix {
44
errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code],
55
getCodeActions: (context: CodeFixContext) => {
66
const sourceFile = context.sourceFile;
7-
const token = getTokenAtPosition(sourceFile, context.span.start);
7+
const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
88
if (token.kind !== SyntaxKind.Identifier) {
99
return undefined;
1010
}

0 commit comments

Comments
 (0)