Files
test-repo/scripts/build/modify-typescript-module.mjs
T

302 lines
7.6 KiB
JavaScript

import path from "node:path";
import escapeStringRegexp from "escape-string-regexp";
import { outdent } from "outdent";
import MagicString from "magic-string";
import { writeFile, PROJECT_ROOT } from "../utils/index.mjs";
function* getModules(text) {
const parts = text.split(/(?<=\n)( {2}\/\/ src\/\S+\n)/);
let start = parts[0].length;
for (let partIndex = 1; partIndex < parts.length - 1; partIndex += 2) {
const comment = parts[partIndex];
const code = parts[partIndex + 1];
const path = comment.slice(" // ".length, -1);
const end = start + comment.length + code.length;
if (/\S/.test(code)) {
const esmRegExp = new RegExp(
[
"\\s*var (?<initFunctionName>\\w+) = __esm\\({",
`\\s*"${escapeStringRegexp(path)}"\\(\\) {`,
".*",
"\\s*}",
"\\s*}\\);",
].join("\\n"),
"s"
);
const match = code.match(esmRegExp);
yield {
isEntry: path === "src/typescript/typescript.ts",
path,
start: start + comment.length,
end: end - 1,
code,
esmModuleInitFunctionName: match?.groups.initFunctionName,
};
}
start = end;
}
}
class TypeScriptModuleSource {
#source;
modules;
constructor(text) {
this.#source = new MagicString(text);
this.modules = [...getModules(text)];
}
replaceModule(module, replacement) {
if (typeof module === "string") {
module = this.modules.find((searching) => searching.path === module);
}
if (!module) {
throw Object.assign(new Error("Module not found"), { module });
}
const { esmModuleInitFunctionName } = module;
const moduleInitCode = esmModuleInitFunctionName
? `var ${esmModuleInitFunctionName} = () => {};`
: "";
this.#source.overwrite(
module.start,
module.end,
moduleInitCode + replacement
);
return this;
}
removeModule(module) {
if (typeof module === "string") {
module = this.modules.find((searching) => searching.path === module);
}
return this.replaceModule(module, "");
}
replaceAlignedCode({ start, end, replacement = "" }) {
const regexp = new RegExp(
[
"(?<=\n)",
"(?<indentString>\\s*)",
escapeStringRegexp(start),
".*?",
"(?<=\n)",
"\\k<indentString>",
escapeStringRegexp(end),
"(?=\n)",
].join(""),
"gsu"
);
this.#source.replaceAll(regexp, replacement);
return this;
}
remove(...args) {
this.#source.remove(...args);
return this;
}
prepend(...args) {
this.#source.prepend(...args);
return this;
}
append(...args) {
this.#source.append(...args);
return this;
}
replace(...args) {
this.#source.replace(...args);
return this;
}
replaceAll(...args) {
this.#source.replaceAll(...args);
return this;
}
applyChanges() {
const text = this.#source.toString();
this.#source = new MagicString(text);
this.modules = getModules(text);
}
toString() {
return this.#source.toString();
}
}
function unwrap(text) {
const startMark = "var ts = (() => {";
const endMark = "return require_typescript();";
const start = text.indexOf(startMark);
const end = text.lastIndexOf(endMark);
if (start === -1 || end === -1) {
throw new Error("Unexpected source");
}
return text.slice(start + startMark.length, end);
}
function modifyTypescriptModule(text) {
text = unwrap(text);
const source = new TypeScriptModuleSource(text);
// Deprecated
for (const module of source.modules) {
if (module.path.startsWith("src/deprecatedCompat/")) {
source.removeModule(module);
}
}
// jsTyping
for (const module of source.modules) {
if (module.path.startsWith("src/jsTyping/")) {
source.removeModule(module);
}
}
// services
for (const module of source.modules) {
if (
module.path === "src/services/services.ts" ||
module.path === "src/services/_namespaces/ts.ts"
) {
continue;
}
// This is a big module, most code except `scanner` is not used
if (module.path === "src/services/utilities.ts") {
source.replaceModule(
module,
outdent`
var scanner;
var ${module.esmModuleInitFunctionName} = () => {
init_debug();
scanner = createScanner(99 /* Latest */, /*skipTrivia*/ true);
};
`
);
continue;
}
if (module.path.startsWith("src/services/")) {
source.removeModule(module);
}
}
// `transformers`
source.removeModule("src/compiler/transformer.ts");
for (const module of source.modules) {
if (module.path.startsWith("src/compiler/transformers/")) {
source.removeModule(module);
}
}
// `ts.moduleSpecifiers`
source.removeModule("src/compiler/_namespaces/ts.moduleSpecifiers.ts");
source.removeModule("src/compiler/moduleSpecifiers.ts");
// Sourcemap
source.removeModule("src/compiler/sourcemap.ts");
// watch
source.removeModule("src/compiler/watch.ts");
source.removeModule("src/compiler/watchPublic.ts");
source.removeModule("src/compiler/watchUtilities.ts");
// build
source.removeModule("src/compiler/commandLineParser.ts");
source.removeModule("src/compiler/builder.ts");
source.removeModule("src/compiler/builderPublic.ts");
source.removeModule("src/compiler/resolutionCache.ts");
source.removeModule("src/compiler/tsbuild.ts");
source.removeModule("src/compiler/tsbuildPublic.ts");
source.removeModule("src/compiler/builderState.ts");
source.removeModule("src/compiler/builderStatePublic.ts");
// Misc
source.removeModule("src/compiler/symbolWalker.ts");
source.removeModule("src/compiler/binder.ts");
source.removeModule("src/compiler/semver.ts");
source.removeModule("src/compiler/program.ts");
source.removeModule("src/compiler/moduleNameResolver.ts");
source.removeModule("src/compiler/checker.ts");
source.removeModule("src/compiler/visitorPublic.ts");
source.removeModule("src/compiler/emitter.ts");
source.removeModule("src/compiler/_namespaces/ts.performance.ts");
// File system
source.replaceModule("src/compiler/sys.ts", "var sys;");
source.replaceModule("src/compiler/tracing.ts", "var tracing;");
// perfLogger
source.replaceModule(
"src/compiler/perfLogger.ts",
"var perfLogger = new Proxy(() => {}, {get: () => perfLogger});"
);
// performanceCore
source.replaceModule(
"src/compiler/performanceCore.ts",
outdent`
var tryGetNativePerformanceHooks = () => {};
var timestamp = Date.now;
`
);
// `factory`
source.removeModule("src/compiler/factory/emitNode.ts");
source.removeModule("src/compiler/factory/emitHelpers.ts");
source.replaceModule(
"src/compiler/factory/nodeConverters.ts",
outdent`
var createNodeConverters = () => new Proxy({}, {get: () => () => {}});
`
);
/* spell-checker: disable */
// `ts.createParenthesizerRules`
source.replaceAlignedCode({
start: "function createParenthesizerRules(",
end: "}",
});
/* spell-checker: enable */
source.replaceAlignedCode({
start: "function getScriptTargetFeatures(",
end: "}",
});
source.replaceAlignedCode({
start: "var __require = ",
end: "});",
});
source.append("module.exports = require_typescript();");
return source.toString();
}
// Save modified code to `{PROJECT_ROOT}/.tmp/modified-typescript.js` for debug
const saveOutputToDisk = (process) => (text) => {
const result = process(text);
writeFile(path.join(PROJECT_ROOT, ".tmp/modified-typescript.js"), result);
return result;
};
export default saveOutputToDisk(modifyTypescriptModule);