Transform @typescript-eslint/* to ESM (#16402)

This commit is contained in:
fisker Cheung authored and GitHub committed 2024-06-14 14:26:30 +08:00
1 parent 997f3f1ffd
commit df8604b518
7 files changed
+322 -192

No files matched your search

-14
View File
@@ -54,20 +54,6 @@ function getEsbuildOptions({ file, files, shouldCollectLicenses, cliOptions }) {
module: "*",
process: transform,
},
// Transform `require("typescript")` to `import`
{
module: "*",
process(text, file) {
text = text.replaceAll(
/(?:const|let|var) (\w+) = (?:__importStar\(require\("typescript"\)\)|require\("typescript"\));/g,
'import * as $1 from "typescript";',
);
if (/require\(["']typescript["']\)/.test(text)) {
throw new Error(`Unexpected \`require("typescript")\` in ${file}.`);
}
return text;
},
},
// #12493, not sure what the problem is, but replace the cjs version with esm version seems fix it
{
module: require.resolve("tslib"),
+108 -110
View File
@@ -3,12 +3,14 @@ import path from "node:path";
import url from "node:url";
import createEsmUtils from "esm-utils";
import { outdent } from "outdent";
import { copyFile, DIST_DIR, PROJECT_ROOT } from "../utils/index.js";
import buildJavascriptModule from "./build-javascript-module.js";
import buildLicense from "./build-license.js";
import buildPackageJson from "./build-package-json.js";
import buildTypes from "./build-types.js";
import esmifyTypescriptEslint from "./esmify-typescript-eslint.js";
import modifyTypescriptModule from "./modify-typescript-module.js";
import { getPackageFile } from "./utils.js";
@@ -124,68 +126,6 @@ const pluginFiles = [
"@typescript-eslint/typescript-estree/dist/parser.js",
),
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/parser.js",
),
process(text) {
text = text
.replace('require("./create-program/createDefaultProgram")', "{}")
.replace('require("./create-program/createIsolatedProgram")', "{}")
.replace('require("./create-program/createProjectProgram")', "{}")
.replace('require("./create-program/useProvidedPrograms")', "{}")
.replace(
'require("./semantic-or-syntactic-errors")',
"{getFirstSemanticOrSyntacticError(){}}",
);
return text;
},
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/parseSettings/createParseSettings.js",
),
process(text) {
return text
.replace('require("./resolveProjectList")', "{}")
.replace(
'require("../create-program/shared")',
"{ensureAbsolutePath: path => path}",
)
.replace(
"process.cwd()",
JSON.stringify("/prettier-security-dirname-placeholder"),
)
.replace(
"parseSettings.projects = ",
"parseSettings.projects = [] || ",
);
},
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/parseSettings/inferSingleRun.js",
),
text: "exports.inferSingleRun = () => false;",
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/parseSettings/ExpiringCache.js",
),
text: "exports.ExpiringCache = class {};",
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/parseSettings/getProjectConfigFiles.js",
),
text: "exports.resolveProjectList = () => [];",
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/parseSettings/warnAboutTSVersion.js",
),
text: "exports.warnAboutTSVersion = () => {};",
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/create-program/getScriptKind.js",
@@ -198,54 +138,115 @@ const pluginFiles = [
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/version-check.js",
"@typescript-eslint/typescript-estree/dist/parseSettings/createParseSettings.js",
),
text: "exports.typescriptVersionIsAtLeast = new Proxy({}, {get: () => true})",
process(text) {
return text
.replace(
"process.cwd()",
JSON.stringify("/prettier-security-dirname-placeholder"),
)
.replace(
"parseSettings.projects = ",
"parseSettings.projects = true ? new Map() : ",
);
},
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/jsx/xhtml-entities.js",
),
text: "exports.xhtmlEntities = {};",
module: "*",
process: esmifyTypescriptEslint,
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/create-program/createProjectService.js",
),
text: "",
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/create-program/getWatchProgramsForProjects.js",
),
text: "",
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/create-program/describeFilePath.js",
),
text: "",
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/create-program/createProjectProgram.js",
),
text: "",
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/useProgramFromProjectService.js",
),
text: "",
module: "*",
process(text, file) {
if (/require\(["'](?:typescript|ts-api-utils)["']\)/.test(text)) {
throw new Error(`Unexpected \`require("typescript")\` in ${file}.`);
}
return text;
},
},
...[
{
file: "@typescript-eslint/typescript-estree/dist/parseSettings/inferSingleRun.js",
text: "export const inferSingleRun = () => false;",
},
{
file: "@typescript-eslint/typescript-estree/dist/parseSettings/ExpiringCache.js",
text: outdent`
export const DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS = undefined;
export const ExpiringCache = class {};
`,
},
{
file: "@typescript-eslint/typescript-estree/dist/parseSettings/getProjectConfigFiles.js",
text: "export const resolveProjectList = () => [];",
},
{
file: "@typescript-eslint/typescript-estree/dist/parseSettings/resolveProjectList.js",
text: "export const resolveProjectList = () => [];",
},
{
file: "@typescript-eslint/typescript-estree/dist/parseSettings/warnAboutTSVersion.js",
text: "export const warnAboutTSVersion = () => {};",
},
{
file: "@typescript-eslint/typescript-estree/dist/version-check.js",
text: "export const typescriptVersionIsAtLeast = new Proxy({}, {get: () => true})",
},
{
file: "@typescript-eslint/typescript-estree/dist/jsx/xhtml-entities.js",
text: "export const xhtmlEntities = {};",
},
{
file: "@typescript-eslint/typescript-estree/dist/simple-traverse.js",
text: "export const simpleTraverse = () => {};",
},
{
file: "@typescript-eslint/typescript-estree/dist/create-program/shared.js",
text: "export const ensureAbsolutePath = path => path;",
},
{
file: "@typescript-eslint/typescript-estree/dist/create-program/createIsolatedProgram.js",
text: "export const createIsolatedProgram = () => {};",
},
{
file: "@typescript-eslint/typescript-estree/dist/create-program/useProvidedPrograms.js",
text: outdent`
export const useProvidedPrograms = () => {};
export const createProgramFromConfigFile = () => {};
`,
},
{
file: "@typescript-eslint/typescript-estree/dist/create-program/createProjectService.js",
text: "export const createProjectService = () => {};",
},
{
file: "@typescript-eslint/typescript-estree/dist/create-program/getWatchProgramsForProjects.js",
text: "export const getWatchProgramsForProjects = () => {};",
},
"@typescript-eslint/typescript-estree/dist/create-program/describeFilePath.js",
{
file: "@typescript-eslint/typescript-estree/dist/create-program/createProjectProgram.js",
text: "export const createProjectProgram = () => {};",
},
{
file: "@typescript-eslint/typescript-estree/dist/useProgramFromProjectService.js",
text: "export const useProgramFromProjectService = () => {};",
},
{
file: "@typescript-eslint/typescript-estree/dist/semantic-or-syntactic-errors.js",
text: "export const getFirstSemanticOrSyntacticError = () => {};",
},
].map((options) => {
options = typeof options === "string" ? { file: options } : options;
return {
module: getPackageFile(options.file),
text: options.text || "export {};",
};
}),
// Only needed if `range`/`loc` in parse options is `false`
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/ast-converter.js",
),
process: (text) => text.replace('require("./simple-traverse")', "{}"),
},
{
module: getPackageFile("debug/src/browser.js"),
path: path.join(dirname, "./shims/debug.js"),
@@ -260,15 +261,12 @@ const pluginFiles = [
},
{
module: getPackageFile(
"@typescript-eslint/typescript-estree/dist/convert-comments.js",
"@typescript-eslint/types/dist/generated/ast-spec.js",
),
process(text) {
text = text.replace(
'const tsutils = __importStar(require("ts-api-utils"));',
'import * as tsutils from "ts-api-utils";',
);
return text;
},
text: outdent`
const TYPE_STORE = new Proxy({}, {get: (_, type) => type});
export { TYPE_STORE as AST_TOKEN_TYPES, TYPE_STORE as AST_NODE_TYPES};
`,
},
// Use named import from `typescript`
{
+13 -28
View File
@@ -9,7 +9,7 @@ const DEFAULT_ON_LOAD_CONCEPTS = {
function processReplacements(replacements) {
const onResolveReplacements = new Map();
const onLoadReplacements = new Map();
const onLoadProcessors = new Map();
const onLoadProcessors = [];
const checkPathReplaced = (module) => {
if (!onResolveReplacements.has(module)) {
@@ -75,34 +75,24 @@ function processReplacements(replacements) {
checkPathReplaced(module);
checkTextReplaced(module);
if (!onLoadProcessors.has(module)) {
onLoadProcessors.set(module, []);
}
const processFunctions = onLoadProcessors.get(module);
let { process } = replacement;
if (Object.hasOwn(replacement, "process")) {
const { process } = replacement;
if (typeof process !== "function") {
throw new TypeError("'process' option should be a function.");
}
processFunctions.push(process);
continue;
}
if (
} else if (
Object.hasOwn(replacement, "find") &&
Object.hasOwn(replacement, "replacement")
) {
processFunctions.push((text) =>
text.replaceAll(replacement.find, replacement.replacement),
);
continue;
process = (text) =>
text.replaceAll(replacement.find, replacement.replacement);
} else {
console.log(replacement);
throw new Error("Unexpected replacement option.");
}
console.log(replacement);
throw new Error("Unexpected replacement option.");
onLoadProcessors.push({ module, process });
}
return { onResolveReplacements, onLoadReplacements, onLoadProcessors };
@@ -148,23 +138,18 @@ function setupOnResolveListener(build, { concepts, replacements }) {
}
function setupOnLoadListener(build, { concepts, replacements, processors }) {
if (replacements.size === 0 && processors.size === 0) {
if (replacements.size === 0 && processors.length === 0) {
return;
}
const processFunctionsForAllModules = processors.has("*")
? processors.get("*")
: [];
build.onLoad(concepts, async ({ path: file }) => {
if (replacements.has(file)) {
return replacements.get(file);
}
const processFunctions = [
...processFunctionsForAllModules,
...(processors.has(file) ? processors.get(file) : []),
];
const processFunctions = processors
.filter(({ module }) => module === "*" || module === file)
.map((processor) => processor.process);
if (processFunctions.length === 0) {
return;
+146
View File
@@ -0,0 +1,146 @@
/* Transform `@typescript-eslint/*` module to ESM */
import * as path from "node:path";
import { outdent } from "outdent";
import { PROJECT_ROOT, writeFile } from "../utils/index.js";
function esmifyTypescriptEslint(text) {
/*
```js
const foo = __importStar(require("foo"));
const foo = require("foo");
```
->
```js
import * as foo from "foo";
````
*/
text = text.replaceAll(
// TODO: Use duplicate capture group name when eslint supports
/(?<=\n)(?:const|let|var) (?<variable>\w+) = (?:__importStar\(require\(["'](?<moduleName1>.*?)["']\)\)|require\(["'](?<moduleName2>.*?)["']\));/g,
(...args) => {
const groups = args.at(-1);
return `import * as ${groups.variable} from "${groups.moduleName1 || groups.moduleName2}";`;
},
);
/*
```js
const foo = __importDefault(require("foo"));
```
->
```js
import foo_default_export from "foo";
const foo = {default: foo_default_export};
````
*/
text = text.replaceAll(
/(?<=\n)(?:const|let|var) (?<variable>\w+) = __importDefault\(require\(["'](?<moduleName>.*?)["']\)\);/g,
outdent`
import $<variable>_default_export from "$<moduleName>";
const $<variable> = {default: $<variable>_default_export};
`,
);
text = text.replaceAll('"use strict";', "");
text = text.replaceAll(
'Object.defineProperty(exports, "__esModule", { value: true });',
"",
);
text = text.replaceAll(/(?<=\n)(?:exports\.\w+ = )+void 0;/g, "");
text = text.replaceAll(
/(?<=\n)exports\.(?<specifier>\w+) = (?<variable>\w+);/g,
(...args) => {
const { variable, specifier } = args.at(-1);
if (specifier === variable) {
return `export {${specifier}};`;
}
return `export const ${specifier} = ${variable};`;
},
);
text = text.replaceAll(
/(?<=\n)exports\.(?<specifier>\w+)(?= = `)/g,
"export const $<specifier>",
);
/*
```js
__exportStar(require("foo"), exports);
```
->
```js
export * from "foo";
````
*/
text = text.replaceAll(
/(?<=\n)__exportStar\(require\(["'](?<moduleName>.*?)["']\), exports\);/g,
'export * from "$<moduleName>";',
);
/*
```js
Object.defineProperty(exports, "foo", { enumerable: true, get: function () { return foo; } });
```
->
```js
export * from "foo";
````
*/
text = text.replaceAll(
/(?<=\n)Object\.defineProperty\(exports, "(?<specifier>\w+)", { enumerable: true, get: function \(\) { return (?<value>.*?); } }\);/g,
"export const $<specifier> = $<value>;",
);
/*
```js
exports.foo = __importStar(require("foo"));
```
->
```js
import * as foo_namespace_export from "foo";
export {foo_namespace_export as foo};
````
*/
text = text.replaceAll(
/(?<=\n)exports\.(?<specifier>\w+) = __importStar\(require\(["'](?<moduleName>.*?)["']\)\);/g,
outdent`
import * as $<specifier>_namespace_export from "$<moduleName>";
export {$<specifier>_namespace_export as $<specifier>};
`,
);
// Make sure ESBuild treat it as Module
text += "\nexport {};";
return text;
}
const NODE_MODULES_DIRECTORY = path.join(
PROJECT_ROOT,
"node_modules/@typescript-eslint/",
);
// Save modified code to `{PROJECT_ROOT}/.tmp/@typescript-eslint/` for debug
const saveToDisk = (process) => (text, file) => {
if (!file.startsWith(NODE_MODULES_DIRECTORY)) {
return text;
}
const code = process(text);
writeFile(
path.join(
PROJECT_ROOT,
".tmp/@typescript-eslint-esm/",
path.relative(NODE_MODULES_DIRECTORY, file),
),
code,
);
return code;
};
export default saveToDisk(esmifyTypescriptEslint);
+48 -35
View File
@@ -149,17 +149,23 @@ function getExports(entry) {
lines = lines.slice(2, -2);
const exports = lines.map((line) => {
const match = line.match(
/^\s*(?<specifier>.*?): \(\) => (?<variable>.*?),?$/,
);
const exports = lines
.map((line) => {
const match = line.match(
/^\s*(?<specifier>.*?): \(\) => (?<variable>.*?),?$/,
);
if (!match) {
throw new Error("Unexpected source");
}
if (!match) {
throw new Error("Unexpected source");
}
return match.groups;
});
if (UNUSED_SPECIFIERS.has(match.groups.specifier)) {
return;
}
return match.groups;
})
.filter(Boolean);
return exports;
}
@@ -170,7 +176,7 @@ function modifyTypescriptModule(text) {
const source = new TypeScriptModuleSource(text);
const entry = source.modules.find((module) => module.isEntry);
let exports = getExports(entry);
const exports = getExports(entry);
source.removeModule(entry);
// Deprecated
@@ -317,38 +323,45 @@ function modifyTypescriptModule(text) {
});
/* spell-checker: enable */
let code = source.toString();
exports = exports.filter(
({ specifier }) => !UNUSED_SPECIFIERS.has(specifier),
);
source.append(createExports(exports));
code = addExports(code, exports);
code +=
"\n" +
// Used in `ts-api-utils`
source.append(
outdent`
export const isUnparsedPrepend = () => false;
export const isUnparsedTextLike = () => false;
`;
`,
);
return { code, exports };
// Used in `@typescript-eslint/typescript-estree`, but we manually removed them
// source.append(
// outdent`
// export const createProgram = () => {};
// export const flattenDiagnosticMessageText = () => {};
// export const FileWatcherEventKind = () => {};
// export const createWatchCompilerHost = () => {};
// export const createAbstractBuilder = () => {};
// export const createWatchProgram = () => {};
// export const getParsedCommandLineOfConfigFile = () => {};
// export const createCompilerHost = () => {};
// export const formatDiagnostics = () => {};
// `,
// );
return { code: source.toString(), exports };
}
function addExports(code, exports) {
return (
code +
"\n\n" +
outdent`
export {
${exports
.map(({ specifier, variable }) =>
variable === specifier ? specifier : `${variable} as ${specifier}`,
)
.map((line) => ` ${line},`)
.join("\n")}
};
`
);
function createExports(exports) {
return outdent`
export {
${exports
.map(({ specifier, variable }) =>
variable === specifier ? specifier : `${variable} as ${specifier}`,
)
.map((line) => ` ${line},`)
.join("\n")}
};
`;
}
// Save modified code to `{PROJECT_ROOT}/.tmp/modified-typescript.js` for debug