@plumeria/eslint-plugin 18.2.7 → 18.2.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/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,35 @@ 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
+
110
+ A pair of spellings is one property in a horizontal writing mode and two
111
+ properties in a vertical one, which is per element and cannot be read from the
112
+ source. The rule reads them as one property; disable the line where an element
113
+ is known to be vertical.
114
+
115
+ ### no-physical-properties / no-logical-properties
116
+
117
+ Disallow one of the two names a property can carry, so a project writes edges
118
+ under one spelling only. `no-physical-properties` reports `paddingLeft` and
119
+ suggests `paddingInlineStart`; `no-logical-properties` reports the reverse.
120
+ Accepts `{ sizes }`, which adds `width`, `height`, their `min`/`max` forms,
121
+ `overflow-x` and `overflow-y` to the properties covered. It is `false` by
122
+ default: a size is one property under either spelling, while an edge is what a
123
+ direction reverses.
124
+
125
+ Neither is part of `recommended`, and turning both on is contradictory. Reach
126
+ for one when you want the pairs `no-order-dependent-overlap` reports to be
127
+ impossible to write: a property that appears under one spelling only can never
128
+ meet its other spelling on an element. A shorthand with no single counterpart,
129
+ such as `borderBlockWidth`, is outside either rule and stays reported.
130
+
101
131
  ### no-unknown-css-properties
102
132
 
103
133
  Disallow unknown CSS properties in camelCase within `css.create`, `css.keyframes`, and `css.viewTransition`.
package/dist/index.js CHANGED
@@ -5,7 +5,10 @@ const no_destructure_1 = require("./rules/no-destructure");
5
5
  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
+ const no_logical_properties_1 = require("./rules/no-logical-properties");
8
9
  const no_mixed_styling_props_1 = require("./rules/no-mixed-styling-props");
10
+ const no_physical_properties_1 = require("./rules/no-physical-properties");
11
+ const no_order_dependent_overlap_1 = require("./rules/no-order-dependent-overlap");
9
12
  const no_unknown_css_properties_1 = require("./rules/no-unknown-css-properties");
10
13
  const no_unused_keys_1 = require("./rules/no-unused-keys");
11
14
  const sort_properties_1 = require("./rules/sort-properties");
