mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-20 19:45:07 -06:00
Support for JSDoc in services
This commit is contained in:
parent
1328cb81e6
commit
38784b761a
@ -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)) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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<T>(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T {
|
||||
export function forEachChild<T>(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray<Node>) => T): T {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<ReferenceJson>(({ 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<ts.ReferencedSymbol, ReferenceJson>(this.findReferencesAtCaret(), ({ definition, references }) => ({
|
||||
const fullActual = ts.map(this.findReferencesAtCaret(), ({ definition, references }) => ({
|
||||
definition: definition.displayParts.map(d => d.text).join(""),
|
||||
ranges: references
|
||||
}));
|
||||
this.assertObjectsEqual<ReferencesJson>(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") {
|
||||
|
||||
@ -172,7 +172,7 @@ namespace Utils {
|
||||
assert.isFalse(child.pos < currentPos, "child.pos < currentPos");
|
||||
currentPos = child.end;
|
||||
},
|
||||
(array: ts.NodeArray<ts.Node>) => {
|
||||
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<ts.Node>) => {
|
||||
array1 => {
|
||||
const childName = findChildName(node1, array1);
|
||||
const array2: ts.NodeArray<ts.Node> = (<any>node2)[childName];
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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 = (<DeclarationWithTypeParameters>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 });
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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<NodeEntry>): 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<NodeEntry>): 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));
|
||||
}
|
||||
|
||||
@ -599,7 +599,7 @@ namespace ts.formatting {
|
||||
child => {
|
||||
processChildNode(child, /*inheritedIndentation*/ Constants.Unknown, node, nodeDynamicIndentation, nodeStartLine, undecoratedNodeStartLine, /*isListItem*/ false);
|
||||
},
|
||||
(nodes: NodeArray<Node>) => {
|
||||
nodes => {
|
||||
processChildNodes(nodes, node, nodeStartLine, nodeDynamicIndentation);
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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(<NodeArray<Node>>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<T>(cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T {
|
||||
public forEachChild<T>(cbNode: (node: Node) => T, cbNodeArray?: (nodes: NodeArray<Node>) => 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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
11
tests/cases/fourslash/findAllRefsPrimitiveJsDoc.ts
Normal file
11
tests/cases/fourslash/findAllRefsPrimitiveJsDoc.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// @noLib: true
|
||||
|
||||
/// <reference path='fourslash.ts'/>
|
||||
|
||||
/////**
|
||||
//// * @param {[|number|]} n
|
||||
//// * @returns {[|number|]}
|
||||
//// */
|
||||
////function f(n: [|number|]): [|number|] {}
|
||||
|
||||
verify.singleReferenceGroup("number");
|
||||
@ -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
|
||||
|
||||
10
tests/cases/fourslash/jsDocGoToImplementation.ts
Normal file
10
tests/cases/fourslash/jsDocGoToImplementation.ts
Normal file
@ -0,0 +1,10 @@
|
||||
///<reference path="fourslash.ts" />
|
||||
|
||||
/////**
|
||||
//// * @param /**/[|foo|] I pity the foo
|
||||
//// */
|
||||
////function f([|foo|]: number) {
|
||||
//// return foo;
|
||||
////}
|
||||
|
||||
verify.allRangesAppearInImplementationList("");
|
||||
24
tests/cases/fourslash/jsDocServices.ts
Normal file
24
tests/cases/fourslash/jsDocServices.ts
Normal file
@ -0,0 +1,24 @@
|
||||
///<reference path="fourslash.ts" />
|
||||
|
||||
// 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();
|
||||
Loading…
x
Reference in New Issue
Block a user