@shapeshift-labs/frontier-lang-compiler 0.2.121 → 0.2.123
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 +17 -0
- package/dist/declarations/js-ts-safe-project-merge.d.ts +78 -0
- package/dist/declarations/native-project.d.ts +8 -5
- package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +10 -17
- package/dist/js-ts-safe-merge-semantic-artifacts.js +12 -0
- package/dist/js-ts-safe-project-merge-graph-conflicts.js +98 -0
- package/dist/js-ts-safe-project-merge-graph-delta-conflicts.js +279 -0
- package/dist/js-ts-safe-project-merge-graph.js +185 -8
- package/dist/js-ts-safe-project-merge.js +41 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -286,6 +286,23 @@ re-export, export-star, local export, `export default`, and TypeScript
|
|
|
286
286
|
`importedName`, `exportedName`, `isTypeOnly`, and public-contract metadata into
|
|
287
287
|
the semantic index and project symbol graph.
|
|
288
288
|
|
|
289
|
+
`safeMergeJsTsProject` stays synchronous. When a caller already has parser-backed
|
|
290
|
+
native import results for merged output files, pass them as `outputProjectImports`
|
|
291
|
+
with `includeOutputProjectSymbolGraph`. The graph builder matches supplied
|
|
292
|
+
imports by `sourcePath` and `sourceHash`, requires hash-verified matches when
|
|
293
|
+
the merged source has a hash, uses them for output graph artifacts, and falls
|
|
294
|
+
back to the lightweight scanner for missing or stale files.
|
|
295
|
+
|
|
296
|
+
For admission queues that need bounded cross-branch API checks, enable
|
|
297
|
+
`includeProjectGraphDelta`. This additionally builds base, worker, head, and
|
|
298
|
+
output project graph stages and blocks the merge when worker and head both
|
|
299
|
+
change the same public contract, re-export identity, or import target in
|
|
300
|
+
incompatible ways. Parser-backed stage imports can be supplied with
|
|
301
|
+
`baseProjectImports`, `workerProjectImports`, `headProjectImports`, and
|
|
302
|
+
`outputProjectImports`; missing or hash-stale stages fall back to the synchronous
|
|
303
|
+
lightweight scanner. This is a conservative admission gate only: results still
|
|
304
|
+
keep `autoMergeClaim: false` and `semanticEquivalenceClaim: false`.
|
|
305
|
+
|
|
289
306
|
High-risk native features also have explicit evidence policies. These policies are advisory in this package: they tell a swarm or admission queue what evidence is missing without silently changing the existing readiness classification.
|
|
290
307
|
|
|
291
308
|
```js
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
JsTsSafeMergeSummary
|
|
8
8
|
} from './js-ts-safe-merge.js';
|
|
9
9
|
import type { JsTsSafeMemberMergePolicy, JsTsSafeMemberMergePolicyRegion } from './js-ts-safe-member-merge.js';
|
|
10
|
+
import type { NativeSourceImportResult } from './import-adapter-core.js';
|
|
10
11
|
import type { NativeProjectImportResult, NativeProjectSymbolGraphSummary } from './native-project.js';
|
|
11
12
|
import type { NativeProjectModuleResolutionOptions } from './native-project-module-resolution.js';
|
|
12
13
|
|
|
@@ -45,6 +46,20 @@ export type JsTsProjectSafeMergeFileMap =
|
|
|
45
46
|
| ReadonlyMap<string, string>
|
|
46
47
|
| readonly { readonly sourcePath?: string; readonly path?: string; readonly sourceText?: string; readonly text?: string }[];
|
|
47
48
|
|
|
49
|
+
export type JsTsProjectSafeMergeOutputProjectImports =
|
|
50
|
+
| readonly NativeSourceImportResult[]
|
|
51
|
+
| ReadonlyMap<string, NativeSourceImportResult>
|
|
52
|
+
| Readonly<Record<string, NativeSourceImportResult>>;
|
|
53
|
+
|
|
54
|
+
export type JsTsProjectGraphStageName = 'base' | 'worker' | 'head' | 'output' | string;
|
|
55
|
+
|
|
56
|
+
export type JsTsProjectSafeMergeProjectGraphImportsByStage = Readonly<{
|
|
57
|
+
base?: JsTsProjectSafeMergeOutputProjectImports;
|
|
58
|
+
worker?: JsTsProjectSafeMergeOutputProjectImports;
|
|
59
|
+
head?: JsTsProjectSafeMergeOutputProjectImports;
|
|
60
|
+
output?: JsTsProjectSafeMergeOutputProjectImports;
|
|
61
|
+
} & Record<string, JsTsProjectSafeMergeOutputProjectImports | undefined>>;
|
|
62
|
+
|
|
48
63
|
export interface JsTsProjectSafeMergeInput {
|
|
49
64
|
readonly id?: string;
|
|
50
65
|
readonly language?: FrontierSourceLanguage | string;
|
|
@@ -56,6 +71,12 @@ export interface JsTsProjectSafeMergeInput {
|
|
|
56
71
|
readonly allowFileAdditions?: boolean;
|
|
57
72
|
readonly allowFileDeletes?: boolean;
|
|
58
73
|
readonly includeOutputProjectSymbolGraph?: boolean;
|
|
74
|
+
readonly includeProjectGraphDelta?: boolean;
|
|
75
|
+
readonly outputProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
76
|
+
readonly baseProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
77
|
+
readonly workerProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
78
|
+
readonly headProjectImports?: JsTsProjectSafeMergeOutputProjectImports;
|
|
79
|
+
readonly projectGraphImports?: JsTsProjectSafeMergeProjectGraphImportsByStage;
|
|
59
80
|
readonly moduleResolution?: NativeProjectModuleResolutionOptions;
|
|
60
81
|
readonly tsconfig?: NativeProjectModuleResolutionOptions;
|
|
61
82
|
readonly workerChangeSetId?: string;
|
|
@@ -108,6 +129,56 @@ export interface JsTsProjectSafeMergeAdmission {
|
|
|
108
129
|
readonly conflictKeys: readonly string[];
|
|
109
130
|
}
|
|
110
131
|
|
|
132
|
+
export interface JsTsProjectGraphDeltaStageSummary {
|
|
133
|
+
readonly stage: JsTsProjectGraphStageName;
|
|
134
|
+
readonly sourceFiles: number;
|
|
135
|
+
readonly documents: number;
|
|
136
|
+
readonly symbols: number;
|
|
137
|
+
readonly fileHashes: number;
|
|
138
|
+
readonly importEdges: number;
|
|
139
|
+
readonly exportEdges: number;
|
|
140
|
+
readonly publicContractRegions: number;
|
|
141
|
+
readonly reExportIdentities: number;
|
|
142
|
+
readonly unresolvedImportEdges: number;
|
|
143
|
+
readonly suppliedImports: number;
|
|
144
|
+
readonly matchedSuppliedImports: number;
|
|
145
|
+
readonly scannerFallbackImports: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface JsTsProjectGraphDeltaStage {
|
|
149
|
+
readonly kind: 'frontier.lang.jsTsProjectGraphStage';
|
|
150
|
+
readonly version: 1;
|
|
151
|
+
readonly stage: JsTsProjectGraphStageName;
|
|
152
|
+
readonly projectImport?: NativeProjectImportResult;
|
|
153
|
+
readonly projectSymbolGraph?: NativeProjectSymbolGraphSummary;
|
|
154
|
+
readonly summary: JsTsProjectGraphDeltaStageSummary;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface JsTsProjectGraphDeltaSummary {
|
|
158
|
+
readonly stages: number;
|
|
159
|
+
readonly sourceFiles: number;
|
|
160
|
+
readonly publicContractRegions: number;
|
|
161
|
+
readonly reExportIdentities: number;
|
|
162
|
+
readonly importEdges: number;
|
|
163
|
+
readonly exportEdges: number;
|
|
164
|
+
readonly unresolvedImportEdges: number;
|
|
165
|
+
readonly suppliedImports: number;
|
|
166
|
+
readonly matchedSuppliedImports: number;
|
|
167
|
+
readonly scannerFallbackImports: number;
|
|
168
|
+
readonly conflicts: number;
|
|
169
|
+
readonly publicContractConflicts: number;
|
|
170
|
+
readonly reExportIdentityConflicts: number;
|
|
171
|
+
readonly importTargetConflicts: number;
|
|
172
|
+
readonly stageSummaries: Readonly<Record<string, JsTsProjectGraphDeltaStageSummary>>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface JsTsProjectGraphDelta {
|
|
176
|
+
readonly kind: 'frontier.lang.jsTsProjectGraphDelta';
|
|
177
|
+
readonly version: 1;
|
|
178
|
+
readonly stages: Readonly<Record<string, JsTsProjectGraphDeltaStage>>;
|
|
179
|
+
readonly summary: JsTsProjectGraphDeltaSummary;
|
|
180
|
+
}
|
|
181
|
+
|
|
111
182
|
export interface JsTsProjectSafeMergeResult {
|
|
112
183
|
readonly kind: 'frontier.lang.jsTsProjectSafeMerge';
|
|
113
184
|
readonly version: 1;
|
|
@@ -119,6 +190,7 @@ export interface JsTsProjectSafeMergeResult {
|
|
|
119
190
|
readonly outputFiles: readonly JsTsProjectSafeMergeOutputFile[];
|
|
120
191
|
readonly outputProjectImport?: NativeProjectImportResult;
|
|
121
192
|
readonly outputProjectSymbolGraph?: NativeProjectSymbolGraphSummary;
|
|
193
|
+
readonly projectGraphDelta?: JsTsProjectGraphDelta;
|
|
122
194
|
readonly conflicts: readonly JsTsSafeMergeConflict[];
|
|
123
195
|
readonly admission: JsTsProjectSafeMergeAdmission;
|
|
124
196
|
readonly summary: {
|
|
@@ -126,6 +198,12 @@ export interface JsTsProjectSafeMergeResult {
|
|
|
126
198
|
readonly mergedFiles: number;
|
|
127
199
|
readonly blockedFiles: number;
|
|
128
200
|
readonly outputFiles: number;
|
|
201
|
+
readonly projectGraphConflicts: number;
|
|
202
|
+
readonly outputProjectGraphConflicts: number;
|
|
203
|
+
readonly projectGraphDeltaConflicts: number;
|
|
204
|
+
readonly projectGraphPublicContractConflicts: number;
|
|
205
|
+
readonly projectGraphReExportIdentityConflicts: number;
|
|
206
|
+
readonly projectGraphImportTargetConflicts: number;
|
|
129
207
|
readonly semanticArtifactFiles: number;
|
|
130
208
|
readonly operations: Readonly<Record<string, number>>;
|
|
131
209
|
};
|
|
@@ -182,13 +182,16 @@ export interface NativeProjectSymbolGraphSummary {
|
|
|
182
182
|
readonly remainingFields: readonly NativeProjectSymbolGraphRemainingField[];
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
export interface NativeProjectSourcePreservationSummary { readonly total: number; readonly exactSourceAvailable: number; readonly sourceBytes: number; readonly tokens: number; readonly trivia: number; readonly directives: number; readonly truncated: boolean; readonly ids: readonly string[]; }
|
|
186
|
+
|
|
185
187
|
export interface NativeProjectImportResultMetadata extends Record<string, unknown> {
|
|
186
|
-
readonly
|
|
187
|
-
readonly projectAdmission?: NativeProjectImportAdmission;
|
|
188
|
-
readonly semanticMergeAdmission?: NativeProjectImportAdmission;
|
|
189
|
-
readonly
|
|
190
|
-
readonly semanticMergeReadiness?: SemanticMergeReadiness;
|
|
188
|
+
readonly sourceCount?: number; readonly sourcePaths?: readonly string[];
|
|
189
|
+
readonly importResultContract?: NativeImportResultContract; readonly projectAdmission?: NativeProjectImportAdmission;
|
|
190
|
+
readonly semanticMergeAdmission?: NativeProjectImportAdmission; readonly nativeImportLossSummary?: NativeImportLossSummary;
|
|
191
|
+
readonly sourcePreservationSummary?: NativeProjectSourcePreservationSummary; readonly semanticMergeReadiness?: SemanticMergeReadiness;
|
|
191
192
|
readonly readinessReasons?: readonly string[];
|
|
193
|
+
readonly regionSummary?: NativeImportRegionSummary; readonly sourceMapSummary?: NativeImportSourceMapSummary;
|
|
194
|
+
readonly adapterCoverageSummary?: NativeImportAdapterCoverageContract;
|
|
192
195
|
readonly projectSymbolGraph?: NativeProjectSymbolGraphSummary;
|
|
193
196
|
}
|
|
194
197
|
|
|
@@ -244,24 +244,17 @@ function publicContractRegionForDeclaration(declaration, ownershipRegion, module
|
|
|
244
244
|
|
|
245
245
|
function reExportIdentityForDeclaration(declaration, input, documentId, relationId, ownershipRegion, moduleEdge) {
|
|
246
246
|
if (!moduleEdge?.isReExport && !declaration.reExport) return undefined;
|
|
247
|
+
const bindingCount = Number(declaration.nativeNode?.metadata?.bindingCount ?? declaration.metadata?.bindingCount ?? 0);
|
|
248
|
+
if (declaration.symbolKind === 'module' && !declaration.exportStar && !declaration.metadata?.exportStar && bindingCount > 1) return undefined;
|
|
249
|
+
const identityId = hashSemanticValue([relationId, declaration.symbolId, declaration.exportedName ?? declaration.metadata?.exportedName, declaration.importedName ?? declaration.metadata?.importedName, declaration.localName ?? declaration.metadata?.localName]);
|
|
250
|
+
const stableId = bindingCount > 1 ? idFragment(identityId) : idFragment(relationId);
|
|
247
251
|
return compactRecord({
|
|
248
|
-
kind: 'frontier.lang.reExportIdentity',
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
moduleSpecifier: moduleEdge?.moduleSpecifier,
|
|
255
|
-
exportedName: declaration.exportedName ?? declaration.metadata?.exportedName,
|
|
256
|
-
importedName: declaration.importedName ?? declaration.metadata?.importedName,
|
|
257
|
-
localName: declaration.localName ?? declaration.metadata?.localName,
|
|
258
|
-
namespace: declaration.namespace ?? declaration.metadata?.namespace,
|
|
259
|
-
isTypeOnly: declaration.isTypeOnly ?? declaration.metadata?.isTypeOnly ?? declaration.metadata?.typeOnly,
|
|
260
|
-
exportStar: declaration.exportStar ?? declaration.metadata?.exportStar,
|
|
261
|
-
symbolId: declaration.symbolId,
|
|
262
|
-
relationId,
|
|
263
|
-
ownershipRegionId: ownershipRegion.id,
|
|
264
|
-
ownershipRegionKey: ownershipRegion.key,
|
|
252
|
+
kind: 'frontier.lang.reExportIdentity', version: 1, id: `reexport_${stableId}`, sourceDocumentId: documentId,
|
|
253
|
+
sourcePath: input.sourcePath, sourceHash: input.sourceHash, moduleSpecifier: moduleEdge?.moduleSpecifier,
|
|
254
|
+
exportedName: declaration.exportedName ?? declaration.metadata?.exportedName, importedName: declaration.importedName ?? declaration.metadata?.importedName,
|
|
255
|
+
localName: declaration.localName ?? declaration.metadata?.localName, namespace: declaration.namespace ?? declaration.metadata?.namespace,
|
|
256
|
+
isTypeOnly: declaration.isTypeOnly ?? declaration.metadata?.isTypeOnly ?? declaration.metadata?.typeOnly, exportStar: declaration.exportStar ?? declaration.metadata?.exportStar,
|
|
257
|
+
symbolId: declaration.symbolId, relationId, ownershipRegionId: ownershipRegion.id, ownershipRegionKey: ownershipRegion.key,
|
|
265
258
|
publicContract: true
|
|
266
259
|
});
|
|
267
260
|
}
|
|
@@ -59,6 +59,7 @@ function createJsTsSafeMergeSemanticArtifacts(input = {}, merge = {}) {
|
|
|
59
59
|
id: `js_ts_safe_merge_replay_${idFragment(id)}`,
|
|
60
60
|
projection,
|
|
61
61
|
currentSourceText: headSourceText,
|
|
62
|
+
currentSourceHash: headReplaySourceHash(input),
|
|
62
63
|
currentSourcePath: sourcePath,
|
|
63
64
|
language
|
|
64
65
|
});
|
|
@@ -66,6 +67,7 @@ function createJsTsSafeMergeSemanticArtifacts(input = {}, merge = {}) {
|
|
|
66
67
|
id: `js_ts_safe_merge_replay_already_applied_${idFragment(id)}`,
|
|
67
68
|
projection,
|
|
68
69
|
currentSourceText: mergedSourceText,
|
|
70
|
+
currentSourceHash: projection.projectedHash,
|
|
69
71
|
currentSourcePath: sourcePath,
|
|
70
72
|
language
|
|
71
73
|
});
|
|
@@ -74,6 +76,8 @@ function createJsTsSafeMergeSemanticArtifacts(input = {}, merge = {}) {
|
|
|
74
76
|
const status = !blocked && replayReady && alreadyAppliedReady ? 'verified' : 'blocked';
|
|
75
77
|
const finalReasonCodes = uniqueStrings([
|
|
76
78
|
...reasonCodes,
|
|
79
|
+
...(replayReady ? [] : replay.admission?.reasonCodes ?? replay.summary?.reasonCodes ?? []),
|
|
80
|
+
...(alreadyAppliedReady ? [] : alreadyAppliedReplay.admission?.reasonCodes ?? alreadyAppliedReplay.summary?.reasonCodes ?? []),
|
|
77
81
|
replayReady ? undefined : `semantic-replay-${replay.status}`,
|
|
78
82
|
alreadyAppliedReady ? undefined : `semantic-replay-already-applied-${alreadyAppliedReplay.status}`
|
|
79
83
|
]);
|
|
@@ -263,4 +267,12 @@ function countBy(values, keyFor) {
|
|
|
263
267
|
return result;
|
|
264
268
|
}
|
|
265
269
|
|
|
270
|
+
function headReplaySourceHash(input) {
|
|
271
|
+
return nativeSemanticHash(input.currentSourceHash) ?? nativeSemanticHash(input.headHash);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function nativeSemanticHash(value) {
|
|
275
|
+
return typeof value === 'string' && /^fnv1a32:[0-9a-f]+$/i.test(value) ? value : undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
266
278
|
export { createJsTsSafeMergeSemanticArtifacts };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
2
|
+
import { projectGraphDeltaConflicts } from './js-ts-safe-project-merge-graph-delta-conflicts.js';
|
|
3
|
+
|
|
4
|
+
function outputProjectGraphConflicts(projectSymbolGraph) {
|
|
5
|
+
const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
|
|
6
|
+
const missingModuleGroups = new Map();
|
|
7
|
+
const missingSymbolGroups = new Map();
|
|
8
|
+
for (const edge of importEdges) {
|
|
9
|
+
if (isMissingProjectImportEdge(edge)) {
|
|
10
|
+
const key = [edge.sourcePath, edge.moduleSpecifier, edge.resolutionKind, edge.resolvedModulePath].join('\u0000');
|
|
11
|
+
const group = missingModuleGroups.get(key) ?? [];
|
|
12
|
+
group.push(edge);
|
|
13
|
+
missingModuleGroups.set(key, group);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (isMissingProjectImportTargetEdge(edge)) {
|
|
17
|
+
const key = [edge.sourcePath, edge.moduleSpecifier, projectImportTargetName(edge), edge.resolvedModulePath].join('\u0000');
|
|
18
|
+
const group = missingSymbolGroups.get(key) ?? [];
|
|
19
|
+
group.push(edge);
|
|
20
|
+
missingSymbolGroups.set(key, group);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return [
|
|
24
|
+
...[...missingModuleGroups.values()].map(projectGraphMissingImportConflict),
|
|
25
|
+
...[...missingSymbolGroups.values()].map(projectGraphMissingTargetConflict)
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function projectGraphMissingImportConflict(group) {
|
|
30
|
+
const edge = group[0] ?? {};
|
|
31
|
+
return {
|
|
32
|
+
code: 'project-output-module-unresolved',
|
|
33
|
+
gateId: 'project-symbol-graph',
|
|
34
|
+
message: `Output project graph contains unresolved ${edge.resolutionKind ?? 'missing'} import ${JSON.stringify(edge.moduleSpecifier ?? edge.resolvedModulePath ?? 'unknown')}.`,
|
|
35
|
+
sourcePath: edge.sourcePath,
|
|
36
|
+
details: compactRecord({
|
|
37
|
+
reasonCode: 'project-output-module-unresolved',
|
|
38
|
+
conflictKey: `project-module#${edge.sourcePath ?? 'unknown'}#${edge.moduleSpecifier ?? edge.resolvedModulePath ?? 'unknown'}`,
|
|
39
|
+
sourcePath: edge.sourcePath,
|
|
40
|
+
moduleSpecifier: edge.moduleSpecifier,
|
|
41
|
+
resolutionKind: edge.resolutionKind,
|
|
42
|
+
resolvedModulePath: edge.resolvedModulePath,
|
|
43
|
+
edgeIds: uniqueStrings(group.map((record) => record.id)),
|
|
44
|
+
importKinds: uniqueStrings(group.map((record) => record.importKind)),
|
|
45
|
+
importedNames: uniqueStrings(group.map((record) => record.importedName))
|
|
46
|
+
})
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function projectGraphMissingTargetConflict(group) {
|
|
51
|
+
const edge = group[0] ?? {};
|
|
52
|
+
const targetName = projectImportTargetName(edge);
|
|
53
|
+
return {
|
|
54
|
+
code: 'project-output-symbol-unresolved',
|
|
55
|
+
gateId: 'project-symbol-graph',
|
|
56
|
+
message: `Output project graph import ${JSON.stringify(edge.moduleSpecifier ?? 'unknown')} resolves to a module without exported symbol ${JSON.stringify(targetName ?? 'unknown')}.`,
|
|
57
|
+
sourcePath: edge.sourcePath,
|
|
58
|
+
details: compactRecord({
|
|
59
|
+
reasonCode: 'project-output-symbol-unresolved',
|
|
60
|
+
conflictKey: `project-symbol#${edge.sourcePath ?? 'unknown'}#${edge.moduleSpecifier ?? edge.resolvedModulePath ?? 'unknown'}#${targetName ?? 'unknown'}`,
|
|
61
|
+
sourcePath: edge.sourcePath,
|
|
62
|
+
moduleSpecifier: edge.moduleSpecifier,
|
|
63
|
+
resolutionKind: edge.resolutionKind,
|
|
64
|
+
resolvedModulePath: edge.resolvedModulePath,
|
|
65
|
+
targetDocumentId: edge.targetDocumentId,
|
|
66
|
+
targetExportName: targetName,
|
|
67
|
+
edgeIds: uniqueStrings(group.map((record) => record.id)),
|
|
68
|
+
importKinds: uniqueStrings(group.map((record) => record.importKind)),
|
|
69
|
+
importedNames: uniqueStrings(group.map((record) => record.importedName)),
|
|
70
|
+
localNames: uniqueStrings(group.map((record) => record.localName))
|
|
71
|
+
})
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isMissingProjectImportEdge(edge) {
|
|
76
|
+
return typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-missing');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isMissingProjectImportTargetEdge(edge) {
|
|
80
|
+
return hasResolvedProjectModule(edge) && Boolean(projectImportTargetName(edge)) && !edge.resolvedTargetSymbolId;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hasResolvedProjectModule(edge) {
|
|
84
|
+
return Boolean(edge?.targetDocumentId) && typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-source');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function projectImportTargetName(edge) {
|
|
88
|
+
if (edge?.importKind === 'side-effect' || edge?.importKind === 'namespace') return undefined;
|
|
89
|
+
const name = edge?.importedName ?? edge?.localName ?? edge?.exportedName;
|
|
90
|
+
if (!name || name === '*') return undefined;
|
|
91
|
+
return String(name);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function uniqueStrings(values) {
|
|
95
|
+
return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { outputProjectGraphConflicts, projectGraphDeltaConflicts };
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
3
|
+
|
|
4
|
+
function projectGraphDeltaConflicts(projectGraphDelta) {
|
|
5
|
+
const baseGraph = projectGraphDelta?.stages?.base?.projectSymbolGraph;
|
|
6
|
+
const workerGraph = projectGraphDelta?.stages?.worker?.projectSymbolGraph;
|
|
7
|
+
const headGraph = projectGraphDelta?.stages?.head?.projectSymbolGraph;
|
|
8
|
+
const outputGraph = projectGraphDelta?.stages?.output?.projectSymbolGraph;
|
|
9
|
+
if (!baseGraph || !workerGraph || !headGraph) return [];
|
|
10
|
+
return [
|
|
11
|
+
...changedIdentityConflicts({
|
|
12
|
+
code: 'project-public-contract-delta-conflict',
|
|
13
|
+
label: 'public contract',
|
|
14
|
+
baseRecords: baseGraph.publicContractRegions,
|
|
15
|
+
workerRecords: workerGraph.publicContractRegions,
|
|
16
|
+
headRecords: headGraph.publicContractRegions,
|
|
17
|
+
outputRecords: outputGraph?.publicContractRegions,
|
|
18
|
+
identityKey: publicContractIdentityKey,
|
|
19
|
+
fingerprint: publicContractFingerprint,
|
|
20
|
+
details: publicContractDetails
|
|
21
|
+
}),
|
|
22
|
+
...changedIdentityConflicts({
|
|
23
|
+
code: 'project-re-export-identity-delta-conflict',
|
|
24
|
+
label: 're-export identity',
|
|
25
|
+
baseRecords: baseGraph.reExportIdentities,
|
|
26
|
+
workerRecords: workerGraph.reExportIdentities,
|
|
27
|
+
headRecords: headGraph.reExportIdentities,
|
|
28
|
+
outputRecords: outputGraph?.reExportIdentities,
|
|
29
|
+
identityKey: reExportIdentityKey,
|
|
30
|
+
fingerprint: reExportIdentityFingerprint,
|
|
31
|
+
details: reExportIdentityDetails
|
|
32
|
+
}),
|
|
33
|
+
...projectImportTargetDeltaConflicts(projectGraphDelta)
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function addProjectGraphDeltaConflictSummary(projectGraphDelta, conflicts) {
|
|
38
|
+
if (!projectGraphDelta) return undefined;
|
|
39
|
+
return {
|
|
40
|
+
...projectGraphDelta,
|
|
41
|
+
summary: {
|
|
42
|
+
...projectGraphDelta.summary,
|
|
43
|
+
conflicts: conflicts.length,
|
|
44
|
+
publicContractConflicts: conflicts.filter((conflict) => conflict.code === 'project-public-contract-delta-conflict').length,
|
|
45
|
+
reExportIdentityConflicts: conflicts.filter((conflict) => conflict.code === 'project-re-export-identity-delta-conflict').length,
|
|
46
|
+
importTargetConflicts: conflicts.filter((conflict) => conflict.code === 'project-import-target-delta-conflict').length
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function changedIdentityConflicts(input) {
|
|
52
|
+
const base = recordsByIdentityKey(input.baseRecords, input.identityKey);
|
|
53
|
+
const worker = recordsByIdentityKey(input.workerRecords, input.identityKey);
|
|
54
|
+
const head = recordsByIdentityKey(input.headRecords, input.identityKey);
|
|
55
|
+
const output = recordsByIdentityKey(input.outputRecords, input.identityKey);
|
|
56
|
+
const keys = uniqueStrings([...base.keys(), ...worker.keys(), ...head.keys()]);
|
|
57
|
+
return keys.flatMap((identityKey) => {
|
|
58
|
+
const baseRecord = base.get(identityKey);
|
|
59
|
+
const workerRecord = worker.get(identityKey);
|
|
60
|
+
const headRecord = head.get(identityKey);
|
|
61
|
+
const baseFingerprint = optionalFingerprint(baseRecord, input.fingerprint);
|
|
62
|
+
const workerFingerprint = optionalFingerprint(workerRecord, input.fingerprint);
|
|
63
|
+
const headFingerprint = optionalFingerprint(headRecord, input.fingerprint);
|
|
64
|
+
if (baseFingerprint === workerFingerprint || baseFingerprint === headFingerprint || workerFingerprint === headFingerprint) return [];
|
|
65
|
+
return [projectGraphDeltaConflict({
|
|
66
|
+
code: input.code,
|
|
67
|
+
label: input.label,
|
|
68
|
+
identityKey,
|
|
69
|
+
baseRecord,
|
|
70
|
+
workerRecord,
|
|
71
|
+
headRecord,
|
|
72
|
+
outputRecord: output.get(identityKey),
|
|
73
|
+
details: input.details
|
|
74
|
+
})];
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function projectImportTargetDeltaConflicts(projectGraphDelta) {
|
|
79
|
+
const baseStage = projectGraphDelta?.stages?.base;
|
|
80
|
+
const workerStage = projectGraphDelta?.stages?.worker;
|
|
81
|
+
const headStage = projectGraphDelta?.stages?.head;
|
|
82
|
+
const outputStage = projectGraphDelta?.stages?.output;
|
|
83
|
+
const workerEdges = importEdgesByIdentityKey(workerStage?.projectSymbolGraph?.importEdges);
|
|
84
|
+
const baseSymbolIds = semanticSymbolIds(baseStage);
|
|
85
|
+
const headSymbolIds = semanticSymbolIds(headStage);
|
|
86
|
+
const conflicts = [];
|
|
87
|
+
for (const [identityKey, edge] of importEdgesByIdentityKey(outputStage?.projectSymbolGraph?.importEdges)) {
|
|
88
|
+
if (!edge.resolvedTargetSymbolId) continue;
|
|
89
|
+
const workerEdge = workerEdges.get(identityKey);
|
|
90
|
+
if (!workerEdge || workerEdge.resolvedTargetSymbolId === edge.resolvedTargetSymbolId) continue;
|
|
91
|
+
if (baseSymbolIds.has(edge.resolvedTargetSymbolId) || !headSymbolIds.has(edge.resolvedTargetSymbolId)) continue;
|
|
92
|
+
conflicts.push(projectImportTargetDeltaConflict(identityKey, edge, workerEdge));
|
|
93
|
+
}
|
|
94
|
+
return conflicts;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function projectGraphDeltaConflict(input) {
|
|
98
|
+
const sourcePath = input.workerRecord?.sourcePath ?? input.headRecord?.sourcePath ?? input.baseRecord?.sourcePath;
|
|
99
|
+
const conflictKey = `project-graph-delta#${input.label.replace(/\s+/g, '-')}#${input.identityKey}`;
|
|
100
|
+
return {
|
|
101
|
+
code: input.code,
|
|
102
|
+
gateId: 'project-graph-delta',
|
|
103
|
+
message: `Worker and head both changed ${input.label} ${JSON.stringify(input.identityKey)} in incompatible ways.`,
|
|
104
|
+
sourcePath,
|
|
105
|
+
details: compactRecord({
|
|
106
|
+
reasonCode: input.code,
|
|
107
|
+
conflictKey,
|
|
108
|
+
identityKey: input.identityKey,
|
|
109
|
+
sourcePath,
|
|
110
|
+
base: input.details(input.baseRecord),
|
|
111
|
+
worker: input.details(input.workerRecord),
|
|
112
|
+
head: input.details(input.headRecord),
|
|
113
|
+
output: input.details(input.outputRecord)
|
|
114
|
+
})
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function projectImportTargetDeltaConflict(identityKey, edge, workerEdge) {
|
|
119
|
+
return {
|
|
120
|
+
code: 'project-import-target-delta-conflict',
|
|
121
|
+
gateId: 'project-graph-delta',
|
|
122
|
+
message: `Output import ${JSON.stringify(projectImportTargetName(edge) ?? edge.moduleSpecifier ?? 'unknown')} resolves to a head-branch exported symbol that the worker import did not resolve against.`,
|
|
123
|
+
sourcePath: edge.sourcePath,
|
|
124
|
+
details: compactRecord({
|
|
125
|
+
reasonCode: 'project-import-target-delta-conflict',
|
|
126
|
+
conflictKey: `project-graph-delta#import-target#${identityKey}`,
|
|
127
|
+
identityKey,
|
|
128
|
+
sourcePath: edge.sourcePath,
|
|
129
|
+
moduleSpecifier: edge.moduleSpecifier,
|
|
130
|
+
importedName: edge.importedName,
|
|
131
|
+
localName: edge.localName,
|
|
132
|
+
importKind: edge.importKind,
|
|
133
|
+
resolvedModulePath: edge.resolvedModulePath,
|
|
134
|
+
outputTargetSymbolId: edge.resolvedTargetSymbolId,
|
|
135
|
+
workerTargetSymbolId: workerEdge.resolvedTargetSymbolId,
|
|
136
|
+
workerResolutionKind: workerEdge.resolutionKind,
|
|
137
|
+
outputResolutionKind: edge.resolutionKind
|
|
138
|
+
})
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function recordsByIdentityKey(records, identityKey) {
|
|
143
|
+
const result = new Map();
|
|
144
|
+
for (const record of records ?? []) {
|
|
145
|
+
const key = identityKey(record);
|
|
146
|
+
if (!key || result.has(key)) continue;
|
|
147
|
+
result.set(key, record);
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function publicContractIdentityKey(record) {
|
|
153
|
+
return firstString(
|
|
154
|
+
record?.key,
|
|
155
|
+
stableKey(['public-contract', record?.sourcePath, record?.apiSurfaceKind, record?.edgeKind, record?.moduleSpecifier, record?.exportedName ?? record?.symbolName ?? record?.symbolId])
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function publicContractFingerprint(record) {
|
|
160
|
+
return hashSemanticValue({
|
|
161
|
+
kind: 'frontier.lang.projectGraphDelta.publicContractFingerprint',
|
|
162
|
+
contractHash: record.contractHash,
|
|
163
|
+
signatureHash: record.signatureHash,
|
|
164
|
+
symbolName: record.symbolName,
|
|
165
|
+
symbolKind: record.symbolKind,
|
|
166
|
+
apiSurfaceKind: record.apiSurfaceKind,
|
|
167
|
+
exportedName: record.exportedName,
|
|
168
|
+
moduleSpecifier: record.moduleSpecifier,
|
|
169
|
+
edgeKind: record.edgeKind
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function publicContractDetails(record) {
|
|
174
|
+
if (!record) return undefined;
|
|
175
|
+
return compactRecord({
|
|
176
|
+
sourcePath: record.sourcePath,
|
|
177
|
+
key: publicContractIdentityKey(record),
|
|
178
|
+
symbolName: record.symbolName,
|
|
179
|
+
symbolKind: record.symbolKind,
|
|
180
|
+
apiSurfaceKind: record.apiSurfaceKind,
|
|
181
|
+
exportedName: record.exportedName,
|
|
182
|
+
moduleSpecifier: record.moduleSpecifier,
|
|
183
|
+
edgeKind: record.edgeKind,
|
|
184
|
+
signatureHash: record.signatureHash,
|
|
185
|
+
contractHash: record.contractHash,
|
|
186
|
+
sourceHash: record.sourceHash
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function reExportIdentityKey(record) {
|
|
191
|
+
return stableKey(['re-export-identity', record?.sourcePath, record?.exportedName ?? record?.localName ?? record?.namespace ?? (record?.exportStar || record?.isExportStar ? '*' : undefined)]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function reExportIdentityFingerprint(record) {
|
|
195
|
+
return hashSemanticValue({
|
|
196
|
+
kind: 'frontier.lang.projectGraphDelta.reExportIdentityFingerprint',
|
|
197
|
+
sourcePath: record.sourcePath,
|
|
198
|
+
moduleSpecifier: record.moduleSpecifier,
|
|
199
|
+
exportedName: record.exportedName,
|
|
200
|
+
importedName: record.importedName,
|
|
201
|
+
localName: record.localName,
|
|
202
|
+
namespace: record.namespace,
|
|
203
|
+
isTypeOnly: record.isTypeOnly,
|
|
204
|
+
exportStar: record.exportStar,
|
|
205
|
+
isExportStar: record.isExportStar,
|
|
206
|
+
originSymbolId: record.originSymbolId,
|
|
207
|
+
exportedSymbolId: record.exportedSymbolId,
|
|
208
|
+
localSymbolId: record.localSymbolId
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function reExportIdentityDetails(record) {
|
|
213
|
+
if (!record) return undefined;
|
|
214
|
+
return compactRecord({
|
|
215
|
+
sourcePath: record.sourcePath,
|
|
216
|
+
key: reExportIdentityKey(record),
|
|
217
|
+
moduleSpecifier: record.moduleSpecifier,
|
|
218
|
+
exportedName: record.exportedName,
|
|
219
|
+
importedName: record.importedName,
|
|
220
|
+
localName: record.localName,
|
|
221
|
+
namespace: record.namespace,
|
|
222
|
+
isTypeOnly: record.isTypeOnly,
|
|
223
|
+
exportStar: record.exportStar,
|
|
224
|
+
isExportStar: record.isExportStar,
|
|
225
|
+
originSymbolId: record.originSymbolId,
|
|
226
|
+
exportedSymbolId: record.exportedSymbolId,
|
|
227
|
+
localSymbolId: record.localSymbolId,
|
|
228
|
+
sourceHash: record.sourceHash
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function importEdgesByIdentityKey(records) {
|
|
233
|
+
const result = new Map();
|
|
234
|
+
for (const record of records ?? []) {
|
|
235
|
+
const key = importEdgeIdentityKey(record);
|
|
236
|
+
if (!key || result.has(key)) continue;
|
|
237
|
+
result.set(key, record);
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function importEdgeIdentityKey(edge) {
|
|
243
|
+
const targetName = projectImportTargetName(edge);
|
|
244
|
+
if (!edge?.sourcePath || !edge.moduleSpecifier || !targetName) return undefined;
|
|
245
|
+
return stableKey(['import-target', edge.sourcePath, edge.moduleSpecifier, targetName, edge.importKind, edge.isTypeOnly]);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function projectImportTargetName(edge) {
|
|
249
|
+
if (edge?.importKind === 'side-effect' || edge?.importKind === 'namespace') return undefined;
|
|
250
|
+
const name = edge?.importedName ?? edge?.localName ?? edge?.exportedName;
|
|
251
|
+
if (!name || name === '*') return undefined;
|
|
252
|
+
return String(name);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function optionalFingerprint(record, fingerprint) {
|
|
256
|
+
return record ? fingerprint(record) : undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function semanticSymbolIds(stage) {
|
|
260
|
+
return new Set((stage?.projectImport?.semanticIndex?.symbols ?? []).map((symbol) => symbol.id).filter(Boolean));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function stableKey(parts) {
|
|
264
|
+
const values = parts.map((part) => part === undefined || part === null ? '' : String(part));
|
|
265
|
+
return values.some(Boolean) ? values.join('#') : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function firstString(...values) {
|
|
269
|
+
for (const value of values) {
|
|
270
|
+
if (value !== undefined && value !== null && String(value)) return String(value);
|
|
271
|
+
}
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function uniqueStrings(values) {
|
|
276
|
+
return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export { addProjectGraphDeltaConflictSummary, projectGraphDeltaConflicts };
|
|
@@ -1,36 +1,213 @@
|
|
|
1
1
|
import { idFragment } from './native-import-utils.js';
|
|
2
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
3
|
+
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
2
4
|
import { createNativeProjectImportResult } from './internal/index-impl/createNativeProjectImportResult.js';
|
|
3
5
|
import { importNativeSource } from './internal/index-impl/importNativeSource.js';
|
|
4
6
|
|
|
5
7
|
function createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, mergeId) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
return createProjectGraphStageArtifacts(input, outputFiles, mergeId, 'output', projectImportsForStage(input, 'output'));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function createJsTsProjectSafeMergeGraphDelta(input, files, outputFiles, mergeId) {
|
|
12
|
+
const stageFiles = projectGraphDeltaStageFiles(files, outputFiles, input);
|
|
13
|
+
const stages = {};
|
|
14
|
+
for (const stageName of ['base', 'worker', 'head', 'output']) {
|
|
15
|
+
const files = stageFiles?.[stageName];
|
|
16
|
+
if (!Array.isArray(files)) continue;
|
|
17
|
+
stages[stageName] = createProjectGraphStageArtifacts(input, files, mergeId, stageName, projectImportsForStage(input, stageName));
|
|
18
|
+
}
|
|
19
|
+
const stageSummaries = Object.fromEntries(Object.entries(stages).map(([stageName, artifacts]) => [stageName, artifacts.summary]));
|
|
20
|
+
return {
|
|
21
|
+
kind: 'frontier.lang.jsTsProjectGraphDelta',
|
|
22
|
+
version: 1,
|
|
23
|
+
stages,
|
|
24
|
+
summary: {
|
|
25
|
+
stages: Object.keys(stages).length,
|
|
26
|
+
sourceFiles: sumStageSummary(stageSummaries, 'sourceFiles'),
|
|
27
|
+
publicContractRegions: sumStageSummary(stageSummaries, 'publicContractRegions'),
|
|
28
|
+
reExportIdentities: sumStageSummary(stageSummaries, 'reExportIdentities'),
|
|
29
|
+
importEdges: sumStageSummary(stageSummaries, 'importEdges'),
|
|
30
|
+
exportEdges: sumStageSummary(stageSummaries, 'exportEdges'),
|
|
31
|
+
unresolvedImportEdges: sumStageSummary(stageSummaries, 'unresolvedImportEdges'),
|
|
32
|
+
suppliedImports: sumStageSummary(stageSummaries, 'suppliedImports'),
|
|
33
|
+
matchedSuppliedImports: sumStageSummary(stageSummaries, 'matchedSuppliedImports'),
|
|
34
|
+
scannerFallbackImports: sumStageSummary(stageSummaries, 'scannerFallbackImports'),
|
|
35
|
+
stageSummaries
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createProjectGraphStageArtifacts(input, files, mergeId, stageName, stageImports) {
|
|
41
|
+
const sources = files.map((file) => ({
|
|
42
|
+
id: `${mergeId}_${stageName}_${idFragment(file.sourcePath)}`,
|
|
8
43
|
language: file.language ?? input.language ?? languageForPath(file.sourcePath),
|
|
9
44
|
sourcePath: file.sourcePath,
|
|
10
45
|
sourceText: file.sourceText,
|
|
11
|
-
|
|
46
|
+
sourceHash: file.sourceHash,
|
|
47
|
+
metadata: { semanticImportExpected: true, projectSafeMergeStage: stageName, projectSafeMergeOutput: stageName === 'output' }
|
|
12
48
|
}));
|
|
13
|
-
const
|
|
49
|
+
const suppliedImports = normalizeProjectImports(stageImports);
|
|
50
|
+
const importSelections = sources.map((source) => {
|
|
51
|
+
const suppliedImport = matchingProjectImport(source, suppliedImports);
|
|
52
|
+
return {
|
|
53
|
+
importResult: suppliedImport ?? importNativeSource(source),
|
|
54
|
+
sourceKind: suppliedImport ? `supplied-${stageName}-project-import` : `lightweight-${stageName}-project-scan`
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
const imports = importSelections.map((selection) => selection.importResult);
|
|
58
|
+
const projectGraphImportSource = {
|
|
59
|
+
stage: stageName,
|
|
60
|
+
suppliedImports: suppliedImports.length,
|
|
61
|
+
matchedSuppliedImports: importSelections.filter((selection) => selection.sourceKind === `supplied-${stageName}-project-import`).length,
|
|
62
|
+
scannerFallbackImports: importSelections.filter((selection) => selection.sourceKind === `lightweight-${stageName}-project-scan`).length
|
|
63
|
+
};
|
|
14
64
|
const projectImport = createNativeProjectImportResult({
|
|
15
|
-
id: `${mergeId}
|
|
65
|
+
id: `${mergeId}_${stageName}_project_import`,
|
|
16
66
|
projectRoot: input.projectRoot,
|
|
17
67
|
moduleResolution: input.moduleResolution ?? input.tsconfig,
|
|
18
68
|
sources,
|
|
19
69
|
metadata: {
|
|
20
70
|
projectSafeMergeId: mergeId,
|
|
21
|
-
|
|
71
|
+
projectGraphStage: stageName,
|
|
72
|
+
outputProjectSymbolGraph: stageName === 'output',
|
|
73
|
+
projectGraphImportSource,
|
|
74
|
+
...(stageName === 'output' ? { outputProjectImportSource: projectGraphImportSource } : {})
|
|
22
75
|
}
|
|
23
76
|
}, imports);
|
|
24
77
|
return {
|
|
78
|
+
kind: 'frontier.lang.jsTsProjectGraphStage',
|
|
79
|
+
version: 1,
|
|
80
|
+
stage: stageName,
|
|
25
81
|
projectImport,
|
|
26
|
-
projectSymbolGraph: projectImport.projectSymbolGraph
|
|
82
|
+
projectSymbolGraph: projectImport.projectSymbolGraph,
|
|
83
|
+
summary: projectGraphStageSummary(stageName, projectImport.projectSymbolGraph, projectGraphImportSource)
|
|
27
84
|
};
|
|
28
85
|
}
|
|
29
86
|
|
|
87
|
+
function normalizeProjectImports(value) {
|
|
88
|
+
if (!value) return [];
|
|
89
|
+
if (Array.isArray(value)) return value.filter(Boolean);
|
|
90
|
+
if (value instanceof Map) return [...value.values()].filter(Boolean);
|
|
91
|
+
if (typeof value === 'object') return Object.values(value).filter(Boolean);
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function projectImportsForStage(input, stageName) {
|
|
96
|
+
if (stageName === 'output') return input.outputProjectImports ?? input.projectGraphImports?.output;
|
|
97
|
+
if (stageName === 'base') return input.baseProjectImports ?? input.projectGraphImports?.base;
|
|
98
|
+
if (stageName === 'worker') return input.workerProjectImports ?? input.projectGraphImports?.worker;
|
|
99
|
+
if (stageName === 'head') return input.headProjectImports ?? input.projectGraphImports?.head;
|
|
100
|
+
return input.projectGraphImports?.[stageName];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function matchingProjectImport(source, imports) {
|
|
104
|
+
const sourcePath = String(source.sourcePath ?? '');
|
|
105
|
+
const sourceHash = String(source.sourceHash ?? '');
|
|
106
|
+
return imports.find((importResult) => {
|
|
107
|
+
const importSourcePath = sourcePathForImport(importResult);
|
|
108
|
+
if (importSourcePath && sourcePath && importSourcePath !== sourcePath) return false;
|
|
109
|
+
const importSourceHash = sourceHashForImport(importResult);
|
|
110
|
+
if (sourceHash && importSourceHash !== sourceHash) return false;
|
|
111
|
+
if (importSourceHash && sourceHash && importSourceHash !== sourceHash) return false;
|
|
112
|
+
return importSourcePath === sourcePath || importSourceHash === sourceHash;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function projectGraphStageSummary(stageName, projectSymbolGraph, importSource) {
|
|
117
|
+
const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
|
|
118
|
+
return {
|
|
119
|
+
stage: stageName,
|
|
120
|
+
sourceFiles: projectSymbolGraph?.sourceCount ?? 0,
|
|
121
|
+
documents: projectSymbolGraph?.documentCount ?? 0,
|
|
122
|
+
symbols: projectSymbolGraph?.symbolCount ?? 0,
|
|
123
|
+
fileHashes: projectSymbolGraph?.fileHashes?.length ?? 0,
|
|
124
|
+
importEdges: importEdges.length,
|
|
125
|
+
exportEdges: projectSymbolGraph?.exportEdges?.length ?? 0,
|
|
126
|
+
publicContractRegions: projectSymbolGraph?.publicContractRegions?.length ?? 0,
|
|
127
|
+
reExportIdentities: projectSymbolGraph?.reExportIdentities?.length ?? 0,
|
|
128
|
+
unresolvedImportEdges: importEdges.filter(isMissingProjectImportEdge).length,
|
|
129
|
+
suppliedImports: importSource.suppliedImports,
|
|
130
|
+
matchedSuppliedImports: importSource.matchedSuppliedImports,
|
|
131
|
+
scannerFallbackImports: importSource.scannerFallbackImports
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isMissingProjectImportEdge(edge) {
|
|
136
|
+
return typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-missing');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sumStageSummary(stageSummaries, field) {
|
|
140
|
+
return Object.values(stageSummaries).reduce((total, summary) => total + Number(summary?.[field] ?? 0), 0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function projectGraphDeltaStageFiles(files, outputFiles, input) {
|
|
144
|
+
return {
|
|
145
|
+
base: files
|
|
146
|
+
.map((file) => stageFile(file, file.baseSourceText, input))
|
|
147
|
+
.filter(Boolean),
|
|
148
|
+
worker: files
|
|
149
|
+
.map((file) => stageFile(file, workerStageSourceText(file), input))
|
|
150
|
+
.filter(Boolean),
|
|
151
|
+
head: files
|
|
152
|
+
.map((file) => stageFile(file, headStageSourceText(file), input))
|
|
153
|
+
.filter(Boolean),
|
|
154
|
+
output: outputFiles
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function workerStageSourceText(file) {
|
|
159
|
+
if (file.workerDeleted) return undefined;
|
|
160
|
+
return file.workerSourceText ?? file.baseSourceText;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function headStageSourceText(file) {
|
|
164
|
+
if (file.headDeleted) return undefined;
|
|
165
|
+
return file.headSourceText ?? file.baseSourceText;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function stageFile(file, sourceText, input) {
|
|
169
|
+
if (typeof sourceText !== 'string' || !file.sourcePath) return undefined;
|
|
170
|
+
return compactRecord({
|
|
171
|
+
sourcePath: file.sourcePath,
|
|
172
|
+
language: file.language ?? input.language,
|
|
173
|
+
sourceText,
|
|
174
|
+
sourceHash: hashText(sourceText)
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function hashText(sourceText) {
|
|
179
|
+
return hashSemanticValue(sourceText);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function sourcePathForImport(importResult) {
|
|
183
|
+
return firstString(
|
|
184
|
+
importResult?.sourcePath,
|
|
185
|
+
importResult?.nativeSource?.sourcePath,
|
|
186
|
+
importResult?.nativeAst?.sourcePath,
|
|
187
|
+
importResult?.semanticIndex?.documents?.[0]?.path
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function sourceHashForImport(importResult) {
|
|
192
|
+
return firstString(
|
|
193
|
+
importResult?.sourceHash,
|
|
194
|
+
importResult?.nativeSource?.sourceHash,
|
|
195
|
+
importResult?.nativeAst?.sourceHash,
|
|
196
|
+
importResult?.semanticIndex?.documents?.[0]?.sourceHash
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
30
200
|
function languageForPath(sourcePath) {
|
|
31
201
|
const path = String(sourcePath ?? '').toLowerCase();
|
|
32
202
|
if (path.endsWith('.js') || path.endsWith('.jsx') || path.endsWith('.mjs') || path.endsWith('.cjs')) return 'javascript';
|
|
33
203
|
return 'typescript';
|
|
34
204
|
}
|
|
35
205
|
|
|
36
|
-
|
|
206
|
+
function firstString(...values) {
|
|
207
|
+
for (const value of values) {
|
|
208
|
+
if (value !== undefined && value !== null && String(value)) return String(value);
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export { createJsTsProjectSafeMergeGraphArtifacts, createJsTsProjectSafeMergeGraphDelta };
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
2
|
import { safeMergeJsTsSource } from './js-ts-safe-merge-composed.js';
|
|
3
3
|
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
4
|
-
import { createJsTsProjectSafeMergeGraphArtifacts } from './js-ts-safe-project-merge-graph.js';
|
|
4
|
+
import { createJsTsProjectSafeMergeGraphArtifacts, createJsTsProjectSafeMergeGraphDelta } from './js-ts-safe-project-merge-graph.js';
|
|
5
|
+
import { addProjectGraphDeltaConflictSummary } from './js-ts-safe-project-merge-graph-delta-conflicts.js';
|
|
6
|
+
import { outputProjectGraphConflicts, projectGraphDeltaConflicts } from './js-ts-safe-project-merge-graph-conflicts.js';
|
|
5
7
|
|
|
6
8
|
function safeMergeJsTsProject(input = {}) {
|
|
7
9
|
const id = String(input.id ?? 'js_ts_project_safe_merge');
|
|
8
10
|
const files = normalizeProjectFiles(input);
|
|
9
11
|
const fileResults = files.map((file) => mergeProjectFile(file, input, id));
|
|
10
12
|
const blockedFiles = fileResults.filter((file) => file.status === 'blocked');
|
|
11
|
-
const status = blockedFiles.length ? 'blocked' : 'merged';
|
|
12
13
|
const outputFiles = fileResults
|
|
13
14
|
.filter((file) => typeof file.outputSourceText === 'string')
|
|
14
15
|
.map((file) => compactRecord({
|
|
@@ -18,10 +19,25 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
18
19
|
sourceHash: file.outputHash,
|
|
19
20
|
operation: file.operation
|
|
20
21
|
}));
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
? createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, id)
|
|
22
|
+
const projectGraphDelta = blockedFiles.length === 0 && input.includeProjectGraphDelta
|
|
23
|
+
? createJsTsProjectSafeMergeGraphDelta(input, files, outputFiles, id)
|
|
24
24
|
: undefined;
|
|
25
|
+
const graphArtifacts = projectGraphDelta?.stages?.output ?? (blockedFiles.length === 0 && input.includeOutputProjectSymbolGraph
|
|
26
|
+
? createJsTsProjectSafeMergeGraphArtifacts(input, outputFiles, id)
|
|
27
|
+
: undefined);
|
|
28
|
+
const outputGraphConflicts = outputProjectGraphConflicts(graphArtifacts?.projectSymbolGraph);
|
|
29
|
+
const deltaGraphConflicts = projectGraphDeltaConflicts(projectGraphDelta);
|
|
30
|
+
const projectGraphDeltaWithConflicts = addProjectGraphDeltaConflictSummary(projectGraphDelta, deltaGraphConflicts);
|
|
31
|
+
const graphConflicts = [...outputGraphConflicts, ...deltaGraphConflicts];
|
|
32
|
+
const status = blockedFiles.length || graphConflicts.length ? 'blocked' : 'merged';
|
|
33
|
+
const reasonCodes = uniqueStrings([
|
|
34
|
+
...blockedFiles.flatMap((file) => file.admission.reasonCodes),
|
|
35
|
+
...graphConflicts.map((conflict) => conflict.code)
|
|
36
|
+
]);
|
|
37
|
+
const conflictKeys = uniqueStrings([
|
|
38
|
+
...fileResults.flatMap((file) => file.conflictKeys),
|
|
39
|
+
...graphConflicts.map((conflict) => conflict.details?.conflictKey)
|
|
40
|
+
]);
|
|
25
41
|
const core = {
|
|
26
42
|
kind: 'frontier.lang.jsTsProjectSafeMerge',
|
|
27
43
|
version: 1,
|
|
@@ -32,7 +48,8 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
32
48
|
outputFiles,
|
|
33
49
|
outputProjectImport: graphArtifacts?.projectImport,
|
|
34
50
|
outputProjectSymbolGraph: graphArtifacts?.projectSymbolGraph,
|
|
35
|
-
|
|
51
|
+
projectGraphDelta: projectGraphDeltaWithConflicts,
|
|
52
|
+
conflicts: [...fileResults.flatMap((file) => file.conflicts), ...graphConflicts],
|
|
36
53
|
admission: {
|
|
37
54
|
status: status === 'merged' ? 'auto-merge-candidate' : 'blocked',
|
|
38
55
|
action: status === 'merged' ? 'apply-project' : 'human-review',
|
|
@@ -41,15 +58,19 @@ function safeMergeJsTsProject(input = {}) {
|
|
|
41
58
|
autoMergeClaim: false,
|
|
42
59
|
semanticEquivalenceClaim: false,
|
|
43
60
|
reasonCodes,
|
|
44
|
-
conflictKeys
|
|
61
|
+
conflictKeys
|
|
45
62
|
},
|
|
46
|
-
summary: projectSummary(fileResults),
|
|
63
|
+
summary: projectSummary(fileResults, graphConflicts),
|
|
47
64
|
metadata: compactRecord({
|
|
48
65
|
workerChangeSetId: input.workerChangeSetId,
|
|
49
66
|
headChangeSetId: input.headChangeSetId,
|
|
50
67
|
projectRoot: input.projectRoot,
|
|
51
68
|
filesInput: Array.isArray(input.files) ? 'records' : 'maps',
|
|
52
69
|
outputProjectSymbolGraph: Boolean(graphArtifacts?.projectSymbolGraph),
|
|
70
|
+
projectGraphDelta: Boolean(projectGraphDeltaWithConflicts),
|
|
71
|
+
projectGraphConflicts: graphConflicts.length || undefined,
|
|
72
|
+
outputProjectGraphConflicts: outputGraphConflicts.length || undefined,
|
|
73
|
+
projectGraphDeltaConflicts: deltaGraphConflicts.length || undefined,
|
|
53
74
|
autoMergeClaim: false,
|
|
54
75
|
semanticEquivalenceClaim: false
|
|
55
76
|
})
|
|
@@ -230,19 +251,24 @@ function policyForFile(input, sourcePath) {
|
|
|
230
251
|
|
|
231
252
|
function sourceLedgersForFile(input, sourcePath) {
|
|
232
253
|
const byPath = input.sourceLedgersByPath?.[sourcePath] ?? input.sourceLedgers?.[sourcePath];
|
|
233
|
-
|
|
234
|
-
if (input.sourceLedgers?.base || input.sourceLedgers?.worker || input.sourceLedgers?.head) return input.sourceLedgers;
|
|
235
|
-
return undefined;
|
|
254
|
+
return byPath ?? (input.sourceLedgers?.base || input.sourceLedgers?.worker || input.sourceLedgers?.head ? input.sourceLedgers : undefined);
|
|
236
255
|
}
|
|
237
256
|
|
|
238
|
-
function projectSummary(files) {
|
|
257
|
+
function projectSummary(files, graphConflicts = []) {
|
|
239
258
|
const byOperation = {};
|
|
240
259
|
for (const file of files) byOperation[file.operation] = (byOperation[file.operation] ?? 0) + 1;
|
|
260
|
+
const deltaConflicts = graphConflicts.filter((conflict) => conflict.gateId === 'project-graph-delta');
|
|
241
261
|
return {
|
|
242
262
|
files: files.length,
|
|
243
263
|
mergedFiles: files.filter((file) => file.status === 'merged').length,
|
|
244
264
|
blockedFiles: files.filter((file) => file.status === 'blocked').length,
|
|
245
265
|
outputFiles: files.filter((file) => typeof file.outputSourceText === 'string').length,
|
|
266
|
+
projectGraphConflicts: graphConflicts.length,
|
|
267
|
+
outputProjectGraphConflicts: graphConflicts.length - deltaConflicts.length,
|
|
268
|
+
projectGraphDeltaConflicts: deltaConflicts.length,
|
|
269
|
+
projectGraphPublicContractConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-public-contract-delta-conflict').length,
|
|
270
|
+
projectGraphReExportIdentityConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-re-export-identity-delta-conflict').length,
|
|
271
|
+
projectGraphImportTargetConflicts: deltaConflicts.filter((conflict) => conflict.code === 'project-import-target-delta-conflict').length,
|
|
246
272
|
semanticArtifactFiles: files.filter((file) => file.semanticArtifacts).length,
|
|
247
273
|
operations: byOperation
|
|
248
274
|
};
|
|
@@ -272,21 +298,15 @@ function blockedAdmission(reasonCode) {
|
|
|
272
298
|
};
|
|
273
299
|
}
|
|
274
300
|
|
|
275
|
-
function hashText(text) {
|
|
276
|
-
return typeof text === 'string' ? hashSemanticValue(text) : undefined;
|
|
277
|
-
}
|
|
301
|
+
function hashText(text) { return typeof text === 'string' ? hashSemanticValue(text) : undefined; }
|
|
278
302
|
|
|
279
|
-
function stringOrUndefined(value) {
|
|
280
|
-
return typeof value === 'string' ? value : undefined;
|
|
281
|
-
}
|
|
303
|
+
function stringOrUndefined(value) { return typeof value === 'string' ? value : undefined; }
|
|
282
304
|
|
|
283
305
|
function safeId(value) {
|
|
284
306
|
return String(value ?? 'unknown').replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'file';
|
|
285
307
|
}
|
|
286
308
|
|
|
287
|
-
function bySourcePath(left, right) {
|
|
288
|
-
return String(left.sourcePath ?? '').localeCompare(String(right.sourcePath ?? ''));
|
|
289
|
-
}
|
|
309
|
+
function bySourcePath(left, right) { return String(left.sourcePath ?? '').localeCompare(String(right.sourcePath ?? '')); }
|
|
290
310
|
|
|
291
311
|
function uniqueStrings(values) {
|
|
292
312
|
return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
|
package/package.json
CHANGED