@invinite-org/chartlang-compiler 1.0.1 → 1.2.0
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/CHANGELOG.md +107 -0
- package/README.md +3 -0
- package/dist/analysis/extractCapabilities.d.ts +5 -1
- package/dist/analysis/extractCapabilities.d.ts.map +1 -1
- package/dist/analysis/extractCapabilities.js +6 -2
- package/dist/analysis/extractCapabilities.js.map +1 -1
- package/dist/analysis/extractDependencyGraph.d.ts +160 -0
- package/dist/analysis/extractDependencyGraph.d.ts.map +1 -0
- package/dist/analysis/extractDependencyGraph.js +690 -0
- package/dist/analysis/extractDependencyGraph.js.map +1 -0
- package/dist/analysis/extractInputs.d.ts +5 -1
- package/dist/analysis/extractInputs.d.ts.map +1 -1
- package/dist/analysis/extractInputs.js +6 -2
- package/dist/analysis/extractInputs.js.map +1 -1
- package/dist/analysis/extractMaxLookback.d.ts +6 -1
- package/dist/analysis/extractMaxLookback.d.ts.map +1 -1
- package/dist/analysis/extractMaxLookback.js +10 -5
- package/dist/analysis/extractMaxLookback.js.map +1 -1
- package/dist/analysis/forbiddenConstructs.d.ts +1 -2
- package/dist/analysis/forbiddenConstructs.d.ts.map +1 -1
- package/dist/analysis/forbiddenConstructs.js +4 -2
- package/dist/analysis/forbiddenConstructs.js.map +1 -1
- package/dist/analysis/index.d.ts +3 -1
- package/dist/analysis/index.d.ts.map +1 -1
- package/dist/analysis/index.js +1 -0
- package/dist/analysis/index.js.map +1 -1
- package/dist/analysis/structuralChecks.d.ts +65 -9
- package/dist/analysis/structuralChecks.d.ts.map +1 -1
- package/dist/analysis/structuralChecks.js +111 -22
- package/dist/analysis/structuralChecks.js.map +1 -1
- package/dist/api.d.ts +52 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +485 -35
- package/dist/api.js.map +1 -1
- package/dist/bundle.d.ts +91 -3
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +88 -5
- package/dist/bundle.js.map +1 -1
- package/dist/dependency/index.d.ts +3 -0
- package/dist/dependency/index.d.ts.map +1 -0
- package/dist/dependency/index.js +4 -0
- package/dist/dependency/index.js.map +1 -0
- package/dist/dependency/resolveProducer.d.ts +183 -0
- package/dist/dependency/resolveProducer.d.ts.map +1 -0
- package/dist/dependency/resolveProducer.js +256 -0
- package/dist/dependency/resolveProducer.js.map +1 -0
- package/dist/diagnostics.d.ts +6 -2
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +8 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +27 -0
- package/dist/manifest.js.map +1 -1
- package/dist/program.d.ts +1 -0
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +80 -4
- package/dist/program.js.map +1 -1
- package/dist/transformers/callsiteIdInjection.d.ts +3 -2
- package/dist/transformers/callsiteIdInjection.d.ts.map +1 -1
- package/dist/transformers/callsiteIdInjection.js +16 -1
- package/dist/transformers/callsiteIdInjection.js.map +1 -1
- package/dist/transformers/index.d.ts +2 -0
- package/dist/transformers/index.d.ts.map +1 -1
- package/dist/transformers/index.js +1 -0
- package/dist/transformers/index.js.map +1 -1
- package/dist/transformers/plotKindFromCallsite.d.ts +43 -0
- package/dist/transformers/plotKindFromCallsite.d.ts.map +1 -0
- package/dist/transformers/plotKindFromCallsite.js +103 -0
- package/dist/transformers/plotKindFromCallsite.js.map +1 -0
- package/dist/transformers/rewriteDependencyAccessors.d.ts +65 -0
- package/dist/transformers/rewriteDependencyAccessors.d.ts.map +1 -0
- package/dist/transformers/rewriteDependencyAccessors.js +204 -0
- package/dist/transformers/rewriteDependencyAccessors.js.map +1 -0
- package/dist/typesEmit.d.ts +14 -11
- package/dist/typesEmit.d.ts.map +1 -1
- package/dist/typesEmit.js +91 -7
- package/dist/typesEmit.js.map +1 -1
- package/package.json +2 -2
package/dist/api.js
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
// See the LICENSE file in the repo root for full license text.
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
5
|
-
import { isAbsolute, join, relative, resolve as resolvePath } from "node:path";
|
|
5
|
+
import { dirname, isAbsolute, join, relative, resolve as resolvePath } from "node:path";
|
|
6
6
|
import { STATEFUL_PRIMITIVES_BY_NAME } from "@invinite-org/chartlang-core";
|
|
7
7
|
import ts from "typescript";
|
|
8
|
-
import { extractAlertConditions, extractCapabilities, extractInputs, extractMaxLookback, extractRequestedIntervals, extractRequiresIntervals, runForbiddenConstructs, runStatefulCallInLoop, runStructuralChecks, validateLowerTfIntervals, } from "./analysis/index.js";
|
|
9
|
-
import { bundleModule, formatManifestAssignment } from "./bundle.js";
|
|
8
|
+
import { extractAlertConditions, extractCapabilities, extractDependencyGraph, extractInputs, extractMaxLookback, extractRequestedIntervals, extractRequiresIntervals, runForbiddenConstructs, runStatefulCallInLoop, runStructuralChecks, validateLowerTfIntervals, } from "./analysis/index.js";
|
|
9
|
+
import { bundleModule, formatDependenciesAssignment, formatManifestAssignment } from "./bundle.js";
|
|
10
|
+
import { createProducerResolver, } from "./dependency/index.js";
|
|
10
11
|
import { mapTsDiagnostic } from "./diagnostics.js";
|
|
11
12
|
import { buildManifest } from "./manifest.js";
|
|
12
13
|
import { createProgramForSource } from "./program.js";
|
|
13
14
|
import { injectCallsiteIds } from "./transformers/callsiteIdInjection.js";
|
|
15
|
+
import { rewriteDependencyAccessors } from "./transformers/rewriteDependencyAccessors.js";
|
|
14
16
|
import { emitTypes } from "./typesEmit.js";
|
|
15
17
|
/**
|
|
16
18
|
* Run the Phase-1 compiler pipeline against an in-memory script source.
|
|
@@ -36,7 +38,11 @@ import { emitTypes } from "./typesEmit.js";
|
|
|
36
38
|
*/
|
|
37
39
|
export function transformAndAnalyse(source, opts) {
|
|
38
40
|
const sourcePath = opts.sourcePath;
|
|
39
|
-
const
|
|
41
|
+
const chartImports = preScanChartImports(source, sourcePath);
|
|
42
|
+
const { program, sourceFile, checker } = createProgramForSource(source, {
|
|
43
|
+
sourcePath,
|
|
44
|
+
chartImports,
|
|
45
|
+
});
|
|
40
46
|
const structural = runStructuralChecks(sourceFile, checker, sourcePath);
|
|
41
47
|
const forbidden = runForbiddenConstructs(sourceFile, sourcePath);
|
|
42
48
|
const statefulInLoop = runStatefulCallInLoop(sourceFile, checker, sourcePath, STATEFUL_PRIMITIVES_BY_NAME);
|
|
@@ -50,11 +56,28 @@ export function transformAndAnalyse(source, opts) {
|
|
|
50
56
|
.getSemanticDiagnostics(sourceFile)
|
|
51
57
|
.filter((d) => d.file?.fileName === sourceFile.fileName)
|
|
52
58
|
.map((d) => mapTsDiagnostic(d, sourcePath));
|
|
59
|
+
const depGraph = extractDependencyGraph(sourceFile, checker, sourcePath, structural.bindings, opts.resolveProducer === undefined
|
|
60
|
+
? () => null
|
|
61
|
+
: (modSpec, expName) => {
|
|
62
|
+
const snap = opts.resolveProducer?.(modSpec, expName);
|
|
63
|
+
/* v8 ignore next */
|
|
64
|
+
if (snap === undefined || snap === null)
|
|
65
|
+
return null;
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
name: snap.name,
|
|
68
|
+
outputs: Object.freeze(snap.outputs.map((o) => Object.freeze({
|
|
69
|
+
title: o.title,
|
|
70
|
+
kind: o.kind,
|
|
71
|
+
}))),
|
|
72
|
+
inputs: snap.inputs,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
53
75
|
const earlyDiagnostics = [
|
|
54
76
|
...semanticDiagnostics,
|
|
55
77
|
...structural.diagnostics,
|
|
56
78
|
...forbidden,
|
|
57
79
|
...statefulInLoop,
|
|
80
|
+
...depGraph.diagnostics,
|
|
58
81
|
];
|
|
59
82
|
const hasError = earlyDiagnostics.some((d) => d.severity === "error");
|
|
60
83
|
if (hasError) {
|
|
@@ -74,41 +97,79 @@ export function transformAndAnalyse(source, opts) {
|
|
|
74
97
|
diagnostics: Object.freeze(earlyDiagnostics.slice()),
|
|
75
98
|
});
|
|
76
99
|
}
|
|
77
|
-
const
|
|
100
|
+
const rewrite = rewriteDependencyAccessors(sourceFile, depGraph, sourcePath);
|
|
101
|
+
const rewrittenSource = rewrite.transformed;
|
|
102
|
+
const plotSlots = [];
|
|
103
|
+
const injection = injectCallsiteIds(rewrittenSource, checker, {
|
|
78
104
|
sourcePath,
|
|
79
105
|
statefulByName: STATEFUL_PRIMITIVES_BY_NAME,
|
|
106
|
+
plotSlots,
|
|
80
107
|
});
|
|
81
|
-
const capabilities = extractCapabilities(sourceFile, checker, structural.kind);
|
|
82
|
-
const lookback = extractMaxLookback(sourceFile, checker, sourcePath);
|
|
83
|
-
const inputs = extractInputs(sourceFile, checker, sourcePath);
|
|
84
108
|
const alertConditions = extractAlertConditions(sourceFile, checker, sourcePath);
|
|
85
109
|
const intervalDiagnostics = [];
|
|
86
|
-
const requestedIntervalsFromCalls = extractRequestedIntervals(sourceFile, checker, inputs.inputs, intervalDiagnostics, sourcePath);
|
|
87
|
-
const requiresIntervals = extractRequiresIntervals(sourceFile, checker, intervalDiagnostics, sourcePath);
|
|
88
110
|
const lowerTfDiagnostics = validateLowerTfIntervals(sourceFile, checker, sourcePath, opts.declaredIntervals ?? []);
|
|
89
|
-
const requestedIntervals = Array.from(new Set([...requestedIntervalsFromCalls, ...requiresIntervals])).sort();
|
|
90
111
|
const { requiresIntervals: structuralRequiresIntervals, ...structuralOverrides } = structural.overrides;
|
|
91
112
|
void structuralRequiresIntervals;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
// The structural pass guarantees a single default-export drawn
|
|
114
|
+
// entry before we reach this code (errors short-circuit above);
|
|
115
|
+
// we surface the default here for manifest assembly.
|
|
116
|
+
const defaultDrawn = depGraph.drawn.find((d) => d.exportName === "default");
|
|
117
|
+
/* v8 ignore start */
|
|
118
|
+
if (defaultDrawn === undefined) {
|
|
119
|
+
// The structural pass guarantees a default-export drawn entry
|
|
120
|
+
// before we reach this code (errors short-circuit above).
|
|
121
|
+
throw new Error("internal: depGraph.drawn missing default entry");
|
|
122
|
+
}
|
|
123
|
+
/* v8 ignore stop */
|
|
124
|
+
const isMultiExport = depGraph.drawn.length > 1;
|
|
125
|
+
// File-level extractions (single-export back-compat path) — full source
|
|
126
|
+
// walk so existing single-script manifests stay byte-identical.
|
|
127
|
+
const fileCapabilities = extractCapabilities(sourceFile, checker, structural.kind);
|
|
128
|
+
const fileLookback = extractMaxLookback(sourceFile, checker, sourcePath);
|
|
129
|
+
const fileInputs = extractInputs(sourceFile, checker, sourcePath);
|
|
130
|
+
const fileRequestedIntervalsFromCalls = extractRequestedIntervals(sourceFile, checker, fileInputs.inputs, intervalDiagnostics, sourcePath);
|
|
131
|
+
const fileRequiresIntervals = extractRequiresIntervals(sourceFile, checker, intervalDiagnostics, sourcePath);
|
|
132
|
+
const fileRequestedIntervals = Array.from(new Set([...fileRequestedIntervalsFromCalls, ...fileRequiresIntervals])).sort();
|
|
133
|
+
const namedManifests = [];
|
|
134
|
+
if (isMultiExport) {
|
|
135
|
+
for (const drawn of depGraph.drawn) {
|
|
136
|
+
if (drawn.exportName === "default")
|
|
137
|
+
continue;
|
|
138
|
+
namedManifests.push(buildDrawnManifest(drawn, depGraph, sourceFile, checker, sourcePath, structural.kind, structuralOverrides, alertConditions.alertConditions));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const defaultDependencies = buildDependencyDeclarations(defaultDrawn, depGraph, sourcePath);
|
|
142
|
+
const defaultOutputs = defaultDrawn.outputs.length === 0 ? undefined : defaultDrawn.outputs;
|
|
143
|
+
const manifest = isMultiExport
|
|
144
|
+
? buildDrawnManifest(defaultDrawn, depGraph, sourceFile, checker, sourcePath, structural.kind, structuralOverrides, alertConditions.alertConditions, namedManifests, plotSlots)
|
|
145
|
+
: buildManifest({
|
|
146
|
+
name: structural.name,
|
|
147
|
+
kind: structural.kind,
|
|
148
|
+
capabilities: fileCapabilities,
|
|
149
|
+
requestedIntervals: fileRequestedIntervals,
|
|
150
|
+
userPickableInterval: fileInputs.userPickableInterval,
|
|
151
|
+
seriesCapacities: fileLookback.seriesCapacities,
|
|
152
|
+
maxLookback: fileLookback.maxLookback,
|
|
153
|
+
inputs: fileInputs.inputs,
|
|
154
|
+
...structuralOverrides,
|
|
155
|
+
...(fileRequiresIntervals.length === 0
|
|
156
|
+
? {}
|
|
157
|
+
: { requiresIntervals: fileRequiresIntervals }),
|
|
158
|
+
...(alertConditions.alertConditions.length === 0
|
|
159
|
+
? {}
|
|
160
|
+
: { alertConditions: alertConditions.alertConditions }),
|
|
161
|
+
...(defaultDependencies === undefined || defaultDependencies.length === 0
|
|
162
|
+
? {}
|
|
163
|
+
: { dependencies: defaultDependencies }),
|
|
164
|
+
...(defaultOutputs === undefined ? {} : { outputs: defaultOutputs }),
|
|
165
|
+
...(plotSlots.length === 0 ? {} : { plots: plotSlots }),
|
|
166
|
+
});
|
|
107
167
|
const allDiagnostics = [
|
|
108
168
|
...earlyDiagnostics,
|
|
109
169
|
...injection.diagnostics,
|
|
110
|
-
...
|
|
111
|
-
...
|
|
170
|
+
...rewrite.diagnostics,
|
|
171
|
+
...fileLookback.diagnostics,
|
|
172
|
+
...fileInputs.diagnostics,
|
|
112
173
|
...alertConditions.diagnostics,
|
|
113
174
|
...intervalDiagnostics,
|
|
114
175
|
...lowerTfDiagnostics,
|
|
@@ -117,8 +178,68 @@ export function transformAndAnalyse(source, opts) {
|
|
|
117
178
|
transformed: injection.transformed,
|
|
118
179
|
manifest,
|
|
119
180
|
diagnostics: Object.freeze(allDiagnostics.slice()),
|
|
181
|
+
...(isMultiExport ? { siblings: Object.freeze(namedManifests.slice()) } : {}),
|
|
120
182
|
});
|
|
121
183
|
}
|
|
184
|
+
function buildDrawnManifest(drawn, depGraph, sourceFile, checker, sourcePath, kind, structuralOverrides, sharedAlertConditions, siblings, plotSlots) {
|
|
185
|
+
const intervalDiagnostics = [];
|
|
186
|
+
const scope = drawn.defineCall;
|
|
187
|
+
const capabilities = extractCapabilities(sourceFile, checker, kind, scope);
|
|
188
|
+
const lookback = extractMaxLookback(sourceFile, checker, sourcePath, scope);
|
|
189
|
+
const inputs = extractInputs(sourceFile, checker, sourcePath, scope);
|
|
190
|
+
const requestedFromCalls = extractRequestedIntervals(sourceFile, checker, inputs.inputs, intervalDiagnostics, sourcePath);
|
|
191
|
+
const requiresIntervalsScoped = extractRequiresIntervals(sourceFile, checker, intervalDiagnostics, sourcePath);
|
|
192
|
+
const requestedIntervals = Array.from(new Set([...requestedFromCalls, ...requiresIntervalsScoped])).sort();
|
|
193
|
+
const dependencies = buildDependencyDeclarations(drawn, depGraph, sourcePath);
|
|
194
|
+
const outputs = drawn.outputs.length === 0 ? undefined : drawn.outputs;
|
|
195
|
+
const isDefault = drawn.exportName === "default";
|
|
196
|
+
const nameForManifest = isDefault ? readDefineCallName(drawn.defineCall) : drawn.bindingName;
|
|
197
|
+
return buildManifest({
|
|
198
|
+
name: nameForManifest,
|
|
199
|
+
kind,
|
|
200
|
+
capabilities,
|
|
201
|
+
requestedIntervals,
|
|
202
|
+
userPickableInterval: inputs.userPickableInterval,
|
|
203
|
+
seriesCapacities: lookback.seriesCapacities,
|
|
204
|
+
maxLookback: lookback.maxLookback,
|
|
205
|
+
inputs: inputs.inputs,
|
|
206
|
+
...structuralOverrides,
|
|
207
|
+
/* v8 ignore start */
|
|
208
|
+
...(requiresIntervalsScoped.length === 0
|
|
209
|
+
? {}
|
|
210
|
+
: { requiresIntervals: requiresIntervalsScoped }),
|
|
211
|
+
...(sharedAlertConditions.length === 0 ? {} : { alertConditions: sharedAlertConditions }),
|
|
212
|
+
/* v8 ignore stop */
|
|
213
|
+
...(dependencies === undefined || dependencies.length === 0 ? {} : { dependencies }),
|
|
214
|
+
...(outputs === undefined ? {} : { outputs }),
|
|
215
|
+
...(plotSlots === undefined || plotSlots.length === 0 ? {} : { plots: plotSlots }),
|
|
216
|
+
exportName: drawn.exportName,
|
|
217
|
+
isDrawn: true,
|
|
218
|
+
...(siblings !== undefined && siblings.length > 0 ? { siblings } : {}),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function readDefineCallName(defineCall) {
|
|
222
|
+
const arg = defineCall.arguments[0];
|
|
223
|
+
/* v8 ignore next 3 */
|
|
224
|
+
if (arg === undefined || !ts.isObjectLiteralExpression(arg)) {
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
for (const property of arg.properties) {
|
|
228
|
+
/* v8 ignore next */
|
|
229
|
+
if (!ts.isPropertyAssignment(property))
|
|
230
|
+
continue;
|
|
231
|
+
const name = property.name;
|
|
232
|
+
/* v8 ignore next */
|
|
233
|
+
if (!ts.isIdentifier(name) || name.text !== "name")
|
|
234
|
+
continue;
|
|
235
|
+
const initializer = property.initializer;
|
|
236
|
+
if (ts.isStringLiteral(initializer))
|
|
237
|
+
return initializer.text;
|
|
238
|
+
/* v8 ignore next */
|
|
239
|
+
}
|
|
240
|
+
/* v8 ignore next 2 */
|
|
241
|
+
return "";
|
|
242
|
+
}
|
|
122
243
|
/**
|
|
123
244
|
* Error thrown by `compile` / `compileFile` / `compileProject` when any
|
|
124
245
|
* compilation produces an error-severity diagnostic. Carries the full
|
|
@@ -166,26 +287,123 @@ const PRINTER = ts.createPrinter({
|
|
|
166
287
|
*/
|
|
167
288
|
export async function compile(source, opts) {
|
|
168
289
|
const sourcePath = opts.sourcePath ?? "script.chart.ts";
|
|
169
|
-
const
|
|
290
|
+
const resolveProducer = opts.resolveProducer ?? createDefaultProducerResolver(sourcePath, opts);
|
|
291
|
+
// Pre-scan the consumer's source for `import X from "./Y.chart"`
|
|
292
|
+
// statements + resolve them in parallel. Each producer's resolved
|
|
293
|
+
// snapshot feeds the sync lookup passed to `transformAndAnalyse`;
|
|
294
|
+
// the same snapshots become the bundler's `inlinedProducers`.
|
|
295
|
+
const preScan = preScanChartImports(source, sourcePath);
|
|
296
|
+
const resolved = await Promise.all(preScan.map(async (specifier) => {
|
|
297
|
+
const compiled = await resolveProducer(specifier, sourcePath);
|
|
298
|
+
return { specifier, compiled };
|
|
299
|
+
}));
|
|
300
|
+
const resolvedBySpecifier = new Map();
|
|
301
|
+
for (const entry of resolved) {
|
|
302
|
+
resolvedBySpecifier.set(entry.specifier, entry.compiled);
|
|
303
|
+
}
|
|
304
|
+
const transformOpts = {
|
|
170
305
|
sourcePath,
|
|
306
|
+
/* v8 ignore next 3 */
|
|
171
307
|
...(opts.declaredIntervals === undefined
|
|
172
308
|
? {}
|
|
173
309
|
: { declaredIntervals: opts.declaredIntervals }),
|
|
174
|
-
|
|
310
|
+
resolveProducer: (modSpec, expName) => {
|
|
311
|
+
const compiled = resolvedBySpecifier.get(modSpec);
|
|
312
|
+
/* v8 ignore next 3 */
|
|
313
|
+
if (compiled === undefined || compiled === null) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const manifest = compiled.drawnByExportName.get(expName);
|
|
317
|
+
/* v8 ignore next 3 */
|
|
318
|
+
if (manifest === undefined) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
/* v8 ignore next */
|
|
322
|
+
const outputs = manifest.outputs ?? [];
|
|
323
|
+
return Object.freeze({
|
|
324
|
+
name: manifest.name,
|
|
325
|
+
outputs: Object.freeze(outputs.map((o) => Object.freeze({ title: o.title, kind: o.kind }))),
|
|
326
|
+
inputs: Object.fromEntries(Object.entries(manifest.inputs).map(([key, descriptor]) => [
|
|
327
|
+
key,
|
|
328
|
+
descriptor,
|
|
329
|
+
])),
|
|
330
|
+
});
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
const result = transformAndAnalyse(source, transformOpts);
|
|
175
334
|
const errors = result.diagnostics.filter((d) => d.severity === "error");
|
|
176
335
|
if (errors.length > 0) {
|
|
177
336
|
throw new CompileError(Object.freeze(errors.slice()));
|
|
178
337
|
}
|
|
179
|
-
const
|
|
338
|
+
const printedSource = PRINTER.printFile(result.transformed);
|
|
180
339
|
const sourcemap = opts.sourcemap ?? false;
|
|
340
|
+
// Walk every direct dep's transitive producer tree to build a
|
|
341
|
+
// topologically-ordered list (leaves first). Dedup by hash so a
|
|
342
|
+
// producer reached via two paths inlines exactly once.
|
|
343
|
+
const orderedProducers = [];
|
|
344
|
+
const seenHashes = new Set();
|
|
345
|
+
const collectTransitive = (p) => {
|
|
346
|
+
if (seenHashes.has(p.hash))
|
|
347
|
+
return;
|
|
348
|
+
for (const nested of p.transitiveProducers)
|
|
349
|
+
collectTransitive(nested);
|
|
350
|
+
seenHashes.add(p.hash);
|
|
351
|
+
orderedProducers.push(p);
|
|
352
|
+
};
|
|
353
|
+
for (const { compiled } of resolved) {
|
|
354
|
+
/* v8 ignore next */
|
|
355
|
+
if (compiled === null)
|
|
356
|
+
continue;
|
|
357
|
+
collectTransitive(compiled);
|
|
358
|
+
}
|
|
359
|
+
// Lower each cross-file `import <name> from "./X.chart"` line in
|
|
360
|
+
// the consumer's source to `const <name> = __producer_<hash>__default;`
|
|
361
|
+
// so the inlined producer's local binding feeds the rest of the
|
|
362
|
+
// consumer's body. Imports of non-resolved producers stay as-is so
|
|
363
|
+
// esbuild surfaces the unresolved-import error.
|
|
364
|
+
const specifierToHash = new Map();
|
|
365
|
+
for (const entry of resolved) {
|
|
366
|
+
/* v8 ignore next */
|
|
367
|
+
if (entry.compiled === null)
|
|
368
|
+
continue;
|
|
369
|
+
specifierToHash.set(entry.specifier, entry.compiled.hash);
|
|
370
|
+
}
|
|
371
|
+
const consumerSourceWithRewrittenImports = rewriteConsumerChartImports(printedSource, specifierToHash);
|
|
372
|
+
// §22.10 indicator-composition: when the default manifest declares
|
|
373
|
+
// private deps, append a hidden `export const __dependencies = [...]`
|
|
374
|
+
// BEFORE handing the source to esbuild so the bundler sees every
|
|
375
|
+
// alias binding referenced from the export graph. Pre-bundle inclusion
|
|
376
|
+
// is load-bearing — appending after `bundleModule` would let esbuild's
|
|
377
|
+
// tree-shaker drop aliases declared via `const trend = baseTrend;`
|
|
378
|
+
// (cross-file aliases reduce to a bare reference after the §22.10
|
|
379
|
+
// `withInputs` chain rewrite, which esbuild treats as side-effect-free
|
|
380
|
+
// and DCE-eligible).
|
|
381
|
+
const defaultDeps = result.manifest.dependencies ?? [];
|
|
382
|
+
const depsAssignment = formatDependenciesAssignment(defaultDeps.map((d) => ({
|
|
383
|
+
localId: d.localId,
|
|
384
|
+
bindingExpression: d.localId,
|
|
385
|
+
...(Object.keys(d.effectiveInputs).length === 0
|
|
386
|
+
? {}
|
|
387
|
+
: { effectiveInputs: d.effectiveInputs }),
|
|
388
|
+
})));
|
|
389
|
+
const transformedSource = `${consumerSourceWithRewrittenImports}\n${depsAssignment}`;
|
|
390
|
+
const inlinedProducers = orderedProducers.map((p) => ({
|
|
391
|
+
hash: p.hash,
|
|
392
|
+
rewrittenSource: p.rewrittenSource,
|
|
393
|
+
}));
|
|
181
394
|
const bundle = await bundleModule({
|
|
182
395
|
transformedSource,
|
|
183
396
|
sourcePath,
|
|
184
397
|
sourcemap,
|
|
185
398
|
minify: opts.minify ?? false,
|
|
399
|
+
...(inlinedProducers.length === 0 ? {} : { inlinedProducers }),
|
|
400
|
+
...(opts.inMemoryModules === undefined ? {} : { inMemoryModules: opts.inMemoryModules }),
|
|
186
401
|
});
|
|
187
|
-
const
|
|
188
|
-
|
|
402
|
+
const sidecar = result.siblings === undefined
|
|
403
|
+
? result.manifest
|
|
404
|
+
: Object.freeze([result.manifest, ...result.siblings]);
|
|
405
|
+
const moduleSource = `${bundle.moduleSource}${formatManifestAssignment(sidecar)}`;
|
|
406
|
+
const types = emitTypes({ manifest: sidecar, sourcePath });
|
|
189
407
|
if (bundle.sourcemap !== undefined) {
|
|
190
408
|
return Object.freeze({
|
|
191
409
|
moduleSource,
|
|
@@ -253,8 +471,11 @@ export async function compileFile(path, opts) {
|
|
|
253
471
|
const jsPath = `${base}.chart.js`;
|
|
254
472
|
const manifestPath = `${base}.chart.manifest.json`;
|
|
255
473
|
const dtsPath = `${base}.chart.d.ts`;
|
|
474
|
+
const sidecar = result.manifest.siblings === undefined
|
|
475
|
+
? result.manifest
|
|
476
|
+
: Object.freeze([result.manifest, ...result.manifest.siblings]);
|
|
256
477
|
await writeAtomic(jsPath, result.moduleSource);
|
|
257
|
-
await writeAtomic(manifestPath, JSON.stringify(
|
|
478
|
+
await writeAtomic(manifestPath, JSON.stringify(sidecar, null, 4));
|
|
258
479
|
await writeAtomic(dtsPath, result.types);
|
|
259
480
|
if ((opts.sourcemap === true || opts.sourcemap === "external") &&
|
|
260
481
|
result.sourcemap !== undefined) {
|
|
@@ -318,10 +539,22 @@ export async function walkChartFiles(rootDir) {
|
|
|
318
539
|
*/
|
|
319
540
|
export async function compileProject(rootDir, opts) {
|
|
320
541
|
const files = await walkChartFiles(rootDir);
|
|
542
|
+
let absoluteRoot;
|
|
543
|
+
if (isAbsolute(rootDir)) {
|
|
544
|
+
absoluteRoot = rootDir;
|
|
545
|
+
/* v8 ignore next 3 */
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
absoluteRoot = resolvePath(process.cwd(), rootDir);
|
|
549
|
+
}
|
|
550
|
+
const sharedResolver = opts.resolveProducer ??
|
|
551
|
+
createProducerResolver({ rootDir: absoluteRoot }, (source, producerSourcePath) => compileProducerArtefacts(source, producerSourcePath, sharedResolver));
|
|
321
552
|
const compiled = await Promise.all(files.map((file) => compileFile(file, {
|
|
322
553
|
...opts,
|
|
323
554
|
write: false,
|
|
324
555
|
sourcePath: toPosixRelative(process.cwd(), file),
|
|
556
|
+
resolveProducer: sharedResolver,
|
|
557
|
+
rootDir: absoluteRoot,
|
|
325
558
|
})));
|
|
326
559
|
return Object.freeze(compiled);
|
|
327
560
|
}
|
|
@@ -336,8 +569,49 @@ async function readDirEntries(dir) {
|
|
|
336
569
|
isFile: entry.isFile(),
|
|
337
570
|
}));
|
|
338
571
|
}
|
|
572
|
+
function buildDependencyDeclarations(drawn, depGraph, sourcePath) {
|
|
573
|
+
if (drawn.consumes.length === 0)
|
|
574
|
+
return Object.freeze([]);
|
|
575
|
+
const declarations = [];
|
|
576
|
+
for (const consume of drawn.consumes) {
|
|
577
|
+
declarations.push(buildOneDependencyDeclaration(consume, depGraph, sourcePath));
|
|
578
|
+
}
|
|
579
|
+
return Object.freeze(declarations);
|
|
580
|
+
}
|
|
581
|
+
function buildOneDependencyDeclaration(consume, depGraph, sourcePath) {
|
|
582
|
+
const ref = consume.producerRef;
|
|
583
|
+
const outputsCopy = consume.outputs.map((o) => Object.freeze({ title: o.title, kind: o.kind }));
|
|
584
|
+
/* v8 ignore next */
|
|
585
|
+
if (ref.kind !== "same-file")
|
|
586
|
+
return buildCrossFileDeclaration(consume, ref, outputsCopy);
|
|
587
|
+
const isDrawn = depGraph.drawn.some((d) => d.bindingName === ref.bindingName && d.exportName !== "default");
|
|
588
|
+
const exportName = depGraph.drawn.find((d) => d.bindingName === ref.bindingName)?.exportName ??
|
|
589
|
+
ref.bindingName;
|
|
590
|
+
return Object.freeze({
|
|
591
|
+
localId: consume.localId,
|
|
592
|
+
producerName: ref.bindingName,
|
|
593
|
+
producerSourcePath: sourcePath,
|
|
594
|
+
producerExportName: exportName,
|
|
595
|
+
effectiveInputs: Object.freeze({ ...consume.effectiveInputs }),
|
|
596
|
+
outputs: Object.freeze(outputsCopy),
|
|
597
|
+
isDrawn,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
/* v8 ignore start */
|
|
601
|
+
function buildCrossFileDeclaration(consume, ref, outputsCopy) {
|
|
602
|
+
return Object.freeze({
|
|
603
|
+
localId: consume.localId,
|
|
604
|
+
producerName: ref.exportName,
|
|
605
|
+
producerSourcePath: ref.sourcePath,
|
|
606
|
+
producerExportName: ref.exportName,
|
|
607
|
+
effectiveInputs: Object.freeze({ ...consume.effectiveInputs }),
|
|
608
|
+
outputs: Object.freeze(outputsCopy),
|
|
609
|
+
isDrawn: false,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
/* v8 ignore stop */
|
|
339
613
|
function stripWriteFlag(opts) {
|
|
340
|
-
const { apiVersion, sourcePath, sourcemap, minify, target, declaredIntervals } = opts;
|
|
614
|
+
const { apiVersion, sourcePath, sourcemap, minify, target, declaredIntervals, resolveProducer, rootDir, } = opts;
|
|
341
615
|
const out = { apiVersion };
|
|
342
616
|
if (sourcePath !== undefined)
|
|
343
617
|
out.sourcePath = sourcePath;
|
|
@@ -349,6 +623,182 @@ function stripWriteFlag(opts) {
|
|
|
349
623
|
out.target = target;
|
|
350
624
|
if (declaredIntervals !== undefined)
|
|
351
625
|
out.declaredIntervals = declaredIntervals;
|
|
626
|
+
if (resolveProducer !== undefined)
|
|
627
|
+
out.resolveProducer = resolveProducer;
|
|
628
|
+
if (rootDir !== undefined)
|
|
629
|
+
out.rootDir = rootDir;
|
|
352
630
|
return out;
|
|
353
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Lower each cross-file `import <name> from "./X.chart"` line in the
|
|
634
|
+
* consumer's printed TS source to `const <name> = __producer_<hash>__default;`
|
|
635
|
+
* so the inlined producer's local binding wires into the consumer's body.
|
|
636
|
+
* Imports of unresolved specifiers (no entry in `specifierToHash`) stay
|
|
637
|
+
* as-is so esbuild surfaces the resolution failure.
|
|
638
|
+
*
|
|
639
|
+
* Phase 1 only supports the default-import form — named imports remain
|
|
640
|
+
* untouched and will be addressed when same-file named-export composition
|
|
641
|
+
* crosses file boundaries.
|
|
642
|
+
*
|
|
643
|
+
* @since 0.7
|
|
644
|
+
*/
|
|
645
|
+
function rewriteConsumerChartImports(source, specifierToHash) {
|
|
646
|
+
if (specifierToHash.size === 0)
|
|
647
|
+
return source;
|
|
648
|
+
return source.replace(/^\s*import\s+([A-Za-z_$][A-Za-z0-9_$]*)\s+from\s+(['"])([^'"]+)\2;\s*$/gm, (match, name, _quote, specifier) => {
|
|
649
|
+
const hash = specifierToHash.get(specifier);
|
|
650
|
+
/* v8 ignore next */
|
|
651
|
+
if (hash === undefined)
|
|
652
|
+
return match;
|
|
653
|
+
return `const ${name} = __producer_${hash}__default;`;
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Pre-scan a `.chart.ts` source for `.chart.ts` / `.chart` import
|
|
658
|
+
* specifiers using a one-pass AST walk. Returns a deduplicated array of
|
|
659
|
+
* specifiers in source-declaration order. The list feeds `compile`'s
|
|
660
|
+
* async resolver before `transformAndAnalyse` runs so the analysis pass
|
|
661
|
+
* can resolve cross-file producer snapshots synchronously.
|
|
662
|
+
*
|
|
663
|
+
* @since 0.7
|
|
664
|
+
*/
|
|
665
|
+
function preScanChartImports(source, sourcePath) {
|
|
666
|
+
const sourceFile = ts.createSourceFile(sourcePath, source, ts.ScriptTarget.ES2022, true, ts.ScriptKind.TS);
|
|
667
|
+
const specifiers = [];
|
|
668
|
+
const seen = new Set();
|
|
669
|
+
for (const statement of sourceFile.statements) {
|
|
670
|
+
if (!ts.isImportDeclaration(statement))
|
|
671
|
+
continue;
|
|
672
|
+
const specifier = statement.moduleSpecifier;
|
|
673
|
+
/* v8 ignore next 3 */
|
|
674
|
+
if (!ts.isStringLiteral(specifier)) {
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
const text = specifier.text;
|
|
678
|
+
if (!text.endsWith(".chart") && !text.endsWith(".chart.ts"))
|
|
679
|
+
continue;
|
|
680
|
+
/* v8 ignore next 3 */
|
|
681
|
+
if (seen.has(text)) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
seen.add(text);
|
|
685
|
+
specifiers.push(text);
|
|
686
|
+
}
|
|
687
|
+
return Object.freeze(specifiers);
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Build a per-`compile` cross-file resolver rooted at the consumer's
|
|
691
|
+
* directory. `compileProject` overrides this by sharing a single
|
|
692
|
+
* resolver across every file so the inline-once invariant holds.
|
|
693
|
+
*
|
|
694
|
+
* @since 0.7
|
|
695
|
+
*/
|
|
696
|
+
function createDefaultProducerResolver(sourcePath, opts) {
|
|
697
|
+
let absSourcePath;
|
|
698
|
+
/* v8 ignore next 3 */
|
|
699
|
+
if (isAbsolute(sourcePath)) {
|
|
700
|
+
absSourcePath = sourcePath;
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
absSourcePath = resolvePath(process.cwd(), sourcePath);
|
|
704
|
+
}
|
|
705
|
+
const rootDir = opts.rootDir ?? dirname(absSourcePath);
|
|
706
|
+
const resolver = createProducerResolver({ rootDir }, (source, producerSourcePath) => compileProducerArtefacts(source, producerSourcePath, resolver));
|
|
707
|
+
return resolver;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Compile one producer file for the resolver. Pre-scans its imports,
|
|
711
|
+
* resolves them recursively (so transitive producers populate the
|
|
712
|
+
* cache before we hand control back), runs the producer's own
|
|
713
|
+
* `compile` + `transformAndAnalyse`, and returns the artefacts the
|
|
714
|
+
* resolver wraps into a {@link ProducerCompiled} snapshot.
|
|
715
|
+
*
|
|
716
|
+
* @since 0.7
|
|
717
|
+
*/
|
|
718
|
+
async function compileProducerArtefacts(source, producerSourcePath, resolver) {
|
|
719
|
+
try {
|
|
720
|
+
// Pre-resolve the producer's own cross-file deps so the
|
|
721
|
+
// recursive `compile` can pull the matching snapshot from the
|
|
722
|
+
// shared resolver's cache instead of compiling twice.
|
|
723
|
+
const preScan = preScanChartImports(source, producerSourcePath);
|
|
724
|
+
const nested = await Promise.all(preScan.map(async (specifier) => ({
|
|
725
|
+
specifier,
|
|
726
|
+
compiled: await resolver(specifier, producerSourcePath),
|
|
727
|
+
})));
|
|
728
|
+
const result = await compile(source, {
|
|
729
|
+
apiVersion: 1,
|
|
730
|
+
sourcePath: producerSourcePath,
|
|
731
|
+
resolveProducer: resolver,
|
|
732
|
+
});
|
|
733
|
+
const transformAndAnalyseResult = transformAndAnalyse(source, {
|
|
734
|
+
sourcePath: producerSourcePath,
|
|
735
|
+
resolveProducer: buildSyncSnapshotResolver(nested),
|
|
736
|
+
});
|
|
737
|
+
const transformedSource = PRINTER.printFile(transformAndAnalyseResult.transformed);
|
|
738
|
+
const transitiveProducers = [];
|
|
739
|
+
const specifierToHash = new Map();
|
|
740
|
+
for (const { specifier, compiled } of nested) {
|
|
741
|
+
/* v8 ignore next */
|
|
742
|
+
if (compiled === null)
|
|
743
|
+
continue;
|
|
744
|
+
transitiveProducers.push(compiled);
|
|
745
|
+
specifierToHash.set(specifier, compiled.hash);
|
|
746
|
+
}
|
|
747
|
+
let siblings;
|
|
748
|
+
if (transformAndAnalyseResult.siblings === undefined) {
|
|
749
|
+
siblings = Object.freeze([]);
|
|
750
|
+
/* v8 ignore next 3 */
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
siblings = transformAndAnalyseResult.siblings;
|
|
754
|
+
}
|
|
755
|
+
return Object.freeze({
|
|
756
|
+
moduleSource: result.moduleSource,
|
|
757
|
+
transformedSource,
|
|
758
|
+
manifest: result.manifest,
|
|
759
|
+
siblings,
|
|
760
|
+
transitiveProducers: Object.freeze(transitiveProducers),
|
|
761
|
+
specifierToHash,
|
|
762
|
+
});
|
|
763
|
+
/* v8 ignore next 3 */
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Build a sync snapshot resolver from a pre-resolved list of cross-file
|
|
771
|
+
* producers. Returns the producer manifest's `ProducerSnapshot` shape
|
|
772
|
+
* `transformAndAnalyse` calls when walking consumer-side
|
|
773
|
+
* `<binding>.output("title")` references.
|
|
774
|
+
*/
|
|
775
|
+
function buildSyncSnapshotResolver(nested) {
|
|
776
|
+
const bySpecifier = new Map();
|
|
777
|
+
for (const entry of nested) {
|
|
778
|
+
if (entry.compiled !== null)
|
|
779
|
+
bySpecifier.set(entry.specifier, entry.compiled);
|
|
780
|
+
}
|
|
781
|
+
return (modSpec, expName) => {
|
|
782
|
+
const compiled = bySpecifier.get(modSpec);
|
|
783
|
+
/* v8 ignore next 3 */
|
|
784
|
+
if (compiled === undefined) {
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
const manifest = compiled.drawnByExportName.get(expName);
|
|
788
|
+
/* v8 ignore next 3 */
|
|
789
|
+
if (manifest === undefined) {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
/* v8 ignore next */
|
|
793
|
+
const outputs = manifest.outputs ?? [];
|
|
794
|
+
return Object.freeze({
|
|
795
|
+
name: manifest.name,
|
|
796
|
+
outputs: Object.freeze(outputs.map((o) => Object.freeze({ title: o.title, kind: o.kind }))),
|
|
797
|
+
inputs: Object.fromEntries(Object.entries(manifest.inputs).map(([key, descriptor]) => [
|
|
798
|
+
key,
|
|
799
|
+
descriptor,
|
|
800
|
+
])),
|
|
801
|
+
});
|
|
802
|
+
};
|
|
803
|
+
}
|
|
354
804
|
//# sourceMappingURL=api.js.map
|