@shapeshift-labs/frontier-lang-compiler 0.2.147 → 0.2.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js-ts-safe-merge-constants.js +1 -0
- package/dist/js-ts-safe-merge-independent-deletion-plan.js +8 -0
- package/dist/js-ts-safe-merge-semantic-edit-already-applied.js +45 -0
- package/dist/js-ts-safe-merge-semantic-edit-artifacts.js +111 -0
- package/dist/js-ts-safe-merge-semantic-edit-fallback-utils.js +11 -0
- package/dist/js-ts-safe-merge-semantic-edit-fallback.js +26 -121
- package/dist/js-ts-safe-merge-top-level-rename-fallback.js +149 -0
- package/dist/js-ts-safe-merge-top-level-rename-result.js +44 -0
- package/dist/js-ts-safe-project-merge-graph-conflicts.js +24 -3
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ export const JsTsSafeMergeConflictCodes = Object.freeze({
|
|
|
19
19
|
malformedSyntax: 'malformed-syntax',
|
|
20
20
|
sideEffectImportReorder: 'side-effect-import-reorder',
|
|
21
21
|
topLevelOrderChanged: 'top-level-order-changed',
|
|
22
|
+
topLevelRenamePublicExportContract: 'top-level-rename-public-export-contract',
|
|
22
23
|
changedExistingDeclaration: 'changed-existing-declaration',
|
|
23
24
|
typeAliasConflict: 'type-alias-conflict',
|
|
24
25
|
importShapeChanged: 'import-shape-changed',
|
|
@@ -50,6 +50,9 @@ function createIndependentTopLevelDeletionPlan(input, topLevelResult) {
|
|
|
50
50
|
if (deletedEntry.kind !== 'declaration' || deletedEntry.declarationInfo?.exported === true) {
|
|
51
51
|
return { ok: false, reasonCodes: ['exported-or-unsupported-top-level-deletion'] };
|
|
52
52
|
}
|
|
53
|
+
if (isUnsupportedTopLevelDeletion(deletedEntry)) {
|
|
54
|
+
return { ok: false, reasonCodes: ['unsupported-top-level-deletion-declaration'] };
|
|
55
|
+
}
|
|
53
56
|
|
|
54
57
|
const expectedWorkerKeys = base.entries
|
|
55
58
|
.filter((entry) => entry.key !== deletedEntry.key)
|
|
@@ -127,6 +130,11 @@ function entriesByKey(entries) {
|
|
|
127
130
|
return new Map(entries.map((entry) => [entry.key, entry]));
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
function isUnsupportedTopLevelDeletion(entry) {
|
|
134
|
+
if (entry.declarationInfo?.declarationKind === 'module') return true;
|
|
135
|
+
return /^\s*(?:export\s+)?declare\b/.test(entry.text ?? '');
|
|
136
|
+
}
|
|
137
|
+
|
|
130
138
|
function sameStringList(left, right) {
|
|
131
139
|
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
132
140
|
}
|
|
@@ -58,6 +58,7 @@ function isProjectedDeleteAlreadyAppliedReplay(input) {
|
|
|
58
58
|
.filter((edit) => edit.editKind === 'delete')
|
|
59
59
|
.map((edit) => edit.operationId));
|
|
60
60
|
const staleDeletes = edits.filter(isProjectedDeleteMissingAnchor);
|
|
61
|
+
if (hasUnsafeProjectedDeleteCompanions(input, staleDeletes)) return false;
|
|
61
62
|
return staleDeletes.length > 0
|
|
62
63
|
&& edits.every((edit) => edit.status === 'already-applied' || isProjectedDeleteMissingAnchor(edit))
|
|
63
64
|
&& staleDeletes.every((edit) => appliedDeleteIds.has(edit.operationId) && projectionDeleteIds.has(edit.operationId));
|
|
@@ -79,4 +80,48 @@ function alreadyAppliedDeleteEdit(edit) {
|
|
|
79
80
|
};
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
function hasUnsafeProjectedDeleteCompanions(input, staleDeletes) {
|
|
84
|
+
const projectionEdits = input.projection?.edits ?? [];
|
|
85
|
+
if (!projectionEdits.length || !staleDeletes.length) return false;
|
|
86
|
+
const projectionDeleteById = new Map(projectionEdits
|
|
87
|
+
.filter((edit) => edit.editKind === 'delete')
|
|
88
|
+
.map((edit) => [edit.operationId, edit]));
|
|
89
|
+
for (const staleDelete of staleDeletes) {
|
|
90
|
+
const projectedDelete = projectionDeleteById.get(staleDelete.operationId);
|
|
91
|
+
const scope = memberBodyDeleteScope(projectedDelete ?? staleDelete);
|
|
92
|
+
if (!scope) continue;
|
|
93
|
+
const companions = projectionEdits.filter((edit) => (
|
|
94
|
+
edit.operationId !== staleDelete.operationId
|
|
95
|
+
&& edit.editKind !== 'delete'
|
|
96
|
+
&& symbolContainerName(edit.symbolName) === scope.container
|
|
97
|
+
));
|
|
98
|
+
if (!companions.length) return true;
|
|
99
|
+
const bodyInserts = companions.filter((edit) => (
|
|
100
|
+
edit.editKind === 'insert'
|
|
101
|
+
&& edit.regionKind === scope.regionKind
|
|
102
|
+
&& edit.symbolKind === scope.symbolKind
|
|
103
|
+
));
|
|
104
|
+
if (bodyInserts.length === 0) return true;
|
|
105
|
+
if (companions.some((edit) => !bodyInserts.includes(edit))) return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function memberBodyDeleteScope(edit) {
|
|
111
|
+
if (edit?.editKind !== 'delete' || edit?.regionKind !== 'body') return undefined;
|
|
112
|
+
const container = symbolContainerName(edit.symbolName);
|
|
113
|
+
if (!container) return undefined;
|
|
114
|
+
return {
|
|
115
|
+
container,
|
|
116
|
+
regionKind: edit.regionKind,
|
|
117
|
+
symbolKind: edit.symbolKind
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function symbolContainerName(symbolName) {
|
|
122
|
+
const normalized = String(symbolName ?? '').split(':controlFlow:')[0];
|
|
123
|
+
const separator = normalized.lastIndexOf('.');
|
|
124
|
+
return separator === -1 ? undefined : normalized.slice(0, separator);
|
|
125
|
+
}
|
|
126
|
+
|
|
82
127
|
export { normalizeAlreadyAppliedDeleteReplay };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { semanticFallbackPhase } from './js-ts-safe-merge-semantic-edit-fallback-utils.js';
|
|
3
|
+
import { idFragment, uniqueStrings } from './native-import-utils.js';
|
|
4
|
+
|
|
5
|
+
function semanticEditArtifacts(input) {
|
|
6
|
+
const reasonCodes = semanticEditArtifactReasonCodes(input);
|
|
7
|
+
const status = reasonCodes.length ? 'blocked' : 'verified';
|
|
8
|
+
const core = {
|
|
9
|
+
kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
|
|
10
|
+
version: 1,
|
|
11
|
+
schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
|
|
12
|
+
id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(input.id)}`,
|
|
13
|
+
sourcePath: input.sourcePath,
|
|
14
|
+
language: input.language,
|
|
15
|
+
status,
|
|
16
|
+
script: input.script,
|
|
17
|
+
projection: input.projection,
|
|
18
|
+
replay: input.replay,
|
|
19
|
+
alreadyAppliedReplay: input.alreadyAppliedReplay,
|
|
20
|
+
admission: {
|
|
21
|
+
status: status === 'verified' ? 'auto-merge-candidate' : 'blocked',
|
|
22
|
+
action: status === 'verified' ? 'apply' : 'human-review',
|
|
23
|
+
reviewRequired: status !== 'verified',
|
|
24
|
+
autoApplyCandidate: status === 'verified',
|
|
25
|
+
autoMergeClaim: false,
|
|
26
|
+
semanticEquivalenceClaim: false,
|
|
27
|
+
reasonCodes
|
|
28
|
+
},
|
|
29
|
+
summary: {
|
|
30
|
+
operations: input.script.summary.operations,
|
|
31
|
+
edits: input.projection.edits.length,
|
|
32
|
+
replayStatus: input.replay.status,
|
|
33
|
+
alreadyAppliedReplayStatus: input.alreadyAppliedReplay.status,
|
|
34
|
+
projectedSourceMatchesMerged: input.projection.sourceText === input.replay.outputSourceText,
|
|
35
|
+
replayOutputMatchesMerged: input.replay.outputSourceText === input.projection.sourceText
|
|
36
|
+
},
|
|
37
|
+
evidence: [{
|
|
38
|
+
id: `evidence_${idFragment(input.id)}_js_ts_semantic_edit_replay`,
|
|
39
|
+
kind: 'js-ts-semantic-edit-replay',
|
|
40
|
+
status: status === 'verified' ? 'passed' : 'needs-review',
|
|
41
|
+
path: input.sourcePath,
|
|
42
|
+
summary: status === 'verified'
|
|
43
|
+
? `JS/TS semantic edit replay verified ${input.script.summary.operations} operation(s).`
|
|
44
|
+
: `JS/TS semantic edit replay requires review: ${reasonCodes.join(', ')}.`
|
|
45
|
+
}],
|
|
46
|
+
metadata: {
|
|
47
|
+
autoMergeClaim: false,
|
|
48
|
+
semanticEquivalenceClaim: false,
|
|
49
|
+
source: input.stagedFallback ? semanticFallbackPhase(input.stagedFallback) : 'js-ts-semantic-edit-fallback',
|
|
50
|
+
originalReasonCodes: input.topLevelResult.admission?.reasonCodes ?? [],
|
|
51
|
+
stagedTopLevelSummary: input.stagedFallback?.stagedTopLevelResult?.summary,
|
|
52
|
+
neutralization: input.stagedFallback?.neutralization?.summary
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function blockedSemanticEditArtifacts(input, topLevelResult, reasonCodes, error) {
|
|
59
|
+
const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
|
|
60
|
+
const core = {
|
|
61
|
+
kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
|
|
62
|
+
version: 1,
|
|
63
|
+
schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
|
|
64
|
+
id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(id)}`,
|
|
65
|
+
sourcePath: input.sourcePath ?? topLevelResult.sourcePath,
|
|
66
|
+
language: input.language ?? topLevelResult.language ?? 'typescript',
|
|
67
|
+
status: 'blocked',
|
|
68
|
+
admission: {
|
|
69
|
+
status: 'blocked',
|
|
70
|
+
action: 'human-review',
|
|
71
|
+
reviewRequired: true,
|
|
72
|
+
autoApplyCandidate: false,
|
|
73
|
+
autoMergeClaim: false,
|
|
74
|
+
semanticEquivalenceClaim: false,
|
|
75
|
+
reasonCodes
|
|
76
|
+
},
|
|
77
|
+
summary: {
|
|
78
|
+
operations: 0,
|
|
79
|
+
edits: 0,
|
|
80
|
+
replayStatus: 'blocked',
|
|
81
|
+
alreadyAppliedReplayStatus: 'blocked',
|
|
82
|
+
projectedSourceMatchesMerged: false,
|
|
83
|
+
replayOutputMatchesMerged: false
|
|
84
|
+
},
|
|
85
|
+
metadata: {
|
|
86
|
+
source: 'js-ts-semantic-edit-fallback',
|
|
87
|
+
error: error?.message
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function semanticEditArtifactReasonCodes(input) {
|
|
94
|
+
const scriptReady = input.script.admission.status === 'auto-merge-candidate';
|
|
95
|
+
const projectionReady = input.projection.status === 'projected';
|
|
96
|
+
const replayReady = input.replay.status === 'accepted-clean';
|
|
97
|
+
const alreadyAppliedReady = input.alreadyAppliedReplay.status === 'already-applied';
|
|
98
|
+
return uniqueStrings([
|
|
99
|
+
scriptReady ? undefined : `semantic-edit-script-${input.script.admission.status}`,
|
|
100
|
+
projectionReady ? undefined : `semantic-edit-projection-${input.projection.status}`,
|
|
101
|
+
replayReady ? undefined : `semantic-edit-replay-${input.replay.status}`,
|
|
102
|
+
input.replay.outputSourceText !== input.projection.sourceText ? 'semantic-edit-replay-output-mismatch' : undefined,
|
|
103
|
+
alreadyAppliedReady ? undefined : `semantic-edit-already-applied-${input.alreadyAppliedReplay.status}`,
|
|
104
|
+
...(scriptReady ? [] : input.script.admission.reasonCodes),
|
|
105
|
+
...(projectionReady ? [] : input.projection.admission.reasonCodes),
|
|
106
|
+
...(replayReady ? [] : input.replay.admission.reasonCodes),
|
|
107
|
+
...(alreadyAppliedReady ? [] : input.alreadyAppliedReplay.admission.reasonCodes)
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { blockedSemanticEditArtifacts, semanticEditArtifacts };
|
|
@@ -33,7 +33,18 @@ function semanticFallbackPhase(fallback) {
|
|
|
33
33
|
: 'staged-top-level-semantic-edit-fallback';
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function semanticFallbackCandidates(stagedFallback) {
|
|
37
|
+
if (!stagedFallback) return [undefined];
|
|
38
|
+
const headChanged = (stagedFallback.neutralization?.summary?.headChangedExistingDeclarations ?? 0) > 0;
|
|
39
|
+
const directFallback = stagedFallback.directProjectionHeadSourceText && (headChanged || stagedFallback.safeTopLevelChanges > 0)
|
|
40
|
+
? { ...stagedFallback, projectionMode: 'direct' }
|
|
41
|
+
: undefined;
|
|
42
|
+
if (headChanged) return directFallback ? [directFallback, undefined] : [undefined];
|
|
43
|
+
return directFallback ? [stagedFallback, directFallback, undefined] : [stagedFallback];
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
export {
|
|
47
|
+
semanticFallbackCandidates,
|
|
37
48
|
semanticFallbackChangedExistingDeclarations,
|
|
38
49
|
semanticFallbackConflictCode,
|
|
39
50
|
semanticFallbackPhase,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
1
|
import { createSemanticEditScript } from './internal/index-impl/semanticEditScripts.js';
|
|
3
2
|
import { projectSemanticEditScriptToSource } from './internal/index-impl/projectSemanticEditScriptToSource.js';
|
|
4
3
|
import { replaySemanticEditProjection } from './internal/index-impl/replaySemanticEditProjection.js';
|
|
5
4
|
import { JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
|
|
6
5
|
import { independentTopLevelDeletionFallbackResult } from './js-ts-safe-merge-independent-deletion-fallback.js';
|
|
7
6
|
import { normalizeAlreadyAppliedDeleteReplay } from './js-ts-safe-merge-semantic-edit-already-applied.js';
|
|
7
|
+
import { blockedSemanticEditArtifacts, semanticEditArtifacts } from './js-ts-safe-merge-semantic-edit-artifacts.js';
|
|
8
8
|
import {
|
|
9
|
+
semanticFallbackCandidates,
|
|
9
10
|
semanticFallbackChangedExistingDeclarations,
|
|
10
11
|
semanticFallbackConflictCode,
|
|
11
12
|
semanticFallbackPhase,
|
|
@@ -19,11 +20,23 @@ import {
|
|
|
19
20
|
} from './js-ts-safe-merge-staged-declaration-replay.js';
|
|
20
21
|
import { createStagedTopLevelSemanticFallback } from './js-ts-safe-merge-staged-top-level-fallback.js';
|
|
21
22
|
import { createSourceShapeSemanticFallbackResult } from './js-ts-safe-merge-source-shape-fallbacks.js';
|
|
22
|
-
import {
|
|
23
|
+
import { analyzeTopLevelRenameAdmission } from './js-ts-safe-merge-top-level-rename-fallback.js';
|
|
24
|
+
import { topLevelRenameBlockedResult } from './js-ts-safe-merge-top-level-rename-result.js';
|
|
23
25
|
|
|
24
26
|
function semanticEditFallbackResult(input, topLevelResult) {
|
|
25
27
|
const independentDeletionResult = independentTopLevelDeletionFallbackResult(input, topLevelResult);
|
|
26
28
|
if (independentDeletionResult) return independentDeletionResult;
|
|
29
|
+
const topLevelRenameAdmission = analyzeTopLevelRenameAdmission(input, topLevelResult);
|
|
30
|
+
if (topLevelRenameAdmission?.status === 'blocked') {
|
|
31
|
+
return topLevelRenameBlockedResult(input, topLevelResult, topLevelRenameAdmission);
|
|
32
|
+
}
|
|
33
|
+
if (topLevelRenameAdmission?.status === 'candidate') {
|
|
34
|
+
const artifacts = createSemanticEditFallbackArtifacts(input, topLevelResult);
|
|
35
|
+
if (artifacts.status !== 'verified') {
|
|
36
|
+
return semanticEditFallbackBlockedResult(input, topLevelResult, artifacts, topLevelRenameAdmission);
|
|
37
|
+
}
|
|
38
|
+
return semanticEditFallbackMergedResult(input, topLevelResult, undefined, artifacts, topLevelRenameAdmission);
|
|
39
|
+
}
|
|
27
40
|
if (!shouldTrySemanticEditFallback(topLevelResult)) return topLevelResult;
|
|
28
41
|
const stagedFallback = createStagedTopLevelSemanticFallback(input, topLevelResult);
|
|
29
42
|
const candidates = semanticFallbackCandidates(stagedFallback);
|
|
@@ -40,6 +53,10 @@ function semanticEditFallbackResult(input, topLevelResult) {
|
|
|
40
53
|
if (sourceShapeResult) return sourceShapeResult;
|
|
41
54
|
return semanticEditFallbackBlockedResult(input, topLevelResult, artifacts);
|
|
42
55
|
}
|
|
56
|
+
return semanticEditFallbackMergedResult(input, topLevelResult, selectedFallback, artifacts);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function semanticEditFallbackMergedResult(input, topLevelResult, selectedFallback, artifacts, topLevelRenameAdmission) {
|
|
43
60
|
const resultBase = selectedFallback?.stagedTopLevelResult ?? topLevelResult;
|
|
44
61
|
const mergedSourceText = artifacts.projection.sourceText;
|
|
45
62
|
const gates = semanticEditGates(artifacts);
|
|
@@ -68,32 +85,26 @@ function semanticEditFallbackResult(input, topLevelResult) {
|
|
|
68
85
|
semanticEditOperations: artifacts.script.summary.operations,
|
|
69
86
|
semanticEditAppliedOperations: artifacts.replay.summary.applied,
|
|
70
87
|
semanticEditReplayStatus: artifacts.replay.status,
|
|
88
|
+
topLevelDeclarationRenames: topLevelRenameAdmission ? 1 : resultBase.summary?.topLevelDeclarationRenames,
|
|
71
89
|
composedPhases: 2
|
|
72
90
|
},
|
|
73
91
|
metadata: {
|
|
74
92
|
...resultBase.metadata,
|
|
75
93
|
composed: {
|
|
76
|
-
phase: semanticFallbackPhase(selectedFallback),
|
|
77
|
-
phases:
|
|
94
|
+
phase: topLevelRenameAdmission ? 'top-level-rename-semantic-edit-fallback' : semanticFallbackPhase(selectedFallback),
|
|
95
|
+
phases: topLevelRenameAdmission
|
|
96
|
+
? ['top-level-rename-admission', 'semantic-edit']
|
|
97
|
+
: selectedFallback ? ['top-level-neutralization', 'top-level-ledger', 'semantic-edit'] : ['top-level-ledger', 'semantic-edit'],
|
|
78
98
|
originalReasonCodes: topLevelResult.admission?.reasonCodes ?? [],
|
|
79
99
|
stagedTopLevelSummary: selectedFallback?.stagedTopLevelResult?.summary,
|
|
80
|
-
neutralization: selectedFallback?.neutralization?.summary
|
|
100
|
+
neutralization: selectedFallback?.neutralization?.summary,
|
|
101
|
+
topLevelRenameAdmission: topLevelRenameAdmission?.summary
|
|
81
102
|
}
|
|
82
103
|
},
|
|
83
104
|
semanticArtifacts: artifacts
|
|
84
105
|
};
|
|
85
106
|
}
|
|
86
107
|
|
|
87
|
-
function semanticFallbackCandidates(stagedFallback) {
|
|
88
|
-
if (!stagedFallback) return [undefined];
|
|
89
|
-
const headChanged = (stagedFallback.neutralization?.summary?.headChangedExistingDeclarations ?? 0) > 0;
|
|
90
|
-
const directFallback = stagedFallback.directProjectionHeadSourceText && (headChanged || stagedFallback.safeTopLevelChanges > 0)
|
|
91
|
-
? { ...stagedFallback, projectionMode: 'direct' }
|
|
92
|
-
: undefined;
|
|
93
|
-
if (headChanged) return directFallback ? [directFallback, undefined] : [undefined];
|
|
94
|
-
return directFallback ? [stagedFallback, directFallback, undefined] : [stagedFallback];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
108
|
function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallback) {
|
|
98
109
|
try {
|
|
99
110
|
const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
|
|
@@ -166,112 +177,6 @@ function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallba
|
|
|
166
177
|
}
|
|
167
178
|
}
|
|
168
179
|
|
|
169
|
-
function semanticEditArtifacts(input) {
|
|
170
|
-
const reasonCodes = semanticEditArtifactReasonCodes(input);
|
|
171
|
-
const status = reasonCodes.length ? 'blocked' : 'verified';
|
|
172
|
-
const core = {
|
|
173
|
-
kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
|
|
174
|
-
version: 1,
|
|
175
|
-
schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
|
|
176
|
-
id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(input.id)}`,
|
|
177
|
-
sourcePath: input.sourcePath,
|
|
178
|
-
language: input.language,
|
|
179
|
-
status,
|
|
180
|
-
script: input.script,
|
|
181
|
-
projection: input.projection,
|
|
182
|
-
replay: input.replay,
|
|
183
|
-
alreadyAppliedReplay: input.alreadyAppliedReplay,
|
|
184
|
-
admission: {
|
|
185
|
-
status: status === 'verified' ? 'auto-merge-candidate' : 'blocked',
|
|
186
|
-
action: status === 'verified' ? 'apply' : 'human-review',
|
|
187
|
-
reviewRequired: status !== 'verified',
|
|
188
|
-
autoApplyCandidate: status === 'verified',
|
|
189
|
-
autoMergeClaim: false,
|
|
190
|
-
semanticEquivalenceClaim: false,
|
|
191
|
-
reasonCodes
|
|
192
|
-
},
|
|
193
|
-
summary: {
|
|
194
|
-
operations: input.script.summary.operations,
|
|
195
|
-
edits: input.projection.edits.length,
|
|
196
|
-
replayStatus: input.replay.status,
|
|
197
|
-
alreadyAppliedReplayStatus: input.alreadyAppliedReplay.status,
|
|
198
|
-
projectedSourceMatchesMerged: input.projection.sourceText === input.replay.outputSourceText,
|
|
199
|
-
replayOutputMatchesMerged: input.replay.outputSourceText === input.projection.sourceText
|
|
200
|
-
},
|
|
201
|
-
evidence: [{
|
|
202
|
-
id: `evidence_${idFragment(input.id)}_js_ts_semantic_edit_replay`,
|
|
203
|
-
kind: 'js-ts-semantic-edit-replay',
|
|
204
|
-
status: status === 'verified' ? 'passed' : 'needs-review',
|
|
205
|
-
path: input.sourcePath,
|
|
206
|
-
summary: status === 'verified'
|
|
207
|
-
? `JS/TS semantic edit replay verified ${input.script.summary.operations} operation(s).`
|
|
208
|
-
: `JS/TS semantic edit replay requires review: ${reasonCodes.join(', ')}.`
|
|
209
|
-
}],
|
|
210
|
-
metadata: {
|
|
211
|
-
autoMergeClaim: false,
|
|
212
|
-
semanticEquivalenceClaim: false,
|
|
213
|
-
source: input.stagedFallback ? semanticFallbackPhase(input.stagedFallback) : 'js-ts-semantic-edit-fallback',
|
|
214
|
-
originalReasonCodes: input.topLevelResult.admission?.reasonCodes ?? [],
|
|
215
|
-
stagedTopLevelSummary: input.stagedFallback?.stagedTopLevelResult?.summary,
|
|
216
|
-
neutralization: input.stagedFallback?.neutralization?.summary
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
return { ...core, hash: hashSemanticValue(core) };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function semanticEditArtifactReasonCodes(input) {
|
|
223
|
-
const scriptReady = input.script.admission.status === 'auto-merge-candidate';
|
|
224
|
-
const projectionReady = input.projection.status === 'projected';
|
|
225
|
-
const replayReady = input.replay.status === 'accepted-clean';
|
|
226
|
-
const alreadyAppliedReady = input.alreadyAppliedReplay.status === 'already-applied';
|
|
227
|
-
return uniqueStrings([
|
|
228
|
-
scriptReady ? undefined : `semantic-edit-script-${input.script.admission.status}`,
|
|
229
|
-
projectionReady ? undefined : `semantic-edit-projection-${input.projection.status}`,
|
|
230
|
-
replayReady ? undefined : `semantic-edit-replay-${input.replay.status}`,
|
|
231
|
-
input.replay.outputSourceText !== input.projection.sourceText ? 'semantic-edit-replay-output-mismatch' : undefined,
|
|
232
|
-
alreadyAppliedReady ? undefined : `semantic-edit-already-applied-${input.alreadyAppliedReplay.status}`,
|
|
233
|
-
...(scriptReady ? [] : input.script.admission.reasonCodes),
|
|
234
|
-
...(projectionReady ? [] : input.projection.admission.reasonCodes),
|
|
235
|
-
...(replayReady ? [] : input.replay.admission.reasonCodes),
|
|
236
|
-
...(alreadyAppliedReady ? [] : input.alreadyAppliedReplay.admission.reasonCodes)
|
|
237
|
-
]);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function blockedSemanticEditArtifacts(input, topLevelResult, reasonCodes, error) {
|
|
241
|
-
const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
|
|
242
|
-
const core = {
|
|
243
|
-
kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
|
|
244
|
-
version: 1,
|
|
245
|
-
schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
|
|
246
|
-
id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(id)}`,
|
|
247
|
-
sourcePath: input.sourcePath ?? topLevelResult.sourcePath,
|
|
248
|
-
language: input.language ?? topLevelResult.language ?? 'typescript',
|
|
249
|
-
status: 'blocked',
|
|
250
|
-
admission: {
|
|
251
|
-
status: 'blocked',
|
|
252
|
-
action: 'human-review',
|
|
253
|
-
reviewRequired: true,
|
|
254
|
-
autoApplyCandidate: false,
|
|
255
|
-
autoMergeClaim: false,
|
|
256
|
-
semanticEquivalenceClaim: false,
|
|
257
|
-
reasonCodes
|
|
258
|
-
},
|
|
259
|
-
summary: {
|
|
260
|
-
operations: 0,
|
|
261
|
-
edits: 0,
|
|
262
|
-
replayStatus: 'blocked',
|
|
263
|
-
alreadyAppliedReplayStatus: 'blocked',
|
|
264
|
-
projectedSourceMatchesMerged: false,
|
|
265
|
-
replayOutputMatchesMerged: false
|
|
266
|
-
},
|
|
267
|
-
metadata: {
|
|
268
|
-
source: 'js-ts-semantic-edit-fallback',
|
|
269
|
-
error: error?.message
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
return { ...core, hash: hashSemanticValue(core) };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
180
|
function semanticEditFallbackBlockedResult(input, topLevelResult, artifacts) {
|
|
276
181
|
const reasonCodes = artifacts.admission.reasonCodes.length
|
|
277
182
|
? artifacts.admission.reasonCodes
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { JsTsSafeMergeConflictCodes } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
import { createMergeContext, sameStatementText } from './js-ts-safe-merge-context.js';
|
|
3
|
+
import { scanJsTsTopLevelLedger, validateLedgerUniqueness } from './js-ts-safe-merge-ledger.js';
|
|
4
|
+
import { uniqueStrings } from './native-import-utils.js';
|
|
5
|
+
|
|
6
|
+
const supportedRenameDeclarationKinds = new Set(['function', 'class', 'type']);
|
|
7
|
+
|
|
8
|
+
function analyzeTopLevelRenameAdmission(input, topLevelResult) {
|
|
9
|
+
const originalReasonCodes = topLevelResult?.admission?.reasonCodes ?? [];
|
|
10
|
+
if (!originalReasonCodes.includes(JsTsSafeMergeConflictCodes.topLevelOrderChanged)) return undefined;
|
|
11
|
+
if (typeof input.baseSourceText !== 'string'
|
|
12
|
+
|| typeof input.workerSourceText !== 'string'
|
|
13
|
+
|| typeof input.headSourceText !== 'string') {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const context = createMergeContext(input);
|
|
18
|
+
const base = scanJsTsTopLevelLedger(input.baseSourceText, 'base', context);
|
|
19
|
+
const worker = scanJsTsTopLevelLedger(input.workerSourceText, 'worker', context);
|
|
20
|
+
const head = scanJsTsTopLevelLedger(input.headSourceText, 'head', context);
|
|
21
|
+
if (!context.conflicts.length) {
|
|
22
|
+
validateLedgerUniqueness(base, context);
|
|
23
|
+
validateLedgerUniqueness(worker, context);
|
|
24
|
+
validateLedgerUniqueness(head, context);
|
|
25
|
+
}
|
|
26
|
+
if (context.conflicts.length) return undefined;
|
|
27
|
+
|
|
28
|
+
const candidate = topLevelRenameCandidate(base, worker, head);
|
|
29
|
+
if (!candidate) return undefined;
|
|
30
|
+
|
|
31
|
+
const publicContractReasonCodes = publicContractRenameReasonCodes(base, worker, head, candidate);
|
|
32
|
+
if (publicContractReasonCodes.length) {
|
|
33
|
+
return {
|
|
34
|
+
status: 'blocked',
|
|
35
|
+
reasonCodes: publicContractReasonCodes,
|
|
36
|
+
summary: candidateSummary(candidate, publicContractReasonCodes),
|
|
37
|
+
ledgers: { base, worker, head }
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
status: 'candidate',
|
|
43
|
+
reasonCodes: ['top-level-rename-source-shape-matches'],
|
|
44
|
+
summary: candidateSummary(candidate, ['top-level-rename-source-shape-matches']),
|
|
45
|
+
ledgers: { base, worker, head }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function topLevelRenameCandidate(base, worker, head) {
|
|
50
|
+
const baseByKey = entriesByKey(base.entries);
|
|
51
|
+
const workerByKey = entriesByKey(worker.entries);
|
|
52
|
+
const baseKeys = base.entries.map((entry) => entry.key);
|
|
53
|
+
const missingBaseDeclarations = base.entries
|
|
54
|
+
.filter((entry) => !workerByKey.has(entry.key))
|
|
55
|
+
.filter(isSupportedRenameDeclaration);
|
|
56
|
+
const addedWorkerDeclarations = worker.entries
|
|
57
|
+
.filter((entry) => !baseByKey.has(entry.key))
|
|
58
|
+
.filter(isSupportedRenameDeclaration);
|
|
59
|
+
|
|
60
|
+
if (missingBaseDeclarations.length !== 1 || addedWorkerDeclarations.length !== 1) return undefined;
|
|
61
|
+
const fromEntry = missingBaseDeclarations[0];
|
|
62
|
+
const toEntry = addedWorkerDeclarations[0];
|
|
63
|
+
const fromName = fromEntry.names?.[0];
|
|
64
|
+
const toName = toEntry.names?.[0];
|
|
65
|
+
if (!fromName || !toName || fromName === toName) return undefined;
|
|
66
|
+
if (fromEntry.declarationInfo?.declarationKind !== toEntry.declarationInfo?.declarationKind) return undefined;
|
|
67
|
+
if (!sameStatementText(renameDeclarationText(fromEntry.text, fromEntry.declarationInfo.declarationKind, fromName, toName), toEntry.text)) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const workerProjectedBaseKeys = worker.entries.map((entry) => entry.key === toEntry.key ? fromEntry.key : entry.key);
|
|
72
|
+
if (!sameStringList(workerProjectedBaseKeys, baseKeys)) return undefined;
|
|
73
|
+
const headProjectedBaseKeys = head.entries
|
|
74
|
+
.filter((entry) => baseByKey.has(entry.key))
|
|
75
|
+
.map((entry) => entry.key);
|
|
76
|
+
if (!sameStringList(headProjectedBaseKeys, baseKeys)) return undefined;
|
|
77
|
+
if (head.entries.length !== base.entries.length) return undefined;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
fromEntry,
|
|
81
|
+
toEntry,
|
|
82
|
+
fromName,
|
|
83
|
+
toName,
|
|
84
|
+
declarationKind: fromEntry.declarationInfo.declarationKind
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isSupportedRenameDeclaration(entry) {
|
|
89
|
+
return entry?.kind === 'declaration'
|
|
90
|
+
&& entry.names?.length === 1
|
|
91
|
+
&& supportedRenameDeclarationKinds.has(entry.declarationInfo?.declarationKind)
|
|
92
|
+
&& !entry.declarationInfo.defaultExport
|
|
93
|
+
&& !/^\s*(?:export\s+)?declare\b/.test(entry.text ?? '');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function publicContractRenameReasonCodes(base, worker, head, candidate) {
|
|
97
|
+
const directExport = candidate.fromEntry.declarationInfo?.exported === true
|
|
98
|
+
|| candidate.toEntry.declarationInfo?.exported === true;
|
|
99
|
+
const exportListMention = [base, worker, head]
|
|
100
|
+
.some((ledger) => ledger.entries
|
|
101
|
+
.some((entry) => entry.kind === 'export' && mentionsName(entry.text, candidate.fromName, candidate.toName)));
|
|
102
|
+
return directExport || exportListMention
|
|
103
|
+
? [JsTsSafeMergeConflictCodes.topLevelRenamePublicExportContract]
|
|
104
|
+
: [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function mentionsName(text, ...names) {
|
|
108
|
+
return names.some((name) => new RegExp(`\\b${escapeRegExp(name)}\\b`).test(text ?? ''));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function renameDeclarationText(text, declarationKind, fromName, toName) {
|
|
112
|
+
if (typeof text !== 'string') return undefined;
|
|
113
|
+
const escaped = escapeRegExp(fromName);
|
|
114
|
+
const replacement = `$1${toName}`;
|
|
115
|
+
if (declarationKind === 'function') {
|
|
116
|
+
return text.replace(new RegExp(`^((?:export\\s+)?(?:async\\s+)?function\\*?\\s+)${escaped}\\b`), replacement);
|
|
117
|
+
}
|
|
118
|
+
if (declarationKind === 'class') {
|
|
119
|
+
return text.replace(new RegExp(`^((?:export\\s+)?(?:abstract\\s+)?class\\s+)${escaped}\\b`), replacement);
|
|
120
|
+
}
|
|
121
|
+
if (declarationKind === 'type') {
|
|
122
|
+
return text.replace(new RegExp(`^((?:export\\s+)?type\\s+)${escaped}\\b`), replacement);
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function candidateSummary(candidate, reasonCodes) {
|
|
128
|
+
return {
|
|
129
|
+
fromName: candidate.fromName,
|
|
130
|
+
toName: candidate.toName,
|
|
131
|
+
declarationKind: candidate.declarationKind,
|
|
132
|
+
exported: candidate.fromEntry.declarationInfo?.exported === true || candidate.toEntry.declarationInfo?.exported === true,
|
|
133
|
+
reasonCodes: uniqueStrings(reasonCodes)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function entriesByKey(entries) {
|
|
138
|
+
return new Map(entries.map((entry) => [entry.key, entry]));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function sameStringList(left, right) {
|
|
142
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function escapeRegExp(value) {
|
|
146
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { analyzeTopLevelRenameAdmission };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
import { uniqueStrings } from './native-import-utils.js';
|
|
3
|
+
|
|
4
|
+
function topLevelRenameBlockedResult(input, topLevelResult, topLevelRenameAdmission) {
|
|
5
|
+
const reasonCodes = uniqueStrings([
|
|
6
|
+
...(topLevelResult.admission?.reasonCodes ?? []),
|
|
7
|
+
...(topLevelRenameAdmission.reasonCodes ?? [])
|
|
8
|
+
]);
|
|
9
|
+
const conflict = {
|
|
10
|
+
code: JsTsSafeMergeConflictCodes.topLevelRenamePublicExportContract,
|
|
11
|
+
gateId: JsTsSafeMergeGateIds.stableExistingDeclarations,
|
|
12
|
+
message: 'Top-level rename changes a public export contract without project-level evidence.',
|
|
13
|
+
side: 'worker',
|
|
14
|
+
sourcePath: input.sourcePath ?? topLevelResult.sourcePath,
|
|
15
|
+
details: {
|
|
16
|
+
...topLevelRenameAdmission.summary,
|
|
17
|
+
reasonCodes: topLevelRenameAdmission.reasonCodes
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
...topLevelResult,
|
|
22
|
+
conflicts: [...(topLevelResult.conflicts ?? []), conflict],
|
|
23
|
+
admission: {
|
|
24
|
+
status: 'blocked',
|
|
25
|
+
action: 'human-review',
|
|
26
|
+
reviewRequired: true,
|
|
27
|
+
autoApplyCandidate: false,
|
|
28
|
+
autoMergeClaim: false,
|
|
29
|
+
semanticEquivalenceClaim: false,
|
|
30
|
+
reasonCodes
|
|
31
|
+
},
|
|
32
|
+
summary: {
|
|
33
|
+
...topLevelResult.summary,
|
|
34
|
+
conflicts: (topLevelResult.conflicts?.length ?? 0) + 1,
|
|
35
|
+
topLevelDeclarationRenames: 1
|
|
36
|
+
},
|
|
37
|
+
metadata: {
|
|
38
|
+
...topLevelResult.metadata,
|
|
39
|
+
topLevelRenameAdmission: topLevelRenameAdmission.summary
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { topLevelRenameBlockedResult };
|
|
@@ -6,6 +6,7 @@ function outputProjectGraphConflicts(projectSymbolGraph) {
|
|
|
6
6
|
const limitConflicts = Array.isArray(projectSymbolGraph?.limitConflicts) ? projectSymbolGraph.limitConflicts : [];
|
|
7
7
|
projectSymbolGraph = projectSymbolGraph?.projectSymbolGraph ?? projectSymbolGraph;
|
|
8
8
|
const importEdges = Array.isArray(projectSymbolGraph?.importEdges) ? projectSymbolGraph.importEdges : [];
|
|
9
|
+
const exportEdges = Array.isArray(projectSymbolGraph?.exportEdges) ? projectSymbolGraph.exportEdges : [];
|
|
9
10
|
const missingModuleGroups = new Map();
|
|
10
11
|
const missingSymbolGroups = new Map();
|
|
11
12
|
for (const edge of importEdges) {
|
|
@@ -16,7 +17,7 @@ function outputProjectGraphConflicts(projectSymbolGraph) {
|
|
|
16
17
|
missingModuleGroups.set(key, group);
|
|
17
18
|
continue;
|
|
18
19
|
}
|
|
19
|
-
if (isMissingProjectImportTargetEdge(edge)) {
|
|
20
|
+
if (isMissingProjectImportTargetEdge(edge, exportEdges)) {
|
|
20
21
|
const key = [edge.sourcePath, edge.moduleSpecifier, projectImportTargetName(edge), edge.resolvedModulePath].join('\u0000');
|
|
21
22
|
const group = missingSymbolGroups.get(key) ?? [];
|
|
22
23
|
group.push(edge);
|
|
@@ -81,8 +82,11 @@ function isMissingProjectImportEdge(edge) {
|
|
|
81
82
|
return typeof edge?.resolutionKind === 'string' && edge.resolutionKind.endsWith('-missing');
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
function isMissingProjectImportTargetEdge(edge) {
|
|
85
|
-
return hasResolvedProjectModule(edge)
|
|
85
|
+
function isMissingProjectImportTargetEdge(edge, exportEdges = []) {
|
|
86
|
+
return hasResolvedProjectModule(edge)
|
|
87
|
+
&& Boolean(projectImportTargetName(edge))
|
|
88
|
+
&& !edge.resolvedTargetSymbolId
|
|
89
|
+
&& !commonJsRequireResolvedByExportAssignment(edge, exportEdges);
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
function hasResolvedProjectModule(edge) {
|
|
@@ -96,6 +100,23 @@ function projectImportTargetName(edge) {
|
|
|
96
100
|
return String(name);
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
function commonJsRequireResolvedByExportAssignment(edge, exportEdges) {
|
|
104
|
+
if (edge?.importKind !== 'commonjs-require' || projectImportTargetName(edge) !== 'default') return false;
|
|
105
|
+
return exportEdges.some((exportEdge) => exportEdge?.exportKind === 'assignment'
|
|
106
|
+
&& exportEdge.exportedName === 'module.exports'
|
|
107
|
+
&& sameProjectDocument(edge, exportEdge));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function sameProjectDocument(importEdge, exportEdge) {
|
|
111
|
+
if (importEdge?.targetDocumentId && exportEdge?.sourceDocumentId) {
|
|
112
|
+
return importEdge.targetDocumentId === exportEdge.sourceDocumentId;
|
|
113
|
+
}
|
|
114
|
+
if (importEdge?.resolvedModulePath && exportEdge?.sourcePath) {
|
|
115
|
+
return importEdge.resolvedModulePath === exportEdge.sourcePath;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
function duplicateReExportIdentityConflicts(records = []) {
|
|
100
121
|
const groups = new Map();
|
|
101
122
|
for (const record of records) {
|
package/package.json
CHANGED