diff --git a/src/compiler/extensions.ts b/src/compiler/extensions.ts index 8f2d07b3d7af7..298b0cdc3925f 100644 --- a/src/compiler/extensions.ts +++ b/src/compiler/extensions.ts @@ -30,6 +30,7 @@ namespace ts { export interface ExtensionHost extends ModuleResolutionHost { loadExtension?(name: string): any; + resolveModuleNames?(moduleNames: string[], containingFile: string, loadJs?: boolean): ResolvedModule[]; } export interface Program { @@ -113,11 +114,22 @@ namespace ts { }; return cache; + // Defer to the host's `resolveModuleName` method if it has it, otherwise use it as a ModuleResolutionHost. + function resolveModuleName(name: string, fromLocation: string) { + if (host.resolveModuleNames) { + const results = host.resolveModuleNames([name], fromLocation, /*loadJs*/true); + return results && results[0]; + } + else { + return ts.resolveModuleName(name, fromLocation, options, host, /*loadJs*/true).resolvedModule; + } + } + function resolveExtensionNames(): Map { const basePath = options.configFilePath || combinePaths(host.getCurrentDirectory ? host.getCurrentDirectory() : "", "tsconfig.json"); const extMap: Map = {}; forEach(extensionNames, name => { - const resolved = resolveModuleName(name, basePath, options, host, /*loadJs*/true).resolvedModule; + const resolved = resolveModuleName(name, basePath); if (resolved) { extMap[name] = resolved.resolvedFileName; } diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 9d98da46cd1a1..5f751856bfb13 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -24,7 +24,7 @@ "declarationEmitter.ts", "emitter.ts", "program.ts", - "extensions.ts", + "extensions.ts", "commandLineParser.ts", "tsc.ts", "diagnosticInformationMap.generated.ts" diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dc750e2c9dff5..398a88cb22fa3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2962,7 +2962,7 @@ namespace ts { * If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just * 'throw new Error("NotImplemented")' */ - resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, loadJs?: boolean): ResolvedModule[]; /** * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index fdd2f6602ecb5..11f14c1f63fef 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -216,7 +216,7 @@ namespace Harness.LanguageService { class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost { private nativeHost: NativeLanguageServiceHost; - public getModuleResolutionsForFile: (fileName: string) => string; + public getModuleResolutionsForFile: (fileName: string, loadJs?: boolean) => string; public getTypeReferenceDirectiveResolutionsForFile: (fileName: string) => string; constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { @@ -232,12 +232,12 @@ namespace Harness.LanguageService { return scriptInfo && scriptInfo.content; } }; - this.getModuleResolutionsForFile = (fileName) => { + this.getModuleResolutionsForFile = (fileName, loadJs) => { const scriptInfo = this.getScriptInfo(fileName); const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); const imports: ts.Map = {}; for (const module of preprocessInfo.importedFiles) { - const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); + const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost, loadJs); if (resolutionInfo.resolvedModule) { imports[module.fileName] = resolutionInfo.resolvedModule.resolvedFileName; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index acc12116ecb57..594e9936d89b4 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -124,8 +124,9 @@ namespace ts.server { names: string[], containingFile: string, cache: ts.FileMap>, - loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, - getResult: (s: T) => R): R[] { + loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, loadJs?: boolean) => T, + getResult: (s: T) => R, + loadJs: boolean): R[] { const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName); const currentResolutionsInFile = cache.get(path); @@ -144,7 +145,7 @@ namespace ts.server { resolution = existingResolution; } else { - resolution = loader(name, containingFile, compilerOptions, this.moduleResolutionHost); + resolution = loader(name, containingFile, compilerOptions, this.moduleResolutionHost, loadJs); resolution.lastCheckTime = Date.now(); newResolutions[name] = resolution; } @@ -177,11 +178,11 @@ namespace ts.server { } resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { - return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective); + return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective, /*loadJs*/false); } - resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] { - return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, resolveModuleName, m => m.resolvedModule); + resolveModuleNames(moduleNames: string[], containingFile: string, loadJs?: boolean): ResolvedModule[] { + return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, resolveModuleName, m => m.resolvedModule, loadJs); } getDefaultLibFileName() { @@ -1377,6 +1378,13 @@ namespace ts.server { return false; } + loadExtension(name: string) { + if (!sys.loadExtension) { + throw new Error("Extension loading not implemented on the active host!"); + } + return sys.loadExtension(name); + } + openConfigFile(configFilename: string, clientFileName?: string): { success: boolean, project?: Project, errors?: Diagnostic[] } { const { succeeded, projectOptions, errors } = this.configFileToProjectOptions(configFilename); if (!succeeded) { diff --git a/src/services/services.ts b/src/services/services.ts index 7805fd19e03b9..5bf6453c42c15 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1163,7 +1163,7 @@ namespace ts { * 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): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, loadJs?: boolean): ResolvedModule[]; resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; directoryExists?(directoryName: string): boolean; getDirectories?(directoryName: string): string[]; @@ -3147,7 +3147,7 @@ namespace ts { } if (host.resolveModuleNames) { - compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); + compilerHost.resolveModuleNames = (moduleNames, containingFile, loadJs) => host.resolveModuleNames(moduleNames, containingFile, loadJs); } if (host.resolveTypeReferenceDirectives) { compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { diff --git a/src/services/shims.ts b/src/services/shims.ts index a73133940bd6e..045b0894bb882 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -67,7 +67,7 @@ namespace ts { getProjectVersion?(): string; useCaseSensitiveFileNames?(): boolean; - getModuleResolutionsForFile?(fileName: string): string; + getModuleResolutionsForFile?(fileName: string, loadJs?: boolean): string; getTypeReferenceDirectiveResolutionsForFile?(fileName: string): string; directoryExists(directoryName: string): boolean; } @@ -303,7 +303,7 @@ namespace ts { private loggingEnabled = false; private tracingEnabled = false; - public resolveModuleNames: (moduleName: string[], containingFile: string) => ResolvedModule[]; + public resolveModuleNames: (moduleName: string[], containingFile: string, loadJs?: boolean) => ResolvedModule[]; public resolveTypeReferenceDirectives: (typeDirectiveNames: string[], containingFile: string) => ResolvedTypeReferenceDirective[]; public directoryExists: (directoryName: string) => boolean; @@ -311,8 +311,8 @@ namespace ts { // 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.resolveModuleNames = (moduleNames: string[], containingFile: string) => { - const resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); + this.resolveModuleNames = (moduleNames: string[], containingFile: string, loadJs?: boolean) => { + const resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile, loadJs)); return map(moduleNames, name => { const result = lookUp(resolutionsInFile, name); return result ? { resolvedFileName: result } : undefined; diff --git a/tests/baselines/reference/CompilerHost/loadsExtensions/test-normal.js b/tests/baselines/reference/CompilerHost/loadsExtensions/test-normal.js new file mode 100644 index 0000000000000..21b4721f1a6ed --- /dev/null +++ b/tests/baselines/reference/CompilerHost/loadsExtensions/test-normal.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/baselines/reference/LanguageServiceHost/loadsExtensions/test-normal.js b/tests/baselines/reference/LanguageServiceHost/loadsExtensions/test-normal.js new file mode 100644 index 0000000000000..b9f140e37bdde --- /dev/null +++ b/tests/baselines/reference/LanguageServiceHost/loadsExtensions/test-normal.js @@ -0,0 +1,5 @@ +//// [hello.ts] +console.log("Hello, world!");/*EOL*/ + +//// [hello.js] +console.log("Hello, world!"); /*EOL*/ diff --git a/tests/cases/extensions/available/dummy/index.ts b/tests/cases/extensions/available/dummy/index.ts new file mode 100644 index 0000000000000..26f98f77f6d17 --- /dev/null +++ b/tests/cases/extensions/available/dummy/index.ts @@ -0,0 +1 @@ +console.log("loaded"); \ No newline at end of file diff --git a/tests/cases/extensions/available/dummy/package.json b/tests/cases/extensions/available/dummy/package.json new file mode 100644 index 0000000000000..96abe59cfd984 --- /dev/null +++ b/tests/cases/extensions/available/dummy/package.json @@ -0,0 +1,7 @@ +{ + "name": "dummy", + "version": "1.0.0", + "description": "", + "main": "index.js", + "author": "" +} \ No newline at end of file diff --git a/tests/cases/extensions/scenarios/loadsExtensions/test-normal.json b/tests/cases/extensions/scenarios/loadsExtensions/test-normal.json new file mode 100644 index 0000000000000..fafffb866d178 --- /dev/null +++ b/tests/cases/extensions/scenarios/loadsExtensions/test-normal.json @@ -0,0 +1,9 @@ +{ + "inputFiles": [ + "hello.ts" + ], + "availableExtensions": ["dummy"], + "compilerOptions": { + "extensions": ["dummy"] + } +} \ No newline at end of file