diff --git a/Jakefile b/Jakefile index 109398b8852..0a34de4182b 100644 --- a/Jakefile +++ b/Jakefile @@ -82,8 +82,9 @@ var harnessSources = [ ].map(function (f) { return path.join(harnessDirectory, f); }).concat([ - "services/colorization.ts", - "services/documentRegistry.ts" + "services/colorization.ts", + "services/documentRegistry.ts", + "services/preProcessFile.ts" ].map(function (f) { return path.join(unittestsDirectory, f); })); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 41243efa171..4a352c30a35 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -745,6 +745,47 @@ module ts { nodeIsNestedInLabel(label: Identifier, requireIterationStatement: boolean, stopAtFunctionBoundary: boolean): ControlBlockContext; } + interface ReferencePathMatchResult { + fileReference?: FileReference + diagnostic?: DiagnosticMessage + isNoDefaultLib?: boolean + } + + export function getFileReferenceFromReferencePath(comment: string, commentRange: CommentRange): ReferencePathMatchResult { + var simpleReferenceRegEx = /^\/\/\/\s*/gim; + if (simpleReferenceRegEx.exec(comment)) { + if (isNoDefaultLibRegEx.exec(comment)) { + return { + isNoDefaultLib: true + } + } + else { + var matchResult = fullTripleSlashReferencePathRegEx.exec(comment); + if (matchResult) { + var start = commentRange.pos; + var end = commentRange.end; + var fileRef = { + pos: start, + end: end, + filename: matchResult[3] + }; + return { + fileReference: fileRef, + isNoDefaultLib: false + }; + } + else { + return { + diagnostic: Diagnostics.Invalid_reference_directive_syntax, + isNoDefaultLib: false + }; + } + } + } + return undefined; + } + export function isKeyword(token: SyntaxKind): boolean { return SyntaxKind.FirstKeyword <= token && token <= SyntaxKind.LastKeyword; } @@ -4168,28 +4209,16 @@ module ts { for (var i = 0; i < commentRanges.length; i++) { var range = commentRanges[i]; var comment = sourceText.substring(range.pos, range.end); - var simpleReferenceRegEx = /^\/\/\/\s*/gim; - if (isNoDefaultLibRegEx.exec(comment)) { - file.hasNoDefaultLib = true; + var referencePathMatchResult = getFileReferenceFromReferencePath(comment, range); + if (referencePathMatchResult) { + var fileReference = referencePathMatchResult.fileReference; + file.hasNoDefaultLib = referencePathMatchResult.isNoDefaultLib; + var diagnostic = referencePathMatchResult.diagnostic; + if (fileReference) { + referencedFiles.push(fileReference); } - else { - var matchResult = fullTripleSlashReferencePathRegEx.exec(comment); - var start = range.pos; - var end = range.end; - var length = end - start; - - if (!matchResult) { - errorAtPos(start, length, Diagnostics.Invalid_reference_directive_syntax); - } - else { - referencedFiles.push({ - pos: start, - end: end, - filename: matchResult[3] - }); - } + if (diagnostic) { + errorAtPos(range.pos, range.end - range.pos, diagnostic); } } else { diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 268a54f8e63..5d8e48da8ca 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -136,10 +136,11 @@ module FourSlash { outDir: 'outDir', sourceMap: 'sourceMap', sourceRoot: 'sourceRoot', + resolveReference: 'ResolveReference', // This flag is used to specify entry file for resolve file references. The flag is only allow once per test file }; // List of allowed metadata names - var fileMetadataNames = [testOptMetadataNames.filename, testOptMetadataNames.emitThisFile]; + var fileMetadataNames = [testOptMetadataNames.filename, testOptMetadataNames.emitThisFile, testOptMetadataNames.resolveReference]; var globalMetadataNames = [testOptMetadataNames.baselineFile, testOptMetadataNames.declaration, testOptMetadataNames.mapRoot, testOptMetadataNames.module, testOptMetadataNames.out, testOptMetadataNames.outDir, testOptMetadataNames.sourceMap, testOptMetadataNames.sourceRoot] @@ -236,6 +237,25 @@ module FourSlash { throw new Error("Operation should be cancelled"); } + // This function creates IScriptSnapshot object for testing getPreProcessedFileInfo + // Return object may lack some functionalities for other purposes. + function createScriptSnapShot(sourceText: string): TypeScript.IScriptSnapshot { + return { + getText: (start: number, end: number) => { + return sourceText.substr(start, end - start); + }, + getLength: () => { + return sourceText.length; + }, + getLineStartPositions: () => { + return []; + }, + getChangeRange: (oldSnapshot: TypeScript.IScriptSnapshot) => { + return undefined; + } + }; + } + export class TestState { // Language service instance public languageServiceShimHost: Harness.LanguageService.TypeScriptLS; @@ -264,6 +284,16 @@ module FourSlash { private scenarioActions: string[] = []; private taoInvalidReason: string = null; + private inputFiles: ts.Map = {}; // Map between inputFile's filename and its content for easily looking up when resolving references + + // Add input file which has matched file name with the given reference-file path. + // This is necessary when resolveReference flag is specified + private addMatchedInputFile(referenceFilePath: string) { + var inputFile = this.inputFiles[referenceFilePath]; + if (inputFile && !Harness.isLibraryFile(referenceFilePath)) { + this.languageServiceShimHost.addScript(referenceFilePath, inputFile); + } + } constructor(public testData: FourSlashData) { // Initialize the language service with all the scripts @@ -273,57 +303,57 @@ module FourSlash { var compilationSettings = convertGlobalOptionsToCompilationSettings(this.testData.globalOptions); this.languageServiceShimHost.setCompilationSettings(compilationSettings); - var inputFiles: { unitName: string; content: string }[] = []; + var startResolveFileRef: FourSlashFile = undefined; - testData.files.forEach(file => { - var fixedPath = file.fileName.substr(file.fileName.indexOf('tests/')); - }); - - // NEWTODO: disable resolution for now. - // If the last unit contains require( or /// reference then consider it the only input file - // and the rest will be added via resolution. If not, then assume we have multiple files - // with 0 references in any of them. We could be smarter here to allow scenarios like - // 2 files without references and 1 file with a reference but we have 0 tests like that - // at the moment and an exhaustive search of the test files for that content could be quite slow. - var lastFile = testData.files[testData.files.length - 1]; - //if (/require\(/.test(lastFile.content) || /reference\spath/.test(lastFile.content)) { - // inputFiles.push({ unitName: lastFile.fileName, content: lastFile.content }); - //} else { - inputFiles = testData.files.map(file => { - return { unitName: file.fileName, content: file.content }; - }); - //} - - - // NEWTODO: Re-implement commented-out section - //harnessCompiler.addInputFiles(inputFiles); - //try { - // var resolvedFiles = harnessCompiler.resolve(); - - // resolvedFiles.forEach(file => { - // if (!Harness.isLibraryFile(file.path)) { - // var fixedPath = file.path.substr(file.path.indexOf('tests/')); - // var content = harnessCompiler.getContentForFile(fixedPath); - // this.languageServiceShimHost.addScript(fixedPath, content); - // } - // }); - - // this.languageServiceShimHost.addScript('lib.d.ts', Harness.Compiler.libTextMinimal); - //} - //finally { - // // harness no longer needs the results of the above work, make sure the next test operations are in a clean state - // harnessCompiler.reset(); - //} - - /// NEWTODO: For now do not resolve, just use the input files - inputFiles.forEach(file => { - if (!Harness.isLibraryFile(file.unitName)) { - this.languageServiceShimHost.addScript(file.unitName, file.content); + ts.forEach(testData.files, file => { + // Create map between fileName and its content for easily looking up when resolveReference flag is specified + this.inputFiles[file.fileName] = file.content; + if (!startResolveFileRef && file.fileOptions[testOptMetadataNames.resolveReference]) { + startResolveFileRef = file; + } else if (startResolveFileRef) { + // If entry point for resolving file references is already specified, report duplication error + throw new Error("There exists a Fourslash file which has resolveReference flag specified; remove duplicated resolveReference flag"); } }); - this.languageServiceShimHost.addDefaultLibrary(); + if (startResolveFileRef) { + // Add the entry-point file itself into the languageServiceShimHost + this.languageServiceShimHost.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; + + // 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; + this.addMatchedInputFile(referenceFilePath); + }); + + // Add import files into language-service host + 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"; + this.addMatchedInputFile(importedFilePath); + }); + + // Check if no-default-lib flag is false and if so add default library + if (!resolvedResult.isLibFile) { + this.languageServiceShimHost.addDefaultLibrary(); + } + } 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.languageServiceShimHost.addDefaultLibrary(); + } // Sneak into the language service and get its compiler so we can examine the syntax trees this.languageService = this.languageServiceShimHost.getLanguageService().languageService; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6307c4d1e78..6163dfd0e79 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -258,6 +258,10 @@ module Harness.LanguageService { 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: TypeScript.IScriptSnapshot): TypeScript.SourceUnitSyntax { return TypeScript.Parser.parse(fileName, TypeScript.SimpleText.fromScriptSnapshot(sourceText), ts.ScriptTarget.Latest, TypeScript.isDTSFile(fileName)).sourceUnit(); diff --git a/src/services/compiler/precompile.ts b/src/services/compiler/precompile.ts deleted file mode 100644 index 3396cb4f2a2..00000000000 --- a/src/services/compiler/precompile.ts +++ /dev/null @@ -1,208 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -module TypeScript { - export interface ILineAndCharacter { - line: number; - character: number; - } - - // Note: This is being using by the host (VS) and is marshaled back and forth. When changing this make sure the changes - // are reflected in the managed side as well. - export interface IFileReference extends ILineAndCharacter { - path: string; - isResident: boolean; - position: number; - length: number; - } - - /// - /// Preprocessing - /// - export interface IPreProcessedFileInfo { - referencedFiles: IFileReference[]; - importedFiles: IFileReference[]; - diagnostics: Diagnostic[]; - isLibFile: boolean; - } - - interface ITripleSlashDirectiveProperties { - noDefaultLib: boolean; - diagnostics: Diagnostic[]; - referencedFiles: IFileReference[]; - } - - function isNoDefaultLibMatch(comment: string): RegExpExecArray { - var isNoDefaultLibRegex = /^(\/\/\/\s*/gim; - return isNoDefaultLibRegex.exec(comment); - } - - export var tripleSlashReferenceRegExp = /^(\/\/\/\s*/; - - function getFileReferenceFromReferencePath(fileName: string, text: ISimpleText, position: number, comment: string, diagnostics: Diagnostic[]): IFileReference { - // First, just see if they've written: /// = 7 && fullReference[6] === "true"; - return { - line: 0, - character: 0, - position: 0, - length: 0, - path: switchToForwardSlashes(adjustedPath), - isResident: isResident - }; - } - } - } - - return null; - } - - var reportDiagnostic = () => { }; - - function processImports(text: ISimpleText, scanner: Scanner.IScanner, token: ISyntaxToken, importedFiles: IFileReference[]): void { - var lineChar = { line: -1, character: -1 }; - - var lineMap = text.lineMap(); - var start = new Date().getTime(); - // Look for: - // import foo = module("foo") - while (token.kind !== SyntaxKind.EndOfFileToken) { - if (token.kind === SyntaxKind.ImportKeyword) { - var importToken = token; - token = scanner.scan(/*allowRegularExpression:*/ false); - - if (SyntaxFacts.isIdentifierNameOrAnyKeyword(token)) { - token = scanner.scan(/*allowRegularExpression:*/ false); - - if (token.kind === SyntaxKind.EqualsToken) { - token = scanner.scan(/*allowRegularExpression:*/ false); - - if (token.kind === SyntaxKind.ModuleKeyword || token.kind === SyntaxKind.RequireKeyword) { - token = scanner.scan(/*allowRegularExpression:*/ false); - - if (token.kind === SyntaxKind.OpenParenToken) { - token = scanner.scan(/*allowRegularExpression:*/ false); - - lineMap.fillLineAndCharacterFromPosition(TypeScript.start(importToken, text), lineChar); - - if (token.kind === SyntaxKind.StringLiteral) { - var ref = { - line: lineChar.line, - character: lineChar.character, - position: TypeScript.start(token, text), - length: width(token), - path: stripStartAndEndQuotes(switchToForwardSlashes(token.text())), - isResident: false - }; - importedFiles.push(ref); - } - } - } - } - } - } - - token = scanner.scan(/*allowRegularExpression:*/ false); - } - - var totalTime = new Date().getTime() - start; - //TypeScript.fileResolutionScanImportsTime += totalTime; - } - - function processTripleSlashDirectives(fileName: string, text: ISimpleText, firstToken: ISyntaxToken): ITripleSlashDirectiveProperties { - var leadingTrivia = firstToken.leadingTrivia(text); - - var position = 0; - var lineChar = { line: -1, character: -1 }; - var noDefaultLib = false; - var diagnostics: Diagnostic[] = []; - var referencedFiles: IFileReference[] = []; - var lineMap = text.lineMap(); - - for (var i = 0, n = leadingTrivia.count(); i < n; i++) { - var trivia = leadingTrivia.syntaxTriviaAt(i); - - if (trivia.kind() === SyntaxKind.SingleLineCommentTrivia) { - var triviaText = trivia.fullText(); - var referencedCode = getFileReferenceFromReferencePath(fileName, text, position, triviaText, diagnostics); - - if (referencedCode) { - lineMap.fillLineAndCharacterFromPosition(position, lineChar); - referencedCode.position = position; - referencedCode.length = trivia.fullWidth(); - referencedCode.line = lineChar.line; - referencedCode.character = lineChar.character; - - referencedFiles.push(referencedCode); - } - - // is it a lib file? - var isNoDefaultLib = isNoDefaultLibMatch(triviaText); - if (isNoDefaultLib) { - noDefaultLib = isNoDefaultLib[3] === "true"; - } - } - - position += trivia.fullWidth(); - } - - return { noDefaultLib: noDefaultLib, diagnostics: diagnostics, referencedFiles: referencedFiles }; - } - - export function preProcessFile(fileName: string, sourceText: IScriptSnapshot, readImportFiles = true): IPreProcessedFileInfo { - var text = SimpleText.fromScriptSnapshot(sourceText); - var scanner = Scanner.createScanner(ts.ScriptTarget.Latest, text, reportDiagnostic); - - var firstToken = scanner.scan(/*allowRegularExpression:*/ false); - - // only search out dynamic mods - // if you find a dynamic mod, ignore every other mod inside, until you balance rcurlies - // var position - - var importedFiles: IFileReference[] = []; - if (readImportFiles) { - processImports(text, scanner, firstToken, importedFiles); - } - - var properties = processTripleSlashDirectives(fileName, text, firstToken); - - return { referencedFiles: properties.referencedFiles, importedFiles: importedFiles, isLibFile: properties.noDefaultLib, diagnostics: properties.diagnostics }; - } - - export function getReferencedFiles(fileName: string, sourceText: IScriptSnapshot): IFileReference[] { - return preProcessFile(fileName, sourceText, false).referencedFiles; - } -} // Tools \ No newline at end of file diff --git a/src/services/core/lineMap.ts b/src/services/core/lineMap.ts index e4bd7d01bcd..947a4fb0bb2 100644 --- a/src/services/core/lineMap.ts +++ b/src/services/core/lineMap.ts @@ -1,6 +1,11 @@ /// module TypeScript { + export interface ILineAndCharacter { + line: number; + character: number; + } + export class LineMap { public static empty = new LineMap(() => [0], 0); private _lineStarts: number[] = undefined; diff --git a/src/services/services.ts b/src/services/services.ts index e02e9638691..9a689a7ff33 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -76,6 +76,12 @@ module ts { update(scriptSnapshot: TypeScript.IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): SourceFile; } + export interface PreProcessedFileInfo { + referencedFiles: FileReference[]; + importedFiles: FileReference[]; + isLibFile: boolean + } + var scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); var emptyArray: any[] = []; @@ -1529,7 +1535,7 @@ module ts { var filenames = host.getScriptFileNames(); for (var i = 0, n = filenames.length; i < n; i++) { var filename = filenames[i]; - this.filenameToEntry[TypeScript.switchToForwardSlashes(filename)] = { + this.filenameToEntry[switchToForwardSlashes(filename)] = { filename: filename, version: host.getScriptVersion(filename), isOpen: host.getScriptIsOpen(filename) @@ -1544,7 +1550,7 @@ module ts { } public getEntry(filename: string): HostFileInformation { - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); return lookUp(this.filenameToEntry, filename); } @@ -1900,6 +1906,68 @@ module ts { }; } + export function preProcessFile(sourceText: string, readImportFiles = true): PreProcessedFileInfo { + var referencedFiles: FileReference[] = []; + var importedFiles: FileReference[] = []; + var isNoDefaultLib = false; + + function processTripleSlashDirectives(): void { + var commentRanges = getLeadingCommentRanges(sourceText, 0); + forEach(commentRanges, commentRange => { + var comment = sourceText.substring(commentRange.pos, commentRange.end); + var referencePathMatchResult = getFileReferenceFromReferencePath(comment, commentRange); + if (referencePathMatchResult) { + isNoDefaultLib = referencePathMatchResult.isNoDefaultLib; + var fileReference = referencePathMatchResult.fileReference; + if (fileReference) { + referencedFiles.push(fileReference); + } + } + }); + } + + function processImport(): void { + scanner.setText(sourceText); + var token = scanner.scan(); + // Look for: + // import foo = module("foo"); + while (token !== SyntaxKind.EndOfFileToken) { + if (token === SyntaxKind.ImportKeyword) { + token = scanner.scan(); + if (token === SyntaxKind.Identifier) { + token = scanner.scan(); + if (token === SyntaxKind.EqualsToken) { + token = scanner.scan(); + if (token === SyntaxKind.RequireKeyword) { + token = scanner.scan(); + if (token === SyntaxKind.OpenParenToken) { + token = scanner.scan(); + if (token === SyntaxKind.StringLiteral) { + var importPath = scanner.getTokenValue(); + var pos = scanner.getTokenPos(); + importedFiles.push({ + filename: importPath, + pos: pos, + end: pos + importPath.length + }); + } + } + } + } + } + } + token = scanner.scan(); + } + scanner.setText(undefined); + } + + if (readImportFiles) { + processImport(); + } + processTripleSlashDirectives(); + return { referencedFiles: referencedFiles, importedFiles: importedFiles, isLibFile: isNoDefaultLib }; + } + /// Helpers export function getNodeModifiers(node: Node): string { var flags = node.flags; @@ -2260,7 +2328,7 @@ module ts { function getSyntacticDiagnostics(filename: string) { synchronizeHostData(); - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); return program.getDiagnostics(getSourceFile(filename).getSourceFile()); } @@ -2272,7 +2340,7 @@ module ts { function getSemanticDiagnostics(filename: string) { synchronizeHostData(); - filename = TypeScript.switchToForwardSlashes(filename) + filename = switchToForwardSlashes(filename) var compilerOptions = program.getCompilerOptions(); var checker = getFullTypeCheckChecker(); var targetSourceFile = getSourceFile(filename); @@ -2353,7 +2421,7 @@ module ts { function getCompletionsAtPosition(filename: string, position: number, isMemberCompletion: boolean) { synchronizeHostData(); - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); var syntacticStart = new Date().getTime(); var sourceFile = getSourceFile(filename); @@ -2698,7 +2766,7 @@ module ts { function getCompletionEntryDetails(filename: string, position: number, entryName: string): CompletionEntryDetails { // Note: No need to call synchronizeHostData, as we have captured all the data we need // in the getCompletionsAtPosition earlier - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); var sourceFile = getSourceFile(filename); @@ -3198,7 +3266,7 @@ module ts { function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { synchronizeHostData(); - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var sourceFile = getSourceFile(fileName); var node = getTouchingPropertyName(sourceFile, position); if (!node) { @@ -3300,7 +3368,7 @@ module ts { synchronizeHostData(); - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); var sourceFile = getSourceFile(filename); var node = getTouchingPropertyName(sourceFile, position); @@ -3364,7 +3432,7 @@ module ts { function getOccurrencesAtPosition(filename: string, position: number): ReferenceEntry[] { synchronizeHostData(); - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); var sourceFile = getSourceFile(filename); var node = getTouchingWord(sourceFile, position); @@ -3817,7 +3885,7 @@ module ts { function findReferences(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ReferenceEntry[] { synchronizeHostData(); - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var sourceFile = getSourceFile(fileName); var node = getTouchingPropertyName(sourceFile, position); @@ -4538,7 +4606,7 @@ module ts { function getEmitOutput(filename: string): EmitOutput { synchronizeHostData(); - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); var compilerOptions = program.getCompilerOptions(); var targetSourceFile = program.getSourceFile(filename); // Current selected file to be output // If --out flag is not specified, shouldEmitToOwnFile is true. Otherwise shouldEmitToOwnFile is false. @@ -4714,7 +4782,7 @@ module ts { function getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems { synchronizeHostData(); - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var sourceFile = getSourceFile(fileName); return SignatureHelp.getSignatureHelpItems(sourceFile, position, typeInfoResolver, cancellationToken); @@ -4784,12 +4852,12 @@ module ts { /// Syntactic features function getSyntaxTree(filename: string): TypeScript.SyntaxTree { - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); return syntaxTreeCache.getCurrentFileSyntaxTree(filename); } function getCurrentSourceFile(filename: string): SourceFile { - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); var currentSourceFile = syntaxTreeCache.getCurrentSourceFile(filename); return currentSourceFile; } @@ -4856,14 +4924,14 @@ module ts { } function getNavigationBarItems(filename: string): NavigationBarItem[] { - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); return NavigationBar.getNavigationBarItems(getCurrentSourceFile(filename)); } function getSemanticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] { synchronizeHostData(); - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var sourceFile = getSourceFile(fileName); @@ -4934,7 +5002,7 @@ module ts { function getSyntacticClassifications(fileName: string, span: TypeScript.TextSpan): ClassifiedSpan[] { // doesn't use compiler - no need to synchronize with host - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var sourceFile = getCurrentSourceFile(fileName); var result: ClassifiedSpan[] = []; @@ -5064,7 +5132,7 @@ module ts { function getOutliningSpans(filename: string): OutliningSpan[] { // doesn't use compiler - no need to synchronize with host - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); var sourceFile = getCurrentSourceFile(filename); return OutliningElementsCollector.collectElements(sourceFile); } @@ -5123,7 +5191,7 @@ module ts { } function getIndentationAtPosition(filename: string, position: number, editorOptions: EditorOptions) { - filename = TypeScript.switchToForwardSlashes(filename); + filename = switchToForwardSlashes(filename); var start = new Date().getTime(); var sourceFile = getCurrentSourceFile(filename); @@ -5160,21 +5228,21 @@ module ts { } function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[] { - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var manager = getFormattingManager(fileName, options); return manager.formatSelection(start, end); } function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] { - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var manager = getFormattingManager(fileName, options); return manager.formatDocument(); } function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions): TextChange[] { - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var manager = getFormattingManager(fileName, options); @@ -5360,7 +5428,7 @@ module ts { function getRenameInfo(fileName: string, position: number): RenameInfo { synchronizeHostData(); - fileName = TypeScript.switchToForwardSlashes(fileName); + fileName = switchToForwardSlashes(fileName); var sourceFile = getSourceFile(fileName); var node = getTouchingWord(sourceFile, position); diff --git a/src/services/shims.ts b/src/services/shims.ts index c6705e34363..8c4da452b73 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -16,7 +16,6 @@ /// /// -/// var debugObjectHost = (this); @@ -55,6 +54,17 @@ module ts { getDefaultLibFilename(): string; } + /// + /// Pre-processing + /// + // Note: This is being using by the host (VS) and is marshaled back and forth. + // When changing this make sure the changes are reflected in the managed side as well + export interface IFileReference { + path: string; + position: number; + length: number; + } + /** Public interface of a language service instance shim. */ export interface ShimFactory { registerShim(shim: Shim): void; @@ -835,12 +845,33 @@ module ts { return forwardJSONCall(this.logger, actionDescription, action); } - public getPreProcessedFileInfo(fileName: string, sourceText: TypeScript.IScriptSnapshot): string { + public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: TypeScript.IScriptSnapshot): string { return this.forwardJSONCall( "getPreProcessedFileInfo('" + fileName + "')", () => { - var result = TypeScript.preProcessFile(fileName, sourceText); - return result; + var result = preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength())); + var convertResult = { + referencedFiles: [], + importedFiles: [], + isLibFile: result.isLibFile + }; + + forEach(result.referencedFiles, refFile => { + convertResult.referencedFiles.push({ + path: switchToForwardSlashes(normalizePath(refFile.filename)), + position: refFile.pos, + length: refFile.end - refFile.pos + }); + }); + + forEach(result.importedFiles, importedFile => { + convertResult.importedFiles.push({ + path: switchToForwardSlashes(importedFile.filename), + position: importedFile.pos, + length: importedFile.end - importedFile.pos + }); + }); + return convertResult; }); } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 88e3601b610..67ed16e7c85 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -259,6 +259,10 @@ module ts { return n.kind === SyntaxKind.StringLiteral || n.kind === SyntaxKind.NumericLiteral || isWord(n); } + export var switchToForwardSlashesRegEx = /\\/g; + export function switchToForwardSlashes(path: string) { + return path.replace(switchToForwardSlashesRegEx, "/"); + } export function isComment(n: Node): boolean { return n.kind === SyntaxKind.SingleLineCommentTrivia || n.kind === SyntaxKind.MultiLineCommentTrivia; } diff --git a/tests/cases/fourslash/getPreProcessedFile.ts b/tests/cases/fourslash/getPreProcessedFile.ts new file mode 100644 index 00000000000..abd26bb6e4b --- /dev/null +++ b/tests/cases/fourslash/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/unittests/services/preProcessFile.ts b/tests/cases/unittests/services/preProcessFile.ts new file mode 100644 index 00000000000..88aa0f5e9ed --- /dev/null +++ b/tests/cases/unittests/services/preProcessFile.ts @@ -0,0 +1,113 @@ +/// +/// + +describe('PreProcessFile:', function () { + function test(sourceText: string, readImportFile: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void { + var resultPreProcess = ts.preProcessFile(sourceText, readImportFile); + + var resultIsLibFile = resultPreProcess.isLibFile; + var resultImportedFiles = resultPreProcess.importedFiles; + var resultReferencedFiles = resultPreProcess.referencedFiles; + + var expectedIsLibFile = expectedPreProcess.isLibFile; + var expectedImportedFiles = expectedPreProcess.importedFiles; + var expectedReferencedFiles = expectedPreProcess.referencedFiles; + + assert.equal(resultIsLibFile, expectedIsLibFile, "Pre-processed file has different value for isLibFile. Expected: " + expectedPreProcess + ". Actual: " + resultIsLibFile); + + assert.equal(resultImportedFiles.length, expectedImportedFiles.length, + "Array's length of imported files does not match expected. Expected: " + expectedImportedFiles.length + ". Actual: " + resultImportedFiles.length); + + assert.equal(resultReferencedFiles.length, expectedReferencedFiles.length, + "Array's length of referenced files does not match expected. Expected: " + expectedReferencedFiles.length + ". Actual: " + resultReferencedFiles.length); + + for (var i = 0; i < expectedImportedFiles.length; ++i) { + var resultImportedFile = resultImportedFiles[i]; + var expectedImportedFile = expectedImportedFiles[i]; + + assert.equal(resultImportedFile.filename, expectedImportedFile.filename, "Imported file path does not match expected. Expected: " + expectedImportedFile.filename + ". Actual: " + resultImportedFile.filename + "."); + + assert.equal(resultImportedFile.pos, expectedImportedFile.pos, "Imported file position does not match expected. Expected: " + expectedImportedFile.pos + ". Actual: " + resultImportedFile.pos + "."); + + assert.equal(resultImportedFile.end, expectedImportedFile.end, "Imported file length does not match expected. Expected: " + expectedImportedFile.end + ". Actual: " + resultImportedFile.end + "."); + } + + for (var i = 0; i < expectedReferencedFiles.length; ++i) { + var resultReferencedFile = resultReferencedFiles[i]; + var expectedReferencedFile = expectedReferencedFiles[i]; + + assert.equal(resultReferencedFile.filename, expectedReferencedFile.filename, "Referenced file path does not match expected. Expected: " + expectedReferencedFile.filename + ". Actual: " + resultReferencedFile.filename + "."); + + assert.equal(resultReferencedFile.pos, expectedReferencedFile.pos, "Referenced file position does not match expected. Expected: " + expectedReferencedFile.pos + ". Actual: " + resultReferencedFile.pos + "."); + + assert.equal(resultReferencedFile.end, expectedReferencedFile.end, "Referenced file length does not match expected. Expected: " + expectedReferencedFile.end + ". Actual: " + resultReferencedFile.end + "."); + } + } + describe("Test preProcessFiles,", function () { + it("Correctly return referenced files from triple slash", function () { + test("///" + "\n" + "///" + "\n" + "///" + "\n" + "///", true, + { + referencedFiles: [{ filename: "refFile1.ts", pos: 0, end: 37 }, { filename: "refFile2.ts", pos: 38, end: 73 }, + { filename: "refFile3.ts", pos: 74, end: 109 }, { filename: "..\\refFile4d.ts", pos: 110, end: 150 }], + importedFiles: [], + isLibFile: false + }); + }), + + it("Do not return reference path because of invalid triple-slash syntax", function () { + test("///" + "\n" + "///" + "\n" + "///" + "\n" + "///", true, + { + referencedFiles: [], + importedFiles: [], + isLibFile: false + }); + }), + + it("Correctly return imported files", function () { + test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");", true, + { + referencedFiles: [], + importedFiles: [{ filename: "r1.ts", pos: 20, end: 25 }, { filename: "r2.ts", pos: 49, end: 54 }, { filename: "r3.ts", pos: 78, end: 83 }, + { filename: "r4.ts", pos: 106, end: 111 }, { filename: "r5.ts", pos: 138, end: 143 }], + isLibFile: false + }); + }), + + it("Do not return imported files if readImportFiles argument is false", function () { + test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");", false, + { + referencedFiles: [], + importedFiles: [], + isLibFile: false + }); + }), + + it("Do not return import path because of invalid import syntax", function () { + test("import i1 require(\"r1.ts\"); import = require(\"r2.ts\") import i3= require(\"r3.ts\"); import i5", true, + { + referencedFiles: [], + importedFiles: [{ filename: "r3.ts", pos: 73, end: 78 }], + isLibFile: false + }); + }), + + it("Correctly return referenced files and import files", function () { + test("///" + "\n" + "///" + "\n" + "import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\");", true, + { + referencedFiles: [{ filename: "refFile1.ts", pos: 0, end: 35 }, { filename: "refFile2.ts", pos: 36, end: 71 }], + importedFiles: [{ filename: "r1.ts", pos: 92, end: 97 }, { filename: "r2.ts", pos: 121, end: 126 }], + isLibFile: false + }); + }), + + it("Correctly return referenced files and import files even with some invalid syntax", function () { + test("///" + "\n" + "///" + "\n" + "import i1 = require(\"r1.ts\"); import = require(\"r2.ts\"); import i2 = require(\"r3.ts\");", true, + { + referencedFiles: [{ filename: "refFile1.ts", pos: 0, end: 35 }], + importedFiles: [{ filename: "r1.ts", pos: 91, end: 96 }, { filename: "r3.ts", pos: 148, end: 153 }], + isLibFile: false + }) + }); + }); +}); +