Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix swc issues #2062

Merged
merged 8 commits into from
Sep 15, 2023
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@
"@cspotcode/ava-lib": "https://github.com/cspotcode/ava-lib#805aab17b2b89c388596b6dc2b4eece403c5fb87",
"@cspotcode/expect-stream": "https://github.com/cspotcode/node-expect-stream#4e425ff1eef240003af8716291e80fbaf3e3ae8f",
"@microsoft/api-extractor": "^7.19.4",
"@swc/core": "1.3.32",
"@swc/wasm": "1.3.32",
"@swc/core": "1.3.85",
"@swc/wasm": "1.3.85",
"@types/diff": "^4.0.2",
"@types/lodash": "^4.14.151",
"@types/node": "13.13.5",
Expand Down Expand Up @@ -145,8 +145,8 @@
"workaround-broken-npm-prepack-behavior": "https://github.com/cspotcode/workaround-broken-npm-prepack-behavior#1a7adbbb8a527784daf97edad6ba42d6e96611f6"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@swc/core": ">=1.3.85",
"@swc/wasm": ">=1.3.85",
"@types/node": "*",
"typescript": ">=4.4"
},
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1158,7 +1158,7 @@ export function createFromPreloadedConfig(foundConfigResult: ReturnType<typeof f
const diagnosticList = filterDiagnostics(result.diagnostics || [], diagnosticFilters);
if (diagnosticList.length) reportTSError(diagnosticList);

return [result.outputText, result.sourceMapText as string, false];
return [result.outputText, result.sourceMapText ?? '{}', false];
};
}

Expand Down
189 changes: 181 additions & 8 deletions src/test/transpilers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@
// Should consolidate them here.

import { context } from './testlib';
import { ctxTsNode, testsDirRequire, tsSupportsImportAssertions, tsSupportsReact17JsxFactories } from './helpers';
import {
CMD_TS_NODE_WITHOUT_PROJECT_FLAG,
createExec,
ctxTsNode,
testsDirRequire,
TEST_DIR,
tsSupportsImportAssertions,
tsSupportsReact17JsxFactories,
} from './helpers';
import { createSwcOptions } from '../transpilers/swc';
import * as expect from 'expect';
import { outdent } from 'outdent';
import { join } from 'path';

const test = context(ctxTsNode);

