From 42f13b0366168170ed827b630564b1adc2aedcfd Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 2 Mar 2020 11:18:49 -0500 Subject: [PATCH 01/13] Re-add "realpath" to LanguageServiceHost Implement resolveModuleNames that forces modules to be considered internal (not from an external library) when we need them to be emitted --- src/index.ts | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 90e6cd6ec..669ad03c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,6 +71,21 @@ export interface TSCommon { formatDiagnosticsWithColorAndContext: typeof _ts.formatDiagnosticsWithColorAndContext } +/** + * Compiler APIs we use that are marked internal and not included in TypeScript's public API declarations + */ +interface TSInternal { + // https://github.com/microsoft/TypeScript/blob/4a34294908bed6701dcba2456ca7ac5eafe0ddff/src/compiler/core.ts#L1906-L1909 + createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): TSInternal.GetCanonicalFileName + + // https://github.com/microsoft/TypeScript/blob/4a34294908bed6701dcba2456ca7ac5eafe0ddff/src/compiler/core.ts#L1430-L1445 + toFileNameLowerCase(x: string): string +} +namespace TSInternal { + // https://github.com/microsoft/TypeScript/blob/4a34294908bed6701dcba2456ca7ac5eafe0ddff/src/compiler/core.ts#L1906 + export type GetCanonicalFileName = (fileName: string) => string; +} + /** * Export the current version. */ @@ -469,7 +484,7 @@ export function create (rawOptions: CreateOptions = {}): Register { } // Create the compiler host for type checking. - const serviceHost: _ts.LanguageServiceHost = { + const serviceHost: _ts.LanguageServiceHost & Required> = { getProjectVersion: () => String(projectVersion), getScriptFileNames: () => rootFileNames, getScriptVersion: (fileName: string) => { @@ -495,13 +510,39 @@ export function create (rawOptions: CreateOptions = {}): Register { getDirectories: cachedLookup(debugFn('getDirectories', ts.sys.getDirectories)), fileExists: cachedLookup(debugFn('fileExists', fileExists)), directoryExists: cachedLookup(debugFn('directoryExists', ts.sys.directoryExists)), + realpath: ts.sys.realpath ? cachedLookup(debugFn('realpath', ts.sys.realpath)) : undefined, getNewLine: () => ts.sys.newLine, useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, getCurrentDirectory: () => cwd, getCompilationSettings: () => config.options, getDefaultLibFileName: () => ts.getDefaultLibFilePath(config.options), - getCustomTransformers: getCustomTransformers + getCustomTransformers: getCustomTransformers, + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: _ts.ResolvedProjectReference | undefined, options: _ts.CompilerOptions): (_ts.ResolvedModule | undefined)[] { + return moduleNames.map(moduleName => { + const {resolvedModule} = ts.resolveModuleName(moduleName, containingFile, options, serviceHost, moduleResolutionCache, redirectedReference) + if(resolvedModule) fixupResolvedModule(resolvedModule) + return resolvedModule + }) + }, + getResolvedModuleWithFailedLookupLocationsFromCache(moduleName, containingFile): _ts.ResolvedModuleWithFailedLookupLocations | undefined { + const ret = ts.resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache) + if(ret && ret.resolvedModule) { + fixupResolvedModule(ret.resolvedModule) + } + return ret + }, + } + /** + * If we need to emit JS for a file, force TS to consider it non-external + */ + function fixupResolvedModule(resolvedModule: _ts.ResolvedModule) { + const canonical = getCanonicalFileName(resolvedModule.resolvedFileName) + if(rootFileNames.some(rootFileName => canonical === getCanonicalFileName(rootFileName))) { + resolvedModule.isExternalLibraryImport = false + } } + const getCanonicalFileName = (ts as unknown as TSInternal).createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames) + const moduleResolutionCache = ts.createModuleResolutionCache(cwd, (s) => getCanonicalFileName(s), config.options) const registry = ts.createDocumentRegistry(ts.sys.useCaseSensitiveFileNames, cwd) const service = ts.createLanguageService(serviceHost, registry) From 99cfd15a3b4f4a39955f9844a23fc5abc5915dee Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 2 Mar 2020 11:30:59 -0500 Subject: [PATCH 02/13] fix linter failures --- src/index.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index 669ad03c1..1b100cab2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,14 +76,14 @@ export interface TSCommon { */ interface TSInternal { // https://github.com/microsoft/TypeScript/blob/4a34294908bed6701dcba2456ca7ac5eafe0ddff/src/compiler/core.ts#L1906-L1909 - createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): TSInternal.GetCanonicalFileName + createGetCanonicalFileName (useCaseSensitiveFileNames: boolean): TSInternal.GetCanonicalFileName // https://github.com/microsoft/TypeScript/blob/4a34294908bed6701dcba2456ca7ac5eafe0ddff/src/compiler/core.ts#L1430-L1445 - toFileNameLowerCase(x: string): string + toFileNameLowerCase (x: string): string } namespace TSInternal { // https://github.com/microsoft/TypeScript/blob/4a34294908bed6701dcba2456ca7ac5eafe0ddff/src/compiler/core.ts#L1906 - export type GetCanonicalFileName = (fileName: string) => string; + export type GetCanonicalFileName = (fileName: string) => string } /** @@ -517,27 +517,27 @@ export function create (rawOptions: CreateOptions = {}): Register { getCompilationSettings: () => config.options, getDefaultLibFileName: () => ts.getDefaultLibFilePath(config.options), getCustomTransformers: getCustomTransformers, - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: _ts.ResolvedProjectReference | undefined, options: _ts.CompilerOptions): (_ts.ResolvedModule | undefined)[] { + resolveModuleNames (moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: _ts.ResolvedProjectReference | undefined, options: _ts.CompilerOptions): (_ts.ResolvedModule | undefined)[] { return moduleNames.map(moduleName => { - const {resolvedModule} = ts.resolveModuleName(moduleName, containingFile, options, serviceHost, moduleResolutionCache, redirectedReference) - if(resolvedModule) fixupResolvedModule(resolvedModule) + const { resolvedModule } = ts.resolveModuleName(moduleName, containingFile, options, serviceHost, moduleResolutionCache, redirectedReference) + if (resolvedModule) fixupResolvedModule(resolvedModule) return resolvedModule }) }, - getResolvedModuleWithFailedLookupLocationsFromCache(moduleName, containingFile): _ts.ResolvedModuleWithFailedLookupLocations | undefined { + getResolvedModuleWithFailedLookupLocationsFromCache (moduleName, containingFile): _ts.ResolvedModuleWithFailedLookupLocations | undefined { const ret = ts.resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache) - if(ret && ret.resolvedModule) { + if (ret && ret.resolvedModule) { fixupResolvedModule(ret.resolvedModule) } return ret - }, + } } /** * If we need to emit JS for a file, force TS to consider it non-external */ - function fixupResolvedModule(resolvedModule: _ts.ResolvedModule) { + const fixupResolvedModule = (resolvedModule: _ts.ResolvedModule) => { const canonical = getCanonicalFileName(resolvedModule.resolvedFileName) - if(rootFileNames.some(rootFileName => canonical === getCanonicalFileName(rootFileName))) { + if (rootFileNames.some(rootFileName => canonical === getCanonicalFileName(rootFileName))) { resolvedModule.isExternalLibraryImport = false } } From 18c1c54550adbd4304ae5074b4bf26d618e2c4e2 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 2 Mar 2020 11:41:47 -0500 Subject: [PATCH 03/13] fix failure on old ts version --- src/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 1b100cab2..9aa8335cb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -517,9 +517,14 @@ export function create (rawOptions: CreateOptions = {}): Register { getCompilationSettings: () => config.options, getDefaultLibFileName: () => ts.getDefaultLibFilePath(config.options), getCustomTransformers: getCustomTransformers, + /* + * NOTE: + * Older ts versions do not pass `redirectedReference` nor `options`. + * We must pass `redirectedReference` to newer ts versions, but cannot rely on `options` + */ resolveModuleNames (moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: _ts.ResolvedProjectReference | undefined, options: _ts.CompilerOptions): (_ts.ResolvedModule | undefined)[] { return moduleNames.map(moduleName => { - const { resolvedModule } = ts.resolveModuleName(moduleName, containingFile, options, serviceHost, moduleResolutionCache, redirectedReference) + const { resolvedModule } = ts.resolveModuleName(moduleName, containingFile, config.options, serviceHost, moduleResolutionCache, redirectedReference) if (resolvedModule) fixupResolvedModule(resolvedModule) return resolvedModule }) From be7797756c173909a0ce0132df41ad5587c0e14b Mon Sep 17 00:00:00 2001 From: Sylvain Date: Sun, 5 Apr 2020 16:33:03 +0800 Subject: [PATCH 04/13] normalized filename --- src/index.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5e6402e00..6a8265047 100644 --- a/src/index.ts +++ b/src/index.ts @@ -507,17 +507,18 @@ export function create (rawOptions: CreateOptions = {}): Register { const service = ts.createLanguageService(serviceHost, registry) const updateMemoryCache = (contents: string, fileName: string) => { + const normalizedFileName = normalizeSlashes(fileName) // Add to `rootFiles` when discovered for the first time. - if (!fileVersions.has(fileName)) { - rootFileNames.push(fileName) + if (!fileVersions.has(normalizedFileName)) { + rootFileNames.push(normalizedFileName) } - const previousVersion = fileVersions.get(fileName) || 0 - const previousContents = fileContents.get(fileName) + const previousVersion = fileVersions.get(normalizedFileName) || 0 + const previousContents = fileContents.get(normalizedFileName) // Avoid incrementing cache when nothing has changed. if (contents !== previousContents) { - fileVersions.set(fileName, previousVersion + 1) - fileContents.set(fileName, contents) + fileVersions.set(normalizedFileName, previousVersion + 1) + fileContents.set(normalizedFileName, contents) // Increment project version for every file change. projectVersion++ } @@ -633,13 +634,14 @@ export function create (rawOptions: CreateOptions = {}): Register { // Set the file contents into cache manually. const updateMemoryCache = (contents: string, fileName: string) => { - const sourceFile = builderProgram.getSourceFile(fileName) + const normalizedFileName = normalizeSlashes(fileName) + const sourceFile = builderProgram.getSourceFile(normalizedFileName) - fileContents.set(fileName, contents) + fileContents.set(normalizedFileName, contents) // Add to `rootFiles` when discovered by compiler for the first time. if (sourceFile === undefined) { - rootFileNames.push(fileName) + rootFileNames.push(normalizedFileName) } // Update program when file changes. From 1202b3b205f578b2b81c792917c455603d7c01ba Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 6 Apr 2020 23:40:51 -0400 Subject: [PATCH 05/13] Add failing test --- src/index.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/index.spec.ts b/src/index.spec.ts index 725bb8b1d..ba57aa4cc 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -467,6 +467,13 @@ describe('ts-node', function () { }) }) }) + + it('should transpile files inside a node_modules directory when not ignored', function (done) { + exec(`${cmd} --skip-ignore tests/from-node-modules`, function (err, stdout) { + if (err) return done('Unexpected error') + done() + }) + }) }) describe('register', function () { From 6c45a1b675b3c0d3f30da1ed1ddac98bd1db18ef Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 6 Apr 2020 23:41:33 -0400 Subject: [PATCH 06/13] bump projectVersion every time getScriptFileNames changes --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index e3d293a53..dbb804a73 100644 --- a/src/index.ts +++ b/src/index.ts @@ -488,6 +488,7 @@ export function create (rawOptions: CreateOptions = {}): Register { fileVersions.set(fileName, 1) fileContents.set(fileName, contents) + projectVersion++ } return ts.ScriptSnapshot.fromString(contents) From 8622dca21eff825b80aab4fb0f0a1230b5475c85 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 6 Apr 2020 23:58:35 -0400 Subject: [PATCH 07/13] tweak issue #884 regression test so it triggers on master --- tests/issue-884/index-2.ts | 5 +++++ tests/issue-884/index-3.ts | 5 +++++ tests/issue-884/index.ts | 4 ++++ 3 files changed, 14 insertions(+) create mode 100644 tests/issue-884/index-2.ts create mode 100644 tests/issue-884/index-3.ts diff --git a/tests/issue-884/index-2.ts b/tests/issue-884/index-2.ts new file mode 100644 index 000000000..84dc7d9a6 --- /dev/null +++ b/tests/issue-884/index-2.ts @@ -0,0 +1,5 @@ +const timeout = setTimeout(() => {}, 0); + +if (timeout.unref) { + timeout.unref(); +} diff --git a/tests/issue-884/index-3.ts b/tests/issue-884/index-3.ts new file mode 100644 index 000000000..84dc7d9a6 --- /dev/null +++ b/tests/issue-884/index-3.ts @@ -0,0 +1,5 @@ +const timeout = setTimeout(() => {}, 0); + +if (timeout.unref) { + timeout.unref(); +} diff --git a/tests/issue-884/index.ts b/tests/issue-884/index.ts index 84dc7d9a6..eb4afa216 100644 --- a/tests/issue-884/index.ts +++ b/tests/issue-884/index.ts @@ -1,5 +1,9 @@ +import './index-2'; + const timeout = setTimeout(() => {}, 0); if (timeout.unref) { timeout.unref(); } + +require('./index-3'); From 3d9cb553da1f1d8fde33e9e3496c66240fcc7be5 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 7 Apr 2020 00:08:24 -0400 Subject: [PATCH 08/13] Simplify while stil reproducing the issue --- tests/issue-884/index-2.ts | 2 ++ tests/issue-884/index-3.ts | 5 ----- tests/issue-884/index.ts | 12 +++--------- 3 files changed, 5 insertions(+), 14 deletions(-) delete mode 100644 tests/issue-884/index-3.ts diff --git a/tests/issue-884/index-2.ts b/tests/issue-884/index-2.ts index 84dc7d9a6..ddacd3c42 100644 --- a/tests/issue-884/index-2.ts +++ b/tests/issue-884/index-2.ts @@ -1,3 +1,5 @@ +export {}; + const timeout = setTimeout(() => {}, 0); if (timeout.unref) { diff --git a/tests/issue-884/index-3.ts b/tests/issue-884/index-3.ts deleted file mode 100644 index 84dc7d9a6..000000000 --- a/tests/issue-884/index-3.ts +++ /dev/null @@ -1,5 +0,0 @@ -const timeout = setTimeout(() => {}, 0); - -if (timeout.unref) { - timeout.unref(); -} diff --git a/tests/issue-884/index.ts b/tests/issue-884/index.ts index eb4afa216..8c23188df 100644 --- a/tests/issue-884/index.ts +++ b/tests/issue-884/index.ts @@ -1,9 +1,3 @@ -import './index-2'; - -const timeout = setTimeout(() => {}, 0); - -if (timeout.unref) { - timeout.unref(); -} - -require('./index-3'); +// 2x index files required so that memory cache is populated with all build-in lib and @types +// declarations *before* this require() call. +require('./index-2'); From 7eb0c30c70b046be5b503833101cf0ade45ec050 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 7 Apr 2020 22:21:57 -0400 Subject: [PATCH 09/13] getScriptFileNames returns rootFileNames, not everything from memory cache --- src/index.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index e3d293a53..18d5799ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -453,13 +453,13 @@ export function create (rawOptions: CreateOptions = {}): Register { // Use full language services when the fast option is disabled. if (!transpileOnly) { const fileContents = new Map() - const rootFileNames = config.fileNames.slice() + const rootFileNames = new Set(config.fileNames) const cachedReadFile = cachedLookup(debugFn('readFile', readFile)) // Use language services by default (TODO: invert next major version). if (!options.compilerHost) { let projectVersion = 1 - const fileVersions = new Map(rootFileNames.map(fileName => [fileName, 0])) + const fileVersions = new Map(Array.from(rootFileNames).map(fileName => [fileName, 0])) const getCustomTransformers = () => { if (typeof transformers === 'function') { @@ -473,7 +473,7 @@ export function create (rawOptions: CreateOptions = {}): Register { // Create the compiler host for type checking. const serviceHost: _ts.LanguageServiceHost = { getProjectVersion: () => String(projectVersion), - getScriptFileNames: () => Array.from(fileVersions.keys()), + getScriptFileNames: () => Array.from(rootFileNames), getScriptVersion: (fileName: string) => { const version = fileVersions.get(fileName) return version ? version.toString() : '' @@ -509,9 +509,12 @@ export function create (rawOptions: CreateOptions = {}): Register { const service = ts.createLanguageService(serviceHost, registry) const updateMemoryCache = (contents: string, fileName: string) => { - // Add to `rootFiles` when discovered for the first time. - if (!fileVersions.has(fileName)) { - rootFileNames.push(fileName) + // Add to `rootFiles` if not already there + // This is necessary to force TS to emit output + if (!rootFileNames.has(fileName)) { + rootFileNames.add(fileName) + // Increment project version for every change to rootFileNames. + projectVersion++ } const previousVersion = fileVersions.get(fileName) || 0 @@ -613,14 +616,14 @@ export function create (rawOptions: CreateOptions = {}): Register { // Fallback for older TypeScript releases without incremental API. let builderProgram = ts.createIncrementalProgram ? ts.createIncrementalProgram({ - rootNames: rootFileNames.slice(), + rootNames: Array.from(rootFileNames), options: config.options, host: host, configFileParsingDiagnostics: config.errors, projectReferences: config.projectReferences }) : ts.createEmitAndSemanticDiagnosticsBuilderProgram( - rootFileNames.slice(), + Array.from(rootFileNames), config.options, host, undefined, @@ -641,13 +644,13 @@ export function create (rawOptions: CreateOptions = {}): Register { // Add to `rootFiles` when discovered by compiler for the first time. if (sourceFile === undefined) { - rootFileNames.push(fileName) + rootFileNames.add(fileName) } // Update program when file changes. if (sourceFile === undefined || sourceFile.text !== contents) { builderProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram( - rootFileNames.slice(), + Array.from(rootFileNames), config.options, host, builderProgram, From ca4a5fab21a734de94bb159b2b6fa500dc5bfe3d Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 7 Apr 2020 22:48:07 -0400 Subject: [PATCH 10/13] fix type error --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 2d3651e82..274362d69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -544,7 +544,7 @@ export function create (rawOptions: CreateOptions = {}): Register { */ const fixupResolvedModule = (resolvedModule: _ts.ResolvedModule) => { const canonical = getCanonicalFileName(resolvedModule.resolvedFileName) - if (rootFileNames.some(rootFileName => canonical === getCanonicalFileName(rootFileName))) { + if (Array.from(rootFileNames).some(rootFileName => canonical === getCanonicalFileName(rootFileName))) { resolvedModule.isExternalLibraryImport = false } } From a236df95ab9472b26e9708d00d634593ffbaef11 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 8 Apr 2020 00:01:28 -0400 Subject: [PATCH 11/13] Switch to normalizing once in our compile() function, so that the rest of ts-node's codebase only deals with / paths --- src/index.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6a8265047..7c0212d01 100644 --- a/src/index.ts +++ b/src/index.ts @@ -507,18 +507,17 @@ export function create (rawOptions: CreateOptions = {}): Register { const service = ts.createLanguageService(serviceHost, registry) const updateMemoryCache = (contents: string, fileName: string) => { - const normalizedFileName = normalizeSlashes(fileName) // Add to `rootFiles` when discovered for the first time. - if (!fileVersions.has(normalizedFileName)) { - rootFileNames.push(normalizedFileName) + if (!fileVersions.has(fileName)) { + rootFileNames.push(fileName) } - const previousVersion = fileVersions.get(normalizedFileName) || 0 - const previousContents = fileContents.get(normalizedFileName) + const previousVersion = fileVersions.get(fileName) || 0 + const previousContents = fileContents.get(fileName) // Avoid incrementing cache when nothing has changed. if (contents !== previousContents) { - fileVersions.set(normalizedFileName, previousVersion + 1) - fileContents.set(normalizedFileName, contents) + fileVersions.set(fileName, previousVersion + 1) + fileContents.set(fileName, contents) // Increment project version for every file change. projectVersion++ } @@ -634,14 +633,13 @@ export function create (rawOptions: CreateOptions = {}): Register { // Set the file contents into cache manually. const updateMemoryCache = (contents: string, fileName: string) => { - const normalizedFileName = normalizeSlashes(fileName) - const sourceFile = builderProgram.getSourceFile(normalizedFileName) + const sourceFile = builderProgram.getSourceFile(fileName) - fileContents.set(normalizedFileName, contents) + fileContents.set(fileName, contents) // Add to `rootFiles` when discovered by compiler for the first time. if (sourceFile === undefined) { - rootFileNames.push(normalizedFileName) + rootFileNames.push(fileName) } // Update program when file changes. @@ -756,8 +754,9 @@ export function create (rawOptions: CreateOptions = {}): Register { // Create a simple TypeScript compiler proxy. function compile (code: string, fileName: string, lineOffset = 0) { - const [value, sourceMap] = getOutput(code, fileName, lineOffset) - const output = updateOutput(value, fileName, sourceMap, getExtension) + const normalizedFileName = normalizeSlashes(fileName) + const [value, sourceMap] = getOutput(code, normalizedFileName, lineOffset) + const output = updateOutput(value, normalizedFileName, sourceMap, getExtension) outputCache.set(fileName, output) return output } From 1aaa2e1ea92f126cef04a8fbe85fd20a84ea2703 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 12 Apr 2020 18:18:19 -0400 Subject: [PATCH 12/13] add failing test for #1004 --- src/index.spec.ts | 8 ++++++++ tests/main-realpath/symlink/symlink.tsx | 1 + tests/main-realpath/symlink/tsconfig.json | 1 + tests/main-realpath/target/target.tsx | 4 ++++ tests/main-realpath/target/tsconfig.json | 5 +++++ 5 files changed, 19 insertions(+) create mode 120000 tests/main-realpath/symlink/symlink.tsx create mode 100644 tests/main-realpath/symlink/tsconfig.json create mode 100644 tests/main-realpath/target/target.tsx create mode 100644 tests/main-realpath/target/tsconfig.json diff --git a/src/index.spec.ts b/src/index.spec.ts index 725bb8b1d..06a07045c 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -379,6 +379,14 @@ describe('ts-node', function () { expect(err).to.equal(null) expect(stdout).to.equal('.ts\n') + return done() + }) + }) + it('should read tsconfig relative to realpath, not symlink, in scriptMode', function (done) { + exec(`node ${BIN_SCRIPT_PATH} tests/main-realpath/symlink/symlink.tsx`, function (err, stdout) { + expect(err).to.equal(null) + expect(stdout).to.equal('') + return done() }) }) diff --git a/tests/main-realpath/symlink/symlink.tsx b/tests/main-realpath/symlink/symlink.tsx new file mode 120000 index 000000000..c2c3efb1f --- /dev/null +++ b/tests/main-realpath/symlink/symlink.tsx @@ -0,0 +1 @@ +../target/target.tsx \ No newline at end of file diff --git a/tests/main-realpath/symlink/tsconfig.json b/tests/main-realpath/symlink/tsconfig.json new file mode 100644 index 000000000..9f78b68d0 --- /dev/null +++ b/tests/main-realpath/symlink/tsconfig.json @@ -0,0 +1 @@ +this tsconfig is intentionally invalid, to confirm that ts-node does *not* attempt to parse it diff --git a/tests/main-realpath/target/target.tsx b/tests/main-realpath/target/target.tsx new file mode 100644 index 000000000..1a206f56d --- /dev/null +++ b/tests/main-realpath/target/target.tsx @@ -0,0 +1,4 @@ +// Will throw a compiler error unless ./tsconfig.json is parsed, which enables JSX +function foo() { +
+} diff --git a/tests/main-realpath/target/tsconfig.json b/tests/main-realpath/target/tsconfig.json new file mode 100644 index 000000000..986627de2 --- /dev/null +++ b/tests/main-realpath/target/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "jsx": "react" + } +} From 5f28488282db12b1537682e0d54fffe54fe1ebd2 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 12 Apr 2020 18:21:05 -0400 Subject: [PATCH 13/13] Fix #1004 --- src/bin.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index c303129a3..a01594532 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -7,7 +7,7 @@ import Module = require('module') import arg = require('arg') import { diffLines } from 'diff' import { Script } from 'vm' -import { readFileSync, statSync } from 'fs' +import { readFileSync, statSync, realpathSync } from 'fs' import { homedir } from 'os' import { VERSION, TSError, parse, Register, register } from './index' @@ -154,6 +154,7 @@ export function main (argv: string[]) { } const cwd = dir || process.cwd() + /** Unresolved. May point to a symlink, not realpath. May be missing file extension */ const scriptPath = args._.length ? resolve(cwd, args._[0]) : undefined const state = new EvalState(scriptPath || join(cwd, EVAL_FILENAME)) @@ -251,7 +252,21 @@ function getCwd (dir?: string, scriptMode?: boolean, scriptPath?: string) { throw new TypeError('Script mode cannot be combined with `--dir`') } - return dirname(scriptPath) + // Use node's own resolution behavior to ensure we follow symlinks + // This may affect which tsconfig we discover + // This happens before we are registered, so tell node's resolver to consider .ts and tsx files + // TODO in extremely rare cases, if a foo.js and foo.ts both exist, we may follow the wrong one, + // because we are not obeying `--prefer-ts-exts` + const hadTsExt = hasOwnProperty(require.extensions, '.ts') // tslint:disable-line + const hadTsxExt = hasOwnProperty(require.extensions, '.tsx') // tslint:disable-line + try { + if(!hadTsExt) require.extensions['.ts'] = function() {} // tslint:disable-line + if(!hadTsxExt) require.extensions['.tsx'] = function() {} // tslint:disable-line + return dirname(require.resolve(scriptPath)) + } finally { + if(!hadTsExt) delete require.extensions['.ts'] // tslint:disable-line + if(!hadTsxExt) delete require.extensions['.tsx'] // tslint:disable-line + } } return dir @@ -481,6 +496,11 @@ function isRecoverable (error: TSError) { return error.diagnosticCodes.every(code => RECOVERY_CODES.has(code)) } +/** Safe `hasOwnProperty` */ +function hasOwnProperty (object: any, property: string): boolean { + return Object.prototype.hasOwnProperty.call(object, property) +} + if (require.main === module) { main(process.argv.slice(2)) }