Skip to content

Commit

Permalink
When TS returns emitSkipped, fallback to transpileOnly (#1629)
Browse files Browse the repository at this point in the history
* When TS returns emitSkipped, fallback to transpileOnly

* fix failing windows test
  • Loading branch information
cspotcode committed Feb 7, 2022
1 parent 01d7ecd commit db6f850
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 13 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Expand Up @@ -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
30 changes: 17 additions & 13 deletions src/index.ts
Expand Up @@ -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.
Expand All @@ -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) => {
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand All @@ -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(
Expand All @@ -1214,7 +1215,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
);
}

return output;
return [outText, outMap, false];
};

getTypeInfo = (code: string, fileName: string, position: number) => {
Expand Down Expand Up @@ -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];
};
}

Expand All @@ -1308,24 +1309,27 @@ 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) {
const normalizedFileName = normalizeSlashes(fileName);
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 });
Expand Down Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions src/test/index.spec.ts
Expand Up @@ -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');
});
15 changes: 15 additions & 0 deletions 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());
7 changes: 7 additions & 0 deletions tests/emit-skipped-fallback/tsconfig.json
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"allowJs": true,
"experimentalDecorators": true,
"rootDir": "rootDir"
}
}

0 comments on commit db6f850

Please sign in to comment.