mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-11 10:00:13 -06:00
2406 lines
104 KiB
TypeScript
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();
|
|
}
|