@@ -19,7 +22,10 @@ const rules = {
19
22
  'no-inline-object': no_inline_object_1.noInlineObject,
20
23
  'no-inner-call': no_inner_call_1.noInnerCall,
21
24
  'no-invalid-selector': no_invalid_selector_1.noInvalidSelector,
25
+ 'no-logical-properties': no_logical_properties_1.noLogicalProperties,
22
26
  'no-mixed-styling-props': no_mixed_styling_props_1.noMixedStylingProps,
27
+ 'no-physical-properties': no_physical_properties_1.noPhysicalProperties,
28
+ 'no-order-dependent-overlap': no_order_dependent_overlap_1.noOrderDependentOverlap,
23
29
  'no-unknown-css-properties': no_unknown_css_properties_1.noUnknownCssProperties,
24
30
  'no-unused-keys': no_unused_keys_1.noUnusedKeys,
25
31
  'sort-properties': sort_properties_1.sortProperties,
@@ -42,6 +48,7 @@ const configs = {
42
48
  '@plumeria/no-inner-call': 'error',
43
49
  '@plumeria/no-invalid-selector': 'error',
44
50
  '@plumeria/no-mixed-styling-props': 'error',
51
+ '@plumeria/no-order-dependent-overlap': 'warn',
45
52
  '@plumeria/no-unknown-css-properties': 'error',
46
53
  '@plumeria/no-unused-keys': 'warn',
47
54
  '@plumeria/sort-properties': 'warn',
@@ -0,0 +1 @@
1
+ export declare const noLogicalProperties: import("eslint").Rule.RuleModule;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noLogicalProperties = void 0;
4
+ const spellingRule_1 = require("../util/spellingRule");
5
+ exports.noLogicalProperties = (0, spellingRule_1.createSpellingRule)('logical');
@@ -0,0 +1,2 @@
1
+ import type { Rule } from 'eslint';
2
+ export declare const noOrderDependentOverlap: Rule.RuleModule;
@@ -0,0 +1,192 @@
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 logicalPhysical_1 = require("../util/logicalPhysical");
6
+ const DIRECT_SHORTHANDS = {};
7
+ for (const [shorthand, longhands] of Object.entries(zss_engine_1.DIRECT_LONGHANDS)) {
8
+ for (const longhand of longhands) {
9
+ if (!DIRECT_SHORTHANDS[longhand])
10
+ DIRECT_SHORTHANDS[longhand] = [];
11
+ DIRECT_SHORTHANDS[longhand].push(shorthand);
12
+ }
13
+ }
14
+ const depths = new Map();
15
+ const depthOf = (property) => {
16
+ const cached = depths.get(property);
17
+ if (cached !== undefined)
18
+ return cached;
19
+ depths.set(property, 0);
20
+ let depth = 0;
21
+ for (const shorthand of DIRECT_SHORTHANDS[property] || []) {
22
+ depth = Math.max(depth, depthOf(shorthand) + 1);
23
+ }
24
+ depths.set(property, depth);
25
+ return depth;
26
+ };
27
+ const coverages = new Map();
28
+ const coverageOf = (property) => {
29
+ const cached = coverages.get(property);
30
+ if (cached)
31
+ return cached;
32
+ const longhands = zss_engine_1.DIRECT_LONGHANDS[property];
33
+ const coverage = longhands
34
+ ? new Set(longhands.flatMap((longhand) => [...coverageOf(longhand)]))
35
+ : new Set([(0, logicalPhysical_1.canonicalProperty)(property)]);
36
+ coverages.set(property, coverage);
37
+ return coverage;
38
+ };
39
+ const overlapOf = (first, second) => {
40
+ if ((0, logicalPhysical_1.canonicalProperty)(first) === (0, logicalPhysical_1.canonicalProperty)(second))
41
+ return 'alias';
42
+ const left = coverageOf(first);
43
+ const right = coverageOf(second);
44
+ if (![...left].some((leaf) => right.has(leaf)))
45
+ return null;
46
+ const contains = (a, b) => [...b].every((leaf) => a.has(leaf));
47
+ if (contains(left, right) || contains(right, left))
48
+ return null;
49
+ return depthOf(first) === depthOf(second) ? 'crossing' : null;
50
+ };
51
+ exports.noOrderDependentOverlap = {
52
+ meta: {
53
+ type: 'problem',
54
+ docs: {
55
+ description: 'Disallow two properties that overlap without either one outranking the other',
56
+ },
57
+ hasSuggestions: true,
58
+ messages: {
59
+ alias: "'{{ first }}' and '{{ second }}' are the same property under two names, so neither outranks the other. The one written last wins.",
60
+ crossing: "'{{ first }}' and '{{ second }}' overlap, but neither property outranks the other. The result depends on the order they are written.",
61
+ keep: "Keep '{{ keep }}'",
62
+ },
63
+ schema: [],
64
+ },
65
+ create(context) {
66
+ const plumeriaAliases = {};
67
+ const sourceCode = context.sourceCode;
68
+ return {
69
+ ImportDeclaration(node) {
70
+ if (node.source.value === '@plumeria/core') {
71
+ node.specifiers.forEach((specifier) => {
72
+ if (specifier.type === 'ImportNamespaceSpecifier' ||
73
+ specifier.type === 'ImportDefaultSpecifier') {
74
+ plumeriaAliases[specifier.local.name] = 'NAMESPACE';
75
+ }
76
+ else {
77
+ const spec = specifier;
78
+ const importedName = spec.imported.type === 'Identifier'
79
+ ? spec.imported.name
80
+ : String(spec.imported.value);
81
+ plumeriaAliases[specifier.local.name] = importedName;
82
+ }
83
+ });
84
+ }
85
+ },
86
+ CallExpression(node) {
87
+ let isCssProperties = false;
88
+ if (node.callee.type === 'MemberExpression') {
89
+ if (node.callee.object.type === 'Identifier' &&
90
+ plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
91
+ const propertyName = node.callee.property.type === 'Identifier'
92
+ ? node.callee.property.name
93
+ : null;
94
+ if (propertyName === 'create' ||
95
+ propertyName === 'keyframes' ||
96
+ propertyName === 'viewTransition') {
97
+ isCssProperties = true;
98
+ }
99
+ }
100
+ }
101
+ else if (node.callee.type === 'Identifier') {
102
+ const aliasName = plumeriaAliases[node.callee.name];
103
+ if (aliasName === 'create' ||
104
+ aliasName === 'keyframes' ||
105
+ aliasName === 'viewTransition') {
106
+ isCssProperties = true;
107
+ }
108
+ }
109
+ if (isCssProperties) {
110
+ node.arguments.forEach((arg) => {
111
+ if (arg.type === 'ObjectExpression') {
112
+ arg.properties.forEach((prop) => {
113
+ if (prop.type === 'Property' &&
114
+ prop.value.type === 'ObjectExpression') {
115
+ checkStyleObject(prop.value);
116
+ }
117
+ });
118
+ }
119
+ });
120
+ }
121
+ },
122
+ };
123
+ function removeProperty(fixer, prop) {
124
+ const after = sourceCode.getTokenAfter(prop);
125
+ if (after && after.value === ',') {
126
+ const next = sourceCode.getTokenAfter(after);
127
+ const end = next && next.value !== '}' ? next.range[0] : after.range[1];
128
+ return fixer.removeRange([prop.range[0], end]);
129
+ }
130
+ const before = sourceCode.getTokenBefore(prop);
131
+ return fixer.removeRange([before.range[0], prop.range[1]]);
132
+ }
133
+ function checkStyleObject(node) {
134
+ const declarations = [];
135
+ node.properties.forEach((prop) => {
136
+ if (prop.type !== 'Property')
137
+ return;
138
+ if (prop.value.type === 'ObjectExpression') {
139
+ checkStyleObject(prop.value);
140
+ return;
141
+ }
142
+ let name = '';
143
+ if (!prop.computed) {
144
+ name =
145
+ prop.key.type === 'Identifier'
146
+ ? prop.key.name
147
+ : String(prop.key.value);
148
+ }
149
+ else if (prop.key.type === 'Literal' &&
150
+ typeof prop.key.value === 'string') {
151
+ name = prop.key.value;
152
+ }
153
+ if (!name ||
154
+ name.startsWith(':') ||
155
+ name.startsWith('[') ||
156
+ name.startsWith('@') ||
157
+ name.startsWith('--')) {
158
+ return;
159
+ }
160
+ declarations.push({ prop, name, kebab: (0, logicalPhysical_1.toKebabCase)(name) });
161
+ });
162
+ for (let i = 0; i < declarations.length; i++) {
163
+ for (let j = i + 1; j < declarations.length; j++) {
164
+ const first = declarations[i];
165
+ const second = declarations[j];
166
+ const overlap = overlapOf(first.kebab, second.kebab);
167
+ if (!overlap)
168
+ continue;
169
+ context.report({
170
+ node: second.prop.key,
171
+ messageId: overlap,
172
+ data: { first: first.name, second: second.name },
173
+ suggest: overlap === 'alias'
174
+ ? [
175
+ {
176
+ messageId: 'keep',
177
+ data: { keep: second.name },
178
+ fix: (fixer) => removeProperty(fixer, first.prop),
179
+ },
180
+ {
181
+ messageId: 'keep',
182
+ data: { keep: first.name },
183
+ fix: (fixer) => removeProperty(fixer, second.prop),
184
+ },
185
+ ]
186
+ : null,
187
+ });
188
+ }
189
+ }
190
+ }
191
+ },
192
+ };
@@ -0,0 +1 @@
1
+ export declare const noPhysicalProperties: import("eslint").Rule.RuleModule;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noPhysicalProperties = void 0;
4
+ const spellingRule_1 = require("../util/spellingRule");
5
+ exports.noPhysicalProperties = (0, spellingRule_1.createSpellingRule)('physical');
@@ -0,0 +1,9 @@
1
+ export declare const LOGICAL_PHYSICAL_EDGES: [string, string][];
2
+ export declare const LOGICAL_PHYSICAL_AXES: [string, string][];
3
+ export declare const LOGICAL_PHYSICAL_PAIRS: [string, string][];
4
+ export type PropertySpelling = 'logical' | 'physical';
5
+ export declare const canonicalProperty: (property: string) => string;
6
+ export declare const counterpartOf: (property: string) => string | undefined;
7
+ export declare const spellingOf: (property: string, includeAxes: boolean) => PropertySpelling | undefined;
8
+ export declare const toKebabCase: (name: string) => string;
9
+ export declare const toCamelCase: (name: string) => string;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toCamelCase = exports.toKebabCase = exports.spellingOf = exports.counterpartOf = exports.canonicalProperty = exports.LOGICAL_PHYSICAL_PAIRS = exports.LOGICAL_PHYSICAL_AXES = exports.LOGICAL_PHYSICAL_EDGES = void 0;
4
+ exports.LOGICAL_PHYSICAL_EDGES = [
5
+ ['margin-block-start', 'margin-top'],
6
+ ['margin-block-end', 'margin-bottom'],
7
+ ['margin-inline-start', 'margin-left'],
8
+ ['margin-inline-end', 'margin-right'],
9
+ ['padding-block-start', 'padding-top'],
10
+ ['padding-block-end', 'padding-bottom'],
11
+ ['padding-inline-start', 'padding-left'],
12
+ ['padding-inline-end', 'padding-right'],
13
+ ['scroll-margin-block-start', 'scroll-margin-top'],
14
+ ['scroll-margin-block-end', 'scroll-margin-bottom'],
15
+ ['scroll-margin-inline-start', 'scroll-margin-left'],
16
+ ['scroll-margin-inline-end', 'scroll-margin-right'],
17
+ ['scroll-padding-block-start', 'scroll-padding-top'],
18
+ ['scroll-padding-block-end', 'scroll-padding-bottom'],
19
+ ['scroll-padding-inline-start', 'scroll-padding-left'],
20
+ ['scroll-padding-inline-end', 'scroll-padding-right'],
21
+ ['inset-block-start', 'top'],
22
+ ['inset-block-end', 'bottom'],
23
+ ['inset-inline-start', 'left'],
24
+ ['inset-inline-end', 'right'],
25
+ ['border-block-start', 'border-top'],
26
+ ['border-block-end', 'border-bottom'],
27
+ ['border-inline-start', 'border-left'],
28
+ ['border-inline-end', 'border-right'],
29
+ ['border-block-start-width', 'border-top-width'],
30
+ ['border-block-start-style', 'border-top-style'],
31
+ ['border-block-start-color', 'border-top-color'],
32
+ ['border-block-end-width', 'border-bottom-width'],
33
+ ['border-block-end-style', 'border-bottom-style'],
34
+ ['border-block-end-color', 'border-bottom-color'],
35
+ ['border-inline-start-width', 'border-left-width'],
36
+ ['border-inline-start-style', 'border-left-style'],
37
+ ['border-inline-start-color', 'border-left-color'],
38
+ ['border-inline-end-width', 'border-right-width'],
39
+ ['border-inline-end-style', 'border-right-style'],
40
+ ['border-inline-end-color', 'border-right-color'],
41
+ ['border-start-start-radius', 'border-top-left-radius'],
42
+ ['border-start-end-radius', 'border-top-right-radius'],
43
+ ['border-end-start-radius', 'border-bottom-left-radius'],
44
+ ['border-end-end-radius', 'border-bottom-right-radius'],
45
+ ['corner-start-start-shape', 'corner-top-left-shape'],
46
+ ['corner-start-end-shape', 'corner-top-right-shape'],
47
+ ['corner-end-start-shape', 'corner-bottom-left-shape'],
48
+ ['corner-end-end-shape', 'corner-bottom-right-shape'],
49
+ ];
50
+ exports.LOGICAL_PHYSICAL_AXES = [
51
+ ['block-size', 'height'],
52
+ ['inline-size', 'width'],
53
+ ['min-block-size', 'min-height'],
54
+ ['min-inline-size', 'min-width'],
55
+ ['max-block-size', 'max-height'],
56
+ ['max-inline-size', 'max-width'],
57
+ ['overflow-block', 'overflow-y'],
58
+ ['overflow-inline', 'overflow-x'],
59
+ ['overscroll-behavior-block', 'overscroll-behavior-y'],
60
+ ['overscroll-behavior-inline', 'overscroll-behavior-x'],
61
+ ['contain-intrinsic-block-size', 'contain-intrinsic-height'],
62
+ ['contain-intrinsic-inline-size', 'contain-intrinsic-width'],
63
+ ];
64
+ exports.LOGICAL_PHYSICAL_PAIRS = [
65
+ ...exports.LOGICAL_PHYSICAL_EDGES,
66
+ ...exports.LOGICAL_PHYSICAL_AXES,
67
+ ];
68
+ const PHYSICAL_OF_LOGICAL = new Map(exports.LOGICAL_PHYSICAL_PAIRS);
69
+ const LOGICAL_OF_PHYSICAL = new Map(exports.LOGICAL_PHYSICAL_PAIRS.map(([logical, physical]) => [physical, logical]));
70
+ const canonicalProperty = (property) => PHYSICAL_OF_LOGICAL.get(property) ?? property;
71
+ exports.canonicalProperty = canonicalProperty;
72
+ const counterpartOf = (property) => PHYSICAL_OF_LOGICAL.get(property) ?? LOGICAL_OF_PHYSICAL.get(property);
73
+ exports.counterpartOf = counterpartOf;
74
+ const spellingOf = (property, includeAxes) => {
75
+ const table = includeAxes ? exports.LOGICAL_PHYSICAL_PAIRS : exports.LOGICAL_PHYSICAL_EDGES;
76
+ for (const [logical, physical] of table) {
77
+ if (property === logical)
78
+ return 'logical';
79
+ if (property === physical)
80
+ return 'physical';
81
+ }
82
+ return undefined;
83
+ };
84
+ exports.spellingOf = spellingOf;
85
+ const kebabCache = new Map();
86
+ const toKebabCase = (name) => {
87
+ const cached = kebabCache.get(name);
88
+ if (cached !== undefined)
89
+ return cached;
90
+ const kebab = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
91
+ kebabCache.set(name, kebab);
92
+ return kebab;
93
+ };
94
+ exports.toKebabCase = toKebabCase;
95
+ const toCamelCase = (name) => name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
96
+ exports.toCamelCase = toCamelCase;
@@ -0,0 +1,3 @@
1
+ import type { PropertySpelling } from './logicalPhysical';
2
+ import type { Rule } from 'eslint';
3
+ export declare const createSpellingRule: (reject: PropertySpelling) => Rule.RuleModule;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSpellingRule = void 0;
4
+ const logicalPhysical_1 = require("./logicalPhysical");
5
+ const DESCRIPTIONS = {
6
+ physical: 'Disallow the physical name of a property that also has a logical name',
7
+ logical: 'Disallow the logical name of a property that also has a physical name',
8
+ };
9
+ const MESSAGES = {
10
+ physical: "'{{ name }}' is the physical name of this property. Write it as '{{ counterpart }}', which follows the writing mode.",
11
+ logical: "'{{ name }}' is the logical name of this property. This project is written in physical properties; use '{{ counterpart }}'.",
12
+ };
13
+ const createSpellingRule = (reject) => ({
14
+ meta: {
15
+ type: 'suggestion',
16
+ docs: { description: DESCRIPTIONS[reject] },
17
+ hasSuggestions: true,
18
+ messages: {
19
+ rejected: MESSAGES[reject],
20
+ rename: "Rename to '{{ counterpart }}'",
21
+ },
22
+ schema: [
23
+ {
24
+ type: 'object',
25
+ properties: {
26
+ sizes: { type: 'boolean' },
27
+ },
28
+ additionalProperties: false,
29
+ },
30
+ ],
31
+ },
32
+ create(context) {
33
+ const includeAxes = context.options[0]?.sizes ?? false;
34
+ const plumeriaAliases = {};
35
+ return {
36
+ ImportDeclaration(node) {
37
+ if (node.source.value === '@plumeria/core') {
38
+ node.specifiers.forEach((specifier) => {
39
+ if (specifier.type === 'ImportNamespaceSpecifier' ||
40
+ specifier.type === 'ImportDefaultSpecifier') {
41
+ plumeriaAliases[specifier.local.name] = 'NAMESPACE';
42
+ }
43
+ else {
44
+ const spec = specifier;
45
+ const importedName = spec.imported.type === 'Identifier'
46
+ ? spec.imported.name
47
+ : String(spec.imported.value);
48
+ plumeriaAliases[specifier.local.name] = importedName;
49
+ }
50
+ });
51
+ }
52
+ },
53
+ CallExpression(node) {
54
+ let isCssProperties = false;
55
+ if (node.callee.type === 'MemberExpression') {
56
+ if (node.callee.object.type === 'Identifier' &&
57
+ plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
58
+ const propertyName = node.callee.property.type === 'Identifier'
59
+ ? node.callee.property.name
60
+ : null;
61
+ if (propertyName === 'create' ||
62
+ propertyName === 'keyframes' ||
63
+ propertyName === 'viewTransition') {
64
+ isCssProperties = true;
65
+ }
66
+ }
67
+ }
68
+ else if (node.callee.type === 'Identifier') {
69
+ const aliasName = plumeriaAliases[node.callee.name];
70
+ if (aliasName === 'create' ||
71
+ aliasName === 'keyframes' ||
72
+ aliasName === 'viewTransition') {
73
+ isCssProperties = true;
74
+ }
75
+ }
76
+ if (isCssProperties) {
77
+ node.arguments.forEach((arg) => {
78
+ if (arg.type === 'ObjectExpression') {
79
+ arg.properties.forEach((prop) => {
80
+ if (prop.type === 'Property' &&
81
+ prop.value.type === 'ObjectExpression') {
82
+ checkStyleObject(prop.value);
83
+ }
84
+ });
85
+ }
86
+ });
87
+ }
88
+ },
89
+ };
90
+ function checkStyleObject(node) {
91
+ node.properties.forEach((prop) => {
92
+ if (prop.type !== 'Property')
93
+ return;
94
+ if (prop.value.type === 'ObjectExpression') {
95
+ checkStyleObject(prop.value);
96
+ return;
97
+ }
98
+ let name = '';
99
+ if (!prop.computed) {
100
+ name =
101
+ prop.key.type === 'Identifier'
102
+ ? prop.key.name
103
+ : String(prop.key.value);
104
+ }
105
+ else if (prop.key.type === 'Literal' &&
106
+ typeof prop.key.value === 'string') {
107
+ name = prop.key.value;
108
+ }
109
+ if (!name)
110
+ return;
111
+ const kebab = (0, logicalPhysical_1.toKebabCase)(name);
112
+ if ((0, logicalPhysical_1.spellingOf)(kebab, includeAxes) !== reject)
113
+ return;
114
+ report(prop, name, (0, logicalPhysical_1.toCamelCase)((0, logicalPhysical_1.counterpartOf)(kebab)));
115
+ });
116
+ }
117
+ function report(prop, name, counterpart) {
118
+ context.report({
119
+ node: prop.key,
120
+ messageId: 'rejected',
121
+ data: { name, counterpart },
122
+ suggest: [
123
+ {
124
+ messageId: 'rename',
125
+ data: { counterpart },
126
+ fix: (fixer) => fixer.replaceText(prop.key, counterpart),
127
+ },
128
+ ],
129
+ });
130
+ }
131
+ },
132
+ });
133
+ exports.createSpellingRule = createSpellingRule;
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.7",
3
+ "version": "18.2.9",
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",