@shapeshift-labs/frontier-lang-compiler 0.2.17 → 0.2.19
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/README.md +45 -0
- package/bench/smoke.mjs +15 -1
- package/dist/index.d.ts +134 -0
- package/dist/index.js +668 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -147,6 +147,51 @@ console.log(sidecar.ownershipRegions[0].key); // source#src/runtime.ts#class#Run
|
|
|
147
147
|
console.log(sidecar.patchHints[0].supportedOperations); // source-region patch operations
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
Compare before/after native source imports from a worker patch and emit a semantic change set for admission scoring:
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
import { diffNativeSources } from '@shapeshift-labs/frontier-lang-compiler';
|
|
154
|
+
|
|
155
|
+
const changeSet = diffNativeSources({
|
|
156
|
+
language: 'javascript',
|
|
157
|
+
sourcePath: 'src/runtime.js',
|
|
158
|
+
beforeSourceText: 'export function step(frame) { return frame + 1; }\n',
|
|
159
|
+
afterSourceText: 'export function step(frame) { return frame + 2; }\n'
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
console.log(changeSet.changedSymbols[0]?.changeKind); // "modified"
|
|
163
|
+
console.log(changeSet.changedRegions[0]?.conflictKey); // semantic ownership key
|
|
164
|
+
console.log(changeSet.mergeCandidate.readiness); // merge-admission classification
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use `diffNativeSourceImports` when the worker or runner already produced `importNativeSource` results. Body-only edits that the lightweight scanner cannot anchor to a symbol are still reported as file-level changed regions instead of being silently treated as safe.
|
|
168
|
+
|
|
169
|
+
Compile native source imports through the same reader/IR/writer facade that swarms use for sidecar evidence. Same-language targets preserve exact source when hashes match; cross-language targets emit declaration stubs until a real adapter provides stronger evidence:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
import { compileNativeSource } from '@shapeshift-labs/frontier-lang-compiler';
|
|
173
|
+
|
|
174
|
+
const compiledJs = compileNativeSource({
|
|
175
|
+
language: 'javascript',
|
|
176
|
+
sourcePath: 'src/runtime.js',
|
|
177
|
+
sourceText: 'export function step(frame) { return frame + 1; }\n'
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
console.log(compiledJs.outputMode); // "preserved-source"
|
|
181
|
+
console.log(compiledJs.readiness.readiness); // scanner imports can still be "needs-review"
|
|
182
|
+
|
|
183
|
+
const rustCandidate = compileNativeSource(compiledJs.importResult, {
|
|
184
|
+
target: 'rust',
|
|
185
|
+
emitOnBlocked: true
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
console.log(rustCandidate.outputMode); // "target-stubs"
|
|
189
|
+
console.log(rustCandidate.targetCoverage.lossClass); // "missingAdapter" without a JS-to-Rust adapter
|
|
190
|
+
console.log(rustCandidate.ok); // true only because emitOnBlocked requested code anyway
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`compileNativeSource` returns the import result, projection, target loss matrix cell, combined losses, readiness, evidence, and output hash. Admission queues should treat `ok` as "code was emitted", not as merge approval; `readiness` and `targetCoverage` carry the merge signal.
|
|
194
|
+
|
|
150
195
|
Project a native import back to source. Exact source is preserved when the import carries matching source-preservation evidence or when supplied text matches the import hash; otherwise the compiler emits declaration stubs with review-required loss evidence:
|
|
151
196
|
|
|
152
197
|
```js
|
package/bench/smoke.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
2
|
import {
|
|
3
|
+
compileNativeSource,
|
|
3
4
|
compileFrontierSource,
|
|
4
5
|
createEstreeNativeImporterAdapter,
|
|
5
6
|
createNativeImportCoverageMatrix,
|
|
@@ -92,6 +93,15 @@ const nativeProjections = nativeImportResults.map((imported) => projectNativeImp
|
|
|
92
93
|
const projectionDurationMs = performance.now() - projectionStart;
|
|
93
94
|
const projectionBytes = nativeProjections.reduce((sum, projection) => sum + projection.sourceText.length, 0);
|
|
94
95
|
|
|
96
|
+
const nativeCompileStart = performance.now();
|
|
97
|
+
const nativeCompiles = nativeImportResults.map((imported, index) => compileNativeSource(imported, {
|
|
98
|
+
target: index % 2 === 0 ? 'javascript' : 'rust',
|
|
99
|
+
emitOnBlocked: true
|
|
100
|
+
}));
|
|
101
|
+
const nativeCompileDurationMs = performance.now() - nativeCompileStart;
|
|
102
|
+
const nativeCompileBytes = nativeCompiles.reduce((sum, result) => sum + result.output.length, 0);
|
|
103
|
+
const nativeCompileBlocked = nativeCompiles.filter((result) => result.readiness.readiness === 'blocked').length;
|
|
104
|
+
|
|
95
105
|
console.log(JSON.stringify({
|
|
96
106
|
compiles: 250,
|
|
97
107
|
bytes,
|
|
@@ -118,5 +128,9 @@ console.log(JSON.stringify({
|
|
|
118
128
|
sidecarDurationMs: Number(sidecarDurationMs.toFixed(2)),
|
|
119
129
|
nativeProjections: nativeProjections.length,
|
|
120
130
|
projectionBytes,
|
|
121
|
-
projectionDurationMs: Number(projectionDurationMs.toFixed(2))
|
|
131
|
+
projectionDurationMs: Number(projectionDurationMs.toFixed(2)),
|
|
132
|
+
nativeCompiles: nativeCompiles.length,
|
|
133
|
+
nativeCompileBytes,
|
|
134
|
+
nativeCompileBlocked,
|
|
135
|
+
nativeCompileDurationMs: Number(nativeCompileDurationMs.toFixed(2))
|
|
122
136
|
}));
|
package/dist/index.d.ts
CHANGED
|
@@ -730,6 +730,100 @@ export interface SemanticImportSidecarOptions {
|
|
|
730
730
|
readonly metadata?: Record<string, unknown>;
|
|
731
731
|
}
|
|
732
732
|
|
|
733
|
+
export type NativeSourceChangeKind = 'added' | 'removed' | 'modified' | 'unchanged';
|
|
734
|
+
|
|
735
|
+
export interface NativeSourceChangeSymbol {
|
|
736
|
+
readonly changeKind: NativeSourceChangeKind;
|
|
737
|
+
readonly key: string;
|
|
738
|
+
readonly id?: string;
|
|
739
|
+
readonly name?: string;
|
|
740
|
+
readonly kind?: string;
|
|
741
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
742
|
+
readonly nativeAstNodeId?: string;
|
|
743
|
+
readonly semanticOccurrenceId?: string;
|
|
744
|
+
readonly sourceMapMappingId?: string;
|
|
745
|
+
readonly sourceSpan?: SourceSpan;
|
|
746
|
+
readonly beforeSignatureHash?: string;
|
|
747
|
+
readonly afterSignatureHash?: string;
|
|
748
|
+
readonly beforeSpanHash?: string;
|
|
749
|
+
readonly afterSpanHash?: string;
|
|
750
|
+
readonly beforeOwnershipKey?: string;
|
|
751
|
+
readonly afterOwnershipKey?: string;
|
|
752
|
+
readonly ownershipRegionId?: string;
|
|
753
|
+
readonly ownershipKey?: string;
|
|
754
|
+
readonly ownershipRegionKind?: NativeImportRegionTaxonomyKind;
|
|
755
|
+
readonly conflictKey: string;
|
|
756
|
+
readonly readiness: SemanticMergeReadiness;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export interface NativeSourceChangeRegion extends SemanticImportOwnershipRegion {
|
|
760
|
+
readonly changeKind: NativeSourceChangeKind;
|
|
761
|
+
readonly conflictKey: string;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export interface NativeSourceChangeSummary {
|
|
765
|
+
readonly sourceChanged: boolean;
|
|
766
|
+
readonly symbols: number;
|
|
767
|
+
readonly regions: number;
|
|
768
|
+
readonly addedSymbols: number;
|
|
769
|
+
readonly removedSymbols: number;
|
|
770
|
+
readonly modifiedSymbols: number;
|
|
771
|
+
readonly byRegionKind: Readonly<Record<string, number>>;
|
|
772
|
+
readonly byChangeKind: Readonly<Record<string, number>>;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
export interface DiffNativeSourceImportsOptions {
|
|
776
|
+
readonly id?: string;
|
|
777
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
778
|
+
readonly sourcePath?: string;
|
|
779
|
+
readonly parser?: string;
|
|
780
|
+
readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
781
|
+
readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
782
|
+
readonly beforeSourceHash?: string;
|
|
783
|
+
readonly afterSourceHash?: string;
|
|
784
|
+
readonly generatedAt?: number;
|
|
785
|
+
readonly regionPrefix?: string;
|
|
786
|
+
readonly evidenceId?: string;
|
|
787
|
+
readonly evidenceStatus?: EvidenceRecord['status'];
|
|
788
|
+
readonly patchId?: string;
|
|
789
|
+
readonly mergeCandidateId?: string;
|
|
790
|
+
readonly author?: string;
|
|
791
|
+
readonly metadata?: Record<string, unknown>;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
export interface DiffNativeSourcesOptions extends Omit<DiffNativeSourceImportsOptions, 'before' | 'after'> {
|
|
795
|
+
readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
796
|
+
readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
|
|
797
|
+
readonly beforeSourceText?: string;
|
|
798
|
+
readonly afterSourceText?: string;
|
|
799
|
+
readonly beforeMetadata?: Record<string, unknown>;
|
|
800
|
+
readonly afterMetadata?: Record<string, unknown>;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
export interface NativeSourceChangeSet {
|
|
804
|
+
readonly kind: 'frontier.lang.nativeSourceChangeSet';
|
|
805
|
+
readonly version: 1;
|
|
806
|
+
readonly id: string;
|
|
807
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
808
|
+
readonly sourcePath?: string;
|
|
809
|
+
readonly before?: NativeSourceImportResult;
|
|
810
|
+
readonly after?: NativeSourceImportResult;
|
|
811
|
+
readonly beforeHash?: string;
|
|
812
|
+
readonly afterHash?: string;
|
|
813
|
+
readonly changedSymbols: readonly NativeSourceChangeSymbol[];
|
|
814
|
+
readonly changedRegions: readonly NativeSourceChangeRegion[];
|
|
815
|
+
readonly patch: SemanticPatchBundle;
|
|
816
|
+
readonly mergeCandidate: SemanticMergeCandidateRecord;
|
|
817
|
+
readonly evidence: readonly EvidenceRecord[];
|
|
818
|
+
readonly readiness: SemanticMergeReadiness;
|
|
819
|
+
readonly reasons: readonly string[];
|
|
820
|
+
readonly sourceMaps: readonly SourceMapRecord[];
|
|
821
|
+
readonly semanticIndex?: SemanticIndexRecord;
|
|
822
|
+
readonly losses: readonly NativeAstLossRecord[];
|
|
823
|
+
readonly summary: NativeSourceChangeSummary;
|
|
824
|
+
readonly metadata?: Record<string, unknown>;
|
|
825
|
+
}
|
|
826
|
+
|
|
733
827
|
export type NativeImporterAdapterExactness =
|
|
734
828
|
| 'exact-parser-ast'
|
|
735
829
|
| 'parser-tree'
|
|
@@ -1127,6 +1221,43 @@ export interface NativeSourceProjectionResult {
|
|
|
1127
1221
|
readonly metadata: Record<string, unknown>;
|
|
1128
1222
|
}
|
|
1129
1223
|
|
|
1224
|
+
export type NativeSourceCompileOutputMode = NativeSourceProjectionMode | 'target-stubs';
|
|
1225
|
+
|
|
1226
|
+
export interface CompileNativeSourceOptions extends ProjectNativeImportToSourceOptions {
|
|
1227
|
+
readonly target?: FrontierCompileTarget | string;
|
|
1228
|
+
readonly adapters?: readonly NativeImporterAdapter[];
|
|
1229
|
+
readonly languages?: readonly NativeImportLanguageProfile[];
|
|
1230
|
+
readonly generatedAt?: number;
|
|
1231
|
+
readonly emitOnBlocked?: boolean;
|
|
1232
|
+
readonly projectionId?: string;
|
|
1233
|
+
readonly projectionEvidenceId?: string;
|
|
1234
|
+
readonly compileEvidenceId?: string;
|
|
1235
|
+
readonly evidence?: readonly EvidenceRecord[];
|
|
1236
|
+
readonly losses?: readonly NativeAstLossRecord[];
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
export interface NativeSourceCompileResult {
|
|
1240
|
+
readonly kind: 'frontier.lang.nativeSourceCompileResult';
|
|
1241
|
+
readonly version: 1;
|
|
1242
|
+
readonly id: string;
|
|
1243
|
+
readonly ok: boolean;
|
|
1244
|
+
readonly target: FrontierCompileTarget | string;
|
|
1245
|
+
readonly language: FrontierSourceLanguage | string;
|
|
1246
|
+
readonly sourcePath?: string;
|
|
1247
|
+
readonly output: string;
|
|
1248
|
+
readonly outputHash: string;
|
|
1249
|
+
readonly outputMode: NativeSourceCompileOutputMode;
|
|
1250
|
+
readonly importResult: NativeSourceImportResult;
|
|
1251
|
+
readonly projection: NativeSourceProjectionResult;
|
|
1252
|
+
readonly targetCoverage: ProjectionTargetCoverageEntry;
|
|
1253
|
+
readonly projectionMatrix: ProjectionTargetLossMatrix;
|
|
1254
|
+
readonly losses: readonly NativeAstLossRecord[];
|
|
1255
|
+
readonly lossSummary: NativeImportLossSummary;
|
|
1256
|
+
readonly readiness: NativeImportReadinessClassification;
|
|
1257
|
+
readonly evidence: readonly EvidenceRecord[];
|
|
1258
|
+
readonly metadata: Record<string, unknown>;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1130
1261
|
export type NativeImportRoundtripReadinessStatus =
|
|
1131
1262
|
| 'exact'
|
|
1132
1263
|
| 'preserved-source'
|
|
@@ -1192,6 +1323,7 @@ export declare const NativeImportLanguageProfiles: readonly NativeImportLanguage
|
|
|
1192
1323
|
export declare function normalizeCompileTarget(target?: string): FrontierCompileTarget;
|
|
1193
1324
|
export declare function compileFrontierSource(source: string, options?: FrontierCompileOptions): FrontierCompileResult;
|
|
1194
1325
|
export declare function compileFrontierDocument(document: FrontierLangDocument, options?: FrontierCompileOptions): FrontierCompileResult;
|
|
1326
|
+
export declare function compileNativeSource(input: ImportNativeSourceOptions | NativeSourceImportResult, options?: CompileNativeSourceOptions): NativeSourceCompileResult;
|
|
1195
1327
|
export declare function projectFrontierAst(document: FrontierLangDocument, target?: FrontierCompileOptions['target'], options?: FrontierCompileEmitOptions): FrontierTargetAst;
|
|
1196
1328
|
export declare function renderTargetAst(ast: FrontierTargetAst, target?: FrontierCompileOptions['target']): string;
|
|
1197
1329
|
export declare function renderTargetAstWithSourceMap(ast: FrontierTargetAst, target?: FrontierCompileOptions['target'], options?: FrontierCompileEmitOptions): FrontierTargetSourceMapResult;
|
|
@@ -1212,6 +1344,8 @@ export declare function createTreeSitterNativeImporterAdapter(options?: TreeSitt
|
|
|
1212
1344
|
export declare function runNativeImporterAdapter(adapter: NativeImporterAdapter, input: RunNativeImporterAdapterOptions): Promise<NativeImporterAdapterImportResult>;
|
|
1213
1345
|
export declare function projectNativeImportToSource(importResult: NativeSourceImportResult | NativeProjectImportResult, options?: ProjectNativeImportToSourceOptions): NativeSourceProjectionResult;
|
|
1214
1346
|
export declare function importNativeSource(input: ImportNativeSourceOptions): NativeSourceImportResult;
|
|
1347
|
+
export declare function diffNativeSources(input: DiffNativeSourcesOptions): NativeSourceChangeSet;
|
|
1348
|
+
export declare function diffNativeSourceImports(input: DiffNativeSourceImportsOptions): NativeSourceChangeSet;
|
|
1215
1349
|
export declare function importNativeProject(input: ImportNativeProjectOptions): Promise<NativeProjectImportResult>;
|
|
1216
1350
|
export declare function createUniversalAstFromDocument(document: FrontierLangDocument, input?: {
|
|
1217
1351
|
readonly id?: string;
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createNativeAstRecord,
|
|
5
5
|
createPatch,
|
|
6
6
|
createSemanticIndexRecord,
|
|
7
|
+
createSemanticMergeCandidateRecord,
|
|
7
8
|
createSourceMapRecord,
|
|
8
9
|
createUniversalAstEnvelope,
|
|
9
10
|
hashDocumentBase,
|
|
@@ -252,6 +253,116 @@ export function compileFrontierDocument(document, options = {}) {
|
|
|
252
253
|
};
|
|
253
254
|
}
|
|
254
255
|
|
|
256
|
+
export function compileNativeSource(input, options = {}) {
|
|
257
|
+
const importResult = isNativeSourceImportResult(input) ? input : importNativeSource(input);
|
|
258
|
+
const sourceLanguage = nativeCompileSourceLanguage(importResult, input);
|
|
259
|
+
const target = nativeCompileTarget(input, importResult, options);
|
|
260
|
+
const sourceTarget = nativeLanguageCompileTarget(sourceLanguage);
|
|
261
|
+
const sameSourceTarget = sourceTarget === target;
|
|
262
|
+
const idPart = idFragment(options.id ?? importResult.id ?? importResult.sourcePath ?? sourceLanguage ?? target);
|
|
263
|
+
const id = options.id ?? `native_source_compile_${idPart}_${idFragment(target)}`;
|
|
264
|
+
const projection = projectNativeImportToSource(importResult, {
|
|
265
|
+
...options,
|
|
266
|
+
id: options.projectionId ?? `${id}_projection`,
|
|
267
|
+
language: sameSourceTarget ? sourceLanguage : target,
|
|
268
|
+
preferPreservedSource: sameSourceTarget ? options.preferPreservedSource : false,
|
|
269
|
+
evidenceId: options.projectionEvidenceId ?? options.evidenceId,
|
|
270
|
+
metadata: {
|
|
271
|
+
sourceLanguage,
|
|
272
|
+
target,
|
|
273
|
+
nativeCompileResultId: id,
|
|
274
|
+
...options.metadata
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const projectionMatrix = createProjectionTargetLossMatrix({
|
|
278
|
+
imports: [importResult],
|
|
279
|
+
adapters: options.adapters,
|
|
280
|
+
languages: options.languages,
|
|
281
|
+
targets: [target],
|
|
282
|
+
generatedAt: options.generatedAt
|
|
283
|
+
});
|
|
284
|
+
const targetCoverage = nativeSourceCompileTargetCoverage(projectionMatrix, sourceLanguage, target);
|
|
285
|
+
const targetLosses = nativeSourceCompileTargetLosses({
|
|
286
|
+
importResult,
|
|
287
|
+
projection,
|
|
288
|
+
targetCoverage,
|
|
289
|
+
sourceLanguage,
|
|
290
|
+
target,
|
|
291
|
+
idPart
|
|
292
|
+
});
|
|
293
|
+
const outputMode = sameSourceTarget ? projection.mode : 'target-stubs';
|
|
294
|
+
const compileEvidence = nativeSourceCompileEvidence({
|
|
295
|
+
id: options.compileEvidenceId ?? `evidence_${idPart}_${idFragment(target)}_native_source_compile`,
|
|
296
|
+
importResult,
|
|
297
|
+
projection,
|
|
298
|
+
targetCoverage,
|
|
299
|
+
targetLosses,
|
|
300
|
+
sourceLanguage,
|
|
301
|
+
target,
|
|
302
|
+
outputMode
|
|
303
|
+
});
|
|
304
|
+
const evidence = uniqueByEvidenceId([
|
|
305
|
+
...(importResult.evidence ?? []),
|
|
306
|
+
...(projection.evidence ?? []),
|
|
307
|
+
compileEvidence,
|
|
308
|
+
...(options.evidence ?? [])
|
|
309
|
+
]);
|
|
310
|
+
const losses = uniqueByLossId([
|
|
311
|
+
...(importResult.losses ?? []),
|
|
312
|
+
...(projection.losses ?? []),
|
|
313
|
+
...targetLosses,
|
|
314
|
+
...(options.losses ?? [])
|
|
315
|
+
]);
|
|
316
|
+
const lossSummary = summarizeNativeImportLosses(losses, {
|
|
317
|
+
evidence,
|
|
318
|
+
parser: importResult.nativeAst?.parser ?? importResult.nativeSource?.parser ?? options.parser,
|
|
319
|
+
scanKind: 'native-source-compile',
|
|
320
|
+
semanticStatus: importResult.metadata?.semanticStatus ?? options.semanticStatus
|
|
321
|
+
});
|
|
322
|
+
const readiness = classifyNativeImportReadiness(losses, {
|
|
323
|
+
evidence,
|
|
324
|
+
parser: importResult.nativeAst?.parser ?? importResult.nativeSource?.parser ?? options.parser,
|
|
325
|
+
scanKind: 'native-source-compile',
|
|
326
|
+
semanticStatus: importResult.metadata?.semanticStatus ?? options.semanticStatus
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
kind: 'frontier.lang.nativeSourceCompileResult',
|
|
330
|
+
version: 1,
|
|
331
|
+
id,
|
|
332
|
+
ok: readiness.readiness !== 'blocked' || options.emitOnBlocked === true,
|
|
333
|
+
target,
|
|
334
|
+
language: sourceLanguage,
|
|
335
|
+
sourcePath: importResult.sourcePath ?? importResult.nativeSource?.sourcePath,
|
|
336
|
+
output: projection.sourceText,
|
|
337
|
+
outputHash: projection.outputHash,
|
|
338
|
+
outputMode,
|
|
339
|
+
importResult,
|
|
340
|
+
projection,
|
|
341
|
+
targetCoverage,
|
|
342
|
+
projectionMatrix,
|
|
343
|
+
losses,
|
|
344
|
+
lossSummary,
|
|
345
|
+
readiness,
|
|
346
|
+
evidence,
|
|
347
|
+
metadata: {
|
|
348
|
+
nativeImportId: importResult.id,
|
|
349
|
+
nativeSourceId: importResult.nativeSource?.id,
|
|
350
|
+
nativeAstId: importResult.nativeAst?.id ?? importResult.nativeSource?.ast?.id,
|
|
351
|
+
semanticIndexId: importResult.semanticIndex?.id ?? importResult.universalAst?.semanticIndex?.id,
|
|
352
|
+
universalAstId: importResult.universalAst?.id,
|
|
353
|
+
projectionId: projection.id,
|
|
354
|
+
projectionMode: projection.mode,
|
|
355
|
+
outputMode,
|
|
356
|
+
sourceTarget,
|
|
357
|
+
sameSourceTarget,
|
|
358
|
+
targetLossClass: targetCoverage.lossClass,
|
|
359
|
+
targetReadiness: targetCoverage.readiness,
|
|
360
|
+
targetSupported: targetCoverage.supported,
|
|
361
|
+
...options.metadata
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
255
366
|
export function projectFrontierAst(document, target = 'typescript', options = {}) {
|
|
256
367
|
const normalized = normalizeCompileTarget(target);
|
|
257
368
|
const projector = projectors[normalized];
|
|
@@ -1416,6 +1527,410 @@ export function importNativeSource(input) {
|
|
|
1416
1527
|
};
|
|
1417
1528
|
}
|
|
1418
1529
|
|
|
1530
|
+
export function diffNativeSources(input) {
|
|
1531
|
+
const language = input.language ?? input.before?.language ?? input.after?.language;
|
|
1532
|
+
if (!language) throw new Error('diffNativeSources requires a language');
|
|
1533
|
+
const sourcePath = input.sourcePath ?? input.before?.sourcePath ?? input.after?.sourcePath;
|
|
1534
|
+
return diffNativeSourceImports({
|
|
1535
|
+
...input,
|
|
1536
|
+
before: input.before ?? (input.beforeSourceText === undefined ? undefined : {
|
|
1537
|
+
language,
|
|
1538
|
+
sourcePath,
|
|
1539
|
+
sourceText: input.beforeSourceText,
|
|
1540
|
+
sourceHash: input.beforeSourceHash,
|
|
1541
|
+
parser: input.parser,
|
|
1542
|
+
metadata: input.beforeMetadata
|
|
1543
|
+
}),
|
|
1544
|
+
after: input.after ?? (input.afterSourceText === undefined ? undefined : {
|
|
1545
|
+
language,
|
|
1546
|
+
sourcePath,
|
|
1547
|
+
sourceText: input.afterSourceText,
|
|
1548
|
+
sourceHash: input.afterSourceHash,
|
|
1549
|
+
parser: input.parser,
|
|
1550
|
+
metadata: input.afterMetadata
|
|
1551
|
+
})
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
export function diffNativeSourceImports(input) {
|
|
1556
|
+
const before = normalizeNativeDiffImport(input.before, input, 'before');
|
|
1557
|
+
const after = normalizeNativeDiffImport(input.after, input, 'after');
|
|
1558
|
+
if (!before && !after) throw new Error('diffNativeSourceImports requires before or after native source input');
|
|
1559
|
+
const language = input.language ?? after?.language ?? before?.language;
|
|
1560
|
+
const sourcePath = input.sourcePath ?? after?.sourcePath ?? before?.sourcePath;
|
|
1561
|
+
const beforeHash = before?.nativeSource?.sourceHash ?? before?.nativeAst?.sourceHash ?? before?.sourceHash;
|
|
1562
|
+
const afterHash = after?.nativeSource?.sourceHash ?? after?.nativeAst?.sourceHash ?? after?.sourceHash;
|
|
1563
|
+
const idPart = idFragment(input.id ?? sourcePath ?? language ?? 'native_source_change');
|
|
1564
|
+
const beforeSidecar = before ? createSemanticImportSidecar(before, { id: `sidecar_before_${idPart}`, generatedAt: input.generatedAt, regionPrefix: input.regionPrefix }) : undefined;
|
|
1565
|
+
const afterSidecar = after ? createSemanticImportSidecar(after, { id: `sidecar_after_${idPart}`, generatedAt: input.generatedAt, regionPrefix: input.regionPrefix }) : undefined;
|
|
1566
|
+
const beforeSymbols = mapDiffSymbols(before, beforeSidecar);
|
|
1567
|
+
const afterSymbols = mapDiffSymbols(after, afterSidecar);
|
|
1568
|
+
const changedSymbols = diffNativeSymbols(beforeSymbols, afterSymbols);
|
|
1569
|
+
let changedRegions = diffNativeOwnershipRegions(beforeSidecar, afterSidecar, changedSymbols);
|
|
1570
|
+
const sourceChanged = Boolean(beforeHash && afterHash && beforeHash !== afterHash);
|
|
1571
|
+
if (sourceChanged && changedSymbols.length === 0 && changedRegions.length === 0) {
|
|
1572
|
+
changedRegions = [fileLevelNativeChangeRegion({ language, sourcePath, beforeHash, afterHash, before, after })];
|
|
1573
|
+
}
|
|
1574
|
+
const evidence = [{
|
|
1575
|
+
id: input.evidenceId ?? `evidence_${idPart}_native_source_diff`,
|
|
1576
|
+
kind: 'import',
|
|
1577
|
+
status: input.evidenceStatus ?? 'passed',
|
|
1578
|
+
path: sourcePath,
|
|
1579
|
+
summary: `Compared ${language ?? 'native'} source imports: ${changedSymbols.length} changed symbol(s), ${changedRegions.length} changed region(s).`,
|
|
1580
|
+
metadata: {
|
|
1581
|
+
beforeImportId: before?.id,
|
|
1582
|
+
afterImportId: after?.id,
|
|
1583
|
+
beforeHash,
|
|
1584
|
+
afterHash,
|
|
1585
|
+
sourceChanged,
|
|
1586
|
+
addedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'added').length,
|
|
1587
|
+
removedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'removed').length,
|
|
1588
|
+
modifiedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'modified').length
|
|
1589
|
+
}
|
|
1590
|
+
}];
|
|
1591
|
+
const readiness = maxSemanticMergeReadiness(
|
|
1592
|
+
maxSemanticMergeReadiness(nativeImportReadiness(before), nativeImportReadiness(after)),
|
|
1593
|
+
sourceChanged && changedSymbols.length === 0 ? 'needs-review' : 'ready'
|
|
1594
|
+
);
|
|
1595
|
+
const reasons = nativeSourceChangeReasons({ before, after, beforeHash, afterHash, changedSymbols, changedRegions, readiness });
|
|
1596
|
+
const conflictKeys = uniqueStrings([
|
|
1597
|
+
...changedSymbols.map((symbol) => symbol.conflictKey),
|
|
1598
|
+
...changedRegions.map((region) => region.conflictKey ?? region.key ?? region.id),
|
|
1599
|
+
...(sourcePath ? [`source:${sourcePath}`] : [])
|
|
1600
|
+
]);
|
|
1601
|
+
const touches = changedRegions.map((region) => ({ id: region.id, access: 'write' }));
|
|
1602
|
+
const operations = [
|
|
1603
|
+
...(after?.nativeSource ? [{ op: 'upsertNode', node: after.nativeSource, touches: touches.length ? touches : [{ id: after.nativeSource.id, access: 'evidence' }] }] : []),
|
|
1604
|
+
...(!after && before?.nativeSource ? [{ op: 'removeNode', id: before.nativeSource.id, touches: touches.length ? touches : [{ id: before.nativeSource.id, access: 'evidence' }] }] : []),
|
|
1605
|
+
{ op: 'addEvidence', evidence: evidence[0], touches }
|
|
1606
|
+
];
|
|
1607
|
+
const patch = createPatch({
|
|
1608
|
+
id: input.patchId ?? `patch_${idPart}_native_source_diff`,
|
|
1609
|
+
baseHash: beforeHash,
|
|
1610
|
+
targetHash: afterHash,
|
|
1611
|
+
author: input.author ?? '@shapeshift-labs/frontier-lang-compiler/diffNativeSourceImports',
|
|
1612
|
+
risk: readiness === 'blocked' ? 'high' : readiness === 'needs-review' ? 'medium' : 'low',
|
|
1613
|
+
operations,
|
|
1614
|
+
evidence,
|
|
1615
|
+
metadata: {
|
|
1616
|
+
sourceLanguage: language,
|
|
1617
|
+
sourcePath,
|
|
1618
|
+
beforeImportId: before?.id,
|
|
1619
|
+
afterImportId: after?.id,
|
|
1620
|
+
beforeHash,
|
|
1621
|
+
afterHash,
|
|
1622
|
+
changedSymbols: changedSymbols.length,
|
|
1623
|
+
changedRegions: changedRegions.length
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
const mergeCandidate = createSemanticMergeCandidateRecord({
|
|
1627
|
+
id: input.mergeCandidateId ?? `merge_candidate_${idPart}_native_source_diff`,
|
|
1628
|
+
importResultId: after?.id ?? before?.id,
|
|
1629
|
+
patchId: patch.id,
|
|
1630
|
+
language,
|
|
1631
|
+
sourcePath,
|
|
1632
|
+
baseHash: beforeHash,
|
|
1633
|
+
targetHash: afterHash,
|
|
1634
|
+
touchedSymbols: changedSymbols.map(nativeChangeTouchedSymbol),
|
|
1635
|
+
touchedSemanticNodes: [],
|
|
1636
|
+
nativeSpans: nativeChangeSpans(changedSymbols, changedRegions, { language, sourcePath }),
|
|
1637
|
+
conflictKeys,
|
|
1638
|
+
readiness,
|
|
1639
|
+
reasons,
|
|
1640
|
+
evidence,
|
|
1641
|
+
metadata: {
|
|
1642
|
+
kind: 'native-source-change-set',
|
|
1643
|
+
beforeImportId: before?.id,
|
|
1644
|
+
afterImportId: after?.id,
|
|
1645
|
+
sourceChanged,
|
|
1646
|
+
changeSummary: nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged)
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
return {
|
|
1650
|
+
kind: 'frontier.lang.nativeSourceChangeSet',
|
|
1651
|
+
version: 1,
|
|
1652
|
+
id: input.id ?? `native_source_change_${idPart}`,
|
|
1653
|
+
language,
|
|
1654
|
+
sourcePath,
|
|
1655
|
+
before,
|
|
1656
|
+
after,
|
|
1657
|
+
beforeHash,
|
|
1658
|
+
afterHash,
|
|
1659
|
+
changedSymbols,
|
|
1660
|
+
changedRegions,
|
|
1661
|
+
patch,
|
|
1662
|
+
mergeCandidate,
|
|
1663
|
+
evidence,
|
|
1664
|
+
readiness,
|
|
1665
|
+
reasons,
|
|
1666
|
+
sourceMaps: uniqueRecordsById([...(before?.sourceMaps ?? []), ...(after?.sourceMaps ?? [])]),
|
|
1667
|
+
semanticIndex: after?.semanticIndex ?? before?.semanticIndex,
|
|
1668
|
+
losses: uniqueByLossId([...(before?.losses ?? []), ...(after?.losses ?? [])]),
|
|
1669
|
+
summary: nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged),
|
|
1670
|
+
metadata: {
|
|
1671
|
+
beforeSidecarId: beforeSidecar?.id,
|
|
1672
|
+
afterSidecarId: afterSidecar?.id,
|
|
1673
|
+
beforeImportContract: before?.metadata?.importResultContract,
|
|
1674
|
+
afterImportContract: after?.metadata?.importResultContract,
|
|
1675
|
+
...input.metadata
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
function normalizeNativeDiffImport(value, input, side) {
|
|
1681
|
+
if (!value) return undefined;
|
|
1682
|
+
if (value.kind === 'frontier.lang.importResult' && value.nativeSource) return value;
|
|
1683
|
+
return importNativeSource({
|
|
1684
|
+
...value,
|
|
1685
|
+
language: value.language ?? input.language,
|
|
1686
|
+
sourcePath: value.sourcePath ?? input.sourcePath,
|
|
1687
|
+
parser: value.parser ?? input.parser,
|
|
1688
|
+
metadata: {
|
|
1689
|
+
...(value.metadata ?? {}),
|
|
1690
|
+
nativeSourceDiffSide: side
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
function mapDiffSymbols(imported, sidecar) {
|
|
1696
|
+
const symbolsById = new Map((imported?.semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
|
|
1697
|
+
const mappingsBySymbolId = new Map((imported?.sourceMaps ?? [])
|
|
1698
|
+
.flatMap((sourceMap) => sourceMap.mappings ?? [])
|
|
1699
|
+
.filter((mapping) => mapping.semanticSymbolId)
|
|
1700
|
+
.map((mapping) => [mapping.semanticSymbolId, mapping]));
|
|
1701
|
+
const sourceText = nativeImportSourceText(imported);
|
|
1702
|
+
const entries = sidecar?.symbols?.length
|
|
1703
|
+
? sidecar.symbols
|
|
1704
|
+
: (imported?.semanticIndex?.symbols ?? []).map((symbol) => ({ id: symbol.id, name: symbol.name, kind: symbol.kind, sourceSpan: symbol.definitionSpan }));
|
|
1705
|
+
const result = new Map();
|
|
1706
|
+
for (const entry of entries) {
|
|
1707
|
+
const symbol = symbolsById.get(entry.id) ?? {};
|
|
1708
|
+
const mapping = mappingsBySymbolId.get(entry.id);
|
|
1709
|
+
const sourceSpan = entry.sourceSpan ?? mapping?.sourceSpan ?? symbol.definitionSpan;
|
|
1710
|
+
const key = nativeDiffSymbolKey(entry, imported);
|
|
1711
|
+
result.set(key, {
|
|
1712
|
+
key,
|
|
1713
|
+
id: entry.id,
|
|
1714
|
+
name: entry.name ?? symbol.name,
|
|
1715
|
+
kind: entry.kind ?? symbol.kind,
|
|
1716
|
+
language: entry.language ?? symbol.language ?? imported?.language,
|
|
1717
|
+
nativeAstNodeId: entry.nativeAstNodeId ?? symbol.nativeAstNodeId ?? mapping?.nativeAstNodeId,
|
|
1718
|
+
semanticOccurrenceId: entry.semanticOccurrenceId ?? mapping?.semanticOccurrenceId,
|
|
1719
|
+
sourceMapMappingId: entry.sourceMapMappingId ?? mapping?.id,
|
|
1720
|
+
sourceSpan,
|
|
1721
|
+
signatureHash: entry.signatureHash ?? symbol.signatureHash,
|
|
1722
|
+
ownershipRegionId: entry.ownershipRegionId ?? symbol.metadata?.ownershipRegionId ?? mapping?.ownershipRegionId,
|
|
1723
|
+
ownershipKey: entry.ownershipKey ?? symbol.metadata?.ownershipRegionKey ?? mapping?.ownershipRegionKey,
|
|
1724
|
+
ownershipRegionKind: entry.ownershipRegionKind ?? symbol.metadata?.ownershipRegionKind ?? mapping?.ownershipRegionKind,
|
|
1725
|
+
spanHash: hashNativeSpanText(sourceText, sourceSpan),
|
|
1726
|
+
sourcePath: sourceSpan?.path ?? imported?.sourcePath,
|
|
1727
|
+
sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash,
|
|
1728
|
+
readiness: entry.readiness ?? imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review'
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
return result;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function diffNativeSymbols(beforeSymbols, afterSymbols) {
|
|
1735
|
+
const keys = uniqueStrings([...beforeSymbols.keys(), ...afterSymbols.keys()]).sort();
|
|
1736
|
+
const changed = [];
|
|
1737
|
+
for (const key of keys) {
|
|
1738
|
+
const before = beforeSymbols.get(key);
|
|
1739
|
+
const after = afterSymbols.get(key);
|
|
1740
|
+
const changeKind = !before ? 'added' : !after ? 'removed' : nativeDiffSymbolChanged(before, after) ? 'modified' : 'unchanged';
|
|
1741
|
+
if (changeKind === 'unchanged') continue;
|
|
1742
|
+
const current = after ?? before;
|
|
1743
|
+
changed.push({
|
|
1744
|
+
changeKind,
|
|
1745
|
+
key,
|
|
1746
|
+
id: current.id,
|
|
1747
|
+
name: current.name,
|
|
1748
|
+
kind: current.kind,
|
|
1749
|
+
language: current.language,
|
|
1750
|
+
nativeAstNodeId: current.nativeAstNodeId,
|
|
1751
|
+
semanticOccurrenceId: current.semanticOccurrenceId,
|
|
1752
|
+
sourceMapMappingId: current.sourceMapMappingId,
|
|
1753
|
+
sourceSpan: current.sourceSpan,
|
|
1754
|
+
beforeSignatureHash: before?.signatureHash,
|
|
1755
|
+
afterSignatureHash: after?.signatureHash,
|
|
1756
|
+
beforeSpanHash: before?.spanHash,
|
|
1757
|
+
afterSpanHash: after?.spanHash,
|
|
1758
|
+
beforeOwnershipKey: before?.ownershipKey,
|
|
1759
|
+
afterOwnershipKey: after?.ownershipKey,
|
|
1760
|
+
ownershipRegionId: current.ownershipRegionId,
|
|
1761
|
+
ownershipKey: current.ownershipKey,
|
|
1762
|
+
ownershipRegionKind: current.ownershipRegionKind,
|
|
1763
|
+
conflictKey: current.ownershipKey ? `region:${current.ownershipKey}` : `symbol:${current.id ?? key}`,
|
|
1764
|
+
readiness: maxSemanticMergeReadiness(before?.readiness ?? 'ready', after?.readiness ?? 'ready')
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
return changed;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
function nativeDiffSymbolChanged(before, after) {
|
|
1771
|
+
if ((before.signatureHash ?? '') !== (after.signatureHash ?? '')) return true;
|
|
1772
|
+
if ((before.spanHash ?? '') !== (after.spanHash ?? '')) return true;
|
|
1773
|
+
if ((before.ownershipKey ?? '') !== (after.ownershipKey ?? '')) return true;
|
|
1774
|
+
if ((before.nativeAstNodeId ?? '') !== (after.nativeAstNodeId ?? '')) return true;
|
|
1775
|
+
return false;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function diffNativeOwnershipRegions(beforeSidecar, afterSidecar, changedSymbols) {
|
|
1779
|
+
const changedRegionIds = new Set(changedSymbols.map((symbol) => symbol.ownershipRegionId).filter(Boolean));
|
|
1780
|
+
const changedRegionKeys = new Set([
|
|
1781
|
+
...changedSymbols.map((symbol) => symbol.beforeOwnershipKey).filter(Boolean),
|
|
1782
|
+
...changedSymbols.map((symbol) => symbol.afterOwnershipKey).filter(Boolean)
|
|
1783
|
+
]);
|
|
1784
|
+
const regions = uniqueRecordsById([...(beforeSidecar?.ownershipRegions ?? []), ...(afterSidecar?.ownershipRegions ?? [])])
|
|
1785
|
+
.filter((region) => changedRegionIds.has(region.id) || changedRegionKeys.has(region.key));
|
|
1786
|
+
return regions.map((region) => ({
|
|
1787
|
+
...region,
|
|
1788
|
+
changeKind: changedSymbols.some((symbol) => symbol.changeKind === 'added' && symbol.afterOwnershipKey === region.key)
|
|
1789
|
+
? 'added'
|
|
1790
|
+
: changedSymbols.some((symbol) => symbol.changeKind === 'removed' && symbol.beforeOwnershipKey === region.key)
|
|
1791
|
+
? 'removed'
|
|
1792
|
+
: 'modified',
|
|
1793
|
+
conflictKey: region.key ? `region:${region.key}` : `region:${region.id}`
|
|
1794
|
+
}));
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function fileLevelNativeChangeRegion(input) {
|
|
1798
|
+
const sourcePath = input.sourcePath ?? input.after?.sourcePath ?? input.before?.sourcePath;
|
|
1799
|
+
const key = ['source', sourcePath ?? `${input.language}:memory`, 'file'].join('#');
|
|
1800
|
+
return {
|
|
1801
|
+
id: `region_${idFragment(key)}`,
|
|
1802
|
+
key,
|
|
1803
|
+
changeKind: 'modified',
|
|
1804
|
+
regionKind: 'body',
|
|
1805
|
+
granularity: 'file',
|
|
1806
|
+
language: input.language,
|
|
1807
|
+
sourcePath,
|
|
1808
|
+
sourceHash: input.afterHash ?? input.beforeHash,
|
|
1809
|
+
precision: 'unknown',
|
|
1810
|
+
mergePolicy: 'file-level-review-required',
|
|
1811
|
+
conflictKey: `region:${key}`,
|
|
1812
|
+
metadata: {
|
|
1813
|
+
reason: 'source-hash-changed-without-symbol-diff',
|
|
1814
|
+
beforeHash: input.beforeHash,
|
|
1815
|
+
afterHash: input.afterHash
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
function nativeImportReadiness(imported) {
|
|
1821
|
+
return imported?.metadata?.semanticMergeReadiness
|
|
1822
|
+
?? imported?.metadata?.nativeImportLossSummary?.semanticMergeReadiness
|
|
1823
|
+
?? imported?.mergeCandidates?.[0]?.readiness
|
|
1824
|
+
?? 'ready';
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function nativeSourceChangeReasons(input) {
|
|
1828
|
+
if (!input.before) return ['Native source was added.'];
|
|
1829
|
+
if (!input.after) return ['Native source was removed.'];
|
|
1830
|
+
if (input.changedSymbols.length) {
|
|
1831
|
+
return [`Native source changed ${input.changedSymbols.length} symbol(s) across ${input.changedRegions.length} ownership region(s).`];
|
|
1832
|
+
}
|
|
1833
|
+
if (input.beforeHash && input.afterHash && input.beforeHash !== input.afterHash) {
|
|
1834
|
+
return ['Native source hash changed without declaration-level symbol changes; file-level review is required.'];
|
|
1835
|
+
}
|
|
1836
|
+
return ['Native source imports are semantically unchanged at available scanner precision.'];
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
function nativeChangeTouchedSymbol(symbol) {
|
|
1840
|
+
return {
|
|
1841
|
+
id: symbol.id ?? symbol.key,
|
|
1842
|
+
name: symbol.name,
|
|
1843
|
+
kind: symbol.kind,
|
|
1844
|
+
nativeAstNodeId: symbol.nativeAstNodeId,
|
|
1845
|
+
span: symbol.sourceSpan,
|
|
1846
|
+
conflictKey: symbol.conflictKey,
|
|
1847
|
+
metadata: {
|
|
1848
|
+
changeKind: symbol.changeKind,
|
|
1849
|
+
beforeSignatureHash: symbol.beforeSignatureHash,
|
|
1850
|
+
afterSignatureHash: symbol.afterSignatureHash,
|
|
1851
|
+
beforeSpanHash: symbol.beforeSpanHash,
|
|
1852
|
+
afterSpanHash: symbol.afterSpanHash,
|
|
1853
|
+
ownershipRegionId: symbol.ownershipRegionId,
|
|
1854
|
+
ownershipRegionKind: symbol.ownershipRegionKind
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function nativeChangeSpans(changedSymbols, changedRegions, input) {
|
|
1860
|
+
const symbolSpans = changedSymbols
|
|
1861
|
+
.filter((symbol) => symbol.sourceSpan)
|
|
1862
|
+
.map((symbol) => ({
|
|
1863
|
+
id: `native_span_${idFragment(symbol.id ?? symbol.key)}`,
|
|
1864
|
+
sourceId: symbol.sourceSpan?.sourceId,
|
|
1865
|
+
path: symbol.sourceSpan?.path ?? input.sourcePath,
|
|
1866
|
+
language: symbol.language ?? input.language,
|
|
1867
|
+
nativeAstNodeId: symbol.nativeAstNodeId,
|
|
1868
|
+
symbolId: symbol.id,
|
|
1869
|
+
span: symbol.sourceSpan,
|
|
1870
|
+
conflictKey: symbol.conflictKey,
|
|
1871
|
+
metadata: { changeKind: symbol.changeKind }
|
|
1872
|
+
}));
|
|
1873
|
+
const regionSpans = changedRegions
|
|
1874
|
+
.filter((region) => region.sourceSpan || region.sourcePath)
|
|
1875
|
+
.map((region) => ({
|
|
1876
|
+
id: `native_span_${idFragment(region.id)}`,
|
|
1877
|
+
path: region.sourceSpan?.path ?? region.sourcePath ?? input.sourcePath,
|
|
1878
|
+
language: region.language ?? input.language,
|
|
1879
|
+
nativeAstNodeId: region.nativeAstNodeId,
|
|
1880
|
+
symbolId: region.symbolId,
|
|
1881
|
+
span: region.sourceSpan,
|
|
1882
|
+
conflictKey: region.conflictKey ?? `region:${region.key ?? region.id}`,
|
|
1883
|
+
metadata: { changeKind: region.changeKind, regionKind: region.regionKind, granularity: region.granularity }
|
|
1884
|
+
}));
|
|
1885
|
+
return uniqueRecordsById([...symbolSpans, ...regionSpans]);
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
function nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged) {
|
|
1889
|
+
return {
|
|
1890
|
+
sourceChanged,
|
|
1891
|
+
symbols: changedSymbols.length,
|
|
1892
|
+
regions: changedRegions.length,
|
|
1893
|
+
addedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'added').length,
|
|
1894
|
+
removedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'removed').length,
|
|
1895
|
+
modifiedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'modified').length,
|
|
1896
|
+
byRegionKind: countBy(changedRegions.map((region) => region.regionKind ?? 'unknown')),
|
|
1897
|
+
byChangeKind: countBy([...changedSymbols.map((symbol) => symbol.changeKind), ...changedRegions.map((region) => region.changeKind)])
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function nativeDiffSymbolKey(symbol, imported) {
|
|
1902
|
+
return [
|
|
1903
|
+
symbol.language ?? imported?.language,
|
|
1904
|
+
symbol.kind ?? 'symbol',
|
|
1905
|
+
symbol.name ?? symbol.id
|
|
1906
|
+
].map((part) => String(part ?? '').trim()).join(':');
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
function nativeImportSourceText(imported) {
|
|
1910
|
+
return imported?.metadata?.sourcePreservation?.sourceText
|
|
1911
|
+
?? imported?.nativeSource?.metadata?.sourcePreservation?.sourceText
|
|
1912
|
+
?? imported?.nativeAst?.metadata?.sourcePreservation?.sourceText
|
|
1913
|
+
?? imported?.universalAst?.metadata?.sourcePreservation?.sourceText;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
function hashNativeSpanText(sourceText, span) {
|
|
1917
|
+
const text = sourceTextForSpan(sourceText, span);
|
|
1918
|
+
return text === undefined ? undefined : hashSemanticValue(text);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
function sourceTextForSpan(sourceText, span) {
|
|
1922
|
+
if (typeof sourceText !== 'string' || !span) return undefined;
|
|
1923
|
+
if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) {
|
|
1924
|
+
return sourceText.slice(span.start, span.end);
|
|
1925
|
+
}
|
|
1926
|
+
if (typeof span.startLine === 'number') {
|
|
1927
|
+
const lines = sourceText.split(/\r?\n/);
|
|
1928
|
+
const endLine = typeof span.endLine === 'number' && span.endLine >= span.startLine ? span.endLine : span.startLine;
|
|
1929
|
+
return lines.slice(span.startLine - 1, endLine).join('\n');
|
|
1930
|
+
}
|
|
1931
|
+
return undefined;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1419
1934
|
function createLightweightNativeImport(input) {
|
|
1420
1935
|
const parser = input.parser ?? `${input.language}.lightweight-declaration-scan`;
|
|
1421
1936
|
const rootId = 'native_root';
|
|
@@ -3670,6 +4185,159 @@ function nativeLanguageCompileTarget(language, aliases = []) {
|
|
|
3670
4185
|
return undefined;
|
|
3671
4186
|
}
|
|
3672
4187
|
|
|
4188
|
+
function isNativeSourceImportResult(input) {
|
|
4189
|
+
return Boolean(input && typeof input === 'object' && input.kind === 'frontier.lang.importResult' && input.nativeSource && input.universalAst);
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
function nativeCompileSourceLanguage(importResult, input) {
|
|
4193
|
+
return normalizeNativeLanguageId(
|
|
4194
|
+
importResult.language
|
|
4195
|
+
?? importResult.nativeSource?.language
|
|
4196
|
+
?? importResult.nativeAst?.language
|
|
4197
|
+
?? importResult.nativeSource?.ast?.language
|
|
4198
|
+
?? input?.language
|
|
4199
|
+
) || String(importResult.language ?? input?.language ?? 'source');
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
function nativeCompileTarget(input, importResult, options) {
|
|
4203
|
+
const targetInput = options.target
|
|
4204
|
+
?? compileTargetLanguage(input?.target)
|
|
4205
|
+
?? compileTargetLanguage(importResult.nativeSource?.target)
|
|
4206
|
+
?? nativeLanguageCompileTarget(nativeCompileSourceLanguage(importResult, input))
|
|
4207
|
+
?? 'typescript';
|
|
4208
|
+
return normalizeProjectionMatrixTargets([targetInput])[0] ?? String(targetInput ?? 'typescript').toLowerCase();
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
function compileTargetLanguage(target) {
|
|
4212
|
+
if (!target) return undefined;
|
|
4213
|
+
if (typeof target === 'string') return target;
|
|
4214
|
+
return target.language ?? target.emitLanguage ?? target.target ?? target.name;
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
function nativeSourceCompileTargetCoverage(matrix, language, target) {
|
|
4218
|
+
const sourceLanguage = normalizeNativeLanguageId(language);
|
|
4219
|
+
const entry = (matrix.languages ?? []).find((candidate) => {
|
|
4220
|
+
const ids = [candidate.language, ...(candidate.aliases ?? [])].map(normalizeNativeLanguageId);
|
|
4221
|
+
return ids.includes(sourceLanguage);
|
|
4222
|
+
});
|
|
4223
|
+
const coverage = entry?.targets?.find((candidate) => candidate.target === target);
|
|
4224
|
+
if (coverage) return coverage;
|
|
4225
|
+
return {
|
|
4226
|
+
target,
|
|
4227
|
+
lossClass: 'missingAdapter',
|
|
4228
|
+
supported: false,
|
|
4229
|
+
readiness: 'blocked',
|
|
4230
|
+
lossKinds: ['targetProjectionLoss'],
|
|
4231
|
+
categories: ['targetProjectionLoss'],
|
|
4232
|
+
reason: `No native-to-${target} projection coverage is available for ${language}.`,
|
|
4233
|
+
adapter: undefined,
|
|
4234
|
+
notes: ['Inject a source-language parser and target projection adapter before treating this cross-language output as merge-ready.']
|
|
4235
|
+
};
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
function nativeSourceCompileTargetLosses(input) {
|
|
4239
|
+
const { importResult, projection, targetCoverage, sourceLanguage, target, idPart } = input;
|
|
4240
|
+
if (!targetCoverage || targetCoverage.lossClass === 'exactSourceProjection') return [];
|
|
4241
|
+
if (targetCoverage.lossClass === 'missingAdapter') {
|
|
4242
|
+
return [nativeSourceCompileTargetLoss({
|
|
4243
|
+
id: `loss_${idPart}_${idFragment(target)}_missing_projection_adapter`,
|
|
4244
|
+
severity: 'error',
|
|
4245
|
+
message: targetCoverage.reason,
|
|
4246
|
+
importResult,
|
|
4247
|
+
projection,
|
|
4248
|
+
targetCoverage,
|
|
4249
|
+
sourceLanguage,
|
|
4250
|
+
target
|
|
4251
|
+
})];
|
|
4252
|
+
}
|
|
4253
|
+
if (targetCoverage.lossClass === 'unsupportedTargetFeatures') {
|
|
4254
|
+
return [nativeSourceCompileTargetLoss({
|
|
4255
|
+
id: `loss_${idPart}_${idFragment(target)}_unsupported_target_features`,
|
|
4256
|
+
severity: 'warning',
|
|
4257
|
+
message: targetCoverage.reason,
|
|
4258
|
+
importResult,
|
|
4259
|
+
projection,
|
|
4260
|
+
targetCoverage,
|
|
4261
|
+
sourceLanguage,
|
|
4262
|
+
target
|
|
4263
|
+
})];
|
|
4264
|
+
}
|
|
4265
|
+
if (targetCoverage.lossClass === 'nativeSourceStubs') {
|
|
4266
|
+
return [nativeSourceCompileTargetLoss({
|
|
4267
|
+
id: `loss_${idPart}_${idFragment(target)}_target_stubs`,
|
|
4268
|
+
severity: 'warning',
|
|
4269
|
+
message: targetCoverage.reason,
|
|
4270
|
+
importResult,
|
|
4271
|
+
projection,
|
|
4272
|
+
targetCoverage,
|
|
4273
|
+
sourceLanguage,
|
|
4274
|
+
target
|
|
4275
|
+
})];
|
|
4276
|
+
}
|
|
4277
|
+
return [];
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
function nativeSourceCompileTargetLoss(input) {
|
|
4281
|
+
const rootSpan = input.importResult.nativeAst?.nodes?.[input.importResult.nativeAst?.rootId]?.span
|
|
4282
|
+
?? input.importResult.nativeSource?.ast?.nodes?.[input.importResult.nativeSource?.ast?.rootId]?.span;
|
|
4283
|
+
return {
|
|
4284
|
+
id: input.id,
|
|
4285
|
+
severity: input.severity,
|
|
4286
|
+
phase: 'emit',
|
|
4287
|
+
sourceFormat: input.sourceLanguage,
|
|
4288
|
+
kind: 'targetProjectionLoss',
|
|
4289
|
+
message: input.message,
|
|
4290
|
+
span: rootSpan ?? {
|
|
4291
|
+
sourceId: input.importResult.nativeSource?.sourceHash ?? input.importResult.sourceHash,
|
|
4292
|
+
path: input.importResult.sourcePath ?? input.importResult.nativeSource?.sourcePath,
|
|
4293
|
+
startLine: 1,
|
|
4294
|
+
startColumn: 1
|
|
4295
|
+
},
|
|
4296
|
+
metadata: {
|
|
4297
|
+
target: input.target,
|
|
4298
|
+
sourceLanguage: input.sourceLanguage,
|
|
4299
|
+
projectionId: input.projection.id,
|
|
4300
|
+
projectionMode: input.projection.mode,
|
|
4301
|
+
targetLossClass: input.targetCoverage.lossClass,
|
|
4302
|
+
targetReadiness: input.targetCoverage.readiness,
|
|
4303
|
+
targetSupported: input.targetCoverage.supported,
|
|
4304
|
+
targetAdapter: input.targetCoverage.adapter,
|
|
4305
|
+
targetLossKinds: input.targetCoverage.lossKinds,
|
|
4306
|
+
lossCategory: 'targetProjectionLoss',
|
|
4307
|
+
semanticMergeAdmission: semanticMergeAdmissionForSeverity(input.severity)
|
|
4308
|
+
}
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
function nativeSourceCompileEvidence(input) {
|
|
4313
|
+
const failed = input.targetLosses.some((loss) => loss.severity === 'error')
|
|
4314
|
+
|| input.projection.evidence?.some((record) => record.status === 'failed')
|
|
4315
|
+
|| input.targetCoverage.readiness === 'blocked';
|
|
4316
|
+
return {
|
|
4317
|
+
id: input.id,
|
|
4318
|
+
kind: 'projection',
|
|
4319
|
+
status: failed ? 'failed' : 'passed',
|
|
4320
|
+
path: input.importResult.sourcePath ?? input.importResult.nativeSource?.sourcePath,
|
|
4321
|
+
summary: failed
|
|
4322
|
+
? `Compiled ${input.sourceLanguage} native source to ${input.target} with blocked projection evidence.`
|
|
4323
|
+
: `Compiled ${input.sourceLanguage} native source to ${input.target} as ${input.outputMode}.`,
|
|
4324
|
+
metadata: {
|
|
4325
|
+
importId: input.importResult.id,
|
|
4326
|
+
projectionId: input.projection.id,
|
|
4327
|
+
sourceLanguage: input.sourceLanguage,
|
|
4328
|
+
target: input.target,
|
|
4329
|
+
outputHash: input.projection.outputHash,
|
|
4330
|
+
outputMode: input.outputMode,
|
|
4331
|
+
projectionMode: input.projection.mode,
|
|
4332
|
+
targetLossClass: input.targetCoverage.lossClass,
|
|
4333
|
+
targetReadiness: input.targetCoverage.readiness,
|
|
4334
|
+
targetSupported: input.targetCoverage.supported,
|
|
4335
|
+
targetReason: input.targetCoverage.reason,
|
|
4336
|
+
targetLossIds: input.targetLosses.map((loss) => loss.id)
|
|
4337
|
+
}
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
|
|
3673
4341
|
function nativeProjectionTargetsForLanguage(language, aliases = []) {
|
|
3674
4342
|
const target = nativeLanguageCompileTarget(language, aliases);
|
|
3675
4343
|
return target ? [target] : [];
|
package/package.json
CHANGED