Merge pull request #15856 from Microsoft/jsdoc

Support for JSDoc in services
This commit is contained in:
Andy 2017-05-22 07:46:52 -07:00 committed by GitHub
commit 24d98f2295
36 changed files with 297 additions and 140 deletions

View file

@ -439,6 +439,7 @@ namespace ts {
// during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation
// and this case is specially handled. Module augmentations should only be merged with original module definition
// and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed.
if (node.kind === SyntaxKind.JSDocTypedefTag) Debug.assert(isInJavaScriptFile(node)); // We shouldn't add symbols for JSDoc nodes if not in a JS file.
const isJSDocTypedefInJSDocNamespace = node.kind === SyntaxKind.JSDocTypedefTag &&
(node as JSDocTypedefTag).name &&
(node as JSDocTypedefTag).name.kind === SyntaxKind.Identifier &&
@ -603,9 +604,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) {
forEach(node.jsDoc, bind);
}
forEach(node.jsDoc, bind);
if (checkUnreachable(node)) {
bindEachChild(node);
return;
@ -1913,9 +1912,7 @@ namespace ts {
// Here the current node is "foo", which is a container, but the scope of "MyType" should
// not be inside "foo". Therefore we always bind @typedef before bind the parent node,
// and skip binding this tag later when binding all the other jsdoc tags.
if (isInJavaScriptFile(node)) {
bindJSDocTypedefTagIfAny(node);
}
bindJSDocTypedefTagIfAny(node);
// First we bind declaration nodes to a symbol if possible. We'll both create a symbol
// and then potentially add the symbol to an appropriate symbol table. Possible
@ -2003,7 +2000,7 @@ namespace ts {
// for typedef type names with namespaces, bind the new jsdoc type symbol here
// because it requires all containing namespaces to be in effect, namely the
// current "blockScopeContainer" needs to be set to its immediate namespace parent.
if ((<Identifier>node).isInJSDocNamespace) {
if (isInJavaScriptFile(node) && (<Identifier>node).isInJSDocNamespace) {
let parentNode = node.parent;
while (parentNode && parentNode.kind !== SyntaxKind.JSDocTypedefTag) {
parentNode = parentNode.parent;
@ -2073,10 +2070,7 @@ namespace ts {
return bindVariableDeclarationOrBindingElement(<VariableDeclaration | BindingElement>node);
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.JSDocRecordMember:
return bindPropertyOrMethodOrAccessor(<Declaration>node, SymbolFlags.Property | ((<PropertyDeclaration>node).questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes);
case SyntaxKind.JSDocPropertyTag:
return bindJSDocProperty(<JSDocPropertyTag>node);
return bindPropertyWorker(node as PropertyDeclaration | PropertySignature);
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
return bindPropertyOrMethodOrAccessor(<Declaration>node, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
@ -2121,13 +2115,10 @@ namespace ts {
return bindPropertyOrMethodOrAccessor(<Declaration>node, SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes);
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.JSDocFunctionType:
return bindFunctionOrConstructorType(<SignatureDeclaration>node);
case SyntaxKind.TypeLiteral:
case SyntaxKind.MappedType:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocRecordType:
return bindAnonymousDeclaration(<Declaration>node, SymbolFlags.TypeLiteral, "__type");
return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode);
case SyntaxKind.ObjectLiteralExpression:
return bindObjectLiteralExpression(<ObjectLiteralExpression>node);
case SyntaxKind.FunctionExpression:
@ -2148,11 +2139,6 @@ namespace ts {
return bindClassLikeDeclaration(<ClassLikeDeclaration>node);
case SyntaxKind.InterfaceDeclaration:
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.Interface, SymbolFlags.InterfaceExcludes);
case SyntaxKind.JSDocTypedefTag:
if (!(<JSDocTypedefTag>node).fullName || (<JSDocTypedefTag>node).fullName.kind === SyntaxKind.Identifier) {
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
}
break;
case SyntaxKind.TypeAliasDeclaration:
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
case SyntaxKind.EnumDeclaration:
@ -2190,9 +2176,41 @@ namespace ts {
// falls through
case SyntaxKind.ModuleBlock:
return updateStrictModeStatementList((<Block | ModuleBlock>node).statements);
default:
if (isInJavaScriptFile(node)) return bindJSDocWorker(node);
}
}
function bindJSDocWorker(node: Node) {
switch (node.kind) {
case SyntaxKind.JSDocRecordMember:
return bindPropertyWorker(node as JSDocRecordMember);
case SyntaxKind.JSDocPropertyTag:
return declareSymbolAndAddToSymbolTable(node as JSDocPropertyTag, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
case SyntaxKind.JSDocFunctionType:
return bindFunctionOrConstructorType(<SignatureDeclaration>node);
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocRecordType:
return bindAnonymousTypeWorker(node as JSDocTypeLiteral | JSDocRecordType);
case SyntaxKind.JSDocTypedefTag: {
const { fullName } = node as JSDocTypedefTag;
if (!fullName || fullName.kind === SyntaxKind.Identifier) {
return bindBlockScopedDeclaration(<Declaration>node, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes);
}
break;
}
}
}
function bindPropertyWorker(node: PropertyDeclaration | PropertySignature) {
return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes);
}
function bindAnonymousTypeWorker(node: TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral | JSDocRecordType) {
return bindAnonymousDeclaration(<Declaration>node, SymbolFlags.TypeLiteral, "__type");
}
function checkTypePredicate(node: TypePredicateNode) {
const { parameterName, type } = node;
if (parameterName && parameterName.kind === SyntaxKind.Identifier) {
@ -2558,10 +2576,8 @@ namespace ts {
}
function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) {
if (!file.isDeclarationFile && !isInAmbientContext(node)) {
if (isAsyncFunction(node)) {
emitFlags |= NodeFlags.HasAsyncFunctions;
}
if (!file.isDeclarationFile && !isInAmbientContext(node) && isAsyncFunction(node)) {
emitFlags |= NodeFlags.HasAsyncFunctions;
}
if (currentFlow && isObjectLiteralOrClassExpressionMethod(node)) {
@ -2573,10 +2589,6 @@ namespace ts {
: declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes);
}
function bindJSDocProperty(node: JSDocPropertyTag) {
return declareSymbolAndAddToSymbolTable(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
}
// reachability checks
function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean {

View file

@ -22327,6 +22327,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.

View file

@ -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 | undefined {
export function forEachChild<T>(node: Node, cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
if (!node) {
return;
}

View file

@ -1599,7 +1599,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) {
@ -1610,11 +1610,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
@ -1635,10 +1632,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);
}

View file

@ -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,12 +994,11 @@ namespace FourSlash {
}
public verifyReferenceGroups(startRanges: Range | Range[], parts: Array<{ definition: string, ranges: Range[] }>): void {
interface ReferenceJson { definition: string; ranges: ts.ReferenceEntry[]; }
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
}));
@ -1046,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") {

View file

@ -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];

View file

@ -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

View file

@ -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 {

View file

@ -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;

View file

@ -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)) {

View file

@ -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);

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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)) {

View file

@ -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();

View file

@ -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 });

View file

@ -354,7 +354,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();
@ -449,7 +449,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)) {

View file

@ -1,7 +1,7 @@
/* @internal */
namespace ts.DocumentHighlights {
export function getDocumentHighlights(program: Program, 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, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile));
}

View file

@ -61,13 +61,18 @@ namespace ts.FindAllReferences {
}
export function getImplementationsAtPosition(program: Program, 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(program, cancellationToken, sourceFiles, node);
const checker = program.getTypeChecker();
return map(referenceEntries, entry => toImplementationLocation(entry, checker));
}
function getImplementationReferenceEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): Entry[] | undefined {
if (node.kind === SyntaxKind.SourceFile) {
return undefined;
}
const checker = program.getTypeChecker();
// If invoked directly on a shorthand property assignment, then return
// the declaration of the symbol being assigned (not the symbol being assigned to).
@ -740,7 +745,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));
@ -778,9 +783,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));
}
@ -802,13 +808,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
@ -1248,7 +1254,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;
@ -1325,7 +1331,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;
}
@ -1379,7 +1385,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));
}

