///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
module ts {
export interface Node {
getSourceFile(): SourceFile;
getChildCount(): number;
getChildAt(index: number): Node;
getChildren(): Node[];
getFullWidth(): number;
getTriviaWidth(): 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[];
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 = 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 node;
}
public getTextPos(): number {
return getTokenPosOfNode(this);
}
public getFullWidth(): number {
return this.end - this.pos;
}
public getTriviaWidth(): number {
return getTokenPosOfNode(this) - 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 {
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) => {
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(): 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);
}
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";
}
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();
var pre = function (cur: TypeScript.ISyntaxElement) {
if (TypeScript.ASTHelpers.isValidAstNode(cur)) {
if (cur.kind() === TypeScript.SyntaxKind.IdentifierName) {
var nodeText = TypeScript.tokenValueText((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);
}
}
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; // 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;
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 contains(filename: string): boolean {
return !!this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)];
}
public getHostfilename(filename: string) {
var hostCacheEntry = this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)];
if (hostCacheEntry) {
return hostCacheEntry.filename;
}
return filename;
}
public getfilenames(): string[] {
var fileNames: string[] = [];
for (var id in this.filenameToEntry) {
fileNames.push(id);
}
return fileNames;
}
public getVersion(filename: string): number {
return this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)].version;
}
public isOpen(filename: string): boolean {
return this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)].isOpen;
}
public getByteOrderMark(filename: string): ByteOrderMark {
return this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)].byteOrderMark;
}
public getScriptSnapshot(filename: string): TypeScript.IScriptSnapshot {
var file = this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)];
if (!file.sourceText) {
file.sourceText = this.host.getScriptSnapshot(file.filename);
}
return file.sourceText;
}
public getScriptTextChangeRangeSinceVersion(filename: string, lastKnownVersion: number): TypeScript.TextChangeRange {
var currentVersion = this.getVersion(filename);
if (lastKnownVersion === currentVersion) {
return TypeScript.TextChangeRange.unchanged; // "No changes"
}
var scriptSnapshot = this.getScriptSnapshot(filename);
return scriptSnapshot.getTextChangeRangeSinceVersion(lastKnownVersion);
}
}
class SyntaxTreeCache {
private hostCache: HostCache;
// For our syntactic only features, we also keep a cache of the syntax tree for the
// currently edited file.
private currentfilename: string = "";
private currentFileVersion: number = -1;
private currentFileSyntaxTree: TypeScript.SyntaxTree = null;
private currentFileScriptSnapshot: TypeScript.IScriptSnapshot = null;
constructor(private host: LanguageServiceHost) {
this.hostCache = new HostCache(host);
}
public getCurrentFileSyntaxTree(filename: string): TypeScript.SyntaxTree {
this.hostCache = new HostCache(this.host);
var version = this.hostCache.getVersion(filename);
var syntaxTree: TypeScript.SyntaxTree = null;
if (this.currentFileSyntaxTree === null || this.currentfilename !== filename) {
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
syntaxTree = this.createSyntaxTree(filename, scriptSnapshot);
}
else if (this.currentFileVersion !== version) {
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
syntaxTree = this.updateSyntaxTree(filename, scriptSnapshot, this.currentFileSyntaxTree, this.currentFileVersion);
}
if (syntaxTree !== null) {
// All done, ensure state is up to date
this.currentFileScriptSnapshot = scriptSnapshot;
this.currentFileVersion = version;
this.currentfilename = filename;
this.currentFileSyntaxTree = syntaxTree;
}
return this.currentFileSyntaxTree;
}
public getCurrentScriptSnapshot(filename: string): 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