diff --git a/.prettierignore b/.prettierignore index e0158cb5c..492a816ce 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,3 +11,4 @@ tests/throw error.ts tests/throw error react tsx.tsx tests/esm/throw error.ts tests/legacy-source-map-support-interop/index.ts +tests/main-realpath/symlink/symlink.tsx diff --git a/src/index.ts b/src/index.ts index 6e0104020..8901fcc24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1016,7 +1016,7 @@ export function create(rawOptions: CreateOptions = {}): Service { if (diagnosticList.length) reportTSError(diagnosticList); if (output.emitSkipped) { - throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`); + return [undefined, undefined, true]; } // Throw an error when requiring `.d.ts` files. @@ -1029,7 +1029,7 @@ export function create(rawOptions: CreateOptions = {}): Service { ); } - return [output.outputFiles[1].text, output.outputFiles[0].text]; + return [output.outputFiles[1].text, output.outputFiles[0].text, false]; }; getTypeInfo = (code: string, fileName: string, position: number) => { @@ -1159,7 +1159,8 @@ export function create(rawOptions: CreateOptions = {}): Service { }; getOutput = (code: string, fileName: string) => { - const output: [string, string] = ['', '']; + let outText = ''; + let outMap = ''; updateMemoryCache(code, fileName); @@ -1179,9 +1180,9 @@ export function create(rawOptions: CreateOptions = {}): Service { sourceFile, (path, file, writeByteOrderMark) => { if (path.endsWith('.map')) { - output[1] = file; + outMap = file; } else { - output[0] = file; + outText = file; } if (options.emit) sys.writeFile(path, file, writeByteOrderMark); @@ -1192,11 +1193,11 @@ export function create(rawOptions: CreateOptions = {}): Service { ); if (result.emitSkipped) { - throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`); + return [undefined, undefined, true]; } // Throw an error when requiring files that cannot be compiled. - if (output[0] === '') { + if (outText === '') { if (program.isSourceFileFromExternalLibrary(sourceFile)) { throw new TypeError( `Unable to compile file from external library: ${relative( @@ -1214,7 +1215,7 @@ export function create(rawOptions: CreateOptions = {}): Service { ); } - return output; + return [outText, outMap, false]; }; getTypeInfo = (code: string, fileName: string, position: number) => { @@ -1291,7 +1292,7 @@ export function create(rawOptions: CreateOptions = {}): Service { ); if (diagnosticList.length) reportTSError(diagnosticList); - return [result.outputText, result.sourceMapText as string]; + return [result.outputText, result.sourceMapText as string, false]; }; } @@ -1308,6 +1309,7 @@ export function create(rawOptions: CreateOptions = {}): Service { : createTranspileOnlyGetOutputFunction( ts.ModuleKind.ES2020 || ts.ModuleKind.ES2015 ); + const getOutputTranspileOnly = createTranspileOnlyGetOutputFunction(); // Create a simple TypeScript compiler proxy. function compile(code: string, fileName: string, lineOffset = 0) { @@ -1315,17 +1317,19 @@ export function create(rawOptions: CreateOptions = {}): Service { const classification = moduleTypeClassifier.classifyModule(normalizedFileName); // Must always call normal getOutput to throw typechecking errors - let [value, sourceMap] = getOutput(code, normalizedFileName); + let [value, sourceMap, emitSkipped] = getOutput(code, normalizedFileName); // If module classification contradicts the above, call the relevant transpiler if (classification.moduleType === 'cjs' && getOutputForceCommonJS) { [value, sourceMap] = getOutputForceCommonJS(code, normalizedFileName); } else if (classification.moduleType === 'esm' && getOutputForceESM) { [value, sourceMap] = getOutputForceESM(code, normalizedFileName); + } else if (emitSkipped) { + [value, sourceMap] = getOutputTranspileOnly(code, normalizedFileName); } const output = updateOutput( - value, + value!, normalizedFileName, - sourceMap, + sourceMap!, getExtension ); outputCache.set(normalizedFileName, { content: output }); @@ -1456,7 +1460,7 @@ function registerExtension( /** * Internal source output. */ -type SourceOutput = [string, string]; +type SourceOutput = [string, string, false] | [undefined, undefined, true]; /** * Update the output remapping the source map. diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index d4163ffdf..8eef6bc0f 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -1256,3 +1256,14 @@ test.suite('ts-node', (test) => { } }); }); + +test('Falls back to transpileOnly when ts compiler returns emitSkipped', async () => { + const { err, stdout } = await exec( + `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} --project tsconfig.json ./outside-rootDir/foo.js`, + { + cwd: join(TEST_DIR, 'emit-skipped-fallback'), + } + ); + expect(err).toBe(null); + expect(stdout).toBe('foo\n'); +}); diff --git a/tests/emit-skipped-fallback/outside-rootDir/foo.js b/tests/emit-skipped-fallback/outside-rootDir/foo.js new file mode 100644 index 000000000..ad12b2b98 --- /dev/null +++ b/tests/emit-skipped-fallback/outside-rootDir/foo.js @@ -0,0 +1,15 @@ +// This file causes TS to return emitSkipped because it's outside of rootDir and +// it's .js. I assume this happens because the emit path is the same as the +// input path, and perhaps also because the file is classified "external" + +const decorator = () => {}; + +class Foo { + // Using a decorator to prove this .js file is getting compiled + @decorator + method() { + return 'foo'; + } +} + +console.log(new Foo().method()); diff --git a/tests/emit-skipped-fallback/tsconfig.json b/tests/emit-skipped-fallback/tsconfig.json new file mode 100644 index 000000000..aae997234 --- /dev/null +++ b/tests/emit-skipped-fallback/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "allowJs": true, + "experimentalDecorators": true, + "rootDir": "rootDir" + } +}