View file

@ -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);
});

View file

@ -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;
}
@ -95,7 +95,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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -107,6 +107,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));
@ -135,10 +136,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) => {
@ -155,7 +161,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
@ -173,8 +179,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 {
@ -215,7 +224,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);
}
}
@ -1356,7 +1365,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;
}
@ -1543,7 +1552,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;
@ -1654,7 +1663,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);
@ -1856,7 +1865,6 @@ namespace ts {
// OK, we have found a match in the file. This is only an acceptable match if
// it is contained within a comment.
if (!isInComment(sourceFile, matchPosition)) {
continue;
}

View file

@ -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

View file

@ -590,29 +590,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)) {
@ -659,7 +659,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;
}
@ -789,7 +789,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;
@ -825,7 +825,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);
}
@ -835,7 +835,11 @@ namespace ts {
* @param tokenAtPosition Must equal `getTokenAtPosition(sourceFile, position)
* @param predicate Additional predicate to test on the comment range.
*/
export function isInComment(sourceFile: SourceFile, position: number, tokenAtPosition = getTokenAtPosition(sourceFile, position), predicate?: (c: CommentRange) => boolean): boolean {
export function isInComment(
sourceFile: SourceFile,
position: number,
tokenAtPosition = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false),
predicate?: (c: CommentRange) => boolean): boolean {
return position <= tokenAtPosition.getStart(sourceFile) &&
(isInCommentRange(getLeadingCommentRanges(sourceFile.text, tokenAtPosition.pos)) ||
isInCommentRange(getTrailingCommentRanges(sourceFile.text, tokenAtPosition.pos)));
@ -870,7 +874,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);
@ -887,7 +891,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:
@ -1354,6 +1358,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);
}
}

View file

@ -0,0 +1,49 @@
tests/cases/compiler/jsdocInTypeScript.ts(16,23): error TS2304: Cannot find name 'MyType'.
tests/cases/compiler/jsdocInTypeScript.ts(23,33): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
tests/cases/compiler/jsdocInTypeScript.ts(25,3): error TS2345: Argument of type '1' is not assignable to parameter of type 'boolean'.
tests/cases/compiler/jsdocInTypeScript.ts(25,15): error TS2339: Property 'length' does not exist on type 'number'.
tests/cases/compiler/jsdocInTypeScript.ts(30,3): error TS2339: Property 'x' does not exist on type '{}'.
==== tests/cases/compiler/jsdocInTypeScript.ts (5 errors) ====
// JSDoc typedef tags are not bound TypeScript files.
/** @typedef {function} T */
declare const x: T;
class T {
prop: number;
}
x.prop;
// Just to be sure that @property has no impact either.
/**
* @typedef {Object} MyType
* @property {string} yes
*/
declare const myType: MyType; // should error, no such type
~~~~~~
!!! error TS2304: Cannot find name 'MyType'.
// @param type has no effect.
/**
* @param {number} x
* @returns string
*/
function f(x: boolean) { return x * 2; } // Should error
~
!!! error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.
// Should fail, because it takes a boolean and returns a number
f(1); f(true).length;
~
!!! error TS2345: Argument of type '1' is not assignable to parameter of type 'boolean'.
~~~~~~
!!! error TS2339: Property 'length' does not exist on type 'number'.
// @type has no effect either.
/** @type {{ x?: number }} */
const z = {};
z.x = 1;
~
!!! error TS2339: Property 'x' does not exist on type '{}'.

View file

@ -8,6 +8,27 @@ class T {
}
x.prop;
// Just to be sure that @property has no impact either.
/**
* @typedef {Object} MyType
* @property {string} yes
*/
declare const myType: MyType; // should error, no such type
// @param type has no effect.
/**
* @param {number} x
* @returns string
*/
function f(x: boolean) { return x * 2; } // Should error
// Should fail, because it takes a boolean and returns a number
f(1); f(true).length;
// @type has no effect either.
/** @type {{ x?: number }} */
const z = {};
z.x = 1;
//// [jsdocInTypeScript.js]
@ -17,3 +38,16 @@ var T = (function () {
return T;
}());
x.prop;
// @param type has no effect.
/**
* @param {number} x
* @returns string
*/
function f(x) { return x * 2; } // Should error
// Should fail, because it takes a boolean and returns a number
f(1);
f(true).length;
// @type has no effect either.
/** @type {{ x?: number }} */
var z = {};
z.x = 1;

View file

@ -1,19 +0,0 @@
=== tests/cases/compiler/jsdocInTypeScript.ts ===
// JSDoc typedef tags are not bound TypeScript files.
/** @typedef {function} T */
declare const x: T;
>x : Symbol(x, Decl(jsdocInTypeScript.ts, 2, 13))
>T : Symbol(T, Decl(jsdocInTypeScript.ts, 2, 19))
class T {
>T : Symbol(T, Decl(jsdocInTypeScript.ts, 2, 19))
prop: number;
>prop : Symbol(T.prop, Decl(jsdocInTypeScript.ts, 4, 9))
}
x.prop;
>x.prop : Symbol(T.prop, Decl(jsdocInTypeScript.ts, 4, 9))
>x : Symbol(x, Decl(jsdocInTypeScript.ts, 2, 13))
>prop : Symbol(T.prop, Decl(jsdocInTypeScript.ts, 4, 9))

View file

@ -1,19 +0,0 @@
=== tests/cases/compiler/jsdocInTypeScript.ts ===
// JSDoc typedef tags are not bound TypeScript files.
/** @typedef {function} T */
declare const x: T;
>x : T
>T : T
class T {
>T : T
prop: number;
>prop : number
}
x.prop;
>x.prop : number
>x : T
>prop : number

View file

@ -7,3 +7,24 @@ class T {
}
x.prop;
// Just to be sure that @property has no impact either.
/**
* @typedef {Object} MyType
* @property {string} yes
*/
declare const myType: MyType; // should error, no such type
// @param type has no effect.
/**
* @param {number} x
* @returns string
*/
function f(x: boolean) { return x * 2; } // Should error
// Should fail, because it takes a boolean and returns a number
f(1); f(true).length;
// @type has no effect either.
/** @type {{ x?: number }} */
const z = {};
z.x = 1;

View file

@ -0,0 +1,9 @@
/// <reference path='fourslash.ts' />
// Just testing that this doesn't cause an exception due to the @typedef contents not having '.parent' set.
// But it isn't bound to a Symbol so find-all-refs will return nothing.
/////** @typedef {Object} [|T|] */
////function foo() {}
verify.referenceGroups(test.ranges()[0], undefined);

View file

@ -0,0 +1,11 @@
// @noLib: true
/// <reference path='fourslash.ts'/>
/////**
//// * @param {[|number|]} n
//// * @returns {[|number|]}
//// */
////function f(n: [|number|]): [|number|] {}
verify.singleReferenceGroup("number");

View 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();