@shapeshift-labs/frontier-lang-css 0.1.3 → 0.1.5
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 +4 -4
- package/dist/index.d.ts +7 -0
- package/dist/index.js +57 -11
- package/dist/semantic-merge-css-modules.js +25 -3
- package/dist/semantic-merge.js +47 -8
- 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, cascade keys, CSS Modules exports, ICSS edges, source spans, stable hashes, and fail-closed proof gaps for cascade/render-sensitive CSS surfaces. `safeMergeCssSource` admits independent
|
|
235
|
+
`sourceMap.mappings` links emitted rule blocks back to Frontier Lang semantic node ids. `createCssSemanticMergeEvidence` records selectors, specificity, declarations, custom properties, 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, 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.
|
|
236
236
|
|
|
237
237
|
## Support Boundary
|
|
238
238
|
|
|
239
|
-
- Ready evidence: style rules, selectors, specificity, declarations, custom properties, CSS Modules local exports, generated class-name map coverage, JS/TS use-site graph hashes, composition graph hashes, ICSS graph hashes, source spans, stable hashes.
|
|
240
|
-
- Safe merge: independent
|
|
241
|
-
- Review-only gaps: incomplete generated class-name maps, unproved CSS Modules use-site graphs, unproved composition or ICSS graphs, shorthands without longhand expansion, scoped cascade under `@media` / `@supports` / `@container` / `@layer`, `@keyframes`, `@font-face`, `@page`, browser layout and render equivalence.
|
|
239
|
+
- Ready evidence: style rules, selectors, specificity, declarations, custom properties, 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, shorthands without longhand expansion, 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
242
|
- Claims: `autoMergeClaim`, `semanticEquivalenceClaim`, `browserCascadeEquivalenceClaim`, and `browserRenderEquivalenceClaim` remain false.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface CssProjectionOptions {
|
|
|
12
12
|
readonly jsTsUseSiteGraphHash?: string;
|
|
13
13
|
readonly cssModuleCompositionGraphHash?: string;
|
|
14
14
|
readonly icssGraphHash?: string;
|
|
15
|
+
readonly scopedCascadeGraphHash?: string;
|
|
15
16
|
readonly targetPath?: string;
|
|
16
17
|
readonly semanticIndexId?: string;
|
|
17
18
|
readonly sourceSpansBySemanticNodeId?: Readonly<Record<string, CssSourceSpan>>;
|
|
@@ -124,6 +125,8 @@ export interface CssSemanticRecord {
|
|
|
124
125
|
readonly atRuleName?: string;
|
|
125
126
|
readonly conditionText?: string;
|
|
126
127
|
readonly scopeKey?: string;
|
|
128
|
+
readonly statementText?: string;
|
|
129
|
+
readonly scopedCascadeGraphHash?: string;
|
|
127
130
|
readonly sourceSpan: CssSourceSpan;
|
|
128
131
|
readonly sourceHash: string;
|
|
129
132
|
readonly ruleHash?: string;
|
|
@@ -266,6 +269,7 @@ export interface CssSafeMergeInput {
|
|
|
266
269
|
readonly jsTsUseSiteGraphHash?: string;
|
|
267
270
|
readonly cssModuleCompositionGraphHash?: string;
|
|
268
271
|
readonly icssGraphHash?: string;
|
|
272
|
+
readonly scopedCascadeGraphHash?: string;
|
|
269
273
|
readonly baseGeneratedClassNameMap?: Readonly<Record<string, string>>;
|
|
270
274
|
readonly workerGeneratedClassNameMap?: Readonly<Record<string, string>>;
|
|
271
275
|
readonly headGeneratedClassNameMap?: Readonly<Record<string, string>>;
|
|
@@ -281,6 +285,9 @@ export interface CssSafeMergeInput {
|
|
|
281
285
|
readonly baseIcssGraphHash?: string;
|
|
282
286
|
readonly workerIcssGraphHash?: string;
|
|
283
287
|
readonly headIcssGraphHash?: string;
|
|
288
|
+
readonly baseScopedCascadeGraphHash?: string;
|
|
289
|
+
readonly workerScopedCascadeGraphHash?: string;
|
|
290
|
+
readonly headScopedCascadeGraphHash?: string;
|
|
284
291
|
}
|
|
285
292
|
|
|
286
293
|
export declare function toCssAst(document: FrontierLangDocument, options?: CssProjectionOptions): CssAstStylesheet;
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ export function emitCssWithSourceMap(document, options = {}) {
|
|
|
59
59
|
export function parseCssSemanticSheet(sourceText, options = {}) {
|
|
60
60
|
const lineStarts = computeLineStarts(sourceText);
|
|
61
61
|
const sourceHash = options.sourceHash ?? hashSemanticValue({ kind: 'frontier.lang.css.source.v1', sourceText });
|
|
62
|
-
const records = parseCssBlocks(sourceText, 0, sourceText.length, [], lineStarts, sourceHash);
|
|
62
|
+
const records = parseCssBlocks(sourceText, 0, sourceText.length, [], lineStarts, sourceHash, options);
|
|
63
63
|
const cssModules = createCssModuleEvidence(records, options, sourceHash);
|
|
64
64
|
const proofGaps = [
|
|
65
65
|
...records.flatMap((record) => record.proofGaps ?? []),
|
|
@@ -112,8 +112,9 @@ export function safeMergeCssSource(input = {}) {
|
|
|
112
112
|
return safeMergeCssSourceImpl(input, { parseCssSemanticSheet, hashSemanticValue });
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function parseCssBlocks(sourceText, start, end, scopes, lineStarts, sourceHash) {
|
|
115
|
+
function parseCssBlocks(sourceText, start, end, scopes, lineStarts, sourceHash, options) {
|
|
116
116
|
const records = [];
|
|
117
|
+
const blockRanges = [];
|
|
117
118
|
let index = start;
|
|
118
119
|
while (index < end) {
|
|
119
120
|
const open = sourceText.indexOf('{', index);
|
|
@@ -121,26 +122,28 @@ function parseCssBlocks(sourceText, start, end, scopes, lineStarts, sourceHash)
|
|
|
121
122
|
const close = matchingBrace(sourceText, open, end);
|
|
122
123
|
if (close < 0) break;
|
|
123
124
|
const preludeStart = previousBoundary(sourceText, index, open);
|
|
125
|
+
blockRanges.push([preludeStart, close + 1]);
|
|
124
126
|
const prelude = sourceText.slice(preludeStart, open).replace(/\/\*[\s\S]*?\*\//g, '').trim();
|
|
125
127
|
const body = sourceText.slice(open + 1, close);
|
|
126
128
|
if (prelude.startsWith('@')) {
|
|
127
|
-
const at = parseAtRule(prelude, preludeStart, close + 1, lineStarts, sourceHash, scopes);
|
|
129
|
+
const at = parseAtRule(prelude, preludeStart, close + 1, lineStarts, sourceHash, scopes, options);
|
|
128
130
|
records.push(at);
|
|
129
|
-
if (ScopeAtRules.has(at.atRuleName)) records.push(...parseCssBlocks(sourceText, open + 1, close, [...scopes, at.scopeKey], lineStarts, sourceHash));
|
|
131
|
+
if (ScopeAtRules.has(at.atRuleName)) records.push(...parseCssBlocks(sourceText, open + 1, close, [...scopes, at.scopeKey], lineStarts, sourceHash, options));
|
|
130
132
|
} else if (prelude) {
|
|
131
|
-
records.push(cssRuleRecord(prelude, body, preludeStart, close + 1, lineStarts, sourceHash, scopes));
|
|
133
|
+
records.push(cssRuleRecord(prelude, body, preludeStart, close + 1, lineStarts, sourceHash, scopes, options));
|
|
132
134
|
}
|
|
133
135
|
index = close + 1;
|
|
134
136
|
}
|
|
135
|
-
|
|
137
|
+
records.push(...parseAtRuleStatements(sourceText, start, end, scopes, lineStarts, sourceHash, options, blockRanges));
|
|
138
|
+
return records.sort((left, right) => left.sourceSpan.startOffset - right.sourceSpan.startOffset);
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
function cssRuleRecord(prelude, body, start, end, lineStarts, sourceHash, scopes) {
|
|
141
|
+
function cssRuleRecord(prelude, body, start, end, lineStarts, sourceHash, scopes, options) {
|
|
139
142
|
const selectors = prelude.split(',').map((selector) => selector.trim()).filter(Boolean);
|
|
140
143
|
const declarations = parseDeclarations(body);
|
|
141
144
|
const proofGaps = [
|
|
142
145
|
...declarations.filter((declaration) => ShorthandProperties.has(declaration.property)).map((declaration) => proofGap('css-shorthand-expansion-unproved', `CSS shorthand ${declaration.property} needs longhand expansion evidence.`)),
|
|
143
|
-
...scopes.length ? [proofGap('css-scoped-cascade-equivalence-unproved', 'Scoped cascade equivalence requires browser/style evidence.')] : []
|
|
146
|
+
...scopes.length && !options.scopedCascadeGraphHash ? [proofGap('css-scoped-cascade-equivalence-unproved', 'Scoped cascade equivalence requires browser/style evidence.')] : []
|
|
144
147
|
];
|
|
145
148
|
return compactRecord({
|
|
146
149
|
kind: 'rule',
|
|
@@ -155,6 +158,7 @@ function cssRuleRecord(prelude, body, start, end, lineStarts, sourceHash, scopes
|
|
|
155
158
|
declarationHash: hashSemanticValue({ kind: 'frontier.lang.css.declaration.v1', scopes, selectors, declaration })
|
|
156
159
|
})),
|
|
157
160
|
customProperties: declarations.filter((declaration) => declaration.property.startsWith('--')).map((declaration) => declaration.property),
|
|
161
|
+
scopedCascadeGraphHash: scopes.length ? options.scopedCascadeGraphHash : undefined,
|
|
158
162
|
sourceSpan: sourceSpan(start, end, lineStarts),
|
|
159
163
|
sourceHash,
|
|
160
164
|
ruleHash: hashSemanticValue({ kind: 'frontier.lang.css.rule.v1', selectors, scopes, declarations }),
|
|
@@ -162,19 +166,20 @@ function cssRuleRecord(prelude, body, start, end, lineStarts, sourceHash, scopes
|
|
|
162
166
|
});
|
|
163
167
|
}
|
|
164
168
|
|
|
165
|
-
function parseAtRule(prelude, start, end, lineStarts, sourceHash, scopes) {
|
|
169
|
+
function parseAtRule(prelude, start, end, lineStarts, sourceHash, scopes, options) {
|
|
166
170
|
const match = /^@([A-Za-z-]+)\s*([\s\S]*)$/.exec(prelude);
|
|
167
171
|
const atRuleName = match?.[1]?.toLowerCase() ?? 'unknown';
|
|
168
172
|
const conditionText = match?.[2]?.trim() ?? '';
|
|
169
173
|
const proofGaps = [];
|
|
170
174
|
if (RuntimeAtRules.has(atRuleName)) proofGaps.push(proofGap(`css-${atRuleName}-runtime-equivalence-unproved`, `CSS @${atRuleName} semantics require browser evidence.`));
|
|
171
|
-
if (ScopeAtRules.has(atRuleName)) proofGaps.push(proofGap(`css-${atRuleName}-cascade-scope-unproved`, `CSS @${atRuleName} scoped cascade requires condition evaluation evidence.`));
|
|
175
|
+
if (ScopeAtRules.has(atRuleName) && !options.scopedCascadeGraphHash) proofGaps.push(proofGap(`css-${atRuleName}-cascade-scope-unproved`, `CSS @${atRuleName} scoped cascade requires condition evaluation evidence.`));
|
|
172
176
|
return compactRecord({
|
|
173
177
|
kind: 'at-rule',
|
|
174
178
|
atRuleName,
|
|
175
179
|
conditionText,
|
|
176
180
|
scopeKey: `@${atRuleName} ${conditionText}`.trim(),
|
|
177
181
|
scopes,
|
|
182
|
+
scopedCascadeGraphHash: ScopeAtRules.has(atRuleName) ? options.scopedCascadeGraphHash : undefined,
|
|
178
183
|
sourceSpan: sourceSpan(start, end, lineStarts),
|
|
179
184
|
sourceHash,
|
|
180
185
|
atRuleHash: hashSemanticValue({ kind: 'frontier.lang.css.atRule.v1', atRuleName, conditionText, scopes }),
|
|
@@ -182,6 +187,47 @@ function parseAtRule(prelude, start, end, lineStarts, sourceHash, scopes) {
|
|
|
182
187
|
});
|
|
183
188
|
}
|
|
184
189
|
|
|
190
|
+
function parseAtRuleStatements(sourceText, start, end, scopes, lineStarts, sourceHash, options, blockRanges) {
|
|
191
|
+
const records = [];
|
|
192
|
+
let index = start;
|
|
193
|
+
while (index < end) {
|
|
194
|
+
const semicolon = sourceText.indexOf(';', index);
|
|
195
|
+
if (semicolon < 0 || semicolon >= end) break;
|
|
196
|
+
const range = blockRanges.find(([rangeStart, rangeEnd]) => semicolon >= rangeStart && semicolon < rangeEnd);
|
|
197
|
+
if (range) {
|
|
198
|
+
index = range[1];
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const statementStart = previousBoundary(sourceText, index, semicolon);
|
|
202
|
+
const statementText = sourceText.slice(statementStart, semicolon + 1).replace(/\/\*[\s\S]*?\*\//g, '').trim();
|
|
203
|
+
if (statementText.startsWith('@') && !statementText.includes('{')) records.push(parseAtRuleStatement(statementText, statementStart, semicolon + 1, lineStarts, sourceHash, scopes, options));
|
|
204
|
+
index = semicolon + 1;
|
|
205
|
+
}
|
|
206
|
+
return records;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function parseAtRuleStatement(statementText, start, end, lineStarts, sourceHash, scopes, options) {
|
|
210
|
+
const body = statementText.replace(/;$/, '').trim();
|
|
211
|
+
const match = /^@([A-Za-z-]+)\s*([\s\S]*)$/.exec(body);
|
|
212
|
+
const atRuleName = match?.[1]?.toLowerCase() ?? 'unknown';
|
|
213
|
+
const conditionText = match?.[2]?.trim() ?? '';
|
|
214
|
+
const proofGaps = [];
|
|
215
|
+
if (atRuleName === 'layer') proofGaps.push(proofGap('css-layer-order-statement-unsupported', 'CSS @layer statement order requires cascade order evidence.'));
|
|
216
|
+
else proofGaps.push(proofGap(`css-${atRuleName}-statement-equivalence-unproved`, `CSS @${atRuleName} statement semantics require host evidence.`));
|
|
217
|
+
return compactRecord({
|
|
218
|
+
kind: 'at-rule-statement',
|
|
219
|
+
atRuleName,
|
|
220
|
+
conditionText,
|
|
221
|
+
statementText,
|
|
222
|
+
scopes,
|
|
223
|
+
scopedCascadeGraphHash: ScopeAtRules.has(atRuleName) ? options.scopedCascadeGraphHash : undefined,
|
|
224
|
+
sourceSpan: sourceSpan(start, end, lineStarts),
|
|
225
|
+
sourceHash,
|
|
226
|
+
atRuleHash: hashSemanticValue({ kind: 'frontier.lang.css.atRuleStatement.v1', atRuleName, conditionText, scopes, statementText }),
|
|
227
|
+
proofGaps
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
185
231
|
function parseDeclarations(body) {
|
|
186
232
|
return body
|
|
187
233
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
@@ -254,7 +300,7 @@ function positionAt(offset, lineStarts) {
|
|
|
254
300
|
|
|
255
301
|
function sourceRef(node, extra = {}) { return { semanticNodeId: node.id, semanticNodeKind: node.kind, semanticNodeName: node.name, ...extra }; }
|
|
256
302
|
function proofGap(code, summary) { return { code, status: 'not-claimed', summary, failClosed: true, semanticEquivalenceClaim: false }; }
|
|
257
|
-
function hashableCssRecord(record) { return { kind: record.kind, selectors: record.selectors, specificity: record.specificity, scopes: record.scopes, atRuleName: record.atRuleName, conditionText: record.conditionText, declarations: record.declarations?.map((item) => ({ property: item.property, value: item.value, important: item.important })), proofGaps: record.proofGaps?.map((gap) => gap.code) }; }
|
|
303
|
+
function hashableCssRecord(record) { return { kind: record.kind, selectors: record.selectors, specificity: record.specificity, scopes: record.scopes, atRuleName: record.atRuleName, conditionText: record.conditionText, statementText: record.statementText, declarations: record.declarations?.map((item) => ({ property: item.property, value: item.value, important: item.important })), proofGaps: record.proofGaps?.map((gap) => gap.code) }; }
|
|
258
304
|
function computeLineStarts(text) { const starts = [0]; for (let index = 0; index < text.length; index += 1) if (text[index] === '\n') starts.push(index + 1); return starts; }
|
|
259
305
|
function cssIdentifier(value) { return String(value ?? 'unknown').replace(/[^A-Za-z0-9_-]/g, '-').replace(/^-+/, '') || 'unknown'; }
|
|
260
306
|
function cssString(value) { return String(value ?? '').replace(/\\/g, '\\\\').replace(/"/g, '\\"'); }
|
|
@@ -8,7 +8,8 @@ function sheetOptions(input, side, sourcePath) {
|
|
|
8
8
|
generatedClassNameMapHash: input[`${prefix}GeneratedClassNameMapHash`] ?? input.generatedClassNameMapHash,
|
|
9
9
|
jsTsUseSiteGraphHash: input[`${prefix}JsTsUseSiteGraphHash`] ?? input.jsTsUseSiteGraphHash,
|
|
10
10
|
cssModuleCompositionGraphHash: input[`${prefix}CssModuleCompositionGraphHash`] ?? input.cssModuleCompositionGraphHash,
|
|
11
|
-
icssGraphHash: input[`${prefix}IcssGraphHash`] ?? input.icssGraphHash
|
|
11
|
+
icssGraphHash: input[`${prefix}IcssGraphHash`] ?? input.icssGraphHash,
|
|
12
|
+
scopedCascadeGraphHash: input[`${prefix}ScopedCascadeGraphHash`] ?? input.scopedCascadeGraphHash
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -149,7 +150,7 @@ function unsupportedSourceShapeChanges(baseSheet, currentSheet, declarationChang
|
|
|
149
150
|
if (changedDeclarationRuleKeys.has(before?.ruleKey) || changedDeclarationRuleKeys.has(after?.ruleKey)) return [];
|
|
150
151
|
return [{
|
|
151
152
|
side,
|
|
152
|
-
reasonCode:
|
|
153
|
+
reasonCode: sourceShapeChangeReason(before, after),
|
|
153
154
|
shapeKey: key,
|
|
154
155
|
before: sourceShapeDetails(before),
|
|
155
156
|
after: sourceShapeDetails(after)
|
|
@@ -180,6 +181,19 @@ function sourceShapeIndex(sheet, hash) {
|
|
|
180
181
|
atRuleName: record.atRuleName,
|
|
181
182
|
conditionText: record.conditionText,
|
|
182
183
|
representedByDeclarations: false,
|
|
184
|
+
unsupportedReasonCode: atRuleUnsupportedReasonCode(record),
|
|
185
|
+
hash: record.atRuleHash
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (record.kind === 'at-rule-statement') {
|
|
189
|
+
const shapeKey = `at-rule-statement:${[...(record.scopes ?? []), record.atRuleName, record.conditionText].join('::')}`;
|
|
190
|
+
result.set(shapeKey, {
|
|
191
|
+
kind: 'at-rule-statement',
|
|
192
|
+
atRuleName: record.atRuleName,
|
|
193
|
+
conditionText: record.conditionText,
|
|
194
|
+
statementText: record.statementText,
|
|
195
|
+
representedByDeclarations: false,
|
|
196
|
+
unsupportedReasonCode: atRuleStatementUnsupportedReasonCode(record),
|
|
183
197
|
hash: record.atRuleHash
|
|
184
198
|
});
|
|
185
199
|
}
|
|
@@ -194,7 +208,15 @@ function conflict(id, sourcePath, code, reasonCode, details = {}) {
|
|
|
194
208
|
|
|
195
209
|
function sameContractChange(left, right) { return (left.after?.hash ?? '') === (right.after?.hash ?? '') && left.kind === right.kind; }
|
|
196
210
|
function contractChangeDetails(change) { return { kind: change.kind, contractKind: (change.after ?? change.before)?.contractKind, name: (change.after ?? change.before)?.name, hash: change.after?.hash }; }
|
|
197
|
-
function sourceShapeDetails(shape) { return shape ? { kind: shape.kind, selectors: shape.selectors, atRuleName: shape.atRuleName, conditionText: shape.conditionText, representedByDeclarations: shape.representedByDeclarations } : undefined; }
|
|
211
|
+
function sourceShapeDetails(shape) { return shape ? { kind: shape.kind, selectors: shape.selectors, atRuleName: shape.atRuleName, conditionText: shape.conditionText, statementText: shape.statementText, representedByDeclarations: shape.representedByDeclarations } : undefined; }
|
|
212
|
+
function sourceShapeChangeReason(before, after) {
|
|
213
|
+
if (!before && after?.kind === 'at-rule') return 'css-atrule-new-scope-unsupported';
|
|
214
|
+
if (before?.kind === 'at-rule' || after?.kind === 'at-rule') return after?.unsupportedReasonCode ?? before?.unsupportedReasonCode ?? 'css-atrule-condition-edit-unsupported';
|
|
215
|
+
if (before?.kind === 'at-rule-statement' || after?.kind === 'at-rule-statement') return after?.unsupportedReasonCode ?? before?.unsupportedReasonCode ?? 'css-atrule-statement-unsupported';
|
|
216
|
+
return 'css-source-shape-unsupported';
|
|
217
|
+
}
|
|
218
|
+
function atRuleUnsupportedReasonCode(record) { return record.atRuleName === 'layer' ? 'css-layer-name-edit-unsupported' : 'css-atrule-condition-edit-unsupported'; }
|
|
219
|
+
function atRuleStatementUnsupportedReasonCode(record) { return record.atRuleName === 'layer' ? 'css-layer-order-statement-unsupported' : 'css-atrule-statement-unsupported'; }
|
|
198
220
|
function ruleIdentityKey(record) { return [...(record.scopes ?? []), record.selectors.join(',')].join('::'); }
|
|
199
221
|
function unique(values) { return [...new Set(values.filter(Boolean))]; }
|
|
200
222
|
function uniqueProofGaps(values) {
|
package/dist/semantic-merge.js
CHANGED
|
@@ -10,8 +10,6 @@ function safeMergeCssSource(input = {}, context = {}) {
|
|
|
10
10
|
const head = input.headSourceText ?? base;
|
|
11
11
|
if (typeof base !== 'string' || typeof worker !== 'string' || typeof head !== 'string') return blocked(id, sourcePath, 'css-source-text-missing');
|
|
12
12
|
if (worker === head) return merged(id, sourcePath, worker, 'worker-head-identical', hash);
|
|
13
|
-
if (worker === base) return merged(id, sourcePath, head, 'worker-unchanged', hash);
|
|
14
|
-
if (head === base) return merged(id, sourcePath, worker, 'head-unchanged', hash);
|
|
15
13
|
const sheets = {
|
|
16
14
|
base: parseSheet(base, sheetOptions(input, 'base', sourcePath)),
|
|
17
15
|
worker: parseSheet(worker, sheetOptions(input, 'worker', sourcePath)),
|
|
@@ -47,7 +45,17 @@ function safeMergeCssSource(input = {}, context = {}) {
|
|
|
47
45
|
function declarationIndex(sheet) {
|
|
48
46
|
const declarations = new Map();
|
|
49
47
|
const order = [];
|
|
48
|
+
const statements = [];
|
|
50
49
|
for (const record of sheet.records) {
|
|
50
|
+
if (record.kind === 'at-rule-statement') {
|
|
51
|
+
statements.push({
|
|
52
|
+
key: record.atRuleHash,
|
|
53
|
+
scopes: record.scopes ?? [],
|
|
54
|
+
statementText: record.statementText,
|
|
55
|
+
atRuleName: record.atRuleName,
|
|
56
|
+
conditionText: record.conditionText
|
|
57
|
+
});
|
|
58
|
+
}
|
|
51
59
|
if (record.kind !== 'rule') continue;
|
|
52
60
|
const ruleKey = ruleIdentityKey(record);
|
|
53
61
|
for (const declaration of record.declarations ?? []) {
|
|
@@ -66,7 +74,7 @@ function declarationIndex(sheet) {
|
|
|
66
74
|
order.push(entry.key);
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
|
-
return { declarations, order: unique(order) };
|
|
77
|
+
return { declarations, order: unique(order), statements };
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
function changedDeclarations(baseIndex, currentIndex, side) {
|
|
@@ -143,25 +151,55 @@ function applyDeclarationChanges(index, changes) {
|
|
|
143
151
|
if (!order.includes(change.key)) order.push(change.key);
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
|
-
return { declarations, order: order.filter((key) => declarations.has(key)) };
|
|
154
|
+
return { declarations, order: order.filter((key) => declarations.has(key)), statements: index.statements ?? [] };
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
function renderDeclarationIndex(index) {
|
|
150
158
|
const groups = new Map();
|
|
151
159
|
for (const key of index.order) {
|
|
152
160
|
const declaration = index.declarations.get(key);
|
|
153
|
-
if (!declaration
|
|
161
|
+
if (!declaration) continue;
|
|
154
162
|
groups.set(declaration.ruleKey, [...(groups.get(declaration.ruleKey) ?? []), declaration]);
|
|
155
163
|
}
|
|
156
164
|
const chunks = [];
|
|
165
|
+
for (const statement of index.statements ?? []) renderAtRuleStatement(chunks, statement);
|
|
157
166
|
for (const declarations of groups.values()) {
|
|
158
|
-
chunks
|
|
159
|
-
for (const declaration of declarations) chunks.push(` ${declaration.property}: ${declaration.value};`);
|
|
160
|
-
chunks.push('}', '');
|
|
167
|
+
renderDeclarationGroup(chunks, declarations);
|
|
161
168
|
}
|
|
162
169
|
return `${chunks.join('\n').trimEnd()}\n`;
|
|
163
170
|
}
|
|
164
171
|
|
|
172
|
+
function renderAtRuleStatement(chunks, statement) {
|
|
173
|
+
let indent = 0;
|
|
174
|
+
for (const scope of statement.scopes ?? []) {
|
|
175
|
+
chunks.push(`${spaces(indent)}${scope} {`);
|
|
176
|
+
indent += 2;
|
|
177
|
+
}
|
|
178
|
+
chunks.push(`${spaces(indent)}${statement.statementText}`);
|
|
179
|
+
for (let index = (statement.scopes ?? []).length - 1; index >= 0; index -= 1) {
|
|
180
|
+
indent -= 2;
|
|
181
|
+
chunks.push(`${spaces(indent)}}`);
|
|
182
|
+
}
|
|
183
|
+
chunks.push('');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function renderDeclarationGroup(chunks, declarations) {
|
|
187
|
+
const first = declarations[0];
|
|
188
|
+
let indent = 0;
|
|
189
|
+
for (const scope of first.scopes) {
|
|
190
|
+
chunks.push(`${spaces(indent)}${scope} {`);
|
|
191
|
+
indent += 2;
|
|
192
|
+
}
|
|
193
|
+
chunks.push(`${spaces(indent)}${first.selectors.join(', ')} {`);
|
|
194
|
+
for (const declaration of declarations) chunks.push(`${spaces(indent + 2)}${declaration.property}: ${declaration.value};`);
|
|
195
|
+
chunks.push(`${spaces(indent)}}`);
|
|
196
|
+
for (let index = first.scopes.length - 1; index >= 0; index -= 1) {
|
|
197
|
+
indent -= 2;
|
|
198
|
+
chunks.push(`${spaces(indent)}}`);
|
|
199
|
+
}
|
|
200
|
+
chunks.push('');
|
|
201
|
+
}
|
|
202
|
+
|
|
165
203
|
function merged(id, sourcePath, sourceText, operation, hash, extra = {}) {
|
|
166
204
|
return result(id, sourcePath, 'merged', {
|
|
167
205
|
operation,
|
|
@@ -209,6 +247,7 @@ function proofGapsForDeclaration(record, declaration) {
|
|
|
209
247
|
return (record.proofGaps ?? []).filter((gap) => gap.code !== 'css-shorthand-expansion-unproved' || gap.summary.includes(` ${declaration.property} `));
|
|
210
248
|
}
|
|
211
249
|
function unique(values) { return [...new Set(values.filter(Boolean))]; }
|
|
250
|
+
function spaces(count) { return ' '.repeat(Math.max(0, count)); }
|
|
212
251
|
|
|
213
252
|
function shorthandGroupForProperty(property) {
|
|
214
253
|
if (ShorthandGroups.has(property)) return property;
|
package/package.json
CHANGED