@shapeshift-labs/frontier-lang-css 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/cascade-runtime-proof.d.ts +34 -0
- package/dist/index.d.ts +20 -26
- package/dist/postcss-parser-evidence.js +1 -1
- package/dist/semantic-merge-cascade-runtime.js +82 -0
- package/dist/semantic-merge-css-modules.js +11 -7
- package/dist/semantic-merge-shorthand.js +147 -0
- package/dist/semantic-merge.js +27 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -232,11 +232,11 @@ const merge = safeMergeCssSource({
|
|
|
232
232
|
});
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
`sourceMap.mappings` links emitted rule blocks back to Frontier Lang semantic node ids. `createCssSemanticMergeEvidence` records selectors, specificity, declarations, custom properties, `var()` fallback references, animation/keyframe links, font-face links, URL asset references, cascade keys, statement-form at-rules, CSS Modules exports, ICSS edges, scoped cascade graph proof hashes, source spans, stable hashes, and fail-closed proof gaps for cascade/render-sensitive CSS surfaces. `safeMergeCssSource` admits independent declaration edits by cascade key, including existing scoped `@media` / `@supports` / `@container` / `@layer` declarations when a scoped cascade graph proof hash is supplied, carries source-bound dependency graph evidence into the merge result, preserves unchanged statement-form at-rules such as `@layer reset, components;`, and for `.module.css` files it classifies exported local classes, `composes`, and ICSS import/export records as explicit merge contracts.
|
|
235
|
+
`sourceMap.mappings` links emitted rule blocks back to Frontier Lang semantic node ids. `createCssSemanticMergeEvidence` records selectors, specificity, declarations, custom properties, `var()` fallback references, animation/keyframe links, font-face links, URL asset references, cascade keys, statement-form at-rules, CSS Modules exports, ICSS edges, scoped cascade graph proof hashes, source spans, stable hashes, and fail-closed proof gaps for cascade/render-sensitive CSS surfaces. `safeMergeCssSource` admits independent declaration edits by cascade key, including existing scoped `@media` / `@supports` / `@container` / `@layer` declarations when a scoped cascade graph proof hash is supplied, carries source-bound dependency graph evidence into the merge result, blocks parallel edits whose CSS shorthand expansions overlap a longhand or sub-shorthand, preserves unchanged statement-form at-rules such as `@layer reset, components;`, and for `.module.css` files it classifies exported local classes, `composes`, and ICSS import/export records as explicit merge contracts. Cascade-sensitive source-shape changes still block by default, but a host can attach a `css-source-bound-cascade-runtime-proof` / `css-cascade-runtime-proof` that is bound to the exact source path, reason, side, shape key, and base/worker/head/output source hashes to admit that specific change.
|
|
236
236
|
|
|
237
237
|
## Support Boundary
|
|
238
238
|
|
|
239
|
-
- Ready evidence: style rules, selectors, specificity, declarations, source-bound dependency graph hashes for custom properties, `var()` fallbacks, animations/keyframes, font faces, and URL assets, statement-form at-rules, CSS Modules local exports, generated class-name map coverage, JS/TS use-site graph hashes, composition graph hashes, ICSS graph hashes, scoped cascade graph hashes, source spans, stable hashes.
|
|
240
|
-
- Safe merge: independent declarations with non-overlapping cascade keys; unchanged statement-form at-rules preserved in canonical output; existing scoped declaration edits when scoped cascade graph proof is supplied; explicit CSS Modules export additions/deletions when generated class-name and JS/TS use-site graph proof is supplied; composition edits when composition graph proof is supplied; ICSS edits when ICSS graph proof is supplied. Output is a canonical CSS render and not a byte/trivia-preserving claim.
|
|
241
|
-
- Review-only gaps: incomplete generated class-name maps, unproved CSS Modules use-site graphs, unproved composition or ICSS graphs,
|
|
242
|
-
- Claims: dependency graph evidence is an admission/review signal only; `autoMergeClaim`, `semanticEquivalenceClaim`,
|
|
239
|
+
- Ready evidence: style rules, selectors, specificity, declarations, source-bound dependency graph hashes for custom properties, `var()` fallbacks, animations/keyframes, font faces, and URL assets, statement-form at-rules, CSS Modules local exports, generated class-name map coverage, JS/TS use-site graph hashes, composition graph hashes, ICSS graph hashes, scoped cascade graph hashes, source-bound cascade runtime proofs, source spans, stable hashes.
|
|
240
|
+
- Safe merge: independent declarations with non-overlapping cascade keys and non-overlapping shorthand expansion sets; unchanged statement-form at-rules preserved in canonical output; existing scoped declaration edits when scoped cascade graph proof is supplied; source-shape/cascade-sensitive edits only when an exact source-bound cascade runtime proof is supplied; explicit CSS Modules export additions/deletions when generated class-name and JS/TS use-site graph proof is supplied; composition edits when composition graph proof is supplied; ICSS edits when ICSS graph proof is supplied. Output is a canonical CSS render and not a byte/trivia-preserving claim.
|
|
241
|
+
- Review-only gaps: incomplete generated class-name maps, unproved CSS Modules use-site graphs, unproved composition or ICSS graphs, shorthand value expansion/equivalence beyond known affected-property overlap checks, statement-form at-rule order/condition changes, one-sided or structurally changed scoped cascade under `@media` / `@supports` / `@container` / `@layer`, `@keyframes`, `@font-face`, `@page`, browser layout and render equivalence.
|
|
242
|
+
- Claims: dependency graph evidence is an admission/review signal only; `autoMergeClaim`, `semanticEquivalenceClaim`, and `browserRenderEquivalenceClaim` remain false. `browserCascadeEquivalenceClaim` is only true on a safe-merge result when a source-bound cascade runtime proof admits the specific cascade-sensitive source-shape change.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface CssCascadeRuntimeProof {
|
|
2
|
+
readonly id?: string;
|
|
3
|
+
readonly kind: 'css-cascade-runtime-proof' | 'css-source-bound-cascade-runtime-proof' | string;
|
|
4
|
+
readonly status: 'passed' | string;
|
|
5
|
+
readonly proofLevel?: string;
|
|
6
|
+
readonly sourcePath?: string;
|
|
7
|
+
readonly reasonCode?: string;
|
|
8
|
+
readonly reasonCodes?: readonly string[];
|
|
9
|
+
readonly side?: 'worker' | 'head' | string;
|
|
10
|
+
readonly sides?: readonly string[];
|
|
11
|
+
readonly shapeKey?: string;
|
|
12
|
+
readonly shapeKeys?: readonly string[];
|
|
13
|
+
readonly baseSourceText?: string; readonly workerSourceText?: string; readonly headSourceText?: string; readonly outputSourceText?: string; readonly mergedSourceText?: string;
|
|
14
|
+
readonly baseSourceHash?: string; readonly workerSourceHash?: string; readonly headSourceHash?: string; readonly outputSourceHash?: string; readonly mergedSourceHash?: string;
|
|
15
|
+
readonly sourceTexts?: Readonly<Record<string, string>>;
|
|
16
|
+
readonly sources?: Readonly<Record<string, string>>;
|
|
17
|
+
readonly sourceHashes?: Readonly<Record<string, string>>;
|
|
18
|
+
readonly hashes?: Readonly<Record<string, string>>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CssCascadeRuntimeProofRecord {
|
|
22
|
+
readonly id?: string;
|
|
23
|
+
readonly kind: string;
|
|
24
|
+
readonly status: 'passed';
|
|
25
|
+
readonly proofLevel: string;
|
|
26
|
+
readonly reasonCode: string;
|
|
27
|
+
readonly side: string;
|
|
28
|
+
readonly shapeKey: string;
|
|
29
|
+
readonly sourcePath?: string;
|
|
30
|
+
readonly baseSourceHash?: string;
|
|
31
|
+
readonly workerSourceHash?: string;
|
|
32
|
+
readonly headSourceHash?: string;
|
|
33
|
+
readonly outputSourceHash?: string;
|
|
34
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FrontierLangDocument } from '@shapeshift-labs/frontier-lang-kernel'; import type { CssDependencyGraphEvidence } from './dependency-graph.js';
|
|
1
|
+
import type { FrontierLangDocument } from '@shapeshift-labs/frontier-lang-kernel'; import type { CssCascadeRuntimeProof, CssCascadeRuntimeProofRecord } from './cascade-runtime-proof.js'; import type { CssDependencyGraphEvidence } from './dependency-graph.js'; export type { CssCascadeRuntimeProof, CssCascadeRuntimeProofRecord } from './cascade-runtime-proof.js';
|
|
2
2
|
|
|
3
3
|
export interface CssProjectionOptions {
|
|
4
4
|
readonly banner?: string;
|
|
@@ -13,6 +13,8 @@ export interface CssProjectionOptions {
|
|
|
13
13
|
readonly cssModuleCompositionGraphHash?: string;
|
|
14
14
|
readonly icssGraphHash?: string;
|
|
15
15
|
readonly scopedCascadeGraphHash?: string;
|
|
16
|
+
readonly cssCascadeRuntimeProof?: CssCascadeRuntimeProof; readonly cssCascadeRuntimeProofs?: readonly CssCascadeRuntimeProof[];
|
|
17
|
+
readonly cssSourceBoundCascadeProof?: CssCascadeRuntimeProof; readonly cssSourceBoundCascadeProofs?: readonly CssCascadeRuntimeProof[];
|
|
16
18
|
readonly selectorTargetGraphHash?: string;
|
|
17
19
|
readonly targetPath?: string;
|
|
18
20
|
readonly semanticIndexId?: string;
|
|
@@ -20,36 +22,16 @@ export interface CssProjectionOptions {
|
|
|
20
22
|
readonly evidence?: readonly CssProjectionEvidenceRecord[];
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
export interface CssProjectionEvidenceRecord {
|
|
24
|
-
readonly id: string;
|
|
25
|
-
readonly kind?: string;
|
|
26
|
-
readonly summary?: string;
|
|
27
|
-
readonly [key: string]: unknown;
|
|
28
|
-
}
|
|
25
|
+
export interface CssProjectionEvidenceRecord { readonly id: string; readonly kind?: string; readonly summary?: string; readonly [key: string]: unknown; }
|
|
29
26
|
|
|
30
27
|
export interface CssSourceSpan {
|
|
31
|
-
readonly path?: string;
|
|
32
|
-
readonly
|
|
33
|
-
readonly endOffset?: number;
|
|
34
|
-
readonly startLine: number;
|
|
35
|
-
readonly startColumn: number;
|
|
36
|
-
readonly endLine: number;
|
|
37
|
-
readonly endColumn: number;
|
|
28
|
+
readonly path?: string; readonly startOffset?: number; readonly endOffset?: number;
|
|
29
|
+
readonly startLine: number; readonly startColumn: number; readonly endLine: number; readonly endColumn: number;
|
|
38
30
|
}
|
|
39
31
|
|
|
40
|
-
export interface CssParserDiagnostic {
|
|
41
|
-
readonly reason?: string;
|
|
42
|
-
readonly line?: number;
|
|
43
|
-
readonly column?: number;
|
|
44
|
-
readonly input?: string;
|
|
45
|
-
readonly [key: string]: unknown;
|
|
46
|
-
}
|
|
32
|
+
export interface CssParserDiagnostic { readonly reason?: string; readonly line?: number; readonly column?: number; readonly input?: string; readonly [key: string]: unknown; }
|
|
47
33
|
|
|
48
|
-
export interface CssParserEvidence {
|
|
49
|
-
readonly name: 'postcss' | string;
|
|
50
|
-
readonly sourceCodeLocationInfo: boolean;
|
|
51
|
-
readonly parseErrors: readonly CssParserDiagnostic[];
|
|
52
|
-
}
|
|
34
|
+
export interface CssParserEvidence { readonly name: 'postcss' | string; readonly sourceCodeLocationInfo: boolean; readonly parseErrors: readonly CssParserDiagnostic[]; }
|
|
53
35
|
|
|
54
36
|
export interface CssSourceRef {
|
|
55
37
|
readonly semanticNodeId: string;
|
|
@@ -242,6 +224,8 @@ export interface CssSafeMergeConflict {
|
|
|
242
224
|
export interface CssSafeMergeAdmission {
|
|
243
225
|
readonly status: 'auto-merge-candidate' | 'blocked' | string; readonly action: 'apply-css' | 'human-review' | string;
|
|
244
226
|
readonly reviewRequired: boolean; readonly reasonCodes: readonly string[];
|
|
227
|
+
readonly browserCascadeEquivalenceClaim?: true;
|
|
228
|
+
readonly cssCascadeRuntimeProofs?: readonly CssCascadeRuntimeProofRecord[];
|
|
245
229
|
}
|
|
246
230
|
|
|
247
231
|
export interface CssSafeMergeResult {
|
|
@@ -251,10 +235,12 @@ export interface CssSafeMergeResult {
|
|
|
251
235
|
readonly conflicts: readonly CssSafeMergeConflict[];
|
|
252
236
|
readonly admission: CssSafeMergeAdmission;
|
|
253
237
|
readonly autoMergeClaim: false; readonly semanticEquivalenceClaim: false;
|
|
238
|
+
readonly browserCascadeEquivalenceClaim: boolean; readonly browserRenderEquivalenceClaim: false;
|
|
254
239
|
readonly baseSheetHash?: string; readonly workerSheetHash?: string; readonly headSheetHash?: string;
|
|
255
240
|
readonly workerChangedDeclarations?: number; readonly headChangedDeclarations?: number;
|
|
256
241
|
readonly workerChangedCssModuleContracts?: number; readonly headChangedCssModuleContracts?: number;
|
|
257
242
|
readonly parserEvidence?: CssSafeMergeParserEvidence; readonly selectorTargetEvidence?: CssSafeMergeSelectorTargetEvidence; readonly dependencyGraphEvidence?: CssDependencyGraphEvidence;
|
|
243
|
+
readonly cascadeRuntimeProofs?: readonly CssCascadeRuntimeProofRecord[];
|
|
258
244
|
}
|
|
259
245
|
|
|
260
246
|
export interface CssSafeMergeParserEvidence {
|
|
@@ -300,6 +286,14 @@ export interface CssSafeMergeInput {
|
|
|
300
286
|
readonly cssModule?: boolean; readonly cssModules?: boolean;
|
|
301
287
|
readonly generatedClassNameMap?: Readonly<Record<string, string>>;
|
|
302
288
|
readonly generatedClassNameMapHash?: string; readonly jsTsUseSiteGraphHash?: string; readonly cssModuleCompositionGraphHash?: string; readonly icssGraphHash?: string; readonly scopedCascadeGraphHash?: string;
|
|
289
|
+
readonly cssCascadeRuntimeProof?: CssCascadeRuntimeProof; readonly cssCascadeRuntimeProofs?: readonly CssCascadeRuntimeProof[];
|
|
290
|
+
readonly cssCascadeRuntimeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
|
|
291
|
+
readonly cssSourceBoundCascadeProof?: CssCascadeRuntimeProof; readonly cssSourceBoundCascadeProofs?: readonly CssCascadeRuntimeProof[];
|
|
292
|
+
readonly cssSourceBoundCascadeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
|
|
293
|
+
readonly cascadeRuntimeProof?: CssCascadeRuntimeProof; readonly cascadeRuntimeProofs?: readonly CssCascadeRuntimeProof[];
|
|
294
|
+
readonly cascadeRuntimeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
|
|
295
|
+
readonly sourceBoundCascadeProof?: CssCascadeRuntimeProof; readonly sourceBoundCascadeProofs?: readonly CssCascadeRuntimeProof[];
|
|
296
|
+
readonly sourceBoundCascadeProofsByPath?: Readonly<Record<string, CssCascadeRuntimeProof | readonly CssCascadeRuntimeProof[]>>;
|
|
303
297
|
readonly selectorTargetGraphHash?: string; readonly selectorTargetEquivalences?: readonly CssSelectorTargetEquivalence[];
|
|
304
298
|
readonly baseGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly workerGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly headGeneratedClassNameMap?: Readonly<Record<string, string>>;
|
|
305
299
|
readonly baseGeneratedClassNameMapHash?: string; readonly workerGeneratedClassNameMapHash?: string; readonly headGeneratedClassNameMapHash?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
2
|
import postcss from 'postcss';
|
|
3
3
|
|
|
4
|
-
const ShorthandProperties = new Set(['all', 'animation', 'background', 'border', 'border-block', 'border-color', 'border-image', 'border-inline', 'border-radius', 'border-style', 'border-width', 'columns', 'flex', 'font', 'gap', 'grid', 'grid-area', 'grid-column', 'grid-row', 'inset', 'list-style', 'margin', 'offset', 'outline', 'overflow', 'padding', 'place-content', 'place-items', 'place-self', 'text-decoration', 'transition']);
|
|
4
|
+
const ShorthandProperties = new Set(['all', 'animation', 'background', 'border', 'border-block', 'border-block-color', 'border-block-end', 'border-block-start', 'border-block-style', 'border-block-width', 'border-bottom', 'border-color', 'border-image', 'border-inline', 'border-inline-color', 'border-inline-end', 'border-inline-start', 'border-inline-style', 'border-inline-width', 'border-left', 'border-radius', 'border-right', 'border-style', 'border-top', 'border-width', 'columns', 'flex', 'font', 'gap', 'grid', 'grid-area', 'grid-column', 'grid-row', 'inset', 'inset-block', 'inset-inline', 'list-style', 'margin', 'margin-block', 'margin-inline', 'offset', 'outline', 'overflow', 'overscroll-behavior', 'padding', 'padding-block', 'padding-inline', 'place-content', 'place-items', 'place-self', 'scroll-margin', 'scroll-margin-block', 'scroll-margin-inline', 'scroll-padding', 'scroll-padding-block', 'scroll-padding-inline', 'text-decoration', 'transition']);
|
|
5
5
|
const RuntimeAtRules = new Set(['keyframes', 'font-face', 'page', 'property']);
|
|
6
6
|
const ScopeAtRules = new Set(['media', 'supports', 'container', 'layer', 'scope']);
|
|
7
7
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function admitCascadeRuntimeProofs({ id, sourcePath, input, sourceShapeChanges, binding, hash }) {
|
|
2
|
+
const proofs = cascadeRuntimeProofCandidates(input, sourcePath);
|
|
3
|
+
const admitted = [];
|
|
4
|
+
const conflicts = [];
|
|
5
|
+
for (const change of sourceShapeChanges) {
|
|
6
|
+
const proof = proofs.find((candidate) => isCascadeRuntimeProofForChange(candidate, change, sourcePath, binding, hash));
|
|
7
|
+
if (proof) admitted.push(cascadeRuntimeProofRecord(proof, change, sourcePath, binding, hash));
|
|
8
|
+
else conflicts.push(conflict(id, sourcePath, 'css-source-shape-unsupported', change.reasonCode, change));
|
|
9
|
+
}
|
|
10
|
+
return { proofs: admitted, conflicts };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function cascadeRuntimeProofCandidates(input = {}, sourcePath) {
|
|
14
|
+
return [
|
|
15
|
+
input.cssCascadeRuntimeProof,
|
|
16
|
+
input.cssCascadeRuntimeProofs,
|
|
17
|
+
input.cssCascadeRuntimeProofsByPath?.[sourcePath],
|
|
18
|
+
input.cssSourceBoundCascadeProof,
|
|
19
|
+
input.cssSourceBoundCascadeProofs,
|
|
20
|
+
input.cssSourceBoundCascadeProofsByPath?.[sourcePath],
|
|
21
|
+
input.cascadeRuntimeProof,
|
|
22
|
+
input.cascadeRuntimeProofs,
|
|
23
|
+
input.cascadeRuntimeProofsByPath?.[sourcePath],
|
|
24
|
+
input.sourceBoundCascadeProof,
|
|
25
|
+
input.sourceBoundCascadeProofs,
|
|
26
|
+
input.sourceBoundCascadeProofsByPath?.[sourcePath]
|
|
27
|
+
].flatMap(asArray).filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isCascadeRuntimeProofForChange(proof, change, sourcePath, binding, hash) {
|
|
31
|
+
return Boolean(proof && typeof proof === 'object') &&
|
|
32
|
+
CascadeRuntimeProofKinds.has(proof.kind) &&
|
|
33
|
+
proof.status === 'passed' &&
|
|
34
|
+
proof.sourcePath === sourcePath &&
|
|
35
|
+
proofCoversValue(proof.reasonCode, proof.reasonCodes, change.reasonCode) &&
|
|
36
|
+
proofCoversValue(proof.side, proof.sides, change.side) &&
|
|
37
|
+
proofCoversValue(proof.shapeKey, proof.shapeKeys, change.shapeKey) &&
|
|
38
|
+
cascadeProofSourceMatches(proof, 'base', binding.base, hash) &&
|
|
39
|
+
cascadeProofSourceMatches(proof, 'worker', binding.worker, hash) &&
|
|
40
|
+
cascadeProofSourceMatches(proof, 'head', binding.head, hash) &&
|
|
41
|
+
cascadeProofSourceMatches(proof, 'output', binding.output, hash);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function cascadeProofSourceMatches(proof, role, sourceText, hash) {
|
|
45
|
+
if (typeof sourceText !== 'string') return false;
|
|
46
|
+
const sourceHash = hash?.(sourceText);
|
|
47
|
+
const textFields = role === 'output' ? ['outputSourceText', 'mergedSourceText'] : [`${role}SourceText`];
|
|
48
|
+
const hashFields = role === 'output' ? ['outputSourceHash', 'mergedSourceHash'] : [`${role}SourceHash`];
|
|
49
|
+
const aliases = role === 'output' ? ['output', 'merged'] : [role];
|
|
50
|
+
return textFields.some((field) => proof[field] === sourceText) ||
|
|
51
|
+
aliases.some((alias) => proof.sourceTexts?.[alias] === sourceText || proof.sources?.[alias] === sourceText) ||
|
|
52
|
+
hashFields.some((field) => sourceHash !== undefined && proof[field] === sourceHash) ||
|
|
53
|
+
aliases.some((alias) => sourceHash !== undefined && (proof.sourceHashes?.[alias] === sourceHash || proof.hashes?.[alias] === sourceHash));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function cascadeRuntimeProofRecord(proof, change, sourcePath, binding, hash) {
|
|
57
|
+
return {
|
|
58
|
+
id: proof.id,
|
|
59
|
+
kind: proof.kind,
|
|
60
|
+
status: 'passed',
|
|
61
|
+
proofLevel: proof.proofLevel ?? 'css-cascade-runtime-source-bound',
|
|
62
|
+
reasonCode: change.reasonCode,
|
|
63
|
+
side: change.side,
|
|
64
|
+
shapeKey: change.shapeKey,
|
|
65
|
+
sourcePath,
|
|
66
|
+
baseSourceHash: hash?.(binding.base),
|
|
67
|
+
workerSourceHash: hash?.(binding.worker),
|
|
68
|
+
headSourceHash: hash?.(binding.head),
|
|
69
|
+
outputSourceHash: hash?.(binding.output)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function conflict(id, sourcePath, code, reasonCode, details = {}) {
|
|
74
|
+
return { code, gateId: 'css-semantic-merge', sourcePath, details: { reasonCode, conflictKey: `css#${id}#${reasonCode}#${details.cascadeKey ?? details.shapeKey ?? sourcePath ?? 'source'}`, ...details } };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function proofCoversValue(value, values, expected) { return value === expected || (Array.isArray(values) && values.includes(expected)); }
|
|
78
|
+
function asArray(value) { return Array.isArray(value) ? value : value === undefined ? [] : [value]; }
|
|
79
|
+
|
|
80
|
+
const CascadeRuntimeProofKinds = new Set(['css-cascade-runtime-proof', 'css-source-bound-cascade-runtime-proof']);
|
|
81
|
+
|
|
82
|
+
export { admitCascadeRuntimeProofs };
|
|
@@ -131,14 +131,18 @@ function cssModuleOverlapConflicts(id, sourcePath, workerChanges, headChanges) {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
function unsupportedSourceShapeConflicts(id, sourcePath, sheets, declarationChanges, hash) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
return unsupportedSourceShapeChanges(sheets, declarationChanges, hash)
|
|
135
|
+
.map((change) => conflict(id, sourcePath, 'css-source-shape-unsupported', change.reasonCode, change));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function unsupportedSourceShapeChanges(sheets, declarationChanges, hash) {
|
|
139
|
+
return [
|
|
140
|
+
...unsupportedSourceShapeChangesForSide(sheets.base, sheets.worker, declarationChanges.worker, 'worker', hash),
|
|
141
|
+
...unsupportedSourceShapeChangesForSide(sheets.base, sheets.head, declarationChanges.head, 'head', hash)
|
|
142
|
+
];
|
|
139
143
|
}
|
|
140
144
|
|
|
141
|
-
function
|
|
145
|
+
function unsupportedSourceShapeChangesForSide(baseSheet, currentSheet, declarationChanges, side, hash) {
|
|
142
146
|
const baseShape = sourceShapeIndex(baseSheet, hash);
|
|
143
147
|
const currentShape = sourceShapeIndex(currentSheet, hash);
|
|
144
148
|
const keys = unique([...baseShape.keys(), ...currentShape.keys()]);
|
|
@@ -226,4 +230,4 @@ function uniqueProofGaps(values) {
|
|
|
226
230
|
return [...byCode.values()];
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
export { cssModuleContractChanges, cssModuleContractConflicts, sheetOptions, unsupportedSourceShapeConflicts };
|
|
233
|
+
export { cssModuleContractChanges, cssModuleContractConflicts, sheetOptions, unsupportedSourceShapeChanges, unsupportedSourceShapeConflicts };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
function shorthandGroupForProperty(property) {
|
|
2
|
+
const normalized = normalizeProperty(property);
|
|
3
|
+
if (!normalized) return undefined;
|
|
4
|
+
if (ShorthandExpansionKeys.has(normalized)) return normalized;
|
|
5
|
+
for (const [group, keys] of ShorthandExpansionKeys) if (keys.has(normalized)) return group;
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function declarationsOverlapByCssProperty(leftProperty, rightProperty) {
|
|
10
|
+
const left = normalizeProperty(leftProperty);
|
|
11
|
+
const right = normalizeProperty(rightProperty);
|
|
12
|
+
if (!left || !right) return false;
|
|
13
|
+
if (left === right) return true;
|
|
14
|
+
if (left.startsWith('--') || right.startsWith('--')) return false;
|
|
15
|
+
const leftKeys = affectedPropertyKeys(left);
|
|
16
|
+
const rightKeys = affectedPropertyKeys(right);
|
|
17
|
+
if (leftKeys.has(AllStandardProperties)) return !allPropertyExcluded(right);
|
|
18
|
+
if (rightKeys.has(AllStandardProperties)) return !allPropertyExcluded(left);
|
|
19
|
+
for (const key of leftKeys) if (rightKeys.has(key)) return true;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function affectedPropertyKeys(property) {
|
|
24
|
+
return ShorthandExpansionKeys.get(property) ?? new Set([property]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeProperty(property) {
|
|
28
|
+
return String(property ?? '').trim().toLowerCase();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function allPropertyExcluded(property) {
|
|
32
|
+
return property.startsWith('--') || property === 'direction' || property === 'unicode-bidi';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const AllStandardProperties = '*';
|
|
36
|
+
|
|
37
|
+
const ShorthandExpansionKeys = new Map(Object.entries({
|
|
38
|
+
all: [AllStandardProperties],
|
|
39
|
+
animation: ['animation-composition', 'animation-delay', 'animation-direction', 'animation-duration', 'animation-fill-mode', 'animation-iteration-count', 'animation-name', 'animation-play-state', 'animation-range-end', 'animation-range-start', 'animation-timeline', 'animation-timing-function'],
|
|
40
|
+
background: ['background-attachment', 'background-blend-mode', 'background-clip', 'background-color', 'background-image', 'background-origin', 'background-position', 'background-repeat', 'background-size'],
|
|
41
|
+
border: [
|
|
42
|
+
...edgeKeys('border', ['width', 'style', 'color']),
|
|
43
|
+
'border-width',
|
|
44
|
+
'border-style',
|
|
45
|
+
'border-color',
|
|
46
|
+
'border-top',
|
|
47
|
+
'border-right',
|
|
48
|
+
'border-bottom',
|
|
49
|
+
'border-left'
|
|
50
|
+
],
|
|
51
|
+
'border-block': [...logicalEdgeKeys('border-block', ['width', 'style', 'color']), 'border-block-width', 'border-block-style', 'border-block-color', 'border-block-start', 'border-block-end'],
|
|
52
|
+
'border-block-color': logicalEdgeKeys('border-block', ['color']),
|
|
53
|
+
'border-block-end': propertyTuple('border-block-end', ['width', 'style', 'color']),
|
|
54
|
+
'border-block-end-color': ['border-block-end-color'],
|
|
55
|
+
'border-block-end-style': ['border-block-end-style'],
|
|
56
|
+
'border-block-end-width': ['border-block-end-width'],
|
|
57
|
+
'border-block-start': propertyTuple('border-block-start', ['width', 'style', 'color']),
|
|
58
|
+
'border-block-start-color': ['border-block-start-color'],
|
|
59
|
+
'border-block-start-style': ['border-block-start-style'],
|
|
60
|
+
'border-block-start-width': ['border-block-start-width'],
|
|
61
|
+
'border-block-style': logicalEdgeKeys('border-block', ['style']),
|
|
62
|
+
'border-block-width': logicalEdgeKeys('border-block', ['width']),
|
|
63
|
+
'border-bottom': propertyTuple('border-bottom', ['width', 'style', 'color']),
|
|
64
|
+
'border-bottom-color': ['border-bottom-color'],
|
|
65
|
+
'border-bottom-left-radius': ['border-bottom-left-radius'],
|
|
66
|
+
'border-bottom-right-radius': ['border-bottom-right-radius'],
|
|
67
|
+
'border-bottom-style': ['border-bottom-style'],
|
|
68
|
+
'border-bottom-width': ['border-bottom-width'],
|
|
69
|
+
'border-color': edgeKeys('border', ['color']),
|
|
70
|
+
'border-image': ['border-image-source', 'border-image-slice', 'border-image-width', 'border-image-outset', 'border-image-repeat'],
|
|
71
|
+
'border-inline': [...logicalEdgeKeys('border-inline', ['width', 'style', 'color']), 'border-inline-width', 'border-inline-style', 'border-inline-color', 'border-inline-start', 'border-inline-end'],
|
|
72
|
+
'border-inline-color': logicalEdgeKeys('border-inline', ['color']),
|
|
73
|
+
'border-inline-end': propertyTuple('border-inline-end', ['width', 'style', 'color']),
|
|
74
|
+
'border-inline-end-color': ['border-inline-end-color'],
|
|
75
|
+
'border-inline-end-style': ['border-inline-end-style'],
|
|
76
|
+
'border-inline-end-width': ['border-inline-end-width'],
|
|
77
|
+
'border-inline-start': propertyTuple('border-inline-start', ['width', 'style', 'color']),
|
|
78
|
+
'border-inline-start-color': ['border-inline-start-color'],
|
|
79
|
+
'border-inline-start-style': ['border-inline-start-style'],
|
|
80
|
+
'border-inline-start-width': ['border-inline-start-width'],
|
|
81
|
+
'border-inline-style': logicalEdgeKeys('border-inline', ['style']),
|
|
82
|
+
'border-inline-width': logicalEdgeKeys('border-inline', ['width']),
|
|
83
|
+
'border-left': propertyTuple('border-left', ['width', 'style', 'color']),
|
|
84
|
+
'border-left-color': ['border-left-color'],
|
|
85
|
+
'border-left-style': ['border-left-style'],
|
|
86
|
+
'border-left-width': ['border-left-width'],
|
|
87
|
+
'border-radius': ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
|
|
88
|
+
'border-right': propertyTuple('border-right', ['width', 'style', 'color']),
|
|
89
|
+
'border-right-color': ['border-right-color'],
|
|
90
|
+
'border-right-style': ['border-right-style'],
|
|
91
|
+
'border-right-width': ['border-right-width'],
|
|
92
|
+
'border-style': edgeKeys('border', ['style']),
|
|
93
|
+
'border-top': propertyTuple('border-top', ['width', 'style', 'color']),
|
|
94
|
+
'border-top-color': ['border-top-color'],
|
|
95
|
+
'border-top-left-radius': ['border-top-left-radius'],
|
|
96
|
+
'border-top-right-radius': ['border-top-right-radius'],
|
|
97
|
+
'border-top-style': ['border-top-style'],
|
|
98
|
+
'border-top-width': ['border-top-width'],
|
|
99
|
+
'border-width': edgeKeys('border', ['width']),
|
|
100
|
+
columns: ['column-width', 'column-count'],
|
|
101
|
+
flex: ['flex-grow', 'flex-shrink', 'flex-basis'],
|
|
102
|
+
font: ['font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'line-height'],
|
|
103
|
+
gap: ['row-gap', 'column-gap'],
|
|
104
|
+
grid: ['grid-template-rows', 'grid-template-columns', 'grid-template-areas', 'grid-auto-rows', 'grid-auto-columns', 'grid-auto-flow'],
|
|
105
|
+
'grid-area': ['grid-row-start', 'grid-column-start', 'grid-row-end', 'grid-column-end'],
|
|
106
|
+
'grid-column': ['grid-column-start', 'grid-column-end'],
|
|
107
|
+
'grid-row': ['grid-row-start', 'grid-row-end'],
|
|
108
|
+
inset: ['top', 'right', 'bottom', 'left'],
|
|
109
|
+
'inset-block': ['inset-block-start', 'inset-block-end'],
|
|
110
|
+
'inset-inline': ['inset-inline-start', 'inset-inline-end'],
|
|
111
|
+
'list-style': ['list-style-image', 'list-style-position', 'list-style-type'],
|
|
112
|
+
margin: ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'],
|
|
113
|
+
'margin-block': ['margin-block-start', 'margin-block-end'],
|
|
114
|
+
'margin-inline': ['margin-inline-start', 'margin-inline-end'],
|
|
115
|
+
offset: ['offset-anchor', 'offset-distance', 'offset-path', 'offset-position', 'offset-rotate'],
|
|
116
|
+
outline: ['outline-color', 'outline-style', 'outline-width'],
|
|
117
|
+
overflow: ['overflow-x', 'overflow-y'],
|
|
118
|
+
'overscroll-behavior': ['overscroll-behavior-x', 'overscroll-behavior-y'],
|
|
119
|
+
padding: ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'],
|
|
120
|
+
'padding-block': ['padding-block-start', 'padding-block-end'],
|
|
121
|
+
'padding-inline': ['padding-inline-start', 'padding-inline-end'],
|
|
122
|
+
'place-content': ['align-content', 'justify-content'],
|
|
123
|
+
'place-items': ['align-items', 'justify-items'],
|
|
124
|
+
'place-self': ['align-self', 'justify-self'],
|
|
125
|
+
'scroll-margin': ['scroll-margin-top', 'scroll-margin-right', 'scroll-margin-bottom', 'scroll-margin-left'],
|
|
126
|
+
'scroll-margin-block': ['scroll-margin-block-start', 'scroll-margin-block-end'],
|
|
127
|
+
'scroll-margin-inline': ['scroll-margin-inline-start', 'scroll-margin-inline-end'],
|
|
128
|
+
'scroll-padding': ['scroll-padding-top', 'scroll-padding-right', 'scroll-padding-bottom', 'scroll-padding-left'],
|
|
129
|
+
'scroll-padding-block': ['scroll-padding-block-start', 'scroll-padding-block-end'],
|
|
130
|
+
'scroll-padding-inline': ['scroll-padding-inline-start', 'scroll-padding-inline-end'],
|
|
131
|
+
'text-decoration': ['text-decoration-line', 'text-decoration-color', 'text-decoration-style', 'text-decoration-thickness'],
|
|
132
|
+
transition: ['transition-behavior', 'transition-delay', 'transition-duration', 'transition-property', 'transition-timing-function']
|
|
133
|
+
}).map(([property, keys]) => [property, new Set(keys)]));
|
|
134
|
+
|
|
135
|
+
function edgeKeys(prefix, attributes) {
|
|
136
|
+
return ['top', 'right', 'bottom', 'left'].flatMap((edge) => attributes.map((attribute) => `${prefix}-${edge}-${attribute}`));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function logicalEdgeKeys(prefix, attributes) {
|
|
140
|
+
return ['start', 'end'].flatMap((edge) => attributes.map((attribute) => `${prefix}-${edge}-${attribute}`));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function propertyTuple(prefix, attributes) {
|
|
144
|
+
return attributes.map((attribute) => `${prefix}-${attribute}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export { declarationsOverlapByCssProperty, shorthandGroupForProperty };
|
package/dist/semantic-merge.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { cssModuleContractChanges, cssModuleContractConflicts, sheetOptions,
|
|
1
|
+
import { cssModuleContractChanges, cssModuleContractConflicts, sheetOptions, unsupportedSourceShapeChanges } from './semantic-merge-css-modules.js';
|
|
2
|
+
import { admitCascadeRuntimeProofs } from './semantic-merge-cascade-runtime.js';
|
|
2
3
|
import { mergeCssDependencyGraphEvidence } from './dependency-graph.js';
|
|
3
4
|
import { mergeSelectorTargetEvidence, planSelectorTargetRebase } from './semantic-merge-selector-targets.js';
|
|
5
|
+
import { declarationsOverlapByCssProperty, shorthandGroupForProperty } from './semantic-merge-shorthand.js';
|
|
4
6
|
|
|
5
7
|
function safeMergeCssSource(input = {}, context = {}) {
|
|
6
8
|
const parseSheet = context.parseCssSemanticSheet;
|
|
@@ -30,14 +32,23 @@ function safeMergeCssSource(input = {}, context = {}) {
|
|
|
30
32
|
...shorthandOverlapConflicts(id, sourcePath, changed.worker, changed.head)
|
|
31
33
|
];
|
|
32
34
|
const moduleConflicts = cssModuleContractConflicts(id, sourcePath, moduleChanges);
|
|
33
|
-
const sourceShapeConflicts = unsupportedSourceShapeConflicts(id, sourcePath, sheets, changed, hash);
|
|
34
35
|
const parserEvidence = mergeParserEvidence(sheets);
|
|
35
36
|
const dependencyGraphEvidence = mergeCssDependencyGraphEvidence(sheets, changed);
|
|
36
37
|
const selectorTargetPlan = planSelectorTargetRebase(id, sourcePath, mergeSelectorTargetEvidence(sheets, changed), changed, input);
|
|
37
|
-
const
|
|
38
|
-
if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence, dependencyGraphEvidence, selectorTargetEvidence: selectorTargetPlan.evidence });
|
|
38
|
+
const shapeChanges = unsupportedSourceShapeChanges(sheets, changed, hash);
|
|
39
39
|
const mergedIndex = applyDeclarationChanges(applyDeclarationChanges(indexes.base, selectorTargetPlan.changed.head), selectorTargetPlan.changed.worker);
|
|
40
|
-
|
|
40
|
+
const mergedSourceText = renderDeclarationIndex(mergedIndex);
|
|
41
|
+
const cascadeRuntimeAdmission = admitCascadeRuntimeProofs({
|
|
42
|
+
id,
|
|
43
|
+
sourcePath,
|
|
44
|
+
input,
|
|
45
|
+
sourceShapeChanges: shapeChanges,
|
|
46
|
+
binding: { base, worker, head, output: mergedSourceText },
|
|
47
|
+
hash
|
|
48
|
+
});
|
|
49
|
+
const conflicts = [...parserConflicts, ...proofConflicts, ...overlapConflicts, ...moduleConflicts, ...cascadeRuntimeAdmission.conflicts, ...selectorTargetPlan.conflicts];
|
|
50
|
+
if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence, dependencyGraphEvidence, selectorTargetEvidence: selectorTargetPlan.evidence, cascadeRuntimeProofs: cascadeRuntimeAdmission.proofs });
|
|
51
|
+
return merged(id, sourcePath, mergedSourceText, 'semantic-declaration-merge', hash, {
|
|
41
52
|
baseSheetHash: sheets.base.sheetHash,
|
|
42
53
|
workerSheetHash: sheets.worker.sheetHash,
|
|
43
54
|
headSheetHash: sheets.head.sheetHash,
|
|
@@ -47,7 +58,9 @@ function safeMergeCssSource(input = {}, context = {}) {
|
|
|
47
58
|
headChangedCssModuleContracts: moduleChanges.head.length,
|
|
48
59
|
parserEvidence,
|
|
49
60
|
dependencyGraphEvidence,
|
|
50
|
-
selectorTargetEvidence: selectorTargetPlan.evidence
|
|
61
|
+
selectorTargetEvidence: selectorTargetPlan.evidence,
|
|
62
|
+
cascadeRuntimeProofs: cascadeRuntimeAdmission.proofs,
|
|
63
|
+
browserCascadeEquivalenceClaim: cascadeRuntimeAdmission.proofs.length > 0
|
|
51
64
|
});
|
|
52
65
|
}
|
|
53
66
|
|
|
@@ -184,9 +197,7 @@ function hasRelatedExistingDeclaration(entry, indexes) {
|
|
|
184
197
|
|
|
185
198
|
function declarationsOverlapByShorthandGroup(left, right) {
|
|
186
199
|
if (!left || !right || left.ruleKey !== right.ruleKey) return false;
|
|
187
|
-
|
|
188
|
-
const rightGroup = shorthandGroupForProperty(right.property);
|
|
189
|
-
return Boolean(leftGroup && rightGroup && leftGroup === rightGroup);
|
|
200
|
+
return declarationsOverlapByCssProperty(left.property, right.property);
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
function applyDeclarationChanges(index, changes) {
|
|
@@ -267,20 +278,25 @@ function blocked(id, sourcePath, reasonCode, conflicts = [], extra = {}) {
|
|
|
267
278
|
}
|
|
268
279
|
|
|
269
280
|
function result(id, sourcePath, status, body) {
|
|
281
|
+
const browserCascadeEquivalenceClaim = status === 'merged' && body.browserCascadeEquivalenceClaim === true;
|
|
270
282
|
return {
|
|
271
283
|
kind: 'frontier.lang.cssSafeMerge',
|
|
272
284
|
version: 1,
|
|
273
285
|
id,
|
|
274
286
|
sourcePath,
|
|
275
287
|
status,
|
|
288
|
+
...body,
|
|
276
289
|
autoMergeClaim: false,
|
|
277
290
|
semanticEquivalenceClaim: false,
|
|
278
|
-
|
|
291
|
+
browserCascadeEquivalenceClaim,
|
|
292
|
+
browserRenderEquivalenceClaim: false,
|
|
279
293
|
admission: {
|
|
280
294
|
status: status === 'merged' ? 'auto-merge-candidate' : 'blocked',
|
|
281
295
|
action: status === 'merged' ? 'apply-css' : 'human-review',
|
|
282
296
|
reviewRequired: status !== 'merged',
|
|
283
|
-
reasonCodes: unique((body.conflicts ?? []).map((item) => item.details.reasonCode))
|
|
297
|
+
reasonCodes: unique((body.conflicts ?? []).map((item) => item.details.reasonCode)),
|
|
298
|
+
browserCascadeEquivalenceClaim: browserCascadeEquivalenceClaim || undefined,
|
|
299
|
+
cssCascadeRuntimeProofs: body.cascadeRuntimeProofs?.length ? body.cascadeRuntimeProofs : undefined
|
|
284
300
|
}
|
|
285
301
|
};
|
|
286
302
|
}
|
|
@@ -298,19 +314,4 @@ function proofGapsForDeclaration(record, declaration) {
|
|
|
298
314
|
function unique(values) { return [...new Set(values.filter(Boolean))]; }
|
|
299
315
|
function spaces(count) { return ' '.repeat(Math.max(0, count)); }
|
|
300
316
|
|
|
301
|
-
function shorthandGroupForProperty(property) {
|
|
302
|
-
if (ShorthandGroups.has(property)) return property;
|
|
303
|
-
for (const [group, longhands] of ShorthandGroups) if (longhands.includes(property)) return group;
|
|
304
|
-
return undefined;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const ShorthandGroups = new Map([
|
|
308
|
-
['background', ['background-attachment', 'background-clip', 'background-color', 'background-image', 'background-origin', 'background-position', 'background-repeat', 'background-size']],
|
|
309
|
-
['border', ['border-color', 'border-style', 'border-width', 'border-top', 'border-right', 'border-bottom', 'border-left']],
|
|
310
|
-
['font', ['font-family', 'font-size', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'line-height']],
|
|
311
|
-
['list-style', ['list-style-image', 'list-style-position', 'list-style-type']],
|
|
312
|
-
['margin', ['margin-top', 'margin-right', 'margin-bottom', 'margin-left']],
|
|
313
|
-
['padding', ['padding-top', 'padding-right', 'padding-bottom', 'padding-left']]
|
|
314
|
-
]);
|
|
315
|
-
|
|
316
317
|
export { safeMergeCssSource };
|
package/package.json
CHANGED