@shapeshift-labs/frontier-lang-compiler 0.2.161 → 0.2.163
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/moduleHostResourceImportMetadata.js +47 -0
- package/dist/internal/index-impl/projectSymbolGraphCompilerAdvancedTypeMetadata.js +215 -1
- package/dist/internal/index-impl/projectSymbolGraphCompilerFacts.js +1 -1
- package/dist/internal/index-impl/projectSymbolGraphCssModuleUtils.js +56 -1
- package/dist/internal/index-impl/projectSymbolGraphCssModules.js +48 -4
- package/dist/internal/index-impl/projectSymbolGraphJsxPropRecordFields.js +91 -0
- package/dist/internal/index-impl/projectSymbolGraphJsxPropValues.js +35 -7
- package/dist/internal/index-impl/projectSymbolGraphJsxRecords.js +4 -31
- package/dist/internal/index-impl/projectSymbolGraphJsxRenderReturnCollectionHelpers.js +201 -0
- package/dist/internal/index-impl/projectSymbolGraphJsxRenderReturnCollections.js +210 -0
- package/dist/internal/index-impl/projectSymbolGraphJsxRenderReturns.js +12 -5
- package/dist/internal/index-impl/projectSymbolGraphJsxSpreadPropValues.js +196 -0
- package/dist/internal/index-impl/projectSymbolGraphJsxStaticLiterals.js +207 -0
- package/dist/internal/index-impl/projectSymbolGraphModuleResolution.js +12 -14
- package/dist/internal/index-impl/projectSymbolGraphPackageConditions.js +33 -31
- package/dist/internal/index-impl/projectSymbolGraphPackageRuntimeConditions.js +22 -0
- package/dist/internal/index-impl/projectSymbolGraphScopeUseDefLexical.js +11 -19
- package/dist/internal/index-impl/syntaxModuleDeclarationEntries.js +27 -1
- package/dist/js-ts-safe-project-merge-admission.js +10 -0
- package/dist/js-ts-safe-project-merge-evidence-routing.js +30 -2
- package/dist/js-ts-safe-project-merge-graph-delta-compiler-conflicts.js +7 -0
- package/dist/js-ts-safe-project-merge-html-css-matrix.js +7 -2
- package/dist/js-ts-safe-project-merge-html-css-summary.js +110 -2
- package/dist/js-ts-safe-project-merge-html-css.js +137 -3
- package/dist/js-ts-safe-project-merge-jsx-graph-conflict-details.js +9 -0
- package/dist/js-ts-safe-project-merge.js +3 -0
- package/dist/native-source-preservation-scanner.js +10 -0
- package/dist/semantic-import-runtime-dynamic-import-evidence.js +141 -0
- package/dist/semantic-import-runtime-order-evidence.js +5 -4
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { nearestPublicOwnerForOffset } from './projectSymbolGraphScopeUseDefOwne
|
|
|
8
8
|
import { readStaticMemberLiteral } from './staticMemberLiteral.js';
|
|
9
9
|
|
|
10
10
|
const jsTsKeywords = new Set('abstract as async await break case catch class const continue debugger declare default delete do else enum export extends false finally for from function if implements import in infer instanceof interface keyof let module namespace new null of package private protected public readonly return satisfies static super switch this throw true try type typeof undefined unique unknown var void while with yield'.split(' '));
|
|
11
|
+
const namespaceComputedMemberDynamicUnsupportedReason = 'lexical-scope-namespace-computed-member-dynamic-unsupported', namespaceComputedMemberUnresolvedReason = 'lexical-scope-namespace-computed-member-unresolved';
|
|
11
12
|
|
|
12
13
|
function lexicalScopeRecordsForImport(sourceText, context) {
|
|
13
14
|
const masked = maskNonCode(sourceText);
|
|
@@ -230,15 +231,17 @@ function namespacePropertyAccess(code, token, binding, context) {
|
|
|
230
231
|
|
|
231
232
|
function namespaceComputedPropertyAccess(sourceText, code, tokenStart, open) {
|
|
232
233
|
const close = findMatchingBracket(code, open);
|
|
233
|
-
if (close === -1) return blockedNamespaceComputedPropertyAccess();
|
|
234
|
+
if (close === -1) return blockedNamespaceComputedPropertyAccess(undefined, [namespaceComputedMemberUnresolvedReason]);
|
|
234
235
|
const writeOperation = namespaceMemberWriteOperation(code, tokenStart, close + 1);
|
|
235
236
|
let index = open + 1;
|
|
236
237
|
while (index < close && /\s/.test(sourceText[index])) index += 1;
|
|
238
|
+
if (index >= close) return blockedNamespaceComputedPropertyAccess(writeOperation, [namespaceComputedMemberUnresolvedReason]);
|
|
237
239
|
const literal = readStaticMemberLiteral(sourceText, index, close);
|
|
238
|
-
if (!literal) return blockedNamespaceComputedPropertyAccess(writeOperation);
|
|
240
|
+
if (!literal) return blockedNamespaceComputedPropertyAccess(writeOperation, [namespaceComputedMemberDynamicUnsupportedReason]);
|
|
239
241
|
let afterLiteral = literal.end + 1;
|
|
240
242
|
while (afterLiteral < close && /\s/.test(sourceText[afterLiteral])) afterLiteral += 1;
|
|
241
|
-
if (afterLiteral !== close
|
|
243
|
+
if (afterLiteral !== close) return blockedNamespaceComputedPropertyAccess(writeOperation, [namespaceComputedMemberDynamicUnsupportedReason]);
|
|
244
|
+
if (!identifierRegExp.test(literal.value)) return blockedNamespaceComputedPropertyAccess(writeOperation);
|
|
242
245
|
if (writeOperation) {
|
|
243
246
|
return blockedNamespaceMemberWrite('namespace-computed-property-write', {
|
|
244
247
|
memberName: literal.value,
|
|
@@ -252,31 +255,18 @@ function namespaceComputedPropertyAccess(sourceText, code, tokenStart, open) {
|
|
|
252
255
|
return { referenceKind: 'namespace-computed-property-read', memberName: literal.value, memberStart: literal.start, memberEnd: literal.end, memberComputed: true, memberLiteralKind: literal.literalKind };
|
|
253
256
|
}
|
|
254
257
|
|
|
255
|
-
function blockedNamespaceComputedPropertyAccess(writeOperation) {
|
|
258
|
+
function blockedNamespaceComputedPropertyAccess(writeOperation, reasonCodes = []) {
|
|
256
259
|
return {
|
|
257
260
|
referenceKind: writeOperation ? 'namespace-computed-property-write' : 'namespace-computed-property-read',
|
|
258
261
|
memberComputed: true,
|
|
259
262
|
writeOperation,
|
|
260
263
|
status: 'blocked',
|
|
261
|
-
reasonCodes: [
|
|
262
|
-
LexicalUseDefReasonCodes.namespaceComputedMemberUnsupported,
|
|
263
|
-
...(writeOperation ? [LexicalUseDefReasonCodes.namespaceMemberWriteUnsupported] : [])
|
|
264
|
-
]
|
|
264
|
+
reasonCodes: uniqueReasonCodes([LexicalUseDefReasonCodes.namespaceComputedMemberUnsupported, ...reasonCodes, ...(writeOperation ? [LexicalUseDefReasonCodes.namespaceMemberWriteUnsupported] : [])])
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
function blockedNamespaceMemberWrite(referenceKind, fields = {}) {
|
|
269
|
-
return {
|
|
270
|
-
referenceKind,
|
|
271
|
-
memberName: fields.memberName,
|
|
272
|
-
memberStart: fields.memberStart,
|
|
273
|
-
memberEnd: fields.memberEnd,
|
|
274
|
-
memberComputed: fields.memberComputed,
|
|
275
|
-
memberLiteralKind: fields.memberLiteralKind,
|
|
276
|
-
status: 'blocked',
|
|
277
|
-
reasonCodes: [LexicalUseDefReasonCodes.namespaceMemberWriteUnsupported],
|
|
278
|
-
writeOperation: fields.writeOperation
|
|
279
|
-
};
|
|
269
|
+
return { referenceKind, memberName: fields.memberName, memberStart: fields.memberStart, memberEnd: fields.memberEnd, memberComputed: fields.memberComputed, memberLiteralKind: fields.memberLiteralKind, status: 'blocked', reasonCodes: [LexicalUseDefReasonCodes.namespaceMemberWriteUnsupported], writeOperation: fields.writeOperation };
|
|
280
270
|
}
|
|
281
271
|
|
|
282
272
|
function namespaceMemberWriteOperation(code, tokenStart, accessEnd) {
|
|
@@ -317,4 +307,6 @@ function findMatchingBracket(code, open) {
|
|
|
317
307
|
return -1;
|
|
318
308
|
}
|
|
319
309
|
|
|
310
|
+
function uniqueReasonCodes(reasonCodes) { return [...new Set(reasonCodes.filter(Boolean))]; }
|
|
311
|
+
|
|
320
312
|
export { lexicalScopeRecordsForImport };
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from './syntaxCommonJsModuleDeclarationEntries.js';
|
|
6
6
|
import { dynamicImportExpressionMetadata } from './dynamicImportExpressionMetadata.js';
|
|
7
7
|
import { hostModuleDependencyMetadata } from './importMetaUrlDependencyMetadata.js';
|
|
8
|
+
import { moduleHostResourceImportMetadata } from './moduleHostResourceImportMetadata.js';
|
|
8
9
|
import { moduleImportAttributeMetadata } from './moduleImportAttributeMetadata.js';
|
|
9
10
|
import {
|
|
10
11
|
declarationName,
|
|
@@ -38,10 +39,15 @@ function importDeclarationEntries(node, nativeNodeId, input) {
|
|
|
38
39
|
if (!moduleSpecifier) return [];
|
|
39
40
|
const typeOnly = node.importKind === 'type';
|
|
40
41
|
const bindings = (node.specifiers ?? []).map((specifier) => importSpecifierBinding(specifier, typeOnly)).filter(Boolean);
|
|
42
|
+
const importKind = bindings.length === 0 ? 'side-effect' : 'module';
|
|
41
43
|
return importModuleEntries(input, nativeNodeId, moduleSpecifier, bindings, {
|
|
42
44
|
typeOnly,
|
|
43
45
|
sideEffectOnly: bindings.length === 0,
|
|
44
|
-
importKind
|
|
46
|
+
importKind,
|
|
47
|
+
...moduleHostResourceImportMetadata(moduleSpecifier, {
|
|
48
|
+
hostDependencyBase: 'module-import',
|
|
49
|
+
expressionText: `import ${JSON.stringify(moduleSpecifier)}`
|
|
50
|
+
}),
|
|
45
51
|
...moduleImportAttributeMetadata(node)
|
|
46
52
|
});
|
|
47
53
|
}
|
|
@@ -54,6 +60,10 @@ function dynamicImportExpressionEntries(node, nativeNodeId, input) {
|
|
|
54
60
|
importKind: 'dynamic-import',
|
|
55
61
|
dynamicImport: true,
|
|
56
62
|
...dynamicImportExpressionMetadata(importArgument, moduleSpecifier),
|
|
63
|
+
...moduleHostResourceImportMetadata(moduleSpecifier, {
|
|
64
|
+
hostDependencyBase: 'dynamic-import',
|
|
65
|
+
expressionText: `import(${JSON.stringify(moduleSpecifier)})`
|
|
66
|
+
}),
|
|
57
67
|
...moduleImportAttributeMetadata(node)
|
|
58
68
|
});
|
|
59
69
|
}
|
|
@@ -75,12 +85,20 @@ function exportNamedDeclarationEntries(node, nativeNodeId, input) {
|
|
|
75
85
|
typeOnly,
|
|
76
86
|
reexport: true,
|
|
77
87
|
importKind: 'reexport',
|
|
88
|
+
...moduleHostResourceImportMetadata(moduleSpecifier, {
|
|
89
|
+
hostDependencyBase: 'module-reexport',
|
|
90
|
+
expressionText: `export from ${JSON.stringify(moduleSpecifier)}`
|
|
91
|
+
}),
|
|
78
92
|
...moduleImportAttributeMetadata(node)
|
|
79
93
|
}) : []),
|
|
80
94
|
...exportModuleEntries(input, nativeNodeId, moduleSpecifier, bindings, {
|
|
81
95
|
typeOnly,
|
|
82
96
|
reExport: Boolean(moduleSpecifier),
|
|
83
97
|
exportKind: typeOnly ? 'type-named' : 'named',
|
|
98
|
+
...moduleHostResourceImportMetadata(moduleSpecifier, {
|
|
99
|
+
hostDependencyBase: 'module-reexport',
|
|
100
|
+
expressionText: `export from ${JSON.stringify(moduleSpecifier)}`
|
|
101
|
+
}),
|
|
84
102
|
...moduleImportAttributeMetadata(node)
|
|
85
103
|
})
|
|
86
104
|
];
|
|
@@ -107,6 +125,10 @@ function exportAllDeclarationEntries(node, nativeNodeId, input) {
|
|
|
107
125
|
reexport: true,
|
|
108
126
|
exportStar: !exportedName,
|
|
109
127
|
importKind: exportedName ? 'namespace-reexport' : 'reexport',
|
|
128
|
+
...moduleHostResourceImportMetadata(moduleSpecifier, {
|
|
129
|
+
hostDependencyBase: 'module-reexport',
|
|
130
|
+
expressionText: `export from ${JSON.stringify(moduleSpecifier)}`
|
|
131
|
+
}),
|
|
110
132
|
...moduleImportAttributeMetadata(node)
|
|
111
133
|
}),
|
|
112
134
|
...exportModuleEntries(input, nativeNodeId, moduleSpecifier, bindings, {
|
|
@@ -114,6 +136,10 @@ function exportAllDeclarationEntries(node, nativeNodeId, input) {
|
|
|
114
136
|
reExport: true,
|
|
115
137
|
exportStar: !exportedName,
|
|
116
138
|
exportKind: exportedName ? 'namespace-reexport' : 'export-star',
|
|
139
|
+
...moduleHostResourceImportMetadata(moduleSpecifier, {
|
|
140
|
+
hostDependencyBase: 'module-reexport',
|
|
141
|
+
expressionText: `export from ${JSON.stringify(moduleSpecifier)}`
|
|
142
|
+
}),
|
|
117
143
|
...moduleImportAttributeMetadata(node)
|
|
118
144
|
})
|
|
119
145
|
];
|
|
@@ -80,10 +80,18 @@ function exactBranchProjectSemanticEditAdmission(options) {
|
|
|
80
80
|
if (requireOtherBranchUnchanged && !otherProjectBranchUnchanged(file, classification.branch, { requireBase: requireBaseForOtherBranchUnchanged })) return undefined;
|
|
81
81
|
}
|
|
82
82
|
const outputHash = hashProjectSourceText(branchText);
|
|
83
|
+
const admissionOutcome = 'safe';
|
|
84
|
+
const admissionOutcomeReasonCode = exactMergedOutput && allowExistingExactOutput
|
|
85
|
+
? 'existing-exact-branch-output'
|
|
86
|
+
: typeof branchText === 'string'
|
|
87
|
+
? 'exact-branch-output-other-branch-unchanged'
|
|
88
|
+
: 'exact-branch-deletion-other-branch-unchanged';
|
|
83
89
|
const admission = {
|
|
84
90
|
id: safeProjectEvidenceId(`${classification.details?.conflictKey ?? classification.code}_${file.sourcePath}`),
|
|
85
91
|
kind: admissionKind,
|
|
86
92
|
status: 'passed',
|
|
93
|
+
admissionOutcome,
|
|
94
|
+
admissionOutcomeReasonCode,
|
|
87
95
|
branch: classification.branch,
|
|
88
96
|
...(admissionFields(classification) ?? {}),
|
|
89
97
|
sourcePath: file.sourcePath,
|
|
@@ -94,6 +102,8 @@ function exactBranchProjectSemanticEditAdmission(options) {
|
|
|
94
102
|
...(details(classification) ?? {}),
|
|
95
103
|
sourcePath: file.sourcePath,
|
|
96
104
|
outputHash,
|
|
105
|
+
admissionOutcome,
|
|
106
|
+
admissionOutcomeReasonCode,
|
|
97
107
|
exactBranchOutput: true,
|
|
98
108
|
deletedOutput: typeof branchText !== 'string' || undefined,
|
|
99
109
|
autoMergeClaim: false,
|
|
@@ -21,7 +21,7 @@ function fileAdmissionEvidenceRecords(files = []) {
|
|
|
21
21
|
return files.flatMap((file) => [
|
|
22
22
|
...recordArraysWithSuffix(file.summary, 'AdmissionEvidence'),
|
|
23
23
|
...recordArraysWithSuffix(file.metadata, 'Admissions')
|
|
24
|
-
]);
|
|
24
|
+
].map(admissionEvidenceRecordWithOutcome));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function recordArraysWithSuffix(record, suffix) {
|
|
@@ -32,7 +32,35 @@ function recordArraysWithSuffix(record, suffix) {
|
|
|
32
32
|
.filter((value) => value?.kind && value?.id);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function admissionEvidenceRecordWithOutcome(record) {
|
|
36
|
+
const admissionOutcome = admissionEvidenceOutcome(record);
|
|
37
|
+
return compactRecord({
|
|
38
|
+
...record,
|
|
39
|
+
admissionOutcome,
|
|
40
|
+
admissionOutcomeReasonCode: admissionEvidenceOutcomeReasonCode(record, admissionOutcome)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function admissionEvidenceOutcome(record) {
|
|
45
|
+
if (['safe', 'review', 'blocked'].includes(record?.admissionOutcome)) return record.admissionOutcome;
|
|
46
|
+
const routeStatus = record?.admissionRoute?.status;
|
|
47
|
+
if (record?.status === 'passed' && (!routeStatus || routeStatus === 'passed')) return 'safe';
|
|
48
|
+
if (record?.status === 'failed' || routeStatus === 'failed' || routeStatus === 'blocked') return 'blocked';
|
|
49
|
+
return 'review';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function admissionEvidenceOutcomeReasonCode(record, admissionOutcome) {
|
|
53
|
+
if (typeof record?.admissionOutcomeReasonCode === 'string' && record.admissionOutcomeReasonCode.length > 0) return record.admissionOutcomeReasonCode;
|
|
54
|
+
const routeReasonCode = record?.admissionRoute?.reasonCodes?.[0];
|
|
55
|
+
if (typeof record?.details?.reasonCode === 'string' && record.details.reasonCode.length > 0) return record.details.reasonCode;
|
|
56
|
+
if (typeof routeReasonCode === 'string' && routeReasonCode.length > 0) return routeReasonCode;
|
|
57
|
+
if (admissionOutcome === 'safe' && record?.details?.exactBranchOutput === true) return 'passed-exact-branch-output';
|
|
58
|
+
if (admissionOutcome === 'safe') return 'passed-admission-evidence';
|
|
59
|
+
if (admissionOutcome === 'blocked') return 'failed-admission-evidence';
|
|
60
|
+
return 'review-admission-evidence';
|
|
61
|
+
}
|
|
62
|
+
|
|
35
63
|
function isPlainObject(value) { return Boolean(value && typeof value === 'object' && !Array.isArray(value)); }
|
|
36
64
|
function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
|
|
37
65
|
|
|
38
|
-
export { failedEvidenceMissingItems, fileAdmissionEvidenceRecords };
|
|
66
|
+
export { admissionEvidenceOutcome, failedEvidenceMissingItems, fileAdmissionEvidenceRecords };
|
|
@@ -117,6 +117,8 @@ function missingSharedCompilerTypeEquivalenceEvidence(workerRecord, headRecord)
|
|
|
117
117
|
enumMemberCount: record.enumMemberCount,
|
|
118
118
|
enumComputedMemberCount: record.enumComputedMemberCount,
|
|
119
119
|
typeEquivalenceEnumRuntimeShapeHash: record.typeEquivalenceEnumRuntimeShapeHash,
|
|
120
|
+
advancedTypeProofRequirement: record.advancedTypeProofRequirement,
|
|
121
|
+
advancedTypeMissingProof: record.advancedTypeMissingProof,
|
|
120
122
|
typeEquivalenceSignatureSetHash: record.typeEquivalenceSignatureSetHash,
|
|
121
123
|
typeEquivalenceCallSignatureSetHash: record.typeEquivalenceCallSignatureSetHash, typeEquivalenceConstructSignatureSetHash: record.typeEquivalenceConstructSignatureSetHash,
|
|
122
124
|
typeEquivalenceTypeParameterSetHash: record.typeEquivalenceTypeParameterSetHash,
|
|
@@ -169,9 +171,14 @@ function hasPassedCompilerTypeEquivalenceProof(record) {
|
|
|
169
171
|
];
|
|
170
172
|
return Boolean(record?.typeEquivalenceStatus === 'compiler-backed-equivalent'
|
|
171
173
|
&& record?.typeEquivalenceProof?.status === 'passed'
|
|
174
|
+
&& !hasMissingAdvancedTypeProof(record)
|
|
172
175
|
&& requiredHashes.every(([required, hash]) => !required || hash)
|
|
173
176
|
&& record?.apiSignatureHash);
|
|
174
177
|
}
|
|
178
|
+
function hasMissingAdvancedTypeProof(record) {
|
|
179
|
+
const status = record?.advancedTypeMissingProof?.status ?? record?.advancedTypeProofRequirement?.status;
|
|
180
|
+
return status === 'missing-compiler-evidence' || status === 'requires-review';
|
|
181
|
+
}
|
|
175
182
|
function recordsByIdentityKey(records, identityKey) { const result = new Map(); for (const record of records ?? []) { const key = identityKey(record); if (!key || result.has(key)) continue; result.set(key, record); } return result; }
|
|
176
183
|
function optionalFingerprint(record, fingerprint) { return record ? fingerprint(record) : undefined; }
|
|
177
184
|
function stableKey(parts) { const values = parts.map((part) => part === undefined || part === null ? '' : String(part)); return values.some(Boolean) ? values.join('#') : undefined; }
|
|
@@ -5,6 +5,7 @@ const HtmlCssProjectMergeMissingSignals = Object.freeze({
|
|
|
5
5
|
cssSelectorTargetEvidence: 'css-selector-target-evidence-missing',
|
|
6
6
|
htmlStructuralMerge: 'html-structural-merge-proof-blocked',
|
|
7
7
|
cssCascadeMerge: 'css-cascade-merge-proof-blocked',
|
|
8
|
+
cssDependencyGraphEvidence: 'css-dependency-graph-evidence-missing',
|
|
8
9
|
htmlCssBrowserRuntimeProof: 'html-css-browser-runtime-proof-not-available'
|
|
9
10
|
});
|
|
10
11
|
|
|
@@ -16,6 +17,7 @@ function htmlCssProjectMergeMissingEvidenceRoutes(route, signals) {
|
|
|
16
17
|
[signals.cssSelectorTargetEvidence]: route('prove-css-selector-target-evidence', 'layout-style-targets', 'supply-selector-target-graph-and-rebase-evidence'),
|
|
17
18
|
[signals.htmlStructuralMerge]: route('admit-html-structural-merge', 'layout-markup-graph', 'supply-html-parser-reference-and-boundary-evidence'),
|
|
18
19
|
[signals.cssCascadeMerge]: route('admit-css-cascade-merge', 'layout-style-graph', 'supply-css-parser-cascade-and-scope-evidence'),
|
|
20
|
+
[signals.cssDependencyGraphEvidence]: route('prove-css-dependency-graph', 'layout-style-graph', 'supply-css-custom-property-animation-font-and-asset-dependency-graph'),
|
|
19
21
|
[signals.htmlCssBrowserRuntimeProof]: route('prove-html-css-browser-runtime', 'browser-proof', 'attach-browser-runtime-proof-bundle')
|
|
20
22
|
};
|
|
21
23
|
}
|
|
@@ -28,6 +30,7 @@ function htmlCssProjectMergeAdmissionMatrixRows(matrixRow, signals) {
|
|
|
28
30
|
matrixRow('css-selector-target-evidence', 'bounded-evidence', ['css-selector-target-evidence'], [signals.cssSelectorTargetEvidence]),
|
|
29
31
|
matrixRow('html-structural-merge-admission', 'partial', ['html-structural-merge'], [signals.htmlStructuralMerge]),
|
|
30
32
|
matrixRow('css-cascade-merge-admission', 'partial', ['css-cascade-merge'], [signals.cssCascadeMerge]),
|
|
33
|
+
matrixRow('css-dependency-graph-evidence', 'bounded-evidence', ['css-dependency-graph'], [signals.cssDependencyGraphEvidence]),
|
|
31
34
|
matrixRow('html-css-browser-runtime-proof', 'bounded-evidence', ['browser-runtime-proof'], [signals.htmlCssBrowserRuntimeProof])
|
|
32
35
|
];
|
|
33
36
|
}
|
|
@@ -36,10 +39,11 @@ function htmlCssProjectMergeMissingEvidenceItems(summary, signals, missingEviden
|
|
|
36
39
|
const items = [];
|
|
37
40
|
if (summary.htmlFiles && summary.htmlParserEvidenceFiles !== summary.htmlFiles) items.push(missingEvidenceItem({ code: signals.htmlParserEvidence, scope: 'layout-markup-parser', kind: 'html-parser-source-evidence', proofLevel: 'html-parser-source-evidence', action: 'review', summary: `HTML project merge has parser/source evidence for ${summary.htmlParserEvidenceFiles}/${summary.htmlFiles} file(s); require parse5 source spans, zero parse errors, and attribute/trivia spans when those surfaces are present before parser evidence admission.` }));
|
|
38
41
|
if (summary.cssFiles && summary.cssParserEvidenceFiles !== summary.cssFiles) items.push(missingEvidenceItem({ code: signals.cssParserEvidence, scope: 'layout-style-parser', kind: 'css-parser-source-evidence', proofLevel: 'css-parser-source-evidence', action: 'review', summary: `CSS project merge has parser/source evidence for ${summary.cssParserEvidenceFiles}/${summary.cssFiles} file(s); require PostCSS rule/declaration spans, raw trivia hashes, and zero parse errors before parser evidence admission.` }));
|
|
39
|
-
if (summary.htmlFiles && summary.htmlIdentityEvidenceFiles !== summary.htmlFiles) items.push(missingEvidenceItem({ code: signals.htmlIdentityEvidence, scope: 'layout-markup-identity', kind: 'html-identity-evidence', proofLevel: 'html-identity-evidence', action: 'review', summary: `HTML project merge has structural identity evidence for ${summary.htmlIdentityEvidenceFiles}/${summary.htmlFiles} file(s); require parser-backed structural spans and stable explicit/path identity accounting before structural admission.` }));
|
|
42
|
+
if (summary.htmlFiles && summary.htmlIdentityEvidenceFiles !== summary.htmlFiles) items.push(missingEvidenceItem({ code: signals.htmlIdentityEvidence, scope: 'layout-markup-identity', kind: 'html-identity-evidence', proofLevel: 'html-identity-evidence', action: 'review', summary: `HTML project merge has structural identity evidence for ${summary.htmlIdentityEvidenceFiles}/${summary.htmlFiles} file(s) with ${summary.htmlDuplicateIdentityEvidenceFiles ?? 0} duplicate identity evidence file(s); require parser-backed structural spans, unique explicit identity keys, and stable explicit/path identity accounting before structural admission.` }));
|
|
40
43
|
if (summary.cssFiles && (summary.cssSelectorTargetEvidenceFiles !== summary.cssFiles || summary.cssSelectorTargetConflictFiles)) items.push(missingEvidenceItem({ code: signals.cssSelectorTargetEvidence, scope: 'layout-style-targets', kind: 'css-selector-target-evidence', proofLevel: 'css-selector-target-evidence', action: 'review', summary: `CSS project merge has selector-target evidence for ${summary.cssSelectorTargetEvidenceFiles}/${summary.cssFiles} file(s) with ${summary.cssSelectorTargetConflictFiles} selector target conflict(s); require selector target graph and proven rebase before target-moving merges.` }));
|
|
41
44
|
if (summary.htmlBlockedFiles) items.push(missingEvidenceItem({ code: signals.htmlStructuralMerge, scope: 'layout-markup-graph', kind: 'html-structural-merge-proof', proofLevel: 'html-structural-merge', action: 'review', summary: `HTML project merge has ${summary.htmlBlockedFiles} blocked file(s); supply parser/source-span evidence, stable element identity, and runtime-boundary proof before admission.` }));
|
|
42
|
-
if (summary.cssBlockedFiles) items.push(missingEvidenceItem({ code: signals.cssCascadeMerge, scope: 'layout-style-graph', kind: 'css-cascade-merge-proof', proofLevel: 'css-cascade-merge', action: 'review', summary: `CSS project merge has ${summary.cssBlockedFiles} blocked file(s); supply parser/cascade/scope evidence and keep browser claims false until runtime proof passes.` }));
|
|
45
|
+
if (summary.cssBlockedFiles) items.push(missingEvidenceItem({ code: signals.cssCascadeMerge, scope: 'layout-style-graph', kind: 'css-cascade-merge-proof', proofLevel: 'css-cascade-merge', action: 'review', summary: `CSS project merge has ${summary.cssBlockedFiles} blocked file(s), including ${summary.cssScopedCascadeBlockedFiles} scoped cascade proof block(s); supply parser/cascade/scope evidence, require scopedCascadeGraphHash for changed declarations under @media/@supports/@container/@layer/@scope or nested scopes, and keep browser claims false until runtime proof passes.` }));
|
|
46
|
+
if (summary.cssDependencySurfaceFiles && (summary.cssDependencyGraphEvidenceFiles !== summary.cssDependencySurfaceFiles || summary.cssDependencyGraphMissingProofFiles || summary.cssDependencyGraphBlockedFiles)) items.push(missingEvidenceItem({ code: signals.cssDependencyGraphEvidence, scope: 'layout-style-graph', kind: 'css-dependency-graph-evidence', proofLevel: 'css-dependency-graph', action: 'review', summary: `CSS project merge has dependency graph evidence for ${summary.cssDependencyGraphEvidenceFiles}/${summary.cssDependencySurfaceFiles} dependency-surface file(s), ${summary.cssDependencyGraphMissingProofFiles} missing dependency graph proof(s), and ${summary.cssDependencyGraphBlockedFiles} dependency blocker(s); require custom property, var() fallback, @keyframes/animation-name, @font-face, and url() asset graph evidence before cascade/browser equivalence claims.`, suggestedInput: { cssDependencyGraphEvidence: true } }));
|
|
43
47
|
if (summary.htmlCssMergedFiles && !summary.htmlCssBrowserRuntimeProofs) items.push(missingEvidenceItem({ code: signals.htmlCssBrowserRuntimeProof, scope: 'browser-proof', kind: 'browser-runtime-proof', proofLevel: 'browser-runtime-proof', action: 'review', summary: 'HTML/CSS structural source merge was available, but browser DOM/cascade/layout/runtime proof was not attached; keep browser equivalence claims false.', suggestedInput: { browserRuntimeProof: true } }));
|
|
44
48
|
return items;
|
|
45
49
|
}
|
|
@@ -51,6 +55,7 @@ function htmlCssProjectMergeMatrixProofStatus(level, summary) {
|
|
|
51
55
|
if (level === 'css-selector-target-evidence') return summary.cssFiles ? (summary.cssSelectorTargetConflictFiles ? 'failed' : summary.cssSelectorTargetEvidenceFiles === summary.cssFiles ? 'passed' : 'missing') : 'absent';
|
|
52
56
|
if (level === 'html-structural-merge') return summary.htmlFiles ? (summary.htmlBlockedFiles ? 'failed' : summary.htmlMergedFiles ? 'passed' : 'missing') : 'absent';
|
|
53
57
|
if (level === 'css-cascade-merge') return summary.cssFiles ? (summary.cssBlockedFiles ? 'failed' : summary.cssMergedFiles ? 'passed' : 'missing') : 'absent';
|
|
58
|
+
if (level === 'css-dependency-graph') return summary.cssDependencySurfaceFiles ? (summary.cssDependencyGraphBlockedFiles ? 'failed' : summary.cssDependencyGraphEvidenceFiles === summary.cssDependencySurfaceFiles ? 'passed' : 'missing') : 'absent';
|
|
54
59
|
if (level === 'browser-runtime-proof') return summary.htmlCssFiles ? (summary.htmlCssBrowserRuntimeProofs ? 'passed' : 'missing') : 'absent';
|
|
55
60
|
return undefined;
|
|
56
61
|
}
|
|
@@ -7,8 +7,13 @@ function htmlCssProjectSummary(files) {
|
|
|
7
7
|
htmlParserEvidenceFiles: htmlFiles.filter(hasHtmlParserEvidence).length, cssParserEvidenceFiles: cssFiles.filter(hasCssParserEvidence).length, htmlCssParserEvidenceFiles: htmlCssFiles.filter((file) => hasHtmlParserEvidence(file) || hasCssParserEvidence(file)).length,
|
|
8
8
|
htmlParserEvidenceFailedFiles: htmlFiles.filter(hasParserEvidenceFailure).length, cssParserEvidenceFailedFiles: cssFiles.filter(hasParserEvidenceFailure).length, htmlCssParserEvidenceFailedFiles: htmlCssFiles.filter(hasParserEvidenceFailure).length,
|
|
9
9
|
htmlIdentityEvidenceFiles: htmlFiles.filter(hasHtmlIdentityEvidence).length, cssSelectorTargetEvidenceFiles: cssFiles.filter(hasCssSelectorTargetEvidence).length, htmlCssStructuralTargetEvidenceFiles: htmlCssFiles.filter((file) => hasHtmlIdentityEvidence(file) || hasCssSelectorTargetEvidence(file)).length,
|
|
10
|
+
cssSelectorTargetGraphEvidenceFiles: cssFiles.filter(hasCssSelectorTargetGraphEvidence).length, cssSelectorSpecificityEvidenceFiles: cssFiles.filter(hasCssSelectorSpecificityEvidence).length, cssSelectorTargetMoveFiles: cssFiles.filter(hasCssSelectorTargetMove).length,
|
|
11
|
+
htmlExplicitIdentityEvidenceFiles: htmlFiles.filter(hasHtmlExplicitIdentityEvidence).length, htmlPathOnlyIdentityResidualFiles: htmlFiles.filter(hasHtmlPathOnlyIdentityResidual).length, htmlDuplicateIdentityEvidenceFiles: htmlFiles.filter(hasHtmlDuplicateIdentityEvidence).length, htmlDuplicateIdentityKeys: htmlFiles.reduce((sum, file) => sum + htmlDuplicateIdentityKeyCount(file), 0),
|
|
12
|
+
htmlRuntimeBoundaryEvidenceFiles: htmlFiles.filter(hasHtmlRuntimeBoundaryEvidence).length, htmlFrameworkBoundaryEvidenceFiles: htmlFiles.filter(hasHtmlFrameworkBoundaryEvidence).length, htmlProofGapBlockedFiles: htmlFiles.filter(hasHtmlProofGapBlockedConflict).length,
|
|
10
13
|
htmlIdentityEvidenceFailedFiles: htmlFiles.filter(hasHtmlIdentityEvidenceFailure).length, cssSelectorTargetConflictFiles: cssFiles.filter(hasCssSelectorTargetConflict).length, htmlCssStructuralTargetEvidenceFailedFiles: htmlCssFiles.filter((file) => hasHtmlIdentityEvidenceFailure(file) || hasCssSelectorTargetConflict(file)).length,
|
|
11
14
|
cssSelectorTargetRebasedFiles: cssFiles.filter(hasCssSelectorTargetRebase).length,
|
|
15
|
+
cssScopedCascadeFiles: cssFiles.filter(hasCssScopedCascadeScope).length, cssScopedCascadeEvidenceFiles: cssFiles.filter(hasCssScopedCascadeEvidence).length, cssScopedCascadeBlockedFiles: cssFiles.filter(hasCssScopedCascadeMissingProof).length,
|
|
16
|
+
cssDependencySurfaceFiles: cssFiles.filter(hasCssDependencySurface).length, cssDependencyGraphEvidenceFiles: cssFiles.filter((file) => hasCssDependencySurface(file) && hasCssDependencyGraphEvidence(file)).length, cssDependencyGraphMissingProofFiles: cssFiles.filter(hasCssDependencyGraphMissingProof).length, cssDependencyGraphBlockedFiles: cssFiles.filter(hasCssDependencyGraphBlockedConflict).length,
|
|
12
17
|
htmlCssBrowserRuntimeProofs: htmlCssFiles.filter(hasBrowserRuntimeProof).length
|
|
13
18
|
};
|
|
14
19
|
}
|
|
@@ -29,23 +34,126 @@ function hasCssParserEvidence(file) {
|
|
|
29
34
|
function hasParserEvidenceFailure(file) { return (file?.result?.parserEvidence?.parseErrors ?? 0) > 0; }
|
|
30
35
|
function hasHtmlIdentityEvidence(file) {
|
|
31
36
|
const evidence = file?.result?.identityEvidence;
|
|
32
|
-
return evidence?.parserBackedStructuralSpans === true && evidence.structuralAddressability === true;
|
|
37
|
+
return evidence?.parserBackedStructuralSpans === true && evidence.structuralAddressability === true && !hasDuplicateExplicitIdentityKeys(evidence);
|
|
38
|
+
}
|
|
39
|
+
function hasHtmlExplicitIdentityEvidence(file) {
|
|
40
|
+
const evidence = file?.result?.identityEvidence;
|
|
41
|
+
return hasHtmlIdentityEvidence(file) && evidence.explicitIdentityAvailable === true;
|
|
42
|
+
}
|
|
43
|
+
function hasHtmlPathOnlyIdentityResidual(file) { return (file?.result?.identityEvidence?.pathOnlyIdentityElements ?? 0) > 0; }
|
|
44
|
+
function hasHtmlRuntimeBoundaryEvidence(file) {
|
|
45
|
+
return (file?.result?.identityEvidence?.runtimeBoundaryElements ?? 0) > 0 || hasRuntimeBoundaryConflict(file);
|
|
46
|
+
}
|
|
47
|
+
function hasHtmlFrameworkBoundaryEvidence(file) {
|
|
48
|
+
return (file?.result?.identityEvidence?.frameworkBoundaryElements ?? 0) > 0 || hasFrameworkBoundaryConflict(file);
|
|
33
49
|
}
|
|
34
50
|
function hasHtmlIdentityEvidenceFailure(file) {
|
|
35
51
|
const evidence = file?.result?.identityEvidence;
|
|
36
|
-
return Boolean(evidence) && (evidence.parserBackedStructuralSpans !== true || evidence.structuralAddressability !== true);
|
|
52
|
+
return Boolean(evidence) && (evidence.parserBackedStructuralSpans !== true || evidence.structuralAddressability !== true || hasDuplicateExplicitIdentityKeys(evidence));
|
|
37
53
|
}
|
|
38
54
|
function hasCssSelectorTargetEvidence(file) {
|
|
39
55
|
const evidence = file?.result?.selectorTargetEvidence;
|
|
40
56
|
return evidence?.parserBackedRuleSpans === true;
|
|
41
57
|
}
|
|
58
|
+
function hasCssSelectorTargetGraphEvidence(file) {
|
|
59
|
+
return file?.result?.selectorTargetEvidence?.selectorTargetGraphHashPresent === true;
|
|
60
|
+
}
|
|
61
|
+
function hasCssSelectorSpecificityEvidence(file) {
|
|
62
|
+
const sides = Object.values(file?.result?.selectorTargetEvidence?.sides ?? {});
|
|
63
|
+
return sides.length > 0 && sides.every((side) => (side?.ruleCount ?? 0) === 0 || (side?.selectorSpecificityRecords ?? 0) >= side.ruleCount);
|
|
64
|
+
}
|
|
65
|
+
function hasCssSelectorTargetMove(file) {
|
|
66
|
+
return (file?.result?.selectorTargetEvidence?.selectorMoveCount ?? 0) > 0;
|
|
67
|
+
}
|
|
42
68
|
function hasCssSelectorTargetConflict(file) {
|
|
43
69
|
return (file?.result?.conflicts ?? file?.conflicts ?? []).some((conflict) => conflict.code === 'css-selector-target-conflict');
|
|
44
70
|
}
|
|
71
|
+
function hasHtmlProofGapBlockedConflict(file) {
|
|
72
|
+
return (file?.result?.conflicts ?? file?.conflicts ?? []).some((conflict) => conflict.code === 'html-proof-gap-blocked');
|
|
73
|
+
}
|
|
74
|
+
function hasRuntimeBoundaryConflict(file) {
|
|
75
|
+
return (file?.result?.conflicts ?? file?.conflicts ?? []).some((conflict) => HtmlRuntimeBoundaryReasonCodes.has(conflict?.details?.reasonCode));
|
|
76
|
+
}
|
|
77
|
+
function hasFrameworkBoundaryConflict(file) {
|
|
78
|
+
return (file?.result?.conflicts ?? file?.conflicts ?? []).some((conflict) => HtmlFrameworkBoundaryReasonCodes.has(conflict?.details?.reasonCode));
|
|
79
|
+
}
|
|
45
80
|
function hasCssSelectorTargetRebase(file) { return (file?.result?.selectorTargetEvidence?.rebasedChangeCount ?? 0) > 0; }
|
|
81
|
+
function hasCssScopedCascadeScope(file) {
|
|
82
|
+
return cssScopedRuleCount(file) > 0 || hasCssScopedCascadeProofReason(file) || file?.result?.parserEvidence?.scopedCascadeGraphHashPresent === false;
|
|
83
|
+
}
|
|
84
|
+
function hasCssScopedCascadeEvidence(file) {
|
|
85
|
+
return hasCssScopedCascadeScope(file) && !hasCssScopedCascadeMissingProof(file) && file?.result?.parserEvidence?.scopedCascadeGraphHashPresent === true;
|
|
86
|
+
}
|
|
87
|
+
function hasCssScopedCascadeMissingProof(file) {
|
|
88
|
+
return hasCssScopedCascadeProofReason(file);
|
|
89
|
+
}
|
|
90
|
+
function hasCssScopedCascadeProofReason(file) {
|
|
91
|
+
return (file?.result?.conflicts ?? file?.conflicts ?? []).some((conflict) => ScopedCascadeMissingProofReasonCodes.has(conflict?.details?.reasonCode));
|
|
92
|
+
}
|
|
93
|
+
function cssScopedRuleCount(file) {
|
|
94
|
+
const sides = Object.values(file?.result?.selectorTargetEvidence?.sides ?? {});
|
|
95
|
+
return sides.reduce((count, side) => Math.max(count, side?.scopedRuleCount ?? 0), 0);
|
|
96
|
+
}
|
|
97
|
+
function hasCssDependencySurface(file) {
|
|
98
|
+
return hasCssDependencyEvidenceSurface(file) || hasCssDependencyTextSurface(file) || hasCssDependencyConflictSurface(file);
|
|
99
|
+
}
|
|
100
|
+
function hasCssDependencyGraphEvidence(file) {
|
|
101
|
+
return cssDependencyEvidenceRecords(file).some((evidence) => evidence.dependencyGraphHashPresent === true || evidence.cssDependencyGraphHashPresent === true || typeof evidence.dependencyGraphHash === 'string' || typeof evidence.cssDependencyGraphHash === 'string' || typeof evidence.graphHash === 'string' || typeof evidence.customPropertyGraphHash === 'string' || typeof evidence.animationGraphHash === 'string' || typeof evidence.fontFaceGraphHash === 'string' || typeof evidence.assetGraphHash === 'string');
|
|
102
|
+
}
|
|
103
|
+
function hasCssDependencyGraphMissingProof(file) { return hasCssDependencySurface(file) && !hasCssDependencyGraphEvidence(file); }
|
|
104
|
+
function hasCssDependencyEvidenceSurface(file) {
|
|
105
|
+
return cssDependencyEvidenceRecords(file).some((evidence) => evidence.hasDependencySurface === true || (evidence.dependencySurfaceCount ?? 0) > 0 || (evidence.customPropertyDefinitions ?? 0) > 0 || (evidence.customPropertyReferences ?? 0) > 0 || (evidence.varReferences ?? 0) > 0 || (evidence.varFallbackReferences ?? 0) > 0 || (evidence.animationNameLinks ?? 0) > 0 || (evidence.keyframeLinks ?? 0) > 0 || (evidence.fontFaceLinks ?? 0) > 0 || (evidence.urlAssetReferences ?? 0) > 0);
|
|
106
|
+
}
|
|
107
|
+
function hasCssDependencyTextSurface(file) {
|
|
108
|
+
const sourceText = cssDependencySourceText(file);
|
|
109
|
+
return sourceText.length > 0 && CssDependencySurfacePatterns.some((pattern) => pattern.test(sourceText));
|
|
110
|
+
}
|
|
111
|
+
function hasCssDependencyConflictSurface(file) {
|
|
112
|
+
return cssFileConflicts(file).some(isCssDependencyConflict);
|
|
113
|
+
}
|
|
114
|
+
function hasCssDependencyGraphBlockedConflict(file) {
|
|
115
|
+
return cssFileConflicts(file).some((conflict) => isCssDependencyConflict(conflict) || CssDependencyMissingProofReasonCodes.has(conflict?.details?.reasonCode));
|
|
116
|
+
}
|
|
117
|
+
function cssDependencyEvidenceRecords(file) {
|
|
118
|
+
const result = file?.result ?? {};
|
|
119
|
+
return [result.dependencyEvidence, result.cssDependencyEvidence, result.dependencyGraphEvidence, result.cssDependencyGraphEvidence, result.parserEvidence?.dependencyEvidence, result.parserEvidence?.cssDependencyEvidence, result.parserEvidence?.dependencyGraphEvidence, result.parserEvidence?.cssDependencyGraphEvidence].filter(isPlainObject);
|
|
120
|
+
}
|
|
121
|
+
function cssDependencySourceText(file) {
|
|
122
|
+
return [file?.outputSourceText, file?.sourceText, file?.result?.mergedSourceText].filter((value) => typeof value === 'string').join('\n');
|
|
123
|
+
}
|
|
124
|
+
function cssFileConflicts(file) { return file?.result?.conflicts ?? file?.conflicts ?? []; }
|
|
125
|
+
function isCssDependencyConflict(conflict) {
|
|
126
|
+
const details = conflict?.details ?? {};
|
|
127
|
+
const codes = [conflict?.code, details.reasonCode, details.proofGap?.code].map((value) => String(value ?? ''));
|
|
128
|
+
return codes.some((code) => CssDependencyCodeFragments.some((fragment) => code.includes(fragment))) || isCssDependencyAtRule(details.before) || isCssDependencyAtRule(details.after);
|
|
129
|
+
}
|
|
130
|
+
function isCssDependencyAtRule(shape) { return CssDependencyAtRuleNames.has(String(shape?.atRuleName ?? '').toLowerCase()); }
|
|
46
131
|
function hasBrowserRuntimeProof(file) {
|
|
47
132
|
const admission = file?.result?.admission ?? file?.admission ?? {};
|
|
48
133
|
return admission.browserRuntimeEquivalenceClaim === true || admission.browserCascadeEquivalenceClaim === true || admission.browserRenderEquivalenceClaim === true;
|
|
49
134
|
}
|
|
135
|
+
function hasHtmlDuplicateIdentityEvidence(file) { return hasDuplicateExplicitIdentityKeys(file?.result?.identityEvidence); }
|
|
136
|
+
function htmlDuplicateIdentityKeyCount(file) {
|
|
137
|
+
return duplicateExplicitIdentityKeys(file?.result?.identityEvidence).length;
|
|
138
|
+
}
|
|
139
|
+
function hasDuplicateExplicitIdentityKeys(evidence) {
|
|
140
|
+
return duplicateExplicitIdentityKeys(evidence).length > 0;
|
|
141
|
+
}
|
|
142
|
+
function duplicateExplicitIdentityKeys(evidence) {
|
|
143
|
+
return Object.values(evidence?.sides ?? {}).flatMap((side) => {
|
|
144
|
+
const counts = new Map();
|
|
145
|
+
for (const key of side?.explicitIdentityKeys ?? []) counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
146
|
+
return [...counts.entries()].filter(([, count]) => count > 1).map(([key]) => key);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const ScopedCascadeMissingProofReasonCodes = new Set(['css-scoped-cascade-equivalence-unproved', 'css-media-cascade-scope-unproved', 'css-supports-cascade-scope-unproved', 'css-container-cascade-scope-unproved', 'css-layer-cascade-scope-unproved', 'css-scope-cascade-scope-unproved']);
|
|
151
|
+
const HtmlRuntimeBoundaryReasonCodes = new Set(['script-runtime-boundary', 'style-runtime-boundary', 'template-runtime-boundary', 'slot-runtime-boundary', 'custom-element-runtime-boundary', 'event-handler-runtime-boundary', 'inline-style-runtime-boundary', 'iframe-runtime-boundary', 'iframe-srcdoc-runtime-boundary']);
|
|
152
|
+
const HtmlFrameworkBoundaryReasonCodes = new Set(['framework-directive-boundary', 'custom-element-runtime-boundary']);
|
|
153
|
+
const CssDependencyMissingProofReasonCodes = new Set(['css-dependency-graph-evidence-missing', 'css-custom-property-dependency-graph-unproved', 'css-var-fallback-dependency-graph-unproved', 'css-animation-name-keyframes-graph-unproved', 'css-font-face-dependency-graph-unproved', 'css-url-asset-dependency-graph-unproved']);
|
|
154
|
+
const CssDependencyAtRuleNames = new Set(['keyframes', 'font-face']);
|
|
155
|
+
const CssDependencyCodeFragments = ['custom-property', 'var-fallback', 'variable-dependency', 'dependency-graph', 'keyframes', 'animation-name', 'font-face', 'url-asset', 'asset-dependency'];
|
|
156
|
+
const CssDependencySurfacePatterns = [/(^|[;{\s])--[-_A-Za-z][\w-]*\s*:/, /\bvar\s*\(/i, /@(?:-[\w]+-)?keyframes\b/i, /(^|[;{\s])animation(?:-name)?\s*:/i, /@font-face\b/i, /\burl\s*\(/i];
|
|
157
|
+
function isPlainObject(value) { return Boolean(value && typeof value === 'object' && !Array.isArray(value)); }
|
|
50
158
|
|
|
51
159
|
export { htmlCssProjectSummary };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { safeMergeCssSource } from '@shapeshift-labs/frontier-lang-css';
|
|
2
2
|
import { safeMergeHtmlSource } from '@shapeshift-labs/frontier-lang-html';
|
|
3
3
|
import { compactRecord } from './js-ts-safe-merge-context.js';
|
|
4
|
-
import { hashText, safeId } from './js-ts-safe-project-merge-core.js';
|
|
4
|
+
import { hashText, safeId, uniqueStrings } from './js-ts-safe-project-merge-core.js';
|
|
5
5
|
|
|
6
6
|
function projectFileLanguage(file, input) {
|
|
7
7
|
return file.language ?? inferLanguageFromPath(file.sourcePath) ?? input.language ?? 'typescript';
|
|
@@ -12,8 +12,12 @@ function maybeMergeHtmlCssProjectFile(options) {
|
|
|
12
12
|
const language = String(context.language ?? '').toLowerCase();
|
|
13
13
|
const merge = language === 'html' ? safeMergeHtmlSource : language === 'css' ? safeMergeCssSource : undefined;
|
|
14
14
|
if (!merge) return undefined;
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const resultId = `${projectId}_${safeId(file.sourcePath)}`;
|
|
16
|
+
const result = merge({ ...sourceInput, ...htmlCssMergeOptionsForProjectFile(input, file.sourcePath, language), ...context, id: resultId, baseSourceText: base, workerSourceText: worker, headSourceText: head });
|
|
17
|
+
const admittedResult = language === 'html' && result.status === 'merged'
|
|
18
|
+
? blockHtmlProofGapChanges({ result, id: resultId, sourcePath: file.sourcePath, base, worker, head }) ?? result
|
|
19
|
+
: result;
|
|
20
|
+
return admittedResult.status === 'merged' ? mergedHtmlCssFile(file, context, admittedResult, language) : blockedHtmlCssFile(file, context, admittedResult);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
function inferLanguageFromPath(sourcePath) {
|
|
@@ -32,6 +36,136 @@ function htmlCssMergeOptionsForProjectFile(input, sourcePath, language) {
|
|
|
32
36
|
return compactRecord({ ...(language === 'css' ? input.cssMergeOptions ?? input.styleMergeOptions : input.htmlMergeOptions ?? input.markupMergeOptions), ...(byPath?.[sourcePath] ?? {}) });
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
function blockHtmlProofGapChanges({ result, id, sourcePath, base, worker, head }) {
|
|
40
|
+
const conflicts = [
|
|
41
|
+
htmlDuplicateIdentityConflict(result),
|
|
42
|
+
...htmlRuntimeBoundaryChanges(base, worker, head).map((change) => htmlProofGapConflict(id, sourcePath, change.reasonCode, change))
|
|
43
|
+
].filter(Boolean);
|
|
44
|
+
if (!conflicts.length) return undefined;
|
|
45
|
+
const allConflicts = [...(result.conflicts ?? []), ...conflicts];
|
|
46
|
+
const { mergedSourceText, mergedSourceHash, ...rest } = result;
|
|
47
|
+
return compactRecord({
|
|
48
|
+
...rest,
|
|
49
|
+
status: 'blocked',
|
|
50
|
+
operation: 'blocked',
|
|
51
|
+
conflicts: allConflicts,
|
|
52
|
+
admission: blockedHtmlProofGapAdmission(result.admission, allConflicts),
|
|
53
|
+
autoMergeClaim: false,
|
|
54
|
+
semanticEquivalenceClaim: false,
|
|
55
|
+
browserRuntimeEquivalenceClaim: false
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function htmlDuplicateIdentityConflict(result) {
|
|
60
|
+
const duplicates = duplicateHtmlExplicitIdentityKeys(result?.identityEvidence);
|
|
61
|
+
if (!duplicates.length) return undefined;
|
|
62
|
+
return htmlProofGapConflict(result.id, result.sourcePath, 'html-duplicate-explicit-identity', {
|
|
63
|
+
boundary: 'html-explicit-identity',
|
|
64
|
+
duplicateIdentityKeys: duplicates
|
|
65
|
+
}, 'html-duplicate-identity-blocked');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function duplicateHtmlExplicitIdentityKeys(identityEvidence) {
|
|
69
|
+
return Object.entries(identityEvidence?.sides ?? {}).flatMap(([side, evidence]) => {
|
|
70
|
+
const counts = new Map();
|
|
71
|
+
for (const key of evidence?.explicitIdentityKeys ?? []) counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
72
|
+
return [...counts.entries()]
|
|
73
|
+
.filter(([, count]) => count > 1)
|
|
74
|
+
.map(([key, count]) => ({ side, key, count }));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function htmlRuntimeBoundaryChanges(base, worker, head) {
|
|
79
|
+
const baseEventHandlers = htmlEventHandlerBoundaryFingerprint(base);
|
|
80
|
+
return [
|
|
81
|
+
htmlRuntimeBoundaryChange('worker', base, worker, baseEventHandlers),
|
|
82
|
+
htmlRuntimeBoundaryChange('head', base, head, baseEventHandlers)
|
|
83
|
+
].filter(Boolean);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function htmlRuntimeBoundaryChange(side, base, sourceText, baseEventHandlers) {
|
|
87
|
+
if (sourceText === base) return undefined;
|
|
88
|
+
const eventHandlers = htmlEventHandlerBoundaryFingerprint(sourceText);
|
|
89
|
+
if (eventHandlers === baseEventHandlers) return undefined;
|
|
90
|
+
return {
|
|
91
|
+
side,
|
|
92
|
+
reasonCode: 'event-handler-runtime-boundary',
|
|
93
|
+
boundary: 'html-event-handler-attribute',
|
|
94
|
+
boundaryAttributes: htmlEventHandlerBoundaryAttributeNames(sourceText)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function htmlEventHandlerBoundaryFingerprint(sourceText) {
|
|
99
|
+
return htmlEventHandlerBoundaryAttributes(sourceText)
|
|
100
|
+
.map((attribute) => `${attribute.tagName}:${attribute.name}=${String(attribute.value)}`)
|
|
101
|
+
.sort()
|
|
102
|
+
.join('\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function htmlEventHandlerBoundaryAttributeNames(sourceText) {
|
|
106
|
+
return uniqueStrings(htmlEventHandlerBoundaryAttributes(sourceText).map((attribute) => attribute.name));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function htmlEventHandlerBoundaryAttributes(sourceText) {
|
|
110
|
+
const attributes = [];
|
|
111
|
+
for (const tag of String(sourceText ?? '').matchAll(/<[A-Za-z][\w:-]*(?:\s+[^<>]*?)?\/?>/g)) {
|
|
112
|
+
const parsed = /^<([A-Za-z][\w:-]*)([\s\S]*?)\/?>$/.exec(tag[0]);
|
|
113
|
+
if (!parsed) continue;
|
|
114
|
+
const tagName = parsed[1].toLowerCase();
|
|
115
|
+
for (const attribute of parseHtmlAttributes(parsed[2] ?? '')) {
|
|
116
|
+
if (/^on[\w:.-]+$/i.test(attribute.name)) attributes.push({ ...attribute, name: attribute.name.toLowerCase(), tagName });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return attributes;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseHtmlAttributes(text) {
|
|
123
|
+
const attributes = [];
|
|
124
|
+
const pattern = /([:@A-Za-z_][\w:.-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
125
|
+
for (const match of text.matchAll(pattern)) attributes.push({ name: match[1], value: match[2] ?? match[3] ?? match[4] ?? true });
|
|
126
|
+
return attributes;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function htmlProofGapConflict(id, sourcePath, reasonCode, details = {}, code = 'html-proof-gap-blocked') {
|
|
130
|
+
return {
|
|
131
|
+
code,
|
|
132
|
+
gateId: 'html-semantic-merge',
|
|
133
|
+
sourcePath,
|
|
134
|
+
details: compactRecord({
|
|
135
|
+
reasonCode,
|
|
136
|
+
conflictKey: `html#${id}#${reasonCode}#${details.side ?? details.duplicateIdentityKeys?.[0]?.key ?? sourcePath ?? 'source'}`,
|
|
137
|
+
proofGap: {
|
|
138
|
+
code: reasonCode,
|
|
139
|
+
status: 'not-claimed',
|
|
140
|
+
summary: htmlProofGapSummary(reasonCode),
|
|
141
|
+
failClosed: true,
|
|
142
|
+
semanticEquivalenceClaim: false
|
|
143
|
+
},
|
|
144
|
+
...details
|
|
145
|
+
})
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function htmlProofGapSummary(reasonCode) {
|
|
150
|
+
if (reasonCode === 'html-duplicate-explicit-identity') return 'Duplicate explicit HTML identity keys make structural target admission ambiguous.';
|
|
151
|
+
if (reasonCode === 'event-handler-runtime-boundary') return 'HTML event handler attributes execute in the browser runtime and require source-bound host evidence.';
|
|
152
|
+
return 'HTML proof gap requires source-bound evidence before structural merge admission.';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function blockedHtmlProofGapAdmission(admission = {}, conflicts = []) {
|
|
156
|
+
return {
|
|
157
|
+
...admission,
|
|
158
|
+
status: 'blocked',
|
|
159
|
+
action: 'human-review',
|
|
160
|
+
reviewRequired: true,
|
|
161
|
+
autoApplyCandidate: false,
|
|
162
|
+
autoMergeClaim: false,
|
|
163
|
+
semanticEquivalenceClaim: false,
|
|
164
|
+
browserRuntimeEquivalenceClaim: false,
|
|
165
|
+
reasonCodes: uniqueStrings([...(admission.reasonCodes ?? []), ...conflicts.map((conflict) => conflict.details?.reasonCode ?? conflict.code)])
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
35
169
|
function mergedHtmlCssFile(file, context, result, language) {
|
|
36
170
|
return compactRecord({
|
|
37
171
|
kind: 'frontier.lang.jsTsProjectSafeMergeFile', version: 1, sourcePath: file.sourcePath, language: context.language, status: 'merged', operation: `merged-${language}-source`,
|
|
@@ -98,6 +98,15 @@ function jsxPropDetails(record) {
|
|
|
98
98
|
propValueReferenceRoot: record.propValueReferenceRoot, propValueReferencePath: record.propValueReferencePath,
|
|
99
99
|
propValueOptionalReference: record.propValueOptionalReference, propValueOptionalReferenceSegments: record.propValueOptionalReferenceSegments,
|
|
100
100
|
propValueOptionalReferenceSegmentIndexes: record.propValueOptionalReferenceSegmentIndexes, propValueOptionalNullishBoundaryCount: record.propValueOptionalNullishBoundaryCount,
|
|
101
|
+
propValueClaimScope: record.propValueClaimScope, propValueRenderEquivalenceClaim: record.propValueRenderEquivalenceClaim,
|
|
102
|
+
propValueStaticSpreadSourceKind: record.propValueStaticSpreadSourceKind, propValueStaticSpreadSourceName: record.propValueStaticSpreadSourceName,
|
|
103
|
+
propValueStaticSpreadPropEntries: record.propValueStaticSpreadPropEntries, propValueStaticSpreadPropNames: record.propValueStaticSpreadPropNames,
|
|
104
|
+
propValueStaticSpreadPropCount: record.propValueStaticSpreadPropCount,
|
|
105
|
+
propValueStaticSpreadEffectivePropEntries: record.propValueStaticSpreadEffectivePropEntries, propValueStaticSpreadEffectivePropNames: record.propValueStaticSpreadEffectivePropNames,
|
|
106
|
+
propValueStaticSpreadExplicitOverridePropNames: record.propValueStaticSpreadExplicitOverridePropNames,
|
|
107
|
+
propValueStaticSpreadOverridesExplicitPropNames: record.propValueStaticSpreadOverridesExplicitPropNames,
|
|
108
|
+
propValueStaticSpreadDuplicatePropNames: record.propValueStaticSpreadDuplicatePropNames,
|
|
109
|
+
propValueStaticSpreadPrecedenceStatus: record.propValueStaticSpreadPrecedenceStatus,
|
|
101
110
|
propValueDynamicText: record.propValueDynamicText, propValueDynamicBlockerReasonCode: record.propValueDynamicBlockerReasonCode,
|
|
102
111
|
propValueExpressionHash: record.propValueExpressionHash, propValueSignatureHash: record.propValueSignatureHash,
|
|
103
112
|
componentPropRenderFlowStatus: record.componentPropRenderFlowStatus, componentPropRenderFlowReasonCode: record.componentPropRenderFlowReasonCode,
|
|
@@ -259,6 +259,9 @@ function mergeProjectFile(file, input, projectId, projectSymbolRenames) {
|
|
|
259
259
|
policy: file.policy ?? file.mergePolicy ?? policyForFile(input, file.sourcePath)
|
|
260
260
|
});
|
|
261
261
|
if (result.status !== 'merged') {
|
|
262
|
+
if (base === worker && base === head) {
|
|
263
|
+
return syntheticFile(file, context, base, 'unchanged-identical');
|
|
264
|
+
}
|
|
262
265
|
return maybeMergeImportSpecifierRemovalFile(file, context, result, input)
|
|
263
266
|
?? mergeBlockedFile(file, context, result);
|
|
264
267
|
}
|