From 785c0838889076a2774679e719d9cf3f38acfd2e Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 11 Aug 2014 13:15:07 -0700 Subject: [PATCH] enable outlining atop of new compiler --- src/services/outliningElementsCollector.ts | 122 +++++++-------------- src/services/services.ts | 59 +++++++++- tests/cases/fourslash/getOutliningSpans.ts | 105 ++++++++++++++++++ 3 files changed, 197 insertions(+), 89 deletions(-) create mode 100644 tests/cases/fourslash/getOutliningSpans.ts diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 8a873a8774d..0d9d01016fb 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -15,95 +15,47 @@ /// -module TypeScript.Services { - export class OutliningElementsCollector extends TypeScript.DepthLimitedWalker { - // The maximum depth for collecting spans; this will cause us to miss deeply nested function/modules spans, - // but will guarantee performance will not be closely tied to tree depth. - private static MaximumDepth: number = 10; - private inObjectLiteralExpression: boolean = false; +module ts { + export module OutliningElementsCollector { + export function collectElements(sourceFile: SourceFile): TypeScript.TextSpan[] { + var elements: TypeScript.TextSpan[] = []; - private elements: TypeScript.TextSpan[] = []; - - constructor() { - super(OutliningElementsCollector.MaximumDepth); - } - - public visitClassDeclaration(node: TypeScript.ClassDeclarationSyntax): void { - this.addOutlineRange(node, node.openBraceToken, node.closeBraceToken); - super.visitClassDeclaration(node); - } - - public visitInterfaceDeclaration(node: TypeScript.InterfaceDeclarationSyntax): void { - this.addOutlineRange(node, node.body.openBraceToken, node.body.closeBraceToken); - super.visitInterfaceDeclaration(node); - } - - public visitModuleDeclaration(node: TypeScript.ModuleDeclarationSyntax): void { - this.addOutlineRange(node, node.openBraceToken, node.closeBraceToken); - super.visitModuleDeclaration(node); - } - - public visitEnumDeclaration(node: TypeScript.EnumDeclarationSyntax): void { - this.addOutlineRange(node, node.openBraceToken, node.closeBraceToken); - super.visitEnumDeclaration(node); - } - - public visitFunctionDeclaration(node: TypeScript.FunctionDeclarationSyntax): void { - this.addOutlineRange(node, node.block, node.block); - super.visitFunctionDeclaration(node); - } - - public visitFunctionExpression(node: TypeScript.FunctionExpressionSyntax): void { - this.addOutlineRange(node, node.block, node.block); - super.visitFunctionExpression(node); - } - - public visitConstructorDeclaration(node: TypeScript.ConstructorDeclarationSyntax): void { - this.addOutlineRange(node, node.block, node.block); - super.visitConstructorDeclaration(node); - } - - public visitMemberFunctionDeclaration(node: TypeScript.MemberFunctionDeclarationSyntax): void { - this.addOutlineRange(node, node.block, node.block); - super.visitMemberFunctionDeclaration(node); - } - - public visitGetAccessor(node: TypeScript.GetAccessorSyntax): void { - if (!this.inObjectLiteralExpression) { - this.addOutlineRange(node, node.block, node.block); + function addOutlineRange(startElement: Node, endElement: Node) { + if (startElement && endElement) { + // Push the new range + elements.push(TypeScript.TextSpan.fromBounds(startElement.pos, endElement.end)); + } } - super.visitGetAccessor(node); - } - public visitSetAccessor(node: TypeScript.SetAccessorSyntax): void { - if (!this.inObjectLiteralExpression) { - this.addOutlineRange(node, node.block, node.block); + function walk(n: Node) { + switch (n.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ObjectLiteral: + var openBrace = forEach(n.getChildren(), c => c.kind === SyntaxKind.OpenBraceToken && c); + var closeBrace = forEach(n.getChildren(), c => c.kind === SyntaxKind.CloseBraceToken && c); + addOutlineRange(openBrace, closeBrace); + break; + case SyntaxKind.Constructor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.Method: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + var body = (n).body; + if (body) { + var openBrace = forEach(body.getChildren(), c => c.kind === SyntaxKind.OpenBraceToken && c); + var closeBrace = forEach(body.getChildren(), c => c.kind === SyntaxKind.CloseBraceToken && c); + addOutlineRange(openBrace, closeBrace); + } + break; + } + forEachChild(n, walk); } - super.visitSetAccessor(node); - } - public visitObjectLiteralExpression(node: TypeScript.ObjectLiteralExpressionSyntax): void { - var savedInObjectLiteralExpression = this.inObjectLiteralExpression; - this.inObjectLiteralExpression = true; - super.visitObjectLiteralExpression(node); - this.inObjectLiteralExpression = savedInObjectLiteralExpression; - } - - private addOutlineRange(node: TypeScript.ISyntaxNode, startElement: TypeScript.ISyntaxNodeOrToken, endElement: TypeScript.ISyntaxNodeOrToken) { - if (startElement && endElement && !isShared(startElement) && !isShared(endElement)) { - // Compute the position - var start = TypeScript.start(startElement); - var end = TypeScript.end(endElement); - - // Push the new range - this.elements.push(TypeScript.TextSpan.fromBounds(start, end)); - } - } - - public static collectElements(node: TypeScript.SourceUnitSyntax): TypeScript.TextSpan[] { - var collector = new OutliningElementsCollector(); - visitNodeOrToken(collector, node); - return collector.elements; + walk(sourceFile); + return elements; } } -} +} \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index 6312e61f9d2..36790058ef5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -962,6 +962,7 @@ module ts { // currently edited file. private currentfilename: string = ""; private currentFileVersion: number = -1; + private currentSourceFile: SourceFile = null; private currentFileSyntaxTree: TypeScript.SyntaxTree = null; private currentFileScriptSnapshot: TypeScript.IScriptSnapshot = null; @@ -969,32 +970,72 @@ module ts { this.hostCache = new HostCache(host); } - public getCurrentFileSyntaxTree(filename: string): TypeScript.SyntaxTree { + private initialize(filename: string) { + // ensure that both source file and syntax tree are either initialized or not initialized + Debug.assert(!!this.currentFileSyntaxTree === !!this.currentSourceFile); this.hostCache = new HostCache(this.host); var version = this.hostCache.getVersion(filename); var syntaxTree: TypeScript.SyntaxTree = null; + var sourceFile: SourceFile; if (this.currentFileSyntaxTree === null || this.currentfilename !== filename) { var scriptSnapshot = this.hostCache.getScriptSnapshot(filename); syntaxTree = this.createSyntaxTree(filename, scriptSnapshot); + sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true); + + fixupParentReferences(sourceFile); } else if (this.currentFileVersion !== version) { var scriptSnapshot = this.hostCache.getScriptSnapshot(filename); syntaxTree = this.updateSyntaxTree(filename, scriptSnapshot, this.currentFileSyntaxTree, this.currentFileVersion); + + var editRange = this.hostCache.getScriptTextChangeRangeSinceVersion(filename, this.currentFileVersion); + sourceFile = !editRange + ? createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true) + : this.currentSourceFile.update(scriptSnapshot, version, /*isOpen*/ true, editRange); + + fixupParentReferences(sourceFile); } if (syntaxTree !== null) { + Debug.assert(sourceFile); // All done, ensure state is up to date this.currentFileScriptSnapshot = scriptSnapshot; this.currentFileVersion = version; this.currentfilename = filename; this.currentFileSyntaxTree = syntaxTree; + this.currentSourceFile = sourceFile; } + function fixupParentReferences(sourceFile: SourceFile) { + // normally parent references are set during binding. + // however here SourceFile data is used only for syntactic features so running the whole binding process is an overhead. + // walk over the nodes and set parent references + var parent: Node = sourceFile; + function walk(n: Node): void { + if (parent) { + n.parent = parent; + } + var saveParent = parent; + parent = n; + forEachChild(n, walk); + parent = saveParent; + } + forEachChild(sourceFile, walk); + } + } + + public getCurrentFileSyntaxTree(filename: string): TypeScript.SyntaxTree { + this.initialize(filename); return this.currentFileSyntaxTree; } + public getCurrentSourceFile(filename: string): SourceFile { + this.initialize(filename); + return this.currentSourceFile; + } + public getCurrentScriptSnapshot(filename: string): TypeScript.IScriptSnapshot { // update currentFileScriptSnapshot as a part of 'getCurrentFileSyntaxTree' call this.getCurrentFileSyntaxTree(filename); @@ -1093,6 +1134,10 @@ module ts { } } + function createSourceFileFromScriptSnapshot(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, settings: CompilerOptions, version: number, isOpen: boolean) { + return createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), settings.target, version, isOpen); + } + export function createDocumentRegistry(): DocumentRegistry { var buckets: Map> = {}; @@ -1140,7 +1185,7 @@ module ts { var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true); var entry = lookUp(bucket, filename); if (!entry) { - var sourceFile = createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), compilationSettings.target, version, isOpen); + var sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, compilationSettings, version, isOpen); bucket[filename] = entry = { sourceFile: sourceFile, @@ -2024,6 +2069,12 @@ module ts { return syntaxTreeCache.getCurrentFileSyntaxTree(filename); } + function getCurrentSourceFile(filename: string): SourceFile { + filename = TypeScript.switchToForwardSlashes(filename); + var currentSourceFile = syntaxTreeCache.getCurrentSourceFile(filename); + return currentSourceFile; + } + function getNameOrDottedNameSpan(filename: string, startPos: number, endPos: number): SpanInfo { function getTypeInfoEligiblePath(filename: string, position: number, isConstructorValidPosition: boolean) { var sourceUnit = syntaxTreeCache.getCurrentFileSyntaxTree(filename).sourceUnit(); @@ -2100,8 +2151,8 @@ module ts { 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()); + var sourceFile = getCurrentSourceFile(filename); + return OutliningElementsCollector.collectElements(sourceFile); } function getBraceMatchingAtPosition(filename: string, position: number) { diff --git a/tests/cases/fourslash/getOutliningSpans.ts b/tests/cases/fourslash/getOutliningSpans.ts new file mode 100644 index 00000000000..57eba365e33 --- /dev/null +++ b/tests/cases/fourslash/getOutliningSpans.ts @@ -0,0 +1,105 @@ +/// + +////// interface +////interface IFoo[| { +//// getDist(): number; +////}|] +//// +////// class members +////class Foo[| { +//// constructor()[| { +//// }|] +//// +//// public foo(): number[| { +//// return 0; +//// }|] +//// +//// public get X()[| { +//// return 1; +//// }|] +//// +//// public set X(v: number)[| { +//// }|] +//// +//// public member = function f()[| { +//// +//// }|] +////}|] +//// +////// modules +////module m1[| { +//// module m2[| { }|] +//// module m3[| { +//// function foo()[| { +//// +//// }|] +//// +//// interface IFoo2[| { +//// +//// }|] +//// +//// class foo2 implements IFoo2[| { +//// +//// }|] +//// }|] +////}|] +//// +////// function declaration +////function foo(): number[| { +//// return 0; +////}|] +//// +////// function expressions +////(function f()[| { +//// +////}|]) +//// +////// trivia handeling +////class ClassFooWithTrivia[| /* some comments */ +//// /* more trivia */ { +//// +//// +//// /*some trailing trivia */ +////}|] /* even more */ +//// +////// object literals +////var x = [|{ +//// a:1, +//// b:2, +//// get foo() [|{ +//// return 1; +//// }|] +////}|] +//////outline with deep nesting +////module m1[|{ +//// module m2[| { +//// module m3[| { +//// module m4[| { +//// module m5[| { +//// module m6[| { +//// module m7[| { +//// module m8[| { +//// module m9[| { +//// module m10[| { +//// module m11 { +//// module m12 { +//// export interface IFoo { +//// } +//// } +//// } +//// }|] +//// }|] +//// }|] +//// }|] +//// }|] +//// }|] +//// }|] +//// }|] +//// }|] +////}|] +//// +//////outline after a deeply nested node +////class AfterNestedNodes[| { +////}|] + +verify.outliningSpansInCurrentFile(test.ranges());