From d8fb8a967f2d8fab7f46bc379947c67446ff2a4b Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 2 Feb 2015 20:06:33 -0800 Subject: [PATCH 01/32] Remove unused NonCachingDocumentRegistry --- src/harness/harnessLanguageService.ts | 28 --------------------------- 1 file changed, 28 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 1d442822866..47fe720f094 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -94,34 +94,6 @@ module Harness.LanguageService { } } - export class NonCachingDocumentRegistry implements ts.DocumentRegistry { - public static Instance: ts.DocumentRegistry = new NonCachingDocumentRegistry(); - - public acquireDocument( - fileName: string, - compilationSettings: ts.CompilerOptions, - scriptSnapshot: ts.IScriptSnapshot, - version: string): ts.SourceFile { - var sourceFile = ts.createSourceFile(fileName, scriptSnapshot.getText(0, scriptSnapshot.getLength()), compilationSettings.target); - sourceFile.version = version; - return sourceFile; - } - - public updateDocument( - document: ts.SourceFile, - fileName: string, - compilationSettings: ts.CompilerOptions, - scriptSnapshot: ts.IScriptSnapshot, - version: string, - textChangeRange: ts.TextChangeRange - ): ts.SourceFile { - return ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, textChangeRange); - } - - public releaseDocument(fileName: string, compilationSettings: ts.CompilerOptions): void { - // no op since this class doesn't cache anything - } - } export class TypeScriptLS implements ts.LanguageServiceShimHost { private ls: ts.LanguageServiceShim = null; From f56e7ea743277f7ae6588c98fc44cd0297d136a8 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 2 Feb 2015 20:23:54 -0800 Subject: [PATCH 02/32] Add a new scriptSnapshot and use the shim as a wrapper --- src/harness/harnessLanguageService.ts | 37 +++++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 47fe720f094..6eab17957e9 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -1,5 +1,6 @@ /// /// +/// module Harness.LanguageService { export class ScriptInfo { @@ -54,15 +55,10 @@ module Harness.LanguageService { } } - class ScriptSnapshotShim implements ts.ScriptSnapshotShim { - private lineMap: number[] = null; - private textSnapshot: string; - private version: number; - - constructor(private scriptInfo: ScriptInfo) { - this.textSnapshot = scriptInfo.content; - this.version = scriptInfo.version; - } + class ScriptSnapshot implements ts.IScriptSnapshot { + public textSnapshot: string; public version: number; + constructor(public scriptInfo: ScriptInfo) { + this.textSnapshot = scriptInfo.content; this.version = scriptInfo.version; } public getText(start: number, end: number): string { return this.textSnapshot.substring(start, end); @@ -72,9 +68,28 @@ module Harness.LanguageService { return this.textSnapshot.length; } + public getChangeRange(oldScript: ts.IScriptSnapshot): ts.TextChangeRange { + var oldShim = oldScript; + return this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version); + } + } + + class ScriptSnapshotShim implements ts.ScriptSnapshotShim { + constructor(public scriptSnapshot: ScriptSnapshot) { + } + + public getText(start: number, end: number): string { + return this.scriptSnapshot.getText(start, end); + } + + public getLength(): number { + return this.scriptSnapshot.getLength(); + } + public getChangeRange(oldScript: ts.ScriptSnapshotShim): string { var oldShim = oldScript; - var range = this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version); + + var range = this.scriptSnapshot.getChangeRange(oldShim.scriptSnapshot); if (range === null) { return null; } @@ -194,7 +209,7 @@ module Harness.LanguageService { public getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim { if (this.contains(fileName)) { - return new ScriptSnapshotShim(this.getScriptInfo(fileName)); + return new ScriptSnapshotShim(new ScriptSnapshot(this.getScriptInfo(fileName))); } return undefined; } From c3bf36e78300e46424fdee5697b1485a108f4ad9 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 Feb 2015 12:40:37 -0800 Subject: [PATCH 03/32] Add missing definition to shims --- src/services/shims.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/shims.ts b/src/services/shims.ts index c9f33914e09..e917323efe7 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -89,6 +89,7 @@ module ts { getCompilerOptionsDiagnostics(): string; getSyntacticClassifications(fileName: string, start: number, length: number): string; + getSemanticClassifications(fileName: string, start: number, length: number): string; getCompletionsAtPosition(fileName: string, position: number): string; getCompletionEntryDetails(fileName: string, position: number, entryName: string): string; From 7a4a8107dd5d934acd743ef7b75494bf40ebf229 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 Feb 2015 12:41:12 -0800 Subject: [PATCH 04/32] Add reference to base runner --- src/harness/harness.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 79defabadeb..52884744535 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -19,6 +19,7 @@ /// /// /// +/// declare var require: any; declare var process: any; From 4c06838d608c831921c274632db2112a2bec74a6 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 Feb 2015 17:28:16 -0800 Subject: [PATCH 05/32] Add new Adaptor layer on top of Harness Language Service --- src/harness/harnessLanguageService.ts | 335 +++++++++++++++++++++++++- 1 file changed, 329 insertions(+), 6 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6eab17957e9..9316ef813c6 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -75,7 +75,7 @@ module Harness.LanguageService { } class ScriptSnapshotShim implements ts.ScriptSnapshotShim { - constructor(public scriptSnapshot: ScriptSnapshot) { + constructor(public scriptSnapshot: ts.IScriptSnapshot) { } public getText(start: number, end: number): string { @@ -109,6 +109,334 @@ module Harness.LanguageService { } } + export interface LanugageServiceAdaptor { + getHost(): LanguageServiceAdaptorHost; + getLanguageService(): ts.LanguageService; + getClassifier(): ts.Classifier; + getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo; + } + + class LanguageServiceHostBase { + protected fileNameToScript: ts.Map = {}; + + constructor(protected cancellationToken: ts.CancellationToken = CancellationToken.None, + protected settings = ts.getDefaultCompilerOptions()) { + + } + + public getFilenames(): string[] { + var fileNames: string[] = []; + ts.forEachKey(this.fileNameToScript,(fileName) => { fileNames.push(fileName); }); + return fileNames; + } + + public getScriptInfo(fileName: string): ScriptInfo { + return ts.lookUp(this.fileNameToScript, fileName); + } + + public addScript(fileName: string, content: string): void { + this.fileNameToScript[fileName] = new ScriptInfo(fileName, content); + } + + public updateScript(fileName: string, content: string) { + var script = this.getScriptInfo(fileName); + if (script !== null) { + script.updateContent(content); + return; + } + + this.addScript(fileName, content); + } + + public editScript(fileName: string, minChar: number, limChar: number, newText: string) { + var script = this.getScriptInfo(fileName); + if (script !== null) { + script.editContent(minChar, limChar, newText); + return; + } + + throw new Error("No script with name '" + fileName + "'"); + } + + /** + * @param line 1 based index + * @param col 1 based index + */ + public lineColToPosition(fileName: string, line: number, col: number): number { + var script: ScriptInfo = this.fileNameToScript[fileName]; + assert.isNotNull(script); + assert.isTrue(line >= 1); + assert.isTrue(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): ts.LineAndCharacter { + var script: ScriptInfo = this.fileNameToScript[fileName]; + assert.isNotNull(script); + + var result = ts.getLineAndCharacterOfPosition(script.lineMap, position); + + assert.isTrue(result.line >= 1); + assert.isTrue(result.character >= 1); + return { line: result.line - 1, character: result.character - 1 }; + } + } + + export interface LanguageServiceAdaptorHost extends LanguageServiceHostBase { + } + + /// Native adabtor + class NativeLanguageServiceHost extends LanguageServiceHostBase implements ts.LanguageServiceHost { + getCompilationSettings(): ts.CompilerOptions { return this.settings; } + getCancellationToken(): ts.CancellationToken { return this.cancellationToken; } + getCurrentDirectory(): string { return ""; } + getDefaultLibFilename(): string { return ""; } + getScriptFileNames(): string[] { return this.getFilenames(); } + getScriptSnapshot(filename: string): ts.IScriptSnapshot { + var script = this.getScriptInfo(filename); + return script ? new ScriptSnapshot(script) : undefined; + } + getScriptVersion(filename: string): string { + var script = this.getScriptInfo(filename); + return script ? script.version.toString() : undefined; + } + log(s: string): void { } + trace(s: string): void { } + error(s: string): void { } + } + + export class NativeLanugageServiceAdaptor implements LanugageServiceAdaptor { + private host: NativeLanguageServiceHost; + constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + this.host = new NativeLanguageServiceHost(cancellationToken, options); + } + getHost() { return this.host; } + getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); } + getClassifier(): ts.Classifier { return ts.createClassifier(); } + getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents); } + } + + /// Shim adabtor + class ShimLanguageServiceHost extends LanguageServiceHostBase implements ts.LanguageServiceShimHost { + private nativeHost: NativeLanguageServiceHost; + constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + super(cancellationToken, options); + this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); + } + + getFilenames(): string[] { return this.nativeHost.getFilenames(); } + getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); } + addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); } + updateScript(fileName: string, content: string): void { return this.nativeHost.updateScript(fileName, content); } + editScript(fileName: string, minChar: number, limChar: number, newText: string): void { this.nativeHost.editScript(fileName, minChar, limChar, newText); } + lineColToPosition(fileName: string, line: number, col: number): number { return this.nativeHost.lineColToPosition(fileName, line, col); } + positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToZeroBasedLineCol(fileName, position); } + + getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); } + getCancellationToken(): ts.CancellationToken { return this.nativeHost.getCancellationToken(); } + getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); } + getDefaultLibFilename(): string { return this.nativeHost.getDefaultLibFilename(); } + getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); } + getScriptSnapshot(filename: string): ts.ScriptSnapshotShim { + var nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(filename); + return nativeScriptSnapshot && new ScriptSnapshotShim(nativeScriptSnapshot); + } + getScriptVersion(filename: string): string { return this.nativeHost.getScriptVersion(filename); } + getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } + log(s: string): void { this.nativeHost.log(s); } + trace(s: string): void { this.nativeHost.trace(s); } + error(s: string): void { this.nativeHost.error(s); } + } + + class ClassifierShimProxy implements ts.Classifier { + constructor(private shim: ts.ClassifierShim) { } + getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult { + var result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split('\n'); + var entries: ts.ClassificationInfo[] = []; + var i = 0; + var position = 0; + + for (; i < result.length - 1; i += 2) { + var t = entries[i / 2] = { + length: parseInt(result[i]), + classification: parseInt(result[i + 1]) + }; + + assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length); + position += t.length; + } + var finalLexState = parseInt(result[result.length - 1]); + + assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position); + + return { + finalLexState, + entries + }; + } + } + + function unwrappJSONCallResult(result: string): any { + var parsedResult = JSON.parse(result); + if (parsedResult.error) { + throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error)); + } + else if (parsedResult.canceled) { + throw new ts.OperationCanceledException(); + } + return parsedResult.result; + } + + class LanguageServiceShimProxy implements ts.LanguageService { + constructor(private shim: ts.LanguageServiceShim) { } + private unwrappJSONCallResult(result: string): any { + var parsedResult = JSON.parse(result); + if (parsedResult.error) { + throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error)); + } + return parsedResult.result; + } + cleanupSemanticCache(): void { + this.shim.cleanupSemanticCache(); + } + getSyntacticDiagnostics(fileName: string): ts.Diagnostic[] { + return unwrappJSONCallResult(this.shim.getSyntacticDiagnostics(fileName)); + } + getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { + return unwrappJSONCallResult(this.shim.getSemanticDiagnostics(fileName)); + } + getCompilerOptionsDiagnostics(): ts.Diagnostic[] { + return unwrappJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); + } + getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { + return unwrappJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length)); + } + getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { + return unwrappJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length)); + } + getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo { + return unwrappJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position)); + } + getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails { + return unwrappJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName)); + } + getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo { + return unwrappJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position)); + } + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan { + return unwrappJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos)); + } + getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan { + return unwrappJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position)); + } + getSignatureHelpItems(fileName: string, position: number): ts.SignatureHelpItems { + return unwrappJSONCallResult(this.shim.getSignatureHelpItems(fileName, position)); + } + getRenameInfo(fileName: string, position: number): ts.RenameInfo { + return unwrappJSONCallResult(this.shim.getRenameInfo(fileName, position)); + } + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ts.RenameLocation[] { + return unwrappJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments)); + } + getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { + return unwrappJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position)); + } + getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { + return unwrappJSONCallResult(this.shim.getReferencesAtPosition(fileName, position)); + } + getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { + return unwrappJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position)); + } + getNavigateToItems(searchValue: string): ts.NavigateToItem[] { + return unwrappJSONCallResult(this.shim.getNavigateToItems(searchValue)); + } + getNavigationBarItems(fileName: string): ts.NavigationBarItem[] { + return unwrappJSONCallResult(this.shim.getNavigationBarItems(fileName)); + } + getOutliningSpans(fileName: string): ts.OutliningSpan[] { + return unwrappJSONCallResult(this.shim.getOutliningSpans(fileName)); + } + getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] { + return unwrappJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors))); + } + getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] { + return unwrappJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position)); + } + getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number { + return unwrappJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options))); + } + getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrappJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options))); + } + getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrappJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options))); + } + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrappJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options))); + } + getEmitOutput(fileName: string): ts.EmitOutput { + return unwrappJSONCallResult(this.shim.getEmitOutput(fileName)); + } + getProgram(): ts.Program { + throw new Error("Program can not be marshalled accross the shim layer."); + } + getSourceFile(filename: string): ts.SourceFile { + throw new Error("SourceFile can not be marshalled accross the shim layer."); + } + dispose(): void { this.shim.dispose({}); } + } + + export class ShimLanugageServiceAdaptor implements LanugageServiceAdaptor { + private host: ShimLanguageServiceHost; + private factory: ts.TypeScriptServicesFactory; + constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { + this.host = new ShimLanguageServiceHost(cancellationToken, options); + this.factory = new TypeScript.Services.TypeScriptServicesFactory(); + } + getHost() { return this.host; } + getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); } + getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); } + getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo { + var shimResult: { + referencedFiles: ts.IFileReference[]; + importedFiles: ts.IFileReference[]; + isLibFile: boolean; + }; + + var coreServicesShim = this.factory.createCoreServicesShim(this.host); + shimResult = unwrappJSONCallResult(coreServicesShim.getPreProcessedFileInfo(filename, ts.ScriptSnapshot.fromString(fileContents))); + + var convertResult: ts.PreProcessedFileInfo = { + referencedFiles: [], + importedFiles: [], + isLibFile: shimResult.isLibFile + }; + + ts.forEach(shimResult.referencedFiles, refFile => { + convertResult.referencedFiles.push({ + filename: refFile.path, + pos: refFile.position, + end: refFile.position + refFile.length + }); + }); + + ts.forEach(shimResult.importedFiles, importedFile => { + convertResult.importedFiles.push({ + filename: importedFile.path, + pos: importedFile.position, + end: importedFile.position + importedFile.length + }); + }); + + return convertResult; + } + } + export class TypeScriptLS implements ts.LanguageServiceShimHost { private ls: ts.LanguageServiceShim = null; @@ -129,11 +457,6 @@ module Harness.LanguageService { return "TypeScriptLS"; } - public addFile(fileName: string) { - var code = Harness.IO.readFile(fileName); - this.addScript(fileName, code); - } - private getScriptInfo(fileName: string): ScriptInfo { return ts.lookUp(this.fileNameToScript, fileName); } From 5aca35e35f978e3387e9724492029d52272b7f31 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 Feb 2015 17:28:33 -0800 Subject: [PATCH 06/32] Move unit tests to use the new adaptors --- .../cases/unittests/services/colorization.ts | 91 ++++++------------- 1 file changed, 26 insertions(+), 65 deletions(-) diff --git a/tests/cases/unittests/services/colorization.ts b/tests/cases/unittests/services/colorization.ts index 20038800a3e..b744b65608b 100644 --- a/tests/cases/unittests/services/colorization.ts +++ b/tests/cases/unittests/services/colorization.ts @@ -1,84 +1,47 @@ /// /// -interface Classification { - position: number; - length: number; - class: ts.TokenClass; -} - -interface ClassiferResult { - tuples: Classification[]; - finalEndOfLineState: ts.EndOfLineState; -} - interface ClassificationEntry { value: any; - class: ts.TokenClass; + classification: ts.TokenClass; } describe('Colorization', function () { - var mytypescriptLS = new Harness.LanguageService.TypeScriptLS(); - var myclassifier = mytypescriptLS.getClassifier(); + // Use the shim adaptor to ensure test coverage of the shim layer for the classifier + var languageServiceAdabtor = new Harness.LanguageService.ShimLanugageServiceAdaptor(); + var classifier = languageServiceAdabtor.getClassifier(); - function getLexicalClassifications(code: string, initialEndOfLineState: ts.EndOfLineState = ts.EndOfLineState.Start): ClassiferResult { - var classResult = myclassifier.getClassificationsForLine(code, initialEndOfLineState).split('\n'); - var tuples: Classification[] = []; - var i = 0; - var position = 0; - - for (; i < classResult.length - 1; i += 2) { - var t = tuples[i / 2] = { - position: position, - length: parseInt(classResult[i]), - class: parseInt(classResult[i + 1]) - }; - - assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length); - position += t.length; - } - var finalEndOfLineState = classResult[classResult.length - 1]; - - assert.equal(position, code.length, "Expected cumulative length of all entries to match the length of the source. expected: " + code.length + ", but got: " + position); - - return { - tuples: tuples, - finalEndOfLineState: parseInt(finalEndOfLineState) - }; - } - - function verifyClassification(classification: Classification, expectedLength: number, expectedClass: number) { - assert.isNotNull(classification); - assert.equal(classification.length, expectedLength, "Classification length does not match expected. Expected: " + expectedLength + ", Actual: " + classification.length); - assert.equal(classification.class, expectedClass, "Classification class does not match expected. Expected: " + ts.TokenClass[expectedClass] + ", Actual: " + ts.TokenClass[classification.class]); - } - - function getEntryAtPosistion(result: ClassiferResult, position: number) { - for (var i = 0, n = result.tuples.length; i < n; i++) { - if (result.tuples[i].position === position) return result.tuples[i]; + function getEntryAtPosistion(result: ts.ClassificationResult, position: number) { + var entryPosition = 0; + for (var i = 0, n = result.entries.length; i < n; i++) { + var entry = result.entries[i]; + if (entryPosition === position) { + return entry; + } + entryPosition += entry.length; } return undefined; } - function punctuation(text: string) { return { value: text, class: ts.TokenClass.Punctuation }; } - function keyword(text: string) { return { value: text, class: ts.TokenClass.Keyword }; } - function operator(text: string) { return { value: text, class: ts.TokenClass.Operator }; } - function comment(text: string) { return { value: text, class: ts.TokenClass.Comment }; } - function whitespace(text: string) { return { value: text, class: ts.TokenClass.Whitespace }; } - function identifier(text: string) { return { value: text, class: ts.TokenClass.Identifier }; } - function numberLiteral(text: string) { return { value: text, class: ts.TokenClass.NumberLiteral }; } - function stringLiteral(text: string) { return { value: text, class: ts.TokenClass.StringLiteral }; } - function regExpLiteral(text: string) { return { value: text, class: ts.TokenClass.RegExpLiteral }; } - function finalEndOfLineState(value: number) { return { value: value, class: undefined }; } + function punctuation(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.Punctuation }; } + function keyword(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.Keyword }; } + function operator(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.Operator }; } + function comment(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.Comment }; } + function whitespace(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.Whitespace }; } + function identifier(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.Identifier }; } + function numberLiteral(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.NumberLiteral }; } + function stringLiteral(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.StringLiteral }; } + function regExpLiteral(text: string): ClassificationEntry { return { value: text, classification: ts.TokenClass.RegExpLiteral }; } + function finalEndOfLineState(value: number): ClassificationEntry { return { value: value, classification: undefined }; } function testLexicalClassification(text: string, initialEndOfLineState: ts.EndOfLineState, ...expectedEntries: ClassificationEntry[]): void { - var result = getLexicalClassifications(text, initialEndOfLineState); + var result = classifier.getClassificationsForLine(text, initialEndOfLineState); for (var i = 0, n = expectedEntries.length; i < n; i++) { var expectedEntry = expectedEntries[i]; - if (expectedEntry.class === undefined) { - assert.equal(result.finalEndOfLineState, expectedEntry.value, "final endOfLineState does not match expected."); + if (expectedEntry.classification === undefined) { + assert.equal(result.finalLexState, expectedEntry.value, "final endOfLineState does not match expected."); } else { var actualEntryPosition = text.indexOf(expectedEntry.value); @@ -87,7 +50,7 @@ describe('Colorization', function () { var actualEntry = getEntryAtPosistion(result, actualEntryPosition); assert(actualEntry, "Could not find classification entry for '" + expectedEntry.value + "' at position: " + actualEntryPosition); - assert.equal(actualEntry.class, expectedEntry.class, "Classification class does not match expected. Expected: " + ts.TokenClass[expectedEntry.class] + ", Actual: " + ts.TokenClass[actualEntry.class]); + assert.equal(actualEntry.classification, expectedEntry.classification, "Classification class does not match expected. Expected: " + ts.TokenClass[expectedEntry.classification] + ", Actual: " + ts.TokenClass[actualEntry.classification]); assert.equal(actualEntry.length, expectedEntry.value.length, "Classification length does not match expected. Expected: " + ts.TokenClass[expectedEntry.value.length] + ", Actual: " + ts.TokenClass[actualEntry.length]); } } @@ -320,8 +283,6 @@ describe('Colorization', function () { }); it("LexicallyClassifiesConflictTokens", () => { - debugger; - // Test conflict markers. testLexicalClassification( "class C {\r\n\ From 65e8c00d9af5fa9fd57160a2de6bcd986d06184e Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 Feb 2015 17:29:12 -0800 Subject: [PATCH 07/32] Update fourslash tests to use the new adaptors --- src/harness/fourslash.ts | 131 +++++++++++++---------------- tests/cases/fourslash/fourslash.ts | 4 +- 2 files changed, 62 insertions(+), 73 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index e0b74318e26..b4fba9a4501 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -245,11 +245,9 @@ module FourSlash { export class TestState { // Language service instance - public languageServiceShimHost: Harness.LanguageService.TypeScriptLS; + private languageServiceAdaptorHost: Harness.LanguageService.LanguageServiceAdaptorHost; private languageService: ts.LanguageService; - - // A reference to the language service's compiler state's compiler instance - private compiler: () => { getSyntaxTree(fileName: string): ts.SourceFile }; + private cancellationToken: TestCancellationToken; // The current caret position in the active file public currentCaretPosition = 0; @@ -263,8 +261,6 @@ module FourSlash { public formatCodeOptions: ts.FormatCodeOptions; - public cancellationToken: TestCancellationToken; - private scenarioActions: string[] = []; private taoInvalidReason: string = null; @@ -275,19 +271,20 @@ module FourSlash { private addMatchedInputFile(referenceFilePath: string) { var inputFile = this.inputFiles[referenceFilePath]; if (inputFile && !Harness.isLibraryFile(referenceFilePath)) { - this.languageServiceShimHost.addScript(referenceFilePath, inputFile); + this.languageServiceAdaptorHost.addScript(referenceFilePath, inputFile); } } constructor(public testData: FourSlashData) { - // Initialize the language service with all the scripts + // Create a new Services Adaptor this.cancellationToken = new TestCancellationToken(); - this.languageServiceShimHost = new Harness.LanguageService.TypeScriptLS(this.cancellationToken); + var compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); + var languageServiceAdaptor = new Harness.LanguageService.ShimLanugageServiceAdaptor(this.cancellationToken, compilationOptions); + this.languageServiceAdaptorHost = languageServiceAdaptor.getHost(); + this.languageService = languageServiceAdaptor.getLanguageService(); - var compilationSettings = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); - this.languageServiceShimHost.setCompilationSettings(compilationSettings); - - var startResolveFileRef: FourSlashFile = undefined; + // Initialize the language service with all the scripts + var startResolveFileRef: FourSlashFile; ts.forEach(testData.files, file => { // Create map between fileName and its content for easily looking up when resolveReference flag is specified @@ -302,18 +299,16 @@ module FourSlash { if (startResolveFileRef) { // Add the entry-point file itself into the languageServiceShimHost - this.languageServiceShimHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content); + this.languageServiceAdaptorHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content); - var jsonResolvedResult = JSON.parse(this.languageServiceShimHost.getCoreService().getPreProcessedFileInfo(startResolveFileRef.fileName, - createScriptSnapShot(startResolveFileRef.content))); - var resolvedResult = jsonResolvedResult.result; - var referencedFiles: ts.IFileReference[] = resolvedResult.referencedFiles; - var importedFiles: ts.IFileReference[] = resolvedResult.importedFiles; + var resolvedResult = languageServiceAdaptor.getPreProcessedFileInfo(startResolveFileRef.fileName, startResolveFileRef.content); + var referencedFiles: ts.FileReference[] = resolvedResult.referencedFiles; + var importedFiles: ts.FileReference[] = resolvedResult.importedFiles; // Add triple reference files into language-service host ts.forEach(referencedFiles, referenceFile => { // Fourslash insert tests/cases/fourslash into inputFile.unitName so we will properly append the same base directory to refFile path - var referenceFilePath = "tests/cases/fourslash/" + referenceFile.path; + var referenceFilePath = "tests/cases/fourslash/" + referenceFile.filename; this.addMatchedInputFile(referenceFilePath); }); @@ -321,29 +316,24 @@ module FourSlash { ts.forEach(importedFiles, importedFile => { // Fourslash insert tests/cases/fourslash into inputFile.unitName and import statement doesn't require ".ts" // so convert them before making appropriate comparison - var importedFilePath = "tests/cases/fourslash/" + importedFile.path + ".ts"; + var importedFilePath = "tests/cases/fourslash/" + importedFile.filename + ".ts"; this.addMatchedInputFile(importedFilePath); }); // Check if no-default-lib flag is false and if so add default library if (!resolvedResult.isLibFile) { - this.languageServiceShimHost.addDefaultLibrary(); + this.languageServiceAdaptorHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text); } } else { // resolveReference file-option is not specified then do not resolve any files and include all inputFiles ts.forEachKey(this.inputFiles, fileName => { if (!Harness.isLibraryFile(fileName)) { - this.languageServiceShimHost.addScript(fileName, this.inputFiles[fileName]); + this.languageServiceAdaptorHost.addScript(fileName, this.inputFiles[fileName]); } }); - this.languageServiceShimHost.addDefaultLibrary(); + this.languageServiceAdaptorHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text); } - // Sneak into the language service and get its compiler so we can examine the syntax trees - this.languageService = this.languageServiceShimHost.getLanguageService().languageService; - var compilerState = (this.languageService).compiler; - this.compiler = () => compilerState.compiler; - this.formatCodeOptions = { IndentSize: 4, TabSize: 4, @@ -369,6 +359,11 @@ module FourSlash { this.openFile(0); } + private getFileContent(filename: string): string { + var script = this.languageServiceAdaptorHost.getScriptInfo(this.activeFile.fileName); + return script.content; + } + // Entry points from fourslash.ts public goToMarker(name = '') { var marker = this.getMarkerByName(name); @@ -376,8 +371,8 @@ module FourSlash { this.openFile(marker.fileName); } - var scriptSnapshot = this.languageServiceShimHost.getScriptSnapshot(marker.fileName); - if (marker.position === -1 || marker.position > scriptSnapshot.getLength()) { + var content = this.getFileContent(marker.fileName); + if (marker.position === -1 || marker.position > content.length) { throw new Error('Marker "' + name + '" has been invalidated by unrecoverable edits to the file.'); } this.lastKnownMarker = name; @@ -387,14 +382,14 @@ module FourSlash { public goToPosition(pos: number) { this.currentCaretPosition = pos; - var lineStarts = ts.computeLineStarts(this.getCurrentFileContent()); + var lineStarts = ts.computeLineStarts(this.getFileContent(this.activeFile.fileName)); var lineCharPos = ts.getLineAndCharacterOfPosition(lineStarts, pos); this.scenarioActions.push(''); } public moveCaretRight(count = 1) { this.currentCaretPosition += count; - this.currentCaretPosition = Math.min(this.currentCaretPosition, this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getLength()); + this.currentCaretPosition = Math.min(this.currentCaretPosition, this.getFileContent(this.activeFile.fileName).length); if (count > 0) { this.scenarioActions.push(''); } else { @@ -453,7 +448,7 @@ module FourSlash { private getAllDiagnostics(): ts.Diagnostic[] { var diagnostics: ts.Diagnostic[] = []; - var fileNames = JSON.parse(this.languageServiceShimHost.getScriptFileNames()); + var fileNames = this.languageServiceAdaptorHost.getFilenames(); for (var i = 0, n = fileNames.length; i < n; i++) { diagnostics.push.apply(this.getDiagnostics(fileNames[i])); } @@ -794,7 +789,6 @@ module FourSlash { } } - public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: { start: number; length: number; }, displayParts: ts.SymbolDisplayPart[], documentation: ts.SymbolDisplayPart[]) { @@ -1189,8 +1183,7 @@ module FourSlash { var file = this.testData.files[i]; var active = (this.activeFile === file); Harness.IO.log('=== Script (' + file.fileName + ') ' + (active ? '(active, cursor at |)' : '') + ' ==='); - var snapshot = this.languageServiceShimHost.getScriptSnapshot(file.fileName); - var content = snapshot.getText(0, snapshot.getLength()); + var content = this.getFileContent(file.fileName); if (active) { content = content.substr(0, this.currentCaretPosition) + (makeCaretVisible ? '|' : "") + content.substr(this.currentCaretPosition); } @@ -1224,8 +1217,7 @@ module FourSlash { } public printContext() { - var fileNames: string[] = JSON.parse(this.languageServiceShimHost.getScriptFileNames()); - ts.forEach(fileNames, Harness.IO.log); + ts.forEach(this.languageServiceAdaptorHost.getFilenames(), Harness.IO.log); } public deleteChar(count = 1) { @@ -1238,7 +1230,7 @@ module FourSlash { for (var i = 0; i < count; i++) { // Make the edit - this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); + this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch); if (i % checkCadence === 0) { @@ -1265,7 +1257,7 @@ module FourSlash { public replace(start: number, length: number, text: string) { this.taoInvalidReason = 'replace NYI'; - this.languageServiceShimHost.editScript(this.activeFile.fileName, start, start + length, text); + this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, start, start + length, text); this.updateMarkersForEdit(this.activeFile.fileName, start, start + length, text); this.checkPostEditInvariants(); } @@ -1280,7 +1272,7 @@ module FourSlash { for (var i = 0; i < count; i++) { offset--; // Make the edit - this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); + this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch); if (i % checkCadence === 0) { @@ -1325,7 +1317,7 @@ module FourSlash { for (var i = 0; i < text.length; i++) { // Make the edit var ch = text.charAt(i); - this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset, ch); + this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, offset, offset, ch); this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, offset); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, ch); @@ -1368,7 +1360,7 @@ module FourSlash { var start = this.currentCaretPosition; var offset = this.currentCaretPosition; - this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset, text); + this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, offset, offset, text); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, text); this.checkPostEditInvariants(); offset += text.length; @@ -1396,8 +1388,7 @@ module FourSlash { var incrementalSyntaxDiagnostics = incrementalSourceFile.getSyntacticDiagnostics(); // Check syntactic structure - var snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName); - var content = snapshot.getText(0, snapshot.getLength()); + var content = this.getFileContent(this.activeFile.fileName); var referenceSourceFile = ts.createLanguageServiceSourceFile( this.activeFile.fileName, createScriptSnapShot(content), ts.ScriptTarget.Latest, /*version:*/ "0", /*setNodeParents:*/ false); @@ -1411,7 +1402,7 @@ module FourSlash { // The caret can potentially end up between the \r and \n, which is confusing. If // that happens, move it back one character if (this.currentCaretPosition > 0) { - var ch = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(this.currentCaretPosition - 1, this.currentCaretPosition); + var ch = this.getFileContent(this.activeFile.fileName).substring(this.currentCaretPosition - 1, this.currentCaretPosition); if (ch === '\r') { this.currentCaretPosition--; } @@ -1424,10 +1415,9 @@ module FourSlash { var runningOffset = 0; edits = edits.sort((a, b) => a.span.start - b.span.start); // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters - var snapshot = this.languageServiceShimHost.getScriptSnapshot(fileName); - var oldContent = snapshot.getText(0, snapshot.getLength()); + var oldContent = this.getFileContent(this.activeFile.fileName); for (var j = 0; j < edits.length; j++) { - this.languageServiceShimHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText); + this.languageServiceAdaptorHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText); this.updateMarkersForEdit(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText); var change = (edits[j].span.start - ts.textSpanEnd(edits[j].span)) + edits[j].newText.length; runningOffset += change; @@ -1436,8 +1426,7 @@ module FourSlash { } if (isFormattingEdit) { - snapshot = this.languageServiceShimHost.getScriptSnapshot(fileName); - var newContent = snapshot.getText(0, snapshot.getLength()); + var newContent = this.getFileContent(fileName); if (newContent.replace(/\s/g, '') !== oldContent.replace(/\s/g, '')) { this.raiseError('Formatting operation destroyed non-whitespace content'); @@ -1484,7 +1473,7 @@ module FourSlash { } public goToEOF() { - var len = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getLength(); + var len = this.getFileContent(this.activeFile.fileName).length; this.goToPosition(len); } @@ -1605,7 +1594,7 @@ module FourSlash { public verifyCurrentFileContent(text: string) { this.taoInvalidReason = 'verifyCurrentFileContent NYI'; - var actual = this.getCurrentFileContent(); + var actual = this.getFileContent(this.activeFile.fileName); var replaceNewlines = (str: string) => str.replace(/\r\n/g, "\n"); if (replaceNewlines(actual) !== replaceNewlines(text)) { throw new Error('verifyCurrentFileContent\n' + @@ -1617,7 +1606,7 @@ module FourSlash { public verifyTextAtCaretIs(text: string) { this.taoInvalidReason = 'verifyCurrentFileContent NYI'; - var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(this.currentCaretPosition, this.currentCaretPosition + text.length); + var actual = this.getFileContent(this.activeFile.fileName).substring(this.currentCaretPosition, this.currentCaretPosition + text.length); if (actual !== text) { throw new Error('verifyTextAtCaretIs\n' + '\tExpected: "' + text + '"\n' + @@ -1635,7 +1624,7 @@ module FourSlash { '\t Actual: undefined'); } - var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.start, ts.textSpanEnd(span)); + var actual = this.getFileContent(this.activeFile.fileName).substring(span.start, ts.textSpanEnd(span)); if (actual !== text) { this.raiseError('verifyCurrentNameOrDottedNameSpanText\n' + '\tExpected: "' + text + '"\n' + @@ -1722,7 +1711,7 @@ module FourSlash { } public verifySyntacticClassifications(expected: { classificationType: string; text: string }[]) { - var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName, + var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName, ts.createTextSpan(0, this.activeFile.content.length)); this.verifyClassifications(expected, actual); @@ -1812,8 +1801,7 @@ module FourSlash { for (var i = 0; i < this.testData.files.length; i++) { var file = this.testData.files[i]; - var snapshot = this.languageServiceShimHost.getScriptSnapshot(file.fileName); - var content = snapshot.getText(0, snapshot.getLength()); + var content = this.getFileContent(file.fileName); referenceLanguageServiceShimHost.addScript(this.testData.files[i].fileName, content); } @@ -1847,8 +1835,7 @@ module FourSlash { var failure = anyFailed || (refName !== pullName); if (failure) { - snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName); - content = snapshot.getText(0, snapshot.getLength()); + content = this.getFileContent(this.activeFile.fileName); var textAtPosition = content.substr(positions[i], 10); var positionDescription = 'Position ' + positions[i] + ' ("' + textAtPosition + '"...)'; @@ -2047,12 +2034,11 @@ module FourSlash { // The current caret position (in line/col terms) var line = this.getCurrentCaretFilePosition().line; // The line/col of the start of this line - var pos = this.languageServiceShimHost.lineColToPosition(this.activeFile.fileName, line, 1); + var pos = this.languageServiceAdaptorHost.lineColToPosition(this.activeFile.fileName, line, 1); // The index of the current file // The text from the start of the line to the end of the file - var snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName); - var text = snapshot.getText(pos, snapshot.getLength()); + var text = this.getFileContent(this.activeFile.fileName).substring(pos); // Truncate to the first newline var newlinePos = text.indexOf('\n'); @@ -2067,13 +2053,8 @@ module FourSlash { } } - private getCurrentFileContent() { - var snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName); - return snapshot.getText(0, snapshot.getLength()); - } - private getCurrentCaretFilePosition() { - var result = this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition); + var result = this.languageServiceAdaptorHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition); if (result.line >= 0) { result.line++; } @@ -2158,7 +2139,7 @@ module FourSlash { } private getLineColStringAtPosition(position: number) { - var pos = this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, position); + var pos = this.languageServiceAdaptorHost.positionToZeroBasedLineCol(this.activeFile.fileName, position); return 'line ' + (pos.line + 1) + ', col ' + pos.character; } @@ -2184,6 +2165,14 @@ module FourSlash { originalName: '' }; } + + public setCancelled(numberOfCalls: number): void { + this.cancellationToken.setCancelled(numberOfCalls) + } + + public resetCancelled(): void { + this.cancellationToken.resetCancelled(); + } } // TOOD: should these just use the Harness's stdout/stderr? diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 5b88b15f9fa..77b5add7a11 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -561,11 +561,11 @@ module FourSlashInterface { export class cancellation { public resetCancelled() { - FourSlash.currentTestState.cancellationToken.resetCancelled(); + FourSlash.currentTestState.resetCancelled(); } public setCancelled(numberOfCalls: number = 0) { - FourSlash.currentTestState.cancellationToken.setCancelled(numberOfCalls); + FourSlash.currentTestState.setCancelled(numberOfCalls); } } From 42457636b6c01b27bc7851bb478e014bff241a79 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 4 Feb 2015 11:07:55 -0800 Subject: [PATCH 08/32] Remove the implementation of TypeScriptLS --- src/harness/fourslash.ts | 63 +--- src/harness/harnessLanguageService.ts | 278 ------------------ .../cases/fourslash/addMethodToInterface1.ts | 1 - .../cases/fourslash/arrayConcatTypeCheck0.ts | 1 - .../cases/fourslash/arrayConcatTypeCheck1.ts | 1 - .../arrayTypeMismatchIncrementalTypeCheck.ts | 31 -- .../cases/fourslash/emptyTypeArgumentList.ts | 7 - tests/cases/fourslash/fourslash.ts | 12 - .../cases/fourslash/getTypeAtModuleExtends.ts | 12 - tests/cases/fourslash/numberAssignement0.ts | 8 - .../pullFullDiffTypeParameterExtends0.ts | 29 -- .../restParametersTypeValidation1.ts | 12 - .../typeCheckAfterAddingGenericParameter.ts | 2 - tests/cases/fourslash/typeCheckExpression0.ts | 27 -- .../typeCheckGenericTypeLiteralArgument.ts | 12 - .../fourslash/typeCheckIndexSignature.ts | 11 - .../fourslash/typeCheckIndexerAccess1.ts | 40 --- 17 files changed, 1 insertion(+), 546 deletions(-) delete mode 100644 tests/cases/fourslash/arrayTypeMismatchIncrementalTypeCheck.ts delete mode 100644 tests/cases/fourslash/emptyTypeArgumentList.ts delete mode 100644 tests/cases/fourslash/getTypeAtModuleExtends.ts delete mode 100644 tests/cases/fourslash/numberAssignement0.ts delete mode 100644 tests/cases/fourslash/pullFullDiffTypeParameterExtends0.ts delete mode 100644 tests/cases/fourslash/restParametersTypeValidation1.ts delete mode 100644 tests/cases/fourslash/typeCheckExpression0.ts delete mode 100644 tests/cases/fourslash/typeCheckGenericTypeLiteralArgument.ts delete mode 100644 tests/cases/fourslash/typeCheckIndexSignature.ts delete mode 100644 tests/cases/fourslash/typeCheckIndexerAccess1.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index b4fba9a4501..5b46ec32a1c 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -279,7 +279,7 @@ module FourSlash { // Create a new Services Adaptor this.cancellationToken = new TestCancellationToken(); var compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); - var languageServiceAdaptor = new Harness.LanguageService.ShimLanugageServiceAdaptor(this.cancellationToken, compilationOptions); + var languageServiceAdaptor = new Harness.LanguageService.NativeLanugageServiceAdaptor(this.cancellationToken, compilationOptions); this.languageServiceAdaptorHost = languageServiceAdaptor.getHost(); this.languageService = languageServiceAdaptor.getLanguageService(); @@ -1787,67 +1787,6 @@ module FourSlash { } } - public verifyTypesAgainstFullCheckAtPositions(positions: number[]) { - this.taoInvalidReason = 'verifyTypesAgainstFullCheckAtPositions impossible'; - - // Create a from-scratch LS to check against - var referenceLanguageServiceShimHost = new Harness.LanguageService.TypeScriptLS(); - var referenceLanguageServiceShim = referenceLanguageServiceShimHost.getLanguageService(); - var referenceLanguageService = referenceLanguageServiceShim.languageService; - - // Add lib.d.ts to the reference language service - referenceLanguageServiceShimHost.addDefaultLibrary(); - - for (var i = 0; i < this.testData.files.length; i++) { - var file = this.testData.files[i]; - - var content = this.getFileContent(file.fileName); - referenceLanguageServiceShimHost.addScript(this.testData.files[i].fileName, content); - } - - for (i = 0; i < positions.length; i++) { - var nameOf = (type: ts.QuickInfo) => type ? ts.displayPartsToString(type.displayParts) : '(none)'; - - var pullName: string, refName: string; - var anyFailed = false; - - var errMsg = ''; - - try { - var pullType = this.languageService.getQuickInfoAtPosition(this.activeFile.fileName, positions[i]); - pullName = nameOf(pullType); - } catch (err1) { - errMsg = 'Failed to get pull type check. Exception: ' + err1 + '\r\n'; - if (err1.stack) errMsg = errMsg + err1.stack; - pullName = '(failed)'; - anyFailed = true; - } - - try { - var referenceType = referenceLanguageService.getQuickInfoAtPosition(this.activeFile.fileName, positions[i]); - refName = nameOf(referenceType); - } catch (err2) { - errMsg = 'Failed to get full type check. Exception: ' + err2 + '\r\n'; - if (err2.stack) errMsg = errMsg + err2.stack; - refName = '(failed)'; - anyFailed = true; - } - - var failure = anyFailed || (refName !== pullName); - if (failure) { - content = this.getFileContent(this.activeFile.fileName); - var textAtPosition = content.substr(positions[i], 10); - var positionDescription = 'Position ' + positions[i] + ' ("' + textAtPosition + '"...)'; - - if (anyFailed) { - throw new Error('Exception thrown in language service for ' + positionDescription + '\r\n' + errMsg); - } else if (refName !== pullName) { - throw new Error('Pull/Full disagreement failed at ' + positionDescription + ' - expected full typecheck type "' + refName + '" to equal pull type "' + pullName + '".'); - } - } - } - } - /* Check number of navigationItems which match both searchValue and matchKind. Report an error if expected value and actual value do not match. diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 9316ef813c6..dcf211d3306 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -436,283 +436,5 @@ module Harness.LanguageService { return convertResult; } } - - export class TypeScriptLS implements ts.LanguageServiceShimHost { - private ls: ts.LanguageServiceShim = null; - - private fileNameToScript: ts.Map = {}; - private settings: ts.CompilerOptions = {}; - - constructor(private cancellationToken: ts.CancellationToken = CancellationToken.None) { - } - - public trace(s: string) { - } - - public addDefaultLibrary() { - this.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text); - } - - public getHostIdentifier(): string { - return "TypeScriptLS"; - } - - private getScriptInfo(fileName: string): ScriptInfo { - return ts.lookUp(this.fileNameToScript, fileName); - } - - public addScript(fileName: string, content: string) { - this.fileNameToScript[fileName] = new ScriptInfo(fileName, content); - } - - private contains(fileName: string): boolean { - return ts.hasProperty(this.fileNameToScript, fileName); - } - - public updateScript(fileName: string, content: string) { - var script = this.getScriptInfo(fileName); - if (script !== null) { - script.updateContent(content); - return; - } - - this.addScript(fileName, content); - } - - public editScript(fileName: string, minChar: number, limChar: number, newText: string) { - var script = this.getScriptInfo(fileName); - if (script !== null) { - script.editContent(minChar, limChar, newText); - return; - } - - throw new Error("No script with name '" + fileName + "'"); - } - - ////////////////////////////////////////////////////////////////////// - // ILogger implementation - // - public information(): boolean { return false; } - public debug(): boolean { return true; } - public warning(): boolean { return true; } - public error(): boolean { return true; } - public fatal(): boolean { return true; } - - public log(s: string): void { - // For debugging... - //TypeScript.Environment.standardOut.WriteLine("TypeScriptLS:" + s); - } - - ////////////////////////////////////////////////////////////////////// - // LanguageServiceShimHost implementation - // - - /// Returns json for Tools.CompilationSettings - public getCompilationSettings(): string { - return JSON.stringify(this.settings); - } - - public getCancellationToken(): ts.CancellationToken { - return this.cancellationToken; - } - - public getCurrentDirectory(): string { - return ""; - } - - public getDefaultLibFilename(): string { - return ""; - } - - public getScriptFileNames(): string { - var fileNames: string[] = []; - ts.forEachKey(this.fileNameToScript,(fileName) => { fileNames.push(fileName); }); - return JSON.stringify(fileNames); - } - - public getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim { - if (this.contains(fileName)) { - return new ScriptSnapshotShim(new ScriptSnapshot(this.getScriptInfo(fileName))); - } - return undefined; - } - - public getScriptVersion(fileName: string): string { - if (this.contains(fileName)) { - return this.getScriptInfo(fileName).version.toString(); - } - return undefined; - } - - public getLocalizedDiagnosticMessages(): string { - return JSON.stringify({}); - } - - /** Return a new instance of the language service shim, up-to-date wrt to typecheck. - * To access the non-shim (i.e. actual) language service, use the "ls.languageService" property. - */ - public getLanguageService(): ts.LanguageServiceShim { - this.ls = new TypeScript.Services.TypeScriptServicesFactory().createLanguageServiceShim(this); - return this.ls; - } - - public setCompilationSettings(settings: ts.CompilerOptions) { - for (var key in settings) { - if (settings.hasOwnProperty(key)) { - this.settings[key] = settings[key]; - } - } - } - - /** Return a new instance of the classifier service shim */ - public getClassifier(): ts.ClassifierShim { - return new TypeScript.Services.TypeScriptServicesFactory().createClassifierShim(this); - } - - public getCoreService(): ts.CoreServicesShim { - return new TypeScript.Services.TypeScriptServicesFactory().createCoreServicesShim(this); - } - - /** Parse file given its source text */ - public parseSourceText(fileName: string, sourceText: ts.IScriptSnapshot): ts.SourceFile { - var result = ts.createSourceFile(fileName, sourceText.getText(0, sourceText.getLength()), ts.ScriptTarget.Latest); - result.version = "1"; - return result; - } - - /** Parse a file on disk given its fileName */ - public parseFile(fileName: string) { - var sourceText = ts.ScriptSnapshot.fromString(Harness.IO.readFile(fileName)); - return this.parseSourceText(fileName, sourceText); - } - - /** - * @param line 1 based index - * @param col 1 based index - */ - public lineColToPosition(fileName: string, line: number, col: number): number { - var script: ScriptInfo = this.fileNameToScript[fileName]; - assert.isNotNull(script); - assert.isTrue(line >= 1); - assert.isTrue(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): ts.LineAndCharacter { - var script: ScriptInfo = this.fileNameToScript[fileName]; - assert.isNotNull(script); - - var result = ts.getLineAndCharacterOfPosition(script.lineMap, position); - - 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 */ - public checkEdits(sourceFileName: string, baselineFileName: string, edits: ts.TextChange[]) { - var script = Harness.IO.readFile(sourceFileName); - var formattedScript = this.applyEdits(script, edits); - var baseline = Harness.IO.readFile(baselineFileName); - - function noDiff(text1: string, text2: string) { - text1 = text1.replace(/^\s+|\s+$/g, "").replace(/\r\n?/g, "\n"); - text2 = text2.replace(/^\s+|\s+$/g, "").replace(/\r\n?/g, "\n"); - - if (text1 !== text2) { - var errorString = ""; - var text1Lines = text1.split(/\n/); - var text2Lines = text2.split(/\n/); - for (var i = 0; i < text1Lines.length; i++) { - if (text1Lines[i] !== text2Lines[i]) { - errorString += "Difference at line " + (i + 1) + ":\n"; - errorString += " Left File: " + text1Lines[i] + "\n"; - errorString += " Right File: " + text2Lines[i] + "\n\n"; - } - } - throw (new Error(errorString)); - } - } - assert.isTrue(noDiff(formattedScript, baseline)); - assert.equal(formattedScript, baseline); - } - - - /** Apply an array of text edits to a string, and return the resulting string. */ - public applyEdits(content: string, edits: ts.TextChange[]): string { - var result = content; - edits = this.normalizeEdits(edits); - - for (var i = edits.length - 1; i >= 0; i--) { - var edit = edits[i]; - var prefix = result.substring(0, edit.span.start); - var middle = edit.newText; - var suffix = result.substring(ts.textSpanEnd(edit.span)); - result = prefix + middle + suffix; - } - return result; - } - - /** Normalize an array of edits by removing overlapping entries and sorting entries on the minChar position. */ - private normalizeEdits(edits: ts.TextChange[]): ts.TextChange[] { - var result: ts.TextChange[] = []; - - function mapEdits(edits: ts.TextChange[]): { edit: ts.TextChange; index: number; }[] { - var result: { edit: ts.TextChange; index: number; }[] = []; - for (var i = 0; i < edits.length; i++) { - result.push({ edit: edits[i], index: i }); - } - return result; - } - - var temp = mapEdits(edits).sort(function (a, b) { - var result = a.edit.span.start - b.edit.span.start; - if (result === 0) - result = a.index - b.index; - return result; - }); - - var current = 0; - var next = 1; - while (current < temp.length) { - var currentEdit = temp[current].edit; - - // Last edit - if (next >= temp.length) { - result.push(currentEdit); - current++; - continue; - } - var nextEdit = temp[next].edit; - - var gap = nextEdit.span.start - ts.textSpanEnd(currentEdit.span); - - // non-overlapping edits - if (gap >= 0) { - result.push(currentEdit); - current = next; - next++; - continue; - } - - // overlapping edits: for now, we only support ignoring an next edit - // entirely contained in the current edit. - if (ts.textSpanEnd(currentEdit.span) >= ts.textSpanEnd(nextEdit.span)) { - next++; - continue; - } - else { - throw new Error("Trying to apply overlapping edits"); - } - } - - return result; - } - } } \ No newline at end of file diff --git a/tests/cases/fourslash/addMethodToInterface1.ts b/tests/cases/fourslash/addMethodToInterface1.ts index 749f5601173..07cd4700583 100644 --- a/tests/cases/fourslash/addMethodToInterface1.ts +++ b/tests/cases/fourslash/addMethodToInterface1.ts @@ -12,4 +12,3 @@ edit.disableFormatting(); goTo.marker('1'); edit.insert(" compareTo(): number;\n"); -diagnostics.validateTypesAtPositions(168,84,53,118,22); diff --git a/tests/cases/fourslash/arrayConcatTypeCheck0.ts b/tests/cases/fourslash/arrayConcatTypeCheck0.ts index ac5404266a6..9250eb5b6fd 100644 --- a/tests/cases/fourslash/arrayConcatTypeCheck0.ts +++ b/tests/cases/fourslash/arrayConcatTypeCheck0.ts @@ -14,4 +14,3 @@ edit.disableFormatting(); goTo.marker('1'); edit.insert(", 'world'"); -diagnostics.validateTypesAtPositions(78); diff --git a/tests/cases/fourslash/arrayConcatTypeCheck1.ts b/tests/cases/fourslash/arrayConcatTypeCheck1.ts index 42d9cdb8a1c..c62bd5b7f14 100644 --- a/tests/cases/fourslash/arrayConcatTypeCheck1.ts +++ b/tests/cases/fourslash/arrayConcatTypeCheck1.ts @@ -23,5 +23,4 @@ goTo.marker('2'); edit.deleteAtCaret(7); goTo.marker('4'); -diagnostics.validateTypesAtPositions(43); diff --git a/tests/cases/fourslash/arrayTypeMismatchIncrementalTypeCheck.ts b/tests/cases/fourslash/arrayTypeMismatchIncrementalTypeCheck.ts deleted file mode 100644 index 778f20db219..00000000000 --- a/tests/cases/fourslash/arrayTypeMismatchIncrementalTypeCheck.ts +++ /dev/null @@ -1,31 +0,0 @@ -/// - -//// interface Iterator { -//// (value: T, index: any): U; -//// } -//// -//// interface WrappedArray { -//// map(iterator: Iterator): U[]; -//// } -//// -//// interface Underscore { -//// (list: T[]): WrappedArray; -//// map(list: T[], iterator: Iterator, context?: any): U[]; -//// } -//// -//// declare var _: Underscore; -//// -//// var a: string[]; -//// var b = _.map(a, x => x.length); // Type any[], should be number[] -//// var c = _(a).map(); -//// var d = a.map(x => x.length); -//// var bb = _.map(aa, x => x.length); -//// var cc = _(aa).map(x => x.length); // Error, could not select overload -//// var dd = aa.map(x => x.length); // Error, could not select overload -//// -//// -//// -//// - -edit.disableFormatting(); -diagnostics.validateTypesAtPositions(364); diff --git a/tests/cases/fourslash/emptyTypeArgumentList.ts b/tests/cases/fourslash/emptyTypeArgumentList.ts deleted file mode 100644 index d8f7947559b..00000000000 --- a/tests/cases/fourslash/emptyTypeArgumentList.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// - -//// function foo2(test); -//// function foo2() { } -//// /**/foo2<>(""); -goTo.marker(); -diagnostics.validateTypeAtCurrentPosition(); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 77b5add7a11..717962fd39f 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -80,16 +80,6 @@ module FourSlashInterface { } } - export class diagnostics { - public validateTypeAtCurrentPosition() { - return this.validateTypesAtPositions(FourSlash.currentTestState.currentCaretPosition); - } - - public validateTypesAtPositions(...positions: number[]) { - return FourSlash.currentTestState.verifyTypesAgainstFullCheckAtPositions(positions); - } - } - export class goTo { // Moves the caret to the specified marker, // or the anonymous marker ('/**/') if no name @@ -647,7 +637,6 @@ module fs { export var edit = new FourSlashInterface.edit(); export var debug = new FourSlashInterface.debug(); export var format = new FourSlashInterface.format(); - export var diagnostics = new FourSlashInterface.diagnostics(); export var cancellation = new FourSlashInterface.cancellation(); } module ts { @@ -666,6 +655,5 @@ var verify = new FourSlashInterface.verify(); var edit = new FourSlashInterface.edit(); var debug = new FourSlashInterface.debug(); var format = new FourSlashInterface.format(); -var diagnostics = new FourSlashInterface.diagnostics(); var cancellation = new FourSlashInterface.cancellation(); var classification = FourSlashInterface.classification; diff --git a/tests/cases/fourslash/getTypeAtModuleExtends.ts b/tests/cases/fourslash/getTypeAtModuleExtends.ts deleted file mode 100644 index 8114ee94c1b..00000000000 --- a/tests/cases/fourslash/getTypeAtModuleExtends.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -////declare module A.B { -//// export class C { } -////} -//// -////import ab = A.B; -//// -////class D extends ab.C/**/{ } - -goTo.marker(); -diagnostics.validateTypesAtPositions(FourSlash.currentTestState.currentCaretPosition); diff --git a/tests/cases/fourslash/numberAssignement0.ts b/tests/cases/fourslash/numberAssignement0.ts deleted file mode 100644 index 540a8d91f3d..00000000000 --- a/tests/cases/fourslash/numberAssignement0.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -//// var x: Number; -//// var y: Number; -//// var z = x ; - -edit.disableFormatting(); -diagnostics.validateTypesAtPositions(28); diff --git a/tests/cases/fourslash/pullFullDiffTypeParameterExtends0.ts b/tests/cases/fourslash/pullFullDiffTypeParameterExtends0.ts deleted file mode 100644 index 990d65404d3..00000000000 --- a/tests/cases/fourslash/pullFullDiffTypeParameterExtends0.ts +++ /dev/null @@ -1,29 +0,0 @@ -/// - -//// class A { } -//// class B { -//// data: A; -//// } -//// -//// // Below 2 should compile without error -//// var x: A< { }, { b: number }>; -//// var y: B< { a: string }, { }>; -//// -//// -//// // Below should be in error -//// var x1: A<{ a: string;}>; -//// var x2: A<{ a: number }>; -//// var x3: B<{ a: string;}, { b: string }>; -//// var x4: B<{ a: string;}>; -//// var x5: A<{ a: string; b: number }, { a: string }>; -//// var x6: B<>; -//// -//// interface I1 { -//// a: string; -//// } -//// var x8: B; -//// - -edit.disableFormatting(); -diagnostics.validateTypesAtPositions(34); - diff --git a/tests/cases/fourslash/restParametersTypeValidation1.ts b/tests/cases/fourslash/restParametersTypeValidation1.ts deleted file mode 100644 index b598ae7fc84..00000000000 --- a/tests/cases/fourslash/restParametersTypeValidation1.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -//// function f18(a?:string, ...b){} -//// -//// function f19(a?:string, b?){} -//// -//// function f20(a:string, b?:string, ...c:number[]){} -//// -//// function f21(a:string, b?:string, ...d:number[]){} - -edit.disableFormatting(); -diagnostics.validateTypesAtPositions(133); diff --git a/tests/cases/fourslash/typeCheckAfterAddingGenericParameter.ts b/tests/cases/fourslash/typeCheckAfterAddingGenericParameter.ts index e2e978dd7ea..fa312f3c711 100644 --- a/tests/cases/fourslash/typeCheckAfterAddingGenericParameter.ts +++ b/tests/cases/fourslash/typeCheckAfterAddingGenericParameter.ts @@ -20,5 +20,3 @@ edit.insert(", X"); goTo.marker('addTypeParam'); edit.insert(", X"); - -diagnostics.validateTypesAtPositions(91, 163); diff --git a/tests/cases/fourslash/typeCheckExpression0.ts b/tests/cases/fourslash/typeCheckExpression0.ts deleted file mode 100644 index 51c6ae75655..00000000000 --- a/tests/cases/fourslash/typeCheckExpression0.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// - -//// class Point { -//// -//// constructor(public x: number) { -//// -//// } -//// getDist() { -//// } -//// static origin = new Point(0); -//// } -//// -//// class Point3D { -//// -//// constructor(x: number, y: number, private z) { -//// super(x, y); -//// } -//// -//// getDist() { -//// return Math.sqrt(this.x*this.x + this.z*this.m); -//// } -//// } -//// -//// - -edit.disableFormatting(); -diagnostics.validateTypesAtPositions(258); diff --git a/tests/cases/fourslash/typeCheckGenericTypeLiteralArgument.ts b/tests/cases/fourslash/typeCheckGenericTypeLiteralArgument.ts deleted file mode 100644 index 107c7a3b525..00000000000 --- a/tests/cases/fourslash/typeCheckGenericTypeLiteralArgument.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -//// interface Sequence { -//// each(iterator: (value: T) => void): void; -//// filter(): Sequence; -//// groupBy(keySelector: () => K): Sequence<{ items: T/**/[]; }>; -//// } - -goTo.file(0); - -// Marker in above file is placed at position 154 -diagnostics.validateTypesAtPositions(154); diff --git a/tests/cases/fourslash/typeCheckIndexSignature.ts b/tests/cases/fourslash/typeCheckIndexSignature.ts deleted file mode 100644 index a0c7d049b93..00000000000 --- a/tests/cases/fourslash/typeCheckIndexSignature.ts +++ /dev/null @@ -1,11 +0,0 @@ -/// - -//// function method() { -//// var dictionary = <{ [index: string]: string; }>{}; -//// } -//// - -edit.disableFormatting(); // Disregard, just here to keep Fourslash happy - -diagnostics.validateTypesAtPositions(44); - diff --git a/tests/cases/fourslash/typeCheckIndexerAccess1.ts b/tests/cases/fourslash/typeCheckIndexerAccess1.ts deleted file mode 100644 index 384f6d003e2..00000000000 --- a/tests/cases/fourslash/typeCheckIndexerAccess1.ts +++ /dev/null @@ -1,40 +0,0 @@ -/// - -//// // @sourcemap: true -//// module Foo.Bar { -//// "use strict"; -//// -//// class Greeter { -//// constructor(public greeting: string) { -//// } -//// -//// greet() { -//// } -//// } -//// -//// -//// function foo(greeting: string): Foo.Bar.Greeter { -//// return new Greeter(greeting); -//// } -//// -//// var greeter = new Greeter("Hello, world!"); -//// var str = greeter.greet(); -//// -//// function foo2(greeting: string, ...restGreetings) { -//// var greeters: Greeter[] = []; -//// new Greeter(greeting); -//// for (var i = 0; restGreetings.length; i++) { -//// greeters.push(new Greeter(restGreetings[i])); -//// } -//// -//// return greeters; -//// } -//// -//// var b = foo2("Hello", "World"); -//// for (var j = 0; j < b.length; j++) { -//// b[j].greet(); -//// } -//// } - -edit.disableFormatting(); -diagnostics.validateTypesAtPositions(705); \ No newline at end of file From c2bca0e84db94c894dac83ec5a69fdd88acbdf65 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 4 Feb 2015 12:16:47 -0800 Subject: [PATCH 09/32] Fix typo in type name --- src/harness/harnessLanguageService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index dcf211d3306..4b188cf96f6 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -109,7 +109,7 @@ module Harness.LanguageService { } } - export interface LanugageServiceAdaptor { + export interface LanguageServiceAdaptor { getHost(): LanguageServiceAdaptorHost; getLanguageService(): ts.LanguageService; getClassifier(): ts.Classifier; @@ -210,7 +210,7 @@ module Harness.LanguageService { error(s: string): void { } } - export class NativeLanugageServiceAdaptor implements LanugageServiceAdaptor { + export class NativeLanugageServiceAdaptor implements LanguageServiceAdaptor { private host: NativeLanguageServiceHost; constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { this.host = new NativeLanguageServiceHost(cancellationToken, options); @@ -391,7 +391,7 @@ module Harness.LanguageService { dispose(): void { this.shim.dispose({}); } } - export class ShimLanugageServiceAdaptor implements LanugageServiceAdaptor { + export class ShimLanugageServiceAdaptor implements LanguageServiceAdaptor { private host: ShimLanguageServiceHost; private factory: ts.TypeScriptServicesFactory; constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { From c5006ca8acb21ad88f5dedc47370322b2c652b63 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 4 Feb 2015 12:17:04 -0800 Subject: [PATCH 10/32] remove new line --- src/harness/harness.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 52884744535..9bf1932b971 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -690,8 +690,6 @@ module Harness { } } - - module Harness { var tcServicesFilename = "typescriptServices.js"; From 2494b2d90fd73bc657321a53520f3d5be41b94ba Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 4 Feb 2015 13:39:24 -0800 Subject: [PATCH 11/32] Support spread operator in call expressions --- src/compiler/checker.ts | 269 +++++++++--------- .../diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 4 + src/compiler/emitter.ts | 106 +++++-- src/compiler/parser.ts | 7 +- 5 files changed, 233 insertions(+), 154 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f44c9b118f8..5140b73f299 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5818,10 +5818,74 @@ module ts { return unknownSignature; } + // Re-order candidate signatures into the result array. Assumes the result array to be empty. + // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order + // A nit here is that we reorder only signatures that belong to the same symbol, + // so order how inherited signatures are processed is still preserved. + // interface A { (x: string): void } + // interface B extends A { (x: 'foo'): string } + // var b: B; + // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] + function reorderCandidates(signatures: Signature[], result: Signature[]): void { + var lastParent: Node; + var lastSymbol: Symbol; + var cutoffIndex: number = 0; + var index: number; + var specializedIndex: number = -1; + var spliceIndex: number; + Debug.assert(!result.length); + for (var i = 0; i < signatures.length; i++) { + var signature = signatures[i]; + var symbol = signature.declaration && getSymbolOfNode(signature.declaration); + var parent = signature.declaration && signature.declaration.parent; + if (!lastSymbol || symbol === lastSymbol) { + if (lastParent && parent === lastParent) { + index++; + } + else { + lastParent = parent; + index = cutoffIndex; + } + } + else { + // current declaration belongs to a different symbol + // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex + index = cutoffIndex = result.length; + lastParent = parent; + } + lastSymbol = symbol; + + // specialized signatures always need to be placed before non-specialized signatures regardless + // of the cutoff position; see GH#1133 + if (signature.hasStringLiterals) { + specializedIndex++; + spliceIndex = specializedIndex; + // The cutoff index always needs to be greater than or equal to the specialized signature index + // in order to prevent non-specialized signatures from being added before a specialized + // signature. + cutoffIndex++; + } + else { + spliceIndex = index; + } + + result.splice(spliceIndex, 0, signature); + } + } + + function getSpreadArgumentIndex(args: Expression[]): number { + for (var i = 0; i < args.length; i++) { + if (args[i].kind === SyntaxKind.SpreadElementExpression) { + return i; + } + } + return -1; + } + function hasCorrectArity(node: CallLikeExpression, args: Expression[], signature: Signature) { - var adjustedArgCount: number; - var typeArguments: NodeArray; - var callIsIncomplete: boolean; + var adjustedArgCount: number; // Apparent number of arguments we will have in this call + var typeArguments: NodeArray; // Type arguments (undefined if none) + var callIsIncomplete: boolean; // In incomplete call we want to be lenient when we have too few arguments if (node.kind === SyntaxKind.TaggedTemplateExpression) { var tagExpression = node; @@ -5866,35 +5930,29 @@ module ts { typeArguments = callExpression.typeArguments; } - Debug.assert(adjustedArgCount !== undefined, "'adjustedArgCount' undefined"); - Debug.assert(callIsIncomplete !== undefined, "'callIsIncomplete' undefined"); - - return checkArity(adjustedArgCount, typeArguments, callIsIncomplete, signature); - - /** - * @param adjustedArgCount The "apparent" number of arguments that we will have in this call. - * @param typeArguments Type arguments node of the call if it exists; undefined otherwise. - * @param callIsIncomplete Whether or not a call is unfinished, and we should be "lenient" when we have too few arguments. - * @param signature The signature whose arity we are comparing. - */ - function checkArity(adjustedArgCount: number, typeArguments: NodeArray, callIsIncomplete: boolean, signature: Signature): boolean { - // Too many arguments implies incorrect arity. - if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) { - return false; - } - - // If the user supplied type arguments, but the number of type arguments does not match - // the declared number of type parameters, the call has an incorrect arity. - var hasRightNumberOfTypeArgs = !typeArguments || - (signature.typeParameters && typeArguments.length === signature.typeParameters.length); - if (!hasRightNumberOfTypeArgs) { - return false; - } - - // If the call is incomplete, we should skip the lower bound check. - var hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount; - return callIsIncomplete || hasEnoughArguments; + // If the user supplied type arguments, but the number of type arguments does not match + // the declared number of type parameters, the call has an incorrect arity. + var hasRightNumberOfTypeArgs = !typeArguments || + (signature.typeParameters && typeArguments.length === signature.typeParameters.length); + if (!hasRightNumberOfTypeArgs) { + return false; } + + // If spread arguments are present, check that they correspond to a rest parameter. If so, no + // further checking is necessary. + var spreadArgIndex = getSpreadArgumentIndex(args); + if (spreadArgIndex >= 0) { + return signature.hasRestParameter && spreadArgIndex >= signature.parameters.length - 1; + } + + // Too many arguments implies incorrect arity. + if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) { + return false; + } + + // If the call is incomplete, we should skip the lower bound check. + var hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount; + return callIsIncomplete || hasEnoughArguments; } // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. @@ -5927,18 +5985,20 @@ module ts { // We perform two passes over the arguments. In the first pass we infer from all arguments, but use // wildcards for all context sensitive function expressions. for (var i = 0; i < args.length; i++) { - if (args[i].kind === SyntaxKind.OmittedExpression) { - continue; + var arg = args[i]; + if (arg.kind !== SyntaxKind.OmittedExpression) { + var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i); + if (i === 0 && args[i].parent.kind === SyntaxKind.TaggedTemplateExpression) { + var argType = globalTemplateStringsArrayType; + } + else { + // For context sensitive arguments we pass the identityMapper, which is a signal to treat all + // context sensitive function expressions as wildcards + var mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : inferenceMapper; + var argType = checkExpressionWithContextualType(arg, paramType, mapper); + } + inferTypes(context, argType, paramType); } - var parameterType = getTypeAtPosition(signature, i); - if (i === 0 && args[i].parent.kind === SyntaxKind.TaggedTemplateExpression) { - inferTypes(context, globalTemplateStringsArrayType, parameterType); - continue; - } - // For context sensitive arguments we pass the identityMapper, which is a signal to treat all - // context sensitive function expressions as wildcards - var mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : inferenceMapper; - inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType); } // In the second pass we visit only context sensitive arguments, and only those that aren't excluded, this @@ -5946,13 +6006,11 @@ module ts { // as we construct types for contextually typed parameters) if (excludeArgument) { for (var i = 0; i < args.length; i++) { - if (args[i].kind === SyntaxKind.OmittedExpression) { - continue; - } - // No need to special-case tagged templates; their excludeArgument value will be 'undefined'. + // No need to check for omitted args and template expressions, their exlusion value is always undefined if (excludeArgument[i] === false) { - var parameterType = getTypeAtPosition(signature, i); - inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, inferenceMapper), parameterType); + var arg = args[i]; + var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i); + inferTypes(context, checkExpressionWithContextualType(arg, paramType, inferenceMapper), paramType); } } } @@ -5990,37 +6048,24 @@ module ts { return typeArgumentsAreAssignable; } - function checkApplicableSignature(node: CallLikeExpression, args: Node[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) { + function checkApplicableSignature(node: CallLikeExpression, args: Expression[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) { for (var i = 0; i < args.length; i++) { var arg = args[i]; - var argType: Type; - - if (arg.kind === SyntaxKind.OmittedExpression) { - continue; - } - - var paramType = getTypeAtPosition(signature, i); - - if (i === 0 && node.kind === SyntaxKind.TaggedTemplateExpression) { - // A tagged template expression has something of a - // "virtual" parameter with the "cooked" strings array type. - argType = globalTemplateStringsArrayType; - } - else { - // String literals get string literal types unless we're reporting errors - argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors - ? getStringLiteralType(arg) - : checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); - } - - // Use argument expression as error location when reporting errors - var isValidArgument = checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined, - Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1); - if (!isValidArgument) { - return false; + if (arg.kind !== SyntaxKind.OmittedExpression) { + // Check spread elements against rest type (from arity check we know spread argument corresponds to a rest parameter) + var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i); + // A tagged template expression provides a special first argument, and string literals get string literal types + // unless we're reporting errors + var argType = i === 0 && node.kind === SyntaxKind.TaggedTemplateExpression ? globalTemplateStringsArrayType : + arg.kind === SyntaxKind.StringLiteral && !reportErrors ? getStringLiteralType(arg) : + checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); + // Use argument expression as error location when reporting errors + if (!checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined, + Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1)) { + return false; + } } } - return true; } @@ -6087,8 +6132,8 @@ module ts { } var candidates = candidatesOutArray || []; - // collectCandidates fills up the candidates array directly - collectCandidates(); + // reorderCandidates fills up the candidates array directly + reorderCandidates(signatures, candidates); if (!candidates.length) { error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target); return resolveErrorCall(node); @@ -6278,60 +6323,6 @@ module ts { return undefined; } - // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order - // A nit here is that we reorder only signatures that belong to the same symbol, - // so order how inherited signatures are processed is still preserved. - // interface A { (x: string): void } - // interface B extends A { (x: 'foo'): string } - // var b: B; - // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] - function collectCandidates(): void { - var result = candidates; - var lastParent: Node; - var lastSymbol: Symbol; - var cutoffIndex: number = 0; - var index: number; - var specializedIndex: number = -1; - var spliceIndex: number; - Debug.assert(!result.length); - for (var i = 0; i < signatures.length; i++) { - var signature = signatures[i]; - var symbol = signature.declaration && getSymbolOfNode(signature.declaration); - var parent = signature.declaration && signature.declaration.parent; - if (!lastSymbol || symbol === lastSymbol) { - if (lastParent && parent === lastParent) { - index++; - } - else { - lastParent = parent; - index = cutoffIndex; - } - } - else { - // current declaration belongs to a different symbol - // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex - index = cutoffIndex = result.length; - lastParent = parent; - } - lastSymbol = symbol; - - // specialized signatures always need to be placed before non-specialized signatures regardless - // of the cutoff position; see GH#1133 - if (signature.hasStringLiterals) { - specializedIndex++; - spliceIndex = specializedIndex; - // The cutoff index always needs to be greater than or equal to the specialized signature index - // in order to prevent non-specialized signatures from being added before a specialized - // signature. - cutoffIndex++; - } - else { - spliceIndex = index; - } - - result.splice(spliceIndex, 0, signature); - } - } } function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature { @@ -6387,6 +6378,13 @@ module ts { } function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[]): Signature { + if (node.arguments && languageVersion < ScriptTarget.ES6) { + var spreadIndex = getSpreadArgumentIndex(node.arguments); + if (spreadIndex >= 0) { + error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_6_and_higher); + } + } + var expressionType = checkExpression(node.expression); // TS 1.0 spec: 4.11 // If ConstructExpr is of type Any, Args can be any argument @@ -6532,9 +6530,14 @@ module ts { } function getTypeAtPosition(signature: Signature, pos: number): Type { + if (pos >= 0) { + return signature.hasRestParameter ? + pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : + pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType; + } return signature.hasRestParameter ? - pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : - pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType; + getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]) : + anyArrayType; } function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) { diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index dc7ef322c9b..b8dad442718 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -303,6 +303,7 @@ module ts { this_cannot_be_referenced_in_a_computed_property_name: { code: 2465, category: DiagnosticCategory.Error, key: "'this' cannot be referenced in a computed property name." }, super_cannot_be_referenced_in_a_computed_property_name: { code: 2466, category: DiagnosticCategory.Error, key: "'super' cannot be referenced in a computed property name." }, A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type: { code: 2466, category: DiagnosticCategory.Error, key: "A computed property name cannot reference a type parameter from its containing type." }, + Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_6_and_higher: { code: 2468, category: DiagnosticCategory.Error, key: "Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher." }, Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." }, Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." }, Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 5ee826812f2..900dc9ecd04 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1204,6 +1204,10 @@ "category": "Error", "code": 2466 }, + "Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher.": { + "category": "Error", + "code": 2468 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index b15a493de92..86d85f37199 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1921,7 +1921,7 @@ module ts { break; } // _a .. _h, _j ... _z, _0, _1, ... - name = "_" + (tempCount < 25 ? String.fromCharCode(tempCount + (tempCount < 8 ? 0: 1) + CharacterCodes.a) : tempCount - 25); + name = "_" + (tempCount < 25 ? String.fromCharCode(tempCount + (tempCount < 8 ? 0 : 1) + CharacterCodes.a) : tempCount - 25); tempCount++; } var result = createNode(SyntaxKind.Identifier); @@ -2350,22 +2350,10 @@ module ts { return true; } - function emitArrayLiteral(node: ArrayLiteralExpression) { - var elements = node.elements; - var length = elements.length; - if (length === 0) { - write("[]"); - return; - } - if (languageVersion >= ScriptTarget.ES6) { - write("["); - emitList(elements, 0, elements.length, /*multiLine*/(node.flags & NodeFlags.MultiLine) !== 0, - /*trailingComma*/ elements.hasTrailingComma); - write("]"); - return; - } + function emitListWithSpread(elements: Expression[], multiLine: boolean, trailingComma: boolean) { var pos = 0; var group = 0; + var length = elements.length; while (pos < length) { // Emit using the pattern .concat(, , ...) if (group === 1) { @@ -2386,8 +2374,7 @@ module ts { i++; } write("["); - emitList(elements, pos, i - pos, /*multiLine*/ (node.flags & NodeFlags.MultiLine) !== 0, - /*trailingComma*/ elements.hasTrailingComma); + emitList(elements, pos, i - pos, multiLine, trailingComma); write("]"); pos = i; } @@ -2398,6 +2385,23 @@ module ts { } } + function emitArrayLiteral(node: ArrayLiteralExpression) { + var elements = node.elements; + if (elements.length === 0) { + write("[]"); + return; + } + if (languageVersion >= ScriptTarget.ES6) { + write("["); + emitList(elements, 0, elements.length, /*multiLine*/(node.flags & NodeFlags.MultiLine) !== 0, + /*trailingComma*/ elements.hasTrailingComma); + write("]"); + return; + } + emitListWithSpread(elements, /*multiLine*/(node.flags & NodeFlags.MultiLine) !== 0, + /*trailingComma*/ elements.hasTrailingComma); + } + function emitObjectLiteral(node: ObjectLiteralExpression) { write("{"); var properties = node.properties; @@ -2492,7 +2496,75 @@ module ts { write("]"); } + function hasSpreadElement(elements: Expression[]) { + return forEach(elements, e => e.kind === SyntaxKind.SpreadElementExpression); + } + + function skipParentheses(node: Expression): Expression { + while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression) { + node = (node).expression; + } + return node; + } + + function emitTarget(node: Expression): Expression { + if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword) { + emit(node); + return node; + } + var temp = createTempVariable(node); + recordTempDeclaration(temp); + write("("); + emit(temp); + write(" = "); + emit(node); + write(")"); + return temp; + } + + function emitCallWithSpread(node: CallExpression) { + var target: Expression; + var expr = skipParentheses(node.expression); + if (expr.kind === SyntaxKind.PropertyAccessExpression) { + target = emitTarget((expr).expression); + write("."); + emit((expr).name); + } + else if (expr.kind === SyntaxKind.ElementAccessExpression) { + target = emitTarget((expr).expression); + write("["); + emit((expr).argumentExpression); + write("]"); + } + else if (expr.kind === SyntaxKind.SuperKeyword) { + target = expr; + write("_super"); + } + else { + emit(node.expression); + } + write(".apply("); + if (target) { + if (target.kind === SyntaxKind.SuperKeyword) { + emitThis(target); + } + else { + emit(target); + } + } + else { + write("void 0"); + } + write(", "); + emitListWithSpread(node.arguments, /*multiLine*/ false, /*trailingComma*/ false); + write(")"); + } + function emitCallExpression(node: CallExpression) { + if (languageVersion < ScriptTarget.ES6 && hasSpreadElement(node.arguments)) { + emitCallWithSpread(node); + return; + } var superCall = false; if (node.expression.kind === SyntaxKind.SuperKeyword) { write("_super"); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 5ae44e5ff39..163c8c1272b 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1440,7 +1440,6 @@ module ts { case ParsingContext.TypeParameters: return isIdentifier(); case ParsingContext.ArgumentExpressions: - return token === SyntaxKind.CommaToken || isStartOfExpression(); case ParsingContext.ArrayLiteralMembers: return token === SyntaxKind.CommaToken || token === SyntaxKind.DotDotDotToken || isStartOfExpression(); case ParsingContext.Parameters: @@ -3544,19 +3543,19 @@ module ts { return finishNode(node); } - function parseArrayLiteralElement(): Expression { + function parseArgumentOrArrayLiteralElement(): Expression { return token === SyntaxKind.DotDotDotToken ? parseSpreadElement() : parseAssignmentExpressionOrOmittedExpression(); } function parseArgumentExpression(): Expression { - return allowInAnd(parseAssignmentExpressionOrOmittedExpression); + return allowInAnd(parseArgumentOrArrayLiteralElement); } function parseArrayLiteralExpression(): ArrayLiteralExpression { var node = createNode(SyntaxKind.ArrayLiteralExpression); parseExpected(SyntaxKind.OpenBracketToken); if (scanner.hasPrecedingLineBreak()) node.flags |= NodeFlags.MultiLine; - node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArrayLiteralElement); + node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); parseExpected(SyntaxKind.CloseBracketToken); return finishNode(node); } From bfef4a03658d6d5ac2519aac38b4fcbae4223437 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 4 Feb 2015 15:36:13 -0800 Subject: [PATCH 12/32] Add new tests for shims --- src/harness/fourslash.ts | 45 +++++-- src/harness/fourslashRunner.ts | 36 ++++-- src/harness/harnessLanguageService.ts | 8 +- src/harness/runner.ts | 10 +- .../getBreakpointStatementAtPosition.baseline | 71 +++++++++++ .../reference/getEmitOutput.baseline | 30 +++++ tests/cases/fourslash/indentation.ts | 2 +- ...cellationWhenfindingAllRefsOnDefinition.ts | 38 ++++++ .../shims/getBraceMatchingAtPosition.ts | 43 +++++++ .../shims/getBreakpointStatementAtPosition.ts | 17 +++ .../shims/getCompletionsAtPosition.ts | 20 ++++ .../shims/getDefinitionAtPosition.ts | 29 +++++ tests/cases/fourslash/shims/getEmitOutput.ts | 22 ++++ .../shims/getIndentationAtPosition.ts | 21 ++++ .../fourslash/shims/getNavigateToItems.ts | 27 +++++ .../fourslash/shims/getNavigationBarItems.ts | 13 ++ .../shims/getOccurrencesAtPosition.ts | 18 +++ .../fourslash/shims/getOutliningSpans.ts | 113 ++++++++++++++++++ .../fourslash/shims/getPreProcessedFile.ts | 32 +++++ .../fourslash/shims/getQuickInfoAtPosition.ts | 16 +++ .../shims/getReferencesAtPosition.ts | 29 +++++ tests/cases/fourslash/shims/getRenameInfo.ts | 11 ++ .../shims/getSemanticClassifications.ts | 15 +++ .../fourslash/shims/getSemanticDiagnostics.ts | 11 ++ .../fourslash/shims/getSignatureHelpItems.ts | 13 ++ .../shims/getSyntacticClassifications.ts | 35 ++++++ .../cases/fourslash/shims/getTodoComments.ts | 3 + .../shims/quickInfoDisplayPartsVar.ts | 76 ++++++++++++ 28 files changed, 776 insertions(+), 28 deletions(-) create mode 100644 tests/baselines/reference/getBreakpointStatementAtPosition.baseline create mode 100644 tests/baselines/reference/getEmitOutput.baseline create mode 100644 tests/cases/fourslash/shims/cancellationWhenfindingAllRefsOnDefinition.ts create mode 100644 tests/cases/fourslash/shims/getBraceMatchingAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getBreakpointStatementAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getCompletionsAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getDefinitionAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getEmitOutput.ts create mode 100644 tests/cases/fourslash/shims/getIndentationAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getNavigateToItems.ts create mode 100644 tests/cases/fourslash/shims/getNavigationBarItems.ts create mode 100644 tests/cases/fourslash/shims/getOccurrencesAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getOutliningSpans.ts create mode 100644 tests/cases/fourslash/shims/getPreProcessedFile.ts create mode 100644 tests/cases/fourslash/shims/getQuickInfoAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getReferencesAtPosition.ts create mode 100644 tests/cases/fourslash/shims/getRenameInfo.ts create mode 100644 tests/cases/fourslash/shims/getSemanticClassifications.ts create mode 100644 tests/cases/fourslash/shims/getSemanticDiagnostics.ts create mode 100644 tests/cases/fourslash/shims/getSignatureHelpItems.ts create mode 100644 tests/cases/fourslash/shims/getSyntacticClassifications.ts create mode 100644 tests/cases/fourslash/shims/getTodoComments.ts create mode 100644 tests/cases/fourslash/shims/quickInfoDisplayPartsVar.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 5b46ec32a1c..cccbc448213 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -275,11 +275,26 @@ module FourSlash { } } - constructor(public testData: FourSlashData) { + private getLanguageServiceAdaptor(testType: FourSlashTestType): + { new (cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions): Harness.LanguageService.LanguageServiceAdaptor } { + switch (testType) { + case FourSlashTestType.Native: + return Harness.LanguageService.NativeLanugageServiceAdaptor; + break; + case FourSlashTestType.Shims: + return Harness.LanguageService.ShimLanugageServiceAdaptor; + break; + default: + throw new Error("Unknown FourSlash test type: "); + } + } + + constructor(private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { // Create a new Services Adaptor this.cancellationToken = new TestCancellationToken(); var compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); - var languageServiceAdaptor = new Harness.LanguageService.NativeLanugageServiceAdaptor(this.cancellationToken, compilationOptions); + var languageserviceAdaptorFactory = this.getLanguageServiceAdaptor(testType); + var languageServiceAdaptor = new languageserviceAdaptorFactory(this.cancellationToken, compilationOptions); this.languageServiceAdaptorHost = languageServiceAdaptor.getHost(); this.languageService = languageServiceAdaptor.getLanguageService(); @@ -308,7 +323,7 @@ module FourSlash { // Add triple reference files into language-service host ts.forEach(referencedFiles, referenceFile => { // Fourslash insert tests/cases/fourslash into inputFile.unitName so we will properly append the same base directory to refFile path - var referenceFilePath = "tests/cases/fourslash/" + referenceFile.filename; + var referenceFilePath = this.basePath+ '/' + referenceFile.filename; this.addMatchedInputFile(referenceFilePath); }); @@ -316,7 +331,7 @@ module FourSlash { ts.forEach(importedFiles, importedFile => { // Fourslash insert tests/cases/fourslash into inputFile.unitName and import statement doesn't require ".ts" // so convert them before making appropriate comparison - var importedFilePath = "tests/cases/fourslash/" + importedFile.filename + ".ts"; + var importedFilePath = this.basePath + '/' + importedFile.filename + ".ts"; this.addMatchedInputFile(importedFilePath); }); @@ -1382,6 +1397,12 @@ module FourSlash { } private checkPostEditInvariants() { + if (this.testType !== FourSlashTestType.Native) { + // getSourcefile() results can not be serialized. Only perform these verifications + // if running against a native LS object. + return; + } + var incrementalSourceFile = this.languageService.getSourceFile(this.activeFile.fileName); Utils.assertInvariants(incrementalSourceFile, /*parent:*/ undefined); @@ -2052,7 +2073,7 @@ module FourSlash { } else if (typeof indexOrName === 'string') { var name = indexOrName; // names are stored in the compiler with this relative path, this allows people to use goTo.file on just the filename - name = name.indexOf('/') === -1 ? 'tests/cases/fourslash/' + name : name; + name = name.indexOf('/') === -1 ? (this.basePath + '/' + name) : name; var availableNames: string[] = []; var foundIt = false; for (var i = 0; i < this.testData.files.length; i++) { @@ -2118,17 +2139,17 @@ module FourSlash { var fsOutput = new Harness.Compiler.WriterAggregator(); var fsErrors = new Harness.Compiler.WriterAggregator(); export var xmlData: TestXmlData[] = []; - export function runFourSlashTest(fileName: string) { + export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) { var content = Harness.IO.readFile(fileName); - var xml = runFourSlashTestContent(content, fileName); + var xml = runFourSlashTestContent(basePath, testType, content, fileName); xmlData.push(xml); } - export function runFourSlashTestContent(content: string, fileName: string): TestXmlData { + export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): TestXmlData { // Parse out the files and their metadata - var testData = parseTestData(content, fileName); + var testData = parseTestData(basePath, content, fileName); - currentTestState = new TestState(testData); + currentTestState = new TestState(basePath, testType, testData); var result = ''; var host = Harness.Compiler.createCompilerHost([{ unitName: Harness.Compiler.fourslashFilename, content: undefined }, @@ -2171,7 +2192,7 @@ module FourSlash { return lines.map(s => s.substr(1)).join('\n'); } - function parseTestData(contents: string, fileName: string): FourSlashData { + function parseTestData(basePath: string, contents: string, fileName: string): FourSlashData { // Regex for parsing options in the format "@Alpha: Value of any sort" var optionRegex = /^\s*@(\w+): (.*)\s*/; @@ -2239,7 +2260,7 @@ module FourSlash { currentFileName = fileName; } - currentFileName = 'tests/cases/fourslash/' + match[2]; + currentFileName = basePath + '/' + match[2]; currentFileOptions[match[1]] = match[2]; } else { // Add other fileMetadata flag diff --git a/src/harness/fourslashRunner.ts b/src/harness/fourslashRunner.ts index 1ecb02df292..fe3b6c7a91f 100644 --- a/src/harness/fourslashRunner.ts +++ b/src/harness/fourslashRunner.ts @@ -2,19 +2,35 @@ /// /// -class FourslashRunner extends RunnerBase { - public basePath = 'tests/cases/fourslash'; +const enum FourSlashTestType { + Native, + Shims +} - constructor() { +class FourSlashRunner extends RunnerBase { + protected basePath: string; + protected testSuiteName: string; + + constructor(private testType: FourSlashTestType) { super(); + switch (testType) { + case FourSlashTestType.Native: + this.basePath = 'tests/cases/fourslash'; + this.testSuiteName = 'fourslash'; + break; + case FourSlashTestType.Shims: + this.basePath = 'tests/cases/fourslash/shims'; + this.testSuiteName = 'fourslash-shims'; + break; + } } public initializeTests() { if (this.tests.length === 0) { - this.tests = this.enumerateFiles(this.basePath, /\.ts/i); + this.tests = this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false }); } - describe("fourslash tests", () => { + describe(this.testSuiteName, () => { this.tests.forEach((fn: string) => { fn = ts.normalizeSlashes(fn); var justName = fn.replace(/^.*[\\\/]/, ''); @@ -24,8 +40,8 @@ class FourslashRunner extends RunnerBase { if (testIndex >= 0) fn = fn.substr(testIndex); if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) { - it('FourSlash test ' + justName + ' runs correctly', function () { - FourSlash.runFourSlashTest(fn); + it(this.testSuiteName + ' test ' + justName + ' runs correctly',() => { + FourSlash.runFourSlashTest(this.basePath, this.testType, fn); }); } }); @@ -82,9 +98,9 @@ class FourslashRunner extends RunnerBase { } } -class GeneratedFourslashRunner extends FourslashRunner { - constructor() { - super(); +class GeneratedFourslashRunner extends FourSlashRunner { + constructor(testType: FourSlashTestType) { + super(testType); this.basePath += '/generated/'; } } \ No newline at end of file diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 4b188cf96f6..2fce141bbf3 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -56,9 +56,13 @@ module Harness.LanguageService { } class ScriptSnapshot implements ts.IScriptSnapshot { - public textSnapshot: string; public version: number; + public textSnapshot: string; + public version: number; + constructor(public scriptInfo: ScriptInfo) { - this.textSnapshot = scriptInfo.content; this.version = scriptInfo.version; } + this.textSnapshot = scriptInfo.content; + this.version = scriptInfo.version; + } public getText(start: number, end: number): string { return this.textSnapshot.substring(start, end); diff --git a/src/harness/runner.ts b/src/harness/runner.ts index 24349ada3fd..942ae56bf6f 100644 --- a/src/harness/runner.ts +++ b/src/harness/runner.ts @@ -61,10 +61,13 @@ if (testConfigFile !== '') { runners.push(new ProjectRunner()); break; case 'fourslash': - runners.push(new FourslashRunner()); + runners.push(new FourSlashRunner(FourSlashTestType.Native)); + break; + case 'fourslash-shims': + runners.push(new FourSlashRunner(FourSlashTestType.Shims)); break; case 'fourslash-generated': - runners.push(new GeneratedFourslashRunner()); + runners.push(new GeneratedFourslashRunner(FourSlashTestType.Native)); break; case 'rwc': runners.push(new RWCRunner()); @@ -90,7 +93,8 @@ if (runners.length === 0) { } // language services - runners.push(new FourslashRunner()); + runners.push(new FourSlashRunner(FourSlashTestType.Native)); + runners.push(new FourSlashRunner(FourSlashTestType.Shims)); //runners.push(new GeneratedFourslashRunner()); } diff --git a/tests/baselines/reference/getBreakpointStatementAtPosition.baseline b/tests/baselines/reference/getBreakpointStatementAtPosition.baseline new file mode 100644 index 00000000000..617589b9047 --- /dev/null +++ b/tests/baselines/reference/getBreakpointStatementAtPosition.baseline @@ -0,0 +1,71 @@ + +1 >while (true) { + + ~~~~~~~~~~~~~~~ => Pos: (0 to 14) SpanInfo: {"start":0,"length":12} + >while (true) + >:=> (line 1, col 0) to (line 1, col 12) +-------------------------------- +2 > break; + + ~~~~~~~~~~~ => Pos: (15 to 25) SpanInfo: {"start":19,"length":5} + >break + >:=> (line 2, col 4) to (line 2, col 9) +-------------------------------- +3 >} + + ~~ => Pos: (26 to 27) SpanInfo: {"start":19,"length":5} + >break + >:=> (line 2, col 4) to (line 2, col 9) +-------------------------------- +4 >y: while (true) { + + ~~~~~~~~~~~~~~~~~~ => Pos: (28 to 45) SpanInfo: {"start":31,"length":12} + >while (true) + >:=> (line 4, col 3) to (line 4, col 15) +-------------------------------- +5 > break y; + + ~~~~~~~~~~~~~ => Pos: (46 to 58) SpanInfo: {"start":50,"length":7} + >break y + >:=> (line 5, col 4) to (line 5, col 11) +-------------------------------- +6 >} + + ~~ => Pos: (59 to 60) SpanInfo: {"start":50,"length":7} + >break y + >:=> (line 5, col 4) to (line 5, col 11) +-------------------------------- +7 >while (true) { + + ~~~~~~~~~~~~~~~ => Pos: (61 to 75) SpanInfo: {"start":61,"length":12} + >while (true) + >:=> (line 7, col 0) to (line 7, col 12) +-------------------------------- +8 > continue; + + ~~~~~~~~~~~~~~ => Pos: (76 to 89) SpanInfo: {"start":80,"length":8} + >continue + >:=> (line 8, col 4) to (line 8, col 12) +-------------------------------- +9 >} + + ~~ => Pos: (90 to 91) SpanInfo: {"start":80,"length":8} + >continue + >:=> (line 8, col 4) to (line 8, col 12) +-------------------------------- +10 >z: while (true) { + + ~~~~~~~~~~~~~~~~~~ => Pos: (92 to 109) SpanInfo: {"start":95,"length":12} + >while (true) + >:=> (line 10, col 3) to (line 10, col 15) +-------------------------------- +11 > continue z; + + ~~~~~~~~~~~~~~~~ => Pos: (110 to 125) SpanInfo: {"start":114,"length":10} + >continue z + >:=> (line 11, col 4) to (line 11, col 14) +-------------------------------- +12 >} + ~ => Pos: (126 to 126) SpanInfo: {"start":114,"length":10} + >continue z + >:=> (line 11, col 4) to (line 11, col 14) \ No newline at end of file diff --git a/tests/baselines/reference/getEmitOutput.baseline b/tests/baselines/reference/getEmitOutput.baseline new file mode 100644 index 00000000000..8ca4f6a4100 --- /dev/null +++ b/tests/baselines/reference/getEmitOutput.baseline @@ -0,0 +1,30 @@ +EmitOutputStatus : Succeeded +Filename : tests/cases/fourslash/shims/inputFile1.js +var x = 5; +var Bar = (function () { + function Bar() { + } + return Bar; +})(); +Filename : tests/cases/fourslash/shims/inputFile1.d.ts +declare var x: number; +declare class Bar { + x: string; + y: number; +} + +EmitOutputStatus : Succeeded +Filename : tests/cases/fourslash/shims/inputFile2.js +var x1 = "hello world"; +var Foo = (function () { + function Foo() { + } + return Foo; +})(); +Filename : tests/cases/fourslash/shims/inputFile2.d.ts +declare var x1: string; +declare class Foo { + x: string; + y: number; +} + diff --git a/tests/cases/fourslash/indentation.ts b/tests/cases/fourslash/indentation.ts index 32319ae282d..2a2090c1d87 100644 --- a/tests/cases/fourslash/indentation.ts +++ b/tests/cases/fourslash/indentation.ts @@ -179,5 +179,5 @@ ////{| "indent": 0 |} test.markers().forEach((marker) => { - verify.indentationAtPositionIs('tests/cases/fourslash/indentation.ts', marker.position, marker.data.indent); + verify.indentationAtPositionIs(marker.fileName, marker.position, marker.data.indent); }); diff --git a/tests/cases/fourslash/shims/cancellationWhenfindingAllRefsOnDefinition.ts b/tests/cases/fourslash/shims/cancellationWhenfindingAllRefsOnDefinition.ts new file mode 100644 index 00000000000..09f580bb965 --- /dev/null +++ b/tests/cases/fourslash/shims/cancellationWhenfindingAllRefsOnDefinition.ts @@ -0,0 +1,38 @@ +/// + +//@Filename: findAllRefsOnDefinition-import.ts +////export class Test{ +//// +//// constructor(){ +//// +//// } +//// +//// public /*1*/start(){ +//// return this; +//// } +//// +//// public stop(){ +//// return this; +//// } +////} + +//@Filename: findAllRefsOnDefinition.ts +////import Second = require("findAllRefsOnDefinition-import"); +//// +////var second = new Second.Test() +////second.start(); +////second.stop(); + +goTo.file("findAllRefsOnDefinition-import.ts"); +goTo.marker("1"); + +verify.referencesCountIs(2); + +cancellation.setCancelled(); +goTo.marker("1"); +verifyOperationIsCancelled(() => verify.referencesCountIs(0) ); + +// verify that internal state is still correct +cancellation.resetCancelled(); +goTo.marker("1"); +verify.referencesCountIs(2); diff --git a/tests/cases/fourslash/shims/getBraceMatchingAtPosition.ts b/tests/cases/fourslash/shims/getBraceMatchingAtPosition.ts new file mode 100644 index 00000000000..fc8a71197db --- /dev/null +++ b/tests/cases/fourslash/shims/getBraceMatchingAtPosition.ts @@ -0,0 +1,43 @@ +/// + +//////curly braces +////module Foo [|{ +//// class Bar [|{ +//// private f() [|{ +//// }|] +//// +//// private f2() [|{ +//// if (true) [|{ }|] [|{ }|]; +//// }|] +//// }|] +////}|] +//// +//////parenthesis +////class FooBar { +//// private f[|()|] { +//// return [|([|(1 + 1)|])|]; +//// } +//// +//// private f2[|()|] { +//// if [|(true)|] { } +//// } +////} +//// +//////square brackets +////class Baz { +//// private f() { +//// var a: any[|[]|] = [|[[|[1, 2]|], [|[3, 4]|], 5]|]; +//// } +////} +//// +////// angular brackets +////class TemplateTest [||] { +//// public foo(a, b) { +//// return [||] a; +//// } +////} + +test.ranges().forEach((range) => { + verify.matchingBracePositionInCurrentFile(range.start, range.end - 1); + verify.matchingBracePositionInCurrentFile(range.end - 1, range.start); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getBreakpointStatementAtPosition.ts b/tests/cases/fourslash/shims/getBreakpointStatementAtPosition.ts new file mode 100644 index 00000000000..9e1d075a17f --- /dev/null +++ b/tests/cases/fourslash/shims/getBreakpointStatementAtPosition.ts @@ -0,0 +1,17 @@ +/// + +// @BaselineFile: getBreakpointStatementAtPosition.baseline +// @Filename: getBreakpointStatementAtPosition.ts +////while (true) { +//// break; +////} +////y: while (true) { +//// break y; +////} +////while (true) { +//// continue; +////} +////z: while (true) { +//// continue z; +////} +verify.baselineCurrentFileBreakpointLocations(); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getCompletionsAtPosition.ts b/tests/cases/fourslash/shims/getCompletionsAtPosition.ts new file mode 100644 index 00000000000..714d3390e76 --- /dev/null +++ b/tests/cases/fourslash/shims/getCompletionsAtPosition.ts @@ -0,0 +1,20 @@ +/// + +////function foo(strOrNum: string | number) { +//// /*1*/ +//// if (typeof strOrNum === "number") { +//// /*2*/ +//// } +//// else { +//// /*3*/ +//// } +////} + +goTo.marker('1'); +verify.completionListContains("strOrNum", "(parameter) strOrNum: string | number"); + +goTo.marker('2'); +verify.completionListContains("strOrNum", "(parameter) strOrNum: number"); + +goTo.marker('3'); +verify.completionListContains("strOrNum", "(parameter) strOrNum: string"); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getDefinitionAtPosition.ts b/tests/cases/fourslash/shims/getDefinitionAtPosition.ts new file mode 100644 index 00000000000..54b799ba65f --- /dev/null +++ b/tests/cases/fourslash/shims/getDefinitionAtPosition.ts @@ -0,0 +1,29 @@ +/// + +// @Filename: goToDefinitionDifferentFile_Definition.ts +////var /*remoteVariableDefinition*/remoteVariable; +/////*remoteFunctionDefinition*/function remoteFunction() { } +/////*remoteClassDefinition*/class remoteClass { } +/////*remoteInterfaceDefinition*/interface remoteInterface{ } +/////*remoteModuleDefinition*/module remoteModule{ export var foo = 1;} + +// @Filename: goToDefinitionDifferentFile_Consumption.ts +/////*remoteVariableReference*/remoteVariable = 1; +/////*remoteFunctionReference*/remoteFunction(); +////var foo = new /*remoteClassReference*/remoteClass(); +////class fooCls implements /*remoteInterfaceReference*/remoteInterface { } +////var fooVar = /*remoteModuleReference*/remoteModule.foo; + +var markerList = [ + "remoteVariable", + "remoteFunction", + "remoteClass", + "remoteInterface", + "remoteModule", +]; + +markerList.forEach((marker) => { + goTo.marker(marker + 'Reference'); + goTo.definition(); + verify.caretAtMarker(marker + 'Definition'); +}); diff --git a/tests/cases/fourslash/shims/getEmitOutput.ts b/tests/cases/fourslash/shims/getEmitOutput.ts new file mode 100644 index 00000000000..699514521ed --- /dev/null +++ b/tests/cases/fourslash/shims/getEmitOutput.ts @@ -0,0 +1,22 @@ +/// + +// @BaselineFile: getEmitOutput.baseline +// @declaration: true + +// @Filename: inputFile1.ts +// @emitThisFile: true +//// var x: number = 5; +//// class Bar { +//// x : string; +//// y : number +//// } + +// @Filename: inputFile2.ts +// @emitThisFile: true +//// var x1: string = "hello world"; +//// class Foo{ +//// x : string; +//// y : number; +//// } + +verify.baselineGetEmitOutput(); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getIndentationAtPosition.ts b/tests/cases/fourslash/shims/getIndentationAtPosition.ts new file mode 100644 index 00000000000..5e26781357e --- /dev/null +++ b/tests/cases/fourslash/shims/getIndentationAtPosition.ts @@ -0,0 +1,21 @@ +/// + +////class Bar { +//// {| "indentation": 4|} +//// private foo: string = ""; +//// {| "indentation": 4|} +//// private f() { +//// var a: any[] = [[1, 2], [3, 4], 5]; +//// {| "indentation": 8|} +//// return ((1 + 1)); +//// } +//// {| "indentation": 4|} +//// private f2() { +//// if (true) { } { }; +//// } +////} +////{| "indentation": 0|} + +test.markers().forEach((marker) => { + verify.indentationAtPositionIs(marker.fileName, marker.position, marker.data.indentation); +}); diff --git a/tests/cases/fourslash/shims/getNavigateToItems.ts b/tests/cases/fourslash/shims/getNavigateToItems.ts new file mode 100644 index 00000000000..9658c52324c --- /dev/null +++ b/tests/cases/fourslash/shims/getNavigateToItems.ts @@ -0,0 +1,27 @@ +/// + +/////// Module +////{| "itemName": "Shapes", "kind": "module", "parentName": "" |}module Shapes { +//// +//// // Class +//// {| "itemName": "Point", "kind": "class", "parentName": "Shapes" |}export class Point { +//// // Instance member +//// {| "itemName": "origin", "kind": "property", "parentName": "Point", "matchKind": "exact"|}private origin = 0.0; +//// +//// {| "itemName": "distFromZero", "kind": "property", "parentName": "Point", "matchKind": "exact"|}private distFromZero = 0.0; +//// +//// // Getter +//// {| "itemName": "distance", "kind": "getter", "parentName": "Point", "matchKind": "exact" |}get distance(): number { return 0; } +//// } +////} +//// +////// Local variables +////{| "itemName": "point", "kind": "var", "parentName": "", "matchKind": "exact"|}var point = new Shapes.Point(); + +//// Testing for exact matching of navigationItems + +test.markers().forEach((marker) => { + if (marker.data) { + verify.navigationItemsListContains(marker.data.itemName, marker.data.kind, marker.data.itemName, marker.data.matchKind, marker.fileName, marker.data.parentName); + } +}); diff --git a/tests/cases/fourslash/shims/getNavigationBarItems.ts b/tests/cases/fourslash/shims/getNavigationBarItems.ts new file mode 100644 index 00000000000..6c0738747f3 --- /dev/null +++ b/tests/cases/fourslash/shims/getNavigationBarItems.ts @@ -0,0 +1,13 @@ +/// + +//// {| "itemName": "c", "kind": "const", "parentName": "" |}const c = 0; + +test.markers().forEach(marker => { + verify.getScriptLexicalStructureListContains( + marker.data.itemName, + marker.data.kind, + marker.fileName, + marker.data.parentName, + marker.data.isAdditionalRange, + marker.position); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getOccurrencesAtPosition.ts b/tests/cases/fourslash/shims/getOccurrencesAtPosition.ts new file mode 100644 index 00000000000..0654cc3962c --- /dev/null +++ b/tests/cases/fourslash/shims/getOccurrencesAtPosition.ts @@ -0,0 +1,18 @@ +/// + +/////*0*/ +////interface A { +//// foo: string; +////} +////function foo(x: A) { +//// x.f/*1*/oo +////} + +goTo.marker("1"); +verify.occurrencesAtPositionCount(2); + +goTo.marker("0"); +edit.insert("\r\n"); + +goTo.marker("1"); +verify.occurrencesAtPositionCount(2); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getOutliningSpans.ts b/tests/cases/fourslash/shims/getOutliningSpans.ts new file mode 100644 index 00000000000..93665889eb4 --- /dev/null +++ b/tests/cases/fourslash/shims/getOutliningSpans.ts @@ -0,0 +1,113 @@ +/// + +////// 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()[| { +//// +//// }|] +////}|] +////switch(1)[| { +//// case 1: break; +////}|] +//// +////var array =[| [ +//// 1, +//// 2 +////]|] +//// +////// 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()); diff --git a/tests/cases/fourslash/shims/getPreProcessedFile.ts b/tests/cases/fourslash/shims/getPreProcessedFile.ts new file mode 100644 index 00000000000..abd26bb6e4b --- /dev/null +++ b/tests/cases/fourslash/shims/getPreProcessedFile.ts @@ -0,0 +1,32 @@ +/// + +// @Filename: refFile1.ts +//// class D { } + +// @Filename: refFile2.ts +//// export class E {} + +// @Filename: main.ts +// @ResolveReference: true +//// /// +//// /*1*/////*2*/ +//// /*3*/////*4*/ +//// import ref2 = require("refFile2"); +//// import noExistref2 = require(/*5*/"NotExistRefFile2"/*6*/); +//// import invalidRef1 /*7*/require/*8*/("refFile2"); +//// /*9*/import invalidRef2 = requi/*10*/("refFile2"); +//// var obj: /*11*/C/*12*/; +//// var obj1: D; +//// var obj2: ref2.E; + +goTo.file("main.ts"); +verify.numberOfErrorsInCurrentFile(7); +verify.errorExistsBetweenMarkers("1", "2"); +verify.errorExistsBetweenMarkers("3", "4"); +verify.errorExistsBetweenMarkers("5", "6"); +verify.errorExistsBetweenMarkers("7", "8"); +verify.errorExistsBetweenMarkers("9", "10"); // At this position, there are two diagnostic messages: ';' expected, Cannot find name 'requi' +verify.errorExistsBetweenMarkers("11", "12"); + + + diff --git a/tests/cases/fourslash/shims/getQuickInfoAtPosition.ts b/tests/cases/fourslash/shims/getQuickInfoAtPosition.ts new file mode 100644 index 00000000000..6c9fc7cf795 --- /dev/null +++ b/tests/cases/fourslash/shims/getQuickInfoAtPosition.ts @@ -0,0 +1,16 @@ +/// + +////class SS{} +//// +////var x/*1*/1 = new SS(); +////var x/*2*/2 = new SS(); +////var x/*3*/3 = new SS; + +goTo.marker('1'); +verify.quickInfoIs('(var) x1: SS'); + +goTo.marker('2'); +verify.quickInfoIs('(var) x2: SS<{}>'); + +goTo.marker('3'); +verify.quickInfoIs('(var) x3: SS<{}>'); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getReferencesAtPosition.ts b/tests/cases/fourslash/shims/getReferencesAtPosition.ts new file mode 100644 index 00000000000..34144b74899 --- /dev/null +++ b/tests/cases/fourslash/shims/getReferencesAtPosition.ts @@ -0,0 +1,29 @@ +/// + +//@Filename: findAllRefsOnDefinition-import.ts +////export class Test{ +//// +//// constructor(){ +//// +//// } +//// +//// public /*1*/start(){ +//// return this; +//// } +//// +//// public stop(){ +//// return this; +//// } +////} + +//@Filename: findAllRefsOnDefinition.ts +////import Second = require("findAllRefsOnDefinition-import"); +//// +////var second = new Second.Test() +////second.start(); +////second.stop(); + +goTo.file("findAllRefsOnDefinition-import.ts"); +goTo.marker("1"); + +verify.referencesCountIs(2); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getRenameInfo.ts b/tests/cases/fourslash/shims/getRenameInfo.ts new file mode 100644 index 00000000000..b5c8ac6aacc --- /dev/null +++ b/tests/cases/fourslash/shims/getRenameInfo.ts @@ -0,0 +1,11 @@ +/// + +/////// + +////function /**/[|Bar|]() { +//// // This is a reference to Bar in a comment. +//// "this is a reference to Bar in a string" +////} + +goTo.marker(); +verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ false); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getSemanticClassifications.ts b/tests/cases/fourslash/shims/getSemanticClassifications.ts new file mode 100644 index 00000000000..4eb7f2a32ed --- /dev/null +++ b/tests/cases/fourslash/shims/getSemanticClassifications.ts @@ -0,0 +1,15 @@ +/// + +//// module /*0*/M { +//// export interface /*1*/I { +//// } +//// } +//// interface /*2*/X extends /*3*/M./*4*/I { } + +var c = classification; +verify.semanticClassificationsAre( + c.moduleName("M", test.marker("0").position), + c.interfaceName("I", test.marker("1").position), + c.interfaceName("X", test.marker("2").position), + c.moduleName("M", test.marker("3").position), + c.interfaceName("I", test.marker("4").position)); diff --git a/tests/cases/fourslash/shims/getSemanticDiagnostics.ts b/tests/cases/fourslash/shims/getSemanticDiagnostics.ts new file mode 100644 index 00000000000..6345c464213 --- /dev/null +++ b/tests/cases/fourslash/shims/getSemanticDiagnostics.ts @@ -0,0 +1,11 @@ +/// + +// @module: CommonJS +// @declaration: true +//// interface privateInterface {} +//// export class Bar implements /*1*/privateInterface/*2*/{ } + +verify.errorExistsBetweenMarkers("1", "2"); +verify.numberOfErrorsInCurrentFile(1); + + diff --git a/tests/cases/fourslash/shims/getSignatureHelpItems.ts b/tests/cases/fourslash/shims/getSignatureHelpItems.ts new file mode 100644 index 00000000000..846c2d5244a --- /dev/null +++ b/tests/cases/fourslash/shims/getSignatureHelpItems.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: signatureHelpInFunctionCallOnFunctionDeclarationInMultipleFiles_file0.ts +////declare function fn(x: string, y: number); + +// @Filename: signatureHelpInFunctionCallOnFunctionDeclarationInMultipleFiles_file1.ts +////declare function fn(x: string); + +// @Filename: signatureHelpInFunctionCallOnFunctionDeclarationInMultipleFiles_file2.ts +////fn(/*1*/ + +goTo.marker('1'); +verify.signatureHelpCountIs(2); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getSyntacticClassifications.ts b/tests/cases/fourslash/shims/getSyntacticClassifications.ts new file mode 100644 index 00000000000..88f655683a3 --- /dev/null +++ b/tests/cases/fourslash/shims/getSyntacticClassifications.ts @@ -0,0 +1,35 @@ +/// + +//// // comment +//// module M { +//// var v = 0 + 1; +//// var s = "string"; +//// +//// class C { +//// } +//// +//// enum E { +//// } +//// +//// interface I { +//// } +//// +//// module M1.M2 { +//// } +//// } + +var c = classification; +verify.syntacticClassificationsAre( + c.comment("// comment"), + c.keyword("module"), c.moduleName("M"), c.punctuation("{"), + c.keyword("var"), c.text("v"), c.operator("="), c.numericLiteral("0"), c.operator("+"), c.numericLiteral("1"), c.punctuation(";"), + c.keyword("var"), c.text("s"), c.operator("="), c.stringLiteral('"string"'), c.punctuation(";"), + c.keyword("class"), c.className("C"), c.punctuation("<"), c.typeParameterName("T"), c.punctuation(">"), c.punctuation("{"), + c.punctuation("}"), + c.keyword("enum"), c.enumName("E"), c.punctuation("{"), + c.punctuation("}"), + c.keyword("interface"), c.interfaceName("I"), c.punctuation("{"), + c.punctuation("}"), + c.keyword("module"), c.moduleName("M1"), c.punctuation("."), c.moduleName("M2"), c.punctuation("{"), + c.punctuation("}"), + c.punctuation("}")); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/getTodoComments.ts b/tests/cases/fourslash/shims/getTodoComments.ts new file mode 100644 index 00000000000..b1e0086b93f --- /dev/null +++ b/tests/cases/fourslash/shims/getTodoComments.ts @@ -0,0 +1,3 @@ +//// // [|TODO|] + +verify.todoCommentsInCurrentFile(["TODO"]); \ No newline at end of file diff --git a/tests/cases/fourslash/shims/quickInfoDisplayPartsVar.ts b/tests/cases/fourslash/shims/quickInfoDisplayPartsVar.ts new file mode 100644 index 00000000000..56ccceb3ab3 --- /dev/null +++ b/tests/cases/fourslash/shims/quickInfoDisplayPartsVar.ts @@ -0,0 +1,76 @@ +/// + +////var /*1*/a = 10; +////function foo() { +//// var /*2*/b = /*3*/a; +////} +////module m { +//// var /*4*/c = 10; +//// export var /*5*/d = 10; +////} +////var /*6*/f: () => number; +////var /*7*/g = /*8*/f; +/////*9*/f(); +////var /*10*/h: { (a: string): number; (a: number): string; }; +////var /*11*/i = /*12*/h; +/////*13*/h(10); +/////*14*/h("hello"); + +var marker = 0; +function verifyVar(name: string, isLocal: boolean, typeDisplay: ts.SymbolDisplayPart[], optionalNameDisplay?: ts.SymbolDisplayPart[], optionalKindModifiers?: string) { + marker++; + goTo.marker(marker.toString()); + var kind = isLocal ? "local var" : "var"; + verify.verifyQuickInfoDisplayParts(kind, optionalKindModifiers || "", { start: test.markerByName(marker.toString()).position, length: name.length }, + [{ text: "(", kind: "punctuation" }, { text: kind, kind: "text" }, { text: ")", kind: "punctuation" }, + { text: " ", kind: "space" }].concat(optionalNameDisplay || [{ text: name, kind: "localName" }]).concat( + { text: ":", kind: "punctuation" }, { text: " ", kind: "space" }).concat(typeDisplay), + []); +} + +var numberTypeDisplay: ts.SymbolDisplayPart[] = [{ text: "number", kind: "keyword" }]; + +verifyVar("a", /*isLocal*/false, numberTypeDisplay); +verifyVar("b", /*isLocal*/true, numberTypeDisplay); +verifyVar("a", /*isLocal*/false, numberTypeDisplay); +verifyVar("c", /*isLocal*/false, numberTypeDisplay); +verifyVar("d", /*isLocal*/false, numberTypeDisplay, [{ text: "m", kind: "moduleName" }, { text: ".", kind: "punctuation" }, { text: "d", kind: "localName" }], "export"); + +var functionTypeReturningNumber: ts.SymbolDisplayPart[] = [{ text: "(", kind: "punctuation" }, { text: ")", kind: "punctuation" }, + { text: " ", kind: "space" }, { text: "=>", kind: "punctuation" }, { text: " ", kind: "space" }, { text: "number", kind: "keyword" }]; +verifyVar("f", /*isLocal*/ false, functionTypeReturningNumber); +verifyVar("g", /*isLocal*/ false, functionTypeReturningNumber); +verifyVar("f", /*isLocal*/ false, functionTypeReturningNumber); +verifyVar("f", /*isLocal*/ false, functionTypeReturningNumber); + + +function getFunctionType(parametertype: string, returnType: string, isArrow?: boolean): ts.SymbolDisplayPart[] { + var functionTypeDisplay = [{ text: "(", kind: "punctuation" }, { text: "a", kind: "parameterName" }, { text: ":", kind: "punctuation" }, + { text: " ", kind: "space" }, { text: parametertype, kind: "keyword" }, { text: ")", kind: "punctuation" }]; + + if (isArrow) { + functionTypeDisplay = functionTypeDisplay.concat({ text: " ", kind: "space" }, { text: "=>", kind: "punctuation" }); + } + else { + functionTypeDisplay = functionTypeDisplay.concat({ text: ":", kind: "punctuation" }); + } + + return functionTypeDisplay.concat({ text: " ", kind: "space" }, { text: returnType, kind: "keyword" }); +} + +var typeLiteralWithOverloadCall: ts.SymbolDisplayPart[] = [{ text: "{", kind: "punctuation" }, { text: "\n", kind: "lineBreak" }, + { text: " ", kind: "space" }].concat(getFunctionType("string", "number")).concat( + { text: ";", kind: "punctuation" }, { text: "\n", kind: "lineBreak" }, + { text: " ", kind: "space" }).concat(getFunctionType("number", "string")).concat( + { text: ";", kind: "punctuation" }, { text: "\n", kind: "lineBreak" }, { text: "}", kind: "punctuation" }); + +verifyVar("h", /*isLocal*/ false, typeLiteralWithOverloadCall); +verifyVar("i", /*isLocal*/ false, typeLiteralWithOverloadCall); +verifyVar("h", /*isLocal*/ false, typeLiteralWithOverloadCall); + +var overloadDisplay: ts.SymbolDisplayPart[] = [{ text: " ", kind: "space" }, { text: "(", kind: "punctuation" }, + { text: "+", kind: "operator" }, { text: "1", kind: "numericLiteral" }, + { text: " ", kind: "space" }, { text: "overload", kind: "text" }, { text: ")", kind: "punctuation" }]; + +verifyVar("h", /*isLocal*/ false, getFunctionType("number", "string", /*isArrow*/true).concat(overloadDisplay)); +verifyVar("h", /*isLocal*/ false, getFunctionType("string", "number", /*isArrow*/true).concat(overloadDisplay)); \ No newline at end of file From bbe51cfafe00e371bbb3e102d9ab50726f098b66 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 4 Feb 2015 15:39:57 -0800 Subject: [PATCH 13/32] Adding tests --- .../reference/callWithSpread.errors.txt | 59 ++++++ tests/baselines/reference/callWithSpread.js | 117 +++++++++++ .../baselines/reference/callWithSpreadES6.js | 105 ++++++++++ .../reference/callWithSpreadES6.types | 196 ++++++++++++++++++ .../functionCalls/callWithSpread.ts | 52 +++++ .../functionCalls/callWithSpreadES6.ts | 54 +++++ 6 files changed, 583 insertions(+) create mode 100644 tests/baselines/reference/callWithSpread.errors.txt create mode 100644 tests/baselines/reference/callWithSpread.js create mode 100644 tests/baselines/reference/callWithSpreadES6.js create mode 100644 tests/baselines/reference/callWithSpreadES6.types create mode 100644 tests/cases/conformance/expressions/functionCalls/callWithSpread.ts create mode 100644 tests/cases/conformance/expressions/functionCalls/callWithSpreadES6.ts diff --git a/tests/baselines/reference/callWithSpread.errors.txt b/tests/baselines/reference/callWithSpread.errors.txt new file mode 100644 index 00000000000..7ea026f4346 --- /dev/null +++ b/tests/baselines/reference/callWithSpread.errors.txt @@ -0,0 +1,59 @@ +tests/cases/conformance/expressions/functionCalls/callWithSpread.ts(52,21): error TS2468: Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher. + + +==== tests/cases/conformance/expressions/functionCalls/callWithSpread.ts (1 errors) ==== + interface X { + foo(x: number, y: number, ...z: string[]); + } + + function foo(x: number, y: number, ...z: string[]) { + } + + var a: string[]; + var z: number[]; + var obj: X; + var xa: X[]; + + foo(1, 2, "abc"); + foo(1, 2, ...a); + foo(1, 2, ...a, "abc"); + + obj.foo(1, 2, "abc"); + obj.foo(1, 2, ...a); + obj.foo(1, 2, ...a, "abc"); + + (obj.foo)(1, 2, "abc"); + (obj.foo)(1, 2, ...a); + (obj.foo)(1, 2, ...a, "abc"); + + xa[1].foo(1, 2, "abc"); + xa[1].foo(1, 2, ...a); + xa[1].foo(1, 2, ...a, "abc"); + + (xa[1].foo)(...[1, 2, "abc"]); + + class C { + constructor(x: number, y: number, ...z: string[]) { + this.foo(x, y); + this.foo(x, y, ...z); + } + foo(x: number, y: number, ...z: string[]) { + } + } + + class D extends C { + constructor() { + super(1, 2); + super(1, 2, ...a); + } + foo() { + super.foo(1, 2); + super.foo(1, 2, ...a); + } + } + + // Only supported in when target is ES6 + var c = new C(1, 2, ...a); + ~~~~ +!!! error TS2468: Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher. + \ No newline at end of file diff --git a/tests/baselines/reference/callWithSpread.js b/tests/baselines/reference/callWithSpread.js new file mode 100644 index 00000000000..d676c0f2a33 --- /dev/null +++ b/tests/baselines/reference/callWithSpread.js @@ -0,0 +1,117 @@ +//// [callWithSpread.ts] +interface X { + foo(x: number, y: number, ...z: string[]); +} + +function foo(x: number, y: number, ...z: string[]) { +} + +var a: string[]; +var z: number[]; +var obj: X; +var xa: X[]; + +foo(1, 2, "abc"); +foo(1, 2, ...a); +foo(1, 2, ...a, "abc"); + +obj.foo(1, 2, "abc"); +obj.foo(1, 2, ...a); +obj.foo(1, 2, ...a, "abc"); + +(obj.foo)(1, 2, "abc"); +(obj.foo)(1, 2, ...a); +(obj.foo)(1, 2, ...a, "abc"); + +xa[1].foo(1, 2, "abc"); +xa[1].foo(1, 2, ...a); +xa[1].foo(1, 2, ...a, "abc"); + +(xa[1].foo)(...[1, 2, "abc"]); + +class C { + constructor(x: number, y: number, ...z: string[]) { + this.foo(x, y); + this.foo(x, y, ...z); + } + foo(x: number, y: number, ...z: string[]) { + } +} + +class D extends C { + constructor() { + super(1, 2); + super(1, 2, ...a); + } + foo() { + super.foo(1, 2); + super.foo(1, 2, ...a); + } +} + +// Only supported in when target is ES6 +var c = new C(1, 2, ...a); + + +//// [callWithSpread.js] +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; +function foo(x, y) { + var z = []; + for (var _i = 2; _i < arguments.length; _i++) { + z[_i - 2] = arguments[_i]; + } +} +var a; +var z; +var obj; +var xa; +foo(1, 2, "abc"); +foo.apply(void 0, [1, 2].concat(a)); +foo.apply(void 0, [1, 2].concat(a, ["abc"])); +obj.foo(1, 2, "abc"); +obj.foo.apply(obj, [1, 2].concat(a)); +obj.foo.apply(obj, [1, 2].concat(a, ["abc"])); +(obj.foo)(1, 2, "abc"); +obj.foo.apply(obj, [1, 2].concat(a)); +obj.foo.apply(obj, [1, 2].concat(a, ["abc"])); +xa[1].foo(1, 2, "abc"); +(_a = xa[1]).foo.apply(_a, [1, 2].concat(a)); +(_b = xa[1]).foo.apply(_b, [1, 2].concat(a, ["abc"])); +(_c = xa[1]).foo.apply(_c, [1, 2, "abc"]); +var C = (function () { + function C(x, y) { + var z = []; + for (var _i = 2; _i < arguments.length; _i++) { + z[_i - 2] = arguments[_i]; + } + this.foo(x, y); + this.foo.apply(this, [x, y].concat(z)); + } + C.prototype.foo = function (x, y) { + var z = []; + for (var _i = 2; _i < arguments.length; _i++) { + z[_i - 2] = arguments[_i]; + } + }; + return C; +})(); +var D = (function (_super) { + __extends(D, _super); + function D() { + _super.call(this, 1, 2); + _super.apply(this, [1, 2].concat(a)); + } + D.prototype.foo = function () { + _super.prototype.foo.call(this, 1, 2); + _super.prototype.foo.apply(this, [1, 2].concat(a)); + }; + return D; +})(C); +// Only supported in when target is ES6 +var c = new C(1, 2, ...a); +var _a, _b, _c; diff --git a/tests/baselines/reference/callWithSpreadES6.js b/tests/baselines/reference/callWithSpreadES6.js new file mode 100644 index 00000000000..bdb5aab33ec --- /dev/null +++ b/tests/baselines/reference/callWithSpreadES6.js @@ -0,0 +1,105 @@ +//// [callWithSpreadES6.ts] + +interface X { + foo(x: number, y: number, ...z: string[]); +} + +function foo(x: number, y: number, ...z: string[]) { +} + +var a: string[]; +var z: number[]; +var obj: X; +var xa: X[]; + +foo(1, 2, "abc"); +foo(1, 2, ...a); +foo(1, 2, ...a, "abc"); + +obj.foo(1, 2, "abc"); +obj.foo(1, 2, ...a); +obj.foo(1, 2, ...a, "abc"); + +(obj.foo)(1, 2, "abc"); +(obj.foo)(1, 2, ...a); +(obj.foo)(1, 2, ...a, "abc"); + +xa[1].foo(1, 2, "abc"); +xa[1].foo(1, 2, ...a); +xa[1].foo(1, 2, ...a, "abc"); + +(xa[1].foo)(...[1, 2, "abc"]); + +class C { + constructor(x: number, y: number, ...z: string[]) { + this.foo(x, y); + this.foo(x, y, ...z); + } + foo(x: number, y: number, ...z: string[]) { + } +} + +class D extends C { + constructor() { + super(1, 2); + super(1, 2, ...a); + } + foo() { + super.foo(1, 2); + super.foo(1, 2, ...a); + } +} + +// Only supported in when target is ES6 +var c = new C(1, 2, ...a); + + +//// [callWithSpreadES6.js] +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; +function foo(x, y, ...z) { +} +var a; +var z; +var obj; +var xa; +foo(1, 2, "abc"); +foo(1, 2, ...a); +foo(1, 2, ...a, "abc"); +obj.foo(1, 2, "abc"); +obj.foo(1, 2, ...a); +obj.foo(1, 2, ...a, "abc"); +(obj.foo)(1, 2, "abc"); +(obj.foo)(1, 2, ...a); +(obj.foo)(1, 2, ...a, "abc"); +xa[1].foo(1, 2, "abc"); +xa[1].foo(1, 2, ...a); +xa[1].foo(1, 2, ...a, "abc"); +xa[1].foo(...[1, 2, "abc"]); +var C = (function () { + function C(x, y, ...z) { + this.foo(x, y); + this.foo(x, y, ...z); + } + C.prototype.foo = function (x, y, ...z) { + }; + return C; +})(); +var D = (function (_super) { + __extends(D, _super); + function D() { + _super.call(this, 1, 2); + _super.call(this, 1, 2, ...a); + } + D.prototype.foo = function () { + _super.prototype.foo.call(this, 1, 2); + _super.prototype.foo.call(this, 1, 2, ...a); + }; + return D; +})(C); +// Only supported in when target is ES6 +var c = new C(1, 2, ...a); diff --git a/tests/baselines/reference/callWithSpreadES6.types b/tests/baselines/reference/callWithSpreadES6.types new file mode 100644 index 00000000000..8a75d21e4ef --- /dev/null +++ b/tests/baselines/reference/callWithSpreadES6.types @@ -0,0 +1,196 @@ +=== tests/cases/conformance/expressions/functionCalls/callWithSpreadES6.ts === + +interface X { +>X : X + + foo(x: number, y: number, ...z: string[]); +>foo : (x: number, y: number, ...z: string[]) => any +>x : number +>y : number +>z : string[] +} + +function foo(x: number, y: number, ...z: string[]) { +>foo : (x: number, y: number, ...z: string[]) => void +>x : number +>y : number +>z : string[] +} + +var a: string[]; +>a : string[] + +var z: number[]; +>z : number[] + +var obj: X; +>obj : X +>X : X + +var xa: X[]; +>xa : X[] +>X : X + +foo(1, 2, "abc"); +>foo(1, 2, "abc") : void +>foo : (x: number, y: number, ...z: string[]) => void + +foo(1, 2, ...a); +>foo(1, 2, ...a) : void +>foo : (x: number, y: number, ...z: string[]) => void +>a : string[] + +foo(1, 2, ...a, "abc"); +>foo(1, 2, ...a, "abc") : void +>foo : (x: number, y: number, ...z: string[]) => void +>a : string[] + +obj.foo(1, 2, "abc"); +>obj.foo(1, 2, "abc") : any +>obj.foo : (x: number, y: number, ...z: string[]) => any +>obj : X +>foo : (x: number, y: number, ...z: string[]) => any + +obj.foo(1, 2, ...a); +>obj.foo(1, 2, ...a) : any +>obj.foo : (x: number, y: number, ...z: string[]) => any +>obj : X +>foo : (x: number, y: number, ...z: string[]) => any +>a : string[] + +obj.foo(1, 2, ...a, "abc"); +>obj.foo(1, 2, ...a, "abc") : any +>obj.foo : (x: number, y: number, ...z: string[]) => any +>obj : X +>foo : (x: number, y: number, ...z: string[]) => any +>a : string[] + +(obj.foo)(1, 2, "abc"); +>(obj.foo)(1, 2, "abc") : any +>(obj.foo) : (x: number, y: number, ...z: string[]) => any +>obj.foo : (x: number, y: number, ...z: string[]) => any +>obj : X +>foo : (x: number, y: number, ...z: string[]) => any + +(obj.foo)(1, 2, ...a); +>(obj.foo)(1, 2, ...a) : any +>(obj.foo) : (x: number, y: number, ...z: string[]) => any +>obj.foo : (x: number, y: number, ...z: string[]) => any +>obj : X +>foo : (x: number, y: number, ...z: string[]) => any +>a : string[] + +(obj.foo)(1, 2, ...a, "abc"); +>(obj.foo)(1, 2, ...a, "abc") : any +>(obj.foo) : (x: number, y: number, ...z: string[]) => any +>obj.foo : (x: number, y: number, ...z: string[]) => any +>obj : X +>foo : (x: number, y: number, ...z: string[]) => any +>a : string[] + +xa[1].foo(1, 2, "abc"); +>xa[1].foo(1, 2, "abc") : any +>xa[1].foo : (x: number, y: number, ...z: string[]) => any +>xa[1] : X +>xa : X[] +>foo : (x: number, y: number, ...z: string[]) => any + +xa[1].foo(1, 2, ...a); +>xa[1].foo(1, 2, ...a) : any +>xa[1].foo : (x: number, y: number, ...z: string[]) => any +>xa[1] : X +>xa : X[] +>foo : (x: number, y: number, ...z: string[]) => any +>a : string[] + +xa[1].foo(1, 2, ...a, "abc"); +>xa[1].foo(1, 2, ...a, "abc") : any +>xa[1].foo : (x: number, y: number, ...z: string[]) => any +>xa[1] : X +>xa : X[] +>foo : (x: number, y: number, ...z: string[]) => any +>a : string[] + +(xa[1].foo)(...[1, 2, "abc"]); +>(xa[1].foo)(...[1, 2, "abc"]) : any +>(xa[1].foo) : Function +>xa[1].foo : Function +>Function : Function +>xa[1].foo : (x: number, y: number, ...z: string[]) => any +>xa[1] : X +>xa : X[] +>foo : (x: number, y: number, ...z: string[]) => any +>[1, 2, "abc"] : (string | number)[] + +class C { +>C : C + + constructor(x: number, y: number, ...z: string[]) { +>x : number +>y : number +>z : string[] + + this.foo(x, y); +>this.foo(x, y) : void +>this.foo : (x: number, y: number, ...z: string[]) => void +>this : C +>foo : (x: number, y: number, ...z: string[]) => void +>x : number +>y : number + + this.foo(x, y, ...z); +>this.foo(x, y, ...z) : void +>this.foo : (x: number, y: number, ...z: string[]) => void +>this : C +>foo : (x: number, y: number, ...z: string[]) => void +>x : number +>y : number +>z : string[] + } + foo(x: number, y: number, ...z: string[]) { +>foo : (x: number, y: number, ...z: string[]) => void +>x : number +>y : number +>z : string[] + } +} + +class D extends C { +>D : D +>C : C + + constructor() { + super(1, 2); +>super(1, 2) : void +>super : typeof C + + super(1, 2, ...a); +>super(1, 2, ...a) : void +>super : typeof C +>a : string[] + } + foo() { +>foo : () => void + + super.foo(1, 2); +>super.foo(1, 2) : void +>super.foo : (x: number, y: number, ...z: string[]) => void +>super : C +>foo : (x: number, y: number, ...z: string[]) => void + + super.foo(1, 2, ...a); +>super.foo(1, 2, ...a) : void +>super.foo : (x: number, y: number, ...z: string[]) => void +>super : C +>foo : (x: number, y: number, ...z: string[]) => void +>a : string[] + } +} + +// Only supported in when target is ES6 +var c = new C(1, 2, ...a); +>c : C +>new C(1, 2, ...a) : C +>C : typeof C +>a : string[] + diff --git a/tests/cases/conformance/expressions/functionCalls/callWithSpread.ts b/tests/cases/conformance/expressions/functionCalls/callWithSpread.ts new file mode 100644 index 00000000000..9acba00697a --- /dev/null +++ b/tests/cases/conformance/expressions/functionCalls/callWithSpread.ts @@ -0,0 +1,52 @@ +interface X { + foo(x: number, y: number, ...z: string[]); +} + +function foo(x: number, y: number, ...z: string[]) { +} + +var a: string[]; +var z: number[]; +var obj: X; +var xa: X[]; + +foo(1, 2, "abc"); +foo(1, 2, ...a); +foo(1, 2, ...a, "abc"); + +obj.foo(1, 2, "abc"); +obj.foo(1, 2, ...a); +obj.foo(1, 2, ...a, "abc"); + +(obj.foo)(1, 2, "abc"); +(obj.foo)(1, 2, ...a); +(obj.foo)(1, 2, ...a, "abc"); + +xa[1].foo(1, 2, "abc"); +xa[1].foo(1, 2, ...a); +xa[1].foo(1, 2, ...a, "abc"); + +(xa[1].foo)(...[1, 2, "abc"]); + +class C { + constructor(x: number, y: number, ...z: string[]) { + this.foo(x, y); + this.foo(x, y, ...z); + } + foo(x: number, y: number, ...z: string[]) { + } +} + +class D extends C { + constructor() { + super(1, 2); + super(1, 2, ...a); + } + foo() { + super.foo(1, 2); + super.foo(1, 2, ...a); + } +} + +// Only supported in when target is ES6 +var c = new C(1, 2, ...a); diff --git a/tests/cases/conformance/expressions/functionCalls/callWithSpreadES6.ts b/tests/cases/conformance/expressions/functionCalls/callWithSpreadES6.ts new file mode 100644 index 00000000000..2f7d16ba368 --- /dev/null +++ b/tests/cases/conformance/expressions/functionCalls/callWithSpreadES6.ts @@ -0,0 +1,54 @@ +// @target: ES6 + +interface X { + foo(x: number, y: number, ...z: string[]); +} + +function foo(x: number, y: number, ...z: string[]) { +} + +var a: string[]; +var z: number[]; +var obj: X; +var xa: X[]; + +foo(1, 2, "abc"); +foo(1, 2, ...a); +foo(1, 2, ...a, "abc"); + +obj.foo(1, 2, "abc"); +obj.foo(1, 2, ...a); +obj.foo(1, 2, ...a, "abc"); + +(obj.foo)(1, 2, "abc"); +(obj.foo)(1, 2, ...a); +(obj.foo)(1, 2, ...a, "abc"); + +xa[1].foo(1, 2, "abc"); +xa[1].foo(1, 2, ...a); +xa[1].foo(1, 2, ...a, "abc"); + +(xa[1].foo)(...[1, 2, "abc"]); + +class C { + constructor(x: number, y: number, ...z: string[]) { + this.foo(x, y); + this.foo(x, y, ...z); + } + foo(x: number, y: number, ...z: string[]) { + } +} + +class D extends C { + constructor() { + super(1, 2); + super(1, 2, ...a); + } + foo() { + super.foo(1, 2); + super.foo(1, 2, ...a); + } +} + +// Only supported in when target is ES6 +var c = new C(1, 2, ...a); From fd2518dcddd664fbad7663e293ba9ecf8a7fc4a0 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 4 Feb 2015 20:07:12 -0800 Subject: [PATCH 14/32] rename type --- src/harness/harnessLanguageService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 2fce141bbf3..15baafb0429 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -78,7 +78,7 @@ module Harness.LanguageService { } } - class ScriptSnapshotShim implements ts.ScriptSnapshotShim { + class ScriptSnapshotProxy implements ts.ScriptSnapshotShim { constructor(public scriptSnapshot: ts.IScriptSnapshot) { } @@ -91,7 +91,7 @@ module Harness.LanguageService { } public getChangeRange(oldScript: ts.ScriptSnapshotShim): string { - var oldShim = oldScript; + var oldShim = oldScript; var range = this.scriptSnapshot.getChangeRange(oldShim.scriptSnapshot); if (range === null) { @@ -248,7 +248,7 @@ module Harness.LanguageService { getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); } getScriptSnapshot(filename: string): ts.ScriptSnapshotShim { var nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(filename); - return nativeScriptSnapshot && new ScriptSnapshotShim(nativeScriptSnapshot); + return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot); } getScriptVersion(filename: string): string { return this.nativeHost.getScriptVersion(filename); } getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } From 68beccc4802d7f294a93e53f52301b7e73568970 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 4 Feb 2015 20:35:21 -0800 Subject: [PATCH 15/32] Fix getFileContents so as not to always return the current file --- src/harness/fourslash.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index eae34cfc082..19ea8927910 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -376,7 +376,7 @@ module FourSlash { } private getFileContent(fileName: string): string { - var script = this.languageServiceAdaptorHost.getScriptInfo(this.activeFile.fileName); + var script = this.languageServiceAdaptorHost.getScriptInfo(fileName); return script.content; } From 0819ca897cf2dc61f8e60f51870e192ae28c764b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 6 Feb 2015 07:39:11 -0800 Subject: [PATCH 16/32] Addressing CR feedback --- src/compiler/emitter.ts | 27 ++++++++++++++++----------- src/compiler/parser.ts | 10 +++------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 86d85f37199..ef4603de575 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2374,7 +2374,7 @@ module ts { i++; } write("["); - emitList(elements, pos, i - pos, multiLine, trailingComma); + emitList(elements, pos, i - pos, multiLine, trailingComma && i === length); write("]"); pos = i; } @@ -2389,17 +2389,17 @@ module ts { var elements = node.elements; if (elements.length === 0) { write("[]"); - return; } - if (languageVersion >= ScriptTarget.ES6) { + else if (languageVersion >= ScriptTarget.ES6) { write("["); - emitList(elements, 0, elements.length, /*multiLine*/(node.flags & NodeFlags.MultiLine) !== 0, + emitList(elements, 0, elements.length, /*multiLine*/ (node.flags & NodeFlags.MultiLine) !== 0, /*trailingComma*/ elements.hasTrailingComma); write("]"); - return; } - emitListWithSpread(elements, /*multiLine*/(node.flags & NodeFlags.MultiLine) !== 0, - /*trailingComma*/ elements.hasTrailingComma); + else { + emitListWithSpread(elements, /*multiLine*/ (node.flags & NodeFlags.MultiLine) !== 0, + /*trailingComma*/ elements.hasTrailingComma); + } } function emitObjectLiteral(node: ObjectLiteralExpression) { @@ -2502,12 +2502,12 @@ module ts { function skipParentheses(node: Expression): Expression { while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression) { - node = (node).expression; + node = (node).expression; } return node; } - function emitTarget(node: Expression): Expression { + function emitCallTarget(node: Expression): Expression { if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword) { emit(node); return node; @@ -2526,12 +2526,14 @@ module ts { var target: Expression; var expr = skipParentheses(node.expression); if (expr.kind === SyntaxKind.PropertyAccessExpression) { - target = emitTarget((expr).expression); + // Target will be emitted as "this" argument + target = emitCallTarget((expr).expression); write("."); emit((expr).name); } else if (expr.kind === SyntaxKind.ElementAccessExpression) { - target = emitTarget((expr).expression); + // Target will be emitted as "this" argument + target = emitCallTarget((expr).expression); write("["); emit((expr).argumentExpression); write("]"); @@ -2546,13 +2548,16 @@ module ts { write(".apply("); if (target) { if (target.kind === SyntaxKind.SuperKeyword) { + // Calls of form super(...) and super.foo(...) emitThis(target); } else { + // Calls of form obj.foo(...) emit(target); } } else { + // Calls of form foo(...) write("void 0"); } write(", "); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 163c8c1272b..937aa2808e1 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3530,12 +3530,6 @@ module ts { return finishNode(node); } - function parseAssignmentExpressionOrOmittedExpression(): Expression { - return token === SyntaxKind.CommaToken - ? createNode(SyntaxKind.OmittedExpression) - : parseAssignmentExpressionOrHigher(); - } - function parseSpreadElement(): Expression { var node = createNode(SyntaxKind.SpreadElementExpression); parseExpected(SyntaxKind.DotDotDotToken); @@ -3544,7 +3538,9 @@ module ts { } function parseArgumentOrArrayLiteralElement(): Expression { - return token === SyntaxKind.DotDotDotToken ? parseSpreadElement() : parseAssignmentExpressionOrOmittedExpression(); + return token === SyntaxKind.DotDotDotToken ? parseSpreadElement() : + token === SyntaxKind.CommaToken ? createNode(SyntaxKind.OmittedExpression) : + parseAssignmentExpressionOrHigher(); } function parseArgumentExpression(): Expression { From f1cb97b6925da108ebdb78122e6d3a919f9b0ac1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 8 Feb 2015 16:10:16 -0800 Subject: [PATCH 17/32] Add additional aggressive checks during incremental parsing. --- src/compiler/parser.ts | 52 ++++++++++++++++--- src/harness/harnessLanguageService.ts | 2 +- src/services/services.ts | 4 +- .../baselines/reference/APISample_compile.js | 4 +- .../reference/APISample_compile.types | 10 ++-- tests/baselines/reference/APISample_linter.js | 4 +- .../reference/APISample_linter.types | 10 ++-- .../reference/APISample_transform.js | 4 +- .../reference/APISample_transform.types | 10 ++-- .../baselines/reference/APISample_watcher.js | 4 +- .../reference/APISample_watcher.types | 10 ++-- 11 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6942be3cf13..88fff6de959 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -357,21 +357,41 @@ module ts { forEachChild(sourceFile, walk); } - function moveElementEntirelyPastChangeRange(element: IncrementalElement, delta: number) { + function shouldCheckNode(node: Node) { + switch (node.kind) { + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.Identifier: + return true; + } + + return false; + } + + function moveElementEntirelyPastChangeRange(element: IncrementalElement, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { if (element.length) { visitArray(element); } else { visitNode(element); } + return; function visitNode(node: IncrementalNode) { + if (aggressiveChecks && shouldCheckNode(node)) { + var text = oldText.substring(node.pos, node.end); + } + // Ditch any existing LS children we may have created. This way we can avoid // moving them forward. node._children = undefined; node.pos += delta; node.end += delta; + if (aggressiveChecks && shouldCheckNode(node)) { + Debug.assert(text === newText.substring(node.pos, node.end)); + } + forEachChild(node, visitNode, visitArray); } @@ -459,14 +479,24 @@ module ts { } } - function updateTokenPositionsAndMarkElements(node: IncrementalNode, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number): void { + function updateTokenPositionsAndMarkElements( + node: IncrementalNode, + changeStart: number, + changeRangeOldEnd: number, + changeRangeNewEnd: number, + delta: number, + oldText: string, + newText: string, + aggressiveChecks: boolean): void { + visitNode(node); + return; function visitNode(child: IncrementalNode) { if (child.pos > changeRangeOldEnd) { // Node is entirely past the change range. We need to move both its pos and // end, forward or backward appropriately. - moveElementEntirelyPastChangeRange(child, delta); + moveElementEntirelyPastChangeRange(child, delta, oldText, newText, aggressiveChecks); return; } @@ -490,7 +520,7 @@ module ts { if (array.pos > changeRangeOldEnd) { // Array is entirely after the change range. We need to move it, and move any of // its children. - moveElementEntirelyPastChangeRange(array, delta); + moveElementEntirelyPastChangeRange(array, delta, oldText, newText, aggressiveChecks); } else { // Check if the element intersects the change range. If it does, then it is not @@ -513,7 +543,6 @@ module ts { } } - function extendToAffectedRange(sourceFile: SourceFile, changeRange: TextChangeRange): TextChangeRange { // Consider the following code: // void foo() { /; } @@ -650,7 +679,9 @@ module ts { // from this SourceFile that are being held onto may change as a result (including // becoming detached from any SourceFile). It is recommended that this SourceFile not // be used once 'update' is called on it. - export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile { + export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { + aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); + if (textChangeRangeIsUnchanged(textChangeRange)) { // if the text didn't change, then we can just return our current source file as-is. return sourceFile; @@ -662,12 +693,19 @@ module ts { return parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion,/*syntaxCursor*/ undefined, /*setNodeParents*/ true) } + var oldText = sourceFile.text; var syntaxCursor = createSyntaxCursor(sourceFile); // Make the actual change larger so that we know to reparse anything whose lookahead // might have intersected the change. var changeRange = extendToAffectedRange(sourceFile, textChangeRange); + // Ensure that extending the affected range only moved the start of the change range + // earlier in the file. + Debug.assert(changeRange.span.start <= textChangeRange.span.start); + Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span)); + Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(textChangeRange))); + // The is the amount the nodes after the edit range need to be adjusted. It can be // positive (if the edit added characters), negative (if the edit deleted characters) // or zero (if this was a pure overwrite with nothing added/removed). @@ -693,7 +731,7 @@ module ts { // Also, mark any syntax elements that intersect the changed span. We know, up front, // that we cannot reuse these elements. updateTokenPositionsAndMarkElements(sourceFile, - changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta); + changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); // Now that we've set up our internal incremental state just proceed and parse the // source file in the normal fashion. When possible the parser will retrieve and diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index bf9c75a7167..595ef7af3f1 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -115,7 +115,7 @@ module Harness.LanguageService { version: string, textChangeRange: ts.TextChangeRange ): ts.SourceFile { - var result = ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, textChangeRange); + var result = ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, textChangeRange, /*aggressiveChecks:*/ true); Utils.assertInvariants(result, /*parent:*/ undefined); return result; } diff --git a/src/services/services.ts b/src/services/services.ts index fcb5c696a38..f1448a3ab0e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1624,7 +1624,7 @@ module ts { export var disableIncrementalParsing = false; - export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile { + export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { var oldText = sourceFile.scriptSnapshot; var newText = scriptSnapshot; @@ -1648,7 +1648,7 @@ module ts { if (version !== sourceFile.version) { // Once incremental parsing is ready, then just call into this function. if (!disableIncrementalParsing) { - var newSourceFile = updateSourceFile(sourceFile, scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange); + var newSourceFile = updateSourceFile(sourceFile, scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange, aggressiveChecks); setSourceFileFields(newSourceFile, scriptSnapshot, version); // after incremental parsing nameTable might not be up-to-date // drop it so it can be lazily recreated later diff --git a/tests/baselines/reference/APISample_compile.js b/tests/baselines/reference/APISample_compile.js index 6840f079972..7d89d88b5b3 100644 --- a/tests/baselines/reference/APISample_compile.js +++ b/tests/baselines/reference/APISample_compile.js @@ -1404,7 +1404,7 @@ declare module "typescript" { function createNode(kind: SyntaxKind): Node; function forEachChild(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T; function modifierToFlag(token: SyntaxKind): NodeFlags; - function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile; + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; function isEvalOrArgumentsIdentifier(node: Node): boolean; function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean): SourceFile; function isLeftHandSideExpression(expr: Expression): boolean; @@ -1895,7 +1895,7 @@ declare module "typescript" { } function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile; var disableIncrementalParsing: boolean; - function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile; + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; function createDocumentRegistry(): DocumentRegistry; function preProcessFile(sourceText: string, readImportFiles?: boolean): PreProcessedFileInfo; function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService; diff --git a/tests/baselines/reference/APISample_compile.types b/tests/baselines/reference/APISample_compile.types index d70172377ca..8cf789bf20f 100644 --- a/tests/baselines/reference/APISample_compile.types +++ b/tests/baselines/reference/APISample_compile.types @@ -4484,13 +4484,14 @@ declare module "typescript" { >SyntaxKind : SyntaxKind >NodeFlags : NodeFlags - function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile; ->updateSourceFile : (sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange) => SourceFile + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +>updateSourceFile : (sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean) => SourceFile >sourceFile : SourceFile >SourceFile : SourceFile >newText : string >textChangeRange : TextChangeRange >TextChangeRange : TextChangeRange +>aggressiveChecks : boolean >SourceFile : SourceFile function isEvalOrArgumentsIdentifier(node: Node): boolean; @@ -5895,8 +5896,8 @@ declare module "typescript" { var disableIncrementalParsing: boolean; >disableIncrementalParsing : boolean - function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile; ->updateLanguageServiceSourceFile : (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange) => SourceFile + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +>updateLanguageServiceSourceFile : (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean) => SourceFile >sourceFile : SourceFile >SourceFile : SourceFile >scriptSnapshot : IScriptSnapshot @@ -5904,6 +5905,7 @@ declare module "typescript" { >version : string >textChangeRange : TextChangeRange >TextChangeRange : TextChangeRange +>aggressiveChecks : boolean >SourceFile : SourceFile function createDocumentRegistry(): DocumentRegistry; diff --git a/tests/baselines/reference/APISample_linter.js b/tests/baselines/reference/APISample_linter.js index c8bec722309..0ce5a277968 100644 --- a/tests/baselines/reference/APISample_linter.js +++ b/tests/baselines/reference/APISample_linter.js @@ -1435,7 +1435,7 @@ declare module "typescript" { function createNode(kind: SyntaxKind): Node; function forEachChild(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T; function modifierToFlag(token: SyntaxKind): NodeFlags; - function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile; + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; function isEvalOrArgumentsIdentifier(node: Node): boolean; function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean): SourceFile; function isLeftHandSideExpression(expr: Expression): boolean; @@ -1926,7 +1926,7 @@ declare module "typescript" { } function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile; var disableIncrementalParsing: boolean; - function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile; + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; function createDocumentRegistry(): DocumentRegistry; function preProcessFile(sourceText: string, readImportFiles?: boolean): PreProcessedFileInfo; function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService; diff --git a/tests/baselines/reference/APISample_linter.types b/tests/baselines/reference/APISample_linter.types index 26254c69d54..115a0f5a16b 100644 --- a/tests/baselines/reference/APISample_linter.types +++ b/tests/baselines/reference/APISample_linter.types @@ -4628,13 +4628,14 @@ declare module "typescript" { >SyntaxKind : SyntaxKind >NodeFlags : NodeFlags - function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile; ->updateSourceFile : (sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange) => SourceFile + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +>updateSourceFile : (sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean) => SourceFile >sourceFile : SourceFile >SourceFile : SourceFile >newText : string >textChangeRange : TextChangeRange >TextChangeRange : TextChangeRange +>aggressiveChecks : boolean >SourceFile : SourceFile function isEvalOrArgumentsIdentifier(node: Node): boolean; @@ -6039,8 +6040,8 @@ declare module "typescript" { var disableIncrementalParsing: boolean; >disableIncrementalParsing : boolean - function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile; ->updateLanguageServiceSourceFile : (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange) => SourceFile + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +>updateLanguageServiceSourceFile : (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean) => SourceFile >sourceFile : SourceFile >SourceFile : SourceFile >scriptSnapshot : IScriptSnapshot @@ -6048,6 +6049,7 @@ declare module "typescript" { >version : string >textChangeRange : TextChangeRange >TextChangeRange : TextChangeRange +>aggressiveChecks : boolean >SourceFile : SourceFile function createDocumentRegistry(): DocumentRegistry; diff --git a/tests/baselines/reference/APISample_transform.js b/tests/baselines/reference/APISample_transform.js index ad66d289eb7..6ecf4e7c395 100644 --- a/tests/baselines/reference/APISample_transform.js +++ b/tests/baselines/reference/APISample_transform.js @@ -1436,7 +1436,7 @@ declare module "typescript" { function createNode(kind: SyntaxKind): Node; function forEachChild(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T; function modifierToFlag(token: SyntaxKind): NodeFlags; - function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile; + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; function isEvalOrArgumentsIdentifier(node: Node): boolean; function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean): SourceFile; function isLeftHandSideExpression(expr: Expression): boolean; @@ -1927,7 +1927,7 @@ declare module "typescript" { } function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile; var disableIncrementalParsing: boolean; - function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile; + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; function createDocumentRegistry(): DocumentRegistry; function preProcessFile(sourceText: string, readImportFiles?: boolean): PreProcessedFileInfo; function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService; diff --git a/tests/baselines/reference/APISample_transform.types b/tests/baselines/reference/APISample_transform.types index c6d74cc811d..e2797fe5267 100644 --- a/tests/baselines/reference/APISample_transform.types +++ b/tests/baselines/reference/APISample_transform.types @@ -4580,13 +4580,14 @@ declare module "typescript" { >SyntaxKind : SyntaxKind >NodeFlags : NodeFlags - function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile; ->updateSourceFile : (sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange) => SourceFile + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +>updateSourceFile : (sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean) => SourceFile >sourceFile : SourceFile >SourceFile : SourceFile >newText : string >textChangeRange : TextChangeRange >TextChangeRange : TextChangeRange +>aggressiveChecks : boolean >SourceFile : SourceFile function isEvalOrArgumentsIdentifier(node: Node): boolean; @@ -5991,8 +5992,8 @@ declare module "typescript" { var disableIncrementalParsing: boolean; >disableIncrementalParsing : boolean - function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile; ->updateLanguageServiceSourceFile : (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange) => SourceFile + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +>updateLanguageServiceSourceFile : (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean) => SourceFile >sourceFile : SourceFile >SourceFile : SourceFile >scriptSnapshot : IScriptSnapshot @@ -6000,6 +6001,7 @@ declare module "typescript" { >version : string >textChangeRange : TextChangeRange >TextChangeRange : TextChangeRange +>aggressiveChecks : boolean >SourceFile : SourceFile function createDocumentRegistry(): DocumentRegistry; diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js index 2add8dfd45e..eb141f20672 100644 --- a/tests/baselines/reference/APISample_watcher.js +++ b/tests/baselines/reference/APISample_watcher.js @@ -1473,7 +1473,7 @@ declare module "typescript" { function createNode(kind: SyntaxKind): Node; function forEachChild(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T; function modifierToFlag(token: SyntaxKind): NodeFlags; - function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile; + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; function isEvalOrArgumentsIdentifier(node: Node): boolean; function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean): SourceFile; function isLeftHandSideExpression(expr: Expression): boolean; @@ -1964,7 +1964,7 @@ declare module "typescript" { } function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile; var disableIncrementalParsing: boolean; - function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile; + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; function createDocumentRegistry(): DocumentRegistry; function preProcessFile(sourceText: string, readImportFiles?: boolean): PreProcessedFileInfo; function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService; diff --git a/tests/baselines/reference/APISample_watcher.types b/tests/baselines/reference/APISample_watcher.types index 3af8537fdb7..692e07f317a 100644 --- a/tests/baselines/reference/APISample_watcher.types +++ b/tests/baselines/reference/APISample_watcher.types @@ -4753,13 +4753,14 @@ declare module "typescript" { >SyntaxKind : SyntaxKind >NodeFlags : NodeFlags - function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile; ->updateSourceFile : (sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange) => SourceFile + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +>updateSourceFile : (sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean) => SourceFile >sourceFile : SourceFile >SourceFile : SourceFile >newText : string >textChangeRange : TextChangeRange >TextChangeRange : TextChangeRange +>aggressiveChecks : boolean >SourceFile : SourceFile function isEvalOrArgumentsIdentifier(node: Node): boolean; @@ -6164,8 +6165,8 @@ declare module "typescript" { var disableIncrementalParsing: boolean; >disableIncrementalParsing : boolean - function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile; ->updateLanguageServiceSourceFile : (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange) => SourceFile + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +>updateLanguageServiceSourceFile : (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean) => SourceFile >sourceFile : SourceFile >SourceFile : SourceFile >scriptSnapshot : IScriptSnapshot @@ -6173,6 +6174,7 @@ declare module "typescript" { >version : string >textChangeRange : TextChangeRange >TextChangeRange : TextChangeRange +>aggressiveChecks : boolean >SourceFile : SourceFile function createDocumentRegistry(): DocumentRegistry; From a82c57c4b936c64bd8377e2f5d50fa49b78e3830 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 8 Feb 2015 16:40:04 -0800 Subject: [PATCH 18/32] Make sure positions of child elements are consistent. --- src/compiler/parser.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 88fff6de959..adf123e6be0 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -393,6 +393,7 @@ module ts { } forEachChild(node, visitNode, visitArray); + checkNodePositions(node, aggressiveChecks); } function visitArray(array: IncrementalNodeArray) { @@ -479,8 +480,19 @@ module ts { } } + function checkNodePositions(node: Node, aggressiveChecks: boolean) { + if (aggressiveChecks) { + var pos = node.pos; + forEachChild(node, child => { + Debug.assert(child.pos >= pos); + pos = child.end; + }); + Debug.assert(pos <= node.end); + } + } + function updateTokenPositionsAndMarkElements( - node: IncrementalNode, + sourceFile: IncrementalNode, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, @@ -489,7 +501,7 @@ module ts { newText: string, aggressiveChecks: boolean): void { - visitNode(node); + visitNode(sourceFile); return; function visitNode(child: IncrementalNode) { @@ -510,6 +522,8 @@ module ts { // Adjust the pos or end (or both) of the intersecting element accordingly. adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); forEachChild(child, visitNode, visitArray); + + checkNodePositions(child, aggressiveChecks); return; } From 1a17fd1daf5737d19defc25167f4ce64a0969284 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 8 Feb 2015 17:30:27 -0800 Subject: [PATCH 19/32] Move assertions into the parsing layer. --- src/compiler/parser.ts | 16 +++++++++++++++- src/services/services.ts | 17 ----------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index adf123e6be0..9ed2f62bf48 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -696,6 +696,21 @@ module ts { export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); + var oldText = sourceFile.text; + if (textChangeRange) { + Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); + + if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { + var oldTextPrefix = oldText.substr(0, textChangeRange.span.start); + var newTextPrefix = newText.substr(0, textChangeRange.span.start); + Debug.assert(oldTextPrefix === newTextPrefix); + + var oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); + var newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); + Debug.assert(oldTextSuffix === newTextSuffix); + } + } + if (textChangeRangeIsUnchanged(textChangeRange)) { // if the text didn't change, then we can just return our current source file as-is. return sourceFile; @@ -707,7 +722,6 @@ module ts { return parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion,/*syntaxCursor*/ undefined, /*setNodeParents*/ true) } - var oldText = sourceFile.text; var syntaxCursor = createSyntaxCursor(sourceFile); // Make the actual change larger so that we know to reparse anything whose lookahead diff --git a/src/services/services.ts b/src/services/services.ts index f1448a3ab0e..56e68b2f5a1 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1625,23 +1625,6 @@ module ts { export var disableIncrementalParsing = false; export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { - if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) { - var oldText = sourceFile.scriptSnapshot; - var newText = scriptSnapshot; - - Debug.assert((oldText.getLength() - textChangeRange.span.length + textChangeRange.newLength) === newText.getLength()); - - if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - var oldTextPrefix = oldText.getText(0, textChangeRange.span.start); - var newTextPrefix = newText.getText(0, textChangeRange.span.start); - Debug.assert(oldTextPrefix === newTextPrefix); - - var oldTextSuffix = oldText.getText(textSpanEnd(textChangeRange.span), oldText.getLength()); - var newTextSuffix = newText.getText(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.getLength()); - Debug.assert(oldTextSuffix === newTextSuffix); - } - } - // If we were given a text change range, and our version or open-ness changed, then // incrementally parse this file. if (textChangeRange) { From ad7c77ea087a03c6ab9420fad7d00c8c149ddd5e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 8 Feb 2015 17:35:54 -0800 Subject: [PATCH 20/32] Check the text change range before and after we expand it. --- src/compiler/parser.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9ed2f62bf48..9eace1946d3 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -683,6 +683,22 @@ module ts { } } + function checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) { + var oldText = sourceFile.text; + if (textChangeRange) { + Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); + + if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) { + var oldTextPrefix = oldText.substr(0, textChangeRange.span.start); + var newTextPrefix = newText.substr(0, textChangeRange.span.start); + Debug.assert(oldTextPrefix === newTextPrefix); + + var oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); + var newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); + Debug.assert(oldTextSuffix === newTextSuffix); + } + } + } // Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter // indicates what changed between the 'text' that this SourceFile has and the 'newText'. @@ -696,21 +712,7 @@ module ts { export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive); - var oldText = sourceFile.text; - if (textChangeRange) { - Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); - - if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) { - var oldTextPrefix = oldText.substr(0, textChangeRange.span.start); - var newTextPrefix = newText.substr(0, textChangeRange.span.start); - Debug.assert(oldTextPrefix === newTextPrefix); - - var oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); - var newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); - Debug.assert(oldTextSuffix === newTextSuffix); - } - } - + checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks); if (textChangeRangeIsUnchanged(textChangeRange)) { // if the text didn't change, then we can just return our current source file as-is. return sourceFile; @@ -722,11 +724,13 @@ module ts { return parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion,/*syntaxCursor*/ undefined, /*setNodeParents*/ true) } + var oldText = sourceFile.text; var syntaxCursor = createSyntaxCursor(sourceFile); // Make the actual change larger so that we know to reparse anything whose lookahead // might have intersected the change. var changeRange = extendToAffectedRange(sourceFile, textChangeRange); + checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks); // Ensure that extending the affected range only moved the start of the change range // earlier in the file. From 9d6b6b422a9b0660a35a556a9b60c4cd9a908ec2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 8 Feb 2015 17:48:56 -0800 Subject: [PATCH 21/32] Rename a few members and clean up comments. --- src/compiler/parser.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9eace1946d3..b6a44229cb2 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -336,11 +336,16 @@ module ts { } 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 + // normally parent references are set during binding. However, for clients that only need + // a syntax tree, and no semantic features, then the binding process is an unnecessary + // overhead. This functions allows us to set all the parents, without all the expense of + // binding. + var parent: Node = sourceFile; - function walk(n: Node): void { + forEachChild(sourceFile, visitNode); + return; + + function visitNode(n: Node): void { // walk down setting parents that differ from the parent we think it should be. This // allows us to quickly bail out of setting parents for subtrees during incremental // parsing @@ -349,12 +354,10 @@ module ts { var saveParent = parent; parent = n; - forEachChild(n, walk); + forEachChild(n, visitNode); parent = saveParent; } } - - forEachChild(sourceFile, walk); } function shouldCheckNode(node: Node) { @@ -721,7 +724,7 @@ module ts { if (sourceFile.statements.length === 0) { // If we don't have any statements in the current source file, then there's no real // way to incrementally parse. So just do a full parse instead. - return parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion,/*syntaxCursor*/ undefined, /*setNodeParents*/ true) + return parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setNodeParents*/ true) } var oldText = sourceFile.text; @@ -744,8 +747,8 @@ module ts { var delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length; // If we added or removed characters during the edit, then we need to go and adjust all - // the nodes after the edit. Those nodes may move forward down (if we inserted chars) - // or they may move backward (if we deleted chars). + // the nodes after the edit. Those nodes may move forward (if we inserted chars) or they + // may move backward (if we deleted chars). // // Doing this helps us out in two ways. First, it means that any nodes/tokens we want // to reuse are already at the appropriate position in the new text. That way when we @@ -1045,6 +1048,7 @@ module ts { fixupParentReferences(sourceFile); } + syntaxCursor = undefined; return sourceFile; function setContextFlag(val: Boolean, flag: ParserContextFlags) { From d0aa7891def3a5b848a06669304cd077169d3c7c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sun, 8 Feb 2015 18:02:13 -0800 Subject: [PATCH 22/32] Add additional incremental assert. --- src/compiler/parser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b6a44229cb2..4ab8d9d1d5c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -580,6 +580,7 @@ module ts { // start of the tree. for (var i = 0; start > 0 && i <= maxLookahead; i++) { var nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start); + Debug.assert(nearestNode.pos <= start); var position = nearestNode.pos; start = Math.max(0, position - 1); @@ -936,7 +937,6 @@ module ts { var identifiers: Map = {}; var identifierCount = 0; var nodeCount = 0; - var scanner: Scanner; var token: SyntaxKind; var sourceFile = createNode(SyntaxKind.SourceFile, /*pos*/ 0); @@ -1029,7 +1029,7 @@ module ts { var parseErrorBeforeNextFinishedNode: boolean = false; // Create and prime the scanner before parsing the source elements. - scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError); + var scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError); token = nextToken(); processReferenceComments(sourceFile); From 08f51b90704d399161ae44d872bc0c0ecb4676c6 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 9 Feb 2015 09:19:50 -0800 Subject: [PATCH 23/32] Respond to code review comments --- src/harness/fourslash.ts | 66 +++++++------- src/harness/harnessLanguageService.ts | 87 +++++++++---------- .../cases/unittests/services/colorization.ts | 4 +- 3 files changed, 76 insertions(+), 81 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index ca860d4cd8a..603a78b5f75 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -246,7 +246,7 @@ module FourSlash { export class TestState { // Language service instance - private languageServiceAdaptorHost: Harness.LanguageService.LanguageServiceAdaptorHost; + private languageServiceAdapterHost: Harness.LanguageService.LanguageServiceAdapterHost; private languageService: ts.LanguageService; private cancellationToken: TestCancellationToken; @@ -272,32 +272,28 @@ module FourSlash { private addMatchedInputFile(referenceFilePath: string) { var inputFile = this.inputFiles[referenceFilePath]; if (inputFile && !Harness.isLibraryFile(referenceFilePath)) { - this.languageServiceAdaptorHost.addScript(referenceFilePath, inputFile); + this.languageServiceAdapterHost.addScript(referenceFilePath, inputFile); } } - private getLanguageServiceAdaptor(testType: FourSlashTestType): - { new (cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions): Harness.LanguageService.LanguageServiceAdaptor } { + private getLanguageServiceAdapter(testType: FourSlashTestType, cancellationToken: TestCancellationToken, compilationOptions: ts.CompilerOptions): Harness.LanguageService.LanguageServiceAdapter { switch (testType) { case FourSlashTestType.Native: - return Harness.LanguageService.NativeLanugageServiceAdaptor; - break; + return new Harness.LanguageService.NativeLanugageServiceAdapter(cancellationToken, compilationOptions); case FourSlashTestType.Shims: - return Harness.LanguageService.ShimLanugageServiceAdaptor; - break; + return new Harness.LanguageService.ShimLanugageServiceAdapter(cancellationToken, compilationOptions); default: throw new Error("Unknown FourSlash test type: "); } } constructor(private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) { - // Create a new Services Adaptor + // Create a new Services Adapter this.cancellationToken = new TestCancellationToken(); var compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions); - var languageserviceAdaptorFactory = this.getLanguageServiceAdaptor(testType); - var languageServiceAdaptor = new languageserviceAdaptorFactory(this.cancellationToken, compilationOptions); - this.languageServiceAdaptorHost = languageServiceAdaptor.getHost(); - this.languageService = languageServiceAdaptor.getLanguageService(); + var languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions); + this.languageServiceAdapterHost = languageServiceAdapter.getHost(); + this.languageService = languageServiceAdapter.getLanguageService(); // Initialize the language service with all the scripts var startResolveFileRef: FourSlashFile; @@ -315,16 +311,16 @@ module FourSlash { if (startResolveFileRef) { // Add the entry-point file itself into the languageServiceShimHost - this.languageServiceAdaptorHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content); + this.languageServiceAdapterHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content); - var resolvedResult = languageServiceAdaptor.getPreProcessedFileInfo(startResolveFileRef.fileName, startResolveFileRef.content); + var resolvedResult = languageServiceAdapter.getPreProcessedFileInfo(startResolveFileRef.fileName, startResolveFileRef.content); var referencedFiles: ts.FileReference[] = resolvedResult.referencedFiles; var importedFiles: ts.FileReference[] = resolvedResult.importedFiles; // Add triple reference files into language-service host ts.forEach(referencedFiles, referenceFile => { // Fourslash insert tests/cases/fourslash into inputFile.unitName so we will properly append the same base directory to refFile path - var referenceFilePath = this.basePath+ '/' + referenceFile.fileName; + var referenceFilePath = this.basePath + '/' + referenceFile.fileName; this.addMatchedInputFile(referenceFilePath); }); @@ -338,16 +334,16 @@ module FourSlash { // Check if no-default-lib flag is false and if so add default library if (!resolvedResult.isLibFile) { - this.languageServiceAdaptorHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text); + this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text); } } else { // resolveReference file-option is not specified then do not resolve any files and include all inputFiles ts.forEachKey(this.inputFiles, fileName => { if (!Harness.isLibraryFile(fileName)) { - this.languageServiceAdaptorHost.addScript(fileName, this.inputFiles[fileName]); + this.languageServiceAdapterHost.addScript(fileName, this.inputFiles[fileName]); } }); - this.languageServiceAdaptorHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text); + this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text); } this.formatCodeOptions = { @@ -376,7 +372,7 @@ module FourSlash { } private getFileContent(fileName: string): string { - var script = this.languageServiceAdaptorHost.getScriptInfo(fileName); + var script = this.languageServiceAdapterHost.getScriptInfo(fileName); return script.content; } @@ -464,7 +460,7 @@ module FourSlash { private getAllDiagnostics(): ts.Diagnostic[] { var diagnostics: ts.Diagnostic[] = []; - var fileNames = this.languageServiceAdaptorHost.getFilenames(); + var fileNames = this.languageServiceAdapterHost.getFilenames(); for (var i = 0, n = fileNames.length; i < n; i++) { diagnostics.push.apply(this.getDiagnostics(fileNames[i])); } @@ -1255,7 +1251,7 @@ module FourSlash { } public printContext() { - ts.forEach(this.languageServiceAdaptorHost.getFilenames(), Harness.IO.log); + ts.forEach(this.languageServiceAdapterHost.getFilenames(), Harness.IO.log); } public deleteChar(count = 1) { @@ -1268,7 +1264,7 @@ module FourSlash { for (var i = 0; i < count; i++) { // Make the edit - this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); + this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch); if (i % checkCadence === 0) { @@ -1295,7 +1291,7 @@ module FourSlash { public replace(start: number, length: number, text: string) { this.taoInvalidReason = 'replace NYI'; - this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, start, start + length, text); + this.languageServiceAdapterHost.editScript(this.activeFile.fileName, start, start + length, text); this.updateMarkersForEdit(this.activeFile.fileName, start, start + length, text); this.checkPostEditInvariants(); } @@ -1310,7 +1306,7 @@ module FourSlash { for (var i = 0; i < count; i++) { offset--; // Make the edit - this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); + this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset + 1, ch); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch); if (i % checkCadence === 0) { @@ -1355,7 +1351,7 @@ module FourSlash { for (var i = 0; i < text.length; i++) { // Make the edit var ch = text.charAt(i); - this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, offset, offset, ch); + this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset, ch); this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, offset); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, ch); @@ -1398,7 +1394,7 @@ module FourSlash { var start = this.currentCaretPosition; var offset = this.currentCaretPosition; - this.languageServiceAdaptorHost.editScript(this.activeFile.fileName, offset, offset, text); + this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset, text); this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, text); this.checkPostEditInvariants(); offset += text.length; @@ -1461,7 +1457,7 @@ module FourSlash { // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters var oldContent = this.getFileContent(this.activeFile.fileName); for (var j = 0; j < edits.length; j++) { - this.languageServiceAdaptorHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText); + this.languageServiceAdapterHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText); this.updateMarkersForEdit(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText); var change = (edits[j].span.start - ts.textSpanEnd(edits[j].span)) + edits[j].newText.length; runningOffset += change; @@ -1742,8 +1738,8 @@ module FourSlash { function jsonMismatchString() { return ts.sys.newLine + - "expected: '" + ts.sys.newLine + JSON.stringify(expected,(k, v) => v, 2) + "'" + ts.sys.newLine + - "actual: '" + ts.sys.newLine + JSON.stringify(actual,(k, v) => v, 2) + "'"; + "expected: '" + ts.sys.newLine + JSON.stringify(expected, (k, v) => v, 2) + "'" + ts.sys.newLine + + "actual: '" + ts.sys.newLine + JSON.stringify(actual, (k, v) => v, 2) + "'"; } } @@ -2017,7 +2013,7 @@ module FourSlash { // The current caret position (in line/col terms) var line = this.getCurrentCaretFilePosition().line; // The line/col of the start of this line - var pos = this.languageServiceAdaptorHost.lineColToPosition(this.activeFile.fileName, line, 1); + var pos = this.languageServiceAdapterHost.lineColToPosition(this.activeFile.fileName, line, 1); // The index of the current file // The text from the start of the line to the end of the file @@ -2037,7 +2033,7 @@ module FourSlash { } private getCurrentCaretFilePosition() { - var result = this.languageServiceAdaptorHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition); + var result = this.languageServiceAdapterHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition); if (result.line >= 0) { result.line++; } @@ -2097,7 +2093,7 @@ module FourSlash { var name = indexOrName; // names are stored in the compiler with this relative path, this allows people to use goTo.file on just the fileName - name = name.indexOf('/') === -1 ? (this.basePath + '/' + name) : name; + name = name.indexOf('/') === -1 ? (this.basePath + '/' + name) : name; var availableNames: string[] = []; var foundIt = false; @@ -2124,7 +2120,7 @@ module FourSlash { } private getLineColStringAtPosition(position: number) { - var pos = this.languageServiceAdaptorHost.positionToZeroBasedLineCol(this.activeFile.fileName, position); + var pos = this.languageServiceAdapterHost.positionToZeroBasedLineCol(this.activeFile.fileName, position); return 'line ' + (pos.line + 1) + ', col ' + pos.character; } @@ -2285,7 +2281,7 @@ module FourSlash { currentFileName = fileName; } - currentFileName = basePath + '/' + match[2]; + currentFileName = basePath + '/' + match[2]; currentFileOptions[match[1]] = match[2]; } else { // Add other fileMetadata flag diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 4ada92e0172..2f95f5072d0 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -113,14 +113,14 @@ module Harness.LanguageService { } } - export interface LanguageServiceAdaptor { - getHost(): LanguageServiceAdaptorHost; + export interface LanguageServiceAdapter { + getHost(): LanguageServiceAdapterHost; getLanguageService(): ts.LanguageService; getClassifier(): ts.Classifier; getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo; } - class LanguageServiceHostBase { + export class LanguageServiceAdapterHost { protected fileNameToScript: ts.Map = {}; constructor(protected cancellationToken: ts.CancellationToken = CancellationToken.None, @@ -194,11 +194,8 @@ module Harness.LanguageService { } } - export interface LanguageServiceAdaptorHost extends LanguageServiceHostBase { - } - - /// Native adabtor - class NativeLanguageServiceHost extends LanguageServiceHostBase implements ts.LanguageServiceHost { + /// Native adapter + class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost { getCompilationSettings(): ts.CompilerOptions { return this.settings; } getCancellationToken(): ts.CancellationToken { return this.cancellationToken; } getCurrentDirectory(): string { return ""; } @@ -217,7 +214,7 @@ module Harness.LanguageService { error(s: string): void { } } - export class NativeLanugageServiceAdaptor implements LanguageServiceAdaptor { + export class NativeLanugageServiceAdapter implements LanguageServiceAdapter { private host: NativeLanguageServiceHost; constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { this.host = new NativeLanguageServiceHost(cancellationToken, options); @@ -228,8 +225,8 @@ module Harness.LanguageService { getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents); } } - /// Shim adabtor - class ShimLanguageServiceHost extends LanguageServiceHostBase implements ts.LanguageServiceShimHost { + /// Shim adapter + class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost { private nativeHost: NativeLanguageServiceHost; constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { super(cancellationToken, options); @@ -261,7 +258,8 @@ module Harness.LanguageService { } class ClassifierShimProxy implements ts.Classifier { - constructor(private shim: ts.ClassifierShim) { } + constructor(private shim: ts.ClassifierShim) { + } getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult { var result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split('\n'); var entries: ts.ClassificationInfo[] = []; @@ -288,7 +286,7 @@ module Harness.LanguageService { } } - function unwrappJSONCallResult(result: string): any { + function unwrapJSONCallResult(result: string): any { var parsedResult = JSON.parse(result); if (parsedResult.error) { throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error)); @@ -300,7 +298,8 @@ module Harness.LanguageService { } class LanguageServiceShimProxy implements ts.LanguageService { - constructor(private shim: ts.LanguageServiceShim) { } + constructor(private shim: ts.LanguageServiceShim) { + } private unwrappJSONCallResult(result: string): any { var parsedResult = JSON.parse(result); if (parsedResult.error) { @@ -312,93 +311,93 @@ module Harness.LanguageService { this.shim.cleanupSemanticCache(); } getSyntacticDiagnostics(fileName: string): ts.Diagnostic[] { - return unwrappJSONCallResult(this.shim.getSyntacticDiagnostics(fileName)); + return unwrapJSONCallResult(this.shim.getSyntacticDiagnostics(fileName)); } getSemanticDiagnostics(fileName: string): ts.Diagnostic[] { - return unwrappJSONCallResult(this.shim.getSemanticDiagnostics(fileName)); + return unwrapJSONCallResult(this.shim.getSemanticDiagnostics(fileName)); } getCompilerOptionsDiagnostics(): ts.Diagnostic[] { - return unwrappJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); + return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); } getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { - return unwrappJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length)); + return unwrapJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length)); } getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { - return unwrappJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length)); + return unwrapJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length)); } getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo { - return unwrappJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position)); + return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position)); } getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails { - return unwrappJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName)); + return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName)); } getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo { - return unwrappJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position)); + return unwrapJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position)); } getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan { - return unwrappJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos)); + return unwrapJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos)); } getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan { - return unwrappJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position)); + return unwrapJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position)); } getSignatureHelpItems(fileName: string, position: number): ts.SignatureHelpItems { - return unwrappJSONCallResult(this.shim.getSignatureHelpItems(fileName, position)); + return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position)); } getRenameInfo(fileName: string, position: number): ts.RenameInfo { - return unwrappJSONCallResult(this.shim.getRenameInfo(fileName, position)); + return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position)); } findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ts.RenameLocation[] { - return unwrappJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments)); + return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments)); } getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { - return unwrappJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position)); + return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position)); } getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { - return unwrappJSONCallResult(this.shim.getReferencesAtPosition(fileName, position)); + return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position)); } getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { - return unwrappJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position)); + return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position)); } getNavigateToItems(searchValue: string): ts.NavigateToItem[] { - return unwrappJSONCallResult(this.shim.getNavigateToItems(searchValue)); + return unwrapJSONCallResult(this.shim.getNavigateToItems(searchValue)); } getNavigationBarItems(fileName: string): ts.NavigationBarItem[] { - return unwrappJSONCallResult(this.shim.getNavigationBarItems(fileName)); + return unwrapJSONCallResult(this.shim.getNavigationBarItems(fileName)); } getOutliningSpans(fileName: string): ts.OutliningSpan[] { - return unwrappJSONCallResult(this.shim.getOutliningSpans(fileName)); + return unwrapJSONCallResult(this.shim.getOutliningSpans(fileName)); } getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] { - return unwrappJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors))); + return unwrapJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors))); } getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] { - return unwrappJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position)); + return unwrapJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position)); } getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number { - return unwrappJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options))); + return unwrapJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options))); } getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrappJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options))); + return unwrapJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options))); } getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrappJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options))); + return unwrapJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options))); } getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrappJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options))); + return unwrapJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options))); } getEmitOutput(fileName: string): ts.EmitOutput { - return unwrappJSONCallResult(this.shim.getEmitOutput(fileName)); + return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); } getProgram(): ts.Program { - throw new Error("Program can not be marshalled accross the shim layer."); + throw new Error("Program can not be marshaled across the shim layer."); } getSourceFile(fileName: string): ts.SourceFile { - throw new Error("SourceFile can not be marshalled accross the shim layer."); + throw new Error("SourceFile can not be marshaled across the shim layer."); } dispose(): void { this.shim.dispose({}); } } - export class ShimLanugageServiceAdaptor implements LanguageServiceAdaptor { + export class ShimLanugageServiceAdapter implements LanguageServiceAdapter { private host: ShimLanguageServiceHost; private factory: ts.TypeScriptServicesFactory; constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) { @@ -416,7 +415,7 @@ module Harness.LanguageService { }; var coreServicesShim = this.factory.createCoreServicesShim(this.host); - shimResult = unwrappJSONCallResult(coreServicesShim.getPreProcessedFileInfo(fileName, ts.ScriptSnapshot.fromString(fileContents))); + shimResult = unwrapJSONCallResult(coreServicesShim.getPreProcessedFileInfo(fileName, ts.ScriptSnapshot.fromString(fileContents))); var convertResult: ts.PreProcessedFileInfo = { referencedFiles: [], diff --git a/tests/cases/unittests/services/colorization.ts b/tests/cases/unittests/services/colorization.ts index b744b65608b..01149764305 100644 --- a/tests/cases/unittests/services/colorization.ts +++ b/tests/cases/unittests/services/colorization.ts @@ -7,8 +7,8 @@ interface ClassificationEntry { } describe('Colorization', function () { - // Use the shim adaptor to ensure test coverage of the shim layer for the classifier - var languageServiceAdabtor = new Harness.LanguageService.ShimLanugageServiceAdaptor(); + // Use the shim adapter to ensure test coverage of the shim layer for the classifier + var languageServiceAdabtor = new Harness.LanguageService.ShimLanugageServiceAdapter(); var classifier = languageServiceAdabtor.getClassifier(); function getEntryAtPosistion(result: ts.ClassificationResult, position: number) { From 318aa8ce7a47c6b0dd5c12b40f3d219c6d7f4da4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 9 Feb 2015 14:07:09 -0800 Subject: [PATCH 24/32] Don't use dynamic type checks while incrementally parsing. --- src/compiler/parser.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4ab8d9d1d5c..d58a9f15105 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -371,8 +371,8 @@ module ts { return false; } - function moveElementEntirelyPastChangeRange(element: IncrementalElement, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { - if (element.length) { + function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { + if (isArray) { visitArray(element); } else { @@ -511,7 +511,7 @@ module ts { if (child.pos > changeRangeOldEnd) { // Node is entirely past the change range. We need to move both its pos and // end, forward or backward appropriately. - moveElementEntirelyPastChangeRange(child, delta, oldText, newText, aggressiveChecks); + moveElementEntirelyPastChangeRange(child, /*isArray:*/ false, delta, oldText, newText, aggressiveChecks); return; } @@ -537,7 +537,7 @@ module ts { if (array.pos > changeRangeOldEnd) { // Array is entirely after the change range. We need to move it, and move any of // its children. - moveElementEntirelyPastChangeRange(array, delta, oldText, newText, aggressiveChecks); + moveElementEntirelyPastChangeRange(array, /*isArray:*/ true, delta, oldText, newText, aggressiveChecks); } else { // Check if the element intersects the change range. If it does, then it is not From d37fdfe213a64105e7ce41797b202a7f81e9d878 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 9 Feb 2015 14:12:32 -0800 Subject: [PATCH 25/32] Add additional asserts. --- src/compiler/parser.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d58a9f15105..3d512a88e56 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -508,6 +508,7 @@ module ts { return; function visitNode(child: IncrementalNode) { + Debug.assert(child.pos <= child.end); if (child.pos > changeRangeOldEnd) { // Node is entirely past the change range. We need to move both its pos and // end, forward or backward appropriately. @@ -531,9 +532,11 @@ module ts { } // Otherwise, the node is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); } function visitArray(array: IncrementalNodeArray) { + Debug.assert(array.pos <= array.end); if (array.pos > changeRangeOldEnd) { // Array is entirely after the change range. We need to move it, and move any of // its children. From e417f3016b9c5aae660491b5ae6a7c67d12fa648 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 9 Feb 2015 14:23:55 -0800 Subject: [PATCH 26/32] Add additional asserts, and make code more unified. --- src/compiler/parser.ts | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 3d512a88e56..e99b8e71c5e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -400,6 +400,7 @@ module ts { } function visitArray(array: IncrementalNodeArray) { + array._children = undefined; array.pos += delta; array.end += delta; @@ -412,6 +413,7 @@ module ts { function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) { Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range"); Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range"); + Debug.assert(element.pos <= element.end); // We have an element that intersects the change range in some way. It may have its // start, or its end (or both) in the changed range. We want to adjust any part @@ -522,6 +524,7 @@ module ts { var fullEnd = child.end; if (fullEnd >= changeStart) { child.intersectsChange = true; + child._children = undefined; // Adjust the pos or end (or both) of the intersecting element accordingly. adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); @@ -541,25 +544,27 @@ module ts { // Array is entirely after the change range. We need to move it, and move any of // its children. moveElementEntirelyPastChangeRange(array, /*isArray:*/ true, delta, oldText, newText, aggressiveChecks); + return; } - else { - // Check if the element intersects the change range. If it does, then it is not - // reusable. Also, we'll need to recurse to see what constituent portions we may - // be able to use. - var fullEnd = array.end; - if (fullEnd >= changeStart) { - array.intersectsChange = true; - // Adjust the pos or end (or both) of the intersecting array accordingly. - adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); - for (var i = 0, n = array.length; i < n; i++) { - visitNode(array[i]); - } + // Check if the element intersects the change range. If it does, then it is not + // reusable. Also, we'll need to recurse to see what constituent portions we may + // be able to use. + var fullEnd = array.end; + if (fullEnd >= changeStart) { + array.intersectsChange = true; + array._children = undefined; + + // Adjust the pos or end (or both) of the intersecting array accordingly. + adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); + for (var i = 0, n = array.length; i < n; i++) { + visitNode(array[i]); } - // else { - // Otherwise, the array is entirely before the change range. No need to do anything with it. - // } + return; } + + // Otherwise, the array is entirely before the change range. No need to do anything with it. + Debug.assert(fullEnd < changeStart); } } From a79e8e928be3a4847508959799acfc3a47b2b70a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 9 Feb 2015 14:34:47 -0800 Subject: [PATCH 27/32] Remove code duplication in isModuleElement. --- src/compiler/parser.ts | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e99b8e71c5e..8f7f68d9b6d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1657,8 +1657,8 @@ module ts { return result; } - function parseListElement(kind: ParsingContext, parseElement: () => T): T { - var node = currentNode(kind); + function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { + var node = currentNode(parsingContext); if (node) { return consumeNode(node); } @@ -1815,29 +1815,10 @@ module ts { case SyntaxKind.InterfaceDeclaration: case SyntaxKind.ModuleDeclaration: case SyntaxKind.EnumDeclaration: - - // Keep in sync with isStatement: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.Block: - case SyntaxKind.IfStatement: - case SyntaxKind.ExpressionStatement: - case SyntaxKind.ThrowStatement: - case SyntaxKind.ReturnStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.EmptyStatement: - case SyntaxKind.TryStatement: - case SyntaxKind.LabeledStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.DebuggerStatement: return true; } + + return isReusableStatement(node); } return false; From 17dd6c2de01b4ded0d7afeec39d19f13c0e858a2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 9 Feb 2015 14:40:03 -0800 Subject: [PATCH 28/32] Be more conservative about reusing parameters. --- src/compiler/parser.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 8f7f68d9b6d..cd0d86bed46 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1924,9 +1924,13 @@ module ts { } function isReusableParameter(node: Node) { - // TODO: this most likely needs the same initializer check that - // isReusableVariableDeclaration has. - return node.kind === SyntaxKind.Parameter; + if (node.kind !== SyntaxKind.Parameter) { + return false; + } + + // See the comment in isReusableVariableDeclaration for why we do this. + var parameter = node; + return parameter.initializer === undefined; } // Returns true if we should abort parsing. From 2eb1a213c724bcf429134d09795f8d2bfcf724e6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 9 Feb 2015 14:55:54 -0800 Subject: [PATCH 29/32] Prevent index out of bounds exception. --- src/compiler/parser.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cd0d86bed46..a1dc0ef7b1d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -850,7 +850,7 @@ module ts { // Much of the time the parser will need the very next node in the array that // we just returned a node from.So just simply check for that case and move // forward in the array instead of searching for the node again. - if (current && current.end === position && currentArrayIndex < currentArray.length) { + if (current && current.end === position && currentArrayIndex < (currentArray.length - 1)) { currentArrayIndex++; current = currentArray[currentArrayIndex]; } @@ -886,6 +886,7 @@ module ts { // Recurse into the source file to find the highest node at this position. forEachChild(sourceFile, visitNode, visitArray); + return; function visitNode(node: Node) { if (position >= node.pos && position < node.end) { From 11d19e30190111bde271bf8febc85afe2952d09d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 9 Feb 2015 17:03:46 -0800 Subject: [PATCH 30/32] Fix issue with cancellation corrupting LS state. The problem here was as follows: 1) Host calls into the LS to do some sort of operation. 2) LS tries to synchronize with the host. 3) During synchronization we attempt to create a new program. 4) Creating the new program causes us to incrementally update some source files. 5) Incrementally updating a source file produces a new source file, and invalidates the old one. 6) *Then* the host asks to cancel this operation. 7) THe synchronization process cancels itself, leaving the LS in an inconsistent state where some of its source files have had their trees updated, but the information about the source file still thinks that we have the previous version. The fix is to not allow cancellation during host synchronization. Once we start, we have to go all the way to completion. --- src/services/services.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 56e68b2f5a1..eda3a4aeff6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2022,6 +2022,12 @@ module ts { return; } + // IMPORTANT - It is critical from this moment onward that we do not check + // cancellation tokens. We are about to mutate source files from a previous program + // instance. If we cancel midway through, we may end up in an inconsistent state where + // the program points to old source files that have been invalidated because of + // incremental parsing. + var oldSettings = program && program.getCompilerOptions(); var newSettings = hostCache.compilationSettings(); var changesInCompilationSettingsAffectSyntax = oldSettings && oldSettings.target !== newSettings.target; @@ -2056,8 +2062,6 @@ module ts { return; function getOrCreateSourceFile(fileName: string): SourceFile { - cancellationToken.throwIfCancellationRequested(); - // The program is asking for this file, check first if the host can locate it. // If the host can not locate the file, then it does not exist. return undefined // to the program to allow reporting of errors for missing files. @@ -5363,9 +5367,6 @@ module ts { cancellationToken.throwIfCancellationRequested(); var fileContents = sourceFile.text; - - cancellationToken.throwIfCancellationRequested(); - var result: TodoComment[] = []; if (descriptors.length > 0) { From b86ef44e59053e2c41fa40579454ac1933adab61 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 9 Feb 2015 17:24:01 -0800 Subject: [PATCH 31/32] Add assert that clients do not try to call updateSourceFile multiple times on a source file. --- src/compiler/parser.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a1dc0ef7b1d..06db869ad54 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -736,6 +736,16 @@ module ts { return parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setNodeParents*/ true) } + // Make sure we're not trying to incrementally update a source file more than once. Once + // we do an update the original source file is considered unusbale from that point onwards. + // + // This is because we do incremental parsing in-place. i.e. we take nodes from the old + // tree and give them new positions and parents. From that point on, trusting the old + // tree at all is not possible as far too much of it may violate invariants. + var incrementalSourceFile = sourceFile; + Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); + incrementalSourceFile.hasBeenIncrementallyParsed = true; + var oldText = sourceFile.text; var syntaxCursor = createSyntaxCursor(sourceFile); @@ -774,7 +784,7 @@ module ts { // // Also, mark any syntax elements that intersect the changed span. We know, up front, // that we cannot reuse these elements. - updateTokenPositionsAndMarkElements(sourceFile, + updateTokenPositionsAndMarkElements(incrementalSourceFile, changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks); // Now that we've set up our internal incremental state just proceed and parse the @@ -815,6 +825,7 @@ module ts { } interface IncrementalNode extends Node, IncrementalElement { + hasBeenIncrementallyParsed: boolean } interface IncrementalNodeArray extends NodeArray, IncrementalElement { From f29d931bd95f12c35e971592161c6e3c479e3f18 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 10 Feb 2015 13:36:24 -0800 Subject: [PATCH 32/32] disallow let to be used as name in let\const in ES6 --- src/compiler/checker.ts | 25 ++++++++++++++++++- .../diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 4 +++ .../letInLetOrConstDeclarations.errors.txt | 23 +++++++++++++++++ .../reference/letInLetOrConstDeclarations.js | 25 +++++++++++++++++++ .../compiler/letInLetOrConstDeclarations.ts | 12 +++++++++ 6 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/letInLetOrConstDeclarations.errors.txt create mode 100644 tests/baselines/reference/letInLetOrConstDeclarations.js create mode 100644 tests/cases/compiler/letInLetOrConstDeclarations.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 39dd49176b6..cccd74e3907 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10916,9 +10916,32 @@ module ts { } } } + + var checkLetConstNames = languageVersion >= ScriptTarget.ES6 && (isLet(node) || isConst(node)); + + // 1. LexicalDeclaration : LetOrConst BindingList ; + // It is a Syntax Error if the BoundNames of BindingList contains "let". + // 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding + // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". + // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code // and its Identifier is eval or arguments - return checkGrammarEvalOrArgumentsInStrictMode(node, node.name); + return (checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name)) || + checkGrammarEvalOrArgumentsInStrictMode(node, node.name); + } + + function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if ((name).text === "let") { + return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); + } + } + else { + var elements = (name).elements; + for (var i = 0; i < elements.length; ++i) { + checkGrammarNameInLetOrConstDeclarations(elements[i].name); + } + } } function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index a0e323dca1c..6c79578e41c 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -380,6 +380,7 @@ module ts { const_enum_member_initializer_was_evaluated_to_a_non_finite_value: { code: 4086, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to a non-finite value." }, const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN: { code: 4087, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to disallowed value 'NaN'." }, Property_0_does_not_exist_on_const_enum_1: { code: 4088, category: DiagnosticCategory.Error, key: "Property '{0}' does not exist on 'const' enum '{1}'." }, + let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations: { code: 4089, category: DiagnosticCategory.Error, key: "'let' is not allowed to be used as a name in 'let' or 'const' declarations." }, The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." }, Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." }, Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 69db958075f..8a5d8d80427 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1513,6 +1513,10 @@ "category": "Error", "code": 4088 }, + "'let' is not allowed to be used as a name in 'let' or 'const' declarations.": { + "category": "Error", + "code": 4089 + }, "The current host does not support the '{0}' option.": { "category": "Error", "code": 5001 diff --git a/tests/baselines/reference/letInLetOrConstDeclarations.errors.txt b/tests/baselines/reference/letInLetOrConstDeclarations.errors.txt new file mode 100644 index 00000000000..93dc307fc85 --- /dev/null +++ b/tests/baselines/reference/letInLetOrConstDeclarations.errors.txt @@ -0,0 +1,23 @@ +tests/cases/compiler/letInLetOrConstDeclarations.ts(2,9): error TS4089: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. +tests/cases/compiler/letInLetOrConstDeclarations.ts(3,14): error TS4089: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. +tests/cases/compiler/letInLetOrConstDeclarations.ts(6,11): error TS4089: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. + + +==== tests/cases/compiler/letInLetOrConstDeclarations.ts (3 errors) ==== + { + let let = 1; // should error + ~~~ +!!! error TS4089: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. + for (let let in []) { } // should error + ~~~ +!!! error TS4089: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. + } + { + const let = 1; // should error + ~~~ +!!! error TS4089: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. + } + { + function let() { // should be ok + } + } \ No newline at end of file diff --git a/tests/baselines/reference/letInLetOrConstDeclarations.js b/tests/baselines/reference/letInLetOrConstDeclarations.js new file mode 100644 index 00000000000..ee23abc4e4f --- /dev/null +++ b/tests/baselines/reference/letInLetOrConstDeclarations.js @@ -0,0 +1,25 @@ +//// [letInLetOrConstDeclarations.ts] +{ + let let = 1; // should error + for (let let in []) { } // should error +} +{ + const let = 1; // should error +} +{ + function let() { // should be ok + } +} + +//// [letInLetOrConstDeclarations.js] +{ + let let = 1; // should error + for (let let in []) { } // should error +} +{ + const let = 1; // should error +} +{ + function let() { + } +} diff --git a/tests/cases/compiler/letInLetOrConstDeclarations.ts b/tests/cases/compiler/letInLetOrConstDeclarations.ts new file mode 100644 index 00000000000..c622759a459 --- /dev/null +++ b/tests/cases/compiler/letInLetOrConstDeclarations.ts @@ -0,0 +1,12 @@ +// @target: es6 +{ + let let = 1; // should error + for (let let in []) { } // should error +} +{ + const let = 1; // should error +} +{ + function let() { // should be ok + } +} \ No newline at end of file