xbintsc 0.3.14 → 0.3.16

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.
Files changed (201) hide show
  1. package/README.md +22 -22
  2. package/README.zh-CN.md +20 -19
  3. package/bin/xbintsc.js +1 -2
  4. package/dist/src/cli/main.js +8 -5
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/state.js +8 -0
  7. package/dist/src/codegen/generator/state.js.map +1 -1
  8. package/dist/src/codegen/generator/tables.d.ts +5 -6
  9. package/dist/src/codegen/generator/tables.js +13 -6
  10. package/dist/src/codegen/generator/tables.js.map +1 -1
  11. package/dist/src/driver/bundler/graph.d.ts +8 -0
  12. package/dist/src/driver/bundler/graph.js +85 -0
  13. package/dist/src/driver/bundler/graph.js.map +1 -0
  14. package/dist/src/driver/bundler/merge.d.ts +11 -0
  15. package/dist/src/driver/bundler/merge.js +288 -0
  16. package/dist/src/driver/bundler/merge.js.map +1 -0
  17. package/dist/src/driver/bundler/resolve.d.ts +12 -0
  18. package/dist/src/driver/bundler/resolve.js +246 -0
  19. package/dist/src/driver/bundler/resolve.js.map +1 -0
  20. package/dist/src/driver/bundler/symbols.d.ts +15 -0
  21. package/dist/src/driver/bundler/symbols.js +51 -0
  22. package/dist/src/driver/bundler/symbols.js.map +1 -0
  23. package/dist/src/driver/bundler/types.d.ts +36 -0
  24. package/dist/src/driver/bundler/types.js +5 -0
  25. package/dist/src/driver/bundler/types.js.map +1 -0
  26. package/dist/src/driver/index.d.ts +1 -2
  27. package/dist/src/driver/index.js +1 -2
  28. package/dist/src/driver/index.js.map +1 -1
  29. package/dist/src/driver/modules.d.ts +9 -16
  30. package/dist/src/driver/modules.js +8 -644
  31. package/dist/src/driver/modules.js.map +1 -1
  32. package/dist/src/driver/paths.d.ts +0 -9
  33. package/dist/src/driver/paths.js +0 -31
  34. package/dist/src/driver/paths.js.map +1 -1
  35. package/dist/src/driver/toolchain-provider.d.ts +10 -10
  36. package/dist/src/driver/toolchain-provider.js +9 -38
  37. package/dist/src/driver/toolchain-provider.js.map +1 -1
  38. package/dist/src/driver/toolchain.js +8 -2
  39. package/dist/src/driver/toolchain.js.map +1 -1
  40. package/dist/src/extensions/catalog.js +2 -1
  41. package/dist/src/extensions/catalog.js.map +1 -1
  42. package/dist/src/extensions/gui/index.d.ts +24 -0
  43. package/dist/src/extensions/gui/index.js +101 -0
  44. package/dist/src/extensions/gui/index.js.map +1 -0
  45. package/dist/src/extensions/native.d.ts +2 -3
  46. package/dist/src/extensions/native.js +2 -3
  47. package/dist/src/extensions/native.js.map +1 -1
  48. package/dist/tests/driver/paths.test.js +1 -9
  49. package/dist/tests/driver/paths.test.js.map +1 -1
  50. package/dist/tests/driver/toolchain-provider.test.js +12 -79
  51. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  52. package/dist/tests/e2e/compile-basics.test.d.ts +4 -0
  53. package/dist/tests/e2e/compile-basics.test.js +134 -0
  54. package/dist/tests/e2e/compile-basics.test.js.map +1 -0
  55. package/dist/tests/e2e/compile-builtins.test.d.ts +4 -0
  56. package/dist/tests/e2e/compile-builtins.test.js +191 -0
  57. package/dist/tests/e2e/compile-builtins.test.js.map +1 -0
  58. package/dist/tests/e2e/compile-control-flow.test.d.ts +4 -0
  59. package/dist/tests/e2e/compile-control-flow.test.js +190 -0
  60. package/dist/tests/e2e/compile-control-flow.test.js.map +1 -0
  61. package/dist/tests/e2e/compile-modules.test.d.ts +4 -0
  62. package/dist/tests/e2e/compile-modules.test.js +48 -0
  63. package/dist/tests/e2e/compile-modules.test.js.map +1 -0
  64. package/dist/tests/e2e/compile-objects-classes.test.d.ts +4 -0
  65. package/dist/tests/e2e/compile-objects-classes.test.js +362 -0
  66. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -0
  67. package/dist/tests/e2e/differential-helpers.d.ts +12 -0
  68. package/dist/tests/e2e/differential-helpers.js +16 -0
  69. package/dist/tests/e2e/differential-helpers.js.map +1 -0
  70. package/dist/tests/e2e/differential-language.test.d.ts +4 -0
  71. package/dist/tests/e2e/{differential.test.js → differential-language.test.js} +5 -287
  72. package/dist/tests/e2e/differential-language.test.js.map +1 -0
  73. package/dist/tests/e2e/differential-symbols.test.d.ts +4 -0
  74. package/dist/tests/e2e/differential-symbols.test.js +92 -0
  75. package/dist/tests/e2e/differential-symbols.test.js.map +1 -0
  76. package/dist/tests/e2e/differential-values.test.d.ts +4 -0
  77. package/dist/tests/e2e/differential-values.test.js +197 -0
  78. package/dist/tests/e2e/differential-values.test.js.map +1 -0
  79. package/dist/tests/e2e/gui.test.d.ts +12 -0
  80. package/dist/tests/e2e/gui.test.js +493 -0
  81. package/dist/tests/e2e/gui.test.js.map +1 -0
  82. package/dist/tests/e2e/native-extensions.test.d.ts +4 -5
  83. package/dist/tests/e2e/native-extensions.test.js +6 -7
  84. package/dist/tests/e2e/native-extensions.test.js.map +1 -1
  85. package/dist/tests/e2e/runtime-coverage-buffer.test.d.ts +4 -0
  86. package/dist/tests/e2e/runtime-coverage-buffer.test.js +97 -0
  87. package/dist/tests/e2e/runtime-coverage-buffer.test.js.map +1 -0
  88. package/dist/tests/e2e/runtime-coverage-globals.test.d.ts +4 -0
  89. package/dist/tests/e2e/runtime-coverage-globals.test.js +149 -0
  90. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -0
  91. package/dist/tests/e2e/runtime-coverage-node-modules.test.d.ts +4 -0
  92. package/dist/tests/e2e/runtime-coverage-node-modules.test.js +177 -0
  93. package/dist/tests/e2e/runtime-coverage-node-modules.test.js.map +1 -0
  94. package/dist/tests/e2e/runtime-coverage-streams.test.d.ts +4 -0
  95. package/dist/tests/e2e/runtime-coverage-streams.test.js +161 -0
  96. package/dist/tests/e2e/runtime-coverage-streams.test.js.map +1 -0
  97. package/dist/tests/e2e/runtime-coverage-system.test.d.ts +4 -0
  98. package/dist/tests/e2e/runtime-coverage-system.test.js +91 -0
  99. package/dist/tests/e2e/runtime-coverage-system.test.js.map +1 -0
  100. package/dist/tests/e2e/timers.test.d.ts +7 -0
  101. package/dist/tests/e2e/timers.test.js +63 -0
  102. package/dist/tests/e2e/timers.test.js.map +1 -0
  103. package/package.json +2 -2
  104. package/runtime/ext_gui/css.cpp +743 -0
  105. package/runtime/ext_gui/css.h +98 -0
  106. package/runtime/ext_gui/document.cpp +264 -0
  107. package/runtime/ext_gui/document.h +89 -0
  108. package/runtime/ext_gui/dom.cpp +472 -0
  109. package/runtime/ext_gui/dom.h +71 -0
  110. package/runtime/ext_gui/gui.cpp +457 -0
  111. package/runtime/ext_gui/gui_engine.h +81 -0
  112. package/runtime/ext_gui/image.cpp +125 -0
  113. package/runtime/ext_gui/image.h +42 -0
  114. package/runtime/ext_gui/layout.cpp +748 -0
  115. package/runtime/ext_gui/layout.h +93 -0
  116. package/runtime/ext_gui/paint.cpp +137 -0
  117. package/runtime/ext_gui/paint.h +75 -0
  118. package/runtime/ext_gui/renderer.cpp +959 -0
  119. package/runtime/ext_gui/renderer.h +68 -0
  120. package/runtime/ext_gui/stb_image.h +7988 -0
  121. package/runtime/ext_gui/style.cpp +771 -0
  122. package/runtime/ext_gui/style.h +117 -0
  123. package/runtime/ext_gui/text.cpp +378 -0
  124. package/runtime/ext_gui/text.h +78 -0
  125. package/runtime/ext_gui/window.cpp +405 -0
  126. package/runtime/ext_node/fs/fs_common.h +13 -527
  127. package/runtime/ext_node/fs/meta_ops.c +3 -3
  128. package/runtime/ext_node/fs/parts/buffer-bridge.inc +6 -0
  129. package/runtime/ext_node/fs/parts/builtins.inc +69 -0
  130. package/runtime/ext_node/fs/parts/dir.inc +47 -0
  131. package/runtime/ext_node/fs/parts/encoding.inc +186 -0
  132. package/runtime/ext_node/fs/parts/errors.inc +66 -0
  133. package/runtime/ext_node/fs/parts/flags.inc +39 -0
  134. package/runtime/ext_node/fs/parts/platform.inc +86 -0
  135. package/runtime/ext_node/fs/parts/promisify.inc +15 -0
  136. package/runtime/ext_node/fs/parts/time.inc +9 -0
  137. package/runtime/rt.h +36 -4
  138. package/runtime/rt_internal.h +10 -23
  139. package/runtime/xt_builtins.c +4 -0
  140. package/runtime/xt_containers/access.inc +168 -0
  141. package/runtime/xt_containers/arrays.inc +108 -0
  142. package/runtime/xt_containers/boxes.inc +11 -0
  143. package/runtime/xt_containers/objects.inc +326 -0
  144. package/runtime/xt_containers.c +8 -618
  145. package/runtime/xt_ext.h +0 -1
  146. package/runtime/xt_io/base64.inc +87 -0
  147. package/runtime/xt_io/console.inc +119 -0
  148. package/runtime/xt_io/exception.inc +142 -0
  149. package/runtime/xt_io/inspect.inc +212 -0
  150. package/runtime/xt_io.c +11 -556
  151. package/runtime/xt_loop.c +262 -24
  152. package/runtime/xt_regex/api.inc +58 -0
  153. package/runtime/xt_regex/helpers.inc +68 -0
  154. package/runtime/xt_regex/matcher.inc +248 -0
  155. package/runtime/xt_regex/parser.inc +236 -0
  156. package/runtime/xt_regex/types.inc +58 -0
  157. package/runtime/xt_regex.h +9 -672
  158. package/runtime/xt_stdlib2/helpers.inc +2 -4
  159. package/runtime/xt_values/arithmetic.inc +150 -0
  160. package/runtime/xt_values/comparison.inc +112 -0
  161. package/runtime/xt_values/constructors.inc +41 -0
  162. package/runtime/xt_values/conversions.inc +115 -0
  163. package/runtime/xt_values/number-format.inc +180 -0
  164. package/runtime/xt_values/strings.inc +22 -0
  165. package/runtime/xt_values.c +10 -626
  166. package/scripts/build-gui.ts +317 -0
  167. package/scripts/build-runtime.ts +7 -8
  168. package/scripts/coverage-runtime.ts +4 -4
  169. package/scripts/package-release.ts +12 -84
  170. package/src/cli/main.ts +8 -4
  171. package/src/codegen/generator/state.ts +8 -0
  172. package/src/codegen/generator/tables.ts +13 -6
  173. package/src/driver/bundler/graph.ts +95 -0
  174. package/src/driver/bundler/merge.ts +312 -0
  175. package/src/driver/bundler/resolve.ts +240 -0
  176. package/src/driver/bundler/symbols.ts +55 -0
  177. package/src/driver/bundler/types.ts +37 -0
  178. package/src/driver/index.ts +1 -9
  179. package/src/driver/modules.ts +9 -693
  180. package/src/driver/paths.ts +0 -31
  181. package/src/driver/toolchain-provider.ts +12 -45
  182. package/src/driver/toolchain.ts +7 -2
  183. package/src/extensions/catalog.ts +2 -1
  184. package/src/extensions/gui/index.ts +107 -0
  185. package/src/extensions/native.ts +2 -3
  186. package/dist/src/driver/toolchain-download.d.ts +0 -50
  187. package/dist/src/driver/toolchain-download.js +0 -102
  188. package/dist/src/driver/toolchain-download.js.map +0 -1
  189. package/dist/tests/driver/toolchain-download.test.d.ts +0 -1
  190. package/dist/tests/driver/toolchain-download.test.js +0 -53
  191. package/dist/tests/driver/toolchain-download.test.js.map +0 -1
  192. package/dist/tests/e2e/compile.test.d.ts +0 -8
  193. package/dist/tests/e2e/compile.test.js +0 -893
  194. package/dist/tests/e2e/compile.test.js.map +0 -1
  195. package/dist/tests/e2e/differential.test.d.ts +0 -7
  196. package/dist/tests/e2e/differential.test.js.map +0 -1
  197. package/dist/tests/e2e/runtime-coverage.test.d.ts +0 -11
  198. package/dist/tests/e2e/runtime-coverage.test.js +0 -646
  199. package/dist/tests/e2e/runtime-coverage.test.js.map +0 -1
  200. package/scripts/fetch-toolchain.ts +0 -101
  201. package/src/driver/toolchain-download.ts +0 -135
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Import-graph loading: parse and bind the entry module and every module it
3
+ * reaches, in dependency order.
4
+ */
5
+
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { dirname } from "node:path";
8
+ import { SyntaxKind, type ExportDeclaration, type ImportDeclaration, type Statement } from "../../ast/nodes.js";
9
+ import { bind } from "../../binder/binder.js";
10
+ import { DiagnosticBag, DiagnosticCode } from "../../diagnostics/diagnostic.js";
11
+ import { SourceFile } from "../../diagnostics/source.js";
12
+ import { Parser } from "../../parser/parser.js";
13
+ import { classifyDependency } from "./resolve.js";
14
+ import type { ModuleRecord } from "./types.js";
15
+
16
+ function moduleSpecifierOf(statement: Statement): string | undefined {
17
+ if (statement.kind === SyntaxKind.ImportDeclaration) {
18
+ return (statement as ImportDeclaration).moduleSpecifier.value;
19
+ }
20
+ if (statement.kind === SyntaxKind.ExportDeclaration) {
21
+ const specifier = (statement as ExportDeclaration).moduleSpecifier;
22
+ if (specifier) return specifier.value;
23
+ }
24
+ return undefined;
25
+ }
26
+
27
+ /** Load, parse and bind the entry module and every module it imports. */
28
+ export function loadGraph(
29
+ entryPath: string,
30
+ diagnostics: DiagnosticBag,
31
+ externalSpecifiers: ReadonlySet<string>,
32
+ ): ModuleRecord[] | undefined {
33
+ const records = new Map<string, ModuleRecord>();
34
+ const order: ModuleRecord[] = [];
35
+ const visiting = new Set<string>();
36
+ let counter = 0;
37
+ let failed = false;
38
+
39
+ const load = (path: string): ModuleRecord | undefined => {
40
+ const existing = records.get(path);
41
+ if (existing) return existing;
42
+ if (visiting.has(path)) {
43
+ diagnostics.error(DiagnosticCode.CodegenError, `Circular import detected involving '${path}'`);
44
+ failed = true;
45
+ return undefined;
46
+ }
47
+ if (!existsSync(path)) {
48
+ diagnostics.error(DiagnosticCode.CodegenError, `Cannot find module '${path}'`);
49
+ failed = true;
50
+ return undefined;
51
+ }
52
+ visiting.add(path);
53
+ const text = readFileSync(path, "utf8");
54
+ const file = new SourceFile(path, text);
55
+ const parser = new Parser(file, diagnostics);
56
+ const sourceFile = parser.parseSourceFile();
57
+ const bindResult = bind(sourceFile);
58
+ const record: ModuleRecord = {
59
+ path,
60
+ text,
61
+ sourceFile,
62
+ bind: bindResult,
63
+ prefix: `m${counter++}_`,
64
+ exports: new Map(),
65
+ originalNames: new Map(),
66
+ finalNames: new Map(),
67
+ };
68
+ records.set(path, record);
69
+
70
+ for (const statement of sourceFile.statements) {
71
+ const specifier = moduleSpecifierOf(statement);
72
+ if (!specifier) continue;
73
+ const dependency = classifyDependency(dirname(path), specifier, externalSpecifiers);
74
+ if (dependency.kind === "external") continue;
75
+ if (dependency.kind === "missing") {
76
+ diagnostics.error(
77
+ DiagnosticCode.CodegenError,
78
+ `Cannot resolve module '${specifier}' from '${path}'`,
79
+ statement,
80
+ path,
81
+ );
82
+ failed = true;
83
+ continue;
84
+ }
85
+ load(dependency.path);
86
+ }
87
+ visiting.delete(path);
88
+ order.push(record);
89
+ return record;
90
+ };
91
+
92
+ load(entryPath);
93
+ if (failed) return undefined;
94
+ return order;
95
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Merge every reachable module's statements into a single source file.
3
+ *
4
+ * Dependencies come first (post-order over the import graph), the entry module
5
+ * last. Top-level bindings are renamed to globally unique names and imports are
6
+ * rewritten to the (renamed) exported binding; namespace imports are lowered
7
+ * to a synthetic object literal.
8
+ */
9
+
10
+ import { dirname } from "node:path";
11
+ import {
12
+ ModifierKind,
13
+ SyntaxKind,
14
+ type ExportAssignment,
15
+ type ExportDeclaration,
16
+ type Identifier,
17
+ type ImportDeclaration,
18
+ type Node,
19
+ type ObjectLiteralExpression,
20
+ type PropertyAssignment,
21
+ type SourceFileNode,
22
+ type Statement,
23
+ type VariableDeclaration,
24
+ type VariableDeclarationList,
25
+ type VariableStatement,
26
+ } from "../../ast/nodes.js";
27
+ import type { BindingName } from "../../ast/declarations.js";
28
+ import { SymbolKind } from "../../binder/binder.js";
29
+ import type { DiagnosticBag } from "../../diagnostics/diagnostic.js";
30
+ import { loadGraph } from "./graph.js";
31
+ import { classifyDependency } from "./resolve.js";
32
+ import {
33
+ declarationName,
34
+ hasModifier,
35
+ moduleScope,
36
+ renameReferences,
37
+ renameSymbol,
38
+ topLevelSymbols,
39
+ } from "./symbols.js";
40
+ import type { BundleResult, ModuleRecord } from "./types.js";
41
+
42
+ export function bundleModules(
43
+ entryPath: string,
44
+ diagnostics: DiagnosticBag,
45
+ externalSpecifiers: ReadonlySet<string> = new Set(),
46
+ ): BundleResult | undefined {
47
+ const records = loadGraph(entryPath, diagnostics, externalSpecifiers);
48
+ if (!records) return undefined;
49
+
50
+ const byPath = new Map(records.map((record) => [record.path, record]));
51
+ const dependencyOf = (record: ModuleRecord, specifier: string): ModuleRecord | undefined => {
52
+ const resolution = classifyDependency(dirname(record.path), specifier, externalSpecifiers);
53
+ return resolution.kind === "file" ? byPath.get(resolution.path) : undefined;
54
+ };
55
+
56
+ // Phase 1: record every top-level binding and pick a unique final name.
57
+ for (const record of records) {
58
+ for (const symbol of topLevelSymbols(record)) {
59
+ record.originalNames.set(symbol.id, symbol.name);
60
+ if (symbol.kind === SymbolKind.Import) continue;
61
+ record.finalNames.set(symbol.id, record.prefix + symbol.name);
62
+ }
63
+ }
64
+
65
+ // Phase 2: collect each module's exported names.
66
+ for (const record of records) {
67
+ const scope = moduleScope(record);
68
+ if (!scope) continue;
69
+ const symbolByName = scope.symbols;
70
+ for (const statement of record.sourceFile.statements) {
71
+ if (statement.kind === SyntaxKind.ExportDeclaration) {
72
+ const declaration = statement as ExportDeclaration;
73
+ if (declaration.exportClause && declaration.exportClause.kind === SyntaxKind.NamedExports) {
74
+ for (const specifier of declaration.exportClause.elements) {
75
+ const localName = specifier.propertyName?.text ?? specifier.name.text;
76
+ const exportedName = specifier.name.text;
77
+ if (declaration.moduleSpecifier) {
78
+ const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
79
+ const target = dependency?.exports.get(localName);
80
+ if (target) record.exports.set(exportedName, target);
81
+ } else {
82
+ const symbol = symbolByName.get(localName);
83
+ if (symbol) {
84
+ const finalName = record.finalNames.get(symbol.id) ?? symbol.name;
85
+ record.exports.set(exportedName, finalName);
86
+ }
87
+ }
88
+ }
89
+ } else if (!declaration.exportClause && declaration.moduleSpecifier) {
90
+ // `export * from "..."`: copy every re-exported name.
91
+ const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
92
+ if (dependency) for (const [name, value] of dependency.exports) record.exports.set(name, value);
93
+ }
94
+ continue;
95
+ }
96
+ if (statement.kind === SyntaxKind.ExportAssignment) {
97
+ record.exports.set("default", `${record.prefix}default`);
98
+ continue;
99
+ }
100
+ if (hasModifier(statement, ModifierKind.Default)) {
101
+ const name = declarationName(statement) ?? declarationName((statement as { declaration?: Node }).declaration ?? statement);
102
+ if (name) {
103
+ const symbol = symbolByName.get(name.text);
104
+ if (symbol) record.exports.set("default", record.finalNames.get(symbol.id) ?? symbol.name);
105
+ }
106
+ continue;
107
+ }
108
+ if (hasModifier(statement, ModifierKind.Export)) {
109
+ const names = exportedNames(statement);
110
+ for (const name of names) {
111
+ const symbol = symbolByName.get(name);
112
+ if (symbol) record.exports.set(name, record.finalNames.get(symbol.id) ?? symbol.name);
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ // Phase 3: rewrite imported names and record re-exports. Namespace imports
119
+ // are lowered to a synthetic object literal holding every export, built
120
+ // from the dependency's (already renamed) final names.
121
+ const namespaceStatements = new Map<Statement, Statement>();
122
+ for (const record of records) {
123
+ const scope = moduleScope(record);
124
+ if (!scope) continue;
125
+ for (const statement of record.sourceFile.statements) {
126
+ if (statement.kind !== SyntaxKind.ImportDeclaration) continue;
127
+ const declaration = statement as ImportDeclaration;
128
+ const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
129
+ const clause = declaration.importClause;
130
+ if (!clause || !dependency) continue;
131
+ if (clause.name) {
132
+ const target = dependency.exports.get("default") ?? dependency.exports.get("default");
133
+ const local = scope.symbols.get(clause.name.text);
134
+ if (target && local) renameReferences(local, target);
135
+ }
136
+ const bindings = clause.namedBindings;
137
+ if (bindings && bindings.kind === SyntaxKind.NamedImports) {
138
+ for (const specifier of bindings.elements) {
139
+ const importedName = specifier.propertyName?.text ?? specifier.name.text;
140
+ const target = dependency.exports.get(importedName);
141
+ const local = scope.symbols.get(specifier.name.text);
142
+ if (target && local) renameReferences(local, target);
143
+ }
144
+ } else if (bindings && bindings.kind === SyntaxKind.NamespaceImport) {
145
+ const local = scope.symbols.get(bindings.name.text);
146
+ const finalName = `${record.prefix}${bindings.name.text}`;
147
+ if (local) renameReferences(local, finalName);
148
+ namespaceStatements.set(statement, namespaceImportToStatement(statement, finalName, dependency));
149
+ }
150
+ }
151
+ }
152
+
153
+ // Phase 4: concatenate statements, dropping imports and pure export wrappers.
154
+ const merged: Statement[] = [];
155
+ const first = records[records.length - 1]!;
156
+ for (const record of records) {
157
+ for (const statement of record.sourceFile.statements) {
158
+ if (statement.kind === SyntaxKind.ImportDeclaration) {
159
+ // External (extension / built-in) imports stay in place for codegen.
160
+ const declaration = statement as ImportDeclaration;
161
+ const resolution = classifyDependency(
162
+ dirname(record.path),
163
+ declaration.moduleSpecifier.value,
164
+ externalSpecifiers,
165
+ );
166
+ if (resolution.kind === "external") {
167
+ merged.push(statement);
168
+ } else {
169
+ const synthetic = namespaceStatements.get(statement);
170
+ if (synthetic) merged.push(synthetic);
171
+ }
172
+ continue;
173
+ }
174
+ if (statement.kind === SyntaxKind.ExportDeclaration) {
175
+ const declaration = statement as ExportDeclaration;
176
+ const inner = (declaration as unknown as { declaration?: Statement }).declaration;
177
+ if (inner) merged.push(inner);
178
+ continue;
179
+ }
180
+ if (statement.kind === SyntaxKind.ExportAssignment) {
181
+ const assignment = statement as ExportAssignment;
182
+ merged.push(exportAssignmentToStatement(record, assignment));
183
+ continue;
184
+ }
185
+ merged.push(statement);
186
+ }
187
+ }
188
+
189
+ // Phase 5: rename every top-level binding (declaration + references) to its
190
+ // unique final name, now that import references have been rewritten.
191
+ for (const record of records) {
192
+ for (const symbol of topLevelSymbols(record)) {
193
+ if (symbol.kind === SymbolKind.Import) continue;
194
+ const finalName = record.finalNames.get(symbol.id);
195
+ if (finalName) renameSymbol(symbol, finalName);
196
+ }
197
+ }
198
+
199
+ const sourceFile: SourceFileNode = {
200
+ kind: SyntaxKind.SourceFile,
201
+ statements: merged,
202
+ fileName: first.path,
203
+ text: records.map((record) => record.text).join("\n"),
204
+ start: 0,
205
+ end: 0,
206
+ };
207
+ return { sourceFile, text: sourceFile.text, moduleCount: records.length };
208
+ }
209
+
210
+ function exportedNames(statement: Statement): string[] {
211
+ const names: string[] = [];
212
+ if (statement.kind === SyntaxKind.VariableStatement) {
213
+ for (const declaration of (statement as VariableStatement).declarationList.declarations) {
214
+ collectBindingIdentifiers(declaration.name, names);
215
+ }
216
+ } else {
217
+ const name = declarationName(statement);
218
+ if (name) names.push(name.text);
219
+ }
220
+ return names;
221
+ }
222
+
223
+ function collectBindingIdentifiers(name: BindingName, out: string[]): void {
224
+ if (name.kind === SyntaxKind.Identifier) {
225
+ out.push(name.text);
226
+ return;
227
+ }
228
+ const elements = name.kind === SyntaxKind.ArrayBindingPattern ? name.elements : name.elements;
229
+ for (const element of elements) {
230
+ if (element) collectBindingIdentifiers(element.name, out);
231
+ }
232
+ }
233
+
234
+ /** Lower `import * as ns from "./mod"` to `const m0_ns = { ...exports }`. */
235
+ function namespaceImportToStatement(
236
+ statement: Statement,
237
+ finalName: string,
238
+ dependency: ModuleRecord,
239
+ ): Statement {
240
+ const properties: PropertyAssignment[] = [];
241
+ for (const [exportedName, target] of dependency.exports) {
242
+ properties.push({
243
+ kind: SyntaxKind.PropertyAssignment,
244
+ name: { kind: SyntaxKind.Identifier, text: exportedName, start: statement.start, end: statement.start },
245
+ initializer: { kind: SyntaxKind.Identifier, text: target, start: statement.start, end: statement.start },
246
+ start: statement.start,
247
+ end: statement.start,
248
+ });
249
+ }
250
+ const initializer: ObjectLiteralExpression = {
251
+ kind: SyntaxKind.ObjectLiteralExpression,
252
+ properties,
253
+ start: statement.start,
254
+ end: statement.end,
255
+ };
256
+ const declaration: VariableDeclaration = {
257
+ kind: SyntaxKind.VariableDeclaration,
258
+ name: { kind: SyntaxKind.Identifier, text: finalName, start: statement.start, end: statement.start },
259
+ exclamation: false,
260
+ initializer,
261
+ start: statement.start,
262
+ end: statement.end,
263
+ };
264
+ const list: VariableDeclarationList = {
265
+ kind: SyntaxKind.VariableDeclarationList,
266
+ declarationKind: "const" as const,
267
+ declarations: [declaration],
268
+ start: statement.start,
269
+ end: statement.end,
270
+ };
271
+ return {
272
+ kind: SyntaxKind.VariableStatement,
273
+ declarationList: list,
274
+ modifiers: [],
275
+ start: statement.start,
276
+ end: statement.end,
277
+ };
278
+ }
279
+
280
+ /** Turn `export default expr` into a synthetic `const` declaration. */
281
+ function exportAssignmentToStatement(record: ModuleRecord, assignment: ExportAssignment): Statement {
282
+ const name: Identifier = {
283
+ kind: SyntaxKind.Identifier,
284
+ text: `${record.prefix}default`,
285
+ start: assignment.start,
286
+ end: assignment.start,
287
+ };
288
+ record.exports.set("default", name.text);
289
+ const declaration: VariableDeclaration = {
290
+ kind: SyntaxKind.VariableDeclaration,
291
+ name,
292
+ exclamation: false,
293
+ initializer: assignment.expression,
294
+ start: assignment.start,
295
+ end: assignment.end,
296
+ };
297
+ const list: VariableDeclarationList = {
298
+ kind: SyntaxKind.VariableDeclarationList,
299
+ declarationKind: "const" as const,
300
+ declarations: [declaration],
301
+ start: assignment.start,
302
+ end: assignment.end,
303
+ };
304
+ const statement: VariableStatement = {
305
+ kind: SyntaxKind.VariableStatement,
306
+ declarationList: list,
307
+ modifiers: [],
308
+ start: assignment.start,
309
+ end: assignment.end,
310
+ };
311
+ return statement;
312
+ }
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Module resolution for the bundler: relative paths, directory `index`
3
+ * entries, package `exports` maps and `node_modules` lookup.
4
+ */
5
+
6
+ import { existsSync, readFileSync, statSync } from "node:fs";
7
+ import { dirname, isAbsolute, join, resolve } from "node:path";
8
+ import type { DependencyResolution, PackageJson } from "./types.js";
9
+
10
+ /**
11
+ * Source extensions probed for an extension-less specifier. TypeScript suffixes
12
+ * are tried before their JavaScript counterparts so a project that ships both
13
+ * `foo.ts` and a compiled `foo.js` prefers the source it is compiled from.
14
+ */
15
+ const RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
16
+ /** File extensions a directory `index` entry may use. */
17
+ const INDEX_SUFFIXES = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
18
+ /** Conditions consulted, in order, when reading a package `exports` map. */
19
+ const EXPORT_CONDITIONS = ["import", "module", "default", "node", "require"];
20
+
21
+ /** A specifier is a file path (`./x`, `../x`, `/x`) rather than a package. */
22
+ function isRelativeSpecifier(specifier: string): boolean {
23
+ return specifier.startsWith(".") || isAbsolute(specifier);
24
+ }
25
+
26
+ /**
27
+ * Resolve a path on disk, tolerating TypeScript's `.js` import convention
28
+ * (`import "./foo.js"` pointing at `foo.ts`) and Node's directory/`index`
29
+ * conventions.
30
+ */
31
+ function resolvePath(base: string): string | undefined {
32
+ // A JavaScript specifier may refer to a TypeScript source that will emit it
33
+ // (`import "./foo.js"` pointing at `foo.ts`). Probe the matching TypeScript
34
+ // extensions after the literal path: `.js`/`.jsx` -> `.ts`/`.tsx`,
35
+ // `.mjs` -> `.mts`, `.cjs` -> `.cts`.
36
+ const candidates = [base];
37
+ const jsExtension = /\.((?:m|c)?jsx?)$/.exec(base);
38
+ if (jsExtension) {
39
+ const stem = base.slice(0, jsExtension.index);
40
+ switch (jsExtension[1]) {
41
+ case "jsx":
42
+ candidates.push(`${stem}.tsx`, `${stem}.ts`);
43
+ break;
44
+ case "mjs":
45
+ candidates.push(`${stem}.mts`);
46
+ break;
47
+ case "cjs":
48
+ candidates.push(`${stem}.cts`);
49
+ break;
50
+ default:
51
+ candidates.push(`${stem}.ts`, `${stem}.tsx`);
52
+ break;
53
+ }
54
+ }
55
+ for (const candidate of candidates) {
56
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
57
+ }
58
+ for (const suffix of RESOLVE_SUFFIXES) {
59
+ const candidate = base + suffix;
60
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
61
+ }
62
+ if (existsSync(base) && statSync(base).isDirectory()) return resolveDirectory(base);
63
+ return undefined;
64
+ }
65
+
66
+ /** Resolve a directory to its package entry or an `index` file. */
67
+ function resolveDirectory(directory: string): string | undefined {
68
+ const pkg = readPackageJson(directory);
69
+ if (pkg && typeof pkg.main === "string") {
70
+ const main = resolvePath(resolve(directory, pkg.main));
71
+ if (main) return main;
72
+ }
73
+ for (const suffix of INDEX_SUFFIXES) {
74
+ const candidate = join(directory, `index${suffix}`);
75
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
76
+ }
77
+ return undefined;
78
+ }
79
+
80
+ function resolveModule(fromDir: string, specifier: string): string | undefined {
81
+ return resolvePath(resolve(fromDir, specifier));
82
+ }
83
+
84
+ function readPackageJson(directory: string): PackageJson | undefined {
85
+ const file = join(directory, "package.json");
86
+ if (!existsSync(file) || !statSync(file).isFile()) return undefined;
87
+ try {
88
+ const parsed = JSON.parse(readFileSync(file, "utf8")) as unknown;
89
+ return parsed && typeof parsed === "object" ? (parsed as PackageJson) : undefined;
90
+ } catch {
91
+ return undefined;
92
+ }
93
+ }
94
+
95
+ /** Split `pkg/sub/path` or `@scope/pkg/sub/path` into package name and subpath. */
96
+ function splitPackageSpecifier(specifier: string): { packageName: string; subpath: string } {
97
+ const parts = specifier.split("/");
98
+ if (specifier.startsWith("@")) {
99
+ return { packageName: parts.slice(0, 2).join("/"), subpath: parts.slice(2).join("/") };
100
+ }
101
+ return { packageName: parts[0] ?? specifier, subpath: parts.slice(1).join("/") };
102
+ }
103
+
104
+ /**
105
+ * Resolve a bare specifier against the `node_modules` directories above
106
+ * `fromDir`, following a package's `exports` map and falling back to
107
+ * `module`/`main`/`index`. Packages that only ship CommonJS resolve here too;
108
+ * their `require` calls are then rejected during code generation.
109
+ */
110
+ function resolveNodePackage(fromDir: string, specifier: string): string | undefined {
111
+ const { packageName, subpath } = splitPackageSpecifier(specifier);
112
+ if (!packageName || packageName.startsWith(".")) return undefined;
113
+ let directory = fromDir;
114
+ for (;;) {
115
+ const packageDir = join(directory, "node_modules", packageName);
116
+ if (existsSync(packageDir) && statSync(packageDir).isDirectory()) {
117
+ const resolved = subpath
118
+ ? resolvePackageSubpath(packageDir, subpath)
119
+ : resolvePackageEntry(packageDir);
120
+ if (resolved) return resolved;
121
+ }
122
+ const parent = dirname(directory);
123
+ if (parent === directory) return undefined;
124
+ directory = parent;
125
+ }
126
+ }
127
+
128
+ function resolvePackageEntry(packageDir: string): string | undefined {
129
+ const pkg = readPackageJson(packageDir);
130
+ if (pkg) {
131
+ const exported = resolveExportTarget(subpathValue(pkg.exports, "."));
132
+ if (exported) {
133
+ const resolved = resolvePath(resolve(packageDir, exported));
134
+ if (resolved) return resolved;
135
+ }
136
+ if (typeof pkg.module === "string") {
137
+ const resolved = resolvePath(resolve(packageDir, pkg.module));
138
+ if (resolved) return resolved;
139
+ }
140
+ if (typeof pkg.main === "string") {
141
+ const resolved = resolvePath(resolve(packageDir, pkg.main));
142
+ if (resolved) return resolved;
143
+ }
144
+ }
145
+ return resolveDirectory(packageDir);
146
+ }
147
+
148
+ function resolvePackageSubpath(packageDir: string, subpath: string): string | undefined {
149
+ const pkg = readPackageJson(packageDir);
150
+ const exported = resolveExportTarget(subpathValue(pkg?.exports, `./${subpath}`));
151
+ if (exported) {
152
+ const resolved = resolvePath(resolve(packageDir, exported));
153
+ if (resolved) return resolved;
154
+ }
155
+ return resolvePath(join(packageDir, subpath));
156
+ }
157
+
158
+ /** Pick the export target for a subpath (`"."` or `"./sub"`) from an exports map. */
159
+ function subpathValue(exportsField: unknown, subpath: string): unknown {
160
+ if (exportsField === undefined) return undefined;
161
+ if (typeof exportsField === "string" || Array.isArray(exportsField)) {
162
+ return subpath === "." ? exportsField : undefined;
163
+ }
164
+ if (!exportsField || typeof exportsField !== "object") return undefined;
165
+ const record = exportsField as Record<string, unknown>;
166
+ const keys = Object.keys(record);
167
+ // A map without `.`-prefixed keys is a condition map for the root export.
168
+ if (!keys.some((key) => key.startsWith("."))) {
169
+ return subpath === "." ? exportsField : undefined;
170
+ }
171
+ if (subpath in record) return record[subpath];
172
+ // Wildcard patterns such as `"./*": "./dist/*.js"`.
173
+ for (const key of keys) {
174
+ const star = key.indexOf("*");
175
+ if (star === -1) continue;
176
+ const prefix = key.slice(0, star);
177
+ const suffix = key.slice(star + 1);
178
+ if (subpath.startsWith(prefix) && subpath.endsWith(suffix)) {
179
+ const matched = subpath.slice(prefix.length, subpath.length - suffix.length);
180
+ return substituteStar(record[key], matched);
181
+ }
182
+ }
183
+ return undefined;
184
+ }
185
+
186
+ function substituteStar(target: unknown, matched: string): unknown {
187
+ if (typeof target === "string") return target.split("*").join(matched);
188
+ if (Array.isArray(target)) return target.map((entry) => substituteStar(entry, matched));
189
+ if (target && typeof target === "object") {
190
+ const record: Record<string, unknown> = {};
191
+ for (const [key, value] of Object.entries(target)) record[key] = substituteStar(value, matched);
192
+ return record;
193
+ }
194
+ return target;
195
+ }
196
+
197
+ /** Pick a runtime string from a (possibly conditional) exports target. */
198
+ function resolveExportTarget(target: unknown): string | undefined {
199
+ if (typeof target === "string") return target;
200
+ if (Array.isArray(target)) {
201
+ for (const entry of target) {
202
+ const resolved = resolveExportTarget(entry);
203
+ if (resolved) return resolved;
204
+ }
205
+ return undefined;
206
+ }
207
+ if (target && typeof target === "object") {
208
+ const record = target as Record<string, unknown>;
209
+ for (const condition of EXPORT_CONDITIONS) {
210
+ if (condition in record) {
211
+ const resolved = resolveExportTarget(record[condition]);
212
+ if (resolved) return resolved;
213
+ }
214
+ }
215
+ }
216
+ return undefined;
217
+ }
218
+
219
+ /**
220
+ * Classify an import specifier. Known platform modules (extension modules and
221
+ * `node:` builtins) are left for code generation; relative paths and
222
+ * `node_modules` packages are resolved to a source file to bundle; a relative
223
+ * path that does not exist is an error.
224
+ */
225
+ export function classifyDependency(
226
+ fromDir: string,
227
+ specifier: string,
228
+ externalSpecifiers: ReadonlySet<string>,
229
+ ): DependencyResolution {
230
+ if (specifier.startsWith("node:") || externalSpecifiers.has(specifier)) {
231
+ return { kind: "external" };
232
+ }
233
+ const resolved = isRelativeSpecifier(specifier)
234
+ ? resolveModule(fromDir, specifier)
235
+ : resolveNodePackage(fromDir, specifier);
236
+ if (resolved) return { kind: "file", path: resolved };
237
+ // A bare specifier that is not a file may still be a platform module the
238
+ // generator knows about (e.g. when the caller passes no extension registry).
239
+ return isRelativeSpecifier(specifier) ? { kind: "missing" } : { kind: "external" };
240
+ }