@shapeshift-labs/frontier-lang-compiler 0.2.145 → 0.2.147
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/internal/index-impl/syntaxDeclaration.js +23 -0
- package/dist/internal/index-impl/typeScriptDeclaration.js +14 -0
- package/dist/internal/index-impl/typeScriptExportedDeclarationEntries.js +1 -0
- package/dist/js-ts-safe-merge-independent-deletion-artifacts.js +112 -0
- package/dist/js-ts-safe-merge-independent-deletion-fallback.js +53 -0
- package/dist/js-ts-safe-merge-independent-deletion-plan.js +148 -0
- package/dist/js-ts-safe-merge-independent-deletion-records.js +251 -0
- package/dist/js-ts-safe-merge-semantic-edit-already-applied.js +82 -0
- package/dist/js-ts-safe-merge-semantic-edit-fallback.js +17 -23
- package/dist/js-ts-safe-merge-semantic-edit-gates.js +16 -0
- package/dist/js-ts-safe-project-merge-graph.js +30 -7
- package/dist/js-ts-safe-project-merge-public-contracts.js +117 -0
- package/package.json +1 -1
|
@@ -15,6 +15,29 @@ export function syntaxDeclaration(node, nativeNodeId, input) {
|
|
|
15
15
|
if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'class');
|
|
16
16
|
if (kind === 'TSInterfaceDeclaration' || kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'interface');
|
|
17
17
|
if (kind === 'TSTypeAliasDeclaration' || kind === 'TypeAliasDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'type');
|
|
18
|
+
if (kind === 'TSModuleDeclaration' || kind === 'ModuleDeclaration') return syntaxModuleDeclaration(input, nativeNodeId, node);
|
|
18
19
|
if (kind === 'VariableDeclarator') return namedDeclaration(input, nativeNodeId, node.id, 'variable');
|
|
19
20
|
return undefined;
|
|
20
21
|
}
|
|
22
|
+
|
|
23
|
+
function syntaxModuleDeclaration(input, nativeNodeId, node) {
|
|
24
|
+
const name = syntaxName(node.id ?? node.name);
|
|
25
|
+
if (!name) return undefined;
|
|
26
|
+
return {
|
|
27
|
+
...declarationRecord(input, nativeNodeId, name, 'module', 'definition'),
|
|
28
|
+
metadata: {
|
|
29
|
+
scan: 'syntax-module-declaration',
|
|
30
|
+
moduleName: name,
|
|
31
|
+
namespace: name
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function syntaxName(node) {
|
|
37
|
+
if (!node) return undefined;
|
|
38
|
+
if (typeof node === 'string') return node;
|
|
39
|
+
if (typeof node.name === 'string') return node.name;
|
|
40
|
+
if (typeof node.value === 'string') return node.value;
|
|
41
|
+
if (typeof node.text === 'string') return node.text;
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
@@ -14,6 +14,7 @@ export function typeScriptDeclaration(node, kind, nativeNodeId, input, options =
|
|
|
14
14
|
if (kind === 'ClassDeclaration') return declarationWithExports(enrich(namedDeclaration(input, nativeNodeId, node.name, 'class')), exportedEntries, enrich, node);
|
|
15
15
|
if (kind === 'InterfaceDeclaration') return declarationWithExports(enrich(namedDeclaration(input, nativeNodeId, node.name, 'interface')), exportedEntries, enrich, node);
|
|
16
16
|
if (kind === 'TypeAliasDeclaration' || kind === 'EnumDeclaration') return declarationWithExports(enrich(namedDeclaration(input, nativeNodeId, node.name, 'type')), exportedEntries, enrich, node);
|
|
17
|
+
if (kind === 'ModuleDeclaration') return declarationWithExports(enrich(moduleDeclaration(input, nativeNodeId, node), node.name ?? node), exportedEntries, enrich, node);
|
|
17
18
|
if (kind === 'VariableDeclaration') return enrich(namedDeclaration(input, nativeNodeId, node.name, 'variable'));
|
|
18
19
|
if (kind === 'MethodDeclaration' || kind === 'MethodSignature') return enrich(namedDeclaration(input, nativeNodeId, node.name, 'method'));
|
|
19
20
|
return undefined;
|
|
@@ -25,6 +26,19 @@ function declarationWithExports(declaration, exportedEntries, enrich, node) {
|
|
|
25
26
|
return exports.length ? [declaration, ...exports] : declaration;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
function moduleDeclaration(input, nativeNodeId, node) {
|
|
30
|
+
const name = stringFromTsExpression(node.name);
|
|
31
|
+
if (!name) return undefined;
|
|
32
|
+
return {
|
|
33
|
+
...declarationRecord(input, nativeNodeId, name, 'module', 'definition'),
|
|
34
|
+
metadata: compactRecord({
|
|
35
|
+
scan: 'typescript-module-declaration',
|
|
36
|
+
moduleName: name,
|
|
37
|
+
namespace: name
|
|
38
|
+
})
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
function enrichTypeScriptDeclaration(node, symbolNode, declaration, input, options) {
|
|
29
43
|
if (!declaration) return declaration;
|
|
30
44
|
const checker = options.typeChecker ?? options.checker ?? options.program?.getTypeChecker?.();
|
|
@@ -92,6 +92,7 @@ function exportedSymbolKind(kind) {
|
|
|
92
92
|
if (kind === 'ClassDeclaration') return 'class';
|
|
93
93
|
if (kind === 'InterfaceDeclaration') return 'interface';
|
|
94
94
|
if (kind === 'TypeAliasDeclaration' || kind === 'EnumDeclaration') return 'type';
|
|
95
|
+
if (kind === 'ModuleDeclaration') return 'module';
|
|
95
96
|
return undefined;
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import {
|
|
3
|
+
independentTopLevelDeletionOperation,
|
|
4
|
+
independentTopLevelDeletionProjection,
|
|
5
|
+
independentTopLevelDeletionReplay,
|
|
6
|
+
independentTopLevelDeletionScript
|
|
7
|
+
} from './js-ts-safe-merge-independent-deletion-records.js';
|
|
8
|
+
import { idFragment, uniqueStrings } from './native-import-utils.js';
|
|
9
|
+
|
|
10
|
+
function createIndependentTopLevelDeletionArtifacts(input, topLevelResult, deletionPlan) {
|
|
11
|
+
const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
|
|
12
|
+
const language = input.language ?? topLevelResult.language ?? 'typescript';
|
|
13
|
+
const sourcePath = input.sourcePath ?? topLevelResult.sourcePath ?? 'inline.ts';
|
|
14
|
+
const operationId = `js_ts_independent_top_level_delete_${idFragment([id, deletionPlan.deletedEntry.key].join(':'))}`;
|
|
15
|
+
const operation = independentTopLevelDeletionOperation({ id, operationId, language, sourcePath, deletionPlan, input });
|
|
16
|
+
const script = independentTopLevelDeletionScript({ id, language, sourcePath, operation, input });
|
|
17
|
+
const projection = independentTopLevelDeletionProjection({ id, language, sourcePath, operation, script, deletionPlan, input });
|
|
18
|
+
const replay = independentTopLevelDeletionReplay({
|
|
19
|
+
id: `${id}_semantic_edit_replay`,
|
|
20
|
+
language,
|
|
21
|
+
sourcePath,
|
|
22
|
+
operation,
|
|
23
|
+
projection,
|
|
24
|
+
deletionPlan,
|
|
25
|
+
currentSourceText: input.headSourceText,
|
|
26
|
+
status: 'accepted-clean',
|
|
27
|
+
editStatus: 'applied',
|
|
28
|
+
reasonCodes: ['head-anchor-matches-base', 'independent-top-level-deletion'],
|
|
29
|
+
outputSourceText: deletionPlan.mergedSourceText
|
|
30
|
+
});
|
|
31
|
+
const alreadyAppliedReplay = independentTopLevelDeletionReplay({
|
|
32
|
+
id: `${id}_semantic_edit_already_applied`,
|
|
33
|
+
language,
|
|
34
|
+
sourcePath,
|
|
35
|
+
operation,
|
|
36
|
+
projection,
|
|
37
|
+
deletionPlan,
|
|
38
|
+
currentSourceText: deletionPlan.mergedSourceText,
|
|
39
|
+
status: 'already-applied',
|
|
40
|
+
editStatus: 'already-applied',
|
|
41
|
+
reasonCodes: ['independent-top-level-deletion-already-applied'],
|
|
42
|
+
outputSourceText: deletionPlan.mergedSourceText
|
|
43
|
+
});
|
|
44
|
+
const status = projection.status === 'projected'
|
|
45
|
+
&& replay.status === 'accepted-clean'
|
|
46
|
+
&& replay.outputSourceText === deletionPlan.mergedSourceText
|
|
47
|
+
&& alreadyAppliedReplay.status === 'already-applied'
|
|
48
|
+
? 'verified'
|
|
49
|
+
: 'blocked';
|
|
50
|
+
const reasonCodes = status === 'verified'
|
|
51
|
+
? []
|
|
52
|
+
: uniqueStrings([
|
|
53
|
+
...(projection.admission?.reasonCodes ?? []),
|
|
54
|
+
...(replay.admission?.reasonCodes ?? []),
|
|
55
|
+
...(alreadyAppliedReplay.admission?.reasonCodes ?? [])
|
|
56
|
+
]);
|
|
57
|
+
const core = {
|
|
58
|
+
kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
|
|
59
|
+
version: 1,
|
|
60
|
+
schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
|
|
61
|
+
id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(id)}`,
|
|
62
|
+
sourcePath,
|
|
63
|
+
language,
|
|
64
|
+
status,
|
|
65
|
+
script,
|
|
66
|
+
projection,
|
|
67
|
+
replay,
|
|
68
|
+
alreadyAppliedReplay,
|
|
69
|
+
admission: {
|
|
70
|
+
status: status === 'verified' ? 'auto-merge-candidate' : 'blocked',
|
|
71
|
+
action: status === 'verified' ? 'apply' : 'human-review',
|
|
72
|
+
reviewRequired: status !== 'verified',
|
|
73
|
+
autoApplyCandidate: status === 'verified',
|
|
74
|
+
autoMergeClaim: false,
|
|
75
|
+
semanticEquivalenceClaim: false,
|
|
76
|
+
reasonCodes
|
|
77
|
+
},
|
|
78
|
+
summary: {
|
|
79
|
+
operations: script.summary.operations,
|
|
80
|
+
edits: projection.edits.length,
|
|
81
|
+
replayStatus: replay.status,
|
|
82
|
+
alreadyAppliedReplayStatus: alreadyAppliedReplay.status,
|
|
83
|
+
projectedSourceMatchesMerged: projection.sourceText === deletionPlan.mergedSourceText,
|
|
84
|
+
replayOutputMatchesMerged: replay.outputSourceText === deletionPlan.mergedSourceText
|
|
85
|
+
},
|
|
86
|
+
evidence: [{
|
|
87
|
+
id: `evidence_${idFragment(id)}_independent_top_level_deletion`,
|
|
88
|
+
kind: 'js-ts-independent-top-level-deletion-replay',
|
|
89
|
+
status: status === 'verified' ? 'passed' : 'needs-review',
|
|
90
|
+
path: sourcePath,
|
|
91
|
+
summary: status === 'verified'
|
|
92
|
+
? 'JS/TS independent top-level deletion replay verified 1 operation.'
|
|
93
|
+
: `JS/TS independent top-level deletion requires review: ${reasonCodes.join(', ')}.`,
|
|
94
|
+
metadata: {
|
|
95
|
+
autoMergeClaim: false,
|
|
96
|
+
semanticEquivalenceClaim: false,
|
|
97
|
+
deletedKey: deletionPlan.deletedEntry.key,
|
|
98
|
+
originalReasonCodes: topLevelResult.admission?.reasonCodes ?? []
|
|
99
|
+
}
|
|
100
|
+
}],
|
|
101
|
+
metadata: {
|
|
102
|
+
autoMergeClaim: false,
|
|
103
|
+
semanticEquivalenceClaim: false,
|
|
104
|
+
source: 'independent-top-level-deletion-fallback',
|
|
105
|
+
originalReasonCodes: topLevelResult.admission?.reasonCodes ?? [],
|
|
106
|
+
deletion: deletionPlan.summary
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { createIndependentTopLevelDeletionArtifacts };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
import { createIndependentTopLevelDeletionArtifacts } from './js-ts-safe-merge-independent-deletion-artifacts.js';
|
|
3
|
+
import { createIndependentTopLevelDeletionPlan } from './js-ts-safe-merge-independent-deletion-plan.js';
|
|
4
|
+
import { semanticEditGates } from './js-ts-safe-merge-semantic-edit-gates.js';
|
|
5
|
+
|
|
6
|
+
function independentTopLevelDeletionFallbackResult(input, topLevelResult) {
|
|
7
|
+
const deletionPlan = createIndependentTopLevelDeletionPlan(input, topLevelResult);
|
|
8
|
+
if (!deletionPlan.ok) return undefined;
|
|
9
|
+
const artifacts = createIndependentTopLevelDeletionArtifacts(input, topLevelResult, deletionPlan);
|
|
10
|
+
if (artifacts.status !== 'verified') return undefined;
|
|
11
|
+
const gates = semanticEditGates(artifacts);
|
|
12
|
+
return {
|
|
13
|
+
...topLevelResult,
|
|
14
|
+
id: String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge'),
|
|
15
|
+
status: JsTsSafeMergeStatuses.merged,
|
|
16
|
+
mergedSourceText: deletionPlan.mergedSourceText,
|
|
17
|
+
outputSourceText: deletionPlan.mergedSourceText,
|
|
18
|
+
conflicts: [],
|
|
19
|
+
gates,
|
|
20
|
+
admission: {
|
|
21
|
+
status: 'auto-merge-candidate',
|
|
22
|
+
action: 'apply',
|
|
23
|
+
reviewRequired: false,
|
|
24
|
+
autoApplyCandidate: true,
|
|
25
|
+
autoMergeClaim: false,
|
|
26
|
+
semanticEquivalenceClaim: false,
|
|
27
|
+
reasonCodes: []
|
|
28
|
+
},
|
|
29
|
+
summary: {
|
|
30
|
+
...topLevelResult.summary,
|
|
31
|
+
changedExistingDeclarations: 0,
|
|
32
|
+
conflicts: 0,
|
|
33
|
+
gatesPassed: gates.filter((gateRecord) => gateRecord.status === 'passed').length,
|
|
34
|
+
topLevelDeclarationDeletions: 1,
|
|
35
|
+
semanticEditOperations: artifacts.script.summary.operations,
|
|
36
|
+
semanticEditAppliedOperations: artifacts.replay.summary.applied,
|
|
37
|
+
semanticEditReplayStatus: artifacts.replay.status,
|
|
38
|
+
composedPhases: 2
|
|
39
|
+
},
|
|
40
|
+
metadata: {
|
|
41
|
+
...topLevelResult.metadata,
|
|
42
|
+
composed: {
|
|
43
|
+
phase: 'independent-top-level-deletion-fallback',
|
|
44
|
+
phases: ['top-level-ledger', 'independent-top-level-deletion'],
|
|
45
|
+
originalReasonCodes: topLevelResult.admission?.reasonCodes ?? [],
|
|
46
|
+
deletion: deletionPlan.summary
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
semanticArtifacts: artifacts
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { independentTopLevelDeletionFallbackResult };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { JsTsSafeMergeConflictCodes } from './js-ts-safe-merge-constants.js';
|
|
2
|
+
import { sameStatementText } from './js-ts-safe-merge-context.js';
|
|
3
|
+
import { scanJsTsTopLevelLedger } from './js-ts-safe-merge-ledger.js';
|
|
4
|
+
import { uniqueStrings } from './native-import-utils.js';
|
|
5
|
+
|
|
6
|
+
function createIndependentTopLevelDeletionPlan(input, topLevelResult) {
|
|
7
|
+
const originalReasonCodes = topLevelResult?.admission?.reasonCodes ?? [];
|
|
8
|
+
if (!originalReasonCodes.includes(JsTsSafeMergeConflictCodes.topLevelOrderChanged)) {
|
|
9
|
+
return { ok: false, reasonCodes: ['top-level-order-not-deletion-shaped'] };
|
|
10
|
+
}
|
|
11
|
+
const allowedOriginalReasonCodes = new Set([
|
|
12
|
+
JsTsSafeMergeConflictCodes.topLevelOrderChanged,
|
|
13
|
+
JsTsSafeMergeConflictCodes.changedExistingDeclaration,
|
|
14
|
+
JsTsSafeMergeConflictCodes.typeAliasConflict
|
|
15
|
+
]);
|
|
16
|
+
if (originalReasonCodes.some((reason) => !allowedOriginalReasonCodes.has(reason))) {
|
|
17
|
+
return { ok: false, reasonCodes: ['top-level-deletion-has-unsafe-original-conflict'] };
|
|
18
|
+
}
|
|
19
|
+
if (typeof input.baseSourceText !== 'string'
|
|
20
|
+
|| typeof input.workerSourceText !== 'string'
|
|
21
|
+
|| typeof input.headSourceText !== 'string') {
|
|
22
|
+
return { ok: false, reasonCodes: ['missing-source-text'] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const context = quietDeletionLedgerContext(input);
|
|
26
|
+
const base = scanJsTsTopLevelLedger(input.baseSourceText, 'base', context);
|
|
27
|
+
const worker = scanJsTsTopLevelLedger(input.workerSourceText, 'worker', context);
|
|
28
|
+
const head = scanJsTsTopLevelLedger(input.headSourceText, 'head', context);
|
|
29
|
+
if (context.conflicts.length) {
|
|
30
|
+
return { ok: false, reasonCodes: uniqueStrings(context.conflicts.map((conflict) => conflict.code)) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const duplicateLedgerReason = firstDuplicateLedgerReason(base, worker, head);
|
|
34
|
+
if (duplicateLedgerReason) return { ok: false, reasonCodes: [duplicateLedgerReason] };
|
|
35
|
+
|
|
36
|
+
const baseByKey = entriesByKey(base.entries);
|
|
37
|
+
const workerByKey = entriesByKey(worker.entries);
|
|
38
|
+
const headByKey = entriesByKey(head.entries);
|
|
39
|
+
const baseKeys = base.entries.map((entry) => entry.key);
|
|
40
|
+
const missingWorkerBaseEntries = base.entries.filter((entry) => !workerByKey.has(entry.key));
|
|
41
|
+
const deletedWorkerEntries = missingWorkerBaseEntries.filter((entry) => entry.kind !== 'import');
|
|
42
|
+
const workerAddedEntries = worker.entries.filter((entry) => !baseByKey.has(entry.key));
|
|
43
|
+
if (missingWorkerBaseEntries.length !== 1
|
|
44
|
+
|| deletedWorkerEntries.length !== 1
|
|
45
|
+
|| workerAddedEntries.length !== 0) {
|
|
46
|
+
return { ok: false, reasonCodes: ['worker-top-level-deletion-not-isolated'] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const deletedEntry = deletedWorkerEntries[0];
|
|
50
|
+
if (deletedEntry.kind !== 'declaration' || deletedEntry.declarationInfo?.exported === true) {
|
|
51
|
+
return { ok: false, reasonCodes: ['exported-or-unsupported-top-level-deletion'] };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const expectedWorkerKeys = base.entries
|
|
55
|
+
.filter((entry) => entry.key !== deletedEntry.key)
|
|
56
|
+
.map((entry) => entry.key);
|
|
57
|
+
if (!sameStringList(worker.entries.map((entry) => entry.key), expectedWorkerKeys)) {
|
|
58
|
+
return { ok: false, reasonCodes: ['worker-top-level-deletion-order-changed'] };
|
|
59
|
+
}
|
|
60
|
+
for (const baseEntry of base.entries) {
|
|
61
|
+
if (baseEntry.key === deletedEntry.key) continue;
|
|
62
|
+
const workerEntry = workerByKey.get(baseEntry.key);
|
|
63
|
+
if (!workerEntry || !sameStatementText(workerEntry.text, baseEntry.text)) {
|
|
64
|
+
return { ok: false, reasonCodes: [`worker-changed-nondeleted-entry:${baseEntry.key}`] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const headProjectedBaseKeys = head.entries
|
|
69
|
+
.filter((entry) => baseByKey.has(entry.key))
|
|
70
|
+
.map((entry) => entry.key);
|
|
71
|
+
if (!sameStringList(headProjectedBaseKeys, baseKeys)) {
|
|
72
|
+
return { ok: false, reasonCodes: ['head-base-order-or-presence-changed'] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const headEntry = headByKey.get(deletedEntry.key);
|
|
76
|
+
if (!headEntry) return { ok: false, reasonCodes: [`head-anchor-missing:${deletedEntry.key}`] };
|
|
77
|
+
if (!sameStatementText(headEntry.text, deletedEntry.text)) {
|
|
78
|
+
return { ok: false, reasonCodes: [`head-anchor-changed-since-base:${deletedEntry.key}`] };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const edit = deleteEntryEdit(input.headSourceText, headEntry);
|
|
82
|
+
const deletedText = input.headSourceText.slice(edit.start, edit.end);
|
|
83
|
+
const mergedSourceText = applySourceEdits(input.headSourceText, [edit]);
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
base,
|
|
87
|
+
worker,
|
|
88
|
+
head,
|
|
89
|
+
deletedEntry,
|
|
90
|
+
headEntry,
|
|
91
|
+
edit,
|
|
92
|
+
deletedText,
|
|
93
|
+
mergedSourceText,
|
|
94
|
+
summary: {
|
|
95
|
+
key: deletedEntry.key,
|
|
96
|
+
name: deletedEntry.names?.[0],
|
|
97
|
+
declarationKind: deletedEntry.declarationInfo?.declarationKind,
|
|
98
|
+
exported: false,
|
|
99
|
+
reasonCodes: ['head-anchor-matches-base', 'independent-top-level-deletion']
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function quietDeletionLedgerContext(input) {
|
|
105
|
+
return {
|
|
106
|
+
id: String(input.id ?? 'js_ts_safe_merge'),
|
|
107
|
+
sourcePath: input.sourcePath,
|
|
108
|
+
language: input.language ?? 'typescript',
|
|
109
|
+
conflicts: [],
|
|
110
|
+
blockedGateIds: new Set(),
|
|
111
|
+
gateReasonCodes: new Map()
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function firstDuplicateLedgerReason(...ledgers) {
|
|
116
|
+
for (const ledger of ledgers) {
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
for (const entry of ledger.entries) {
|
|
119
|
+
if (seen.has(entry.key)) return `duplicate-ledger-key:${ledger.label}:${entry.key}`;
|
|
120
|
+
seen.add(entry.key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function entriesByKey(entries) {
|
|
127
|
+
return new Map(entries.map((entry) => [entry.key, entry]));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function sameStringList(left, right) {
|
|
131
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function deleteEntryEdit(sourceText, entry) {
|
|
135
|
+
const lineEnd = sourceText.indexOf('\n', entry.end);
|
|
136
|
+
const end = lineEnd === -1 ? entry.end : lineEnd + 1;
|
|
137
|
+
const lineStart = sourceText.lastIndexOf('\n', Math.max(0, entry.start - 1)) + 1;
|
|
138
|
+
const start = /^[\t ]*$/.test(sourceText.slice(lineStart, entry.start)) ? lineStart : entry.start;
|
|
139
|
+
return { start, end, text: '' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function applySourceEdits(sourceText, edits) {
|
|
143
|
+
return [...edits]
|
|
144
|
+
.sort((left, right) => right.start - left.start || right.end - left.end)
|
|
145
|
+
.reduce((current, edit) => `${current.slice(0, edit.start)}${edit.text}${current.slice(edit.end)}`, sourceText);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { createIndependentTopLevelDeletionPlan };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { idFragment } from './native-import-utils.js';
|
|
3
|
+
|
|
4
|
+
function independentTopLevelDeletionOperation(input) {
|
|
5
|
+
const entry = input.deletionPlan.deletedEntry;
|
|
6
|
+
const name = entry.names?.[0] ?? entry.key;
|
|
7
|
+
const symbolKind = entry.declarationInfo?.declarationKind ?? 'declaration';
|
|
8
|
+
const anchorKey = `source#${input.sourcePath}#declaration#${name}`;
|
|
9
|
+
const operation = {
|
|
10
|
+
id: input.operationId,
|
|
11
|
+
kind: 'jsTsRemoveTopLevelDeclaration',
|
|
12
|
+
changeKind: 'removed',
|
|
13
|
+
anchor: {
|
|
14
|
+
key: anchorKey,
|
|
15
|
+
conflictKey: `declaration:${entry.key}`,
|
|
16
|
+
regionId: anchorKey,
|
|
17
|
+
regionKind: 'declaration',
|
|
18
|
+
granularity: 'js-ts-ledger-entry',
|
|
19
|
+
language: input.language,
|
|
20
|
+
sourcePath: input.sourcePath,
|
|
21
|
+
symbolId: anchorKey,
|
|
22
|
+
symbolName: name,
|
|
23
|
+
symbolKind,
|
|
24
|
+
sourceSpan: { start: input.deletionPlan.headEntry.start, end: input.deletionPlan.headEntry.end }
|
|
25
|
+
},
|
|
26
|
+
spans: {
|
|
27
|
+
base: { start: entry.start, end: entry.end },
|
|
28
|
+
head: { start: input.deletionPlan.headEntry.start, end: input.deletionPlan.headEntry.end }
|
|
29
|
+
},
|
|
30
|
+
hashes: {
|
|
31
|
+
baseSourceHash: input.input.baseHash,
|
|
32
|
+
workerSourceHash: input.input.workerHash,
|
|
33
|
+
headSourceHash: input.input.headHash,
|
|
34
|
+
baseTextHash: hashSemanticValue(entry.text),
|
|
35
|
+
headTextHash: hashSemanticValue(input.deletionPlan.headEntry.text),
|
|
36
|
+
workerTextHash: hashSemanticValue('')
|
|
37
|
+
},
|
|
38
|
+
status: 'portable',
|
|
39
|
+
readiness: 'ready',
|
|
40
|
+
confidence: 1,
|
|
41
|
+
reasonCodes: ['head-anchor-matches-base', 'independent-top-level-deletion'],
|
|
42
|
+
evidenceIds: [`evidence_${idFragment(input.id)}_independent_top_level_deletion`],
|
|
43
|
+
metadata: {
|
|
44
|
+
autoMergeClaim: false,
|
|
45
|
+
semanticEquivalenceClaim: false,
|
|
46
|
+
ledgerKey: entry.key,
|
|
47
|
+
exported: false
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return { ...operation, operationContentHash: hashSemanticValue(operation) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function independentTopLevelDeletionScript(input) {
|
|
54
|
+
const core = {
|
|
55
|
+
kind: 'frontier.lang.semanticEditScript',
|
|
56
|
+
version: 1,
|
|
57
|
+
schema: 'frontier.lang.semanticEditScript.v1',
|
|
58
|
+
id: `${input.id}_semantic_edit`,
|
|
59
|
+
stableId: `semantic_edit_script_${idFragment([input.id, input.operation.operationContentHash].join(':'))}`,
|
|
60
|
+
language: input.language,
|
|
61
|
+
sourcePath: input.sourcePath,
|
|
62
|
+
baseHash: input.input.baseHash,
|
|
63
|
+
workerHash: input.input.workerHash,
|
|
64
|
+
headHash: input.input.headHash,
|
|
65
|
+
operations: [input.operation],
|
|
66
|
+
summary: {
|
|
67
|
+
operations: 1,
|
|
68
|
+
byStatus: { portable: 1 },
|
|
69
|
+
byKind: { jsTsRemoveTopLevelDeclaration: 1 },
|
|
70
|
+
portable: 1,
|
|
71
|
+
alreadyApplied: 0,
|
|
72
|
+
needsPort: 0,
|
|
73
|
+
conflicts: 0,
|
|
74
|
+
stale: 0,
|
|
75
|
+
blocked: 0,
|
|
76
|
+
candidates: 0,
|
|
77
|
+
autoMergeCandidates: 1,
|
|
78
|
+
operationContentHashes: [input.operation.operationContentHash]
|
|
79
|
+
},
|
|
80
|
+
admission: {
|
|
81
|
+
status: 'auto-merge-candidate',
|
|
82
|
+
action: 'run-gates-and-apply',
|
|
83
|
+
reviewRequired: false,
|
|
84
|
+
autoApplyCandidate: true,
|
|
85
|
+
autoMergeClaim: false,
|
|
86
|
+
semanticEquivalenceClaim: false,
|
|
87
|
+
reasonCodes: input.operation.reasonCodes,
|
|
88
|
+
conflictKeys: [input.operation.anchor.conflictKey],
|
|
89
|
+
evidenceIds: input.operation.evidenceIds
|
|
90
|
+
},
|
|
91
|
+
evidence: [{
|
|
92
|
+
id: `evidence_${idFragment(input.id)}_independent_top_level_deletion_script`,
|
|
93
|
+
kind: 'semantic-edit-script',
|
|
94
|
+
status: 'passed',
|
|
95
|
+
path: input.sourcePath,
|
|
96
|
+
summary: 'Created independent top-level deletion script with 1 operation.',
|
|
97
|
+
metadata: {
|
|
98
|
+
autoMergeClaim: false,
|
|
99
|
+
semanticEquivalenceClaim: false
|
|
100
|
+
}
|
|
101
|
+
}],
|
|
102
|
+
metadata: {
|
|
103
|
+
autoMergeClaim: false,
|
|
104
|
+
semanticEquivalenceClaim: false,
|
|
105
|
+
source: 'independent-top-level-deletion-fallback'
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function independentTopLevelDeletionProjection(input) {
|
|
112
|
+
const editContentHash = hashSemanticValue({
|
|
113
|
+
operationId: input.operation.id,
|
|
114
|
+
deletedText: input.deletionPlan.deletedText,
|
|
115
|
+
replacementText: ''
|
|
116
|
+
});
|
|
117
|
+
const edit = {
|
|
118
|
+
operationId: input.operation.id,
|
|
119
|
+
status: 'applied',
|
|
120
|
+
kind: input.operation.kind,
|
|
121
|
+
editKind: 'delete',
|
|
122
|
+
changeKind: 'removed',
|
|
123
|
+
anchorKey: input.operation.anchor.key,
|
|
124
|
+
conflictKey: input.operation.anchor.conflictKey,
|
|
125
|
+
regionId: input.operation.anchor.regionId,
|
|
126
|
+
regionKind: input.operation.anchor.regionKind,
|
|
127
|
+
sourcePath: input.sourcePath,
|
|
128
|
+
symbolId: input.operation.anchor.symbolId,
|
|
129
|
+
symbolName: input.operation.anchor.symbolName,
|
|
130
|
+
symbolKind: input.operation.anchor.symbolKind,
|
|
131
|
+
operationContentHash: input.operation.operationContentHash,
|
|
132
|
+
editContentHash,
|
|
133
|
+
headStart: input.deletionPlan.edit.start,
|
|
134
|
+
headEnd: input.deletionPlan.edit.end,
|
|
135
|
+
editOrder: 0,
|
|
136
|
+
deletedBytes: input.deletionPlan.deletedText.length,
|
|
137
|
+
replacementBytes: 0,
|
|
138
|
+
deletedTextHash: hashSemanticValue(input.deletionPlan.deletedText),
|
|
139
|
+
replacementTextHash: hashSemanticValue(''),
|
|
140
|
+
deletedTextLineEndingStableHash: lineEndingStableTextHash(input.deletionPlan.deletedText),
|
|
141
|
+
replacementTextLineEndingStableHash: lineEndingStableTextHash(''),
|
|
142
|
+
replacementText: ''
|
|
143
|
+
};
|
|
144
|
+
const core = {
|
|
145
|
+
kind: 'frontier.lang.semanticEditProjection',
|
|
146
|
+
version: 1,
|
|
147
|
+
id: `${input.id}_semantic_edit_projection`,
|
|
148
|
+
scriptId: input.script.id,
|
|
149
|
+
status: 'projected',
|
|
150
|
+
sourcePath: input.sourcePath,
|
|
151
|
+
language: input.language,
|
|
152
|
+
baseHash: input.input.baseHash,
|
|
153
|
+
workerHash: input.input.workerHash,
|
|
154
|
+
headHash: input.input.headHash,
|
|
155
|
+
projectedHash: hashSemanticValue(input.deletionPlan.mergedSourceText),
|
|
156
|
+
appliedOperations: [input.operation.id],
|
|
157
|
+
skippedOperations: [],
|
|
158
|
+
edits: [edit],
|
|
159
|
+
sourceText: input.deletionPlan.mergedSourceText,
|
|
160
|
+
admission: {
|
|
161
|
+
status: 'auto-merge-candidate',
|
|
162
|
+
autoMergeClaim: false,
|
|
163
|
+
semanticEquivalenceClaim: false,
|
|
164
|
+
reasonCodes: []
|
|
165
|
+
},
|
|
166
|
+
metadata: {
|
|
167
|
+
autoMergeClaim: false,
|
|
168
|
+
semanticEquivalenceClaim: false,
|
|
169
|
+
source: 'independent-top-level-deletion-fallback'
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function independentTopLevelDeletionReplay(input) {
|
|
176
|
+
const applied = input.editStatus === 'applied';
|
|
177
|
+
const edit = {
|
|
178
|
+
operationId: input.operation.id,
|
|
179
|
+
editKind: 'delete',
|
|
180
|
+
editOrder: 0,
|
|
181
|
+
sourcePath: input.sourcePath,
|
|
182
|
+
symbolName: input.operation.anchor.symbolName,
|
|
183
|
+
symbolKind: input.operation.anchor.symbolKind,
|
|
184
|
+
status: input.editStatus,
|
|
185
|
+
start: applied ? input.deletionPlan.edit.start : undefined,
|
|
186
|
+
end: applied ? input.deletionPlan.edit.end : undefined,
|
|
187
|
+
replacementBytes: 0,
|
|
188
|
+
replacementText: '',
|
|
189
|
+
reasonCodes: input.reasonCodes
|
|
190
|
+
};
|
|
191
|
+
const core = {
|
|
192
|
+
kind: 'frontier.lang.semanticEditReplay',
|
|
193
|
+
version: 1,
|
|
194
|
+
schema: 'frontier.lang.semanticEditReplay.v1',
|
|
195
|
+
id: input.id,
|
|
196
|
+
projectionId: input.projection.id,
|
|
197
|
+
scriptId: input.projection.scriptId,
|
|
198
|
+
sourcePath: input.sourcePath,
|
|
199
|
+
language: input.language,
|
|
200
|
+
currentHash: hashSemanticValue(input.currentSourceText),
|
|
201
|
+
projectedHash: input.projection.projectedHash,
|
|
202
|
+
outputHash: hashSemanticValue(input.outputSourceText),
|
|
203
|
+
status: input.status,
|
|
204
|
+
edits: [edit],
|
|
205
|
+
appliedOperations: applied ? [input.operation.id] : [],
|
|
206
|
+
skippedOperations: applied ? [] : [input.operation.id],
|
|
207
|
+
admission: {
|
|
208
|
+
status: input.status,
|
|
209
|
+
action: applied ? 'apply' : 'skip',
|
|
210
|
+
reviewRequired: false,
|
|
211
|
+
autoApplyCandidate: applied,
|
|
212
|
+
autoMergeClaim: false,
|
|
213
|
+
semanticEquivalenceClaim: false,
|
|
214
|
+
reasonCodes: []
|
|
215
|
+
},
|
|
216
|
+
outputSourceText: input.outputSourceText,
|
|
217
|
+
summary: {
|
|
218
|
+
edits: 1,
|
|
219
|
+
applied: applied ? 1 : 0,
|
|
220
|
+
alreadyApplied: applied ? 0 : 1,
|
|
221
|
+
conflicts: 0,
|
|
222
|
+
stale: 0,
|
|
223
|
+
blocked: 0,
|
|
224
|
+
reasonCodes: []
|
|
225
|
+
},
|
|
226
|
+
metadata: {
|
|
227
|
+
autoMergeClaim: false,
|
|
228
|
+
semanticEquivalenceClaim: false,
|
|
229
|
+
source: 'independent-top-level-deletion-fallback'
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function lineEndingStableTextHash(value) {
|
|
236
|
+
const normalized = lineEndingStableText(value);
|
|
237
|
+
return normalized === undefined ? undefined : hashSemanticValue(normalized);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function lineEndingStableText(value) {
|
|
241
|
+
if (typeof value !== 'string') return undefined;
|
|
242
|
+
const normalized = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
243
|
+
return normalized.length > 1 && normalized.endsWith('\n') ? normalized.slice(0, -1) : normalized;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export {
|
|
247
|
+
independentTopLevelDeletionOperation,
|
|
248
|
+
independentTopLevelDeletionProjection,
|
|
249
|
+
independentTopLevelDeletionReplay,
|
|
250
|
+
independentTopLevelDeletionScript
|
|
251
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { uniqueStrings } from './native-import-utils.js';
|
|
3
|
+
|
|
4
|
+
function normalizeAlreadyAppliedDeleteReplay(input) {
|
|
5
|
+
if (!isProjectedDeleteAlreadyAppliedReplay(input)) return input.alreadyAppliedReplay;
|
|
6
|
+
const { hash: _hash, ...alreadyAppliedReplay } = input.alreadyAppliedReplay;
|
|
7
|
+
const normalizedEdits = input.alreadyAppliedReplay.edits.map((edit) => alreadyAppliedDeleteEdit(edit));
|
|
8
|
+
const reasonCodes = uniqueStrings(normalizedEdits.flatMap((edit) => edit.reasonCodes ?? []));
|
|
9
|
+
const core = {
|
|
10
|
+
...alreadyAppliedReplay,
|
|
11
|
+
currentHash: input.projection.projectedHash,
|
|
12
|
+
outputHash: input.projection.projectedHash,
|
|
13
|
+
status: 'already-applied',
|
|
14
|
+
edits: normalizedEdits,
|
|
15
|
+
appliedOperations: [],
|
|
16
|
+
skippedOperations: normalizedEdits.map((edit) => edit.operationId).filter(Boolean),
|
|
17
|
+
diagnostics: [],
|
|
18
|
+
admission: {
|
|
19
|
+
status: 'already-applied',
|
|
20
|
+
action: 'skip',
|
|
21
|
+
reviewRequired: false,
|
|
22
|
+
autoApplyCandidate: false,
|
|
23
|
+
autoMergeClaim: false,
|
|
24
|
+
semanticEquivalenceClaim: false,
|
|
25
|
+
reasonCodes
|
|
26
|
+
},
|
|
27
|
+
outputSourceText: input.projection.sourceText,
|
|
28
|
+
summary: {
|
|
29
|
+
edits: normalizedEdits.length,
|
|
30
|
+
applied: 0,
|
|
31
|
+
alreadyApplied: normalizedEdits.length,
|
|
32
|
+
conflicts: 0,
|
|
33
|
+
stale: 0,
|
|
34
|
+
blocked: 0,
|
|
35
|
+
reasonCodes
|
|
36
|
+
},
|
|
37
|
+
metadata: {
|
|
38
|
+
...input.alreadyAppliedReplay.metadata,
|
|
39
|
+
normalizedAlreadyAppliedReplay: 'projected-delete-anchor-absent'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isProjectedDeleteAlreadyAppliedReplay(input) {
|
|
46
|
+
if (input.projection?.status !== 'projected') return false;
|
|
47
|
+
if (input.replay?.status !== 'accepted-clean') return false;
|
|
48
|
+
if (input.replay.outputSourceText !== input.projection.sourceText) return false;
|
|
49
|
+
const replay = input.alreadyAppliedReplay;
|
|
50
|
+
if (replay?.status !== 'stale') return false;
|
|
51
|
+
if (replay.currentHash !== input.projection.projectedHash) return false;
|
|
52
|
+
const edits = replay.edits ?? [];
|
|
53
|
+
if (!edits.length) return false;
|
|
54
|
+
const appliedDeleteIds = new Set((input.replay.edits ?? [])
|
|
55
|
+
.filter((edit) => edit.status === 'applied' && edit.editKind === 'delete')
|
|
56
|
+
.map((edit) => edit.operationId));
|
|
57
|
+
const projectionDeleteIds = new Set((input.projection.edits ?? [])
|
|
58
|
+
.filter((edit) => edit.editKind === 'delete')
|
|
59
|
+
.map((edit) => edit.operationId));
|
|
60
|
+
const staleDeletes = edits.filter(isProjectedDeleteMissingAnchor);
|
|
61
|
+
return staleDeletes.length > 0
|
|
62
|
+
&& edits.every((edit) => edit.status === 'already-applied' || isProjectedDeleteMissingAnchor(edit))
|
|
63
|
+
&& staleDeletes.every((edit) => appliedDeleteIds.has(edit.operationId) && projectionDeleteIds.has(edit.operationId));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isProjectedDeleteMissingAnchor(edit) {
|
|
67
|
+
return edit.editKind === 'delete'
|
|
68
|
+
&& edit.status === 'stale'
|
|
69
|
+
&& (edit.reasonCodes ?? []).includes('current-symbol-anchor-missing');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function alreadyAppliedDeleteEdit(edit) {
|
|
73
|
+
if (!isProjectedDeleteMissingAnchor(edit)) return edit;
|
|
74
|
+
return {
|
|
75
|
+
...edit,
|
|
76
|
+
status: 'already-applied',
|
|
77
|
+
reasonCodes: ['projected-delete-anchor-absent'],
|
|
78
|
+
diagnostics: []
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { normalizeAlreadyAppliedDeleteReplay };
|
|
@@ -3,12 +3,15 @@ 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 { JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
|
|
6
|
+
import { independentTopLevelDeletionFallbackResult } from './js-ts-safe-merge-independent-deletion-fallback.js';
|
|
7
|
+
import { normalizeAlreadyAppliedDeleteReplay } from './js-ts-safe-merge-semantic-edit-already-applied.js';
|
|
6
8
|
import {
|
|
7
9
|
semanticFallbackChangedExistingDeclarations,
|
|
8
10
|
semanticFallbackConflictCode,
|
|
9
11
|
semanticFallbackPhase,
|
|
10
12
|
shouldTrySemanticEditFallback
|
|
11
13
|
} from './js-ts-safe-merge-semantic-edit-fallback-utils.js';
|
|
14
|
+
import { semanticEditGates } from './js-ts-safe-merge-semantic-edit-gates.js';
|
|
12
15
|
import {
|
|
13
16
|
createStagedDeclarationAlreadyAppliedReplay,
|
|
14
17
|
createStagedDeclarationProjection,
|
|
@@ -19,6 +22,8 @@ import { createSourceShapeSemanticFallbackResult } from './js-ts-safe-merge-sour
|
|
|
19
22
|
import { idFragment, uniqueStrings } from './native-import-utils.js';
|
|
20
23
|
|
|
21
24
|
function semanticEditFallbackResult(input, topLevelResult) {
|
|
25
|
+
const independentDeletionResult = independentTopLevelDeletionFallbackResult(input, topLevelResult);
|
|
26
|
+
if (independentDeletionResult) return independentDeletionResult;
|
|
22
27
|
if (!shouldTrySemanticEditFallback(topLevelResult)) return topLevelResult;
|
|
23
28
|
const stagedFallback = createStagedTopLevelSemanticFallback(input, topLevelResult);
|
|
24
29
|
const candidates = semanticFallbackCandidates(stagedFallback);
|
|
@@ -132,14 +137,18 @@ function createSemanticEditFallbackArtifacts(input, topLevelResult, stagedFallba
|
|
|
132
137
|
});
|
|
133
138
|
const alreadyAppliedReplay = stagedDeclarationProjection
|
|
134
139
|
? createStagedDeclarationAlreadyAppliedReplay({ id, projection, sourcePath, language })
|
|
135
|
-
:
|
|
136
|
-
id: `${id}_semantic_edit_already_applied`,
|
|
140
|
+
: normalizeAlreadyAppliedDeleteReplay({
|
|
137
141
|
projection,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
replay,
|
|
143
|
+
alreadyAppliedReplay: replaySemanticEditProjection({
|
|
144
|
+
id: `${id}_semantic_edit_already_applied`,
|
|
145
|
+
projection,
|
|
146
|
+
currentSourceText: projection.sourceText,
|
|
147
|
+
currentSourcePath: sourcePath,
|
|
148
|
+
currentSourceHash: projection.projectedHash,
|
|
149
|
+
language,
|
|
150
|
+
parser: input.parser
|
|
151
|
+
})
|
|
143
152
|
});
|
|
144
153
|
return semanticEditArtifacts({
|
|
145
154
|
id,
|
|
@@ -301,19 +310,4 @@ function semanticEditFallbackBlockedResult(input, topLevelResult, artifacts) {
|
|
|
301
310
|
};
|
|
302
311
|
}
|
|
303
312
|
|
|
304
|
-
|
|
305
|
-
return [
|
|
306
|
-
gate('semantic-edit-script', artifacts.script?.admission?.status === 'auto-merge-candidate', artifacts.script?.admission?.reasonCodes),
|
|
307
|
-
gate('semantic-edit-projection', artifacts.projection?.status === 'projected', artifacts.projection?.admission?.reasonCodes),
|
|
308
|
-
gate('semantic-edit-replay', artifacts.replay?.status === 'accepted-clean', artifacts.replay?.admission?.reasonCodes),
|
|
309
|
-
gate('semantic-edit-already-applied', artifacts.alreadyAppliedReplay?.status === 'already-applied', artifacts.alreadyAppliedReplay?.admission?.reasonCodes)
|
|
310
|
-
];
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function gate(id, passed, reasonCodes = []) {
|
|
314
|
-
return { id, status: passed ? 'passed' : 'blocked', reasonCodes: passed ? [] : uniqueStrings(reasonCodes) };
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export {
|
|
318
|
-
semanticEditFallbackResult
|
|
319
|
-
};
|
|
313
|
+
export { semanticEditFallbackResult };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { uniqueStrings } from './native-import-utils.js';
|
|
2
|
+
|
|
3
|
+
function semanticEditGates(artifacts) {
|
|
4
|
+
return [
|
|
5
|
+
gate('semantic-edit-script', artifacts.script?.admission?.status === 'auto-merge-candidate', artifacts.script?.admission?.reasonCodes),
|
|
6
|
+
gate('semantic-edit-projection', artifacts.projection?.status === 'projected', artifacts.projection?.admission?.reasonCodes),
|
|
7
|
+
gate('semantic-edit-replay', artifacts.replay?.status === 'accepted-clean', artifacts.replay?.admission?.reasonCodes),
|
|
8
|
+
gate('semantic-edit-already-applied', artifacts.alreadyAppliedReplay?.status === 'already-applied', artifacts.alreadyAppliedReplay?.admission?.reasonCodes)
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function gate(id, passed, reasonCodes = []) {
|
|
13
|
+
return { id, status: passed ? 'passed' : 'blocked', reasonCodes: passed ? [] : uniqueStrings(reasonCodes) };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { semanticEditGates };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { idFragment } from './native-import-utils.js';
|
|
2
2
|
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
3
3
|
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
4
|
+
import { augmentProjectSymbolGraphPublicContracts } from './js-ts-safe-project-merge-public-contracts.js';
|
|
4
5
|
import { createNativeProjectImportResult } from './internal/index-impl/createNativeProjectImportResult.js';
|
|
5
6
|
import { importNativeSource } from './internal/index-impl/importNativeSource.js';
|
|
6
7
|
import {
|
|
@@ -92,22 +93,26 @@ function createProjectGraphStageArtifacts(input, files, mergeId, stageName, stag
|
|
|
92
93
|
...(stageName === 'output' ? { outputProjectImportSource: projectGraphImportSource } : {})
|
|
93
94
|
}
|
|
94
95
|
}, imports);
|
|
95
|
-
const
|
|
96
|
+
const projectSymbolGraph = augmentProjectSymbolGraphPublicContracts(projectImport.projectSymbolGraph, files, input, stageName);
|
|
97
|
+
const stageProjectImport = projectSymbolGraph === projectImport.projectSymbolGraph
|
|
98
|
+
? projectImport
|
|
99
|
+
: attachProjectSymbolGraph(projectImport, projectSymbolGraph);
|
|
100
|
+
const edgeLimitConflicts = projectGraphEdgeLimitConflicts(limits, stageName, projectSymbolGraph);
|
|
96
101
|
const serialized = projectGraphSerializedLimitConflict(limits, stageName, {
|
|
97
|
-
projectImport,
|
|
98
|
-
projectSymbolGraph
|
|
102
|
+
projectImport: stageProjectImport,
|
|
103
|
+
projectSymbolGraph
|
|
99
104
|
});
|
|
100
105
|
const limitConflicts = [...edgeLimitConflicts, serialized.conflict].filter(Boolean);
|
|
101
106
|
if (limitConflicts.length) {
|
|
102
|
-
return limitedProjectGraphStage(stageName, projectGraphImportSource, sourceStats,
|
|
107
|
+
return limitedProjectGraphStage(stageName, projectGraphImportSource, sourceStats, projectSymbolGraph, limitConflicts, serialized.serializedBytes);
|
|
103
108
|
}
|
|
104
109
|
return {
|
|
105
110
|
kind: 'frontier.lang.jsTsProjectGraphStage',
|
|
106
111
|
version: 1,
|
|
107
112
|
stage: stageName,
|
|
108
|
-
projectImport,
|
|
109
|
-
projectSymbolGraph
|
|
110
|
-
summary: projectGraphStageSummary(stageName,
|
|
113
|
+
projectImport: stageProjectImport,
|
|
114
|
+
projectSymbolGraph,
|
|
115
|
+
summary: projectGraphStageSummary(stageName, projectSymbolGraph, projectGraphImportSource, sourceStats, serialized.serializedBytes, [])
|
|
111
116
|
};
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -215,6 +220,24 @@ function stageFile(file, sourceText, input) {
|
|
|
215
220
|
});
|
|
216
221
|
}
|
|
217
222
|
|
|
223
|
+
function attachProjectSymbolGraph(projectImport, projectSymbolGraph) {
|
|
224
|
+
return {
|
|
225
|
+
...projectImport,
|
|
226
|
+
projectSymbolGraph,
|
|
227
|
+
semanticIndex: projectImport.semanticIndex ? {
|
|
228
|
+
...projectImport.semanticIndex,
|
|
229
|
+
metadata: {
|
|
230
|
+
...projectImport.semanticIndex.metadata,
|
|
231
|
+
projectSymbolGraph
|
|
232
|
+
}
|
|
233
|
+
} : projectImport.semanticIndex,
|
|
234
|
+
metadata: {
|
|
235
|
+
...projectImport.metadata,
|
|
236
|
+
projectSymbolGraph
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
218
241
|
function hashText(sourceText) {
|
|
219
242
|
return hashSemanticValue(sourceText);
|
|
220
243
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
3
|
+
import { findContainer, normalizeKind, normalizeMemberText, parseMembers } from './js-ts-semantic-merge-parse.js';
|
|
4
|
+
import { idFragment } from './native-import-utils.js';
|
|
5
|
+
|
|
6
|
+
function augmentProjectSymbolGraphPublicContracts(projectSymbolGraph, files, input, stageName) {
|
|
7
|
+
const existingRegions = Array.isArray(projectSymbolGraph?.publicContractRegions) ? projectSymbolGraph.publicContractRegions : [];
|
|
8
|
+
const seenKeys = new Set(existingRegions.map((region) => region?.key).filter(Boolean));
|
|
9
|
+
const syntheticRegions = [];
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
syntheticRegions.push(...syntheticPublicContractRegions(file, input, stageName, seenKeys));
|
|
12
|
+
}
|
|
13
|
+
if (!syntheticRegions.length) return projectSymbolGraph;
|
|
14
|
+
return {
|
|
15
|
+
...projectSymbolGraph,
|
|
16
|
+
publicContractRegions: [...existingRegions, ...syntheticRegions]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function syntheticPublicContractRegions(file, input, stageName, seenKeys) {
|
|
21
|
+
if (typeof file?.sourceText !== 'string' || !file.sourcePath) return [];
|
|
22
|
+
const records = [];
|
|
23
|
+
for (const region of publicContractPolicyRegionsForPath(input, file.sourcePath)) {
|
|
24
|
+
const kind = normalizeKind(region?.kind);
|
|
25
|
+
if (!['interface', 'type', 'class'].includes(kind) || typeof region?.name !== 'string') continue;
|
|
26
|
+
const container = findContainer(file.sourceText, region);
|
|
27
|
+
if (container.reasonCodes.length || !isExportedContainer(file.sourceText, container.value)) continue;
|
|
28
|
+
const members = parseMembers(container.value.body, kind);
|
|
29
|
+
if (members.reasonCodes.length) continue;
|
|
30
|
+
const key = `source#${file.sourcePath}#export#${region.name}`;
|
|
31
|
+
if (seenKeys.has(key)) continue;
|
|
32
|
+
seenKeys.add(key);
|
|
33
|
+
records.push(syntheticPublicContractRegion(file, input, stageName, kind, region.name, key, members.members));
|
|
34
|
+
}
|
|
35
|
+
return records;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function syntheticPublicContractRegion(file, input, stageName, kind, name, key, members) {
|
|
39
|
+
const language = file.language ?? input.language ?? languageForPath(file.sourcePath);
|
|
40
|
+
const memberRecords = members.map((member) => compactRecord({
|
|
41
|
+
key: member.key,
|
|
42
|
+
memberKind: member.memberKind,
|
|
43
|
+
signature: normalizeMemberText(member.text, kind)
|
|
44
|
+
}));
|
|
45
|
+
const signatureHash = hashSemanticValue({
|
|
46
|
+
kind: 'frontier.lang.syntheticPublicContractSignature',
|
|
47
|
+
language,
|
|
48
|
+
sourcePath: file.sourcePath,
|
|
49
|
+
symbolName: name,
|
|
50
|
+
sourceKind: kind,
|
|
51
|
+
members: memberRecords
|
|
52
|
+
});
|
|
53
|
+
const contractHash = hashSemanticValue({
|
|
54
|
+
kind: 'frontier.lang.syntheticPublicContractRegionHash',
|
|
55
|
+
language,
|
|
56
|
+
sourcePath: file.sourcePath,
|
|
57
|
+
key,
|
|
58
|
+
symbolName: name,
|
|
59
|
+
symbolKind: 'export',
|
|
60
|
+
apiSurfaceKind: 'module-export',
|
|
61
|
+
exportedName: name,
|
|
62
|
+
edgeKind: 'export',
|
|
63
|
+
signatureHash
|
|
64
|
+
});
|
|
65
|
+
return compactRecord({
|
|
66
|
+
id: `region_${idFragment(stageName)}_${idFragment(key)}`,
|
|
67
|
+
key,
|
|
68
|
+
regionKind: 'export',
|
|
69
|
+
granularity: 'symbol',
|
|
70
|
+
language,
|
|
71
|
+
sourcePath: file.sourcePath,
|
|
72
|
+
sourceHash: file.sourceHash,
|
|
73
|
+
symbolId: `symbol:${language}:export:${idFragment(name)}`,
|
|
74
|
+
symbolName: name,
|
|
75
|
+
symbolKind: 'export',
|
|
76
|
+
publicContract: true,
|
|
77
|
+
exportedName: name,
|
|
78
|
+
edgeKind: 'export',
|
|
79
|
+
apiSurfaceKind: 'module-export',
|
|
80
|
+
signatureHash,
|
|
81
|
+
contractHash,
|
|
82
|
+
metadata: {
|
|
83
|
+
source: 'js-ts-safe-project-merge-policy',
|
|
84
|
+
projectGraphStage: stageName,
|
|
85
|
+
sourceKind: kind,
|
|
86
|
+
memberKeys: memberRecords.map((member) => member.key)
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function publicContractPolicyRegionsForPath(input, sourcePath) {
|
|
92
|
+
const policy = input.policyByPath?.[sourcePath]
|
|
93
|
+
?? input.mergePolicyByPath?.[sourcePath]
|
|
94
|
+
?? input.policy
|
|
95
|
+
?? input.mergePolicy;
|
|
96
|
+
const regions = Array.isArray(policy)
|
|
97
|
+
? policy
|
|
98
|
+
: policy?.unorderedRegions
|
|
99
|
+
?? policy?.unorderedMemberRegions
|
|
100
|
+
?? policy?.safeList
|
|
101
|
+
?? policy?.safeMembers
|
|
102
|
+
?? [];
|
|
103
|
+
return Array.isArray(regions) ? regions : [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isExportedContainer(sourceText, container) {
|
|
107
|
+
if (!container) return false;
|
|
108
|
+
return /^\s*export\b/.test(sourceText.slice(container.start, container.openStart));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function languageForPath(sourcePath) {
|
|
112
|
+
const path = String(sourcePath ?? '').toLowerCase();
|
|
113
|
+
if (path.endsWith('.js') || path.endsWith('.jsx') || path.endsWith('.mjs') || path.endsWith('.cjs')) return 'javascript';
|
|
114
|
+
return 'typescript';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { augmentProjectSymbolGraphPublicContracts };
|
package/package.json
CHANGED