mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-12 11:50:54 -06:00
Break many functions out of services.ts and into their own modules.
This commit is contained in:
parent
4685646281
commit
4d6bd9df72
@ -232,8 +232,8 @@ namespace Harness.LanguageService {
|
||||
}
|
||||
getHost() { return this.host; }
|
||||
getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); }
|
||||
getClassifier(): ts.Classifier { return ts.createClassifier(); }
|
||||
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJavaScriptFileExtension(fileName)); }
|
||||
getClassifier(): ts.Classifier { return ts.Classifier.createClassifier(); }
|
||||
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.PreProcess.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJavaScriptFileExtension(fileName)); }
|
||||
}
|
||||
|
||||
/// Shim adapter
|
||||
@ -258,7 +258,7 @@ namespace Harness.LanguageService {
|
||||
};
|
||||
this.getModuleResolutionsForFile = (fileName) => {
|
||||
const scriptInfo = this.getScriptInfo(fileName);
|
||||
const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true);
|
||||
const preprocessInfo = ts.PreProcess.preProcessFile(scriptInfo.content, /*readImportFiles*/ true);
|
||||
const imports = ts.createMap<string>();
|
||||
for (const module of preprocessInfo.importedFiles) {
|
||||
const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost);
|
||||
@ -271,7 +271,7 @@ namespace Harness.LanguageService {
|
||||
this.getTypeReferenceDirectiveResolutionsForFile = (fileName) => {
|
||||
const scriptInfo = this.getScriptInfo(fileName);
|
||||
if (scriptInfo) {
|
||||
const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false);
|
||||
const preprocessInfo = ts.PreProcess.preProcessFile(scriptInfo.content, /*readImportFiles*/ false);
|
||||
const resolutions = ts.createMap<ts.ResolvedTypeReferenceDirective>();
|
||||
const settings = this.nativeHost.getCompilationSettings();
|
||||
for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
describe("PreProcessFile:", function () {
|
||||
function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void {
|
||||
const resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports);
|
||||
const resultPreProcess = ts.PreProcess.preProcessFile(sourceText, readImportFile, detectJavaScriptImports);
|
||||
|
||||
assert.equal(resultPreProcess.isLibFile, expectedPreProcess.isLibFile, "Pre-processed file has different value for isLibFile. Expected: " + expectedPreProcess.isLibFile + ". Actual: " + resultPreProcess.isLibFile);
|
||||
|
||||
|
||||
@ -1566,7 +1566,7 @@ namespace ts.server {
|
||||
this.setCompilerOptions(defaultOpts);
|
||||
}
|
||||
this.languageService = ts.createLanguageService(this.host, this.documentRegistry);
|
||||
this.classifier = ts.createClassifier();
|
||||
this.classifier = ts.Classifier.createClassifier();
|
||||
}
|
||||
|
||||
setCompilerOptions(opt: ts.CompilerOptions) {
|
||||
|
||||
633
src/services/allocators.ts
Normal file
633
src/services/allocators.ts
Normal file
@ -0,0 +1,633 @@
|
||||
/* @internal */
|
||||
namespace ts.Allocators {
|
||||
function createNode(kind: SyntaxKind, pos: number, end: number, parent?: Node): NodeObject | TokenObject | IdentifierObject {
|
||||
const node = kind >= SyntaxKind.FirstNode ? new NodeObject(kind, pos, end) :
|
||||
kind === SyntaxKind.Identifier ? new IdentifierObject(kind, pos, end) :
|
||||
new TokenObject(kind, pos, end);
|
||||
node.parent = parent;
|
||||
return node;
|
||||
}
|
||||
|
||||
class NodeObject implements Node {
|
||||
public kind: SyntaxKind;
|
||||
public pos: number;
|
||||
public end: number;
|
||||
public flags: NodeFlags;
|
||||
public parent: Node;
|
||||
public jsDocComments: JSDocComment[];
|
||||
public original: Node;
|
||||
public transformFlags: TransformFlags;
|
||||
public excludeTransformFlags: TransformFlags;
|
||||
private _children: Node[];
|
||||
|
||||
constructor(kind: SyntaxKind, pos: number, end: number) {
|
||||
this.pos = pos;
|
||||
this.end = end;
|
||||
this.flags = NodeFlags.None;
|
||||
this.transformFlags = undefined;
|
||||
this.excludeTransformFlags = undefined;
|
||||
this.parent = undefined;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public getSourceFile(): SourceFile {
|
||||
return getSourceFileOfNode(this);
|
||||
}
|
||||
|
||||
public getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number {
|
||||
return getTokenPosOfNode(this, sourceFile, includeJsDocComment);
|
||||
}
|
||||
|
||||
public getFullStart(): number {
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
public getEnd(): number {
|
||||
return this.end;
|
||||
}
|
||||
|
||||
public getWidth(sourceFile?: SourceFile): number {
|
||||
return this.getEnd() - this.getStart(sourceFile);
|
||||
}
|
||||
|
||||
public getFullWidth(): number {
|
||||
return this.end - this.pos;
|
||||
}
|
||||
|
||||
public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
|
||||
return this.getStart(sourceFile) - this.pos;
|
||||
}
|
||||
|
||||
public getFullText(sourceFile?: SourceFile): string {
|
||||
return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
|
||||
}
|
||||
|
||||
public getText(sourceFile?: SourceFile): string {
|
||||
return (sourceFile || this.getSourceFile()).text.substring(this.getStart(), this.getEnd());
|
||||
}
|
||||
|
||||
private addSyntheticNodes(nodes: Node[], pos: number, end: number, useJSDocScanner?: boolean): number {
|
||||
scanner.setTextPos(pos);
|
||||
while (pos < end) {
|
||||
const token = useJSDocScanner ? scanner.scanJSDocToken() : scanner.scan();
|
||||
const textPos = scanner.getTextPos();
|
||||
if (textPos <= end) {
|
||||
nodes.push(createNode(token, pos, textPos, this));
|
||||
}
|
||||
pos = textPos;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private createSyntaxList(nodes: NodeArray<Node>): Node {
|
||||
const list = <NodeObject>createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, this);
|
||||
list._children = [];
|
||||
let pos = nodes.pos;
|
||||
|
||||
for (const node of nodes) {
|
||||
if (pos < node.pos) {
|
||||
pos = this.addSyntheticNodes(list._children, pos, node.pos);
|
||||
}
|
||||
list._children.push(node);
|
||||
pos = node.end;
|
||||
}
|
||||
if (pos < nodes.end) {
|
||||
this.addSyntheticNodes(list._children, pos, nodes.end);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private createChildren(sourceFile?: SourceFile) {
|
||||
let children: Node[];
|
||||
if (this.kind >= SyntaxKind.FirstNode) {
|
||||
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) => {
|
||||
const isJSDocTagNode = isJSDocTag(node);
|
||||
if (!isJSDocTagNode && pos < node.pos) {
|
||||
pos = this.addSyntheticNodes(children, pos, node.pos, useJSDocScanner);
|
||||
}
|
||||
children.push(node);
|
||||
if (!isJSDocTagNode) {
|
||||
pos = node.end;
|
||||
}
|
||||
};
|
||||
const processNodes = (nodes: NodeArray<Node>) => {
|
||||
if (pos < nodes.pos) {
|
||||
pos = this.addSyntheticNodes(children, pos, nodes.pos, useJSDocScanner);
|
||||
}
|
||||
children.push(this.createSyntaxList(<NodeArray<Node>>nodes));
|
||||
pos = nodes.end;
|
||||
};
|
||||
// jsDocComments need to be the first children
|
||||
if (this.jsDocComments) {
|
||||
for (const jsDocComment of this.jsDocComments) {
|
||||
processNode(jsDocComment);
|
||||
}
|
||||
}
|
||||
// For syntactic classifications, all trivia are classcified together, including jsdoc comments.
|
||||
// For that to work, the jsdoc comments should still be the leading trivia of the first child.
|
||||
// Restoring the scanner position ensures that.
|
||||
pos = this.pos;
|
||||
forEachChild(this, processNode, processNodes);
|
||||
if (pos < this.end) {
|
||||
this.addSyntheticNodes(children, pos, this.end);
|
||||
}
|
||||
scanner.setText(undefined);
|
||||
}
|
||||
this._children = children || emptyArray;
|
||||
}
|
||||
|
||||
public getChildCount(sourceFile?: SourceFile): number {
|
||||
if (!this._children) this.createChildren(sourceFile);
|
||||
return this._children.length;
|
||||
}
|
||||
|
||||
public getChildAt(index: number, sourceFile?: SourceFile): Node {
|
||||
if (!this._children) this.createChildren(sourceFile);
|
||||
return this._children[index];
|
||||
}
|
||||
|
||||
public getChildren(sourceFile?: SourceFile): Node[] {
|
||||
if (!this._children) this.createChildren(sourceFile);
|
||||
return this._children;
|
||||
}
|
||||
|
||||
public getFirstToken(sourceFile?: SourceFile): Node {
|
||||
const children = this.getChildren(sourceFile);
|
||||
if (!children.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const child = children[0];
|
||||
|
||||
return child.kind < SyntaxKind.FirstNode ? child : child.getFirstToken(sourceFile);
|
||||
}
|
||||
|
||||
public getLastToken(sourceFile?: SourceFile): Node {
|
||||
const children = this.getChildren(sourceFile);
|
||||
|
||||
const child = lastOrUndefined(children);
|
||||
if (!child) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
class TokenOrIdentifierObject implements Token {
|
||||
public kind: SyntaxKind;
|
||||
public pos: number;
|
||||
public end: number;
|
||||
public flags: NodeFlags;
|
||||
public parent: Node;
|
||||
public jsDocComments: JSDocComment[];
|
||||
public __tokenTag: any;
|
||||
|
||||
constructor(pos: number, end: number) {
|
||||
// Set properties in same order as NodeObject
|
||||
this.pos = pos;
|
||||
this.end = end;
|
||||
this.flags = NodeFlags.None;
|
||||
this.parent = undefined;
|
||||
}
|
||||
|
||||
public getSourceFile(): SourceFile {
|
||||
return getSourceFileOfNode(this);
|
||||
}
|
||||
|
||||
public getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number {
|
||||
return getTokenPosOfNode(this, sourceFile, includeJsDocComment);
|
||||
}
|
||||
|
||||
public getFullStart(): number {
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
public getEnd(): number {
|
||||
return this.end;
|
||||
}
|
||||
|
||||
public getWidth(sourceFile?: SourceFile): number {
|
||||
return this.getEnd() - this.getStart(sourceFile);
|
||||
}
|
||||
|
||||
public getFullWidth(): number {
|
||||
return this.end - this.pos;
|
||||
}
|
||||
|
||||
public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
|
||||
return this.getStart(sourceFile) - this.pos;
|
||||
}
|
||||
|
||||
public getFullText(sourceFile?: SourceFile): string {
|
||||
return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
|
||||
}
|
||||
|
||||
public getText(sourceFile?: SourceFile): string {
|
||||
return (sourceFile || this.getSourceFile()).text.substring(this.getStart(), this.getEnd());
|
||||
}
|
||||
|
||||
public getChildCount(sourceFile?: SourceFile): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public getChildAt(index: number, sourceFile?: SourceFile): Node {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getChildren(sourceFile?: SourceFile): Node[] {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
public getFirstToken(sourceFile?: SourceFile): Node {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getLastToken(sourceFile?: SourceFile): Node {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class SymbolObject implements Symbol {
|
||||
flags: SymbolFlags;
|
||||
name: string;
|
||||
declarations: Declaration[];
|
||||
|
||||
// Undefined is used to indicate the value has not been computed. If, after computing, the
|
||||
// symbol has no doc comment, then the empty string will be returned.
|
||||
documentationComment: SymbolDisplayPart[];
|
||||
|
||||
constructor(flags: SymbolFlags, name: string) {
|
||||
this.flags = flags;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
getFlags(): SymbolFlags {
|
||||
return this.flags;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
getDeclarations(): Declaration[] {
|
||||
return this.declarations;
|
||||
}
|
||||
|
||||
getDocumentationComment(): SymbolDisplayPart[] {
|
||||
if (this.documentationComment === undefined) {
|
||||
this.documentationComment = JsDoc.getJsDocCommentsFromDeclarations(this.declarations, this.name, !(this.flags & SymbolFlags.Property));
|
||||
}
|
||||
|
||||
return this.documentationComment;
|
||||
}
|
||||
}
|
||||
|
||||
class TokenObject extends TokenOrIdentifierObject {
|
||||
public kind: SyntaxKind;
|
||||
constructor(kind: SyntaxKind, pos: number, end: number) {
|
||||
super(pos, end);
|
||||
this.kind = kind;
|
||||
}
|
||||
}
|
||||
|
||||
class IdentifierObject extends TokenOrIdentifierObject {
|
||||
constructor(kind: SyntaxKind, pos: number, end: number) {
|
||||
super(pos, end);
|
||||
}
|
||||
}
|
||||
IdentifierObject.prototype.kind = SyntaxKind.Identifier;
|
||||
|
||||
class TypeObject implements Type {
|
||||
checker: TypeChecker;
|
||||
flags: TypeFlags;
|
||||
id: number;
|
||||
symbol: Symbol;
|
||||
constructor(checker: TypeChecker, flags: TypeFlags) {
|
||||
this.checker = checker;
|
||||
this.flags = flags;
|
||||
}
|
||||
getFlags(): TypeFlags {
|
||||
return this.flags;
|
||||
}
|
||||
getSymbol(): Symbol {
|
||||
return this.symbol;
|
||||
}
|
||||
getProperties(): Symbol[] {
|
||||
return this.checker.getPropertiesOfType(this);
|
||||
}
|
||||
getProperty(propertyName: string): Symbol {
|
||||
return this.checker.getPropertyOfType(this, propertyName);
|
||||
}
|
||||
getApparentProperties(): Symbol[] {
|
||||
return this.checker.getAugmentedPropertiesOfType(this);
|
||||
}
|
||||
getCallSignatures(): Signature[] {
|
||||
return this.checker.getSignaturesOfType(this, SignatureKind.Call);
|
||||
}
|
||||
getConstructSignatures(): Signature[] {
|
||||
return this.checker.getSignaturesOfType(this, SignatureKind.Construct);
|
||||
}
|
||||
getStringIndexType(): Type {
|
||||
return this.checker.getIndexTypeOfType(this, IndexKind.String);
|
||||
}
|
||||
getNumberIndexType(): Type {
|
||||
return this.checker.getIndexTypeOfType(this, IndexKind.Number);
|
||||
}
|
||||
getBaseTypes(): ObjectType[] {
|
||||
return this.flags & (TypeFlags.Class | TypeFlags.Interface)
|
||||
? this.checker.getBaseTypes(<InterfaceType><Type>this)
|
||||
: undefined;
|
||||
}
|
||||
getNonNullableType(): Type {
|
||||
return this.checker.getNonNullableType(this);
|
||||
}
|
||||
}
|
||||
|
||||
class SignatureObject implements Signature {
|
||||
checker: TypeChecker;
|
||||
declaration: SignatureDeclaration;
|
||||
typeParameters: TypeParameter[];
|
||||
parameters: Symbol[];
|
||||
thisParameter: Symbol;
|
||||
resolvedReturnType: Type;
|
||||
minArgumentCount: number;
|
||||
hasRestParameter: boolean;
|
||||
hasLiteralTypes: boolean;
|
||||
|
||||
// Undefined is used to indicate the value has not been computed. If, after computing, the
|
||||
// symbol has no doc comment, then the empty string will be returned.
|
||||
documentationComment: SymbolDisplayPart[];
|
||||
|
||||
constructor(checker: TypeChecker) {
|
||||
this.checker = checker;
|
||||
}
|
||||
getDeclaration(): SignatureDeclaration {
|
||||
return this.declaration;
|
||||
}
|
||||
getTypeParameters(): Type[] {
|
||||
return this.typeParameters;
|
||||
}
|
||||
getParameters(): Symbol[] {
|
||||
return this.parameters;
|
||||
}
|
||||
getReturnType(): Type {
|
||||
return this.checker.getReturnTypeOfSignature(this);
|
||||
}
|
||||
|
||||
getDocumentationComment(): SymbolDisplayPart[] {
|
||||
if (this.documentationComment === undefined) {
|
||||
this.documentationComment = this.declaration ? JsDoc.getJsDocCommentsFromDeclarations(
|
||||
[this.declaration],
|
||||
/*name*/ undefined,
|
||||
/*canUseParsedParamTagComments*/ false) : [];
|
||||
}
|
||||
|
||||
return this.documentationComment;
|
||||
}
|
||||
}
|
||||
|
||||
class SourceFileObject extends NodeObject implements SourceFile {
|
||||
public _declarationBrand: any;
|
||||
public fileName: string;
|
||||
public path: Path;
|
||||
public text: string;
|
||||
public scriptSnapshot: IScriptSnapshot;
|
||||
public lineMap: number[];
|
||||
|
||||
public statements: NodeArray<Statement>;
|
||||
public endOfFileToken: Node;
|
||||
|
||||
public amdDependencies: { name: string; path: string }[];
|
||||
public moduleName: string;
|
||||
public referencedFiles: FileReference[];
|
||||
public typeReferenceDirectives: FileReference[];
|
||||
|
||||
public syntacticDiagnostics: Diagnostic[];
|
||||
public referenceDiagnostics: Diagnostic[];
|
||||
public parseDiagnostics: Diagnostic[];
|
||||
public bindDiagnostics: Diagnostic[];
|
||||
|
||||
public isDeclarationFile: boolean;
|
||||
public isDefaultLib: boolean;
|
||||
public hasNoDefaultLib: boolean;
|
||||
public externalModuleIndicator: Node; // The first node that causes this file to be an external module
|
||||
public commonJsModuleIndicator: Node; // The first node that causes this file to be a CommonJS module
|
||||
public nodeCount: number;
|
||||
public identifierCount: number;
|
||||
public symbolCount: number;
|
||||
public version: string;
|
||||
public scriptKind: ScriptKind;
|
||||
public languageVersion: ScriptTarget;
|
||||
public languageVariant: LanguageVariant;
|
||||
public identifiers: Map<string>;
|
||||
public nameTable: Map<number>;
|
||||
public resolvedModules: Map<ResolvedModule>;
|
||||
public resolvedTypeReferenceDirectiveNames: Map<ResolvedTypeReferenceDirective>;
|
||||
public imports: LiteralExpression[];
|
||||
public moduleAugmentations: LiteralExpression[];
|
||||
private namedDeclarations: Map<Declaration[]>;
|
||||
|
||||
constructor(kind: SyntaxKind, pos: number, end: number) {
|
||||
super(kind, pos, end);
|
||||
}
|
||||
|
||||
public update(newText: string, textChangeRange: TextChangeRange): SourceFile {
|
||||
return updateSourceFile(this, newText, textChangeRange);
|
||||
}
|
||||
|
||||
public getLineAndCharacterOfPosition(position: number): LineAndCharacter {
|
||||
return ts.getLineAndCharacterOfPosition(this, position);
|
||||
}
|
||||
|
||||
public getLineStarts(): number[] {
|
||||
return getLineStarts(this);
|
||||
}
|
||||
|
||||
public getPositionOfLineAndCharacter(line: number, character: number): number {
|
||||
return ts.getPositionOfLineAndCharacter(this, line, character);
|
||||
}
|
||||
|
||||
public getNamedDeclarations(): Map<Declaration[]> {
|
||||
if (!this.namedDeclarations) {
|
||||
this.namedDeclarations = this.computeNamedDeclarations();
|
||||
}
|
||||
|
||||
return this.namedDeclarations;
|
||||
}
|
||||
|
||||
private computeNamedDeclarations(): Map<Declaration[]> {
|
||||
const result = createMap<Declaration[]>();
|
||||
|
||||
forEachChild(this, visit);
|
||||
|
||||
return result;
|
||||
|
||||
function addDeclaration(declaration: Declaration) {
|
||||
const name = getDeclarationName(declaration);
|
||||
if (name) {
|
||||
multiMapAdd(result, name, declaration);
|
||||
}
|
||||
}
|
||||
|
||||
function getDeclarations(name: string) {
|
||||
return result[name] || (result[name] = []);
|
||||
}
|
||||
|
||||
function getDeclarationName(declaration: Declaration) {
|
||||
if (declaration.name) {
|
||||
const result = getTextOfIdentifierOrLiteral(declaration.name);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (declaration.name.kind === SyntaxKind.ComputedPropertyName) {
|
||||
const expr = (<ComputedPropertyName>declaration.name).expression;
|
||||
if (expr.kind === SyntaxKind.PropertyAccessExpression) {
|
||||
return (<PropertyAccessExpression>expr).name.text;
|
||||
}
|
||||
|
||||
return getTextOfIdentifierOrLiteral(expr);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getTextOfIdentifierOrLiteral(node: Node) {
|
||||
if (node) {
|
||||
if (node.kind === SyntaxKind.Identifier ||
|
||||
node.kind === SyntaxKind.StringLiteral ||
|
||||
node.kind === SyntaxKind.NumericLiteral) {
|
||||
|
||||
return (<Identifier | LiteralExpression>node).text;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function visit(node: Node): void {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
const functionDeclaration = <FunctionLikeDeclaration>node;
|
||||
const declarationName = getDeclarationName(functionDeclaration);
|
||||
|
||||
if (declarationName) {
|
||||
const declarations = getDeclarations(declarationName);
|
||||
const lastDeclaration = lastOrUndefined(declarations);
|
||||
|
||||
// Check whether this declaration belongs to an "overload group".
|
||||
if (lastDeclaration && functionDeclaration.parent === lastDeclaration.parent && functionDeclaration.symbol === lastDeclaration.symbol) {
|
||||
// Overwrite the last declaration if it was an overload
|
||||
// and this one is an implementation.
|
||||
if (functionDeclaration.body && !(<FunctionLikeDeclaration>lastDeclaration).body) {
|
||||
declarations[declarations.length - 1] = functionDeclaration;
|
||||
}
|
||||
}
|
||||
else {
|
||||
declarations.push(functionDeclaration);
|
||||
}
|
||||
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
case SyntaxKind.ExportSpecifier:
|
||||
case SyntaxKind.ImportSpecifier:
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
case SyntaxKind.ImportClause:
|
||||
case SyntaxKind.NamespaceImport:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
addDeclaration(<Declaration>node);
|
||||
forEachChild(node, visit);
|
||||
break;
|
||||
|
||||
case SyntaxKind.Parameter:
|
||||
// Only consider parameter properties
|
||||
if (!hasModifier(node, ModifierFlags.ParameterPropertyModifier)) {
|
||||
break;
|
||||
}
|
||||
// fall through
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
case SyntaxKind.BindingElement: {
|
||||
const decl = <VariableDeclaration>node;
|
||||
if (isBindingPattern(decl.name)) {
|
||||
forEachChild(decl.name, visit);
|
||||
break;
|
||||
}
|
||||
if (decl.initializer)
|
||||
visit(decl.initializer);
|
||||
}
|
||||
case SyntaxKind.EnumMember:
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertySignature:
|
||||
addDeclaration(<Declaration>node);
|
||||
break;
|
||||
|
||||
case SyntaxKind.ExportDeclaration:
|
||||
// Handle named exports case e.g.:
|
||||
// export {a, b as B} from "mod";
|
||||
if ((<ExportDeclaration>node).exportClause) {
|
||||
forEach((<ExportDeclaration>node).exportClause.elements, visit);
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
const importClause = (<ImportDeclaration>node).importClause;
|
||||
if (importClause) {
|
||||
// Handle default import case e.g.:
|
||||
// import d from "mod";
|
||||
if (importClause.name) {
|
||||
addDeclaration(importClause);
|
||||
}
|
||||
|
||||
// Handle named bindings in imports e.g.:
|
||||
// import * as NS from "mod";
|
||||
// import {a, b as B} from "mod";
|
||||
if (importClause.namedBindings) {
|
||||
if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) {
|
||||
addDeclaration(<NamespaceImport>importClause.namedBindings);
|
||||
}
|
||||
else {
|
||||
forEach((<NamedImports>importClause.namedBindings).elements, visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getServicesObjectAllocator(): ObjectAllocator {
|
||||
return {
|
||||
getNodeConstructor: () => NodeObject,
|
||||
getTokenConstructor: () => TokenObject,
|
||||
getIdentifierConstructor: () => IdentifierObject,
|
||||
getSourceFileConstructor: () => SourceFileObject,
|
||||
getSymbolConstructor: () => SymbolObject,
|
||||
getTypeConstructor: () => TypeObject,
|
||||
getSignatureConstructor: () => SignatureObject,
|
||||
};
|
||||
}
|
||||
}
|
||||
982
src/services/classifier.ts
Normal file
982
src/services/classifier.ts
Normal file
@ -0,0 +1,982 @@
|
||||
/* @internal */
|
||||
namespace ts.Classifier {
|
||||
/// Classifier
|
||||
export function createClassifier(): Classifier {
|
||||
const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false);
|
||||
|
||||
/// We do not have a full parser support to know when we should parse a regex or not
|
||||
/// If we consider every slash token to be a regex, we could be missing cases like "1/2/3", where
|
||||
/// we have a series of divide operator. this list allows us to be more accurate by ruling out
|
||||
/// locations where a regexp cannot exist.
|
||||
const noRegexTable: boolean[] = [];
|
||||
noRegexTable[SyntaxKind.Identifier] = true;
|
||||
noRegexTable[SyntaxKind.StringLiteral] = true;
|
||||
noRegexTable[SyntaxKind.NumericLiteral] = true;
|
||||
noRegexTable[SyntaxKind.RegularExpressionLiteral] = true;
|
||||
noRegexTable[SyntaxKind.ThisKeyword] = true;
|
||||
noRegexTable[SyntaxKind.PlusPlusToken] = true;
|
||||
noRegexTable[SyntaxKind.MinusMinusToken] = true;
|
||||
noRegexTable[SyntaxKind.CloseParenToken] = true;
|
||||
noRegexTable[SyntaxKind.CloseBracketToken] = true;
|
||||
noRegexTable[SyntaxKind.CloseBraceToken] = true;
|
||||
noRegexTable[SyntaxKind.TrueKeyword] = true;
|
||||
noRegexTable[SyntaxKind.FalseKeyword] = true;
|
||||
|
||||
// Just a stack of TemplateHeads and OpenCurlyBraces, used to perform rudimentary (inexact)
|
||||
// classification on template strings. Because of the context free nature of templates,
|
||||
// the only precise way to classify a template portion would be by propagating the stack across
|
||||
// lines, just as we do with the end-of-line state. However, this is a burden for implementers,
|
||||
// and the behavior is entirely subsumed by the syntactic classifier anyway, so we instead
|
||||
// flatten any nesting when the template stack is non-empty and encode it in the end-of-line state.
|
||||
// Situations in which this fails are
|
||||
// 1) When template strings are nested across different lines:
|
||||
// `hello ${ `world
|
||||
// ` }`
|
||||
//
|
||||
// Where on the second line, you will get the closing of a template,
|
||||
// a closing curly, and a new template.
|
||||
//
|
||||
// 2) When substitution expressions have curly braces and the curly brace falls on the next line:
|
||||
// `hello ${ () => {
|
||||
// return "world" } } `
|
||||
//
|
||||
// Where on the second line, you will get the 'return' keyword,
|
||||
// a string literal, and a template end consisting of '} } `'.
|
||||
const templateStack: SyntaxKind[] = [];
|
||||
|
||||
/** Returns true if 'keyword2' can legally follow 'keyword1' in any language construct. */
|
||||
function canFollow(keyword1: SyntaxKind, keyword2: SyntaxKind) {
|
||||
if (isAccessibilityModifier(keyword1)) {
|
||||
if (keyword2 === SyntaxKind.GetKeyword ||
|
||||
keyword2 === SyntaxKind.SetKeyword ||
|
||||
keyword2 === SyntaxKind.ConstructorKeyword ||
|
||||
keyword2 === SyntaxKind.StaticKeyword) {
|
||||
|
||||
// Allow things like "public get", "public constructor" and "public static".
|
||||
// These are all legal.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Any other keyword following "public" is actually an identifier an not a real
|
||||
// keyword.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assume any other keyword combination is legal. This can be refined in the future
|
||||
// if there are more cases we want the classifier to be better at.
|
||||
return true;
|
||||
}
|
||||
|
||||
function convertClassifications(classifications: Classifications, text: string): ClassificationResult {
|
||||
const entries: ClassificationInfo[] = [];
|
||||
const dense = classifications.spans;
|
||||
let lastEnd = 0;
|
||||
|
||||
for (let i = 0, n = dense.length; i < n; i += 3) {
|
||||
const start = dense[i];
|
||||
const length = dense[i + 1];
|
||||
const type = <ClassificationType>dense[i + 2];
|
||||
|
||||
// Make a whitespace entry between the last item and this one.
|
||||
if (lastEnd >= 0) {
|
||||
const whitespaceLength = start - lastEnd;
|
||||
if (whitespaceLength > 0) {
|
||||
entries.push({ length: whitespaceLength, classification: TokenClass.Whitespace });
|
||||
}
|
||||
}
|
||||
|
||||
entries.push({ length, classification: convertClassification(type) });
|
||||
lastEnd = start + length;
|
||||
}
|
||||
|
||||
const whitespaceLength = text.length - lastEnd;
|
||||
if (whitespaceLength > 0) {
|
||||
entries.push({ length: whitespaceLength, classification: TokenClass.Whitespace });
|
||||
}
|
||||
|
||||
return { entries, finalLexState: classifications.endOfLineState };
|
||||
}
|
||||
|
||||
function convertClassification(type: ClassificationType): TokenClass {
|
||||
switch (type) {
|
||||
case ClassificationType.comment: return TokenClass.Comment;
|
||||
case ClassificationType.keyword: return TokenClass.Keyword;
|
||||
case ClassificationType.numericLiteral: return TokenClass.NumberLiteral;
|
||||
case ClassificationType.operator: return TokenClass.Operator;
|
||||
case ClassificationType.stringLiteral: return TokenClass.StringLiteral;
|
||||
case ClassificationType.whiteSpace: return TokenClass.Whitespace;
|
||||
case ClassificationType.punctuation: return TokenClass.Punctuation;
|
||||
case ClassificationType.identifier:
|
||||
case ClassificationType.className:
|
||||
case ClassificationType.enumName:
|
||||
case ClassificationType.interfaceName:
|
||||
case ClassificationType.moduleName:
|
||||
case ClassificationType.typeParameterName:
|
||||
case ClassificationType.typeAliasName:
|
||||
case ClassificationType.text:
|
||||
case ClassificationType.parameterName:
|
||||
default:
|
||||
return TokenClass.Identifier;
|
||||
}
|
||||
}
|
||||
|
||||
function getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult {
|
||||
return convertClassifications(getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent), text);
|
||||
}
|
||||
|
||||
// If there is a syntactic classifier ('syntacticClassifierAbsent' is false),
|
||||
// we will be more conservative in order to avoid conflicting with the syntactic classifier.
|
||||
function getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications {
|
||||
let offset = 0;
|
||||
let token = SyntaxKind.Unknown;
|
||||
let lastNonTriviaToken = SyntaxKind.Unknown;
|
||||
|
||||
// Empty out the template stack for reuse.
|
||||
while (templateStack.length > 0) {
|
||||
templateStack.pop();
|
||||
}
|
||||
|
||||
// If we're in a string literal, then prepend: "\
|
||||
// (and a newline). That way when we lex we'll think we're still in a string literal.
|
||||
//
|
||||
// If we're in a multiline comment, then prepend: /*
|
||||
// (and a newline). That way when we lex we'll think we're still in a multiline comment.
|
||||
switch (lexState) {
|
||||
case EndOfLineState.InDoubleQuoteStringLiteral:
|
||||
text = "\"\\\n" + text;
|
||||
offset = 3;
|
||||
break;
|
||||
case EndOfLineState.InSingleQuoteStringLiteral:
|
||||
text = "'\\\n" + text;
|
||||
offset = 3;
|
||||
break;
|
||||
case EndOfLineState.InMultiLineCommentTrivia:
|
||||
text = "/*\n" + text;
|
||||
offset = 3;
|
||||
break;
|
||||
case EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate:
|
||||
text = "`\n" + text;
|
||||
offset = 2;
|
||||
break;
|
||||
case EndOfLineState.InTemplateMiddleOrTail:
|
||||
text = "}\n" + text;
|
||||
offset = 2;
|
||||
// fallthrough
|
||||
case EndOfLineState.InTemplateSubstitutionPosition:
|
||||
templateStack.push(SyntaxKind.TemplateHead);
|
||||
break;
|
||||
}
|
||||
|
||||
scanner.setText(text);
|
||||
|
||||
const result: Classifications = {
|
||||
endOfLineState: EndOfLineState.None,
|
||||
spans: []
|
||||
};
|
||||
|
||||
// We can run into an unfortunate interaction between the lexical and syntactic classifier
|
||||
// when the user is typing something generic. Consider the case where the user types:
|
||||
//
|
||||
// Foo<number
|
||||
//
|
||||
// From the lexical classifier's perspective, 'number' is a keyword, and so the word will
|
||||
// be classified as such. However, from the syntactic classifier's tree-based perspective
|
||||
// this is simply an expression with the identifier 'number' on the RHS of the less than
|
||||
// token. So the classification will go back to being an identifier. The moment the user
|
||||
// types again, number will become a keyword, then an identifier, etc. etc.
|
||||
//
|
||||
// To try to avoid this problem, we avoid classifying contextual keywords as keywords
|
||||
// when the user is potentially typing something generic. We just can't do a good enough
|
||||
// job at the lexical level, and so well leave it up to the syntactic classifier to make
|
||||
// the determination.
|
||||
//
|
||||
// In order to determine if the user is potentially typing something generic, we use a
|
||||
// weak heuristic where we track < and > tokens. It's a weak heuristic, but should
|
||||
// work well enough in practice.
|
||||
let angleBracketStack = 0;
|
||||
|
||||
do {
|
||||
token = scanner.scan();
|
||||
|
||||
if (!isTrivia(token)) {
|
||||
if ((token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) && !noRegexTable[lastNonTriviaToken]) {
|
||||
if (scanner.reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) {
|
||||
token = SyntaxKind.RegularExpressionLiteral;
|
||||
}
|
||||
}
|
||||
else if (lastNonTriviaToken === SyntaxKind.DotToken && isKeyword(token)) {
|
||||
token = SyntaxKind.Identifier;
|
||||
}
|
||||
else if (isKeyword(lastNonTriviaToken) && isKeyword(token) && !canFollow(lastNonTriviaToken, token)) {
|
||||
// We have two keywords in a row. Only treat the second as a keyword if
|
||||
// it's a sequence that could legally occur in the language. Otherwise
|
||||
// treat it as an identifier. This way, if someone writes "private var"
|
||||
// we recognize that 'var' is actually an identifier here.
|
||||
token = SyntaxKind.Identifier;
|
||||
}
|
||||
else if (lastNonTriviaToken === SyntaxKind.Identifier &&
|
||||
token === SyntaxKind.LessThanToken) {
|
||||
// Could be the start of something generic. Keep track of that by bumping
|
||||
// up the current count of generic contexts we may be in.
|
||||
angleBracketStack++;
|
||||
}
|
||||
else if (token === SyntaxKind.GreaterThanToken && angleBracketStack > 0) {
|
||||
// If we think we're currently in something generic, then mark that that
|
||||
// generic entity is complete.
|
||||
angleBracketStack--;
|
||||
}
|
||||
else if (token === SyntaxKind.AnyKeyword ||
|
||||
token === SyntaxKind.StringKeyword ||
|
||||
token === SyntaxKind.NumberKeyword ||
|
||||
token === SyntaxKind.BooleanKeyword ||
|
||||
token === SyntaxKind.SymbolKeyword) {
|
||||
if (angleBracketStack > 0 && !syntacticClassifierAbsent) {
|
||||
// If it looks like we're could be in something generic, don't classify this
|
||||
// as a keyword. We may just get overwritten by the syntactic classifier,
|
||||
// causing a noisy experience for the user.
|
||||
token = SyntaxKind.Identifier;
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.TemplateHead) {
|
||||
templateStack.push(token);
|
||||
}
|
||||
else if (token === SyntaxKind.OpenBraceToken) {
|
||||
// If we don't have anything on the template stack,
|
||||
// then we aren't trying to keep track of a previously scanned template head.
|
||||
if (templateStack.length > 0) {
|
||||
templateStack.push(token);
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.CloseBraceToken) {
|
||||
// If we don't have anything on the template stack,
|
||||
// then we aren't trying to keep track of a previously scanned template head.
|
||||
if (templateStack.length > 0) {
|
||||
const lastTemplateStackToken = lastOrUndefined(templateStack);
|
||||
|
||||
if (lastTemplateStackToken === SyntaxKind.TemplateHead) {
|
||||
token = scanner.reScanTemplateToken();
|
||||
|
||||
// Only pop on a TemplateTail; a TemplateMiddle indicates there is more for us.
|
||||
if (token === SyntaxKind.TemplateTail) {
|
||||
templateStack.pop();
|
||||
}
|
||||
else {
|
||||
Debug.assert(token === SyntaxKind.TemplateMiddle, "Should have been a template middle. Was " + token);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Debug.assert(lastTemplateStackToken === SyntaxKind.OpenBraceToken, "Should have been an open brace. Was: " + token);
|
||||
templateStack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastNonTriviaToken = token;
|
||||
}
|
||||
|
||||
processToken();
|
||||
}
|
||||
while (token !== SyntaxKind.EndOfFileToken);
|
||||
|
||||
return result;
|
||||
|
||||
function processToken(): void {
|
||||
const start = scanner.getTokenPos();
|
||||
const end = scanner.getTextPos();
|
||||
|
||||
addResult(start, end, classFromKind(token));
|
||||
|
||||
if (end >= text.length) {
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// Check to see if we finished up on a multiline string literal.
|
||||
const tokenText = scanner.getTokenText();
|
||||
if (scanner.isUnterminated()) {
|
||||
const lastCharIndex = tokenText.length - 1;
|
||||
|
||||
let numBackslashes = 0;
|
||||
while (tokenText.charCodeAt(lastCharIndex - numBackslashes) === CharacterCodes.backslash) {
|
||||
numBackslashes++;
|
||||
}
|
||||
|
||||
// If we have an odd number of backslashes, then the multiline string is unclosed
|
||||
if (numBackslashes & 1) {
|
||||
const quoteChar = tokenText.charCodeAt(0);
|
||||
result.endOfLineState = quoteChar === CharacterCodes.doubleQuote
|
||||
? EndOfLineState.InDoubleQuoteStringLiteral
|
||||
: EndOfLineState.InSingleQuoteStringLiteral;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.MultiLineCommentTrivia) {
|
||||
// Check to see if the multiline comment was unclosed.
|
||||
if (scanner.isUnterminated()) {
|
||||
result.endOfLineState = EndOfLineState.InMultiLineCommentTrivia;
|
||||
}
|
||||
}
|
||||
else if (isTemplateLiteralKind(token)) {
|
||||
if (scanner.isUnterminated()) {
|
||||
if (token === SyntaxKind.TemplateTail) {
|
||||
result.endOfLineState = EndOfLineState.InTemplateMiddleOrTail;
|
||||
}
|
||||
else if (token === SyntaxKind.NoSubstitutionTemplateLiteral) {
|
||||
result.endOfLineState = EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate;
|
||||
}
|
||||
else {
|
||||
Debug.fail("Only 'NoSubstitutionTemplateLiteral's and 'TemplateTail's can be unterminated; got SyntaxKind #" + token);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (templateStack.length > 0 && lastOrUndefined(templateStack) === SyntaxKind.TemplateHead) {
|
||||
result.endOfLineState = EndOfLineState.InTemplateSubstitutionPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addResult(start: number, end: number, classification: ClassificationType): void {
|
||||
if (classification === ClassificationType.whiteSpace) {
|
||||
// Don't bother with whitespace classifications. They're not needed.
|
||||
return;
|
||||
}
|
||||
|
||||
if (start === 0 && offset > 0) {
|
||||
// We're classifying the first token, and this was a case where we prepended
|
||||
// text. We should consider the start of this token to be at the start of
|
||||
// the original text.
|
||||
start += offset;
|
||||
}
|
||||
|
||||
// All our tokens are in relation to the augmented text. Move them back to be
|
||||
// relative to the original text.
|
||||
start -= offset;
|
||||
end -= offset;
|
||||
const length = end - start;
|
||||
|
||||
if (length > 0) {
|
||||
result.spans.push(start);
|
||||
result.spans.push(length);
|
||||
result.spans.push(classification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isBinaryExpressionOperatorToken(token: SyntaxKind): boolean {
|
||||
switch (token) {
|
||||
case SyntaxKind.AsteriskToken:
|
||||
case SyntaxKind.SlashToken:
|
||||
case SyntaxKind.PercentToken:
|
||||
case SyntaxKind.PlusToken:
|
||||
case SyntaxKind.MinusToken:
|
||||
case SyntaxKind.LessThanLessThanToken:
|
||||
case SyntaxKind.GreaterThanGreaterThanToken:
|
||||
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
||||
case SyntaxKind.LessThanToken:
|
||||
case SyntaxKind.GreaterThanToken:
|
||||
case SyntaxKind.LessThanEqualsToken:
|
||||
case SyntaxKind.GreaterThanEqualsToken:
|
||||
case SyntaxKind.InstanceOfKeyword:
|
||||
case SyntaxKind.InKeyword:
|
||||
case SyntaxKind.AsKeyword:
|
||||
case SyntaxKind.EqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsToken:
|
||||
case SyntaxKind.EqualsEqualsEqualsToken:
|
||||
case SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
case SyntaxKind.AmpersandToken:
|
||||
case SyntaxKind.CaretToken:
|
||||
case SyntaxKind.BarToken:
|
||||
case SyntaxKind.AmpersandAmpersandToken:
|
||||
case SyntaxKind.BarBarToken:
|
||||
case SyntaxKind.BarEqualsToken:
|
||||
case SyntaxKind.AmpersandEqualsToken:
|
||||
case SyntaxKind.CaretEqualsToken:
|
||||
case SyntaxKind.LessThanLessThanEqualsToken:
|
||||
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
|
||||
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
|
||||
case SyntaxKind.PlusEqualsToken:
|
||||
case SyntaxKind.MinusEqualsToken:
|
||||
case SyntaxKind.AsteriskEqualsToken:
|
||||
case SyntaxKind.SlashEqualsToken:
|
||||
case SyntaxKind.PercentEqualsToken:
|
||||
case SyntaxKind.EqualsToken:
|
||||
case SyntaxKind.CommaToken:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isPrefixUnaryExpressionOperatorToken(token: SyntaxKind): boolean {
|
||||
switch (token) {
|
||||
case SyntaxKind.PlusToken:
|
||||
case SyntaxKind.MinusToken:
|
||||
case SyntaxKind.TildeToken:
|
||||
case SyntaxKind.ExclamationToken:
|
||||
case SyntaxKind.PlusPlusToken:
|
||||
case SyntaxKind.MinusMinusToken:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isKeyword(token: SyntaxKind): boolean {
|
||||
return token >= SyntaxKind.FirstKeyword && token <= SyntaxKind.LastKeyword;
|
||||
}
|
||||
|
||||
function classFromKind(token: SyntaxKind): ClassificationType {
|
||||
if (isKeyword(token)) {
|
||||
return ClassificationType.keyword;
|
||||
}
|
||||
else if (isBinaryExpressionOperatorToken(token) || isPrefixUnaryExpressionOperatorToken(token)) {
|
||||
return ClassificationType.operator;
|
||||
}
|
||||
else if (token >= SyntaxKind.FirstPunctuation && token <= SyntaxKind.LastPunctuation) {
|
||||
return ClassificationType.punctuation;
|
||||
}
|
||||
|
||||
switch (token) {
|
||||
case SyntaxKind.NumericLiteral:
|
||||
return ClassificationType.numericLiteral;
|
||||
case SyntaxKind.StringLiteral:
|
||||
return ClassificationType.stringLiteral;
|
||||
case SyntaxKind.RegularExpressionLiteral:
|
||||
return ClassificationType.regularExpressionLiteral;
|
||||
case SyntaxKind.ConflictMarkerTrivia:
|
||||
case SyntaxKind.MultiLineCommentTrivia:
|
||||
case SyntaxKind.SingleLineCommentTrivia:
|
||||
return ClassificationType.comment;
|
||||
case SyntaxKind.WhitespaceTrivia:
|
||||
case SyntaxKind.NewLineTrivia:
|
||||
return ClassificationType.whiteSpace;
|
||||
case SyntaxKind.Identifier:
|
||||
default:
|
||||
if (isTemplateLiteralKind(token)) {
|
||||
return ClassificationType.stringLiteral;
|
||||
}
|
||||
return ClassificationType.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getClassificationsForLine,
|
||||
getEncodedLexicalClassifications
|
||||
};
|
||||
}
|
||||
|
||||
export function getSemanticClassifications(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, classifiableNames: Map<string>, span: TextSpan): ClassifiedSpan[] {
|
||||
return convertClassifications(getEncodedSemanticClassifications(typeChecker, cancellationToken, sourceFile, classifiableNames, span));
|
||||
}
|
||||
|
||||
function checkForClassificationCancellation(cancellationToken: CancellationToken, kind: SyntaxKind) {
|
||||
// We don't want to actually call back into our host on every node to find out if we've
|
||||
// been canceled. That would be an enormous amount of chattyness, along with the all
|
||||
// the overhead of marshalling the data to/from the host. So instead we pick a few
|
||||
// reasonable node kinds to bother checking on. These node kinds represent high level
|
||||
// constructs that we would expect to see commonly, but just at a far less frequent
|
||||
// interval.
|
||||
//
|
||||
// For example, in checker.ts (around 750k) we only have around 600 of these constructs.
|
||||
// That means we're calling back into the host around every 1.2k of the file we process.
|
||||
// Lib.d.ts has similar numbers.
|
||||
switch (kind) {
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
|
||||
export function getEncodedSemanticClassifications(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, classifiableNames: Map<string>, span: TextSpan): Classifications {
|
||||
const result: number[] = [];
|
||||
processNode(sourceFile);
|
||||
|
||||
return { spans: result, endOfLineState: EndOfLineState.None };
|
||||
|
||||
function pushClassification(start: number, length: number, type: ClassificationType) {
|
||||
result.push(start);
|
||||
result.push(length);
|
||||
result.push(type);
|
||||
}
|
||||
|
||||
function classifySymbol(symbol: Symbol, meaningAtPosition: Meaning.SemanticMeaning): ClassificationType {
|
||||
const flags = symbol.getFlags();
|
||||
if ((flags & SymbolFlags.Classifiable) === SymbolFlags.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags & SymbolFlags.Class) {
|
||||
return ClassificationType.className;
|
||||
}
|
||||
else if (flags & SymbolFlags.Enum) {
|
||||
return ClassificationType.enumName;
|
||||
}
|
||||
else if (flags & SymbolFlags.TypeAlias) {
|
||||
return ClassificationType.typeAliasName;
|
||||
}
|
||||
else if (meaningAtPosition & Meaning.SemanticMeaning.Type) {
|
||||
if (flags & SymbolFlags.Interface) {
|
||||
return ClassificationType.interfaceName;
|
||||
}
|
||||
else if (flags & SymbolFlags.TypeParameter) {
|
||||
return ClassificationType.typeParameterName;
|
||||
}
|
||||
}
|
||||
else if (flags & SymbolFlags.Module) {
|
||||
// Only classify a module as such if
|
||||
// - It appears in a namespace context.
|
||||
// - There exists a module declaration which actually impacts the value side.
|
||||
if (meaningAtPosition & Meaning.SemanticMeaning.Namespace ||
|
||||
(meaningAtPosition & Meaning.SemanticMeaning.Value && hasValueSideModule(symbol))) {
|
||||
return ClassificationType.moduleName;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
/**
|
||||
* Returns true if there exists a module that introduces entities on the value side.
|
||||
*/
|
||||
function hasValueSideModule(symbol: Symbol): boolean {
|
||||
return forEach(symbol.declarations, declaration => {
|
||||
return declaration.kind === SyntaxKind.ModuleDeclaration &&
|
||||
getModuleInstanceState(declaration) === ModuleInstanceState.Instantiated;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(node: Node) {
|
||||
// Only walk into nodes that intersect the requested span.
|
||||
if (node && textSpanIntersectsWith(span, node.getFullStart(), node.getFullWidth())) {
|
||||
const kind = node.kind;
|
||||
checkForClassificationCancellation(cancellationToken, kind);
|
||||
|
||||
if (kind === SyntaxKind.Identifier && !nodeIsMissing(node)) {
|
||||
const identifier = <Identifier>node;
|
||||
|
||||
// Only bother calling into the typechecker if this is an identifier that
|
||||
// could possibly resolve to a type name. This makes classification run
|
||||
// in a third of the time it would normally take.
|
||||
if (classifiableNames[identifier.text]) {
|
||||
const symbol = typeChecker.getSymbolAtLocation(node);
|
||||
if (symbol) {
|
||||
const type = classifySymbol(symbol, Meaning.getMeaningFromLocation(node));
|
||||
if (type) {
|
||||
pushClassification(node.getStart(), node.getWidth(), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forEachChild(node, processNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getClassificationTypeName(type: ClassificationType) {
|
||||
switch (type) {
|
||||
case ClassificationType.comment: return ClassificationTypeNames.comment;
|
||||
case ClassificationType.identifier: return ClassificationTypeNames.identifier;
|
||||
case ClassificationType.keyword: return ClassificationTypeNames.keyword;
|
||||
case ClassificationType.numericLiteral: return ClassificationTypeNames.numericLiteral;
|
||||
case ClassificationType.operator: return ClassificationTypeNames.operator;
|
||||
case ClassificationType.stringLiteral: return ClassificationTypeNames.stringLiteral;
|
||||
case ClassificationType.whiteSpace: return ClassificationTypeNames.whiteSpace;
|
||||
case ClassificationType.text: return ClassificationTypeNames.text;
|
||||
case ClassificationType.punctuation: return ClassificationTypeNames.punctuation;
|
||||
case ClassificationType.className: return ClassificationTypeNames.className;
|
||||
case ClassificationType.enumName: return ClassificationTypeNames.enumName;
|
||||
case ClassificationType.interfaceName: return ClassificationTypeNames.interfaceName;
|
||||
case ClassificationType.moduleName: return ClassificationTypeNames.moduleName;
|
||||
case ClassificationType.typeParameterName: return ClassificationTypeNames.typeParameterName;
|
||||
case ClassificationType.typeAliasName: return ClassificationTypeNames.typeAliasName;
|
||||
case ClassificationType.parameterName: return ClassificationTypeNames.parameterName;
|
||||
case ClassificationType.docCommentTagName: return ClassificationTypeNames.docCommentTagName;
|
||||
case ClassificationType.jsxOpenTagName: return ClassificationTypeNames.jsxOpenTagName;
|
||||
case ClassificationType.jsxCloseTagName: return ClassificationTypeNames.jsxCloseTagName;
|
||||
case ClassificationType.jsxSelfClosingTagName: return ClassificationTypeNames.jsxSelfClosingTagName;
|
||||
case ClassificationType.jsxAttribute: return ClassificationTypeNames.jsxAttribute;
|
||||
case ClassificationType.jsxText: return ClassificationTypeNames.jsxText;
|
||||
case ClassificationType.jsxAttributeStringLiteralValue: return ClassificationTypeNames.jsxAttributeStringLiteralValue;
|
||||
}
|
||||
}
|
||||
|
||||
function convertClassifications(classifications: Classifications): ClassifiedSpan[] {
|
||||
Debug.assert(classifications.spans.length % 3 === 0);
|
||||
const dense = classifications.spans;
|
||||
const result: ClassifiedSpan[] = [];
|
||||
for (let i = 0, n = dense.length; i < n; i += 3) {
|
||||
result.push({
|
||||
textSpan: createTextSpan(dense[i], dense[i + 1]),
|
||||
classificationType: getClassificationTypeName(dense[i + 2])
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getSyntacticClassifications(cancellationToken: CancellationToken, sourceFile: SourceFile, span: TextSpan): ClassifiedSpan[] {
|
||||
return convertClassifications(getEncodedSyntacticClassifications(cancellationToken, sourceFile, span));
|
||||
}
|
||||
|
||||
export function getEncodedSyntacticClassifications(cancellationToken: CancellationToken, sourceFile: SourceFile, span: TextSpan): Classifications {
|
||||
const spanStart = span.start;
|
||||
const spanLength = span.length;
|
||||
|
||||
// Make a scanner we can get trivia from.
|
||||
const triviaScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, sourceFile.languageVariant, sourceFile.text);
|
||||
const mergeConflictScanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ false, sourceFile.languageVariant, sourceFile.text);
|
||||
|
||||
const result: number[] = [];
|
||||
processElement(sourceFile);
|
||||
|
||||
return { spans: result, endOfLineState: EndOfLineState.None };
|
||||
|
||||
function pushClassification(start: number, length: number, type: ClassificationType) {
|
||||
result.push(start);
|
||||
result.push(length);
|
||||
result.push(type);
|
||||
}
|
||||
|
||||
function classifyLeadingTriviaAndGetTokenStart(token: Node): number {
|
||||
triviaScanner.setTextPos(token.pos);
|
||||
while (true) {
|
||||
const start = triviaScanner.getTextPos();
|
||||
// only bother scanning if we have something that could be trivia.
|
||||
if (!couldStartTrivia(sourceFile.text, start)) {
|
||||
return start;
|
||||
}
|
||||
|
||||
const kind = triviaScanner.scan();
|
||||
const end = triviaScanner.getTextPos();
|
||||
const width = end - start;
|
||||
|
||||
// The moment we get something that isn't trivia, then stop processing.
|
||||
if (!isTrivia(kind)) {
|
||||
return start;
|
||||
}
|
||||
|
||||
// Don't bother with newlines/whitespace.
|
||||
if (kind === SyntaxKind.NewLineTrivia || kind === SyntaxKind.WhitespaceTrivia) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only bother with the trivia if it at least intersects the span of interest.
|
||||
if (isComment(kind)) {
|
||||
classifyComment(token, kind, start, width);
|
||||
|
||||
// Classifying a comment might cause us to reuse the trivia scanner
|
||||
// (because of jsdoc comments). So after we classify the comment make
|
||||
// sure we set the scanner position back to where it needs to be.
|
||||
triviaScanner.setTextPos(end);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (kind === SyntaxKind.ConflictMarkerTrivia) {
|
||||
const text = sourceFile.text;
|
||||
const ch = text.charCodeAt(start);
|
||||
|
||||
// for the <<<<<<< and >>>>>>> markers, we just add them in as comments
|
||||
// in the classification stream.
|
||||
if (ch === CharacterCodes.lessThan || ch === CharacterCodes.greaterThan) {
|
||||
pushClassification(start, width, ClassificationType.comment);
|
||||
continue;
|
||||
}
|
||||
|
||||
// for the ======== add a comment for the first line, and then lex all
|
||||
// subsequent lines up until the end of the conflict marker.
|
||||
Debug.assert(ch === CharacterCodes.equals);
|
||||
classifyDisabledMergeCode(text, start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function classifyComment(token: Node, kind: SyntaxKind, start: number, width: number) {
|
||||
if (kind === SyntaxKind.MultiLineCommentTrivia) {
|
||||
// See if this is a doc comment. If so, we'll classify certain portions of it
|
||||
// specially.
|
||||
const docCommentAndDiagnostics = parseIsolatedJSDocComment(sourceFile.text, start, width);
|
||||
if (docCommentAndDiagnostics && docCommentAndDiagnostics.jsDocComment) {
|
||||
docCommentAndDiagnostics.jsDocComment.parent = token;
|
||||
classifyJSDocComment(docCommentAndDiagnostics.jsDocComment);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple comment. Just add as is.
|
||||
pushCommentRange(start, width);
|
||||
}
|
||||
|
||||
function pushCommentRange(start: number, width: number) {
|
||||
pushClassification(start, width, ClassificationType.comment);
|
||||
}
|
||||
|
||||
function classifyJSDocComment(docComment: JSDocComment) {
|
||||
let pos = docComment.pos;
|
||||
|
||||
for (const tag of docComment.tags) {
|
||||
// As we walk through each tag, classify the portion of text from the end of
|
||||
// the last tag (or the start of the entire doc comment) as 'comment'.
|
||||
if (tag.pos !== pos) {
|
||||
pushCommentRange(pos, tag.pos - pos);
|
||||
}
|
||||
|
||||
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation);
|
||||
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName);
|
||||
|
||||
pos = tag.tagName.end;
|
||||
|
||||
switch (tag.kind) {
|
||||
case SyntaxKind.JSDocParameterTag:
|
||||
processJSDocParameterTag(<JSDocParameterTag>tag);
|
||||
break;
|
||||
case SyntaxKind.JSDocTemplateTag:
|
||||
processJSDocTemplateTag(<JSDocTemplateTag>tag);
|
||||
break;
|
||||
case SyntaxKind.JSDocTypeTag:
|
||||
processElement((<JSDocTypeTag>tag).typeExpression);
|
||||
break;
|
||||
case SyntaxKind.JSDocReturnTag:
|
||||
processElement((<JSDocReturnTag>tag).typeExpression);
|
||||
break;
|
||||
}
|
||||
|
||||
pos = tag.end;
|
||||
}
|
||||
|
||||
if (pos !== docComment.end) {
|
||||
pushCommentRange(pos, docComment.end - pos);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
function processJSDocParameterTag(tag: JSDocParameterTag) {
|
||||
if (tag.preParameterName) {
|
||||
pushCommentRange(pos, tag.preParameterName.pos - pos);
|
||||
pushClassification(tag.preParameterName.pos, tag.preParameterName.end - tag.preParameterName.pos, ClassificationType.parameterName);
|
||||
pos = tag.preParameterName.end;
|
||||
}
|
||||
|
||||
if (tag.typeExpression) {
|
||||
pushCommentRange(pos, tag.typeExpression.pos - pos);
|
||||
processElement(tag.typeExpression);
|
||||
pos = tag.typeExpression.end;
|
||||
}
|
||||
|
||||
if (tag.postParameterName) {
|
||||
pushCommentRange(pos, tag.postParameterName.pos - pos);
|
||||
pushClassification(tag.postParameterName.pos, tag.postParameterName.end - tag.postParameterName.pos, ClassificationType.parameterName);
|
||||
pos = tag.postParameterName.end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processJSDocTemplateTag(tag: JSDocTemplateTag) {
|
||||
for (const child of tag.getChildren()) {
|
||||
processElement(child);
|
||||
}
|
||||
}
|
||||
|
||||
function classifyDisabledMergeCode(text: string, start: number, end: number) {
|
||||
// Classify the line that the ======= marker is on as a comment. Then just lex
|
||||
// all further tokens and add them to the result.
|
||||
let i: number;
|
||||
for (i = start; i < end; i++) {
|
||||
if (isLineBreak(text.charCodeAt(i))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pushClassification(start, i - start, ClassificationType.comment);
|
||||
mergeConflictScanner.setTextPos(i);
|
||||
|
||||
while (mergeConflictScanner.getTextPos() < end) {
|
||||
classifyDisabledCodeToken();
|
||||
}
|
||||
}
|
||||
|
||||
function classifyDisabledCodeToken() {
|
||||
const start = mergeConflictScanner.getTextPos();
|
||||
const tokenKind = mergeConflictScanner.scan();
|
||||
const end = mergeConflictScanner.getTextPos();
|
||||
|
||||
const type = classifyTokenType(tokenKind);
|
||||
if (type) {
|
||||
pushClassification(start, end - start, type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if node should be treated as classified and no further processing is required.
|
||||
* False will mean that node is not classified and traverse routine should recurse into node contents.
|
||||
*/
|
||||
function tryClassifyNode(node: Node): boolean {
|
||||
if (isJSDocTag(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nodeIsMissing(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const classifiedElementName = tryClassifyJsxElementName(node);
|
||||
if (!isToken(node) && node.kind !== SyntaxKind.JsxText && classifiedElementName === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tokenStart = node.kind === SyntaxKind.JsxText ? node.pos : classifyLeadingTriviaAndGetTokenStart(node);
|
||||
|
||||
const tokenWidth = node.end - tokenStart;
|
||||
Debug.assert(tokenWidth >= 0);
|
||||
if (tokenWidth > 0) {
|
||||
const type = classifiedElementName || classifyTokenType(node.kind, node);
|
||||
if (type) {
|
||||
pushClassification(tokenStart, tokenWidth, type);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function tryClassifyJsxElementName(token: Node): ClassificationType {
|
||||
switch (token.parent && token.parent.kind) {
|
||||
case SyntaxKind.JsxOpeningElement:
|
||||
if ((<JsxOpeningElement>token.parent).tagName === token) {
|
||||
return ClassificationType.jsxOpenTagName;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.JsxClosingElement:
|
||||
if ((<JsxClosingElement>token.parent).tagName === token) {
|
||||
return ClassificationType.jsxCloseTagName;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.JsxSelfClosingElement:
|
||||
if ((<JsxSelfClosingElement>token.parent).tagName === token) {
|
||||
return ClassificationType.jsxSelfClosingTagName;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.JsxAttribute:
|
||||
if ((<JsxAttribute>token.parent).name === token) {
|
||||
return ClassificationType.jsxAttribute;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// for accurate classification, the actual token should be passed in. however, for
|
||||
// cases like 'disabled merge code' classification, we just get the token kind and
|
||||
// classify based on that instead.
|
||||
function classifyTokenType(tokenKind: SyntaxKind, token?: Node): ClassificationType {
|
||||
if (isKeyword(tokenKind)) {
|
||||
return ClassificationType.keyword;
|
||||
}
|
||||
|
||||
// Special case < and > If they appear in a generic context they are punctuation,
|
||||
// not operators.
|
||||
if (tokenKind === SyntaxKind.LessThanToken || tokenKind === SyntaxKind.GreaterThanToken) {
|
||||
// If the node owning the token has a type argument list or type parameter list, then
|
||||
// we can effectively assume that a '<' and '>' belong to those lists.
|
||||
if (token && getTypeArgumentOrTypeParameterList(token.parent)) {
|
||||
return ClassificationType.punctuation;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPunctuation(tokenKind)) {
|
||||
if (token) {
|
||||
if (tokenKind === SyntaxKind.EqualsToken) {
|
||||
// the '=' in a variable declaration is special cased here.
|
||||
if (token.parent.kind === SyntaxKind.VariableDeclaration ||
|
||||
token.parent.kind === SyntaxKind.PropertyDeclaration ||
|
||||
token.parent.kind === SyntaxKind.Parameter ||
|
||||
token.parent.kind === SyntaxKind.JsxAttribute) {
|
||||
return ClassificationType.operator;
|
||||
}
|
||||
}
|
||||
|
||||
if (token.parent.kind === SyntaxKind.BinaryExpression ||
|
||||
token.parent.kind === SyntaxKind.PrefixUnaryExpression ||
|
||||
token.parent.kind === SyntaxKind.PostfixUnaryExpression ||
|
||||
token.parent.kind === SyntaxKind.ConditionalExpression) {
|
||||
return ClassificationType.operator;
|
||||
}
|
||||
}
|
||||
|
||||
return ClassificationType.punctuation;
|
||||
}
|
||||
else if (tokenKind === SyntaxKind.NumericLiteral) {
|
||||
return ClassificationType.numericLiteral;
|
||||
}
|
||||
else if (tokenKind === SyntaxKind.StringLiteral) {
|
||||
return token.parent.kind === SyntaxKind.JsxAttribute ? ClassificationType.jsxAttributeStringLiteralValue : ClassificationType.stringLiteral;
|
||||
}
|
||||
else if (tokenKind === SyntaxKind.RegularExpressionLiteral) {
|
||||
// TODO: we should get another classification type for these literals.
|
||||
return ClassificationType.stringLiteral;
|
||||
}
|
||||
else if (isTemplateLiteralKind(tokenKind)) {
|
||||
// TODO (drosen): we should *also* get another classification type for these literals.
|
||||
return ClassificationType.stringLiteral;
|
||||
}
|
||||
else if (tokenKind === SyntaxKind.JsxText) {
|
||||
return ClassificationType.jsxText;
|
||||
}
|
||||
else if (tokenKind === SyntaxKind.Identifier) {
|
||||
if (token) {
|
||||
switch (token.parent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
if ((<ClassDeclaration>token.parent).name === token) {
|
||||
return ClassificationType.className;
|
||||
}
|
||||
return;
|
||||
case SyntaxKind.TypeParameter:
|
||||
if ((<TypeParameterDeclaration>token.parent).name === token) {
|
||||
return ClassificationType.typeParameterName;
|
||||
}
|
||||
return;
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
if ((<InterfaceDeclaration>token.parent).name === token) {
|
||||
return ClassificationType.interfaceName;
|
||||
}
|
||||
return;
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
if ((<EnumDeclaration>token.parent).name === token) {
|
||||
return ClassificationType.enumName;
|
||||
}
|
||||
return;
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
if ((<ModuleDeclaration>token.parent).name === token) {
|
||||
return ClassificationType.moduleName;
|
||||
}
|
||||
return;
|
||||
case SyntaxKind.Parameter:
|
||||
if ((<ParameterDeclaration>token.parent).name === token) {
|
||||
const isThis = token.kind === SyntaxKind.Identifier && (<Identifier>token).originalKeywordKind === SyntaxKind.ThisKeyword;
|
||||
return isThis ? ClassificationType.keyword : ClassificationType.parameterName;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
return ClassificationType.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
function processElement(element: Node) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore nodes that don't intersect the original span to classify.
|
||||
if (decodedTextSpanIntersectsWith(spanStart, spanLength, element.pos, element.getFullWidth())) {
|
||||
checkForClassificationCancellation(cancellationToken, element.kind);
|
||||
|
||||
const children = element.getChildren(sourceFile);
|
||||
for (let i = 0, n = children.length; i < n; i++) {
|
||||
const child = children[i];
|
||||
if (!tryClassifyNode(child)) {
|
||||
// Recurse into our child nodes.
|
||||
processElement(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1647
src/services/completions.ts
Normal file
1647
src/services/completions.ts
Normal file
File diff suppressed because it is too large
Load Diff
638
src/services/documentHighlights.ts
Normal file
638
src/services/documentHighlights.ts
Normal file
@ -0,0 +1,638 @@
|
||||
/* @internal */
|
||||
namespace ts.DocumentHighlights {
|
||||
export function getDocumentHighlights(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] {
|
||||
const node = getTouchingWord(sourceFile, position);
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getSemanticDocumentHighlights(node) || getSyntacticDocumentHighlights(node);
|
||||
|
||||
function getHighlightSpanForNode(node: Node): HighlightSpan {
|
||||
const start = node.getStart();
|
||||
const end = node.getEnd();
|
||||
|
||||
return {
|
||||
fileName: sourceFile.fileName,
|
||||
textSpan: createTextSpanFromBounds(start, end),
|
||||
kind: HighlightSpanKind.none
|
||||
};
|
||||
}
|
||||
|
||||
function getSemanticDocumentHighlights(node: Node): DocumentHighlights[] {
|
||||
if (node.kind === SyntaxKind.Identifier ||
|
||||
node.kind === SyntaxKind.ThisKeyword ||
|
||||
node.kind === SyntaxKind.ThisType ||
|
||||
node.kind === SyntaxKind.SuperKeyword ||
|
||||
node.kind === SyntaxKind.StringLiteral ||
|
||||
isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
|
||||
|
||||
const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false);
|
||||
return convertReferencedSymbols(referencedSymbols);
|
||||
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
function convertReferencedSymbols(referencedSymbols: ReferencedSymbol[]): DocumentHighlights[] {
|
||||
if (!referencedSymbols) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fileNameToDocumentHighlights = createMap<DocumentHighlights>();
|
||||
const result: DocumentHighlights[] = [];
|
||||
for (const referencedSymbol of referencedSymbols) {
|
||||
for (const referenceEntry of referencedSymbol.references) {
|
||||
const fileName = referenceEntry.fileName;
|
||||
let documentHighlights = fileNameToDocumentHighlights[fileName];
|
||||
if (!documentHighlights) {
|
||||
documentHighlights = { fileName, highlightSpans: [] };
|
||||
|
||||
fileNameToDocumentHighlights[fileName] = documentHighlights;
|
||||
result.push(documentHighlights);
|
||||
}
|
||||
|
||||
documentHighlights.highlightSpans.push({
|
||||
textSpan: referenceEntry.textSpan,
|
||||
kind: referenceEntry.isWriteAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function getSyntacticDocumentHighlights(node: Node): DocumentHighlights[] {
|
||||
const fileName = sourceFile.fileName;
|
||||
|
||||
const highlightSpans = getHighlightSpans(node);
|
||||
if (!highlightSpans || highlightSpans.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [{ fileName, highlightSpans }];
|
||||
|
||||
// returns true if 'node' is defined and has a matching 'kind'.
|
||||
function hasKind(node: Node, kind: SyntaxKind) {
|
||||
return node !== undefined && node.kind === kind;
|
||||
}
|
||||
|
||||
// Null-propagating 'parent' function.
|
||||
function parent(node: Node): Node {
|
||||
return node && node.parent;
|
||||
}
|
||||
|
||||
function getHighlightSpans(node: Node): HighlightSpan[] {
|
||||
if (node) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.IfKeyword:
|
||||
case SyntaxKind.ElseKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.IfStatement)) {
|
||||
return getIfElseOccurrences(<IfStatement>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ReturnKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.ReturnStatement)) {
|
||||
return getReturnOccurrences(<ReturnStatement>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ThrowKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.ThrowStatement)) {
|
||||
return getThrowOccurrences(<ThrowStatement>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.CatchKeyword:
|
||||
if (hasKind(parent(parent(node)), SyntaxKind.TryStatement)) {
|
||||
return getTryCatchFinallyOccurrences(<TryStatement>node.parent.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.TryKeyword:
|
||||
case SyntaxKind.FinallyKeyword:
|
||||
if (hasKind(parent(node), SyntaxKind.TryStatement)) {
|
||||
return getTryCatchFinallyOccurrences(<TryStatement>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.SwitchKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.SwitchStatement)) {
|
||||
return getSwitchCaseDefaultOccurrences(<SwitchStatement>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.CaseKeyword:
|
||||
case SyntaxKind.DefaultKeyword:
|
||||
if (hasKind(parent(parent(parent(node))), SyntaxKind.SwitchStatement)) {
|
||||
return getSwitchCaseDefaultOccurrences(<SwitchStatement>node.parent.parent.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.BreakKeyword:
|
||||
case SyntaxKind.ContinueKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.BreakStatement) || hasKind(node.parent, SyntaxKind.ContinueStatement)) {
|
||||
return getBreakOrContinueStatementOccurrences(<BreakOrContinueStatement>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ForKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.ForStatement) ||
|
||||
hasKind(node.parent, SyntaxKind.ForInStatement) ||
|
||||
hasKind(node.parent, SyntaxKind.ForOfStatement)) {
|
||||
return getLoopBreakContinueOccurrences(<IterationStatement>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.WhileKeyword:
|
||||
case SyntaxKind.DoKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.WhileStatement) || hasKind(node.parent, SyntaxKind.DoStatement)) {
|
||||
return getLoopBreakContinueOccurrences(<IterationStatement>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ConstructorKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.Constructor)) {
|
||||
return getConstructorOccurrences(<ConstructorDeclaration>node.parent);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.GetKeyword:
|
||||
case SyntaxKind.SetKeyword:
|
||||
if (hasKind(node.parent, SyntaxKind.GetAccessor) || hasKind(node.parent, SyntaxKind.SetAccessor)) {
|
||||
return getGetAndSetOccurrences(<AccessorDeclaration>node.parent);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (isModifierKind(node.kind) && node.parent &&
|
||||
(isDeclaration(node.parent) || node.parent.kind === SyntaxKind.VariableStatement)) {
|
||||
return getModifierOccurrences(node.kind, node.parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates all throw-statements within this node *without* crossing
|
||||
* into function boundaries and try-blocks with catch-clauses.
|
||||
*/
|
||||
function aggregateOwnedThrowStatements(node: Node): ThrowStatement[] {
|
||||
const statementAccumulator: ThrowStatement[] = [];
|
||||
aggregate(node);
|
||||
return statementAccumulator;
|
||||
|
||||
function aggregate(node: Node): void {
|
||||
if (node.kind === SyntaxKind.ThrowStatement) {
|
||||
statementAccumulator.push(<ThrowStatement>node);
|
||||
}
|
||||
else if (node.kind === SyntaxKind.TryStatement) {
|
||||
const tryStatement = <TryStatement>node;
|
||||
|
||||
if (tryStatement.catchClause) {
|
||||
aggregate(tryStatement.catchClause);
|
||||
}
|
||||
else {
|
||||
// Exceptions thrown within a try block lacking a catch clause
|
||||
// are "owned" in the current context.
|
||||
aggregate(tryStatement.tryBlock);
|
||||
}
|
||||
|
||||
if (tryStatement.finallyBlock) {
|
||||
aggregate(tryStatement.finallyBlock);
|
||||
}
|
||||
}
|
||||
// Do not cross function boundaries.
|
||||
else if (!isFunctionLike(node)) {
|
||||
forEachChild(node, aggregate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For lack of a better name, this function takes a throw statement and returns the
|
||||
* nearest ancestor that is a try-block (whose try statement has a catch clause),
|
||||
* function-block, or source file.
|
||||
*/
|
||||
function getThrowStatementOwner(throwStatement: ThrowStatement): Node {
|
||||
let child: Node = throwStatement;
|
||||
|
||||
while (child.parent) {
|
||||
const parent = child.parent;
|
||||
|
||||
if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
// A throw-statement is only owned by a try-statement if the try-statement has
|
||||
// a catch clause, and if the throw-statement occurs within the try block.
|
||||
if (parent.kind === SyntaxKind.TryStatement) {
|
||||
const tryStatement = <TryStatement>parent;
|
||||
|
||||
if (tryStatement.tryBlock === child && tryStatement.catchClause) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
child = parent;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function aggregateAllBreakAndContinueStatements(node: Node): BreakOrContinueStatement[] {
|
||||
const statementAccumulator: BreakOrContinueStatement[] = [];
|
||||
aggregate(node);
|
||||
return statementAccumulator;
|
||||
|
||||
function aggregate(node: Node): void {
|
||||
if (node.kind === SyntaxKind.BreakStatement || node.kind === SyntaxKind.ContinueStatement) {
|
||||
statementAccumulator.push(<BreakOrContinueStatement>node);
|
||||
}
|
||||
// Do not cross function boundaries.
|
||||
else if (!isFunctionLike(node)) {
|
||||
forEachChild(node, aggregate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean {
|
||||
const actualOwner = getBreakOrContinueOwner(statement);
|
||||
|
||||
return actualOwner && actualOwner === owner;
|
||||
}
|
||||
|
||||
function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node {
|
||||
for (let node = statement.parent; node; node = node.parent) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.SwitchStatement:
|
||||
if (statement.kind === SyntaxKind.ContinueStatement) {
|
||||
continue;
|
||||
}
|
||||
// Fall through.
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
if (!statement.label || isLabeledBy(node, statement.label.text)) {
|
||||
return node;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Don't cross function boundaries.
|
||||
if (isFunctionLike(node)) {
|
||||
return undefined;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getModifierOccurrences(modifier: SyntaxKind, declaration: Node): HighlightSpan[] {
|
||||
const container = declaration.parent;
|
||||
|
||||
// Make sure we only highlight the keyword when it makes sense to do so.
|
||||
if (isAccessibilityModifier(modifier)) {
|
||||
if (!(container.kind === SyntaxKind.ClassDeclaration ||
|
||||
container.kind === SyntaxKind.ClassExpression ||
|
||||
(declaration.kind === SyntaxKind.Parameter && hasKind(container, SyntaxKind.Constructor)))) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
else if (modifier === SyntaxKind.StaticKeyword) {
|
||||
if (!(container.kind === SyntaxKind.ClassDeclaration || container.kind === SyntaxKind.ClassExpression)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
else if (modifier === SyntaxKind.ExportKeyword || modifier === SyntaxKind.DeclareKeyword) {
|
||||
if (!(container.kind === SyntaxKind.ModuleBlock || container.kind === SyntaxKind.SourceFile)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
else if (modifier === SyntaxKind.AbstractKeyword) {
|
||||
if (!(container.kind === SyntaxKind.ClassDeclaration || declaration.kind === SyntaxKind.ClassDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// unsupported modifier
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
const modifierFlag: ModifierFlags = getFlagFromModifier(modifier);
|
||||
|
||||
let nodes: Node[];
|
||||
switch (container.kind) {
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.SourceFile:
|
||||
// Container is either a class declaration or the declaration is a classDeclaration
|
||||
if (modifierFlag & ModifierFlags.Abstract) {
|
||||
nodes = (<Node[]>(<ClassDeclaration>declaration).members).concat(declaration);
|
||||
}
|
||||
else {
|
||||
nodes = (<Block>container).statements;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.Constructor:
|
||||
nodes = (<Node[]>(<ConstructorDeclaration>container).parameters).concat(
|
||||
(<ClassDeclaration>container.parent).members);
|
||||
break;
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
nodes = (<ClassLikeDeclaration>container).members;
|
||||
|
||||
// If we're an accessibility modifier, we're in an instance member and should search
|
||||
// the constructor's parameter list for instance members as well.
|
||||
if (modifierFlag & ModifierFlags.AccessibilityModifier) {
|
||||
const constructor = forEach((<ClassLikeDeclaration>container).members, member => {
|
||||
return member.kind === SyntaxKind.Constructor && <ConstructorDeclaration>member;
|
||||
});
|
||||
|
||||
if (constructor) {
|
||||
nodes = nodes.concat(constructor.parameters);
|
||||
}
|
||||
}
|
||||
else if (modifierFlag & ModifierFlags.Abstract) {
|
||||
nodes = nodes.concat(container);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Debug.fail("Invalid container kind.");
|
||||
}
|
||||
|
||||
forEach(nodes, node => {
|
||||
if (getModifierFlags(node) & modifierFlag) {
|
||||
forEach(node.modifiers, child => pushKeywordIf(keywords, child, modifier));
|
||||
}
|
||||
});
|
||||
|
||||
return map(keywords, getHighlightSpanForNode);
|
||||
|
||||
function getFlagFromModifier(modifier: SyntaxKind) {
|
||||
switch (modifier) {
|
||||
case SyntaxKind.PublicKeyword:
|
||||
return ModifierFlags.Public;
|
||||
case SyntaxKind.PrivateKeyword:
|
||||
return ModifierFlags.Private;
|
||||
case SyntaxKind.ProtectedKeyword:
|
||||
return ModifierFlags.Protected;
|
||||
case SyntaxKind.StaticKeyword:
|
||||
return ModifierFlags.Static;
|
||||
case SyntaxKind.ExportKeyword:
|
||||
return ModifierFlags.Export;
|
||||
case SyntaxKind.DeclareKeyword:
|
||||
return ModifierFlags.Ambient;
|
||||
case SyntaxKind.AbstractKeyword:
|
||||
return ModifierFlags.Abstract;
|
||||
default:
|
||||
Debug.fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pushKeywordIf(keywordList: Node[], token: Node, ...expected: SyntaxKind[]): boolean {
|
||||
if (token && contains(expected, token.kind)) {
|
||||
keywordList.push(token);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getGetAndSetOccurrences(accessorDeclaration: AccessorDeclaration): HighlightSpan[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
tryPushAccessorKeyword(accessorDeclaration.symbol, SyntaxKind.GetAccessor);
|
||||
tryPushAccessorKeyword(accessorDeclaration.symbol, SyntaxKind.SetAccessor);
|
||||
|
||||
return map(keywords, getHighlightSpanForNode);
|
||||
|
||||
function tryPushAccessorKeyword(accessorSymbol: Symbol, accessorKind: SyntaxKind): void {
|
||||
const accessor = getDeclarationOfKind(accessorSymbol, accessorKind);
|
||||
|
||||
if (accessor) {
|
||||
forEach(accessor.getChildren(), child => pushKeywordIf(keywords, child, SyntaxKind.GetKeyword, SyntaxKind.SetKeyword));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getConstructorOccurrences(constructorDeclaration: ConstructorDeclaration): HighlightSpan[] {
|
||||
const declarations = constructorDeclaration.symbol.getDeclarations();
|
||||
|
||||
const keywords: Node[] = [];
|
||||
|
||||
forEach(declarations, declaration => {
|
||||
forEach(declaration.getChildren(), token => {
|
||||
return pushKeywordIf(keywords, token, SyntaxKind.ConstructorKeyword);
|
||||
});
|
||||
});
|
||||
|
||||
return map(keywords, getHighlightSpanForNode);
|
||||
}
|
||||
|
||||
function getLoopBreakContinueOccurrences(loopNode: IterationStatement): HighlightSpan[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) {
|
||||
// If we succeeded and got a do-while loop, then start looking for a 'while' keyword.
|
||||
if (loopNode.kind === SyntaxKind.DoStatement) {
|
||||
const loopTokens = loopNode.getChildren();
|
||||
|
||||
for (let i = loopTokens.length - 1; i >= 0; i--) {
|
||||
if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const breaksAndContinues = aggregateAllBreakAndContinueStatements(loopNode.statement);
|
||||
|
||||
forEach(breaksAndContinues, statement => {
|
||||
if (ownsBreakOrContinueStatement(loopNode, statement)) {
|
||||
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword);
|
||||
}
|
||||
});
|
||||
|
||||
return map(keywords, getHighlightSpanForNode);
|
||||
}
|
||||
|
||||
function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): HighlightSpan[] {
|
||||
const owner = getBreakOrContinueOwner(breakOrContinueStatement);
|
||||
|
||||
if (owner) {
|
||||
switch (owner.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
return getLoopBreakContinueOccurrences(<IterationStatement>owner);
|
||||
case SyntaxKind.SwitchStatement:
|
||||
return getSwitchCaseDefaultOccurrences(<SwitchStatement>owner);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): HighlightSpan[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword);
|
||||
|
||||
// Go through each clause in the switch statement, collecting the 'case'/'default' keywords.
|
||||
forEach(switchStatement.caseBlock.clauses, clause => {
|
||||
pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword);
|
||||
|
||||
const breaksAndContinues = aggregateAllBreakAndContinueStatements(clause);
|
||||
|
||||
forEach(breaksAndContinues, statement => {
|
||||
if (ownsBreakOrContinueStatement(switchStatement, statement)) {
|
||||
pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return map(keywords, getHighlightSpanForNode);
|
||||
}
|
||||
|
||||
function getTryCatchFinallyOccurrences(tryStatement: TryStatement): HighlightSpan[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword);
|
||||
|
||||
if (tryStatement.catchClause) {
|
||||
pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword);
|
||||
}
|
||||
|
||||
if (tryStatement.finallyBlock) {
|
||||
const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile);
|
||||
pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword);
|
||||
}
|
||||
|
||||
return map(keywords, getHighlightSpanForNode);
|
||||
}
|
||||
|
||||
function getThrowOccurrences(throwStatement: ThrowStatement): HighlightSpan[] {
|
||||
const owner = getThrowStatementOwner(throwStatement);
|
||||
|
||||
if (!owner) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
|
||||
forEach(aggregateOwnedThrowStatements(owner), throwStatement => {
|
||||
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
|
||||
});
|
||||
|
||||
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
|
||||
// ability to "jump out" of the function, and include occurrences for both.
|
||||
if (isFunctionBlock(owner)) {
|
||||
forEachReturnStatement(<Block>owner, returnStatement => {
|
||||
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
|
||||
});
|
||||
}
|
||||
|
||||
return map(keywords, getHighlightSpanForNode);
|
||||
}
|
||||
|
||||
function getReturnOccurrences(returnStatement: ReturnStatement): HighlightSpan[] {
|
||||
const func = <FunctionLikeDeclaration>getContainingFunction(returnStatement);
|
||||
|
||||
// If we didn't find a containing function with a block body, bail out.
|
||||
if (!(func && hasKind(func.body, SyntaxKind.Block))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keywords: Node[] = [];
|
||||
forEachReturnStatement(<Block>func.body, returnStatement => {
|
||||
pushKeywordIf(keywords, returnStatement.getFirstToken(), SyntaxKind.ReturnKeyword);
|
||||
});
|
||||
|
||||
// Include 'throw' statements that do not occur within a try block.
|
||||
forEach(aggregateOwnedThrowStatements(func.body), throwStatement => {
|
||||
pushKeywordIf(keywords, throwStatement.getFirstToken(), SyntaxKind.ThrowKeyword);
|
||||
});
|
||||
|
||||
return map(keywords, getHighlightSpanForNode);
|
||||
}
|
||||
|
||||
function getIfElseOccurrences(ifStatement: IfStatement): HighlightSpan[] {
|
||||
const keywords: Node[] = [];
|
||||
|
||||
// Traverse upwards through all parent if-statements linked by their else-branches.
|
||||
while (hasKind(ifStatement.parent, SyntaxKind.IfStatement) && (<IfStatement>ifStatement.parent).elseStatement === ifStatement) {
|
||||
ifStatement = <IfStatement>ifStatement.parent;
|
||||
}
|
||||
|
||||
// Now traverse back down through the else branches, aggregating if/else keywords of if-statements.
|
||||
while (ifStatement) {
|
||||
const children = ifStatement.getChildren();
|
||||
pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword);
|
||||
|
||||
// Generally the 'else' keyword is second-to-last, so we traverse backwards.
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasKind(ifStatement.elseStatement, SyntaxKind.IfStatement)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ifStatement = <IfStatement>ifStatement.elseStatement;
|
||||
}
|
||||
|
||||
const result: HighlightSpan[] = [];
|
||||
|
||||
// We'd like to highlight else/ifs together if they are only separated by whitespace
|
||||
// (i.e. the keywords are separated by no comments, no newlines).
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) {
|
||||
const elseKeyword = keywords[i];
|
||||
const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword.
|
||||
|
||||
let shouldCombindElseAndIf = true;
|
||||
|
||||
// Avoid recalculating getStart() by iterating backwards.
|
||||
for (let j = ifKeyword.getStart() - 1; j >= elseKeyword.end; j--) {
|
||||
if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) {
|
||||
shouldCombindElseAndIf = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCombindElseAndIf) {
|
||||
result.push({
|
||||
fileName: fileName,
|
||||
textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end),
|
||||
kind: HighlightSpanKind.reference
|
||||
});
|
||||
i++; // skip the next keyword
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Ordinary case: just highlight the keyword.
|
||||
result.push(getHighlightSpanForNode(keywords[i]));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not a 'node' is preceded by a label of the given string.
|
||||
* Note: 'node' cannot be a SourceFile.
|
||||
*/
|
||||
function isLabeledBy(node: Node, labelName: string) {
|
||||
for (let owner = node.parent; owner.kind === SyntaxKind.LabeledStatement; owner = owner.parent) {
|
||||
if ((<LabeledStatement>owner).label.text === labelName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
1117
src/services/findAllReferences.ts
Normal file
1117
src/services/findAllReferences.ts
Normal file
File diff suppressed because it is too large
Load Diff
256
src/services/goToDefinition.ts
Normal file
256
src/services/goToDefinition.ts
Normal file
@ -0,0 +1,256 @@
|
||||
/* @internal */
|
||||
namespace ts.GoToDefinition {
|
||||
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] {
|
||||
/// Triple slash reference comments
|
||||
const comment = findReferenceInPosition(sourceFile.referencedFiles, position);
|
||||
if (comment) {
|
||||
const referenceFile = tryResolveScriptReference(program, sourceFile, comment);
|
||||
if (referenceFile) {
|
||||
return [getDefinitionInfoForFileReference(comment.fileName, referenceFile.fileName)];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Type reference directives
|
||||
const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
|
||||
if (typeReferenceDirective) {
|
||||
const referenceFile = program.getResolvedTypeReferenceDirectives()[typeReferenceDirective.fileName];
|
||||
if (referenceFile && referenceFile.resolvedFileName) {
|
||||
return [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const node = getTouchingPropertyName(sourceFile, position);
|
||||
if (node === sourceFile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Labels
|
||||
if (isJumpStatementTarget(node)) {
|
||||
const labelName = (<Identifier>node).text;
|
||||
const label = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text);
|
||||
return label ? [createDefinitionInfo(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined;
|
||||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
|
||||
const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
|
||||
if (calledDeclaration) {
|
||||
return [createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration)];
|
||||
}
|
||||
|
||||
let symbol = typeChecker.getSymbolAtLocation(node);
|
||||
|
||||
// Could not find a symbol e.g. node is string or number keyword,
|
||||
// or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol
|
||||
if (!symbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If this is an alias, and the request came at the declaration location
|
||||
// get the aliased symbol instead. This allows for goto def on an import e.g.
|
||||
// import {A, B} from "mod";
|
||||
// to jump to the implementation directly.
|
||||
if (symbol.flags & SymbolFlags.Alias) {
|
||||
const declaration = symbol.declarations[0];
|
||||
|
||||
// Go to the original declaration for cases:
|
||||
//
|
||||
// (1) when the aliased symbol was declared in the location(parent).
|
||||
// (2) when the aliased symbol is originating from a named import.
|
||||
//
|
||||
if (node.kind === SyntaxKind.Identifier &&
|
||||
(node.parent === declaration ||
|
||||
(declaration.kind === SyntaxKind.ImportSpecifier && declaration.parent && declaration.parent.kind === SyntaxKind.NamedImports))) {
|
||||
|
||||
symbol = typeChecker.getAliasedSymbol(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Because name in short-hand property assignment has two different meanings: property name and property value,
|
||||
// using go-to-definition at such position should go to the variable declaration of the property value rather than
|
||||
// go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition
|
||||
// is performed at the location of property access, we would like to go to definition of the property in the short-hand
|
||||
// assignment. This case and others are handled by the following code.
|
||||
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
||||
const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration);
|
||||
if (!shorthandSymbol) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const shorthandDeclarations = shorthandSymbol.getDeclarations();
|
||||
const shorthandSymbolKind = SymbolDisplay.getSymbolKind(typeChecker, shorthandSymbol, node);
|
||||
const shorthandSymbolName = typeChecker.symbolToString(shorthandSymbol);
|
||||
const shorthandContainerName = typeChecker.symbolToString(symbol.parent, node);
|
||||
return map(shorthandDeclarations,
|
||||
declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
|
||||
}
|
||||
|
||||
return getDefinitionFromSymbol(typeChecker, symbol, node);
|
||||
}
|
||||
|
||||
/// Goto type
|
||||
export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): DefinitionInfo[] {
|
||||
const node = getTouchingPropertyName(sourceFile, position);
|
||||
if (node === sourceFile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const symbol = typeChecker.getSymbolAtLocation(node);
|
||||
if (!symbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
|
||||
if (!type) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum)) {
|
||||
const result: DefinitionInfo[] = [];
|
||||
forEach((<UnionType>type).types, t => {
|
||||
if (t.symbol) {
|
||||
addRange(/*to*/ result, /*from*/ getDefinitionFromSymbol(typeChecker, t.symbol, node));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!type.symbol) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getDefinitionFromSymbol(typeChecker, type.symbol, node);
|
||||
}
|
||||
|
||||
function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo[] {
|
||||
const result: DefinitionInfo[] = [];
|
||||
const declarations = symbol.getDeclarations();
|
||||
const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, symbol, node);
|
||||
|
||||
if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
|
||||
!tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
|
||||
// Just add all the declarations.
|
||||
forEach(declarations, declaration => {
|
||||
result.push(createDefinitionInfo(declaration, symbolKind, symbolName, containerName));
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
function tryAddConstructSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
|
||||
// Applicable only if we are in a new expression, or we are on a constructor declaration
|
||||
// and in either case the symbol has a construct signature definition, i.e. class
|
||||
if (isNewExpressionTarget(location) || location.kind === SyntaxKind.ConstructorKeyword) {
|
||||
if (symbol.flags & SymbolFlags.Class) {
|
||||
// Find the first class-like declaration and try to get the construct signature.
|
||||
for (const declaration of symbol.getDeclarations()) {
|
||||
if (isClassLike(declaration)) {
|
||||
return tryAddSignature(declaration.members,
|
||||
/*selectConstructors*/ true,
|
||||
symbolKind,
|
||||
symbolName,
|
||||
containerName,
|
||||
result);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.fail("Expected declaration to have at least one class-like declaration");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryAddCallSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
|
||||
if (isCallExpressionTarget(location) || isNewExpressionTarget(location) || isNameOfFunctionDeclaration(location)) {
|
||||
return tryAddSignature(symbol.declarations, /*selectConstructors*/ false, symbolKind, symbolName, containerName, result);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
|
||||
const declarations: Declaration[] = [];
|
||||
let definition: Declaration;
|
||||
|
||||
forEach(signatureDeclarations, d => {
|
||||
if ((selectConstructors && d.kind === SyntaxKind.Constructor) ||
|
||||
(!selectConstructors && (d.kind === SyntaxKind.FunctionDeclaration || d.kind === SyntaxKind.MethodDeclaration || d.kind === SyntaxKind.MethodSignature))) {
|
||||
declarations.push(d);
|
||||
if ((<FunctionLikeDeclaration>d).body) definition = d;
|
||||
}
|
||||
});
|
||||
|
||||
if (definition) {
|
||||
result.push(createDefinitionInfo(definition, symbolKind, symbolName, containerName));
|
||||
return true;
|
||||
}
|
||||
else if (declarations.length) {
|
||||
result.push(createDefinitionInfo(lastOrUndefined(declarations), symbolKind, symbolName, containerName));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function createDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo {
|
||||
return {
|
||||
fileName: node.getSourceFile().fileName,
|
||||
textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd()),
|
||||
kind: symbolKind,
|
||||
name: symbolName,
|
||||
containerKind: undefined,
|
||||
containerName
|
||||
};
|
||||
}
|
||||
|
||||
function getSymbolInfo(typeChecker: TypeChecker, symbol: Symbol, node: Node) {
|
||||
return {
|
||||
symbolName: typeChecker.symbolToString(symbol), // Do not get scoped name, just the name of the symbol
|
||||
symbolKind: SymbolDisplay.getSymbolKind(typeChecker, symbol, node),
|
||||
containerName: symbol.parent ? typeChecker.symbolToString(symbol.parent, node) : ""
|
||||
};
|
||||
}
|
||||
|
||||
function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo {
|
||||
const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, decl.symbol, decl);
|
||||
return createDefinitionInfo(decl, symbolKind, symbolName, containerName);
|
||||
}
|
||||
|
||||
function findReferenceInPosition(refs: FileReference[], pos: number): FileReference {
|
||||
for (const ref of refs) {
|
||||
if (ref.pos <= pos && pos < ref.end) {
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getDefinitionInfoForFileReference(name: string, targetFileName: string): DefinitionInfo {
|
||||
return {
|
||||
fileName: targetFileName,
|
||||
textSpan: createTextSpanFromBounds(0, 0),
|
||||
kind: ScriptElementKind.scriptElement,
|
||||
name: name,
|
||||
containerName: undefined,
|
||||
containerKind: undefined
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns a CallLikeExpression where `node` is the target being invoked. */
|
||||
function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined {
|
||||
const target = climbPastManyPropertyAccesses(node);
|
||||
const callLike = target.parent;
|
||||
return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target && callLike;
|
||||
}
|
||||
|
||||
function climbPastManyPropertyAccesses(node: Node): Node {
|
||||
return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
|
||||
}
|
||||
|
||||
function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
|
||||
const callLike = getAncestorCallLikeExpression(node);
|
||||
return callLike && typeChecker.getResolvedSignature(callLike).declaration;
|
||||
}
|
||||
}
|
||||
545
src/services/jsDoc.ts
Normal file
545
src/services/jsDoc.ts
Normal file
@ -0,0 +1,545 @@
|
||||
/* @internal */
|
||||
namespace ts.JsDoc {
|
||||
const jsDocTagNames = [
|
||||
"augments",
|
||||
"author",
|
||||
"argument",
|
||||
"borrows",
|
||||
"class",
|
||||
"constant",
|
||||
"constructor",
|
||||
"constructs",
|
||||
"default",
|
||||
"deprecated",
|
||||
"description",
|
||||
"event",
|
||||
"example",
|
||||
"extends",
|
||||
"field",
|
||||
"fileOverview",
|
||||
"function",
|
||||
"ignore",
|
||||
"inner",
|
||||
"lends",
|
||||
"link",
|
||||
"memberOf",
|
||||
"name",
|
||||
"namespace",
|
||||
"param",
|
||||
"private",
|
||||
"property",
|
||||
"public",
|
||||
"requires",
|
||||
"returns",
|
||||
"see",
|
||||
"since",
|
||||
"static",
|
||||
"throws",
|
||||
"type",
|
||||
"typedef",
|
||||
"property",
|
||||
"prop",
|
||||
"version"
|
||||
];
|
||||
let jsDocCompletionEntries: CompletionEntry[];
|
||||
|
||||
export function getJsDocCommentsFromDeclarations(declarations: Declaration[], name: string, canUseParsedParamTagComments: boolean) {
|
||||
const documentationComment = <SymbolDisplayPart[]>[];
|
||||
const docComments = getJsDocCommentsSeparatedByNewLines();
|
||||
ts.forEach(docComments, docComment => {
|
||||
if (documentationComment.length) {
|
||||
documentationComment.push(lineBreakPart());
|
||||
}
|
||||
documentationComment.push(docComment);
|
||||
});
|
||||
|
||||
return documentationComment;
|
||||
|
||||
function getJsDocCommentsSeparatedByNewLines() {
|
||||
const paramTag = "@param";
|
||||
const jsDocCommentParts: SymbolDisplayPart[] = [];
|
||||
|
||||
ts.forEach(declarations, (declaration, indexOfDeclaration) => {
|
||||
// Make sure we are collecting doc comment from declaration once,
|
||||
// In case of union property there might be same declaration multiple times
|
||||
// which only varies in type parameter
|
||||
// Eg. const a: Array<string> | Array<number>; a.length
|
||||
// The property length will have two declarations of property length coming
|
||||
// from Array<T> - Array<string> and Array<number>
|
||||
if (indexOf(declarations, declaration) === indexOfDeclaration) {
|
||||
const sourceFileOfDeclaration = getSourceFileOfNode(declaration);
|
||||
// If it is parameter - try and get the jsDoc comment with @param tag from function declaration's jsDoc comments
|
||||
if (canUseParsedParamTagComments && declaration.kind === SyntaxKind.Parameter) {
|
||||
if ((declaration.parent.kind === SyntaxKind.FunctionExpression || declaration.parent.kind === SyntaxKind.ArrowFunction) &&
|
||||
declaration.parent.parent.kind === SyntaxKind.VariableDeclaration) {
|
||||
addCommentParts(declaration.parent.parent.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
|
||||
}
|
||||
addCommentParts(declaration.parent, sourceFileOfDeclaration, getCleanedParamJsDocComment);
|
||||
}
|
||||
|
||||
// If this is left side of dotted module declaration, there is no doc comments associated with this node
|
||||
if (declaration.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>declaration).body && (<ModuleDeclaration>declaration).body.kind === SyntaxKind.ModuleDeclaration) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((declaration.kind === SyntaxKind.FunctionExpression || declaration.kind === SyntaxKind.ArrowFunction) &&
|
||||
declaration.parent.kind === SyntaxKind.VariableDeclaration) {
|
||||
addCommentParts(declaration.parent.parent, sourceFileOfDeclaration, getCleanedJsDocComment);
|
||||
}
|
||||
|
||||
// If this is dotted module name, get the doc comments from the parent
|
||||
while (declaration.kind === SyntaxKind.ModuleDeclaration && declaration.parent.kind === SyntaxKind.ModuleDeclaration) {
|
||||
declaration = <ModuleDeclaration>declaration.parent;
|
||||
}
|
||||
addCommentParts(declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration,
|
||||
sourceFileOfDeclaration,
|
||||
getCleanedJsDocComment);
|
||||
|
||||
if (declaration.kind === SyntaxKind.VariableDeclaration) {
|
||||
const init = (declaration as VariableDeclaration).initializer;
|
||||
if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) {
|
||||
// Get the cleaned js doc comment text from the initializer
|
||||
addCommentParts(init, sourceFileOfDeclaration, getCleanedJsDocComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return jsDocCommentParts;
|
||||
|
||||
function addCommentParts(commented: Node,
|
||||
sourceFileOfDeclaration: SourceFile,
|
||||
getCommentPart: (pos: number, end: number, file: SourceFile) => SymbolDisplayPart[]): void {
|
||||
const ranges = getJsDocCommentTextRange(commented, sourceFileOfDeclaration);
|
||||
// Get the cleaned js doc comment text from the declaration
|
||||
ts.forEach(ranges, jsDocCommentTextRange => {
|
||||
const cleanedComment = getCommentPart(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
|
||||
if (cleanedComment) {
|
||||
addRange(jsDocCommentParts, cleanedComment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getJsDocCommentTextRange(node: Node, sourceFile: SourceFile): TextRange[] {
|
||||
return ts.map(getJsDocComments(node, sourceFile),
|
||||
jsDocComment => {
|
||||
return {
|
||||
pos: jsDocComment.pos + "/*".length, // Consume /* from the comment
|
||||
end: jsDocComment.end - "*/".length // Trim off comment end indicator
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function consumeWhiteSpacesOnTheLine(pos: number, end: number, sourceFile: SourceFile, maxSpacesToRemove?: number) {
|
||||
if (maxSpacesToRemove !== undefined) {
|
||||
end = Math.min(end, pos + maxSpacesToRemove);
|
||||
}
|
||||
|
||||
for (; pos < end; pos++) {
|
||||
const ch = sourceFile.text.charCodeAt(pos);
|
||||
if (!isWhiteSpaceSingleLine(ch)) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
function consumeLineBreaks(pos: number, end: number, sourceFile: SourceFile) {
|
||||
while (pos < end && isLineBreak(sourceFile.text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
function isName(pos: number, end: number, sourceFile: SourceFile, name: string) {
|
||||
return pos + name.length < end &&
|
||||
sourceFile.text.substr(pos, name.length) === name &&
|
||||
isWhiteSpace(sourceFile.text.charCodeAt(pos + name.length));
|
||||
}
|
||||
|
||||
function isParamTag(pos: number, end: number, sourceFile: SourceFile) {
|
||||
// If it is @param tag
|
||||
return isName(pos, end, sourceFile, paramTag);
|
||||
}
|
||||
|
||||
function pushDocCommentLineText(docComments: SymbolDisplayPart[], text: string, blankLineCount: number) {
|
||||
// Add the empty lines in between texts
|
||||
while (blankLineCount) {
|
||||
blankLineCount--;
|
||||
docComments.push(textPart(""));
|
||||
}
|
||||
|
||||
docComments.push(textPart(text));
|
||||
}
|
||||
|
||||
function getCleanedJsDocComment(pos: number, end: number, sourceFile: SourceFile) {
|
||||
let spacesToRemoveAfterAsterisk: number;
|
||||
const docComments: SymbolDisplayPart[] = [];
|
||||
let blankLineCount = 0;
|
||||
let isInParamTag = false;
|
||||
|
||||
while (pos < end) {
|
||||
let docCommentTextOfLine = "";
|
||||
// First consume leading white space
|
||||
pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile);
|
||||
|
||||
// If the comment starts with '*' consume the spaces on this line
|
||||
if (pos < end && sourceFile.text.charCodeAt(pos) === CharacterCodes.asterisk) {
|
||||
const lineStartPos = pos + 1;
|
||||
pos = consumeWhiteSpacesOnTheLine(pos + 1, end, sourceFile, spacesToRemoveAfterAsterisk);
|
||||
|
||||
// Set the spaces to remove after asterisk as margin if not already set
|
||||
if (spacesToRemoveAfterAsterisk === undefined && pos < end && !isLineBreak(sourceFile.text.charCodeAt(pos))) {
|
||||
spacesToRemoveAfterAsterisk = pos - lineStartPos;
|
||||
}
|
||||
}
|
||||
else if (spacesToRemoveAfterAsterisk === undefined) {
|
||||
spacesToRemoveAfterAsterisk = 0;
|
||||
}
|
||||
|
||||
// Analyze text on this line
|
||||
while (pos < end && !isLineBreak(sourceFile.text.charCodeAt(pos))) {
|
||||
const ch = sourceFile.text.charAt(pos);
|
||||
if (ch === "@") {
|
||||
// If it is @param tag
|
||||
if (isParamTag(pos, end, sourceFile)) {
|
||||
isInParamTag = true;
|
||||
pos += paramTag.length;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
isInParamTag = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the ch to doc text if we arent in param tag
|
||||
if (!isInParamTag) {
|
||||
docCommentTextOfLine += ch;
|
||||
}
|
||||
|
||||
// Scan next character
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Continue with next line
|
||||
pos = consumeLineBreaks(pos, end, sourceFile);
|
||||
if (docCommentTextOfLine) {
|
||||
pushDocCommentLineText(docComments, docCommentTextOfLine, blankLineCount);
|
||||
blankLineCount = 0;
|
||||
}
|
||||
else if (!isInParamTag && docComments.length) {
|
||||
// This is blank line when there is text already parsed
|
||||
blankLineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return docComments;
|
||||
}
|
||||
|
||||
function getCleanedParamJsDocComment(pos: number, end: number, sourceFile: SourceFile) {
|
||||
let paramHelpStringMargin: number;
|
||||
const paramDocComments: SymbolDisplayPart[] = [];
|
||||
while (pos < end) {
|
||||
if (isParamTag(pos, end, sourceFile)) {
|
||||
let blankLineCount = 0;
|
||||
let recordedParamTag = false;
|
||||
// Consume leading spaces
|
||||
pos = consumeWhiteSpaces(pos + paramTag.length);
|
||||
if (pos >= end) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore type expression
|
||||
if (sourceFile.text.charCodeAt(pos) === CharacterCodes.openBrace) {
|
||||
pos++;
|
||||
for (let curlies = 1; pos < end; pos++) {
|
||||
const charCode = sourceFile.text.charCodeAt(pos);
|
||||
|
||||
// { character means we need to find another } to match the found one
|
||||
if (charCode === CharacterCodes.openBrace) {
|
||||
curlies++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// } char
|
||||
if (charCode === CharacterCodes.closeBrace) {
|
||||
curlies--;
|
||||
if (curlies === 0) {
|
||||
// We do not have any more } to match the type expression is ignored completely
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// there are more { to be matched with }
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Found start of another tag
|
||||
if (charCode === CharacterCodes.at) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Consume white spaces
|
||||
pos = consumeWhiteSpaces(pos);
|
||||
if (pos >= end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parameter name
|
||||
if (isName(pos, end, sourceFile, name)) {
|
||||
// Found the parameter we are looking for consume white spaces
|
||||
pos = consumeWhiteSpaces(pos + name.length);
|
||||
if (pos >= end) {
|
||||
break;
|
||||
}
|
||||
|
||||
let paramHelpString = "";
|
||||
const firstLineParamHelpStringPos = pos;
|
||||
while (pos < end) {
|
||||
const ch = sourceFile.text.charCodeAt(pos);
|
||||
|
||||
// at line break, set this comment line text and go to next line
|
||||
if (isLineBreak(ch)) {
|
||||
if (paramHelpString) {
|
||||
pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount);
|
||||
paramHelpString = "";
|
||||
blankLineCount = 0;
|
||||
recordedParamTag = true;
|
||||
}
|
||||
else if (recordedParamTag) {
|
||||
blankLineCount++;
|
||||
}
|
||||
|
||||
// Get the pos after cleaning start of the line
|
||||
setPosForParamHelpStringOnNextLine(firstLineParamHelpStringPos);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Done scanning param help string - next tag found
|
||||
if (ch === CharacterCodes.at) {
|
||||
break;
|
||||
}
|
||||
|
||||
paramHelpString += sourceFile.text.charAt(pos);
|
||||
|
||||
// Go to next character
|
||||
pos++;
|
||||
}
|
||||
|
||||
// If there is param help text, add it top the doc comments
|
||||
if (paramHelpString) {
|
||||
pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount);
|
||||
}
|
||||
paramHelpStringMargin = undefined;
|
||||
}
|
||||
|
||||
// If this is the start of another tag, continue with the loop in search of param tag with symbol name
|
||||
if (sourceFile.text.charCodeAt(pos) === CharacterCodes.at) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Next character
|
||||
pos++;
|
||||
}
|
||||
|
||||
return paramDocComments;
|
||||
|
||||
function consumeWhiteSpaces(pos: number) {
|
||||
while (pos < end && isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
function setPosForParamHelpStringOnNextLine(firstLineParamHelpStringPos: number) {
|
||||
// Get the pos after consuming line breaks
|
||||
pos = consumeLineBreaks(pos, end, sourceFile);
|
||||
if (pos >= end) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (paramHelpStringMargin === undefined) {
|
||||
paramHelpStringMargin = sourceFile.getLineAndCharacterOfPosition(firstLineParamHelpStringPos).character;
|
||||
}
|
||||
|
||||
// Now consume white spaces max
|
||||
const startOfLinePos = pos;
|
||||
pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile, paramHelpStringMargin);
|
||||
if (pos >= end) {
|
||||
return;
|
||||
}
|
||||
|
||||
const consumedSpaces = pos - startOfLinePos;
|
||||
if (consumedSpaces < paramHelpStringMargin) {
|
||||
const ch = sourceFile.text.charCodeAt(pos);
|
||||
if (ch === CharacterCodes.asterisk) {
|
||||
// Consume more spaces after asterisk
|
||||
pos = consumeWhiteSpacesOnTheLine(pos + 1, end, sourceFile, paramHelpStringMargin - consumedSpaces - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllJsDocCompletionEntries(): CompletionEntry[] {
|
||||
return jsDocCompletionEntries || (jsDocCompletionEntries = ts.map(jsDocTagNames, tagName => {
|
||||
return {
|
||||
name: tagName,
|
||||
kind: ScriptElementKind.keyword,
|
||||
kindModifiers: "",
|
||||
sortText: "0",
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if position points to a valid position to add JSDoc comments, and if so,
|
||||
* returns the appropriate template. Otherwise returns an empty string.
|
||||
* Valid positions are
|
||||
* - outside of comments, statements, and expressions, and
|
||||
* - preceding a:
|
||||
* - function/constructor/method declaration
|
||||
* - class declarations
|
||||
* - variable statements
|
||||
* - namespace declarations
|
||||
*
|
||||
* Hosts should ideally check that:
|
||||
* - The line is all whitespace up to 'position' before performing the insertion.
|
||||
* - If the keystroke sequence "/\*\*" induced the call, we also check that the next
|
||||
* non-whitespace character is '*', which (approximately) indicates whether we added
|
||||
* the second '*' to complete an existing (JSDoc) comment.
|
||||
* @param fileName The file in which to perform the check.
|
||||
* @param position The (character-indexed) position in the file where the check should
|
||||
* be performed.
|
||||
*/
|
||||
export function getDocCommentTemplateAtPosition(newLine: string, sourceFile: SourceFile, position: number): TextInsertion {
|
||||
// Check if in a context where we don't want to perform any insertion
|
||||
if (isInString(sourceFile, position) || isInComment(sourceFile, position) || hasDocComment(sourceFile, position)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tokenAtPos = getTokenAtPosition(sourceFile, position);
|
||||
const tokenStart = tokenAtPos.getStart();
|
||||
if (!tokenAtPos || tokenStart < position) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// TODO: add support for:
|
||||
// - enums/enum members
|
||||
// - interfaces
|
||||
// - property declarations
|
||||
// - potentially property assignments
|
||||
let commentOwner: Node;
|
||||
findOwner: for (commentOwner = tokenAtPos; commentOwner; commentOwner = commentOwner.parent) {
|
||||
switch (commentOwner.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.VariableStatement:
|
||||
break findOwner;
|
||||
case SyntaxKind.SourceFile:
|
||||
return undefined;
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
// If in walking up the tree, we hit a a nested namespace declaration,
|
||||
// then we must be somewhere within a dotted namespace name; however we don't
|
||||
// want to give back a JSDoc template for the 'b' or 'c' in 'namespace a.b.c { }'.
|
||||
if (commentOwner.parent.kind === SyntaxKind.ModuleDeclaration) {
|
||||
return undefined;
|
||||
}
|
||||
break findOwner;
|
||||
}
|
||||
}
|
||||
|
||||
if (!commentOwner || commentOwner.getStart() < position) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parameters = getParametersForJsDocOwningNode(commentOwner);
|
||||
const posLineAndChar = sourceFile.getLineAndCharacterOfPosition(position);
|
||||
const lineStart = sourceFile.getLineStarts()[posLineAndChar.line];
|
||||
|
||||
const indentationStr = sourceFile.text.substr(lineStart, posLineAndChar.character);
|
||||
|
||||
let docParams = "";
|
||||
for (let i = 0, numParams = parameters.length; i < numParams; i++) {
|
||||
const currentName = parameters[i].name;
|
||||
const paramName = currentName.kind === SyntaxKind.Identifier ?
|
||||
(<Identifier>currentName).text :
|
||||
"param" + i;
|
||||
|
||||
docParams += `${indentationStr} * @param ${paramName}${newLine}`;
|
||||
}
|
||||
|
||||
// A doc comment consists of the following
|
||||
// * The opening comment line
|
||||
// * the first line (without a param) for the object's untagged info (this is also where the caret ends up)
|
||||
// * the '@param'-tagged lines
|
||||
// * TODO: other tags.
|
||||
// * the closing comment line
|
||||
// * if the caret was directly in front of the object, then we add an extra line and indentation.
|
||||
const preamble = "/**" + newLine +
|
||||
indentationStr + " * ";
|
||||
const result =
|
||||
preamble + newLine +
|
||||
docParams +
|
||||
indentationStr + " */" +
|
||||
(tokenStart === position ? newLine + indentationStr : "");
|
||||
|
||||
return { newText: result, caretOffset: preamble.length };
|
||||
}
|
||||
|
||||
function getParametersForJsDocOwningNode(commentOwner: Node): ParameterDeclaration[] {
|
||||
if (isFunctionLike(commentOwner)) {
|
||||
return commentOwner.parameters;
|
||||
}
|
||||
|
||||
if (commentOwner.kind === SyntaxKind.VariableStatement) {
|
||||
const varStatement = <VariableStatement>commentOwner;
|
||||
const varDeclarations = varStatement.declarationList.declarations;
|
||||
|
||||
if (varDeclarations.length === 1 && varDeclarations[0].initializer) {
|
||||
return getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer);
|
||||
}
|
||||
}
|
||||
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Digs into an an initializer or RHS operand of an assignment operation
|
||||
* to get the parameters of an apt signature corresponding to a
|
||||
* function expression or a class expression.
|
||||
*
|
||||
* @param rightHandSide the expression which may contain an appropriate set of parameters
|
||||
* @returns the parameters of a signature found on the RHS if one exists; otherwise 'emptyArray'.
|
||||
*/
|
||||
function getParametersFromRightHandSideOfAssignment(rightHandSide: Expression): ParameterDeclaration[] {
|
||||
while (rightHandSide.kind === SyntaxKind.ParenthesizedExpression) {
|
||||
rightHandSide = (<ParenthesizedExpression>rightHandSide).expression;
|
||||
}
|
||||
|
||||
switch (rightHandSide.kind) {
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return (<FunctionExpression>rightHandSide).parameters;
|
||||
case SyntaxKind.ClassExpression:
|
||||
for (const member of (<ClassExpression>rightHandSide).members) {
|
||||
if (member.kind === SyntaxKind.Constructor) {
|
||||
return (<ConstructorDeclaration>member).parameters;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return emptyArray;
|
||||
}
|
||||
}
|
||||
160
src/services/meaning.ts
Normal file
160
src/services/meaning.ts
Normal file
@ -0,0 +1,160 @@
|
||||
/* @internal */
|
||||
namespace ts.Meaning {
|
||||
export const enum SemanticMeaning {
|
||||
None = 0x0,
|
||||
Value = 0x1,
|
||||
Type = 0x2,
|
||||
Namespace = 0x4,
|
||||
All = Value | Type | Namespace
|
||||
}
|
||||
|
||||
export function getMeaningFromDeclaration(node: Node): SemanticMeaning {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Parameter:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
case SyntaxKind.BindingElement:
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertySignature:
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
case SyntaxKind.ShorthandPropertyAssignment:
|
||||
case SyntaxKind.EnumMember:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.CatchClause:
|
||||
return SemanticMeaning.Value;
|
||||
|
||||
case SyntaxKind.TypeParameter:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
return SemanticMeaning.Type;
|
||||
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
return SemanticMeaning.Value | SemanticMeaning.Type;
|
||||
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
if (isAmbientModule(<ModuleDeclaration>node)) {
|
||||
return SemanticMeaning.Namespace | SemanticMeaning.Value;
|
||||
}
|
||||
else if (getModuleInstanceState(node) === ModuleInstanceState.Instantiated) {
|
||||
return SemanticMeaning.Namespace | SemanticMeaning.Value;
|
||||
}
|
||||
else {
|
||||
return SemanticMeaning.Namespace;
|
||||
}
|
||||
|
||||
case SyntaxKind.NamedImports:
|
||||
case SyntaxKind.ImportSpecifier:
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
case SyntaxKind.ExportAssignment:
|
||||
case SyntaxKind.ExportDeclaration:
|
||||
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
|
||||
|
||||
// An external module can be a Value
|
||||
case SyntaxKind.SourceFile:
|
||||
return SemanticMeaning.Namespace | SemanticMeaning.Value;
|
||||
}
|
||||
|
||||
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
|
||||
}
|
||||
|
||||
export function getMeaningFromLocation(node: Node): SemanticMeaning {
|
||||
if (node.parent.kind === SyntaxKind.ExportAssignment) {
|
||||
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
|
||||
}
|
||||
else if (isInRightSideOfImport(node)) {
|
||||
return getMeaningFromRightHandSideOfImportEquals(node);
|
||||
}
|
||||
else if (isDeclarationName(node)) {
|
||||
return getMeaningFromDeclaration(node.parent);
|
||||
}
|
||||
else if (isTypeReference(node)) {
|
||||
return SemanticMeaning.Type;
|
||||
}
|
||||
else if (isNamespaceReference(node)) {
|
||||
return SemanticMeaning.Namespace;
|
||||
}
|
||||
else {
|
||||
return SemanticMeaning.Value;
|
||||
}
|
||||
}
|
||||
|
||||
function getMeaningFromRightHandSideOfImportEquals(node: Node) {
|
||||
Debug.assert(node.kind === SyntaxKind.Identifier);
|
||||
|
||||
// import a = |b|; // Namespace
|
||||
// import a = |b.c|; // Value, type, namespace
|
||||
// import a = |b.c|.d; // Namespace
|
||||
|
||||
if (node.parent.kind === SyntaxKind.QualifiedName &&
|
||||
(<QualifiedName>node.parent).right === node &&
|
||||
node.parent.parent.kind === SyntaxKind.ImportEqualsDeclaration) {
|
||||
return SemanticMeaning.Value | SemanticMeaning.Type | SemanticMeaning.Namespace;
|
||||
}
|
||||
return SemanticMeaning.Namespace;
|
||||
}
|
||||
|
||||
function isInRightSideOfImport(node: Node) {
|
||||
while (node.parent.kind === SyntaxKind.QualifiedName) {
|
||||
node = node.parent;
|
||||
}
|
||||
return isInternalModuleImportEqualsDeclaration(node.parent) && (<ImportEqualsDeclaration>node.parent).moduleReference === node;
|
||||
}
|
||||
|
||||
function isNamespaceReference(node: Node): boolean {
|
||||
return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node);
|
||||
}
|
||||
|
||||
function isQualifiedNameNamespaceReference(node: Node): boolean {
|
||||
let root = node;
|
||||
let isLastClause = true;
|
||||
if (root.parent.kind === SyntaxKind.QualifiedName) {
|
||||
while (root.parent && root.parent.kind === SyntaxKind.QualifiedName) {
|
||||
root = root.parent;
|
||||
}
|
||||
|
||||
isLastClause = (<QualifiedName>root).right === node;
|
||||
}
|
||||
|
||||
return root.parent.kind === SyntaxKind.TypeReference && !isLastClause;
|
||||
}
|
||||
|
||||
function isPropertyAccessNamespaceReference(node: Node): boolean {
|
||||
let root = node;
|
||||
let isLastClause = true;
|
||||
if (root.parent.kind === SyntaxKind.PropertyAccessExpression) {
|
||||
while (root.parent && root.parent.kind === SyntaxKind.PropertyAccessExpression) {
|
||||
root = root.parent;
|
||||
}
|
||||
|
||||
isLastClause = (<PropertyAccessExpression>root).name === node;
|
||||
}
|
||||
|
||||
if (!isLastClause && root.parent.kind === SyntaxKind.ExpressionWithTypeArguments && root.parent.parent.kind === SyntaxKind.HeritageClause) {
|
||||
const decl = root.parent.parent.parent;
|
||||
return (decl.kind === SyntaxKind.ClassDeclaration && (<HeritageClause>root.parent.parent).token === SyntaxKind.ImplementsKeyword) ||
|
||||
(decl.kind === SyntaxKind.InterfaceDeclaration && (<HeritageClause>root.parent.parent).token === SyntaxKind.ExtendsKeyword);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isTypeReference(node: Node): boolean {
|
||||
if (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
return node.parent.kind === SyntaxKind.TypeReference ||
|
||||
(node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && !isExpressionWithTypeArgumentsInClassExtendsClause(<ExpressionWithTypeArguments>node.parent)) ||
|
||||
(node.kind === SyntaxKind.ThisKeyword && !isPartOfExpression(node)) ||
|
||||
node.kind === SyntaxKind.ThisType;
|
||||
}
|
||||
}
|
||||
360
src/services/preProcess.ts
Normal file
360
src/services/preProcess.ts
Normal file
@ -0,0 +1,360 @@
|
||||
/* @internal */
|
||||
namespace ts.PreProcess {
|
||||
export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
|
||||
const referencedFiles: FileReference[] = [];
|
||||
const typeReferenceDirectives: FileReference[] = [];
|
||||
const importedFiles: FileReference[] = [];
|
||||
let ambientExternalModules: { ref: FileReference, depth: number }[];
|
||||
let isNoDefaultLib = false;
|
||||
let braceNesting = 0;
|
||||
// assume that text represent an external module if it contains at least one top level import/export
|
||||
// ambient modules that are found inside external modules are interpreted as module augmentations
|
||||
let externalModule = false;
|
||||
|
||||
function nextToken() {
|
||||
const token = scanner.scan();
|
||||
if (token === SyntaxKind.OpenBraceToken) {
|
||||
braceNesting++;
|
||||
}
|
||||
else if (token === SyntaxKind.CloseBraceToken) {
|
||||
braceNesting--;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function processTripleSlashDirectives(): void {
|
||||
const commentRanges = getLeadingCommentRanges(sourceText, 0);
|
||||
forEach(commentRanges, commentRange => {
|
||||
const comment = sourceText.substring(commentRange.pos, commentRange.end);
|
||||
const referencePathMatchResult = getFileReferenceFromReferencePath(comment, commentRange);
|
||||
if (referencePathMatchResult) {
|
||||
isNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
|
||||
const fileReference = referencePathMatchResult.fileReference;
|
||||
if (fileReference) {
|
||||
const collection = referencePathMatchResult.isTypeReferenceDirective
|
||||
? typeReferenceDirectives
|
||||
: referencedFiles;
|
||||
|
||||
collection.push(fileReference);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getFileReference() {
|
||||
const file = scanner.getTokenValue();
|
||||
const pos = scanner.getTokenPos();
|
||||
return {
|
||||
fileName: file,
|
||||
pos: pos,
|
||||
end: pos + file.length
|
||||
};
|
||||
}
|
||||
|
||||
function recordAmbientExternalModule(): void {
|
||||
if (!ambientExternalModules) {
|
||||
ambientExternalModules = [];
|
||||
}
|
||||
ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting });
|
||||
}
|
||||
|
||||
function recordModuleName() {
|
||||
importedFiles.push(getFileReference());
|
||||
|
||||
markAsExternalModuleIfTopLevel();
|
||||
}
|
||||
|
||||
function markAsExternalModuleIfTopLevel() {
|
||||
if (braceNesting === 0) {
|
||||
externalModule = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one token was consumed from the stream
|
||||
*/
|
||||
function tryConsumeDeclare(): boolean {
|
||||
let token = scanner.getToken();
|
||||
if (token === SyntaxKind.DeclareKeyword) {
|
||||
// declare module "mod"
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.ModuleKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
recordAmbientExternalModule();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one token was consumed from the stream
|
||||
*/
|
||||
function tryConsumeImport(): boolean {
|
||||
let token = scanner.getToken();
|
||||
if (token === SyntaxKind.ImportKeyword) {
|
||||
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// import "mod";
|
||||
recordModuleName();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (token === SyntaxKind.Identifier || isKeyword(token)) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// import d from "mod";
|
||||
recordModuleName();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.EqualsToken) {
|
||||
if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.CommaToken) {
|
||||
// consume comma and keep going
|
||||
token = nextToken();
|
||||
}
|
||||
else {
|
||||
// unknown syntax
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (token === SyntaxKind.OpenBraceToken) {
|
||||
token = nextToken();
|
||||
// consume "{ a as B, c, d as D}" clauses
|
||||
// make sure that it stops on EOF
|
||||
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
|
||||
token = nextToken();
|
||||
}
|
||||
|
||||
if (token === SyntaxKind.CloseBraceToken) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// import {a as A} from "mod";
|
||||
// import d, {a, b as B} from "mod"
|
||||
recordModuleName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.AsteriskToken) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.AsKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.Identifier || isKeyword(token)) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// import * as NS from "mod"
|
||||
// import d, * as NS from "mod"
|
||||
recordModuleName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryConsumeExport(): boolean {
|
||||
let token = scanner.getToken();
|
||||
if (token === SyntaxKind.ExportKeyword) {
|
||||
markAsExternalModuleIfTopLevel();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.OpenBraceToken) {
|
||||
token = nextToken();
|
||||
// consume "{ a as B, c, d as D}" clauses
|
||||
// make sure it stops on EOF
|
||||
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
|
||||
token = nextToken();
|
||||
}
|
||||
|
||||
if (token === SyntaxKind.CloseBraceToken) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// export {a as A} from "mod";
|
||||
// export {a, b as B} from "mod"
|
||||
recordModuleName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.AsteriskToken) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// export * from "mod"
|
||||
recordModuleName();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.ImportKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.Identifier || isKeyword(token)) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.EqualsToken) {
|
||||
if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryConsumeRequireCall(skipCurrentToken: boolean): boolean {
|
||||
let token = skipCurrentToken ? nextToken() : scanner.getToken();
|
||||
if (token === SyntaxKind.RequireKeyword) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.OpenParenToken) {
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// require("mod");
|
||||
recordModuleName();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryConsumeDefine(): boolean {
|
||||
let token = scanner.getToken();
|
||||
if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") {
|
||||
token = nextToken();
|
||||
if (token !== SyntaxKind.OpenParenToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// looks like define ("modname", ... - skip string literal and comma
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.CommaToken) {
|
||||
token = nextToken();
|
||||
}
|
||||
else {
|
||||
// unexpected token
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// should be start of dependency list
|
||||
if (token !== SyntaxKind.OpenBracketToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// skip open bracket
|
||||
token = nextToken();
|
||||
let i = 0;
|
||||
// scan until ']' or EOF
|
||||
while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) {
|
||||
// record string literals as module names
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
recordModuleName();
|
||||
i++;
|
||||
}
|
||||
|
||||
token = nextToken();
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function processImports(): void {
|
||||
scanner.setText(sourceText);
|
||||
nextToken();
|
||||
// Look for:
|
||||
// import "mod";
|
||||
// import d from "mod"
|
||||
// import {a as A } from "mod";
|
||||
// import * as NS from "mod"
|
||||
// import d, {a, b as B} from "mod"
|
||||
// import i = require("mod");
|
||||
//
|
||||
// export * from "mod"
|
||||
// export {a as b} from "mod"
|
||||
// export import i = require("mod")
|
||||
// (for JavaScript files) require("mod")
|
||||
|
||||
while (true) {
|
||||
if (scanner.getToken() === SyntaxKind.EndOfFileToken) {
|
||||
break;
|
||||
}
|
||||
|
||||
// check if at least one of alternative have moved scanner forward
|
||||
if (tryConsumeDeclare() ||
|
||||
tryConsumeImport() ||
|
||||
tryConsumeExport() ||
|
||||
(detectJavaScriptImports && (tryConsumeRequireCall(/*skipCurrentToken*/ false) || tryConsumeDefine()))) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
scanner.setText(undefined);
|
||||
}
|
||||
|
||||
if (readImportFiles) {
|
||||
processImports();
|
||||
}
|
||||
processTripleSlashDirectives();
|
||||
if (externalModule) {
|
||||
// for external modules module all nested ambient modules are augmentations
|
||||
if (ambientExternalModules) {
|
||||
// move all detected ambient modules to imported files since they need to be resolved
|
||||
for (const decl of ambientExternalModules) {
|
||||
importedFiles.push(decl.ref);
|
||||
}
|
||||
}
|
||||
return { referencedFiles, typeReferenceDirectives, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: undefined };
|
||||
}
|
||||
else {
|
||||
// for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0
|
||||
let ambientModuleNames: string[];
|
||||
if (ambientExternalModules) {
|
||||
for (const decl of ambientExternalModules) {
|
||||
if (decl.depth === 0) {
|
||||
if (!ambientModuleNames) {
|
||||
ambientModuleNames = [];
|
||||
}
|
||||
ambientModuleNames.push(decl.ref.fileName);
|
||||
}
|
||||
else {
|
||||
importedFiles.push(decl.ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { referencedFiles, typeReferenceDirectives, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: ambientModuleNames };
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -986,7 +986,7 @@ namespace ts {
|
||||
|
||||
constructor(factory: ShimFactory, private logger: Logger) {
|
||||
super(factory);
|
||||
this.classifier = createClassifier();
|
||||
this.classifier = Classifier.createClassifier();
|
||||
}
|
||||
|
||||
public getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent?: boolean): string {
|
||||
@ -1047,7 +1047,7 @@ namespace ts {
|
||||
`getPreProcessedFileInfo('${fileName}')`,
|
||||
() => {
|
||||
// for now treat files as JavaScript
|
||||
const result = preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()), /* readImportFiles */ true, /* detectJavaScriptImports */ true);
|
||||
const result = PreProcess.preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()), /* readImportFiles */ true, /* detectJavaScriptImports */ true);
|
||||
return {
|
||||
referencedFiles: this.convertFileReferences(result.referencedFiles),
|
||||
importedFiles: this.convertFileReferences(result.importedFiles),
|
||||
|
||||
523
src/services/symbolDisplay.ts
Normal file
523
src/services/symbolDisplay.ts
Normal file
@ -0,0 +1,523 @@
|
||||
/* @internal */
|
||||
namespace ts.SymbolDisplay {
|
||||
// TODO(drosen): use contextual SemanticMeaning.
|
||||
export function getSymbolKind(typeChecker: TypeChecker, symbol: Symbol, location: Node): string {
|
||||
const flags = symbol.getFlags();
|
||||
|
||||
if (flags & SymbolFlags.Class) return getDeclarationOfKind(symbol, SyntaxKind.ClassExpression) ?
|
||||
ScriptElementKind.localClassElement : ScriptElementKind.classElement;
|
||||
if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement;
|
||||
if (flags & SymbolFlags.TypeAlias) return ScriptElementKind.typeElement;
|
||||
if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement;
|
||||
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
|
||||
|
||||
const result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, flags, location);
|
||||
if (result === ScriptElementKind.unknown) {
|
||||
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
|
||||
if (flags & SymbolFlags.EnumMember) return ScriptElementKind.variableElement;
|
||||
if (flags & SymbolFlags.Alias) return ScriptElementKind.alias;
|
||||
if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker: TypeChecker, symbol: Symbol, flags: SymbolFlags, location: Node) {
|
||||
if (typeChecker.isUndefinedSymbol(symbol)) {
|
||||
return ScriptElementKind.variableElement;
|
||||
}
|
||||
if (typeChecker.isArgumentsSymbol(symbol)) {
|
||||
return ScriptElementKind.localVariableElement;
|
||||
}
|
||||
if (location.kind === SyntaxKind.ThisKeyword && isExpression(location)) {
|
||||
return ScriptElementKind.parameterElement;
|
||||
}
|
||||
if (flags & SymbolFlags.Variable) {
|
||||
if (isFirstDeclarationOfSymbolParameter(symbol)) {
|
||||
return ScriptElementKind.parameterElement;
|
||||
}
|
||||
else if (symbol.valueDeclaration && isConst(symbol.valueDeclaration)) {
|
||||
return ScriptElementKind.constElement;
|
||||
}
|
||||
else if (forEach(symbol.declarations, isLet)) {
|
||||
return ScriptElementKind.letElement;
|
||||
}
|
||||
return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localVariableElement : ScriptElementKind.variableElement;
|
||||
}
|
||||
if (flags & SymbolFlags.Function) return isLocalVariableOrFunction(symbol) ? ScriptElementKind.localFunctionElement : ScriptElementKind.functionElement;
|
||||
if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement;
|
||||
if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement;
|
||||
if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement;
|
||||
if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement;
|
||||
|
||||
if (flags & SymbolFlags.Property) {
|
||||
if (flags & SymbolFlags.SyntheticProperty) {
|
||||
// If union property is result of union of non method (property/accessors/variables), it is labeled as property
|
||||
const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => {
|
||||
const rootSymbolFlags = rootSymbol.getFlags();
|
||||
if (rootSymbolFlags & (SymbolFlags.PropertyOrAccessor | SymbolFlags.Variable)) {
|
||||
return ScriptElementKind.memberVariableElement;
|
||||
}
|
||||
Debug.assert(!!(rootSymbolFlags & SymbolFlags.Method));
|
||||
});
|
||||
if (!unionPropertyKind) {
|
||||
// If this was union of all methods,
|
||||
// make sure it has call signatures before we can label it as method
|
||||
const typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location);
|
||||
if (typeOfUnionProperty.getCallSignatures().length) {
|
||||
return ScriptElementKind.memberFunctionElement;
|
||||
}
|
||||
return ScriptElementKind.memberVariableElement;
|
||||
}
|
||||
return unionPropertyKind;
|
||||
}
|
||||
return ScriptElementKind.memberVariableElement;
|
||||
}
|
||||
|
||||
return ScriptElementKind.unknown;
|
||||
}
|
||||
|
||||
export function getSymbolModifiers(symbol: Symbol): string {
|
||||
return symbol && symbol.declarations && symbol.declarations.length > 0
|
||||
? getNodeModifiers(symbol.declarations[0])
|
||||
: ScriptElementKindModifier.none;
|
||||
}
|
||||
|
||||
// TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location
|
||||
export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: TypeChecker, symbol: Symbol, sourceFile: SourceFile, enclosingDeclaration: Node,
|
||||
location: Node, semanticMeaning = Meaning.getMeaningFromLocation(location)) {
|
||||
|
||||
const displayParts: SymbolDisplayPart[] = [];
|
||||
let documentation: SymbolDisplayPart[];
|
||||
const symbolFlags = symbol.flags;
|
||||
let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, symbolFlags, location);
|
||||
let hasAddedSymbolInfo: boolean;
|
||||
const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location);
|
||||
let type: Type;
|
||||
|
||||
// Class at constructor site need to be shown as constructor apart from property,method, vars
|
||||
if (symbolKind !== ScriptElementKind.unknown || symbolFlags & SymbolFlags.Class || symbolFlags & SymbolFlags.Alias) {
|
||||
// If it is accessor they are allowed only if location is at name of the accessor
|
||||
if (symbolKind === ScriptElementKind.memberGetAccessorElement || symbolKind === ScriptElementKind.memberSetAccessorElement) {
|
||||
symbolKind = ScriptElementKind.memberVariableElement;
|
||||
}
|
||||
|
||||
let signature: Signature;
|
||||
type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol, location);
|
||||
if (type) {
|
||||
if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) {
|
||||
const right = (<PropertyAccessExpression>location.parent).name;
|
||||
// Either the location is on the right of a property access, or on the left and the right is missing
|
||||
if (right === location || (right && right.getFullWidth() === 0)) {
|
||||
location = location.parent;
|
||||
}
|
||||
}
|
||||
|
||||
// try get the call/construct signature from the type if it matches
|
||||
let callExpression: CallExpression;
|
||||
if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) {
|
||||
callExpression = <CallExpression>location;
|
||||
}
|
||||
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
|
||||
callExpression = <CallExpression>location.parent;
|
||||
}
|
||||
|
||||
if (callExpression) {
|
||||
const candidateSignatures: Signature[] = [];
|
||||
signature = typeChecker.getResolvedSignature(callExpression, candidateSignatures);
|
||||
if (!signature && candidateSignatures.length) {
|
||||
// Use the first candidate:
|
||||
signature = candidateSignatures[0];
|
||||
}
|
||||
|
||||
const useConstructSignatures = callExpression.kind === SyntaxKind.NewExpression || callExpression.expression.kind === SyntaxKind.SuperKeyword;
|
||||
const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures();
|
||||
|
||||
if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) {
|
||||
// Get the first signature if there is one -- allSignatures may contain
|
||||
// either the original signature or its target, so check for either
|
||||
signature = allSignatures.length ? allSignatures[0] : undefined;
|
||||
}
|
||||
|
||||
if (signature) {
|
||||
if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) {
|
||||
// Constructor
|
||||
symbolKind = ScriptElementKind.constructorImplementationElement;
|
||||
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
|
||||
}
|
||||
else if (symbolFlags & SymbolFlags.Alias) {
|
||||
symbolKind = ScriptElementKind.alias;
|
||||
pushTypePart(symbolKind);
|
||||
displayParts.push(spacePart());
|
||||
if (useConstructSignatures) {
|
||||
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
|
||||
displayParts.push(spacePart());
|
||||
}
|
||||
addFullSymbolName(symbol);
|
||||
}
|
||||
else {
|
||||
addPrefixForAnyFunctionOrVar(symbol, symbolKind);
|
||||
}
|
||||
|
||||
switch (symbolKind) {
|
||||
case ScriptElementKind.memberVariableElement:
|
||||
case ScriptElementKind.variableElement:
|
||||
case ScriptElementKind.constElement:
|
||||
case ScriptElementKind.letElement:
|
||||
case ScriptElementKind.parameterElement:
|
||||
case ScriptElementKind.localVariableElement:
|
||||
// If it is call or construct signature of lambda's write type name
|
||||
displayParts.push(punctuationPart(SyntaxKind.ColonToken));
|
||||
displayParts.push(spacePart());
|
||||
if (useConstructSignatures) {
|
||||
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
|
||||
displayParts.push(spacePart());
|
||||
}
|
||||
if (!(type.flags & TypeFlags.Anonymous) && type.symbol) {
|
||||
addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments));
|
||||
}
|
||||
addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Just signature
|
||||
addSignatureDisplayParts(signature, allSignatures);
|
||||
}
|
||||
hasAddedSymbolInfo = true;
|
||||
}
|
||||
}
|
||||
else if ((isNameOfFunctionDeclaration(location) && !(symbol.flags & SymbolFlags.Accessor)) || // name of function declaration
|
||||
(location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration
|
||||
// get the signature from the declaration and write it
|
||||
const functionDeclaration = <FunctionLikeDeclaration>location.parent;
|
||||
const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures();
|
||||
if (!typeChecker.isImplementationOfOverload(functionDeclaration)) {
|
||||
signature = typeChecker.getSignatureFromDeclaration(functionDeclaration);
|
||||
}
|
||||
else {
|
||||
signature = allSignatures[0];
|
||||
}
|
||||
|
||||
if (functionDeclaration.kind === SyntaxKind.Constructor) {
|
||||
// show (constructor) Type(...) signature
|
||||
symbolKind = ScriptElementKind.constructorImplementationElement;
|
||||
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
|
||||
}
|
||||
else {
|
||||
// (function/method) symbol(..signature)
|
||||
addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature &&
|
||||
!(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind);
|
||||
}
|
||||
|
||||
addSignatureDisplayParts(signature, allSignatures);
|
||||
hasAddedSymbolInfo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (symbolFlags & SymbolFlags.Class && !hasAddedSymbolInfo && !isThisExpression) {
|
||||
if (getDeclarationOfKind(symbol, SyntaxKind.ClassExpression)) {
|
||||
// Special case for class expressions because we would like to indicate that
|
||||
// the class name is local to the class body (similar to function expression)
|
||||
// (local class) class <className>
|
||||
pushTypePart(ScriptElementKind.localClassElement);
|
||||
}
|
||||
else {
|
||||
// Class declaration has name which is not local.
|
||||
displayParts.push(keywordPart(SyntaxKind.ClassKeyword));
|
||||
}
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
writeTypeParametersOfSymbol(symbol, sourceFile);
|
||||
}
|
||||
if ((symbolFlags & SymbolFlags.Interface) && (semanticMeaning & Meaning.SemanticMeaning.Type)) {
|
||||
addNewLineIfDisplayPartsExist();
|
||||
displayParts.push(keywordPart(SyntaxKind.InterfaceKeyword));
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
writeTypeParametersOfSymbol(symbol, sourceFile);
|
||||
}
|
||||
if (symbolFlags & SymbolFlags.TypeAlias) {
|
||||
addNewLineIfDisplayPartsExist();
|
||||
displayParts.push(keywordPart(SyntaxKind.TypeKeyword));
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
writeTypeParametersOfSymbol(symbol, sourceFile);
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
|
||||
displayParts.push(spacePart());
|
||||
addRange(displayParts, typeToDisplayParts(typeChecker, typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias));
|
||||
}
|
||||
if (symbolFlags & SymbolFlags.Enum) {
|
||||
addNewLineIfDisplayPartsExist();
|
||||
if (forEach(symbol.declarations, isConstEnumDeclaration)) {
|
||||
displayParts.push(keywordPart(SyntaxKind.ConstKeyword));
|
||||
displayParts.push(spacePart());
|
||||
}
|
||||
displayParts.push(keywordPart(SyntaxKind.EnumKeyword));
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
}
|
||||
if (symbolFlags & SymbolFlags.Module) {
|
||||
addNewLineIfDisplayPartsExist();
|
||||
const declaration = <ModuleDeclaration>getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration);
|
||||
const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier;
|
||||
displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword));
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
}
|
||||
if ((symbolFlags & SymbolFlags.TypeParameter) && (semanticMeaning & Meaning.SemanticMeaning.Type)) {
|
||||
addNewLineIfDisplayPartsExist();
|
||||
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
|
||||
displayParts.push(textPart("type parameter"));
|
||||
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(keywordPart(SyntaxKind.InKeyword));
|
||||
displayParts.push(spacePart());
|
||||
if (symbol.parent) {
|
||||
// Class/Interface type parameter
|
||||
addFullSymbolName(symbol.parent, enclosingDeclaration);
|
||||
writeTypeParametersOfSymbol(symbol.parent, enclosingDeclaration);
|
||||
}
|
||||
else {
|
||||
// Method/function type parameter
|
||||
let declaration = <Node>getDeclarationOfKind(symbol, SyntaxKind.TypeParameter);
|
||||
Debug.assert(declaration !== undefined);
|
||||
declaration = declaration.parent;
|
||||
|
||||
if (declaration) {
|
||||
if (isFunctionLikeKind(declaration.kind)) {
|
||||
const signature = typeChecker.getSignatureFromDeclaration(<SignatureDeclaration>declaration);
|
||||
if (declaration.kind === SyntaxKind.ConstructSignature) {
|
||||
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
|
||||
displayParts.push(spacePart());
|
||||
}
|
||||
else if (declaration.kind !== SyntaxKind.CallSignature && (<SignatureDeclaration>declaration).name) {
|
||||
addFullSymbolName(declaration.symbol);
|
||||
}
|
||||
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature));
|
||||
}
|
||||
else {
|
||||
// Type alias type parameter
|
||||
// For example
|
||||
// type list<T> = T[]; // Both T will go through same code path
|
||||
displayParts.push(keywordPart(SyntaxKind.TypeKeyword));
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(declaration.symbol);
|
||||
writeTypeParametersOfSymbol(declaration.symbol, sourceFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (symbolFlags & SymbolFlags.EnumMember) {
|
||||
addPrefixForAnyFunctionOrVar(symbol, "enum member");
|
||||
const declaration = symbol.declarations[0];
|
||||
if (declaration.kind === SyntaxKind.EnumMember) {
|
||||
const constantValue = typeChecker.getConstantValue(<EnumMember>declaration);
|
||||
if (constantValue !== undefined) {
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(displayPart(constantValue.toString(), SymbolDisplayPartKind.numericLiteral));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (symbolFlags & SymbolFlags.Alias) {
|
||||
addNewLineIfDisplayPartsExist();
|
||||
if (symbol.declarations[0].kind === SyntaxKind.NamespaceExportDeclaration) {
|
||||
displayParts.push(keywordPart(SyntaxKind.ExportKeyword));
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword));
|
||||
}
|
||||
else {
|
||||
displayParts.push(keywordPart(SyntaxKind.ImportKeyword));
|
||||
}
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
ts.forEach(symbol.declarations, declaration => {
|
||||
if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) {
|
||||
const importEqualsDeclaration = <ImportEqualsDeclaration>declaration;
|
||||
if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) {
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(keywordPart(SyntaxKind.RequireKeyword));
|
||||
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
|
||||
displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral));
|
||||
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
|
||||
}
|
||||
else {
|
||||
const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference);
|
||||
if (internalAliasSymbol) {
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(internalAliasSymbol, enclosingDeclaration);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!hasAddedSymbolInfo) {
|
||||
if (symbolKind !== ScriptElementKind.unknown) {
|
||||
if (type) {
|
||||
if (isThisExpression) {
|
||||
addNewLineIfDisplayPartsExist();
|
||||
displayParts.push(keywordPart(SyntaxKind.ThisKeyword));
|
||||
}
|
||||
else {
|
||||
addPrefixForAnyFunctionOrVar(symbol, symbolKind);
|
||||
}
|
||||
|
||||
// For properties, variables and local vars: show the type
|
||||
if (symbolKind === ScriptElementKind.memberVariableElement ||
|
||||
symbolFlags & SymbolFlags.Variable ||
|
||||
symbolKind === ScriptElementKind.localVariableElement ||
|
||||
isThisExpression) {
|
||||
displayParts.push(punctuationPart(SyntaxKind.ColonToken));
|
||||
displayParts.push(spacePart());
|
||||
// If the type is type parameter, format it specially
|
||||
if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter) {
|
||||
const typeParameterParts = mapToDisplayParts(writer => {
|
||||
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(<TypeParameter>type, writer, enclosingDeclaration);
|
||||
});
|
||||
addRange(displayParts, typeParameterParts);
|
||||
}
|
||||
else {
|
||||
addRange(displayParts, typeToDisplayParts(typeChecker, type, enclosingDeclaration));
|
||||
}
|
||||
}
|
||||
else if (symbolFlags & SymbolFlags.Function ||
|
||||
symbolFlags & SymbolFlags.Method ||
|
||||
symbolFlags & SymbolFlags.Constructor ||
|
||||
symbolFlags & SymbolFlags.Signature ||
|
||||
symbolFlags & SymbolFlags.Accessor ||
|
||||
symbolKind === ScriptElementKind.memberFunctionElement) {
|
||||
const allSignatures = type.getNonNullableType().getCallSignatures();
|
||||
addSignatureDisplayParts(allSignatures[0], allSignatures);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
symbolKind = getSymbolKind(typeChecker, symbol, location);
|
||||
}
|
||||
}
|
||||
|
||||
if (!documentation) {
|
||||
documentation = symbol.getDocumentationComment();
|
||||
if (documentation.length === 0 && symbol.flags & SymbolFlags.Property) {
|
||||
// For some special property access expressions like `experts.foo = foo` or `module.exports.foo = foo`
|
||||
// there documentation comments might be attached to the right hand side symbol of their declarations.
|
||||
// The pattern of such special property access is that the parent symbol is the symbol of the file.
|
||||
if (symbol.parent && forEach(symbol.parent.declarations, declaration => declaration.kind === SyntaxKind.SourceFile)) {
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (!declaration.parent || declaration.parent.kind !== SyntaxKind.BinaryExpression) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rhsSymbol = typeChecker.getSymbolAtLocation((<BinaryExpression>declaration.parent).right);
|
||||
if (!rhsSymbol) {
|
||||
continue;
|
||||
}
|
||||
|
||||
documentation = rhsSymbol.getDocumentationComment();
|
||||
if (documentation.length > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { displayParts, documentation, symbolKind };
|
||||
|
||||
function addNewLineIfDisplayPartsExist() {
|
||||
if (displayParts.length) {
|
||||
displayParts.push(lineBreakPart());
|
||||
}
|
||||
}
|
||||
|
||||
function addFullSymbolName(symbol: Symbol, enclosingDeclaration?: Node) {
|
||||
const fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbol, enclosingDeclaration || sourceFile, /*meaning*/ undefined,
|
||||
SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing);
|
||||
addRange(displayParts, fullSymbolDisplayParts);
|
||||
}
|
||||
|
||||
function addPrefixForAnyFunctionOrVar(symbol: Symbol, symbolKind: string) {
|
||||
addNewLineIfDisplayPartsExist();
|
||||
if (symbolKind) {
|
||||
pushTypePart(symbolKind);
|
||||
displayParts.push(spacePart());
|
||||
addFullSymbolName(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
function pushTypePart(symbolKind: string) {
|
||||
switch (symbolKind) {
|
||||
case ScriptElementKind.variableElement:
|
||||
case ScriptElementKind.functionElement:
|
||||
case ScriptElementKind.letElement:
|
||||
case ScriptElementKind.constElement:
|
||||
case ScriptElementKind.constructorImplementationElement:
|
||||
displayParts.push(textOrKeywordPart(symbolKind));
|
||||
return;
|
||||
default:
|
||||
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
|
||||
displayParts.push(textOrKeywordPart(symbolKind));
|
||||
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function addSignatureDisplayParts(signature: Signature, allSignatures: Signature[], flags?: TypeFormatFlags) {
|
||||
addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags | TypeFormatFlags.WriteTypeArgumentsOfSignature));
|
||||
if (allSignatures.length > 1) {
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
|
||||
displayParts.push(operatorPart(SyntaxKind.PlusToken));
|
||||
displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral));
|
||||
displayParts.push(spacePart());
|
||||
displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads"));
|
||||
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
|
||||
}
|
||||
documentation = signature.getDocumentationComment();
|
||||
}
|
||||
|
||||
function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node) {
|
||||
const typeParameterParts = mapToDisplayParts(writer => {
|
||||
typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplayFromSymbol(symbol, writer, enclosingDeclaration);
|
||||
});
|
||||
addRange(displayParts, typeParameterParts);
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalVariableOrFunction(symbol: Symbol) {
|
||||
if (symbol.parent) {
|
||||
return false; // This is exported symbol
|
||||
}
|
||||
|
||||
return ts.forEach(symbol.declarations, declaration => {
|
||||
// Function expressions are local
|
||||
if (declaration.kind === SyntaxKind.FunctionExpression) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.FunctionDeclaration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the parent is not sourceFile or module block it is local variable
|
||||
for (let parent = declaration.parent; !isFunctionBlock(parent); parent = parent.parent) {
|
||||
// Reached source file or module block
|
||||
if (parent.kind === SyntaxKind.SourceFile || parent.kind === SyntaxKind.ModuleBlock) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// parent is in function block
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -40,16 +40,26 @@
|
||||
"../compiler/program.ts",
|
||||
"../compiler/commandLineParser.ts",
|
||||
"../compiler/diagnosticInformationMap.generated.ts",
|
||||
"types.ts",
|
||||
"utilities.ts",
|
||||
"allocators.ts",
|
||||
"breakpoints.ts",
|
||||
"classifier.ts",
|
||||
"completions.ts",
|
||||
"documentHighlights.ts",
|
||||
"findAllReferences.ts",
|
||||
"goToDefinition.ts",
|
||||
"jsDoc.ts",
|
||||
"meaning.ts",
|
||||
"navigateTo.ts",
|
||||
"navigationBar.ts",
|
||||
"outliningElementsCollector.ts",
|
||||
"patternMatcher.ts",
|
||||
"preProcess.ts",
|
||||
"services.ts",
|
||||
"shims.ts",
|
||||
"signatureHelp.ts",
|
||||
"types.ts",
|
||||
"utilities.ts",
|
||||
"symbolDisplay.ts",
|
||||
"jsTyping.ts",
|
||||
"formatting/formatting.ts",
|
||||
"formatting/formattingContext.ts",
|
||||
|
||||
@ -1,6 +1,237 @@
|
||||
// These utilities are common to multiple language service features.
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true);
|
||||
export const emptyArray: any[] = [];
|
||||
|
||||
export function isCallExpressionTarget(node: Node): boolean {
|
||||
return isCallOrNewExpressionTarget(node, SyntaxKind.CallExpression);
|
||||
}
|
||||
|
||||
export function isNewExpressionTarget(node: Node): boolean {
|
||||
return isCallOrNewExpressionTarget(node, SyntaxKind.NewExpression);
|
||||
}
|
||||
|
||||
function isCallOrNewExpressionTarget(node: Node, kind: SyntaxKind) {
|
||||
const target = climbPastPropertyAccess(node);
|
||||
return target && target.parent && target.parent.kind === kind && (<CallExpression>target.parent).expression === target;
|
||||
}
|
||||
|
||||
export function climbPastPropertyAccess(node: Node) {
|
||||
return isRightSideOfPropertyAccess(node) ? node.parent : node;
|
||||
}
|
||||
|
||||
export function getTargetLabel(referenceNode: Node, labelName: string): Identifier {
|
||||
while (referenceNode) {
|
||||
if (referenceNode.kind === SyntaxKind.LabeledStatement && (<LabeledStatement>referenceNode).label.text === labelName) {
|
||||
return (<LabeledStatement>referenceNode).label;
|
||||
}
|
||||
referenceNode = referenceNode.parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isJumpStatementTarget(node: Node): boolean {
|
||||
return node.kind === SyntaxKind.Identifier &&
|
||||
(node.parent.kind === SyntaxKind.BreakStatement || node.parent.kind === SyntaxKind.ContinueStatement) &&
|
||||
(<BreakOrContinueStatement>node.parent).label === node;
|
||||
}
|
||||
|
||||
function isLabelOfLabeledStatement(node: Node): boolean {
|
||||
return node.kind === SyntaxKind.Identifier &&
|
||||
node.parent.kind === SyntaxKind.LabeledStatement &&
|
||||
(<LabeledStatement>node.parent).label === node;
|
||||
}
|
||||
|
||||
export function isLabelName(node: Node): boolean {
|
||||
return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node);
|
||||
}
|
||||
|
||||
export function isRightSideOfQualifiedName(node: Node) {
|
||||
return node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node;
|
||||
}
|
||||
|
||||
export function isRightSideOfPropertyAccess(node: Node) {
|
||||
return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node;
|
||||
}
|
||||
|
||||
export function isNameOfModuleDeclaration(node: Node) {
|
||||
return node.parent.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node.parent).name === node;
|
||||
}
|
||||
|
||||
export function isNameOfFunctionDeclaration(node: Node): boolean {
|
||||
return node.kind === SyntaxKind.Identifier &&
|
||||
isFunctionLike(node.parent) && (<FunctionLikeDeclaration>node.parent).name === node;
|
||||
}
|
||||
|
||||
export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: Node): boolean {
|
||||
if (node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) {
|
||||
switch (node.parent.kind) {
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertySignature:
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
case SyntaxKind.EnumMember:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return (<Declaration>node.parent).name === node;
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
return (<ElementAccessExpression>node.parent).argumentExpression === node;
|
||||
case SyntaxKind.ComputedPropertyName:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isNameOfExternalModuleImportOrDeclaration(node: Node): boolean {
|
||||
if (node.kind === SyntaxKind.StringLiteral) {
|
||||
return isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isExpressionOfExternalModuleImportEqualsDeclaration(node: Node) {
|
||||
return isExternalModuleImportEqualsDeclaration(node.parent.parent) &&
|
||||
getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node;
|
||||
}
|
||||
|
||||
/** Returns true if the position is within a comment */
|
||||
export function isInsideComment(sourceFile: SourceFile, token: Node, position: number): boolean {
|
||||
// The position has to be: 1. in the leading trivia (before token.getStart()), and 2. within a comment
|
||||
return position <= token.getStart(sourceFile) &&
|
||||
(isInsideCommentRange(getTrailingCommentRanges(sourceFile.text, token.getFullStart())) ||
|
||||
isInsideCommentRange(getLeadingCommentRanges(sourceFile.text, token.getFullStart())));
|
||||
|
||||
function isInsideCommentRange(comments: CommentRange[]): boolean {
|
||||
return forEach(comments, comment => {
|
||||
// either we are 1. completely inside the comment, or 2. at the end of the comment
|
||||
if (comment.pos < position && position < comment.end) {
|
||||
return true;
|
||||
}
|
||||
else if (position === comment.end) {
|
||||
const text = sourceFile.text;
|
||||
const width = comment.end - comment.pos;
|
||||
// is single line comment or just /*
|
||||
if (width <= 2 || text.charCodeAt(comment.pos + 1) === CharacterCodes.slash) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// is unterminated multi-line comment
|
||||
return !(text.charCodeAt(comment.end - 1) === CharacterCodes.slash &&
|
||||
text.charCodeAt(comment.end - 2) === CharacterCodes.asterisk);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getContainerNode(node: Node): Declaration {
|
||||
while (true) {
|
||||
node = node.parent;
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.SourceFile:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return <Declaration>node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getNodeKind(node: Node): string {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.SourceFile:
|
||||
return isExternalModule(<SourceFile>node) ? ScriptElementKind.moduleElement : ScriptElementKind.scriptElement;
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return ScriptElementKind.moduleElement;
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
return ScriptElementKind.classElement;
|
||||
case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement;
|
||||
case SyntaxKind.TypeAliasDeclaration: return ScriptElementKind.typeElement;
|
||||
case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement;
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return getKindOfVariableDeclaration(<VariableDeclaration>node);
|
||||
case SyntaxKind.BindingElement:
|
||||
return getKindOfVariableDeclaration(<VariableDeclaration>getRootDeclaration(node));
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
return ScriptElementKind.functionElement;
|
||||
case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement;
|
||||
case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement;
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
return ScriptElementKind.memberFunctionElement;
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertySignature:
|
||||
return ScriptElementKind.memberVariableElement;
|
||||
case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement;
|
||||
case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement;
|
||||
case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement;
|
||||
case SyntaxKind.Constructor: return ScriptElementKind.constructorImplementationElement;
|
||||
case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement;
|
||||
case SyntaxKind.EnumMember: return ScriptElementKind.enumMemberElement;
|
||||
case SyntaxKind.Parameter: return hasModifier(node, ModifierFlags.ParameterPropertyModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement;
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
case SyntaxKind.ImportSpecifier:
|
||||
case SyntaxKind.ImportClause:
|
||||
case SyntaxKind.ExportSpecifier:
|
||||
case SyntaxKind.NamespaceImport:
|
||||
return ScriptElementKind.alias;
|
||||
case SyntaxKind.JSDocTypedefTag:
|
||||
return ScriptElementKind.typeElement;
|
||||
default:
|
||||
return ScriptElementKind.unknown;
|
||||
}
|
||||
|
||||
function getKindOfVariableDeclaration(v: VariableDeclaration): string {
|
||||
return isConst(v)
|
||||
? ScriptElementKind.constElement
|
||||
: isLet(v)
|
||||
? ScriptElementKind.letElement
|
||||
: ScriptElementKind.variableElement;
|
||||
}
|
||||
}
|
||||
|
||||
export function getStringLiteralTypeForNode(node: StringLiteral | LiteralTypeNode, typeChecker: TypeChecker): LiteralType {
|
||||
const searchNode = node.parent.kind === SyntaxKind.LiteralType ? <LiteralTypeNode>node.parent : node;
|
||||
const type = typeChecker.getTypeAtLocation(searchNode);
|
||||
if (type && type.flags & TypeFlags.StringLiteral) {
|
||||
return <LiteralType>type;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isThis(node: Node): boolean {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ThisKeyword:
|
||||
// case SyntaxKind.ThisType: TODO: GH#9267
|
||||
return true;
|
||||
case SyntaxKind.Identifier:
|
||||
// 'this' as a parameter
|
||||
return (node as Identifier).originalKeywordKind === SyntaxKind.ThisKeyword && node.parent.kind === SyntaxKind.Parameter;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Matches the beginning of a triple slash directive
|
||||
const tripleSlashDirectivePrefixRegex = /^\/\/\/\s*</;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user