diff --git a/src/services/classifier.ts b/src/services/classifier.ts deleted file mode 100644 index 412d96b6eba..00000000000 --- a/src/services/classifier.ts +++ /dev/null @@ -1,199 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -/// - -module TypeScript.Services { - export enum EndOfLineState { - Start, - InMultiLineCommentTrivia, - InSingleQuoteStringLiteral, - InDoubleQuoteStringLiteral, - } - - 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 function createClassifier(host: Logger): Classifier { - var scanner: TypeScript.Scanner.IScanner; - var lastDiagnosticKey: string = null; - var noRegexTable: boolean[]; - var reportDiagnostic = (position: number, fullWidth: number, key: string, args: any[]) => { - lastDiagnosticKey = key; - }; - - if (!noRegexTable) { - noRegexTable= []; - noRegexTable[TypeScript.SyntaxKind.IdentifierName] = true; - noRegexTable[TypeScript.SyntaxKind.StringLiteral] = true; - noRegexTable[TypeScript.SyntaxKind.NumericLiteral] = true; - noRegexTable[TypeScript.SyntaxKind.RegularExpressionLiteral] = true; - noRegexTable[TypeScript.SyntaxKind.ThisKeyword] = true; - noRegexTable[TypeScript.SyntaxKind.PlusPlusToken] = true; - noRegexTable[TypeScript.SyntaxKind.MinusMinusToken] = true; - noRegexTable[TypeScript.SyntaxKind.CloseParenToken] = true; - noRegexTable[TypeScript.SyntaxKind.CloseBracketToken] = true; - noRegexTable[TypeScript.SyntaxKind.CloseBraceToken] = true; - noRegexTable[TypeScript.SyntaxKind.TrueKeyword] = true; - noRegexTable[TypeScript.SyntaxKind.FalseKeyword] = true; - } - - function getClassificationsForLine(text: string, lexState: EndOfLineState): ClassificationResult { - var offset = 0; - if (lexState !== EndOfLineState.Start) { - // 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. - if (lexState === EndOfLineState.InDoubleQuoteStringLiteral) { - text = '"\\\n' + text; - } - else if (lexState === EndOfLineState.InSingleQuoteStringLiteral) { - text = "'\\\n" + text; - } - else if (lexState === EndOfLineState.InMultiLineCommentTrivia) { - text = "/*\n" + text; - } - - offset = 3; - } - - var result = { - finalLexState: EndOfLineState.Start, - entries: [] - }; - - var simpleText = TypeScript.SimpleText.fromString(text); - scanner = Scanner.createScanner(ts.ScriptTarget.ES5, simpleText, reportDiagnostic); - - var lastTokenKind = TypeScript.SyntaxKind.None; - var token: ISyntaxToken = null; - do { - lastDiagnosticKey = null; - - token = scanner.scan(!noRegexTable[lastTokenKind]); - lastTokenKind = token.kind(); - - processToken(text, simpleText, offset, token, result); - } - while (token.kind() !== SyntaxKind.EndOfFileToken); - - lastDiagnosticKey = null; - return result; - } - - function processToken(text: string, simpleText: ISimpleText, offset: number, token: TypeScript.ISyntaxToken, result: ClassificationResult): void { - processTriviaList(text, offset, token.leadingTrivia(simpleText), result); - addResult(text, offset, result, width(token), token.kind()); - processTriviaList(text, offset, token.trailingTrivia(simpleText), result); - - if (fullEnd(token) >= text.length) { - // We're at the end. - if (lastDiagnosticKey === TypeScript.DiagnosticCode.AsteriskSlash_expected) { - result.finalLexState = EndOfLineState.InMultiLineCommentTrivia; - return; - } - - if (token.kind() === TypeScript.SyntaxKind.StringLiteral) { - var tokenText = token.text(); - if (tokenText.length > 0 && tokenText.charCodeAt(tokenText.length - 1) === TypeScript.CharacterCodes.backslash) { - var quoteChar = tokenText.charCodeAt(0); - result.finalLexState = quoteChar === TypeScript.CharacterCodes.doubleQuote - ? EndOfLineState.InDoubleQuoteStringLiteral - : EndOfLineState.InSingleQuoteStringLiteral; - return; - } - } - } - } - - function processTriviaList(text: string, offset: number, triviaList: TypeScript.ISyntaxTriviaList, result: ClassificationResult): void { - for (var i = 0, n = triviaList.count(); i < n; i++) { - var trivia = triviaList.syntaxTriviaAt(i); - addResult(text, offset, result, trivia.fullWidth(), trivia.kind()); - } - } - - function addResult(text: string, offset: number, result: ClassificationResult, length: number, kind: TypeScript.SyntaxKind): 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: classFromKind(kind) }); - } - } - - function classFromKind(kind: TypeScript.SyntaxKind) { - if (TypeScript.SyntaxFacts.isAnyKeyword(kind)) { - return TokenClass.Keyword; - } - else if (TypeScript.SyntaxFacts.isBinaryExpressionOperatorToken(kind) || - TypeScript.SyntaxFacts.isPrefixUnaryExpressionOperatorToken(kind)) { - return TokenClass.Operator; - } - else if (TypeScript.SyntaxFacts.isAnyPunctuation(kind)) { - return TokenClass.Punctuation; - } - - switch (kind) { - case TypeScript.SyntaxKind.WhitespaceTrivia: - return TokenClass.Whitespace; - case TypeScript.SyntaxKind.MultiLineCommentTrivia: - case TypeScript.SyntaxKind.SingleLineCommentTrivia: - return TokenClass.Comment; - case TypeScript.SyntaxKind.NumericLiteral: - return TokenClass.NumberLiteral; - case TypeScript.SyntaxKind.StringLiteral: - return TokenClass.StringLiteral; - case TypeScript.SyntaxKind.RegularExpressionLiteral: - return TokenClass.RegExpLiteral; - case TypeScript.SyntaxKind.IdentifierName: - default: - return TokenClass.Identifier; - } - } - - return { - getClassificationsForLine: getClassificationsForLine - }; - } -} diff --git a/src/services/compiler/base64.ts b/src/services/compiler/base64.ts deleted file mode 100644 index 06eac4f4b74..00000000000 --- a/src/services/compiler/base64.ts +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -module TypeScript { - class Base64Format { - static encodedValues = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - static encode(inValue: number) { - if (inValue < 64) { - return Base64Format.encodedValues.charAt(inValue); - } - throw TypeError(inValue + ": not a 64 based value"); - } - - static decodeChar(inChar: string) { - if (inChar.length === 1) { - return Base64Format.encodedValues.indexOf(inChar); - } - else { - throw TypeError('"' + inChar + '" must have length 1'); - } - } - } - - export class Base64VLQFormat { - static encode(inValue: number) { - // Add a new least significant bit that has the sign of the value. - // if negative number the least significant bit that gets added to the number has value 1 - // else least significant bit value that gets added is 0 - // eg. -1 changes to binary : 01 [1] => 3 - // +1 changes to binary : 01 [0] => 2 - if (inValue < 0) { - inValue = ((-inValue) << 1) + 1; - } - else { - inValue = inValue << 1; - } - - // Encode 5 bits at a time starting from least significant bits - var encodedStr = ""; - do { - var currentDigit = inValue & 31; // 11111 - inValue = inValue >> 5; - if (inValue > 0) { - // There are still more digits to decode, set the msb (6th bit) - currentDigit = currentDigit | 32; - } - encodedStr = encodedStr + Base64Format.encode(currentDigit); - } while (inValue > 0); - - return encodedStr; - } - - static decode(inString: string) { - var result = 0; - var negative = false; - - var shift = 0; - for (var i = 0; i < inString.length; i++) { - var byte = Base64Format.decodeChar(inString[i]); - if (i === 0) { - // Sign bit appears in the LSBit of the first value - if ((byte & 1) === 1) { - negative = true; - } - result = (byte >> 1) & 15; // 1111x - } - else { - result = result | ((byte & 31) << shift); // 11111 - } - - shift += (i === 0) ? 4 : 5; - - if ((byte & 32) === 32) { - // Continue - } - else { - return { value: negative ? -(result) : result, rest: inString.substr(i + 1) }; - } - } - - throw new Error(getDiagnosticMessage(DiagnosticCode.Base64_value_0_finished_with_a_continuation_bit, [inString])); - } - } -} diff --git a/src/services/compiler/document.ts b/src/services/compiler/document.ts deleted file mode 100644 index 6da1b143f91..00000000000 --- a/src/services/compiler/document.ts +++ /dev/null @@ -1,151 +0,0 @@ -/// - -module TypeScript { - export interface IncrementalParse { - (oldSyntaxTree: SyntaxTree, textChangeRange: TextChangeRange, newText: ISimpleText): SyntaxTree - } - - - export class Document { - private _bloomFilter: BloomFilter = null; - - // By default, our Document class doesn't support incremental update of its contents. - // However, we enable other layers (like teh services layer) to inject the capability - // into us by setting this function. - public static incrementalParse: IncrementalParse = null; - - constructor(private compilationSettings: ts.CompilerOptions, - public filename: string, - public referencedFiles: string[], - private _scriptSnapshot: IScriptSnapshot, - public byteOrderMark: ts.ByteOrderMark, - public version: number, - public isOpen: boolean, - private _syntaxTree: SyntaxTree, - private _soruceFile: ts.SourceFile) { - } - - public isDeclareFile(): boolean { - return isDTSFile(this.filename); - } - - public sourceUnit(): SourceUnitSyntax { - // If we don't have a script, create one from our parse tree. - return this.syntaxTree().sourceUnit(); - } - - public diagnostics(): Diagnostic[] { - return this.syntaxTree().diagnostics(); - } - - public lineMap(): LineMap { - return this.syntaxTree().lineMap(); - } - - public syntaxTree(): SyntaxTree { - if (!this._syntaxTree) { - var start = new Date().getTime(); - - this._syntaxTree = Parser.parse( - this.filename, SimpleText.fromScriptSnapshot(this._scriptSnapshot), this.compilationSettings.target, this.isDeclareFile()); - - var time = new Date().getTime() - start; - - //TypeScript.syntaxTreeParseTime += time; - } - - return this._syntaxTree; - } - - public sourceFile(): ts.SourceFile { - if (!this._soruceFile) { - var start = new Date().getTime(); - - this._soruceFile = ts.createSourceFile(this.filename, this._scriptSnapshot.getText(0, this._scriptSnapshot.getLength()), this.compilationSettings.target); - - var time = new Date().getTime() - start; - - //TypeScript.astParseTime += time; - } - - return this._soruceFile; - } - - public bloomFilter(): BloomFilter { - if (!this._bloomFilter) { - var identifiers = createIntrinsicsObject(); - var pre = function (cur: TypeScript.ISyntaxElement) { - if (ASTHelpers.isValidAstNode(cur)) { - if (cur.kind() === SyntaxKind.IdentifierName) { - var nodeText = tokenValueText((cur)); - - identifiers[nodeText] = true; - } - } - }; - - TypeScript.getAstWalkerFactory().simpleWalk(this.sourceUnit(), pre, null, identifiers); - - var identifierCount = 0; - for (var name in identifiers) { - if (identifiers[name]) { - identifierCount++; - } - } - - this._bloomFilter = new BloomFilter(identifierCount); - this._bloomFilter.addKeys(identifiers); - } - return this._bloomFilter; - } - - // Returns true if this file should get emitted into its own unique output file. - // Otherwise, it should be written into a single output file along with the rest of hte - // documents in the compilation. - public emitToOwnOutputFile(): boolean { - // If we haven't specified an output file in our settings, then we're definitely - // emitting to our own file. Also, if we're an external module, then we're - // definitely emitting to our own file. - return !this.compilationSettings.out || this.syntaxTree().isExternalModule(); - } - - public update(scriptSnapshot: IScriptSnapshot, version: number, isOpen: boolean, textChangeRange: TextChangeRange): Document { - // 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 !== null && 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 = 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 === null || oldSyntaxTree === null || Document.incrementalParse === null - ? TypeScript.Parser.parse(this.filename, text, this.compilationSettings.target, TypeScript.isDTSFile(this.filename)) - : Document.incrementalParse(oldSyntaxTree, textChangeRange, text); - - return new Document(this.compilationSettings, this.filename, this.referencedFiles, scriptSnapshot, this.byteOrderMark, version, isOpen, newSyntaxTree, /*soruceFile*/ null); - } - - public static create(compilationSettings: ts.CompilerOptions, fileName: string, scriptSnapshot: IScriptSnapshot, byteOrderMark: ts.ByteOrderMark, version: number, isOpen: boolean, referencedFiles: string[]): Document { - return new Document(compilationSettings, fileName, referencedFiles, scriptSnapshot, byteOrderMark, version, isOpen, /*syntaxTree:*/ null, /*soruceFile*/ null); - } - } -} \ No newline at end of file diff --git a/src/services/compiler/flags.ts b/src/services/compiler/flags.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/services/document.ts b/src/services/document.ts deleted file mode 100644 index 44c97e6aa20..00000000000 --- a/src/services/document.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// - -module TypeScript.Services { - // Inject support for incremental parsing to the core compiler Document class. - Document.incrementalParse = IncrementalParser.parse; -} \ No newline at end of file diff --git a/src/services/languageService.ts b/src/services/languageService.ts deleted file mode 100644 index 8b2708856fe..00000000000 --- a/src/services/languageService.ts +++ /dev/null @@ -1,357 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -/// - -module TypeScript.Services { - - // - // Public interface of the host of a language service instance. - // - export interface LanguageServiceHost extends TypeScript.Logger, TypeScript.IReferenceResolverHost { - getCompilationSettings(): ts.CompilerOptions; - getScriptFileNames(): string[]; - getScriptVersion(fileName: string): number; - getScriptIsOpen(fileName: string): boolean; - getScriptByteOrderMark(fileName: string): ts.ByteOrderMark; - getScriptSnapshot(fileName: string): TypeScript.IScriptSnapshot; - getLocalizedDiagnosticMessages(): any; - getCancellationToken(): ts.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): ts.Diagnostic[]; - getSemanticDiagnostics(fileName: string): ts.Diagnostic[]; - getCompilerOptionsDiagnostics(): ts.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): TypeScript.TextSpan[]; - getBraceMatchingAtPosition(fileName: string, position: number): TypeScript.TextSpan[]; - getIndentationAtPosition(fileName: string, position: number, options: TypeScript.Services.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 function logInternalError(logger: TypeScript.Logger, err: Error) { - logger.log("*INTERNAL ERROR* - Exception in typescript services: " + err.message); - } - - 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 class TextEdit { - constructor(public minChar: number, public limChar: number, public text: string) { - } - - static createInsert(pos: number, text: string): TextEdit { - return new TextEdit(pos, pos, text); - } - static createDelete(minChar: number, limChar: number): TextEdit { - return new TextEdit(minChar, limChar, ""); - } - static createReplace(minChar: number, limChar: number, text: string): TextEdit { - return new TextEdit(minChar, limChar, text); - } - } - - export class EditorOptions { - public IndentSize: number = 4; - public TabSize: number = 4; - public NewLineCharacter: string = "\r\n"; - public ConvertTabsToSpaces: boolean = true; - - public static clone(objectToClone: EditorOptions): EditorOptions { - var editorOptions = new EditorOptions(); - editorOptions.IndentSize = objectToClone.IndentSize; - editorOptions.TabSize = objectToClone.TabSize; - editorOptions.NewLineCharacter = objectToClone.NewLineCharacter; - editorOptions.ConvertTabsToSpaces = objectToClone.ConvertTabsToSpaces; - return editorOptions; - } - } - - export class FormatCodeOptions extends EditorOptions { - public InsertSpaceAfterCommaDelimiter: boolean = true; - public InsertSpaceAfterSemicolonInForStatements: boolean = true; - public InsertSpaceBeforeAndAfterBinaryOperators: boolean = true; - public InsertSpaceAfterKeywordsInControlFlowStatements: boolean = true; - public InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean = false; - public InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean = false; - public PlaceOpenBraceOnNewLineForFunctions: boolean = false; - public PlaceOpenBraceOnNewLineForControlBlocks: boolean = false; - - public static clone(objectToClone: FormatCodeOptions ): FormatCodeOptions { - var formatCodeOptions = EditorOptions.clone(objectToClone); - formatCodeOptions.InsertSpaceAfterCommaDelimiter = objectToClone.InsertSpaceAfterCommaDelimiter; - formatCodeOptions.InsertSpaceAfterSemicolonInForStatements = objectToClone.InsertSpaceAfterSemicolonInForStatements; - formatCodeOptions.InsertSpaceBeforeAndAfterBinaryOperators = objectToClone.InsertSpaceBeforeAndAfterBinaryOperators; - formatCodeOptions.InsertSpaceAfterKeywordsInControlFlowStatements = objectToClone.InsertSpaceAfterKeywordsInControlFlowStatements; - formatCodeOptions.InsertSpaceAfterFunctionKeywordForAnonymousFunctions = objectToClone.InsertSpaceAfterFunctionKeywordForAnonymousFunctions; - formatCodeOptions.InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis = objectToClone.InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis; - formatCodeOptions.PlaceOpenBraceOnNewLineForFunctions = objectToClone.PlaceOpenBraceOnNewLineForFunctions; - formatCodeOptions.PlaceOpenBraceOnNewLineForControlBlocks = objectToClone.PlaceOpenBraceOnNewLineForControlBlocks; - return formatCodeOptions; - } - } - - export interface DefinitionInfo { - fileName: string; - minChar: number; - limChar: number; - kind: string; - name: string; - containerKind: string; - containerName: string; - } - - export interface TypeInfo { - memberName: TypeScript.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; - } - - // 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"; - } - - 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"; - } - - export class DiagnosticCategory { - static none = ""; - static error = "error"; - static warning = "warning"; - static message = "message"; - } -} diff --git a/src/services/pullLanguageService.ts b/src/services/pullLanguageService.ts deleted file mode 100644 index 0155eed384d..00000000000 --- a/src/services/pullLanguageService.ts +++ /dev/null @@ -1,1090 +0,0 @@ -/// - -/// -/// -/// -/// -/// - -module TypeScript.Services { - 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: ts.Map; // symbols by entry name map - location: ts.Node; // the node where the completion was requested - typeChecker: ts.TypeChecker;// the typeChecker used to generate this completion - } - - // Information about a specific host file. - interface HostFileInformation { - filename: string; - version: number; - isOpen: boolean; - byteOrderMark: ts.ByteOrderMark; - _sourceText?: TypeScript.IScriptSnapshot; - } - - export function getDefaultCompilerOptions(): ts.CompilerOptions { - // Set "ES5" target by default for language service - return { - target: ts.ScriptTarget.ES5, - module: ts.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 CancellationToken { - - public static None: CancellationToken = new CancellationToken(null) - - constructor(private cancellationToken: ts.CancellationToken) { - } - - public isCancellationRequested() { - return this.cancellationToken && this.cancellationToken.isCancellationRequested(); - } - - public throwIfCancellationRequested(): void { - if (this.isCancellationRequested()) { - throw new OperationCanceledException(); - } - } - } - - // Cache host information about scripts. 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: ts.Map; - private _compilationSettings: ts.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), - byteOrderMark: host.getScriptByteOrderMark(filename) - }; - } - - this._compilationSettings = host.getCompilationSettings() || getDefaultCompilerOptions(); - } - - public compilationSettings() { - return this._compilationSettings; - } - - public contains(filename: string): boolean { - return !!this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)]; - } - - public getHostfilename(filename: string) { - var hostCacheEntry = this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)]; - if (hostCacheEntry) { - return hostCacheEntry.filename; - } - return filename; - } - - public getfilenames(): string[] { - var fileNames: string[] = []; - for (var id in this._filenameToEntry) { - fileNames.push(id); - } - return fileNames; - } - - public getVersion(filename: string): number { - return this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)].version; - } - - public isOpen(filename: string): boolean { - return this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)].isOpen; - } - - public getByteOrderMark(filename: string): ts.ByteOrderMark { - return this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)].byteOrderMark; - } - - public getScriptSnapshot(filename: string): TypeScript.IScriptSnapshot { - var file = this._filenameToEntry[TypeScript.switchToForwardSlashes(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 _currentFileSyntaxTree: TypeScript.SyntaxTree = null; - private _currentFileScriptSnapshot: TypeScript.IScriptSnapshot = null; - - constructor(private _host: LanguageServiceHost) { - this._hostCache = new HostCache(_host); - } - - public getCurrentFileSyntaxTree(filename: string): TypeScript.SyntaxTree { - this._hostCache = new HostCache(this._host); - - var version = this._hostCache.getVersion(filename); - var syntaxTree: TypeScript.SyntaxTree = null; - - if (this._currentFileSyntaxTree === null || this._currentfilename !== filename) { - var scriptSnapshot = this._hostCache.getScriptSnapshot(filename); - syntaxTree = this.createSyntaxTree(filename, scriptSnapshot); - } - else if (this._currentFileVersion !== version) { - var scriptSnapshot = this._hostCache.getScriptSnapshot(filename); - syntaxTree = this.updateSyntaxTree(filename, scriptSnapshot, this._currentFileSyntaxTree, this._currentFileVersion); - } - - if (syntaxTree !== null) { - // All done, ensure state is up to date - this._currentFileScriptSnapshot = scriptSnapshot; - this._currentFileVersion = version; - this._currentfilename = filename; - this._currentFileSyntaxTree = syntaxTree; - } - - return this._currentFileSyntaxTree; - } - - public getCurrentScriptSnapshot(filename: string): 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 = IncrementalParser.parse( - previousSyntaxTree, editRange, 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 = fullText(incrementalTree.sourceUnit()); - var actualSnapshotText = newScriptSnapshot.getText(0, newScriptSnapshot.getLength()); - Debug.assert(incrementalTreeText === actualSnapshotText, 'Expected full texts to be equal'); - } - } - } - - interface FormattingOptions { - useTabs: boolean; - spacesPerTab: number; - indentSpaces: number; - newLineCharacter: string; - } - - class DocumentRegistryEntry { - public refCount: number = 0; - public owners: string[] = []; - constructor(public document: Document) { - } - } - - export interface IDocumentRegistry { - acquireDocument( - filename: string, - compilationSettings: ts.CompilerOptions, - scriptSnapshot: IScriptSnapshot, - byteOrderMark: ts.ByteOrderMark, - version: number, - isOpen: boolean, - referencedFiles: string[]): TypeScript.Document; - - updateDocument( - document: Document, - filename: string, - compilationSettings: ts.CompilerOptions, - scriptSnapshot: IScriptSnapshot, - version: number, - isOpen: boolean, - textChangeRange: TextChangeRange - ): TypeScript.Document; - - releaseDocument(filename: string, compilationSettings: ts.CompilerOptions): void - } - - export class DocumentRegistry implements IDocumentRegistry { - private buckets: ts.Map> = {}; - - private getKeyFromCompilationSettings(settings: ts.CompilerOptions): string { - return "_" + ts.ScriptTarget[settings.target]; // + "|" + settings.propagateEnumConstants.toString() - } - - private getBucketForCompilationSettings(settings: ts.CompilerOptions, createIfMissing: boolean): ts.Map { - var key = this.getKeyFromCompilationSettings(settings); - var bucket = this.buckets[key]; - if (!bucket && createIfMissing) { - this.buckets[key] = bucket = {}; - } - return bucket; - } - - public reportStats() { - var bucketInfoArray = Object.keys(this.buckets).filter(name => name && name.charAt(0) === '_').map(name => { - var entries = this.buckets[name]; - var documents = []; - for (var i in entries) { - var entry = entries[i]; - documents.push({ - name: i, - refCount: entry.refCount, - references: entry.owners.slice(0) - }); - } - documents.sort((x, y) => y.refCount - x.refCount); - return { bucket: name, documents: documents } - }); - return JSON.stringify(bucketInfoArray, null, 2); - } - - public acquireDocument( - filename: string, - compilationSettings: ts.CompilerOptions, - scriptSnapshot: IScriptSnapshot, - byteOrderMark: ts.ByteOrderMark, - version: number, - isOpen: boolean, - referencedFiles: string[]= []): TypeScript.Document { - - var bucket = this.getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true); - var entry = bucket[filename]; - if (!entry) { - var document = Document.create(compilationSettings, filename, scriptSnapshot, byteOrderMark, version, isOpen, referencedFiles); - - entry = new DocumentRegistryEntry(document); - bucket[filename] = entry; - } - entry.refCount++; - - return entry.document; - } - - public updateDocument( - document: Document, - filename: string, - compilationSettings: ts.CompilerOptions, - scriptSnapshot: IScriptSnapshot, - version: number, - isOpen: boolean, - textChangeRange: TextChangeRange - ): TypeScript.Document { - - var bucket = this.getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ false); - Debug.assert(bucket); - var entry = bucket[filename]; - Debug.assert(entry); - - if (entry.document.isOpen === isOpen && entry.document.version === version) { - return entry.document; - } - - entry.document = entry.document.update(scriptSnapshot, version, isOpen, textChangeRange); - return entry.document; - } - - public releaseDocument(filename: string, compilationSettings: ts.CompilerOptions): void { - var bucket = this.getBucketForCompilationSettings(compilationSettings, false); - Debug.assert(bucket); - - var entry = bucket[filename]; - entry.refCount--; - - Debug.assert(entry.refCount >= 0); - if (entry.refCount === 0) { - delete bucket[filename]; - } - } - } - - export function createLanguageService(host: LanguageServiceHost, documentRegistry: IDocumentRegistry) :LanguageService{ - var logger: TypeScript.Logger = host; - var _syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); - var formattingRulesProvider: Formatting.RulesProvider; - var hostCache: HostCache; // A cache of all the information about the files on the host side. - var program: ts.Program; - var typeChecker: ts.TypeChecker; - var useCaseSensitivefilenames = false; - var documentsByName: ts.Map = {}; - var documentRegistry = documentRegistry; - var cancellationToken = new CancellationToken(host.getCancellationToken()); - var activeCompletionSession: CompletionSession; - - // Check if the localized messages json is set, otherwise query the host for it - if (!TypeScript.LocalizedDiagnosticMessages) { - TypeScript.LocalizedDiagnosticMessages = host.getLocalizedDiagnosticMessages(); - } - - function createCompilerHost(): ts.CompilerHost { - return { - getSourceFile: (filename, languageVersion) => { - var document = documentsByName[filename]; - - Debug.assert(!!document, "document can not be undefined"); - - return document.sourceFile(); - }, - getCancellationToken: () => cancellationToken, - getCanonicalFileName: (filename) => useCaseSensitivefilenames ? filename : filename.toLowerCase(), - useCaseSensitiveFileNames: () => useCaseSensitivefilenames, - getNewLine: () => "\n", - // Need something that doesn't depend on sys.ts here - getDefaultLibFilename: (): string => { - throw Error("TOD:: getDefaultLibfilename"); - }, - writeFile: (filename, data) => { - throw Error("TODO: write file"); - }, - getCurrentDirectory: (): string => { - throw Error("TODO: getCurrentDirectory"); - } - }; - } - - function synchronizeHostData(): void { - // Reset the cache at start of every refresh - hostCache = new HostCache(host); - - var compilationSettings = hostCache.compilationSettings(); - - // TODO: check if we need to create a new compiler to start with - // 1. files are identical - // 2. compilation settings are identical - - // 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 - var settingsChangeAffectsSyntax = oldSettings.target !== compilationSettings.target; - - 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 documentsByName[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 document: Document = documentsByName[filename]; - if (document) { - // - // If the document is the same, assume no update - // - if (document.version === version && document.isOpen === isOpen) { - 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: TextChangeRange = null; - if (document.isOpen && isOpen) { - textChangeRange = hostCache.getScriptTextChangeRangeSinceVersion(filename, document.version); - } - - document = documentRegistry.updateDocument(document, filename, compilationSettings, scriptSnapshot, version, isOpen, textChangeRange); - } - else { - document = documentRegistry.acquireDocument(filename, compilationSettings, scriptSnapshot, hostCache.getByteOrderMark(filename), version, isOpen, []); - } - - // Remeber the new document - documentsByName[filename] = document; - } - - // Now create a new compiler - program = ts.createProgram(hostfilenames, compilationSettings, createCompilerHost()); - typeChecker = program.getTypeChecker(); - } - - function dispose(): void { - if (program) { - ts.forEach(program.getSourceFiles(), - (f) => documentRegistry.releaseDocument(f.filename, program.getCompilerOptions())); - } - } - - /// Diagnostics - function getSyntacticDiagnostics(filename: string) { - synchronizeHostData(); - return program.getDiagnostics(program.getSourceFile(filename)); - } - - function getSemanticDiagnostics(filename: string) { - synchronizeHostData(); - return typeChecker.getDiagnostics(program.getSourceFile(filename)); - } - - function getCompilerOptionsDiagnostics() { - synchronizeHostData(); - return program.getGlobalDiagnostics(); - } - - /// Completion - function createCompletionEntry(symbol: ts.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 = CompletionHelpers.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: ts.Symbol[], session: CompletionSession): void { - ts.forEach(symbols, (symbol) => { - var entry = createCompletionEntry(symbol); - if (entry) { - session.entries.push(entry); - session.symbols[entry.name] = symbol; - } - }); - } - - synchronizeHostData(); - - filename = TypeScript.switchToForwardSlashes(filename); - - var document = documentsByName[filename]; - var sourceUnit = document.sourceUnit(); - - if (CompletionHelpers.isCompletionListBlocker(document.syntaxTree().sourceUnit(), position)) { - logger.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 && - start(node) === end(node)) { - // Ignore missing name nodes - node = node.parent; - } - - var isRightOfDot = false; - if (node && - node.kind() === TypeScript.SyntaxKind.MemberAccessExpression && - end((node).expression) < position) { - - isRightOfDot = true; - node = (node).expression; - } - else if (node && - node.kind() === TypeScript.SyntaxKind.QualifiedName && - end((node).left) < position) { - - isRightOfDot = true; - node = (node).left; - } - else if (node && node.parent && - node.kind() === TypeScript.SyntaxKind.IdentifierName && - node.parent.kind() === TypeScript.SyntaxKind.MemberAccessExpression && - (node.parent).name === node) { - - isRightOfDot = true; - node = (node.parent).expression; - } - else if (node && node.parent && - node.kind() === TypeScript.SyntaxKind.IdentifierName && - node.parent.kind() === TypeScript.SyntaxKind.QualifiedName && - (node.parent).right === node) { - - isRightOfDot = true; - node = (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(document.sourceFile(), 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: typeChecker - }; - - // Right of dot member completion list - if (isRightOfDot) { - var type: ts.Type = typeChecker.getTypeOfExpression(mappedNode); - if (!type) { - return undefined; - } - - var symbols = type.getApparentProperties(); - isMemberCompletion = true; - getCompletionEntriesFromSymbols(symbols, activeCompletionSession); - } - else { - var containingObjectLiteral = CompletionHelpers.getContainingObjectLiteralApplicableForCompletion(document.syntaxTree().sourceUnit(), position); - - // Object literal expression, look up possible property names from contextual type - if (containingObjectLiteral) { - var searchPosition = Math.min(position, 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: CompletionHelpers.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 = ts.SymbolFlags.Type | ts.SymbolFlags.Value | ts.SymbolFlags.Namespace; - var symbols = typeChecker.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.getKeywordCompltions()); - } - - 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 = 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: typeChecker.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: ts.SourceFile, position: number) { - var current: ts.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 (ts.getTokenPosOfNode(child) <= position && position < child.end) { - current = child; - continue outer; - } - if (child.end > position) break; - } - return current; - } - } - - function getEnclosingDeclaration(node: ts.Node): ts.Node { - while (true) { - node = node.parent; - if (!node) { - return node; - } - switch (node.kind) { - case ts.SyntaxKind.Method: - case ts.SyntaxKind.FunctionDeclaration: - case ts.SyntaxKind.FunctionExpression: - case ts.SyntaxKind.GetAccessor: - case ts.SyntaxKind.SetAccessor: - case ts.SyntaxKind.ClassDeclaration: - case ts.SyntaxKind.InterfaceDeclaration: - case ts.SyntaxKind.EnumDeclaration: - case ts.SyntaxKind.ModuleDeclaration: - return node; - } - } - } - - function getSymbolKind(symbol: ts.Symbol): string { - var flags = symbol.getFlags(); - - if (flags & ts.SymbolFlags.Module) return ScriptElementKind.moduleElement; - if (flags & ts.SymbolFlags.Class) return ScriptElementKind.classElement; - if (flags & ts.SymbolFlags.Interface) return ScriptElementKind.interfaceElement; - if (flags & ts.SymbolFlags.Enum) return ScriptElementKind.enumElement; - if (flags & ts.SymbolFlags.Variable) return ScriptElementKind.variableElement; - if (flags & ts.SymbolFlags.Function) return ScriptElementKind.functionElement; - if (flags & ts.SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement; - if (flags & ts.SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement; - if (flags & ts.SymbolFlags.Method) return ScriptElementKind.memberFunctionElement; - if (flags & ts.SymbolFlags.Property) return ScriptElementKind.memberVariableElement; - if (flags & ts.SymbolFlags.IndexSignature) return ScriptElementKind.indexSignatureElement; - if (flags & ts.SymbolFlags.ConstructSignature) return ScriptElementKind.constructSignatureElement; - if (flags & ts.SymbolFlags.CallSignature) return ScriptElementKind.callSignatureElement; - if (flags & ts.SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; - if (flags & ts.SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; - if (flags & ts.SymbolFlags.EnumMember) return ScriptElementKind.variableElement; - - return ScriptElementKind.unknown; - } - - function getTypeKind(type: ts.Type): string { - var flags = type.getFlags(); - - if (flags & ts.TypeFlags.Enum) return ScriptElementKind.enumElement; - if (flags & ts.TypeFlags.Class) return ScriptElementKind.classElement; - if (flags & ts.TypeFlags.Interface) return ScriptElementKind.interfaceElement; - if (flags & ts.TypeFlags.TypeParameter) return ScriptElementKind.typeParameterElement; - if (flags & ts.TypeFlags.Intrinsic) return ScriptElementKind.primitiveType; - if (flags & ts.TypeFlags.StringLiteral) return ScriptElementKind.primitiveType; - - return ScriptElementKind.unknown; - } - - function getNodeModifiers(node: ts.Node): string { - var flags = node.flags; - var result: string[] = []; - - if (flags & ts.NodeFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); - if (flags & ts.NodeFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier); - if (flags & ts.NodeFlags.Static) result.push(ScriptElementKindModifier.staticModifier); - if (flags & ts.NodeFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); - if (ts.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 document = documentsByName[filename]; - var node = getNodeAtPosition(document.sourceFile(), position); - if (!node) return undefined; - - switch (node.kind) { - // A declaration - case ts.SyntaxKind.Identifier: - if (node.parent.kind === ts.SyntaxKind.CallExpression || node.parent.kind === ts.SyntaxKind.NewExpression) { - // TODO: handle new and call expressions - } - - var symbol = typeChecker. getSymbolOfIdentifier(node); - Debug.assert(symbol, "getTypeAtPosition: Could not find symbol for node"); - var type = typeChecker.getTypeOfSymbol(symbol); - - return { - memberName: new MemberNameString(typeChecker.typeToString(type)), - docComment: "", - fullSymbolName: typeChecker.symbolToString(symbol, getEnclosingDeclaration(node)), - kind: getSymbolKind(symbol), - minChar: node.pos, - limChar: node.end - }; - - // An Expression - case ts.SyntaxKind.ThisKeyword: - case ts.SyntaxKind.QualifiedName: - case ts.SyntaxKind.SuperKeyword: - case ts.SyntaxKind.StringLiteral: - var type = typeChecker.getTypeOfExpression(node); - Debug.assert(type, "getTypeAtPosition: Could not find type for node"); - return { - memberName: new MemberNameString(""), - docComment: "", - fullSymbolName: typeChecker.typeToString(type, getEnclosingDeclaration(node)), - kind: getTypeKind(type), - minChar: node.pos, - limChar: node.end - }; - break; - } - } - - /// Syntactic features - function getSyntaxTree(filename: string): TypeScript.SyntaxTree { - filename = TypeScript.switchToForwardSlashes(filename); - return _syntaxTreeCache.getCurrentFileSyntaxTree(filename); - } - - 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() === SyntaxKind.ParameterList && ast.parent.kind() === SyntaxKind.CallSignature && ast.parent.parent.kind() === SyntaxKind.ConstructorDeclaration) { - ast = ast.parent.parent; - } - - switch (ast.kind()) { - default: - return null; - case TypeScript.SyntaxKind.ConstructorDeclaration: - var constructorAST = ast; - if (!isConstructorValidPosition || !(position >= start(constructorAST) && position <= 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: start(node), - limChar: 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[] = []; - GetScriptLexicalStructureWalker.getListsOfAllScriptLexicalStructure(items, filename, syntaxTree.sourceUnit()); - return items; - } - - function getOutliningRegions(filename: string) { - // doesn't use compiler - no need to synchronize with host - filename = TypeScript.switchToForwardSlashes(filename); - var syntaxTree = getSyntaxTree(filename); - return OutliningElementsCollector.collectElements(syntaxTree.sourceUnit()); - } - - function getBraceMatchingAtPosition(filename: string, position: number) { - filename = TypeScript.switchToForwardSlashes(filename); - var syntaxTree = getSyntaxTree(filename); - return 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 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(logger); - } - - 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: () => { }, - getSyntacticDiagnostics: getSyntacticDiagnostics, - getSemanticDiagnostics: getSemanticDiagnostics, - getCompilerOptionsDiagnostics: getCompilerOptionsDiagnostics, - getCompletionsAtPosition: getCompletionsAtPosition, - getCompletionEntryDetails: getCompletionEntryDetails, - getTypeAtPosition: getTypeAtPosition, - getSignatureAtPosition: (filename, position) => undefined, - getDefinitionAtPosition: (filename, position) => [], - getReferencesAtPosition: (filename, position) => [], - 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) => undefined, - }; - } -} \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index e8cd12002fd..4d6dbd1c058 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4,6 +4,16 @@ /// /// +/// +/// +/// +/// +/// +/// +/// +/// +/// + module ts { export interface Node { @@ -279,3 +289,1765 @@ module ts { initializeServices(); } + +module TypeScript.Services { + export interface IncrementalParse { + (oldSyntaxTree: SyntaxTree, textChangeRange: TextChangeRange, newText: ISimpleText): SyntaxTree + } + + + export class Document { + private _bloomFilter: BloomFilter = null; + + // By default, our Document class doesn't support incremental update of its contents. + // However, we enable other layers (like teh services layer) to inject the capability + // into us by setting this function. + public static incrementalParse: IncrementalParse = null; + + constructor(private compilationSettings: ts.CompilerOptions, + public filename: string, + public referencedFiles: string[], + private _scriptSnapshot: IScriptSnapshot, + public byteOrderMark: ts.ByteOrderMark, + public version: number, + public isOpen: boolean, + private _syntaxTree: SyntaxTree, + private _soruceFile: ts.SourceFile) { + } + + public isDeclareFile(): boolean { + return isDTSFile(this.filename); + } + + public sourceUnit(): SourceUnitSyntax { + // If we don't have a script, create one from our parse tree. + return this.syntaxTree().sourceUnit(); + } + + public diagnostics(): Diagnostic[] { + return this.syntaxTree().diagnostics(); + } + + public lineMap(): LineMap { + return this.syntaxTree().lineMap(); + } + + public syntaxTree(): SyntaxTree { + if (!this._syntaxTree) { + var start = new Date().getTime(); + + this._syntaxTree = Parser.parse( + this.filename, SimpleText.fromScriptSnapshot(this._scriptSnapshot), this.compilationSettings.target, this.isDeclareFile()); + + var time = new Date().getTime() - start; + + //TypeScript.syntaxTreeParseTime += time; + } + + return this._syntaxTree; + } + + public sourceFile(): ts.SourceFile { + if (!this._soruceFile) { + var start = new Date().getTime(); + + this._soruceFile = ts.createSourceFile(this.filename, this._scriptSnapshot.getText(0, this._scriptSnapshot.getLength()), this.compilationSettings.target); + + var time = new Date().getTime() - start; + + //TypeScript.astParseTime += time; + } + + return this._soruceFile; + } + + public bloomFilter(): BloomFilter { + if (!this._bloomFilter) { + var identifiers = createIntrinsicsObject(); + var pre = function (cur: TypeScript.ISyntaxElement) { + if (ASTHelpers.isValidAstNode(cur)) { + if (cur.kind() === SyntaxKind.IdentifierName) { + var nodeText = tokenValueText((cur)); + + identifiers[nodeText] = true; + } + } + }; + + TypeScript.getAstWalkerFactory().simpleWalk(this.sourceUnit(), pre, null, identifiers); + + var identifierCount = 0; + for (var name in identifiers) { + if (identifiers[name]) { + identifierCount++; + } + } + + this._bloomFilter = new BloomFilter(identifierCount); + this._bloomFilter.addKeys(identifiers); + } + return this._bloomFilter; + } + + // Returns true if this file should get emitted into its own unique output file. + // Otherwise, it should be written into a single output file along with the rest of hte + // documents in the compilation. + public emitToOwnOutputFile(): boolean { + // If we haven't specified an output file in our settings, then we're definitely + // emitting to our own file. Also, if we're an external module, then we're + // definitely emitting to our own file. + return !this.compilationSettings.out || this.syntaxTree().isExternalModule(); + } + + public update(scriptSnapshot: IScriptSnapshot, version: number, isOpen: boolean, textChangeRange: TextChangeRange): Document { + // 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 !== null && 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 = 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 === null || oldSyntaxTree === null || Document.incrementalParse === null + ? TypeScript.Parser.parse(this.filename, text, this.compilationSettings.target, TypeScript.isDTSFile(this.filename)) + : Document.incrementalParse(oldSyntaxTree, textChangeRange, text); + + return new Document(this.compilationSettings, this.filename, this.referencedFiles, scriptSnapshot, this.byteOrderMark, version, isOpen, newSyntaxTree, /*soruceFile*/ null); + } + + public static create(compilationSettings: ts.CompilerOptions, fileName: string, scriptSnapshot: IScriptSnapshot, byteOrderMark: ts.ByteOrderMark, version: number, isOpen: boolean, referencedFiles: string[]): Document { + return new Document(compilationSettings, fileName, referencedFiles, scriptSnapshot, byteOrderMark, version, isOpen, /*syntaxTree:*/ null, /*soruceFile*/ null); + } + } +} + +module TypeScript.Services { + // Inject support for incremental parsing to the core compiler Document class. + Document.incrementalParse = IncrementalParser.parse; +} + +module TypeScript.Services { + + // + // Public interface of the host of a language service instance. + // + export interface LanguageServiceHost extends TypeScript.Logger, TypeScript.IReferenceResolverHost { + getCompilationSettings(): ts.CompilerOptions; + getScriptFileNames(): string[]; + getScriptVersion(fileName: string): number; + getScriptIsOpen(fileName: string): boolean; + getScriptByteOrderMark(fileName: string): ts.ByteOrderMark; + getScriptSnapshot(fileName: string): TypeScript.IScriptSnapshot; + getLocalizedDiagnosticMessages(): any; + getCancellationToken(): ts.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): ts.Diagnostic[]; + getSemanticDiagnostics(fileName: string): ts.Diagnostic[]; + getCompilerOptionsDiagnostics(): ts.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): TypeScript.TextSpan[]; + getBraceMatchingAtPosition(fileName: string, position: number): TypeScript.TextSpan[]; + getIndentationAtPosition(fileName: string, position: number, options: TypeScript.Services.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 function logInternalError(logger: TypeScript.Logger, err: Error) { + logger.log("*INTERNAL ERROR* - Exception in typescript services: " + err.message); + } + + 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 class TextEdit { + constructor(public minChar: number, public limChar: number, public text: string) { + } + + static createInsert(pos: number, text: string): TextEdit { + return new TextEdit(pos, pos, text); + } + static createDelete(minChar: number, limChar: number): TextEdit { + return new TextEdit(minChar, limChar, ""); + } + static createReplace(minChar: number, limChar: number, text: string): TextEdit { + return new TextEdit(minChar, limChar, text); + } + } + + export class EditorOptions { + public IndentSize: number = 4; + public TabSize: number = 4; + public NewLineCharacter: string = "\r\n"; + public ConvertTabsToSpaces: boolean = true; + + public static clone(objectToClone: EditorOptions): EditorOptions { + var editorOptions = new EditorOptions(); + editorOptions.IndentSize = objectToClone.IndentSize; + editorOptions.TabSize = objectToClone.TabSize; + editorOptions.NewLineCharacter = objectToClone.NewLineCharacter; + editorOptions.ConvertTabsToSpaces = objectToClone.ConvertTabsToSpaces; + return editorOptions; + } + } + + export class FormatCodeOptions extends EditorOptions { + public InsertSpaceAfterCommaDelimiter: boolean = true; + public InsertSpaceAfterSemicolonInForStatements: boolean = true; + public InsertSpaceBeforeAndAfterBinaryOperators: boolean = true; + public InsertSpaceAfterKeywordsInControlFlowStatements: boolean = true; + public InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean = false; + public InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean = false; + public PlaceOpenBraceOnNewLineForFunctions: boolean = false; + public PlaceOpenBraceOnNewLineForControlBlocks: boolean = false; + + public static clone(objectToClone: FormatCodeOptions): FormatCodeOptions { + var formatCodeOptions = EditorOptions.clone(objectToClone); + formatCodeOptions.InsertSpaceAfterCommaDelimiter = objectToClone.InsertSpaceAfterCommaDelimiter; + formatCodeOptions.InsertSpaceAfterSemicolonInForStatements = objectToClone.InsertSpaceAfterSemicolonInForStatements; + formatCodeOptions.InsertSpaceBeforeAndAfterBinaryOperators = objectToClone.InsertSpaceBeforeAndAfterBinaryOperators; + formatCodeOptions.InsertSpaceAfterKeywordsInControlFlowStatements = objectToClone.InsertSpaceAfterKeywordsInControlFlowStatements; + formatCodeOptions.InsertSpaceAfterFunctionKeywordForAnonymousFunctions = objectToClone.InsertSpaceAfterFunctionKeywordForAnonymousFunctions; + formatCodeOptions.InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis = objectToClone.InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis; + formatCodeOptions.PlaceOpenBraceOnNewLineForFunctions = objectToClone.PlaceOpenBraceOnNewLineForFunctions; + formatCodeOptions.PlaceOpenBraceOnNewLineForControlBlocks = objectToClone.PlaceOpenBraceOnNewLineForControlBlocks; + return formatCodeOptions; + } + } + + export interface DefinitionInfo { + fileName: string; + minChar: number; + limChar: number; + kind: string; + name: string; + containerKind: string; + containerName: string; + } + + export interface TypeInfo { + memberName: TypeScript.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; + } + + // 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"; + } + + 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"; + } + + export class DiagnosticCategory { + static none = ""; + static error = "error"; + static warning = "warning"; + static message = "message"; + } +} + +module TypeScript.Services { + export enum EndOfLineState { + Start, + InMultiLineCommentTrivia, + InSingleQuoteStringLiteral, + InDoubleQuoteStringLiteral, + } + + 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 function createClassifier(host: Logger): Classifier { + var scanner: TypeScript.Scanner.IScanner; + var lastDiagnosticKey: string = null; + var noRegexTable: boolean[]; + var reportDiagnostic = (position: number, fullWidth: number, key: string, args: any[]) => { + lastDiagnosticKey = key; + }; + + if (!noRegexTable) { + noRegexTable = []; + noRegexTable[TypeScript.SyntaxKind.IdentifierName] = true; + noRegexTable[TypeScript.SyntaxKind.StringLiteral] = true; + noRegexTable[TypeScript.SyntaxKind.NumericLiteral] = true; + noRegexTable[TypeScript.SyntaxKind.RegularExpressionLiteral] = true; + noRegexTable[TypeScript.SyntaxKind.ThisKeyword] = true; + noRegexTable[TypeScript.SyntaxKind.PlusPlusToken] = true; + noRegexTable[TypeScript.SyntaxKind.MinusMinusToken] = true; + noRegexTable[TypeScript.SyntaxKind.CloseParenToken] = true; + noRegexTable[TypeScript.SyntaxKind.CloseBracketToken] = true; + noRegexTable[TypeScript.SyntaxKind.CloseBraceToken] = true; + noRegexTable[TypeScript.SyntaxKind.TrueKeyword] = true; + noRegexTable[TypeScript.SyntaxKind.FalseKeyword] = true; + } + + function getClassificationsForLine(text: string, lexState: EndOfLineState): ClassificationResult { + var offset = 0; + if (lexState !== EndOfLineState.Start) { + // 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. + if (lexState === EndOfLineState.InDoubleQuoteStringLiteral) { + text = '"\\\n' + text; + } + else if (lexState === EndOfLineState.InSingleQuoteStringLiteral) { + text = "'\\\n" + text; + } + else if (lexState === EndOfLineState.InMultiLineCommentTrivia) { + text = "/*\n" + text; + } + + offset = 3; + } + + var result = { + finalLexState: EndOfLineState.Start, + entries: [] + }; + + var simpleText = TypeScript.SimpleText.fromString(text); + scanner = Scanner.createScanner(ts.ScriptTarget.ES5, simpleText, reportDiagnostic); + + var lastTokenKind = TypeScript.SyntaxKind.None; + var token: ISyntaxToken = null; + do { + lastDiagnosticKey = null; + + token = scanner.scan(!noRegexTable[lastTokenKind]); + lastTokenKind = token.kind(); + + processToken(text, simpleText, offset, token, result); + } + while (token.kind() !== SyntaxKind.EndOfFileToken); + + lastDiagnosticKey = null; + return result; + } + + function processToken(text: string, simpleText: ISimpleText, offset: number, token: TypeScript.ISyntaxToken, result: ClassificationResult): void { + processTriviaList(text, offset, token.leadingTrivia(simpleText), result); + addResult(text, offset, result, width(token), token.kind()); + processTriviaList(text, offset, token.trailingTrivia(simpleText), result); + + if (fullEnd(token) >= text.length) { + // We're at the end. + if (lastDiagnosticKey === TypeScript.DiagnosticCode.AsteriskSlash_expected) { + result.finalLexState = EndOfLineState.InMultiLineCommentTrivia; + return; + } + + if (token.kind() === TypeScript.SyntaxKind.StringLiteral) { + var tokenText = token.text(); + if (tokenText.length > 0 && tokenText.charCodeAt(tokenText.length - 1) === TypeScript.CharacterCodes.backslash) { + var quoteChar = tokenText.charCodeAt(0); + result.finalLexState = quoteChar === TypeScript.CharacterCodes.doubleQuote + ? EndOfLineState.InDoubleQuoteStringLiteral + : EndOfLineState.InSingleQuoteStringLiteral; + return; + } + } + } + } + + function processTriviaList(text: string, offset: number, triviaList: TypeScript.ISyntaxTriviaList, result: ClassificationResult): void { + for (var i = 0, n = triviaList.count(); i < n; i++) { + var trivia = triviaList.syntaxTriviaAt(i); + addResult(text, offset, result, trivia.fullWidth(), trivia.kind()); + } + } + + function addResult(text: string, offset: number, result: ClassificationResult, length: number, kind: TypeScript.SyntaxKind): 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: classFromKind(kind) }); + } + } + + function classFromKind(kind: TypeScript.SyntaxKind) { + if (TypeScript.SyntaxFacts.isAnyKeyword(kind)) { + return TokenClass.Keyword; + } + else if (TypeScript.SyntaxFacts.isBinaryExpressionOperatorToken(kind) || + TypeScript.SyntaxFacts.isPrefixUnaryExpressionOperatorToken(kind)) { + return TokenClass.Operator; + } + else if (TypeScript.SyntaxFacts.isAnyPunctuation(kind)) { + return TokenClass.Punctuation; + } + + switch (kind) { + case TypeScript.SyntaxKind.WhitespaceTrivia: + return TokenClass.Whitespace; + case TypeScript.SyntaxKind.MultiLineCommentTrivia: + case TypeScript.SyntaxKind.SingleLineCommentTrivia: + return TokenClass.Comment; + case TypeScript.SyntaxKind.NumericLiteral: + return TokenClass.NumberLiteral; + case TypeScript.SyntaxKind.StringLiteral: + return TokenClass.StringLiteral; + case TypeScript.SyntaxKind.RegularExpressionLiteral: + return TokenClass.RegExpLiteral; + case TypeScript.SyntaxKind.IdentifierName: + default: + return TokenClass.Identifier; + } + } + + return { + getClassificationsForLine: getClassificationsForLine + }; + } +} + +module TypeScript.Services { + 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: ts.Map; // symbols by entry name map + location: ts.Node; // the node where the completion was requested + typeChecker: ts.TypeChecker;// the typeChecker used to generate this completion + } + + // Information about a specific host file. + interface HostFileInformation { + filename: string; + version: number; + isOpen: boolean; + byteOrderMark: ts.ByteOrderMark; + _sourceText?: TypeScript.IScriptSnapshot; + } + + export function getDefaultCompilerOptions(): ts.CompilerOptions { + // Set "ES5" target by default for language service + return { + target: ts.ScriptTarget.ES5, + module: ts.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 CancellationToken { + + public static None: CancellationToken = new CancellationToken(null) + + constructor(private cancellationToken: ts.CancellationToken) { + } + + public isCancellationRequested() { + return this.cancellationToken && this.cancellationToken.isCancellationRequested(); + } + + public throwIfCancellationRequested(): void { + if (this.isCancellationRequested()) { + throw new OperationCanceledException(); + } + } + } + + // Cache host information about scripts. 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: ts.Map; + private _compilationSettings: ts.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), + byteOrderMark: host.getScriptByteOrderMark(filename) + }; + } + + this._compilationSettings = host.getCompilationSettings() || getDefaultCompilerOptions(); + } + + public compilationSettings() { + return this._compilationSettings; + } + + public contains(filename: string): boolean { + return !!this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)]; + } + + public getHostfilename(filename: string) { + var hostCacheEntry = this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)]; + if (hostCacheEntry) { + return hostCacheEntry.filename; + } + return filename; + } + + public getfilenames(): string[] { + var fileNames: string[] = []; + for (var id in this._filenameToEntry) { + fileNames.push(id); + } + return fileNames; + } + + public getVersion(filename: string): number { + return this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)].version; + } + + public isOpen(filename: string): boolean { + return this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)].isOpen; + } + + public getByteOrderMark(filename: string): ts.ByteOrderMark { + return this._filenameToEntry[TypeScript.switchToForwardSlashes(filename)].byteOrderMark; + } + + public getScriptSnapshot(filename: string): TypeScript.IScriptSnapshot { + var file = this._filenameToEntry[TypeScript.switchToForwardSlashes(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 _currentFileSyntaxTree: TypeScript.SyntaxTree = null; + private _currentFileScriptSnapshot: TypeScript.IScriptSnapshot = null; + + constructor(private _host: LanguageServiceHost) { + this._hostCache = new HostCache(_host); + } + + public getCurrentFileSyntaxTree(filename: string): TypeScript.SyntaxTree { + this._hostCache = new HostCache(this._host); + + var version = this._hostCache.getVersion(filename); + var syntaxTree: TypeScript.SyntaxTree = null; + + if (this._currentFileSyntaxTree === null || this._currentfilename !== filename) { + var scriptSnapshot = this._hostCache.getScriptSnapshot(filename); + syntaxTree = this.createSyntaxTree(filename, scriptSnapshot); + } + else if (this._currentFileVersion !== version) { + var scriptSnapshot = this._hostCache.getScriptSnapshot(filename); + syntaxTree = this.updateSyntaxTree(filename, scriptSnapshot, this._currentFileSyntaxTree, this._currentFileVersion); + } + + if (syntaxTree !== null) { + // All done, ensure state is up to date + this._currentFileScriptSnapshot = scriptSnapshot; + this._currentFileVersion = version; + this._currentfilename = filename; + this._currentFileSyntaxTree = syntaxTree; + } + + return this._currentFileSyntaxTree; + } + + public getCurrentScriptSnapshot(filename: string): 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 = IncrementalParser.parse( + previousSyntaxTree, editRange, 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 = fullText(incrementalTree.sourceUnit()); + var actualSnapshotText = newScriptSnapshot.getText(0, newScriptSnapshot.getLength()); + Debug.assert(incrementalTreeText === actualSnapshotText, 'Expected full texts to be equal'); + } + } + } + + interface FormattingOptions { + useTabs: boolean; + spacesPerTab: number; + indentSpaces: number; + newLineCharacter: string; + } + + class DocumentRegistryEntry { + public refCount: number = 0; + public owners: string[] = []; + constructor(public document: Document) { + } + } + + export interface IDocumentRegistry { + acquireDocument( + filename: string, + compilationSettings: ts.CompilerOptions, + scriptSnapshot: IScriptSnapshot, + byteOrderMark: ts.ByteOrderMark, + version: number, + isOpen: boolean, + referencedFiles: string[]): Document; + + updateDocument( + document: Document, + filename: string, + compilationSettings: ts.CompilerOptions, + scriptSnapshot: IScriptSnapshot, + version: number, + isOpen: boolean, + textChangeRange: TextChangeRange + ): Document; + + releaseDocument(filename: string, compilationSettings: ts.CompilerOptions): void + } + + export class DocumentRegistry implements IDocumentRegistry { + private buckets: ts.Map> = {}; + + private getKeyFromCompilationSettings(settings: ts.CompilerOptions): string { + return "_" + ts.ScriptTarget[settings.target]; // + "|" + settings.propagateEnumConstants.toString() + } + + private getBucketForCompilationSettings(settings: ts.CompilerOptions, createIfMissing: boolean): ts.Map { + var key = this.getKeyFromCompilationSettings(settings); + var bucket = this.buckets[key]; + if (!bucket && createIfMissing) { + this.buckets[key] = bucket = {}; + } + return bucket; + } + + public reportStats() { + var bucketInfoArray = Object.keys(this.buckets).filter(name => name && name.charAt(0) === '_').map(name => { + var entries = this.buckets[name]; + var documents = []; + for (var i in entries) { + var entry = entries[i]; + documents.push({ + name: i, + refCount: entry.refCount, + references: entry.owners.slice(0) + }); + } + documents.sort((x, y) => y.refCount - x.refCount); + return { bucket: name, documents: documents } + }); + return JSON.stringify(bucketInfoArray, null, 2); + } + + public acquireDocument( + filename: string, + compilationSettings: ts.CompilerOptions, + scriptSnapshot: IScriptSnapshot, + byteOrderMark: ts.ByteOrderMark, + version: number, + isOpen: boolean, + referencedFiles: string[]= []): Document { + + var bucket = this.getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true); + var entry = bucket[filename]; + if (!entry) { + var document = Document.create(compilationSettings, filename, scriptSnapshot, byteOrderMark, version, isOpen, referencedFiles); + + entry = new DocumentRegistryEntry(document); + bucket[filename] = entry; + } + entry.refCount++; + + return entry.document; + } + + public updateDocument( + document: Document, + filename: string, + compilationSettings: ts.CompilerOptions, + scriptSnapshot: IScriptSnapshot, + version: number, + isOpen: boolean, + textChangeRange: TextChangeRange + ): Document { + + var bucket = this.getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ false); + Debug.assert(bucket); + var entry = bucket[filename]; + Debug.assert(entry); + + if (entry.document.isOpen === isOpen && entry.document.version === version) { + return entry.document; + } + + entry.document = entry.document.update(scriptSnapshot, version, isOpen, textChangeRange); + return entry.document; + } + + public releaseDocument(filename: string, compilationSettings: ts.CompilerOptions): void { + var bucket = this.getBucketForCompilationSettings(compilationSettings, false); + Debug.assert(bucket); + + var entry = bucket[filename]; + entry.refCount--; + + Debug.assert(entry.refCount >= 0); + if (entry.refCount === 0) { + delete bucket[filename]; + } + } + } + + export function createLanguageService(host: LanguageServiceHost, documentRegistry: IDocumentRegistry): LanguageService { + var logger: TypeScript.Logger = host; + var _syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); + var formattingRulesProvider: Formatting.RulesProvider; + var hostCache: HostCache; // A cache of all the information about the files on the host side. + var program: ts.Program; + var typeChecker: ts.TypeChecker; + var useCaseSensitivefilenames = false; + var documentsByName: ts.Map = {}; + var documentRegistry = documentRegistry; + var cancellationToken = new CancellationToken(host.getCancellationToken()); + var activeCompletionSession: CompletionSession; + + // Check if the localized messages json is set, otherwise query the host for it + if (!TypeScript.LocalizedDiagnosticMessages) { + TypeScript.LocalizedDiagnosticMessages = host.getLocalizedDiagnosticMessages(); + } + + function createCompilerHost(): ts.CompilerHost { + return { + getSourceFile: (filename, languageVersion) => { + var document = documentsByName[filename]; + + Debug.assert(!!document, "document can not be undefined"); + + return document.sourceFile(); + }, + getCancellationToken: () => cancellationToken, + getCanonicalFileName: (filename) => useCaseSensitivefilenames ? filename : filename.toLowerCase(), + useCaseSensitiveFileNames: () => useCaseSensitivefilenames, + getNewLine: () => "\n", + // Need something that doesn't depend on sys.ts here + getDefaultLibFilename: (): string => { + throw Error("TOD:: getDefaultLibfilename"); + }, + writeFile: (filename, data) => { + throw Error("TODO: write file"); + }, + getCurrentDirectory: (): string => { + throw Error("TODO: getCurrentDirectory"); + } + }; + } + + function synchronizeHostData(): void { + // Reset the cache at start of every refresh + hostCache = new HostCache(host); + + var compilationSettings = hostCache.compilationSettings(); + + // TODO: check if we need to create a new compiler to start with + // 1. files are identical + // 2. compilation settings are identical + + // 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 + var settingsChangeAffectsSyntax = oldSettings.target !== compilationSettings.target; + + 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 documentsByName[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 document: Document = documentsByName[filename]; + if (document) { + // + // If the document is the same, assume no update + // + if (document.version === version && document.isOpen === isOpen) { + 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: TextChangeRange = null; + if (document.isOpen && isOpen) { + textChangeRange = hostCache.getScriptTextChangeRangeSinceVersion(filename, document.version); + } + + document = documentRegistry.updateDocument(document, filename, compilationSettings, scriptSnapshot, version, isOpen, textChangeRange); + } + else { + document = documentRegistry.acquireDocument(filename, compilationSettings, scriptSnapshot, hostCache.getByteOrderMark(filename), version, isOpen, []); + } + + // Remeber the new document + documentsByName[filename] = document; + } + + // Now create a new compiler + program = ts.createProgram(hostfilenames, compilationSettings, createCompilerHost()); + typeChecker = program.getTypeChecker(); + } + + function dispose(): void { + if (program) { + ts.forEach(program.getSourceFiles(), + (f) => documentRegistry.releaseDocument(f.filename, program.getCompilerOptions())); + } + } + + /// Diagnostics + function getSyntacticDiagnostics(filename: string) { + synchronizeHostData(); + return program.getDiagnostics(program.getSourceFile(filename)); + } + + function getSemanticDiagnostics(filename: string) { + synchronizeHostData(); + return typeChecker.getDiagnostics(program.getSourceFile(filename)); + } + + function getCompilerOptionsDiagnostics() { + synchronizeHostData(); + return program.getGlobalDiagnostics(); + } + + /// Completion + function createCompletionEntry(symbol: ts.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 = CompletionHelpers.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: ts.Symbol[], session: CompletionSession): void { + ts.forEach(symbols, (symbol) => { + var entry = createCompletionEntry(symbol); + if (entry) { + session.entries.push(entry); + session.symbols[entry.name] = symbol; + } + }); + } + + synchronizeHostData(); + + filename = TypeScript.switchToForwardSlashes(filename); + + var document = documentsByName[filename]; + var sourceUnit = document.sourceUnit(); + + if (CompletionHelpers.isCompletionListBlocker(document.syntaxTree().sourceUnit(), position)) { + logger.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 && + start(node) === end(node)) { + // Ignore missing name nodes + node = node.parent; + } + + var isRightOfDot = false; + if (node && + node.kind() === TypeScript.SyntaxKind.MemberAccessExpression && + end((node).expression) < position) { + + isRightOfDot = true; + node = (node).expression; + } + else if (node && + node.kind() === TypeScript.SyntaxKind.QualifiedName && + end((node).left) < position) { + + isRightOfDot = true; + node = (node).left; + } + else if (node && node.parent && + node.kind() === TypeScript.SyntaxKind.IdentifierName && + node.parent.kind() === TypeScript.SyntaxKind.MemberAccessExpression && + (node.parent).name === node) { + + isRightOfDot = true; + node = (node.parent).expression; + } + else if (node && node.parent && + node.kind() === TypeScript.SyntaxKind.IdentifierName && + node.parent.kind() === TypeScript.SyntaxKind.QualifiedName && + (node.parent).right === node) { + + isRightOfDot = true; + node = (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(document.sourceFile(), 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: typeChecker + }; + + // Right of dot member completion list + if (isRightOfDot) { + var type: ts.Type = typeChecker.getTypeOfExpression(mappedNode); + if (!type) { + return undefined; + } + + var symbols = type.getApparentProperties(); + isMemberCompletion = true; + getCompletionEntriesFromSymbols(symbols, activeCompletionSession); + } + else { + var containingObjectLiteral = CompletionHelpers.getContainingObjectLiteralApplicableForCompletion(document.syntaxTree().sourceUnit(), position); + + // Object literal expression, look up possible property names from contextual type + if (containingObjectLiteral) { + var searchPosition = Math.min(position, 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: CompletionHelpers.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 = ts.SymbolFlags.Type | ts.SymbolFlags.Value | ts.SymbolFlags.Namespace; + var symbols = typeChecker.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.getKeywordCompltions()); + } + + 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 = 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: typeChecker.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: ts.SourceFile, position: number) { + var current: ts.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 (ts.getTokenPosOfNode(child) <= position && position < child.end) { + current = child; + continue outer; + } + if (child.end > position) break; + } + return current; + } + } + + function getEnclosingDeclaration(node: ts.Node): ts.Node { + while (true) { + node = node.parent; + if (!node) { + return node; + } + switch (node.kind) { + case ts.SyntaxKind.Method: + case ts.SyntaxKind.FunctionDeclaration: + case ts.SyntaxKind.FunctionExpression: + case ts.SyntaxKind.GetAccessor: + case ts.SyntaxKind.SetAccessor: + case ts.SyntaxKind.ClassDeclaration: + case ts.SyntaxKind.InterfaceDeclaration: + case ts.SyntaxKind.EnumDeclaration: + case ts.SyntaxKind.ModuleDeclaration: + return node; + } + } + } + + function getSymbolKind(symbol: ts.Symbol): string { + var flags = symbol.getFlags(); + + if (flags & ts.SymbolFlags.Module) return ScriptElementKind.moduleElement; + if (flags & ts.SymbolFlags.Class) return ScriptElementKind.classElement; + if (flags & ts.SymbolFlags.Interface) return ScriptElementKind.interfaceElement; + if (flags & ts.SymbolFlags.Enum) return ScriptElementKind.enumElement; + if (flags & ts.SymbolFlags.Variable) return ScriptElementKind.variableElement; + if (flags & ts.SymbolFlags.Function) return ScriptElementKind.functionElement; + if (flags & ts.SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement; + if (flags & ts.SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement; + if (flags & ts.SymbolFlags.Method) return ScriptElementKind.memberFunctionElement; + if (flags & ts.SymbolFlags.Property) return ScriptElementKind.memberVariableElement; + if (flags & ts.SymbolFlags.IndexSignature) return ScriptElementKind.indexSignatureElement; + if (flags & ts.SymbolFlags.ConstructSignature) return ScriptElementKind.constructSignatureElement; + if (flags & ts.SymbolFlags.CallSignature) return ScriptElementKind.callSignatureElement; + if (flags & ts.SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; + if (flags & ts.SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement; + if (flags & ts.SymbolFlags.EnumMember) return ScriptElementKind.variableElement; + + return ScriptElementKind.unknown; + } + + function getTypeKind(type: ts.Type): string { + var flags = type.getFlags(); + + if (flags & ts.TypeFlags.Enum) return ScriptElementKind.enumElement; + if (flags & ts.TypeFlags.Class) return ScriptElementKind.classElement; + if (flags & ts.TypeFlags.Interface) return ScriptElementKind.interfaceElement; + if (flags & ts.TypeFlags.TypeParameter) return ScriptElementKind.typeParameterElement; + if (flags & ts.TypeFlags.Intrinsic) return ScriptElementKind.primitiveType; + if (flags & ts.TypeFlags.StringLiteral) return ScriptElementKind.primitiveType; + + return ScriptElementKind.unknown; + } + + function getNodeModifiers(node: ts.Node): string { + var flags = node.flags; + var result: string[] = []; + + if (flags & ts.NodeFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); + if (flags & ts.NodeFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier); + if (flags & ts.NodeFlags.Static) result.push(ScriptElementKindModifier.staticModifier); + if (flags & ts.NodeFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); + if (ts.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 document = documentsByName[filename]; + var node = getNodeAtPosition(document.sourceFile(), position); + if (!node) return undefined; + + switch (node.kind) { + // A declaration + case ts.SyntaxKind.Identifier: + if (node.parent.kind === ts.SyntaxKind.CallExpression || node.parent.kind === ts.SyntaxKind.NewExpression) { + // TODO: handle new and call expressions + } + + var symbol = typeChecker.getSymbolOfIdentifier(node); + Debug.assert(symbol, "getTypeAtPosition: Could not find symbol for node"); + var type = typeChecker.getTypeOfSymbol(symbol); + + return { + memberName: new MemberNameString(typeChecker.typeToString(type)), + docComment: "", + fullSymbolName: typeChecker.symbolToString(symbol, getEnclosingDeclaration(node)), + kind: getSymbolKind(symbol), + minChar: node.pos, + limChar: node.end + }; + + // An Expression + case ts.SyntaxKind.ThisKeyword: + case ts.SyntaxKind.QualifiedName: + case ts.SyntaxKind.SuperKeyword: + case ts.SyntaxKind.StringLiteral: + var type = typeChecker.getTypeOfExpression(node); + Debug.assert(type, "getTypeAtPosition: Could not find type for node"); + return { + memberName: new MemberNameString(""), + docComment: "", + fullSymbolName: typeChecker.typeToString(type, getEnclosingDeclaration(node)), + kind: getTypeKind(type), + minChar: node.pos, + limChar: node.end + }; + break; + } + } + + /// Syntactic features + function getSyntaxTree(filename: string): TypeScript.SyntaxTree { + filename = TypeScript.switchToForwardSlashes(filename); + return _syntaxTreeCache.getCurrentFileSyntaxTree(filename); + } + + 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() === SyntaxKind.ParameterList && ast.parent.kind() === SyntaxKind.CallSignature && ast.parent.parent.kind() === SyntaxKind.ConstructorDeclaration) { + ast = ast.parent.parent; + } + + switch (ast.kind()) { + default: + return null; + case TypeScript.SyntaxKind.ConstructorDeclaration: + var constructorAST = ast; + if (!isConstructorValidPosition || !(position >= start(constructorAST) && position <= 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: start(node), + limChar: 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[] = []; + GetScriptLexicalStructureWalker.getListsOfAllScriptLexicalStructure(items, filename, syntaxTree.sourceUnit()); + return items; + } + + function getOutliningRegions(filename: string) { + // doesn't use compiler - no need to synchronize with host + filename = TypeScript.switchToForwardSlashes(filename); + var syntaxTree = getSyntaxTree(filename); + return OutliningElementsCollector.collectElements(syntaxTree.sourceUnit()); + } + + function getBraceMatchingAtPosition(filename: string, position: number) { + filename = TypeScript.switchToForwardSlashes(filename); + var syntaxTree = getSyntaxTree(filename); + return 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 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(logger); + } + + 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: () => { }, + getSyntacticDiagnostics: getSyntacticDiagnostics, + getSemanticDiagnostics: getSemanticDiagnostics, + getCompilerOptionsDiagnostics: getCompilerOptionsDiagnostics, + getCompletionsAtPosition: getCompletionsAtPosition, + getCompletionEntryDetails: getCompletionEntryDetails, + getTypeAtPosition: getTypeAtPosition, + getSignatureAtPosition: (filename, position) => undefined, + getDefinitionAtPosition: (filename, position) => [], + getReferencesAtPosition: (filename, position) => [], + 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) => undefined, + }; + } +} \ No newline at end of file diff --git a/src/services/typescriptServices.ts b/src/services/typescriptServices.ts index d4af024bd31..249afeaf9b9 100644 --- a/src/services/typescriptServices.ts +++ b/src/services/typescriptServices.ts @@ -20,32 +20,14 @@ /// /// -/// - -/// -/// -/// /// -/// -/// -/// -/// -/// -/// -/// -/// - - -/// /// /// -/// /// /// /// /// -/// /// /// ///