///
///
///
///
///
///
///
///
module ts {
export var servicesVersion = "0.4"
export interface Node {
getSourceFile(): SourceFile;
getChildCount(sourceFile?: SourceFile): number;
getChildAt(index: number, sourceFile?: SourceFile): Node;
getChildren(sourceFile?: SourceFile): Node[];
getStart(sourceFile?: SourceFile): number;
getFullStart(): number;
getEnd(): number;
getWidth(sourceFile?: SourceFile): number;
getFullWidth(): number;
getLeadingTriviaWidth(sourceFile?: SourceFile): number;
getFullText(sourceFile?: SourceFile): string;
getText(sourceFile?: SourceFile): string;
getFirstToken(sourceFile?: SourceFile): Node;
getLastToken(sourceFile?: SourceFile): Node;
}
export interface Symbol {
getFlags(): SymbolFlags;
getName(): string;
getDeclarations(): Declaration[];
getDocumentationComment(): SymbolDisplayPart[];
}
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;
getDocumentationComment(): SymbolDisplayPart[];
}
export interface SourceFile {
isOpen: boolean;
version: string;
scriptSnapshot: IScriptSnapshot;
getNamedDeclarations(): Declaration[];
}
/**
* Represents an immutable snapshot of a script at a specified time.Once acquired, the
* snapshot is observably immutable. i.e. the same calls with the same parameters will return
* the same values.
*/
export interface IScriptSnapshot {
/** Gets a portion of the script snapshot specified by [start, end). */
getText(start: number, end: number): string;
/** Gets the length of this script snapshot. */
getLength(): number;
/**
* This call returns the array containing the start position of every line.
* i.e."[0, 10, 55]". TODO: consider making this optional. The language service could
* always determine this (albeit in a more expensive manner).
*/
getLineStartPositions(): number[];
/**
* Gets the TextChangeRange that describe how the text changed between this text and
* an older version. This information is used by the incremental parser to determine
* what sections of the script need to be re-parsed. 'undefined' can be returned if the
* change range cannot be determined. However, in that case, incremental parsing will
* not happen and the entire document will be re - parsed.
*/
getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange;
}
export module ScriptSnapshot {
class StringScriptSnapshot implements IScriptSnapshot {
private _lineStartPositions: number[] = undefined;
constructor(private text: string) {
}
public getText(start: number, end: number): string {
return this.text.substring(start, end);
}
public getLength(): number {
return this.text.length;
}
public getLineStartPositions(): number[] {
if (!this._lineStartPositions) {
this._lineStartPositions = computeLineStarts(this.text);
}
return this._lineStartPositions;
}
public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange {
throw new Error("not yet implemented");
}
}
export function fromString(text: string): IScriptSnapshot {
return new StringScriptSnapshot(text);
}
}
export interface PreProcessedFileInfo {
referencedFiles: FileReference[];
importedFiles: FileReference[];
isLibFile: boolean
}
var scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true);
var emptyArray: any[] = [];
function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject {
var node = 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 {
return getSourceFileOfNode(this);
}
public getStart(sourceFile?: SourceFile): number {
return getTokenPosOfNode(this, sourceFile);
}
public getFullStart(): number {
return this.pos;
}
public getEnd(): number {
return this.end;
}
public getWidth(sourceFile?: SourceFile): number {
return this.getEnd() - this.getStart(sourceFile);
}
public getFullWidth(): number {
return this.end - this.getFullStart();
}
public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
return this.getStart(sourceFile) - this.pos;
}
public getFullText(sourceFile?: SourceFile): string {
return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
}
public getText(sourceFile?: SourceFile): string {
return (sourceFile || this.getSourceFile()).text.substring(this.getStart(), this.getEnd());
}
private addSyntheticNodes(nodes: Node[], pos: number, end: number): number {
scanner.setTextPos(pos);
while (pos < end) {
var token = scanner.scan();
var textPos = scanner.getTextPos();
nodes.push(createNode(token, pos, textPos, NodeFlags.Synthetic, this));
pos = textPos;
}
return pos;
}
private createSyntaxList(nodes: NodeArray): 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(sourceFile?: SourceFile) {
if (this.kind >= SyntaxKind.FirstNode) {
scanner.setText((sourceFile || 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) => {
if (pos < nodes.pos) {
pos = this.addSyntheticNodes(children, pos, nodes.pos);
}
children.push(this.createSyntaxList(>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(sourceFile?: SourceFile): number {
if (!this._children) this.createChildren(sourceFile);
return this._children.length;
}
public getChildAt(index: number, sourceFile?: SourceFile): Node {
if (!this._children) this.createChildren(sourceFile);
return this._children[index];
}
public getChildren(sourceFile?: SourceFile): Node[] {
if (!this._children) this.createChildren(sourceFile);
return this._children;
}
public getFirstToken(sourceFile?: SourceFile): Node {
var children = this.getChildren();
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.kind < SyntaxKind.FirstNode) {
return child;
}
return child.getFirstToken(sourceFile);
}
}
public getLastToken(sourceFile?: SourceFile): Node {
var children = this.getChildren(sourceFile);
for (var i = children.length - 1; i >= 0; i--) {
var child = children[i];
if (child.kind < SyntaxKind.FirstNode) {
return child;
}
return child.getLastToken(sourceFile);
}
}
}
class SymbolObject implements Symbol {
flags: SymbolFlags;
name: string;
declarations: Declaration[];
// Undefined is used to indicate the value has not been computed. If, after computing, the
// symbol has no doc comment, then the empty string will be returned.
documentationComment: SymbolDisplayPart[];
constructor(flags: SymbolFlags, name: string) {
this.flags = flags;
this.name = name;
}
getFlags(): SymbolFlags {
return this.flags;
}
getName(): string {
return this.name;
}
getDeclarations(): Declaration[] {
return this.declarations;
}
getDocumentationComment(): SymbolDisplayPart[] {
if (this.documentationComment === undefined) {
this.documentationComment = getJsDocCommentsFromDeclarations(this.declarations, this.name, !(this.flags & SymbolFlags.Property));
}
return this.documentationComment;
}
}
function getJsDocCommentsFromDeclarations(declarations: Declaration[], name: string, canUseParsedParamTagComments: boolean) {
var documentationComment = [];
var docComments = getJsDocCommentsSeparatedByNewLines();
ts.forEach(docComments, docComment => {
if (documentationComment.length) {
documentationComment.push(lineBreakPart());
}
documentationComment.push(docComment);
});
return documentationComment;
function getJsDocCommentsSeparatedByNewLines() {
var paramTag = "@param";
var jsDocCommentParts: SymbolDisplayPart[] = [];
ts.forEach(declarations, declaration => {
var sourceFileOfDeclaration = getSourceFileOfNode(declaration);
// If it is parameter - try and get the jsDoc comment with @param tag from function declaration's jsDoc comments
if (canUseParsedParamTagComments && declaration.kind === SyntaxKind.Parameter) {
ts.forEach(getJsDocCommentTextRange(declaration.parent, sourceFileOfDeclaration), jsDocCommentTextRange => {
var cleanedParamJsDocComment = getCleanedParamJsDocComment(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
if (cleanedParamJsDocComment) {
jsDocCommentParts.push.apply(jsDocCommentParts, cleanedParamJsDocComment);
}
});
}
// If this is left side of dotted module declaration, there is no doc comments associated with this node
if (declaration.kind === SyntaxKind.ModuleDeclaration && (declaration).body.kind === SyntaxKind.ModuleDeclaration) {
return;
}
// If this is dotted module name, get the doc comments from the parent
while (declaration.kind === SyntaxKind.ModuleDeclaration && declaration.parent.kind === SyntaxKind.ModuleDeclaration) {
declaration = declaration.parent;
}
// Get the cleaned js doc comment text from the declaration
ts.forEach(getJsDocCommentTextRange(
declaration.kind === SyntaxKind.VariableDeclaration ? declaration.parent.parent : declaration, sourceFileOfDeclaration), jsDocCommentTextRange => {
var cleanedJsDocComment = getCleanedJsDocComment(jsDocCommentTextRange.pos, jsDocCommentTextRange.end, sourceFileOfDeclaration);
if (cleanedJsDocComment) {
jsDocCommentParts.push.apply(jsDocCommentParts, cleanedJsDocComment);
}
});
});
return jsDocCommentParts;
function getJsDocCommentTextRange(node: Node, sourceFile: SourceFile): TextRange[] {
return ts.map(getJsDocComments(node, sourceFile),
jsDocComment => {
return {
pos: jsDocComment.pos + "/*".length, // Consume /* from the comment
end: jsDocComment.end - "*/".length // Trim off comment end indicator
};
});
}
function consumeWhiteSpacesOnTheLine(pos: number, end: number, sourceFile: SourceFile, maxSpacesToRemove?: number) {
if (maxSpacesToRemove !== undefined) {
end = Math.min(end, pos + maxSpacesToRemove);
}
for (; pos < end; pos++) {
var ch = sourceFile.text.charCodeAt(pos);
if (!isWhiteSpace(ch) || isLineBreak(ch)) {
// Either found lineBreak or non whiteSpace
return pos;
}
}
return end;
}
function consumeLineBreaks(pos: number, end: number, sourceFile: SourceFile) {
while (pos < end && isLineBreak(sourceFile.text.charCodeAt(pos))) {
pos++;
}
return pos;
}
function isName(pos: number, end: number, sourceFile: SourceFile, name: string) {
return pos + name.length < end &&
sourceFile.text.substr(pos, name.length) === name &&
(isWhiteSpace(sourceFile.text.charCodeAt(pos + name.length)) ||
isLineBreak(sourceFile.text.charCodeAt(pos + name.length)));
}
function isParamTag(pos: number, end: number, sourceFile: SourceFile) {
// If it is @param tag
return isName(pos, end, sourceFile, paramTag);
}
function pushDocCommentLineText(docComments: SymbolDisplayPart[], text: string, blankLineCount: number) {
// Add the empty lines in between texts
while (blankLineCount--) docComments.push(textPart(""));
docComments.push(textPart(text));
}
function getCleanedJsDocComment(pos: number, end: number, sourceFile: SourceFile) {
var spacesToRemoveAfterAsterisk: number;
var docComments: SymbolDisplayPart[] = [];
var blankLineCount = 0;
var isInParamTag = false;
while (pos < end) {
var docCommentTextOfLine = "";
// First consume leading white space
pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile);
// If the comment starts with '*' consume the spaces on this line
if (pos < end && sourceFile.text.charCodeAt(pos) === CharacterCodes.asterisk) {
var lineStartPos = pos + 1;
pos = consumeWhiteSpacesOnTheLine(pos + 1, end, sourceFile, spacesToRemoveAfterAsterisk);
// Set the spaces to remove after asterisk as margin if not already set
if (spacesToRemoveAfterAsterisk === undefined && pos < end && !isLineBreak(sourceFile.text.charCodeAt(pos))) {
spacesToRemoveAfterAsterisk = pos - lineStartPos;
}
}
else if (spacesToRemoveAfterAsterisk === undefined) {
spacesToRemoveAfterAsterisk = 0;
}
// Analyse text on this line
while (pos < end && !isLineBreak(sourceFile.text.charCodeAt(pos))) {
var ch = sourceFile.text.charAt(pos);
if (ch === "@") {
// If it is @param tag
if (isParamTag(pos, end, sourceFile)) {
isInParamTag = true;
pos += paramTag.length;
continue;
}
else {
isInParamTag = false;
}
}
// Add the ch to doc text if we arent in param tag
if (!isInParamTag) {
docCommentTextOfLine += ch;
}
// Scan next character
pos++;
}
// Continue with next line
pos = consumeLineBreaks(pos, end, sourceFile);
if (docCommentTextOfLine) {
pushDocCommentLineText(docComments, docCommentTextOfLine, blankLineCount);
blankLineCount = 0;
}
else if (!isInParamTag && docComments.length) {
// This is blank line when there is text already parsed
blankLineCount++;
}
}
return docComments;
}
function getCleanedParamJsDocComment(pos: number, end: number, sourceFile: SourceFile) {
var paramHelpStringMargin: number;
var paramDocComments: SymbolDisplayPart[] = [];
while (pos < end) {
if (isParamTag(pos, end, sourceFile)) {
var blankLineCount = 0;
var recordedParamTag = false;
// Consume leading spaces
pos = consumeWhiteSpaces(pos + paramTag.length);
if (pos >= end) {
break;
}
// Ignore type expression
if (sourceFile.text.charCodeAt(pos) === CharacterCodes.openBrace) {
pos++;
for (var curlies = 1; pos < end; pos++) {
var charCode = sourceFile.text.charCodeAt(pos);
// { character means we need to find another } to match the found one
if (charCode === CharacterCodes.openBrace) {
curlies++;
continue;
}
// } char
if (charCode === CharacterCodes.closeBrace) {
curlies--;
if (curlies === 0) {
// We do not have any more } to match the type expression is ignored completely
pos++;
break;
}
else {
// there are more { to be matched with }
continue;
}
}
// Found start of another tag
if (charCode === CharacterCodes.at) {
break;
}
}
// Consume white spaces
pos = consumeWhiteSpaces(pos);
if (pos >= end) {
break;
}
}
// Parameter name
if (isName(pos, end, sourceFile, name)) {
// Found the parameter we are looking for consume white spaces
pos = consumeWhiteSpaces(pos + name.length);
if (pos >= end) {
break;
}
var paramHelpString = "";
var firstLineParamHelpStringPos = pos;
while (pos < end) {
var ch = sourceFile.text.charCodeAt(pos);
// at line break, set this comment line text and go to next line
if (isLineBreak(ch)) {
if (paramHelpString) {
pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount);
paramHelpString = "";
blankLineCount = 0;
recordedParamTag = true;
}
else if (recordedParamTag) {
blankLineCount++;
}
// Get the pos after cleaning start of the line
setPosForParamHelpStringOnNextLine(firstLineParamHelpStringPos);
continue;
}
// Done scanning param help string - next tag found
if (ch === CharacterCodes.at) {
break;
}
paramHelpString += sourceFile.text.charAt(pos);
// Go to next character
pos++;
}
// If there is param help text, add it top the doc comments
if (paramHelpString) {
pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount);
}
paramHelpStringMargin = undefined;
}
// If this is the start of another tag, continue with the loop in seach of param tag with symbol name
if (sourceFile.text.charCodeAt(pos) === CharacterCodes.at) {
continue;
}
}
// Next character
pos++;
}
return paramDocComments;
function consumeWhiteSpaces(pos: number) {
while (pos < end && isWhiteSpace(sourceFile.text.charCodeAt(pos))) {
pos++;
}
return pos;
}
function setPosForParamHelpStringOnNextLine(firstLineParamHelpStringPos: number) {
// Get the pos after consuming line breaks
pos = consumeLineBreaks(pos, end, sourceFile);
if (pos >= end) {
return;
}
if (paramHelpStringMargin === undefined) {
paramHelpStringMargin = sourceFile.getLineAndCharacterFromPosition(firstLineParamHelpStringPos).character - 1;
}
// Now consume white spaces max
var startOfLinePos = pos;
pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile, paramHelpStringMargin);
if (pos >= end) {
return;
}
var consumedSpaces = pos - startOfLinePos;
if (consumedSpaces < paramHelpStringMargin) {
var ch = sourceFile.text.charCodeAt(pos);
if (ch === CharacterCodes.asterisk) {
// Consume more spaces after asterisk
pos = consumeWhiteSpacesOnTheLine(pos + 1, end, sourceFile, paramHelpStringMargin - consumedSpaces - 1);
}
}
}
}
}
}
class TypeObject implements Type {
checker: TypeChecker;
flags: TypeFlags;
id: number;
symbol: Symbol;
constructor(checker: TypeChecker, flags: TypeFlags) {
this.checker = checker;
this.flags = flags;
}
getFlags(): TypeFlags {
return this.flags;
}
getSymbol(): Symbol {
return this.symbol;
}
getProperties(): Symbol[] {
return this.checker.getPropertiesOfType(this);
}
getProperty(propertyName: string): Symbol {
return this.checker.getPropertyOfType(this, propertyName);
}
getApparentProperties(): Symbol[] {
return this.checker.getAugmentedPropertiesOfType(this);
}
getCallSignatures(): Signature[] {
return this.checker.getSignaturesOfType(this, SignatureKind.Call);
}
getConstructSignatures(): Signature[] {
return this.checker.getSignaturesOfType(this, SignatureKind.Construct);
}
getStringIndexType(): Type {
return this.checker.getIndexTypeOfType(this, IndexKind.String);
}
getNumberIndexType(): Type {
return this.checker.getIndexTypeOfType(this, IndexKind.Number);
}
}
class SignatureObject implements Signature {
checker: TypeChecker;
declaration: SignatureDeclaration;
typeParameters: TypeParameter[];
parameters: Symbol[];
resolvedReturnType: Type;
minArgumentCount: number;
hasRestParameter: boolean;
hasStringLiterals: boolean;
// Undefined is used to indicate the value has not been computed. If, after computing, the
// symbol has no doc comment, then the empty string will be returned.
documentationComment: SymbolDisplayPart[];
constructor(checker: TypeChecker) {
this.checker = checker;
}
getDeclaration(): SignatureDeclaration {
return this.declaration;
}
getTypeParameters(): Type[] {
return this.typeParameters;
}
getParameters(): Symbol[] {
return this.parameters;
}
getReturnType(): Type {
return this.checker.getReturnTypeOfSignature(this);
}
getDocumentationComment(): SymbolDisplayPart[] {
if (this.documentationComment === undefined) {
this.documentationComment = this.declaration ? getJsDocCommentsFromDeclarations(
[this.declaration],
/*name*/ undefined,
/*canUseParsedParamTagComments*/ false) : [];
}
return this.documentationComment;
}
}
class SourceFileObject extends NodeObject implements SourceFile {
public _declarationBrand: any;
public filename: string;
public text: string;
public scriptSnapshot: IScriptSnapshot;
public statements: NodeArray;
public endOfFileToken: Node;
// These methods will have their implementation provided by the implementation the
// compiler actually exports off of SourceFile.
public getLineAndCharacterFromPosition: (position: number) => LineAndCharacter;
public getPositionFromLineAndCharacter: (line: number, character: number) => number;
public getLineStarts: () => number[];
public getSyntacticDiagnostics: () => Diagnostic[];
public update: (newText: string, textChangeRange: TextChangeRange) => SourceFile;
public amdDependencies: string[];
public amdModuleName: string;
public referencedFiles: FileReference[];
public referenceDiagnostics: Diagnostic[];
public parseDiagnostics: Diagnostic[];
public semanticDiagnostics: Diagnostic[];
public hasNoDefaultLib: boolean;
public externalModuleIndicator: Node; // The first node that causes this file to be an external module
public nodeCount: number;
public identifierCount: number;
public symbolCount: number;
public version: string;
public isOpen: boolean;
public languageVersion: ScriptTarget;
public identifiers: Map;
private namedDeclarations: Declaration[];
public getNamedDeclarations() {
if (!this.namedDeclarations) {
var sourceFile = this;
var namedDeclarations: Declaration[] = [];
forEachChild(sourceFile, function visit(node: Node): void {
switch (node.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
var functionDeclaration = node;
if (functionDeclaration.name && functionDeclaration.name.getFullWidth() > 0) {
var lastDeclaration = namedDeclarations.length > 0 ?
namedDeclarations[namedDeclarations.length - 1] :
undefined;
// Check whether this declaration belongs to an "overload group".
if (lastDeclaration && functionDeclaration.symbol === lastDeclaration.symbol) {
// Overwrite the last declaration if it was an overload
// and this one is an implementation.
if (functionDeclaration.body && !(lastDeclaration).body) {
namedDeclarations[namedDeclarations.length - 1] = functionDeclaration;
}
}
else {
namedDeclarations.push(functionDeclaration);
}
forEachChild(node, visit);
}
break;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ImportDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.TypeLiteral:
if ((node).name) {
namedDeclarations.push(node);
}
// fall through
case SyntaxKind.Constructor:
case SyntaxKind.VariableStatement:
case SyntaxKind.VariableDeclarationList:
case SyntaxKind.ObjectBindingPattern:
case SyntaxKind.ArrayBindingPattern:
case SyntaxKind.ModuleBlock:
forEachChild(node, visit);
break;
case SyntaxKind.Block:
if (isFunctionBlock(node)) {
forEachChild(node, visit);
}
break;
case SyntaxKind.Parameter:
// Only consider properties defined as constructor parameters
if (!(node.flags & NodeFlags.AccessibilityModifier)) {
break;
}
// fall through
case SyntaxKind.VariableDeclaration:
case SyntaxKind.BindingElement:
if (isBindingPattern((node).name)) {
forEachChild((node).name, visit);
break;
}
case SyntaxKind.EnumMember:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
namedDeclarations.push(node);
break;
}
});
this.namedDeclarations = namedDeclarations;
}
return this.namedDeclarations;
}
}
export interface Logger {
log(s: string): void;
trace(s: string): void;
error(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): string;
getScriptIsOpen(fileName: string): boolean;
getScriptSnapshot(fileName: string): IScriptSnapshot;
getLocalizedDiagnosticMessages?(): any;
getCancellationToken?(): CancellationToken;
getCurrentDirectory(): string;
getDefaultLibFilename(options: CompilerOptions): string;
}
//
// Public services of a language service instance associated
// with a language service host instance
//
export interface LanguageService {
cleanupSemanticCache(): void;
getSyntacticDiagnostics(fileName: string): Diagnostic[];
getSemanticDiagnostics(fileName: string): Diagnostic[];
getCompilerOptionsDiagnostics(): Diagnostic[];
getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[];
getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[];
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo;
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails;
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan;
getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan;
getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems;
getRenameInfo(fileName: string, position: number): RenameInfo;
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[];
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[];
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[];
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[];
getNavigateToItems(searchValue: string): NavigateToItem[];
getNavigationBarItems(fileName: string): NavigationBarItem[];
getOutliningSpans(fileName: string): OutliningSpan[];
getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[];
getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[];
getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number;
getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[];
getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[];
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextChange[];
getEmitOutput(fileName: string): EmitOutput;
getSourceFile(filename: string): SourceFile;
dispose(): void;
}
export interface ClassifiedSpan {
textSpan: TextSpan;
classificationType: string; // ClassificationTypeNames
}
export interface NavigationBarItem {
text: string;
kind: string;
kindModifiers: string;
spans: TextSpan[];
childItems: NavigationBarItem[];
indent: number;
bolded: boolean;
grayed: boolean;
}
export interface TodoCommentDescriptor {
text: string;
priority: number;
}
export interface TodoComment {
descriptor: TodoCommentDescriptor;
message: string;
position: number;
}
export class TextChange {
span: TextSpan;
newText: string;
}
export interface RenameLocation {
textSpan: TextSpan;
fileName: string;
}
export interface ReferenceEntry {
textSpan: TextSpan;
fileName: string;
isWriteAccess: boolean;
}
export interface NavigateToItem {
name: string;
kind: string;
kindModifiers: string;
matchKind: string;
fileName: string;
textSpan: TextSpan;
containerName: string;
containerKind: 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;
textSpan: TextSpan;
kind: string;
name: string;
containerKind: string;
containerName: string;
}
export enum SymbolDisplayPartKind {
aliasName,
className,
enumName,
fieldName,
interfaceName,
keyword,
lineBreak,
numericLiteral,
stringLiteral,
localName,
methodName,
moduleName,
operator,
parameterName,
propertyName,
punctuation,
space,
text,
typeParameterName,
enumMemberName,
functionName,
regularExpressionLiteral,
}
export interface SymbolDisplayPart {
text: string;
kind: string;
}
export interface QuickInfo {
kind: string;
kindModifiers: string;
textSpan: TextSpan;
displayParts: SymbolDisplayPart[];
documentation: SymbolDisplayPart[];
}
export interface RenameInfo {
canRename: boolean;
localizedErrorMessage: string;
displayName: string;
fullDisplayName: string;
kind: string;
kindModifiers: string;
triggerSpan: TextSpan;
}
export interface SignatureHelpParameter {
name: string;
documentation: SymbolDisplayPart[];
displayParts: SymbolDisplayPart[];
isOptional: boolean;
}
/**
* Represents a single signature to show in signature help.
* The id is used for subsequent calls into the language service to ask questions about the
* signature help item in the context of any documents that have been updated. i.e. after
* an edit has happened, while signature help is still active, the host can ask important
* questions like 'what parameter is the user currently contained within?'.
*/
export interface SignatureHelpItem {
isVariadic: boolean;
prefixDisplayParts: SymbolDisplayPart[];
suffixDisplayParts: SymbolDisplayPart[];
separatorDisplayParts: SymbolDisplayPart[];
parameters: SignatureHelpParameter[];
documentation: SymbolDisplayPart[];
}
/**
* Represents a set of signature help items, and the preferred item that should be selected.
*/
export interface SignatureHelpItems {
items: SignatureHelpItem[];
applicableSpan: TextSpan;
selectedItemIndex: number;
argumentIndex: number;
argumentCount: number;
}
export interface CompletionInfo {
isMemberCompletion: boolean;
isBuilder: 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
displayParts: SymbolDisplayPart[];
documentation: SymbolDisplayPart[];
}
export interface OutliningSpan {
/** The span of the document to actually collapse. */
textSpan: TextSpan;
/** The span of the document to display when the user hovers over the collapsed span. */
hintSpan: TextSpan;
/** The text to display in the editor for the collapsed region. */
bannerText: string;
/**
* Whether or not this region should be automatically collapsed when
* the 'Collapse to Definitions' command is invoked.
*/
autoCollapse: boolean;
}
export interface EmitOutput {
outputFiles: OutputFile[];
emitOutputStatus: EmitReturnStatus;
}
export const enum OutputFileType {
JavaScript,
SourceMap,
Declaration
}
export interface OutputFile {
name: string;
writeByteOrderMark: boolean;
text: string;
}
export const 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, classifyKeywordsInGenerics?: boolean): ClassificationResult;
}
export interface DocumentRegistry {
acquireDocument(
filename: string,
compilationSettings: CompilerOptions,
scriptSnapshot: IScriptSnapshot,
version: string,
isOpen: boolean): SourceFile;
updateDocument(
sourceFile: SourceFile,
filename: string,
compilationSettings: CompilerOptions,
scriptSnapshot: IScriptSnapshot,
version: string,
isOpen: boolean,
textChangeRange: TextChangeRange
): SourceFile;
releaseDocument(filename: string, compilationSettings: CompilerOptions): void
}
// TODO: move these to enums
export class ScriptElementKind {
static unknown = "";
// predefined type (void) or keyword (class)
static keyword = "keyword";
// top level script node
static scriptElement = "script";
// module foo {}
static moduleElement = "module";
// class X {}
static classElement = "class";
// interface Y {}
static interfaceElement = "interface";
// type T = ...
static typeElement = "type";
// 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";
static alias = "alias";
static constElement = "const";
static letElement = "let";
}
export class ScriptElementKindModifier {
static none = "";
static publicMemberModifier = "public";
static privateMemberModifier = "private";
static protectedMemberModifier = "protected";
static exportedModifier = "export";
static ambientModifier = "declare";
static staticModifier = "static";
}
export class ClassificationTypeNames {
public static comment = "comment";
public static identifier = "identifier";
public static keyword = "keyword";
public static numericLiteral = "number";
public static operator = "operator";
public static stringLiteral = "string";
public static whiteSpace = "whitespace";
public static text = "text";
public static punctuation = "punctuation";
public static className = "class name";
public static enumName = "enum name";
public static interfaceName = "interface name";
public static moduleName = "module name";
public static typeParameterName = "type parameter name";
public static typeAlias = "type alias name";
}
enum MatchKind {
none = 0,
exact = 1,
substring = 2,
prefix = 3
}
/// 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; // symbols by entry name map
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: string;
isOpen: boolean;
sourceText?: IScriptSnapshot;
}
interface DocumentRegistryEntry {
sourceFile: SourceFile;
refCount: number;
owners: string[];
}
export interface DisplayPartsSymbolWriter extends SymbolWriter {
displayParts(): SymbolDisplayPart[];
}
export function displayPartsToString(displayParts: SymbolDisplayPart[]) {
if (displayParts) {
return map(displayParts, displayPart => displayPart.text).join("");
}
return "";
}
function isLocalVariableOrFunction(symbol: Symbol) {
if (symbol.parent) {
return false; // This is exported symbol
}
return ts.forEach(symbol.declarations, declaration => {
// Function expressions are local
if (declaration.kind === SyntaxKind.FunctionExpression) {
return true;
}
if (declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.FunctionDeclaration) {
return false;
}
// If the parent is not sourceFile or module block it is local variable
for (var parent = declaration.parent; !isFunctionBlock(parent); parent = parent.parent) {
// Reached source file or module block
if (parent.kind === SyntaxKind.SourceFile || parent.kind === SyntaxKind.ModuleBlock) {
return false;
}
}
// parent is in function block
return true;
});
}
export function getDefaultCompilerOptions(): CompilerOptions {
// Set "ScriptTarget.Latest" target by default for language service
return {
target: ScriptTarget.Latest,
module: ModuleKind.None,
};
}
export class OperationCanceledException { }
export 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;
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[normalizeSlashes(filename)] = {
filename: filename,
version: host.getScriptVersion(filename),
isOpen: host.getScriptIsOpen(filename)
};
}
this._compilationSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
}
public compilationSettings() {
return this._compilationSettings;
}
public getEntry(filename: string): HostFileInformation {
filename = normalizeSlashes(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): string {
return this.getEntry(filename).version;
}
public isOpen(filename: string): boolean {
return this.getEntry(filename).isOpen;
}
public getScriptSnapshot(filename: string): IScriptSnapshot {
var file = this.getEntry(filename);
if (!file.sourceText) {
file.sourceText = this.host.getScriptSnapshot(file.filename);
}
return file.sourceText;
}
public getChangeRange(filename: string, lastKnownVersion: string, oldScriptSnapshot: IScriptSnapshot): TextChangeRange {
var currentVersion = this.getVersion(filename);
if (lastKnownVersion === currentVersion) {
return unchangedTextChangeRange; // "No changes"
}
var scriptSnapshot = this.getScriptSnapshot(filename);
return scriptSnapshot.getChangeRange(oldScriptSnapshot);
}
}
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: string = null;
private currentSourceFile: SourceFile = null;
constructor(private host: LanguageServiceHost) {
}
private initialize(filename: string) {
// ensure that both source file and syntax tree are either initialized or not initialized
var start = new Date().getTime();
this.hostCache = new HostCache(this.host);
this.host.log("SyntaxTreeCache.Initialize: new HostCache: " + (new Date().getTime() - start));
var version = this.hostCache.getVersion(filename);
var sourceFile: SourceFile;
if (this.currentFilename !== filename) {
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
var start = new Date().getTime();
sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, ScriptTarget.Latest, version, /*isOpen*/ true, /*setNodeParents;*/ true);
this.host.log("SyntaxTreeCache.Initialize: createSourceFile: " + (new Date().getTime() - start));
}
else if (this.currentFileVersion !== version) {
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
var editRange = this.hostCache.getChangeRange(filename, this.currentFileVersion, this.currentSourceFile.scriptSnapshot);
var start = new Date().getTime();
sourceFile = updateLanguageServiceSourceFile(this.currentSourceFile, scriptSnapshot, version, /*isOpen*/ true, editRange);
this.host.log("SyntaxTreeCache.Initialize: updateSourceFile: " + (new Date().getTime() - start));
}
if (sourceFile) {
// All done, ensure state is up to date
this.currentFileVersion = version;
this.currentFilename = filename;
this.currentSourceFile = sourceFile;
}
}
public getCurrentSourceFile(filename: string): SourceFile {
this.initialize(filename);
return this.currentSourceFile;
}
public getCurrentScriptSnapshot(filename: string): IScriptSnapshot {
return this.getCurrentSourceFile(filename).scriptSnapshot;
}
}
function setSourceFileFields(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean) {
sourceFile.version = version;
sourceFile.isOpen = isOpen;
sourceFile.scriptSnapshot = scriptSnapshot;
}
export function createLanguageServiceSourceFile(filename: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, isOpen: boolean, setNodeParents: boolean): SourceFile {
var sourceFile = createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), scriptTarget, setNodeParents);
setSourceFileFields(sourceFile, scriptSnapshot, version, isOpen);
return sourceFile;
}
export var disableIncrementalParsing = false;
export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile {
if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) {
var oldText = sourceFile.scriptSnapshot;
var newText = scriptSnapshot;
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);
Debug.assert(oldTextPrefix === newTextPrefix);
var oldTextSuffix = oldText.getText(textSpanEnd(textChangeRange.span), oldText.getLength());
var newTextSuffix = newText.getText(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.getLength());
Debug.assert(oldTextSuffix === newTextSuffix);
}
}
// If we were given a text change range, and our version or open-ness changed, then
// incrementally parse this file.
if (textChangeRange) {
if (version !== sourceFile.version || isOpen != sourceFile.isOpen) {
// Once incremental parsing is ready, then just call into this function.
if (!disableIncrementalParsing) {
var newSourceFile = sourceFile.update(scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange);
setSourceFileFields(newSourceFile, scriptSnapshot, version, isOpen);
return newSourceFile;
}
}
}
// Otherwise, just create a new source file.
return createLanguageServiceSourceFile(sourceFile.filename, scriptSnapshot, sourceFile.languageVersion, version, isOpen, /*setNodeParents:*/ true);
}
export function createDocumentRegistry(): DocumentRegistry {
var buckets: Map