@wix/origami-lint 1.34.25 → 1.34.27
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BREAKPOINT_AT_RULES, createCssFileReporter, describeAtRule, getCssChildren, getDashedId, getMixinId, getSegmentClass, getSelectorSegments, isCssIdentifier, isSafeCssIdentifier } from "../../rules-api/index.js";
|
|
2
2
|
import { defineRule } from "./define-rule.js";
|
|
3
|
-
const CHECKS = ['invalid_mixin_id', 'invalid_mixin_result', 'missing_parent_selector', 'bare_parent_selector', 'nested_style_rule', 'apply_at_result_root', 'nested_mixin', 'invalid_result_at_rule', 'invalid_parent_selector', 'conditional_global_style_link', 'invalid_linked_style_id', 'missing_linked_style', 'duplicate_global_style_link', 'link_with_values', 'missing_global_style_link'];
|
|
3
|
+
const CHECKS = ['invalid_mixin_id', 'invalid_mixin_result', 'missing_parent_selector', 'bare_parent_selector', 'nested_style_rule', 'apply_at_result_root', 'nested_mixin', 'invalid_result_at_rule', 'invalid_parent_selector', 'conditional_global_style_link', 'invalid_linked_style_id', 'missing_linked_style', 'duplicate_global_style_link', 'link_with_values', 'missing_global_style_link', 'mixin_link_cycle'];
|
|
4
4
|
export default defineRule({
|
|
5
5
|
name: 'global-styles',
|
|
6
6
|
checks: CHECKS,
|
|
@@ -22,9 +22,12 @@ function checkMixinsAndLinks(root, file, report) {
|
|
|
22
22
|
const mixins = getCssChildren(root).filter((node) => node.type === 'atrule' && node.name === 'mixin');
|
|
23
23
|
const rules = mixins.flatMap((mixin) => checkMixin(mixin, reportInFile));
|
|
24
24
|
const declaredIds = new Set(mixins.flatMap((mixin) => getMixinId(mixin) ?? []));
|
|
25
|
-
const linkedParts = checkLinks(rules.filter((rule) => rule.apply), declaredIds, reportInFile);
|
|
25
|
+
const { linkedParts, validLinks } = checkLinks(rules.filter((rule) => rule.apply), declaredIds, reportInFile);
|
|
26
26
|
for (const group of groupBySelector(rules))
|
|
27
27
|
checkLinkOrValues(group, linkedParts, reportInFile);
|
|
28
|
+
// the build keeps the last `@mixin` of an id (mixins.js bodies.set): its links are the ones it follows
|
|
29
|
+
const lastMixinById = new Map(mixins.flatMap((mixin) => { const id = getMixinId(mixin); return id === null ? [] : [[id, mixin]]; }));
|
|
30
|
+
checkLinkCycles(validLinks.filter((link) => getEnclosingMixin(link.node) === lastMixinById.get(link.mixin)), reportInFile);
|
|
28
31
|
}
|
|
29
32
|
/** `@mixin --id { @result { … } }` (CONV-astParser.ts:287, :293), then its `@result`; returns its `&` rules that pass. */
|
|
30
33
|
function checkMixin(mixin, reportInFile) {
|
|
@@ -107,6 +110,7 @@ function checkSelector(rule, reportInFile) {
|
|
|
107
110
|
function checkLinks(links, declaredIds, reportInFile) {
|
|
108
111
|
const linkedParts = new Set();
|
|
109
112
|
const validLinkParts = new Set();
|
|
113
|
+
const validLinks = [];
|
|
110
114
|
for (const link of links) {
|
|
111
115
|
const child = link.apply.params.trim();
|
|
112
116
|
const linkDescription = `\`@mixin --${link.mixin}\`'s link \`@apply ${child}\``;
|
|
@@ -135,8 +139,43 @@ function checkLinks(links, declaredIds, reportInFile) {
|
|
|
135
139
|
continue;
|
|
136
140
|
}
|
|
137
141
|
validLinkParts.add(part);
|
|
142
|
+
validLinks.push(link);
|
|
138
143
|
}
|
|
139
|
-
return linkedParts;
|
|
144
|
+
return { linkedParts, validLinks };
|
|
145
|
+
}
|
|
146
|
+
/** Reports each link that closes a cycle (OUR-007): a walk from each mixin in the file's order, depth first, visiting each mixin once; a link
|
|
147
|
+
* to a mixin still on the walk's path leads back to it. Every cycle holds such a link, so removing the reported ones breaks them all. */
|
|
148
|
+
function checkLinkCycles(validLinks, reportInFile) {
|
|
149
|
+
const linksByMixin = new Map();
|
|
150
|
+
for (const link of validLinks)
|
|
151
|
+
linksByMixin.set(link.mixin, [...(linksByMixin.get(link.mixin) ?? []), link]);
|
|
152
|
+
const visited = new Set();
|
|
153
|
+
for (const start of linksByMixin.keys()) {
|
|
154
|
+
if (visited.has(start))
|
|
155
|
+
continue;
|
|
156
|
+
visited.add(start);
|
|
157
|
+
// the walk's path: each mixin on it, and how many of its links were followed
|
|
158
|
+
const path = [{ mixin: start, next: 0 }];
|
|
159
|
+
while (path.length > 0) {
|
|
160
|
+
const step = path.at(-1);
|
|
161
|
+
const link = linksByMixin.get(step.mixin)?.[step.next++];
|
|
162
|
+
if (!link) {
|
|
163
|
+
path.pop();
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const child = getDashedId(link.apply.params);
|
|
167
|
+
const onPath = path.findIndex(({ mixin }) => mixin === child);
|
|
168
|
+
if (onPath !== -1)
|
|
169
|
+
reportCycle(link, child, [...path.slice(onPath).map(({ mixin }) => mixin), child], reportInFile);
|
|
170
|
+
else if (!visited.has(child)) {
|
|
171
|
+
visited.add(child);
|
|
172
|
+
path.push({ mixin: child, next: 0 });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function reportCycle(link, child, chain, reportInFile) {
|
|
178
|
+
reportInFile(link.apply, 'mixin_link_cycle', `\`@mixin --${link.mixin}\` links \`--${child}\`, which leads back to it: ${chain.map((id) => `--${id}`).join(' → ')}. A global style can't contain itself: remove this link, or link a style that doesn't lead back to \`--${child}\` (tracker OUR-007).`);
|
|
140
179
|
}
|
|
141
180
|
/** One selector of a mixin (across its breakpoints): a link alone, or values; values on an inner part where a link names it (CONV-styleParser.ts:803, :698). */
|
|
142
181
|
function checkLinkOrValues(group, linkedParts, reportInFile) {
|
|
@@ -152,6 +191,13 @@ function checkLinkOrValues(group, linkedParts, reportInFile) {
|
|
|
152
191
|
reportInFile(first.node, 'missing_global_style_link', `\`@mixin --${first.mixin}\` styles \`${part}\` but links no global style there: add \`& ${part} { @apply --<child> }\` and move the values to that child's @mixin (tracker CONV-styleParser.ts:698).`);
|
|
153
192
|
}
|
|
154
193
|
// ── reading a mixin ──
|
|
194
|
+
/** The `@mixin` a rule of its `@result` sits in. */
|
|
195
|
+
function getEnclosingMixin(rule) {
|
|
196
|
+
for (let node = rule.parent; node; node = node.parent)
|
|
197
|
+
if (node.type === 'atrule' && node.name === 'mixin')
|
|
198
|
+
return node;
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
155
201
|
/** The mixin's one `@result` with no params and a block, beside nothing; undefined otherwise. */
|
|
156
202
|
function getResult(mixin) {
|
|
157
203
|
if (mixin.nodes === undefined)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/origami-lint",
|
|
3
|
-
"version": "1.34.
|
|
3
|
+
"version": "1.34.27",
|
|
4
4
|
"description": "Offline lint for an exported Origami site: lints the pages that changed since the last passing run",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"oxc-parser": "^0.151.0",
|
|
26
26
|
"oxlint": "^1.39.0",
|
|
27
27
|
"postcss": "^8.5.3",
|
|
28
|
-
"@wix/origami-bi": "0.
|
|
28
|
+
"@wix/origami-bi": "0.13.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^22.0.0",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
]
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
|
-
"falconPackageHash": "
|
|
55
|
+
"falconPackageHash": "b4ea23b3bf64c300ff406d11fcb377f370fb48f7fdf2e92f3a66f7c9"
|
|
56
56
|
}
|