@shapeshift-labs/frontier-lang-css 0.1.8 → 0.1.9

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/index.d.ts CHANGED
@@ -235,9 +235,7 @@ export interface CssSemanticMergeEvidence {
235
235
  }
236
236
 
237
237
  export interface CssSafeMergeConflict {
238
- readonly code: string;
239
- readonly gateId: 'css-semantic-merge' | string;
240
- readonly sourcePath?: string;
238
+ readonly code: string; readonly gateId: 'css-semantic-merge' | string; readonly sourcePath?: string;
241
239
  readonly details: Readonly<Record<string, unknown>> & { readonly reasonCode: string; readonly conflictKey: string };
242
240
  }
243
241
 
@@ -262,22 +260,20 @@ export interface CssSafeMergeResult {
262
260
  export interface CssSafeMergeParserEvidence {
263
261
  readonly kind: 'frontier.lang.cssSafeMergeParserEvidence'; readonly version: 1; readonly parserNames: readonly string[];
264
262
  readonly sourceCodeLocationInfo: boolean; readonly parserBackedSourceSpans: boolean; readonly parserBackedDeclarationSpans: boolean; readonly parserBackedTriviaHashes: boolean;
265
- readonly scopedCascadeGraphHashPresent: boolean; readonly parseErrors: number;
266
- readonly sides: Readonly<Record<string, CssSafeMergeParserSideEvidence>>;
263
+ readonly scopedCascadeGraphHashPresent: boolean; readonly parseErrors: number; readonly sides: Readonly<Record<string, CssSafeMergeParserSideEvidence>>;
267
264
  }
268
265
 
269
266
  export interface CssSafeMergeParserSideEvidence {
270
- readonly parserName: string;
271
- readonly sourceCodeLocationInfo: boolean; readonly parserBackedSourceSpans: boolean; readonly parserBackedDeclarationSpans: boolean; readonly parserBackedTriviaHashes: boolean;
267
+ readonly parserName: string; readonly sourceCodeLocationInfo: boolean; readonly parserBackedSourceSpans: boolean; readonly parserBackedDeclarationSpans: boolean; readonly parserBackedTriviaHashes: boolean;
272
268
  readonly scopedCascadeGraphHashPresent: boolean; readonly parseErrors: number; readonly recordCount: number; readonly declarationCount: number;
273
269
  }
274
270
 
275
271
  export interface CssSafeMergeSelectorTargetEvidence {
276
- readonly kind: 'frontier.lang.cssSafeMergeSelectorTargetEvidence'; readonly version: 1;
277
- readonly selectorTargetGraphHashPresent: boolean; readonly parserBackedRuleSpans: boolean;
272
+ readonly kind: 'frontier.lang.cssSafeMergeSelectorTargetEvidence'; readonly version: 1; readonly selectorTargetGraphHashPresent: boolean; readonly parserBackedRuleSpans: boolean;
278
273
  readonly selectorMoveCount: number; readonly workerSelectorMoves: number; readonly headSelectorMoves: number;
279
274
  readonly sides: Readonly<Record<string, CssSafeMergeSelectorTargetSideEvidence>>;
280
275
  readonly moves: Readonly<Record<'worker' | 'head', readonly CssSafeMergeSelectorMove[]>>;
276
+ readonly rebasedChangeCount?: number; readonly rebaseProofs?: readonly CssSafeMergeSelectorTargetRebaseProof[];
281
277
  }
282
278
 
283
279
  export interface CssSafeMergeSelectorTargetSideEvidence {
@@ -288,7 +284,15 @@ export interface CssSafeMergeSelectorTargetSideEvidence {
288
284
  export interface CssSafeMergeSelectorMove {
289
285
  readonly side: string; readonly property: string; readonly beforeRuleKey: string; readonly afterRuleKey: string;
290
286
  readonly beforeSelectors?: readonly string[]; readonly afterSelectors?: readonly string[]; readonly beforeScopes?: readonly string[]; readonly afterScopes?: readonly string[];
291
- readonly declarationHash: string; readonly selectorTargetGraphHashPresent: boolean;
287
+ readonly declarationHash: string; readonly beforeSelectorTargetGraphHash?: string; readonly afterSelectorTargetGraphHash?: string; readonly selectorTargetGraphHashPresent: boolean;
288
+ }
289
+
290
+ export interface CssSafeMergeSelectorTargetRebaseProof {
291
+ readonly kind: 'css-selector-target-rebase'; readonly side: string; readonly fromRuleKey: string; readonly toRuleKey: string; readonly property: string; readonly cascadeKey: string;
292
+ }
293
+
294
+ export interface CssSelectorTargetEquivalence {
295
+ readonly fromRuleKey?: string; readonly toRuleKey?: string; readonly fromSelectors?: readonly string[]; readonly toSelectors?: readonly string[]; readonly graphHash?: string;
292
296
  }
293
297
 
294
298
  export interface CssSafeMergeInput {
@@ -296,7 +300,7 @@ export interface CssSafeMergeInput {
296
300
  readonly cssModule?: boolean; readonly cssModules?: boolean;
297
301
  readonly generatedClassNameMap?: Readonly<Record<string, string>>;
298
302
  readonly generatedClassNameMapHash?: string; readonly jsTsUseSiteGraphHash?: string; readonly cssModuleCompositionGraphHash?: string; readonly icssGraphHash?: string; readonly scopedCascadeGraphHash?: string;
299
- readonly selectorTargetGraphHash?: string;
303
+ readonly selectorTargetGraphHash?: string; readonly selectorTargetEquivalences?: readonly CssSelectorTargetEquivalence[];
300
304
  readonly baseGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly workerGeneratedClassNameMap?: Readonly<Record<string, string>>; readonly headGeneratedClassNameMap?: Readonly<Record<string, string>>;
301
305
  readonly baseGeneratedClassNameMapHash?: string; readonly workerGeneratedClassNameMapHash?: string; readonly headGeneratedClassNameMapHash?: string;
302
306
  readonly baseJsTsUseSiteGraphHash?: string; readonly workerJsTsUseSiteGraphHash?: string; readonly headJsTsUseSiteGraphHash?: string;
@@ -46,26 +46,64 @@ function selectorTargetMoves(changes, side) {
46
46
  beforeScopes: deletion.before.scopes,
47
47
  afterScopes: addition.after.scopes,
48
48
  declarationHash: addition.after.declarationHash,
49
+ beforeSelectorTargetGraphHash: deletion.before.selectorTargetGraphHash,
50
+ afterSelectorTargetGraphHash: addition.after.selectorTargetGraphHash,
49
51
  selectorTargetGraphHashPresent: Boolean(deletion.before.selectorTargetGraphHash && addition.after.selectorTargetGraphHash)
50
52
  });
51
53
  }
52
54
  return moves;
53
55
  }
54
56
 
55
- function selectorTargetMoveConflicts(id, sourcePath, selectorTargetEvidence, changed) {
56
- return [
57
- ...selectorTargetMoveSideConflicts(id, sourcePath, selectorTargetEvidence.moves.worker, selectorTargetEvidence.moves.head, changed.head),
58
- ...selectorTargetMoveSideConflicts(id, sourcePath, selectorTargetEvidence.moves.head, selectorTargetEvidence.moves.worker, changed.worker)
59
- ];
57
+ function planSelectorTargetRebase(id, sourcePath, selectorTargetEvidence, changed, options = {}) {
58
+ const planned = { worker: [...changed.worker], head: [...changed.head] };
59
+ const worker = selectorTargetMoveSidePlan(id, sourcePath, selectorTargetEvidence.moves.worker, selectorTargetEvidence.moves.head, planned.head, options);
60
+ const head = selectorTargetMoveSidePlan(id, sourcePath, selectorTargetEvidence.moves.head, selectorTargetEvidence.moves.worker, planned.worker, options);
61
+ return {
62
+ changed: planned,
63
+ conflicts: [...worker.conflicts, ...head.conflicts],
64
+ evidence: { ...selectorTargetEvidence, rebasedChangeCount: worker.rebaseProofs.length + head.rebaseProofs.length, rebaseProofs: [...worker.rebaseProofs, ...head.rebaseProofs] }
65
+ };
66
+ }
67
+
68
+ function selectorTargetMoveSidePlan(id, sourcePath, moves, oppositeMoves, oppositeChanges, options) {
69
+ const conflicts = [];
70
+ const rebaseProofs = [];
71
+ for (const move of moves) {
72
+ if (oppositeMoves.some((oppositeMove) => sameSelectorMove(move, oppositeMove))) continue;
73
+ for (let index = 0; index < oppositeChanges.length; index += 1) {
74
+ const change = oppositeChanges[index];
75
+ if (!selectorMoveTouchesChange(move, change)) continue;
76
+ if (canRebaseChange(move, change, options)) {
77
+ const rebased = rebaseChangeToSelectorMove(change, move);
78
+ oppositeChanges[index] = rebased.change;
79
+ rebaseProofs.push(rebased.proof);
80
+ } else conflicts.push(conflict(id, sourcePath, change.key, move, change));
81
+ }
82
+ }
83
+ return { conflicts, rebaseProofs };
60
84
  }
61
85
 
62
- function selectorTargetMoveSideConflicts(id, sourcePath, moves, oppositeMoves, oppositeChanges) {
63
- return moves.flatMap((move) => {
64
- if (oppositeMoves.some((oppositeMove) => sameSelectorMove(move, oppositeMove))) return [];
65
- return oppositeChanges.filter((change) => selectorMoveTouchesChange(move, change)).map((change) => conflict(id, sourcePath, change.key, move, change));
86
+ function canRebaseChange(move, change, options) {
87
+ return change.kind === 'add' && change.after && hasSelectorTargetEquivalence(move, options);
88
+ }
89
+
90
+ function hasSelectorTargetEquivalence(move, options) {
91
+ return (options.selectorTargetEquivalences ?? []).some((entry) => {
92
+ const ruleKeysMatch = entry.fromRuleKey === move.beforeRuleKey && entry.toRuleKey === move.afterRuleKey;
93
+ const selectorsMatch = selectorListKey(entry.fromSelectors) === selectorListKey(move.beforeSelectors) && selectorListKey(entry.toSelectors) === selectorListKey(move.afterSelectors);
94
+ const graphMatches = !entry.graphHash || entry.graphHash === move.beforeSelectorTargetGraphHash || entry.graphHash === move.afterSelectorTargetGraphHash;
95
+ return graphMatches && (ruleKeysMatch || selectorsMatch);
66
96
  });
67
97
  }
68
98
 
99
+ function rebaseChangeToSelectorMove(change, move) {
100
+ const after = { ...change.after, ruleKey: move.afterRuleKey, selectors: move.afterSelectors, scopes: move.afterScopes ?? [], key: cascadeKey(move.afterScopes, move.afterSelectors, change.after.property), rebasedFromRuleKey: move.beforeRuleKey };
101
+ return {
102
+ change: { ...change, key: after.key, after },
103
+ proof: { kind: 'css-selector-target-rebase', side: change.side, fromRuleKey: move.beforeRuleKey, toRuleKey: move.afterRuleKey, property: change.after.property, cascadeKey: after.key }
104
+ };
105
+ }
106
+
69
107
  function conflict(id, sourcePath, cascadeKey, selectorMove, change) {
70
108
  return {
71
109
  code: 'css-selector-target-conflict',
@@ -94,6 +132,8 @@ function sameSelectorMove(left, right) {
94
132
  return left.property === right.property && left.beforeRuleKey === right.beforeRuleKey && left.afterRuleKey === right.afterRuleKey && left.declarationHash === right.declarationHash;
95
133
  }
96
134
 
135
+ function cascadeKey(scopes = [], selectors = [], property) { return [...scopes, selectors.join(','), property].join('::'); }
136
+ function selectorListKey(value = []) { return Array.isArray(value) ? value.join(',') : undefined; }
97
137
  function changeDetails(change) { return { kind: change.kind, property: (change.after ?? change.before)?.property, value: change.after?.value, important: change.after?.important }; }
98
138
 
99
- export { mergeSelectorTargetEvidence, selectorTargetMoveConflicts };
139
+ export { mergeSelectorTargetEvidence, planSelectorTargetRebase };
@@ -1,5 +1,5 @@
1
1
  import { cssModuleContractChanges, cssModuleContractConflicts, sheetOptions, unsupportedSourceShapeConflicts } from './semantic-merge-css-modules.js';
2
- import { mergeSelectorTargetEvidence, selectorTargetMoveConflicts } from './semantic-merge-selector-targets.js';
2
+ import { mergeSelectorTargetEvidence, planSelectorTargetRebase } from './semantic-merge-selector-targets.js';
3
3
 
4
4
  function safeMergeCssSource(input = {}, context = {}) {
5
5
  const parseSheet = context.parseCssSemanticSheet;
@@ -31,11 +31,10 @@ function safeMergeCssSource(input = {}, context = {}) {
31
31
  const moduleConflicts = cssModuleContractConflicts(id, sourcePath, moduleChanges);
32
32
  const sourceShapeConflicts = unsupportedSourceShapeConflicts(id, sourcePath, sheets, changed, hash);
33
33
  const parserEvidence = mergeParserEvidence(sheets);
34
- const selectorTargetEvidence = mergeSelectorTargetEvidence(sheets, changed);
35
- const selectorTargetConflicts = selectorTargetMoveConflicts(id, sourcePath, selectorTargetEvidence, changed);
36
- const conflicts = [...parserConflicts, ...proofConflicts, ...overlapConflicts, ...moduleConflicts, ...sourceShapeConflicts, ...selectorTargetConflicts];
37
- if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence, selectorTargetEvidence });
38
- const mergedIndex = applyDeclarationChanges(applyDeclarationChanges(indexes.base, changed.head), changed.worker);
34
+ const selectorTargetPlan = planSelectorTargetRebase(id, sourcePath, mergeSelectorTargetEvidence(sheets, changed), changed, input);
35
+ const conflicts = [...parserConflicts, ...proofConflicts, ...overlapConflicts, ...moduleConflicts, ...sourceShapeConflicts, ...selectorTargetPlan.conflicts];
36
+ if (conflicts.length) return blocked(id, sourcePath, 'css-semantic-merge-conflict', conflicts, { parserEvidence, selectorTargetEvidence: selectorTargetPlan.evidence });
37
+ const mergedIndex = applyDeclarationChanges(applyDeclarationChanges(indexes.base, selectorTargetPlan.changed.head), selectorTargetPlan.changed.worker);
39
38
  return merged(id, sourcePath, renderDeclarationIndex(mergedIndex), 'semantic-declaration-merge', hash, {
40
39
  baseSheetHash: sheets.base.sheetHash,
41
40
  workerSheetHash: sheets.worker.sheetHash,
@@ -45,7 +44,7 @@ function safeMergeCssSource(input = {}, context = {}) {
45
44
  workerChangedCssModuleContracts: moduleChanges.worker.length,
46
45
  headChangedCssModuleContracts: moduleChanges.head.length,
47
46
  parserEvidence,
48
- selectorTargetEvidence
47
+ selectorTargetEvidence: selectorTargetPlan.evidence
49
48
  });
50
49
  }
51
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-css",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "CSS semantic merge evidence and projection adapter for Frontier Lang semantic source documents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",