Expand Down Expand Up @@ -78,10 +87,7 @@ test.suite('swc', (test) => {
.create({
swc: true,
skipProject: true,
compilerOptions: {
module: 'esnext',
...compilerOptions,
},
compilerOptions,
})
.compile(input, 'input.tsx');
expect(code.replace(/\/\/# sourceMappingURL.*/, '').trim()).toBe(expectedOutput);
Expand All @@ -93,12 +99,17 @@ test.suite('swc', (test) => {
const div = <div></div>;
`;

test(compileMacro, { jsx: 'react' }, input, `const div = /*#__PURE__*/ React.createElement("div", null);`);
test(
compileMacro,
{ module: 'esnext', jsx: 'react' },
input,
`const div = /*#__PURE__*/ React.createElement("div", null);`
);
test.suite('react 17 jsx factories', (test) => {
test.if(tsSupportsReact17JsxFactories);
test(
compileMacro,
{ jsx: 'react-jsx' },
{ module: 'esnext', jsx: 'react-jsx' },
input,
outdent`
import { jsx as _jsx } from "react/jsx-runtime";
Expand All @@ -107,7 +118,7 @@ test.suite('swc', (test) => {
);
test(
compileMacro,
{ jsx: 'react-jsxdev' },
{ module: 'esnext', jsx: 'react-jsxdev' },
input,
outdent`
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
Expand Down Expand Up @@ -139,4 +150,166 @@ test.suite('swc', (test) => {
`
);
});

test.suite('useDefineForClassFields', (test) => {
const input = outdent`
class Foo {
bar = 1;
}
`;
const outputNative = outdent`
let Foo = class Foo {
bar = 1;
};
`;
const outputCtorAssignment = outdent`
let Foo = class Foo {
constructor(){
this.bar = 1;
}
};
`;
const outputDefine = outdent`
function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
let Foo = class Foo {
constructor(){
_define_property(this, "bar", 1);
}
};
`;
test(
'useDefineForClassFields unset, should default to true and emit native property assignment b/c `next` target',
compileMacro,
{ module: 'esnext', target: 'ESNext' },
input,
outputNative
);
test(
'useDefineForClassFields unset, should default to true and emit native property assignment b/c new target',
compileMacro,
{ module: 'esnext', target: 'ES2022' },
input,
outputNative
);
test(
'useDefineForClassFields unset, should default to false b/c old target',
compileMacro,
{ module: 'esnext', target: 'ES2021' },
input,
outputCtorAssignment
);
test(
'useDefineForClassFields=true, should emit native property assignment b/c new target',
compileMacro,
{
module: 'esnext',
useDefineForClassFields: true,
target: 'ES2022',
},
input,
outputNative
);
test(
'useDefineForClassFields=true, should emit define b/c old target',
compileMacro,
{
module: 'esnext',
useDefineForClassFields: true,
target: 'ES2021',
},
input,
outputDefine
);
test(
'useDefineForClassFields=false, new target, should still emit legacy property assignment in ctor',
compileMacro,
{
module: 'esnext',
useDefineForClassFields: false,
target: 'ES2022',
},
input,
outputCtorAssignment
);
test(
'useDefineForClassFields=false, old target, should emit legacy property assignment in ctor',
compileMacro,
{
module: 'esnext',
useDefineForClassFields: false,
},
input,
outputCtorAssignment
);
});

test.suite('jsx and jsxImportSource', (test) => {
test(
'jsx=react-jsx',
compileMacro,
{
module: 'esnext',
jsx: 'react-jsx',
},
outdent`
<div></div>
`,
outdent`
/*#__PURE__*/ import { jsx as _jsx } from "react/jsx-runtime";
_jsx("div", {});
`
);
test(
'jsx=react-jsx w/custom jsxImportSource',
compileMacro,
{
module: 'esnext',
jsx: 'react-jsx',
jsxImportSource: 'foo',
},
outdent`
<div></div>
`,
outdent`
/*#__PURE__*/ import { jsx as _jsx } from "foo/jsx-runtime";
_jsx("div", {});
`
);
});

test.suite(
'#1996 regression: ts-node gracefully allows swc to not return a sourcemap for type-only files',
(test) => {
// https://github.com/TypeStrong/ts-node/issues/1996
// @swc/core 1.3.51 returned `undefined` instead of sourcemap if the file was empty or only exported types.
// Newer swc versions do not do this. But our typedefs technically allow it.
const exec = createExec({
cwd: join(TEST_DIR, '1996'),
});
test('import empty file w/swc', async (t) => {
const r = await exec(`${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} ./index.ts`);
expect(r.err).toBe(null);
expect(r.stdout).toMatch(/#1996 regression test./);
});
test('use custom transpiler which never returns a sourcemap', async (t) => {
const r = await exec(
`${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} --project tsconfig.custom-transpiler.json ./empty.ts`
);
expect(r.err).toBe(null);
expect(r.stdout).toMatch(/#1996 regression test with custom transpiler./);
});
}
);
});
31 changes: 21 additions & 10 deletions src/transpilers/swc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import type * as swcTypes from '@swc/core';
import type { CreateTranspilerOptions, Transpiler } from './types';
import type { NodeModuleEmitKind } from '..';
import { getUseDefineForClassFields } from '../ts-internals';

type SwcInstance = typeof swcWasm;
type SwcInstance = typeof swcTypes;
export interface SwcTranspilerOptions extends CreateTranspilerOptions {
/**
* swc compiler to use for compilation
Expand All @@ -28,7 +29,7 @@
let swcDepName: string = 'swc';
if (typeof swc === 'string') {
swcDepName = swc;
swcInstance = require(transpilerConfigLocalResolveHelper(swc, true)) as typeof swcWasm;
swcInstance = require(transpilerConfigLocalResolveHelper(swc, true)) as SwcInstance;
} else if (swc == null) {
let swcResolved;
try {
Expand All @@ -44,9 +45,9 @@
);
}
}
swcInstance = require(swcResolved) as typeof swcWasm;
swcInstance = require(swcResolved) as SwcInstance;
} else {
swcInstance = swc;
swcInstance = swc as any as SwcInstance;

Check warning on line 50 in src/transpilers/swc.ts

View check run for this annotation

Codecov / codecov/patch

src/transpilers/swc.ts#L50

Added line #L50 was not covered by tests
}

// Prepare SWC options derived from typescript compiler options
Expand Down Expand Up @@ -142,6 +143,7 @@
strict,
alwaysStrict,
noImplicitUseStrict,
jsxImportSource,
} = compilerOptions;

let swcTarget = targetMapping.get(target!) ?? 'es3';
Expand Down Expand Up @@ -194,6 +196,8 @@
jsx === JsxEmit.ReactJSX || jsx === JsxEmit.ReactJSXDev ? 'automatic' : undefined;
const jsxDevelopment: swcTypes.ReactConfig['development'] = jsx === JsxEmit.ReactJSXDev ? true : undefined;

const useDefineForClassFields = getUseDefineForClassFields(compilerOptions);

const nonTsxOptions = createVariant(false);
const tsxOptions = createVariant(true);
return { nonTsxOptions, tsxOptions };
Expand All @@ -204,11 +208,15 @@
// isModule: true,
module: moduleType
? {
noInterop: !esModuleInterop,
type: moduleType,
strictMode,
// For NodeNext and Node12, emit as CJS but do not transform dynamic imports
ignoreDynamic: nodeModuleEmitKind === 'nodecjs',
...(moduleType === 'amd' || moduleType === 'commonjs' || moduleType === 'umd'
? {
noInterop: !esModuleInterop,
strictMode,
// For NodeNext and Node12, emit as CJS but do not transform dynamic imports
ignoreDynamic: nodeModuleEmitKind === 'nodecjs',
}
: {}),
}
: undefined,
swcrc: false,
Expand All @@ -232,12 +240,15 @@
pragma: jsxFactory!,
pragmaFrag: jsxFragmentFactory!,
runtime: jsxRuntime,
importSource: jsxImportSource,
},
useDefineForClassFields,
},
keepClassNames,
experimental: {
keepImportAssertions: true,
},
keepImportAttributes: true,
emitAssertForImportAttributes: true,
} as swcTypes.JscConfig['experimental'],
},
};

Expand Down
25 changes: 25 additions & 0 deletions src/ts-internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,28 @@ function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: st
function isImplicitGlob(lastPathComponent: string): boolean {
return !/[.*?]/.test(lastPathComponent);
}

const ts_ScriptTarget_ES5 = 1;
const ts_ScriptTarget_ES2022 = 9;
const ts_ScriptTarget_ESNext = 99;
const ts_ModuleKind_Node16 = 100;
const ts_ModuleKind_NodeNext = 199;
// https://github.com/microsoft/TypeScript/blob/fc418a2e611c88cf9afa0115ff73490b2397d311/src/compiler/utilities.ts#L8761
export function getUseDefineForClassFields(compilerOptions: _ts.CompilerOptions): boolean {
return compilerOptions.useDefineForClassFields === undefined
? getEmitScriptTarget(compilerOptions) >= ts_ScriptTarget_ES2022
: compilerOptions.useDefineForClassFields;
}

// https://github.com/microsoft/TypeScript/blob/fc418a2e611c88cf9afa0115ff73490b2397d311/src/compiler/utilities.ts#L8556
export function getEmitScriptTarget(compilerOptions: {
module?: _ts.CompilerOptions['module'];
target?: _ts.CompilerOptions['target'];
}): _ts.ScriptTarget {
return (
compilerOptions.target ??
((compilerOptions.module === ts_ModuleKind_Node16 && ts_ScriptTarget_ES2022) ||
(compilerOptions.module === ts_ModuleKind_NodeNext && ts_ScriptTarget_ESNext) ||
ts_ScriptTarget_ES5)
);
}
1 change: 1 addition & 0 deletions tests/1996/empty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
3 changes: 3 additions & 0 deletions tests/1996/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as empty from './empty';
empty;
console.log('#1996 regression test.');
13 changes: 13 additions & 0 deletions tests/1996/transpiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// A custom transpiler that returns `undefined` instead of a sourcemap, which is
// allowed according to our typedefs.

exports.create = function () {
return {
transpile(input, options) {
return {
outputText: 'console.log("#1996 regression test with custom transpiler.")',
sourceMapText: undefined,
};
},
};
};