mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-10-28 16:36:04 +08:00
Make all the codegen we do incremental (#64331)
This commit is contained in:
1 parent
36482c8dbb
commit
1ac2d5aed0
35 files changed
+1525
-245
No files matched your search
@@ -294,12 +294,6 @@ jobs:
|
||||
- uses: ./.github/actions/setup-go
|
||||
- run: npm ci
|
||||
- run: npx hereby generate
|
||||
- run: npx hereby generate:enums
|
||||
- run: npx hereby generate:ast
|
||||
- run: npx hereby generate:vendor
|
||||
- run: node ./tsc/internal/lsp/lsproto/_generate/fetchModel.mts
|
||||
- run: node ./tsc/internal/lsp/lsproto/_generate/generate.mts
|
||||
- run: npx hereby generate:extension
|
||||
- run: npx hereby check:scripts
|
||||
- run: git add .
|
||||
- run: git diff --staged --exit-code --stat
|
||||
|
||||
+9
-1
@@ -103,12 +103,20 @@ npx hereby build # Build the native compiler into built/local/tsc
|
||||
npx hereby test # Run compiler and language-service Go tests
|
||||
npx hereby test:all # Also run benchmarks, tools, and API tests
|
||||
npx hereby lint # Run custom golangci-lint for both Go modules
|
||||
npx hereby generate # Regenerate compiler sources and bundled assets
|
||||
npx hereby generate:go # Run the generators also exposed through go generate
|
||||
npx hereby generate # Regenerate all sources, assets, localization, and vendored files
|
||||
npx hereby format # Format Go, TypeScript, JSON, and YAML
|
||||
npx hereby check:format # Check formatting without changing files
|
||||
npx hereby tidy # Tidy both modules and synchronize go.work
|
||||
```
|
||||
|
||||
Generation is owned by `Herebyfile.mjs`; Go directives forward to the same
|
||||
`generate:*` tasks for compatibility. Use a subtask such as
|
||||
`npx hereby generate:diagnostics` to run one generator group, and pass `--force`
|
||||
to bypass incremental caches. `generate` includes every generator and fetches
|
||||
the pinned LSP model when its local cache is missing or stale. Use
|
||||
`generate:go` for the narrower scope of `go generate ./...` in the `tsc` module.
|
||||
|
||||
Package-specific commands:
|
||||
|
||||
```bash
|
||||
|
||||
+345
-76
@@ -14,10 +14,8 @@ import {
|
||||
styleText,
|
||||
} from "node:util";
|
||||
import * as tar from "tar";
|
||||
import {
|
||||
x,
|
||||
xSync,
|
||||
} from "tinyexec";
|
||||
import { xSync } from "tinyexec";
|
||||
import { run } from "./tools/scripts/gen/utils.mts";
|
||||
|
||||
if (process.platform === "win32") {
|
||||
process.chdir(fs.realpathSync.native(process.cwd()));
|
||||
@@ -30,39 +28,7 @@ const isCI = !!process.env.CI || !!process.env.TF_BUILD;
|
||||
const stableThreeComponentVersionPatternSource = String.raw`^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`;
|
||||
const stableThreeComponentVersionPattern = new RegExp(stableThreeComponentVersionPatternSource);
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* captureOutput?: boolean;
|
||||
* cwd?: string;
|
||||
* env?: NodeJS.ProcessEnv;
|
||||
* signal?: AbortSignal;
|
||||
* }} RunOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} arg
|
||||
*/
|
||||
function formatCommandArg(arg) {
|
||||
return arg && /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} command
|
||||
* @param {readonly string[]} [args]
|
||||
* @param {RunOptions} [options]
|
||||
*/
|
||||
function run(command, args = [], options = {}) {
|
||||
console.log("$ " + [command, ...args].map(formatCommandArg).join(" "));
|
||||
return x(command, args, {
|
||||
throwOnError: true,
|
||||
...(options.signal ? { signal: options.signal } : {}),
|
||||
nodeOptions: {
|
||||
cwd: options.cwd,
|
||||
env: options.env ? { ...process.env, ...options.env } : undefined,
|
||||
stdio: options.captureOutput ? "pipe" : "inherit",
|
||||
},
|
||||
});
|
||||
}
|
||||
/** @typedef {import("./tools/scripts/gen/utils.mts").RunOptions} RunOptions */
|
||||
|
||||
/**
|
||||
* @param {string} command
|
||||
@@ -105,6 +71,7 @@ const { values: rawOptions } = parseArgs({
|
||||
options: {
|
||||
tests: { type: "string", short: "t" },
|
||||
fix: { type: "boolean" },
|
||||
force: { type: "boolean", default: parseEnvBoolean("FORCE") },
|
||||
api: { type: "boolean" },
|
||||
all: { type: "boolean" },
|
||||
debug: { type: "boolean" },
|
||||
@@ -368,14 +335,203 @@ export const cleanBuilt = task({
|
||||
run: () => rimraf("built"),
|
||||
});
|
||||
|
||||
async function runGenerate() {
|
||||
return await run("go", ["generate", "-v", "./..."], { cwd: "./tsc" });
|
||||
/** @type {(() => Promise<void>)[]} */
|
||||
const goGenerateActions = [];
|
||||
|
||||
async function runGenerateGo() {
|
||||
for (const generate of goGenerateActions) {
|
||||
await generate();
|
||||
}
|
||||
}
|
||||
|
||||
export const generate = task({
|
||||
name: "generate",
|
||||
description: "Runs go generate on the project.",
|
||||
run: runGenerate,
|
||||
export const generateGo = task({
|
||||
name: "generate:go",
|
||||
description: "Runs the project's Go code generators directly. Pass --force to regenerate unchanged files.",
|
||||
run: runGenerateGo,
|
||||
});
|
||||
|
||||
const getGoGenerateEnvironment = memoize(async () => {
|
||||
const { stdout } = await runOutput("go", ["env", "-json", "GOOS", "GOARCH", "GOROOT"], { cwd: "./tsc" });
|
||||
return /** @type {{ GOOS: string; GOARCH: string; GOROOT: string }} */ (JSON.parse(stdout));
|
||||
});
|
||||
|
||||
/** @typedef {import("./tools/scripts/gen/cache.mts").CacheOptions & { file: string }} GoGenerator */
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {GoGenerator} generator
|
||||
*/
|
||||
async function runGoGenerator(name, { file, ...spec }) {
|
||||
const { default: cache } = await import("./tools/scripts/gen/cache.mts");
|
||||
const sourcePath = path.resolve(__dirname, file);
|
||||
const source = fs.readFileSync(sourcePath, "utf8");
|
||||
const packageName = /^package\s+(\w+)/m.exec(source)?.[1];
|
||||
const line = source.split(/\r?\n/).findIndex(line => line.trimEnd() === `//go:generate npx hereby ${name}`) + 1;
|
||||
assert(packageName && line, `Missing package or generation directive in ${file}`);
|
||||
const goEnv = await getGoGenerateEnvironment();
|
||||
const pathKey = process.platform === "win32" ? Object.keys(process.env).find(key => key.toUpperCase() === "PATH") ?? "PATH" : "PATH";
|
||||
const goBin = path.join(goEnv.GOROOT, "bin");
|
||||
const searchPath = (process.env[pathKey] ?? "").split(path.delimiter).filter(entry => path.resolve(entry) !== goBin);
|
||||
await cache({
|
||||
...spec,
|
||||
cwd: spec.cwd ?? path.dirname(sourcePath),
|
||||
inputs: [__filename, sourcePath, ...spec.inputs],
|
||||
envInputs: [
|
||||
...(spec.envInputs ?? ["GOOS", "GOARCH", "GOFLAGS", "GOTOOLCHAIN", "GOEXPERIMENT", "CGO_ENABLED", "GOWORK"]),
|
||||
"GOROOT",
|
||||
"GOFILE",
|
||||
"GOLINE",
|
||||
"GOPACKAGE",
|
||||
"DOLLAR",
|
||||
],
|
||||
env: {
|
||||
...goEnv,
|
||||
GOFILE: path.basename(sourcePath),
|
||||
GOLINE: String(line),
|
||||
GOPACKAGE: packageName,
|
||||
DOLLAR: "$",
|
||||
[pathKey]: [goBin, ...searchPath].join(path.delimiter),
|
||||
},
|
||||
force: !!options.force,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {GoGenerator[] | (() => Promise<void>)} generators
|
||||
*/
|
||||
function goGenerateTask(name, generators) {
|
||||
const run = typeof generators === "function" ? generators : async () => {
|
||||
for (const generator of generators) await runGoGenerator(name, generator);
|
||||
};
|
||||
goGenerateActions.push(run);
|
||||
return task({
|
||||
name,
|
||||
description: `Generates ${name.slice("generate:".length)} files. Pass --force to regenerate unchanged files.`,
|
||||
run,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {string} type
|
||||
* @param {string} output
|
||||
* @param {string} [trimPrefix]
|
||||
* @returns {GoGenerator}
|
||||
*/
|
||||
function stringerGenerator(file, type, output, trimPrefix) {
|
||||
return {
|
||||
file,
|
||||
inputs: [],
|
||||
outputs: [output],
|
||||
commands: [
|
||||
["go", "tool", "golang.org/x/tools/cmd/stringer", `-type=${type}`, ...(trimPrefix ? [`-trimprefix=${trimPrefix}`] : []), `-output=${output}`],
|
||||
["dprint", "fmt", output],
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {string} type
|
||||
* @param {string} output
|
||||
* @param {{ source?: string; packageName: string; inputs?: string[]; stub?: boolean }} options
|
||||
* @returns {GoGenerator}
|
||||
*/
|
||||
function moqGenerator(file, type, output, { source = ".", packageName, inputs = [], stub = false }) {
|
||||
return {
|
||||
file,
|
||||
inputs,
|
||||
outputs: [output],
|
||||
commands: [
|
||||
["go", "tool", "github.com/matryer/moq", ...(stub ? ["-stub"] : []), "-fmt", "goimports", "-pkg", packageName, "-out", output, source, type],
|
||||
["dprint", "fmt", output],
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async function runGenerateASTStringer() {
|
||||
await runGoGenerator("generate:ast-stringer", stringerGenerator("tsc/internal/ast/kind_generated.go", "Kind", "kind_stringer_generated.go"));
|
||||
}
|
||||
|
||||
export const generateASTStringer = goGenerateTask("generate:ast-stringer", runGenerateASTStringer);
|
||||
|
||||
export const generateBundled = goGenerateTask("generate:bundled", [{
|
||||
file: "tsc/internal/bundled/bundled.go",
|
||||
inputs: ["generate.go", "CopyrightNotice.txt", "libs/*"],
|
||||
outputs: ["libs_generated.go", "embed_generated.go"],
|
||||
commands: [["go", "run", "generate.go"]],
|
||||
}]);
|
||||
|
||||
export const generateChecker = goGenerateTask("generate:checker", [
|
||||
stringerGenerator("tsc/internal/checker/types.go", "SignatureKind", "stringer_generated.go"),
|
||||
]);
|
||||
|
||||
export const generateCompilerOptions = goGenerateTask("generate:compileroptions", [
|
||||
stringerGenerator("tsc/internal/core/compileroptions.go", "ModuleKind", "modulekind_stringer_generated.go", "ModuleKind"),
|
||||
stringerGenerator("tsc/internal/core/compileroptions.go", "ScriptTarget", "scripttarget_stringer_generated.go", "ScriptTarget"),
|
||||
]);
|
||||
|
||||
export const generateLanguageVariant = goGenerateTask("generate:languagevariant", [
|
||||
stringerGenerator("tsc/internal/core/languagevariant.go", "LanguageVariant", "languagevariant_stringer_generated.go"),
|
||||
]);
|
||||
|
||||
export const generateScriptKind = goGenerateTask("generate:scriptkind", [
|
||||
stringerGenerator("tsc/internal/core/scriptkind.go", "ScriptKind", "scriptkind_stringer_generated.go"),
|
||||
]);
|
||||
|
||||
export const generateTristate = goGenerateTask("generate:tristate", [
|
||||
stringerGenerator("tsc/internal/core/tristate.go", "Tristate", "tristate_stringer_generated.go"),
|
||||
]);
|
||||
|
||||
export const generateDiagnostics = goGenerateTask("generate:diagnostics", [
|
||||
{
|
||||
file: "tsc/internal/diagnostics/diagnostics.go",
|
||||
inputs: ["generate.go", "diagnosticMessages.json", "extraDiagnosticMessages.json", "../{collections,json}/*.go", "../locale/lcl/*/diagnosticMessages/diagnosticMessages.generated.json.lcl"],
|
||||
exclude: ["**/*_test.go"],
|
||||
outputs: ["diagnostics_generated.go", "loc_generated.go", "loc/*.json.gz"],
|
||||
commands: [
|
||||
["go", "run", "generate.go", "-diagnostics", "diagnostics_generated.go", "-loc", "loc_generated.go", "-locdir", "loc"],
|
||||
["dprint", "fmt", "diagnostics_generated.go", "loc_generated.go"],
|
||||
],
|
||||
},
|
||||
stringerGenerator("tsc/internal/diagnostics/diagnostics.go", "Category", "stringer_generated.go"),
|
||||
]);
|
||||
|
||||
export const generateAutoImport = goGenerateTask("generate:autoimport", [
|
||||
stringerGenerator("tsc/internal/ls/autoimport/export.go", "ExportSyntax", "export_stringer_generated.go"),
|
||||
]);
|
||||
|
||||
export const generateProject = goGenerateTask("generate:project", [
|
||||
stringerGenerator("tsc/internal/project/project.go", "Kind", "project_stringer_generated.go", "Kind"),
|
||||
]);
|
||||
|
||||
export const generateProjectTestUtil = goGenerateTask("generate:projecttestutil", [
|
||||
moqGenerator("tsc/internal/testutil/projecttestutil/projecttestutil.go", "Client", "clientmock_generated.go", {
|
||||
source: "../../project",
|
||||
packageName: "projecttestutil",
|
||||
inputs: ["../../project/client.go"],
|
||||
stub: true,
|
||||
}),
|
||||
moqGenerator("tsc/internal/testutil/projecttestutil/projecttestutil.go", "NpmExecutor", "npmexecutormock_generated.go", {
|
||||
source: "../../project/ata",
|
||||
packageName: "projecttestutil",
|
||||
inputs: ["../../project/ata/ata.go"],
|
||||
stub: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
export const generateVFS = goGenerateTask("generate:vfs", [
|
||||
moqGenerator("tsc/internal/vfs/vfs.go", "FS", "vfsmock/mock_generated.go", { packageName: "vfsmock" }),
|
||||
]);
|
||||
|
||||
export const generateVFSMatch = goGenerateTask("generate:vfsmatch", [
|
||||
stringerGenerator("tsc/internal/vfs/vfsmatch/vfsmatch.go", "Usage", "stringer_generated.go", "Usage"),
|
||||
]);
|
||||
|
||||
export const generateUnicode = goGenerateTask("generate:unicode", async () => {
|
||||
const { default: generate } = await import("./tsc/internal/stringutil/_scripts/generate-unicode-data.mts");
|
||||
await generate(!!options.force);
|
||||
});
|
||||
|
||||
async function runGenerateExtension() {
|
||||
@@ -388,6 +544,48 @@ export const generateExtension = task({
|
||||
run: runGenerateExtension,
|
||||
});
|
||||
|
||||
async function runGenerateExtensionTest() {
|
||||
await run("npm", ["run", "-w", "native-preview", "generateLocTest"]);
|
||||
}
|
||||
|
||||
export const generateExtensionTest = task({
|
||||
name: "generate:extension-test",
|
||||
description: "Generates pseudo-localized extension resources.",
|
||||
dependencies: [generateExtension],
|
||||
run: runGenerateExtensionTest,
|
||||
});
|
||||
|
||||
async function runGenerateLSP() {
|
||||
const { GeneratedFile } = await import("./tools/scripts/gen/generatedFile.mts");
|
||||
const directory = path.join(__dirname, "tsc/internal/lsp/lsproto/_generate");
|
||||
const modelFiles = ["metaModel.json", "metaModelSchema.mts"].map(file => new GeneratedFile(path.join(directory, file), [path.join(directory, "fetchModel.mts"), path.join(__dirname, "package-lock.json")]));
|
||||
if (!modelFiles.every(file => file.isCurrent(!!options.force))) {
|
||||
for (const file of modelFiles) file.invalidate();
|
||||
const { default: fetchModel } = await import("./tsc/internal/lsp/lsproto/_generate/fetchModel.mts");
|
||||
await fetchModel();
|
||||
for (const file of modelFiles) file.markCurrent();
|
||||
}
|
||||
const output = new GeneratedFile(path.join(directory, "../lsp_generated.go"), [
|
||||
__filename,
|
||||
path.join(directory, "generate.mts"),
|
||||
...modelFiles.map(file => file.fileName),
|
||||
]);
|
||||
if (output.isCurrent(!!options.force)) {
|
||||
console.log("LSP bindings are up to date.");
|
||||
return;
|
||||
}
|
||||
output.invalidate();
|
||||
const { default: generate } = await import("./tsc/internal/lsp/lsproto/_generate/generate.mts");
|
||||
await generate();
|
||||
output.markCurrent();
|
||||
}
|
||||
|
||||
export const generateLSP = task({
|
||||
name: "generate:lsp",
|
||||
description: "Generates LSP bindings from the pinned protocol model. Pass --force to regenerate unchanged files.",
|
||||
run: runGenerateLSP,
|
||||
});
|
||||
|
||||
// ── Enum generation from Go source ──────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -775,9 +973,10 @@ const enumValuesGeneratedGoPath = "tsc/internal/api/enum_values_generated.go";
|
||||
* because it already imports (nearly) every package enums are sourced from.
|
||||
*
|
||||
* @param {GeneratedEnum[]} generatedEnums
|
||||
* @param {import("./tools/scripts/gen/generatedFile.mts").GeneratedFile} generatedGoFile
|
||||
* @returns {Promise<Record<string, Record<string, number>>>} enum def name -> (memberName -> Go value)
|
||||
*/
|
||||
async function computeGoGroundTruth(generatedEnums) {
|
||||
async function computeGoGroundTruth(generatedEnums, generatedGoFile) {
|
||||
/** @type {Map<string, {importPath: string, pkgName: string}>} */
|
||||
const packagesByDir = new Map();
|
||||
/**
|
||||
@@ -849,7 +1048,7 @@ func toInt32[T ~int8 | ~int16 | ~int32 | ~int | ~uint8 | ~uint16 | ~uint32](v T)
|
||||
|
||||
`;
|
||||
|
||||
fs.writeFileSync(enumValuesGeneratedGoPath, goSource);
|
||||
generatedGoFile.write(goSource);
|
||||
await run("dprint", ["fmt", enumValuesGeneratedGoPath]);
|
||||
|
||||
const { stdout } = await runOutput("go", ["run", enumValuesGeneratedGoPath]);
|
||||
@@ -873,6 +1072,26 @@ async function evaluateEnumMembers(enumSource, enumName) {
|
||||
}
|
||||
|
||||
async function runGenerateEnums() {
|
||||
const { GeneratedFile } = await import("./tools/scripts/gen/generatedFile.mts");
|
||||
const inputs = [
|
||||
__filename,
|
||||
...fs.globSync(["go.work", "go.work.sum", "{tsc,tools}/go.{mod,sum}"]),
|
||||
];
|
||||
const enumFiles = enumDefs.map(def => {
|
||||
const camelName = def.name.charAt(0).toLowerCase() + def.name.slice(1);
|
||||
return {
|
||||
def,
|
||||
camelName,
|
||||
typeFile: new GeneratedFile(path.join(def.outDir, `${camelName}.enum.ts`), [...inputs, def.goFile]),
|
||||
runtimeFile: new GeneratedFile(path.join(def.outDir, `${camelName}.ts`), [...inputs, def.goFile]),
|
||||
};
|
||||
});
|
||||
const generatedGoFile = new GeneratedFile(enumValuesGeneratedGoPath, [...inputs, ...enumDefs.map(def => def.goFile)]);
|
||||
const generatedFiles = [generatedGoFile, ...enumFiles.flatMap(({ typeFile, runtimeFile }) => [typeFile, runtimeFile])];
|
||||
if (generatedFiles.every(file => file.isCurrent(!!options.force))) {
|
||||
console.log("Enums are up to date.");
|
||||
return;
|
||||
}
|
||||
const ts = /** @type {typeof import("typescript")} */ (await import("typescript"));
|
||||
|
||||
/**
|
||||
@@ -903,34 +1122,29 @@ async function runGenerateEnums() {
|
||||
console.log("Generating enums from Go source...");
|
||||
/** @type {Array<GeneratedEnum>} */
|
||||
const generatedEnums = [];
|
||||
for (const def of enumDefs) {
|
||||
for (const { def, camelName, typeFile, runtimeFile } of enumFiles) {
|
||||
const members = parseGoEnum(def);
|
||||
const camelName = def.name.charAt(0).toLowerCase() + def.name.slice(1);
|
||||
|
||||
fs.mkdirSync(def.outDir, { recursive: true });
|
||||
|
||||
// Generate .enum.ts (TypeScript enum — used for types)
|
||||
const enumTS = renderEnumTS(def, members);
|
||||
const enumPath = path.join(def.outDir, `${camelName}.enum.ts`);
|
||||
fs.writeFileSync(enumPath, enumTS);
|
||||
typeFile.write(enumTS);
|
||||
|
||||
// Generate .ts (IIFE — used at runtime)
|
||||
const enumJsCode = transpile(enumTS, def.name);
|
||||
const iifeSource = convertEnumToTs(enumJsCode, def.name);
|
||||
const iifePath = path.join(def.outDir, `${camelName}.ts`);
|
||||
fs.writeFileSync(iifePath, iifeSource);
|
||||
runtimeFile.write(iifeSource);
|
||||
generatedEnums.push({
|
||||
code: enumJsCode,
|
||||
def,
|
||||
members,
|
||||
fileNames: [enumPath, iifePath],
|
||||
fileNames: [typeFile.fileName, runtimeFile.fileName],
|
||||
});
|
||||
|
||||
console.log(` ${def.name}: ${members.length} members → ${camelName}.enum.ts, ${camelName}.ts`);
|
||||
}
|
||||
|
||||
console.log("Getting values from go");
|
||||
const goValuesByEnum = await computeGoGroundTruth(generatedEnums);
|
||||
const goValuesByEnum = await computeGoGroundTruth(generatedEnums, generatedGoFile);
|
||||
/** @type {string[]} */
|
||||
const mismatches = [];
|
||||
for (const { def, members, code } of generatedEnums) {
|
||||
@@ -958,31 +1172,63 @@ async function runGenerateEnums() {
|
||||
console.log("All generated values match Go.");
|
||||
|
||||
await run("dprint", ["fmt", ...generatedEnums.flatMap(e => e.fileNames)]);
|
||||
for (const file of generatedFiles) file.markCurrent();
|
||||
console.log("Done.");
|
||||
}
|
||||
|
||||
export const generateEnums = task({
|
||||
name: "generate:enums",
|
||||
description: "Generates TypeScript enum files from Go source.",
|
||||
description: "Generates TypeScript enum files from Go source. Pass --force to regenerate unchanged files.",
|
||||
run: runGenerateEnums,
|
||||
});
|
||||
|
||||
async function runGenerateAST() {
|
||||
const { default: generate } = await import("./tools/scripts/tsc/generate.ts");
|
||||
generate(!!options.force);
|
||||
await runGenerateASTStringer();
|
||||
}
|
||||
|
||||
export const generateAST = task({
|
||||
name: "generate:ast",
|
||||
description: "Generates AST and encoder files from ast.json.",
|
||||
run: () => run("node", ["./tools/scripts/tsc/generate.ts"]),
|
||||
description: "Generates AST, kind stringer, and encoder files from ast.json. Pass --force to regenerate unchanged files.",
|
||||
run: runGenerateAST,
|
||||
});
|
||||
|
||||
async function runGenerateSync() {
|
||||
const { generateSync } = await import("./packages/typescript/scripts/generateSync.ts");
|
||||
generateSync(!!options.force);
|
||||
}
|
||||
|
||||
export const generateSync = task({
|
||||
name: "generate:sync",
|
||||
description: "Generates synchronous and generator APIs and tests. Pass --force to regenerate unchanged files.",
|
||||
run: runGenerateSync,
|
||||
});
|
||||
|
||||
async function runGenerateAPI() {
|
||||
await run("go", ["-C", "./tools", "run", "./gen-proto", "../tsc/internal/api/proto.go", "../packages/typescript/src/api/proto.generated.ts"]);
|
||||
await run("npx", ["dprint", "fmt", "packages/typescript/src/api/proto.generated.ts"]);
|
||||
await runGoGenerator("generate:api", {
|
||||
file: "tsc/internal/api/proto.go",
|
||||
cwd: __dirname,
|
||||
inputs: [
|
||||
"tsc/internal/api/*.go",
|
||||
"tsc/internal/api/requestfilesystem/*.go",
|
||||
"tsc/internal/core/*.go",
|
||||
"tsc/internal/checker/types.go",
|
||||
"tsc/internal/diagnostics/diagnostics.go",
|
||||
"tsc/internal/tspath/path.go",
|
||||
"tools/gen-proto/*.go",
|
||||
],
|
||||
exclude: ["**/*_test.go", "**/*_generated.go"],
|
||||
envInputs: [],
|
||||
outputs: ["packages/typescript/src/api/proto.generated.ts"],
|
||||
commands: [
|
||||
["go", "-C", "./tools", "run", "./gen-proto", "../tsc/internal/api/proto.go", "../packages/typescript/src/api/proto.generated.ts"],
|
||||
["dprint", "fmt", "packages/typescript/src/api/proto.generated.ts"],
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export const generateAPI = task({
|
||||
name: "generate:api",
|
||||
description: "Generates API files from internal/api/proto.go and internal/api/session.go.",
|
||||
run: runGenerateAPI,
|
||||
});
|
||||
export const generateAPI = goGenerateTask("generate:api", runGenerateAPI);
|
||||
|
||||
// ── Vendored npm dependencies ───────────────────────────────────
|
||||
|
||||
@@ -1012,6 +1258,22 @@ export const generateVendor = task({
|
||||
run: runGenerateVendor,
|
||||
});
|
||||
|
||||
const generateCompiler = task({
|
||||
name: "generate:compiler",
|
||||
hiddenFromTaskList: true,
|
||||
dependencies: [generateAST, generateLSP],
|
||||
run: async () => {
|
||||
await runGenerateGo();
|
||||
await runGenerateEnums();
|
||||
},
|
||||
});
|
||||
|
||||
export const generate = task({
|
||||
name: "generate",
|
||||
description: "Runs all code generation, including AST, LSP, APIs, extension localization, and vendored dependencies.",
|
||||
dependencies: [generateCompiler, generateSync, generateExtensionTest, generateVendor],
|
||||
});
|
||||
|
||||
const coverageDir = path.join(__dirname, "coverage");
|
||||
|
||||
const ensureCoverageDirExists = memoize(() => {
|
||||
@@ -1241,6 +1503,12 @@ export const testTools = task({
|
||||
run: runTestTools,
|
||||
});
|
||||
|
||||
export const testCodegen = task({
|
||||
name: "test:codegen",
|
||||
description: "Runs opt-in incremental codegen tests; excluded from validate and test:all. Because this runs asserts on build codegen, it takes awhile and is somewhat redundant.",
|
||||
run: () => run("node", ["--test", "./tools/scripts/gen/*.test.mts"]),
|
||||
});
|
||||
|
||||
export const buildAPI = task({
|
||||
name: "build:api",
|
||||
description: "Builds @typescript/typescript JS API.",
|
||||
@@ -1249,8 +1517,8 @@ export const buildAPI = task({
|
||||
},
|
||||
});
|
||||
|
||||
async function runBuildAPITests() {
|
||||
await run("npm", ["run", "-w", "@typescript/typescript", "generate:sync"]);
|
||||
async function runBuildAPITests(generateSources = true) {
|
||||
if (generateSources) await runGenerateSync();
|
||||
await run("npm", ["run", "-w", "@typescript/typescript", "build:test"]);
|
||||
}
|
||||
|
||||
@@ -1277,7 +1545,7 @@ export const testAPIBenchmarks = task({
|
||||
|
||||
export const testAll = task({
|
||||
name: "test:all",
|
||||
description: "Runs ALL tests in the repo, including benchmarks, tools, and the API tests.",
|
||||
description: "Runs compiler, extension, benchmark, tools, and API tests. Codegen tests are opt-in via test:codegen.",
|
||||
dependencies: [tsgo, buildAPITests],
|
||||
run: async () => {
|
||||
// Prevent interleaving by running these directly instead of in parallel.
|
||||
@@ -1415,9 +1683,12 @@ async function runFormat() {
|
||||
|
||||
export const validate = task({
|
||||
name: "validate",
|
||||
description: "Builds, tests, lints, and formats the repo. Pass --api to include API tests, or --all to include ancillary repository tests. Benchmarks are separate: test:benchmarks and test:benchmarks:api.",
|
||||
dependencies: [build],
|
||||
description: "Generates, builds, tests, lints, and formats the repo. Pass --api to include API tests, or --all to include all code generation and ancillary repository tests Benchmarks are separate: test:benchmarks and test:benchmarks:api.",
|
||||
dependencies: [options.all ? generate : generateGo],
|
||||
run: async () => {
|
||||
await generateLibs(builtLocal);
|
||||
await buildTsc({ extraFlags: options.release ? getReleaseBuildFlags() : [] });
|
||||
|
||||
/** @type {{ name: string; error: unknown }[]} */
|
||||
const failures = [];
|
||||
/** @param {string} name @param {() => Promise<void>} action */
|
||||
@@ -1431,18 +1702,14 @@ export const validate = task({
|
||||
}
|
||||
};
|
||||
|
||||
await runGenerate();
|
||||
await runValidation("test:tsc", runTests);
|
||||
await runValidation("test:extension", runTestExtension);
|
||||
if (options.api || options.all) {
|
||||
await runGenerateEnums(); // prereqs for test:api not included in `validate` deps
|
||||
await runGenerateAPI();
|
||||
await runBuildAPITests();
|
||||
if (!options.all) await runGenerateEnums();
|
||||
await runBuildAPITests(!options.all);
|
||||
await runValidation("test:api", runTestAPI);
|
||||
}
|
||||
if (options.all) {
|
||||
await runGenerateExtension();
|
||||
await runGenerateVendor();
|
||||
await runValidation("test:tools", runTestTools);
|
||||
await runValidation("test:smoke", runSmokeTest); // in CI this is run with `--race`
|
||||
}
|
||||
@@ -1486,6 +1753,7 @@ export const checkHerebyfile = task({
|
||||
"./node_modules/typescript/bin/tsc",
|
||||
"--noEmit",
|
||||
"--allowJs",
|
||||
"--allowImportingTsExtensions",
|
||||
"--checkJs",
|
||||
"--target",
|
||||
"es2022",
|
||||
@@ -1540,6 +1808,7 @@ export const checkVsceVersion = task({
|
||||
});
|
||||
|
||||
const scriptTsconfigs = [
|
||||
"./tools/scripts/gen/tsconfig.json",
|
||||
"./tools/scripts/tsc/tsconfig.json",
|
||||
"./tsc/internal/lsp/lsproto/_generate/tsconfig.json",
|
||||
];
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"node": "node --conditions @typescript/source",
|
||||
"generate": "npm run generate:ast && npm run generate:encoder && npm run generate:sync",
|
||||
"generate": "npm run node -- scripts/generate.ts",
|
||||
"generate:ast": "node ../../tools/scripts/tsc/generate-ts-ast.ts",
|
||||
"generate:encoder": "npm run node -- ../../tools/scripts/tsc/generate-encoder.ts",
|
||||
"generate:sync": "npm run node -- scripts/generateSync.ts",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { parseGeneratorArgs } from "../../../tools/scripts/gen/utils.mts";
|
||||
import generateEncoder from "../../../tools/scripts/tsc/generate-encoder.ts";
|
||||
import generateAST from "../../../tools/scripts/tsc/generate-ts-ast.ts";
|
||||
import { generateSync } from "./generateSync.ts";
|
||||
|
||||
const { force } = parseGeneratorArgs({});
|
||||
generateAST(force);
|
||||
generateEncoder(force);
|
||||
generateSync(force);
|
||||
@@ -21,18 +21,17 @@
|
||||
* node generateSync.ts
|
||||
*/
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import {
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import {
|
||||
dirname,
|
||||
join,
|
||||
relative,
|
||||
} from "node:path";
|
||||
import { xSync } from "tinyexec";
|
||||
import ts from "typescript";
|
||||
import { GeneratedFile } from "../../../tools/scripts/gen/generatedFile.mts";
|
||||
import {
|
||||
formatFilesSync,
|
||||
parseGeneratorArgs,
|
||||
} from "../../../tools/scripts/gen/utils.mts";
|
||||
|
||||
function generatedHeader(asyncSourceRelPath: string): string {
|
||||
return [
|
||||
@@ -59,8 +58,11 @@ function generateSyncFile(
|
||||
srcPath: string,
|
||||
destPath: string,
|
||||
transform: SourceTransform,
|
||||
force: boolean,
|
||||
variant: SyncVariant = "sync",
|
||||
): string {
|
||||
): GeneratedFile | undefined {
|
||||
const generated = new GeneratedFile(destPath, [import.meta.filename, srcPath]);
|
||||
if (generated.isCurrent(force)) return;
|
||||
const source = readFileSync(srcPath, "utf-8");
|
||||
|
||||
// Normalize line endings to LF
|
||||
@@ -77,12 +79,11 @@ function generateSyncFile(
|
||||
const srcRelPath = relative(ROOT, srcPath).replaceAll("\\", "/");
|
||||
result = generatedHeader(srcRelPath) + result;
|
||||
|
||||
mkdirSync(dirname(destPath), { recursive: true });
|
||||
writeFileSync(destPath, result);
|
||||
generated.write(result);
|
||||
const label = relative(ROOT, srcPath).replaceAll("\\", "/");
|
||||
const destLabel = relative(ROOT, destPath).replaceAll("\\", "/");
|
||||
console.log(` ${label} → ${destLabel}`);
|
||||
return destPath;
|
||||
return generated;
|
||||
}
|
||||
|
||||
// ── Directive processing ─────────────────────────────────────────
|
||||
@@ -626,15 +627,11 @@ function getIndent(source: string, position: number): string {
|
||||
|
||||
// ── Formatting ───────────────────────────────────────────────────
|
||||
|
||||
function formatFiles(paths: string[]): void {
|
||||
xSync("dprint", ["fmt", ...paths], { throwOnError: true });
|
||||
}
|
||||
|
||||
// ── Main ─────────────────────────────────────────────────────────
|
||||
|
||||
export function generateSync(): void {
|
||||
export function generateSync(force = false): void {
|
||||
console.log("Generating sync API from async source...");
|
||||
const generatedFiles: string[] = [];
|
||||
const generatedFiles: (GeneratedFile | undefined)[] = [];
|
||||
|
||||
// Source files
|
||||
for (const relPath of ["types.ts", "api.ts"]) {
|
||||
@@ -655,6 +652,7 @@ export function generateSync(): void {
|
||||
"",
|
||||
transformAsyncSource(source, fileName, true),
|
||||
].join("\n"),
|
||||
force,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -664,6 +662,7 @@ export function generateSync(): void {
|
||||
join(TEST, "async", relPath),
|
||||
join(TEST, "sync", relPath),
|
||||
(source, fileName) => transformAsyncSource(source, fileName, false),
|
||||
force,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -671,14 +670,21 @@ export function generateSync(): void {
|
||||
join(TEST, "async", "api.bench.ts"),
|
||||
join(TEST, "generators", "api.bench.ts"),
|
||||
(source, fileName) => transformAsyncSource(source, fileName, false),
|
||||
force,
|
||||
"generators",
|
||||
));
|
||||
|
||||
const changedFiles = generatedFiles.filter(file => file !== undefined);
|
||||
if (!changedFiles.length) {
|
||||
console.log("Sync API is up to date.");
|
||||
return;
|
||||
}
|
||||
console.log("Formatting...");
|
||||
formatFiles(generatedFiles);
|
||||
formatFilesSync(changedFiles.map(file => file.fileName));
|
||||
for (const file of changedFiles) file.markCurrent();
|
||||
console.log("Done.");
|
||||
}
|
||||
|
||||
if (process.argv[1] === import.meta.filename) {
|
||||
generateSync();
|
||||
generateSync(parseGeneratorArgs({}).force);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { GeneratedFile } from "./generatedFile.mts";
|
||||
import {
|
||||
formatCommandArg,
|
||||
goInputs,
|
||||
repoRoot,
|
||||
run,
|
||||
} from "./utils.mts";
|
||||
|
||||
export interface CacheOptions {
|
||||
inputs: string[];
|
||||
outputs: string[];
|
||||
commands: string[][];
|
||||
cwd?: string;
|
||||
exclude?: string[];
|
||||
envInputs?: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
function expand(patterns: string[], cwd: string, exclude: string[] = []) {
|
||||
return [...new Set(fs.globSync(patterns, { cwd, exclude }).map(file => path.resolve(cwd, file)))].sort();
|
||||
}
|
||||
|
||||
export default async function cache({
|
||||
inputs,
|
||||
outputs,
|
||||
commands,
|
||||
cwd = process.cwd(),
|
||||
exclude = [],
|
||||
envInputs = ["GOOS", "GOARCH", "GOFLAGS", "GOTOOLCHAIN", "GOEXPERIMENT", "CGO_ENABLED", "GOWORK"],
|
||||
env,
|
||||
force = false,
|
||||
}: CacheOptions): Promise<boolean> {
|
||||
if (!outputs.length || !commands.length || commands.some(command => !command.length)) {
|
||||
throw new Error("Cached generation requires outputs and nonempty commands.");
|
||||
}
|
||||
cwd = path.resolve(cwd);
|
||||
const environment = { ...process.env, ...env };
|
||||
const snapshotInputs = () => {
|
||||
const entries = [
|
||||
...new Set([
|
||||
import.meta.filename,
|
||||
path.join(import.meta.dirname, "generatedFile.mts"),
|
||||
path.join(repoRoot, "package-lock.json"),
|
||||
path.join(repoRoot, ".dprint.jsonc"),
|
||||
...goInputs(),
|
||||
...inputs.flatMap(pattern => {
|
||||
const matches = expand([pattern], cwd, exclude);
|
||||
if (!matches.length) throw new Error(`No inputs matched: ${pattern}`);
|
||||
return matches;
|
||||
}),
|
||||
]),
|
||||
].sort();
|
||||
const files: string[] = [];
|
||||
const hash = createHash("sha256");
|
||||
for (const file of entries) {
|
||||
if (fs.statSync(file).isDirectory()) {
|
||||
hash.update(JSON.stringify([file, "directory"]));
|
||||
}
|
||||
else {
|
||||
files.push(file);
|
||||
hash.update(JSON.stringify([file, createHash("sha256").update(fs.readFileSync(file)).digest("hex")]));
|
||||
}
|
||||
}
|
||||
return { files, hash: hash.digest("hex") };
|
||||
};
|
||||
const before = snapshotInputs();
|
||||
const outputFiles = () => expand(outputs, cwd).filter(file => fs.statSync(file).isFile());
|
||||
const artifacts = (files: string[]) =>
|
||||
files.map(file =>
|
||||
new GeneratedFile(file, before.files, undefined, {
|
||||
cwd,
|
||||
commands,
|
||||
inputs,
|
||||
outputs,
|
||||
exclude,
|
||||
inputHash: before.hash,
|
||||
outputFiles: files,
|
||||
environment: envInputs.map(name => [name, environment[name]]),
|
||||
})
|
||||
);
|
||||
const previous = artifacts(outputFiles());
|
||||
const complete = () => outputs.every(pattern => expand([pattern], cwd).some(file => fs.statSync(file).isFile()));
|
||||
const commandText = commands.map(command => command.map(formatCommandArg).join(" ")).join("; ");
|
||||
if (complete() && previous.every(file => file.isCurrent(force))) {
|
||||
console.log(`skipped ${commandText}: codegen outputs are already up to date`);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const file of previous) file.invalidate();
|
||||
for (const [command, ...args] of commands) {
|
||||
await run(command, args, { cwd, env: environment });
|
||||
}
|
||||
if (!complete()) throw new Error(`Generation did not produce all declared outputs: ${outputs.join(", ")}`);
|
||||
if (snapshotInputs().hash === before.hash) {
|
||||
for (const file of artifacts(outputFiles())) file.markCurrent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import assert from "node:assert/strict";
|
||||
import * as fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { test } from "node:test";
|
||||
import cache from "./cache.mts";
|
||||
|
||||
test("cache commands use and track an explicit environment without changing the parent", async context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-cache-env-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const parentValue = process.env.GOFILE;
|
||||
const options = {
|
||||
cwd: directory,
|
||||
inputs: [],
|
||||
outputs: ["output.json"],
|
||||
envInputs: ["GOFILE", "GOPACKAGE"],
|
||||
env: { GOFILE: "source.go", GOPACKAGE: "example" },
|
||||
commands: [[process.execPath, "-e", "require('node:fs').writeFileSync('output.json', JSON.stringify({ file: process.env.GOFILE, pkg: process.env.GOPACKAGE, cwd: process.cwd() }))"]],
|
||||
};
|
||||
assert.equal(await cache(options), false);
|
||||
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(directory, "output.json"), "utf8")), {
|
||||
file: "source.go",
|
||||
pkg: "example",
|
||||
cwd: directory,
|
||||
});
|
||||
assert.equal(await cache(options), true);
|
||||
assert.equal(await cache({ ...options, env: { ...options.env, GOFILE: "other.go" } }), false);
|
||||
assert.equal(process.env.GOFILE, parentValue);
|
||||
});
|
||||
|
||||
test("cache commands preserve the working directory and arguments", async context => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const directory = fs.mkdtempSync(path.join(root, "tsc/internal/_cache probe-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true, maxRetries: 10 }));
|
||||
fs.writeFileSync(path.join(directory, "input.txt"), "input");
|
||||
fs.writeFileSync(path.join(directory, "generate.cjs"), "const fs = require('node:fs'); fs.writeFileSync('output.txt', fs.readFileSync('input.txt') + JSON.stringify(process.argv.slice(2)));\n");
|
||||
fs.writeFileSync(path.join(directory, "finish.cjs"), "require('node:fs').appendFileSync('output.txt', ' finished');\n");
|
||||
const generate = (force = false, failure = false) =>
|
||||
cache({
|
||||
cwd: directory,
|
||||
inputs: ["input.txt", "*.cjs"],
|
||||
outputs: ["output.txt"],
|
||||
commands: [
|
||||
[process.execPath, "generate.cjs", "--help", "--force", "space value"],
|
||||
[process.execPath, failure ? "missing.cjs" : "finish.cjs"],
|
||||
],
|
||||
force,
|
||||
});
|
||||
const output = path.join(directory, "output.txt");
|
||||
assert.equal(await generate(), false);
|
||||
assert.equal(fs.readFileSync(output, "utf8"), 'input["--help","--force","space value"] finished');
|
||||
const timestamp = fs.statSync(output).mtimeMs;
|
||||
assert.equal(await generate(), true);
|
||||
assert.equal(fs.statSync(output).mtimeMs, timestamp);
|
||||
assert.equal(await generate(true), false);
|
||||
await assert.rejects(generate(false, true));
|
||||
assert.equal(await generate(), false);
|
||||
assert.equal(await generate(), true);
|
||||
});
|
||||
|
||||
test("cache commands track inputs, outputs, arguments, and failures", async context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-cache-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true, maxRetries: 10 }));
|
||||
const input = path.join(directory, "input.txt");
|
||||
const output = path.join(directory, "output.txt");
|
||||
fs.writeFileSync(input, "first");
|
||||
fs.writeFileSync(path.join(directory, "generate.cjs"), "const fs = require('node:fs'); fs.writeFileSync('output.txt', fs.readFileSync('input.txt'));\n");
|
||||
fs.writeFileSync(path.join(directory, "finish.cjs"), "const fs = require('node:fs'); fs.appendFileSync('output.txt', process.argv[2]); fs.writeFileSync('extra.txt', 'extra');\n");
|
||||
const generate = (suffix = " done", force = false, failure = false, env: NodeJS.ProcessEnv = {}) =>
|
||||
cache({
|
||||
cwd: directory,
|
||||
inputs: ["input*.txt", "*.cjs"],
|
||||
outputs: ["output.txt", "extra*.txt"],
|
||||
envInputs: ["TSGO_CACHE_PROBE"],
|
||||
env,
|
||||
commands: [
|
||||
[process.execPath, "generate.cjs"],
|
||||
[process.execPath, failure ? "missing.cjs" : "finish.cjs", suffix],
|
||||
],
|
||||
force,
|
||||
});
|
||||
await generate();
|
||||
assert.equal(fs.readFileSync(output, "utf8"), "first done");
|
||||
const timestamp = fs.statSync(output).mtimeMs;
|
||||
assert.equal(await generate(), true);
|
||||
assert.equal(fs.statSync(output).mtimeMs, timestamp);
|
||||
fs.writeFileSync(input, "second");
|
||||
await generate();
|
||||
assert.equal(fs.readFileSync(output, "utf8"), "second done");
|
||||
const addedInput = path.join(directory, "input-added.txt");
|
||||
fs.writeFileSync(addedInput, "added");
|
||||
assert.equal(await generate(), false);
|
||||
fs.rmSync(addedInput);
|
||||
assert.equal(await generate(), false);
|
||||
fs.rmSync(path.join(directory, "extra.txt"));
|
||||
assert.equal(await generate(), false);
|
||||
fs.writeFileSync(path.join(directory, "extra-added.txt"), "extra");
|
||||
assert.equal(await generate(), false);
|
||||
fs.writeFileSync(output, "edited");
|
||||
assert.equal(await generate(), false);
|
||||
assert.equal(await generate(" changed"), false);
|
||||
assert.equal(fs.readFileSync(output, "utf8"), "second changed");
|
||||
assert.equal(await generate(" changed"), true);
|
||||
assert.equal(await generate(" changed", true), false);
|
||||
await assert.rejects(generate(" changed", true, true));
|
||||
assert.equal(await generate(" changed"), false);
|
||||
assert.equal(await generate(" changed"), true);
|
||||
const environment = { TSGO_CACHE_PROBE: "changed" };
|
||||
assert.equal(await generate(" changed", false, false, environment), false);
|
||||
assert.equal(await generate(" changed", false, false, environment), true);
|
||||
assert.equal(await generate("--force"), false);
|
||||
assert.equal(fs.readFileSync(output, "utf8"), "second--force");
|
||||
assert.equal(await generate("--force"), true);
|
||||
});
|
||||
|
||||
test("cache commands reject missing inputs, outputs, and empty commands", async context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-cache-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const options = {
|
||||
cwd: directory,
|
||||
inputs: ["input.txt"],
|
||||
outputs: ["output.txt"],
|
||||
commands: [[process.execPath, "-e", "require('node:fs').writeFileSync('output.txt', 'generated')"]],
|
||||
};
|
||||
await assert.rejects(cache(options), /No inputs matched/);
|
||||
assert.equal(fs.existsSync(path.join(directory, "output.txt")), false);
|
||||
fs.writeFileSync(path.join(directory, "input.txt"), "input");
|
||||
await assert.rejects(cache({ ...options, inputs: ["missing*.txt"] }), /No inputs matched/);
|
||||
await assert.rejects(cache({ ...options, commands: [] }), /nonempty commands/);
|
||||
await assert.rejects(cache({ ...options, commands: [[]] }), /nonempty commands/);
|
||||
await assert.rejects(cache({ ...options, outputs: [] }), /requires outputs/);
|
||||
await assert.rejects(cache({ ...options, outputs: ["output.txt", "missing.txt"] }), /did not produce all declared outputs/);
|
||||
assert.equal(await cache(options), false);
|
||||
assert.equal(await cache(options), true);
|
||||
});
|
||||
|
||||
test("cache commands do not cache inputs changed during execution", async context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-cache-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
for (const change of ["fs.writeFileSync('input.txt', 'changed')", "fs.writeFileSync('input-added.txt', 'added')"]) {
|
||||
fs.writeFileSync(path.join(directory, "input.txt"), "initial");
|
||||
const options = {
|
||||
cwd: directory,
|
||||
inputs: ["input*.txt"],
|
||||
outputs: ["output.txt"],
|
||||
commands: [[process.execPath, "-e", `const fs = require('node:fs'); fs.writeFileSync('output.txt', 'generated'); ${change};`]],
|
||||
};
|
||||
assert.equal(await cache(options), false);
|
||||
assert.equal(await cache(options), false);
|
||||
assert.equal(await cache(options), true);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { repoRoot } from "./utils.mts";
|
||||
|
||||
const commonInputs = [
|
||||
import.meta.filename,
|
||||
path.join(import.meta.dirname, "utils.mts"),
|
||||
path.join(repoRoot, "package-lock.json"),
|
||||
path.join(repoRoot, ".dprint.jsonc"),
|
||||
];
|
||||
|
||||
function digest(value: string | Buffer): string {
|
||||
return createHash("sha256").update(value).digest("hex");
|
||||
}
|
||||
|
||||
function readIfExists(file: string): Buffer | undefined {
|
||||
try {
|
||||
return fs.readFileSync(file);
|
||||
}
|
||||
catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export class GeneratedFile {
|
||||
readonly fileName: string;
|
||||
private readonly inputs: readonly string[];
|
||||
private readonly inputHash: string;
|
||||
private readonly cacheFile: string;
|
||||
private readonly key: unknown;
|
||||
|
||||
constructor(fileName: string, inputs: readonly string[], cacheDirectory = path.join(os.tmpdir(), "typescript-codegen"), key?: unknown) {
|
||||
this.fileName = path.resolve(fileName);
|
||||
this.inputs = [...new Set([...commonInputs, ...inputs].map(file => path.resolve(file)))].sort();
|
||||
this.key = key;
|
||||
this.inputHash = this.hashInputs();
|
||||
this.cacheFile = path.join(cacheDirectory, digest(this.fileName) + ".json");
|
||||
}
|
||||
|
||||
private hashInputs(): string {
|
||||
return digest(JSON.stringify([
|
||||
process.version,
|
||||
process.platform,
|
||||
process.arch,
|
||||
this.key,
|
||||
this.inputs.map(file => [file, digest(fs.readFileSync(file))]),
|
||||
]));
|
||||
}
|
||||
|
||||
private state(output: Buffer): string {
|
||||
return JSON.stringify([this.inputHash, digest(output)]);
|
||||
}
|
||||
|
||||
isCurrent(force = false): boolean {
|
||||
if (force) return false;
|
||||
const output = readIfExists(this.fileName);
|
||||
return output !== undefined && readIfExists(this.cacheFile)?.toString() === this.state(output);
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
fs.rmSync(this.cacheFile, { force: true });
|
||||
}
|
||||
|
||||
write(content: string): void {
|
||||
this.invalidate();
|
||||
fs.mkdirSync(path.dirname(this.fileName), { recursive: true });
|
||||
fs.writeFileSync(this.fileName, content);
|
||||
}
|
||||
|
||||
markCurrent(): void {
|
||||
if (this.inputHash !== this.hashInputs()) return;
|
||||
const state = this.state(fs.readFileSync(this.fileName));
|
||||
fs.mkdirSync(path.dirname(this.cacheFile), { recursive: true });
|
||||
fs.writeFileSync(this.cacheFile, state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { test } from "node:test";
|
||||
import { stripVTControlCharacters } from "node:util";
|
||||
import { runInNewContext } from "node:vm";
|
||||
import { x } from "tinyexec";
|
||||
import ts from "typescript";
|
||||
import cache from "./cache.mts";
|
||||
import { GeneratedFile } from "./generatedFile.mts";
|
||||
|
||||
test("validate generates before building and selects the generation scope", async () => {
|
||||
const fileName = path.resolve(import.meta.dirname, "../../../Herebyfile.mjs");
|
||||
const source = ts.createSourceFile(fileName, fs.readFileSync(fileName, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
||||
const declaration = source.statements.filter(ts.isVariableStatement)
|
||||
.flatMap(statement => statement.declarationList.declarations)
|
||||
.find(declaration => declaration.name.getText(source) === "validate");
|
||||
assert.ok(declaration?.initializer);
|
||||
for (const options of [{}, { api: true }, { all: true }]) {
|
||||
const calls: string[] = [];
|
||||
const action = (name: string) => async () => {
|
||||
calls.push(name);
|
||||
};
|
||||
const generate = { run: action("generate") };
|
||||
const generateGo = { run: action("generate:go") };
|
||||
const build = { run: action("build") };
|
||||
const validation = runInNewContext(declaration.initializer.getText(source), {
|
||||
task: (spec: unknown) => spec,
|
||||
options,
|
||||
generate,
|
||||
generateGo,
|
||||
build,
|
||||
builtLocal: "./built/local",
|
||||
generateLibs: action("lib"),
|
||||
buildTsc: action("build"),
|
||||
getReleaseBuildFlags: () => [],
|
||||
runGenerateGo: action("generate:go"),
|
||||
runGenerateEnums: action("generate:enums"),
|
||||
runGenerateAPI: action("generate:api"),
|
||||
runGenerateExtension: action("generate:extension"),
|
||||
runGenerateVendor: action("generate:vendor"),
|
||||
runBuildAPITests: async (generate = true) => {
|
||||
if (generate) calls.push("generate:sync");
|
||||
calls.push("build:api:test");
|
||||
},
|
||||
runTests: action("test:tsc"),
|
||||
runTestExtension: action("test:extension"),
|
||||
runTestAPI: action("test:api"),
|
||||
runTestBenchmarks: action("test:benchmarks"),
|
||||
runTestTools: action("test:tools"),
|
||||
runSmokeTest: action("test:smoke"),
|
||||
runLint: action("lint"),
|
||||
runFormat: action("format"),
|
||||
}) as { dependencies: { run: () => Promise<void>; }[]; run: () => Promise<void>; };
|
||||
await Promise.all(validation.dependencies.map(dependency => dependency.run()));
|
||||
await validation.run();
|
||||
assert.equal(calls[0], "all" in options ? "generate" : "generate:go");
|
||||
assert.ok(calls.indexOf("build") > 0);
|
||||
assert.ok(calls.indexOf("build") < calls.indexOf("test:tsc"));
|
||||
assert.equal(calls.includes("test:api"), "api" in options || "all" in options);
|
||||
assert.equal(calls.includes("test:tools"), "all" in options);
|
||||
if ("all" in options) {
|
||||
assert.deepEqual(calls.filter(name => name.startsWith("generate")), ["generate"]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("generator options preserve repeated inputs and force overrides", async () => {
|
||||
const { parseGeneratorArgs, resolveForce } = await import("./utils.mts");
|
||||
const options = { type: { type: "string" }, input: { type: "string", multiple: true } } as const;
|
||||
const args = ["--type", "Kind", "--input=kind.go", "--input", "extra.go", "--force"];
|
||||
const parsed = parseGeneratorArgs(options, args);
|
||||
assert.equal(parsed.values.type, "Kind");
|
||||
assert.deepEqual(parsed.values.input, ["kind.go", "extra.go"]);
|
||||
assert.equal(parsed.force, true);
|
||||
assert.equal(resolveForce(undefined, "ON"), true);
|
||||
assert.equal(resolveForce(undefined, "0"), false);
|
||||
assert.equal(resolveForce(false, "1"), false);
|
||||
assert.equal(parseGeneratorArgs({}, ["--no-force"]).force, false);
|
||||
});
|
||||
|
||||
test("generated files are current only while their inputs and formatted output match", context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-codegen-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const input = path.join(directory, "input.ts");
|
||||
const output = path.join(directory, "output.ts");
|
||||
const cache = path.join(directory, "cache");
|
||||
fs.writeFileSync(input, "input");
|
||||
const generated = () => new GeneratedFile(output, [input], cache);
|
||||
let file = generated();
|
||||
assert.equal(file.isCurrent(), false);
|
||||
file.write("unformatted");
|
||||
fs.writeFileSync(output, "formatted");
|
||||
file.markCurrent();
|
||||
assert.equal(generated().isCurrent(), true);
|
||||
assert.equal(generated().isCurrent(true), false);
|
||||
file.invalidate();
|
||||
assert.equal(generated().isCurrent(), false);
|
||||
file.write("formatted");
|
||||
file.markCurrent();
|
||||
assert.equal(generated().isCurrent(), true);
|
||||
fs.utimesSync(input, new Date(), new Date());
|
||||
assert.equal(generated().isCurrent(), true);
|
||||
fs.writeFileSync(input, "changed");
|
||||
assert.equal(generated().isCurrent(), false);
|
||||
fs.writeFileSync(input, "input");
|
||||
assert.equal(generated().isCurrent(), true);
|
||||
fs.writeFileSync(output, "edited");
|
||||
assert.equal(generated().isCurrent(), false);
|
||||
file = generated();
|
||||
file.write("formatted");
|
||||
assert.equal(generated().isCurrent(), false);
|
||||
file.markCurrent();
|
||||
assert.equal(generated().isCurrent(), true);
|
||||
const dependency = path.join(directory, "generator.ts");
|
||||
fs.writeFileSync(dependency, "generator");
|
||||
assert.equal(new GeneratedFile(output, [input, dependency], cache).isCurrent(), false);
|
||||
fs.rmSync(dependency);
|
||||
assert.throws(() => new GeneratedFile(output, [input, dependency], cache), { code: "ENOENT" });
|
||||
fs.rmSync(output);
|
||||
assert.equal(generated().isCurrent(), false);
|
||||
});
|
||||
|
||||
test("changing inputs during generation does not mark stale output current", context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-codegen-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const input = path.join(directory, "input.ts");
|
||||
const output = path.join(directory, "output.ts");
|
||||
const cache = path.join(directory, "cache");
|
||||
fs.writeFileSync(input, "before");
|
||||
const file = new GeneratedFile(output, [input], cache);
|
||||
file.write("generated");
|
||||
fs.writeFileSync(input, "after");
|
||||
file.markCurrent();
|
||||
assert.equal(new GeneratedFile(output, [input], cache).isCurrent(), false);
|
||||
});
|
||||
|
||||
test("default metadata has a stable path in the OS temporary directory", context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-codegen-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const output = path.join(directory, "output.ts");
|
||||
const cacheFile = path.join(os.tmpdir(), "typescript-codegen", createHash("sha256").update(output).digest("hex") + ".json");
|
||||
context.after(() => fs.rmSync(cacheFile, { force: true }));
|
||||
const file = new GeneratedFile(output, []);
|
||||
file.write("generated");
|
||||
file.markCurrent();
|
||||
assert.equal(fs.existsSync(cacheFile), true);
|
||||
assert.equal(new GeneratedFile(output, []).isCurrent(), true);
|
||||
fs.rmSync(cacheFile);
|
||||
assert.equal(new GeneratedFile(output, []).isCurrent(), false);
|
||||
file.markCurrent();
|
||||
assert.equal(new GeneratedFile(output, []).isCurrent(), true);
|
||||
});
|
||||
|
||||
test("metadata directories can disappear during generation", context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-codegen-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const output = path.join(directory, "output.ts");
|
||||
const cache = path.join(directory, "cache");
|
||||
const file = new GeneratedFile(output, [], cache);
|
||||
file.write("generated");
|
||||
file.markCurrent();
|
||||
fs.rmSync(cache, { recursive: true });
|
||||
assert.equal(file.isCurrent(), false);
|
||||
file.write("regenerated");
|
||||
file.markCurrent();
|
||||
assert.equal(new GeneratedFile(output, [], cache).isCurrent(), true);
|
||||
});
|
||||
|
||||
test("generated files track each output independently", context => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "tsgo-codegen-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const files = ["output.go", "locale.gz"].map(file => new GeneratedFile(path.join(directory, file), [], path.join(directory, "cache")));
|
||||
for (const file of files) {
|
||||
file.write("generated");
|
||||
file.markCurrent();
|
||||
}
|
||||
assert.equal(files.every(file => file.isCurrent()), true);
|
||||
fs.writeFileSync(files[1].fileName, "modified");
|
||||
assert.equal(files[0].isCurrent(), true);
|
||||
assert.equal(files[1].isCurrent(), false);
|
||||
fs.writeFileSync(files[1].fileName, "generated");
|
||||
assert.equal(files.every(file => file.isCurrent()), true);
|
||||
fs.rmSync(files[1].fileName);
|
||||
assert.equal(files[0].isCurrent(), true);
|
||||
assert.equal(files[1].isCurrent(), false);
|
||||
files[1].write("generated");
|
||||
files[1].markCurrent();
|
||||
files[1].invalidate();
|
||||
assert.equal(files[0].isCurrent(), true);
|
||||
assert.equal(files[1].isCurrent(), false);
|
||||
});
|
||||
|
||||
test("generate:go runs Go generators directly and shares caches with Go fallback", async () => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const generate = () => x("npx", ["hereby", "generate:go"], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
const first = await generate();
|
||||
assert.doesNotMatch(first.stdout, /\$ go generate|npm run --silent cache|\$ node .*generate-unicode-data/);
|
||||
const files = fs.globSync(["tsc/internal/**/*generated.go", "packages/typescript/src/api/proto.generated.ts"], { cwd: root });
|
||||
const timestamps = files.map(file => fs.statSync(path.join(root, file)).mtimeMs);
|
||||
const current = await generate();
|
||||
assert.doesNotMatch(current.stdout, /Generated codegen outputs|Generated Unicode tables/);
|
||||
assert.match(current.stdout, /Unicode tables are up to date/);
|
||||
assert.deepEqual(files.map(file => fs.statSync(path.join(root, file)).mtimeMs), timestamps);
|
||||
const fallback = await x("go", ["-C", "./tsc", "generate", "./internal/diagnostics"], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
assert.equal(fallback.stdout.match(/Codegen outputs are up to date/g)?.length, 2);
|
||||
const nested = await x("npx", ["hereby", "generate:compileroptions"], { throwOnError: true, nodeOptions: { cwd: path.join(root, "tsc/internal/core") } });
|
||||
assert.equal(nested.stdout.match(/Codegen outputs are up to date/g)?.length, 2);
|
||||
});
|
||||
|
||||
test("generate includes standalone generators without Go traversal", async () => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const { stdout } = await x("npx", ["hereby", "generate"], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
const log = stripVTControlCharacters(stdout);
|
||||
const eventIndex = (event: string) => {
|
||||
const index = log.indexOf(event);
|
||||
assert.ok(index >= 0, `Missing task event: ${event}`);
|
||||
return index;
|
||||
};
|
||||
const compilerStart = eventIndex("Starting generate:compiler");
|
||||
assert.ok(eventIndex("Finished generate:ast ") < compilerStart);
|
||||
assert.ok(eventIndex("Finished generate:lsp ") < compilerStart);
|
||||
assert.ok(eventIndex("Starting generate:sync") < compilerStart);
|
||||
assert.ok(eventIndex("Starting generate:vendor") < compilerStart);
|
||||
assert.ok(eventIndex("Finished generate:extension ") < eventIndex("Starting generate:extension-test"));
|
||||
assert.doesNotMatch(stdout, /\$ go generate|npm run --silent cache/);
|
||||
assert.match(stdout, /Unicode tables/);
|
||||
assert.match(stdout, /Enums are up to date|All generated values match Go/);
|
||||
assert.match(stdout, /Sync API is up to date|Formatting\.\.\./);
|
||||
assert.match(stdout, /LSP bindings are up to date|Successfully generated .*lsp_generated\.go/);
|
||||
assert.match(stdout, /generateLocBundle/);
|
||||
assert.match(stdout, /generateLocTest/);
|
||||
assert.deepEqual(
|
||||
fs.readFileSync(path.join(root, "packages/typescript/vendor/vscode-jsonrpc/package.json")),
|
||||
fs.readFileSync(path.join(root, "node_modules/vscode-jsonrpc/package.json")),
|
||||
);
|
||||
const lspOutput = path.join(root, "tsc/internal/lsp/lsproto/lsp_generated.go");
|
||||
const timestamp = fs.statSync(lspOutput).mtimeMs;
|
||||
const current = await x("npx", ["hereby", "generate:lsp"], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
assert.match(current.stdout, /LSP bindings are up to date/);
|
||||
assert.doesNotMatch(current.stdout, /Using vscode-languageclient/);
|
||||
assert.equal(fs.statSync(lspOutput).mtimeMs, timestamp);
|
||||
});
|
||||
|
||||
test("bundled generation skips unchanged library outputs", async () => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const generate = () => x("go", ["-C", "./tsc", "generate", "./internal/bundled"], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
await generate();
|
||||
const files = ["libs_generated.go", "embed_generated.go"].map(file => path.join(root, "tsc/internal/bundled", file));
|
||||
const timestamps = files.map(file => fs.statSync(file).mtimeMs);
|
||||
assert.match((await generate()).stdout, /Codegen outputs are up to date\./);
|
||||
assert.deepEqual(files.map(file => fs.statSync(file).mtimeMs), timestamps);
|
||||
});
|
||||
|
||||
test("bundled generation tracks libraries, validates inputs, and supports force", async context => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const directory = fs.mkdtempSync(path.join(root, "tsc/internal/_bundled-probe-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true, maxRetries: 10 }));
|
||||
const notice = path.join(directory, "CopyrightNotice.txt");
|
||||
const libs = path.join(directory, "libs");
|
||||
fs.mkdirSync(libs);
|
||||
fs.writeFileSync(notice, "Fixture notice\n\n");
|
||||
const library = path.join(libs, "lib.first.d.ts");
|
||||
const content = "Fixture notice\n\n\ndeclare const first: string;\n";
|
||||
fs.writeFileSync(library, content);
|
||||
const options = {
|
||||
cwd: directory,
|
||||
inputs: [path.join(root, "tsc/internal/bundled/generate.go"), "CopyrightNotice.txt", "libs/*"],
|
||||
outputs: ["libs_generated.go", "embed_generated.go"],
|
||||
commands: [["go", "run", path.join(root, "tsc/internal/bundled/generate.go")]],
|
||||
};
|
||||
const generate = (force = false) => cache({ ...options, force });
|
||||
await generate();
|
||||
const files = ["libs_generated.go", "embed_generated.go"].map(file => path.join(directory, file));
|
||||
const originals = files.map(file => fs.readFileSync(file));
|
||||
assert.equal(await generate(), true);
|
||||
const added = path.join(libs, "lib.second.d.ts");
|
||||
fs.writeFileSync(added, content);
|
||||
assert.equal(await generate(), false);
|
||||
for (const file of files) assert.match(fs.readFileSync(file, "utf8"), /lib\.second\.d\.ts/);
|
||||
fs.rmSync(added);
|
||||
assert.equal(await generate(), false);
|
||||
for (const [index, file] of files.entries()) {
|
||||
assert.deepEqual(fs.readFileSync(file), originals[index]);
|
||||
fs.writeFileSync(file, "modified");
|
||||
assert.equal(await generate(), false);
|
||||
assert.deepEqual(fs.readFileSync(file), originals[index]);
|
||||
fs.rmSync(file, { recursive: true, maxRetries: 10 });
|
||||
assert.equal(await generate(), false);
|
||||
assert.deepEqual(fs.readFileSync(file), originals[index]);
|
||||
}
|
||||
fs.writeFileSync(library, content.replace("string", "number"));
|
||||
assert.equal(await generate(), false);
|
||||
fs.writeFileSync(library, content.replaceAll("\n", "\r\n"));
|
||||
await assert.rejects(generate());
|
||||
fs.writeFileSync(library, content);
|
||||
assert.equal(await generate(), false);
|
||||
fs.mkdirSync(path.join(libs, "unexpected"));
|
||||
await assert.rejects(generate());
|
||||
fs.rmdirSync(path.join(libs, "unexpected"));
|
||||
assert.equal(await generate(), false);
|
||||
fs.writeFileSync(notice, "Changed fixture notice\n\n");
|
||||
await assert.rejects(generate());
|
||||
fs.writeFileSync(notice, "Fixture notice\n\n");
|
||||
assert.equal(await generate(), false);
|
||||
assert.equal(await generate(true), false);
|
||||
assert.equal(await generate(), true);
|
||||
});
|
||||
|
||||
test("stringer generation tracks declared inputs, options, output, and force", async context => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const directory = fs.mkdtempSync(path.join(root, "tsc/internal/_stringer-probe-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const source = path.join(directory, "kind.go");
|
||||
const extra = path.join(directory, "extra.go");
|
||||
const output = path.join(directory, "kind_stringer_generated.go");
|
||||
fs.writeFileSync(source, "package probe\n\ntype Kind int\n\nconst KindFirst Kind = 0\n");
|
||||
const generate = (trimPrefix = false, force = false) =>
|
||||
cache({
|
||||
cwd: directory,
|
||||
inputs: ["kind.go"],
|
||||
outputs: ["kind_stringer_generated.go"],
|
||||
env: { GOFILE: "kind.go" },
|
||||
force,
|
||||
commands: [
|
||||
["go", "tool", "golang.org/x/tools/cmd/stringer", "-type=Kind", "-output=kind_stringer_generated.go", ...(trimPrefix ? ["-trimprefix=Kind"] : [])],
|
||||
["dprint", "fmt", "kind_stringer_generated.go"],
|
||||
],
|
||||
});
|
||||
await generate();
|
||||
const timestamp = fs.statSync(output).mtimeMs;
|
||||
assert.equal(await generate(), true);
|
||||
assert.equal(fs.statSync(output).mtimeMs, timestamp);
|
||||
fs.writeFileSync(extra, "package probe\n\nconst Unrelated = 1\n");
|
||||
assert.equal(await generate(), true);
|
||||
fs.appendFileSync(source, "\nconst KindSecond Kind = 1\n");
|
||||
assert.equal(await generate(), false);
|
||||
assert.match(fs.readFileSync(output, "utf8"), /KindSecond/);
|
||||
fs.rmSync(extra);
|
||||
assert.equal(await generate(), true);
|
||||
fs.writeFileSync(source, "package probe\n\ntype Kind int\n\nconst KindFirst Kind = 0\n");
|
||||
assert.equal(await generate(), false);
|
||||
assert.doesNotMatch(fs.readFileSync(output, "utf8"), /KindSecond/);
|
||||
assert.equal(await generate(true), false);
|
||||
assert.match(fs.readFileSync(output, "utf8"), /_Kind_name = "First"/);
|
||||
assert.equal(await generate(true), true);
|
||||
fs.appendFileSync(output, "\n");
|
||||
assert.equal(await generate(true), false);
|
||||
fs.rmSync(output);
|
||||
assert.equal(await generate(true), false);
|
||||
assert.equal(await generate(true, true), false);
|
||||
assert.equal(await generate(true), true);
|
||||
fs.appendFileSync(source, "\nconst KindInvalid Kind = missing\n");
|
||||
await assert.rejects(generate(true));
|
||||
fs.writeFileSync(source, "package probe\n\ntype Kind int\n\nconst KindFirst Kind = 0\n");
|
||||
assert.equal(await generate(true), false);
|
||||
assert.equal(await generate(true), true);
|
||||
});
|
||||
|
||||
test("moq generation tracks interface inputs, output, and force", async context => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const directory = fs.mkdtempSync(path.join(root, "tsc/internal/_moq-probe-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const source = path.join(directory, "client.go");
|
||||
const base = path.join(directory, "base.go");
|
||||
fs.mkdirSync(path.join(directory, "mock"));
|
||||
const output = path.join(directory, "mock/mock_generated.go");
|
||||
fs.writeFileSync(base, "package probe\n\ntype Base interface { Read() string }\n");
|
||||
fs.writeFileSync(source, "package probe\n\ntype Client interface { Base }\n");
|
||||
const options = {
|
||||
cwd: directory,
|
||||
inputs: ["client.go", "base.go"],
|
||||
outputs: ["mock/mock_generated.go"],
|
||||
commands: [
|
||||
["go", "tool", "github.com/matryer/moq", "-stub", "-fmt=goimports", "-pkg=mock", "-out=mock/mock_generated.go", ".", "Client"],
|
||||
["dprint", "fmt", "mock/mock_generated.go"],
|
||||
],
|
||||
};
|
||||
const generate = (force = false) => cache({ ...options, force });
|
||||
await generate();
|
||||
const timestamp = fs.statSync(output).mtimeMs;
|
||||
assert.equal(await generate(), true);
|
||||
assert.equal(fs.statSync(output).mtimeMs, timestamp);
|
||||
fs.writeFileSync(base, "package probe\n\ntype Base interface { Read() string; Close() error }\n");
|
||||
assert.equal(await generate(), false);
|
||||
assert.match(fs.readFileSync(output, "utf8"), /CloseFunc/);
|
||||
fs.rmSync(output);
|
||||
assert.equal(await generate(), false);
|
||||
assert.equal(await generate(true), false);
|
||||
assert.equal(await generate(), true);
|
||||
});
|
||||
|
||||
test("diagnostic generation tracks Go and locale outputs and supports force", async context => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const directory = fs.mkdtempSync(path.join(root, "tsc/internal/_diagnostics-probe-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const output = path.join(directory, "diagnostics_generated.go");
|
||||
const localized = path.join(directory, "loc_generated.go");
|
||||
const options = {
|
||||
cwd: path.join(root, "tsc/internal/diagnostics"),
|
||||
inputs: ["generate.go", "diagnosticMessages.json", "extraDiagnosticMessages.json", "../{collections,json}/*.go", "../locale/lcl/*/diagnosticMessages/diagnosticMessages.generated.json.lcl"],
|
||||
exclude: ["**/*_test.go"],
|
||||
outputs: [output, localized, path.join(directory, "loc/*.json.gz")],
|
||||
commands: [
|
||||
["go", "run", "generate.go", "-diagnostics", output, "-loc", localized, "-locdir", path.join(directory, "loc")],
|
||||
["dprint", "fmt", output, localized],
|
||||
],
|
||||
};
|
||||
const generate = (force = false) => cache({ ...options, force });
|
||||
await generate();
|
||||
const locales = fs.globSync("loc/*.json.gz", { cwd: directory }).map(file => path.join(directory, file));
|
||||
assert.ok(locales.length > 0);
|
||||
const files = [output, localized, ...locales];
|
||||
const timestamps = files.map(file => fs.statSync(file).mtimeMs);
|
||||
assert.equal(await generate(), true);
|
||||
assert.deepEqual(files.map(file => fs.statSync(file).mtimeMs), timestamps);
|
||||
const archive = fs.readFileSync(locales[0]);
|
||||
fs.rmSync(locales[0]);
|
||||
assert.equal(await generate(), false);
|
||||
assert.deepEqual(fs.readFileSync(locales[0]), archive);
|
||||
fs.writeFileSync(locales[0], "modified");
|
||||
assert.equal(await generate(), false);
|
||||
assert.deepEqual(fs.readFileSync(locales[0]), archive);
|
||||
const unexpected = path.join(directory, "loc/unexpected.json.gz");
|
||||
fs.writeFileSync(unexpected, "unexpected");
|
||||
assert.equal(await generate(), false);
|
||||
assert.equal(fs.existsSync(unexpected), false);
|
||||
fs.rmSync(path.join(directory, "loc"), { recursive: true });
|
||||
assert.equal(await generate(), false);
|
||||
assert.deepEqual(fs.readFileSync(locales[0]), archive);
|
||||
fs.rmSync(localized);
|
||||
assert.equal(await generate(), false);
|
||||
assert.equal(await generate(true), false);
|
||||
assert.equal(await generate(), true);
|
||||
});
|
||||
|
||||
test("Unicode generation skips unchanged tables and formatting", async () => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const generate = () => x("go", ["-C", "./tsc", "generate", "./internal/stringutil"], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
await generate();
|
||||
const files = ["js_case_generated.go", "identifier_parts_generated.go"].map(file => path.join(root, "tsc/internal/stringutil", file));
|
||||
const timestamps = files.map(file => fs.statSync(file).mtimeMs);
|
||||
const current = await generate();
|
||||
assert.match(current.stdout, /Unicode tables are up to date\./);
|
||||
assert.doesNotMatch(current.stdout, /Formatted/);
|
||||
assert.deepEqual(files.map(file => fs.statSync(file).mtimeMs), timestamps);
|
||||
});
|
||||
|
||||
test("Unicode generation repairs outputs and supports force", async context => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const directory = fs.mkdtempSync(path.join(root, "tsc/internal/_unicode-probe-"));
|
||||
context.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const command = [path.join(root, "tsc/internal/stringutil/_scripts/generate-unicode-data.mts"), "--outDir", directory];
|
||||
const generate = (...args: string[]) => x(process.execPath, [...command, ...args], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
await generate();
|
||||
const files = ["js_case_generated.go", "identifier_parts_generated.go"].map(file => path.join(directory, file));
|
||||
const originals = files.map(file => fs.readFileSync(file));
|
||||
const timestamps = files.map(file => fs.statSync(file).mtimeMs);
|
||||
assert.match((await generate()).stdout, /Unicode tables are up to date\./);
|
||||
assert.deepEqual(files.map(file => fs.statSync(file).mtimeMs), timestamps);
|
||||
for (const [index, file] of files.entries()) {
|
||||
assert.deepEqual(originals[index], fs.readFileSync(path.join(root, "tsc/internal/stringutil", path.basename(file))));
|
||||
fs.writeFileSync(file, "modified");
|
||||
assert.match((await generate()).stdout, /Generated Unicode tables\./);
|
||||
assert.deepEqual(fs.readFileSync(file), originals[index]);
|
||||
fs.rmSync(file);
|
||||
assert.match((await generate()).stdout, /Generated Unicode tables\./);
|
||||
assert.deepEqual(fs.readFileSync(file), originals[index]);
|
||||
}
|
||||
const cacheFile = path.join(os.tmpdir(), "typescript-codegen", createHash("sha256").update(files[0]).digest("hex") + ".json");
|
||||
context.after(() => fs.rmSync(cacheFile, { force: true }));
|
||||
fs.rmSync(cacheFile);
|
||||
assert.match((await generate()).stdout, /Generated Unicode tables\./);
|
||||
assert.match((await generate("--force")).stdout, /Generated Unicode tables\./);
|
||||
assert.match((await generate()).stdout, /Unicode tables are up to date\./);
|
||||
const forced = await x(process.execPath, command, {
|
||||
throwOnError: true,
|
||||
nodeOptions: { cwd: root, env: { ...process.env, TSGO_HEREBY_FORCE: "1" } },
|
||||
});
|
||||
assert.match(forced.stdout, /Generated Unicode tables\./);
|
||||
const overridden = await x(process.execPath, [...command, "--no-force"], {
|
||||
throwOnError: true,
|
||||
nodeOptions: { cwd: root, env: { ...process.env, TSGO_HEREBY_FORCE: "1" } },
|
||||
});
|
||||
assert.match(overridden.stdout, /Unicode tables are up to date\./);
|
||||
});
|
||||
|
||||
test("enum generation skips unchanged outputs and Go verification", async () => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const generate = (force = false) => x("npx", ["hereby", "generate:enums", ...(force ? ["--force"] : [])], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
await generate();
|
||||
const files = [
|
||||
...fs.globSync("packages/typescript/src/enums/*.ts", { cwd: root }),
|
||||
"tsc/internal/api/enum_values_generated.go",
|
||||
].map(file => path.join(root, file));
|
||||
const timestamps = files.map(file => fs.statSync(file).mtimeMs);
|
||||
const { stdout } = await generate();
|
||||
assert.match(stdout, /Enums are up to date\./);
|
||||
assert.doesNotMatch(stdout, /Getting values from go/);
|
||||
assert.deepEqual(files.map(file => fs.statSync(file).mtimeMs), timestamps);
|
||||
const forced = await generate(true);
|
||||
assert.match(forced.stdout, /All generated values match Go\./);
|
||||
assert.match((await generate()).stdout, /Enums are up to date\./);
|
||||
const verifier = path.join(root, "tsc/internal/api/enum_values_generated.go");
|
||||
const cacheFile = path.join(os.tmpdir(), "typescript-codegen", createHash("sha256").update(verifier).digest("hex") + ".json");
|
||||
fs.rmSync(cacheFile);
|
||||
const regenerated = await generate();
|
||||
assert.match(regenerated.stdout, /All generated values match Go\./);
|
||||
assert.match((await generate()).stdout, /Enums are up to date\./);
|
||||
});
|
||||
|
||||
test("AST generation forwards force to schema generators and the kind stringer", async () => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const generate = (force = false) => x("npx", ["hereby", "generate:ast", ...(force ? ["--force"] : [])], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
await generate();
|
||||
const forced = await generate(true);
|
||||
assert.match(forced.stdout, /Wrote .*encoder_generated\.go/);
|
||||
assert.match(forced.stdout, /Wrote .*ast_generated\.go/);
|
||||
assert.match(forced.stdout, /Generated .*ast\.generated\.ts/);
|
||||
assert.match(forced.stdout, /Generated codegen outputs\./);
|
||||
const output = path.join(root, "tsc/internal/ast/kind_stringer_generated.go");
|
||||
const timestamp = fs.statSync(output).mtimeMs;
|
||||
assert.doesNotMatch(forced.stdout, /\$ node .*tools\/scripts\/tsc\/generate\.ts/);
|
||||
const current = await generate();
|
||||
assert.doesNotMatch(current.stdout, /(?:Wrote|Generated) /);
|
||||
assert.match(current.stdout, /Codegen outputs are up to date\./);
|
||||
assert.equal(fs.statSync(output).mtimeMs, timestamp);
|
||||
});
|
||||
|
||||
test("package generation forwards force to AST, encoder, and sync generators", async () => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const generate = (force = false) => x("npm", ["run", "-w", "@typescript/typescript", "generate", ...(force ? ["--", "--force"] : [])], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
await generate();
|
||||
const forced = await generate(true);
|
||||
assert.match(forced.stdout, /Generated .*ast\.generated\.ts/);
|
||||
assert.match(forced.stdout, /Wrote .*encoder_generated\.go/);
|
||||
assert.match(forced.stdout, /Formatting\.\.\./);
|
||||
const current = await generate();
|
||||
assert.doesNotMatch(current.stdout, /(?:Wrote|Generated) |Formatting\.\.\./);
|
||||
assert.match(current.stdout, /Sync API is up to date\./);
|
||||
});
|
||||
|
||||
test("API protocol generation caches formatted output and supports force", async context => {
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const output = path.join(root, "packages/typescript/src/api/proto.generated.ts");
|
||||
const generate = (force = false) => x("npx", ["hereby", "generate:api", ...(force ? ["--force"] : [])], { throwOnError: true, nodeOptions: { cwd: root } });
|
||||
await generate();
|
||||
const timestamp = fs.statSync(output).mtimeMs;
|
||||
const current = await generate();
|
||||
assert.match(current.stdout, /Codegen outputs are up to date\./);
|
||||
assert.doesNotMatch(current.stdout, /\$ node .*cache\.mts/);
|
||||
assert.equal(fs.statSync(output).mtimeMs, timestamp);
|
||||
const unrelated = path.join(root, "tsc/internal/parser/codegen_cache_probe.go");
|
||||
assert.equal(fs.existsSync(unrelated), false);
|
||||
context.after(() => fs.rmSync(unrelated, { force: true }));
|
||||
fs.writeFileSync(unrelated, "package parser\n");
|
||||
assert.match((await generate()).stdout, /Codegen outputs are up to date\./);
|
||||
fs.rmSync(unrelated);
|
||||
const schema = path.join(root, "tsc/internal/api/requestfilesystem/codegen_cache_probe.go");
|
||||
assert.equal(fs.existsSync(schema), false);
|
||||
const original = fs.readFileSync(output, "utf8");
|
||||
context.after(async () => {
|
||||
if (fs.existsSync(schema)) {
|
||||
fs.rmSync(schema);
|
||||
await generate(true);
|
||||
}
|
||||
});
|
||||
fs.writeFileSync(schema, 'package requestfilesystem\n\nconst KindCodegenCacheProbe Kind = "codegen-cache-probe"\n');
|
||||
assert.match((await generate()).stdout, /Generated codegen outputs\./);
|
||||
assert.match(fs.readFileSync(output, "utf8"), /"codegen-cache-probe"/);
|
||||
fs.rmSync(schema);
|
||||
assert.match((await generate()).stdout, /Generated codegen outputs\./);
|
||||
assert.equal(fs.readFileSync(output, "utf8"), original);
|
||||
assert.match((await generate(true)).stdout, /Generated codegen outputs\./);
|
||||
assert.match((await generate()).stdout, /Codegen outputs are up to date\./);
|
||||
const nested = await x("go", ["-C", "./tsc", "generate", "./internal/api"], {
|
||||
throwOnError: true,
|
||||
nodeOptions: { cwd: root, env: { ...process.env, TSGO_HEREBY_FORCE: "1" } },
|
||||
});
|
||||
assert.match(nested.stdout, /Generated codegen outputs\./);
|
||||
assert.match((await generate()).stdout, /Codegen outputs are up to date\./);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"lib": ["esnext"],
|
||||
"noEmit": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import * as fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
parseArgs,
|
||||
type ParseArgsOptionsConfig,
|
||||
} from "node:util";
|
||||
import {
|
||||
x,
|
||||
xSync,
|
||||
} from "tinyexec";
|
||||
|
||||
export const repoRoot = path.resolve(import.meta.dirname, "../../..");
|
||||
|
||||
export interface RunOptions {
|
||||
captureOutput?: boolean;
|
||||
cwd?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export function formatCommandArg(arg: string) {
|
||||
return arg && /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);
|
||||
}
|
||||
|
||||
export function run(command: string, args: readonly string[] = [], options: RunOptions = {}) {
|
||||
console.log("$ " + [command, ...args].map(formatCommandArg).join(" "));
|
||||
return x(command, args, {
|
||||
throwOnError: true,
|
||||
...(options.signal ? { signal: options.signal } : {}),
|
||||
nodeOptions: {
|
||||
cwd: options.cwd,
|
||||
env: options.env ? { ...process.env, ...options.env } : undefined,
|
||||
stdio: options.captureOutput ? "pipe" : "inherit",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function globInputs(patterns: string[], exclude: string[] = []): string[] {
|
||||
return fs.globSync(patterns, { cwd: repoRoot, exclude }).map(file => path.join(repoRoot, file));
|
||||
}
|
||||
|
||||
export function goInputs(): string[] {
|
||||
return [import.meta.filename, ...globInputs(["go.work", "go.work.sum", "{tsc,tools}/go.{mod,sum}"])];
|
||||
}
|
||||
|
||||
export function resolveForce(force?: boolean, environment = process.env.TSGO_HEREBY_FORCE): boolean {
|
||||
return force ?? /^(1|true|yes|on)$/i.test(environment ?? "");
|
||||
}
|
||||
|
||||
export function parseGeneratorArgs<const Options extends ParseArgsOptionsConfig>(options: Options, args = process.argv.slice(2)) {
|
||||
const { values } = parseArgs({
|
||||
args,
|
||||
options: { ...options, force: { type: "boolean" as const } },
|
||||
allowNegative: true,
|
||||
});
|
||||
return { values, force: resolveForce((values as { force?: boolean; }).force) };
|
||||
}
|
||||
|
||||
export async function formatFiles(files: string[]): Promise<void> {
|
||||
await x("dprint", ["fmt", ...files], { throwOnError: true, nodeOptions: { cwd: repoRoot, stdio: "inherit" } });
|
||||
}
|
||||
|
||||
export function formatFilesSync(files: string[]): void {
|
||||
xSync("dprint", ["fmt", ...files], { throwOnError: true, nodeOptions: { cwd: repoRoot, stdio: "inherit" } });
|
||||
}
|
||||
@@ -10,10 +10,14 @@
|
||||
* - packages/typescript/src/api/node/protocol.generated.ts
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { xSync } from "tinyexec";
|
||||
import { GeneratedFile } from "../gen/generatedFile.mts";
|
||||
import {
|
||||
formatFilesSync,
|
||||
parseGeneratorArgs,
|
||||
repoRoot as ROOT,
|
||||
} from "../gen/utils.mts";
|
||||
import type {
|
||||
KindType,
|
||||
MemberInfo,
|
||||
@@ -21,8 +25,6 @@ import type {
|
||||
} from "./schema.ts";
|
||||
import { api } from "./schema.ts";
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname!, "../../..");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Code writer
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -2043,14 +2045,13 @@ function emitRemoteNodeClassClose(w: CodeWriter) {
|
||||
// Main: generate and write all files
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
function writeAndFormat(filePath: string, content: string, formatter: string) {
|
||||
fs.writeFileSync(filePath, content);
|
||||
function writeAndFormat(filePath: string, generate: () => string, force: boolean) {
|
||||
const generated = new GeneratedFile(filePath, [import.meta.filename, path.join(ROOT, "tools/scripts/tsc/schema.ts"), path.join(ROOT, "tools/scripts/tsc/ast.json")]);
|
||||
if (generated.isCurrent(force)) return;
|
||||
generated.write(generate() + "\n");
|
||||
try {
|
||||
const [cmd, ...args] = formatter.split(" ");
|
||||
xSync(cmd, [...args, filePath], {
|
||||
throwOnError: true,
|
||||
nodeOptions: { stdio: "inherit", cwd: ROOT },
|
||||
});
|
||||
formatFilesSync([filePath]);
|
||||
generated.markCurrent();
|
||||
}
|
||||
catch {
|
||||
console.warn(`Warning: formatter failed for ${filePath}`);
|
||||
@@ -2058,47 +2059,42 @@ function writeAndFormat(filePath: string, content: string, formatter: string) {
|
||||
console.log(`Wrote ${filePath}`);
|
||||
}
|
||||
|
||||
export default function main() {
|
||||
export default function main(force = false) {
|
||||
console.log("Generating encoder/decoder code...");
|
||||
|
||||
const goEncoder = generateGoEncoder();
|
||||
writeAndFormat(
|
||||
path.join(ROOT, "tsc/internal/api/encoder/encoder_generated.go"),
|
||||
goEncoder + "\n",
|
||||
"dprint fmt",
|
||||
generateGoEncoder,
|
||||
force,
|
||||
);
|
||||
|
||||
const goDecoder = generateGoDecoder();
|
||||
writeAndFormat(
|
||||
path.join(ROOT, "tsc/internal/api/encoder/decoder_generated.go"),
|
||||
goDecoder + "\n",
|
||||
"dprint fmt",
|
||||
generateGoDecoder,
|
||||
force,
|
||||
);
|
||||
|
||||
const tsProtocol = generateTSProtocol();
|
||||
writeAndFormat(
|
||||
path.join(ROOT, "packages/typescript/src/api/node/protocol.generated.ts"),
|
||||
tsProtocol + "\n",
|
||||
"dprint fmt",
|
||||
generateTSProtocol,
|
||||
force,
|
||||
);
|
||||
|
||||
const tsEncoder = generateTSEncoder();
|
||||
writeAndFormat(
|
||||
path.join(ROOT, "packages/typescript/src/api/node/encoder.generated.ts"),
|
||||
tsEncoder + "\n",
|
||||
"dprint fmt",
|
||||
generateTSEncoder,
|
||||
force,
|
||||
);
|
||||
|
||||
const tsNode = generateTSNodeGenerated();
|
||||
writeAndFormat(
|
||||
path.join(ROOT, "packages/typescript/src/api/node/node.generated.ts"),
|
||||
tsNode + "\n",
|
||||
"dprint fmt",
|
||||
generateTSNodeGenerated,
|
||||
force,
|
||||
);
|
||||
|
||||
console.log("Done!");
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
main();
|
||||
main(parseGeneratorArgs({}).force);
|
||||
}
|
||||
@@ -14,10 +14,14 @@
|
||||
* - Is*() type guard functions
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { xSync } from "tinyexec";
|
||||
import { GeneratedFile } from "../gen/generatedFile.mts";
|
||||
import {
|
||||
formatFilesSync,
|
||||
parseGeneratorArgs,
|
||||
repoRoot as ROOT,
|
||||
} from "../gen/utils.mts";
|
||||
import type {
|
||||
MemberInfo,
|
||||
NodeType,
|
||||
@@ -32,8 +36,6 @@ import {
|
||||
// Load schema
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname!, "../../..");
|
||||
|
||||
// Members that participate in factory/visitor/clone (excludes noFactory)
|
||||
function schemaMembers(node: NodeType): MemberInfo[] {
|
||||
return node.members.filter(m => !m.noFactory);
|
||||
@@ -984,8 +986,7 @@ function generateKind(): string {
|
||||
w.write("");
|
||||
w.write("package ast");
|
||||
w.write("");
|
||||
w.write("//go:generate go tool golang.org/x/tools/cmd/stringer -type=Kind -output=kind_stringer_generated.go");
|
||||
w.write("//go:generate npx dprint fmt kind_stringer_generated.go");
|
||||
w.write("//go:generate npx hereby generate:ast-stringer");
|
||||
w.write("");
|
||||
w.write("type Kind int16");
|
||||
w.write("");
|
||||
@@ -1082,27 +1083,25 @@ function generateKind(): string {
|
||||
return w.toString();
|
||||
}
|
||||
|
||||
function writeAndFormat(filePath: string, content: string) {
|
||||
fs.writeFileSync(filePath, content);
|
||||
xSync("dprint", ["fmt", filePath], {
|
||||
throwOnError: true,
|
||||
nodeOptions: { stdio: "inherit", cwd: ROOT },
|
||||
});
|
||||
function writeAndFormat(filePath: string, generateContent: () => string, force: boolean) {
|
||||
const generated = new GeneratedFile(filePath, [import.meta.filename, path.join(ROOT, "tools/scripts/tsc/schema.ts"), path.join(ROOT, "tools/scripts/tsc/ast.json")]);
|
||||
if (generated.isCurrent(force)) return;
|
||||
generated.write(generateContent());
|
||||
formatFilesSync([filePath]);
|
||||
generated.markCurrent();
|
||||
console.log(`Wrote ${filePath}`);
|
||||
}
|
||||
|
||||
export default function main() {
|
||||
export default function main(force = false) {
|
||||
console.log("Generating Go AST code...");
|
||||
|
||||
const code = generate();
|
||||
const outPath = path.join(ROOT, "tsc/internal/ast/ast_generated.go");
|
||||
writeAndFormat(outPath, code + "\n");
|
||||
writeAndFormat(outPath, () => generate() + "\n", force);
|
||||
|
||||
const kindCode = generateKind();
|
||||
const kindOutPath = path.join(ROOT, "tsc/internal/ast/kind_generated.go");
|
||||
writeAndFormat(kindOutPath, kindCode + "\n");
|
||||
writeAndFormat(kindOutPath, () => generateKind() + "\n", force);
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
main();
|
||||
main(parseGeneratorArgs({}).force);
|
||||
}
|
||||
@@ -8,10 +8,14 @@
|
||||
* Usage: node tools/scripts/tsc/generate-ts-ast.ts
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { xSync } from "tinyexec";
|
||||
import { GeneratedFile } from "../gen/generatedFile.mts";
|
||||
import {
|
||||
formatFilesSync,
|
||||
parseGeneratorArgs,
|
||||
repoRoot as ROOT,
|
||||
} from "../gen/utils.mts";
|
||||
import type {
|
||||
MemberInfo,
|
||||
NodeType,
|
||||
@@ -26,8 +30,6 @@ import {
|
||||
// Load schema
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname!, "../../..");
|
||||
|
||||
// TS members only (filter noTS/inherited issues)
|
||||
function tsMembers(node: NodeType): MemberInfo[] {
|
||||
return node.members.filter(member => !member.isKindParam() && !member.noTS);
|
||||
@@ -1810,16 +1812,16 @@ function generateVisitor(): string {
|
||||
// Main
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
function writeAndFormat(filePath: string, content: string) {
|
||||
fs.writeFileSync(filePath, content);
|
||||
xSync("dprint", ["fmt", filePath], {
|
||||
throwOnError: true,
|
||||
nodeOptions: { stdio: "inherit", cwd: ROOT },
|
||||
});
|
||||
function writeAndFormat(filePath: string, generate: () => string, force: boolean) {
|
||||
const generated = new GeneratedFile(filePath, [import.meta.filename, path.join(ROOT, "tools/scripts/tsc/schema.ts"), path.join(ROOT, "tools/scripts/tsc/ast.json")]);
|
||||
if (generated.isCurrent(force)) return;
|
||||
generated.write(generate());
|
||||
formatFilesSync([filePath]);
|
||||
generated.markCurrent();
|
||||
console.log(`Generated ${filePath}`);
|
||||
}
|
||||
|
||||
export default function main() {
|
||||
export default function main(force = false) {
|
||||
console.log("Generating TS AST code...");
|
||||
|
||||
const factoryPath = path.join(ROOT, "packages/typescript/src/ast/factory.generated.ts");
|
||||
@@ -1827,12 +1829,12 @@ export default function main() {
|
||||
const astGenPath = path.join(ROOT, "packages/typescript/src/ast/ast.generated.ts");
|
||||
const visitorPath = path.join(ROOT, "packages/typescript/src/ast/visitor.generated.ts");
|
||||
|
||||
writeAndFormat(astGenPath, generateAstGenerated());
|
||||
writeAndFormat(factoryPath, generateFactory());
|
||||
writeAndFormat(isGenPath, generateIsGenerated());
|
||||
writeAndFormat(visitorPath, generateVisitor());
|
||||
writeAndFormat(astGenPath, generateAstGenerated, force);
|
||||
writeAndFormat(factoryPath, generateFactory, force);
|
||||
writeAndFormat(isGenPath, generateIsGenerated, force);
|
||||
writeAndFormat(visitorPath, generateVisitor, force);
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
main();
|
||||
main(parseGeneratorArgs({}).force);
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseGeneratorArgs } from "../gen/utils.mts";
|
||||
import generateEncoder from "./generate-encoder.ts";
|
||||
import generateGoAST from "./generate-go-ast.ts";
|
||||
import generateTSAST from "./generate-ts-ast.ts";
|
||||
|
||||
export default function generate() {
|
||||
generateEncoder();
|
||||
generateGoAST();
|
||||
generateTSAST();
|
||||
export default function generate(force = false) {
|
||||
generateEncoder(force);
|
||||
generateGoAST(force);
|
||||
generateTSAST(force);
|
||||
}
|
||||
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
generate();
|
||||
generate(parseGeneratorArgs({}).force);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { repoRoot as ROOT } from "../gen/utils.mts";
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Schema type definitions
|
||||
@@ -1260,7 +1261,6 @@ export class SchemaAPI {
|
||||
}
|
||||
}
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname!, "../../..");
|
||||
export const api = new SchemaAPI(JSON.parse(
|
||||
fs.readFileSync(path.join(ROOT, "tools/scripts/tsc/ast.json"), "utf-8"),
|
||||
) as Schema);
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"lib": ["esnext"],
|
||||
"noEmit": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
{
|
||||
"extends": "../gen/tsconfig.json",
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
package ast
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Kind -output=kind_stringer_generated.go
|
||||
//go:generate npx dprint fmt kind_stringer_generated.go
|
||||
//go:generate npx hereby generate:ast-stringer
|
||||
|
||||
type Kind int16
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/microsoft/TypeScript/tsc/internal/vfs"
|
||||
)
|
||||
|
||||
//go:generate go run generate.go
|
||||
//go:generate npx hereby generate:bundled
|
||||
|
||||
// Define the below here to consolidate documentation.
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ import (
|
||||
"github.com/microsoft/TypeScript/tsc/internal/evaluator"
|
||||
)
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=SignatureKind -output=stringer_generated.go
|
||||
//go:generate npx dprint fmt stringer_generated.go
|
||||
//go:generate npx hereby generate:checker
|
||||
|
||||
// ParseFlags
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ import (
|
||||
"github.com/microsoft/TypeScript/tsc/internal/tspath"
|
||||
)
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ModuleKind -trimprefix=ModuleKind -output=modulekind_stringer_generated.go
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ScriptTarget -trimprefix=ScriptTarget -output=scripttarget_stringer_generated.go
|
||||
//go:generate npx dprint fmt modulekind_stringer_generated.go scripttarget_stringer_generated.go
|
||||
//go:generate npx hereby generate:compileroptions
|
||||
|
||||
// CompilerOptions contains the compiler options exposed by the API.
|
||||
type CompilerOptions struct {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package core
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=LanguageVariant -output=languagevariant_stringer_generated.go
|
||||
//go:generate npx dprint fmt languagevariant_stringer_generated.go
|
||||
//go:generate npx hereby generate:languagevariant
|
||||
|
||||
type LanguageVariant int32
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package core
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ScriptKind -output=scriptkind_stringer_generated.go
|
||||
//go:generate npx dprint fmt scriptkind_stringer_generated.go
|
||||
//go:generate npx hereby generate:scriptkind
|
||||
|
||||
type ScriptKind int32
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package core
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Tristate -output=tristate_stringer_generated.go
|
||||
//go:generate npx dprint fmt tristate_stringer_generated.go
|
||||
//go:generate npx hereby generate:tristate
|
||||
|
||||
// Tristate
|
||||
|
||||
|
||||
@@ -13,9 +13,7 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
//go:generate go run generate.go -diagnostics ./diagnostics_generated.go -loc ./loc_generated.go -locdir ./loc
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Category -output=stringer_generated.go
|
||||
//go:generate npx dprint fmt diagnostics_generated.go loc_generated.go stringer_generated.go
|
||||
//go:generate npx hereby generate:diagnostics
|
||||
|
||||
type Category int32
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ import (
|
||||
"github.com/microsoft/TypeScript/tsc/internal/tspath"
|
||||
)
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=ExportSyntax -output=export_stringer_generated.go
|
||||
//go:generate npx dprint fmt export_stringer_generated.go
|
||||
//go:generate npx hereby generate:autoimport
|
||||
|
||||
// ModuleID uniquely identifies a module across multiple declarations.
|
||||
// If the export is from an ambient module declaration, this is the module name.
|
||||
|
||||
@@ -12,28 +12,35 @@ const __dirname = path.dirname(__filename);
|
||||
const metaModelPath = path.join(__dirname, "metaModel.json");
|
||||
const metaModelSchemaPath = path.join(__dirname, "metaModelSchema.mts");
|
||||
|
||||
// Resolve the vscode-languageclient version from the root package-lock.json.
|
||||
const lockfilePath = path.resolve(__dirname, "../../../../../package-lock.json");
|
||||
const lockfile = JSON.parse(fs.readFileSync(lockfilePath, "utf-8"));
|
||||
const clientVersion: string = lockfile.packages["node_modules/vscode-languageclient"].version;
|
||||
export default async function fetchModel() {
|
||||
const lockfilePath = path.resolve(__dirname, "../../../../../package-lock.json");
|
||||
const lockfile = JSON.parse(fs.readFileSync(lockfilePath, "utf-8"));
|
||||
const clientVersion: string = lockfile.packages["node_modules/vscode-languageclient"].version;
|
||||
|
||||
const ref = `release/client/${clientVersion}`;
|
||||
console.log(`Using vscode-languageclient@${clientVersion}`);
|
||||
const ref = `release/client/${clientVersion}`;
|
||||
console.log(`Using vscode-languageclient@${clientVersion}`);
|
||||
|
||||
const metaModelURL = `https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/${ref}/protocol/metaModel.json`;
|
||||
const metaModelSchemaURL = `https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/${ref}/tools/src/metaModel.ts`;
|
||||
const metaModelURL = `https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/${ref}/protocol/metaModel.json`;
|
||||
const metaModelSchemaURL = `https://raw.githubusercontent.com/microsoft/vscode-languageserver-node/${ref}/tools/src/metaModel.ts`;
|
||||
|
||||
const metaModelResponse = await fetch(metaModelURL);
|
||||
const metaModel = await metaModelResponse.text();
|
||||
fs.writeFileSync(metaModelPath, metaModel);
|
||||
const metaModelResponse = await fetch(metaModelURL);
|
||||
if (!metaModelResponse.ok) throw new Error(`Failed to fetch ${metaModelURL}: ${metaModelResponse.status}`);
|
||||
const metaModel = await metaModelResponse.text();
|
||||
JSON.parse(metaModel);
|
||||
|
||||
const metaModelSchemaResponse = await fetch(metaModelSchemaURL);
|
||||
let metaModelSchema = await metaModelSchemaResponse.text();
|
||||
const metaModelSchemaResponse = await fetch(metaModelSchemaURL);
|
||||
if (!metaModelSchemaResponse.ok) throw new Error(`Failed to fetch ${metaModelSchemaURL}: ${metaModelSchemaResponse.status}`);
|
||||
let metaModelSchema = await metaModelSchemaResponse.text();
|
||||
|
||||
// Patch the schema to add omitzeroValue property to Property type
|
||||
metaModelSchema = metaModelSchema.replace(
|
||||
/(\t \* Whether the property is deprecated or not\. If deprecated\n\t \* the property contains the deprecation message\.\n\t \*\/\n\tdeprecated\?: string;)\n}/m,
|
||||
`$1\n\n\t/**\n\t * Whether this property uses omitzero without being a pointer.\n\t * Custom extension for special value types.\n\t */\n\tomitzeroValue?: boolean;\n}`,
|
||||
);
|
||||
metaModelSchema = metaModelSchema.replace(
|
||||
/(\t \* Whether the property is deprecated or not\. If deprecated\n\t \* the property contains the deprecation message\.\n\t \*\/\n\tdeprecated\?: string;)\n}/m,
|
||||
`$1\n\n\t/**\n\t * Whether this property uses omitzero without being a pointer.\n\t * Custom extension for special value types.\n\t */\n\tomitzeroValue?: boolean;\n}`,
|
||||
);
|
||||
|
||||
fs.writeFileSync(metaModelSchemaPath, metaModelSchema);
|
||||
fs.writeFileSync(metaModelPath, metaModel);
|
||||
fs.writeFileSync(metaModelSchemaPath, metaModelSchema);
|
||||
}
|
||||
|
||||
if (process.argv[1] === __filename) {
|
||||
await fetchModel();
|
||||
}
|
||||
@@ -3665,7 +3665,7 @@ function getLocationUriProperty(structure: Structure) {
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
async function main() {
|
||||
export default async function generate() {
|
||||
collectTypeDefinitions();
|
||||
const generatedCode = generateCode();
|
||||
fs.writeFileSync(out, generatedCode);
|
||||
@@ -3678,7 +3678,6 @@ async function main() {
|
||||
console.log(`Successfully generated ${out}`);
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
if (process.argv[1] === __filename) {
|
||||
await generate();
|
||||
}
|
||||
@@ -105,8 +105,7 @@ func ParseSyntheticProjectID(value string) (SyntheticProjectID, bool) {
|
||||
return NewSyntheticProjectID(id), true
|
||||
}
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Kind -trimprefix=Kind -output=project_stringer_generated.go
|
||||
//go:generate npx dprint fmt project_stringer_generated.go
|
||||
//go:generate npx hereby generate:project
|
||||
|
||||
type Kind int
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as fs from "fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import * as path from "path";
|
||||
import { GeneratedFile } from "../../../../tools/scripts/gen/generatedFile.mts";
|
||||
import {
|
||||
formatFiles,
|
||||
parseGeneratorArgs,
|
||||
} from "../../../../tools/scripts/gen/utils.mts";
|
||||
|
||||
// All Unicode data is sourced from a single version-pinned @unicode/unicode-*
|
||||
// package so the generated tables are reproducible regardless of the Node.js
|
||||
@@ -12,8 +17,6 @@ const UNICODE_VERSION = "15.1.0";
|
||||
const PACKAGE = `@unicode/unicode-${UNICODE_VERSION}`;
|
||||
|
||||
const scriptDir = import.meta.dirname;
|
||||
const CASE_OUTPUT_PATH = path.join(scriptDir, "..", "js_case_generated.go");
|
||||
const IDENTIFIER_OUTPUT_PATH = path.join(scriptDir, "..", "identifier_parts_generated.go");
|
||||
|
||||
// A *unicode.RangeTable is split into 16-bit (BMP) and 32-bit (astral) ranges,
|
||||
// each carrying a stride so arithmetic sequences (e.g. the alternating
|
||||
@@ -228,20 +231,40 @@ ${renderRangeTable("unicodeESNextIdentifierPart", partTable)}
|
||||
`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
export default async function generateUnicode(force = false, outDir = path.resolve(scriptDir, "..")) {
|
||||
outDir = path.resolve(outDir);
|
||||
const inputs = [
|
||||
import.meta.filename,
|
||||
fileURLToPath(import.meta.resolve(`${PACKAGE}/package.json`)),
|
||||
];
|
||||
const caseOutput = new GeneratedFile(path.join(outDir, "js_case_generated.go"), inputs);
|
||||
const identifierOutput = new GeneratedFile(path.join(outDir, "identifier_parts_generated.go"), inputs);
|
||||
const generatedFiles = [caseOutput, identifierOutput];
|
||||
if (generatedFiles.every(file => file.isCurrent(force))) {
|
||||
console.log("Unicode tables are up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of generatedFiles) file.invalidate();
|
||||
const simpleLowercase = await loadSimpleMapping("Simple_Case_Mapping/Lowercase");
|
||||
const simpleUppercase = await loadSimpleMapping("Simple_Case_Mapping/Uppercase");
|
||||
const entries = await buildSpecialCasing(simpleLowercase, simpleUppercase);
|
||||
const casedTable = toRangeTable(await loadCodePoints("Binary_Property/Cased"));
|
||||
const caseIgnorableTable = toRangeTable(await loadCodePoints("Binary_Property/Case_Ignorable"));
|
||||
fs.writeFileSync(CASE_OUTPUT_PATH, renderCaseFile(entries, casedTable, caseIgnorableTable));
|
||||
caseOutput.write(renderCaseFile(entries, casedTable, caseIgnorableTable));
|
||||
|
||||
const idStart = await loadCodePoints("Binary_Property/ID_Start");
|
||||
const idContinue = await loadCodePoints("Binary_Property/ID_Continue");
|
||||
// Other_ID_Start/Other_ID_Continue are already folded into ID_Start/ID_Continue.
|
||||
const startTable = toRangeTable(idStart);
|
||||
const partTable = toRangeTable([...idContinue, ...idStart]);
|
||||
fs.writeFileSync(IDENTIFIER_OUTPUT_PATH, renderIdentifierFile(startTable, partTable));
|
||||
identifierOutput.write(renderIdentifierFile(startTable, partTable));
|
||||
await formatFiles(generatedFiles.map(file => file.fileName));
|
||||
for (const file of generatedFiles) file.markCurrent();
|
||||
console.log("Generated Unicode tables.");
|
||||
}
|
||||
|
||||
await main();
|
||||
if (process.argv[1] === import.meta.filename) {
|
||||
const { values, force } = parseGeneratorArgs({ outDir: { type: "string" } });
|
||||
await generateUnicode(force, values.outDir);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
package stringutil
|
||||
|
||||
//go:generate node ./_scripts/generate-unicode-data.mts
|
||||
//go:generate npx dprint fmt js_case_generated.go identifier_parts_generated.go
|
||||
//go:generate npx hereby generate:unicode
|
||||
@@ -23,11 +23,7 @@ import (
|
||||
"github.com/microsoft/TypeScript/tsc/internal/vfs/vfstest"
|
||||
)
|
||||
|
||||
//go:generate go tool github.com/matryer/moq -stub -fmt goimports -pkg projecttestutil -out clientmock_generated.go ../../project Client
|
||||
//go:generate npx dprint fmt clientmock_generated.go
|
||||
|
||||
//go:generate go tool github.com/matryer/moq -stub -fmt goimports -pkg projecttestutil -out npmexecutormock_generated.go ../../project/ata NpmExecutor
|
||||
//go:generate npx dprint fmt npmexecutormock_generated.go
|
||||
//go:generate npx hereby generate:projecttestutil
|
||||
|
||||
const (
|
||||
TestTypingsLocation = "/home/src/Library/Caches/typescript"
|
||||
|
||||
@@ -5,8 +5,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate go tool github.com/matryer/moq -fmt goimports -out vfsmock/mock_generated.go -pkg vfsmock . FS
|
||||
//go:generate npx dprint fmt vfsmock/mock_generated.go
|
||||
//go:generate npx hereby generate:vfs
|
||||
|
||||
// FS is a file system abstraction.
|
||||
type FS interface {
|
||||
|
||||
@@ -12,8 +12,7 @@ import (
|
||||
"github.com/microsoft/TypeScript/tsc/internal/vfs"
|
||||
)
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=Usage -trimprefix=Usage -output=stringer_generated.go
|
||||
//go:generate npx dprint fmt stringer_generated.go
|
||||
//go:generate npx hereby generate:vfsmatch
|
||||
|
||||
// This file implements the glob matching algorithm specified in MATCHING_ALGORITHM.md.
|
||||
|
||||
|
||||
Reference in new issue
Block a user