@plumeria/eslint-plugin 18.2.8 → 18.2.10
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 +42 -0
- package/dist/index.js +6 -0
- package/dist/rules/expand-border-shorthands.d.ts +2 -0
- package/dist/rules/expand-border-shorthands.js +128 -0
- package/dist/rules/no-logical-properties.d.ts +1 -0
- package/dist/rules/no-logical-properties.js +5 -0
- package/dist/rules/no-order-dependent-overlap.js +4 -60
- package/dist/rules/no-physical-properties.d.ts +1 -0
- package/dist/rules/no-physical-properties.js +5 -0
- package/dist/util/borderShorthand.d.ts +6 -0
- package/dist/util/borderShorthand.js +85 -0
- package/dist/util/logicalPhysical.d.ts +9 -0
- package/dist/util/logicalPhysical.js +96 -0
- package/dist/util/spellingRule.d.ts +3 -0
- package/dist/util/spellingRule.js +133 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,6 +107,48 @@ physical name, and two shorthands that cross without either containing the
|
|
|
107
107
|
other. A shorthand and its longhand are ranked by specificity and never
|
|
108
108
|
reported.
|
|
109
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
|
+
### expand-border-shorthands
|
|
116
|
+
|
|
117
|
+
Expands a border shorthand that bundles a width, a style and a color —
|
|
118
|
+
`border`, `borderBlock`, `borderInline`, and the eight edge forms — into the
|
|
119
|
+
three declarations it stands for. Those bundles are the only properties left
|
|
120
|
+
that cross an axis shorthand without either containing the other, so expanding
|
|
121
|
+
them turns the last unrankable pairs into ordinary shorthand-to-longhand ones.
|
|
122
|
+
|
|
123
|
+
Fixable. A value it cannot split, such as `var(--edge)` or `inherit`, is
|
|
124
|
+
reported without a fix: leaving it silent would let the expanded declarations
|
|
125
|
+
elsewhere outrank it. Not part of `recommended`.
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
borderTop: '1px solid red'
|
|
129
|
+
// becomes
|
|
130
|
+
borderTopWidth: '1px', borderTopStyle: 'solid', borderTopColor: 'red'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A shorthand resets what it omits, so `borderBlock: 'solid'` expands with
|
|
134
|
+
`medium` and `currentcolor` written out.
|
|
135
|
+
|
|
136
|
+
### no-physical-properties / no-logical-properties
|
|
137
|
+
|
|
138
|
+
Disallow one of the two names a property can carry, so a project writes edges
|
|
139
|
+
under one spelling only. `no-physical-properties` reports `paddingLeft` and
|
|
140
|
+
suggests `paddingInlineStart`; `no-logical-properties` reports the reverse.
|
|
141
|
+
Accepts `{ sizes }`, which adds `width`, `height`, their `min`/`max` forms,
|
|
142
|
+
`overflow-x` and `overflow-y` to the properties covered. It is `false` by
|
|
143
|
+
default: a size is one property under either spelling, while an edge is what a
|
|
144
|
+
direction reverses.
|
|
145
|
+
|
|
146
|
+
Neither is part of `recommended`, and turning both on is contradictory. Reach
|
|
147
|
+
for one when you want the pairs `no-order-dependent-overlap` reports to be
|
|
148
|
+
impossible to write: a property that appears under one spelling only can never
|
|
149
|
+
meet its other spelling on an element. A shorthand with no single counterpart,
|
|
150
|
+
such as `borderBlockWidth`, is outside either rule and stays reported.
|
|
151
|
+
|
|
110
152
|
### no-unknown-css-properties
|
|
111
153
|
|
|
112
154
|
Disallow unknown CSS properties in camelCase within `css.create`, `css.keyframes`, and `css.viewTransition`.
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const props_require_import_1 = require("./rules/props-require-import");
|
|
3
|
+
const expand_border_shorthands_1 = require("./rules/expand-border-shorthands");
|
|
3
4
|
const no_combinator_1 = require("./rules/no-combinator");
|
|
4
5
|
const no_destructure_1 = require("./rules/no-destructure");
|
|
5
6
|
const no_inline_object_1 = require("./rules/no-inline-object");
|
|
6
7
|
const no_inner_call_1 = require("./rules/no-inner-call");
|
|
7
8
|
const no_invalid_selector_1 = require("./rules/no-invalid-selector");
|
|
9
|
+
const no_logical_properties_1 = require("./rules/no-logical-properties");
|
|
8
10
|
const no_mixed_styling_props_1 = require("./rules/no-mixed-styling-props");
|
|
11
|
+
const no_physical_properties_1 = require("./rules/no-physical-properties");
|
|
9
12
|
const no_order_dependent_overlap_1 = require("./rules/no-order-dependent-overlap");
|
|
10
13
|
const no_unknown_css_properties_1 = require("./rules/no-unknown-css-properties");
|
|
11
14
|
const no_unused_keys_1 = require("./rules/no-unused-keys");
|
|
@@ -15,12 +18,15 @@ const validate_values_1 = require("./rules/validate-values");
|
|
|
15
18
|
const validate_pseudos_1 = require("./rules/validate-pseudos");
|
|
16
19
|
const rules = {
|
|
17
20
|
'props-require-import': props_require_import_1.propsRequireImport,
|
|
21
|
+
'expand-border-shorthands': expand_border_shorthands_1.expandBorderShorthands,
|
|
18
22
|
'no-combinator': no_combinator_1.noCombinator,
|
|
19
23
|
'no-destructure': no_destructure_1.noDestructure,
|
|
20
24
|
'no-inline-object': no_inline_object_1.noInlineObject,
|
|
21
25
|
'no-inner-call': no_inner_call_1.noInnerCall,
|
|
22
26
|
'no-invalid-selector': no_invalid_selector_1.noInvalidSelector,
|
|
27
|
+
'no-logical-properties': no_logical_properties_1.noLogicalProperties,
|
|
23
28
|
'no-mixed-styling-props': no_mixed_styling_props_1.noMixedStylingProps,
|
|
29
|
+
'no-physical-properties': no_physical_properties_1.noPhysicalProperties,
|
|
24
30
|
'no-order-dependent-overlap': no_order_dependent_overlap_1.noOrderDependentOverlap,
|
|
25
31
|
'no-unknown-css-properties': no_unknown_css_properties_1.noUnknownCssProperties,
|
|
26
32
|
'no-unused-keys': no_unused_keys_1.noUnusedKeys,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expandBorderShorthands = void 0;
|
|
4
|
+
const borderShorthand_1 = require("../util/borderShorthand");
|
|
5
|
+
const logicalPhysical_1 = require("../util/logicalPhysical");
|
|
6
|
+
const BUNDLES = new Set(borderShorthand_1.BORDER_BUNDLES);
|
|
7
|
+
exports.expandBorderShorthands = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Expand a border shorthand that bundles width, style and color into the three declarations it sets',
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
messages: {
|
|
15
|
+
expand: "'{{ name }}' sets a width, a style and a color at once. It crosses the axis shorthands without either containing the other, so write the three declarations it stands for.",
|
|
16
|
+
opaque: "'{{ name }}' sets a width, a style and a color at once, and this value cannot be split. Write the three declarations yourself, or the ones this rule expands elsewhere will outrank it.",
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const plumeriaAliases = {};
|
|
22
|
+
const sourceCode = context.sourceCode;
|
|
23
|
+
return {
|
|
24
|
+
ImportDeclaration(node) {
|
|
25
|
+
if (node.source.value === '@plumeria/core') {
|
|
26
|
+
node.specifiers.forEach((specifier) => {
|
|
27
|
+
if (specifier.type === 'ImportNamespaceSpecifier' ||
|
|
28
|
+
specifier.type === 'ImportDefaultSpecifier') {
|
|
29
|
+
plumeriaAliases[specifier.local.name] = 'NAMESPACE';
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const spec = specifier;
|
|
33
|
+
const importedName = spec.imported.type === 'Identifier'
|
|
34
|
+
? spec.imported.name
|
|
35
|
+
: String(spec.imported.value);
|
|
36
|
+
plumeriaAliases[specifier.local.name] = importedName;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
CallExpression(node) {
|
|
42
|
+
let isCssProperties = false;
|
|
43
|
+
if (node.callee.type === 'MemberExpression') {
|
|
44
|
+
if (node.callee.object.type === 'Identifier' &&
|
|
45
|
+
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
46
|
+
const propertyName = node.callee.property.type === 'Identifier'
|
|
47
|
+
? node.callee.property.name
|
|
48
|
+
: null;
|
|
49
|
+
if (propertyName === 'create' ||
|
|
50
|
+
propertyName === 'keyframes' ||
|
|
51
|
+
propertyName === 'viewTransition') {
|
|
52
|
+
isCssProperties = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (node.callee.type === 'Identifier') {
|
|
57
|
+
const aliasName = plumeriaAliases[node.callee.name];
|
|
58
|
+
if (aliasName === 'create' ||
|
|
59
|
+
aliasName === 'keyframes' ||
|
|
60
|
+
aliasName === 'viewTransition') {
|
|
61
|
+
isCssProperties = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (isCssProperties) {
|
|
65
|
+
node.arguments.forEach((arg) => {
|
|
66
|
+
if (arg.type === 'ObjectExpression') {
|
|
67
|
+
arg.properties.forEach((prop) => {
|
|
68
|
+
if (prop.type === 'Property' &&
|
|
69
|
+
prop.value.type === 'ObjectExpression') {
|
|
70
|
+
checkStyleObject(prop.value);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
function checkStyleObject(node) {
|
|
79
|
+
node.properties.forEach((prop) => {
|
|
80
|
+
if (prop.type !== 'Property')
|
|
81
|
+
return;
|
|
82
|
+
if (prop.value.type === 'ObjectExpression') {
|
|
83
|
+
checkStyleObject(prop.value);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let name = '';
|
|
87
|
+
if (!prop.computed) {
|
|
88
|
+
name =
|
|
89
|
+
prop.key.type === 'Identifier'
|
|
90
|
+
? prop.key.name
|
|
91
|
+
: String(prop.key.value);
|
|
92
|
+
}
|
|
93
|
+
else if (prop.key.type === 'Literal' &&
|
|
94
|
+
typeof prop.key.value === 'string') {
|
|
95
|
+
name = prop.key.value;
|
|
96
|
+
}
|
|
97
|
+
const kebab = (0, logicalPhysical_1.toKebabCase)(name);
|
|
98
|
+
if (!BUNDLES.has(kebab))
|
|
99
|
+
return;
|
|
100
|
+
const literal = prop.value.type === 'Literal' && typeof prop.value.value === 'string'
|
|
101
|
+
? prop.value.value
|
|
102
|
+
: null;
|
|
103
|
+
const parts = literal === null ? null : (0, borderShorthand_1.splitBorderValue)(literal);
|
|
104
|
+
if (!parts) {
|
|
105
|
+
context.report({
|
|
106
|
+
node: prop.key,
|
|
107
|
+
messageId: 'opaque',
|
|
108
|
+
data: { name },
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const quote = sourceCode.getText(prop.value).trim().startsWith('"')
|
|
113
|
+
? '"'
|
|
114
|
+
: "'";
|
|
115
|
+
const indent = ' '.repeat(prop.loc.start.column);
|
|
116
|
+
const declarations = ['width', 'style', 'color']
|
|
117
|
+
.map((part) => `${(0, logicalPhysical_1.toCamelCase)(`${kebab}-${part}`)}: ${quote}${parts[part]}${quote}`)
|
|
118
|
+
.join(`,\n${indent}`);
|
|
119
|
+
context.report({
|
|
120
|
+
node: prop.key,
|
|
121
|
+
messageId: 'expand',
|
|
122
|
+
data: { name },
|
|
123
|
+
fix: (fixer) => fixer.replaceText(prop, declarations),
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noLogicalProperties: import("eslint").Rule.RuleModule;
|
|
@@ -2,54 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noOrderDependentOverlap = void 0;
|
|
4
4
|
const zss_engine_1 = require("zss-engine");
|
|
5
|
-
const
|
|
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;
|
|
5
|
+
const logicalPhysical_1 = require("../util/logicalPhysical");
|
|
53
6
|
const DIRECT_SHORTHANDS = {};
|
|
54
7
|
for (const [shorthand, longhands] of Object.entries(zss_engine_1.DIRECT_LONGHANDS)) {
|
|
55
8
|
for (const longhand of longhands) {
|
|
@@ -79,21 +32,12 @@ const coverageOf = (property) => {
|
|
|
79
32
|
const longhands = zss_engine_1.DIRECT_LONGHANDS[property];
|
|
80
33
|
const coverage = longhands
|
|
81
34
|
? new Set(longhands.flatMap((longhand) => [...coverageOf(longhand)]))
|
|
82
|
-
: new Set([
|
|
35
|
+
: new Set([(0, logicalPhysical_1.canonicalProperty)(property)]);
|
|
83
36
|
coverages.set(property, coverage);
|
|
84
37
|
return coverage;
|
|
85
38
|
};
|
|
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
39
|
const overlapOf = (first, second) => {
|
|
96
|
-
if (
|
|
40
|
+
if ((0, logicalPhysical_1.canonicalProperty)(first) === (0, logicalPhysical_1.canonicalProperty)(second))
|
|
97
41
|
return 'alias';
|
|
98
42
|
const left = coverageOf(first);
|
|
99
43
|
const right = coverageOf(second);
|
|
@@ -213,7 +157,7 @@ exports.noOrderDependentOverlap = {
|
|
|
213
157
|
name.startsWith('--')) {
|
|
214
158
|
return;
|
|
215
159
|
}
|
|
216
|
-
declarations.push({ prop, name, kebab: toKebabCase(name) });
|
|
160
|
+
declarations.push({ prop, name, kebab: (0, logicalPhysical_1.toKebabCase)(name) });
|
|
217
161
|
});
|
|
218
162
|
for (let i = 0; i < declarations.length; i++) {
|
|
219
163
|
for (let j = i + 1; j < declarations.length; j++) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noPhysicalProperties: import("eslint").Rule.RuleModule;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitBorderValue = exports.BORDER_BUNDLES = void 0;
|
|
4
|
+
exports.BORDER_BUNDLES = [
|
|
5
|
+
'border',
|
|
6
|
+
'border-block',
|
|
7
|
+
'border-inline',
|
|
8
|
+
'border-top',
|
|
9
|
+
'border-bottom',
|
|
10
|
+
'border-left',
|
|
11
|
+
'border-right',
|
|
12
|
+
'border-block-start',
|
|
13
|
+
'border-block-end',
|
|
14
|
+
'border-inline-start',
|
|
15
|
+
'border-inline-end',
|
|
16
|
+
];
|
|
17
|
+
const STYLES = new Set([
|
|
18
|
+
'none',
|
|
19
|
+
'hidden',
|
|
20
|
+
'dotted',
|
|
21
|
+
'dashed',
|
|
22
|
+
'solid',
|
|
23
|
+
'double',
|
|
24
|
+
'groove',
|
|
25
|
+
'ridge',
|
|
26
|
+
'inset',
|
|
27
|
+
'outset',
|
|
28
|
+
]);
|
|
29
|
+
const WIDTH_KEYWORDS = new Set(['thin', 'medium', 'thick']);
|
|
30
|
+
const LENGTH = /^[+-]?(\d+\.?\d*|\.\d+)([a-z%]+)?$/i;
|
|
31
|
+
const INITIAL = { width: 'medium', style: 'none', color: 'currentcolor' };
|
|
32
|
+
const tokenize = (value) => {
|
|
33
|
+
const tokens = [];
|
|
34
|
+
let depth = 0;
|
|
35
|
+
let current = '';
|
|
36
|
+
for (const character of value.trim()) {
|
|
37
|
+
if (character === '(')
|
|
38
|
+
depth++;
|
|
39
|
+
if (character === ')')
|
|
40
|
+
depth--;
|
|
41
|
+
if (depth === 0 && /\s/.test(character)) {
|
|
42
|
+
if (current)
|
|
43
|
+
tokens.push(current);
|
|
44
|
+
current = '';
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
current += character;
|
|
48
|
+
}
|
|
49
|
+
if (current)
|
|
50
|
+
tokens.push(current);
|
|
51
|
+
return depth === 0 && tokens.length > 0 && tokens.length <= 3 ? tokens : null;
|
|
52
|
+
};
|
|
53
|
+
const isWidth = (token) => WIDTH_KEYWORDS.has(token) || LENGTH.test(token) || /^calc\(/i.test(token);
|
|
54
|
+
const isColor = (token) => /^(#|rgb|hsl|hwb|lab|lch|oklab|oklch|color\(|light-dark\()/i.test(token) ||
|
|
55
|
+
/^[a-z-]+$/i.test(token);
|
|
56
|
+
const splitBorderValue = (value) => {
|
|
57
|
+
const tokens = tokenize(value);
|
|
58
|
+
if (!tokens)
|
|
59
|
+
return null;
|
|
60
|
+
if (tokens.some((token) => /\bvar\(|^inherit$|^initial$|^unset$|^revert/i.test(token)))
|
|
61
|
+
return null;
|
|
62
|
+
const parts = {};
|
|
63
|
+
for (const token of tokens) {
|
|
64
|
+
const lower = token.toLowerCase();
|
|
65
|
+
if (!parts.style && STYLES.has(lower)) {
|
|
66
|
+
parts.style = token;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!parts.width && isWidth(lower)) {
|
|
70
|
+
parts.width = token;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!parts.color && isColor(token)) {
|
|
74
|
+
parts.color = token;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
width: parts.width ?? INITIAL.width,
|
|
81
|
+
style: parts.style ?? INITIAL.style,
|
|
82
|
+
color: parts.color ?? INITIAL.color,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
exports.splitBorderValue = splitBorderValue;
|
|
@@ -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,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;
|