stablekit.ts 0.6.0 → 0.6.1
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/stylelint.cjs +63 -1
- package/dist/stylelint.js +63 -1
- package/package.json +1 -1
package/dist/stylelint.cjs
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(stylelint_exports);
|
|
|
26
26
|
var pluginRuleName = "stablekit/no-functional-in-utility";
|
|
27
27
|
var descendantColorRuleName = "stablekit/no-descendant-color-in-state";
|
|
28
28
|
var duplicateRulesetRuleName = "stablekit/no-duplicate-ruleset";
|
|
29
|
+
var nearDuplicateRuleName = "stablekit/no-near-duplicate-ruleset";
|
|
29
30
|
function createFunctionalTokenPlugin(prefixes) {
|
|
30
31
|
const rule = (enabled) => {
|
|
31
32
|
return (root, result) => {
|
|
@@ -118,13 +119,72 @@ Declarations: ${fingerprint}`,
|
|
|
118
119
|
rule
|
|
119
120
|
};
|
|
120
121
|
}
|
|
122
|
+
function createNearDuplicatePlugin() {
|
|
123
|
+
function collectDirectDecls(ruleNode) {
|
|
124
|
+
const map = /* @__PURE__ */ new Map();
|
|
125
|
+
for (const child of ruleNode.nodes ?? []) {
|
|
126
|
+
if (child.type === "decl") {
|
|
127
|
+
map.set(child.prop, child.value);
|
|
128
|
+
} else if (child.type === "atrule" && child.name === "apply") {
|
|
129
|
+
map.set(`@apply`, child.params);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return map;
|
|
133
|
+
}
|
|
134
|
+
const rule = (enabled) => {
|
|
135
|
+
return (root, result) => {
|
|
136
|
+
if (!enabled) return;
|
|
137
|
+
const groups = /* @__PURE__ */ new Map();
|
|
138
|
+
root.walkRules((ruleNode) => {
|
|
139
|
+
if (ruleNode.parent?.type === "atrule" && ruleNode.parent.name === "keyframes") return;
|
|
140
|
+
const propMap = collectDirectDecls(ruleNode);
|
|
141
|
+
if (propMap.size < 2) return;
|
|
142
|
+
const propKey = [...propMap.keys()].sort().join(", ");
|
|
143
|
+
const entry = { selector: ruleNode.selector, propKey, propMap, node: ruleNode };
|
|
144
|
+
const group = groups.get(propKey);
|
|
145
|
+
if (group) {
|
|
146
|
+
for (const existing of group) {
|
|
147
|
+
let diffCount = 0;
|
|
148
|
+
let diffProp = "";
|
|
149
|
+
let diffOld = "";
|
|
150
|
+
let diffNew = "";
|
|
151
|
+
for (const [prop, value] of entry.propMap) {
|
|
152
|
+
const existingValue = existing.propMap.get(prop);
|
|
153
|
+
if (existingValue !== value) {
|
|
154
|
+
diffCount++;
|
|
155
|
+
diffProp = prop;
|
|
156
|
+
diffOld = existingValue ?? "(missing)";
|
|
157
|
+
diffNew = value;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (diffCount === 0) continue;
|
|
161
|
+
if (diffCount === 1 && diffProp !== "@apply") {
|
|
162
|
+
result.warn(
|
|
163
|
+
`Near-duplicate ruleset \u2014 "${ruleNode.selector}" differs from "${existing.selector}" only in ${diffProp} (${diffOld} \u2192 ${diffNew}). Consider consolidating to a single class.`,
|
|
164
|
+
{ node: ruleNode }
|
|
165
|
+
);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
group.push(entry);
|
|
170
|
+
} else {
|
|
171
|
+
groups.set(propKey, [entry]);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
ruleName: nearDuplicateRuleName,
|
|
178
|
+
rule
|
|
179
|
+
};
|
|
180
|
+
}
|
|
121
181
|
function createStyleLint(options = {}) {
|
|
122
182
|
const {
|
|
123
183
|
ignoreTypes = ["html", "body"],
|
|
124
184
|
functionalTokens = [],
|
|
125
185
|
files = ["src/**/*.css"]
|
|
126
186
|
} = options;
|
|
127
|
-
const plugins = [createDescendantColorPlugin(), createDuplicateRulesetPlugin()];
|
|
187
|
+
const plugins = [createDescendantColorPlugin(), createDuplicateRulesetPlugin(), createNearDuplicatePlugin()];
|
|
128
188
|
if (functionalTokens.length > 0) {
|
|
129
189
|
plugins.push(createFunctionalTokenPlugin(functionalTokens));
|
|
130
190
|
}
|
|
@@ -142,6 +202,8 @@ function createStyleLint(options = {}) {
|
|
|
142
202
|
[descendantColorRuleName]: true,
|
|
143
203
|
// Flag selectors with identical declarations for consolidation.
|
|
144
204
|
[duplicateRulesetRuleName]: true,
|
|
205
|
+
// Flag near-duplicate rulesets (same props, differ by 1 value).
|
|
206
|
+
[nearDuplicateRuleName]: true,
|
|
145
207
|
// Ban animating layout properties — causes reflow on every frame.
|
|
146
208
|
// Use transform (scaleY, translateY) or opacity instead.
|
|
147
209
|
"declaration-property-value-disallowed-list": [
|
package/dist/stylelint.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
var pluginRuleName = "stablekit/no-functional-in-utility";
|
|
3
3
|
var descendantColorRuleName = "stablekit/no-descendant-color-in-state";
|
|
4
4
|
var duplicateRulesetRuleName = "stablekit/no-duplicate-ruleset";
|
|
5
|
+
var nearDuplicateRuleName = "stablekit/no-near-duplicate-ruleset";
|
|
5
6
|
function createFunctionalTokenPlugin(prefixes) {
|
|
6
7
|
const rule = (enabled) => {
|
|
7
8
|
return (root, result) => {
|
|
@@ -94,13 +95,72 @@ Declarations: ${fingerprint}`,
|
|
|
94
95
|
rule
|
|
95
96
|
};
|
|
96
97
|
}
|
|
98
|
+
function createNearDuplicatePlugin() {
|
|
99
|
+
function collectDirectDecls(ruleNode) {
|
|
100
|
+
const map = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const child of ruleNode.nodes ?? []) {
|
|
102
|
+
if (child.type === "decl") {
|
|
103
|
+
map.set(child.prop, child.value);
|
|
104
|
+
} else if (child.type === "atrule" && child.name === "apply") {
|
|
105
|
+
map.set(`@apply`, child.params);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return map;
|
|
109
|
+
}
|
|
110
|
+
const rule = (enabled) => {
|
|
111
|
+
return (root, result) => {
|
|
112
|
+
if (!enabled) return;
|
|
113
|
+
const groups = /* @__PURE__ */ new Map();
|
|
114
|
+
root.walkRules((ruleNode) => {
|
|
115
|
+
if (ruleNode.parent?.type === "atrule" && ruleNode.parent.name === "keyframes") return;
|
|
116
|
+
const propMap = collectDirectDecls(ruleNode);
|
|
117
|
+
if (propMap.size < 2) return;
|
|
118
|
+
const propKey = [...propMap.keys()].sort().join(", ");
|
|
119
|
+
const entry = { selector: ruleNode.selector, propKey, propMap, node: ruleNode };
|
|
120
|
+
const group = groups.get(propKey);
|
|
121
|
+
if (group) {
|
|
122
|
+
for (const existing of group) {
|
|
123
|
+
let diffCount = 0;
|
|
124
|
+
let diffProp = "";
|
|
125
|
+
let diffOld = "";
|
|
126
|
+
let diffNew = "";
|
|
127
|
+
for (const [prop, value] of entry.propMap) {
|
|
128
|
+
const existingValue = existing.propMap.get(prop);
|
|
129
|
+
if (existingValue !== value) {
|
|
130
|
+
diffCount++;
|
|
131
|
+
diffProp = prop;
|
|
132
|
+
diffOld = existingValue ?? "(missing)";
|
|
133
|
+
diffNew = value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (diffCount === 0) continue;
|
|
137
|
+
if (diffCount === 1 && diffProp !== "@apply") {
|
|
138
|
+
result.warn(
|
|
139
|
+
`Near-duplicate ruleset \u2014 "${ruleNode.selector}" differs from "${existing.selector}" only in ${diffProp} (${diffOld} \u2192 ${diffNew}). Consider consolidating to a single class.`,
|
|
140
|
+
{ node: ruleNode }
|
|
141
|
+
);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
group.push(entry);
|
|
146
|
+
} else {
|
|
147
|
+
groups.set(propKey, [entry]);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
ruleName: nearDuplicateRuleName,
|
|
154
|
+
rule
|
|
155
|
+
};
|
|
156
|
+
}
|
|
97
157
|
function createStyleLint(options = {}) {
|
|
98
158
|
const {
|
|
99
159
|
ignoreTypes = ["html", "body"],
|
|
100
160
|
functionalTokens = [],
|
|
101
161
|
files = ["src/**/*.css"]
|
|
102
162
|
} = options;
|
|
103
|
-
const plugins = [createDescendantColorPlugin(), createDuplicateRulesetPlugin()];
|
|
163
|
+
const plugins = [createDescendantColorPlugin(), createDuplicateRulesetPlugin(), createNearDuplicatePlugin()];
|
|
104
164
|
if (functionalTokens.length > 0) {
|
|
105
165
|
plugins.push(createFunctionalTokenPlugin(functionalTokens));
|
|
106
166
|
}
|
|
@@ -118,6 +178,8 @@ function createStyleLint(options = {}) {
|
|
|
118
178
|
[descendantColorRuleName]: true,
|
|
119
179
|
// Flag selectors with identical declarations for consolidation.
|
|
120
180
|
[duplicateRulesetRuleName]: true,
|
|
181
|
+
// Flag near-duplicate rulesets (same props, differ by 1 value).
|
|
182
|
+
[nearDuplicateRuleName]: true,
|
|
121
183
|
// Ban animating layout properties — causes reflow on every frame.
|
|
122
184
|
// Use transform (scaleY, translateY) or opacity instead.
|
|
123
185
|
"declaration-property-value-disallowed-list": [
|
package/package.json
CHANGED