From 226deec4b0404361778451a7052c27c9f86082a5 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 22 Jun 2015 17:48:44 -0700 Subject: [PATCH 01/20] reuse structure of the program if changes in files don't affect imports/references, remove module resolution from the checker --- src/compiler/checker.ts | 21 +--- src/compiler/parser.ts | 3 +- src/compiler/program.ts | 243 +++++++++++++++++++++++++++++--------- src/compiler/types.ts | 14 ++- src/compiler/utilities.ts | 34 ++++++ src/services/services.ts | 10 +- 6 files changed, 250 insertions(+), 75 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a18f2921a9a53..99bcf9f1617e7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -892,7 +892,9 @@ namespace ts { // Escape the name in the "require(...)" clause to ensure we find the right symbol. let moduleName = escapeIdentifier(moduleReferenceLiteral.text); - if (!moduleName) return; + if (!moduleName) { + return; + } let isRelative = isExternalModuleNameRelative(moduleName); if (!isRelative) { let symbol = getSymbol(globals, '"' + moduleName + '"', SymbolFlags.ValueModule); @@ -900,20 +902,9 @@ namespace ts { return symbol; } } - let fileName: string; - let sourceFile: SourceFile; - while (true) { - fileName = normalizePath(combinePaths(searchPath, moduleName)); - sourceFile = forEach(supportedExtensions, extension => host.getSourceFile(fileName + extension)); - if (sourceFile || isRelative) { - break; - } - let parentPath = getDirectoryPath(searchPath); - if (parentPath === searchPath) { - break; - } - searchPath = parentPath; - } + + let fileName = getResolvedModuleFileName(getSourceFile(location), moduleReferenceLiteral); + let sourceFile = fileName && host.getSourceFile(fileName); if (sourceFile) { if (sourceFile.symbol) { return sourceFile.symbol; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index fbb3dad5037b8..a4878569329da 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5634,7 +5634,8 @@ namespace ts { // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. let result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /* setParentNode */ true) - + // pass set of modules that were resolved before so 'createProgram' can reuse previous resolution results + result.resolvedModules = sourceFile.resolvedModules; return result; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2af9ad9987d4f..878f4eda32283 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -9,7 +9,9 @@ namespace ts { /** The version of the TypeScript compiler release */ export const version = "1.5.3"; - + + var emptyArray: any[] = []; + export function findConfigFile(searchPath: string): string { var fileName = "tsconfig.json"; while (true) { @@ -143,7 +145,7 @@ namespace ts { } } - export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost): Program { + export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program { let program: Program; let files: SourceFile[] = []; let diagnostics = createDiagnosticCollection(); @@ -160,15 +162,18 @@ namespace ts { host = host || createCompilerHost(options); let filesByName = createFileMap(fileName => host.getCanonicalFileName(fileName)); + + let structureIsReused = oldProgram && host.hasChanges && tryReuseStructureFromOldProgram(); - forEach(rootNames, name => processRootFile(name, /*isDefaultLib:*/ false)); - - // Do not process the default library if: - // - The '--noLib' flag is used. - // - A 'no-default-lib' reference comment is encountered in - // processing the root files. - if (!skipDefaultLib) { - processRootFile(host.getDefaultLibFileName(options), /*isDefaultLib:*/ true); + if (!structureIsReused) { + forEach(rootNames, name => processRootFile(name, false)); + // Do not process the default library if: + // - The '--noLib' flag is used. + // - A 'no-default-lib' reference comment is encountered in + // processing the root files. + if (!skipDefaultLib) { + processRootFile(host.getDefaultLibFileName(options), true); + } } verifyCompilerOptions(); @@ -176,6 +181,7 @@ namespace ts { programTime += new Date().getTime() - start; program = { + getRootFileNames: () => rootNames, getSourceFile: getSourceFile, getSourceFiles: () => files, getCompilerOptions: () => options, @@ -211,6 +217,67 @@ namespace ts { return classifiableNames; } + function tryReuseStructureFromOldProgram(): boolean { + if (!oldProgram) { + return false; + } + + // there is an old program, check if we can reuse its structure + let oldRootNames = oldProgram.getRootFileNames(); + if (rootNames.length !== oldRootNames.length) { + // different amount of root names - structure cannot be reused + return false; + } + + for (let i = 0; i < rootNames.length; i++) { + if (oldRootNames[i] !== rootNames[i]) { + // different order of root names - structure cannot be reused + return false; + } + } + + // check if program source files has changed in the way that can affect structure of the program + let newSourceFiles: SourceFile[] = []; + for (let oldSourceFile of oldProgram.getSourceFiles()) { + let newSourceFile: SourceFile; + if (host.hasChanges(oldSourceFile)) { + newSourceFile = host.getSourceFile(oldSourceFile.fileName, options.target); + if (!newSourceFile) { + return false; + } + + // check tripleslash references + if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { + // tripleslash references has changed + return false; + } + + // check imports + collectExternalModuleReferences(newSourceFile); + if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) { + // imports has changed + return false; + } + } + else { + // file has no changes - use it as is + newSourceFile = oldSourceFile; + } + + // if file has passed all checks it should be safe to reuse it + newSourceFiles.push(newSourceFile); + } + + // update fileName -> file mapping + for (let file of newSourceFiles) { + filesByName.set(file.fileName, file); + } + + files = newSourceFiles; + + return true; + } + function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { return { getCanonicalFileName: fileName => host.getCanonicalFileName(fileName), @@ -332,6 +399,62 @@ namespace ts { function processRootFile(fileName: string, isDefaultLib: boolean) { processSourceFile(normalizePath(fileName), isDefaultLib); + } + + function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean { + return a.fileName === b.fileName; + } + + function moduleNameIsEqualTo(a: LiteralExpression, b: LiteralExpression): boolean { + return a.text ===b.text; + } + + function collectExternalModuleReferences(file: SourceFile): void { + if (file.imports) { + return; + } + + let imports: LiteralExpression[]; + for (let node of file.statements) { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ExportDeclaration: + let moduleNameExpr = getExternalModuleName(node); + if (!moduleNameExpr || moduleNameExpr.kind !== SyntaxKind.StringLiteral) { + break; + } + if (!(moduleNameExpr).text) { + break; + } + + (imports || (imports = [])).push(moduleNameExpr); + break; + case SyntaxKind.ModuleDeclaration: + if ((node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) { + // TypeScript 1.0 spec (April 2014): 12.1.6 + // An AmbientExternalModuleDeclaration declares an external module. + // This type of declaration is permitted only in the global module. + // The StringLiteral must specify a top - level external module name. + // Relative external module names are not permitted + forEachChild((node).body, node => { + if (isExternalModuleImportEqualsDeclaration(node) && + getExternalModuleImportEqualsDeclarationExpression(node).kind === SyntaxKind.StringLiteral) { + let moduleName = getExternalModuleImportEqualsDeclarationExpression(node); + // TypeScript 1.0 spec (April 2014): 12.1.6 + // An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules + // only through top - level external module names. Relative external module names are not permitted. + if (moduleName) { + (imports || (imports = [])).push(moduleName); + } + } + }); + } + break; + } + } + + file.imports = imports || emptyArray; } function processSourceFile(fileName: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number) { @@ -450,57 +573,69 @@ namespace ts { processSourceFile(normalizePath(referencedFileName), /* isDefaultLib */ false, file, ref.pos, ref.end); }); } - - function processImportedModules(file: SourceFile, basePath: string) { - forEach(file.statements, node => { - if (node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration || node.kind === SyntaxKind.ExportDeclaration) { - let moduleNameExpr = getExternalModuleName(node); - if (moduleNameExpr && moduleNameExpr.kind === SyntaxKind.StringLiteral) { - let moduleNameText = (moduleNameExpr).text; - if (moduleNameText) { - let searchPath = basePath; - let searchName: string; - while (true) { - searchName = normalizePath(combinePaths(searchPath, moduleNameText)); - if (forEach(supportedExtensions, extension => findModuleSourceFile(searchName + extension, moduleNameExpr))) { - break; - } - let parentPath = getDirectoryPath(searchPath); - if (parentPath === searchPath) { - break; - } - searchPath = parentPath; - } - } + + function processImportedModules(file: SourceFile, basePath: string) { + collectExternalModuleReferences(file); + if (file.imports.length) { + let allImportsInTheCache = true; + // check that all imports are contained in resolved modules cache + // if at least one of imports in not in the cache - cache needs to be reinitialized + for (let moduleName of file.imports) { + if (!hasResolvedModuleName(file, moduleName)) { + allImportsInTheCache = false; + break; } } - else if (node.kind === SyntaxKind.ModuleDeclaration && (node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) { - // TypeScript 1.0 spec (April 2014): 12.1.6 - // An AmbientExternalModuleDeclaration declares an external module. - // This type of declaration is permitted only in the global module. - // The StringLiteral must specify a top - level external module name. - // Relative external module names are not permitted - forEachChild((node).body, node => { - if (isExternalModuleImportEqualsDeclaration(node) && - getExternalModuleImportEqualsDeclarationExpression(node).kind === SyntaxKind.StringLiteral) { - - let nameLiteral = getExternalModuleImportEqualsDeclarationExpression(node); - let moduleName = nameLiteral.text; - if (moduleName) { - // TypeScript 1.0 spec (April 2014): 12.1.6 - // An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules - // only through top - level external module names. Relative external module names are not permitted. - let searchName = normalizePath(combinePaths(basePath, moduleName)); - forEach(supportedExtensions, extension => findModuleSourceFile(searchName + extension, nameLiteral)); - } - } - }); + + if (!allImportsInTheCache) { + // initialize resolvedModules with empty map + // old module resolutions still can be used to avoid actual file system probing + let resolvedModules = file.resolvedModules; + file.resolvedModules = {}; + for (let moduleName of file.imports) { + resolveModule(moduleName, resolvedModules); + } } - }); + } + else { + // no imports - drop cached module resolutions + file.resolvedModules = undefined; + } + return; function findModuleSourceFile(fileName: string, nameLiteral: Expression) { return findSourceFile(fileName, /* isDefaultLib */ false, file, nameLiteral.pos, nameLiteral.end - nameLiteral.pos); } + + function resolveModule(moduleNameExpr: LiteralExpression, existingResolutions: Map): void { + let searchPath = basePath; + let searchName: string; + + if (existingResolutions && hasProperty(existingResolutions, moduleNameExpr.text)) { + let fileName = existingResolutions[moduleNameExpr.text]; + if (fileName) { + findModuleSourceFile(fileName, moduleNameExpr); + } + return; + } + + while (true) { + searchName = normalizePath(combinePaths(searchPath, moduleNameExpr.text)); + let referencedSourceFile = forEach(supportedExtensions, extension => findModuleSourceFile(searchName + extension, moduleNameExpr)); + if (referencedSourceFile) { + setResolvedModuleName(file, moduleNameExpr, referencedSourceFile.fileName); + return; + } + + let parentPath = getDirectoryPath(searchPath); + if (parentPath === searchPath) { + break; + } + searchPath = parentPath; + } + // mark reference as non-resolved + setResolvedModuleName(file, moduleNameExpr, undefined); + } } function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f9a2fa3b7b434..baf55c1537d3a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1176,8 +1176,11 @@ namespace ts { // Stores a line map for the file. // This field should never be used directly to obtain line map, use getLineMap function instead. /* @internal */ lineMap: number[]; - /* @internal */ classifiableNames?: Map; + // Stores a mapping 'external module reference text' -> 'resolved file name' | undefined + // Content of this fiels should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead + /* @internal */ resolvedModules: Map; + /* @internal */ imports: LiteralExpression[]; } export interface ScriptReferenceHost { @@ -1195,6 +1198,12 @@ namespace ts { } export interface Program extends ScriptReferenceHost { + + /** + * Get a list of root file names that were passed to a 'createProgram' + */ + getRootFileNames(): string[] + /** * Get a list of files in the program */ @@ -1881,7 +1890,7 @@ namespace ts { CarriageReturnLineFeed = 0, LineFeed = 1, } - + export interface LineAndCharacter { line: number; /* @@ -2065,6 +2074,7 @@ namespace ts { getCanonicalFileName(fileName: string): string; useCaseSensitiveFileNames(): boolean; getNewLine(): string; + hasChanges?(oldFile: SourceFile): boolean; } export interface TextSpan { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ee75454ad9331..f6534a1b7e5c3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -78,6 +78,40 @@ namespace ts { return node.end - node.pos; } + export function arrayIsEqualTo(arr1: T[], arr2: T[], comparer: (a: T, b: T) => boolean): boolean { + if (!arr1 || !arr2) { + return arr1 === arr2; + } + + if (arr1.length !== arr2.length) { + return false; + } + + for (let i = 0; i < arr1.length; ++i) { + if (!comparer(arr1[i], arr2[i])) { + return false; + } + } + + return true; + } + + export function hasResolvedModuleName(sourceFile: SourceFile, moduleName: LiteralExpression): boolean { + return sourceFile.resolvedModules && hasProperty(sourceFile.resolvedModules, moduleName.text); + } + + export function getResolvedModuleFileName(sourceFile: SourceFile, moduleName: LiteralExpression): string { + return sourceFile.resolvedModules && sourceFile.resolvedModules[moduleName.text]; + } + + export function setResolvedModuleName(sourceFile: SourceFile, moduleName: LiteralExpression, resolvedFileName: string): void { + if (!sourceFile.resolvedModules) { + sourceFile.resolvedModules = {}; + } + + sourceFile.resolvedModules[moduleName.text] = resolvedFileName; + } + // Returns true if this node contains a parse error anywhere underneath it. export function containsParseError(node: Node): boolean { aggregateChildData(node); diff --git a/src/services/services.ts b/src/services/services.ts index 70022cfe0bf47..acb844f201105 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -753,7 +753,8 @@ namespace ts { public languageVersion: ScriptTarget; public identifiers: Map; public nameTable: Map; - + public resolvedModules: Map; + public imports: LiteralExpression[]; private namedDeclarations: Map; public update(newText: string, textChangeRange: TextChangeRange): SourceFile { @@ -2471,6 +2472,8 @@ namespace ts { let newSettings = hostCache.compilationSettings(); let changesInCompilationSettingsAffectSyntax = oldSettings && oldSettings.target !== newSettings.target; + let reusableOldProgram = changesInCompilationSettingsAffectSyntax ? undefined : program; + // Now create a new compiler let newProgram = createProgram(hostCache.getRootFileNames(), newSettings, { getSourceFile: getOrCreateSourceFile, @@ -2480,8 +2483,9 @@ namespace ts { getNewLine: () => host.getNewLine ? host.getNewLine() : "\r\n", getDefaultLibFileName: (options) => host.getDefaultLibFileName(options), writeFile: (fileName, data, writeByteOrderMark) => { }, - getCurrentDirectory: () => host.getCurrentDirectory() - }); + getCurrentDirectory: () => host.getCurrentDirectory(), + hasChanges: oldFile => oldFile.version !== hostCache.getVersion(oldFile.fileName) + }, reusableOldProgram); // Release any files we have acquired in the old program but are // not part of the new program. From 39e832da55f198c0d854cf8c2659cbbb047ac664 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 23 Jun 2015 10:51:00 -0700 Subject: [PATCH 02/20] use existing information about module resolutions --- src/compiler/program.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 878f4eda32283..f187b68ef5b5b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -613,6 +613,8 @@ namespace ts { if (existingResolutions && hasProperty(existingResolutions, moduleNameExpr.text)) { let fileName = existingResolutions[moduleNameExpr.text]; + // use existing resolution + setResolvedModuleName(file, moduleNameExpr, fileName); if (fileName) { findModuleSourceFile(fileName, moduleNameExpr); } From ba3eb0d0cff37bafc18c52d22d235275f96cb5c2 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 23 Jun 2015 21:06:57 -0700 Subject: [PATCH 03/20] added Program.structureIsReused property, disallow reuse if target module kind differs in old and new programs, move setting of resolvedModules cache to the program, added tests --- Jakefile.js | 3 +- src/compiler/parser.ts | 2 - src/compiler/program.ts | 48 +++- src/compiler/types.ts | 1 + .../cases/unittests/reuseProgramStructure.ts | 261 ++++++++++++++++++ 5 files changed, 301 insertions(+), 14 deletions(-) create mode 100644 tests/cases/unittests/reuseProgramStructure.ts diff --git a/Jakefile.js b/Jakefile.js index 07a2ade06a6a3..f2e61c4579d82 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -138,7 +138,8 @@ var harnessSources = [ "services/patternMatcher.ts", "versionCache.ts", "convertToBase64.ts", - "transpile.ts" + "transpile.ts", + "reuseProgramStructure.ts" ].map(function (f) { return path.join(unittestsDirectory, f); })).concat([ diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a4878569329da..024cc96cb79f3 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5634,8 +5634,6 @@ namespace ts { // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. let result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /* setParentNode */ true) - // pass set of modules that were resolved before so 'createProgram' can reuse previous resolution results - result.resolvedModules = sourceFile.resolvedModules; return result; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index f187b68ef5b5b..3f19c28a9b29e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -163,9 +163,13 @@ namespace ts { let filesByName = createFileMap(fileName => host.getCanonicalFileName(fileName)); - let structureIsReused = oldProgram && host.hasChanges && tryReuseStructureFromOldProgram(); - - if (!structureIsReused) { + // if old program was provided by has different target module kind - assume that it cannot be reused + // different module kind can lead to different way of resolving modules + if (oldProgram && oldProgram.getCompilerOptions().module !== options.module) { + oldProgram = undefined; + } + + if (!tryReuseStructureFromOldProgram()) { forEach(rootNames, name => processRootFile(name, false)); // Do not process the default library if: // - The '--noLib' flag is used. @@ -218,9 +222,15 @@ namespace ts { } function tryReuseStructureFromOldProgram(): boolean { + if (!host.hasChanges) { + // host does not support method 'hasChanges' + return false; + } if (!oldProgram) { return false; } + + Debug.assert(!oldProgram.structureIsReused); // there is an old program, check if we can reuse its structure let oldRootNames = oldProgram.getRootFileNames(); @@ -258,6 +268,8 @@ namespace ts { // imports has changed return false; } + // pass the cache of module resolutions from the old source file + newSourceFile.resolvedModules = oldSourceFile.resolvedModules; } else { // file has no changes - use it as is @@ -275,6 +287,8 @@ namespace ts { files = newSourceFiles; + oldProgram.structureIsReused = true; + return true; } @@ -576,14 +590,26 @@ namespace ts { function processImportedModules(file: SourceFile, basePath: string) { collectExternalModuleReferences(file); - if (file.imports.length) { - let allImportsInTheCache = true; - // check that all imports are contained in resolved modules cache - // if at least one of imports in not in the cache - cache needs to be reinitialized - for (let moduleName of file.imports) { - if (!hasResolvedModuleName(file, moduleName)) { - allImportsInTheCache = false; - break; + if (file.imports.length) { + let allImportsInTheCache = false; + + // try to grab existing module resolutions from the old source file + let oldSourceFile: SourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); + if (oldSourceFile) { + file.resolvedModules = oldSourceFile.resolvedModules; + + // check that all imports are contained in resolved modules cache + // if at least one of imports in not in the cache - cache needs to be reinitialized + checkImports: { + if (file.resolvedModules) { + for (let moduleName of file.imports) { + if (!hasResolvedModuleName(file, moduleName)) { + break checkImports; + } + } + + allImportsInTheCache = true; + } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index baf55c1537d3a..7c7b0c52eefc9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1244,6 +1244,7 @@ namespace ts { /* @internal */ getIdentifierCount(): number; /* @internal */ getSymbolCount(): number; /* @internal */ getTypeCount(): number; + /* @internal */ structureIsReused?: boolean; } export interface SourceMapSpan { diff --git a/tests/cases/unittests/reuseProgramStructure.ts b/tests/cases/unittests/reuseProgramStructure.ts new file mode 100644 index 0000000000000..996f6bb316b45 --- /dev/null +++ b/tests/cases/unittests/reuseProgramStructure.ts @@ -0,0 +1,261 @@ +/// +/// +/// + +module ts { + + const enum ChangedPart { + references = 1 << 0, + importsAndExports = 1 << 1, + program = 1 << 2 + } + + let newLine = "\r\n"; + + interface SourceFileWithText extends SourceFile { + sourceText?: SourceText; + } + + interface NamedSourceText { + name: string; + text: SourceText + } + + interface ProgramWithSourceTexts extends Program { + sourceTexts?: NamedSourceText[]; + } + + class SourceText implements IScriptSnapshot { + private fullText: string; + + constructor(private references: string, + private importsAndExports: string, + private program: string, + private changedPart: ChangedPart = 0, + private version = 0) { + } + + static New(references: string, importsAndExports: string, program: string): SourceText { + Debug.assert(references !== undefined); + Debug.assert(importsAndExports !== undefined); + Debug.assert(program !== undefined); + return new SourceText(references + newLine, importsAndExports + newLine, program || ""); + } + + public getVersion(): number { + return this.version; + } + + public updateReferences(newReferences: string): SourceText { + Debug.assert(newReferences !== undefined); + return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1); + } + public updateImportsAndExports(newImportsAndExports: string): SourceText { + Debug.assert(newImportsAndExports !== undefined); + return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1); + } + public updateProgram(newProgram: string): SourceText { + Debug.assert(newProgram !== undefined); + return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1); + } + + public getFullText() { + return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program); + } + + public getText(start: number, end: number): string { + return this.getFullText().substring(start, end); + } + + getLength(): number { + return this.getFullText().length; + } + + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { + var oldText = oldSnapshot; + var oldSpan: TextSpan; + var newLength: number; + switch(oldText.changedPart ^ this.changedPart){ + case ChangedPart.references: + oldSpan = createTextSpan(0, oldText.references.length); + newLength = this.references.length; + break; + case ChangedPart.importsAndExports: + oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length); + newLength = this.importsAndExports.length + break; + case ChangedPart.program: + oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length); + newLength = this.program.length; + break; + default: + Debug.assert(false, "Unexpected change"); + } + + return createTextChangeRange(oldSpan, newLength); + } + } + + function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget): CompilerHost { + let files: Map = {}; + for (let t of texts) { + let file = createSourceFile(t.name, t.text.getFullText(), target); + file.sourceText = t.text; + files[t.name] = file; + } + return { + getSourceFile(fileName): SourceFile { + return files[fileName]; + }, + getDefaultLibFileName(): string { + return "lib.d.ts" + }, + writeFile(file, text) { + throw new Error("NYI"); + }, + getCurrentDirectory(): string { + return ""; + }, + getCanonicalFileName(fileName): string { + return sys.useCaseSensitiveFileNames ? fileName: fileName.toLowerCase(); + }, + useCaseSensitiveFileNames(): boolean { + return sys.useCaseSensitiveFileNames; + }, + getNewLine() : string { + return sys.newLine; + }, + hasChanges(oldFile: SourceFileWithText): boolean { + let current = files[oldFile.fileName]; + return !current || oldFile.sourceText.getVersion() !== current.sourceText.getVersion(); + } + + } + } + + function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): Program { + var host = createTestCompilerHost(texts, options.target); + let program = createProgram(rootNames, options, host); + program.sourceTexts = texts; + return program; + } + + function updateProgram(oldProgram: Program, rootNames: string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void) { + var texts: NamedSourceText[] = (oldProgram).sourceTexts.slice(0); + updater(texts); + var host = createTestCompilerHost(texts, options.target); + var program = createProgram(rootNames, options, host, oldProgram); + program.sourceTexts = texts; + return program; + } + + function getSizeOfMap(map: Map): number { + let size = 0; + for (let id in map) { + if (hasProperty(map, id)) { + size++; + } + } + return size; + } + + function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: Map): void { + let file = program.getSourceFile(fileName); + assert.isTrue(file !==undefined, `cannot find file ${fileName}`); + if (expectedContent === undefined) { + assert.isTrue(file.resolvedModules === undefined, "expected resolvedModules to be undefined"); + } + else { + assert.isTrue(file.resolvedModules !== undefined, "expected resolvedModuled to be set"); + let actualCacheSize = getSizeOfMap(file.resolvedModules); + let expectedSize = getSizeOfMap(expectedContent); + assert.isTrue(actualCacheSize === expectedSize, `expected actual size: ${actualCacheSize} to be equal to ${expectedSize}`); + + for (let id in expectedContent) { + if (hasProperty(expectedContent, id)) { + assert.isTrue(hasProperty(file.resolvedModules, id), `expected ${id} to be found in resolved modules`); + assert.isTrue(expectedContent[id] === file.resolvedModules[id], `expected '${expectedContent[id]}' to be equal to '${file.resolvedModules[id]}'`); + } + } + } + } + + describe("Reuse program structure", () => { + let target = ScriptTarget.Latest; + let files = [ + {name: "a.ts", text: SourceText.New(`/// `, "", `var x = 1`)}, + {name: "b.ts", text: SourceText.New(`/// `, "", `var y = 2`)}, + {name: "c.ts", text: SourceText.New("", "", `var z = 1;`)}, + ] + + it("successful if change does not affect imports", () => { + var program_1 = newProgram(files, ["a.ts"], {target}); + var program_2 = updateProgram(program_1, ["a.ts"], {target}, files => { + files[0].text = files[0].text.updateProgram("var x = 100"); + }); + assert.isTrue(program_1.structureIsReused); + }); + + it("fails if change affects tripleslash references", () => { + var program_1 = newProgram(files, ["a.ts"], {target}); + var program_2 = updateProgram(program_1, ["a.ts"], {target}, files => { + let newReferences = `/// + /// + `; + files[0].text = files[0].text.updateReferences(newReferences); + }); + assert.isTrue(!program_1.structureIsReused); + }); + + it("fails if change affects imports", () => { + var program_1 = newProgram(files, ["a.ts"], {target}); + var program_2 = updateProgram(program_1, ["a.ts"], {target}, files => { + files[2].text = files[2].text.updateImportsAndExports("import x from 'b'"); + }); + assert.isTrue(!program_1.structureIsReused); + }); + + it("fails if module kind changes", () => { + var program_1 = newProgram(files, ["a.ts"], {target, module: ModuleKind.CommonJS}); + var program_2 = updateProgram(program_1, ["a.ts"], {target, module: ModuleKind.AMD}, files => void 0); + assert.isTrue(!program_1.structureIsReused); + }); + + it("resolution cache follows imports", () => { + let files = [ + { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, + { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, + ]; + var options: CompilerOptions = {target}; + + var program_1 = newProgram(files, ["a.ts"], options); + checkResolvedModulesCache(program_1, "a.ts", { "b": "b.ts" }); + checkResolvedModulesCache(program_1, "b.ts", undefined); + + var program_2 = updateProgram(program_1, ["a.ts"], options, files => { + files[0].text = files[0].text.updateProgram("var x = 2"); + }); + assert.isTrue(program_1.structureIsReused); + + // content of resolution cache should not change + checkResolvedModulesCache(program_1, "a.ts", { "b": "b.ts" }); + checkResolvedModulesCache(program_1, "b.ts", undefined); + + // imports has changed - program is not reused + var program_3 = updateProgram(program_2, ["a.ts"], options, files => { + files[0].text = files[0].text.updateImportsAndExports(""); + }); + assert.isTrue(!program_2.structureIsReused); + checkResolvedModulesCache(program_3, "a.ts", undefined); + + var program_4 = updateProgram(program_3, ["a.ts"], options, files => { + let newImports = `import x from 'b' + import y from 'c' + `; + files[0].text = files[0].text.updateImportsAndExports(newImports); + }); + assert.isTrue(!program_3.structureIsReused); + checkResolvedModulesCache(program_4, "a.ts", {"b": "b.ts", "c": undefined}); + }); + }) +} \ No newline at end of file From 16deccdf987c15764d8481f73d964b6ecf3b4760 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 24 Jun 2015 13:20:43 -0700 Subject: [PATCH 04/20] revert unintentional change --- src/compiler/parser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 024cc96cb79f3..361c14040bf34 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5634,6 +5634,7 @@ namespace ts { // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. let result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /* setParentNode */ true) + return result; } From df508de39037b9386fc7bc496e3c191d829530ac Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 24 Jun 2015 15:18:22 -0700 Subject: [PATCH 05/20] fix formatting in parser --- src/compiler/parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 361c14040bf34..fbb3dad5037b8 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -5634,7 +5634,7 @@ namespace ts { // will immediately bail out of walking any subtrees when we can see that their parents // are already correct. let result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /* setParentNode */ true) - + return result; } From c968b3653e2cfba46e1491777d2e6917d12c7156 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 24 Jun 2015 17:40:04 -0700 Subject: [PATCH 06/20] addressed PR feedback --- src/compiler/checker.ts | 2 +- src/compiler/program.ts | 49 ++++++++----------- src/compiler/types.ts | 1 - src/compiler/utilities.ts | 17 ++++--- src/services/services.ts | 7 +-- .../unittests/services/documentRegistry.ts | 9 +++- 6 files changed, 39 insertions(+), 46 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b7020344aab5b..fdffb81321620 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -899,7 +899,7 @@ namespace ts { } } - let fileName = getResolvedModuleFileName(getSourceFile(location), moduleReferenceLiteral); + let fileName = getResolvedModuleFileName(getSourceFile(location), moduleReferenceLiteral.text); let sourceFile = fileName && host.getSourceFile(fileName); if (sourceFile) { if (sourceFile.symbol) { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 3f19c28a9b29e..7d1b0ce5fbf5f 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -163,10 +163,14 @@ namespace ts { let filesByName = createFileMap(fileName => host.getCanonicalFileName(fileName)); - // if old program was provided by has different target module kind - assume that it cannot be reused - // different module kind can lead to different way of resolving modules - if (oldProgram && oldProgram.getCompilerOptions().module !== options.module) { - oldProgram = undefined; + if (oldProgram) { + let oldOptions = oldProgram.getCompilerOptions(); + if ((oldOptions.module !== options.module) || + (oldOptions.noResolve !== options.noResolve) || + (oldOptions.target !== options.target) || + (oldOptions.noLib !== options.noLib)) { + oldProgram = undefined; + } } if (!tryReuseStructureFromOldProgram()) { @@ -222,10 +226,6 @@ namespace ts { } function tryReuseStructureFromOldProgram(): boolean { - if (!host.hasChanges) { - // host does not support method 'hasChanges' - return false; - } if (!oldProgram) { return false; } @@ -234,28 +234,19 @@ namespace ts { // there is an old program, check if we can reuse its structure let oldRootNames = oldProgram.getRootFileNames(); - if (rootNames.length !== oldRootNames.length) { - // different amount of root names - structure cannot be reused + if (!arrayIsEqualTo(oldRootNames, rootNames)) { return false; } - for (let i = 0; i < rootNames.length; i++) { - if (oldRootNames[i] !== rootNames[i]) { - // different order of root names - structure cannot be reused - return false; - } - } - // check if program source files has changed in the way that can affect structure of the program let newSourceFiles: SourceFile[] = []; for (let oldSourceFile of oldProgram.getSourceFiles()) { - let newSourceFile: SourceFile; - if (host.hasChanges(oldSourceFile)) { - newSourceFile = host.getSourceFile(oldSourceFile.fileName, options.target); - if (!newSourceFile) { - return false; - } - + let newSourceFile = host.getSourceFile(oldSourceFile.fileName, options.target); + if (!newSourceFile) { + return false; + } + + if (oldSourceFile !== newSourceFile) { // check tripleslash references if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { // tripleslash references has changed @@ -420,7 +411,7 @@ namespace ts { } function moduleNameIsEqualTo(a: LiteralExpression, b: LiteralExpression): boolean { - return a.text ===b.text; + return a.text === b.text; } function collectExternalModuleReferences(file: SourceFile): void { @@ -603,7 +594,7 @@ namespace ts { checkImports: { if (file.resolvedModules) { for (let moduleName of file.imports) { - if (!hasResolvedModuleName(file, moduleName)) { + if (!hasResolvedModuleName(file, moduleName.text)) { break checkImports; } } @@ -640,7 +631,7 @@ namespace ts { if (existingResolutions && hasProperty(existingResolutions, moduleNameExpr.text)) { let fileName = existingResolutions[moduleNameExpr.text]; // use existing resolution - setResolvedModuleName(file, moduleNameExpr, fileName); + setResolvedModuleName(file, moduleNameExpr.text, fileName); if (fileName) { findModuleSourceFile(fileName, moduleNameExpr); } @@ -651,7 +642,7 @@ namespace ts { searchName = normalizePath(combinePaths(searchPath, moduleNameExpr.text)); let referencedSourceFile = forEach(supportedExtensions, extension => findModuleSourceFile(searchName + extension, moduleNameExpr)); if (referencedSourceFile) { - setResolvedModuleName(file, moduleNameExpr, referencedSourceFile.fileName); + setResolvedModuleName(file, moduleNameExpr.text, referencedSourceFile.fileName); return; } @@ -662,7 +653,7 @@ namespace ts { searchPath = parentPath; } // mark reference as non-resolved - setResolvedModuleName(file, moduleNameExpr, undefined); + setResolvedModuleName(file, moduleNameExpr.text, undefined); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1dbb3cabe09d1..79ac4e85dd850 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2075,7 +2075,6 @@ namespace ts { getCanonicalFileName(fileName: string): string; useCaseSensitiveFileNames(): boolean; getNewLine(): string; - hasChanges?(oldFile: SourceFile): boolean; } export interface TextSpan { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 41cf35e0483e3..150101b641b9c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -78,7 +78,7 @@ namespace ts { return node.end - node.pos; } - export function arrayIsEqualTo(arr1: T[], arr2: T[], comparer: (a: T, b: T) => boolean): boolean { + export function arrayIsEqualTo(arr1: T[], arr2: T[], comparer?: (a: T, b: T) => boolean): boolean { if (!arr1 || !arr2) { return arr1 === arr2; } @@ -88,7 +88,8 @@ namespace ts { } for (let i = 0; i < arr1.length; ++i) { - if (!comparer(arr1[i], arr2[i])) { + let equals = comparer ? comparer(arr1[i], arr2[i]) : arr1[i] === arr2[i]; + if (!equals) { return false; } } @@ -96,20 +97,20 @@ namespace ts { return true; } - export function hasResolvedModuleName(sourceFile: SourceFile, moduleName: LiteralExpression): boolean { - return sourceFile.resolvedModules && hasProperty(sourceFile.resolvedModules, moduleName.text); + export function hasResolvedModuleName(sourceFile: SourceFile, moduleNameText: string): boolean { + return sourceFile.resolvedModules && hasProperty(sourceFile.resolvedModules, moduleNameText); } - export function getResolvedModuleFileName(sourceFile: SourceFile, moduleName: LiteralExpression): string { - return sourceFile.resolvedModules && sourceFile.resolvedModules[moduleName.text]; + export function getResolvedModuleFileName(sourceFile: SourceFile, moduleNameText: string): string { + return hasResolvedModuleName(sourceFile, moduleNameText) ? sourceFile.resolvedModules[moduleNameText]: undefined; } - export function setResolvedModuleName(sourceFile: SourceFile, moduleName: LiteralExpression, resolvedFileName: string): void { + export function setResolvedModuleName(sourceFile: SourceFile, moduleNameText: string, resolvedFileName: string): void { if (!sourceFile.resolvedModules) { sourceFile.resolvedModules = {}; } - sourceFile.resolvedModules[moduleName.text] = resolvedFileName; + sourceFile.resolvedModules[moduleNameText] = resolvedFileName; } // Returns true if this node contains a parse error anywhere underneath it. diff --git a/src/services/services.ts b/src/services/services.ts index a27327b77b46a..b8363fb4598f5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1898,7 +1898,7 @@ namespace ts { let getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); function getKeyFromCompilationSettings(settings: CompilerOptions): string { - return "_" + settings.target; // + "|" + settings.propagateEnumConstantoString() + return "_" + settings.target + "|" + settings.module + "|" + settings.noResolve; } function getBucketForCompilationSettings(settings: CompilerOptions, createIfMissing: boolean): FileMap { @@ -2472,8 +2472,6 @@ namespace ts { let newSettings = hostCache.compilationSettings(); let changesInCompilationSettingsAffectSyntax = oldSettings && oldSettings.target !== newSettings.target; - let reusableOldProgram = changesInCompilationSettingsAffectSyntax ? undefined : program; - // Now create a new compiler let newProgram = createProgram(hostCache.getRootFileNames(), newSettings, { getSourceFile: getOrCreateSourceFile, @@ -2484,8 +2482,7 @@ namespace ts { getDefaultLibFileName: (options) => host.getDefaultLibFileName(options), writeFile: (fileName, data, writeByteOrderMark) => { }, getCurrentDirectory: () => host.getCurrentDirectory(), - hasChanges: oldFile => oldFile.version !== hostCache.getVersion(oldFile.fileName) - }, reusableOldProgram); + }, program); // Release any files we have acquired in the old program but are // not part of the new program. diff --git a/tests/cases/unittests/services/documentRegistry.ts b/tests/cases/unittests/services/documentRegistry.ts index 8fc466b857f0e..50a144d305653 100644 --- a/tests/cases/unittests/services/documentRegistry.ts +++ b/tests/cases/unittests/services/documentRegistry.ts @@ -30,10 +30,15 @@ describe("DocumentRegistry", () => { assert(f1 !== f3, "Changed target: Expected to have different instances of document"); - compilerOptions.module = ts.ModuleKind.CommonJS; + compilerOptions.preserveConstEnums = true; var f4 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1"); - assert(f3 === f4, "Changed module: Expected to have the same instance of the document"); + assert(f3 === f4, "Changed preserveConstEnums: Expected to have the same instance of the document"); + + compilerOptions.module = ts.ModuleKind.System; + var f5 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1"); + + assert(f4 !== f5, "Changed module: Expected to have different instances of the document"); }); it("Acquiring document gets correct version 1", () => { From 66f673618a0a00ec298478ba37a70df751984f62 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 24 Jun 2015 18:12:02 -0700 Subject: [PATCH 07/20] addressed PR feedback --- src/compiler/program.ts | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7d1b0ce5fbf5f..27154a6610ac1 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -582,37 +582,12 @@ namespace ts { function processImportedModules(file: SourceFile, basePath: string) { collectExternalModuleReferences(file); if (file.imports.length) { - let allImportsInTheCache = false; - - // try to grab existing module resolutions from the old source file - let oldSourceFile: SourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); - if (oldSourceFile) { - file.resolvedModules = oldSourceFile.resolvedModules; - - // check that all imports are contained in resolved modules cache - // if at least one of imports in not in the cache - cache needs to be reinitialized - checkImports: { - if (file.resolvedModules) { - for (let moduleName of file.imports) { - if (!hasResolvedModuleName(file, moduleName.text)) { - break checkImports; - } - } - - allImportsInTheCache = true; - } - } + file.resolvedModules = {}; + let oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); + for (let moduleName of file.imports) { + resolveModule(moduleName, oldSourceFile && oldSourceFile.resolvedModules); } - if (!allImportsInTheCache) { - // initialize resolvedModules with empty map - // old module resolutions still can be used to avoid actual file system probing - let resolvedModules = file.resolvedModules; - file.resolvedModules = {}; - for (let moduleName of file.imports) { - resolveModule(moduleName, resolvedModules); - } - } } else { // no imports - drop cached module resolutions From 2685d409d577549f12d83420919cc442ecce13c2 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 9 Jul 2015 14:40:33 -0700 Subject: [PATCH 08/20] addressed PR feedback --- src/compiler/program.ts | 10 +- src/compiler/types.ts | 2 + src/compiler/utilities.ts | 2 +- .../cases/unittests/reuseProgramStructure.ts | 510 +++++++++--------- 4 files changed, 267 insertions(+), 257 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 27154a6610ac1..13c49989347b8 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -164,6 +164,8 @@ namespace ts { let filesByName = createFileMap(fileName => host.getCanonicalFileName(fileName)); if (oldProgram) { + // check properties that can affect structure of the program or module resolution strategy + // if any of these properties has changed - structure cannot be reused let oldOptions = oldProgram.getCompilerOptions(); if ((oldOptions.module !== options.module) || (oldOptions.noResolve !== options.noResolve) || @@ -246,7 +248,13 @@ namespace ts { return false; } - if (oldSourceFile !== newSourceFile) { + if (oldSourceFile !== newSourceFile) { + if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) { + // value of no-default-lib has changed + // this will affect if default library is injected into the list of files + return false; + } + // check tripleslash references if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) { // tripleslash references has changed diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 79ac4e85dd850..0fbbf0053ff8e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1244,6 +1244,8 @@ namespace ts { /* @internal */ getIdentifierCount(): number; /* @internal */ getSymbolCount(): number; /* @internal */ getTypeCount(): number; + + // For testing purposes only. /* @internal */ structureIsReused?: boolean; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 150101b641b9c..bb5112c58082c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -102,7 +102,7 @@ namespace ts { } export function getResolvedModuleFileName(sourceFile: SourceFile, moduleNameText: string): string { - return hasResolvedModuleName(sourceFile, moduleNameText) ? sourceFile.resolvedModules[moduleNameText]: undefined; + return hasResolvedModuleName(sourceFile, moduleNameText) ? sourceFile.resolvedModules[moduleNameText] : undefined; } export function setResolvedModuleName(sourceFile: SourceFile, moduleNameText: string, resolvedFileName: string): void { diff --git a/tests/cases/unittests/reuseProgramStructure.ts b/tests/cases/unittests/reuseProgramStructure.ts index 996f6bb316b45..1cb389f7717fc 100644 --- a/tests/cases/unittests/reuseProgramStructure.ts +++ b/tests/cases/unittests/reuseProgramStructure.ts @@ -3,259 +3,259 @@ /// module ts { - - const enum ChangedPart { - references = 1 << 0, - importsAndExports = 1 << 1, - program = 1 << 2 - } - - let newLine = "\r\n"; - - interface SourceFileWithText extends SourceFile { - sourceText?: SourceText; - } - - interface NamedSourceText { - name: string; - text: SourceText - } - - interface ProgramWithSourceTexts extends Program { - sourceTexts?: NamedSourceText[]; - } - - class SourceText implements IScriptSnapshot { - private fullText: string; - - constructor(private references: string, - private importsAndExports: string, - private program: string, - private changedPart: ChangedPart = 0, - private version = 0) { - } - - static New(references: string, importsAndExports: string, program: string): SourceText { - Debug.assert(references !== undefined); - Debug.assert(importsAndExports !== undefined); - Debug.assert(program !== undefined); - return new SourceText(references + newLine, importsAndExports + newLine, program || ""); - } - - public getVersion(): number { - return this.version; - } - - public updateReferences(newReferences: string): SourceText { - Debug.assert(newReferences !== undefined); - return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1); - } - public updateImportsAndExports(newImportsAndExports: string): SourceText { - Debug.assert(newImportsAndExports !== undefined); - return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1); - } - public updateProgram(newProgram: string): SourceText { - Debug.assert(newProgram !== undefined); - return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1); - } - - public getFullText() { - return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program); - } - - public getText(start: number, end: number): string { - return this.getFullText().substring(start, end); - } - - getLength(): number { - return this.getFullText().length; - } - - getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { - var oldText = oldSnapshot; - var oldSpan: TextSpan; - var newLength: number; - switch(oldText.changedPart ^ this.changedPart){ - case ChangedPart.references: - oldSpan = createTextSpan(0, oldText.references.length); - newLength = this.references.length; - break; - case ChangedPart.importsAndExports: - oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length); - newLength = this.importsAndExports.length - break; - case ChangedPart.program: - oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length); - newLength = this.program.length; - break; - default: - Debug.assert(false, "Unexpected change"); - } - - return createTextChangeRange(oldSpan, newLength); - } - } - - function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget): CompilerHost { - let files: Map = {}; - for (let t of texts) { - let file = createSourceFile(t.name, t.text.getFullText(), target); - file.sourceText = t.text; - files[t.name] = file; - } - return { - getSourceFile(fileName): SourceFile { - return files[fileName]; - }, - getDefaultLibFileName(): string { - return "lib.d.ts" - }, - writeFile(file, text) { - throw new Error("NYI"); - }, - getCurrentDirectory(): string { - return ""; - }, - getCanonicalFileName(fileName): string { - return sys.useCaseSensitiveFileNames ? fileName: fileName.toLowerCase(); - }, - useCaseSensitiveFileNames(): boolean { - return sys.useCaseSensitiveFileNames; - }, - getNewLine() : string { - return sys.newLine; - }, - hasChanges(oldFile: SourceFileWithText): boolean { - let current = files[oldFile.fileName]; - return !current || oldFile.sourceText.getVersion() !== current.sourceText.getVersion(); - } - - } - } - - function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): Program { - var host = createTestCompilerHost(texts, options.target); - let program = createProgram(rootNames, options, host); - program.sourceTexts = texts; - return program; - } - - function updateProgram(oldProgram: Program, rootNames: string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void) { - var texts: NamedSourceText[] = (oldProgram).sourceTexts.slice(0); - updater(texts); - var host = createTestCompilerHost(texts, options.target); - var program = createProgram(rootNames, options, host, oldProgram); - program.sourceTexts = texts; - return program; - } - - function getSizeOfMap(map: Map): number { - let size = 0; - for (let id in map) { - if (hasProperty(map, id)) { - size++; - } - } - return size; - } - - function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: Map): void { - let file = program.getSourceFile(fileName); - assert.isTrue(file !==undefined, `cannot find file ${fileName}`); - if (expectedContent === undefined) { - assert.isTrue(file.resolvedModules === undefined, "expected resolvedModules to be undefined"); - } - else { - assert.isTrue(file.resolvedModules !== undefined, "expected resolvedModuled to be set"); - let actualCacheSize = getSizeOfMap(file.resolvedModules); - let expectedSize = getSizeOfMap(expectedContent); - assert.isTrue(actualCacheSize === expectedSize, `expected actual size: ${actualCacheSize} to be equal to ${expectedSize}`); - - for (let id in expectedContent) { - if (hasProperty(expectedContent, id)) { - assert.isTrue(hasProperty(file.resolvedModules, id), `expected ${id} to be found in resolved modules`); - assert.isTrue(expectedContent[id] === file.resolvedModules[id], `expected '${expectedContent[id]}' to be equal to '${file.resolvedModules[id]}'`); - } - } - } - } - - describe("Reuse program structure", () => { - let target = ScriptTarget.Latest; - let files = [ - {name: "a.ts", text: SourceText.New(`/// `, "", `var x = 1`)}, - {name: "b.ts", text: SourceText.New(`/// `, "", `var y = 2`)}, - {name: "c.ts", text: SourceText.New("", "", `var z = 1;`)}, - ] - - it("successful if change does not affect imports", () => { - var program_1 = newProgram(files, ["a.ts"], {target}); - var program_2 = updateProgram(program_1, ["a.ts"], {target}, files => { - files[0].text = files[0].text.updateProgram("var x = 100"); - }); - assert.isTrue(program_1.structureIsReused); - }); - - it("fails if change affects tripleslash references", () => { - var program_1 = newProgram(files, ["a.ts"], {target}); - var program_2 = updateProgram(program_1, ["a.ts"], {target}, files => { - let newReferences = `/// - /// - `; - files[0].text = files[0].text.updateReferences(newReferences); - }); - assert.isTrue(!program_1.structureIsReused); - }); - - it("fails if change affects imports", () => { - var program_1 = newProgram(files, ["a.ts"], {target}); - var program_2 = updateProgram(program_1, ["a.ts"], {target}, files => { - files[2].text = files[2].text.updateImportsAndExports("import x from 'b'"); - }); - assert.isTrue(!program_1.structureIsReused); - }); - - it("fails if module kind changes", () => { - var program_1 = newProgram(files, ["a.ts"], {target, module: ModuleKind.CommonJS}); - var program_2 = updateProgram(program_1, ["a.ts"], {target, module: ModuleKind.AMD}, files => void 0); - assert.isTrue(!program_1.structureIsReused); - }); - - it("resolution cache follows imports", () => { - let files = [ - { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, - { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, - ]; - var options: CompilerOptions = {target}; - - var program_1 = newProgram(files, ["a.ts"], options); - checkResolvedModulesCache(program_1, "a.ts", { "b": "b.ts" }); - checkResolvedModulesCache(program_1, "b.ts", undefined); - - var program_2 = updateProgram(program_1, ["a.ts"], options, files => { - files[0].text = files[0].text.updateProgram("var x = 2"); - }); - assert.isTrue(program_1.structureIsReused); - - // content of resolution cache should not change - checkResolvedModulesCache(program_1, "a.ts", { "b": "b.ts" }); - checkResolvedModulesCache(program_1, "b.ts", undefined); - - // imports has changed - program is not reused - var program_3 = updateProgram(program_2, ["a.ts"], options, files => { - files[0].text = files[0].text.updateImportsAndExports(""); - }); - assert.isTrue(!program_2.structureIsReused); - checkResolvedModulesCache(program_3, "a.ts", undefined); - - var program_4 = updateProgram(program_3, ["a.ts"], options, files => { - let newImports = `import x from 'b' - import y from 'c' - `; - files[0].text = files[0].text.updateImportsAndExports(newImports); - }); - assert.isTrue(!program_3.structureIsReused); - checkResolvedModulesCache(program_4, "a.ts", {"b": "b.ts", "c": undefined}); - }); - }) + + const enum ChangedPart { + references = 1 << 0, + importsAndExports = 1 << 1, + program = 1 << 2 + } + + let newLine = "\r\n"; + + interface SourceFileWithText extends SourceFile { + sourceText?: SourceText; + } + + interface NamedSourceText { + name: string; + text: SourceText + } + + interface ProgramWithSourceTexts extends Program { + sourceTexts?: NamedSourceText[]; + } + + class SourceText implements IScriptSnapshot { + private fullText: string; + + constructor(private references: string, + private importsAndExports: string, + private program: string, + private changedPart: ChangedPart = 0, + private version = 0) { + } + + static New(references: string, importsAndExports: string, program: string): SourceText { + Debug.assert(references !== undefined); + Debug.assert(importsAndExports !== undefined); + Debug.assert(program !== undefined); + return new SourceText(references + newLine, importsAndExports + newLine, program || ""); + } + + public getVersion(): number { + return this.version; + } + + public updateReferences(newReferences: string): SourceText { + Debug.assert(newReferences !== undefined); + return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1); + } + public updateImportsAndExports(newImportsAndExports: string): SourceText { + Debug.assert(newImportsAndExports !== undefined); + return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1); + } + public updateProgram(newProgram: string): SourceText { + Debug.assert(newProgram !== undefined); + return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1); + } + + public getFullText() { + return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program); + } + + public getText(start: number, end: number): string { + return this.getFullText().substring(start, end); + } + + getLength(): number { + return this.getFullText().length; + } + + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { + var oldText = oldSnapshot; + var oldSpan: TextSpan; + var newLength: number; + switch (oldText.changedPart ^ this.changedPart) { + case ChangedPart.references: + oldSpan = createTextSpan(0, oldText.references.length); + newLength = this.references.length; + break; + case ChangedPart.importsAndExports: + oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length); + newLength = this.importsAndExports.length + break; + case ChangedPart.program: + oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length); + newLength = this.program.length; + break; + default: + Debug.assert(false, "Unexpected change"); + } + + return createTextChangeRange(oldSpan, newLength); + } + } + + function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget): CompilerHost { + let files: Map = {}; + for (let t of texts) { + let file = createSourceFile(t.name, t.text.getFullText(), target); + file.sourceText = t.text; + files[t.name] = file; + } + return { + getSourceFile(fileName): SourceFile { + return files[fileName]; + }, + getDefaultLibFileName(): string { + return "lib.d.ts" + }, + writeFile(file, text) { + throw new Error("NYI"); + }, + getCurrentDirectory(): string { + return ""; + }, + getCanonicalFileName(fileName): string { + return sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); + }, + useCaseSensitiveFileNames(): boolean { + return sys.useCaseSensitiveFileNames; + }, + getNewLine(): string { + return sys.newLine; + }, + hasChanges(oldFile: SourceFileWithText): boolean { + let current = files[oldFile.fileName]; + return !current || oldFile.sourceText.getVersion() !== current.sourceText.getVersion(); + } + + } + } + + function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): Program { + var host = createTestCompilerHost(texts, options.target); + let program = createProgram(rootNames, options, host); + program.sourceTexts = texts; + return program; + } + + function updateProgram(oldProgram: Program, rootNames: string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void) { + var texts: NamedSourceText[] = (oldProgram).sourceTexts.slice(0); + updater(texts); + var host = createTestCompilerHost(texts, options.target); + var program = createProgram(rootNames, options, host, oldProgram); + program.sourceTexts = texts; + return program; + } + + function getSizeOfMap(map: Map): number { + let size = 0; + for (let id in map) { + if (hasProperty(map, id)) { + size++; + } + } + return size; + } + + function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: Map): void { + let file = program.getSourceFile(fileName); + assert.isTrue(file !== undefined, `cannot find file ${fileName}`); + if (expectedContent === undefined) { + assert.isTrue(file.resolvedModules === undefined, "expected resolvedModules to be undefined"); + } + else { + assert.isTrue(file.resolvedModules !== undefined, "expected resolvedModuled to be set"); + let actualCacheSize = getSizeOfMap(file.resolvedModules); + let expectedSize = getSizeOfMap(expectedContent); + assert.isTrue(actualCacheSize === expectedSize, `expected actual size: ${actualCacheSize} to be equal to ${expectedSize}`); + + for (let id in expectedContent) { + if (hasProperty(expectedContent, id)) { + assert.isTrue(hasProperty(file.resolvedModules, id), `expected ${id} to be found in resolved modules`); + assert.isTrue(expectedContent[id] === file.resolvedModules[id], `expected '${expectedContent[id]}' to be equal to '${file.resolvedModules[id]}'`); + } + } + } + } + + describe("Reuse program structure", () => { + let target = ScriptTarget.Latest; + let files = [ + { name: "a.ts", text: SourceText.New(`/// `, "", `var x = 1`) }, + { name: "b.ts", text: SourceText.New(`/// `, "", `var y = 2`) }, + { name: "c.ts", text: SourceText.New("", "", `var z = 1;`) }, + ] + + it("successful if change does not affect imports", () => { + var program_1 = newProgram(files, ["a.ts"], { target }); + var program_2 = updateProgram(program_1, ["a.ts"], { target }, files => { + files[0].text = files[0].text.updateProgram("var x = 100"); + }); + assert.isTrue(program_1.structureIsReused); + }); + + it("fails if change affects tripleslash references", () => { + var program_1 = newProgram(files, ["a.ts"], { target }); + var program_2 = updateProgram(program_1, ["a.ts"], { target }, files => { + let newReferences = `/// + /// + `; + files[0].text = files[0].text.updateReferences(newReferences); + }); + assert.isTrue(!program_1.structureIsReused); + }); + + it("fails if change affects imports", () => { + var program_1 = newProgram(files, ["a.ts"], { target }); + var program_2 = updateProgram(program_1, ["a.ts"], { target }, files => { + files[2].text = files[2].text.updateImportsAndExports("import x from 'b'"); + }); + assert.isTrue(!program_1.structureIsReused); + }); + + it("fails if module kind changes", () => { + var program_1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS }); + var program_2 = updateProgram(program_1, ["a.ts"], { target, module: ModuleKind.AMD }, files => void 0); + assert.isTrue(!program_1.structureIsReused); + }); + + it("resolution cache follows imports", () => { + let files = [ + { name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") }, + { name: "b.ts", text: SourceText.New("", "", "var y = 2") }, + ]; + var options: CompilerOptions = { target }; + + var program_1 = newProgram(files, ["a.ts"], options); + checkResolvedModulesCache(program_1, "a.ts", { "b": "b.ts" }); + checkResolvedModulesCache(program_1, "b.ts", undefined); + + var program_2 = updateProgram(program_1, ["a.ts"], options, files => { + files[0].text = files[0].text.updateProgram("var x = 2"); + }); + assert.isTrue(program_1.structureIsReused); + + // content of resolution cache should not change + checkResolvedModulesCache(program_1, "a.ts", { "b": "b.ts" }); + checkResolvedModulesCache(program_1, "b.ts", undefined); + + // imports has changed - program is not reused + var program_3 = updateProgram(program_2, ["a.ts"], options, files => { + files[0].text = files[0].text.updateImportsAndExports(""); + }); + assert.isTrue(!program_2.structureIsReused); + checkResolvedModulesCache(program_3, "a.ts", undefined); + + var program_4 = updateProgram(program_3, ["a.ts"], options, files => { + let newImports = `import x from 'b' + import y from 'c' + `; + files[0].text = files[0].text.updateImportsAndExports(newImports); + }); + assert.isTrue(!program_3.structureIsReused); + checkResolvedModulesCache(program_4, "a.ts", { "b": "b.ts", "c": undefined }); + }); + }) } \ No newline at end of file From e15c70054963cd990d2c99fbaebfd0241bcf2c7b Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 9 Jul 2015 14:45:39 -0700 Subject: [PATCH 09/20] clean old program to prevent it from being captured into the closure --- src/compiler/program.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2aeab800f0f4a..957b519af5e29 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -188,6 +188,9 @@ namespace ts { verifyCompilerOptions(); + // unconditionally set oldProgram to undefined to prevent it from being captured in closure + oldProgram = undefined; + programTime += new Date().getTime() - start; program = { From 9332f7e1e3e9b47314e838db660de16e82939e69 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 13 Jul 2015 17:44:50 -0700 Subject: [PATCH 10/20] introduce ModuleResolutionHost interface --- src/compiler/program.ts | 112 ++++++++++++------ src/compiler/types.ts | 17 ++- src/harness/harness.ts | 55 +++++---- src/harness/harnessLanguageService.ts | 3 +- src/harness/projectsRunner.ts | 7 +- src/services/services.ts | 41 ++++++- src/services/shims.ts | 31 ++++- .../cases/unittests/reuseProgramStructure.ts | 11 +- 8 files changed, 204 insertions(+), 73 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 957b519af5e29..f429e338cfbd7 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -27,6 +27,52 @@ namespace ts { } return undefined; } + + export function getDefaultModuleNameResolver(options: CompilerOptions): ModuleNameResolver { + // TODO: return different resolver based on compiler options (i.e. module kind) + return resolveModuleName; + } + + export function resolveTripleslashReference(moduleName: string, containingFile: string): string { + let basePath = getDirectoryPath(containingFile); + let referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); + return normalizePath(referencedFileName); + } + + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { + + let searchPath = getDirectoryPath(containingFile); + let searchName: string; + + let failedLookupLocations: string[] = []; + + let referencedSourceFile: string; + while (true) { + searchName = normalizePath(combinePaths(searchPath, moduleName)); + referencedSourceFile = forEach(supportedExtensions, extension => { + let candidate = searchName + extension; + let ok = host.fileExists(candidate) ? candidate : undefined; + if (host.fileExists(candidate)) { + return candidate; + } + else { + failedLookupLocations.push(candidate); + } + }); + + if (referencedSourceFile) { + break; + } + + let parentPath = getDirectoryPath(searchPath); + if (parentPath === searchPath) { + break; + } + searchPath = parentPath; + } + + return { resolvedFileName: referencedSourceFile, failedLookupLocations }; + } export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { let currentDirectory: string; @@ -94,7 +140,11 @@ namespace ts { } const newLine = getNewLineCharacter(options); - + + let moduleResolutionHost: ModuleResolutionHost = { + fileExists: fileName => sys.fileExists(fileName), + } + return { getSourceFile, getDefaultLibFileName: options => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), getDefaultLibFileName(options)), @@ -102,7 +152,8 @@ namespace ts { getCurrentDirectory: () => currentDirectory || (currentDirectory = sys.getCurrentDirectory()), useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, getCanonicalFileName, - getNewLine: () => newLine + getNewLine: () => newLine, + getModuleResolutionHost: () => moduleResolutionHost }; } @@ -160,6 +211,20 @@ namespace ts { let start = new Date().getTime(); host = host || createCompilerHost(options); + + // initialize resolveModuleNameWorker only if noResolve is false + let resolveModuleNameWorker: (moduleName: string, containingFile: string) => string; + if (!options.noResolve) { + resolveModuleNameWorker = host.resolveModuleName; + if (!resolveModuleNameWorker) { + Debug.assert(host.getModuleResolutionHost !== undefined); + let defaultResolver = getDefaultModuleNameResolver(options); + resolveModuleNameWorker = (moduleName, containingFile) => { + let moduleResolution = defaultResolver(moduleName, containingFile, options, host.getModuleResolutionHost()); + return moduleResolution.resolvedFileName; + } + } + } let filesByName = createFileMap(fileName => host.getCanonicalFileName(fileName)); @@ -622,8 +687,8 @@ namespace ts { function processReferencedFiles(file: SourceFile, basePath: string) { forEach(file.referencedFiles, ref => { - let referencedFileName = isRootedDiskPath(ref.fileName) ? ref.fileName : combinePaths(basePath, ref.fileName); - processSourceFile(normalizePath(referencedFileName), /* isDefaultLib */ false, file, ref.pos, ref.end); + let referencedFileName = resolveTripleslashReference(ref.fileName, file.fileName); + processSourceFile(referencedFileName, /* isDefaultLib */ false, file, ref.pos, ref.end); }); } @@ -647,36 +712,17 @@ namespace ts { return findSourceFile(fileName, /* isDefaultLib */ false, file, nameLiteral.pos, nameLiteral.end - nameLiteral.pos); } - function resolveModule(moduleNameExpr: LiteralExpression, existingResolutions: Map): void { - let searchPath = basePath; - let searchName: string; - - if (existingResolutions && hasProperty(existingResolutions, moduleNameExpr.text)) { - let fileName = existingResolutions[moduleNameExpr.text]; - // use existing resolution - setResolvedModuleName(file, moduleNameExpr.text, fileName); - if (fileName) { - findModuleSourceFile(fileName, moduleNameExpr); - } - return; - } - - while (true) { - searchName = normalizePath(combinePaths(searchPath, moduleNameExpr.text)); - let referencedSourceFile = forEach(supportedExtensions, extension => findModuleSourceFile(searchName + extension, moduleNameExpr)); - if (referencedSourceFile) { - setResolvedModuleName(file, moduleNameExpr.text, referencedSourceFile.fileName); - return; - } - - let parentPath = getDirectoryPath(searchPath); - if (parentPath === searchPath) { - break; - } - searchPath = parentPath; + function resolveModule(moduleNameExpr: LiteralExpression, existingResolutions: Map): void { + Debug.assert(resolveModuleNameWorker !== undefined); + + let resolvedModuleName = existingResolutions && hasProperty(existingResolutions, moduleNameExpr.text) + ? existingResolutions[moduleNameExpr.text] + : resolveModuleNameWorker(moduleNameExpr.text, file.fileName); + + setResolvedModuleName(file, moduleNameExpr.text, resolvedModuleName); + if (resolvedModuleName) { + findModuleSourceFile(resolvedModuleName, moduleNameExpr); } - // mark reference as non-resolved - setResolvedModuleName(file, moduleNameExpr.text, undefined); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 31a8fbb408258..d404ba2e00d3c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1285,7 +1285,7 @@ namespace ts { getCurrentDirectory(): string; } - export interface ParseConfigHost { + export interface ParseConfigHost extends ModuleResolutionHost { readDirectory(rootDir: string, extension: string, exclude: string[]): string[]; } @@ -2197,7 +2197,18 @@ namespace ts { byteOrderMark = 0xFEFF, tab = 0x09, // \t verticalTab = 0x0B, // \v + } + + export interface ModuleResolutionHost { + fileExists(fileName: string): boolean; + } + + export interface ResolvedModule { + resolvedFileName: string; + failedLookupLocations: string[]; } + + export type ModuleNameResolver = (moduleName: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => ResolvedModule; export interface CompilerHost { getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile; @@ -2207,6 +2218,10 @@ namespace ts { getCanonicalFileName(fileName: string): string; useCaseSensitiveFileNames(): boolean; getNewLine(): string; + + // compiler host should implement one of these methods + resolveModuleName?(moduleName: string, containingFile: string): string; + getModuleResolutionHost?(): ModuleResolutionHost; } export interface TextSpan { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 9f26887ff9090..2ac806c78e56e 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -866,41 +866,48 @@ module Harness { } }; inputFiles.forEach(register); + + function getSourceFile(fn: string, languageVersion: ts.ScriptTarget) { + fn = ts.normalizePath(fn); + if (Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(fn))) { + return filemap[getCanonicalFileName(fn)]; + } + else if (currentDirectory) { + var canonicalAbsolutePath = getCanonicalFileName(ts.getNormalizedAbsolutePath(fn, currentDirectory)); + return Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(canonicalAbsolutePath)) ? filemap[canonicalAbsolutePath] : undefined; + } + else if (fn === fourslashFileName) { + var tsFn = 'tests/cases/fourslash/' + fourslashFileName; + fourslashSourceFile = fourslashSourceFile || createSourceFileAndAssertInvariants(tsFn, Harness.IO.readFile(tsFn), scriptTarget); + return fourslashSourceFile; + } + else { + if (fn === defaultLibFileName) { + return languageVersion === ts.ScriptTarget.ES6 ? defaultES6LibSourceFile : defaultLibSourceFile; + } + // Don't throw here -- the compiler might be looking for a test that actually doesn't exist as part of the TC + return undefined; + } + } let newLine = newLineKind === ts.NewLineKind.CarriageReturnLineFeed ? carriageReturnLineFeed : newLineKind === ts.NewLineKind.LineFeed ? lineFeed : ts.sys.newLine; - + + let moduleResolutionHost: ts.ModuleResolutionHost = { + fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined, + } + return { getCurrentDirectory, - getSourceFile: (fn, languageVersion) => { - fn = ts.normalizePath(fn); - if (Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(fn))) { - return filemap[getCanonicalFileName(fn)]; - } - else if (currentDirectory) { - var canonicalAbsolutePath = getCanonicalFileName(ts.getNormalizedAbsolutePath(fn, currentDirectory)); - return Object.prototype.hasOwnProperty.call(filemap, getCanonicalFileName(canonicalAbsolutePath)) ? filemap[canonicalAbsolutePath] : undefined; - } - else if (fn === fourslashFileName) { - var tsFn = 'tests/cases/fourslash/' + fourslashFileName; - fourslashSourceFile = fourslashSourceFile || createSourceFileAndAssertInvariants(tsFn, Harness.IO.readFile(tsFn), scriptTarget); - return fourslashSourceFile; - } - else { - if (fn === defaultLibFileName) { - return languageVersion === ts.ScriptTarget.ES6 ? defaultES6LibSourceFile : defaultLibSourceFile; - } - // Don't throw here -- the compiler might be looking for a test that actually doesn't exist as part of the TC - return undefined; - } - }, + getSourceFile, getDefaultLibFileName: options => defaultLibFileName, writeFile, getCanonicalFileName, useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, - getNewLine: () => newLine + getNewLine: () => newLine, + getModuleResolutionHost: () => moduleResolutionHost }; } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6cb92df59489a..bb149cc71c920 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -228,7 +228,8 @@ module Harness.LanguageService { readDirectory(rootDir: string, extension: string): string { throw new Error("NYI"); - } + } + fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } log(s: string): void { this.nativeHost.log(s); } trace(s: string): void { this.nativeHost.trace(s); } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 5eb0016ff8d00..20866591248e4 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -128,6 +128,10 @@ class ProjectRunner extends RunnerBase { getSourceFileText: (fileName: string) => string, writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void): CompileProjectFilesResult { + let moduleResolutionHost: ts.ModuleResolutionHost = { + fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined, + } + var program = ts.createProgram(getInputFiles(), createCompilerOptions(), createCompilerHost()); var errors = ts.getPreEmitDiagnostics(program); @@ -190,7 +194,8 @@ class ProjectRunner extends RunnerBase { getCurrentDirectory, getCanonicalFileName: Harness.Compiler.getCanonicalFileName, useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, - getNewLine: () => ts.sys.newLine + getNewLine: () => ts.sys.newLine, + getModuleResolutionHost: () => moduleResolutionHost }; } } diff --git a/src/services/services.ts b/src/services/services.ts index 8972d0b07d0de..a61d02cb3bcbf 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -122,7 +122,7 @@ namespace ts { referencedFiles: FileReference[]; importedFiles: FileReference[]; isLibFile: boolean - } + } let scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); @@ -967,6 +967,10 @@ namespace ts { trace? (s: string): void; error? (s: string): void; useCaseSensitiveFileNames? (): boolean; + + // LS host should implement one of these methods + resolveModuleName?(moduleName: string, containingFile: string): string; + getModuleResolutionHost?(): ModuleResolutionHost; } // @@ -1783,6 +1787,10 @@ namespace ts { // Output let outputText: string; + + let moduleResolutionHost: ModuleResolutionHost = { + fileExists: fileName => fileName === inputFileName, + } // Create a compilerHost object to allow the compiler to read and write files let compilerHost: CompilerHost = { @@ -1795,7 +1803,8 @@ namespace ts { useCaseSensitiveFileNames: () => false, getCanonicalFileName: fileName => fileName, getCurrentDirectory: () => "", - getNewLine: () => newLine + getNewLine: () => newLine, + getModuleResolutionHost: () => moduleResolutionHost }; let program = createProgram([inputFileName], options, compilerHost); @@ -1989,6 +1998,11 @@ namespace ts { reportStats }; } + + export function resolveModuleName(fileName: string, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { + let resolver = getDefaultModuleNameResolver(compilerOptions); + return resolver(moduleName, fileName, compilerOptions, host); + } export function preProcessFile(sourceText: string, readImportFiles = true): PreProcessedFileInfo { let referencedFiles: FileReference[] = []; @@ -2472,9 +2486,9 @@ namespace ts { let oldSettings = program && program.getCompilerOptions(); let newSettings = hostCache.compilationSettings(); let changesInCompilationSettingsAffectSyntax = oldSettings && oldSettings.target !== newSettings.target; - + // Now create a new compiler - let newProgram = createProgram(hostCache.getRootFileNames(), newSettings, { + let compilerHost: CompilerHost = { getSourceFile: getOrCreateSourceFile, getCancellationToken: () => cancellationToken, getCanonicalFileName, @@ -2483,7 +2497,24 @@ namespace ts { getDefaultLibFileName: (options) => host.getDefaultLibFileName(options), writeFile: (fileName, data, writeByteOrderMark) => { }, getCurrentDirectory: () => host.getCurrentDirectory(), - }, program); + }; + + if (host.resolveModuleName) { + compilerHost.resolveModuleName = (moduleName, containingFile) => host.resolveModuleName(moduleName, containingFile) + } + else if (host.getModuleResolutionHost) { + compilerHost.getModuleResolutionHost = () => host.getModuleResolutionHost() + } + else { + compilerHost.getModuleResolutionHost = () => { + // stub missing host functionality + return { + fileExists: fileName => hostCache.getOrCreateEntry(fileName) != undefined, + }; + } + } + + let newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program); // Release any files we have acquired in the old program but are // not part of the new program. diff --git a/src/services/shims.ts b/src/services/shims.ts index 6e765eff499f1..bca712f1dab97 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -57,10 +57,12 @@ namespace ts { getNewLine?(): string; getProjectVersion?(): string; useCaseSensitiveFileNames?(): boolean; + + getModuleResolutionsForFile?(fileName: string): string; } /** Public interface of the the of a config service shim instance.*/ - export interface CoreServicesShimHost extends Logger { + export interface CoreServicesShimHost extends Logger, ModuleResolutionHost { /** Returns a JSON-encoded value of the type: string[] */ readDirectory(rootDir: string, extension: string): string; } @@ -249,8 +251,20 @@ namespace ts { private files: string[]; private loggingEnabled = false; private tracingEnabled = false; + private lastRequestedFile: string; + private lastRequestedModuleResolutions: Map; constructor(private shimHost: LanguageServiceShimHost) { + if ("getModuleResolutionsForFile" in this.shimHost) { + (this).resolveModuleName = (moduleName: string, containingFile: string) => { + if (this.lastRequestedFile !== containingFile) { + this.lastRequestedModuleResolutions = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); + this.lastRequestedFile = containingFile; + } + + return this.lastRequestedModuleResolutions[moduleName]; + }; + } } public log(s: string): void { @@ -267,7 +281,7 @@ namespace ts { public error(s: string): void { this.shimHost.error(s); - } + } public getProjectVersion(): string { if (!this.shimHost.getProjectVersion) { @@ -379,6 +393,10 @@ namespace ts { var encoded = this.shimHost.readDirectory(rootDir, extension); return JSON.parse(encoded); } + + public fileExists(fileName: string): boolean { + return this.shimHost.fileExists(fileName); + } } function simpleForwardCall(logger: Logger, actionDescription: string, action: () => any, logPerformance: boolean): any { @@ -505,7 +523,7 @@ namespace ts { private realizeDiagnostics(diagnostics: Diagnostic[]): { message: string; start: number; length: number; category: string; }[]{ var newLine = this.getNewLine(); return ts.realizeDiagnostics(diagnostics, newLine); - } + } public getSyntacticClassifications(fileName: string, start: number, length: number): string { return this.forwardJSONCall( @@ -881,6 +899,13 @@ namespace ts { private forwardJSONCall(actionDescription: string, action: () => any): any { return forwardJSONCall(this.logger, actionDescription, action, this.logPerformance); } + + public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string { + return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => { + let compilerOptions = JSON.parse(compilerOptionsJson); + return resolveModuleName(fileName, moduleName, compilerOptions, this.host); + }); + } public getPreProcessedFileInfo(fileName: string, sourceTextSnapshot: IScriptSnapshot): string { return this.forwardJSONCall( diff --git a/tests/cases/unittests/reuseProgramStructure.ts b/tests/cases/unittests/reuseProgramStructure.ts index 1cb389f7717fc..5314ff48f55e3 100644 --- a/tests/cases/unittests/reuseProgramStructure.ts +++ b/tests/cases/unittests/reuseProgramStructure.ts @@ -103,6 +103,11 @@ module ts { file.sourceText = t.text; files[t.name] = file; } + + let moduleResolutionHost: ModuleResolutionHost = { + fileExists: fileName => hasProperty(files, fileName) + } + return { getSourceFile(fileName): SourceFile { return files[fileName]; @@ -125,11 +130,7 @@ module ts { getNewLine(): string { return sys.newLine; }, - hasChanges(oldFile: SourceFileWithText): boolean { - let current = files[oldFile.fileName]; - return !current || oldFile.sourceText.getVersion() !== current.sourceText.getVersion(); - } - + getModuleResolutionHost: () => moduleResolutionHost } } From d7661ecf8a05446e9c411db478d4233888c17c78 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 29 Jul 2015 16:24:16 -0700 Subject: [PATCH 11/20] do not try to resolve modules that has '!' in the name, put .tsx extension to the end of the list --- src/compiler/core.ts | 4 ++-- src/compiler/program.ts | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 427084b57795c..1446ba1d45833 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -709,7 +709,7 @@ namespace ts { /** * List of supported extensions in order of file resolution precedence. */ - export const supportedExtensions = [".tsx", ".ts", ".d.ts"]; + export const supportedExtensions = [".ts", ".d.ts", ".tsx"]; const extensionsToRemove = [".d.ts", ".ts", ".js", ".tsx", ".jsx"]; export function removeFileExtension(path: string): string { @@ -805,4 +805,4 @@ namespace ts { Debug.assert(false, message); } } -} +} diff --git a/src/compiler/program.ts b/src/compiler/program.ts index f429e338cfbd7..1b16996aca662 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -40,7 +40,12 @@ namespace ts { } function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { - + + // module names that contain '!' are used to reference resources and are not resolved to actual files on disk + if (moduleName.indexOf('!') != -1) { + return { resolvedFileName: undefined, failedLookupLocations: [] }; + } + let searchPath = getDirectoryPath(containingFile); let searchName: string; @@ -50,8 +55,13 @@ namespace ts { while (true) { searchName = normalizePath(combinePaths(searchPath, moduleName)); referencedSourceFile = forEach(supportedExtensions, extension => { + if (extension === ".tsx" && !compilerOptions.jsx) { + // resolve .tsx files only if jsx support is enabled + // 'logical not' handles both undefined and None cases + return undefined; + } + let candidate = searchName + extension; - let ok = host.fileExists(candidate) ? candidate : undefined; if (host.fileExists(candidate)) { return candidate; } From 544a7939f0f9c24d29cce16c88283507b842ba13 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 29 Jul 2015 16:25:22 -0700 Subject: [PATCH 12/20] return ambient external modules as a results of preprocessing --- src/harness/harnessLanguageService.ts | 1 + src/services/services.ts | 30 +++++++++++++++---- src/services/shims.ts | 3 +- .../amd/invalidRootFile.errors.txt | 4 +-- .../node/invalidRootFile.errors.txt | 4 +-- .../unittests/services/preProcessFile.ts | 23 ++++++++++++++ 6 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index bb149cc71c920..fc31993bbbecc 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -417,6 +417,7 @@ module Harness.LanguageService { var convertResult: ts.PreProcessedFileInfo = { referencedFiles: [], importedFiles: [], + ambientExternalModules: [], isLibFile: shimResult.isLibFile }; diff --git a/src/services/services.ts b/src/services/services.ts index a61d02cb3bcbf..7fe96969b659e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -121,6 +121,7 @@ namespace ts { export interface PreProcessedFileInfo { referencedFiles: FileReference[]; importedFiles: FileReference[]; + ambientExternalModules: string[]; isLibFile: boolean } @@ -2007,6 +2008,7 @@ namespace ts { export function preProcessFile(sourceText: string, readImportFiles = true): PreProcessedFileInfo { let referencedFiles: FileReference[] = []; let importedFiles: FileReference[] = []; + let ambientExternalModules: string[]; let isNoDefaultLib = false; function processTripleSlashDirectives(): void { @@ -2023,6 +2025,13 @@ namespace ts { } }); } + + function recordAmbientExternalModule(): void { + if (!ambientExternalModules) { + ambientExternalModules = []; + } + ambientExternalModules.push(scanner.getTokenValue()); + } function recordModuleName() { let importPath = scanner.getTokenValue(); @@ -2033,7 +2042,7 @@ namespace ts { end: pos + importPath.length }); } - + function processImport(): void { scanner.setText(sourceText); let token = scanner.scan(); @@ -2049,7 +2058,18 @@ namespace ts { // export {a as b} from "mod" while (token !== SyntaxKind.EndOfFileToken) { - if (token === SyntaxKind.ImportKeyword) { + if (token === SyntaxKind.DeclareKeyword) { + // declare module "mod" + token = scanner.scan(); + if (token === SyntaxKind.ModuleKeyword) { + token = scanner.scan(); + if (token === SyntaxKind.StringLiteral) { + recordAmbientExternalModule(); + continue; + } + } + } + else if (token === SyntaxKind.ImportKeyword) { token = scanner.scan(); if (token === SyntaxKind.StringLiteral) { // import "mod"; @@ -2057,7 +2077,7 @@ namespace ts { continue; } else { - if (token === SyntaxKind.Identifier) { + if (token === SyntaxKind.Identifier || isKeyword(token)) { token = scanner.scan(); if (token === SyntaxKind.FromKeyword) { token = scanner.scan(); @@ -2114,7 +2134,7 @@ namespace ts { token = scanner.scan(); if (token === SyntaxKind.AsKeyword) { token = scanner.scan(); - if (token === SyntaxKind.Identifier) { + if (token === SyntaxKind.Identifier || isKeyword(token)) { token = scanner.scan(); if (token === SyntaxKind.FromKeyword) { token = scanner.scan(); @@ -2170,7 +2190,7 @@ namespace ts { processImport(); } processTripleSlashDirectives(); - return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib }; + return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules }; } /// Helpers diff --git a/src/services/shims.ts b/src/services/shims.ts index bca712f1dab97..ec2ff68247672 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -903,7 +903,7 @@ namespace ts { public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string { return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => { let compilerOptions = JSON.parse(compilerOptionsJson); - return resolveModuleName(fileName, moduleName, compilerOptions, this.host); + return resolveModuleName(normalizeSlashes(fileName), moduleName, compilerOptions, this.host); }); } @@ -915,6 +915,7 @@ namespace ts { var convertResult = { referencedFiles: [], importedFiles: [], + ambientExternalModules: result.ambientExternalModules, isLibFile: result.isLibFile }; diff --git a/tests/baselines/reference/project/invalidRootFile/amd/invalidRootFile.errors.txt b/tests/baselines/reference/project/invalidRootFile/amd/invalidRootFile.errors.txt index bafcc39f48d33..4433f671a1e99 100644 --- a/tests/baselines/reference/project/invalidRootFile/amd/invalidRootFile.errors.txt +++ b/tests/baselines/reference/project/invalidRootFile/amd/invalidRootFile.errors.txt @@ -1,6 +1,6 @@ error TS6053: File 'a.ts' not found. -error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.tsx', '.ts', '.d.ts'. +error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.d.ts', '.tsx'. !!! error TS6053: File 'a.ts' not found. -!!! error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.tsx', '.ts', '.d.ts'. \ No newline at end of file +!!! error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.d.ts', '.tsx'. \ No newline at end of file diff --git a/tests/baselines/reference/project/invalidRootFile/node/invalidRootFile.errors.txt b/tests/baselines/reference/project/invalidRootFile/node/invalidRootFile.errors.txt index bafcc39f48d33..4433f671a1e99 100644 --- a/tests/baselines/reference/project/invalidRootFile/node/invalidRootFile.errors.txt +++ b/tests/baselines/reference/project/invalidRootFile/node/invalidRootFile.errors.txt @@ -1,6 +1,6 @@ error TS6053: File 'a.ts' not found. -error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.tsx', '.ts', '.d.ts'. +error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.d.ts', '.tsx'. !!! error TS6053: File 'a.ts' not found. -!!! error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.tsx', '.ts', '.d.ts'. \ No newline at end of file +!!! error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.d.ts', '.tsx'. \ No newline at end of file diff --git a/tests/cases/unittests/services/preProcessFile.ts b/tests/cases/unittests/services/preProcessFile.ts index c504f444f18fa..982c45f0f2d5e 100644 --- a/tests/cases/unittests/services/preProcessFile.ts +++ b/tests/cases/unittests/services/preProcessFile.ts @@ -50,6 +50,7 @@ describe('PreProcessFile:', function () { 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: [], + ambientExternalModules: undefined, isLibFile: false }); }), @@ -59,6 +60,7 @@ describe('PreProcessFile:', function () { { referencedFiles: [], importedFiles: [], + ambientExternalModules: undefined, isLibFile: false }); }), @@ -69,6 +71,7 @@ describe('PreProcessFile:', function () { 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 }], + ambientExternalModules: undefined, isLibFile: false }); }), @@ -78,6 +81,7 @@ describe('PreProcessFile:', function () { { referencedFiles: [], importedFiles: [], + ambientExternalModules: undefined, isLibFile: false }); }), @@ -87,6 +91,7 @@ describe('PreProcessFile:', function () { { referencedFiles: [], importedFiles: [{ fileName: "r3.ts", pos: 73, end: 78 }], + ambientExternalModules: undefined, isLibFile: false }); }), @@ -96,6 +101,7 @@ describe('PreProcessFile:', function () { { 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 }], + ambientExternalModules: undefined, isLibFile: false }); }), @@ -105,6 +111,7 @@ describe('PreProcessFile:', function () { { referencedFiles: [{ fileName: "refFile1.ts", pos: 0, end: 35 }], importedFiles: [{ fileName: "r1.ts", pos: 91, end: 96 }, { fileName: "r3.ts", pos: 148, end: 153 }], + ambientExternalModules: undefined, isLibFile: false }) }); @@ -129,6 +136,7 @@ describe('PreProcessFile:', function () { { fileName: "m6", pos: 160, end: 162 }, { fileName: "m7", pos: 199, end: 201 } ], + ambientExternalModules: undefined, isLibFile: false }) }); @@ -147,9 +155,24 @@ describe('PreProcessFile:', function () { { fileName: "m3", pos: 63, end: 65 }, { fileName: "m4", pos: 101, end: 103 }, ], + ambientExternalModules: undefined, isLibFile: false }) }); + + it("Correctly return ambient external modules", () => { + test(` + declare module A {} + declare module "B" {} + function foo() { + } + `, false, { + referencedFiles: [], + importedFiles: [], + ambientExternalModules: ["B"], + isLibFile: false + }) + }); }); }); From f9c07586b498e4dc7f6598602873000dd1c8eb14 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 29 Jul 2015 18:14:13 -0700 Subject: [PATCH 13/20] fix formatting --- src/services/services.ts | 4 ++-- src/services/shims.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index fb12cddf7f8af..59058f4812533 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -127,7 +127,7 @@ namespace ts { importedFiles: FileReference[]; ambientExternalModules: string[]; isLibFile: boolean - } + } let scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); @@ -2097,7 +2097,7 @@ namespace ts { end: pos + importPath.length }); } - + function processImport(): void { scanner.setText(sourceText); let token = scanner.scan(); diff --git a/src/services/shims.ts b/src/services/shims.ts index d6d0f333a6519..21b6593b5f04b 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -297,7 +297,7 @@ namespace ts { public error(s: string): void { this.shimHost.error(s); - } + } public getProjectVersion(): string { if (!this.shimHost.getProjectVersion) { @@ -549,7 +549,7 @@ namespace ts { private realizeDiagnostics(diagnostics: Diagnostic[]): { message: string; start: number; length: number; category: string; }[]{ var newLine = this.getNewLine(); return ts.realizeDiagnostics(diagnostics, newLine); - } + } public getSyntacticClassifications(fileName: string, start: number, length: number): string { return this.forwardJSONCall( From 49ad395de197a87ae704537c20eb9feda29202fe Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 3 Aug 2015 17:42:29 -0700 Subject: [PATCH 14/20] resolveModuleName => resolvedModuleNames, added tests --- Jakefile.js | 3 +- src/compiler/program.ts | 51 +++-- src/compiler/types.ts | 2 +- src/server/editorServices.ts | 56 +++-- src/services/services.ts | 9 +- src/services/shims.ts | 14 +- .../cases/unittests/cachingInServerLSHost.ts | 206 ++++++++++++++++++ 7 files changed, 283 insertions(+), 58 deletions(-) create mode 100644 tests/cases/unittests/cachingInServerLSHost.ts diff --git a/Jakefile.js b/Jakefile.js index 059da04b570f9..396f15e2730d2 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -142,7 +142,8 @@ var harnessSources = harnessCoreSources.concat([ "versionCache.ts", "convertToBase64.ts", "transpile.ts", - "reuseProgramStructure.ts" + "reuseProgramStructure.ts", + "cachingInServerLSHost.ts" ].map(function (f) { return path.join(unittestsDirectory, f); })).concat([ diff --git a/src/compiler/program.ts b/src/compiler/program.ts index b56423a8c49da..4e13de58953ce 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -224,15 +224,17 @@ namespace ts { host = host || createCompilerHost(options); // initialize resolveModuleNameWorker only if noResolve is false - let resolveModuleNameWorker: (moduleName: string, containingFile: string) => string; + let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => string[]; if (!options.noResolve) { - resolveModuleNameWorker = host.resolveModuleName; - if (!resolveModuleNameWorker) { + resolveModuleNamesWorker = host.resolveModuleNames; + if (!resolveModuleNamesWorker) { Debug.assert(host.getModuleResolutionHost !== undefined); let defaultResolver = getDefaultModuleNameResolver(options); - resolveModuleNameWorker = (moduleName, containingFile) => { - let moduleResolution = defaultResolver(moduleName, containingFile, options, host.getModuleResolutionHost()); - return moduleResolution.resolvedFileName; + resolveModuleNamesWorker = (moduleNames, containingFile) => { + return map(moduleNames, moduleName => { + let moduleResolution = defaultResolver(moduleName, containingFile, options, host.getModuleResolutionHost()); + return moduleResolution.resolvedFileName; + }); } } } @@ -347,15 +349,16 @@ namespace ts { return false; } - if (resolveModuleNameWorker) { + if (resolveModuleNamesWorker) { + let moduleNames = map(newSourceFile.imports, name => name.text); + let resolutions = resolveModuleNamesWorker(moduleNames, newSourceFile.fileName); // ensure that module resolution results are still correct - for (let importName of newSourceFile.imports) { - var oldResolution = getResolvedModuleFileName(oldSourceFile, importName.text); - var newResolution = resolveModuleNameWorker(importName.text, newSourceFile.fileName); - if (oldResolution !== newResolution) { + for (let i = 0; i < moduleNames.length; ++i) { + let oldResolution = getResolvedModuleFileName(oldSourceFile, moduleNames[i]); + if (oldResolution !== resolutions[i]) { return false; - } - } + } + } } // pass the cache of module resolutions from the old source file newSourceFile.resolvedModules = oldSourceFile.resolvedModules; @@ -719,10 +722,16 @@ namespace ts { if (file.imports.length) { file.resolvedModules = {}; let oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName); - for (let moduleName of file.imports) { - resolveModule(moduleName); - } + let moduleNames = map(file.imports, name => name.text); + let resolutions = resolveModuleNamesWorker(moduleNames, file.fileName); + for (let i = 0; i < file.imports.length; ++i) { + let resolution = resolutions[i]; + setResolvedModuleName(file, moduleNames[i], resolution); + if (resolution) { + findModuleSourceFile(resolution, file.imports[i]); + } + } } else { // no imports - drop cached module resolutions @@ -733,16 +742,6 @@ namespace ts { function findModuleSourceFile(fileName: string, nameLiteral: Expression) { return findSourceFile(fileName, /* isDefaultLib */ false, file, nameLiteral.pos, nameLiteral.end - nameLiteral.pos); } - - function resolveModule(moduleNameExpr: LiteralExpression): void { - Debug.assert(resolveModuleNameWorker !== undefined); - - let resolvedModuleName = resolveModuleNameWorker(moduleNameExpr.text, file.fileName); - setResolvedModuleName(file, moduleNameExpr.text, resolvedModuleName); - if (resolvedModuleName) { - findModuleSourceFile(resolvedModuleName, moduleNameExpr); - } - } } function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b26c710d397f2..f7aef302da022 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2266,7 +2266,7 @@ namespace ts { // if getModuleResolutionHost is implemented then compiler will apply one of built-in ways to resolve module names // and ModuleResolutionHost will be used to ask host specific questions // if resolveModuleName is implemented - this will mean that host is completely in charge of module name resolution - resolveModuleName?(moduleName: string, containingFile: string): string; + resolveModuleNames?(moduleNames: string[], containingFile: string): string[]; getModuleResolutionHost?(): ModuleResolutionHost; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3783f16df5e53..3d566594ed009 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -98,30 +98,54 @@ namespace ts.server { } } - resolveModuleName(moduleName: string, containingFile: string): string { - let resolutionsInFile = this.resolvedModuleNames.get(containingFile); - if (!resolutionsInFile) { - resolutionsInFile = {}; - this.resolvedModuleNames.set(containingFile, resolutionsInFile); + resolveModuleNames(moduleNames: string[], containingFile: string): string[] { + let currentResolutionsInFile = this.resolvedModuleNames.get(containingFile); + + let newResolutions: Map = {}; + let resolvedFileNames: string[] = []; + + let compilerOptions = this.getCompilationSettings(); + let defaultResolver = ts.getDefaultModuleNameResolver(compilerOptions); + + for (let moduleName of moduleNames) { + // check if this is a duplicate entry in the list + let resolution = lookUp(newResolutions, moduleName); + if (!resolution) { + let existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, moduleName); + if (moduleResolutionIsValid(existingResolution)) { + // ok, it is safe to use existing module resolution results + resolution = existingResolution; + } + else { + resolution = defaultResolver(moduleName, containingFile, compilerOptions, this.moduleResolutionHost); + resolution.lastCheckTime = Date.now(); + newResolutions[moduleName] = resolution; + } + } + + ts.Debug.assert(resolution !== undefined); + + resolvedFileNames.push(resolution.resolvedFileName); } - let resolution = ts.lookUp(resolutionsInFile, moduleName); - if (!moduleResolutionIsValid(resolution)) { - let compilerOptions = this.getCompilationSettings(); - let defaultResolver = ts.getDefaultModuleNameResolver(compilerOptions); - resolution = defaultResolver(moduleName, containingFile, compilerOptions, this.moduleResolutionHost); - resolution.lastCheckTime = Date.now(); - resolutionsInFile[moduleName] = resolution; - } - return resolution.resolvedFileName; + // replace old results with a new one + this.resolvedModuleNames.set(containingFile, newResolutions); + return resolvedFileNames; function moduleResolutionIsValid(resolution: TimestampedResolvedModule): boolean { if (!resolution) { return false; } - // TODO: use lastCheckTime assuming that module resolution results are legal for some period of time - return !resolution.resolvedFileName && resolution.failedLookupLocations.length !== 0; + if (resolution.resolvedFileName) { + // TODO: consider checking failedLookupLocations + // TODO: use lastCheckTime to track expiration for module name resolution + return true; + } + + // consider situation if we have no candidate locations as valid resolution. + // after all there is no point to invalidate it if we have no idea where to look for the module. + return resolution.failedLookupLocations.length === 0; } } diff --git a/src/services/services.ts b/src/services/services.ts index bfc403ebc455c..c0a617646b8df 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -983,7 +983,7 @@ namespace ts { // if resolveModuleName is implemented - this will mean that host is completely in charge of module name resolution // if none of these methods are implemented then language service will try to emulate getModuleResolutionHost atop of 'getScriptSnapshot' getModuleResolutionHost?(): ModuleResolutionHost; - resolveModuleName?(moduleName: string, containingFile: string): string; + resolveModuleNames?(moduleNames: string[], containingFile: string): string[]; } // @@ -2564,7 +2564,8 @@ namespace ts { let oldSettings = program && program.getCompilerOptions(); let newSettings = hostCache.compilationSettings(); - let changesInCompilationSettingsAffectSyntax = oldSettings && oldSettings.target !== newSettings.target; + let changesInCompilationSettingsAffectSyntax = oldSettings && + (oldSettings.target !== newSettings.target || oldSettings.module !== newSettings.module || oldSettings.noResolve !== newSettings.noResolve); // Now create a new compiler let compilerHost: CompilerHost = { @@ -2578,8 +2579,8 @@ namespace ts { getCurrentDirectory: () => host.getCurrentDirectory(), }; - if (host.resolveModuleName) { - compilerHost.resolveModuleName = (moduleName, containingFile) => host.resolveModuleName(moduleName, containingFile) + if (host.resolveModuleNames) { + compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile) } else if (host.getModuleResolutionHost) { compilerHost.getModuleResolutionHost = () => host.getModuleResolutionHost() diff --git a/src/services/shims.ts b/src/services/shims.ts index 4a2ed7b162f17..f290be8ba0bbb 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -267,22 +267,16 @@ namespace ts { private files: string[]; private loggingEnabled = false; private tracingEnabled = false; - private lastRequestedFile: string; - private lastRequestedModuleResolutions: Map; - public resolveModuleName: (moduleName: string, containingFile: string) => string; + public resolveModuleNames: (moduleName: string[], containingFile: string) => string[]; constructor(private shimHost: LanguageServiceShimHost) { // if shimHost is a COM object then property check will become method call with no arguments. // 'in' does not have this effect. if ("getModuleResolutionsForFile" in this.shimHost) { - this.resolveModuleName = (moduleName: string, containingFile: string) => { - if (this.lastRequestedFile !== containingFile) { - this.lastRequestedModuleResolutions = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); - this.lastRequestedFile = containingFile; - } - - return this.lastRequestedModuleResolutions[moduleName]; + this.resolveModuleNames = (moduleNames: string[], containingFile: string) => { + let resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); + return map(moduleNames, name => lookUp(resolutionsInFile, name)); }; } } diff --git a/tests/cases/unittests/cachingInServerLSHost.ts b/tests/cases/unittests/cachingInServerLSHost.ts new file mode 100644 index 0000000000000..ff2da76ba4f13 --- /dev/null +++ b/tests/cases/unittests/cachingInServerLSHost.ts @@ -0,0 +1,206 @@ +/// + +module ts { + interface File { + name: string; + content: string; + } + + function createDefaultServerHost(fileMap: Map): server.ServerHost { + return { + args: [], + newLine: "\r\n", + useCaseSensitiveFileNames: false, + write: (s: string) => { + }, + readFile: (path: string, encoding?: string): string => { + return hasProperty(fileMap, path) && fileMap[path].content; + }, + writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => { + throw new Error("NYI"); + }, + resolvePath: (path: string): string => { + throw new Error("NYI"); + }, + fileExists: (path: string): boolean => { + return hasProperty(fileMap, path); + }, + directoryExists: (path: string): boolean => { + throw new Error("NYI"); + }, + createDirectory: (path: string) => { + }, + getExecutingFilePath: (): string => { + return ""; + }, + getCurrentDirectory: (): string => { + return ""; + }, + readDirectory: (path: string, extension?: string, exclude?: string[]): string[] => { + throw new Error("NYI"); + }, + exit: (exitCode?: number) => { + }, + watchFile: (path, callback) => { + return { + close: () => { } + } + } + }; + } + + function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } { + let logger: server.Logger = { + close() { }, + isVerbose: () => false, + loggingEnabled: () => false, + perftrc: (s: string) => { }, + info: (s: string) => { }, + startGroup: () => { }, + endGroup: () => { }, + msg: (s: string, type?: string) => { } + }; + + let projectService = new server.ProjectService(serverHost, logger); + let rootScriptInfo = projectService.openFile(rootFile, /* openedByClient */true); + let project = projectService.createInferredProject(rootScriptInfo); + project.setProjectOptions( {files: [rootScriptInfo.fileName], compilerOptions: {module: ts.ModuleKind.AMD} } ); + return { + project, + rootScriptInfo + }; + } + + describe("Caching in LSHost", () => { + it("works using legacy resolution logic", () => { + let root: File = { + name: "c:/d/f0.ts", + content: `import {x} from "f1"` + }; + + let imported: File = { + name: "c:/f1.ts", + content: `foo()` + }; + + let serverHost = createDefaultServerHost({ [root.name]: root, [imported.name]: imported }); + let { project, rootScriptInfo } = createProject(root.name, serverHost); + + // ensure that imported file was found + let diags = project.compilerService.languageService.getSemanticDiagnostics(imported.name); + assert.equal(diags.length, 1); + + let originalFileExists = serverHost.fileExists; + { + // patch fileExists to make sure that disk is not touched + serverHost.fileExists = (fileName): boolean => { + assert.isTrue(false, "fileExists should not be called"); + return false; + }; + + let newContent = `import {x} from "f1" + var x: string = 1;`; + rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent); + // trigger synchronization to make sure that import will be fetched from the cache + diags = project.compilerService.languageService.getSemanticDiagnostics(imported.name); + // ensure file has correct number of errors after edit + assert.equal(diags.length, 1); + } + { + let fileExistsIsCalled = false; + serverHost.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsIsCalled = true; + assert.isTrue(fileName.indexOf('/f2.') !== -1); + return originalFileExists(fileName); + }; + let newContent = `import {x} from "f2"`; + rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent); + + try { + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + project.compilerService.languageService.getSemanticDiagnostics(imported.name); + assert.isTrue(false, `should not find file '${imported.name}'`) + } + catch(e) { + assert.isTrue(e.message.indexOf(`Could not find file: '${imported.name}'.`) === 0); + } + + assert.isTrue(fileExistsIsCalled); + } + { + let fileExistsCalled = false; + serverHost.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsCalled = true; + assert.isTrue(fileName.indexOf('/f1.') !== -1); + return originalFileExists(fileName); + }; + + let newContent = `import {x} from "f1"`; + rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent); + project.compilerService.languageService.getSemanticDiagnostics(imported.name); + assert.isTrue(fileExistsCalled); + + // setting compiler options discards module resolution cache + fileExistsCalled = false; + + let opts = ts.clone(project.projectOptions); + opts.compilerOptions = ts.clone(opts.compilerOptions); + opts.compilerOptions.target = ts.ScriptTarget.ES5; + project.setProjectOptions(opts); + + project.compilerService.languageService.getSemanticDiagnostics(imported.name); + assert.isTrue(fileExistsCalled); + } + }); + + it("loads missing files from disk", () => { + let root: File = { + name: 'c:/foo.ts', + content: `import {x} from "bar"` + }; + + let imported: File = { + name: 'c:/bar.d.ts', + content: `export var y = 1` + }; + + let fileMap: Map = { [root.name]: root }; + let serverHost = createDefaultServerHost(fileMap); + let originalFileExists = serverHost.fileExists; + + let fileExistsCalledForBar = false; + serverHost.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + + return originalFileExists(fileName); + }; + + let { project, rootScriptInfo } = createProject(root.name, serverHost); + + let diags = project.compilerService.languageService.getSemanticDiagnostics(root.name); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + assert.isTrue(diags.length === 1, "one diagnostic expected"); + assert.isTrue(typeof diags[0].messageText === "string" && ((diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message"); + + // assert that import will success once file appear on disk + fileMap[imported.name] = imported; + fileExistsCalledForBar = false; + rootScriptInfo.editContent(0, rootScriptInfo.content.length, `import {y} from "bar"`) + + diags = project.compilerService.languageService.getSemanticDiagnostics(root.name); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + assert.isTrue(diags.length === 0); + }) + }); +} \ No newline at end of file From a72994d78ce318faaaf9e9ecbd04622ffc4fcefd Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 4 Aug 2015 15:27:16 -0700 Subject: [PATCH 15/20] removed extra whitespaces, added commments --- src/compiler/types.ts | 1 + src/services/shims.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f7aef302da022..e22e80ea79645 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1275,6 +1275,7 @@ namespace ts { /* @internal */ lineMap: number[]; /* @internal */ classifiableNames?: Map; // Stores a mapping 'external module reference text' -> 'resolved file name' | undefined + // It is used to resolve module names in the checker. // Content of this fiels should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead /* @internal */ resolvedModules: Map; /* @internal */ imports: LiteralExpression[]; diff --git a/src/services/shims.ts b/src/services/shims.ts index f290be8ba0bbb..ace7fbeff01af 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -547,7 +547,7 @@ namespace ts { private realizeDiagnostics(diagnostics: Diagnostic[]): { message: string; start: number; length: number; category: string; }[]{ var newLine = this.getNewLine(); return ts.realizeDiagnostics(diagnostics, newLine); - } + } public getSyntacticClassifications(fileName: string, start: number, length: number): string { return this.forwardJSONCall( From 03aaf7cd7c5353fd27839bbffe730fb340c76861 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 4 Aug 2015 21:22:37 -0700 Subject: [PATCH 16/20] addressed PR feedback --- src/compiler/core.ts | 2 +- src/compiler/program.ts | 14 +- src/compiler/types.ts | 15 +- src/harness/harness.ts | 9 +- src/harness/harnessLanguageService.ts | 5 +- src/harness/projectsRunner.ts | 7 +- src/server/editorServices.ts | 5 +- src/services/services.ts | 288 +++++++++--------- src/services/shims.ts | 4 + .../amd/invalidRootFile.errors.txt | 4 +- .../node/invalidRootFile.errors.txt | 4 +- .../cases/unittests/reuseProgramStructure.ts | 10 +- 12 files changed, 185 insertions(+), 182 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1382a94120447..b028c1ab0c00d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -714,7 +714,7 @@ namespace ts { /** * List of supported extensions in order of file resolution precedence. */ - export const supportedExtensions = [".ts", ".d.ts", ".tsx"]; + export const supportedExtensions = [".ts", ".tsx", ".d.ts"]; const extensionsToRemove = [".d.ts", ".ts", ".js", ".tsx", ".jsx"]; export function removeFileExtension(path: string): string { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4e13de58953ce..227372ce30de4 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -152,10 +152,7 @@ namespace ts { const newLine = getNewLineCharacter(options); - let moduleResolutionHost: ModuleResolutionHost = { - fileExists: fileName => sys.fileExists(fileName), - } - + return { getSourceFile, getDefaultLibFileName: options => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), getDefaultLibFileName(options)), @@ -164,7 +161,8 @@ namespace ts { useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, - getModuleResolutionHost: () => moduleResolutionHost + fileExists: fileName => sys.fileExists(fileName), + readFile: fileName => sys.readFile(fileName) }; } @@ -228,11 +226,10 @@ namespace ts { if (!options.noResolve) { resolveModuleNamesWorker = host.resolveModuleNames; if (!resolveModuleNamesWorker) { - Debug.assert(host.getModuleResolutionHost !== undefined); let defaultResolver = getDefaultModuleNameResolver(options); resolveModuleNamesWorker = (moduleNames, containingFile) => { return map(moduleNames, moduleName => { - let moduleResolution = defaultResolver(moduleName, containingFile, options, host.getModuleResolutionHost()); + let moduleResolution = defaultResolver(moduleName, containingFile, options, host); return moduleResolution.resolvedFileName; }); } @@ -248,7 +245,8 @@ namespace ts { if ((oldOptions.module !== options.module) || (oldOptions.noResolve !== options.noResolve) || (oldOptions.target !== options.target) || - (oldOptions.noLib !== options.noLib)) { + (oldOptions.noLib !== options.noLib) || + (oldOptions.jsx !== options.jsx)) { oldProgram = undefined; } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e22e80ea79645..1bac2917e8a9b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2244,6 +2244,7 @@ namespace ts { export interface ModuleResolutionHost { fileExists(fileName: string): boolean; + readFile(fileName: string): string; } export interface ResolvedModule { @@ -2253,7 +2254,7 @@ namespace ts { export type ModuleNameResolver = (moduleName: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => ResolvedModule; - export interface CompilerHost { + export interface CompilerHost extends ModuleResolutionHost { getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile; getCancellationToken?(): CancellationToken; getDefaultLibFileName(options: CompilerOptions): string; @@ -2263,12 +2264,14 @@ namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; - // Compiler host must implement one of these methods - // if getModuleResolutionHost is implemented then compiler will apply one of built-in ways to resolve module names - // and ModuleResolutionHost will be used to ask host specific questions - // if resolveModuleName is implemented - this will mean that host is completely in charge of module name resolution + /* + * CompilerHost must either implement resolveModuleNames (in case if it wants to be completely in charge of + * module name resolution) or provide implementation for methods from ModuleResolutionHost (in this case compiler + * will appply built-in module resolution logic and use members of ModuleResolutionHost to ask host specific questions). + * If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just + * 'throw new Error("NotImplemented")' + */ resolveModuleNames?(moduleNames: string[], containingFile: string): string[]; - getModuleResolutionHost?(): ModuleResolutionHost; } export interface TextSpan { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 645bcb4696480..5d5d020f2b703 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -893,11 +893,7 @@ module Harness { newLineKind === ts.NewLineKind.CarriageReturnLineFeed ? carriageReturnLineFeed : newLineKind === ts.NewLineKind.LineFeed ? lineFeed : ts.sys.newLine; - - let moduleResolutionHost: ts.ModuleResolutionHost = { - fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined, - } - + return { getCurrentDirectory, getSourceFile, @@ -906,7 +902,8 @@ module Harness { getCanonicalFileName, useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getNewLine: () => newLine, - getModuleResolutionHost: () => moduleResolutionHost + fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined, + readFile: (fileName: string): string => { throw new Error("NotYetImplemented"); } }; } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 5314d877648b9..e989eee77a973 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -230,7 +230,10 @@ module Harness.LanguageService { throw new Error("NYI"); } fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } - + readFile(fileName: string) { + let snapshot = this.nativeHost.getScriptSnapshot(fileName); + return snapshot && snapshot.getText(0, snapshot.getLength()); + } log(s: string): void { this.nativeHost.log(s); } trace(s: string): void { this.nativeHost.trace(s); } error(s: string): void { this.nativeHost.error(s); } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 2d7febe0b67fe..74989331c4466 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -129,10 +129,6 @@ class ProjectRunner extends RunnerBase { getSourceFileText: (fileName: string) => string, writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void): CompileProjectFilesResult { - let moduleResolutionHost: ts.ModuleResolutionHost = { - fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined, - } - let program = ts.createProgram(getInputFiles(), createCompilerOptions(), createCompilerHost()); let errors = ts.getPreEmitDiagnostics(program); @@ -196,7 +192,8 @@ class ProjectRunner extends RunnerBase { getCanonicalFileName: Harness.Compiler.getCanonicalFileName, useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, getNewLine: () => ts.sys.newLine, - getModuleResolutionHost: () => moduleResolutionHost + fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined, + readFile: fileName => Harness.IO.readFile(fileName) }; } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3d566594ed009..578c9cce55315 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -94,12 +94,13 @@ namespace ts.server { constructor(public host: ServerHost, public project: Project) { this.resolvedModuleNames = ts.createFileMap>(ts.createGetCanonicalFileName(host.useCaseSensitiveFileNames)) this.moduleResolutionHost = { - fileExists: fileName => this.fileExists(fileName) + fileExists: fileName => this.fileExists(fileName), + readFile: fileName => this.host.readFile(fileName) } } resolveModuleNames(moduleNames: string[], containingFile: string): string[] { - let currentResolutionsInFile = this.resolvedModuleNames.get(containingFile); + let currentResolutionsInFile = this.resolvedModuleNames.get(containingFile); let newResolutions: Map = {}; let resolvedFileNames: string[] = []; diff --git a/src/services/services.ts b/src/services/services.ts index c0a617646b8df..a3b31afabf075 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -73,7 +73,7 @@ namespace ts { } /** - * Represents an immutable snapshot of a script at a specified time.Once acquired, the + * Represents an immutable snapshot of a script at a specified time.Once acquired, the * snapshot is observably immutable. i.e. the same calls with the same parameters will return * the same values. */ @@ -85,9 +85,9 @@ namespace ts { getLength(): number; /** - * Gets the TextChangeRange that describe how the text changed between this text and + * Gets the TextChangeRange that describe how the text changed between this text and * an older version. This information is used by the incremental parser to determine - * what sections of the script need to be re-parsed. 'undefined' can be returned if the + * what sections of the script need to be re-parsed. 'undefined' can be returned if the * change range cannot be determined. However, in that case, incremental parsing will * not happen and the entire document will be re - parsed. */ @@ -276,8 +276,8 @@ namespace ts { let children = this.getChildren(sourceFile); let child = lastOrUndefined(children); - if (!child) { - return undefined; + if (!child) { + return undefined; } return child.kind < SyntaxKind.FirstNode ? child : child.getLastToken(sourceFile); @@ -337,10 +337,10 @@ namespace ts { ts.forEach(declarations, (declaration, indexOfDeclaration) => { // Make sure we are collecting doc comment from declaration once, - // In case of union property there might be same declaration multiple times + // In case of union property there might be same declaration multiple times // which only varies in type parameter // Eg. let a: Array | Array; a.length - // The property length will have two declarations of property length coming + // The property length will have two declarations of property length coming // from Array - Array and Array if (indexOf(declarations, declaration) === indexOfDeclaration) { let sourceFileOfDeclaration = getSourceFileOfNode(declaration); @@ -362,7 +362,7 @@ namespace ts { // If this is dotted module name, get the doc comments from the parent while (declaration.kind === SyntaxKind.ModuleDeclaration && declaration.parent.kind === SyntaxKind.ModuleDeclaration) { declaration = declaration.parent; - } + } // Get the cleaned js doc comment text from the declaration ts.forEach(getJsDocCommentTextRange( @@ -382,7 +382,7 @@ namespace ts { jsDocComment => { return { pos: jsDocComment.pos + "/*".length, // Consume /* from the comment - end: jsDocComment.end - "*/".length // Trim off comment end indicator + end: jsDocComment.end - "*/".length // Trim off comment end indicator }; }); } @@ -487,7 +487,7 @@ namespace ts { pushDocCommentLineText(docComments, docCommentTextOfLine, blankLineCount); blankLineCount = 0; } - else if (!isInParamTag && docComments.length) { + else if (!isInParamTag && docComments.length) { // This is blank line when there is text already parsed blankLineCount++; } @@ -503,7 +503,7 @@ namespace ts { if (isParamTag(pos, end, sourceFile)) { let blankLineCount = 0; let recordedParamTag = false; - // Consume leading spaces + // Consume leading spaces pos = consumeWhiteSpaces(pos + paramTag.length); if (pos >= end) { break; @@ -561,7 +561,7 @@ namespace ts { while (pos < end) { let ch = sourceFile.text.charCodeAt(pos); - // at line break, set this comment line text and go to next line + // at line break, set this comment line text and go to next line if (isLineBreak(ch)) { if (paramHelpString) { pushDocCommentLineText(paramDocComments, paramHelpString, blankLineCount); @@ -627,7 +627,7 @@ namespace ts { paramHelpStringMargin = sourceFile.getLineAndCharacterOfPosition(firstLineParamHelpStringPos).character; } - // Now consume white spaces max + // Now consume white spaces max let startOfLinePos = pos; pos = consumeWhiteSpacesOnTheLine(pos, end, sourceFile, paramHelpStringMargin); if (pos >= end) { @@ -976,13 +976,12 @@ namespace ts { trace? (s: string): void; error? (s: string): void; useCaseSensitiveFileNames? (): boolean; - - // LS host can implement one of these methods - // if getModuleResolutionHost is implemented then compiler will apply one of built-in ways to resolve module names - // and ModuleResolutionHost will be used to ask host specific questions - // if resolveModuleName is implemented - this will mean that host is completely in charge of module name resolution - // if none of these methods are implemented then language service will try to emulate getModuleResolutionHost atop of 'getScriptSnapshot' - getModuleResolutionHost?(): ModuleResolutionHost; + + /* + * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. + * if implementation is omitted then language service will use built-in module resolution logic and get answers to + * host specific questions using 'getScriptSnapshot'. + */ resolveModuleNames?(moduleNames: string[], containingFile: string): string[]; } @@ -1000,17 +999,17 @@ namespace ts { // diagnostics present for the program level, and not just 'options' diagnostics. getCompilerOptionsDiagnostics(): Diagnostic[]; - /** + /** * @deprecated Use getEncodedSyntacticClassifications instead. */ getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; - /** + /** * @deprecated Use getEncodedSemanticClassifications instead. */ getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; - // Encoded as triples of [start, length, ClassificationType]. + // Encoded as triples of [start, length, ClassificationType]. getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications; getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications; @@ -1229,7 +1228,7 @@ namespace ts { * Represents a single signature to show in signature help. * The id is used for subsequent calls into the language service to ask questions about the * signature help item in the context of any documents that have been updated. i.e. after - * an edit has happened, while signature help is still active, the host can ask important + * an edit has happened, while signature help is still active, the host can ask important * questions like 'what parameter is the user currently contained within?'. */ export interface SignatureHelpItem { @@ -1283,8 +1282,8 @@ namespace ts { /** The text to display in the editor for the collapsed region. */ bannerText: string; - /** - * Whether or not this region should be automatically collapsed when + /** + * Whether or not this region should be automatically collapsed when * the 'Collapse to Definitions' command is invoked. */ autoCollapse: boolean; @@ -1365,18 +1364,18 @@ namespace ts { } /** - * The document registry represents a store of SourceFile objects that can be shared between + * The document registry represents a store of SourceFile objects that can be shared between * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) - * of files in the context. - * SourceFile objects account for most of the memory usage by the language service. Sharing - * the same DocumentRegistry instance between different instances of LanguageService allow - * for more efficient memory utilization since all projects will share at least the library + * of files in the context. + * SourceFile objects account for most of the memory usage by the language service. Sharing + * the same DocumentRegistry instance between different instances of LanguageService allow + * for more efficient memory utilization since all projects will share at least the library * file (lib.d.ts). * - * A more advanced use of the document registry is to serialize sourceFile objects to disk + * A more advanced use of the document registry is to serialize sourceFile objects to disk * and re-hydrate them when needed. * - * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it + * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it * to all subsequent createLanguageService calls. */ export interface DocumentRegistry { @@ -1386,7 +1385,7 @@ namespace ts { * the SourceFile if was not found in the registry. * * @param fileName The name of the file requested - * @param compilationSettings Some compilation settings like target affects the + * @param compilationSettings Some compilation settings like target affects the * shape of a the resulting SourceFile. This allows the DocumentRegistry to store * multiple copies of the same file for different compilation settings. * @parm scriptSnapshot Text of the file. Only used if the file was not found @@ -1406,10 +1405,10 @@ namespace ts { * to get an updated SourceFile. * * @param fileName The name of the file requested - * @param compilationSettings Some compilation settings like target affects the + * @param compilationSettings Some compilation settings like target affects the * shape of a the resulting SourceFile. This allows the DocumentRegistry to store * multiple copies of the same file for different compilation settings. - * @param scriptSnapshot Text of the file. + * @param scriptSnapshot Text of the file. * @param version Current version of the file. */ updateDocument( @@ -1588,7 +1587,7 @@ namespace ts { sourceFile: SourceFile; // The number of language services that this source file is referenced in. When no more - // language services are referencing the file, then the file can be removed from the + // language services are referencing the file, then the file can be removed from the // registry. languageServiceRefCount: number; owners: string[]; @@ -1643,8 +1642,8 @@ namespace ts { }; } - // Cache host information about scrip Should be refreshed - // at each language service public entry point, since we don't know when + // Cache host information about scrip Should be refreshed + // at each language service public entry point, since we don't know when // set of scripts handled by the host changes. class HostCache { private fileNameToEntry: FileMap; @@ -1723,8 +1722,8 @@ namespace ts { } class SyntaxTreeCache { - // For our syntactic only features, we also keep a cache of the syntax tree for the - // currently edited file. + // For our syntactic only features, we also keep a cache of the syntax tree for the + // currently edited file. private currentFileName: string; private currentFileVersion: string; private currentFileScriptSnapshot: IScriptSnapshot; @@ -1769,20 +1768,20 @@ namespace ts { sourceFile.version = version; sourceFile.scriptSnapshot = scriptSnapshot; } - + export interface TranspileOptions { compilerOptions?: CompilerOptions; fileName?: string; reportDiagnostics?: boolean; moduleName?: string; } - + export interface TranspileOutput { outputText: string; diagnostics?: Diagnostic[]; sourceMapText?: string; } - + /* * This function will compile source text from 'input' argument using specified compiler options. * If not options are provided - it will use a set of default compiler options. @@ -1791,7 +1790,7 @@ namespace ts { * - allowNonTsExtensions = true * - noLib = true * - noResolve = true - */ + */ export function transpileModule(input: string, transpileOptions?: TranspileOptions): TranspileOutput { let options = transpileOptions.compilerOptions ? clone(transpileOptions.compilerOptions) : getDefaultCompilerOptions(); @@ -1800,7 +1799,7 @@ namespace ts { // Filename can be non-ts file. options.allowNonTsExtensions = true; - // We are not returning a sourceFile for lib file when asked by the program, + // We are not returning a sourceFile for lib file when asked by the program, // so pass --noLib to avoid reporting a file not found error. options.noLib = true; @@ -1820,9 +1819,6 @@ namespace ts { // Output let outputText: string; let sourceMapText: string; - let moduleResolutionHost: ModuleResolutionHost = { - fileExists: fileName => fileName === inputFileName, - } // Create a compilerHost object to allow the compiler to read and write files let compilerHost: CompilerHost = { getSourceFile: (fileName, target) => fileName === inputFileName ? sourceFile : undefined, @@ -1841,11 +1837,13 @@ namespace ts { getCanonicalFileName: fileName => fileName, getCurrentDirectory: () => "", getNewLine: () => newLine, - getModuleResolutionHost: () => moduleResolutionHost + + fileExists: (fileName): boolean => { throw new Error("Should never be called."); }, + readFile: (fileName): string => { throw new Error("Should never be called."); } }; let program = createProgram([inputFileName], options, compilerHost); - + let diagnostics: Diagnostic[]; if (transpileOptions.reportDiagnostics) { diagnostics = []; @@ -1857,11 +1855,11 @@ namespace ts { Debug.assert(outputText !== undefined, "Output generation failed"); - return { outputText, diagnostics, sourceMapText }; + return { outputText, diagnostics, sourceMapText }; } /* - * This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result. + * This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result. */ export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string { let output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName }); @@ -1882,19 +1880,19 @@ namespace ts { export let disableIncrementalParsing = false; export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile { - // If we were given a text change range, and our version or open-ness changed, then + // If we were given a text change range, and our version or open-ness changed, then // incrementally parse this file. if (textChangeRange) { if (version !== sourceFile.version) { // Once incremental parsing is ready, then just call into this function. if (!disableIncrementalParsing) { let newText: string; - + // grab the fragment from the beginning of the original text to the beginning of the span let prefix = textChangeRange.span.start !== 0 ? sourceFile.text.substr(0, textChangeRange.span.start) : ""; - + // grab the fragment from the end of the span till the end of the original text let suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length ? sourceFile.text.substr(textSpanEnd(textChangeRange.span)) @@ -1908,10 +1906,10 @@ namespace ts { // it was actual edit, fetch the fragment of new text that correspond to new span let changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength); // combine prefix, changed text and suffix - newText = prefix && suffix + newText = prefix && suffix ? prefix + changedText + suffix : prefix - ? (prefix + changedText) + ? (prefix + changedText) : (changedText + suffix); } @@ -1953,7 +1951,7 @@ namespace ts { let getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); function getKeyFromCompilationSettings(settings: CompilerOptions): string { - return "_" + settings.target + "|" + settings.module + "|" + settings.noResolve; + return "_" + settings.target + "|" + settings.module + "|" + settings.noResolve + "|" + settings.jsx; } function getBucketForCompilationSettings(settings: CompilerOptions, createIfMissing: boolean): FileMap { @@ -2017,7 +2015,7 @@ namespace ts { bucket.set(fileName, entry); } else { - // We have an entry for this file. However, it may be for a different version of + // We have an entry for this file. However, it may be for a different version of // the script snapshot. If so, update it appropriately. Otherwise, we can just // return it as is. if (entry.sourceFile.version !== version) { @@ -2058,7 +2056,7 @@ namespace ts { reportStats }; } - + export function resolveModuleName(fileName: string, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { let resolver = getDefaultModuleNameResolver(compilerOptions); return resolver(moduleName, fileName, compilerOptions, host); @@ -2084,7 +2082,7 @@ namespace ts { } }); } - + function recordAmbientExternalModule(): void { if (!ambientExternalModules) { ambientExternalModules = []; @@ -2556,17 +2554,20 @@ namespace ts { return; } - // IMPORTANT - It is critical from this moment onward that we do not check + // 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 + // the program points to old source files that have been invalidated because of // incremental parsing. let oldSettings = program && program.getCompilerOptions(); let newSettings = hostCache.compilationSettings(); - let changesInCompilationSettingsAffectSyntax = oldSettings && - (oldSettings.target !== newSettings.target || oldSettings.module !== newSettings.module || oldSettings.noResolve !== newSettings.noResolve); - + let changesInCompilationSettingsAffectSyntax = oldSettings && + (oldSettings.target !== newSettings.target || + oldSettings.module !== newSettings.module || + oldSettings.noResolve !== newSettings.noResolve || + oldSettings.jsx !== newSettings.jsx); + // Now create a new compiler let compilerHost: CompilerHost = { getSourceFile: getOrCreateSourceFile, @@ -2577,26 +2578,25 @@ namespace ts { getDefaultLibFileName: (options) => host.getDefaultLibFileName(options), writeFile: (fileName, data, writeByteOrderMark) => { }, getCurrentDirectory: () => host.getCurrentDirectory(), + fileExists: (fileName): boolean => { throw new Error("Not implemented"); }, + readFile: (fileName): string => { throw new Error("Not implemented"); } }; - + if (host.resolveModuleNames) { compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile) } - else if (host.getModuleResolutionHost) { - compilerHost.getModuleResolutionHost = () => host.getModuleResolutionHost() - } else { - compilerHost.getModuleResolutionHost = () => { - // stub missing host functionality - return { - fileExists: fileName => hostCache.getOrCreateEntry(fileName) != undefined, - }; + // stub missing host functionality + compilerHost.fileExists = fileName => hostCache.getOrCreateEntry(fileName) !== undefined; + compilerHost.readFile = fileName => { + let entry = hostCache.getOrCreateEntry(fileName); + return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength()); } } - + let newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program); - // Release any files we have acquired in the old program but are + // Release any files we have acquired in the old program but are // not part of the new program. if (program) { let oldSourceFiles = program.getSourceFiles(); @@ -2614,7 +2614,7 @@ namespace ts { program = newProgram; - // Make sure all the nodes in the program are both bound, and have their parent + // Make sure all the nodes in the program are both bound, and have their parent // pointers set property. program.getTypeChecker(); return; @@ -2636,7 +2636,7 @@ namespace ts { // Check if the old program had this file already let oldSourceFile = program && program.getSourceFile(fileName); if (oldSourceFile) { - // We already had a source file for this file name. Go to the registry to + // We already had a source file for this file name. Go to the registry to // ensure that we get the right up to date version of it. We need this to // address the following 'race'. Specifically, say we have the following: // @@ -2647,15 +2647,15 @@ namespace ts { // LS2 // // Each LS has a reference to file 'foo.ts' at version 1. LS2 then updates - // it's version of 'foo.ts' to version 2. This will cause LS2 and the - // DocumentRegistry to have version 2 of the document. HOwever, LS1 will + // it's version of 'foo.ts' to version 2. This will cause LS2 and the + // DocumentRegistry to have version 2 of the document. HOwever, LS1 will // have version 1. And *importantly* this source file will be *corrupt*. // The act of creating version 2 of the file irrevocably damages the version // 1 file. // // So, later when we call into LS1, we need to make sure that it doesn't use // it's source file any more, and instead defers to DocumentRegistry to get - // either version 1, version 2 (or some other version) depending on what the + // either version 1, version 2 (or some other version) depending on what the // host says should be used. return documentRegistry.updateDocument(fileName, newSettings, hostFileInformation.scriptSnapshot, hostFileInformation.version); } @@ -2721,7 +2721,7 @@ namespace ts { /** * getSemanticDiagnostiscs return array of Diagnostics. If '-d' is not enabled, only report semantic errors - * If '-d' enabled, report both semantic and emitter errors + * If '-d' enabled, report both semantic and emitter errors */ function getSemanticDiagnostics(fileName: string): Diagnostic[] { synchronizeHostData(); @@ -2729,7 +2729,7 @@ namespace ts { let targetSourceFile = getValidSourceFile(fileName); // For JavaScript files, we don't want to report the normal typescript semantic errors. - // Instead, we just report errors for using TypeScript-only constructs from within a + // Instead, we just report errors for using TypeScript-only constructs from within a // JavaScript file. if (isJavaScript(fileName)) { return getJavaScriptSemanticDiagnostics(targetSourceFile); @@ -3109,7 +3109,7 @@ namespace ts { } if (isJavaScriptFile && type.flags & TypeFlags.Union) { - // In javascript files, for union types, we don't just get the members that + // In javascript files, for union types, we don't just get the members that // the individual types have in common, we also include all the members that // each individual type has. This is because we're going to add all identifiers // anyways. So we might as well elevate the members that were at least part @@ -3164,10 +3164,10 @@ namespace ts { // aggregating completion candidates. This is achieved in 'getScopeNode' // by finding the first node that encompasses a position, accounting for whether a node // is "complete" to decide whether a position belongs to the node. - // + // // However, at the end of an identifier, we are interested in the scope of the identifier // itself, but fall outside of the identifier. For instance: - // + // // xyz => x$ // // the cursor is outside of both the 'x' and the arrow function 'xyz => x', @@ -3194,7 +3194,7 @@ namespace ts { /// TODO filter meaning based on the current context let symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias; symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings); - + return true; } @@ -3332,7 +3332,7 @@ namespace ts { let rootDeclaration = getRootDeclaration(objectLikeContainer.parent); if (isVariableLike(rootDeclaration)) { - // We don't want to complete using the type acquired by the shape + // We don't want to complete using the type acquired by the shape // of the binding pattern; we are only interested in types acquired // through type declaration or inference. if (rootDeclaration.initializer || rootDeclaration.type) { @@ -3457,8 +3457,8 @@ namespace ts { // its parent is a JsxExpression, whose parent is a JsxAttribute, // whose parent is a JsxOpeningLikeElement if(parent && - parent.kind === SyntaxKind.JsxExpression && - parent.parent && + parent.kind === SyntaxKind.JsxExpression && + parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) { return parent.parent.parent; @@ -3503,16 +3503,16 @@ namespace ts { containingNodeKind === SyntaxKind.FunctionDeclaration || // function A !lookUp(existingMemberNames, m.name)); } - + /** * Filters out completion suggestions from 'symbols' according to existing JSX attributes. * @@ -3747,7 +3747,7 @@ namespace ts { } function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry { - // Try to get a valid display name for this symbol, if we could not find one, then ignore it. + // Try to get a valid display name for this symbol, if we could not find one, then ignore it. // We would like to only show things that can be added after a dot, so for instance numeric properties can // not be accessed with a dot (a.1 <- invalid) let displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, /*performCharacterChecks:*/ true, location); @@ -3755,10 +3755,10 @@ namespace ts { return undefined; } - // TODO(drosen): Right now we just permit *all* semantic meanings when calling - // 'getSymbolKind' which is permissible given that it is backwards compatible; but + // TODO(drosen): Right now we just permit *all* semantic meanings when calling + // 'getSymbolKind' which is permissible given that it is backwards compatible; but // really we should consider passing the meaning for the node so that we don't report - // that a suggestion for a value is an interface. We COULD also just do what + // that a suggestion for a value is an interface. We COULD also just do what // 'getSymbolModifiers' does, which is to use the first declaration. // Use a 'sortText' of 0' so that all symbol completion entries come before any other @@ -3804,8 +3804,8 @@ namespace ts { // Find the symbol with the matching entry name. let target = program.getCompilerOptions().target; - // We don't need to perform character checks here because we're only comparing the - // name against 'entryName' (which is known to be good), not building a new + // We don't need to perform character checks here because we're only comparing the + // name against 'entryName' (which is known to be good), not building a new // completion entry. let symbol = forEach(symbols, s => getCompletionEntryDisplayNameForSymbol(s, target, /*performCharacterChecks:*/ false, location) === entryName ? s : undefined); @@ -3820,7 +3820,7 @@ namespace ts { }; } } - + // Didn't find a symbol with this name. See if we can find a keyword instead. let keywordCompletion = forEach(keywordCompletions, c => c.name === entryName); if (keywordCompletion) { @@ -3896,7 +3896,7 @@ namespace ts { Debug.assert(!!(rootSymbolFlags & SymbolFlags.Method)); }); if (!unionPropertyKind) { - // If this was union of all methods, + // If this was union of all methods, //make sure it has call signatures before we can label it as method let typeOfUnionProperty = typeChecker.getTypeOfSymbolAtLocation(symbol, location); if (typeOfUnionProperty.getCallSignatures().length) { @@ -3970,7 +3970,7 @@ namespace ts { let allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures(); if (!contains(allSignatures, signature.target || signature)) { - // Get the first signature if there + // Get the first signature if there signature = allSignatures.length ? allSignatures[0] : undefined; } @@ -4346,7 +4346,7 @@ namespace ts { if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) && !tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) { - // Just add all the declarations. + // Just add all the declarations. forEach(declarations, declaration => { result.push(createDefinitionInfo(declaration, symbolKind, symbolName, containerName)); }); @@ -4457,7 +4457,7 @@ namespace ts { // Because name in short-hand property assignment has two different meanings: property name and property value, // using go-to-definition at such position should go to the variable declaration of the property value rather than - // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition + // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition // is performed at the location of property access, we would like to go to definition of the property in the short-hand // assignment. This case and others are handled by the following code. if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { @@ -4523,7 +4523,7 @@ namespace ts { if (results) { let sourceFile = getCanonicalFileName(normalizeSlashes(fileName)); - // Get occurrences only supports reporting occurrences for the file queried. So + // Get occurrences only supports reporting occurrences for the file queried. So // filter down to that list. results = filter(results, r => getCanonicalFileName(ts.normalizeSlashes(r.fileName)) === sourceFile); } @@ -4750,7 +4750,7 @@ namespace ts { if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) { return parent; } - + // A throw-statement is only owned by a try-statement if the try-statement has // a catch clause, and if the throw-statement occurs within the try block. if (parent.kind === SyntaxKind.TryStatement) { @@ -4844,7 +4844,7 @@ namespace ts { return undefined; } } - else { + else { // unsupported modifier return undefined; } @@ -5440,13 +5440,13 @@ namespace ts { // If we are past the end, stop looking if (position > end) break; - // We found a match. Make sure it's not part of a larger word (i.e. the char + // We found a match. Make sure it's not part of a larger word (i.e. the char // before and after it have to be a non-identifier char). let endPosition = position + symbolNameLength; if ((position === 0 || !isIdentifierPart(text.charCodeAt(position - 1), ScriptTarget.Latest)) && (endPosition === sourceLength || !isIdentifierPart(text.charCodeAt(endPosition), ScriptTarget.Latest))) { - // Found a real match. Keep searching. + // Found a real match. Keep searching. positions.push(position); } position = text.indexOf(symbolName, position + symbolNameLength + 1); @@ -5513,9 +5513,9 @@ namespace ts { return false; } - /** Search within node "container" for references for a search value, where the search value is defined as a + /** Search within node "container" for references for a search value, where the search value is defined as a * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). - * searchLocation: a node where the search value + * searchLocation: a node where the search value */ function getReferencesInNode(container: Node, searchSymbol: Symbol, @@ -5541,7 +5541,7 @@ namespace ts { let referenceLocation = getTouchingPropertyName(sourceFile, position); if (!isValidReferencePosition(referenceLocation, searchText)) { - // This wasn't the start of a token. Check to see if it might be a + // This wasn't the start of a token. Check to see if it might be a // match in a comment or string if that's what the caller is asking // for. if ((findInStrings && isInString(position)) || @@ -5883,7 +5883,7 @@ namespace ts { } } - // If the reference location is in an object literal, try to get the contextual type for the + // If the reference location is in an object literal, try to get the contextual type for the // object literal, lookup the property symbol in the contextual type, and use this symbol to // compare to our searchSymbol if (isNameOfPropertyAssignment(referenceLocation)) { @@ -5900,7 +5900,7 @@ namespace ts { return rootSymbol; } - // Finally, try all properties with the same name in any type the containing type extended or implemented, and + // Finally, try all properties with the same name in any type the containing type extended or implemented, and // see if any is in the list if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { let result: Symbol[] = []; @@ -6251,7 +6251,7 @@ namespace ts { } else if (isNameOfModuleDeclaration(nodeForStartPos)) { // If this is name of a module declarations, check if this is right side of dotted module name - // If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of + // If parent of the module declaration which is parent of this node is module declaration and its body is the module declaration that this node is name of // Then this name is name from dotted module if (nodeForStartPos.parent.parent.kind === SyntaxKind.ModuleDeclaration && (nodeForStartPos.parent.parent).body === nodeForStartPos.parent) { @@ -6294,7 +6294,7 @@ namespace ts { // been canceled. That would be an enormous amount of chattyness, along with the all // the overhead of marshalling the data to/from the host. So instead we pick a few // reasonable node kinds to bother checking on. These node kinds represent high level - // constructs that we would expect to see commonly, but just at a far less frequent + // constructs that we would expect to see commonly, but just at a far less frequent // interval. // // For example, in checker.ts (around 750k) we only have around 600 of these constructs. @@ -6367,7 +6367,7 @@ namespace ts { */ function hasValueSideModule(symbol: Symbol): boolean { return forEach(symbol.declarations, declaration => { - return declaration.kind === SyntaxKind.ModuleDeclaration && + return declaration.kind === SyntaxKind.ModuleDeclaration && getModuleInstanceState(declaration) === ModuleInstanceState.Instantiated; }); } @@ -6488,8 +6488,8 @@ namespace ts { // Only bother with the trivia if it at least intersects the span of interest. if (isComment(kind)) { classifyComment(token, kind, start, width); - - // Classifying a comment might cause us to reuse the trivia scanner + + // Classifying a comment might cause us to reuse the trivia scanner // (because of jsdoc comments). So after we classify the comment make // sure we set the scanner position back to where it needs to be. triviaScanner.setTextPos(end); @@ -6540,7 +6540,7 @@ namespace ts { for (let tag of docComment.tags) { // As we walk through each tag, classify the portion of text from the end of - // the last tag (or the start of the entire doc comment) as 'comment'. + // the last tag (or the start of the entire doc comment) as 'comment'. if (tag.pos !== pos) { pushCommentRange(pos, tag.pos - pos); } @@ -6602,7 +6602,7 @@ namespace ts { } function classifyDisabledMergeCode(text: string, start: number, end: number) { - // Classify the line that the ======= marker is on as a comment. Then just lex + // Classify the line that the ======= marker is on as a comment. Then just lex // all further tokens and add them to the result. for (var i = start; i < end; i++) { if (isLineBreak(text.charCodeAt(i))) { @@ -6645,7 +6645,7 @@ namespace ts { } } - // for accurate classification, the actual token should be passed in. however, for + // for accurate classification, the actual token should be passed in. however, for // cases like 'disabled merge code' classification, we just get the token kind and // classify based on that instead. function classifyTokenType(tokenKind: SyntaxKind, token?: Node): ClassificationType { @@ -6860,11 +6860,11 @@ namespace ts { } function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] { - // Note: while getting todo comments seems like a syntactic operation, we actually + // Note: while getting todo comments seems like a syntactic operation, we actually // treat it as a semantic operation here. This is because we expect our host to call // this on every single file. If we treat this syntactically, then that will cause // us to populate and throw away the tree in our syntax tree cache for each file. By - // treating this as a semantic operation, we can access any tree without throwing + // treating this as a semantic operation, we can access any tree without throwing // anything away. synchronizeHostData(); @@ -6894,7 +6894,7 @@ namespace ts { // 0) The full match for the entire regexp. // 1) The preamble to the message portion. // 2) The message portion. - // 3...N) The descriptor that was matched - by index. 'undefined' for each + // 3...N) The descriptor that was matched - by index. 'undefined' for each // descriptor that didn't match. an actual value if it did match. // // i.e. 'undefined' in position 3 above means TODO(jason) didn't match. @@ -6920,7 +6920,7 @@ namespace ts { } Debug.assert(descriptor !== undefined); - // We don't want to match something like 'TODOBY', so we make sure a non + // We don't want to match something like 'TODOBY', so we make sure a non // letter/digit follows the match. if (isLetterOrDigit(fileContents.charCodeAt(matchPosition + descriptor.text.length))) { continue; @@ -6972,11 +6972,11 @@ namespace ts { // (?:(TODO\(jason\))|(HACK)) // // Note that the outermost group is *not* a capture group, but the innermost groups - // *are* capture groups. By capturing the inner literals we can determine after + // *are* capture groups. By capturing the inner literals we can determine after // matching which descriptor we are dealing with. let literals = "(?:" + map(descriptors, d => "(" + escapeRegExp(d.text) + ")").join("|") + ")"; - // After matching a descriptor literal, the following regexp matches the rest of the + // After matching a descriptor literal, the following regexp matches the rest of the // text up to the end of the line (or */). let endOfLineOrEndOfComment = /(?:$|\*\/)/.source let messageRemainder = /(?:.*?)/.source @@ -7158,7 +7158,7 @@ namespace ts { /// We do not have a full parser support to know when we should parse a regex or not /// If we consider every slash token to be a regex, we could be missing cases like "1/2/3", where - /// we have a series of divide operator. this list allows us to be more accurate by ruling out + /// we have a series of divide operator. this list allows us to be more accurate by ruling out /// locations where a regexp cannot exist. let noRegexTable: boolean[] = []; noRegexTable[SyntaxKind.Identifier] = true; @@ -7204,7 +7204,7 @@ namespace ts { keyword2 === SyntaxKind.ConstructorKeyword || keyword2 === SyntaxKind.StaticKeyword) { - // Allow things like "public get", "public constructor" and "public static". + // Allow things like "public get", "public constructor" and "public static". // These are all legal. return true; } @@ -7275,7 +7275,7 @@ namespace ts { function getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult { return convertClassifications(getEncodedLexicalClassifications(text, lexState, syntacticClassifierAbsent), text); } - + // If there is a syntactic classifier ('syntacticClassifierAbsent' is false), // we will be more conservative in order to avoid conflicting with the syntactic classifier. function getEncodedLexicalClassifications(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications { @@ -7337,12 +7337,12 @@ namespace ts { // token. So the classification will go back to being an identifier. The moment the user // types again, number will become a keyword, then an identifier, etc. etc. // - // To try to avoid this problem, we avoid classifying contextual keywords as keywords + // To try to avoid this problem, we avoid classifying contextual keywords as keywords // when the user is potentially typing something generic. We just can't do a good enough // job at the lexical level, and so well leave it up to the syntactic classifier to make // the determination. // - // In order to determine if the user is potentially typing something generic, we use a + // In order to determine if the user is potentially typing something generic, we use a // weak heuristic where we track < and > tokens. It's a weak heuristic, but should // work well enough in practice. let angleBracketStack = 0; @@ -7360,7 +7360,7 @@ namespace ts { token = SyntaxKind.Identifier; } else if (isKeyword(lastNonTriviaToken) && isKeyword(token) && !canFollow(lastNonTriviaToken, token)) { - // We have two keywords in a row. Only treat the second as a keyword if + // We have two keywords in a row. Only treat the second as a keyword if // it's a sequence that could legally occur in the language. Otherwise // treat it as an identifier. This way, if someone writes "private var" // we recognize that 'var' is actually an identifier here. @@ -7368,7 +7368,7 @@ namespace ts { } else if (lastNonTriviaToken === SyntaxKind.Identifier && token === SyntaxKind.LessThanToken) { - // Could be the start of something generic. Keep track of that by bumping + // Could be the start of something generic. Keep track of that by bumping // up the current count of generic contexts we may be in. angleBracketStack++; } @@ -7383,7 +7383,7 @@ namespace ts { token === SyntaxKind.BooleanKeyword || token === SyntaxKind.SymbolKeyword) { if (angleBracketStack > 0 && !syntacticClassifierAbsent) { - // If it looks like we're could be in something generic, don't classify this + // If it looks like we're could be in something generic, don't classify this // as a keyword. We may just get overwritten by the syntactic classifier, // causing a noisy experience for the user. token = SyntaxKind.Identifier; @@ -7491,8 +7491,8 @@ namespace ts { } if (start === 0 && offset > 0) { - // We're classifying the first token, and this was a case where we prepended - // text. We should consider the start of this token to be at the start of + // We're classifying the first token, and this was a case where we prepended + // text. We should consider the start of this token to be at the start of // the original text. start += offset; } @@ -7615,11 +7615,11 @@ namespace ts { /// getDefaultLibraryFilePath declare let __dirname: string; - + /** * Get the path of the default library files (lib.d.ts) as distributed with the typescript * node package. - * The functionality is not supported if the ts module is consumed outside of a node module. + * The functionality is not supported if the ts module is consumed outside of a node module. */ export function getDefaultLibFilePath(options: CompilerOptions): string { // Check __dirname is defined and that we are on a node.js system. diff --git a/src/services/shims.ts b/src/services/shims.ts index ace7fbeff01af..f203dc6cbc425 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -421,6 +421,10 @@ namespace ts { public fileExists(fileName: string): boolean { return this.shimHost.fileExists(fileName); } + + public readFile(fileName: string): string { + return this.shimHost.readFile(fileName); + } } function simpleForwardCall(logger: Logger, actionDescription: string, action: () => any, logPerformance: boolean): any { diff --git a/tests/baselines/reference/project/invalidRootFile/amd/invalidRootFile.errors.txt b/tests/baselines/reference/project/invalidRootFile/amd/invalidRootFile.errors.txt index 4433f671a1e99..9cd0dd7b0cf0a 100644 --- a/tests/baselines/reference/project/invalidRootFile/amd/invalidRootFile.errors.txt +++ b/tests/baselines/reference/project/invalidRootFile/amd/invalidRootFile.errors.txt @@ -1,6 +1,6 @@ error TS6053: File 'a.ts' not found. -error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.d.ts', '.tsx'. +error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'. !!! error TS6053: File 'a.ts' not found. -!!! error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.d.ts', '.tsx'. \ No newline at end of file +!!! error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'. \ No newline at end of file diff --git a/tests/baselines/reference/project/invalidRootFile/node/invalidRootFile.errors.txt b/tests/baselines/reference/project/invalidRootFile/node/invalidRootFile.errors.txt index 4433f671a1e99..9cd0dd7b0cf0a 100644 --- a/tests/baselines/reference/project/invalidRootFile/node/invalidRootFile.errors.txt +++ b/tests/baselines/reference/project/invalidRootFile/node/invalidRootFile.errors.txt @@ -1,6 +1,6 @@ error TS6053: File 'a.ts' not found. -error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.d.ts', '.tsx'. +error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'. !!! error TS6053: File 'a.ts' not found. -!!! error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.d.ts', '.tsx'. \ No newline at end of file +!!! error TS6054: File 'a.t' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'. \ No newline at end of file diff --git a/tests/cases/unittests/reuseProgramStructure.ts b/tests/cases/unittests/reuseProgramStructure.ts index 5314ff48f55e3..6c043299c8f15 100644 --- a/tests/cases/unittests/reuseProgramStructure.ts +++ b/tests/cases/unittests/reuseProgramStructure.ts @@ -104,10 +104,6 @@ module ts { files[t.name] = file; } - let moduleResolutionHost: ModuleResolutionHost = { - fileExists: fileName => hasProperty(files, fileName) - } - return { getSourceFile(fileName): SourceFile { return files[fileName]; @@ -130,7 +126,11 @@ module ts { getNewLine(): string { return sys.newLine; }, - getModuleResolutionHost: () => moduleResolutionHost + fileExists: fileName => hasProperty(files, fileName), + readFile: fileName => { + let file = lookUp(files, fileName); + return file && file.text; + } } } From fc1e89ace52cd8fce2c6002186315e2d1dd3382c Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 5 Aug 2015 14:30:41 -0700 Subject: [PATCH 17/20] addressed CR feedback: merged getDefaultModuleResolver and resolveModuleName into one function, added comments --- src/compiler/program.ts | 15 +++++++-------- src/compiler/types.ts | 2 ++ src/server/editorServices.ts | 3 +-- src/services/services.ts | 27 +++++++++++---------------- 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 227372ce30de4..8ceccc0e880dd 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -29,18 +29,18 @@ namespace ts { return undefined; } - export function getDefaultModuleNameResolver(options: CompilerOptions): ModuleNameResolver { - // TODO: return different resolver based on compiler options (i.e. module kind) - return resolveModuleName; - } - export function resolveTripleslashReference(moduleName: string, containingFile: string): string { let basePath = getDirectoryPath(containingFile); let referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); return normalizePath(referencedFileName); } + + export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { + // TODO: use different resolution strategy based on compiler options + return legacyNameResolver(moduleName, containingFile, compilerOptions, host); + } - function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { + function legacyNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { // module names that contain '!' are used to reference resources and are not resolved to actual files on disk if (moduleName.indexOf('!') != -1) { @@ -226,10 +226,9 @@ namespace ts { if (!options.noResolve) { resolveModuleNamesWorker = host.resolveModuleNames; if (!resolveModuleNamesWorker) { - let defaultResolver = getDefaultModuleNameResolver(options); resolveModuleNamesWorker = (moduleNames, containingFile) => { return map(moduleNames, moduleName => { - let moduleResolution = defaultResolver(moduleName, containingFile, options, host); + let moduleResolution = resolveModuleName(moduleName, containingFile, options, host); return moduleResolution.resolvedFileName; }); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1bac2917e8a9b..d709e11d4e36b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2244,6 +2244,8 @@ namespace ts { export interface ModuleResolutionHost { fileExists(fileName: string): boolean; + // readFile function is used to read arbitrary text files on disk, i.e. when resolution procedure needs the content of 'package.json' + // to determine location of bundled typings for node module readFile(fileName: string): string; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 578c9cce55315..82621db291a1c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -106,7 +106,6 @@ namespace ts.server { let resolvedFileNames: string[] = []; let compilerOptions = this.getCompilationSettings(); - let defaultResolver = ts.getDefaultModuleNameResolver(compilerOptions); for (let moduleName of moduleNames) { // check if this is a duplicate entry in the list @@ -118,7 +117,7 @@ namespace ts.server { resolution = existingResolution; } else { - resolution = defaultResolver(moduleName, containingFile, compilerOptions, this.moduleResolutionHost); + resolution = resolveModuleName(moduleName, containingFile, compilerOptions, this.moduleResolutionHost); resolution.lastCheckTime = Date.now(); newResolutions[moduleName] = resolution; } diff --git a/src/services/services.ts b/src/services/services.ts index a3b31afabf075..c01b7316a8178 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1837,7 +1837,7 @@ namespace ts { getCanonicalFileName: fileName => fileName, getCurrentDirectory: () => "", getNewLine: () => newLine, - + // these two methods should never be called in transpile scenarios since 'noResolve' is set to 'true' fileExists: (fileName): boolean => { throw new Error("Should never be called."); }, readFile: (fileName): string => { throw new Error("Should never be called."); } }; @@ -2057,11 +2057,6 @@ namespace ts { }; } - export function resolveModuleName(fileName: string, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModule { - let resolver = getDefaultModuleNameResolver(compilerOptions); - return resolver(moduleName, fileName, compilerOptions, host); - } - export function preProcessFile(sourceText: string, readImportFiles = true): PreProcessedFileInfo { let referencedFiles: FileReference[] = []; let importedFiles: FileReference[] = []; @@ -2578,21 +2573,21 @@ namespace ts { getDefaultLibFileName: (options) => host.getDefaultLibFileName(options), writeFile: (fileName, data, writeByteOrderMark) => { }, getCurrentDirectory: () => host.getCurrentDirectory(), - fileExists: (fileName): boolean => { throw new Error("Not implemented"); }, - readFile: (fileName): string => { throw new Error("Not implemented"); } + fileExists: (fileName): boolean => { + // stub missing host functionality + Debug.assert(!host.resolveModuleNames); + return hostCache.getOrCreateEntry(fileName) !== undefined; + }, + readFile: (fileName): string => { + // stub missing host functionality + let entry = hostCache.getOrCreateEntry(fileName); + return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength()); + } }; if (host.resolveModuleNames) { compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile) } - else { - // stub missing host functionality - compilerHost.fileExists = fileName => hostCache.getOrCreateEntry(fileName) !== undefined; - compilerHost.readFile = fileName => { - let entry = hostCache.getOrCreateEntry(fileName); - return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength()); - } - } let newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program); From a69b04145d0ed68bce62267650f32d2a9ae3ada3 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 6 Aug 2015 16:23:10 -0700 Subject: [PATCH 18/20] delete entry from the cache when referenced file is removed, added tests --- src/harness/fourslash.ts | 4 ++- src/harness/fourslashRunner.ts | 5 +++ src/harness/harnessLanguageService.ts | 34 ++++++++++++++++--- src/harness/runner.ts | 6 +++- src/server/editorServices.ts | 1 + src/services/shims.ts | 2 +- .../cases/unittests/services/colorization.ts | 4 +-- 7 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2ebf086cb7466..121d1938a4af6 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -285,7 +285,9 @@ module FourSlash { case FourSlashTestType.Native: return new Harness.LanguageService.NativeLanugageServiceAdapter(cancellationToken, compilationOptions); case FourSlashTestType.Shims: - return new Harness.LanguageService.ShimLanugageServiceAdapter(cancellationToken, compilationOptions); + return new Harness.LanguageService.ShimLanugageServiceAdapter(/*preprocessToResolve*/ false, cancellationToken, compilationOptions); + case FourSlashTestType.ShimsWithPreprocess: + return new Harness.LanguageService.ShimLanugageServiceAdapter(/*preprocessToResolve*/ true, cancellationToken, compilationOptions); case FourSlashTestType.Server: return new Harness.LanguageService.ServerLanugageServiceAdapter(cancellationToken, compilationOptions); default: diff --git a/src/harness/fourslashRunner.ts b/src/harness/fourslashRunner.ts index 3914232dbe8d5..d30a30d88e31e 100644 --- a/src/harness/fourslashRunner.ts +++ b/src/harness/fourslashRunner.ts @@ -5,6 +5,7 @@ const enum FourSlashTestType { Native, Shims, + ShimsWithPreprocess, Server } @@ -23,6 +24,10 @@ class FourSlashRunner extends RunnerBase { this.basePath = "tests/cases/fourslash/shims"; this.testSuiteName = "fourslash-shims"; break; + case FourSlashTestType.ShimsWithPreprocess: + this.basePath = 'tests/cases/fourslash/shims-pp'; + this.testSuiteName = 'fourslash-shims-pp'; + break; case FourSlashTestType.Server: this.basePath = "tests/cases/fourslash/server"; this.testSuiteName = "fourslash-server"; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index f33562a5a2f01..db388c9561056 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -203,9 +203,35 @@ module Harness.LanguageService { /// Shim adapter class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost { private nativeHost: NativeLanguageServiceHost; - constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + + public getModuleResolutionsForFile: (fileName: string)=> string; + + constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { super(cancellationToken, options); this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); + + if (preprocessToResolve) { + let compilerOptions = this.nativeHost.getCompilationSettings() + let moduleResolutionHost: ts.ModuleResolutionHost = { + fileExists: fileName => this.getScriptInfo(fileName) !== undefined, + readFile: fileName => { + let scriptInfo = this.getScriptInfo(fileName); + return scriptInfo && scriptInfo.content; + } + }; + this.getModuleResolutionsForFile = (fileName) => { + let scriptInfo = this.getScriptInfo(fileName); + let preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); + let imports: ts.Map = {}; + for (let module of preprocessInfo.importedFiles) { + let resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); + if (resolutionInfo.resolvedFileName) { + imports[module.fileName] = resolutionInfo.resolvedFileName; + } + } + return JSON.stringify(imports); + } + } } getFilenames(): string[] { return this.nativeHost.getFilenames(); } @@ -228,7 +254,7 @@ module Harness.LanguageService { readDirectory(rootDir: string, extension: string): string { throw new Error("NYI"); - } + } fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } readFile(fileName: string) { let snapshot = this.nativeHost.getScriptSnapshot(fileName); @@ -400,8 +426,8 @@ module Harness.LanguageService { export class ShimLanugageServiceAdapter implements LanguageServiceAdapter { private host: ShimLanguageServiceHost; private factory: ts.TypeScriptServicesFactory; - constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - this.host = new ShimLanguageServiceHost(cancellationToken, options); + constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + this.host = new ShimLanguageServiceHost(preprocessToResolve, cancellationToken, options); this.factory = new TypeScript.Services.TypeScriptServicesFactory(); } getHost() { return this.host; } diff --git a/src/harness/runner.ts b/src/harness/runner.ts index 06aaf3d32b543..3d97938011ce1 100644 --- a/src/harness/runner.ts +++ b/src/harness/runner.ts @@ -68,7 +68,10 @@ if (testConfigFile !== "") { case "fourslash-shims": runners.push(new FourSlashRunner(FourSlashTestType.Shims)); break; - case "fourslash-server": + case 'fourslash-shims-pp': + runners.push(new FourSlashRunner(FourSlashTestType.ShimsWithPreprocess)); + break; + case 'fourslash-server': runners.push(new FourSlashRunner(FourSlashTestType.Server)); break; case "fourslash-generated": @@ -98,6 +101,7 @@ if (runners.length === 0) { // language services runners.push(new FourSlashRunner(FourSlashTestType.Native)); runners.push(new FourSlashRunner(FourSlashTestType.Shims)); + runners.push(new FourSlashRunner(FourSlashTestType.ShimsWithPreprocess)); runners.push(new FourSlashRunner(FourSlashTestType.Server)); // runners.push(new GeneratedFourslashRunner()); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 82621db291a1c..eec7d4758906a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -200,6 +200,7 @@ namespace ts.server { removeReferencedFile(info: ScriptInfo) { if (!info.isOpen) { this.filenameToScript[info.fileName] = undefined; + this.resolvedModuleNames.remove(info.fileName); } } diff --git a/src/services/shims.ts b/src/services/shims.ts index f203dc6cbc425..1512c5df523f2 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -61,7 +61,7 @@ namespace ts { getProjectVersion?(): string; useCaseSensitiveFileNames?(): boolean; - getModuleResolutionsForFile?(fileName: string): string; + getModuleResolutionsForFile?(fileName: string): string; } /** Public interface of the the of a config service shim instance.*/ diff --git a/tests/cases/unittests/services/colorization.ts b/tests/cases/unittests/services/colorization.ts index da1fa91036c94..82f2e106aa4b4 100644 --- a/tests/cases/unittests/services/colorization.ts +++ b/tests/cases/unittests/services/colorization.ts @@ -9,8 +9,8 @@ interface ClassificationEntry { describe('Colorization', function () { // 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(); + var languageServiceAdapter = new Harness.LanguageService.ShimLanugageServiceAdapter(/*preprocessToResolve*/ false); + var classifier = languageServiceAdapter.getClassifier(); function getEntryAtPosistion(result: ts.ClassificationResult, position: number) { var entryPosition = 0; From b4faf3c812a1a17d4a437a0084700268e5903f5b Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Thu, 6 Aug 2015 16:53:14 -0700 Subject: [PATCH 19/20] added missing test assets --- .../reference/getEmitOutput-pp.baseline | 30 +++++ .../shims-pp/getBraceMatchingAtPosition.ts | 43 +++++++ .../getBreakpointStatementAtPosition.ts | 17 +++ .../shims-pp/getCompletionsAtPosition.ts | 20 ++++ .../shims-pp/getDefinitionAtPosition.ts | 29 +++++ .../cases/fourslash/shims-pp/getEmitOutput.ts | 22 ++++ .../shims-pp/getIndentationAtPosition.ts | 21 ++++ .../fourslash/shims-pp/getNavigateToItems.ts | 27 +++++ .../shims-pp/getNavigationBarItems.ts | 13 ++ .../shims-pp/getOccurrencesAtPosition.ts | 18 +++ .../fourslash/shims-pp/getOutliningSpans.ts | 113 ++++++++++++++++++ .../fourslash/shims-pp/getPreProcessedFile.ts | 30 +++++ .../shims-pp/getQuickInfoAtPosition.ts | 16 +++ .../shims-pp/getReferencesAtPosition.ts | 29 +++++ .../cases/fourslash/shims-pp/getRenameInfo.ts | 11 ++ .../shims-pp/getSemanticClassifications.ts | 15 +++ .../shims-pp/getSemanticDiagnostics.ts | 11 ++ .../shims-pp/getSignatureHelpItems.ts | 13 ++ .../shims-pp/getSyntacticClassifications.ts | 35 ++++++ .../fourslash/shims-pp/getTodoComments.ts | 3 + .../fourslash/shims-pp/goToTypeDefinition.ts | 14 +++ .../shims-pp/quickInfoDisplayPartsVar.ts | 86 +++++++++++++ 22 files changed, 616 insertions(+) create mode 100644 tests/baselines/reference/getEmitOutput-pp.baseline create mode 100644 tests/cases/fourslash/shims-pp/getBraceMatchingAtPosition.ts create mode 100644 tests/cases/fourslash/shims-pp/getBreakpointStatementAtPosition.ts create mode 100644 tests/cases/fourslash/shims-pp/getCompletionsAtPosition.ts create mode 100644 tests/cases/fourslash/shims-pp/getDefinitionAtPosition.ts create mode 100644 tests/cases/fourslash/shims-pp/getEmitOutput.ts create mode 100644 tests/cases/fourslash/shims-pp/getIndentationAtPosition.ts create mode 100644 tests/cases/fourslash/shims-pp/getNavigateToItems.ts create mode 100644 tests/cases/fourslash/shims-pp/getNavigationBarItems.ts create mode 100644 tests/cases/fourslash/shims-pp/getOccurrencesAtPosition.ts create mode 100644 tests/cases/fourslash/shims-pp/getOutliningSpans.ts create mode 100644 tests/cases/fourslash/shims-pp/getPreProcessedFile.ts create mode 100644 tests/cases/fourslash/shims-pp/getQuickInfoAtPosition.ts create mode 100644 tests/cases/fourslash/shims-pp/getReferencesAtPosition.ts create mode 100644 tests/cases/fourslash/shims-pp/getRenameInfo.ts create mode 100644 tests/cases/fourslash/shims-pp/getSemanticClassifications.ts create mode 100644 tests/cases/fourslash/shims-pp/getSemanticDiagnostics.ts create mode 100644 tests/cases/fourslash/shims-pp/getSignatureHelpItems.ts create mode 100644 tests/cases/fourslash/shims-pp/getSyntacticClassifications.ts create mode 100644 tests/cases/fourslash/shims-pp/getTodoComments.ts create mode 100644 tests/cases/fourslash/shims-pp/goToTypeDefinition.ts create mode 100644 tests/cases/fourslash/shims-pp/quickInfoDisplayPartsVar.ts diff --git a/tests/baselines/reference/getEmitOutput-pp.baseline b/tests/baselines/reference/getEmitOutput-pp.baseline new file mode 100644 index 0000000000000..d5487a14d95a3 --- /dev/null +++ b/tests/baselines/reference/getEmitOutput-pp.baseline @@ -0,0 +1,30 @@ +EmitSkipped: false +FileName : tests/cases/fourslash/shims-pp/inputFile1.js +var x = 5; +var Bar = (function () { + function Bar() { + } + return Bar; +})(); +FileName : tests/cases/fourslash/shims-pp/inputFile1.d.ts +declare var x: number; +declare class Bar { + x: string; + y: number; +} + +EmitSkipped: false +FileName : tests/cases/fourslash/shims-pp/inputFile2.js +var x1 = "hello world"; +var Foo = (function () { + function Foo() { + } + return Foo; +})(); +FileName : tests/cases/fourslash/shims-pp/inputFile2.d.ts +declare var x1: string; +declare class Foo { + x: string; + y: number; +} + diff --git a/tests/cases/fourslash/shims-pp/getBraceMatchingAtPosition.ts b/tests/cases/fourslash/shims-pp/getBraceMatchingAtPosition.ts new file mode 100644 index 0000000000000..fc8a71197db11 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getBreakpointStatementAtPosition.ts b/tests/cases/fourslash/shims-pp/getBreakpointStatementAtPosition.ts new file mode 100644 index 0000000000000..9e1d075a17ff5 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getCompletionsAtPosition.ts b/tests/cases/fourslash/shims-pp/getCompletionsAtPosition.ts new file mode 100644 index 0000000000000..714d3390e760b --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getDefinitionAtPosition.ts b/tests/cases/fourslash/shims-pp/getDefinitionAtPosition.ts new file mode 100644 index 0000000000000..3aa31d1556c56 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getEmitOutput.ts b/tests/cases/fourslash/shims-pp/getEmitOutput.ts new file mode 100644 index 0000000000000..45679066278c0 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/getEmitOutput.ts @@ -0,0 +1,22 @@ +/// + +// @BaselineFile: getEmitOutput-pp.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-pp/getIndentationAtPosition.ts b/tests/cases/fourslash/shims-pp/getIndentationAtPosition.ts new file mode 100644 index 0000000000000..ba2936bd70223 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getNavigateToItems.ts b/tests/cases/fourslash/shims-pp/getNavigateToItems.ts new file mode 100644 index 0000000000000..5481f4295cb3b --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getNavigationBarItems.ts b/tests/cases/fourslash/shims-pp/getNavigationBarItems.ts new file mode 100644 index 0000000000000..6c0738747f37c --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getOccurrencesAtPosition.ts b/tests/cases/fourslash/shims-pp/getOccurrencesAtPosition.ts new file mode 100644 index 0000000000000..0654cc3962c20 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getOutliningSpans.ts b/tests/cases/fourslash/shims-pp/getOutliningSpans.ts new file mode 100644 index 0000000000000..93665889eb429 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getPreProcessedFile.ts b/tests/cases/fourslash/shims-pp/getPreProcessedFile.ts new file mode 100644 index 0000000000000..aa0ff4c066935 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/getPreProcessedFile.ts @@ -0,0 +1,30 @@ +/// + +// @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"); +//// import invalidRef2 = /*9*/requi/*10*/(/*10A*/"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"); +verify.errorExistsBetweenMarkers("10", "10A"); +verify.errorExistsBetweenMarkers("11", "12"); diff --git a/tests/cases/fourslash/shims-pp/getQuickInfoAtPosition.ts b/tests/cases/fourslash/shims-pp/getQuickInfoAtPosition.ts new file mode 100644 index 0000000000000..ef201f6709670 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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<{}>'); diff --git a/tests/cases/fourslash/shims-pp/getReferencesAtPosition.ts b/tests/cases/fourslash/shims-pp/getReferencesAtPosition.ts new file mode 100644 index 0000000000000..34144b748991d --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getRenameInfo.ts b/tests/cases/fourslash/shims-pp/getRenameInfo.ts new file mode 100644 index 0000000000000..b7a1f5d61ae95 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getSemanticClassifications.ts b/tests/cases/fourslash/shims-pp/getSemanticClassifications.ts new file mode 100644 index 0000000000000..4eb7f2a32edab --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getSemanticDiagnostics.ts b/tests/cases/fourslash/shims-pp/getSemanticDiagnostics.ts new file mode 100644 index 0000000000000..804abbde33bb7 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getSignatureHelpItems.ts b/tests/cases/fourslash/shims-pp/getSignatureHelpItems.ts new file mode 100644 index 0000000000000..846c2d5244a1d --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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-pp/getSyntacticClassifications.ts b/tests/cases/fourslash/shims-pp/getSyntacticClassifications.ts new file mode 100644 index 0000000000000..1dbe2944b8ae5 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/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.identifier("v"), c.operator("="), c.numericLiteral("0"), c.operator("+"), c.numericLiteral("1"), c.punctuation(";"), + c.keyword("var"), c.identifier("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-pp/getTodoComments.ts b/tests/cases/fourslash/shims-pp/getTodoComments.ts new file mode 100644 index 0000000000000..b1e0086b93f7b --- /dev/null +++ b/tests/cases/fourslash/shims-pp/getTodoComments.ts @@ -0,0 +1,3 @@ +//// // [|TODO|] + +verify.todoCommentsInCurrentFile(["TODO"]); \ No newline at end of file diff --git a/tests/cases/fourslash/shims-pp/goToTypeDefinition.ts b/tests/cases/fourslash/shims-pp/goToTypeDefinition.ts new file mode 100644 index 0000000000000..6b34e7e48866c --- /dev/null +++ b/tests/cases/fourslash/shims-pp/goToTypeDefinition.ts @@ -0,0 +1,14 @@ +/// + +// @Filename: goToTypeDefinition_Definition.ts +/////*definition*/class C { +//// p; +////} +////var c: C; + +// @Filename: goToTypeDefinition_Consumption.ts +/////*reference*/c = undefined; + +goTo.marker('reference'); +goTo.type(); +verify.caretAtMarker('definition'); diff --git a/tests/cases/fourslash/shims-pp/quickInfoDisplayPartsVar.ts b/tests/cases/fourslash/shims-pp/quickInfoDisplayPartsVar.ts new file mode 100644 index 0000000000000..234c23277b6a0 --- /dev/null +++ b/tests/cases/fourslash/shims-pp/quickInfoDisplayPartsVar.ts @@ -0,0 +1,86 @@ +/// + +////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, typeDisplay: ts.SymbolDisplayPart[], optionalNameDisplay?: ts.SymbolDisplayPart[], optionalKindModifiers?: string) { + marker++; + goTo.marker(marker.toString()); + var kind = "var"; + verify.verifyQuickInfoDisplayParts(kind, optionalKindModifiers || "", { start: test.markerByName(marker.toString()).position, length: name.length }, + [{ text: kind, kind: "keyword" }, + { text: " ", kind: "space" }].concat(optionalNameDisplay || [{ text: name, kind: "localName" }]).concat( + { text: ":", kind: "punctuation" }, { text: " ", kind: "space" }).concat(typeDisplay), + []); +} +function verifyLocalVar(name: string, typeDisplay: ts.SymbolDisplayPart[], optionalNameDisplay?: ts.SymbolDisplayPart[], optionalKindModifiers?: string) { + marker++; + goTo.marker(marker.toString()); + var kind = "local 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", numberTypeDisplay); +verifyLocalVar("b", numberTypeDisplay); +verifyVar("a", numberTypeDisplay); +verifyVar("c", numberTypeDisplay); +verifyVar("d", 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", functionTypeReturningNumber); +verifyVar("g", functionTypeReturningNumber); +verifyVar("f", functionTypeReturningNumber); +verifyVar("f", 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", typeLiteralWithOverloadCall); +verifyVar("i", typeLiteralWithOverloadCall); +verifyVar("h", 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", getFunctionType("number", "string", /*isArrow*/true).concat(overloadDisplay)); +verifyVar("h", getFunctionType("string", "number", /*isArrow*/true).concat(overloadDisplay)); \ No newline at end of file From 405db829a0fd6a1677da82568dbdfaa69f0b36dd Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 12 Aug 2015 13:55:41 -0700 Subject: [PATCH 20/20] mismatch order of arguments --- src/services/shims.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/shims.ts b/src/services/shims.ts index 1512c5df523f2..d298dfe1cb276 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -931,7 +931,7 @@ namespace ts { public resolveModuleName(fileName: string, moduleName: string, compilerOptionsJson: string): string { return this.forwardJSONCall(`resolveModuleName('${fileName}')`, () => { let compilerOptions = JSON.parse(compilerOptionsJson); - return resolveModuleName(normalizeSlashes(fileName), moduleName, compilerOptions, this.host); + return resolveModuleName(moduleName, normalizeSlashes(fileName), compilerOptions, this.host); }); }