stablekit.ts 0.6.0 → 0.6.2
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 +106 -1
- package/dist/stylelint.d.cts +5 -0
- package/dist/stylelint.d.ts +5 -0
- package/dist/stylelint.js +106 -1
- package/package.json +1 -1
package/dist/stylelint.cjs
CHANGED
|
@@ -26,6 +26,8 @@ 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";
|
|
30
|
+
var undefinedTokenRuleName = "stablekit/no-undefined-token";
|
|
29
31
|
function createFunctionalTokenPlugin(prefixes) {
|
|
30
32
|
const rule = (enabled) => {
|
|
31
33
|
return (root, result) => {
|
|
@@ -118,13 +120,112 @@ Declarations: ${fingerprint}`,
|
|
|
118
120
|
rule
|
|
119
121
|
};
|
|
120
122
|
}
|
|
123
|
+
function createNearDuplicatePlugin() {
|
|
124
|
+
function collectDirectDecls(ruleNode) {
|
|
125
|
+
const map = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const child of ruleNode.nodes ?? []) {
|
|
127
|
+
if (child.type === "decl") {
|
|
128
|
+
map.set(child.prop, child.value);
|
|
129
|
+
} else if (child.type === "atrule" && child.name === "apply") {
|
|
130
|
+
map.set(`@apply`, child.params);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return map;
|
|
134
|
+
}
|
|
135
|
+
const rule = (enabled) => {
|
|
136
|
+
return (root, result) => {
|
|
137
|
+
if (!enabled) return;
|
|
138
|
+
const groups = /* @__PURE__ */ new Map();
|
|
139
|
+
root.walkRules((ruleNode) => {
|
|
140
|
+
if (ruleNode.parent?.type === "atrule" && ruleNode.parent.name === "keyframes") return;
|
|
141
|
+
const propMap = collectDirectDecls(ruleNode);
|
|
142
|
+
if (propMap.size < 2) return;
|
|
143
|
+
const propKey = [...propMap.keys()].sort().join(", ");
|
|
144
|
+
const entry = { selector: ruleNode.selector, propKey, propMap, node: ruleNode };
|
|
145
|
+
const group = groups.get(propKey);
|
|
146
|
+
if (group) {
|
|
147
|
+
for (const existing of group) {
|
|
148
|
+
let diffCount = 0;
|
|
149
|
+
let diffProp = "";
|
|
150
|
+
let diffOld = "";
|
|
151
|
+
let diffNew = "";
|
|
152
|
+
for (const [prop, value] of entry.propMap) {
|
|
153
|
+
const existingValue = existing.propMap.get(prop);
|
|
154
|
+
if (existingValue !== value) {
|
|
155
|
+
diffCount++;
|
|
156
|
+
diffProp = prop;
|
|
157
|
+
diffOld = existingValue ?? "(missing)";
|
|
158
|
+
diffNew = value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (diffCount === 0) continue;
|
|
162
|
+
if (diffCount === 1 && diffProp !== "@apply") {
|
|
163
|
+
result.warn(
|
|
164
|
+
`Near-duplicate ruleset \u2014 "${ruleNode.selector}" differs from "${existing.selector}" only in ${diffProp} (${diffOld} \u2192 ${diffNew}). Consider consolidating to a single class.`,
|
|
165
|
+
{ node: ruleNode }
|
|
166
|
+
);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
group.push(entry);
|
|
171
|
+
} else {
|
|
172
|
+
groups.set(propKey, [entry]);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
ruleName: nearDuplicateRuleName,
|
|
179
|
+
rule
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function createUndefinedTokenPlugin(runtimePrefixes) {
|
|
183
|
+
const varPattern = /var\(--([a-zA-Z0-9_-]+)/g;
|
|
184
|
+
const rule = (enabled) => {
|
|
185
|
+
return (root, result) => {
|
|
186
|
+
if (!enabled) return;
|
|
187
|
+
const defined = /* @__PURE__ */ new Set();
|
|
188
|
+
root.walkDecls((decl) => {
|
|
189
|
+
if (decl.prop.startsWith("--")) {
|
|
190
|
+
defined.add(decl.prop);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
root.walkAtRules("theme", (atRule) => {
|
|
194
|
+
atRule.walkDecls((decl) => {
|
|
195
|
+
if (decl.prop.startsWith("--")) {
|
|
196
|
+
defined.add(decl.prop);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
root.walkDecls((decl) => {
|
|
201
|
+
if (decl.prop.startsWith("--")) return;
|
|
202
|
+
let match;
|
|
203
|
+
varPattern.lastIndex = 0;
|
|
204
|
+
while ((match = varPattern.exec(decl.value)) !== null) {
|
|
205
|
+
const name = `--${match[1]}`;
|
|
206
|
+
if (defined.has(name)) continue;
|
|
207
|
+
if (runtimePrefixes.some((p) => name.startsWith(p))) continue;
|
|
208
|
+
result.warn(
|
|
209
|
+
`Reference to undefined custom property "${name}". This token is not defined anywhere in this file.`,
|
|
210
|
+
{ node: decl }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
ruleName: undefinedTokenRuleName,
|
|
218
|
+
rule
|
|
219
|
+
};
|
|
220
|
+
}
|
|
121
221
|
function createStyleLint(options = {}) {
|
|
122
222
|
const {
|
|
123
223
|
ignoreTypes = ["html", "body"],
|
|
124
224
|
functionalTokens = [],
|
|
225
|
+
runtimeTokens = [],
|
|
125
226
|
files = ["src/**/*.css"]
|
|
126
227
|
} = options;
|
|
127
|
-
const plugins = [createDescendantColorPlugin(), createDuplicateRulesetPlugin()];
|
|
228
|
+
const plugins = [createDescendantColorPlugin(), createDuplicateRulesetPlugin(), createNearDuplicatePlugin(), createUndefinedTokenPlugin(runtimeTokens)];
|
|
128
229
|
if (functionalTokens.length > 0) {
|
|
129
230
|
plugins.push(createFunctionalTokenPlugin(functionalTokens));
|
|
130
231
|
}
|
|
@@ -142,6 +243,10 @@ function createStyleLint(options = {}) {
|
|
|
142
243
|
[descendantColorRuleName]: true,
|
|
143
244
|
// Flag selectors with identical declarations for consolidation.
|
|
144
245
|
[duplicateRulesetRuleName]: true,
|
|
246
|
+
// Flag near-duplicate rulesets (same props, differ by 1 value).
|
|
247
|
+
[nearDuplicateRuleName]: true,
|
|
248
|
+
// Flag var() references to custom properties not defined in this file.
|
|
249
|
+
[undefinedTokenRuleName]: true,
|
|
145
250
|
// Ban animating layout properties — causes reflow on every frame.
|
|
146
251
|
// Use transform (scaleY, translateY) or opacity instead.
|
|
147
252
|
"declaration-property-value-disallowed-list": [
|
package/dist/stylelint.d.cts
CHANGED
|
@@ -40,6 +40,11 @@ interface StyleLintOptions {
|
|
|
40
40
|
* e.g. ["--color-status-", "--color-danger"]
|
|
41
41
|
* Any var() referencing these inside @utility is a lint error. */
|
|
42
42
|
functionalTokens?: string[];
|
|
43
|
+
/** Custom property prefixes set at runtime (JS, Radix, inline styles)
|
|
44
|
+
* that should not be flagged as undefined.
|
|
45
|
+
* e.g. ["--radix-", "--bar-"]
|
|
46
|
+
* @default [] */
|
|
47
|
+
runtimeTokens?: string[];
|
|
43
48
|
/** Glob patterns for files to lint.
|
|
44
49
|
* @default ["src/**\/*.css"] */
|
|
45
50
|
files?: string[];
|
package/dist/stylelint.d.ts
CHANGED
|
@@ -40,6 +40,11 @@ interface StyleLintOptions {
|
|
|
40
40
|
* e.g. ["--color-status-", "--color-danger"]
|
|
41
41
|
* Any var() referencing these inside @utility is a lint error. */
|
|
42
42
|
functionalTokens?: string[];
|
|
43
|
+
/** Custom property prefixes set at runtime (JS, Radix, inline styles)
|
|
44
|
+
* that should not be flagged as undefined.
|
|
45
|
+
* e.g. ["--radix-", "--bar-"]
|
|
46
|
+
* @default [] */
|
|
47
|
+
runtimeTokens?: string[];
|
|
43
48
|
/** Glob patterns for files to lint.
|
|
44
49
|
* @default ["src/**\/*.css"] */
|
|
45
50
|
files?: string[];
|
package/dist/stylelint.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
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";
|
|
6
|
+
var undefinedTokenRuleName = "stablekit/no-undefined-token";
|
|
5
7
|
function createFunctionalTokenPlugin(prefixes) {
|
|
6
8
|
const rule = (enabled) => {
|
|
7
9
|
return (root, result) => {
|
|
@@ -94,13 +96,112 @@ Declarations: ${fingerprint}`,
|
|
|
94
96
|
rule
|
|
95
97
|
};
|
|
96
98
|
}
|
|
99
|
+
function createNearDuplicatePlugin() {
|
|
100
|
+
function collectDirectDecls(ruleNode) {
|
|
101
|
+
const map = /* @__PURE__ */ new Map();
|
|
102
|
+
for (const child of ruleNode.nodes ?? []) {
|
|
103
|
+
if (child.type === "decl") {
|
|
104
|
+
map.set(child.prop, child.value);
|
|
105
|
+
} else if (child.type === "atrule" && child.name === "apply") {
|
|
106
|
+
map.set(`@apply`, child.params);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return map;
|
|
110
|
+
}
|
|
111
|
+
const rule = (enabled) => {
|
|
112
|
+
return (root, result) => {
|
|
113
|
+
if (!enabled) return;
|
|
114
|
+
const groups = /* @__PURE__ */ new Map();
|
|
115
|
+
root.walkRules((ruleNode) => {
|
|
116
|
+
if (ruleNode.parent?.type === "atrule" && ruleNode.parent.name === "keyframes") return;
|
|
117
|
+
const propMap = collectDirectDecls(ruleNode);
|
|
118
|
+
if (propMap.size < 2) return;
|
|
119
|
+
const propKey = [...propMap.keys()].sort().join(", ");
|
|
120
|
+
const entry = { selector: ruleNode.selector, propKey, propMap, node: ruleNode };
|
|
121
|
+
const group = groups.get(propKey);
|
|
122
|
+
if (group) {
|
|
123
|
+
for (const existing of group) {
|
|
124
|
+
let diffCount = 0;
|
|
125
|
+
let diffProp = "";
|
|
126
|
+
let diffOld = "";
|
|
127
|
+
let diffNew = "";
|
|
128
|
+
for (const [prop, value] of entry.propMap) {
|
|
129
|
+
const existingValue = existing.propMap.get(prop);
|
|
130
|
+
if (existingValue !== value) {
|
|
131
|
+
diffCount++;
|
|
132
|
+
diffProp = prop;
|
|
133
|
+
diffOld = existingValue ?? "(missing)";
|
|
134
|
+
diffNew = value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (diffCount === 0) continue;
|
|
138
|
+
if (diffCount === 1 && diffProp !== "@apply") {
|
|
139
|
+
result.warn(
|
|
140
|
+
`Near-duplicate ruleset \u2014 "${ruleNode.selector}" differs from "${existing.selector}" only in ${diffProp} (${diffOld} \u2192 ${diffNew}). Consider consolidating to a single class.`,
|
|
141
|
+
{ node: ruleNode }
|
|
142
|
+
);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
group.push(entry);
|
|
147
|
+
} else {
|
|
148
|
+
groups.set(propKey, [entry]);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
ruleName: nearDuplicateRuleName,
|
|
155
|
+
rule
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function createUndefinedTokenPlugin(runtimePrefixes) {
|
|
159
|
+
const varPattern = /var\(--([a-zA-Z0-9_-]+)/g;
|
|
160
|
+
const rule = (enabled) => {
|
|
161
|
+
return (root, result) => {
|
|
162
|
+
if (!enabled) return;
|
|
163
|
+
const defined = /* @__PURE__ */ new Set();
|
|
164
|
+
root.walkDecls((decl) => {
|
|
165
|
+
if (decl.prop.startsWith("--")) {
|
|
166
|
+
defined.add(decl.prop);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
root.walkAtRules("theme", (atRule) => {
|
|
170
|
+
atRule.walkDecls((decl) => {
|
|
171
|
+
if (decl.prop.startsWith("--")) {
|
|
172
|
+
defined.add(decl.prop);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
root.walkDecls((decl) => {
|
|
177
|
+
if (decl.prop.startsWith("--")) return;
|
|
178
|
+
let match;
|
|
179
|
+
varPattern.lastIndex = 0;
|
|
180
|
+
while ((match = varPattern.exec(decl.value)) !== null) {
|
|
181
|
+
const name = `--${match[1]}`;
|
|
182
|
+
if (defined.has(name)) continue;
|
|
183
|
+
if (runtimePrefixes.some((p) => name.startsWith(p))) continue;
|
|
184
|
+
result.warn(
|
|
185
|
+
`Reference to undefined custom property "${name}". This token is not defined anywhere in this file.`,
|
|
186
|
+
{ node: decl }
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
ruleName: undefinedTokenRuleName,
|
|
194
|
+
rule
|
|
195
|
+
};
|
|
196
|
+
}
|
|
97
197
|
function createStyleLint(options = {}) {
|
|
98
198
|
const {
|
|
99
199
|
ignoreTypes = ["html", "body"],
|
|
100
200
|
functionalTokens = [],
|
|
201
|
+
runtimeTokens = [],
|
|
101
202
|
files = ["src/**/*.css"]
|
|
102
203
|
} = options;
|
|
103
|
-
const plugins = [createDescendantColorPlugin(), createDuplicateRulesetPlugin()];
|
|
204
|
+
const plugins = [createDescendantColorPlugin(), createDuplicateRulesetPlugin(), createNearDuplicatePlugin(), createUndefinedTokenPlugin(runtimeTokens)];
|
|
104
205
|
if (functionalTokens.length > 0) {
|
|
105
206
|
plugins.push(createFunctionalTokenPlugin(functionalTokens));
|
|
106
207
|
}
|
|
@@ -118,6 +219,10 @@ function createStyleLint(options = {}) {
|
|
|
118
219
|
[descendantColorRuleName]: true,
|
|
119
220
|
// Flag selectors with identical declarations for consolidation.
|
|
120
221
|
[duplicateRulesetRuleName]: true,
|
|
222
|
+
// Flag near-duplicate rulesets (same props, differ by 1 value).
|
|
223
|
+
[nearDuplicateRuleName]: true,
|
|
224
|
+
// Flag var() references to custom properties not defined in this file.
|
|
225
|
+
[undefinedTokenRuleName]: true,
|
|
121
226
|
// Ban animating layout properties — causes reflow on every frame.
|
|
122
227
|
// Use transform (scaleY, translateY) or opacity instead.
|
|
123
228
|
"declaration-property-value-disallowed-list": [
|
package/package.json
CHANGED