TypeScript/src/services/services.ts

2406 lines
104 KiB
TypeScript

/// <reference path="..\compiler\types.ts"/>
/// <reference path="..\compiler\core.ts"/>
/// <reference path="..\compiler\scanner.ts"/>
/// <reference path="..\compiler\parser.ts"/>
/// <reference path="..\compiler\checker.ts"/>
/// <reference path='syntax\incrementalParser.ts' />
/// <reference path='outliningElementsCollector.ts' />
/// <reference path='getScriptLexicalStructureWalker.ts' />
/// <reference path='braceMatcher.ts' />
/// <reference path='breakpoints.ts' />
/// <reference path='indentation.ts' />
/// <reference path='formatting\formatting.ts' />
/// <reference path='compiler\bloomFilter.ts' />
/// <reference path='core\references.ts' />
/// <reference path='resources\references.ts' />
/// <reference path='text\references.ts' />
/// <reference path='syntax\references.ts' />
/// <reference path='compiler\diagnostics.ts' />
/// <reference path='compiler\hashTable.ts' />
/// <reference path='compiler\ast.ts' />
/// <reference path='compiler\astWalker.ts' />
/// <reference path='compiler\astHelpers.ts' />
/// <reference path='compiler\types.ts' />
/// <reference path='compiler\pathUtils.ts' />
module ts {
export interface Node {
getSourceFile(): SourceFile;
getChildCount(): number;
getChildAt(index: number): Node;
getChildren(): Node[];
getStart(): number;
getFullStart(): number;
getEnd(): number;
getWidth(): number;
getFullWidth(): number;
getLeadingTriviaWidth(): number;
getFullText(): string;
getFirstToken(): Node;
getLastToken(): Node;
}
export interface Symbol {
getFlags(): SymbolFlags;
getName(): string;
getDeclarations(): Declaration[];
}
export interface Type {
getFlags(): TypeFlags;
getSymbol(): Symbol;
getProperties(): Symbol[];
getProperty(propertyName: string): Symbol;
getApparentProperties(): Symbol[];
getCallSignatures(): Signature[];
getConstructSignatures(): Signature[];
getStringIndexType(): Type;
getNumberIndexType(): Type;
}
export interface Signature {
getDeclaration(): SignatureDeclaration;
getTypeParameters(): Type[];
getParameters(): Symbol[];
getReturnType(): Type;
}
var scanner: Scanner = createScanner(ScriptTarget.ES5);
var emptyArray: any [] = [];
function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject {
var node = <NodeObject> new (getNodeConstructor(kind))();
node.pos = pos;
node.end = end;
node.flags = flags;
node.parent = parent;
return node;
}
class NodeObject implements Node {
public kind: SyntaxKind;
public pos: number;
public end: number;
public flags: NodeFlags;
public parent: Node;
private _children: Node[];
public getSourceFile(): SourceFile {
var node: Node = this;
while (node.kind !== SyntaxKind.SourceFile) node = node.parent;
return <SourceFile>node;
}
public getStart(): number {
return getTokenPosOfNode(this);
}
public getFullStart(): number {
return this.pos;
}
public getEnd(): number {
return this.end;
}
public getWidth(): number {
return this.getEnd() - this.getStart();
}
public getFullWidth(): number {
return this.end - this.getFullStart();
}
public getLeadingTriviaWidth(): number {
return this.getStart() - this.pos;
}
public getFullText(): string {
return this.getSourceFile().text.substring(this.pos, this.end);
}
private addSyntheticNodes(nodes: Node[], pos: number, end: number): number {
scanner.setTextPos(pos);
while (pos < end) {
var token = scanner.scan();
var textPos = scanner.getTextPos();
var node = nodes.push(createNode(token, pos, textPos, NodeFlags.Synthetic, this));
pos = textPos;
}
return pos;
}
private createSyntaxList(nodes: NodeArray<Node>): Node {
var list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, NodeFlags.Synthetic, this);
list._children = [];
var pos = nodes.pos;
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
if (pos < node.pos) {
pos = this.addSyntheticNodes(list._children, pos, node.pos);
}
list._children.push(node);
pos = node.end;
}
if (pos < nodes.end) {
this.addSyntheticNodes(list._children, pos, nodes.end);
}
return list;
}
private createChildren() {
if (this.kind > SyntaxKind.Missing) {
scanner.setText(this.getSourceFile().text);
var children: Node[] = [];
var pos = this.pos;
var processNode = (node: Node) => {
if (pos < node.pos) {
pos = this.addSyntheticNodes(children, pos, node.pos);
}
children.push(node);
pos = node.end;
};
var processNodes = (nodes: NodeArray<Node>) => {
if (pos < nodes.pos) {
pos = this.addSyntheticNodes(children, pos, nodes.pos);
}
children.push(this.createSyntaxList(<NodeArray<Node>>nodes));
pos = nodes.end;
};
forEachChild(this, processNode, processNodes);
if (pos < this.end) {
this.addSyntheticNodes(children, pos, this.end);
}
scanner.setText(undefined);
}
this._children = children || emptyArray;
}
public getChildCount(): number {
if (!this._children) this.createChildren();
return this._children.length;
}
public getChildAt(index: number): Node {
if (!this._children) this.createChildren();
return this._children[index];
}
public getChildren(): Node[] {
if (!this._children) this.createChildren();
return this._children;
}
public getFirstToken(): Node {
var children = this.getChildren();
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.kind < SyntaxKind.Missing) return child;
if (child.kind > SyntaxKind.Missing) return child.getFirstToken();
}
}
public getLastToken(): Node {
var children = this.getChildren();
for (var i = children.length - 1; i >= 0; i--) {
var child = children[i];
if (child.kind < SyntaxKind.Missing) return child;
if (child.kind > SyntaxKind.Missing) return child.getLastToken();
}
}
}
class SymbolObject implements Symbol {
flags: SymbolFlags;
name: string;
declarations: Declaration[];
constructor(flags: SymbolFlags, name: string) {
this.flags = flags;
this.name = name;
}
getFlags(): SymbolFlags {
return this.flags;
}
getName(): string {
return this.name;
}
getDeclarations(): Declaration[] {
return this.declarations;
}
}
class TypeObject implements Type {
checker: TypeChecker;
flags: TypeFlags;
id: number;
symbol: Symbol;
constructor(checker: TypeChecker, flags: TypeFlags) {
this.checker = checker;
this.flags = flags;
}
getFlags(): TypeFlags {
return this.flags;
}
getSymbol(): Symbol {
return this.symbol;
}
getProperties(): Symbol[] {
return this.checker.getPropertiesOfType(this);
}
getProperty(propertyName: string): Symbol {
return this.checker.getPropertyOfType(this, propertyName);
}
getApparentProperties(): Symbol[]{
return this.checker.getAugmentedPropertiesOfApparentType(this);
}
getCallSignatures(): Signature[] {
return this.checker.getSignaturesOfType(this, SignatureKind.Call);
}
getConstructSignatures(): Signature[] {
return this.checker.getSignaturesOfType(this, SignatureKind.Construct);
}
getStringIndexType(): Type {
return this.checker.getIndexTypeOfType(this, IndexKind.String);
}
getNumberIndexType(): Type {
return this.checker.getIndexTypeOfType(this, IndexKind.Number);
}
}
class SignatureObject implements Signature {
checker: TypeChecker;
declaration: SignatureDeclaration;
typeParameters: TypeParameter[];
parameters: Symbol[];
resolvedReturnType: Type;
minArgumentCount: number;
hasRestParameter: boolean;
hasStringLiterals: boolean;
constructor(checker: TypeChecker) {
this.checker = checker;
}
getDeclaration(): SignatureDeclaration {
return this.declaration;
}
getTypeParameters(): Type[] {
return this.typeParameters;
}
getParameters(): Symbol[] {
return this.parameters;
}
getReturnType(): Type {
return this.checker.getReturnTypeOfSignature(this);
}
}
export interface Logger {
information(): boolean;
debug(): boolean;
warning(): boolean;
error(): boolean;
fatal(): boolean;
log(s: string): void;
}
//
// Public interface of the host of a language service instance.
//
export interface LanguageServiceHost extends Logger {
getCompilationSettings(): CompilerOptions;
getScriptFileNames(): string[];
getScriptVersion(fileName: string): number;
getScriptIsOpen(fileName: string): boolean;
getScriptByteOrderMark(fileName: string): ByteOrderMark;
getScriptSnapshot(fileName: string): TypeScript.IScriptSnapshot;
getLocalizedDiagnosticMessages(): any;
getCancellationToken(): CancellationToken;
}
//
// Public services of a language service instance associated
// with a language service host instance
//
export interface LanguageService {
// Note: refresh is a no-op now. It is only around for back compat purposes.
refresh(): void;
cleanupSemanticCache(): void;
getSyntacticDiagnostics(fileName: string): Diagnostic[];
getSemanticDiagnostics(fileName: string): Diagnostic[];
getCompilerOptionsDiagnostics(): Diagnostic[];
getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): CompletionInfo;
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
getTypeAtPosition(fileName: string, position: number): TypeInfo;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): SpanInfo;
getBreakpointStatementAtPosition(fileName: string, position: number): SpanInfo;
getSignatureAtPosition(fileName: string, position: number): SignatureInfo;
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[];
getImplementorsAtPosition(fileName: string, position: number): ReferenceEntry[];
getNavigateToItems(searchValue: string): NavigateToItem[];
getScriptLexicalStructure(fileName: string): NavigateToItem[];
getOutliningRegions(fileName: string): TypeScript.TextSpan[];
getBraceMatchingAtPosition(fileName: string, position: number): TypeScript.TextSpan[];
getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number;
getFormattingEditsForRange(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[];
getFormattingEditsForDocument(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[];
getFormattingEditsOnPaste(fileName: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[];
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextEdit[];
getEmitOutput(fileName: string): EmitOutput;
//getSyntaxTree(fileName: string): TypeScript.SyntaxTree;
dispose(): void;
}
export interface ReferenceEntry {
fileName: string;
minChar: number;
limChar: number;
isWriteAccess: boolean;
}
export interface NavigateToItem {
name: string;
kind: string; // see ScriptElementKind
kindModifiers: string; // see ScriptElementKindModifier, comma separated
matchKind: string;
fileName: string;
minChar: number;
limChar: number;
additionalSpans?: SpanInfo[];
containerName: string;
containerKind: string; // see ScriptElementKind
}
export interface TextEdit {
minChar: number;
limChar: number;
text: string;
}
export interface EditorOptions {
IndentSize: number;
TabSize: number;
NewLineCharacter: string;
ConvertTabsToSpaces: boolean;
}
export interface FormatCodeOptions extends EditorOptions {
InsertSpaceAfterCommaDelimiter: boolean;
InsertSpaceAfterSemicolonInForStatements: boolean;
InsertSpaceBeforeAndAfterBinaryOperators: boolean;
InsertSpaceAfterKeywordsInControlFlowStatements: boolean;
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean;
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
}
export interface DefinitionInfo {
fileName: string;
minChar: number;
limChar: number;
kind: string;
name: string;
containerKind: string;
containerName: string;
}
export interface MemberName {
prefix: string;
suffix: string;
text: string;
}
export interface TypeInfo {
memberName: MemberName;
docComment: string;
fullSymbolName: string;
kind: string;
minChar: number;
limChar: number;
}
export interface SpanInfo {
minChar: number;
limChar: number;
// text?: string;
}
export interface SignatureInfo {
actual: ActualSignatureInfo;
formal: FormalSignatureItemInfo[]; // Formal signatures
activeFormal: number; // Index of the "best match" formal signature
}
export interface FormalSignatureItemInfo {
signatureInfo: string;
typeParameters: FormalTypeParameterInfo[];
parameters: FormalParameterInfo[]; // Array of parameters
docComment: string; // Help for the signature
}
export interface FormalTypeParameterInfo {
name: string; // Type parameter name
docComment: string; // Comments that contain help for the parameter
minChar: number; // minChar for parameter info in the formal signature info string
limChar: number; // lim char for parameter info in the formal signature info string
}
export interface FormalParameterInfo {
name: string; // Parameter name
isVariable: boolean; // true if parameter is var args
docComment: string; // Comments that contain help for the parameter
minChar: number; // minChar for parameter info in the formal signature info string
limChar: number; // lim char for parameter info in the formal signature info string
}
export interface ActualSignatureInfo {
parameterMinChar: number;
parameterLimChar: number;
currentParameterIsTypeParameter: boolean; // current parameter is a type argument or a normal argument
currentParameter: number; // Index of active parameter in "parameters" or "typeParamters" array
}
export interface CompletionInfo {
isMemberCompletion: boolean;
entries: CompletionEntry[];
}
export interface CompletionEntry {
name: string;
kind: string; // see ScriptElementKind
kindModifiers: string; // see ScriptElementKindModifier, comma separated
}
export interface CompletionEntryDetails {
name: string;
kind: string; // see ScriptElementKind
kindModifiers: string; // see ScriptElementKindModifier, comma separated
type: string;
fullSymbolName: string;
docComment: string;
}
export enum EmitOutputResult {
Succeeded,
FailedBecauseOfSyntaxErrors,
FailedBecauseOfCompilerOptionsErrors,
FailedToGenerateDeclarationsBecauseOfSemanticErrors
}
export interface EmitOutput {
outputFiles: OutputFile[];
emitOutputResult: EmitOutputResult;
}
export enum OutputFileType {
JavaScript,
SourceMap,
Declaration
}
export interface OutputFile {
name: string;
writeByteOrderMark: boolean;
text: string;
fileType: OutputFileType;
sourceMapOutput: any;
}
export enum EndOfLineState {
Start,
InMultiLineCommentTrivia,
InSingleQuoteStringLiteral,
InDoubleQuoteStringLiteral,
}
export enum TokenClass {
Punctuation,
Keyword,
Operator,
Comment,
Whitespace,
Identifier,
NumberLiteral,
StringLiteral,
RegExpLiteral,
}
export interface ClassificationResult {
finalLexState: EndOfLineState;
entries: ClassificationInfo[];
}
export interface ClassificationInfo {
length: number;
classification: TokenClass;
}
export interface Classifier {
getClassificationsForLine(text: string, lexState: EndOfLineState): ClassificationResult;
}
export interface DocumentRegistry {
acquireDocument(
filename: string,
compilationSettings: CompilerOptions,
scriptSnapshot: TypeScript.IScriptSnapshot,
byteOrderMark: ByteOrderMark,
version: number,
isOpen: boolean,
referencedFiles: string[]): Document;
updateDocument(
document: Document,
filename: string,
compilationSettings: CompilerOptions,
scriptSnapshot: TypeScript.IScriptSnapshot,
version: number,
isOpen: boolean,
textChangeRange: TypeScript.TextChangeRange
): Document;
releaseDocument(filename: string, compilationSettings: CompilerOptions): void
}
// TODO: move these to enums
export class ScriptElementKind {
static unknown = "";
// predefined type (void) or keyword (class)
static keyword = "keyword";
// top level script node
static scriptElement = "script";
// module foo {}
static moduleElement = "module";
// class X {}
static classElement = "class";
// interface Y {}
static interfaceElement = "interface";
// enum E
static enumElement = "enum";
// Inside module and script only
// var v = ..
static variableElement = "var";
// Inside function
static localVariableElement = "local var";
// Inside module and script only
// function f() { }
static functionElement = "function";
// Inside function
static localFunctionElement = "local function";
// class X { [public|private]* foo() {} }
static memberFunctionElement = "method";
// class X { [public|private]* [get|set] foo:number; }
static memberGetAccessorElement = "getter";
static memberSetAccessorElement = "setter";
// class X { [public|private]* foo:number; }
// interface Y { foo:number; }
static memberVariableElement = "property";
// class X { constructor() { } }
static constructorImplementationElement = "constructor";
// interface Y { ():number; }
static callSignatureElement = "call";
// interface Y { []:number; }
static indexSignatureElement = "index";
// interface Y { new():Y; }
static constructSignatureElement = "construct";
// function foo(*Y*: string)
static parameterElement = "parameter";
static typeParameterElement = "type parameter";
static primitiveType = "primitive type";
static label = "label";
}
export class ScriptElementKindModifier {
static none = "";
static publicMemberModifier = "public";
static privateMemberModifier = "private";
static exportedModifier = "export";
static ambientModifier = "declare";
static staticModifier = "static";
}
export class MatchKind {
static none: string = null;
static exact = "exact";
static subString = "substring";
static prefix = "prefix";
}
interface IncrementalParse {
(oldSyntaxTree: TypeScript.SyntaxTree, textChangeRange: TypeScript.TextChangeRange, newText: TypeScript.ISimpleText): TypeScript.SyntaxTree
}
export interface Document {
getFilename(): string;
getByteOrderMark(): ByteOrderMark;
getVersion(): number;
isOpen(): boolean;
getSourceUnit(): TypeScript.SourceUnitSyntax;
getSyntaxTree(): TypeScript.SyntaxTree;
getSourceFile(): SourceFile;
getBloomFilter(): TypeScript.BloomFilter;
update(scriptSnapshot: TypeScript.IScriptSnapshot, version: number, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): Document;
}
class DocumentObject implements Document {
private bloomFilter: TypeScript.BloomFilter = null;
// By default, our Document class doesn't support incremental update of its conten
// However, we enable other layers (like teh services layer) to inject the capability
// into us by setting this function.
public static incrementalParse: IncrementalParse = TypeScript.IncrementalParser.parse;
constructor(private compilationSettings: CompilerOptions,
public filename: string,
public referencedFiles: string[],
private scriptSnapshot: TypeScript.IScriptSnapshot,
public byteOrderMark: ByteOrderMark,
public version: number,
public _isOpen: boolean,
private syntaxTree: TypeScript.SyntaxTree,
private soruceFile: SourceFile) {
}
public getFilename(): string {
return this.filename;
}
public getVersion(): number {
return this.version;
}
public isOpen(): boolean {
return this._isOpen;
}
public getByteOrderMark(): ByteOrderMark {
return this.byteOrderMark;
}
public isDeclareFile(): boolean {
return TypeScript.isDTSFile(this.filename);
}
public getSourceUnit(): TypeScript.SourceUnitSyntax {
// If we don't have a script, create one from our parse tree.
return this.getSyntaxTree().sourceUnit();
}
public getLineMap(): TypeScript.LineMap {
return this.getSyntaxTree().lineMap();
}
public getSyntaxTree(): TypeScript.SyntaxTree {
if (!this.syntaxTree) {
var start = new Date().getTime();
this.syntaxTree = TypeScript.Parser.parse(
this.filename, TypeScript.SimpleText.fromScriptSnapshot(this.scriptSnapshot), this.compilationSettings.target, this.isDeclareFile());
var time = new Date().getTime() - start;
//TypeScript.syntaxTreeParseTime += time;
}
return this.syntaxTree;
}
public getSourceFile(): SourceFile {
if (!this.soruceFile) {
var start = new Date().getTime();
this.soruceFile = 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 getBloomFilter(): TypeScript.BloomFilter {
if (!this.bloomFilter) {
var identifiers = TypeScript.createIntrinsicsObject<boolean>();
var pre = function (cur: TypeScript.ISyntaxElement) {
if (TypeScript.ASTHelpers.isValidAstNode(cur)) {
if (cur.kind() === TypeScript.SyntaxKind.IdentifierName) {
var nodeText = TypeScript.tokenValueText((<TypeScript.ISyntaxToken>cur));
identifiers[nodeText] = true;
}
}
};
TypeScript.getAstWalkerFactory().simpleWalk(this.getSourceUnit(), pre, null, identifiers);
var identifierCount = 0;
for (var name in identifiers) {
if (identifiers[name]) {
identifierCount++;
}
}
this.bloomFilter = new TypeScript.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.getSyntaxTree().isExternalModule();
}
public update(scriptSnapshot: TypeScript.IScriptSnapshot, version: number, isOpen: boolean, textChangeRange: TypeScript.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 = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
// If we don't have a text change, or we don't have an old syntax tree, then do a full
// parse. Otherwise, do an incremental parse.
var newSyntaxTree = textChangeRange === null || oldSyntaxTree === null || DocumentObject.incrementalParse === null
? TypeScript.Parser.parse(this.filename, text, this.compilationSettings.target, TypeScript.isDTSFile(this.filename))
: DocumentObject.incrementalParse(oldSyntaxTree, textChangeRange, text);
return new DocumentObject(this.compilationSettings, this.filename, this.referencedFiles, scriptSnapshot, this.byteOrderMark, version, isOpen, newSyntaxTree, /*soruceFile*/ null);
}
}
export function createDocument(compilationSettings: CompilerOptions, fileName: string, scriptSnapshot: TypeScript.IScriptSnapshot, byteOrderMark: ByteOrderMark, version: number, isOpen: boolean, referencedFiles: string[]): Document {
return new DocumentObject(compilationSettings, fileName, referencedFiles, scriptSnapshot, byteOrderMark, version, isOpen, /*syntaxTree:*/ null, /*soruceFile*/ null);
}
/// Language Service
interface CompletionSession {
filename: string; // the file where the completion was requested
position: number; // position in the file where the completion was requested
entries: CompletionEntry[]; // entries for this completion
symbols: Map<Symbol>; // symbols by entry name map
location: Node; // the node where the completion was requested
typeChecker: TypeChecker;// the typeChecker used to generate this completion
}
interface FormattingOptions {
useTabs: boolean;
spacesPerTab: number;
indentSpaces: number;
newLineCharacter: string;
}
// Information about a specific host file.
interface HostFileInformation {
filename: string;
version: number;
isOpen: boolean;
byteOrderMark: ByteOrderMark;
sourceText?: TypeScript.IScriptSnapshot;
}
interface DocumentRegistryEntry {
document: Document;
refCount: number;
owners: string[];
}
export function getDefaultCompilerOptions(): CompilerOptions {
// Set "ES5" target by default for language service
return {
target: ScriptTarget.ES5,
module: ModuleKind.None,
};
}
export function compareDataObjects(dst: any, src: any): boolean {
for (var e in dst) {
if (typeof dst[e] === "object") {
if (!compareDataObjects(dst[e], src[e]))
return false;
}
else if (typeof dst[e] !== "function") {
if (dst[e] !== src[e])
return false;
}
}
return true;
}
export class OperationCanceledException { }
class CancellationTokenObject {
public static None: CancellationTokenObject = new CancellationTokenObject(null)
constructor(private cancellationToken: CancellationToken) {
}
public isCancellationRequested() {
return this.cancellationToken && this.cancellationToken.isCancellationRequested();
}
public throwIfCancellationRequested(): void {
if (this.isCancellationRequested()) {
throw new OperationCanceledException();
}
}
}
// Cache host information about scrip Should be refreshed
// at each language service public entry point, since we don't know when
// set of scripts handled by the host changes.
class HostCache {
private filenameToEntry: Map<HostFileInformation>;
private _compilationSettings: CompilerOptions;
constructor(private host: LanguageServiceHost) {
// script id => script index
this.filenameToEntry = {};
var filenames = host.getScriptFileNames();
for (var i = 0, n = filenames.length; i < n; i++) {
var filename = filenames[i];
this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)] = {
filename: filename,
version: host.getScriptVersion(filename),
isOpen: host.getScriptIsOpen(filename),
byteOrderMark: host.getScriptByteOrderMark(filename)
};
}
this._compilationSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
}
public compilationSettings() {
return this._compilationSettings;
}
public getEntry(filename: string): HostFileInformation {
filename = TypeScript.switchToForwardSlashes(filename);
return lookUp(this.filenameToEntry, filename);
}
public contains(filename: string): boolean {
return !!this.getEntry(filename);
}
public getHostfilename(filename: string) {
var hostCacheEntry = this.getEntry(filename);
if (hostCacheEntry) {
return hostCacheEntry.filename;
}
return filename;
}
public getFilenames(): string[] {
var fileNames: string[] = [];
forEachKey(this.filenameToEntry, key => {
if (hasProperty(this.filenameToEntry, key))
fileNames.push(key);
});
return fileNames;
}
public getVersion(filename: string): number {
return this.getEntry(filename).version;
}
public isOpen(filename: string): boolean {
return this.getEntry(filename).isOpen;
}
public getByteOrderMark(filename: string): ByteOrderMark {
return this.getEntry(filename).byteOrderMark;
}
public getScriptSnapshot(filename: string): TypeScript.IScriptSnapshot {
var file = this.getEntry(filename);
if (!file.sourceText) {
file.sourceText = this.host.getScriptSnapshot(file.filename);
}
return file.sourceText;
}
public getScriptTextChangeRangeSinceVersion(filename: string, lastKnownVersion: number): TypeScript.TextChangeRange {
var currentVersion = this.getVersion(filename);
if (lastKnownVersion === currentVersion) {
return TypeScript.TextChangeRange.unchanged; // "No changes"
}
var scriptSnapshot = this.getScriptSnapshot(filename);
return scriptSnapshot.getTextChangeRangeSinceVersion(lastKnownVersion);
}
}
class SyntaxTreeCache {
private hostCache: HostCache;
// For our syntactic only features, we also keep a cache of the syntax tree for the
// currently edited file.
private currentfilename: string = "";
private currentFileVersion: number = -1;
private 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): TypeScript.IScriptSnapshot {
// update currentFileScriptSnapshot as a part of 'getCurrentFileSyntaxTree' call
this.getCurrentFileSyntaxTree(filename);
return this.currentFileScriptSnapshot;
}
private createSyntaxTree(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot): TypeScript.SyntaxTree {
var text = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
// For the purposes of features that use this syntax tree, we can just use the default
// compilation settings. The features only use the syntax (and not the diagnostics),
// and the syntax isn't affected by the compilation settings.
var syntaxTree = TypeScript.Parser.parse(filename, text, getDefaultCompilerOptions().target, TypeScript.isDTSFile(filename));
return syntaxTree;
}
private updateSyntaxTree(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, previousSyntaxTree: TypeScript.SyntaxTree, previousFileVersion: number): TypeScript.SyntaxTree {
var editRange = this.hostCache.getScriptTextChangeRangeSinceVersion(filename, previousFileVersion);
// Debug.assert(newLength >= 0);
// The host considers the entire buffer changed. So parse a completely new tree.
if (editRange === null) {
return this.createSyntaxTree(filename, scriptSnapshot);
}
var nextSyntaxTree = TypeScript.IncrementalParser.parse(
previousSyntaxTree, editRange, TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot));
this.ensureInvariants(filename, editRange, nextSyntaxTree, this.currentFileScriptSnapshot, scriptSnapshot);
return nextSyntaxTree;
}
private ensureInvariants(filename: string, editRange: TypeScript.TextChangeRange, incrementalTree: TypeScript.SyntaxTree, oldScriptSnapshot: TypeScript.IScriptSnapshot, newScriptSnapshot: TypeScript.IScriptSnapshot) {
// First, verify that the edit range and the script snapshots make sense.
// If this fires, then the edit range is completely bogus. Somehow the lengths of the
// old snapshot, the change range and the new snapshot aren't in sync. This is very
// bad.
var expectedNewLength = oldScriptSnapshot.getLength() - editRange.span().length() + editRange.newLength();
var actualNewLength = newScriptSnapshot.getLength();
function provideMoreDebugInfo() {
var debugInformation = ["expected length:", expectedNewLength, "and actual length:", actualNewLength, "are not equal\r\n"];
var oldSpan = editRange.span();
function prettyPrintString(s: string): string {
return '"' + s.replace(/\r/g, '\\r').replace(/\n/g, '\\n') + '"';
}
debugInformation.push('Edit range (old text) (start: ' + oldSpan.start() + ', end: ' + oldSpan.end() + ') \r\n');
debugInformation.push('Old text edit range contents: ' + prettyPrintString(oldScriptSnapshot.getText(oldSpan.start(), oldSpan.end())));
var newSpan = editRange.newSpan();
debugInformation.push('Edit range (new text) (start: ' + newSpan.start() + ', end: ' + newSpan.end() + ') \r\n');
debugInformation.push('New text edit range contents: ' + prettyPrintString(newScriptSnapshot.getText(newSpan.start(), newSpan.end())));
return debugInformation.join(' ');
}
Debug.assert(
expectedNewLength === actualNewLength,
"Expected length is different from actual!",
provideMoreDebugInfo);
if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) {
// If this fires, the text change range is bogus. It says the change starts at point
// 'X', but we can see a text difference *before* that point.
var oldPrefixText = oldScriptSnapshot.getText(0, editRange.span().start());
var newPrefixText = newScriptSnapshot.getText(0, editRange.span().start());
Debug.assert(oldPrefixText === newPrefixText, 'Expected equal prefix texts!');
// If this fires, the text change range is bogus. It says the change goes only up to
// point 'X', but we can see a text difference *after* that point.
var oldSuffixText = oldScriptSnapshot.getText(editRange.span().end(), oldScriptSnapshot.getLength());
var newSuffixText = newScriptSnapshot.getText(editRange.newSpan().end(), newScriptSnapshot.getLength());
Debug.assert(oldSuffixText === newSuffixText, 'Expected equal suffix texts!');
// Ok, text change range and script snapshots look ok. Let's verify that our
// incremental parsing worked properly.
//var normalTree = this.createSyntaxTree(filename, newScriptSnapshot);
//Debug.assert(normalTree.structuralEquals(incrementalTree), 'Expected equal incremental and normal trees');
// Ok, the trees looked good. So at least our incremental parser agrees with the
// normal parser. Now, verify that the incremental tree matches the contents of the
// script snapshot.
var incrementalTreeText = TypeScript.fullText(incrementalTree.sourceUnit());
var actualSnapshotText = newScriptSnapshot.getText(0, newScriptSnapshot.getLength());
Debug.assert(incrementalTreeText === actualSnapshotText, 'Expected full texts to be equal');
}
}
}
export function createDocumentRegistry(): DocumentRegistry {
var buckets: Map<Map<DocumentRegistryEntry>> = {};
function getKeyFromCompilationSettings(settings: CompilerOptions): string {
return "_" + ScriptTarget[settings.target]; // + "|" + settings.propagateEnumConstantoString()
}
function getBucketForCompilationSettings(settings: CompilerOptions, createIfMissing: boolean): Map<DocumentRegistryEntry> {
var key = getKeyFromCompilationSettings(settings);
var bucket = lookUp(buckets, key);
if (!bucket && createIfMissing) {
buckets[key] = bucket = {};
}
return bucket;
}
function reportStats() {
var bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === '_').map(name => {
var entries = lookUp(buckets, name);
var documents: { name: string; refCount: number; references: string[]; }[] = [];
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);
}
function acquireDocument(
filename: string,
compilationSettings: CompilerOptions,
scriptSnapshot: TypeScript.IScriptSnapshot,
byteOrderMark: ByteOrderMark,
version: number,
isOpen: boolean,
referencedFiles: string[]= []): Document {
var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true);
var entry = lookUp(bucket, filename);
if (!entry) {
var document = createDocument(compilationSettings, filename, scriptSnapshot, byteOrderMark, version, isOpen, referencedFiles);
bucket[filename] = entry = {
document: document,
refCount: 0,
owners: []
};
}
entry.refCount++;
return entry.document;
}
function updateDocument(
document: Document,
filename: string,
compilationSettings: CompilerOptions,
scriptSnapshot: TypeScript.IScriptSnapshot,
version: number,
isOpen: boolean,
textChangeRange: TypeScript.TextChangeRange
): Document {
var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ false);
Debug.assert(bucket);
var entry = lookUp(bucket, filename);
Debug.assert(entry);
if (entry.document.isOpen() === isOpen && entry.document.getVersion() === version) {
return entry.document;
}
entry.document = entry.document.update(scriptSnapshot, version, isOpen, textChangeRange);
return entry.document;
}
function releaseDocument(filename: string, compilationSettings: CompilerOptions): void {
var bucket = getBucketForCompilationSettings(compilationSettings, false);
Debug.assert(bucket);
var entry = lookUp(bucket, filename);
entry.refCount--;
Debug.assert(entry.refCount >= 0);
if (entry.refCount === 0) {
delete bucket[filename];
}
}
return {
acquireDocument: acquireDocument,
updateDocument: updateDocument,
releaseDocument: releaseDocument,
reportStats: reportStats
};
}
// A cache of completion entries for keywords, these do not change between sessions
var keywordCompletions:CompletionEntry[] = [];
for (var i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) {
keywordCompletions.push({
name: tokenToString(i),
kind: ScriptElementKind.keyword,
kindModifiers: ScriptElementKindModifier.none
});
}
export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry): LanguageService {
var syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
var formattingRulesProvider: TypeScript.Services.Formatting.RulesProvider;
var hostCache: HostCache; // A cache of all the information about the files on the host side.
var program: Program;
var typeChecker: TypeChecker;
var useCaseSensitivefilenames = false;
var documentsByName: Map<Document> = {};
var documentRegistry = documentRegistry;
var cancellationToken = new CancellationTokenObject(host.getCancellationToken());
var activeCompletionSession: CompletionSession; // The current active completion session, used to get the completion entry details
// Check if the localized messages json is set, otherwise query the host for it
if (!TypeScript.LocalizedDiagnosticMessages) {
TypeScript.LocalizedDiagnosticMessages = host.getLocalizedDiagnosticMessages();
}
function getDocument(filename: string): Document {
return lookUp(documentsByName, filename);
}
function createCompilerHost(): CompilerHost {
return {
getSourceFile: (filename, languageVersion) => {
var document = getDocument(filename);
Debug.assert(!!document, "document can not be undefined");
return document.getSourceFile();
},
getCancellationToken: () => cancellationToken,
getCanonicalFileName: (filename) => useCaseSensitivefilenames ? filename : filename.toLowerCase(),
useCaseSensitiveFileNames: () => useCaseSensitivefilenames,
getNewLine: () => "\r\n",
// Need something that doesn't depend on sys.ts here
getDefaultLibFilename: (): string => {
throw Error("TOD:: getDefaultLibfilename");
},
writeFile: (filename, data) => {
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
// TODO: is module still needed
var settingsChangeAffectsSyntax = oldSettings.target !== compilationSettings.target || oldSettings.module !== compilationSettings.module;
var changesInCompilationSettingsAffectSyntax =
oldSettings && compilationSettings && !compareDataObjects(oldSettings, compilationSettings) && settingsChangeAffectsSyntax;
var oldSourceFiles = program.getSourceFiles();
for (var i = 0, n = oldSourceFiles.length; i < n; i++) {
cancellationToken.throwIfCancellationRequested();
var filename = oldSourceFiles[i].filename;
if (!hostCache.contains(filename) || changesInCompilationSettingsAffectSyntax) {
documentRegistry.releaseDocument(filename, oldSettings);
delete 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 = getDocument(filename);
if (document) {
//
// If the document is the same, assume no update
//
if (document.getVersion() === 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: TypeScript.TextChangeRange = null;
if (document.isOpen && isOpen) {
textChangeRange = hostCache.getScriptTextChangeRangeSinceVersion(filename, document.getVersion());
}
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 = createProgram(hostfilenames, compilationSettings, createCompilerHost());
typeChecker = program.getTypeChecker();
}
function dispose(): void {
if (program) {
forEach(program.getSourceFiles(),
(f) => { documentRegistry.releaseDocument(f.filename, program.getCompilerOptions()); });
}
}
/// Diagnostics
function getSyntacticDiagnostics(filename: string) {
synchronizeHostData();
filename = TypeScript.switchToForwardSlashes(filename);
return program.getDiagnostics(getDocument(filename).getSourceFile());
}
function getSemanticDiagnostics(filename: string) {
synchronizeHostData();
filename = TypeScript.switchToForwardSlashes(filename)
return typeChecker.getDiagnostics(getDocument(filename).getSourceFile());
}
function getCompilerOptionsDiagnostics() {
synchronizeHostData();
return program.getGlobalDiagnostics();
}
/// Completion
function getValidCompletionEntryDisplayName(displayName: string, target: ScriptTarget): string {
if (displayName && displayName.length > 0) {
var firstChar = displayName.charCodeAt(0);
if (firstChar === TypeScript.CharacterCodes.singleQuote || firstChar === TypeScript.CharacterCodes.doubleQuote) {
// If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an
// invalid identifer name. We need to check if whatever was inside the quotes is actually a valid identifier name.
displayName = TypeScript.stripStartAndEndQuotes(displayName);
}
if (TypeScript.Scanner.isValidIdentifier(TypeScript.SimpleText.fromString(displayName), target)) {
return displayName;
}
}
return undefined;
}
function createCompletionEntry(symbol: Symbol): CompletionEntry {
// Try to get a valid display name for this symbol, if we could not find one, then ignore it.
// We would like to only show things that can be added after a dot, so for instance numeric properties can
// not be accessed with a dot (a.1 <- invalid)
var displayName = getValidCompletionEntryDisplayName(symbol.getName(), program.getCompilerOptions().target);
if (!displayName) {
return undefined;
}
var declarations = symbol.getDeclarations();
var firstDeclaration = [0];
return {
name: displayName,
kind: getSymbolKind(symbol),
kindModifiers: declarations ? getNodeModifiers(declarations[0]) : ScriptElementKindModifier.none
};
}
function getCompletionsAtPosition(filename: string, position: number, isMemberCompletion: boolean) {
function getCompletionEntriesFromSymbols(symbols: Symbol[], session: CompletionSession): void {
forEach(symbols, (symbol) => {
var entry = createCompletionEntry(symbol);
if (entry) {
session.entries.push(entry);
session.symbols[entry.name] = symbol;
}
});
}
function isCompletionListBlocker(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean {
// We shouldn't be getting a possition that is outside the file because
// isEntirelyInsideComment can't handle when the position is out of bounds,
// callers should be fixed, however we should be resiliant to bad inputs
// so we return true (this position is a blocker for getting completions)
if (position < 0 || position > TypeScript.fullWidth(sourceUnit)) {
return true;
}
// This method uses Fidelity completely. Some information can be reached using the AST, but not everything.
return TypeScript.Syntax.isEntirelyInsideComment(sourceUnit, position) ||
TypeScript.Syntax.isEntirelyInStringOrRegularExpressionLiteral(sourceUnit, position) ||
isIdentifierDefinitionLocation(sourceUnit, position) ||
isRightOfIllegalDot(sourceUnit, position);
}
function getContainingObjectLiteralApplicableForCompletion(sourceUnit: TypeScript.SourceUnitSyntax, position: number): TypeScript.ISyntaxElement {
// The locations in an object literal expression that are applicable for completion are property name definition locations.
var previousToken = getNonIdentifierCompleteTokenOnLeft(sourceUnit, position);
if (previousToken) {
var parent = previousToken.parent;
switch (previousToken.kind()) {
case TypeScript.SyntaxKind.OpenBraceToken: // var x = { |
case TypeScript.SyntaxKind.CommaToken: // var x = { a: 0, |
if (parent && parent.kind() === TypeScript.SyntaxKind.SeparatedList) {
parent = parent.parent;
}
if (parent && parent.kind() === TypeScript.SyntaxKind.ObjectLiteralExpression) {
return parent;
}
break;
}
}
return undefined;
}
function isIdentifierDefinitionLocation(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean {
var positionedToken = getNonIdentifierCompleteTokenOnLeft(sourceUnit, position);
if (positionedToken) {
var containingNodeKind = TypeScript.Syntax.containingNode(positionedToken) && TypeScript.Syntax.containingNode(positionedToken).kind();
switch (positionedToken.kind()) {
case TypeScript.SyntaxKind.CommaToken:
return containingNodeKind === TypeScript.SyntaxKind.ParameterList ||
containingNodeKind === TypeScript.SyntaxKind.VariableDeclaration ||
containingNodeKind === TypeScript.SyntaxKind.EnumDeclaration; // enum { foo, |
case TypeScript.SyntaxKind.OpenParenToken:
return containingNodeKind === TypeScript.SyntaxKind.ParameterList ||
containingNodeKind === TypeScript.SyntaxKind.CatchClause;
case TypeScript.SyntaxKind.OpenBraceToken:
return containingNodeKind === TypeScript.SyntaxKind.EnumDeclaration; // enum { |
case TypeScript.SyntaxKind.PublicKeyword:
case TypeScript.SyntaxKind.PrivateKeyword:
case TypeScript.SyntaxKind.StaticKeyword:
case TypeScript.SyntaxKind.DotDotDotToken:
return containingNodeKind === TypeScript.SyntaxKind.Parameter;
case TypeScript.SyntaxKind.ClassKeyword:
case TypeScript.SyntaxKind.ModuleKeyword:
case TypeScript.SyntaxKind.EnumKeyword:
case TypeScript.SyntaxKind.InterfaceKeyword:
case TypeScript.SyntaxKind.FunctionKeyword:
case TypeScript.SyntaxKind.VarKeyword:
case TypeScript.SyntaxKind.GetKeyword:
case TypeScript.SyntaxKind.SetKeyword:
return true;
}
// Previous token may have been a keyword that was converted to an identifier.
switch (positionedToken.text()) {
case "class":
case "interface":
case "enum":
case "module":
return true;
}
}
return false;
}
function getNonIdentifierCompleteTokenOnLeft(sourceUnit: TypeScript.SourceUnitSyntax, position: number): TypeScript.ISyntaxToken {
var positionedToken = TypeScript.Syntax.findCompleteTokenOnLeft(sourceUnit, position, /*includeSkippedTokens*/true);
if (positionedToken && position === TypeScript.end(positionedToken) && positionedToken.kind() == TypeScript.SyntaxKind.EndOfFileToken) {
// EndOfFile token is not intresting, get the one before it
positionedToken = TypeScript. previousToken(positionedToken, /*includeSkippedTokens*/true);
}
if (positionedToken && position === TypeScript.end(positionedToken) && positionedToken.kind() === TypeScript.SyntaxKind.IdentifierName) {
// The caret is at the end of an identifier, the decession to provide completion depends on the previous token
positionedToken = TypeScript.previousToken(positionedToken, /*includeSkippedTokens*/true);
}
return positionedToken;
}
function isRightOfIllegalDot(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean {
var positionedToken = getNonIdentifierCompleteTokenOnLeft(sourceUnit, position);
if (positionedToken) {
switch (positionedToken.kind()) {
case TypeScript.SyntaxKind.DotToken:
var leftOfDotPositionedToken = TypeScript.previousToken(positionedToken, /*includeSkippedTokens*/true);
return leftOfDotPositionedToken && leftOfDotPositionedToken.kind() === TypeScript.SyntaxKind.NumericLiteral;
case TypeScript.SyntaxKind.NumericLiteral:
var text = positionedToken.text();
return text.charAt(text.length - 1) === ".";
}
}
return false;
}
synchronizeHostData();
filename = TypeScript.switchToForwardSlashes(filename);
var document = getDocument(filename);
var sourceUnit = document.getSourceUnit();
if (isCompletionListBlocker(document.getSyntaxTree().sourceUnit(), position)) {
host.log("Returning an empty list because completion was blocked.");
return null;
}
var node = TypeScript.ASTHelpers.getAstAtPosition(sourceUnit, position, /*useTrailingTriviaAsLimChar*/ true, /*forceInclusive*/ true);
if (node && node.kind() === TypeScript.SyntaxKind.IdentifierName &&
TypeScript.start(node) === TypeScript.end(node)) {
// Ignore missing name nodes
node = node.parent;
}
var isRightOfDot = false;
if (node &&
node.kind() === TypeScript.SyntaxKind.MemberAccessExpression &&
TypeScript.end((<TypeScript.MemberAccessExpressionSyntax>node).expression) < position) {
isRightOfDot = true;
node = (<TypeScript.MemberAccessExpressionSyntax>node).expression;
}
else if (node &&
node.kind() === TypeScript.SyntaxKind.QualifiedName &&
TypeScript.end((<TypeScript.QualifiedNameSyntax>node).left) < position) {
isRightOfDot = true;
node = (<TypeScript.QualifiedNameSyntax>node).left;
}
else if (node && node.parent &&
node.kind() === TypeScript.SyntaxKind.IdentifierName &&
node.parent.kind() === TypeScript.SyntaxKind.MemberAccessExpression &&
(<TypeScript.MemberAccessExpressionSyntax>node.parent).name === node) {
isRightOfDot = true;
node = (<TypeScript.MemberAccessExpressionSyntax>node.parent).expression;
}
else if (node && node.parent &&
node.kind() === TypeScript.SyntaxKind.IdentifierName &&
node.parent.kind() === TypeScript.SyntaxKind.QualifiedName &&
(<TypeScript.QualifiedNameSyntax>node.parent).right === node) {
isRightOfDot = true;
node = (<TypeScript.QualifiedNameSyntax>node.parent).left;
}
// TODO: this is a hack for now, we need a proper walking mechanism to verify that we have the correct node
var mappedNode = getNodeAtPosition(document.getSourceFile(), TypeScript.end(node) - 1);
Debug.assert(mappedNode, "Could not map a Fidelity node to an AST node");
// Get the completions
activeCompletionSession = {
filename: filename,
position: position,
entries: [],
symbols: {},
location: mappedNode,
typeChecker: typeChecker
};
// Right of dot member completion list
if (isRightOfDot) {
var type: Type = typeChecker.getTypeOfExpression(mappedNode);
if (!type) {
return undefined;
}
var symbols = type.getApparentProperties();
isMemberCompletion = true;
getCompletionEntriesFromSymbols(symbols, activeCompletionSession);
}
else {
var containingObjectLiteral = getContainingObjectLiteralApplicableForCompletion(document.getSyntaxTree().sourceUnit(), position);
// Object literal expression, look up possible property names from contextual type
if (containingObjectLiteral) {
var searchPosition = Math.min(position, TypeScript.end(containingObjectLiteral));
var path = TypeScript.ASTHelpers.getAstAtPosition(sourceUnit, searchPosition);
// Get the object literal node
while (node && node.kind() !== TypeScript.SyntaxKind.ObjectLiteralExpression) {
node = node.parent;
}
if (!node || node.kind() !== TypeScript.SyntaxKind.ObjectLiteralExpression) {
// AST Path look up did not result in the same node as Fidelity Syntax Tree look up.
// Once we remove AST this will no longer be a problem.
return null;
}
isMemberCompletion = true;
//// Try to get the object members form contextual typing
//var contextualMembers = compiler.getContextualMembersFromAST(node, document);
//if (contextualMembers && contextualMembers.symbols && contextualMembers.symbols.length > 0) {
// // get existing members
// var existingMembers = compiler.getVisibleMemberSymbolsFromAST(node, document);
// // Add filtterd items to the completion list
// getCompletionEntriesFromSymbols({
// symbols: filterContextualMembersList(contextualMembers.symbols, existingMembers, filename, position),
// enclosingScopeSymbol: contextualMembers.enclosingScopeSymbol
// }, entries);
//}
}
// Get scope memebers
else {
isMemberCompletion = false;
/// TODO filter meaning based on the current context
var symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace;
var symbols = 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);
}
return {
isMemberCompletion: isMemberCompletion,
entries: activeCompletionSession.entries
};
}
function getCompletionEntryDetails(filename: string, position: number, entryName: string) {
// Note: No need to call synchronizeHostData, as we have captured all the data we need
// in the getCompletionsAtPosition erlier
filename = TypeScript.switchToForwardSlashes(filename);
var session = activeCompletionSession;
// Ensure that the current active completion session is still valid for this request
if (!session || session.filename !== filename || session.position !== position) {
return undefined;
}
var symbol = lookUp(activeCompletionSession.symbols, entryName);
if (symbol) {
var type = session.typeChecker.getTypeOfSymbol(symbol);
Debug.assert(type, "Could not find type for symbol");
var completionEntry = createCompletionEntry(symbol);
return {
name: entryName,
kind: completionEntry.kind,
kindModifiers: completionEntry.kindModifiers,
type: session.typeChecker.typeToString(type, session.location),
fullSymbolName: 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: SourceFile, position: number) {
var current: Node = sourceFile;
outer: while (true) {
// find the child that has this
for (var i = 0, n = current.getChildCount(); i < n; i++) {
var child = current.getChildAt(i);
if (child.getStart() <= position && position < child.getEnd()) {
current = child;
continue outer;
}
if (child.end > position) break;
}
return current;
}
}
function getContainerNode(node: Node): Node {
while (true) {
node = node.parent;
if (!node) {
return node;
}
switch (node.kind) {
case SyntaxKind.Method:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ModuleDeclaration:
return node;
}
}
}
function getSymbolKind(symbol: Symbol): string {
var flags = symbol.getFlags();
if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement;
if (flags & SymbolFlags.Class) return ScriptElementKind.classElement;
if (flags & SymbolFlags.Interface) return ScriptElementKind.interfaceElement;
if (flags & SymbolFlags.Enum) return ScriptElementKind.enumElement;
if (flags & SymbolFlags.Variable) return ScriptElementKind.variableElement;
if (flags & SymbolFlags.Function) return ScriptElementKind.functionElement;
if (flags & SymbolFlags.GetAccessor) return ScriptElementKind.memberGetAccessorElement;
if (flags & SymbolFlags.SetAccessor) return ScriptElementKind.memberSetAccessorElement;
if (flags & SymbolFlags.Method) return ScriptElementKind.memberFunctionElement;
if (flags & SymbolFlags.Property) return ScriptElementKind.memberVariableElement;
if (flags & SymbolFlags.IndexSignature) return ScriptElementKind.indexSignatureElement;
if (flags & SymbolFlags.ConstructSignature) return ScriptElementKind.constructSignatureElement;
if (flags & SymbolFlags.CallSignature) return ScriptElementKind.callSignatureElement;
if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement;
if (flags & SymbolFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
if (flags & SymbolFlags.EnumMember) return ScriptElementKind.variableElement;
return ScriptElementKind.unknown;
}
function getTypeKind(type: Type): string {
var flags = type.getFlags();
if (flags & TypeFlags.Enum) return ScriptElementKind.enumElement;
if (flags & TypeFlags.Class) return ScriptElementKind.classElement;
if (flags & TypeFlags.Interface) return ScriptElementKind.interfaceElement;
if (flags & TypeFlags.TypeParameter) return ScriptElementKind.typeParameterElement;
if (flags & TypeFlags.Intrinsic) return ScriptElementKind.primitiveType;
if (flags & TypeFlags.StringLiteral) return ScriptElementKind.primitiveType;
return ScriptElementKind.unknown;
}
function getNodeModifiers(node: Node): string {
var flags = node.flags;
var result: string[] = [];
if (flags & NodeFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier);
if (flags & NodeFlags.Public) result.push(ScriptElementKindModifier.publicMemberModifier);
if (flags & NodeFlags.Static) result.push(ScriptElementKindModifier.staticModifier);
if (flags & NodeFlags.Export) result.push(ScriptElementKindModifier.exportedModifier);
if (isInAmbientContext(node)) result.push(ScriptElementKindModifier.ambientModifier);
return result.length > 0 ? result.join(',') : ScriptElementKindModifier.none;
}
/// QuickInfo
function getTypeAtPosition(filename: string, position: number): TypeInfo {
synchronizeHostData();
filename = TypeScript.switchToForwardSlashes(filename);
var document = getDocument(filename);
var node = getNodeAtPosition(document.getSourceFile(), position);
if (!node) return undefined;
var symbol = typeChecker.getSymbolOfIdentifierLikeNode(node);
var type = symbol && typeChecker.getTypeOfSymbol(symbol);
if (type) {
return {
memberName: new TypeScript.MemberNameString(typeChecker.typeToString(type)),
docComment: "",
fullSymbolName: typeChecker.symbolToString(symbol, getContainerNode(node)),
kind: getSymbolKind(symbol),
minChar: node.pos,
limChar: node.end
};
}
return undefined;
}
/// Goto definition
function getDefinitionAtPosition(filename: string, position: number): DefinitionInfo[]{
function getTargetLabel(statement: BreakOrContinueStatement, labelName: string): Identifier {
var current = statement.parent;
while (current) {
switch (current.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.Method:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ClassDeclaration:
// Label targets cannot be across function boundaries, so do not walk any further
return undefined;
case SyntaxKind.LabelledStatement:
if ((<LabelledStatement>current).label.text === labelName) {
return (<LabelledStatement>current).label;
}
break;
}
current = current.parent;
}
return undefined;
}
function isJumpStatementTarget(node: Node): boolean {
return node.kind === SyntaxKind.Identifier &&
(node.parent.kind === SyntaxKind.BreakStatement || node.parent.kind === SyntaxKind.ContinueStatement) &&
(<BreakOrContinueStatement>node.parent).label === node;
}
function isCallExpressionTarget(node: Node): boolean {
if (node.parent.kind === SyntaxKind.PropertyAccess && (<PropertyAccess>node.parent).right === node)
node = node.parent;
return node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).func === node;
}
function isNewExpressionTarget(node: Node): boolean {
if (node.parent.kind === SyntaxKind.PropertyAccess && (<PropertyAccess>node.parent).right === node)
node = node.parent;
return node.parent.kind === SyntaxKind.NewExpression && (<CallExpression>node.parent).func === node;
}
function isFunctionDeclaration(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.Method:
case SyntaxKind.FunctionExpression:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.ArrowFunction:
return true;
}
return false;
}
function isNameOfFunctionDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.Identifier &&
isFunctionDeclaration(node.parent) && (<FunctionDeclaration>node.parent).name === node;
}
function getDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo {
return {
fileName: node.getSourceFile().filename,
minChar: node.getStart(),
limChar: node.getEnd(),
kind: symbolKind,
name: symbolName,
containerName: containerName,
containerKind: undefined
};
}
function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
var declarations: Declaration[] = [];
var definition: Declaration;
forEach(signatureDeclarations, d => {
if ((selectConstructors && d.kind === SyntaxKind.Constructor) ||
(!selectConstructors && (d.kind === SyntaxKind.FunctionDeclaration || d.kind === SyntaxKind.Method))) {
declarations.push(d);
if ((<FunctionDeclaration>d).body) definition = d;
}
});
if (definition) {
result.push(getDefinitionInfo(definition, symbolKind, symbolName, containerName));
return true;
}
else if (declarations.length) {
result.push(getDefinitionInfo(declarations[declarations.length - 1], symbolKind, symbolName, containerName));
return true;
}
return false
}
function tryAddConstructSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
// Applicable only if we are in a new expression, or we are on a constructor declaration
// and in either case the symbol has a construct signature definition, i.e. class
if (isNewExpressionTarget(location) || location.kind === SyntaxKind.ConstructorKeyword) {
if (symbol.flags & SymbolFlags.Class) {
var classDeclaration = <ClassDeclaration>symbol.getDeclarations()[0];
Debug.assert(classDeclaration && classDeclaration.kind === SyntaxKind.ClassDeclaration);
return tryAddSignature(classDeclaration.members, /*selectConstructors*/ true, symbolKind, symbolName, containerName, result);
}
}
return false;
}
function tryAddCallSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) {
if (isCallExpressionTarget(location) || isNewExpressionTarget(location) || isNameOfFunctionDeclaration(location)) {
return tryAddSignature(symbol.declarations, /*selectConstructors*/ false, symbolKind, symbolName, containerName, result);
}
return false;
}
synchronizeHostData();
filename = TypeScript.switchToForwardSlashes(filename);
var document = getDocument(filename);
var node = getNodeAtPosition(document.getSourceFile(), position);
if (!node) {
return undefined;
}
// Labels
if (isJumpStatementTarget(node)) {
var labelName = (<Identifier>node).text;
var label = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text);
return label ? [getDefinitionInfo(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined;
}
/// Triple slash reference comments
var comment = forEach(document.getSourceFile().referencedFiles, r => (r.pos <= position && position < r.end) ? r : undefined);
if (comment) {
var targetFilename = normalizePath(combinePaths(getDirectoryPath(filename), comment.filename));
if (program.getSourceFile(targetFilename)) {
return [{
fileName: targetFilename, minChar: 0, limChar: 0,
kind: ScriptElementKind.scriptElement,
name: comment.filename, containerName: undefined, containerKind: undefined
}];
}
return undefined;
}
var symbol = typeChecker.getSymbolOfIdentifierLikeNode(node);
// Could not find a symbol e.g. node is string or number keyword,
// or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol
if (!symbol || !(symbol.getDeclarations())) {
return undefined;
}
var result: DefinitionInfo[] = [];
var declarations = symbol.getDeclarations();
var symbolName = typeChecker.symbolToString(symbol, node);
var symbolKind = getSymbolKind(symbol);
var containerSymbol = symbol.parent;
var containerName = containerSymbol ? typeChecker.symbolToString(containerSymbol, node) : "";
var containerKind = containerSymbol ? getSymbolKind(symbol) : "";
if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
!tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
// Just add all the declarations.
forEach(declarations, declaration => {
result.push(getDefinitionInfo(declaration, symbolKind, symbolName, containerName));
});
}
return result;
}
/// 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() === TypeScript.SyntaxKind.ParameterList && ast.parent.kind() === TypeScript.SyntaxKind.CallSignature && ast.parent.parent.kind() === TypeScript.SyntaxKind.ConstructorDeclaration) {
ast = ast.parent.parent;
}
switch (ast.kind()) {
default:
return null;
case TypeScript.SyntaxKind.ConstructorDeclaration:
var constructorAST = <TypeScript.ConstructorDeclarationSyntax>ast;
if (!isConstructorValidPosition || !(position >= TypeScript.start(constructorAST) && position <= TypeScript.start(constructorAST) + "constructor".length)) {
return null;
}
else {
return ast;
}
case TypeScript.SyntaxKind.FunctionDeclaration:
return null;
case TypeScript.SyntaxKind.MemberAccessExpression:
case TypeScript.SyntaxKind.QualifiedName:
case TypeScript.SyntaxKind.SuperKeyword:
case TypeScript.SyntaxKind.StringLiteral:
case TypeScript.SyntaxKind.ThisKeyword:
case TypeScript.SyntaxKind.IdentifierName:
return ast;
}
}
filename = TypeScript.switchToForwardSlashes(filename);
var node = getTypeInfoEligiblePath(filename, startPos, false);
if (!node) return null;
while (node) {
if (TypeScript.ASTHelpers.isNameOfMemberAccessExpression(node) ||
TypeScript.ASTHelpers.isRightSideOfQualifiedName(node)) {
node = node.parent;
}
else {
break;
}
}
return {
minChar: TypeScript.start(node),
limChar: TypeScript.end(node)
};
}
function getBreakpointStatementAtPosition(filename: string, position: number) {
// doesn't use compiler - no need to synchronize with host
filename = TypeScript.switchToForwardSlashes(filename);
var syntaxtree = getSyntaxTree(filename);
return TypeScript.Services.Breakpoints.getBreakpointLocation(syntaxtree, position);
}
function getScriptLexicalStructure(filename: string) {
filename = TypeScript.switchToForwardSlashes(filename);
var syntaxTree = getSyntaxTree(filename);
var items: NavigateToItem[] = [];
TypeScript.Services.GetScriptLexicalStructureWalker.getListsOfAllScriptLexicalStructure(items, filename, syntaxTree.sourceUnit());
return items;
}
function getOutliningRegions(filename: string) {
// doesn't use compiler - no need to synchronize with host
filename = TypeScript.switchToForwardSlashes(filename);
var syntaxTree = getSyntaxTree(filename);
return TypeScript.Services.OutliningElementsCollector.collectElements(syntaxTree.sourceUnit());
}
function getBraceMatchingAtPosition(filename: string, position: number) {
filename = TypeScript.switchToForwardSlashes(filename);
var syntaxTree = getSyntaxTree(filename);
return TypeScript.Services.BraceMatcher.getMatchSpans(syntaxTree, position);
}
function getIndentationAtPosition(filename: string, position: number, editorOptions: EditorOptions) {
filename = TypeScript.switchToForwardSlashes(filename);
var syntaxTree = getSyntaxTree(filename);
var scriptSnapshot = syntaxTreeCache.getCurrentScriptSnapshot(filename);
var scriptText = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
var textSnapshot = new TypeScript.Services.Formatting.TextSnapshot(scriptText);
var options = new TypeScript.FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter)
return TypeScript.Services.Formatting.SingleTokenIndenter.getIndentationAmount(position, syntaxTree.sourceUnit(), textSnapshot, options);
}
function getFormattingManager(filename: string, options: FormatCodeOptions) {
// Ensure rules are initialized and up to date wrt to formatting options
if (formattingRulesProvider == null) {
formattingRulesProvider = new TypeScript.Services.Formatting.RulesProvider(host);
}
formattingRulesProvider.ensureUpToDate(options);
// Get the Syntax Tree
var syntaxTree = getSyntaxTree(filename);
// Convert IScriptSnapshot to ITextSnapshot
var scriptSnapshot = syntaxTreeCache.getCurrentScriptSnapshot(filename);
var scriptText = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
var textSnapshot = new TypeScript.Services.Formatting.TextSnapshot(scriptText);
var manager = new TypeScript.Services.Formatting.FormattingManager(syntaxTree, textSnapshot, formattingRulesProvider, options);
return manager;
}
function getFormattingEditsForRange(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] {
filename = TypeScript.switchToForwardSlashes(filename);
var manager = getFormattingManager(filename, options);
return manager.formatSelection(minChar, limChar);
}
function getFormattingEditsForDocument(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] {
filename = TypeScript.switchToForwardSlashes(filename);
var manager = getFormattingManager(filename, options);
return manager.formatDocument(minChar, limChar);
}
function getFormattingEditsOnPaste(filename: string, minChar: number, limChar: number, options: FormatCodeOptions): TextEdit[] {
filename = TypeScript.switchToForwardSlashes(filename);
var manager = getFormattingManager(filename, options);
return manager.formatOnPaste(minChar, limChar);
}
function getFormattingEditsAfterKeystroke(filename: string, position: number, key: string, options: FormatCodeOptions): TextEdit[] {
filename = TypeScript.switchToForwardSlashes(filename);
var manager = getFormattingManager(filename, options);
if (key === "}") return manager.formatOnClosingCurlyBrace(position);
else if (key === ";") return manager.formatOnSemicolon(position);
else if (key === "\n") return manager.formatOnEnter(position);
else return [];
}
return {
dispose: dispose,
refresh: () => { },
cleanupSemanticCache: () => { },
getSyntacticDiagnostics: getSyntacticDiagnostics,
getSemanticDiagnostics: getSemanticDiagnostics,
getCompilerOptionsDiagnostics: getCompilerOptionsDiagnostics,
getCompletionsAtPosition: getCompletionsAtPosition,
getCompletionEntryDetails: getCompletionEntryDetails,
getTypeAtPosition: getTypeAtPosition,
getSignatureAtPosition: (filename, position): SignatureInfo => undefined,
getDefinitionAtPosition: getDefinitionAtPosition,
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): EmitOutput => undefined,
};
}
/// Classifier
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: ClassificationResult = {
finalLexState: EndOfLineState.Start,
entries: []
};
var simpleText = TypeScript.SimpleText.fromString(text);
scanner = TypeScript.Scanner.createScanner(ScriptTarget.ES5, simpleText, reportDiagnostic);
var lastTokenKind = TypeScript.SyntaxKind.None;
var token: TypeScript.ISyntaxToken = null;
do {
lastDiagnosticKey = null;
token = scanner.scan(!noRegexTable[lastTokenKind]);
lastTokenKind = token.kind();
processToken(text, simpleText, offset, token, result);
}
while (token.kind() !== TypeScript.SyntaxKind.EndOfFileToken);
lastDiagnosticKey = null;
return result;
}
function processToken(text: string, simpleText: TypeScript.ISimpleText, offset: number, token: TypeScript.ISyntaxToken, result: ClassificationResult): void {
processTriviaList(text, offset, token.leadingTrivia(simpleText), result);
addResult(text, offset, result, TypeScript.width(token), token.kind());
processTriviaList(text, offset, token.trailingTrivia(simpleText), result);
if (TypeScript.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
};
}
function initializeServices() {
objectAllocator = {
getNodeConstructor: kind => {
function Node() {
}
var proto = new NodeObject();
proto.kind = kind;
proto.pos = 0;
proto.end = 0;
proto.flags = 0;
proto.parent = undefined;
Node.prototype = proto;
return <any>Node;
},
getSymbolConstructor: () => SymbolObject,
getTypeConstructor: () => TypeObject,
getSignatureConstructor: () => SignatureObject,
};
}
initializeServices();
}