@dunx/transform 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-yzfpwzqk.js → chunk-x58rytje.js} +15 -10
- package/dist/chunk-x58rytje.js.map +14 -0
- package/dist/erased.d.ts +10 -2
- package/dist/index.js +1 -1
- package/dist/preload.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-yzfpwzqk.js.map +0 -14
|
@@ -45,14 +45,14 @@ var nameOf = (node) => isIdentifier(node) ? node.name : undefined;
|
|
|
45
45
|
|
|
46
46
|
// src/erased.ts
|
|
47
47
|
var collectTypeOnlyNames = (program) => {
|
|
48
|
-
const names = new
|
|
48
|
+
const names = new Map;
|
|
49
49
|
walk(program, (node) => {
|
|
50
50
|
if (isImportDeclaration(node)) {
|
|
51
51
|
for (const specifier of node.specifiers) {
|
|
52
52
|
if (!isImportSpecifier(specifier))
|
|
53
53
|
continue;
|
|
54
54
|
if (node.importKind === "type" || specifier.importKind === "type") {
|
|
55
|
-
names.
|
|
55
|
+
names.set(specifier.local.name, "import-type");
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
return;
|
|
@@ -61,7 +61,7 @@ var collectTypeOnlyNames = (program) => {
|
|
|
61
61
|
const declared = node.id;
|
|
62
62
|
const name = nameOf(declared);
|
|
63
63
|
if (name !== undefined)
|
|
64
|
-
names.
|
|
64
|
+
names.set(name, "declared-type");
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
return names;
|
|
@@ -79,7 +79,10 @@ var collectTypeParameters = (klass) => {
|
|
|
79
79
|
});
|
|
80
80
|
return names;
|
|
81
81
|
};
|
|
82
|
-
var erasedNames = (typeOnly, klass) => new
|
|
82
|
+
var erasedNames = (typeOnly, klass) => new Map([
|
|
83
|
+
...typeOnly,
|
|
84
|
+
...[...collectTypeParameters(klass)].map((name) => [name, "declared-type"])
|
|
85
|
+
]);
|
|
83
86
|
|
|
84
87
|
// src/deps.ts
|
|
85
88
|
var DEPS_KEY = "Symbol.for('dunx.deps')";
|
|
@@ -95,14 +98,16 @@ var annotationOf = (param) => {
|
|
|
95
98
|
return inner.typeAnnotation?.typeAnnotation;
|
|
96
99
|
};
|
|
97
100
|
var entryFor = (source, param, erased) => {
|
|
98
|
-
const
|
|
101
|
+
const text = JSON.stringify(slice(source, param));
|
|
102
|
+
const unresolved = `{ unresolved: ${text} }`;
|
|
99
103
|
const annotation = annotationOf(param);
|
|
100
104
|
if (!annotation || !isTypeReference(annotation))
|
|
101
105
|
return unresolved;
|
|
102
106
|
const token = slice(source, annotation.typeName);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
const cause = erased.get(token);
|
|
108
|
+
if (cause === undefined)
|
|
109
|
+
return token;
|
|
110
|
+
return cause === "import-type" ? `{ unresolved: ${text}, typeOnly: ${JSON.stringify(token)} }` : unresolved;
|
|
106
111
|
};
|
|
107
112
|
var transform = (source, filename = "input.ts") => {
|
|
108
113
|
const parsed = parseSync(filename, source);
|
|
@@ -155,5 +160,5 @@ var depsPlugin = {
|
|
|
155
160
|
|
|
156
161
|
export { applyEdits, transform, depsPlugin };
|
|
157
162
|
|
|
158
|
-
//# debugId=
|
|
159
|
-
//# sourceMappingURL=chunk-
|
|
163
|
+
//# debugId=CF2175FED8CFE81664756E2164756E21
|
|
164
|
+
//# sourceMappingURL=chunk-x58rytje.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/edits.ts", "../src/deps.ts", "../src/ast.ts", "../src/erased.ts", "../src/plugin.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export interface Edit {\n readonly start: number;\n readonly end: number;\n readonly text: string;\n}\n\n/**\n * Splices edits into the original text rather than reprinting the AST, so\n * everything untouched keeps its exact bytes - comments, formatting, and the\n * line numbers stack traces point at.\n */\nexport const applyEdits = (source: string, edits: readonly Edit[]): string => {\n const sorted = [...edits].sort(\n (left, right) => left.start - right.start || left.end - right.end,\n );\n\n let out = '';\n let cursor = 0;\n\n for (const edit of sorted) {\n if (edit.start < cursor) {\n throw new Error(\n `Overlapping edit at ${edit.start}..${edit.end}; the previous edit ended ` +\n `at ${cursor}.`,\n );\n }\n out += source.slice(cursor, edit.start) + edit.text;\n cursor = edit.end;\n }\n\n return out + source.slice(cursor);\n};\n",
|
|
6
|
+
"import { parseSync } from 'oxc-parser';\nimport {\n isClassDeclaration,\n isIdentifier,\n isMethodDefinition,\n isParameterProperty,\n isTypeReference,\n nameOf,\n walk,\n type ClassNode,\n type Node,\n} from './ast.js';\nimport { applyEdits, type Edit } from './edits.js';\nimport {\n collectTypeOnlyNames,\n erasedNames,\n type ErasureCause,\n} from './erased.js';\n\n/**\n * `Symbol.for`, not `Symbol`: two copies of `@dunx/core` in one dependency tree\n * still agree on the key. Same technique as module and route markers.\n */\nconst DEPS_KEY = \"Symbol.for('dunx.deps')\";\n\nexport interface TransformResult {\n readonly code: string;\n readonly changed: boolean;\n /** Classes that received dependency metadata, in source order. */\n readonly annotated: readonly string[];\n}\n\nconst slice = (source: string, node: Node): string =>\n source.slice(node.start, node.end);\n\nconst constructorParams = (klass: ClassNode): readonly Node[] => {\n const found = klass.body.body.find(\n (member) =>\n isMethodDefinition(member) && nameOf(member.key) === 'constructor',\n );\n return isMethodDefinition(found) ? found.value.params : [];\n};\n\n/** The declared type of a parameter, unwrapping `private readonly x: X`. */\nconst annotationOf = (param: Node): Node | undefined => {\n const inner = isParameterProperty(param) ? param.parameter : param;\n if (!isIdentifier(inner)) return undefined;\n return inner.typeAnnotation?.typeAnnotation;\n};\n\n/**\n * One entry per constructor parameter. A parameter whose type names something\n * that exists at runtime becomes that expression; anything else becomes an\n * `unresolved` descriptor so the container can name it precisely at boot instead\n * of constructing a broken object.\n */\nconst entryFor = (\n source: string,\n param: Node,\n erased: ReadonlyMap<string, ErasureCause>,\n): string => {\n const text = JSON.stringify(slice(source, param));\n const unresolved = `{ unresolved: ${text} }`;\n const annotation = annotationOf(param);\n\n if (!annotation || !isTypeReference(annotation)) return unresolved;\n\n const token = slice(source, annotation.typeName);\n // A qualified name (`ns.Thing`) is a runtime value; a bare erased name is not.\n const cause = erased.get(token);\n if (cause === undefined) return token;\n\n // The annotation reads the same whether the name was imported with\n // `import type` or declared as an interface, so the one case with a one-line\n // fix carries the identifier for the boot error to name.\n return cause === 'import-type'\n ? `{ unresolved: ${text}, typeOnly: ${JSON.stringify(token)} }`\n : unresolved;\n};\n\n/**\n * Records each class's constructor dependencies, and each decorated field's\n * declared type, as thunks on the class itself.\n *\n * A thunk, not a literal: the body is evaluated when the record is read rather\n * than when the module is defined, so a dependency declared later in the file -\n * or in a circular import - is not a temporal-dead-zone crash. That is what\n * removes the need for Nest's `forwardRef`, and it is also why a class decorator\n * cannot read the field record while it runs: the statement is appended after the\n * class, which is after decoration.\n */\nexport const transform = (\n source: string,\n filename = 'input.ts',\n): TransformResult => {\n const parsed = parseSync(filename, source);\n\n if (parsed.errors.length > 0) {\n const detail = parsed.errors\n .slice(0, 3)\n .map((error) => error.message)\n .join('; ');\n throw new Error(`${filename}: could not parse - ${detail}`);\n }\n\n const program = parsed.program;\n const typeOnly = collectTypeOnlyNames(program);\n const edits: Edit[] = [];\n const annotated: string[] = [];\n\n walk(program, (node) => {\n if (!isClassDeclaration(node)) return;\n\n const name = node.id?.name;\n if (name === undefined) return;\n\n const erased = erasedNames(typeOnly, node);\n const params = constructorParams(node);\n\n if (params.length > 0) {\n const entries = params.map((param) => entryFor(source, param, erased));\n annotated.push(name);\n edits.push({\n start: node.end,\n end: node.end,\n text:\n `\\nObject.defineProperty(${name}, ${DEPS_KEY}, {\\n` +\n ` value: () => [${entries.join(', ')}],\\n});`,\n });\n }\n });\n\n const code = applyEdits(source, edits);\n return { code, changed: code !== source, annotated };\n};\n",
|
|
7
|
+
"/**\n * Minimal structural views over oxc's ESTree output - only the node shapes the\n * dependency transform reads. Verified against real `oxc-parser` output.\n */\nexport interface Node {\n readonly type: string;\n readonly start: number;\n readonly end: number;\n}\n\nexport interface Identifier extends Node {\n readonly type: 'Identifier';\n readonly name: string;\n readonly typeAnnotation?: TSTypeAnnotation | null;\n}\n\nexport interface ClassBody extends Node {\n readonly type: 'ClassBody';\n readonly body: readonly Node[];\n}\n\n/** `ClassDeclaration` and `ClassExpression` share every field read here. */\nexport interface ClassNode extends Node {\n readonly id: Identifier | null;\n readonly typeParameters: Node | null;\n readonly body: ClassBody;\n}\n\nexport interface FunctionExpression extends Node {\n readonly params: readonly Node[];\n}\n\nexport interface MethodDefinition extends Node {\n readonly type: 'MethodDefinition';\n readonly key: Node;\n readonly value: FunctionExpression;\n}\n\nexport interface TSParameterProperty extends Node {\n readonly type: 'TSParameterProperty';\n readonly parameter: Node;\n}\n\nexport interface TSTypeAnnotation extends Node {\n readonly type: 'TSTypeAnnotation';\n readonly typeAnnotation: Node;\n}\n\nexport interface TSTypeReference extends Node {\n readonly type: 'TSTypeReference';\n readonly typeName: Node;\n}\n\nexport interface ImportDeclaration extends Node {\n readonly type: 'ImportDeclaration';\n readonly specifiers: readonly Node[];\n readonly importKind: 'value' | 'type';\n}\n\nexport interface ImportSpecifier extends Node {\n readonly type: 'ImportSpecifier';\n readonly local: Identifier;\n readonly importKind: 'value' | 'type';\n}\n\nconst guard =\n <T extends Node>(type: T['type']) =>\n (node: Node | null | undefined): node is T =>\n node?.type === type;\n\nexport const isIdentifier = guard<Identifier>('Identifier');\nexport const isMethodDefinition = guard<MethodDefinition>('MethodDefinition');\nexport const isTypeReference = guard<TSTypeReference>('TSTypeReference');\nexport const isImportDeclaration =\n guard<ImportDeclaration>('ImportDeclaration');\nexport const isImportSpecifier = guard<ImportSpecifier>('ImportSpecifier');\nexport const isParameterProperty = guard<TSParameterProperty>(\n 'TSParameterProperty',\n);\n\n/**\n * Declarations only. A `ClassExpression`'s own name is bound inside the class\n * body, not outside it, so a statement appended after `const X = class Foo {}`\n * could not reference `Foo` - it would be a ReferenceError at load.\n */\nexport const isClassDeclaration = (node: Node): node is ClassNode =>\n node.type === 'ClassDeclaration';\n\n/**\n * Depth-first over every object in the tree. oxc's ESTree output is a plain\n * object graph with no parent links, so recursing every own value is safe and\n * needs no visitor-key table.\n */\nexport const walk = (root: unknown, visit: (node: Node) => void): void => {\n if (Array.isArray(root)) {\n for (const child of root) walk(child, visit);\n return;\n }\n if (root === null || typeof root !== 'object') return;\n\n const candidate = root as Partial<Node>;\n if (\n typeof candidate.type === 'string' &&\n typeof candidate.start === 'number' &&\n typeof candidate.end === 'number'\n ) {\n visit(root as Node);\n }\n\n for (const child of Object.values(root)) walk(child, visit);\n};\n\nexport const nameOf = (node: Node | null | undefined): string | undefined =>\n isIdentifier(node) ? node.name : undefined;\n",
|
|
8
|
+
"import {\n isImportDeclaration,\n isImportSpecifier,\n nameOf,\n walk,\n type ClassNode,\n type Node,\n} from './ast.js';\n\n/**\n * Why a name cannot be used in a value position. Kept apart because only one of\n * these has a one-line fix: an `import type` becomes a value import, whereas an\n * interface has no runtime counterpart to import at all. The boot error quotes\n * the annotation, which reads identically either way, so without this the\n * message points at a line that is already correct.\n */\nexport type ErasureCause = 'import-type' | 'declared-type';\n\n/**\n * Names that exist only in the type system, so emitting them in a value position\n * would be a `ReferenceError` at runtime. Collected per file: type-only imports,\n * inline `type` specifiers, local interfaces and type aliases.\n */\nexport const collectTypeOnlyNames = (\n program: Node,\n): ReadonlyMap<string, ErasureCause> => {\n const names = new Map<string, ErasureCause>();\n\n walk(program, (node) => {\n if (isImportDeclaration(node)) {\n for (const specifier of node.specifiers) {\n if (!isImportSpecifier(specifier)) continue;\n if (node.importKind === 'type' || specifier.importKind === 'type') {\n names.set(specifier.local.name, 'import-type');\n }\n }\n return;\n }\n\n if (\n node.type === 'TSInterfaceDeclaration' ||\n node.type === 'TSTypeAliasDeclaration'\n ) {\n const declared = (node as { id?: Node }).id;\n const name = nameOf(declared);\n if (name !== undefined) names.set(name, 'declared-type');\n }\n });\n\n return names;\n};\n\n/** A class's own type parameters are erased, so `T` is never a usable token. */\nexport const collectTypeParameters = (\n klass: ClassNode,\n): ReadonlySet<string> => {\n const names = new Set<string>();\n if (klass.typeParameters === null) return names;\n\n walk(klass.typeParameters, (node) => {\n if (node.type !== 'TSTypeParameter') return;\n const name = nameOf((node as { name?: Node }).name);\n if (name !== undefined) names.add(name);\n });\n\n return names;\n};\n\n/** Every name in this file that a value position cannot use. */\nexport const erasedNames = (\n typeOnly: ReadonlyMap<string, ErasureCause>,\n klass: ClassNode,\n): ReadonlyMap<string, ErasureCause> =>\n new Map<string, ErasureCause>([\n ...typeOnly,\n // A type parameter is erased for the same reason an interface is: there is\n // nothing to import.\n ...[...collectTypeParameters(klass)].map(\n (name) => [name, 'declared-type'] as const,\n ),\n ]);\n",
|
|
9
|
+
"import type { BunPlugin } from 'bun';\nimport { transform } from './deps.js';\n\n/**\n * Rewrites TypeScript as it is loaded so the container can read constructor\n * dependencies. Usable in three places, all the same object:\n *\n * - `bunfig.toml` -> `preload = [\"@dunx/transform/preload\"]` for `bun run`\n * - `Bun.build({ plugins: [depsPlugin] })` for a production build\n * - `Bun.plugin(depsPlugin)` from a test preload\n *\n * Dependencies are skipped: a published package was already transformed by its\n * own build, and re-parsing `node_modules` on every load is pure cost.\n */\nexport const depsPlugin: BunPlugin = {\n name: 'dunx-deps',\n setup(build) {\n // A runtime plugin's onLoad must always return a result - there is no\n // \"decline and fall through\", so untransformed files are handed back as-is.\n build.onLoad({ filter: /\\.tsx?$/ }, async ({ path }) => {\n const source = await Bun.file(path).text();\n const loader = path.endsWith('.tsx') ? 'tsx' : 'ts';\n\n if (path.includes('/node_modules/')) return { contents: source, loader };\n\n return { contents: transform(source, path).code, loader };\n });\n },\n};\n"
|
|
10
|
+
],
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "CF2175FED8CFE81664756E2164756E21",
|
|
13
|
+
"names": []
|
|
14
|
+
}
|
package/dist/erased.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { type ClassNode, type Node } from './ast.js';
|
|
2
|
+
/**
|
|
3
|
+
* Why a name cannot be used in a value position. Kept apart because only one of
|
|
4
|
+
* these has a one-line fix: an `import type` becomes a value import, whereas an
|
|
5
|
+
* interface has no runtime counterpart to import at all. The boot error quotes
|
|
6
|
+
* the annotation, which reads identically either way, so without this the
|
|
7
|
+
* message points at a line that is already correct.
|
|
8
|
+
*/
|
|
9
|
+
export type ErasureCause = 'import-type' | 'declared-type';
|
|
2
10
|
/**
|
|
3
11
|
* Names that exist only in the type system, so emitting them in a value position
|
|
4
12
|
* would be a `ReferenceError` at runtime. Collected per file: type-only imports,
|
|
5
13
|
* inline `type` specifiers, local interfaces and type aliases.
|
|
6
14
|
*/
|
|
7
|
-
export declare const collectTypeOnlyNames: (program: Node) =>
|
|
15
|
+
export declare const collectTypeOnlyNames: (program: Node) => ReadonlyMap<string, ErasureCause>;
|
|
8
16
|
/** A class's own type parameters are erased, so `T` is never a usable token. */
|
|
9
17
|
export declare const collectTypeParameters: (klass: ClassNode) => ReadonlySet<string>;
|
|
10
18
|
/** Every name in this file that a value position cannot use. */
|
|
11
|
-
export declare const erasedNames: (typeOnly:
|
|
19
|
+
export declare const erasedNames: (typeOnly: ReadonlyMap<string, ErasureCause>, klass: ClassNode) => ReadonlyMap<string, ErasureCause>;
|
package/dist/index.js
CHANGED
package/dist/preload.js
CHANGED
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/edits.ts", "../src/deps.ts", "../src/ast.ts", "../src/erased.ts", "../src/plugin.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"export interface Edit {\n readonly start: number;\n readonly end: number;\n readonly text: string;\n}\n\n/**\n * Splices edits into the original text rather than reprinting the AST, so\n * everything untouched keeps its exact bytes - comments, formatting, and the\n * line numbers stack traces point at.\n */\nexport const applyEdits = (source: string, edits: readonly Edit[]): string => {\n const sorted = [...edits].sort(\n (left, right) => left.start - right.start || left.end - right.end,\n );\n\n let out = '';\n let cursor = 0;\n\n for (const edit of sorted) {\n if (edit.start < cursor) {\n throw new Error(\n `Overlapping edit at ${edit.start}..${edit.end}; the previous edit ended ` +\n `at ${cursor}.`,\n );\n }\n out += source.slice(cursor, edit.start) + edit.text;\n cursor = edit.end;\n }\n\n return out + source.slice(cursor);\n};\n",
|
|
6
|
-
"import { parseSync } from 'oxc-parser';\nimport {\n isClassDeclaration,\n isIdentifier,\n isMethodDefinition,\n isParameterProperty,\n isTypeReference,\n nameOf,\n walk,\n type ClassNode,\n type Node,\n} from './ast.js';\nimport { applyEdits, type Edit } from './edits.js';\nimport { collectTypeOnlyNames, erasedNames } from './erased.js';\n\n/**\n * `Symbol.for`, not `Symbol`: two copies of `@dunx/core` in one dependency tree\n * still agree on the key. Same technique as module and route markers.\n */\nconst DEPS_KEY = \"Symbol.for('dunx.deps')\";\n\nexport interface TransformResult {\n readonly code: string;\n readonly changed: boolean;\n /** Classes that received dependency metadata, in source order. */\n readonly annotated: readonly string[];\n}\n\nconst slice = (source: string, node: Node): string =>\n source.slice(node.start, node.end);\n\nconst constructorParams = (klass: ClassNode): readonly Node[] => {\n const found = klass.body.body.find(\n (member) =>\n isMethodDefinition(member) && nameOf(member.key) === 'constructor',\n );\n return isMethodDefinition(found) ? found.value.params : [];\n};\n\n/** The declared type of a parameter, unwrapping `private readonly x: X`. */\nconst annotationOf = (param: Node): Node | undefined => {\n const inner = isParameterProperty(param) ? param.parameter : param;\n if (!isIdentifier(inner)) return undefined;\n return inner.typeAnnotation?.typeAnnotation;\n};\n\n/**\n * One entry per constructor parameter. A parameter whose type names something\n * that exists at runtime becomes that expression; anything else becomes an\n * `unresolved` descriptor so the container can name it precisely at boot instead\n * of constructing a broken object.\n */\nconst entryFor = (\n source: string,\n param: Node,\n erased: ReadonlySet<string>,\n): string => {\n const unresolved = `{ unresolved: ${JSON.stringify(slice(source, param))} }`;\n const annotation = annotationOf(param);\n\n if (!annotation || !isTypeReference(annotation)) return unresolved;\n\n const token = slice(source, annotation.typeName);\n // A qualified name (`ns.Thing`) is a runtime value; a bare erased name is not.\n if (erased.has(token)) return unresolved;\n\n return token;\n};\n\n/**\n * Records each class's constructor dependencies, and each decorated field's\n * declared type, as thunks on the class itself.\n *\n * A thunk, not a literal: the body is evaluated when the record is read rather\n * than when the module is defined, so a dependency declared later in the file -\n * or in a circular import - is not a temporal-dead-zone crash. That is what\n * removes the need for Nest's `forwardRef`, and it is also why a class decorator\n * cannot read the field record while it runs: the statement is appended after the\n * class, which is after decoration.\n */\nexport const transform = (\n source: string,\n filename = 'input.ts',\n): TransformResult => {\n const parsed = parseSync(filename, source);\n\n if (parsed.errors.length > 0) {\n const detail = parsed.errors\n .slice(0, 3)\n .map((error) => error.message)\n .join('; ');\n throw new Error(`${filename}: could not parse - ${detail}`);\n }\n\n const program = parsed.program;\n const typeOnly = collectTypeOnlyNames(program);\n const edits: Edit[] = [];\n const annotated: string[] = [];\n\n walk(program, (node) => {\n if (!isClassDeclaration(node)) return;\n\n const name = node.id?.name;\n if (name === undefined) return;\n\n const erased = erasedNames(typeOnly, node);\n const params = constructorParams(node);\n\n if (params.length > 0) {\n const entries = params.map((param) => entryFor(source, param, erased));\n annotated.push(name);\n edits.push({\n start: node.end,\n end: node.end,\n text:\n `\\nObject.defineProperty(${name}, ${DEPS_KEY}, {\\n` +\n ` value: () => [${entries.join(', ')}],\\n});`,\n });\n }\n });\n\n const code = applyEdits(source, edits);\n return { code, changed: code !== source, annotated };\n};\n",
|
|
7
|
-
"/**\n * Minimal structural views over oxc's ESTree output - only the node shapes the\n * dependency transform reads. Verified against real `oxc-parser` output.\n */\nexport interface Node {\n readonly type: string;\n readonly start: number;\n readonly end: number;\n}\n\nexport interface Identifier extends Node {\n readonly type: 'Identifier';\n readonly name: string;\n readonly typeAnnotation?: TSTypeAnnotation | null;\n}\n\nexport interface ClassBody extends Node {\n readonly type: 'ClassBody';\n readonly body: readonly Node[];\n}\n\n/** `ClassDeclaration` and `ClassExpression` share every field read here. */\nexport interface ClassNode extends Node {\n readonly id: Identifier | null;\n readonly typeParameters: Node | null;\n readonly body: ClassBody;\n}\n\nexport interface FunctionExpression extends Node {\n readonly params: readonly Node[];\n}\n\nexport interface MethodDefinition extends Node {\n readonly type: 'MethodDefinition';\n readonly key: Node;\n readonly value: FunctionExpression;\n}\n\nexport interface TSParameterProperty extends Node {\n readonly type: 'TSParameterProperty';\n readonly parameter: Node;\n}\n\nexport interface TSTypeAnnotation extends Node {\n readonly type: 'TSTypeAnnotation';\n readonly typeAnnotation: Node;\n}\n\nexport interface TSTypeReference extends Node {\n readonly type: 'TSTypeReference';\n readonly typeName: Node;\n}\n\nexport interface ImportDeclaration extends Node {\n readonly type: 'ImportDeclaration';\n readonly specifiers: readonly Node[];\n readonly importKind: 'value' | 'type';\n}\n\nexport interface ImportSpecifier extends Node {\n readonly type: 'ImportSpecifier';\n readonly local: Identifier;\n readonly importKind: 'value' | 'type';\n}\n\nconst guard =\n <T extends Node>(type: T['type']) =>\n (node: Node | null | undefined): node is T =>\n node?.type === type;\n\nexport const isIdentifier = guard<Identifier>('Identifier');\nexport const isMethodDefinition = guard<MethodDefinition>('MethodDefinition');\nexport const isTypeReference = guard<TSTypeReference>('TSTypeReference');\nexport const isImportDeclaration =\n guard<ImportDeclaration>('ImportDeclaration');\nexport const isImportSpecifier = guard<ImportSpecifier>('ImportSpecifier');\nexport const isParameterProperty = guard<TSParameterProperty>(\n 'TSParameterProperty',\n);\n\n/**\n * Declarations only. A `ClassExpression`'s own name is bound inside the class\n * body, not outside it, so a statement appended after `const X = class Foo {}`\n * could not reference `Foo` - it would be a ReferenceError at load.\n */\nexport const isClassDeclaration = (node: Node): node is ClassNode =>\n node.type === 'ClassDeclaration';\n\n/**\n * Depth-first over every object in the tree. oxc's ESTree output is a plain\n * object graph with no parent links, so recursing every own value is safe and\n * needs no visitor-key table.\n */\nexport const walk = (root: unknown, visit: (node: Node) => void): void => {\n if (Array.isArray(root)) {\n for (const child of root) walk(child, visit);\n return;\n }\n if (root === null || typeof root !== 'object') return;\n\n const candidate = root as Partial<Node>;\n if (\n typeof candidate.type === 'string' &&\n typeof candidate.start === 'number' &&\n typeof candidate.end === 'number'\n ) {\n visit(root as Node);\n }\n\n for (const child of Object.values(root)) walk(child, visit);\n};\n\nexport const nameOf = (node: Node | null | undefined): string | undefined =>\n isIdentifier(node) ? node.name : undefined;\n",
|
|
8
|
-
"import {\n isImportDeclaration,\n isImportSpecifier,\n nameOf,\n walk,\n type ClassNode,\n type Node,\n} from './ast.js';\n\n/**\n * Names that exist only in the type system, so emitting them in a value position\n * would be a `ReferenceError` at runtime. Collected per file: type-only imports,\n * inline `type` specifiers, local interfaces and type aliases.\n */\nexport const collectTypeOnlyNames = (program: Node): ReadonlySet<string> => {\n const names = new Set<string>();\n\n walk(program, (node) => {\n if (isImportDeclaration(node)) {\n for (const specifier of node.specifiers) {\n if (!isImportSpecifier(specifier)) continue;\n if (node.importKind === 'type' || specifier.importKind === 'type') {\n names.add(specifier.local.name);\n }\n }\n return;\n }\n\n if (\n node.type === 'TSInterfaceDeclaration' ||\n node.type === 'TSTypeAliasDeclaration'\n ) {\n const declared = (node as { id?: Node }).id;\n const name = nameOf(declared);\n if (name !== undefined) names.add(name);\n }\n });\n\n return names;\n};\n\n/** A class's own type parameters are erased, so `T` is never a usable token. */\nexport const collectTypeParameters = (\n klass: ClassNode,\n): ReadonlySet<string> => {\n const names = new Set<string>();\n if (klass.typeParameters === null) return names;\n\n walk(klass.typeParameters, (node) => {\n if (node.type !== 'TSTypeParameter') return;\n const name = nameOf((node as { name?: Node }).name);\n if (name !== undefined) names.add(name);\n });\n\n return names;\n};\n\n/** Every name in this file that a value position cannot use. */\nexport const erasedNames = (\n typeOnly: ReadonlySet<string>,\n klass: ClassNode,\n): ReadonlySet<string> =>\n new Set([...typeOnly, ...collectTypeParameters(klass)]);\n",
|
|
9
|
-
"import type { BunPlugin } from 'bun';\nimport { transform } from './deps.js';\n\n/**\n * Rewrites TypeScript as it is loaded so the container can read constructor\n * dependencies. Usable in three places, all the same object:\n *\n * - `bunfig.toml` -> `preload = [\"@dunx/transform/preload\"]` for `bun run`\n * - `Bun.build({ plugins: [depsPlugin] })` for a production build\n * - `Bun.plugin(depsPlugin)` from a test preload\n *\n * Dependencies are skipped: a published package was already transformed by its\n * own build, and re-parsing `node_modules` on every load is pure cost.\n */\nexport const depsPlugin: BunPlugin = {\n name: 'dunx-deps',\n setup(build) {\n // A runtime plugin's onLoad must always return a result - there is no\n // \"decline and fall through\", so untransformed files are handed back as-is.\n build.onLoad({ filter: /\\.tsx?$/ }, async ({ path }) => {\n const source = await Bun.file(path).text();\n const loader = path.endsWith('.tsx') ? 'tsx' : 'ts';\n\n if (path.includes('/node_modules/')) return { contents: source, loader };\n\n return { contents: transform(source, path).code, loader };\n });\n },\n};\n"
|
|
10
|
-
],
|
|
11
|
-
"mappings": "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",
|
|
12
|
-
"debugId": "1B91E58995A13AAC64756E2164756E21",
|
|
13
|
-
"names": []
|
|
14
|
-
}
|