From d2250356226f5f07b8b7f4a1d4781bd01bbe6057 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 17 Nov 2014 15:57:40 -0800 Subject: [PATCH 1/2] cut usage of the old tree from the services layer --- src/services/formatting/rulesProvider.ts | 2 +- src/services/services.ts | 136 +---------------------- 2 files changed, 3 insertions(+), 135 deletions(-) diff --git a/src/services/formatting/rulesProvider.ts b/src/services/formatting/rulesProvider.ts index 1469e86971..7ad1b65400 100644 --- a/src/services/formatting/rulesProvider.ts +++ b/src/services/formatting/rulesProvider.ts @@ -22,7 +22,7 @@ module ts.formatting { private activeRules: Rule[]; private rulesMap: RulesMap; - constructor(private logger: TypeScript.Logger) { + constructor(private logger: Logger) { this.globalRules = new Rules(); } diff --git a/src/services/services.ts b/src/services/services.ts index 334bcc9da7..b7adb56e82 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4,7 +4,6 @@ /// /// -/// /// /// /// @@ -13,17 +12,6 @@ /// /// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// - module ts { export interface Node { getSourceFile(): SourceFile; @@ -1263,10 +1251,6 @@ module ts { prefix = 3 } - interface IncrementalParse { - (oldSyntaxTree: TypeScript.SyntaxTree, textChangeRange: TypeScript.TextChangeRange, newText: TypeScript.ISimpleText): TypeScript.SyntaxTree - } - /// Language Service interface CompletionSession { @@ -1606,7 +1590,6 @@ module ts { private currentFilename: string = ""; private currentFileVersion: string = null; private currentSourceFile: SourceFile = null; - private currentFileSyntaxTree: TypeScript.SyntaxTree = null; constructor(private host: LanguageServiceHost) { this.hostCache = new HostCache(host); @@ -1614,20 +1597,15 @@ module ts { private initialize(filename: string) { // ensure that both source file and syntax tree are either initialized or not initialized - Debug.assert(!!this.currentFileSyntaxTree === !!this.currentSourceFile); var start = new Date().getTime(); this.hostCache = new HostCache(this.host); this.host.log("SyntaxTreeCache.Initialize: new HostCache: " + (new Date().getTime() - start)); var version = this.hostCache.getVersion(filename); - var syntaxTree: TypeScript.SyntaxTree = null; var sourceFile: SourceFile; - if (this.currentFileSyntaxTree === null || this.currentFilename !== filename) { + if (this.currentFilename !== filename) { var scriptSnapshot = this.hostCache.getScriptSnapshot(filename); - var start = new Date().getTime(); - syntaxTree = this.createSyntaxTree(filename, scriptSnapshot); - this.host.log("SyntaxTreeCache.Initialize: createSyntaxTree: " + (new Date().getTime() - start)); var start = new Date().getTime(); sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true); @@ -1640,11 +1618,6 @@ module ts { else if (this.currentFileVersion !== version) { var scriptSnapshot = this.hostCache.getScriptSnapshot(filename); - var start = new Date().getTime(); - syntaxTree = this.updateSyntaxTree(filename, scriptSnapshot, - this.currentSourceFile.getScriptSnapshot(), this.currentFileSyntaxTree, this.currentFileVersion); - this.host.log("SyntaxTreeCache.Initialize: updateSyntaxTree: " + (new Date().getTime() - start)); - var editRange = this.hostCache.getChangeRange(filename, this.currentFileVersion, this.currentSourceFile.getScriptSnapshot()); var start = new Date().getTime(); @@ -1658,12 +1631,10 @@ module ts { this.host.log("SyntaxTreeCache.Initialize: fixupParentRefs : " + (new Date().getTime() - start)); } - if (syntaxTree !== null) { - Debug.assert(sourceFile !== undefined); + if (sourceFile) { // All done, ensure state is up to date this.currentFileVersion = version; this.currentFilename = filename; - this.currentFileSyntaxTree = syntaxTree; this.currentSourceFile = sourceFile; } @@ -1684,112 +1655,14 @@ module ts { } } - 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); return this.getCurrentSourceFile(filename).getScriptSnapshot(); } - - 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, previousScriptSnapshot: TypeScript.IScriptSnapshot, previousSyntaxTree: TypeScript.SyntaxTree, previousFileVersion: string): TypeScript.SyntaxTree { - var editRange = this.hostCache.getChangeRange(filename, previousFileVersion, previousScriptSnapshot); - - // 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, previousScriptSnapshot, 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'); - } - } } function createSourceFileFromScriptSnapshot(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, settings: CompilerOptions, version: string, isOpen: boolean) { @@ -4883,11 +4756,6 @@ module ts { } /// Syntactic features - function getSyntaxTree(filename: string): TypeScript.SyntaxTree { - filename = normalizeSlashes(filename); - return syntaxTreeCache.getCurrentFileSyntaxTree(filename); - } - function getCurrentSourceFile(filename: string): SourceFile { filename = normalizeSlashes(filename); var currentSourceFile = syntaxTreeCache.getCurrentSourceFile(filename); From ced8785bd3b58c2841aa583bac0dacb741cf46ff Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 17 Nov 2014 17:01:23 -0800 Subject: [PATCH 2/2] eliminate usage of TypeScript module from services layer --- src/harness/fourslash.ts | 25 +- src/harness/harnessLanguageService.ts | 46 +-- src/services/breakpoints.ts | 36 +-- src/services/formatting.ts | 2 +- src/services/formatting/references.ts | 1 + src/services/formatting/rules.ts | 2 +- src/services/formatting/tokenSpan.ts | 2 +- src/services/navigationBar.ts | 7 +- src/services/outliningElementsCollector.ts | 10 +- src/services/services.ts | 192 ++++++++---- src/services/shims.ts | 22 +- src/services/signatureHelp.ts | 2 +- src/services/text.ts | 296 ++++++++++++++++++ .../formattingInMultilineComments.ts | 14 + .../unittests/services/documentRegistry.ts | 12 +- 15 files changed, 519 insertions(+), 150 deletions(-) create mode 100644 src/services/text.ts create mode 100644 tests/cases/fourslash/formattingInMultilineComments.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index ffb0ed16cb..fb3bed30c3 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -215,7 +215,7 @@ module FourSlash { } public setCancelled(numberOfCalls: number = 0): void { - TypeScript.Debug.assert(numberOfCalls >= 0); + ts.Debug.assert(numberOfCalls >= 0); this.numberOfCallsBeforeCancellation = numberOfCalls; } @@ -239,7 +239,7 @@ module FourSlash { // This function creates IScriptSnapshot object for testing getPreProcessedFileInfo // Return object may lack some functionalities for other purposes. - function createScriptSnapShot(sourceText: string): TypeScript.IScriptSnapshot { + function createScriptSnapShot(sourceText: string): ts.IScriptSnapshot { return { getText: (start: number, end: number) => { return sourceText.substr(start, end - start); @@ -250,8 +250,8 @@ module FourSlash { getLineStartPositions: () => { return []; }, - getChangeRange: (oldSnapshot: TypeScript.IScriptSnapshot) => { - return undefined; + getChangeRange: (oldSnapshot: ts.IScriptSnapshot) => { + return undefined; } }; } @@ -262,7 +262,7 @@ module FourSlash { private languageService: ts.LanguageService; // A reference to the language service's compiler state's compiler instance - private compiler: () => { getSyntaxTree(fileName: string): TypeScript.SyntaxTree; getSourceUnit(fileName: string): TypeScript.SourceUnitSyntax; }; + private compiler: () => { getSyntaxTree(fileName: string): ts.SourceFile }; // The current caret position in the active file public currentCaretPosition = 0; @@ -403,8 +403,9 @@ module FourSlash { public goToPosition(pos: number) { this.currentCaretPosition = pos; - var lineCharPos = TypeScript.LineMap1.fromString(this.getCurrentFileContent()).getLineAndCharacterFromPosition(pos); - this.scenarioActions.push(''); + var lineStarts = ts.computeLineStarts(this.getCurrentFileContent()); + var lineCharPos = ts.getLineAndCharacterOfPosition(lineStarts, pos); + this.scenarioActions.push(''); } public moveCaretRight(count = 1) { @@ -1017,7 +1018,7 @@ module FourSlash { private alignmentForExtraInfo = 50; - private spanInfoToString(pos: number, spanInfo: TypeScript.TextSpan, prefixString: string) { + private spanInfoToString(pos: number, spanInfo: ts.TextSpan, prefixString: string) { var resultString = "SpanInfo: " + JSON.stringify(spanInfo); if (spanInfo) { var spanString = this.activeFile.content.substr(spanInfo.start(), spanInfo.length()); @@ -1034,7 +1035,7 @@ module FourSlash { return resultString; } - private baselineCurrentFileLocations(getSpanAtPos: (pos: number) => TypeScript.TextSpan): string { + private baselineCurrentFileLocations(getSpanAtPos: (pos: number) => ts.TextSpan): string { var fileLineMap = ts.computeLineStarts(this.activeFile.content); var nextLine = 0; var resultString = ""; @@ -1748,14 +1749,14 @@ module FourSlash { public verifySemanticClassifications(expected: { classificationType: string; text: string }[]) { var actual = this.languageService.getSemanticClassifications(this.activeFile.fileName, - new TypeScript.TextSpan(0, this.activeFile.content.length)); + new ts.TextSpan(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); } public verifySyntacticClassifications(expected: { classificationType: string; text: string }[]) { var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName, - new TypeScript.TextSpan(0, this.activeFile.content.length)); + new ts.TextSpan(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); } @@ -1789,7 +1790,7 @@ module FourSlash { for (var i = 0; i < spans.length; i++) { var expectedSpan = spans[i]; var actualComment = actual[i]; - var actualCommentSpan = new TypeScript.TextSpan(actualComment.position, actualComment.message.length); + var actualCommentSpan = new ts.TextSpan(actualComment.position, actualComment.message.length); if (expectedSpan.start !== actualCommentSpan.start() || expectedSpan.end !== actualCommentSpan.end()) { this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start() + ',' + actualCommentSpan.end() + ')'); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6163dfd0e7..d3082a17ba 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -4,8 +4,8 @@ module Harness.LanguageService { export class ScriptInfo { public version: number = 1; - public editRanges: { length: number; textChangeRange: TypeScript.TextChangeRange; }[] = []; - public lineMap: TypeScript.LineMap = null; + public editRanges: { length: number; textChangeRange: ts.TextChangeRange; }[] = []; + public lineMap: number[] = null; constructor(public fileName: string, public content: string, public isOpen = true) { this.setContent(content); @@ -13,7 +13,7 @@ module Harness.LanguageService { private setContent(content: string): void { this.content = content; - this.lineMap = TypeScript.LineMap1.fromString(content); + this.lineMap = ts.computeLineStarts(content); } public updateContent(content: string): void { @@ -32,30 +32,30 @@ module Harness.LanguageService { // Store edit range + new length of script this.editRanges.push({ length: this.content.length, - textChangeRange: new TypeScript.TextChangeRange( - TypeScript.TextSpan.fromBounds(minChar, limChar), newText.length) + textChangeRange: new ts.TextChangeRange( + ts.TextSpan.fromBounds(minChar, limChar), newText.length) }); // Update version # this.version++; } - public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): TypeScript.TextChangeRange { + public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { if (startVersion === endVersion) { // No edits! - return TypeScript.TextChangeRange.unchanged; + return ts.TextChangeRange.unchanged; } var initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); var lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); var entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); - return TypeScript.TextChangeRange.collapseChangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); + return ts.TextChangeRange.collapseChangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); } } class ScriptSnapshotShim implements ts.ScriptSnapshotShim { - private lineMap: TypeScript.LineMap = null; + private lineMap: number[] = null; private textSnapshot: string; private version: number; @@ -74,10 +74,10 @@ module Harness.LanguageService { public getLineStartPositions(): string { if (this.lineMap === null) { - this.lineMap = TypeScript.LineMap1.fromString(this.textSnapshot); + this.lineMap = ts.computeLineStarts(this.textSnapshot); } - return JSON.stringify(this.lineMap.lineStarts()); + return JSON.stringify(this.lineMap); } public getChangeRange(oldScript: ts.ScriptSnapshotShim): string { @@ -108,7 +108,7 @@ module Harness.LanguageService { public acquireDocument( fileName: string, compilationSettings: ts.CompilerOptions, - scriptSnapshot: TypeScript.IScriptSnapshot, + scriptSnapshot: ts.IScriptSnapshot, version: string, isOpen: boolean): ts.SourceFile { return ts.createSourceFile(fileName, scriptSnapshot.getText(0, scriptSnapshot.getLength()), compilationSettings.target, version, isOpen); @@ -118,10 +118,10 @@ module Harness.LanguageService { document: ts.SourceFile, fileName: string, compilationSettings: ts.CompilerOptions, - scriptSnapshot: TypeScript.IScriptSnapshot, + scriptSnapshot: ts.IScriptSnapshot, version: string, isOpen: boolean, - textChangeRange: TypeScript.TextChangeRange + textChangeRange: ts.TextChangeRange ): ts.SourceFile { return document.update(scriptSnapshot, version, isOpen, textChangeRange); } @@ -263,13 +263,13 @@ module Harness.LanguageService { } /** Parse file given its source text */ - public parseSourceText(fileName: string, sourceText: TypeScript.IScriptSnapshot): TypeScript.SourceUnitSyntax { - return TypeScript.Parser.parse(fileName, TypeScript.SimpleText.fromScriptSnapshot(sourceText), ts.ScriptTarget.Latest, TypeScript.isDTSFile(fileName)).sourceUnit(); + public parseSourceText(fileName: string, sourceText: ts.IScriptSnapshot): ts.SourceFile { + return ts.createSourceFile(fileName, sourceText.getText(0, sourceText.getLength()), ts.ScriptTarget.Latest, "1", true); } /** Parse a file on disk given its fileName */ public parseFile(fileName: string) { - var sourceText = TypeScript.ScriptSnapshot.fromString(Harness.IO.readFile(fileName)); + var sourceText = ts.ScriptSnapshot.fromString(Harness.IO.readFile(fileName)); return this.parseSourceText(fileName, sourceText); } @@ -283,22 +283,22 @@ module Harness.LanguageService { assert.isTrue(line >= 1); assert.isTrue(col >= 1); - return script.lineMap.getPosition(line - 1, col - 1); + return ts.getPositionFromLineAndCharacter(script.lineMap, line, col); } /** * @param line 0 based index * @param col 0 based index */ - public positionToZeroBasedLineCol(fileName: string, position: number): TypeScript.ILineAndCharacter { + public positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { var script: ScriptInfo = this.fileNameToScript[fileName]; assert.isNotNull(script); - var result = script.lineMap.getLineAndCharacterFromPosition(position); + var result = ts.getLineAndCharacterOfPosition(script.lineMap, position); - assert.isTrue(result.line() >= 0); - assert.isTrue(result.character() >= 0); - return { line: result.line(), character: result.character() }; + assert.isTrue(result.line >= 1); + assert.isTrue(result.character >= 1); + return { line: result.line - 1, character: result.character - 1 }; } /** Verify that applying edits to sourceFileName result in the content of the file baselineFileName */ diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index f8688b3c47..7867751a96 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -38,25 +38,25 @@ module ts.BreakpointResolver { return spanInNode(tokenAtLocation); function textSpan(startNode: Node, endNode?: Node) { - return TypeScript.TextSpan.fromBounds(startNode.getStart(), (endNode || startNode).getEnd()); + return TextSpan.fromBounds(startNode.getStart(), (endNode || startNode).getEnd()); } - function spanInNodeIfStartsOnSameLine(node: Node, otherwiseOnNode?: Node): TypeScript.TextSpan { + function spanInNodeIfStartsOnSameLine(node: Node, otherwiseOnNode?: Node): TextSpan { if (node && lineOfPosition === sourceFile.getLineAndCharacterFromPosition(node.getStart()).line) { return spanInNode(node); } return spanInNode(otherwiseOnNode); } - function spanInPreviousNode(node: Node): TypeScript.TextSpan { + function spanInPreviousNode(node: Node): TextSpan { return spanInNode(findPrecedingToken(node.pos, sourceFile)); } - function spanInNextNode(node: Node): TypeScript.TextSpan { + function spanInNextNode(node: Node): TextSpan { return spanInNode(findNextToken(node, node.parent)); } - function spanInNode(node: Node): TypeScript.TextSpan { + function spanInNode(node: Node): TextSpan { if (node) { if (isExpression(node)) { if (node.parent.kind === SyntaxKind.DoStatement) { @@ -256,7 +256,7 @@ module ts.BreakpointResolver { } } - function spanInVariableDeclaration(variableDeclaration: VariableDeclaration): TypeScript.TextSpan { + function spanInVariableDeclaration(variableDeclaration: VariableDeclaration): TextSpan { // If declaration of for in statement, just set the span in parent if (variableDeclaration.parent.kind === SyntaxKind.ForInStatement) { return spanInNode(variableDeclaration.parent); @@ -301,7 +301,7 @@ module ts.BreakpointResolver { !!(parameter.flags & NodeFlags.Public) || !!(parameter.flags & NodeFlags.Private); } - function spanInParameterDeclaration(parameter: ParameterDeclaration): TypeScript.TextSpan { + function spanInParameterDeclaration(parameter: ParameterDeclaration): TextSpan { if (canHaveSpanInParameterDeclaration(parameter)) { return textSpan(parameter); } @@ -324,7 +324,7 @@ module ts.BreakpointResolver { (functionDeclaration.parent.kind === SyntaxKind.ClassDeclaration && functionDeclaration.kind !== SyntaxKind.Constructor); } - function spanInFunctionDeclaration(functionDeclaration: FunctionLikeDeclaration): TypeScript.TextSpan { + function spanInFunctionDeclaration(functionDeclaration: FunctionLikeDeclaration): TextSpan { // No breakpoints in the function signature if (!functionDeclaration.body) { return undefined; @@ -339,7 +339,7 @@ module ts.BreakpointResolver { return spanInNode(functionDeclaration.body); } - function spanInFunctionBlock(block: Block): TypeScript.TextSpan { + function spanInFunctionBlock(block: Block): TextSpan { var nodeForSpanInBlock = block.statements.length ? block.statements[0] : block.getLastToken(); if (canFunctionHaveSpanInWholeDeclaration(block.parent)) { return spanInNodeIfStartsOnSameLine(block.parent, nodeForSpanInBlock); @@ -348,7 +348,7 @@ module ts.BreakpointResolver { return spanInNode(nodeForSpanInBlock); } - function spanInBlock(block: Block): TypeScript.TextSpan { + function spanInBlock(block: Block): TextSpan { switch (block.parent.kind) { case SyntaxKind.ModuleDeclaration: if (getModuleInstanceState(block.parent) !== ModuleInstanceState.Instantiated) { @@ -370,7 +370,7 @@ module ts.BreakpointResolver { return spanInNode(block.statements[0]); } - function spanInForStatement(forStatement: ForStatement): TypeScript.TextSpan { + function spanInForStatement(forStatement: ForStatement): TextSpan { if (forStatement.declarations) { return spanInNode(forStatement.declarations[0]); } @@ -386,7 +386,7 @@ module ts.BreakpointResolver { } // Tokens: - function spanInOpenBraceToken(node: Node): TypeScript.TextSpan { + function spanInOpenBraceToken(node: Node): TextSpan { switch (node.parent.kind) { case SyntaxKind.EnumDeclaration: var enumDeclaration = node.parent; @@ -404,7 +404,7 @@ module ts.BreakpointResolver { return spanInNode(node.parent); } - function spanInCloseBraceToken(node: Node): TypeScript.TextSpan { + function spanInCloseBraceToken(node: Node): TextSpan { switch (node.parent.kind) { case SyntaxKind.ModuleBlock: // If this is not instantiated module block no bp span @@ -439,7 +439,7 @@ module ts.BreakpointResolver { } } - function spanInOpenParenToken(node: Node): TypeScript.TextSpan { + function spanInOpenParenToken(node: Node): TextSpan { if (node.parent.kind === SyntaxKind.DoStatement) { // Go to while keyword and do action instead return spanInPreviousNode(node); @@ -449,7 +449,7 @@ module ts.BreakpointResolver { return spanInNode(node.parent); } - function spanInCloseParenToken(node: Node): TypeScript.TextSpan { + function spanInCloseParenToken(node: Node): TextSpan { // Is this close paren token of parameter list, set span in previous token switch (node.parent.kind) { case SyntaxKind.FunctionExpression: @@ -473,7 +473,7 @@ module ts.BreakpointResolver { return spanInNode(node.parent); } - function spanInColonToken(node: Node): TypeScript.TextSpan { + function spanInColonToken(node: Node): TextSpan { // Is this : specifying return annotation of the function declaration if (isAnyFunction(node.parent) || node.parent.kind === SyntaxKind.PropertyAssignment) { return spanInPreviousNode(node); @@ -482,7 +482,7 @@ module ts.BreakpointResolver { return spanInNode(node.parent); } - function spanInGreaterThanOrLessThanToken(node: Node): TypeScript.TextSpan { + function spanInGreaterThanOrLessThanToken(node: Node): TextSpan { if (node.parent.kind === SyntaxKind.TypeAssertion) { return spanInNode((node.parent).operand); } @@ -490,7 +490,7 @@ module ts.BreakpointResolver { return spanInNode(node.parent); } - function spanInWhileKeyword(node: Node): TypeScript.TextSpan { + function spanInWhileKeyword(node: Node): TextSpan { if (node.parent.kind === SyntaxKind.DoStatement) { // Set span on while expression return textSpan(node, findNextToken((node.parent).expression, node.parent)); diff --git a/src/services/formatting.ts b/src/services/formatting.ts index dbe85fc4c6..91a3a362fd 100644 --- a/src/services/formatting.ts +++ b/src/services/formatting.ts @@ -834,7 +834,7 @@ module ts.formatting { } function newTextChange(start: number, len: number, newText: string): TextChange { - return { span: new TypeScript.TextSpan(start, len), newText: newText } + return { span: new TextSpan(start, len), newText: newText } } function recordDelete(start: number, len: number) { diff --git a/src/services/formatting/references.ts b/src/services/formatting/references.ts index 3d19b33d82..9f4b7e3743 100644 --- a/src/services/formatting/references.ts +++ b/src/services/formatting/references.ts @@ -13,6 +13,7 @@ // limitations under the License. // +/// /// /// /// diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 4b42591936..2485945df0 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -24,7 +24,7 @@ module ts.formatting { return name; } } - throw new Error(TypeScript.getDiagnosticMessage(TypeScript.DiagnosticCode.Unknown_rule, null)); + throw new Error("Unknown rule"); } [name: string]: any; diff --git a/src/services/formatting/tokenSpan.ts b/src/services/formatting/tokenSpan.ts index 95f9bab3ee..1d8173a822 100644 --- a/src/services/formatting/tokenSpan.ts +++ b/src/services/formatting/tokenSpan.ts @@ -16,7 +16,7 @@ /// module ts.formatting { - export class TokenSpan extends TypeScript.TextSpan { + export class TokenSpan extends TextSpan { constructor(public kind: SyntaxKind, start: number, length: number) { super(start, length); } diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index ca06d40fa8..c0ec80c98e 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -1,5 +1,4 @@ /// -/// module ts.NavigationBar { export function getNavigationBarItems(sourceFile: SourceFile): ts.NavigationBarItem[] { @@ -257,7 +256,7 @@ module ts.NavigationBar { return !text || text.trim() === ""; } - function getNavigationBarItem(text: string, kind: string, kindModifiers: string, spans: TypeScript.TextSpan[], childItems: ts.NavigationBarItem[] = [], indent: number = 0): ts.NavigationBarItem { + function getNavigationBarItem(text: string, kind: string, kindModifiers: string, spans: TextSpan[], childItems: NavigationBarItem[] = [], indent: number = 0): NavigationBarItem { if (isEmpty(text)) { return undefined; } @@ -424,8 +423,8 @@ module ts.NavigationBar { function getNodeSpan(node: Node) { return node.kind === SyntaxKind.SourceFile - ? TypeScript.TextSpan.fromBounds(node.getFullStart(), node.getEnd()) - : TypeScript.TextSpan.fromBounds(node.getStart(), node.getEnd()); + ? TextSpan.fromBounds(node.getFullStart(), node.getEnd()) + : TextSpan.fromBounds(node.getStart(), node.getEnd()); } function getTextOfNode(node: Node): string { diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index aaaedb735e..27f75b8d20 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -24,8 +24,8 @@ module ts { * @param autoCollapse Whether or not this region should be automatically collapsed when * the 'Collapse to Definitions' command is invoked. */ - textSpan: TypeScript.TextSpan; - hintSpan: TypeScript.TextSpan; + textSpan: TextSpan; + hintSpan: TextSpan; bannerText: string; autoCollapse: boolean; } @@ -38,8 +38,8 @@ module ts { function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean) { if (hintSpanNode && startElement && endElement) { var span: OutliningSpan = { - textSpan: TypeScript.TextSpan.fromBounds(startElement.pos, endElement.end), - hintSpan: TypeScript.TextSpan.fromBounds(hintSpanNode.getStart(), hintSpanNode.end), + textSpan: TextSpan.fromBounds(startElement.pos, endElement.end), + hintSpan: TextSpan.fromBounds(hintSpanNode.getStart(), hintSpanNode.end), bannerText: collapseText, autoCollapse: autoCollapse }; @@ -86,7 +86,7 @@ module ts { else { // Block was a standalone block. In this case we want to only collapse // the span of the block, independent of any parent span. - var span = TypeScript.TextSpan.fromBounds(n.getStart(), n.end); + var span = TextSpan.fromBounds(n.getStart(), n.end); elements.push({ textSpan: span, hintSpan: span, diff --git a/src/services/services.ts b/src/services/services.ts index b7adb56e82..540a4b0b6c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4,6 +4,7 @@ /// /// +/// /// /// /// @@ -58,11 +59,72 @@ module ts { } export interface SourceFile { - getScriptSnapshot(): TypeScript.IScriptSnapshot; + getScriptSnapshot(): IScriptSnapshot; getNamedDeclarations(): Declaration[]; - update(scriptSnapshot: TypeScript.IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): SourceFile; + update(scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile; } + /** + * Represents an immutable snapshot of a script at a specified time.Once acquired, the + * snapshot is observably immutable. i.e. the same calls with the same parameters will return + * the same values. + */ + export interface IScriptSnapshot { + /** Gets a portion of the script snapshot specified by [start, end). */ + getText(start: number, end: number): string; + + /** Gets the length of this script snapshot. */ + getLength(): number; + + /** + * This call returns the array containing the start position of every line. + * i.e."[0, 10, 55]". TODO: consider making this optional. The language service could + * always determine this (albeit in a more expensive manner). + */ + getLineStartPositions(): number[]; + + /** + * Gets the TextChangeRange that describe how the text changed between this text and + * an older version. This information is used by the incremental parser to determine + * what sections of the script need to be re-parsed. 'undefined' can be returned if the + * change range cannot be determined. However, in that case, incremental parsing will + * not happen and the entire document will be re - parsed. + */ + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange; + } + + export module ScriptSnapshot { + class StringScriptSnapshot implements IScriptSnapshot { + private _lineStartPositions: number[] = undefined; + + constructor(private text: string) { + } + + public getText(start: number, end: number): string { + return this.text.substring(start, end); + } + + public getLength(): number { + return this.text.length; + } + + public getLineStartPositions(): number[] { + if (!this._lineStartPositions) { + this._lineStartPositions = computeLineStarts(this.text); + } + + return this._lineStartPositions; + } + + public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { + throw new Error("not yet implemented"); + } + } + + export function fromString(text: string): IScriptSnapshot { + return new StringScriptSnapshot(text); + } + } export interface PreProcessedFileInfo { referencedFiles: FileReference[]; importedFiles: FileReference[]; @@ -670,10 +732,10 @@ module ts { public languageVersion: ScriptTarget; public identifiers: Map; - private scriptSnapshot: TypeScript.IScriptSnapshot; + private scriptSnapshot: IScriptSnapshot; private namedDeclarations: Declaration[]; - public getScriptSnapshot(): TypeScript.IScriptSnapshot { + public getScriptSnapshot(): IScriptSnapshot { return this.scriptSnapshot; } @@ -749,28 +811,28 @@ module ts { return this.namedDeclarations; } - public update(scriptSnapshot: TypeScript.IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): SourceFile { + public update(scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TextChangeRange): SourceFile { if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { var oldText = this.scriptSnapshot; var newText = scriptSnapshot; - TypeScript.Debug.assert((oldText.getLength() - textChangeRange.span().length() + textChangeRange.newLength()) === newText.getLength()); + 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); + 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); + Debug.assert(oldTextSuffix === newTextSuffix); } } return SourceFileObject.createSourceFileObject(this.filename, scriptSnapshot, this.languageVersion, version, isOpen); } - public static createSourceFileObject(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, languageVersion: ScriptTarget, version: string, isOpen: boolean) { + public static createSourceFileObject(filename: string, scriptSnapshot: IScriptSnapshot, languageVersion: ScriptTarget, version: string, isOpen: boolean) { var newSourceFile = createSourceFile(filename, scriptSnapshot.getText(0, scriptSnapshot.getLength()), languageVersion, version, isOpen); newSourceFile.scriptSnapshot = scriptSnapshot; return newSourceFile; @@ -789,7 +851,7 @@ module ts { getScriptFileNames(): string[]; getScriptVersion(fileName: string): string; getScriptIsOpen(fileName: string): boolean; - getScriptSnapshot(fileName: string): TypeScript.IScriptSnapshot; + getScriptSnapshot(fileName: string): IScriptSnapshot; getLocalizedDiagnosticMessages(): any; getCancellationToken(): CancellationToken; getCurrentDirectory(): string; @@ -807,17 +869,17 @@ module ts { getSemanticDiagnostics(fileName: string): Diagnostic[]; getCompilerOptionsDiagnostics(): Diagnostic[]; - getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[]; - getSemanticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[]; + getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; getCompletionsAtPosition(fileName: string, position: number, isMemberCompletion: boolean): CompletionInfo; getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails; getQuickInfoAtPosition(fileName: string, position: number): QuickInfo; - getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TypeScript.TextSpan; + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan; - getBreakpointStatementAtPosition(fileName: string, position: number): TypeScript.TextSpan; + getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan; getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems; @@ -837,7 +899,7 @@ module ts { getOutliningSpans(fileName: string): OutliningSpan[]; getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; - getBraceMatchingAtPosition(fileName: string, position: number): TypeScript.TextSpan[]; + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; getIndentationAtPosition(fileName: string, position: number, options: EditorOptions): number; getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[]; @@ -846,8 +908,6 @@ module ts { getEmitOutput(fileName: string): EmitOutput; - //getSyntaxTree(fileName: string): TypeScript.SyntaxTree; - dispose(): void; } @@ -887,7 +947,7 @@ module ts { } export interface ClassifiedSpan { - textSpan: TypeScript.TextSpan; + textSpan: TextSpan; classificationType: string; // ClassificationTypeNames } @@ -895,7 +955,7 @@ module ts { text: string; kind: string; kindModifiers: string; - spans: TypeScript.TextSpan[]; + spans: TextSpan[]; childItems: NavigationBarItem[]; indent: number; bolded: boolean; @@ -914,17 +974,17 @@ module ts { } export class TextChange { - span: TypeScript.TextSpan; + span: TextSpan; newText: string; } export interface RenameLocation { - textSpan: TypeScript.TextSpan; + textSpan: TextSpan; fileName: string; } export interface ReferenceEntry { - textSpan: TypeScript.TextSpan; + textSpan: TextSpan; fileName: string; isWriteAccess: boolean; } @@ -935,7 +995,7 @@ module ts { kindModifiers: string; matchKind: string; fileName: string; - textSpan: TypeScript.TextSpan; + textSpan: TextSpan; containerName: string; containerKind: string; } @@ -960,7 +1020,7 @@ module ts { export interface DefinitionInfo { fileName: string; - textSpan: TypeScript.TextSpan; + textSpan: TextSpan; kind: string; name: string; containerKind: string; @@ -1000,7 +1060,7 @@ module ts { export interface QuickInfo { kind: string; kindModifiers: string; - textSpan: TypeScript.TextSpan; + textSpan: TextSpan; displayParts: SymbolDisplayPart[]; documentation: SymbolDisplayPart[]; } @@ -1012,7 +1072,7 @@ module ts { fullDisplayName: string; kind: string; kindModifiers: string; - triggerSpan: TypeScript.TextSpan; + triggerSpan: TextSpan; } export interface SignatureHelpParameter { @@ -1043,7 +1103,7 @@ module ts { */ export interface SignatureHelpItems { items: SignatureHelpItem[]; - applicableSpan: TypeScript.TextSpan; + applicableSpan: TextSpan; selectedItemIndex: number; argumentIndex: number; argumentCount: number; @@ -1122,7 +1182,7 @@ module ts { acquireDocument( filename: string, compilationSettings: CompilerOptions, - scriptSnapshot: TypeScript.IScriptSnapshot, + scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean): SourceFile; @@ -1130,10 +1190,10 @@ module ts { sourceFile: SourceFile, filename: string, compilationSettings: CompilerOptions, - scriptSnapshot: TypeScript.IScriptSnapshot, + scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, - textChangeRange: TypeScript.TextChangeRange + textChangeRange: TextChangeRange ): SourceFile; releaseDocument(filename: string, compilationSettings: CompilerOptions): void @@ -1273,7 +1333,7 @@ module ts { filename: string; version: string; isOpen: boolean; - sourceText?: TypeScript.IScriptSnapshot; + sourceText?: IScriptSnapshot; } interface DocumentRegistryEntry { @@ -1563,7 +1623,7 @@ module ts { return this.getEntry(filename).isOpen; } - public getScriptSnapshot(filename: string): TypeScript.IScriptSnapshot { + public getScriptSnapshot(filename: string): IScriptSnapshot { var file = this.getEntry(filename); if (!file.sourceText) { file.sourceText = this.host.getScriptSnapshot(file.filename); @@ -1571,10 +1631,10 @@ module ts { return file.sourceText; } - public getChangeRange(filename: string, lastKnownVersion: string, oldScriptSnapshot: TypeScript.IScriptSnapshot): TypeScript.TextChangeRange { + public getChangeRange(filename: string, lastKnownVersion: string, oldScriptSnapshot: IScriptSnapshot): TextChangeRange { var currentVersion = this.getVersion(filename); if (lastKnownVersion === currentVersion) { - return TypeScript.TextChangeRange.unchanged; // "No changes" + return TextChangeRange.unchanged; // "No changes" } var scriptSnapshot = this.getScriptSnapshot(filename); @@ -1660,12 +1720,12 @@ module ts { return this.currentSourceFile; } - public getCurrentScriptSnapshot(filename: string): TypeScript.IScriptSnapshot { + public getCurrentScriptSnapshot(filename: string): IScriptSnapshot { return this.getCurrentSourceFile(filename).getScriptSnapshot(); } } - function createSourceFileFromScriptSnapshot(filename: string, scriptSnapshot: TypeScript.IScriptSnapshot, settings: CompilerOptions, version: string, isOpen: boolean) { + function createSourceFileFromScriptSnapshot(filename: string, scriptSnapshot: IScriptSnapshot, settings: CompilerOptions, version: string, isOpen: boolean) { return SourceFileObject.createSourceFileObject(filename, scriptSnapshot, settings.target, version, isOpen); } @@ -1709,7 +1769,7 @@ module ts { function acquireDocument( filename: string, compilationSettings: CompilerOptions, - scriptSnapshot: TypeScript.IScriptSnapshot, + scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean): SourceFile { @@ -1733,10 +1793,10 @@ module ts { sourceFile: SourceFile, filename: string, compilationSettings: CompilerOptions, - scriptSnapshot: TypeScript.IScriptSnapshot, + scriptSnapshot: IScriptSnapshot, version: string, isOpen: boolean, - textChangeRange: TypeScript.TextChangeRange + textChangeRange: TextChangeRange ): SourceFile { var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ false); @@ -2010,7 +2070,7 @@ module ts { export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry): LanguageService { var syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); - var ruleProvider: ts.formatting.RulesProvider; + var ruleProvider: formatting.RulesProvider; var hostCache: HostCache; // A cache of all the information about the files on the host side. var program: Program; @@ -2044,7 +2104,7 @@ module ts { function getRuleProvider(options: FormatCodeOptions) { // Ensure rules are initialized and up to date wrt to formatting options if (!ruleProvider) { - ruleProvider = new ts.formatting.RulesProvider(host); + ruleProvider = new formatting.RulesProvider(host); } ruleProvider.ensureUpToDate(options); @@ -2161,7 +2221,7 @@ module ts { // file was closed, then we always want to re-parse. 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; + var textChangeRange: TextChangeRange = null; if (sourceFile.isOpen && isOpen) { textChangeRange = hostCache.getChangeRange(filename, sourceFile.version, sourceFile.getScriptSnapshot()); } @@ -3168,7 +3228,7 @@ module ts { return { kind: ScriptElementKind.unknown, kindModifiers: ScriptElementKindModifier.none, - textSpan: new TypeScript.TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpan(node.getStart(), node.getWidth()), displayParts: typeToDisplayParts(typeInfoResolver, type, getContainerNode(node)), documentation: type.symbol ? type.symbol.getDocumentationComment() : undefined }; @@ -3182,7 +3242,7 @@ module ts { return { kind: displayPartsDocumentationsAndKind.symbolKind, kindModifiers: getSymbolModifiers(symbol), - textSpan: new TypeScript.TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpan(node.getStart(), node.getWidth()), displayParts: displayPartsDocumentationsAndKind.displayParts, documentation: displayPartsDocumentationsAndKind.documentation }; @@ -3193,7 +3253,7 @@ module ts { function getDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo { return { fileName: node.getSourceFile().filename, - textSpan: TypeScript.TextSpan.fromBounds(node.getStart(), node.getEnd()), + textSpan: TextSpan.fromBounds(node.getStart(), node.getEnd()), kind: symbolKind, name: symbolName, containerKind: undefined, @@ -3271,7 +3331,7 @@ module ts { if (program.getSourceFile(targetFilename)) { return [{ fileName: targetFilename, - textSpan: TypeScript.TextSpan.fromBounds(0, 0), + textSpan: TextSpan.fromBounds(0, 0), kind: ScriptElementKind.scriptElement, name: comment.filename, containerName: undefined, @@ -3440,7 +3500,7 @@ module ts { if (shouldHighlightNextKeyword) { result.push({ fileName: filename, - textSpan: TypeScript.TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), + textSpan: TextSpan.fromBounds(elseKeyword.getStart(), ifKeyword.end), isWriteAccess: false }); i++; // skip the next keyword @@ -4026,7 +4086,7 @@ module ts { (findInComments && isInComment(position))) { result.push({ fileName: sourceFile.filename, - textSpan: new TypeScript.TextSpan(position, searchText.length), + textSpan: new TextSpan(position, searchText.length), isWriteAccess: false }); } @@ -4395,7 +4455,7 @@ module ts { return { fileName: node.getSourceFile().filename, - textSpan: TypeScript.TextSpan.fromBounds(start, end), + textSpan: TextSpan.fromBounds(start, end), isWriteAccess: isWriteAccess(node) }; } @@ -4451,7 +4511,7 @@ module ts { kindModifiers: getNodeModifiers(declaration), matchKind: MatchKind[matchKind], fileName: filename, - textSpan: TypeScript.TextSpan.fromBounds(declaration.getStart(), declaration.getEnd()), + textSpan: TextSpan.fromBounds(declaration.getStart(), declaration.getEnd()), // TODO(jfreeman): What should be the containerName when the container has a computed name? containerName: container.name ? (container.name).text : "", containerKind: container.name ? getNodeKind(container) : "" @@ -4762,7 +4822,7 @@ module ts { return currentSourceFile; } - function getNameOrDottedNameSpan(filename: string, startPos: number, endPos: number): TypeScript.TextSpan { + function getNameOrDottedNameSpan(filename: string, startPos: number, endPos: number): TextSpan { filename = ts.normalizeSlashes(filename); // Get node at the location var node = getTouchingPropertyName(getCurrentSourceFile(filename), startPos); @@ -4814,7 +4874,7 @@ module ts { } } - return TypeScript.TextSpan.fromBounds(nodeForStartPos.getStart(), node.getEnd()); + return TextSpan.fromBounds(nodeForStartPos.getStart(), node.getEnd()); } function getBreakpointStatementAtPosition(filename: string, position: number) { @@ -4829,7 +4889,7 @@ module ts { return NavigationBar.getNavigationBarItems(getCurrentSourceFile(filename)); } - function getSemanticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] { + function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { synchronizeHostData(); fileName = normalizeSlashes(fileName); @@ -4888,7 +4948,7 @@ module ts { var type = classifySymbol(symbol, getMeaningFromLocation(node)); if (type) { result.push({ - textSpan: new TypeScript.TextSpan(node.getStart(), node.getWidth()), + textSpan: new TextSpan(node.getStart(), node.getWidth()), classificationType: type }); } @@ -4900,7 +4960,7 @@ module ts { } } - function getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] { + function getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { // doesn't use compiler - no need to synchronize with host fileName = normalizeSlashes(fileName); var sourceFile = getCurrentSourceFile(fileName); @@ -4914,7 +4974,7 @@ module ts { var width = comment.end - comment.pos; if (span.intersectsWith(comment.pos, width)) { result.push({ - textSpan: new TypeScript.TextSpan(comment.pos, width), + textSpan: new TextSpan(comment.pos, width), classificationType: ClassificationTypeNames.comment }); } @@ -4927,7 +4987,7 @@ module ts { var type = classifyTokenType(token); if (type) { result.push({ - textSpan: new TypeScript.TextSpan(token.getStart(), token.getWidth()), + textSpan: new TextSpan(token.getStart(), token.getWidth()), classificationType: type }); } @@ -5039,7 +5099,7 @@ module ts { function getBraceMatchingAtPosition(filename: string, position: number) { var sourceFile = getCurrentSourceFile(filename); - var result: TypeScript.TextSpan[] = []; + var result: TextSpan[] = []; var token = getTouchingToken(sourceFile, position); @@ -5051,12 +5111,12 @@ module ts { var parentElement = token.parent; var childNodes = parentElement.getChildren(sourceFile); - for (var i = 0, n = childNodes.length; i < n; i++) { + for (var i = 0, n = childNodes.length; i < n; i++) {33 var current = childNodes[i]; if (current.kind === matchKind) { - var range1 = new TypeScript.TextSpan(token.getStart(sourceFile), token.getWidth(sourceFile)); - var range2 = new TypeScript.TextSpan(current.getStart(sourceFile), current.getWidth(sourceFile)); + var range1 = new TextSpan(token.getStart(sourceFile), token.getWidth(sourceFile)); + var range2 = new TextSpan(current.getStart(sourceFile), current.getWidth(sourceFile)); // We want to order the braces when we return the result. if (range1.start() < range2.start()) { @@ -5295,9 +5355,9 @@ module ts { } function isLetterOrDigit(char: number): boolean { - return (char >= TypeScript.CharacterCodes.a && char <= TypeScript.CharacterCodes.z) || - (char >= TypeScript.CharacterCodes.A && char <= TypeScript.CharacterCodes.Z) || - (char >= TypeScript.CharacterCodes._0 && char <= TypeScript.CharacterCodes._9); + return (char >= CharacterCodes.a && char <= CharacterCodes.z) || + (char >= CharacterCodes.A && char <= CharacterCodes.Z) || + (char >= CharacterCodes._0 && char <= CharacterCodes._9); } } @@ -5320,7 +5380,7 @@ module ts { if (kind) { return getRenameInfo(symbol.name, typeInfoResolver.getFullyQualifiedName(symbol), kind, getSymbolModifiers(symbol), - new TypeScript.TextSpan(node.getStart(), node.getWidth())); + new TextSpan(node.getStart(), node.getWidth())); } } } @@ -5339,7 +5399,7 @@ module ts { }; } - function getRenameInfo(displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: TypeScript.TextSpan): RenameInfo { + function getRenameInfo(displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: TextSpan): RenameInfo { return { canRename: true, localizedErrorMessage: undefined, diff --git a/src/services/shims.ts b/src/services/shims.ts index 00acff3798..38c68c067f 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -15,8 +15,6 @@ /// -/// - var debugObjectHost = (this); module ts { @@ -176,7 +174,7 @@ module ts { } export interface CoreServicesShim extends Shim { - getPreProcessedFileInfo(fileName: string, sourceText: TypeScript.IScriptSnapshot): string; + getPreProcessedFileInfo(fileName: string, sourceText: IScriptSnapshot): string; getDefaultCompilationSettings(): string; } @@ -309,7 +307,7 @@ module ts { logger.log("*INTERNAL ERROR* - Exception in typescript services: " + err.message); } - class ScriptSnapshotShimAdapter implements TypeScript.IScriptSnapshot { + class ScriptSnapshotShimAdapter implements IScriptSnapshot { private lineStartPositions: number[] = null; constructor(private scriptSnapshotShim: ScriptSnapshotShim) { @@ -331,7 +329,7 @@ module ts { return this.lineStartPositions; } - public getChangeRange(oldSnapshot: TypeScript.IScriptSnapshot): TypeScript.TextChangeRange { + public getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { var oldSnapshotShim = oldSnapshot; var encoded = this.scriptSnapshotShim.getChangeRange(oldSnapshotShim.scriptSnapshotShim); if (encoded == null) { @@ -339,8 +337,8 @@ module ts { } var decoded: { span: { start: number; length: number; }; newLength: number; } = JSON.parse(encoded); - return new TypeScript.TextChangeRange( - new TypeScript.TextSpan(decoded.span.start, decoded.span.length), decoded.newLength); + return new TextChangeRange( + new TextSpan(decoded.span.start, decoded.span.length), decoded.newLength); } } @@ -368,7 +366,7 @@ module ts { return JSON.parse(encoded); } - public getScriptSnapshot(fileName: string): TypeScript.IScriptSnapshot { + public getScriptSnapshot(fileName: string): IScriptSnapshot { return new ScriptSnapshotShimAdapter(this.shimHost.getScriptSnapshot(fileName)); } @@ -521,7 +519,7 @@ module ts { return this.forwardJSONCall( "getSyntacticClassifications('" + fileName + "', " + start + ", " + length + ")", () => { - var classifications = this.languageService.getSyntacticClassifications(fileName, new TypeScript.TextSpan(start, length)); + var classifications = this.languageService.getSyntacticClassifications(fileName, new TextSpan(start, length)); return classifications; }); } @@ -530,7 +528,7 @@ module ts { return this.forwardJSONCall( "getSemanticClassifications('" + fileName + "', " + start + ", " + length + ")", () => { - var classifications = this.languageService.getSemanticClassifications(fileName, new TypeScript.TextSpan(start, length)); + var classifications = this.languageService.getSemanticClassifications(fileName, new TextSpan(start, length)); return classifications; }); } @@ -845,7 +843,7 @@ module ts { return forwardJSONCall(this.logger, actionDescription, action); } - public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: TypeScript.IScriptSnapshot): string { + public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { return this.forwardJSONCall( "getPreProcessedFileInfo('" + fileName + "')", () => { @@ -938,7 +936,7 @@ module ts { } } - throw TypeScript.Errors.invalidOperation(); + throw new Error("Invalid operation"); } } } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 41f64c1b8d..a4580582ed 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -353,7 +353,7 @@ module ts.SignatureHelp { // but not including parentheses) var applicableSpanStart = argumentListOrTypeArgumentList.getFullStart(); var applicableSpanEnd = skipTrivia(sourceFile.text, argumentListOrTypeArgumentList.end, /*stopAfterLineBreak*/ false); - var applicableSpan = new TypeScript.TextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + var applicableSpan = new TextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); // The listItemIndex we got back includes commas. Our goal is to return the index of the proper // item (not including commas). Here are some examples: diff --git a/src/services/text.ts b/src/services/text.ts new file mode 100644 index 0000000000..fd768785de --- /dev/null +++ b/src/services/text.ts @@ -0,0 +1,296 @@ +module ts { + export class TextSpan { + private _start: number; + private _length: number; + + /** + * Creates a TextSpan instance beginning with the position Start and having the Length + * specified with length. + */ + constructor(start: number, length: number) { + Debug.assert(start >= 0, "start"); + Debug.assert(length >= 0, "length"); + + this._start = start; + this._length = length; + } + + public toJSON(key: any): any { + return { start: this._start, length: this._length }; + } + + public start(): number { + return this._start; + } + + public length(): number { + return this._length; + } + + public end(): number { + return this._start + this._length; + } + + public isEmpty(): boolean { + return this._length === 0; + } + + /** + * Determines whether the position lies within the span. Returns true if the position is greater than or equal to Start and strictly less + * than End, otherwise false. + * @param position The position to check. + */ + public containsPosition(position: number): boolean { + return position >= this._start && position < this.end(); + } + + /** + * Determines whether span falls completely within this span. Returns true if the specified span falls completely within this span, otherwise false. + * @param span The span to check. + */ + public containsTextSpan(span: TextSpan): boolean { + return span._start >= this._start && span.end() <= this.end(); + } + + /** + * Determines whether the given span overlaps this span. Two spans are considered to overlap + * if they have positions in common and neither is empty. Empty spans do not overlap with any + * other span. Returns true if the spans overlap, false otherwise. + * @param span The span to check. + */ + public overlapsWith(span: TextSpan): boolean { + var overlapStart = Math.max(this._start, span._start); + var overlapEnd = Math.min(this.end(), span.end()); + + return overlapStart < overlapEnd; + } + + /** + * Returns the overlap with the given span, or undefined if there is no overlap. + * @param span The span to check. + */ + public overlap(span: TextSpan): TextSpan { + var overlapStart = Math.max(this._start, span._start); + var overlapEnd = Math.min(this.end(), span.end()); + + if (overlapStart < overlapEnd) { + return TextSpan.fromBounds(overlapStart, overlapEnd); + } + + return undefined; + } + + /** + * Determines whether span intersects this span. Two spans are considered to + * intersect if they have positions in common or the end of one span + * coincides with the start of the other span. Returns true if the spans intersect, false otherwise. + * @param The span to check. + */ + public intersectsWithTextSpan(span: TextSpan): boolean { + return span._start <= this.end() && span.end() >= this._start; + } + + public intersectsWith(start: number, length: number): boolean { + var end = start + length; + return start <= this.end() && end >= this._start; + } + + /** + * Determines whether the given position intersects this span. + * A position is considered to intersect if it is between the start and + * end positions (inclusive) of this span. Returns true if the position intersects, false otherwise. + * @param position The position to check. + */ + public intersectsWithPosition(position: number): boolean { + return position <= this.end() && position >= this._start; + } + + /** + * Returns the intersection with the given span, or undefined if there is no intersection. + * @param span The span to check. + */ + public intersection(span: TextSpan): TextSpan { + var intersectStart = Math.max(this._start, span._start); + var intersectEnd = Math.min(this.end(), span.end()); + + if (intersectStart <= intersectEnd) { + return TextSpan.fromBounds(intersectStart, intersectEnd); + } + + return undefined; + } + + /** + * Creates a new TextSpan from the given start and end positions + * as opposed to a position and length. + */ + public static fromBounds(start: number, end: number): TextSpan { + Debug.assert(start >= 0); + Debug.assert(end - start >= 0); + return new TextSpan(start, end - start); + } + } + + export class TextChangeRange { + public static unchanged = new TextChangeRange(new TextSpan(0, 0), 0); + + private _span: TextSpan; + private _newLength: number; + + /** + * Initializes a new instance of TextChangeRange. + */ + constructor(span: TextSpan, newLength: number) { + Debug.assert(newLength >= 0, "newLength"); + + this._span = span; + this._newLength = newLength; + } + + /** + * The span of text before the edit which is being changed + */ + public span(): TextSpan { + return this._span; + } + + /** + * Width of the span after the edit. A 0 here would represent a delete + */ + public newLength(): number { + return this._newLength; + } + + public newSpan(): TextSpan { + return new TextSpan(this.span().start(), this.newLength()); + } + + public isUnchanged(): boolean { + return this.span().isEmpty() && this.newLength() === 0; + } + + /** + * Called to merge all the changes that occurred across several versions of a script snapshot + * into a single change. i.e. if a user keeps making successive edits to a script we will + * have a text change from V1 to V2, V2 to V3, ..., Vn. + * + * This function will then merge those changes into a single change range valid between V1 and + * Vn. + */ + public static collapseChangesAcrossMultipleVersions(changes: TextChangeRange[]): TextChangeRange { + if (changes.length === 0) { + return TextChangeRange.unchanged; + } + + if (changes.length === 1) { + return changes[0]; + } + + // We change from talking about { { oldStart, oldLength }, newLength } to { oldStart, oldEnd, newEnd } + // as it makes things much easier to reason about. + var change0 = changes[0]; + + var oldStartN = change0.span().start(); + var oldEndN = change0.span().end(); + var newEndN = oldStartN + change0.newLength(); + + for (var i = 1; i < changes.length; i++) { + var nextChange = changes[i]; + + // Consider the following case: + // i.e. two edits. The first represents the text change range { { 10, 50 }, 30 }. i.e. The span starting + // at 10, with length 50 is reduced to length 30. The second represents the text change range { { 30, 30 }, 40 }. + // i.e. the span starting at 30 with length 30 is increased to length 40. + // + // 0 10 20 30 40 50 60 70 80 90 100 + // ------------------------------------------------------------------------------------------------------- + // | / + // | /---- + // T1 | /---- + // | /---- + // | /---- + // ------------------------------------------------------------------------------------------------------- + // | \ + // | \ + // T2 | \ + // | \ + // | \ + // ------------------------------------------------------------------------------------------------------- + // + // Merging these turns out to not be too difficult. First, determining the new start of the change is trivial + // it's just the min of the old and new starts. i.e.: + // + // 0 10 20 30 40 50 60 70 80 90 100 + // ------------------------------------------------------------*------------------------------------------ + // | / + // | /---- + // T1 | /---- + // | /---- + // | /---- + // ----------------------------------------$-------------------$------------------------------------------ + // . | \ + // . | \ + // T2 . | \ + // . | \ + // . | \ + // ----------------------------------------------------------------------*-------------------------------- + // + // (Note the dots represent the newly inferrred start. + // Determining the new and old end is also pretty simple. Basically it boils down to paying attention to the + // absolute positions at the asterixes, and the relative change between the dollar signs. Basically, we see + // which if the two $'s precedes the other, and we move that one forward until they line up. in this case that + // means: + // + // 0 10 20 30 40 50 60 70 80 90 100 + // --------------------------------------------------------------------------------*---------------------- + // | / + // | /---- + // T1 | /---- + // | /---- + // | /---- + // ------------------------------------------------------------$------------------------------------------ + // . | \ + // . | \ + // T2 . | \ + // . | \ + // . | \ + // ----------------------------------------------------------------------*-------------------------------- + // + // In other words (in this case), we're recognizing that the second edit happened after where the first edit + // ended with a delta of 20 characters (60 - 40). Thus, if we go back in time to where the first edit started + // that's the same as if we started at char 80 instead of 60. + // + // As it so happens, the same logic applies if the second edit precedes the first edit. In that case rahter + // than pusing the first edit forward to match the second, we'll push the second edit forward to match the + // first. + // + // In this case that means we have { oldStart: 10, oldEnd: 80, newEnd: 70 } or, in TextChangeRange + // semantics: { { start: 10, length: 70 }, newLength: 60 } + // + // The math then works out as follows. + // If we have { oldStart1, oldEnd1, newEnd1 } and { oldStart2, oldEnd2, newEnd2 } then we can compute the + // final result like so: + // + // { + // oldStart3: Min(oldStart1, oldStart2), + // oldEnd3 : Max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)), + // newEnd3 : Max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)) + // } + + var oldStart1 = oldStartN; + var oldEnd1 = oldEndN; + var newEnd1 = newEndN; + + var oldStart2 = nextChange.span().start(); + var oldEnd2 = nextChange.span().end(); + var newEnd2 = oldStart2 + nextChange.newLength(); + + oldStartN = Math.min(oldStart1, oldStart2); + oldEndN = Math.max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)); + newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)); + } + + return new TextChangeRange(TextSpan.fromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); + } + } +} \ No newline at end of file diff --git a/tests/cases/fourslash/formattingInMultilineComments.ts b/tests/cases/fourslash/formattingInMultilineComments.ts new file mode 100644 index 0000000000..7c58aa957a --- /dev/null +++ b/tests/cases/fourslash/formattingInMultilineComments.ts @@ -0,0 +1,14 @@ +/// + +////var x = function() { +//// if (true) { +//// /*1*/} else {/*2*/ +////} +//// +////// newline at the end of the file + +goTo.marker("2"); +edit.insertLine(""); +goTo.marker("1"); +// else formating should not be affected +verify.currentLineContentIs(' } else {'); diff --git a/tests/cases/unittests/services/documentRegistry.ts b/tests/cases/unittests/services/documentRegistry.ts index 8850314971..044aea0095 100644 --- a/tests/cases/unittests/services/documentRegistry.ts +++ b/tests/cases/unittests/services/documentRegistry.ts @@ -5,8 +5,8 @@ describe("DocumentRegistry", () => { var documentRegistry = ts.createDocumentRegistry(); var defaultCompilerOptions = ts.getDefaultCompilerOptions(); - var f1 = documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, TypeScript.ScriptSnapshot.fromString("var x = 1;"), "1", false); - var f2 = documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, TypeScript.ScriptSnapshot.fromString("var x = 1;"), "1", false); + var f1 = documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), "1", false); + var f2 = documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), "1", false); assert(f1 === f2, "DocumentRegistry should return the same document for the same name"); }); @@ -17,21 +17,21 @@ describe("DocumentRegistry", () => { // change compilation setting that doesn't affect parsing - should have the same document compilerOptions.declaration = true; - var f1 = documentRegistry.acquireDocument("file1.ts", compilerOptions, TypeScript.ScriptSnapshot.fromString("var x = 1;"), "1", false); + var f1 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), "1", false); compilerOptions.declaration = false; - var f2 = documentRegistry.acquireDocument("file1.ts", compilerOptions, TypeScript.ScriptSnapshot.fromString("var x = 1;"), "1", false); + var f2 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), "1", false); assert(f1 === f2, "Expected to have the same document instance"); // change value of compilation setting that is used during production of AST - new document is required compilerOptions.target = ts.ScriptTarget.ES3; - var f3 = documentRegistry.acquireDocument("file1.ts", compilerOptions, TypeScript.ScriptSnapshot.fromString("var x = 1;"), "1", false); + var f3 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), "1", false); assert(f1 !== f3, "Changed target: Expected to have different instances of document"); compilerOptions.module = ts.ModuleKind.CommonJS; - var f4 = documentRegistry.acquireDocument("file1.ts", compilerOptions, TypeScript.ScriptSnapshot.fromString("var x = 1;"), "1", false); + var f4 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), "1", false); assert(f1 !== f4, "Changed module: Expected to have different instances of document"); });