mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 11:35:42 -06:00
3068 lines
136 KiB
TypeScript
3068 lines
136 KiB
TypeScript
/// <reference path="..\compiler\types.ts"/>
|
|
/// <reference path="..\compiler\core.ts"/>
|
|
/// <reference path="..\compiler\scanner.ts"/>
|
|
/// <reference path="..\compiler\parser.ts"/>
|
|
/// <reference path="..\compiler\checker.ts"/>
|
|
|
|
/// <reference path='syntax\incrementalParser.ts' />
|
|
/// <reference path='outliningElementsCollector.ts' />
|
|
/// <reference path='getScriptLexicalStructureWalker.ts' />
|
|
/// <reference path='braceMatcher.ts' />
|
|
/// <reference path='breakpoints.ts' />
|
|
/// <reference path='indentation.ts' />
|
|
/// <reference path='formatting\formatting.ts' />
|
|
|
|
/// <reference path='core\references.ts' />
|
|
/// <reference path='resources\references.ts' />
|
|
/// <reference path='text\references.ts' />
|
|
/// <reference path='syntax\references.ts' />
|
|
/// <reference path='compiler\diagnostics.ts' />
|
|
/// <reference path='compiler\hashTable.ts' />
|
|
/// <reference path='compiler\ast.ts' />
|
|
/// <reference path='compiler\astWalker.ts' />
|
|
/// <reference path='compiler\astHelpers.ts' />
|
|
/// <reference path='compiler\types.ts' />
|
|
/// <reference path='compiler\pathUtils.ts' />
|
|
|
|
module ts {
|
|
|
|
export interface Node {
|
|
getSourceFile(): SourceFile;
|
|
getChildCount(): number;
|
|
getChildAt(index: number): Node;
|
|
getChildren(): Node[];
|
|
getStart(): number;
|
|
getFullStart(): number;
|
|
getEnd(): number;
|
|
getWidth(): number;
|
|
getFullWidth(): number;
|
|
getLeadingTriviaWidth(): number;
|
|
getFullText(): string;
|
|
getFirstToken(): Node;
|
|
getLastToken(): Node;
|
|
}
|
|
|
|
export interface Symbol {
|
|
getFlags(): SymbolFlags;
|
|
getName(): string;
|
|
getDeclarations(): Declaration[];
|
|
}
|
|
|
|
export interface Type {
|
|
getFlags(): TypeFlags;
|
|
getSymbol(): Symbol;
|
|
getProperties(): Symbol[];
|
|
getProperty(propertyName: string): Symbol;
|
|
getApparentProperties(): Symbol[];
|
|
getCallSignatures(): Signature[];
|
|
getConstructSignatures(): Signature[];
|
|
getStringIndexType(): Type;
|
|
getNumberIndexType(): Type;
|
|
}
|
|
|
|
export interface Signature {
|
|
getDeclaration(): SignatureDeclaration;
|
|
getTypeParameters(): Type[];
|
|
getParameters(): Symbol[];
|
|
getReturnType(): Type;
|
|
}
|
|
|
|
export interface SourceFile {
|
|
getSourceUnit(): TypeScript.SourceUnitSyntax;
|
|
getSyntaxTree(): TypeScript.SyntaxTree;
|
|
update(scriptSnapshot: TypeScript.IScriptSnapshot, version: number, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): SourceFile;
|
|
}
|
|
|
|
var scanner: Scanner = createScanner(ScriptTarget.ES5);
|
|
|
|
var emptyArray: any [] = [];
|
|
|
|
function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject {
|
|
var node = <NodeObject> new (getNodeConstructor(kind))();
|
|
node.pos = pos;
|
|
node.end = end;
|
|
node.flags = flags;
|
|
node.parent = parent;
|
|
return node;
|
|
}
|
|
|
|
class NodeObject implements Node {
|
|
public kind: SyntaxKind;
|
|
public pos: number;
|
|
public end: number;
|
|
public flags: NodeFlags;
|
|
public parent: Node;
|
|
private _children: Node[];
|
|
|
|
public getSourceFile(): SourceFile {
|
|
var node: Node = this;
|
|
while (node.kind !== SyntaxKind.SourceFile) node = node.parent;
|
|
return <SourceFile>node;
|
|
}
|
|
|
|
public getStart(): number {
|
|
return getTokenPosOfNode(this);
|
|
}
|
|
|
|
public getFullStart(): number {
|
|
return this.pos;
|
|
}
|
|
|
|
public getEnd(): number {
|
|
return this.end;
|
|
}
|
|
|
|
public getWidth(): number {
|
|
return this.getEnd() - this.getStart();
|
|
}
|
|
|
|
public getFullWidth(): number {
|
|
return this.end - this.getFullStart();
|
|
}
|
|
|
|
public getLeadingTriviaWidth(): number {
|
|
return this.getStart() - this.pos;
|
|
}
|
|
|
|
public getFullText(): string {
|
|
return this.getSourceFile().text.substring(this.pos, this.end);
|
|
}
|
|
|
|
private addSyntheticNodes(nodes: Node[], pos: number, end: number): number {
|
|
scanner.setTextPos(pos);
|
|
while (pos < end) {
|
|
var token = scanner.scan();
|
|
var textPos = scanner.getTextPos();
|
|
var node = nodes.push(createNode(token, pos, textPos, NodeFlags.Synthetic, this));
|
|
pos = textPos;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
private createSyntaxList(nodes: NodeArray<Node>): Node {
|
|
var list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, NodeFlags.Synthetic, this);
|
|
list._children = [];
|
|
var pos = nodes.pos;
|
|
for (var i = 0, len = nodes.length; i < len; i++) {
|
|
var node = nodes[i];
|
|
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() {
|
|
if (this.kind > SyntaxKind.Missing) {
|
|
scanner.setText(this.getSourceFile().text);
|
|
var children: Node[] = [];
|
|
var pos = this.pos;
|
|
var processNode = (node: Node) => {
|
|
if (pos < node.pos) {
|
|
pos = this.addSyntheticNodes(children, pos, node.pos);
|
|
}
|
|
children.push(node);
|
|
pos = node.end;
|
|
};
|
|
var processNodes = (nodes: NodeArray<Node>) => {
|
|
if (pos < nodes.pos) {
|
|
pos = this.addSyntheticNodes(children, pos, nodes.pos);
|
|
}
|
|
children.push(this.createSyntaxList(<NodeArray<Node>>nodes));
|
|
pos = nodes.end;
|
|
};
|
|
forEachChild(this, processNode, processNodes);
|
|
if (pos < this.end) {
|
|
this.addSyntheticNodes(children, pos, this.end);
|
|
}
|
|
scanner.setText(undefined);
|
|
}
|
|
this._children = children || emptyArray;
|
|
}
|
|
|
|
public getChildCount(): number {
|
|
if (!this._children) this.createChildren();
|
|
return this._children.length;
|
|
}
|
|
|
|
public getChildAt(index: number): Node {
|
|
if (!this._children) this.createChildren();
|
|
return this._children[index];
|
|
}
|
|
|
|
public getChildren(): Node[] {
|
|
if (!this._children) this.createChildren();
|
|
return this._children;
|
|
}
|
|
|
|
public getFirstToken(): Node {
|
|
var children = this.getChildren();
|
|
for (var i = 0; i < children.length; i++) {
|
|
var child = children[i];
|
|
if (child.kind < SyntaxKind.Missing) return child;
|
|
if (child.kind > SyntaxKind.Missing) return child.getFirstToken();
|
|
}
|
|
}
|
|
|
|
public getLastToken(): Node {
|
|
var children = this.getChildren();
|
|
for (var i = children.length - 1; i >= 0; i--) {
|
|
var child = children[i];
|
|
if (child.kind < SyntaxKind.Missing) return child;
|
|
if (child.kind > SyntaxKind.Missing) return child.getLastToken();
|
|
}
|
|
}
|
|
}
|
|
|
|
class SymbolObject implements Symbol {
|
|
flags: SymbolFlags;
|
|
name: string;
|
|
declarations: Declaration[];
|
|
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;
|
|
}
|
|
}
|
|
|
|
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.getAugmentedPropertiesOfApparentType(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);
|
|
}
|
|
}
|
|
|
|
class SignatureObject implements Signature {
|
|
checker: TypeChecker;
|
|
declaration: SignatureDeclaration;
|
|
typeParameters: TypeParameter[];
|
|
parameters: Symbol[];
|
|
resolvedReturnType: Type;
|
|
minArgumentCount: number;
|
|
hasRestParameter: boolean;
|
|
hasStringLiterals: boolean;
|
|
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);
|
|
}
|
|
}
|
|
|
|
var incrementalParse: IncrementalParse = TypeScript.IncrementalParser.parse;
|
|
|
|
class SourceFileObject extends NodeObject implements SourceFile {
|
|
public filename: string;
|
|
public text: string;
|
|
public getLineAndCharacterFromPosition(position: number): { line: number; character: number } { return null; }
|
|
public getPositionFromLineAndCharacter(line: number, character: number): number { return -1; }
|
|
public amdDependencies: string[];
|
|
public referencedFiles: FileReference[];
|
|
public syntacticErrors: Diagnostic[];
|
|
public semanticErrors: Diagnostic[];
|
|
public hasNoDefaultLib: boolean;
|
|
public externalModuleIndicator: Node; // The first node that causes this file to be an external module
|
|
public nodeCount: number;
|
|
public identifierCount: number;
|
|
public symbolCount: number;
|
|
public statements: NodeArray<Statement>;
|
|
public version: number;
|
|
public isOpen: boolean;
|
|
public languageVersion: ScriptTarget;
|
|
public identifiers: Map<string>;
|
|
|
|
private syntaxTree: TypeScript.SyntaxTree;
|
|
private scriptSnapshot: TypeScript.IScriptSnapshot;
|
|
|
|
public getSourceUnit(): TypeScript.SourceUnitSyntax {
|
|
// If we don't have a script, create one from our parse tree.
|
|
return this.getSyntaxTree().sourceUnit();
|
|
}
|
|
|
|
public getLineMap(): TypeScript.LineMap {
|
|
return this.getSyntaxTree().lineMap();
|
|
}
|
|
|
|
public getSyntaxTree(): TypeScript.SyntaxTree {
|
|
if (!this.syntaxTree) {
|
|
var start = new Date().getTime();
|
|
|
|
this.syntaxTree = TypeScript.Parser.parse(
|
|
this.filename, TypeScript.SimpleText.fromScriptSnapshot(this.scriptSnapshot), this.languageVersion, this.isDeclareFile());
|
|
|
|
var time = new Date().getTime() - start;
|
|
|
|
//TypeScript.syntaxTreeParseTime += time;
|
|
}
|
|
|
|
return this.syntaxTree;
|
|
}
|
|
|
|
private isDeclareFile(): boolean {
|
|
return TypeScript.isDTSFile(this.filename);
|
|
}
|
|
|
|
public update(scriptSnapshot: TypeScript.IScriptSnapshot, version: number, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): SourceFile {
|
|
// See if we are currently holding onto a syntax tree. We may not be because we're
|
|
// either a closed file, or we've just been lazy and haven't had to create the syntax
|
|
// tree yet. Access the field instead of the method so we don't accidently realize
|
|
// the old syntax tree.
|
|
var oldSyntaxTree = this.syntaxTree;
|
|
|
|
if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) {
|
|
var oldText = this.scriptSnapshot;
|
|
var newText = scriptSnapshot;
|
|
|
|
TypeScript.Debug.assert((oldText.getLength() - textChangeRange.span().length() + textChangeRange.newLength()) === newText.getLength());
|
|
|
|
if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) {
|
|
var oldTextPrefix = oldText.getText(0, textChangeRange.span().start());
|
|
var newTextPrefix = newText.getText(0, textChangeRange.span().start());
|
|
TypeScript.Debug.assert(oldTextPrefix === newTextPrefix);
|
|
|
|
var oldTextSuffix = oldText.getText(textChangeRange.span().end(), oldText.getLength());
|
|
var newTextSuffix = newText.getText(textChangeRange.newSpan().end(), newText.getLength());
|
|
TypeScript.Debug.assert(oldTextSuffix === newTextSuffix);
|
|
}
|
|
}
|
|
|
|
var text = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
|
|
|
|
// If we don't have a text change, or we don't have an old syntax tree, then do a full
|
|
// parse. Otherwise, do an incremental parse.
|
|
var newSyntaxTree = !textChangeRange || !oldSyntaxTree
|
|
? TypeScript.Parser.parse(this.filename, text, this.languageVersion, TypeScript.isDTSFile(this.filename))
|
|
: TypeScript.IncrementalParser.parse(oldSyntaxTree, textChangeRange, text);
|
|
|
|
return SourceFileObject.createSourceFileObject(this.filename, scriptSnapshot, this.languageVersion, version, isOpen, newSyntaxTree);
|
|
}
|
|
|
|
public static createSourceFileObject(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, languageVersion: ScriptTarget, version: number, isOpen: boolean, syntaxTree?: TypeScript.SyntaxTree) {
|
|
var newSourceFile = <SourceFileObject><any>createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), languageVersion, version, isOpen);
|
|
newSourceFile.scriptSnapshot = scriptSnapshot;
|
|
newSourceFile.syntaxTree = syntaxTree;
|
|
return newSourceFile;
|
|
}
|
|
}
|
|
|
|
export interface Logger {
|
|
information(): boolean;
|
|
debug(): boolean;
|
|
warning(): boolean;
|
|
error(): boolean;
|
|
fatal(): boolean;
|
|
log(s: string): void;
|
|
}
|
|
|
|
//
|
|
// Public interface of the host of a language service instance.
|
|
//
|
|
export interface LanguageServiceHost extends Logger {
|
|
getCompilationSettings(): CompilerOptions;
|
|
getScriptFileNames(): string[];
|
|
getScriptVersion(fileName: string): number;
|
|
getScriptIsOpen(fileName: string): boolean;
|
|
getScriptSnapshot(fileName: string): TypeScript.IScriptSnapshot;
|
|
getLocalizedDiagnosticMessages(): any;
|
|
getCancellationToken(): CancellationToken;
|
|
}
|
|
|
|
//
|
|
// Public services of a language service instance associated
|
|
// with a language service host instance
|
|
//
|
|
export interface LanguageService {
|
|
// Note: refresh is a no-op now. It is only around for back compat purposes.
|
|
refresh(): void;
|
|
|
|
cleanupSemanticCache(): void;
|
|
|
|
getSyntacticDiagnostics(fileName: string): Diagnostic[];
|
|
getSemanticDiagnostics(fileName: string): Diagnostic[];
|
|
getCompilerOptionsDiagnostics(): Diagnostic[];
|
|
|
|
getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): CompletionInfo;
|
|
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
|
|
|
|
getTypeAtPosition(fileName: string, position: number): TypeInfo;
|
|
|
|
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): SpanInfo;
|
|
|
|
getBreakpointStatementAtPosition(fileName: string, position: number): SpanInfo;
|
|
|
|
getSignatureAtPosition(fileName: string, position: number): SignatureInfo;
|
|
|
|
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
|
|
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
|
|
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[];
|
|
getImplementorsAtPosition(fileName: string, position: number): ReferenceEntry[];
|
|
|
|
getNavigateToItems(searchValue: string): NavigateToItem[];
|
|
getScriptLexicalStructure(fileName: string): NavigateToItem[];
|
|
|
|
getOutliningRegions(fileName: string): OutliningSpan[];
|
|
getBraceMatchingAtPosition(fileName: string, position: number): TypeScript.TextSpan[];
|
|
getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number;
|
|
|
|
getFormattingEditsForRange(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[];
|
|
getFormattingEditsForDocument(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[];
|
|
getFormattingEditsOnPaste(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[];
|
|
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextEdit[];
|
|
|
|
getEmitOutput(fileName: string): EmitOutput;
|
|
|
|
//getSyntaxTree(fileName: string): TypeScript.SyntaxTree;
|
|
|
|
dispose(): void;
|
|
}
|
|
|
|
export interface ReferenceEntry {
|
|
fileName: string;
|
|
minChar: number;
|
|
limChar: number;
|
|
isWriteAccess: boolean;
|
|
}
|
|
|
|
export interface NavigateToItem {
|
|
name: string;
|
|
kind: string; // see ScriptElementKind
|
|
kindModifiers: string; // see ScriptElementKindModifier, comma separated
|
|
matchKind: string;
|
|
fileName: string;
|
|
minChar: number;
|
|
limChar: number;
|
|
additionalSpans?: SpanInfo[];
|
|
containerName: string;
|
|
containerKind: string; // see ScriptElementKind
|
|
}
|
|
|
|
export interface TextEdit {
|
|
minChar: number;
|
|
limChar: number;
|
|
text: string;
|
|
}
|
|
|
|
export interface EditorOptions {
|
|
IndentSize: number;
|
|
TabSize: number;
|
|
NewLineCharacter: string;
|
|
ConvertTabsToSpaces: boolean;
|
|
}
|
|
|
|
export interface FormatCodeOptions extends EditorOptions {
|
|
InsertSpaceAfterCommaDelimiter: boolean;
|
|
InsertSpaceAfterSemicolonInForStatements: boolean;
|
|
InsertSpaceBeforeAndAfterBinaryOperators: boolean;
|
|
InsertSpaceAfterKeywordsInControlFlowStatements: boolean;
|
|
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean;
|
|
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
|
|
PlaceOpenBraceOnNewLineForFunctions: boolean;
|
|
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
|
|
}
|
|
|
|
export interface DefinitionInfo {
|
|
fileName: string;
|
|
minChar: number;
|
|
limChar: number;
|
|
kind: string;
|
|
name: string;
|
|
containerKind: string;
|
|
containerName: string;
|
|
}
|
|
|
|
export interface MemberName {
|
|
prefix: string;
|
|
suffix: string;
|
|
text: string;
|
|
}
|
|
|
|
export interface TypeInfo {
|
|
memberName: MemberName;
|
|
docComment: string;
|
|
fullSymbolName: string;
|
|
kind: string;
|
|
minChar: number;
|
|
limChar: number;
|
|
}
|
|
|
|
export interface SpanInfo {
|
|
minChar: number;
|
|
limChar: number;
|
|
// text?: string;
|
|
}
|
|
|
|
export interface SignatureInfo {
|
|
actual: ActualSignatureInfo;
|
|
formal: FormalSignatureItemInfo[]; // Formal signatures
|
|
activeFormal: number; // Index of the "best match" formal signature
|
|
}
|
|
|
|
export interface FormalSignatureItemInfo {
|
|
signatureInfo: string;
|
|
typeParameters: FormalTypeParameterInfo[];
|
|
parameters: FormalParameterInfo[]; // Array of parameters
|
|
docComment: string; // Help for the signature
|
|
}
|
|
|
|
export interface FormalTypeParameterInfo {
|
|
name: string; // Type parameter name
|
|
docComment: string; // Comments that contain help for the parameter
|
|
minChar: number; // minChar for parameter info in the formal signature info string
|
|
limChar: number; // lim char for parameter info in the formal signature info string
|
|
}
|
|
|
|
export interface FormalParameterInfo {
|
|
name: string; // Parameter name
|
|
isVariable: boolean; // true if parameter is var args
|
|
docComment: string; // Comments that contain help for the parameter
|
|
minChar: number; // minChar for parameter info in the formal signature info string
|
|
limChar: number; // lim char for parameter info in the formal signature info string
|
|
}
|
|
|
|
export interface ActualSignatureInfo {
|
|
parameterMinChar: number;
|
|
parameterLimChar: number;
|
|
currentParameterIsTypeParameter: boolean; // current parameter is a type argument or a normal argument
|
|
currentParameter: number; // Index of active parameter in "parameters" or "typeParamters" array
|
|
}
|
|
|
|
export interface CompletionInfo {
|
|
isMemberCompletion: boolean;
|
|
entries: CompletionEntry[];
|
|
}
|
|
|
|
export interface CompletionEntry {
|
|
name: string;
|
|
kind: string; // see ScriptElementKind
|
|
kindModifiers: string; // see ScriptElementKindModifier, comma separated
|
|
}
|
|
|
|
export interface CompletionEntryDetails {
|
|
name: string;
|
|
kind: string; // see ScriptElementKind
|
|
kindModifiers: string; // see ScriptElementKindModifier, comma separated
|
|
type: string;
|
|
fullSymbolName: string;
|
|
docComment: string;
|
|
}
|
|
|
|
export enum EmitOutputResult {
|
|
Succeeded,
|
|
FailedBecauseOfSyntaxErrors,
|
|
FailedBecauseOfCompilerOptionsErrors,
|
|
FailedToGenerateDeclarationsBecauseOfSemanticErrors
|
|
}
|
|
|
|
export interface EmitOutput {
|
|
outputFiles: OutputFile[];
|
|
emitOutputResult: EmitOutputResult;
|
|
}
|
|
|
|
export enum OutputFileType {
|
|
JavaScript,
|
|
SourceMap,
|
|
Declaration
|
|
}
|
|
|
|
export interface OutputFile {
|
|
name: string;
|
|
writeByteOrderMark: boolean;
|
|
text: string;
|
|
fileType: OutputFileType;
|
|
sourceMapOutput: any;
|
|
}
|
|
|
|
export enum EndOfLineState {
|
|
Start,
|
|
InMultiLineCommentTrivia,
|
|
InSingleQuoteStringLiteral,
|
|
InDoubleQuoteStringLiteral,
|
|
EndingWithDotToken,
|
|
}
|
|
|
|
export enum TokenClass {
|
|
Punctuation,
|
|
Keyword,
|
|
Operator,
|
|
Comment,
|
|
Whitespace,
|
|
Identifier,
|
|
NumberLiteral,
|
|
StringLiteral,
|
|
RegExpLiteral,
|
|
}
|
|
|
|
export interface ClassificationResult {
|
|
finalLexState: EndOfLineState;
|
|
entries: ClassificationInfo[];
|
|
}
|
|
|
|
export interface ClassificationInfo {
|
|
length: number;
|
|
classification: TokenClass;
|
|
}
|
|
|
|
export interface Classifier {
|
|
getClassificationsForLine(text: string, lexState: EndOfLineState): ClassificationResult;
|
|
}
|
|
|
|
export interface DocumentRegistry {
|
|
acquireDocument(
|
|
filename: string,
|
|
compilationSettings: CompilerOptions,
|
|
scriptSnapshot: TypeScript.IScriptSnapshot,
|
|
version: number,
|
|
isOpen: boolean): SourceFile;
|
|
|
|
updateDocument(
|
|
sourceFile: SourceFile,
|
|
filename: string,
|
|
compilationSettings: CompilerOptions,
|
|
scriptSnapshot: TypeScript.IScriptSnapshot,
|
|
version: number,
|
|
isOpen: boolean,
|
|
textChangeRange: TypeScript.TextChangeRange
|
|
): SourceFile;
|
|
|
|
releaseDocument(filename: string, compilationSettings: CompilerOptions): void
|
|
}
|
|
|
|
// TODO: move these to enums
|
|
export class ScriptElementKind {
|
|
static unknown = "";
|
|
|
|
// predefined type (void) or keyword (class)
|
|
static keyword = "keyword";
|
|
|
|
// top level script node
|
|
static scriptElement = "script";
|
|
|
|
// module foo {}
|
|
static moduleElement = "module";
|
|
|
|
// class X {}
|
|
static classElement = "class";
|
|
|
|
// interface Y {}
|
|
static interfaceElement = "interface";
|
|
|
|
// enum E
|
|
static enumElement = "enum";
|
|
|
|
// Inside module and script only
|
|
// var v = ..
|
|
static variableElement = "var";
|
|
|
|
// Inside function
|
|
static localVariableElement = "local var";
|
|
|
|
// Inside module and script only
|
|
// function f() { }
|
|
static functionElement = "function";
|
|
|
|
// Inside function
|
|
static localFunctionElement = "local function";
|
|
|
|
// class X { [public|private]* foo() {} }
|
|
static memberFunctionElement = "method";
|
|
|
|
// class X { [public|private]* [get|set] foo:number; }
|
|
static memberGetAccessorElement = "getter";
|
|
static memberSetAccessorElement = "setter";
|
|
|
|
// class X { [public|private]* foo:number; }
|
|
// interface Y { foo:number; }
|
|
static memberVariableElement = "property";
|
|
|
|
// class X { constructor() { } }
|
|
static constructorImplementationElement = "constructor";
|
|
|
|
// interface Y { ():number; }
|
|
static callSignatureElement = "call";
|
|
|
|
// interface Y { []:number; }
|
|
static indexSignatureElement = "index";
|
|
|
|
// interface Y { new():Y; }
|
|
static constructSignatureElement = "construct";
|
|
|
|
// function foo(*Y*: string)
|
|
static parameterElement = "parameter";
|
|
|
|
static typeParameterElement = "type parameter";
|
|
|
|
static primitiveType = "primitive type";
|
|
|
|
static label = "label";
|
|
}
|
|
|
|
export class ScriptElementKindModifier {
|
|
static none = "";
|
|
static publicMemberModifier = "public";
|
|
static privateMemberModifier = "private";
|
|
static exportedModifier = "export";
|
|
static ambientModifier = "declare";
|
|
static staticModifier = "static";
|
|
}
|
|
|
|
export class MatchKind {
|
|
static none: string = null;
|
|
static exact = "exact";
|
|
static subString = "substring";
|
|
static prefix = "prefix";
|
|
}
|
|
|
|
interface IncrementalParse {
|
|
(oldSyntaxTree: TypeScript.SyntaxTree, textChangeRange: TypeScript.TextChangeRange, newText: TypeScript.ISimpleText): TypeScript.SyntaxTree
|
|
}
|
|
|
|
/// Language Service
|
|
|
|
interface CompletionSession {
|
|
filename: string; // the file where the completion was requested
|
|
position: number; // position in the file where the completion was requested
|
|
entries: CompletionEntry[]; // entries for this completion
|
|
symbols: Map<Symbol>; // symbols by entry name map
|
|
location: Node; // the node where the completion was requested
|
|
typeChecker: TypeChecker;// the typeChecker used to generate this completion
|
|
}
|
|
|
|
interface FormattingOptions {
|
|
useTabs: boolean;
|
|
spacesPerTab: number;
|
|
indentSpaces: number;
|
|
newLineCharacter: string;
|
|
}
|
|
|
|
// Information about a specific host file.
|
|
interface HostFileInformation {
|
|
filename: string;
|
|
version: number;
|
|
isOpen: boolean;
|
|
sourceText?: TypeScript.IScriptSnapshot;
|
|
}
|
|
|
|
interface DocumentRegistryEntry {
|
|
sourceFile: SourceFile;
|
|
refCount: number;
|
|
owners: string[];
|
|
}
|
|
|
|
export function getDefaultCompilerOptions(): CompilerOptions {
|
|
// Set "ES5" target by default for language service
|
|
return {
|
|
target: ScriptTarget.ES5,
|
|
module: ModuleKind.None,
|
|
};
|
|
}
|
|
|
|
export function compareDataObjects(dst: any, src: any): boolean {
|
|
for (var e in dst) {
|
|
if (typeof dst[e] === "object") {
|
|
if (!compareDataObjects(dst[e], src[e]))
|
|
return false;
|
|
}
|
|
else if (typeof dst[e] !== "function") {
|
|
if (dst[e] !== src[e])
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export class OperationCanceledException { }
|
|
|
|
class CancellationTokenObject {
|
|
|
|
public static None: CancellationTokenObject = new CancellationTokenObject(null)
|
|
|
|
constructor(private cancellationToken: CancellationToken) {
|
|
}
|
|
|
|
public isCancellationRequested() {
|
|
return this.cancellationToken && this.cancellationToken.isCancellationRequested();
|
|
}
|
|
|
|
public throwIfCancellationRequested(): void {
|
|
if (this.isCancellationRequested()) {
|
|
throw new OperationCanceledException();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cache host information about scrip Should be refreshed
|
|
// at each language service public entry point, since we don't know when
|
|
// set of scripts handled by the host changes.
|
|
class HostCache {
|
|
private filenameToEntry: Map<HostFileInformation>;
|
|
private _compilationSettings: CompilerOptions;
|
|
|
|
constructor(private host: LanguageServiceHost) {
|
|
// script id => script index
|
|
this.filenameToEntry = {};
|
|
|
|
var filenames = host.getScriptFileNames();
|
|
for (var i = 0, n = filenames.length; i < n; i++) {
|
|
var filename = filenames[i];
|
|
this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)] = {
|
|
filename: filename,
|
|
version: host.getScriptVersion(filename),
|
|
isOpen: host.getScriptIsOpen(filename)
|
|
};
|
|
}
|
|
|
|
this._compilationSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
|
|
}
|
|
|
|
public compilationSettings() {
|
|
return this._compilationSettings;
|
|
}
|
|
|
|
public getEntry(filename: string): HostFileInformation {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
return lookUp(this.filenameToEntry, filename);
|
|
}
|
|
|
|
public contains(filename: string): boolean {
|
|
return !!this.getEntry(filename);
|
|
}
|
|
|
|
public getHostfilename(filename: string) {
|
|
var hostCacheEntry = this.getEntry(filename);
|
|
if (hostCacheEntry) {
|
|
return hostCacheEntry.filename;
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
public getFilenames(): string[] {
|
|
var fileNames: string[] = [];
|
|
|
|
forEachKey(this.filenameToEntry, key => {
|
|
if (hasProperty(this.filenameToEntry, key))
|
|
fileNames.push(key);
|
|
});
|
|
|
|
return fileNames;
|
|
}
|
|
|
|
public getVersion(filename: string): number {
|
|
return this.getEntry(filename).version;
|
|
}
|
|
|
|
public isOpen(filename: string): boolean {
|
|
return this.getEntry(filename).isOpen;
|
|
}
|
|
|
|
public getScriptSnapshot(filename: string): TypeScript.IScriptSnapshot {
|
|
var file = this.getEntry(filename);
|
|
if (!file.sourceText) {
|
|
file.sourceText = this.host.getScriptSnapshot(file.filename);
|
|
}
|
|
return file.sourceText;
|
|
}
|
|
|
|
public getScriptTextChangeRangeSinceVersion(filename: string, lastKnownVersion: number): TypeScript.TextChangeRange {
|
|
var currentVersion = this.getVersion(filename);
|
|
if (lastKnownVersion === currentVersion) {
|
|
return TypeScript.TextChangeRange.unchanged; // "No changes"
|
|
}
|
|
|
|
var scriptSnapshot = this.getScriptSnapshot(filename);
|
|
return scriptSnapshot.getTextChangeRangeSinceVersion(lastKnownVersion);
|
|
}
|
|
}
|
|
|
|
class SyntaxTreeCache {
|
|
private hostCache: HostCache;
|
|
|
|
// For our syntactic only features, we also keep a cache of the syntax tree for the
|
|
// currently edited file.
|
|
private currentfilename: string = "";
|
|
private currentFileVersion: number = -1;
|
|
private currentSourceFile: SourceFile = null;
|
|
private currentFileSyntaxTree: TypeScript.SyntaxTree = null;
|
|
private currentFileScriptSnapshot: TypeScript.IScriptSnapshot = null;
|
|
|
|
constructor(private host: LanguageServiceHost) {
|
|
this.hostCache = new HostCache(host);
|
|
}
|
|
|
|
private initialize(filename: string) {
|
|
// ensure that both source file and syntax tree are either initialized or not initialized
|
|
Debug.assert(!!this.currentFileSyntaxTree === !!this.currentSourceFile);
|
|
this.hostCache = new HostCache(this.host);
|
|
|
|
var version = this.hostCache.getVersion(filename);
|
|
var syntaxTree: TypeScript.SyntaxTree = null;
|
|
var sourceFile: SourceFile;
|
|
|
|
if (this.currentFileSyntaxTree === null || this.currentfilename !== filename) {
|
|
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
|
|
syntaxTree = this.createSyntaxTree(filename, scriptSnapshot);
|
|
sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true);
|
|
|
|
fixupParentReferences(sourceFile);
|
|
}
|
|
else if (this.currentFileVersion !== version) {
|
|
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
|
|
syntaxTree = this.updateSyntaxTree(filename, scriptSnapshot, this.currentFileSyntaxTree, this.currentFileVersion);
|
|
|
|
var editRange = this.hostCache.getScriptTextChangeRangeSinceVersion(filename, this.currentFileVersion);
|
|
sourceFile = !editRange
|
|
? createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true)
|
|
: this.currentSourceFile.update(scriptSnapshot, version, /*isOpen*/ true, editRange);
|
|
|
|
fixupParentReferences(sourceFile);
|
|
}
|
|
|
|
if (syntaxTree !== null) {
|
|
Debug.assert(sourceFile);
|
|
// All done, ensure state is up to date
|
|
this.currentFileScriptSnapshot = scriptSnapshot;
|
|
this.currentFileVersion = version;
|
|
this.currentfilename = filename;
|
|
this.currentFileSyntaxTree = syntaxTree;
|
|
this.currentSourceFile = sourceFile;
|
|
}
|
|
|
|
function fixupParentReferences(sourceFile: SourceFile) {
|
|
// normally parent references are set during binding.
|
|
// however here SourceFile data is used only for syntactic features so running the whole binding process is an overhead.
|
|
// walk over the nodes and set parent references
|
|
var parent: Node = sourceFile;
|
|
function walk(n: Node): void {
|
|
n.parent = parent;
|
|
|
|
var saveParent = parent;
|
|
parent = n;
|
|
forEachChild(n, walk);
|
|
parent = saveParent;
|
|
}
|
|
forEachChild(sourceFile, walk);
|
|
}
|
|
}
|
|
|
|
public getCurrentFileSyntaxTree(filename: string): TypeScript.SyntaxTree {
|
|
this.initialize(filename);
|
|
return this.currentFileSyntaxTree;
|
|
}
|
|
|
|
public getCurrentSourceFile(filename: string): SourceFile {
|
|
this.initialize(filename);
|
|
return this.currentSourceFile;
|
|
}
|
|
|
|
public getCurrentScriptSnapshot(filename: string): TypeScript.IScriptSnapshot {
|
|
// update currentFileScriptSnapshot as a part of 'getCurrentFileSyntaxTree' call
|
|
this.getCurrentFileSyntaxTree(filename);
|
|
return this.currentFileScriptSnapshot;
|
|
}
|
|
|
|
private createSyntaxTree(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot): TypeScript.SyntaxTree {
|
|
var text = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
|
|
|
|
// For the purposes of features that use this syntax tree, we can just use the default
|
|
// compilation settings. The features only use the syntax (and not the diagnostics),
|
|
// and the syntax isn't affected by the compilation settings.
|
|
var syntaxTree = TypeScript.Parser.parse(filename, text, getDefaultCompilerOptions().target, TypeScript.isDTSFile(filename));
|
|
|
|
return syntaxTree;
|
|
}
|
|
|
|
private updateSyntaxTree(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, previousSyntaxTree: TypeScript.SyntaxTree, previousFileVersion: number): TypeScript.SyntaxTree {
|
|
var editRange = this.hostCache.getScriptTextChangeRangeSinceVersion(filename, previousFileVersion);
|
|
|
|
// Debug.assert(newLength >= 0);
|
|
|
|
// The host considers the entire buffer changed. So parse a completely new tree.
|
|
if (editRange === null) {
|
|
return this.createSyntaxTree(filename, scriptSnapshot);
|
|
}
|
|
|
|
var nextSyntaxTree = TypeScript.IncrementalParser.parse(
|
|
previousSyntaxTree, editRange, TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot));
|
|
|
|
this.ensureInvariants(filename, editRange, nextSyntaxTree, this.currentFileScriptSnapshot, scriptSnapshot);
|
|
|
|
return nextSyntaxTree;
|
|
}
|
|
|
|
private ensureInvariants(filename: string, editRange: TypeScript.TextChangeRange, incrementalTree: TypeScript.SyntaxTree, oldScriptSnapshot: TypeScript.IScriptSnapshot, newScriptSnapshot: TypeScript.IScriptSnapshot) {
|
|
// First, verify that the edit range and the script snapshots make sense.
|
|
|
|
// If this fires, then the edit range is completely bogus. Somehow the lengths of the
|
|
// old snapshot, the change range and the new snapshot aren't in sync. This is very
|
|
// bad.
|
|
var expectedNewLength = oldScriptSnapshot.getLength() - editRange.span().length() + editRange.newLength();
|
|
var actualNewLength = newScriptSnapshot.getLength();
|
|
|
|
function provideMoreDebugInfo() {
|
|
|
|
var debugInformation = ["expected length:", expectedNewLength, "and actual length:", actualNewLength, "are not equal\r\n"];
|
|
|
|
var oldSpan = editRange.span();
|
|
|
|
function prettyPrintString(s: string): string {
|
|
return '"' + s.replace(/\r/g, '\\r').replace(/\n/g, '\\n') + '"';
|
|
}
|
|
|
|
debugInformation.push('Edit range (old text) (start: ' + oldSpan.start() + ', end: ' + oldSpan.end() + ') \r\n');
|
|
debugInformation.push('Old text edit range contents: ' + prettyPrintString(oldScriptSnapshot.getText(oldSpan.start(), oldSpan.end())));
|
|
|
|
var newSpan = editRange.newSpan();
|
|
|
|
debugInformation.push('Edit range (new text) (start: ' + newSpan.start() + ', end: ' + newSpan.end() + ') \r\n');
|
|
debugInformation.push('New text edit range contents: ' + prettyPrintString(newScriptSnapshot.getText(newSpan.start(), newSpan.end())));
|
|
|
|
return debugInformation.join(' ');
|
|
}
|
|
|
|
Debug.assert(
|
|
expectedNewLength === actualNewLength,
|
|
"Expected length is different from actual!",
|
|
provideMoreDebugInfo);
|
|
|
|
if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) {
|
|
// If this fires, the text change range is bogus. It says the change starts at point
|
|
// 'X', but we can see a text difference *before* that point.
|
|
var oldPrefixText = oldScriptSnapshot.getText(0, editRange.span().start());
|
|
var newPrefixText = newScriptSnapshot.getText(0, editRange.span().start());
|
|
Debug.assert(oldPrefixText === newPrefixText, 'Expected equal prefix texts!');
|
|
|
|
// If this fires, the text change range is bogus. It says the change goes only up to
|
|
// point 'X', but we can see a text difference *after* that point.
|
|
var oldSuffixText = oldScriptSnapshot.getText(editRange.span().end(), oldScriptSnapshot.getLength());
|
|
var newSuffixText = newScriptSnapshot.getText(editRange.newSpan().end(), newScriptSnapshot.getLength());
|
|
Debug.assert(oldSuffixText === newSuffixText, 'Expected equal suffix texts!');
|
|
|
|
// Ok, text change range and script snapshots look ok. Let's verify that our
|
|
// incremental parsing worked properly.
|
|
//var normalTree = this.createSyntaxTree(filename, newScriptSnapshot);
|
|
//Debug.assert(normalTree.structuralEquals(incrementalTree), 'Expected equal incremental and normal trees');
|
|
|
|
// Ok, the trees looked good. So at least our incremental parser agrees with the
|
|
// normal parser. Now, verify that the incremental tree matches the contents of the
|
|
// script snapshot.
|
|
var incrementalTreeText = TypeScript.fullText(incrementalTree.sourceUnit());
|
|
var actualSnapshotText = newScriptSnapshot.getText(0, newScriptSnapshot.getLength());
|
|
Debug.assert(incrementalTreeText === actualSnapshotText, 'Expected full texts to be equal');
|
|
}
|
|
}
|
|
}
|
|
|
|
function createSourceFileFromScriptSnapshot(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, settings: CompilerOptions, version: number, isOpen: boolean) {
|
|
return SourceFileObject.createSourceFileObject(filename, scriptSnapshot, settings.target, version, isOpen);
|
|
}
|
|
|
|
export function createDocumentRegistry(): DocumentRegistry {
|
|
var buckets: Map<Map<DocumentRegistryEntry>> = {};
|
|
|
|
function getKeyFromCompilationSettings(settings: CompilerOptions): string {
|
|
return "_" + ScriptTarget[settings.target]; // + "|" + settings.propagateEnumConstantoString()
|
|
}
|
|
|
|
function getBucketForCompilationSettings(settings: CompilerOptions, createIfMissing: boolean): Map<DocumentRegistryEntry> {
|
|
var key = getKeyFromCompilationSettings(settings);
|
|
var bucket = lookUp(buckets, key);
|
|
if (!bucket && createIfMissing) {
|
|
buckets[key] = bucket = {};
|
|
}
|
|
return bucket;
|
|
}
|
|
|
|
function reportStats() {
|
|
var bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === '_').map(name => {
|
|
var entries = lookUp(buckets, name);
|
|
var sourceFiles: { name: string; refCount: number; references: string[]; }[] = [];
|
|
for (var i in entries) {
|
|
var entry = entries[i];
|
|
sourceFiles.push({
|
|
name: i,
|
|
refCount: entry.refCount,
|
|
references: entry.owners.slice(0)
|
|
});
|
|
}
|
|
sourceFiles.sort((x, y) => y.refCount - x.refCount);
|
|
return {
|
|
bucket: name,
|
|
sourceFiles: sourceFiles
|
|
};
|
|
});
|
|
return JSON.stringify(bucketInfoArray, null, 2);
|
|
}
|
|
|
|
function acquireDocument(
|
|
filename: string,
|
|
compilationSettings: CompilerOptions,
|
|
scriptSnapshot: TypeScript.IScriptSnapshot,
|
|
version: number,
|
|
isOpen: boolean): SourceFile {
|
|
|
|
var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true);
|
|
var entry = lookUp(bucket, filename);
|
|
if (!entry) {
|
|
var sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, compilationSettings, version, isOpen);
|
|
|
|
bucket[filename] = entry = {
|
|
sourceFile: sourceFile,
|
|
refCount: 0,
|
|
owners: []
|
|
};
|
|
}
|
|
entry.refCount++;
|
|
|
|
return entry.sourceFile;
|
|
}
|
|
|
|
function updateDocument(
|
|
sourceFile: SourceFile,
|
|
filename: string,
|
|
compilationSettings: CompilerOptions,
|
|
scriptSnapshot: TypeScript.IScriptSnapshot,
|
|
version: number,
|
|
isOpen: boolean,
|
|
textChangeRange: TypeScript.TextChangeRange
|
|
): SourceFile {
|
|
|
|
var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ false);
|
|
Debug.assert(bucket);
|
|
var entry = lookUp(bucket, filename);
|
|
Debug.assert(entry);
|
|
|
|
if (entry.sourceFile.isOpen === isOpen && entry.sourceFile.version === version) {
|
|
return entry.sourceFile;
|
|
}
|
|
|
|
entry.sourceFile = entry.sourceFile.update(scriptSnapshot, version, isOpen, textChangeRange);
|
|
return entry.sourceFile;
|
|
}
|
|
|
|
function releaseDocument(filename: string, compilationSettings: CompilerOptions): void {
|
|
var bucket = getBucketForCompilationSettings(compilationSettings, false);
|
|
Debug.assert(bucket);
|
|
|
|
var entry = lookUp(bucket, filename);
|
|
entry.refCount--;
|
|
|
|
Debug.assert(entry.refCount >= 0);
|
|
if (entry.refCount === 0) {
|
|
delete bucket[filename];
|
|
}
|
|
}
|
|
|
|
return {
|
|
acquireDocument: acquireDocument,
|
|
updateDocument: updateDocument,
|
|
releaseDocument: releaseDocument,
|
|
reportStats: reportStats
|
|
};
|
|
}
|
|
|
|
/// Helpers
|
|
function getTargetLabel(referenceNode: Node, labelName: string): Identifier {
|
|
while (referenceNode) {
|
|
if (referenceNode.kind === SyntaxKind.LabelledStatement && (<LabelledStatement>referenceNode).label.text === labelName) {
|
|
return (<LabelledStatement>referenceNode).label;
|
|
}
|
|
referenceNode = referenceNode.parent;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
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.LabelledStatement &&
|
|
(<LabelledStatement>node.parent).label === node;
|
|
}
|
|
|
|
function isLabelName(node: Node): boolean {
|
|
return isLabelOfLabeledStatement(node) || isJumpStatementTarget(node);
|
|
}
|
|
|
|
function isCallExpressionTarget(node: Node): boolean {
|
|
if (node.parent.kind === SyntaxKind.PropertyAccess && (<PropertyAccess>node.parent).right === node)
|
|
node = node.parent;
|
|
return node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).func === node;
|
|
}
|
|
|
|
function isNewExpressionTarget(node: Node): boolean {
|
|
if (node.parent.kind === SyntaxKind.PropertyAccess && (<PropertyAccess>node.parent).right === node)
|
|
node = node.parent;
|
|
return node.parent.kind === SyntaxKind.NewExpression && (<CallExpression>node.parent).func === node;
|
|
}
|
|
|
|
function isAnyFunction(node: Node): boolean {
|
|
switch (node.kind) {
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.Method:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.ArrowFunction:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isNameOfFunctionDeclaration(node: Node): boolean {
|
|
return node.kind === SyntaxKind.Identifier &&
|
|
isAnyFunction(node.parent) && (<FunctionDeclaration>node.parent).name === node;
|
|
}
|
|
|
|
/// Returns true if node is a name of an object literal property, e.g. "a" in x = { "a": 1 }
|
|
function isNameOfPropertyAssignment(node: Node): boolean {
|
|
return (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) &&
|
|
node.parent.kind === SyntaxKind.PropertyAssignment && (<PropertyDeclaration>node.parent).name === node;
|
|
}
|
|
|
|
function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: Node): boolean {
|
|
if (node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) {
|
|
switch (node.parent.kind) {
|
|
case SyntaxKind.Property:
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.EnumMember:
|
|
case SyntaxKind.Method:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
return (<Declaration>node.parent).name === node;
|
|
case SyntaxKind.IndexedAccess:
|
|
return (<IndexedAccess>node.parent).index === node;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isNameOfExternalModuleImportOrDeclaration(node: Node): boolean {
|
|
return node.kind === SyntaxKind.StringLiteral &&
|
|
((node.parent.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node.parent).name === node) ||
|
|
(node.parent.kind === SyntaxKind.ImportDeclaration && (<ImportDeclaration>node.parent).externalModuleName === node));
|
|
}
|
|
|
|
enum SearchMeaning {
|
|
Value = 0x1,
|
|
Type = 0x2,
|
|
Namespace = 0x4
|
|
}
|
|
|
|
// A cache of completion entries for keywords, these do not change between sessions
|
|
var keywordCompletions:CompletionEntry[] = [];
|
|
for (var i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) {
|
|
keywordCompletions.push({
|
|
name: tokenToString(i),
|
|
kind: ScriptElementKind.keyword,
|
|
kindModifiers: ScriptElementKindModifier.none
|
|
});
|
|
}
|
|
|
|
export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry): LanguageService {
|
|
var syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
|
|
var formattingRulesProvider: TypeScript.Services.Formatting.RulesProvider;
|
|
var hostCache: HostCache; // A cache of all the information about the files on the host side.
|
|
var program: Program;
|
|
// this checker is used to answer all LS questions except errors
|
|
var typeInfoResolver: TypeChecker;
|
|
// the sole purpose of this checkes is to reutrn semantic diagnostics
|
|
// creation is deferred - use getFullTypeCheckChecker to get instance
|
|
var fullTypeCheckChecker_doNotAccessDirectly: TypeChecker;
|
|
var useCaseSensitivefilenames = false;
|
|
var sourceFilesByName: Map<SourceFile> = {};
|
|
var documentRegistry = documentRegistry;
|
|
var cancellationToken = new CancellationTokenObject(host.getCancellationToken());
|
|
var activeCompletionSession: CompletionSession; // The current active completion session, used to get the completion entry details
|
|
|
|
// Check if the localized messages json is set, otherwise query the host for it
|
|
if (!TypeScript.LocalizedDiagnosticMessages) {
|
|
TypeScript.LocalizedDiagnosticMessages = host.getLocalizedDiagnosticMessages();
|
|
}
|
|
|
|
function getSourceFile(filename: string): SourceFile {
|
|
return lookUp(sourceFilesByName, filename);
|
|
}
|
|
|
|
function getFullTypeCheckChecker() {
|
|
return fullTypeCheckChecker_doNotAccessDirectly || (fullTypeCheckChecker_doNotAccessDirectly = program.getTypeChecker(/*fullTypeCheck*/ true));
|
|
}
|
|
|
|
function createCompilerHost(): CompilerHost {
|
|
return {
|
|
getSourceFile: (filename, languageVersion) => {
|
|
var sourceFile = getSourceFile(filename);
|
|
|
|
Debug.assert(!!sourceFile, "sourceFile can not be undefined");
|
|
|
|
return sourceFile;
|
|
},
|
|
getCancellationToken: () => cancellationToken,
|
|
getCanonicalFileName: (filename) => useCaseSensitivefilenames ? filename : filename.toLowerCase(),
|
|
useCaseSensitiveFileNames: () => useCaseSensitivefilenames,
|
|
getNewLine: () => "\r\n",
|
|
// Need something that doesn't depend on sys.ts here
|
|
getDefaultLibFilename: (): string => {
|
|
throw Error("TOD:: getDefaultLibfilename");
|
|
},
|
|
writeFile: (filename, data, writeByteOrderMark) => {
|
|
throw Error("TODO: write file");
|
|
},
|
|
getCurrentDirectory: (): string => {
|
|
throw Error("TODO: getCurrentDirectory");
|
|
}
|
|
};
|
|
}
|
|
|
|
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
|
|
return sourceFile && sourceFile.version === hostCache.getVersion(sourceFile.filename) && sourceFile.isOpen === hostCache.isOpen(sourceFile.filename);
|
|
}
|
|
|
|
function programUpToDate(): boolean {
|
|
// If we haven't create a program yet, then it is not up-to-date
|
|
if (!program) {
|
|
return false;
|
|
}
|
|
|
|
// If number of files in the program do not match, it is not up-to-date
|
|
var hostFilenames = hostCache.getFilenames();
|
|
if (program.getSourceFiles().length !== hostFilenames.length) {
|
|
return false;
|
|
}
|
|
|
|
// If any file is not up-to-date, then the whole program is not up-to-date
|
|
for (var i = 0, n = hostFilenames.length; i < n; i++) {
|
|
if (!sourceFileUpToDate(program.getSourceFile(hostFilenames[i]))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If the compilation settings do no match, then the program is not up-to-date
|
|
return compareDataObjects(program.getCompilerOptions(), hostCache.compilationSettings());
|
|
}
|
|
|
|
function synchronizeHostData(): void {
|
|
// Reset the cache at start of every refresh
|
|
hostCache = new HostCache(host);
|
|
|
|
// If the program is already up-to-date, we can reuse it
|
|
if (programUpToDate()) {
|
|
return;
|
|
}
|
|
|
|
var compilationSettings = hostCache.compilationSettings();
|
|
|
|
// Now, remove any files from the compiler that are no longer in the host.
|
|
var oldProgram = program;
|
|
if (oldProgram) {
|
|
var oldSettings = program.getCompilerOptions();
|
|
// If the language version changed, then that affects what types of things we parse. So
|
|
// we have to dump all syntax trees.
|
|
// TODO: handle propagateEnumConstants
|
|
// TODO: is module still needed
|
|
var settingsChangeAffectsSyntax = oldSettings.target !== compilationSettings.target || oldSettings.module !== compilationSettings.module;
|
|
|
|
var changesInCompilationSettingsAffectSyntax =
|
|
oldSettings && compilationSettings && !compareDataObjects(oldSettings, compilationSettings) && settingsChangeAffectsSyntax;
|
|
var oldSourceFiles = program.getSourceFiles();
|
|
|
|
for (var i = 0, n = oldSourceFiles.length; i < n; i++) {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
var filename = oldSourceFiles[i].filename;
|
|
if (!hostCache.contains(filename) || changesInCompilationSettingsAffectSyntax) {
|
|
documentRegistry.releaseDocument(filename, oldSettings);
|
|
delete sourceFilesByName[filename];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now, for every file the host knows about, either add the file (if the compiler
|
|
// doesn't know about it.). Or notify the compiler about any changes (if it does
|
|
// know about it.)
|
|
var hostfilenames = hostCache.getFilenames();
|
|
for (var i = 0, n = hostfilenames.length; i < n; i++) {
|
|
var filename = hostfilenames[i];
|
|
|
|
var version = hostCache.getVersion(filename);
|
|
var isOpen = hostCache.isOpen(filename);
|
|
var scriptSnapshot = hostCache.getScriptSnapshot(filename);
|
|
|
|
var sourceFile: SourceFile = getSourceFile(filename);
|
|
if (sourceFile) {
|
|
//
|
|
// If the sourceFile is the same, assume no update
|
|
//
|
|
if (sourceFileUpToDate(sourceFile)) {
|
|
continue;
|
|
}
|
|
|
|
// Only perform incremental parsing on open files that are being edited. If a file was
|
|
// open, but is now closed, we want to reparse entirely so we don't have any tokens that
|
|
// are holding onto expensive script snapshot instances on the host. Similarly, if a
|
|
// file was closed, then we always want to reparse. This is so our tree doesn't keep
|
|
// the old buffer alive that represented the file on disk (as the host has moved to a
|
|
// new text buffer).
|
|
var textChangeRange: TypeScript.TextChangeRange = null;
|
|
if (sourceFile.isOpen && isOpen) {
|
|
textChangeRange = hostCache.getScriptTextChangeRangeSinceVersion(filename, sourceFile.version);
|
|
}
|
|
|
|
sourceFile = documentRegistry.updateDocument(sourceFile, filename, compilationSettings, scriptSnapshot, version, isOpen, textChangeRange);
|
|
}
|
|
else {
|
|
sourceFile = documentRegistry.acquireDocument(filename, compilationSettings, scriptSnapshot, version, isOpen);
|
|
}
|
|
|
|
// Remeber the new sourceFile
|
|
sourceFilesByName[filename] = sourceFile;
|
|
}
|
|
|
|
// Now create a new compiler
|
|
program = createProgram(hostfilenames, compilationSettings, createCompilerHost());
|
|
typeInfoResolver = program.getTypeChecker(/*fullTypeCheckMode*/ false);
|
|
fullTypeCheckChecker_doNotAccessDirectly = undefined;
|
|
}
|
|
|
|
/// Clean up any semantic caches that are not needed.
|
|
/// The host can call this method if it wants to jettison unused memory.
|
|
/// We will just dump the typeChecker and recreate a new one. this should have the effect of destroying all the semantic caches.
|
|
function cleanupSemanticCache(): void {
|
|
if (program) {
|
|
typeInfoResolver = program.getTypeChecker(/*fullTypeCheckMode*/ false);
|
|
fullTypeCheckChecker_doNotAccessDirectly = undefined;
|
|
}
|
|
}
|
|
|
|
function dispose(): void {
|
|
if (program) {
|
|
forEach(program.getSourceFiles(),
|
|
(f) => { documentRegistry.releaseDocument(f.filename, program.getCompilerOptions()); });
|
|
}
|
|
}
|
|
|
|
/// Diagnostics
|
|
function getSyntacticDiagnostics(filename: string) {
|
|
synchronizeHostData();
|
|
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
return program.getDiagnostics(getSourceFile(filename).getSourceFile());
|
|
}
|
|
|
|
function getSemanticDiagnostics(filename: string) {
|
|
synchronizeHostData();
|
|
|
|
filename = TypeScript.switchToForwardSlashes(filename)
|
|
|
|
return getFullTypeCheckChecker().getDiagnostics(getSourceFile(filename));
|
|
}
|
|
|
|
function getCompilerOptionsDiagnostics() {
|
|
synchronizeHostData();
|
|
return program.getGlobalDiagnostics();
|
|
}
|
|
|
|
/// Completion
|
|
function getValidCompletionEntryDisplayName(displayName: string, target: ScriptTarget): string {
|
|
if (displayName && displayName.length > 0) {
|
|
var firstChar = displayName.charCodeAt(0);
|
|
if (firstChar === TypeScript.CharacterCodes.singleQuote || firstChar === TypeScript.CharacterCodes.doubleQuote) {
|
|
// If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an
|
|
// invalid identifer name. We need to check if whatever was inside the quotes is actually a valid identifier name.
|
|
displayName = TypeScript.stripStartAndEndQuotes(displayName);
|
|
}
|
|
|
|
if (TypeScript.Scanner.isValidIdentifier(TypeScript.SimpleText.fromString(displayName), target)) {
|
|
return displayName;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function createCompletionEntry(symbol: Symbol): CompletionEntry {
|
|
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
|
|
// We would like to only show things that can be added after a dot, so for instance numeric properties can
|
|
// not be accessed with a dot (a.1 <- invalid)
|
|
var displayName = getValidCompletionEntryDisplayName(symbol.getName(), program.getCompilerOptions().target);
|
|
if (!displayName) {
|
|
return undefined;
|
|
}
|
|
|
|
var declarations = symbol.getDeclarations();
|
|
var firstDeclaration = [0];
|
|
return {
|
|
name: displayName,
|
|
kind: getSymbolKind(symbol),
|
|
kindModifiers: declarations ? getNodeModifiers(declarations[0]) : ScriptElementKindModifier.none
|
|
};
|
|
}
|
|
|
|
function getCompletionsAtPosition(filename: string, position: number, isMemberCompletion: boolean) {
|
|
function getCompletionEntriesFromSymbols(symbols: Symbol[], session: CompletionSession): void {
|
|
forEach(symbols, (symbol) => {
|
|
var entry = createCompletionEntry(symbol);
|
|
if (entry) {
|
|
session.entries.push(entry);
|
|
session.symbols[entry.name] = symbol;
|
|
}
|
|
});
|
|
}
|
|
|
|
function isCompletionListBlocker(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean {
|
|
// We shouldn't be getting a possition that is outside the file because
|
|
// isEntirelyInsideComment can't handle when the position is out of bounds,
|
|
// callers should be fixed, however we should be resiliant to bad inputs
|
|
// so we return true (this position is a blocker for getting completions)
|
|
if (position < 0 || position > TypeScript.fullWidth(sourceUnit)) {
|
|
return true;
|
|
}
|
|
|
|
// This method uses Fidelity completely. Some information can be reached using the AST, but not everything.
|
|
return TypeScript.Syntax.isEntirelyInsideComment(sourceUnit, position) ||
|
|
TypeScript.Syntax.isEntirelyInStringOrRegularExpressionLiteral(sourceUnit, position) ||
|
|
isIdentifierDefinitionLocation(sourceUnit, position) ||
|
|
isRightOfIllegalDot(sourceUnit, position);
|
|
}
|
|
|
|
function getContainingObjectLiteralApplicableForCompletion(sourceUnit: TypeScript.SourceUnitSyntax, position: number): TypeScript.ISyntaxElement {
|
|
// The locations in an object literal expression that are applicable for completion are property name definition locations.
|
|
var previousToken = getNonIdentifierCompleteTokenOnLeft(sourceUnit, position);
|
|
|
|
if (previousToken) {
|
|
var parent = previousToken.parent;
|
|
|
|
switch (previousToken.kind()) {
|
|
case TypeScript.SyntaxKind.OpenBraceToken: // var x = { |
|
|
case TypeScript.SyntaxKind.CommaToken: // var x = { a: 0, |
|
|
if (parent && parent.kind() === TypeScript.SyntaxKind.SeparatedList) {
|
|
parent = parent.parent;
|
|
}
|
|
|
|
if (parent && parent.kind() === TypeScript.SyntaxKind.ObjectLiteralExpression) {
|
|
return parent;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function isIdentifierDefinitionLocation(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean {
|
|
var positionedToken = getNonIdentifierCompleteTokenOnLeft(sourceUnit, position);
|
|
|
|
if (positionedToken) {
|
|
var containingNodeKind = TypeScript.Syntax.containingNode(positionedToken) && TypeScript.Syntax.containingNode(positionedToken).kind();
|
|
switch (positionedToken.kind()) {
|
|
case TypeScript.SyntaxKind.CommaToken:
|
|
return containingNodeKind === TypeScript.SyntaxKind.ParameterList ||
|
|
containingNodeKind === TypeScript.SyntaxKind.VariableDeclaration ||
|
|
containingNodeKind === TypeScript.SyntaxKind.EnumDeclaration; // enum { foo, |
|
|
|
|
case TypeScript.SyntaxKind.OpenParenToken:
|
|
return containingNodeKind === TypeScript.SyntaxKind.ParameterList ||
|
|
containingNodeKind === TypeScript.SyntaxKind.CatchClause;
|
|
|
|
case TypeScript.SyntaxKind.OpenBraceToken:
|
|
return containingNodeKind === TypeScript.SyntaxKind.EnumDeclaration; // enum { |
|
|
|
|
case TypeScript.SyntaxKind.PublicKeyword:
|
|
case TypeScript.SyntaxKind.PrivateKeyword:
|
|
case TypeScript.SyntaxKind.StaticKeyword:
|
|
case TypeScript.SyntaxKind.DotDotDotToken:
|
|
return containingNodeKind === TypeScript.SyntaxKind.Parameter;
|
|
|
|
case TypeScript.SyntaxKind.ClassKeyword:
|
|
case TypeScript.SyntaxKind.ModuleKeyword:
|
|
case TypeScript.SyntaxKind.EnumKeyword:
|
|
case TypeScript.SyntaxKind.InterfaceKeyword:
|
|
case TypeScript.SyntaxKind.FunctionKeyword:
|
|
case TypeScript.SyntaxKind.VarKeyword:
|
|
case TypeScript.SyntaxKind.GetKeyword:
|
|
case TypeScript.SyntaxKind.SetKeyword:
|
|
return true;
|
|
}
|
|
|
|
// Previous token may have been a keyword that was converted to an identifier.
|
|
switch (positionedToken.text()) {
|
|
case "class":
|
|
case "interface":
|
|
case "enum":
|
|
case "module":
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getNonIdentifierCompleteTokenOnLeft(sourceUnit: TypeScript.SourceUnitSyntax, position: number): TypeScript.ISyntaxToken {
|
|
var positionedToken = TypeScript.Syntax.findCompleteTokenOnLeft(sourceUnit, position, /*includeSkippedTokens*/true);
|
|
|
|
if (positionedToken && position === TypeScript.end(positionedToken) && positionedToken.kind() == TypeScript.SyntaxKind.EndOfFileToken) {
|
|
// EndOfFile token is not intresting, get the one before it
|
|
positionedToken = TypeScript. previousToken(positionedToken, /*includeSkippedTokens*/true);
|
|
}
|
|
|
|
if (positionedToken && position === TypeScript.end(positionedToken) && positionedToken.kind() === TypeScript.SyntaxKind.IdentifierName) {
|
|
// The caret is at the end of an identifier, the decession to provide completion depends on the previous token
|
|
positionedToken = TypeScript.previousToken(positionedToken, /*includeSkippedTokens*/true);
|
|
}
|
|
|
|
return positionedToken;
|
|
}
|
|
|
|
function isRightOfIllegalDot(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean {
|
|
var positionedToken = getNonIdentifierCompleteTokenOnLeft(sourceUnit, position);
|
|
|
|
if (positionedToken) {
|
|
switch (positionedToken.kind()) {
|
|
case TypeScript.SyntaxKind.DotToken:
|
|
var leftOfDotPositionedToken = TypeScript.previousToken(positionedToken, /*includeSkippedTokens*/true);
|
|
return leftOfDotPositionedToken && leftOfDotPositionedToken.kind() === TypeScript.SyntaxKind.NumericLiteral;
|
|
|
|
case TypeScript.SyntaxKind.NumericLiteral:
|
|
var text = positionedToken.text();
|
|
return text.charAt(text.length - 1) === ".";
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
synchronizeHostData();
|
|
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var sourceFile = getSourceFile(filename);
|
|
var sourceUnit = sourceFile.getSourceUnit();
|
|
|
|
if (isCompletionListBlocker(sourceFile.getSyntaxTree().sourceUnit(), position)) {
|
|
host.log("Returning an empty list because completion was blocked.");
|
|
return null;
|
|
}
|
|
|
|
var node = TypeScript.ASTHelpers.getAstAtPosition(sourceUnit, position, /*useTrailingTriviaAsLimChar*/ true, /*forceInclusive*/ true);
|
|
|
|
if (node && node.kind() === TypeScript.SyntaxKind.IdentifierName &&
|
|
TypeScript.start(node) === TypeScript.end(node)) {
|
|
// Ignore missing name nodes
|
|
node = node.parent;
|
|
}
|
|
|
|
var isRightOfDot = false;
|
|
if (node &&
|
|
node.kind() === TypeScript.SyntaxKind.MemberAccessExpression &&
|
|
TypeScript.end((<TypeScript.MemberAccessExpressionSyntax>node).expression) < position) {
|
|
|
|
isRightOfDot = true;
|
|
node = (<TypeScript.MemberAccessExpressionSyntax>node).expression;
|
|
}
|
|
else if (node &&
|
|
node.kind() === TypeScript.SyntaxKind.QualifiedName &&
|
|
TypeScript.end((<TypeScript.QualifiedNameSyntax>node).left) < position) {
|
|
|
|
isRightOfDot = true;
|
|
node = (<TypeScript.QualifiedNameSyntax>node).left;
|
|
}
|
|
else if (node && node.parent &&
|
|
node.kind() === TypeScript.SyntaxKind.IdentifierName &&
|
|
node.parent.kind() === TypeScript.SyntaxKind.MemberAccessExpression &&
|
|
(<TypeScript.MemberAccessExpressionSyntax>node.parent).name === node) {
|
|
|
|
isRightOfDot = true;
|
|
node = (<TypeScript.MemberAccessExpressionSyntax>node.parent).expression;
|
|
}
|
|
else if (node && node.parent &&
|
|
node.kind() === TypeScript.SyntaxKind.IdentifierName &&
|
|
node.parent.kind() === TypeScript.SyntaxKind.QualifiedName &&
|
|
(<TypeScript.QualifiedNameSyntax>node.parent).right === node) {
|
|
|
|
isRightOfDot = true;
|
|
node = (<TypeScript.QualifiedNameSyntax>node.parent).left;
|
|
}
|
|
|
|
// TODO: this is a hack for now, we need a proper walking mechanism to verify that we have the correct node
|
|
var mappedNode = getNodeAtPosition(sourceFile, TypeScript.end(node) - 1);
|
|
|
|
Debug.assert(mappedNode, "Could not map a Fidelity node to an AST node");
|
|
|
|
// Get the completions
|
|
activeCompletionSession = {
|
|
filename: filename,
|
|
position: position,
|
|
entries: [],
|
|
symbols: {},
|
|
location: mappedNode,
|
|
typeChecker: typeInfoResolver
|
|
};
|
|
|
|
// Right of dot member completion list
|
|
if (isRightOfDot) {
|
|
var type: ApparentType = typeInfoResolver.getApparentType(typeInfoResolver.getTypeOfNode(mappedNode));
|
|
if (!type) {
|
|
return undefined;
|
|
}
|
|
|
|
var symbols = type.getApparentProperties();
|
|
isMemberCompletion = true;
|
|
getCompletionEntriesFromSymbols(symbols, activeCompletionSession);
|
|
}
|
|
else {
|
|
var containingObjectLiteral = getContainingObjectLiteralApplicableForCompletion(sourceFile.getSyntaxTree().sourceUnit(), position);
|
|
|
|
// Object literal expression, look up possible property names from contextual type
|
|
if (containingObjectLiteral) {
|
|
var searchPosition = Math.min(position, TypeScript.end(containingObjectLiteral));
|
|
var path = TypeScript.ASTHelpers.getAstAtPosition(sourceUnit, searchPosition);
|
|
// Get the object literal node
|
|
|
|
while (node && node.kind() !== TypeScript.SyntaxKind.ObjectLiteralExpression) {
|
|
node = node.parent;
|
|
}
|
|
|
|
if (!node || node.kind() !== TypeScript.SyntaxKind.ObjectLiteralExpression) {
|
|
// AST Path look up did not result in the same node as Fidelity Syntax Tree look up.
|
|
// Once we remove AST this will no longer be a problem.
|
|
return null;
|
|
}
|
|
|
|
isMemberCompletion = true;
|
|
|
|
//// Try to get the object members form contextual typing
|
|
//var contextualMembers = compiler.getContextualMembersFromAST(node, document);
|
|
//if (contextualMembers && contextualMembers.symbols && contextualMembers.symbols.length > 0) {
|
|
// // get existing members
|
|
// var existingMembers = compiler.getVisibleMemberSymbolsFromAST(node, document);
|
|
|
|
// // Add filtterd items to the completion list
|
|
// getCompletionEntriesFromSymbols({
|
|
// symbols: filterContextualMembersList(contextualMembers.symbols, existingMembers, filename, position),
|
|
// enclosingScopeSymbol: contextualMembers.enclosingScopeSymbol
|
|
// }, entries);
|
|
//}
|
|
}
|
|
// Get scope memebers
|
|
else {
|
|
isMemberCompletion = false;
|
|
/// TODO filter meaning based on the current context
|
|
var symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace;
|
|
var symbols = typeInfoResolver.getSymbolsInScope(mappedNode, symbolMeanings);
|
|
|
|
getCompletionEntriesFromSymbols(symbols, activeCompletionSession);
|
|
}
|
|
}
|
|
|
|
// Add keywords if this is not a member completion list
|
|
if (!isMemberCompletion) {
|
|
Array.prototype.push.apply(activeCompletionSession.entries, keywordCompletions);
|
|
}
|
|
|
|
return {
|
|
isMemberCompletion: isMemberCompletion,
|
|
entries: activeCompletionSession.entries
|
|
};
|
|
}
|
|
|
|
function getCompletionEntryDetails(filename: string, position: number, entryName: string) {
|
|
// Note: No need to call synchronizeHostData, as we have captured all the data we need
|
|
// in the getCompletionsAtPosition erlier
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var session = activeCompletionSession;
|
|
|
|
// Ensure that the current active completion session is still valid for this request
|
|
if (!session || session.filename !== filename || session.position !== position) {
|
|
return undefined;
|
|
}
|
|
|
|
var symbol = lookUp(activeCompletionSession.symbols, entryName);
|
|
if (symbol) {
|
|
var type = session.typeChecker.getTypeOfSymbol(symbol);
|
|
Debug.assert(type, "Could not find type for symbol");
|
|
var completionEntry = createCompletionEntry(symbol);
|
|
return {
|
|
name: entryName,
|
|
kind: completionEntry.kind,
|
|
kindModifiers: completionEntry.kindModifiers,
|
|
type: session.typeChecker.typeToString(type, session.location),
|
|
fullSymbolName: typeInfoResolver.symbolToString(symbol, session.location),
|
|
docComment: ""
|
|
};
|
|
}
|
|
else {
|
|
// No symbol, it is a keyword
|
|
return {
|
|
name: entryName,
|
|
kind: ScriptElementKind.keyword,
|
|
kindModifiers: ScriptElementKindModifier.none,
|
|
type: undefined,
|
|
fullSymbolName: entryName,
|
|
docComment: undefined
|
|
};
|
|
}
|
|
}
|
|
|
|
function getNodeAtPosition(sourceFile: SourceFile, position: number) {
|
|
var current: Node = sourceFile;
|
|
outer: while (true) {
|
|
// find the child that has this
|
|
for (var i = 0, n = current.getChildCount(); i < n; i++) {
|
|
var child = current.getChildAt(i);
|
|
if (child.getStart() <= position && position < child.getEnd()) {
|
|
current = child;
|
|
continue outer;
|
|
}
|
|
if (child.end > position) break;
|
|
}
|
|
return current;
|
|
}
|
|
}
|
|
|
|
function getContainerNode(node: Node): Node {
|
|
while (true) {
|
|
node = node.parent;
|
|
if (!node) {
|
|
return node;
|
|
}
|
|
switch (node.kind) {
|
|
case SyntaxKind.SourceFile:
|
|
case SyntaxKind.Method:
|
|
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 node;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getSymbolKind(symbol: Symbol): string {
|
|
var flags = symbol.getFlags();
|
|
|
|
if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement;
|
|
if (flags & SymbolFlags.Class) return ScriptElementKind.classElement;
|
|
if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement;
|
|
if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement;
|
|
if (flags & SymbolFlags.Variable) return ScriptElementKind.variableElement;
|
|
if (flags & SymbolFlags.Function) return 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.Property) return ScriptElementKind.memberVariableElement;
|
|
if (flags & SymbolFlags.IndexSignature) return ScriptElementKind.indexSignatureElement;
|
|
if (flags & SymbolFlags.ConstructSignature) return ScriptElementKind.constructSignatureElement;
|
|
if (flags & SymbolFlags.CallSignature) return ScriptElementKind.callSignatureElement;
|
|
if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement;
|
|
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
|
|
if (flags & SymbolFlags.EnumMember) return ScriptElementKind.variableElement;
|
|
|
|
return ScriptElementKind.unknown;
|
|
}
|
|
|
|
function getTypeKind(type: Type): string {
|
|
var flags = type.getFlags();
|
|
|
|
if (flags & TypeFlags.Enum) return ScriptElementKind.enumElement;
|
|
if (flags & TypeFlags.Class) return ScriptElementKind.classElement;
|
|
if (flags & TypeFlags.Interface) return ScriptElementKind.interfaceElement;
|
|
if (flags & TypeFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
|
|
if (flags & TypeFlags.Intrinsic) return ScriptElementKind.primitiveType;
|
|
if (flags & TypeFlags.StringLiteral) return ScriptElementKind.primitiveType;
|
|
|
|
return ScriptElementKind.unknown;
|
|
}
|
|
|
|
function getNodeModifiers(node: Node): string {
|
|
var flags = node.flags;
|
|
var result: string[] = [];
|
|
|
|
if (flags & NodeFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier);
|
|
if (flags & NodeFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier);
|
|
if (flags & NodeFlags.Static) result.push(ScriptElementKindModifier.staticModifier);
|
|
if (flags & NodeFlags.Export) result.push(ScriptElementKindModifier.exportedModifier);
|
|
if (isInAmbientContext(node)) result.push(ScriptElementKindModifier.ambientModifier);
|
|
|
|
return result.length > 0 ? result.join(',') : ScriptElementKindModifier.none;
|
|
}
|
|
|
|
/// QuickInfo
|
|
function getTypeAtPosition(filename: string, position: number): TypeInfo {
|
|
synchronizeHostData();
|
|
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
var sourceFile = getSourceFile(filename);
|
|
var node = getNodeAtPosition(sourceFile, position);
|
|
if (!node) return undefined;
|
|
|
|
var symbol = typeInfoResolver.getSymbolInfo(node);
|
|
var type = symbol && typeInfoResolver.getTypeOfSymbol(symbol);
|
|
if (type) {
|
|
return {
|
|
memberName: new TypeScript.MemberNameString(typeInfoResolver.typeToString(type)),
|
|
docComment: "",
|
|
fullSymbolName: typeInfoResolver.symbolToString(symbol, getContainerNode(node)),
|
|
kind: getSymbolKind(symbol),
|
|
minChar: node.pos,
|
|
limChar: node.end
|
|
};
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/// Goto definition
|
|
function getDefinitionAtPosition(filename: string, position: number): DefinitionInfo[]{
|
|
function getDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo {
|
|
return {
|
|
fileName: node.getSourceFile().filename,
|
|
minChar: node.getStart(),
|
|
limChar: node.getEnd(),
|
|
kind: symbolKind,
|
|
name: symbolName,
|
|
containerName: containerName,
|
|
containerKind: undefined
|
|
};
|
|
}
|
|
|
|
function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
|
|
var declarations: Declaration[] = [];
|
|
var definition: Declaration;
|
|
|
|
forEach(signatureDeclarations, d => {
|
|
if ((selectConstructors && d.kind === SyntaxKind.Constructor) ||
|
|
(!selectConstructors && (d.kind === SyntaxKind.FunctionDeclaration || d.kind === SyntaxKind.Method))) {
|
|
declarations.push(d);
|
|
if ((<FunctionDeclaration>d).body) definition = d;
|
|
}
|
|
});
|
|
|
|
if (definition) {
|
|
result.push(getDefinitionInfo(definition, symbolKind, symbolName, containerName));
|
|
return true;
|
|
}
|
|
else if (declarations.length) {
|
|
result.push(getDefinitionInfo(declarations[declarations.length - 1], symbolKind, symbolName, containerName));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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) {
|
|
var classDeclaration = <ClassDeclaration>symbol.getDeclarations()[0];
|
|
Debug.assert(classDeclaration && classDeclaration.kind === SyntaxKind.ClassDeclaration);
|
|
|
|
return tryAddSignature(classDeclaration.members, /*selectConstructors*/ true, symbolKind, symbolName, containerName, result);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
synchronizeHostData();
|
|
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
var sourceFile = getSourceFile(filename);
|
|
|
|
var node = getNodeAtPosition(sourceFile, position);
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
|
|
// Labels
|
|
if (isJumpStatementTarget(node)) {
|
|
var labelName = (<Identifier>node).text;
|
|
var label = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text);
|
|
return label ? [getDefinitionInfo(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined;
|
|
}
|
|
|
|
/// Triple slash reference comments
|
|
var comment = forEach(sourceFile.referencedFiles, r => (r.pos <= position && position < r.end) ? r : undefined);
|
|
if (comment) {
|
|
var targetFilename = normalizePath(combinePaths(getDirectoryPath(filename), comment.filename));
|
|
if (program.getSourceFile(targetFilename)) {
|
|
return [{
|
|
fileName: targetFilename, minChar: 0, limChar: 0,
|
|
kind: ScriptElementKind.scriptElement,
|
|
name: comment.filename, containerName: undefined, containerKind: undefined
|
|
}];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
var symbol = typeInfoResolver.getSymbolInfo(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 || !(symbol.getDeclarations())) {
|
|
return undefined;
|
|
}
|
|
|
|
var result: DefinitionInfo[] = [];
|
|
|
|
var declarations = symbol.getDeclarations();
|
|
var symbolName = typeInfoResolver.symbolToString(symbol, node);
|
|
var symbolKind = getSymbolKind(symbol);
|
|
var containerSymbol = symbol.parent;
|
|
var containerName = containerSymbol ? typeInfoResolver.symbolToString(containerSymbol, node) : "";
|
|
var containerKind = containerSymbol ? getSymbolKind(symbol) : "";
|
|
|
|
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(getDefinitionInfo(declaration, symbolKind, symbolName, containerName));
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Find references
|
|
function getReferencesAtPosition(filename: string, position: number): ReferenceEntry[] {
|
|
synchronizeHostData();
|
|
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
var sourceFile = getSourceFile(filename);
|
|
|
|
var node = getNodeAtPosition(sourceFile, position);
|
|
if (!node) {
|
|
return undefined;
|
|
}
|
|
|
|
if (node.kind !== SyntaxKind.Identifier &&
|
|
!isLiteralNameOfPropertyDeclarationOrIndexAccess(node) &&
|
|
!isNameOfExternalModuleImportOrDeclaration(node)) {
|
|
return undefined;
|
|
}
|
|
|
|
// Labels
|
|
if (isLabelName(node)) {
|
|
if (isJumpStatementTarget(node)) {
|
|
var labelDefinition = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text);
|
|
// if we have a label definition, look within its statement for references, if not, then
|
|
// the label is undefined, just return a set of one for the current node.
|
|
return labelDefinition ? getLabelReferencesInNode(labelDefinition.parent, labelDefinition) : [getReferenceEntry(node)];
|
|
}
|
|
else {
|
|
// it is a label definition and not a target, search within the parent labeledStatement
|
|
return getLabelReferencesInNode(node.parent, <Identifier>node);
|
|
}
|
|
}
|
|
|
|
var symbol = typeInfoResolver.getSymbolInfo(node);
|
|
|
|
// Could not find a symbol e.g. unknown identifier
|
|
if (!symbol) {
|
|
// Even if we did not find a symbol, we have an identifer, so there is at least
|
|
// one reference that we know of. return than instead of undefined.
|
|
return [getReferenceEntry(node)];
|
|
}
|
|
|
|
// the symbol was an internal symbol and does not have a declaration e.g.undefined symbol
|
|
if (!symbol.getDeclarations()) {
|
|
return undefined;
|
|
}
|
|
|
|
var result: ReferenceEntry[];
|
|
|
|
// Compute the meaning from the location and the symbol it references
|
|
var searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), symbol.getDeclarations());
|
|
|
|
// Get the text to search for, we need to normalize it as external module names will have quote
|
|
var symbolName = getNormalizedSymbolName(symbol.getName());
|
|
|
|
var scope = getSymbolScope(symbol);
|
|
|
|
if (scope) {
|
|
result = [];
|
|
getReferencesInNode(scope, symbol, symbolName, node, searchMeaning, result);
|
|
}
|
|
else {
|
|
forEach(program.getSourceFiles(), sourceFile => {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
|
|
if (lookUp(sourceFile.identifiers, symbolName)) {
|
|
result = result || [];
|
|
getReferencesInNode(sourceFile, symbol, symbolName, node, searchMeaning, result);
|
|
}
|
|
});
|
|
}
|
|
|
|
return result;
|
|
|
|
function getNormalizedSymbolName(name: string): string {
|
|
var length = name.length;
|
|
if (length >= 2 && name.charCodeAt(0) === CharacterCodes.doubleQuote && name.charCodeAt(length - 1) === CharacterCodes.doubleQuote) {
|
|
return name.substring(1, length - 1);
|
|
};
|
|
return name;
|
|
}
|
|
|
|
function getSymbolScope(symbol: Symbol): Node {
|
|
// If this is private property or method, the scope is the containing class
|
|
if (symbol.getFlags() && (SymbolFlags.Property | SymbolFlags.Method)) {
|
|
var privateDeclaration = forEach(symbol.getDeclarations(), d => (d.flags & NodeFlags.Private) ? d : undefined);
|
|
if (privateDeclaration) {
|
|
return privateDeclaration.parent;
|
|
}
|
|
}
|
|
|
|
// if this symbol is visible from its parent container, e.g. exported, then bail out
|
|
if (symbol.parent) {
|
|
return undefined;
|
|
}
|
|
|
|
var scope: Node = undefined;
|
|
|
|
var declarations = symbol.getDeclarations();
|
|
for (var i = 0, n = declarations.length; i < n; i++) {
|
|
var container = getContainerNode(declarations[i]);
|
|
|
|
if (scope && scope !== container) {
|
|
// Diffrent declarations have diffrent containers, bail out
|
|
return undefined;
|
|
}
|
|
|
|
if (container.kind === SyntaxKind.SourceFile && !isExternalModule(<SourceFile>container)) {
|
|
// This is a global variable and not an external module, any declaration defined
|
|
// withen this scope is visible outside the file
|
|
return undefined;
|
|
}
|
|
|
|
// The search scope is the container node
|
|
scope = container;
|
|
}
|
|
|
|
return scope;
|
|
}
|
|
|
|
function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, start: number, end: number): number[] {
|
|
var positions: number[] = [];
|
|
|
|
/// TODO: Cache symbol existence for files to save text search
|
|
// Also, need to make this work for unicode escapes.
|
|
|
|
// Be resilient in the face of a symbol with no name or zero length name
|
|
if (!symbolName || !symbolName.length) {
|
|
return positions;
|
|
}
|
|
|
|
var text = sourceFile.text;
|
|
var sourceLength = text.length;
|
|
var symbolNameLength = symbolName.length;
|
|
|
|
var position = text.indexOf(symbolName, start);
|
|
while (position >= 0) {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
|
|
// If we are past the end, stop looking
|
|
if (position > end) break;
|
|
|
|
// We found a match. Make sure it's not part of a larger word (i.e. the char
|
|
// before and after it have to be a non-identifier char).
|
|
var endPosition = position + symbolNameLength;
|
|
|
|
if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.ES5)) &&
|
|
(endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.ES5))) {
|
|
// Found a real match. Keep searching.
|
|
positions.push(position);
|
|
}
|
|
position = text.indexOf(symbolName, position + symbolNameLength + 1);
|
|
}
|
|
|
|
return positions;
|
|
}
|
|
|
|
function getLabelReferencesInNode(container: Node, targetLabel: Identifier): ReferenceEntry[] {
|
|
var result: ReferenceEntry[] = [];
|
|
var sourceFile = container.getSourceFile();
|
|
var labelName = targetLabel.text;
|
|
var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd());
|
|
forEach(possiblePositions, position => {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
|
|
var node = getNodeAtPosition(sourceFile, position);
|
|
if (!node || node.getWidth() !== labelName.length) {
|
|
return;
|
|
}
|
|
|
|
// Only pick labels that are either the target label, or have a target that is the target label
|
|
if (node === targetLabel ||
|
|
(isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) {
|
|
result.push(getReferenceEntry(node));
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function isValidReferencePosition(node: Node, searchSymbolName: string): boolean {
|
|
if (node) {
|
|
// Compare the length so we filter out strict superstrings of the symbol we are looking for
|
|
switch (node.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return node.getWidth() === searchSymbolName.length;
|
|
|
|
case SyntaxKind.StringLiteral:
|
|
if (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) ||
|
|
isNameOfExternalModuleImportOrDeclaration(node)) {
|
|
// For string literals we have two additional chars for the quotes
|
|
return node.getWidth() === searchSymbolName.length + 2;
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.NumericLiteral:
|
|
if (isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) {
|
|
return node.getWidth() === searchSymbolName.length;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Search within node "container" for references for a search value, where the search value is defined as a
|
|
/// tuple of(searchSymbol, searchText, searchLocation, and searchMeaning).
|
|
/// searchLocation: a node where the search value
|
|
function getReferencesInNode(container: Node, searchSymbol: Symbol, searchText: string, searchLocation: Node, searchMeaning: SearchMeaning, result: ReferenceEntry[]): void {
|
|
var sourceFile = container.getSourceFile();
|
|
|
|
var possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, container.getStart(), container.getEnd());
|
|
|
|
if (possiblePositions.length) {
|
|
// Build the set of symbols to search for, initially it has only the current symbol
|
|
var searchSymbols = populateSearchSymbolSet(searchSymbol, searchLocation);
|
|
|
|
forEach(possiblePositions, position => {
|
|
cancellationToken.throwIfCancellationRequested();
|
|
|
|
var referenceLocation = getNodeAtPosition(sourceFile, position);
|
|
if (!isValidReferencePosition(referenceLocation, searchText)) {
|
|
return;
|
|
}
|
|
|
|
if (!(getMeaningFromLocation(referenceLocation) & searchMeaning)) {
|
|
return;
|
|
}
|
|
|
|
var referenceSymbol = typeInfoResolver.getSymbolInfo(referenceLocation);
|
|
|
|
// 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 (!referenceSymbol || !(referenceSymbol.getDeclarations())) {
|
|
return;
|
|
}
|
|
|
|
if (isRelatableToSearchSet(searchSymbols, referenceSymbol, referenceLocation)) {
|
|
result.push(getReferenceEntry(referenceLocation));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function populateSearchSymbolSet(symbol: Symbol, location: Node): Symbol[] {
|
|
// The search set contains at least the current symbol
|
|
var result = [symbol];
|
|
|
|
// If the symbol is an instantiation from a another symbol (e.g. widened symbol) , add the root the list
|
|
var rootSymbol = typeInfoResolver.getRootSymbol(symbol);
|
|
if (rootSymbol && rootSymbol !== symbol) {
|
|
result.push(rootSymbol);
|
|
}
|
|
|
|
// If the location is in a context sensitive location (i.e. in an object literal) try
|
|
// to get a contextual type for it, and add the property symbol from the contextual
|
|
// type to the search set
|
|
if (isNameOfPropertyAssignment(location)) {
|
|
var symbolFromContextualType = getPropertySymbolFromContextualType(location);
|
|
if (symbolFromContextualType) result.push(typeInfoResolver.getRootSymbol(symbolFromContextualType));
|
|
}
|
|
|
|
// Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions
|
|
if (symbol.parent && symbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
|
|
getPropertySymbolsFromBaseTypes(symbol.parent, symbol.getName(), result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Symbol[]): void {
|
|
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
|
|
forEach(symbol.getDeclarations(), declaration => {
|
|
if (declaration.kind === SyntaxKind.ClassDeclaration) {
|
|
getPropertySymbolFromTypeReference((<ClassDeclaration>declaration).baseType);
|
|
forEach((<ClassDeclaration>declaration).implementedTypes, getPropertySymbolFromTypeReference);
|
|
}
|
|
else if (declaration.kind === SyntaxKind.InterfaceDeclaration) {
|
|
forEach((<InterfaceDeclaration>declaration).baseTypes, getPropertySymbolFromTypeReference);
|
|
}
|
|
});
|
|
}
|
|
return;
|
|
|
|
function getPropertySymbolFromTypeReference(typeReference: TypeReferenceNode) {
|
|
if (typeReference) {
|
|
// TODO: move to getTypeOfNode instead
|
|
var typeReferenceSymbol = typeInfoResolver.getSymbolInfo(typeReference.typeName);
|
|
if (typeReferenceSymbol) {
|
|
var propertySymbol = typeReferenceSymbol.members[propertyName];
|
|
if (propertySymbol) result.push(typeReferenceSymbol.members[propertyName]);
|
|
|
|
// Visit the typeReference as well to see if it directelly or indirectelly use that property
|
|
getPropertySymbolsFromBaseTypes(typeReferenceSymbol, propertyName, result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isRelatableToSearchSet(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): boolean {
|
|
// Unwrap symbols to get to the root (e.g. triansient symbols as a result of widenning)
|
|
var referenceSymbolTarget = typeInfoResolver.getRootSymbol(referenceSymbol);
|
|
|
|
// if it is in the list, then we are done
|
|
if (searchSymbols.indexOf(referenceSymbolTarget) >= 0) {
|
|
return true;
|
|
}
|
|
|
|
// If the reference location is in an object literal, try to get the contextual type for the
|
|
// object literal, lookup the property symbol in the contextual type, and use this symbol to
|
|
// compare to our searchSymbol
|
|
if (isNameOfPropertyAssignment(referenceLocation)) {
|
|
var symbolFromContextualType = getPropertySymbolFromContextualType(referenceLocation);
|
|
if (symbolFromContextualType && searchSymbols.indexOf(typeInfoResolver.getRootSymbol(symbolFromContextualType)) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Finally, try all properties with the same name in any type the containing type extened or implemented, and
|
|
// see if any is in the list
|
|
if (referenceSymbol.parent && referenceSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
|
|
var result: Symbol[] = [];
|
|
getPropertySymbolsFromBaseTypes(referenceSymbol.parent, referenceSymbol.getName(), result);
|
|
return forEach(result, s => searchSymbols.indexOf(s) >= 0);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getPropertySymbolFromContextualType(node: Node): Symbol {
|
|
if (isNameOfPropertyAssignment(node)) {
|
|
var objectLiteral = node.parent.parent;
|
|
var contextualType = typeInfoResolver.getContextualType(objectLiteral);
|
|
if (contextualType) {
|
|
return typeInfoResolver.getPropertyOfType(contextualType, (<Identifier>node).text);
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getReferenceEntry(node: Node): ReferenceEntry {
|
|
return {
|
|
fileName: node.getSourceFile().filename,
|
|
minChar: node.kind === SyntaxKind.StringLiteral ? node.getStart() + 1 : node.getStart(),
|
|
limChar: node.kind === SyntaxKind.StringLiteral ? node.getEnd() - 1 : node.getEnd(),
|
|
isWriteAccess: isWriteAccess(node)
|
|
};
|
|
}
|
|
|
|
function getMeaningFromDeclaration(node: Declaration): SearchMeaning {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.VariableDeclaration:
|
|
case SyntaxKind.Property:
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.EnumMember:
|
|
case SyntaxKind.Method:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.ArrowFunction:
|
|
case SyntaxKind.CatchBlock:
|
|
return SearchMeaning.Value;
|
|
|
|
case SyntaxKind.TypeParameter:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.TypeLiteral:
|
|
return SearchMeaning.Type;
|
|
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
return SearchMeaning.Value | SearchMeaning.Type;
|
|
|
|
case SyntaxKind.ModuleDeclaration:
|
|
if ((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral) {
|
|
return SearchMeaning.Namespace | SearchMeaning.Value;
|
|
}
|
|
else if (isInstantiated(node)) {
|
|
return SearchMeaning.Namespace | SearchMeaning.Value;
|
|
}
|
|
else {
|
|
return SearchMeaning.Namespace;
|
|
}
|
|
break;
|
|
|
|
case SyntaxKind.ImportDeclaration:
|
|
return SearchMeaning.Value | SearchMeaning.Type | SearchMeaning.Namespace;
|
|
}
|
|
Debug.fail("Unkown declaration type");
|
|
}
|
|
|
|
function isTypeReference(node: Node): boolean {
|
|
if (node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node)
|
|
node = node.parent;
|
|
|
|
return node.parent.kind === SyntaxKind.TypeReference;
|
|
}
|
|
|
|
function isNamespaceReference(node: Node): boolean {
|
|
var root = node;
|
|
var 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 isInRightSideOfImport(node: EntityName) {
|
|
while (node.parent.kind === SyntaxKind.QualifiedName) {
|
|
node = node.parent;
|
|
}
|
|
|
|
return node.parent.kind === SyntaxKind.ImportDeclaration && (<ImportDeclaration>node.parent).entityName === node;
|
|
}
|
|
|
|
function getMeaningFromRightHandSideOfImport(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.ImportDeclaration) {
|
|
return SearchMeaning.Value | SearchMeaning.Type | SearchMeaning.Namespace;
|
|
}
|
|
return SearchMeaning.Namespace;
|
|
}
|
|
|
|
function getMeaningFromLocation(node: Node): SearchMeaning {
|
|
if (node.parent.kind === SyntaxKind.ExportAssignment) {
|
|
return SearchMeaning.Value | SearchMeaning.Type | SearchMeaning.Namespace;
|
|
}
|
|
else if (isInRightSideOfImport(node)) {
|
|
return getMeaningFromRightHandSideOfImport(node);
|
|
}
|
|
else if (isDeclarationOrFunctionExpressionOrCatchVariableName(node)) {
|
|
return getMeaningFromDeclaration(node.parent);
|
|
}
|
|
else if (isTypeReference(node)) {
|
|
return SearchMeaning.Type;
|
|
}
|
|
else if (isNamespaceReference(node)) {
|
|
return SearchMeaning.Namespace;
|
|
}
|
|
else {
|
|
return SearchMeaning.Value;
|
|
}
|
|
}
|
|
|
|
/// Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations
|
|
/// of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class
|
|
/// then we need to widen the search to include type positions as well.
|
|
/// On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated
|
|
/// module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module)
|
|
/// do not intersect in any of the three spaces.
|
|
function getIntersectingMeaningFromDeclarations(meaning: SearchMeaning, declarations: Declaration[]): SearchMeaning {
|
|
if (declarations) {
|
|
do {
|
|
// The result is order-sensitive, for instance if initialMeaning === Namespace, and declarations = [class, instantiated module]
|
|
// we need to consider both as they initialMeaning intersects with the module in the namespace space, and the module
|
|
// intersects with the class in the value space.
|
|
// To achieve that we will keep iterating until the result stabilizes.
|
|
|
|
// Remeber the last meaning
|
|
var lastIterationMeaning = meaning;
|
|
|
|
for (var i = 0, n = declarations.length; i < n; i++) {
|
|
var declarationMeaning = getMeaningFromDeclaration(declarations[i]);
|
|
|
|
if (declarationMeaning & meaning) {
|
|
meaning |= declarationMeaning;
|
|
}
|
|
}
|
|
} while (meaning !== lastIterationMeaning);
|
|
}
|
|
return meaning;
|
|
}
|
|
|
|
/// A node is considedered a writeAccess iff it is a name of a declaration or a target of an assignment
|
|
function isWriteAccess(node: Node): boolean {
|
|
if (node.kind === SyntaxKind.Identifier && isDeclarationOrFunctionExpressionOrCatchVariableName(node)) {
|
|
return true;
|
|
}
|
|
|
|
var parent = node.parent;
|
|
if (parent) {
|
|
if (parent.kind === SyntaxKind.PostfixOperator || parent.kind === SyntaxKind.PrefixOperator) {
|
|
return true;
|
|
}
|
|
else if (parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>parent).left === node) {
|
|
var operator = (<BinaryExpression>parent).operator;
|
|
switch (operator) {
|
|
case SyntaxKind.AsteriskEqualsToken:
|
|
case SyntaxKind.SlashEqualsToken:
|
|
case SyntaxKind.PercentEqualsToken:
|
|
case SyntaxKind.MinusEqualsToken:
|
|
case SyntaxKind.LessThanLessThanEqualsToken:
|
|
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
|
|
case SyntaxKind.BarEqualsToken:
|
|
case SyntaxKind.CaretEqualsToken:
|
|
case SyntaxKind.AmpersandEqualsToken:
|
|
case SyntaxKind.PlusEqualsToken:
|
|
case SyntaxKind.EqualsToken:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Syntactic features
|
|
function getSyntaxTree(filename: string): TypeScript.SyntaxTree {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
return syntaxTreeCache.getCurrentFileSyntaxTree(filename);
|
|
}
|
|
|
|
function getCurrentSourceFile(filename: string): SourceFile {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
var currentSourceFile = syntaxTreeCache.getCurrentSourceFile(filename);
|
|
return currentSourceFile;
|
|
}
|
|
|
|
function getNameOrDottedNameSpan(filename: string, startPos: number, endPos: number): SpanInfo {
|
|
function getTypeInfoEligiblePath(filename: string, position: number, isConstructorValidPosition: boolean) {
|
|
var sourceUnit = syntaxTreeCache.getCurrentFileSyntaxTree(filename).sourceUnit();
|
|
|
|
var ast = TypeScript.ASTHelpers.getAstAtPosition(sourceUnit, position, /*useTrailingTriviaAsLimChar*/ false, /*forceInclusive*/ true);
|
|
if (ast === null) {
|
|
return null;
|
|
}
|
|
|
|
if (ast.kind() === TypeScript.SyntaxKind.ParameterList && ast.parent.kind() === TypeScript.SyntaxKind.CallSignature && ast.parent.parent.kind() === TypeScript.SyntaxKind.ConstructorDeclaration) {
|
|
ast = ast.parent.parent;
|
|
}
|
|
|
|
switch (ast.kind()) {
|
|
default:
|
|
return null;
|
|
case TypeScript.SyntaxKind.ConstructorDeclaration:
|
|
var constructorAST = <TypeScript.ConstructorDeclarationSyntax>ast;
|
|
if (!isConstructorValidPosition || !(position >= TypeScript.start(constructorAST) && position <= TypeScript.start(constructorAST) + "constructor".length)) {
|
|
return null;
|
|
}
|
|
else {
|
|
return ast;
|
|
}
|
|
case TypeScript.SyntaxKind.FunctionDeclaration:
|
|
return null;
|
|
case TypeScript.SyntaxKind.MemberAccessExpression:
|
|
case TypeScript.SyntaxKind.QualifiedName:
|
|
case TypeScript.SyntaxKind.SuperKeyword:
|
|
case TypeScript.SyntaxKind.StringLiteral:
|
|
case TypeScript.SyntaxKind.ThisKeyword:
|
|
case TypeScript.SyntaxKind.IdentifierName:
|
|
return ast;
|
|
}
|
|
}
|
|
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var node = getTypeInfoEligiblePath(filename, startPos, false);
|
|
if (!node) return null;
|
|
|
|
while (node) {
|
|
if (TypeScript.ASTHelpers.isNameOfMemberAccessExpression(node) ||
|
|
TypeScript.ASTHelpers.isRightSideOfQualifiedName(node)) {
|
|
node = node.parent;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
minChar: TypeScript.start(node),
|
|
limChar: TypeScript.end(node)
|
|
};
|
|
}
|
|
|
|
function getBreakpointStatementAtPosition(filename: string, position: number) {
|
|
// doesn't use compiler - no need to synchronize with host
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var syntaxtree = getSyntaxTree(filename);
|
|
return TypeScript.Services.Breakpoints.getBreakpointLocation(syntaxtree, position);
|
|
}
|
|
|
|
function getScriptLexicalStructure(filename: string) {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
var syntaxTree = getSyntaxTree(filename);
|
|
var items: NavigateToItem[] = [];
|
|
TypeScript.Services.GetScriptLexicalStructureWalker.getListsOfAllScriptLexicalStructure(items, filename, syntaxTree.sourceUnit());
|
|
return items;
|
|
}
|
|
|
|
function getOutliningRegions(filename: string): OutliningSpan[] {
|
|
// doesn't use compiler - no need to synchronize with host
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
var sourceFile = getCurrentSourceFile(filename);
|
|
return OutliningElementsCollector.collectElements(sourceFile);
|
|
}
|
|
|
|
function getBraceMatchingAtPosition(filename: string, position: number) {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
var syntaxTree = getSyntaxTree(filename);
|
|
return TypeScript.Services.BraceMatcher.getMatchSpans(syntaxTree, position);
|
|
}
|
|
|
|
function getIndentationAtPosition(filename: string, position: number, editorOptions: EditorOptions) {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var syntaxTree = getSyntaxTree(filename);
|
|
|
|
var scriptSnapshot = syntaxTreeCache.getCurrentScriptSnapshot(filename);
|
|
var scriptText = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
|
|
var textSnapshot = new TypeScript.Services.Formatting.TextSnapshot(scriptText);
|
|
var options = new TypeScript.FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter)
|
|
|
|
return TypeScript.Services.Formatting.SingleTokenIndenter.getIndentationAmount(position, syntaxTree.sourceUnit(), textSnapshot, options);
|
|
}
|
|
|
|
function getFormattingManager(filename: string, options: FormatCodeOptions) {
|
|
// Ensure rules are initialized and up to date wrt to formatting options
|
|
if (formattingRulesProvider == null) {
|
|
formattingRulesProvider = new TypeScript.Services.Formatting.RulesProvider(host);
|
|
}
|
|
|
|
formattingRulesProvider.ensureUpToDate(options);
|
|
|
|
// Get the Syntax Tree
|
|
var syntaxTree = getSyntaxTree(filename);
|
|
|
|
// Convert IScriptSnapshot to ITextSnapshot
|
|
var scriptSnapshot = syntaxTreeCache.getCurrentScriptSnapshot(filename);
|
|
var scriptText = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
|
|
var textSnapshot = new TypeScript.Services.Formatting.TextSnapshot(scriptText);
|
|
|
|
var manager = new TypeScript.Services.Formatting.FormattingManager(syntaxTree, textSnapshot, formattingRulesProvider, options);
|
|
|
|
return manager;
|
|
}
|
|
|
|
function getFormattingEditsForRange(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var manager = getFormattingManager(filename, options);
|
|
return manager.formatSelection(minChar, limChar);
|
|
}
|
|
|
|
function getFormattingEditsForDocument(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var manager = getFormattingManager(filename, options);
|
|
return manager.formatDocument(minChar, limChar);
|
|
}
|
|
|
|
function getFormattingEditsOnPaste(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var manager = getFormattingManager(filename, options);
|
|
return manager.formatOnPaste(minChar, limChar);
|
|
}
|
|
|
|
function getFormattingEditsAfterKeystroke(filename: string, position: number, key: string, options: FormatCodeOptions): TextEdit[] {
|
|
filename = TypeScript.switchToForwardSlashes(filename);
|
|
|
|
var manager = getFormattingManager(filename, options);
|
|
if (key === "}") return manager.formatOnClosingCurlyBrace(position);
|
|
else if (key === ";") return manager.formatOnSemicolon(position);
|
|
else if (key === "\n") return manager.formatOnEnter(position);
|
|
else return [];
|
|
}
|
|
|
|
|
|
return {
|
|
dispose: dispose,
|
|
refresh: () => { },
|
|
cleanupSemanticCache: cleanupSemanticCache,
|
|
getSyntacticDiagnostics: getSyntacticDiagnostics,
|
|
getSemanticDiagnostics: getSemanticDiagnostics,
|
|
getCompilerOptionsDiagnostics: getCompilerOptionsDiagnostics,
|
|
getCompletionsAtPosition: getCompletionsAtPosition,
|
|
getCompletionEntryDetails: getCompletionEntryDetails,
|
|
getTypeAtPosition: getTypeAtPosition,
|
|
getSignatureAtPosition: (filename, position): SignatureInfo => undefined,
|
|
getDefinitionAtPosition: getDefinitionAtPosition,
|
|
getReferencesAtPosition: getReferencesAtPosition,
|
|
getOccurrencesAtPosition: (filename, position) => [],
|
|
getImplementorsAtPosition: (filename, position) => [],
|
|
getNameOrDottedNameSpan: getNameOrDottedNameSpan,
|
|
getBreakpointStatementAtPosition: getBreakpointStatementAtPosition,
|
|
getNavigateToItems: (searchValue) => [],
|
|
getScriptLexicalStructure: getScriptLexicalStructure,
|
|
getOutliningRegions: getOutliningRegions,
|
|
getBraceMatchingAtPosition: getBraceMatchingAtPosition,
|
|
getIndentationAtPosition: getIndentationAtPosition,
|
|
getFormattingEditsForRange: getFormattingEditsForRange,
|
|
getFormattingEditsForDocument: getFormattingEditsForDocument,
|
|
getFormattingEditsOnPaste: getFormattingEditsOnPaste,
|
|
getFormattingEditsAfterKeystroke: getFormattingEditsAfterKeystroke,
|
|
getEmitOutput: (filename): EmitOutput => undefined,
|
|
};
|
|
}
|
|
|
|
/// Classifier
|
|
export function createClassifier(host: Logger): Classifier {
|
|
var scanner: Scanner;
|
|
var noRegexTable: boolean[];
|
|
|
|
/// 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.
|
|
if (!noRegexTable) {
|
|
noRegexTable = [];
|
|
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;
|
|
}
|
|
|
|
function getClassificationsForLine(text: string, lexState: EndOfLineState): ClassificationResult {
|
|
var offset = 0;
|
|
var lastTokenOrCommentEnd = 0;
|
|
var lastToken = SyntaxKind.Unknown;
|
|
var inUnterminatedMultiLineComment = false;
|
|
|
|
// 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.EndingWithDotToken:
|
|
lastToken = SyntaxKind.DotToken;
|
|
break;
|
|
}
|
|
|
|
var result: ClassificationResult = {
|
|
finalLexState: EndOfLineState.Start,
|
|
entries: []
|
|
};
|
|
|
|
scanner = createScanner(ScriptTarget.ES5, text, onError, processComment);
|
|
|
|
var token = SyntaxKind.Unknown;
|
|
do {
|
|
token = scanner.scan();
|
|
|
|
if ((token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) && !noRegexTable[lastToken]) {
|
|
if (scanner.reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) {
|
|
token = SyntaxKind.RegularExpressionLiteral;
|
|
}
|
|
}
|
|
else if (lastToken === SyntaxKind.DotToken) {
|
|
token = SyntaxKind.Identifier;
|
|
}
|
|
|
|
lastToken = token;
|
|
|
|
processToken();
|
|
}
|
|
while (token !== SyntaxKind.EndOfFileToken);
|
|
|
|
return result;
|
|
|
|
|
|
function onError(message: DiagnosticMessage): void {
|
|
inUnterminatedMultiLineComment = message.key === Diagnostics.Asterisk_Slash_expected.key;
|
|
}
|
|
|
|
function processComment(start: number, end: number) {
|
|
// add Leading white spaces
|
|
addLeadingWhiteSpace(start, end);
|
|
|
|
// add the comment
|
|
addResult(end - start, TokenClass.Comment);
|
|
}
|
|
|
|
function processToken(): void {
|
|
var start = scanner.getTokenPos();
|
|
var end = scanner.getTextPos();
|
|
|
|
// add Leading white spaces
|
|
addLeadingWhiteSpace(start, end);
|
|
|
|
// add the token
|
|
addResult(end - start, classFromKind(token));
|
|
|
|
if (end >= text.length) {
|
|
// We're at the end.
|
|
if (inUnterminatedMultiLineComment) {
|
|
result.finalLexState = EndOfLineState.InMultiLineCommentTrivia;
|
|
}
|
|
else if (token === SyntaxKind.StringLiteral) {
|
|
var tokenText = scanner.getTokenText();
|
|
if (tokenText.length > 0 && tokenText.charCodeAt(tokenText.length - 1) === CharacterCodes.backslash) {
|
|
var quoteChar = tokenText.charCodeAt(0);
|
|
result.finalLexState = quoteChar === CharacterCodes.doubleQuote
|
|
? EndOfLineState.InDoubleQuoteStringLiteral
|
|
: EndOfLineState.InSingleQuoteStringLiteral;
|
|
}
|
|
}
|
|
else if (token === SyntaxKind.DotToken) {
|
|
result.finalLexState = EndOfLineState.EndingWithDotToken;
|
|
}
|
|
}
|
|
}
|
|
|
|
function addLeadingWhiteSpace(start: number, end: number): void {
|
|
if (start > lastTokenOrCommentEnd) {
|
|
addResult(start - lastTokenOrCommentEnd, TokenClass.Whitespace);
|
|
}
|
|
|
|
// Remeber the end of the last token
|
|
lastTokenOrCommentEnd = end;
|
|
}
|
|
|
|
function addResult(length: number, classification: TokenClass): void {
|
|
if (length > 0) {
|
|
// If this is the first classification we're adding to the list, then remove any
|
|
// offset we have if we were continuing a construct from the previous line.
|
|
if (result.entries.length === 0) {
|
|
length -= offset;
|
|
}
|
|
|
|
result.entries.push({ length: length, classification: 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.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) {
|
|
if (isKeyword(token)) {
|
|
return TokenClass.Keyword;
|
|
}
|
|
else if (isBinaryExpressionOperatorToken(token) || isPrefixUnaryExpressionOperatorToken(token)) {
|
|
return TokenClass.Operator;
|
|
}
|
|
else if (token >= SyntaxKind.FirstPunctuation && token <= SyntaxKind.LastPunctuation) {
|
|
return TokenClass.Punctuation;
|
|
}
|
|
|
|
switch (token) {
|
|
case SyntaxKind.NumericLiteral:
|
|
return TokenClass.NumberLiteral;
|
|
case SyntaxKind.StringLiteral:
|
|
return TokenClass.StringLiteral;
|
|
case SyntaxKind.RegularExpressionLiteral:
|
|
return TokenClass.RegExpLiteral;
|
|
case SyntaxKind.Identifier:
|
|
default:
|
|
return TokenClass.Identifier;
|
|
}
|
|
}
|
|
|
|
return {
|
|
getClassificationsForLine: getClassificationsForLine
|
|
};
|
|
}
|
|
|
|
function initializeServices() {
|
|
objectAllocator = {
|
|
getNodeConstructor: kind => {
|
|
function Node() {
|
|
}
|
|
var proto = kind === SyntaxKind.SourceFile ? new SourceFileObject() : new NodeObject();
|
|
proto.kind = kind;
|
|
proto.pos = 0;
|
|
proto.end = 0;
|
|
proto.flags = 0;
|
|
proto.parent = undefined;
|
|
Node.prototype = proto;
|
|
return <any>Node;
|
|
},
|
|
getSymbolConstructor: () => SymbolObject,
|
|
getTypeConstructor: () => TypeObject,
|
|
getSignatureConstructor: () => SignatureObject,
|
|
};
|
|
}
|
|
|
|
initializeServices();
|
|
}
|