2026-09-02 16:23:09 +08:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
2025-12-07 07:20:19 +08:00
|
|
|
import fs from "node:fs/promises";
|
2025-12-07 16:02:13 +08:00
|
|
|
import url from "node:url";
|
|
|
|
|
import { codeFrameColumns } from "@babel/code-frame";
|
|
|
|
|
import * as ts from "typescript";
|
2026-06-11 01:04:51 +08:00
|
|
|
import * as prettier from "../node_modules/prettier/index.mjs";
|
2025-12-07 07:20:19 +08:00
|
|
|
|
2026-09-02 16:23:09 +08:00
|
|
|
const buildScriptVersion = "1";
|
2025-12-07 07:20:19 +08:00
|
|
|
const PROJECT_ROOT = new URL("../", import.meta.url);
|
|
|
|
|
const FLOW_TYPES = new URL(
|
2026-06-22 17:44:23 -07:00
|
|
|
"./node_modules/flow-estree/dist/types.js.flow",
|
2025-12-07 07:20:19 +08:00
|
|
|
PROJECT_ROOT,
|
|
|
|
|
);
|
|
|
|
|
const FLOW_TYPES_DTS = new URL(
|
|
|
|
|
"./src/language-js/types/flow-estree.d.ts",
|
|
|
|
|
PROJECT_ROOT,
|
|
|
|
|
);
|
2026-09-02 16:23:09 +08:00
|
|
|
const IS_CI = Boolean(process.env.CI);
|
2025-12-07 07:20:19 +08:00
|
|
|
|
2026-09-02 16:23:09 +08:00
|
|
|
async function generateFlowEstreeTypeDefinition() {
|
|
|
|
|
const {
|
|
|
|
|
default: { version },
|
|
|
|
|
} = await import("flow-estree/package.json", {
|
|
|
|
|
with: { type: "json" },
|
|
|
|
|
});
|
|
|
|
|
const versionComment = `// flow-estree:v${version}, build-script:v${buildScriptVersion}`;
|
2025-12-07 07:20:19 +08:00
|
|
|
|
2026-09-02 16:23:09 +08:00
|
|
|
if (!IS_CI) {
|
|
|
|
|
try {
|
|
|
|
|
const dtsText = await fs.readFile(FLOW_TYPES_DTS, "utf8");
|
|
|
|
|
if (dtsText.includes(versionComment + "\n")) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// No op
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let text = await fs.readFile(FLOW_TYPES, "utf8");
|
|
|
|
|
|
|
|
|
|
text = toDts(text);
|
|
|
|
|
|
|
|
|
|
const getRelativePath = (url) =>
|
|
|
|
|
new URL(url).href.slice(PROJECT_ROOT.href.length);
|
|
|
|
|
text = `
|
2025-12-07 07:20:19 +08:00
|
|
|
// ! Do NOT edit !
|
|
|
|
|
// Generated from '${getRelativePath(FLOW_TYPES)}'
|
|
|
|
|
// Run \`node ${getRelativePath(import.meta.url)}\` to update
|
2026-09-02 16:23:09 +08:00
|
|
|
${versionComment}
|
2025-12-07 07:20:19 +08:00
|
|
|
// spell-checker: disable
|
|
|
|
|
|
|
|
|
|
${text}
|
|
|
|
|
`;
|
|
|
|
|
|
2026-09-02 16:23:09 +08:00
|
|
|
text = await prettier.format(text, {
|
|
|
|
|
// Bug? should accept URL
|
|
|
|
|
filepath: "flow-estree.d.ts",
|
|
|
|
|
parser: "typescript",
|
|
|
|
|
});
|
2025-12-07 07:20:19 +08:00
|
|
|
|
2026-09-02 16:23:09 +08:00
|
|
|
await fs.writeFile(FLOW_TYPES_DTS, text);
|
2025-12-07 16:02:13 +08:00
|
|
|
|
2026-09-02 16:23:09 +08:00
|
|
|
const program = ts.createProgram([url.fileURLToPath(FLOW_TYPES_DTS)], {
|
|
|
|
|
strict: true,
|
|
|
|
|
});
|
|
|
|
|
const diagnostics = ts.getPreEmitDiagnostics(program);
|
2025-12-07 16:02:13 +08:00
|
|
|
|
2026-09-02 16:23:09 +08:00
|
|
|
if (diagnostics.length > 0) {
|
|
|
|
|
await fs.rm(FLOW_TYPES_DTS);
|
|
|
|
|
const [diagnostic] = diagnostics;
|
|
|
|
|
const { line, character: column } = ts.getLineAndCharacterOfPosition(
|
|
|
|
|
diagnostic.file,
|
|
|
|
|
diagnostic.start,
|
|
|
|
|
);
|
|
|
|
|
throw new Error(
|
|
|
|
|
diagnostic.messageText +
|
|
|
|
|
":\n" +
|
|
|
|
|
codeFrameColumns(
|
|
|
|
|
text,
|
|
|
|
|
{ start: { line: line + 1, column } },
|
|
|
|
|
{
|
|
|
|
|
message: `TS${diagnostic.code}: ${diagnostic.messageText}`,
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-12-07 16:02:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toDts(text) {
|
|
|
|
|
// Useless directive
|
2026-06-22 17:44:23 -07:00
|
|
|
text = text.replaceAll("'use strict';", "");
|
2025-12-07 16:02:13 +08:00
|
|
|
|
|
|
|
|
// `{foo: interface {}}` -> `{foo: {}}`
|
|
|
|
|
// `{foo: interface extends T {}}` -> `{foo: T & {}}`
|
|
|
|
|
text = text.replaceAll(
|
2026-09-02 16:23:09 +08:00
|
|
|
/(?<=: )interface(?: extends (?<type>\w+))?(?= \{)/g,
|
|
|
|
|
(...args) => {
|
|
|
|
|
const { type } = args.at(-1);
|
|
|
|
|
return type ? `${type} & ` : "";
|
|
|
|
|
},
|
2025-12-07 16:02:13 +08:00
|
|
|
);
|
|
|
|
|
|
2026-06-22 17:44:23 -07:00
|
|
|
// `ReadonlyArray<?T>` -> `ReadonlyArray<T | null>`
|
2025-12-07 16:02:13 +08:00
|
|
|
text = text.replaceAll(
|
2026-06-22 17:44:23 -07:00
|
|
|
/ReadonlyArray<\?(?<type>\w+)>/g,
|
|
|
|
|
"ReadonlyArray<$<type> | null>",
|
2025-12-07 16:02:13 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// `{[string]: T}` -> `{[key: string]: T}`
|
|
|
|
|
text = text.replaceAll(
|
2026-09-02 16:23:09 +08:00
|
|
|
/(?<=\n[ {2}]+\[)(?<type>string)(?=\]: )/g,
|
|
|
|
|
"key: $<type>",
|
2025-12-07 16:02:13 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return text;
|
|
|
|
|
}
|
2026-09-02 16:23:09 +08:00
|
|
|
|
|
|
|
|
await generateFlowEstreeTypeDefinition();
|