@shapeshift-labs/frontier-lang-compiler 0.2.135 → 0.2.137
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-plan.js +12 -2
- package/dist/js-ts-safe-merge-semantic-edit-fallback.js +63 -37
- package/dist/js-ts-safe-merge-staged-declaration-replay.js +200 -0
- package/dist/js-ts-safe-merge-staged-top-level-fallback.js +52 -0
- package/dist/js-ts-safe-merge-top-level-neutralization.js +190 -0
- package/package.json +1 -1
|
@@ -47,7 +47,9 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
47
47
|
label: 'import'
|
|
48
48
|
});
|
|
49
49
|
const headImportGroupsByAnchor = new Map(headImportInsertionGroups.map((group) => [insertionGroupKey(group), group]));
|
|
50
|
+
const countedImportInsertionGroups = new Set();
|
|
50
51
|
for (const group of importInsertionGroups) {
|
|
52
|
+
countedImportInsertionGroups.add(insertionGroupKey(group));
|
|
51
53
|
const anchor = headEntriesByBaseKey.get(group.anchorKey);
|
|
52
54
|
if (!anchor) {
|
|
53
55
|
addConflict(context, {
|
|
@@ -68,7 +70,10 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
68
70
|
end: insertionSpan.end,
|
|
69
71
|
text: importInsertionText(entries, detectLineEnding(head.sourceText))
|
|
70
72
|
});
|
|
71
|
-
importDeclarationAdditions +=
|
|
73
|
+
importDeclarationAdditions += entries.length;
|
|
74
|
+
}
|
|
75
|
+
for (const group of headImportInsertionGroups) {
|
|
76
|
+
if (!countedImportInsertionGroups.has(insertionGroupKey(group))) importDeclarationAdditions += group.entries.length;
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
const insertionGroups = variantInsertionGroups(worker, workerPlan.matchedVariantKeys, 'worker', context, {
|
|
@@ -83,7 +88,9 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
83
88
|
});
|
|
84
89
|
const headDeclarationGroupsByAnchor = new Map(headInsertionGroups.map((group) => [insertionGroupKey(group), group]));
|
|
85
90
|
let topLevelDeclarationAdditions = 0;
|
|
91
|
+
const countedDeclarationInsertionGroups = new Set();
|
|
86
92
|
for (const group of insertionGroups) {
|
|
93
|
+
countedDeclarationInsertionGroups.add(insertionGroupKey(group));
|
|
87
94
|
const anchor = headEntriesByBaseKey.get(group.anchorKey);
|
|
88
95
|
if (!anchor) {
|
|
89
96
|
addConflict(context, {
|
|
@@ -104,7 +111,10 @@ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan,
|
|
|
104
111
|
end: insertionSpan.end,
|
|
105
112
|
text: declarationInsertionText(entries, detectLineEnding(head.sourceText))
|
|
106
113
|
});
|
|
107
|
-
topLevelDeclarationAdditions +=
|
|
114
|
+
topLevelDeclarationAdditions += entries.length;
|
|
115
|
+
}
|
|
116
|
+
for (const group of headInsertionGroups) {
|
|
117
|
+
if (!countedDeclarationInsertionGroups.has(insertionGroupKey(group))) topLevelDeclarationAdditions += group.entries.length;
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
return {
|
|
@@ -3,16 +3,25 @@ import { createSemanticEditScript } from './internal/index-impl/semanticEditScri
|
|
|
3
3
|
import { projectSemanticEditScriptToSource } from './internal/index-impl/projectSemanticEditScriptToSource.js';
|
|
4
4
|
import { replaySemanticEditProjection } from './internal/index-impl/replaySemanticEditProjection.js';
|
|
5
5
|
import { JsTsSafeMergeConflictCodes, JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
|
|
6
|
+
import {
|
|
7
|
+
createStagedDeclarationAlreadyAppliedReplay,
|
|
8
|
+
createStagedDeclarationProjection,
|
|
9
|
+
createStagedDeclarationReplayRecord
|
|
10
|
+
} from './js-ts-safe-merge-staged-declaration-replay.js';
|
|
11
|
+
import { createStagedTopLevelSemanticFallback } from './js-ts-safe-merge-staged-top-level-fallback.js';
|
|
6
12
|
import { idFragment, uniqueStrings } from './native-import-utils.js';
|
|
7
13
|
|
|
8
14
|
function semanticEditFallbackResult(input, topLevelResult) {
|
|
9
15
|
if (!shouldTrySemanticEditFallback(topLevelResult)) return topLevelResult;
|
|
10
|
-
const
|
|
16
|
+
const stagedFallback = createStagedTopLevelSemanticFallback(input, topLevelResult);
|
|
17
|
+
const artifacts = createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallback);
|
|
11
18
|
if (artifacts.status !== 'verified') return semanticEditFallbackBlockedResult(input, topLevelResult, artifacts);
|
|
19
|
+
const resultBase = stagedFallback?.stagedTopLevelResult ?? topLevelResult;
|
|
12
20
|
const mergedSourceText = artifacts.projection.sourceText;
|
|
13
21
|
const gates = semanticEditGates(artifacts);
|
|
14
22
|
return {
|
|
15
|
-
...
|
|
23
|
+
...resultBase,
|
|
24
|
+
id: String(input.id ?? resultBase.id ?? topLevelResult.id),
|
|
16
25
|
status: JsTsSafeMergeStatuses.merged,
|
|
17
26
|
mergedSourceText,
|
|
18
27
|
outputSourceText: mergedSourceText,
|
|
@@ -28,7 +37,8 @@ function semanticEditFallbackResult(input, topLevelResult) {
|
|
|
28
37
|
reasonCodes: []
|
|
29
38
|
},
|
|
30
39
|
summary: {
|
|
31
|
-
...
|
|
40
|
+
...resultBase.summary,
|
|
41
|
+
changedExistingDeclarations: topLevelResult.summary?.changedExistingDeclarations ?? resultBase.summary?.changedExistingDeclarations ?? 0,
|
|
32
42
|
conflicts: 0,
|
|
33
43
|
gatesPassed: gates.filter((gate) => gate.status === 'passed').length,
|
|
34
44
|
semanticEditOperations: artifacts.script.summary.operations,
|
|
@@ -37,11 +47,13 @@ function semanticEditFallbackResult(input, topLevelResult) {
|
|
|
37
47
|
composedPhases: 2
|
|
38
48
|
},
|
|
39
49
|
metadata: {
|
|
40
|
-
...
|
|
50
|
+
...resultBase.metadata,
|
|
41
51
|
composed: {
|
|
42
|
-
phase: 'semantic-edit-fallback',
|
|
43
|
-
phases: ['top-level-ledger', 'semantic-edit'],
|
|
44
|
-
originalReasonCodes: topLevelResult.admission?.reasonCodes ?? []
|
|
52
|
+
phase: stagedFallback ? 'staged-top-level-semantic-edit-fallback' : 'semantic-edit-fallback',
|
|
53
|
+
phases: stagedFallback ? ['top-level-neutralization', 'top-level-ledger', 'semantic-edit'] : ['top-level-ledger', 'semantic-edit'],
|
|
54
|
+
originalReasonCodes: topLevelResult.admission?.reasonCodes ?? [],
|
|
55
|
+
stagedTopLevelSummary: stagedFallback?.stagedTopLevelResult?.summary,
|
|
56
|
+
neutralization: stagedFallback?.neutralization?.summary
|
|
45
57
|
}
|
|
46
58
|
},
|
|
47
59
|
semanticArtifacts: artifacts
|
|
@@ -53,42 +65,53 @@ function shouldTrySemanticEditFallback(result) {
|
|
|
53
65
|
return conflicts.length > 0 && conflicts.every((conflict) => conflict.code === JsTsSafeMergeConflictCodes.changedExistingDeclaration);
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
function createSemanticEditFallbackArtifacts(input, topLevelResult) {
|
|
68
|
+
function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallback) {
|
|
57
69
|
try {
|
|
58
70
|
const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
|
|
59
71
|
const language = input.language ?? topLevelResult.language ?? 'typescript';
|
|
60
72
|
const sourcePath = input.sourcePath ?? topLevelResult.sourcePath ?? 'inline.ts';
|
|
73
|
+
const scriptInput = stagedFallback?.scriptInput ?? input;
|
|
74
|
+
const projectionHeadSourceText = stagedFallback?.projectionHeadSourceText ?? input.headSourceText;
|
|
75
|
+
const replayCurrentSourceText = stagedFallback?.replayCurrentSourceText ?? input.headSourceText;
|
|
61
76
|
const script = createSemanticEditScript({
|
|
62
|
-
...
|
|
77
|
+
...scriptInput,
|
|
63
78
|
id: `${id}_semantic_edit`,
|
|
64
79
|
language,
|
|
65
80
|
sourcePath
|
|
66
81
|
});
|
|
67
|
-
const projection =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
const projection = stagedFallback
|
|
83
|
+
? createStagedDeclarationProjection({ id, script, sourcePath, language, stagedFallback })
|
|
84
|
+
: projectSemanticEditScriptToSource({
|
|
85
|
+
id: `${id}_semantic_edit_projection`,
|
|
86
|
+
script,
|
|
87
|
+
workerSourceText: scriptInput.workerSourceText,
|
|
88
|
+
headSourceText: projectionHeadSourceText,
|
|
89
|
+
headSourcePath: sourcePath,
|
|
90
|
+
parser: input.parser,
|
|
91
|
+
metadata: stagedFallback?.metadata
|
|
92
|
+
});
|
|
93
|
+
const replay = stagedFallback
|
|
94
|
+
? createStagedDeclarationReplayRecord({ id, projection, sourcePath, language, stagedFallback, replayCurrentSourceText })
|
|
95
|
+
: replaySemanticEditProjection({
|
|
96
|
+
id: `${id}_semantic_edit_replay`,
|
|
97
|
+
projection,
|
|
98
|
+
currentSourceText: replayCurrentSourceText,
|
|
99
|
+
currentSourcePath: sourcePath,
|
|
100
|
+
language,
|
|
101
|
+
parser: input.parser,
|
|
102
|
+
metadata: stagedFallback?.metadata
|
|
103
|
+
});
|
|
104
|
+
const alreadyAppliedReplay = stagedFallback
|
|
105
|
+
? createStagedDeclarationAlreadyAppliedReplay({ id, projection, sourcePath, language })
|
|
106
|
+
: replaySemanticEditProjection({
|
|
107
|
+
id: `${id}_semantic_edit_already_applied`,
|
|
108
|
+
projection,
|
|
109
|
+
currentSourceText: projection.sourceText,
|
|
110
|
+
currentSourcePath: sourcePath,
|
|
111
|
+
currentSourceHash: projection.projectedHash,
|
|
112
|
+
language,
|
|
113
|
+
parser: input.parser
|
|
114
|
+
});
|
|
92
115
|
return semanticEditArtifacts({
|
|
93
116
|
id,
|
|
94
117
|
language,
|
|
@@ -97,7 +120,8 @@ function createSemanticEditFallbackArtifacts(input, topLevelResult) {
|
|
|
97
120
|
projection,
|
|
98
121
|
replay,
|
|
99
122
|
alreadyAppliedReplay,
|
|
100
|
-
topLevelResult
|
|
123
|
+
topLevelResult,
|
|
124
|
+
stagedFallback
|
|
101
125
|
});
|
|
102
126
|
} catch (error) {
|
|
103
127
|
return blockedSemanticEditArtifacts(input, topLevelResult, ['semantic-edit-fallback-error'], error);
|
|
@@ -148,8 +172,10 @@ function semanticEditArtifacts(input) {
|
|
|
148
172
|
metadata: {
|
|
149
173
|
autoMergeClaim: false,
|
|
150
174
|
semanticEquivalenceClaim: false,
|
|
151
|
-
source: 'js-ts-semantic-edit-fallback',
|
|
152
|
-
originalReasonCodes: input.topLevelResult.admission?.reasonCodes ?? []
|
|
175
|
+
source: input.stagedFallback ? 'js-ts-staged-top-level-semantic-edit-fallback' : 'js-ts-semantic-edit-fallback',
|
|
176
|
+
originalReasonCodes: input.topLevelResult.admission?.reasonCodes ?? [],
|
|
177
|
+
stagedTopLevelSummary: input.stagedFallback?.stagedTopLevelResult?.summary,
|
|
178
|
+
neutralization: input.stagedFallback?.neutralization?.summary
|
|
153
179
|
}
|
|
154
180
|
};
|
|
155
181
|
return { ...core, hash: hashSemanticValue(core) };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { idFragment, uniqueStrings } from './native-import-utils.js';
|
|
3
|
+
|
|
4
|
+
function createStagedDeclarationProjection(input) {
|
|
5
|
+
const replay = input.stagedFallback.declarationReplay;
|
|
6
|
+
const reasonCodes = uniqueStrings(replay.reasonCodes);
|
|
7
|
+
const blocked = reasonCodes.length > 0;
|
|
8
|
+
const sourceText = blocked ? undefined : replay.outputSourceText;
|
|
9
|
+
const edits = blocked ? [] : replay.edits.map((edit, index) => stagedDeclarationProjectionEdit(edit, index, input));
|
|
10
|
+
const core = {
|
|
11
|
+
kind: 'frontier.lang.semanticEditProjection',
|
|
12
|
+
version: 1,
|
|
13
|
+
id: `${input.id}_semantic_edit_projection`,
|
|
14
|
+
scriptId: input.script.id,
|
|
15
|
+
status: blocked ? 'blocked' : 'projected',
|
|
16
|
+
sourcePath: input.sourcePath,
|
|
17
|
+
language: input.language,
|
|
18
|
+
baseHash: input.script.baseHash,
|
|
19
|
+
workerHash: input.script.workerHash,
|
|
20
|
+
headHash: input.script.headHash,
|
|
21
|
+
projectedHash: sourceText === undefined ? undefined : hashSemanticValue(sourceText),
|
|
22
|
+
appliedOperations: edits.map((edit) => edit.operationId),
|
|
23
|
+
skippedOperations: blocked ? (input.script.operations ?? []).map((operation) => operation.id) : [],
|
|
24
|
+
edits,
|
|
25
|
+
sourceText,
|
|
26
|
+
admission: {
|
|
27
|
+
status: blocked ? 'blocked' : 'auto-merge-candidate',
|
|
28
|
+
autoMergeClaim: false,
|
|
29
|
+
semanticEquivalenceClaim: false,
|
|
30
|
+
reasonCodes
|
|
31
|
+
},
|
|
32
|
+
metadata: {
|
|
33
|
+
autoMergeClaim: false,
|
|
34
|
+
semanticEquivalenceClaim: false,
|
|
35
|
+
source: 'js-ts-staged-declaration-replay',
|
|
36
|
+
...input.stagedFallback.metadata
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function stagedDeclarationProjectionEdit(edit, index, input) {
|
|
43
|
+
const operationId = `staged_declaration_replay_${idFragment([input.id, edit.key, index].join(':'))}`;
|
|
44
|
+
const replacementText = edit.replacementText;
|
|
45
|
+
const deletedText = edit.currentText;
|
|
46
|
+
return {
|
|
47
|
+
operationId,
|
|
48
|
+
status: 'applied',
|
|
49
|
+
kind: 'replaceDeclaration',
|
|
50
|
+
editKind: 'replace',
|
|
51
|
+
changeKind: 'modified',
|
|
52
|
+
anchorKey: edit.key,
|
|
53
|
+
conflictKey: `declaration:${edit.key}`,
|
|
54
|
+
regionKind: 'declaration',
|
|
55
|
+
sourcePath: input.sourcePath,
|
|
56
|
+
symbolName: edit.names?.[0],
|
|
57
|
+
symbolKind: edit.declarationKind,
|
|
58
|
+
editContentHash: hashSemanticValue({ operationId, replacementText, deletedText }),
|
|
59
|
+
headStart: edit.start,
|
|
60
|
+
headEnd: edit.end,
|
|
61
|
+
editOrder: index,
|
|
62
|
+
deletedBytes: deletedText.length,
|
|
63
|
+
replacementBytes: replacementText.length,
|
|
64
|
+
deletedTextHash: hashSemanticValue(deletedText),
|
|
65
|
+
replacementTextHash: hashSemanticValue(replacementText),
|
|
66
|
+
replacementText
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createStagedDeclarationReplayRecord(input) {
|
|
71
|
+
const projectionReady = input.projection.status === 'projected';
|
|
72
|
+
const outputSourceText = projectionReady ? input.projection.sourceText : undefined;
|
|
73
|
+
const edits = projectionReady ? input.projection.edits.map((edit) => ({
|
|
74
|
+
operationId: edit.operationId,
|
|
75
|
+
editKind: edit.editKind,
|
|
76
|
+
editOrder: edit.editOrder,
|
|
77
|
+
sourcePath: edit.sourcePath,
|
|
78
|
+
symbolName: edit.symbolName,
|
|
79
|
+
symbolKind: edit.symbolKind,
|
|
80
|
+
status: 'applied',
|
|
81
|
+
start: edit.headStart,
|
|
82
|
+
end: edit.headEnd,
|
|
83
|
+
replacementBytes: edit.replacementBytes,
|
|
84
|
+
replacementText: edit.replacementText,
|
|
85
|
+
reasonCodes: ['staged-declaration-replay']
|
|
86
|
+
})) : [];
|
|
87
|
+
const status = projectionReady ? 'accepted-clean' : 'blocked';
|
|
88
|
+
const reasonCodes = projectionReady ? [] : input.projection.admission.reasonCodes;
|
|
89
|
+
const core = {
|
|
90
|
+
kind: 'frontier.lang.semanticEditReplay',
|
|
91
|
+
version: 1,
|
|
92
|
+
schema: 'frontier.lang.semanticEditReplay.v1',
|
|
93
|
+
id: `${input.id}_semantic_edit_replay`,
|
|
94
|
+
projectionId: input.projection.id,
|
|
95
|
+
scriptId: input.projection.scriptId,
|
|
96
|
+
sourcePath: input.sourcePath,
|
|
97
|
+
language: input.language,
|
|
98
|
+
currentHash: hashSemanticValue(input.replayCurrentSourceText),
|
|
99
|
+
projectedHash: input.projection.projectedHash,
|
|
100
|
+
outputHash: outputSourceText === undefined ? undefined : hashSemanticValue(outputSourceText),
|
|
101
|
+
status,
|
|
102
|
+
edits,
|
|
103
|
+
appliedOperations: edits.map((edit) => edit.operationId),
|
|
104
|
+
skippedOperations: [],
|
|
105
|
+
admission: {
|
|
106
|
+
status,
|
|
107
|
+
action: projectionReady ? 'apply' : 'block',
|
|
108
|
+
reviewRequired: !projectionReady,
|
|
109
|
+
autoApplyCandidate: projectionReady,
|
|
110
|
+
autoMergeClaim: false,
|
|
111
|
+
semanticEquivalenceClaim: false,
|
|
112
|
+
reasonCodes
|
|
113
|
+
},
|
|
114
|
+
outputSourceText,
|
|
115
|
+
summary: {
|
|
116
|
+
edits: edits.length,
|
|
117
|
+
applied: edits.length,
|
|
118
|
+
alreadyApplied: 0,
|
|
119
|
+
conflicts: 0,
|
|
120
|
+
stale: 0,
|
|
121
|
+
blocked: projectionReady ? 0 : 1,
|
|
122
|
+
reasonCodes
|
|
123
|
+
},
|
|
124
|
+
metadata: {
|
|
125
|
+
autoMergeClaim: false,
|
|
126
|
+
semanticEquivalenceClaim: false,
|
|
127
|
+
source: 'js-ts-staged-declaration-replay',
|
|
128
|
+
...input.stagedFallback.metadata
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function createStagedDeclarationAlreadyAppliedReplay(input) {
|
|
135
|
+
const projectionReady = input.projection.status === 'projected';
|
|
136
|
+
const edits = projectionReady ? input.projection.edits.map((edit) => ({
|
|
137
|
+
operationId: edit.operationId,
|
|
138
|
+
editKind: edit.editKind,
|
|
139
|
+
editOrder: edit.editOrder,
|
|
140
|
+
sourcePath: edit.sourcePath,
|
|
141
|
+
symbolName: edit.symbolName,
|
|
142
|
+
symbolKind: edit.symbolKind,
|
|
143
|
+
status: 'already-applied',
|
|
144
|
+
start: edit.headStart,
|
|
145
|
+
end: edit.headStart + edit.replacementBytes,
|
|
146
|
+
replacementBytes: edit.replacementBytes,
|
|
147
|
+
replacementText: edit.replacementText,
|
|
148
|
+
reasonCodes: ['staged-declaration-already-applied']
|
|
149
|
+
})) : [];
|
|
150
|
+
const status = projectionReady ? 'already-applied' : 'blocked';
|
|
151
|
+
const reasonCodes = projectionReady ? [] : input.projection.admission.reasonCodes;
|
|
152
|
+
const core = {
|
|
153
|
+
kind: 'frontier.lang.semanticEditReplay',
|
|
154
|
+
version: 1,
|
|
155
|
+
schema: 'frontier.lang.semanticEditReplay.v1',
|
|
156
|
+
id: `${input.id}_semantic_edit_already_applied`,
|
|
157
|
+
projectionId: input.projection.id,
|
|
158
|
+
scriptId: input.projection.scriptId,
|
|
159
|
+
sourcePath: input.sourcePath,
|
|
160
|
+
language: input.language,
|
|
161
|
+
currentHash: input.projection.projectedHash,
|
|
162
|
+
projectedHash: input.projection.projectedHash,
|
|
163
|
+
outputHash: input.projection.projectedHash,
|
|
164
|
+
status,
|
|
165
|
+
edits,
|
|
166
|
+
appliedOperations: [],
|
|
167
|
+
skippedOperations: edits.map((edit) => edit.operationId),
|
|
168
|
+
admission: {
|
|
169
|
+
status,
|
|
170
|
+
action: projectionReady ? 'skip' : 'block',
|
|
171
|
+
reviewRequired: !projectionReady,
|
|
172
|
+
autoApplyCandidate: false,
|
|
173
|
+
autoMergeClaim: false,
|
|
174
|
+
semanticEquivalenceClaim: false,
|
|
175
|
+
reasonCodes
|
|
176
|
+
},
|
|
177
|
+
outputSourceText: input.projection.sourceText,
|
|
178
|
+
summary: {
|
|
179
|
+
edits: edits.length,
|
|
180
|
+
applied: 0,
|
|
181
|
+
alreadyApplied: edits.length,
|
|
182
|
+
conflicts: 0,
|
|
183
|
+
stale: 0,
|
|
184
|
+
blocked: projectionReady ? 0 : 1,
|
|
185
|
+
reasonCodes
|
|
186
|
+
},
|
|
187
|
+
metadata: {
|
|
188
|
+
autoMergeClaim: false,
|
|
189
|
+
semanticEquivalenceClaim: false,
|
|
190
|
+
source: 'js-ts-staged-declaration-replay'
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export {
|
|
197
|
+
createStagedDeclarationAlreadyAppliedReplay,
|
|
198
|
+
createStagedDeclarationProjection,
|
|
199
|
+
createStagedDeclarationReplayRecord
|
|
200
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { safeMergeJsTsImportsAndDeclarations } from './js-ts-safe-merge.js';
|
|
2
|
+
import { JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
|
|
3
|
+
import { createJsTsChangedDeclarationReplay, neutralizeJsTsSafeTopLevelMergeSources } from './js-ts-safe-merge-top-level-neutralization.js';
|
|
4
|
+
|
|
5
|
+
function createStagedTopLevelSemanticFallback(input, topLevelResult) {
|
|
6
|
+
const neutralization = neutralizeJsTsSafeTopLevelMergeSources(input);
|
|
7
|
+
if (!neutralization.ok) return undefined;
|
|
8
|
+
const stagedTopLevelResult = safeMergeJsTsImportsAndDeclarations({
|
|
9
|
+
...input,
|
|
10
|
+
baseSourceText: neutralization.baseSourceText,
|
|
11
|
+
workerSourceText: neutralization.topLevelWorkerSourceText,
|
|
12
|
+
headSourceText: neutralization.topLevelHeadSourceText
|
|
13
|
+
});
|
|
14
|
+
if (stagedTopLevelResult.status !== JsTsSafeMergeStatuses.merged) return undefined;
|
|
15
|
+
const safeTopLevelChanges = safeTopLevelChangeCount(stagedTopLevelResult.summary);
|
|
16
|
+
const declarationReplay = createJsTsChangedDeclarationReplay(input, neutralization, stagedTopLevelResult.mergedSourceText);
|
|
17
|
+
const workerDeclarationChanges = neutralization.summary.workerChangedExistingDeclarations ?? 0;
|
|
18
|
+
if (safeTopLevelChanges === 0 && workerDeclarationChanges === 0) return undefined;
|
|
19
|
+
return {
|
|
20
|
+
neutralization,
|
|
21
|
+
declarationReplay,
|
|
22
|
+
stagedTopLevelResult,
|
|
23
|
+
scriptInput: {
|
|
24
|
+
...input,
|
|
25
|
+
workerSourceText: neutralization.semanticWorkerSourceText,
|
|
26
|
+
headSourceText: neutralization.semanticHeadSourceText,
|
|
27
|
+
workerSourceHash: undefined,
|
|
28
|
+
headSourceHash: undefined
|
|
29
|
+
},
|
|
30
|
+
projectionHeadSourceText: stagedTopLevelResult.mergedSourceText,
|
|
31
|
+
replayCurrentSourceText: stagedTopLevelResult.mergedSourceText,
|
|
32
|
+
metadata: {
|
|
33
|
+
stagedTopLevelSummary: stagedTopLevelResult.summary,
|
|
34
|
+
declarationReplay: {
|
|
35
|
+
edits: declarationReplay.edits.length,
|
|
36
|
+
reasonCodes: declarationReplay.reasonCodes
|
|
37
|
+
},
|
|
38
|
+
neutralization: neutralization.summary,
|
|
39
|
+
originalReasonCodes: topLevelResult.admission?.reasonCodes ?? []
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function safeTopLevelChangeCount(summary = {}) {
|
|
45
|
+
return (summary.importSpecifierAdditions ?? 0)
|
|
46
|
+
+ (summary.importDeclarationAdditions ?? 0)
|
|
47
|
+
+ (summary.topLevelDeclarationAdditions ?? 0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
createStagedTopLevelSemanticFallback
|
|
52
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { findCompatibleBaseImportEntry, findSameImportTargetBaseEntry } from './js-ts-safe-merge-import-shape.js';
|
|
2
|
+
import { scanJsTsTopLevelLedger } from './js-ts-safe-merge-ledger.js';
|
|
3
|
+
import { sameStatementText } from './js-ts-safe-merge-context.js';
|
|
4
|
+
|
|
5
|
+
function neutralizeJsTsSafeTopLevelMergeSources(input = {}) {
|
|
6
|
+
if (typeof input.baseSourceText !== 'string'
|
|
7
|
+
|| typeof input.workerSourceText !== 'string'
|
|
8
|
+
|| typeof input.headSourceText !== 'string') {
|
|
9
|
+
return { ok: false, reasonCodes: ['missing-source-text'] };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const context = quietLedgerContext(input);
|
|
13
|
+
const base = scanJsTsTopLevelLedger(input.baseSourceText, 'base', context);
|
|
14
|
+
const worker = scanJsTsTopLevelLedger(input.workerSourceText, 'worker', context);
|
|
15
|
+
const head = scanJsTsTopLevelLedger(input.headSourceText, 'head', context);
|
|
16
|
+
if (context.conflicts.length) {
|
|
17
|
+
return { ok: false, reasonCodes: context.conflicts.map((conflict) => conflict.code) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const baseEntries = base.entries;
|
|
21
|
+
const topLevelWorker = neutralizeChangedExistingDeclarations(input.workerSourceText, worker, baseEntries);
|
|
22
|
+
const topLevelHead = neutralizeChangedExistingDeclarations(input.headSourceText, head, baseEntries);
|
|
23
|
+
const semanticWorker = neutralizeSafeTopLevelAdditions(input.workerSourceText, worker, baseEntries);
|
|
24
|
+
const semanticHead = neutralizeSafeTopLevelAdditions(input.headSourceText, head, baseEntries);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
ok: true,
|
|
28
|
+
base,
|
|
29
|
+
worker,
|
|
30
|
+
head,
|
|
31
|
+
baseSourceText: input.baseSourceText,
|
|
32
|
+
topLevelWorkerSourceText: topLevelWorker.sourceText,
|
|
33
|
+
topLevelHeadSourceText: topLevelHead.sourceText,
|
|
34
|
+
semanticWorkerSourceText: semanticWorker.sourceText,
|
|
35
|
+
semanticHeadSourceText: semanticHead.sourceText,
|
|
36
|
+
summary: {
|
|
37
|
+
workerChangedExistingDeclarations: topLevelWorker.changedExistingDeclarations,
|
|
38
|
+
headChangedExistingDeclarations: topLevelHead.changedExistingDeclarations,
|
|
39
|
+
workerNeutralizedSafeTopLevelChanges: semanticWorker.neutralizedSafeTopLevelChanges,
|
|
40
|
+
headNeutralizedSafeTopLevelChanges: semanticHead.neutralizedSafeTopLevelChanges
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createJsTsChangedDeclarationReplay(input = {}, neutralization, currentSourceText) {
|
|
46
|
+
if (!neutralization?.ok || typeof currentSourceText !== 'string') {
|
|
47
|
+
return { ok: false, reasonCodes: ['missing-neutralized-current-source'], edits: [] };
|
|
48
|
+
}
|
|
49
|
+
const context = quietLedgerContext(input);
|
|
50
|
+
const current = scanJsTsTopLevelLedger(currentSourceText, 'current', context);
|
|
51
|
+
if (context.conflicts.length) {
|
|
52
|
+
return { ok: false, reasonCodes: context.conflicts.map((conflict) => conflict.code), edits: [] };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const baseEntries = neutralization.base.entries;
|
|
56
|
+
const workerMatches = matchedEntriesByBaseKey(neutralization.worker, baseEntries);
|
|
57
|
+
const headMatches = matchedEntriesByBaseKey(neutralization.head, baseEntries);
|
|
58
|
+
const currentMatches = matchedEntriesByBaseKey(current, baseEntries);
|
|
59
|
+
const edits = [];
|
|
60
|
+
const reasonCodes = [];
|
|
61
|
+
for (const baseEntry of baseEntries) {
|
|
62
|
+
if (baseEntry.kind === 'import') continue;
|
|
63
|
+
const workerEntry = workerMatches.get(baseEntry.key);
|
|
64
|
+
if (!workerEntry || sameStatementText(workerEntry.text, baseEntry.text)) continue;
|
|
65
|
+
const headEntry = headMatches.get(baseEntry.key);
|
|
66
|
+
const currentEntry = currentMatches.get(baseEntry.key);
|
|
67
|
+
if (!headEntry || !sameStatementText(headEntry.text, baseEntry.text)) {
|
|
68
|
+
reasonCodes.push(`head-anchor-changed-since-base:${baseEntry.key}`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!currentEntry || !sameStatementText(currentEntry.text, baseEntry.text)) {
|
|
72
|
+
reasonCodes.push(`current-anchor-changed-since-base:${baseEntry.key}`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
edits.push({
|
|
76
|
+
key: baseEntry.key,
|
|
77
|
+
names: workerEntry.names ?? baseEntry.names ?? [],
|
|
78
|
+
declarationKind: workerEntry.declarationInfo?.declarationKind ?? baseEntry.declarationInfo?.declarationKind,
|
|
79
|
+
start: currentEntry.start,
|
|
80
|
+
end: currentEntry.end,
|
|
81
|
+
currentText: currentEntry.text,
|
|
82
|
+
replacementText: workerEntry.text
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
ok: reasonCodes.length === 0,
|
|
87
|
+
reasonCodes,
|
|
88
|
+
edits,
|
|
89
|
+
outputSourceText: reasonCodes.length ? undefined : applySourceEdits(
|
|
90
|
+
currentSourceText,
|
|
91
|
+
edits.map((edit) => ({ start: edit.start, end: edit.end, text: edit.replacementText }))
|
|
92
|
+
)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function neutralizeChangedExistingDeclarations(sourceText, ledger, baseEntries) {
|
|
97
|
+
const usedBaseKeys = new Set();
|
|
98
|
+
const edits = [];
|
|
99
|
+
let changedExistingDeclarations = 0;
|
|
100
|
+
for (const entry of ledger.entries) {
|
|
101
|
+
const baseEntry = findBaseEntry(entry, baseEntries, usedBaseKeys);
|
|
102
|
+
if (!baseEntry) continue;
|
|
103
|
+
usedBaseKeys.add(baseEntry.key);
|
|
104
|
+
if (entry.kind === 'import' || sameStatementText(entry.text, baseEntry.text)) continue;
|
|
105
|
+
edits.push({ start: entry.start, end: entry.end, text: baseEntry.text });
|
|
106
|
+
changedExistingDeclarations += 1;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
sourceText: applySourceEdits(sourceText, edits),
|
|
110
|
+
changedExistingDeclarations
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function neutralizeSafeTopLevelAdditions(sourceText, ledger, baseEntries) {
|
|
115
|
+
const usedBaseKeys = new Set();
|
|
116
|
+
const edits = [];
|
|
117
|
+
let neutralizedSafeTopLevelChanges = 0;
|
|
118
|
+
for (const entry of ledger.entries) {
|
|
119
|
+
const baseEntry = findBaseEntry(entry, baseEntries, usedBaseKeys);
|
|
120
|
+
if (!baseEntry) {
|
|
121
|
+
edits.push(deleteEntryEdit(sourceText, entry));
|
|
122
|
+
neutralizedSafeTopLevelChanges += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
usedBaseKeys.add(baseEntry.key);
|
|
126
|
+
if (entry.kind !== 'import' || sameStatementText(entry.text, baseEntry.text)) continue;
|
|
127
|
+
edits.push({ start: entry.start, end: entry.end, text: baseEntry.text });
|
|
128
|
+
neutralizedSafeTopLevelChanges += 1;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
sourceText: applySourceEdits(sourceText, edits),
|
|
132
|
+
neutralizedSafeTopLevelChanges
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function findBaseEntry(entry, baseEntries, usedBaseKeys) {
|
|
137
|
+
if (!entry) return undefined;
|
|
138
|
+
const direct = baseEntries.find((candidate) => candidate.key === entry.key && !usedBaseKeys.has(candidate.key));
|
|
139
|
+
if (direct) return direct;
|
|
140
|
+
return entry.kind === 'import'
|
|
141
|
+
? findCompatibleBaseImportEntry(entry, baseEntries, usedBaseKeys)
|
|
142
|
+
?? findSameImportTargetBaseEntry(entry, baseEntries, usedBaseKeys)
|
|
143
|
+
: undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function matchedEntriesByBaseKey(ledger, baseEntries) {
|
|
147
|
+
const usedBaseKeys = new Set();
|
|
148
|
+
const matches = new Map();
|
|
149
|
+
for (const entry of ledger.entries) {
|
|
150
|
+
const baseEntry = findBaseEntry(entry, baseEntries, usedBaseKeys);
|
|
151
|
+
if (!baseEntry) continue;
|
|
152
|
+
usedBaseKeys.add(baseEntry.key);
|
|
153
|
+
matches.set(baseEntry.key, entry);
|
|
154
|
+
}
|
|
155
|
+
return matches;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function deleteEntryEdit(sourceText, entry) {
|
|
159
|
+
const lineEnd = sourceText.indexOf('\n', entry.end);
|
|
160
|
+
const end = lineEnd === -1 ? entry.end : lineEnd + 1;
|
|
161
|
+
const lineStart = sourceText.lastIndexOf('\n', Math.max(0, entry.start - 1)) + 1;
|
|
162
|
+
const start = onlyWhitespace(sourceText.slice(lineStart, entry.start)) ? lineStart : entry.start;
|
|
163
|
+
return { start, end, text: '' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function onlyWhitespace(text) {
|
|
167
|
+
return /^[\t ]*$/.test(text);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function applySourceEdits(sourceText, edits) {
|
|
171
|
+
return [...edits]
|
|
172
|
+
.sort((left, right) => right.start - left.start || right.end - left.end)
|
|
173
|
+
.reduce((current, edit) => `${current.slice(0, edit.start)}${edit.text}${current.slice(edit.end)}`, sourceText);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function quietLedgerContext(input) {
|
|
177
|
+
return {
|
|
178
|
+
id: String(input.id ?? 'js_ts_safe_merge'),
|
|
179
|
+
sourcePath: input.sourcePath,
|
|
180
|
+
language: input.language ?? 'typescript',
|
|
181
|
+
conflicts: [],
|
|
182
|
+
blockedGateIds: new Set(),
|
|
183
|
+
gateReasonCodes: new Map()
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export {
|
|
188
|
+
createJsTsChangedDeclarationReplay,
|
|
189
|
+
neutralizeJsTsSafeTopLevelMergeSources
|
|
190
|
+
};
|
package/package.json
CHANGED