@plumeria/eslint-plugin 18.2.7 → 18.2.8
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
CHANGED
|
@@ -14,6 +14,7 @@ The `plugin:@plumeria/recommended` config enables the following:
|
|
|
14
14
|
- `@plumeria/no-inner-call`: **error**
|
|
15
15
|
- `@plumeria/no-invalid-selector`: **error**
|
|
16
16
|
- `@plumeria/no-mixed-styling-props`: **error**
|
|
17
|
+
- `@plumeria/no-order-dependent-overlap`: **warn**
|
|
17
18
|
- `@plumeria/no-unknown-css-properties`: **error**
|
|
18
19
|
- `@plumeria/no-unused-keys`: **warn**
|
|
19
20
|
- `@plumeria/sort-properties`: **warn**
|
|
@@ -98,6 +99,14 @@ Disallow mixing the styling prop with `className` or `style`. `classStyle` can h
|
|
|
98
99
|
|
|
99
100
|
Accepts `{ styleProp }`; see [Configuring the styling prop](#configuring-the-styling-prop).
|
|
100
101
|
|
|
102
|
+
### no-order-dependent-overlap
|
|
103
|
+
|
|
104
|
+
Warns when two properties in one style overlap but neither outranks the other,
|
|
105
|
+
so the one written last wins. It covers one property under its logical and its
|
|
106
|
+
physical name, and two shorthands that cross without either containing the
|
|
107
|
+
other. A shorthand and its longhand are ranked by specificity and never
|
|
108
|
+
reported.
|
|
109
|
+
|
|
101
110
|
### no-unknown-css-properties
|
|
102
111
|
|
|
103
112
|
Disallow unknown CSS properties in camelCase within `css.create`, `css.keyframes`, and `css.viewTransition`.
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const no_inline_object_1 = require("./rules/no-inline-object");
|
|
|
6
6
|
const no_inner_call_1 = require("./rules/no-inner-call");
|
|
7
7
|
const no_invalid_selector_1 = require("./rules/no-invalid-selector");
|
|
8
8
|
const no_mixed_styling_props_1 = require("./rules/no-mixed-styling-props");
|
|
9
|
+
const no_order_dependent_overlap_1 = require("./rules/no-order-dependent-overlap");
|
|
9
10
|
const no_unknown_css_properties_1 = require("./rules/no-unknown-css-properties");
|
|
10
11
|
const no_unused_keys_1 = require("./rules/no-unused-keys");
|
|
11
12
|
const sort_properties_1 = require("./rules/sort-properties");
|
|
@@ -20,6 +21,7 @@ const rules = {
|
|
|
20
21
|
'no-inner-call': no_inner_call_1.noInnerCall,
|
|
21
22
|
'no-invalid-selector': no_invalid_selector_1.noInvalidSelector,
|
|
22
23
|
'no-mixed-styling-props': no_mixed_styling_props_1.noMixedStylingProps,
|
|
24
|
+
'no-order-dependent-overlap': no_order_dependent_overlap_1.noOrderDependentOverlap,
|
|
23
25
|
'no-unknown-css-properties': no_unknown_css_properties_1.noUnknownCssProperties,
|
|
24
26
|
'no-unused-keys': no_unused_keys_1.noUnusedKeys,
|
|
25
27
|
'sort-properties': sort_properties_1.sortProperties,
|
|
@@ -42,6 +44,7 @@ const configs = {
|
|
|
42
44
|
'@plumeria/no-inner-call': 'error',
|
|
43
45
|
'@plumeria/no-invalid-selector': 'error',
|
|
44
46
|
'@plumeria/no-mixed-styling-props': 'error',
|
|
47
|
+
'@plumeria/no-order-dependent-overlap': 'warn',
|
|
45
48
|
'@plumeria/no-unknown-css-properties': 'error',
|
|
46
49
|
'@plumeria/no-unused-keys': 'warn',
|
|
47
50
|
'@plumeria/sort-properties': 'warn',
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noOrderDependentOverlap = void 0;
|
|
4
|
+
const zss_engine_1 = require("zss-engine");
|
|
5
|
+
const BOX_FAMILIES = ['margin', 'padding', 'scroll-margin', 'scroll-padding'];
|
|
6
|
+
const LOGICAL_PHYSICAL_EDGES = [
|
|
7
|
+
['block-start', 'top'],
|
|
8
|
+
['block-end', 'bottom'],
|
|
9
|
+
['inline-start', 'left'],
|
|
10
|
+
['inline-end', 'right'],
|
|
11
|
+
];
|
|
12
|
+
const BORDER_VALUES = ['width', 'style', 'color'];
|
|
13
|
+
const CORNERS = [
|
|
14
|
+
['start-start', 'top-left'],
|
|
15
|
+
['start-end', 'top-right'],
|
|
16
|
+
['end-start', 'bottom-left'],
|
|
17
|
+
['end-end', 'bottom-right'],
|
|
18
|
+
];
|
|
19
|
+
const SIZES = ['', 'min-', 'max-'];
|
|
20
|
+
const LOGICAL_PHYSICAL_PAIRS = [
|
|
21
|
+
...BOX_FAMILIES.flatMap((family) => LOGICAL_PHYSICAL_EDGES.map(([logical, physical]) => [
|
|
22
|
+
`${family}-${logical}`,
|
|
23
|
+
`${family}-${physical}`,
|
|
24
|
+
])),
|
|
25
|
+
...LOGICAL_PHYSICAL_EDGES.map(([logical, physical]) => [
|
|
26
|
+
`inset-${logical}`,
|
|
27
|
+
physical,
|
|
28
|
+
]),
|
|
29
|
+
...LOGICAL_PHYSICAL_EDGES.flatMap(([logical, physical]) => [
|
|
30
|
+
[`border-${logical}`, `border-${physical}`],
|
|
31
|
+
...BORDER_VALUES.map((value) => [
|
|
32
|
+
`border-${logical}-${value}`,
|
|
33
|
+
`border-${physical}-${value}`,
|
|
34
|
+
]),
|
|
35
|
+
]),
|
|
36
|
+
...CORNERS.flatMap(([logical, physical]) => [
|
|
37
|
+
[`border-${logical}-radius`, `border-${physical}-radius`],
|
|
38
|
+
[`corner-${logical}-shape`, `corner-${physical}-shape`],
|
|
39
|
+
]),
|
|
40
|
+
...SIZES.flatMap((size) => [
|
|
41
|
+
[`${size}block-size`, `${size}height`],
|
|
42
|
+
[`${size}inline-size`, `${size}width`],
|
|
43
|
+
]),
|
|
44
|
+
['overflow-block', 'overflow-y'],
|
|
45
|
+
['overflow-inline', 'overflow-x'],
|
|
46
|
+
['overscroll-behavior-block', 'overscroll-behavior-y'],
|
|
47
|
+
['overscroll-behavior-inline', 'overscroll-behavior-x'],
|
|
48
|
+
['contain-intrinsic-block-size', 'contain-intrinsic-height'],
|
|
49
|
+
['contain-intrinsic-inline-size', 'contain-intrinsic-width'],
|
|
50
|
+
];
|
|
51
|
+
const alias = new Map(LOGICAL_PHYSICAL_PAIRS);
|
|
52
|
+
const canonical = (property) => alias.get(property) ?? property;
|
|
53
|
+
const DIRECT_SHORTHANDS = {};
|
|
54
|
+
for (const [shorthand, longhands] of Object.entries(zss_engine_1.DIRECT_LONGHANDS)) {
|
|
55
|
+
for (const longhand of longhands) {
|
|
56
|
+
if (!DIRECT_SHORTHANDS[longhand])
|
|
57
|
+
DIRECT_SHORTHANDS[longhand] = [];
|
|
58
|
+
DIRECT_SHORTHANDS[longhand].push(shorthand);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const depths = new Map();
|
|
62
|
+
const depthOf = (property) => {
|
|
63
|
+
const cached = depths.get(property);
|
|
64
|
+
if (cached !== undefined)
|
|
65
|
+
return cached;
|
|
66
|
+
depths.set(property, 0);
|
|
67
|
+
let depth = 0;
|
|
68
|
+
for (const shorthand of DIRECT_SHORTHANDS[property] || []) {
|
|
69
|
+
depth = Math.max(depth, depthOf(shorthand) + 1);
|
|
70
|
+
}
|
|
71
|
+
depths.set(property, depth);
|
|
72
|
+
return depth;
|
|
73
|
+
};
|
|
74
|
+
const coverages = new Map();
|
|
75
|
+
const coverageOf = (property) => {
|
|
76
|
+
const cached = coverages.get(property);
|
|
77
|
+
if (cached)
|
|
78
|
+
return cached;
|
|
79
|
+
const longhands = zss_engine_1.DIRECT_LONGHANDS[property];
|
|
80
|
+
const coverage = longhands
|
|
81
|
+
? new Set(longhands.flatMap((longhand) => [...coverageOf(longhand)]))
|
|
82
|
+
: new Set([canonical(property)]);
|
|
83
|
+
coverages.set(property, coverage);
|
|
84
|
+
return coverage;
|
|
85
|
+
};
|
|
86
|
+
const kebabCache = new Map();
|
|
87
|
+
const toKebabCase = (name) => {
|
|
88
|
+
const cached = kebabCache.get(name);
|
|
89
|
+
if (cached !== undefined)
|
|
90
|
+
return cached;
|
|
91
|
+
const kebab = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
92
|
+
kebabCache.set(name, kebab);
|
|
93
|
+
return kebab;
|
|
94
|
+
};
|
|
95
|
+
const overlapOf = (first, second) => {
|
|
96
|
+
if (canonical(first) === canonical(second))
|
|
97
|
+
return 'alias';
|
|
98
|
+
const left = coverageOf(first);
|
|
99
|
+
const right = coverageOf(second);
|
|
100
|
+
if (![...left].some((leaf) => right.has(leaf)))
|
|
101
|
+
return null;
|
|
102
|
+
const contains = (a, b) => [...b].every((leaf) => a.has(leaf));
|
|
103
|
+
if (contains(left, right) || contains(right, left))
|
|
104
|
+
return null;
|
|
105
|
+
return depthOf(first) === depthOf(second) ? 'crossing' : null;
|
|
106
|
+
};
|
|
107
|
+
exports.noOrderDependentOverlap = {
|
|
108
|
+
meta: {
|
|
109
|
+
type: 'problem',
|
|
110
|
+
docs: {
|
|
111
|
+
description: 'Disallow two properties that overlap without either one outranking the other',
|
|
112
|
+
},
|
|
113
|
+
hasSuggestions: true,
|
|
114
|
+
messages: {
|
|
115
|
+
alias: "'{{ first }}' and '{{ second }}' are the same property under two names, so neither outranks the other. The one written last wins.",
|
|
116
|
+
crossing: "'{{ first }}' and '{{ second }}' overlap, but neither property outranks the other. The result depends on the order they are written.",
|
|
117
|
+
keep: "Keep '{{ keep }}'",
|
|
118
|
+
},
|
|
119
|
+
schema: [],
|
|
120
|
+
},
|
|
121
|
+
create(context) {
|
|
122
|
+
const plumeriaAliases = {};
|
|
123
|
+
const sourceCode = context.sourceCode;
|
|
124
|
+
return {
|
|
125
|
+
ImportDeclaration(node) {
|
|
126
|
+
if (node.source.value === '@plumeria/core') {
|
|
127
|
+
node.specifiers.forEach((specifier) => {
|
|
128
|
+
if (specifier.type === 'ImportNamespaceSpecifier' ||
|
|
129
|
+
specifier.type === 'ImportDefaultSpecifier') {
|
|
130
|
+
plumeriaAliases[specifier.local.name] = 'NAMESPACE';
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const spec = specifier;
|
|
134
|
+
const importedName = spec.imported.type === 'Identifier'
|
|
135
|
+
? spec.imported.name
|
|
136
|
+
: String(spec.imported.value);
|
|
137
|
+
plumeriaAliases[specifier.local.name] = importedName;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
CallExpression(node) {
|
|
143
|
+
let isCssProperties = false;
|
|
144
|
+
if (node.callee.type === 'MemberExpression') {
|
|
145
|
+
if (node.callee.object.type === 'Identifier' &&
|
|
146
|
+
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
147
|
+
const propertyName = node.callee.property.type === 'Identifier'
|
|
148
|
+
? node.callee.property.name
|
|
149
|
+
: null;
|
|
150
|
+
if (propertyName === 'create' ||
|
|
151
|
+
propertyName === 'keyframes' ||
|
|
152
|
+
propertyName === 'viewTransition') {
|
|
153
|
+
isCssProperties = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else if (node.callee.type === 'Identifier') {
|
|
158
|
+
const aliasName = plumeriaAliases[node.callee.name];
|
|
159
|
+
if (aliasName === 'create' ||
|
|
160
|
+
aliasName === 'keyframes' ||
|
|
161
|
+
aliasName === 'viewTransition') {
|
|
162
|
+
isCssProperties = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (isCssProperties) {
|
|
166
|
+
node.arguments.forEach((arg) => {
|
|
167
|
+
if (arg.type === 'ObjectExpression') {
|
|
168
|
+
arg.properties.forEach((prop) => {
|
|
169
|
+
if (prop.type === 'Property' &&
|
|
170
|
+
prop.value.type === 'ObjectExpression') {
|
|
171
|
+
checkStyleObject(prop.value);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
function removeProperty(fixer, prop) {
|
|
180
|
+
const after = sourceCode.getTokenAfter(prop);
|
|
181
|
+
if (after && after.value === ',') {
|
|
182
|
+
const next = sourceCode.getTokenAfter(after);
|
|
183
|
+
const end = next && next.value !== '}' ? next.range[0] : after.range[1];
|
|
184
|
+
return fixer.removeRange([prop.range[0], end]);
|
|
185
|
+
}
|
|
186
|
+
const before = sourceCode.getTokenBefore(prop);
|
|
187
|
+
return fixer.removeRange([before.range[0], prop.range[1]]);
|
|
188
|
+
}
|
|
189
|
+
function checkStyleObject(node) {
|
|
190
|
+
const declarations = [];
|
|
191
|
+
node.properties.forEach((prop) => {
|
|
192
|
+
if (prop.type !== 'Property')
|
|
193
|
+
return;
|
|
194
|
+
if (prop.value.type === 'ObjectExpression') {
|
|
195
|
+
checkStyleObject(prop.value);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
let name = '';
|
|
199
|
+
if (!prop.computed) {
|
|
200
|
+
name =
|
|
201
|
+
prop.key.type === 'Identifier'
|
|
202
|
+
? prop.key.name
|
|
203
|
+
: String(prop.key.value);
|
|
204
|
+
}
|
|
205
|
+
else if (prop.key.type === 'Literal' &&
|
|
206
|
+
typeof prop.key.value === 'string') {
|
|
207
|
+
name = prop.key.value;
|
|
208
|
+
}
|
|
209
|
+
if (!name ||
|
|
210
|
+
name.startsWith(':') ||
|
|
211
|
+
name.startsWith('[') ||
|
|
212
|
+
name.startsWith('@') ||
|
|
213
|
+
name.startsWith('--')) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
declarations.push({ prop, name, kebab: toKebabCase(name) });
|
|
217
|
+
});
|
|
218
|
+
for (let i = 0; i < declarations.length; i++) {
|
|
219
|
+
for (let j = i + 1; j < declarations.length; j++) {
|
|
220
|
+
const first = declarations[i];
|
|
221
|
+
const second = declarations[j];
|
|
222
|
+
const overlap = overlapOf(first.kebab, second.kebab);
|
|
223
|
+
if (!overlap)
|
|
224
|
+
continue;
|
|
225
|
+
context.report({
|
|
226
|
+
node: second.prop.key,
|
|
227
|
+
messageId: overlap,
|
|
228
|
+
data: { first: first.name, second: second.name },
|
|
229
|
+
suggest: overlap === 'alias'
|
|
230
|
+
? [
|
|
231
|
+
{
|
|
232
|
+
messageId: 'keep',
|
|
233
|
+
data: { keep: second.name },
|
|
234
|
+
fix: (fixer) => removeProperty(fixer, first.prop),
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
messageId: 'keep',
|
|
238
|
+
data: { keep: first.name },
|
|
239
|
+
fix: (fixer) => removeProperty(fixer, second.prop),
|
|
240
|
+
},
|
|
241
|
+
]
|
|
242
|
+
: null,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
};
|
package/oxlint.json
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@plumeria/no-inner-call": "error",
|
|
17
17
|
"@plumeria/no-invalid-selector": "error",
|
|
18
18
|
"@plumeria/no-mixed-styling-props": "error",
|
|
19
|
+
"@plumeria/no-order-dependent-overlap": "warn",
|
|
19
20
|
"@plumeria/no-unknown-css-properties": "error",
|
|
20
21
|
"@plumeria/no-unused-keys": "warn",
|
|
21
22
|
"@plumeria/sort-properties": "warn",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumeria/eslint-plugin",
|
|
3
|
-
"version": "18.2.
|
|
3
|
+
"version": "18.2.8",
|
|
4
4
|
"description": "Plumeria ESLint plugin",
|
|
5
5
|
"author": "Refirst 11",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@typescript-eslint/utils": "^8.60.0",
|
|
56
|
-
"known-css-properties": "^0.37.0"
|
|
56
|
+
"known-css-properties": "^0.37.0",
|
|
57
|
+
"zss-engine": "2.4.3"
|
|
57
58
|
},
|
|
58
59
|
"scripts": {
|
|
59
60
|
"build": "rimraf dist && pnpm cjs",
|