diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b6074162fff..4292b06e775 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -603,7 +603,7 @@ namespace ts { // Binding of JsDocComment should be done before the current block scope container changes. // because the scope of JsDocComment should not be affected by whether the current node is a // container or not. - if (isInJavaScriptFile(node) && node.jsDoc) { + if (node.jsDoc) { forEach(node.jsDoc, bind); } if (checkUnreachable(node)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 64a4095162b..e1bd629c7b4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22213,6 +22213,11 @@ namespace ts { } } + if (entityName.parent!.kind === SyntaxKind.JSDocParameterTag) { + const parameter = ts.getParameterFromJSDoc(entityName.parent as JSDocParameterTag); + return parameter && parameter.symbol; + } + if (isPartOfExpression(entityName)) { if (nodeIsMissing(entityName)) { // Missing entity name. diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cc3fb24bdb8..4393dc74c98 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -50,7 +50,7 @@ namespace ts { // stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise, // embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns // a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. - export function forEachChild(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T { + export function forEachChild(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray) => T): T { if (!node) { return; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5725d04a9cf..75ff89dd645 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1591,7 +1591,7 @@ namespace ts { // Pull parameter comments from declaring function as well if (node.kind === SyntaxKind.Parameter) { - cache = concatenate(cache, getJSDocParameterTags(node)); + cache = concatenate(cache, getJSDocParameterTags(node as ParameterDeclaration)); } if (isVariableLike(node) && node.initializer) { @@ -1602,11 +1602,8 @@ namespace ts { } } - export function getJSDocParameterTags(param: Node): JSDocParameterTag[] { - if (!isParameter(param)) { - return undefined; - } - const func = param.parent as FunctionLikeDeclaration; + export function getJSDocParameterTags(param: ParameterDeclaration): JSDocParameterTag[] { + const func = param.parent; const tags = getJSDocTags(func, SyntaxKind.JSDocParameterTag) as JSDocParameterTag[]; if (!param.name) { // this is an anonymous jsdoc param from a `function(type1, type2): type3` specification @@ -1627,10 +1624,22 @@ namespace ts { } } + /** Does the opposite of `getJSDocParameterTags`: given a JSDoc parameter, finds the parameter corresponding to it. */ + export function getParameterFromJSDoc(node: JSDocParameterTag): ParameterDeclaration | undefined { + const name = node.parameterName.text; + const grandParent = node.parent!.parent!; + Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment); + if (!isFunctionLike(grandParent)) { + return undefined; + } + return find(grandParent.parameters, p => + p.name.kind === SyntaxKind.Identifier && p.name.text === name); + } + export function getJSDocType(node: Node): JSDocType { let tag: JSDocTypeTag | JSDocParameterTag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag; if (!tag && node.kind === SyntaxKind.Parameter) { - const paramTags = getJSDocParameterTags(node); + const paramTags = getJSDocParameterTags(node as ParameterDeclaration); if (paramTags) { tag = find(paramTags, tag => !!tag.typeExpression); } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 83d27c951be..ceb6bf16a56 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -916,7 +916,7 @@ namespace FourSlash { } private getNode(): ts.Node { - return ts.getTouchingPropertyName(this.getSourceFile(), this.currentCaretPosition); + return ts.getTouchingPropertyName(this.getSourceFile(), this.currentCaretPosition, /*includeJsDocComment*/ false); } private goToAndGetNode(range: Range): ts.Node { @@ -994,17 +994,15 @@ namespace FourSlash { } public verifyReferenceGroups(startRanges: Range | Range[], parts: Array<{ definition: string, ranges: Range[] }>): void { - interface ReferenceJson { definition: string; ranges: ts.ReferenceEntry[]; } - type ReferencesJson = ReferenceJson[]; - const fullExpected = parts.map(({ definition, ranges }) => ({ definition, ranges: ranges.map(rangeToReferenceEntry) })); + const fullExpected = ts.map(parts, ({ definition, ranges }) => ({ definition, ranges: ranges.map(rangeToReferenceEntry) })); for (const startRange of toArray(startRanges)) { this.goToRangeStart(startRange); - const fullActual = ts.map(this.findReferencesAtCaret(), ({ definition, references }) => ({ + const fullActual = ts.map(this.findReferencesAtCaret(), ({ definition, references }) => ({ definition: definition.displayParts.map(d => d.text).join(""), ranges: references })); - this.assertObjectsEqual(fullActual, fullExpected); + this.assertObjectsEqual(fullActual, fullExpected); } function rangeToReferenceEntry(r: Range): ts.ReferenceEntry { @@ -1047,6 +1045,10 @@ namespace FourSlash { this.raiseError(`${msgPrefix}At ${path}: ${msg}`); }; + if ((actual === undefined) !== (expected === undefined)) { + fail(`Expected ${expected}, got ${actual}`); + } + for (const key in actual) if (ts.hasProperty(actual as any, key)) { const ak = actual[key], ek = expected[key]; if (typeof ak === "object" && typeof ek === "object") { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index c0ef2672b27..b46d7652e1d 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -172,7 +172,7 @@ namespace Utils { assert.isFalse(child.pos < currentPos, "child.pos < currentPos"); currentPos = child.end; }, - (array: ts.NodeArray) => { + array => { assert.isFalse(array.pos < node.pos, "array.pos < node.pos"); assert.isFalse(array.end > node.end, "array.end > node.end"); assert.isFalse(array.pos < currentPos, "array.pos < currentPos"); @@ -383,7 +383,7 @@ namespace Utils { assertStructuralEquals(child1, child2); }, - (array1: ts.NodeArray) => { + array1 => { const childName = findChildName(node1, array1); const array2: ts.NodeArray = (node2)[childName]; diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index e47f6d016c7..38da43bdee8 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -14,7 +14,7 @@ namespace ts.BreakpointResolver { return undefined; } - let tokenAtLocation = getTokenAtPosition(sourceFile, position); + let tokenAtLocation = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); const lineOfPosition = sourceFile.getLineAndCharacterOfPosition(position).line; if (sourceFile.getLineAndCharacterOfPosition(tokenAtLocation.getStart(sourceFile)).line > lineOfPosition) { // Get previous token if the token is returned starts on new line diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index 7fa5c5bf23f..b5f9e5587a3 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -22,7 +22,7 @@ namespace ts.codefix { // We also want to check if the previous line holds a comment for a node on the next line // if so, we do not want to separate the node from its comment if we can. if (!isInComment(sourceFile, startPosition) && !isInString(sourceFile, startPosition) && !isInTemplateString(sourceFile, startPosition)) { - const token = getTouchingToken(sourceFile, startPosition); + const token = getTouchingToken(sourceFile, startPosition, /*includeJsDocComment*/ false); const tokenLeadingCommnets = getLeadingCommentRangesOfNode(token, sourceFile); if (!tokenLeadingCommnets || !tokenLeadingCommnets.length || tokenLeadingCommnets[0].pos >= startPosition) { return { diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 346cdaaaf19..64e7e560094 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -13,7 +13,7 @@ namespace ts.codefix { // This is the identifier of the missing property. eg: // this.missing = 1; // ^^^^^^^ - const token = getTokenAtPosition(sourceFile, start); + const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); if (token.kind !== SyntaxKind.Identifier) { return undefined; diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index 62f89b1a21a..768eaf752b7 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -15,7 +15,7 @@ namespace ts.codefix { const start = context.span.start; // This is the identifier in the case of a class declaration // or the class keyword token in the case of a class expression. - const token = getTokenAtPosition(sourceFile, start); + const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); const checker = context.program.getTypeChecker(); if (isClassLike(token.parent)) { diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 3b1b99febb0..bed9621b729 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -8,7 +8,7 @@ namespace ts.codefix { function getActionForClassLikeIncorrectImplementsInterface(context: CodeFixContext): CodeAction[] | undefined { const sourceFile = context.sourceFile; const start = context.span.start; - const token = getTokenAtPosition(sourceFile, start); + const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); const checker = context.program.getTypeChecker(); const classDeclaration = getContainingClass(token); diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 2217dda8b0c..56128be9d07 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -5,7 +5,7 @@ namespace ts.codefix { getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start); + const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); if (token.kind !== SyntaxKind.ThisKeyword) { return undefined; } diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 822c34842fd..517a79e39bd 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -4,7 +4,7 @@ namespace ts.codefix { errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code], getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start); + const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); if (token.kind !== SyntaxKind.ConstructorKeyword) { return undefined; diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index 80d4345a943..d23f61d0f92 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -5,7 +5,7 @@ namespace ts.codefix { getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; const start = context.span.start; - const token = getTokenAtPosition(sourceFile, start); + const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); const classDeclNode = getContainingClass(token); if (!(token.kind === SyntaxKind.Identifier && isClassLike(classDeclNode))) { return undefined; diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index 711a3289a27..6925b557755 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -4,7 +4,7 @@ namespace ts.codefix { errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code], getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; - const token = getTokenAtPosition(sourceFile, context.span.start); + const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); if (token.kind !== SyntaxKind.Identifier) { return undefined; } diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index b58dab95002..c8f539e8d34 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -12,7 +12,7 @@ namespace ts.codefix { // This is the identifier of the misspelled word. eg: // this.speling = 1; // ^^^^^^^ - const node = getTokenAtPosition(sourceFile, context.span.start); + const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); // TODO: GH#15852 const checker = context.program.getTypeChecker(); let suggestion: string; if (node.kind === SyntaxKind.Identifier && isPropertyAccessExpression(node.parent)) { diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index aaf7b535add..7ca57c2a69f 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -128,7 +128,7 @@ namespace ts.codefix { const allSourceFiles = context.program.getSourceFiles(); const useCaseSensitiveFileNames = context.host.useCaseSensitiveFileNames ? context.host.useCaseSensitiveFileNames() : false; - const token = getTokenAtPosition(sourceFile, context.span.start); + const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); const name = token.getText(); const symbolIdActionMap = new ImportCodeActionMap(); diff --git a/src/services/codefixes/unusedIdentifierFixes.ts b/src/services/codefixes/unusedIdentifierFixes.ts index 422a28098a6..5d85979fcf9 100644 --- a/src/services/codefixes/unusedIdentifierFixes.ts +++ b/src/services/codefixes/unusedIdentifierFixes.ts @@ -9,11 +9,11 @@ namespace ts.codefix { const sourceFile = context.sourceFile; const start = context.span.start; - let token = getTokenAtPosition(sourceFile, start); + let token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); // this handles var ["computed"] = 12; if (token.kind === SyntaxKind.OpenBracketToken) { - token = getTokenAtPosition(sourceFile, start + 1); + token = getTokenAtPosition(sourceFile, start + 1, /*includeJsDocComment*/ false); } switch (token.kind) { @@ -48,11 +48,11 @@ namespace ts.codefix { case SyntaxKind.TypeParameter: const typeParameters = (token.parent.parent).typeParameters; if (typeParameters.length === 1) { - const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1); + const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) { return deleteRange(typeParameters); } - const nextToken = getTokenAtPosition(sourceFile, typeParameters.end); + const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) { return deleteRange(typeParameters); } @@ -99,7 +99,7 @@ namespace ts.codefix { else { // import |d,| * as ns from './file' const start = importClause.name.getStart(sourceFile); - const nextToken = getTokenAtPosition(sourceFile, importClause.name.end); + const nextToken = getTokenAtPosition(sourceFile, importClause.name.end, /*includeJsDocComment*/ false); if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { // shift first non-whitespace position after comma to the start position of the node return deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true) }); @@ -116,7 +116,7 @@ namespace ts.codefix { return deleteNode(importDecl); } else { - const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1); + const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1, /*includeJsDocComment*/ false); if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart); return deleteRange({ pos: startPosition, end: namespaceImport.end }); diff --git a/src/services/completions.ts b/src/services/completions.ts index 318c02683ff..734402514c9 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -346,7 +346,7 @@ namespace ts.Completions { let requestJsDocTag = false; let start = timestamp(); - const currentToken = getTokenAtPosition(sourceFile, position); + const currentToken = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); // TODO: GH#15853 log("getCompletionData: Get current token: " + (timestamp() - start)); start = timestamp(); @@ -441,7 +441,7 @@ namespace ts.Completions { let isRightOfOpenTag = false; let isStartingCloseTag = false; - let location = getTouchingPropertyName(sourceFile, position); + let location = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ false); // TODO: GH#15853 if (contextToken) { // Bail out if this is a known invalid completion location if (isCompletionListBlocker(contextToken)) { diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index a8afc46d736..5f15b887924 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.DocumentHighlights { export function getDocumentHighlights(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] { - const node = getTouchingWord(sourceFile, position); + const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ true); return node && (getSemanticDocumentHighlights(node, typeChecker, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile)); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 395c282f6d5..16a516c2603 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -60,15 +60,19 @@ namespace ts.FindAllReferences { } export function getImplementationsAtPosition(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] { - const node = getTouchingPropertyName(sourceFile, position); + // A node in a JSDoc comment can't have an implementation anyway. + const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ false); const referenceEntries = getImplementationReferenceEntries(checker, cancellationToken, sourceFiles, node); return map(referenceEntries, entry => toImplementationLocation(entry, checker)); } function getImplementationReferenceEntries(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): Entry[] | undefined { + if (node.parent.kind === SyntaxKind.SourceFile) { + return undefined; + } // If invoked directly on a shorthand property assignment, then return // the declaration of the symbol being assigned (not the symbol being assigned to). - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + else if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { const result: NodeEntry[] = []; Core.getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, node => result.push(nodeEntry(node))); return result; @@ -680,7 +684,7 @@ namespace ts.FindAllReferences.Core { const labelName = targetLabel.text; const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container); for (const position of possiblePositions) { - const node = getTouchingWord(sourceFile, position); + const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ false); // Only pick labels that are either the target label, or have a target that is the target label if (node && (node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel))) { references.push(nodeEntry(node)); @@ -718,9 +722,10 @@ namespace ts.FindAllReferences.Core { } function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, references: Push): void { - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText); + // Want fullStart so we can find the symbol in JSDoc comments + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile, /*fullStart*/ true); for (const position of possiblePositions) { - const referenceLocation = getTouchingPropertyName(sourceFile, position); + const referenceLocation = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); if (referenceLocation.kind === kind) { references.push(nodeEntry(referenceLocation)); } @@ -742,13 +747,13 @@ namespace ts.FindAllReferences.Core { return; } - for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container, /*fullStart*/ state.findInComments)) { + for (const position of getPossibleSymbolReferencePositions(sourceFile, search.text, container, /*fullStart*/ state.findInComments || container.jsDoc !== undefined)) { getReferencesAtLocation(sourceFile, position, search, state); } } function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State): void { - const referenceLocation = getTouchingPropertyName(sourceFile, position); + const referenceLocation = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); if (!isValidReferencePosition(referenceLocation, search.text)) { // This wasn't the start of a token. Check to see if it might be a @@ -1188,7 +1193,7 @@ namespace ts.FindAllReferences.Core { const sourceFile = searchSpaceNode.getSourceFile(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode); for (const position of possiblePositions) { - const node = getTouchingWord(sourceFile, position); + const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ false); if (!node || node.kind !== SyntaxKind.SuperKeyword) { continue; @@ -1265,7 +1270,7 @@ namespace ts.FindAllReferences.Core { function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: Entry[]): void { forEach(possiblePositions, position => { - const node = getTouchingWord(sourceFile, position); + const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ false); if (!node || !isThis(node)) { return; } @@ -1319,7 +1324,7 @@ namespace ts.FindAllReferences.Core { function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchText: string, possiblePositions: number[], references: Push): void { for (const position of possiblePositions) { - const node = getTouchingWord(sourceFile, position); + const node = getTouchingWord(sourceFile, position, /*includeJsDocComment*/ false); if (node && node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).text === searchText) { references.push(nodeEntry(node, /*isInString*/ true)); } diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index d5bd33e33cc..531b768f6d8 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -599,7 +599,7 @@ namespace ts.formatting { child => { processChildNode(child, /*inheritedIndentation*/ Constants.Unknown, node, nodeDynamicIndentation, nodeStartLine, undecoratedNodeStartLine, /*isListItem*/ false); }, - (nodes: NodeArray) => { + nodes => { processChildNodes(nodes, node, nodeStartLine, nodeDynamicIndentation); }); diff --git a/src/services/goToDefinition.ts b/src/services/goToDefinition.ts index 07b1e4448bd..bc669a6d200 100644 --- a/src/services/goToDefinition.ts +++ b/src/services/goToDefinition.ts @@ -19,7 +19,7 @@ namespace ts.GoToDefinition { [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)]; } - const node = getTouchingPropertyName(sourceFile, position); + const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); if (node === sourceFile) { return undefined; } @@ -104,7 +104,7 @@ namespace ts.GoToDefinition { /// Goto type export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): DefinitionInfo[] { - const node = getTouchingPropertyName(sourceFile, position); + const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); if (node === sourceFile) { return undefined; } diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 4422aab7156..b130a7f754b 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -158,7 +158,7 @@ namespace ts.JsDoc { return undefined; } - const tokenAtPos = getTokenAtPosition(sourceFile, position); + const tokenAtPos = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); const tokenStart = tokenAtPos.getStart(); if (!tokenAtPos || tokenStart < position) { return undefined; diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index 2b45457c147..58de3ec8137 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -283,7 +283,7 @@ namespace ts.Completions.PathCompletions { } export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionInfo { - const token = getTokenAtPosition(sourceFile, position); + const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); if (!token) { return undefined; } diff --git a/src/services/services.ts b/src/services/services.ts index 1b4678b5dbc..732d54427ba 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -105,6 +105,7 @@ namespace ts { scanner.setTextPos(pos); while (pos < end) { const token = useJSDocScanner ? scanner.scanJSDocToken() : scanner.scan(); + Debug.assert(token !== SyntaxKind.EndOfFileToken); // Else it would infinitely loop const textPos = scanner.getTextPos(); if (textPos <= end) { nodes.push(createNode(token, pos, textPos, this)); @@ -133,10 +134,15 @@ namespace ts { } private createChildren(sourceFile?: SourceFileLike) { - let children: Node[]; - if (this.kind >= SyntaxKind.FirstNode) { + if (isJSDocTag(this)) { + /** Don't add trivia for "tokens" since this is in a comment. */ + const children: Node[] = []; + this.forEachChild(child => { children.push(child); }); + this._children = children; + } + else if (this.kind >= SyntaxKind.FirstNode) { + const children: Node[] = []; scanner.setText((sourceFile || this.getSourceFile()).text); - children = []; let pos = this.pos; const useJSDocScanner = this.kind >= SyntaxKind.FirstJSDocTagNode && this.kind <= SyntaxKind.LastJSDocTagNode; const processNode = (node: Node) => { @@ -153,7 +159,7 @@ namespace ts { if (pos < nodes.pos) { pos = this.addSyntheticNodes(children, pos, nodes.pos, useJSDocScanner); } - children.push(this.createSyntaxList(>nodes)); + children.push(this.createSyntaxList(nodes)); pos = nodes.end; }; // jsDocComments need to be the first children @@ -171,8 +177,11 @@ namespace ts { this.addSyntheticNodes(children, pos, this.end); } scanner.setText(undefined); + this._children = children; + } + else { + this._children = emptyArray; } - this._children = children || emptyArray; } public getChildCount(sourceFile?: SourceFile): number { @@ -213,7 +222,7 @@ namespace ts { return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile); } - public forEachChild(cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T { + public forEachChild(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray) => T): T { return forEachChild(this, cbNode, cbNodeArray); } } @@ -1353,7 +1362,7 @@ namespace ts { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); - const node = getTouchingPropertyName(sourceFile, position); + const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); if (node === sourceFile) { return undefined; } @@ -1540,7 +1549,7 @@ namespace ts { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); // Get node at the location - const node = getTouchingPropertyName(sourceFile, startPos); + const node = getTouchingPropertyName(sourceFile, startPos, /*includeJsDocComment*/ false); if (node === sourceFile) { return; @@ -1651,7 +1660,7 @@ namespace ts { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const result: TextSpan[] = []; - const token = getTouchingToken(sourceFile, position); + const token = getTouchingToken(sourceFile, position, /*includeJsDocComment*/ false); if (token.getStart(sourceFile) === position) { const matchKind = getMatchingTokenKind(token); @@ -1853,7 +1862,7 @@ namespace ts { // OK, we have found a match in the file. This is only an acceptable match if // it is contained within a comment. - const token = getTokenAtPosition(sourceFile, matchPosition); + const token = getTokenAtPosition(sourceFile, matchPosition, /*includeJsDocComment*/ false); if (!isInsideComment(sourceFile, token, matchPosition)) { continue; } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 7ff0c37fa93..c82a28953c7 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -202,7 +202,7 @@ namespace ts.textChanges { return this; } if (index !== containingList.length - 1) { - const nextToken = getTokenAtPosition(sourceFile, node.end); + const nextToken = getTokenAtPosition(sourceFile, node.end, /*includeJsDocComment*/ false); if (nextToken && isSeparator(node, nextToken)) { // find first non-whitespace position in the leading trivia of the node const startPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); @@ -214,7 +214,7 @@ namespace ts.textChanges { } } else { - const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end); + const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end, /*includeJsDocComment*/ false); if (previousToken && isSeparator(node, previousToken)) { this.deleteNodeRange(sourceFile, previousToken, node); } @@ -292,7 +292,7 @@ namespace ts.textChanges { if (index !== containingList.length - 1) { // any element except the last one // use next sibling as an anchor - const nextToken = getTokenAtPosition(sourceFile, after.end); + const nextToken = getTokenAtPosition(sourceFile, after.end, /*includeJsDocComment*/ false); if (nextToken && isSeparator(after, nextToken)) { // for list // a, b, c diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 4ae9204f7ed..cf46df3708e 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -621,29 +621,29 @@ namespace ts { /* Gets the token whose text has range [start, end) and * position >= start and (position < end or (position === end && token is keyword or identifier)) */ - export function getTouchingWord(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { - return getTouchingToken(sourceFile, position, n => isWord(n.kind), includeJsDocComment); + export function getTouchingWord(sourceFile: SourceFile, position: number, includeJsDocComment: boolean): Node { + return getTouchingToken(sourceFile, position, includeJsDocComment, n => isWord(n.kind)); } /* Gets the token whose text has range [start, end) and position >= start * and (position < end or (position === end && token is keyword or identifier or numeric/string literal)) */ - export function getTouchingPropertyName(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { - return getTouchingToken(sourceFile, position, n => isPropertyName(n.kind), includeJsDocComment); + export function getTouchingPropertyName(sourceFile: SourceFile, position: number, includeJsDocComment: boolean): Node { + return getTouchingToken(sourceFile, position, includeJsDocComment, n => isPropertyName(n.kind)); } /** Returns the token if position is in [start, end) or if position === end and includeItemAtEndPosition(token) === true */ - export function getTouchingToken(sourceFile: SourceFile, position: number, includeItemAtEndPosition?: (n: Node) => boolean, includeJsDocComment = false): Node { + export function getTouchingToken(sourceFile: SourceFile, position: number, includeJsDocComment: boolean, includeItemAtEndPosition?: (n: Node) => boolean): Node { return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, includeItemAtEndPosition, includeJsDocComment); } /** Returns a token if position is in [start-of-leading-trivia, end) */ - export function getTokenAtPosition(sourceFile: SourceFile, position: number, includeJsDocComment = false): Node { + export function getTokenAtPosition(sourceFile: SourceFile, position: number, includeJsDocComment: boolean): Node { return getTokenAtPositionWorker(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includeItemAtEndPosition*/ undefined, includeJsDocComment); } /** Get the token whose text contains the position */ - function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeItemAtEndPosition: (n: Node) => boolean, includeJsDocComment = false): Node { + function getTokenAtPositionWorker(sourceFile: SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeItemAtEndPosition: (n: Node) => boolean, includeJsDocComment: boolean): Node { let current: Node = sourceFile; outer: while (true) { if (isToken(current)) { @@ -708,7 +708,7 @@ namespace ts { export function findTokenOnLeftOfPosition(file: SourceFile, position: number): Node { // Ideally, getTokenAtPosition should return a token. However, it is currently // broken, so we do a check to make sure the result was indeed a token. - const tokenAtPosition = getTokenAtPosition(file, position); + const tokenAtPosition = getTokenAtPosition(file, position, /*includeJsDocComment*/ false); if (isToken(tokenAtPosition) && position > tokenAtPosition.getStart(file) && position < tokenAtPosition.getEnd()) { return tokenAtPosition; } @@ -842,7 +842,7 @@ namespace ts { * returns true if the position is in between the open and close elements of an JSX expression. */ export function isInsideJsxElementOrAttribute(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); + const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); if (!token) { return false; @@ -878,7 +878,7 @@ namespace ts { } export function isInTemplateString(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); + const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile); } @@ -886,8 +886,8 @@ namespace ts { * Returns true if the cursor at position in sourceFile is within a comment that additionally * satisfies predicate, and false otherwise. */ - export function isInCommentHelper(sourceFile: SourceFile, position: number, predicate?: (c: CommentRange) => boolean): boolean { - const token = getTokenAtPosition(sourceFile, position); + function isInCommentHelper(sourceFile: SourceFile, position: number, predicate?: (c: CommentRange) => boolean): boolean { + const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); if (token && position <= token.getStart(sourceFile)) { const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); @@ -914,7 +914,7 @@ namespace ts { } export function hasDocComment(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); + const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); // First, we have to see if this position actually landed in a comment. const commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); @@ -931,7 +931,7 @@ namespace ts { * Get the corresponding JSDocTag node if the position is in a jsDoc comment */ export function getJsDocTagAtPosition(sourceFile: SourceFile, position: number): JSDocTag { - let node = ts.getTokenAtPosition(sourceFile, position); + let node = ts.getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false); if (isToken(node)) { switch (node.kind) { case SyntaxKind.VarKeyword: @@ -1398,6 +1398,6 @@ namespace ts { } export function getOpenBraceOfClassLike(declaration: ClassLikeDeclaration, sourceFile: SourceFile) { - return getTokenAtPosition(sourceFile, declaration.members.pos - 1); + return getTokenAtPosition(sourceFile, declaration.members.pos - 1, /*includeJsDocComment*/ false); } } diff --git a/tests/cases/fourslash/findAllRefsPrimitiveJsDoc.ts b/tests/cases/fourslash/findAllRefsPrimitiveJsDoc.ts new file mode 100644 index 00000000000..82daf720d19 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsPrimitiveJsDoc.ts @@ -0,0 +1,11 @@ +// @noLib: true + +/// + +/////** +//// * @param {[|number|]} n +//// * @returns {[|number|]} +//// */ +////function f(n: [|number|]): [|number|] {} + +verify.singleReferenceGroup("number"); diff --git a/tests/cases/fourslash/getOccurrencesIsDefinitionOfComputedProperty.ts b/tests/cases/fourslash/getOccurrencesIsDefinitionOfComputedProperty.ts index 27c34e56600..7d6cb0a48af 100644 --- a/tests/cases/fourslash/getOccurrencesIsDefinitionOfComputedProperty.ts +++ b/tests/cases/fourslash/getOccurrencesIsDefinitionOfComputedProperty.ts @@ -6,4 +6,4 @@ const ranges = test.ranges(); const [r0, r1, r2] = ranges; verify.referenceGroups(r0, [{ definition: '(property) ["foo"]: number', ranges }]); -verify.referenceGroups([r1, r2], []); // TODO: fix +verify.referenceGroups([r1, r2], undefined); // TODO: fix diff --git a/tests/cases/fourslash/jsDocGoToImplementation.ts b/tests/cases/fourslash/jsDocGoToImplementation.ts new file mode 100644 index 00000000000..cad5c29c82f --- /dev/null +++ b/tests/cases/fourslash/jsDocGoToImplementation.ts @@ -0,0 +1,10 @@ +/// + +/////** +//// * @param /**/[|foo|] I pity the foo +//// */ +////function f([|foo|]: number) { +//// return foo; +////} + +verify.allRangesAppearInImplementationList(""); diff --git a/tests/cases/fourslash/jsDocServices.ts b/tests/cases/fourslash/jsDocServices.ts new file mode 100644 index 00000000000..df1e985f876 --- /dev/null +++ b/tests/cases/fourslash/jsDocServices.ts @@ -0,0 +1,24 @@ +/// + +// Note: We include the word "foo" in the documentation to test for a bug where +// the `.getChildren()` of the JSDocParameterTag included an identifier at that position with no '.text'. +////interface /*I*/I {} +//// +/////** +//// * @param /*use*/[|foo|] I pity the foo +//// */ +////function f([|/*def*/{| "isWriteAccess": true, "isDefinition": true |}foo|]: I) { +//// return [|foo|]; +////} + +const ranges = test.ranges(); +goTo.marker("use"); +verify.goToDefinitionIs("def"); +verify.goToType("use", "I"); + +goTo.marker("use"); +verify.quickInfoIs("(parameter) foo: I", "I pity the foo"); + +verify.singleReferenceGroup("(parameter) foo: I"); +verify.rangesAreDocumentHighlights(); +verify.rangesAreRenameLocations();