From 1aaa2e1ea92f126cef04a8fbe85fd20a84ea2703 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 12 Apr 2020 18:18:19 -0400 Subject: [PATCH 1/3] 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 2/3] 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)) } From 4e5b6b8b5515f24f6d5c9e0ae94d3edfef8abccd Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 20 Apr 2020 14:32:13 -0400 Subject: [PATCH 3/3] Tweak scriptMode entrypoint resolver to include all extensions --- src/bin.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index a01594532..9d923967d 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -252,20 +252,27 @@ function getCwd (dir?: string, scriptMode?: boolean, scriptPath?: string) { throw new TypeError('Script mode cannot be combined with `--dir`') } - // 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 + // Use node's own resolution behavior to ensure we follow symlinks. + // scriptPath may omit file extension or point to a directory with or without package.json. + // This happens before we are registered, so we tell node's resolver to consider ts, tsx, and jsx files. + // In extremely rare cases, is is technically possible to resolve the wrong directory, + // because we do not yet know preferTsExts, jsx, nor allowJs. + // See also, justification why this will not happen in real-world situations: + // https://github.com/TypeStrong/ts-node/pull/1009#issuecomment-613017081 + const exts = ['.js', '.jsx', '.ts', '.tsx'] + const extsTemporarilyInstalled: string[] = [] + for (const ext of exts) { + if (!hasOwnProperty(require.extensions, ext)) { // tslint:disable-line + extsTemporarilyInstalled.push(ext) + require.extensions[ext] = function() {} // 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 + for (const ext of extsTemporarilyInstalled) { + delete require.extensions[ext] // tslint:disable-line + } } }