@plumeria/eslint-plugin 19.14.4 → 20.0.0
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 +2 -2
- package/dist/rules/expand-border-shorthands.js +11 -2
- package/dist/rules/format-properties.js +11 -2
- package/dist/rules/no-destructure.js +2 -0
- package/dist/rules/no-inner-call.js +3 -1
- package/dist/rules/no-mutable-binding.js +2 -0
- package/dist/rules/no-order-dependent-overlap.js +11 -2
- package/dist/rules/no-unknown-css-properties.js +86 -3
- package/dist/rules/sort-properties.js +11 -2
- package/dist/rules/validate-at-rules.js +3 -7
- package/dist/rules/validate-values.js +190 -25
- package/dist/util/propertyGroups.js +8 -0
- package/dist/util/spellingRule.js +11 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ Accepts `{ styleProp }`; see [Configuring the styling prop](#configuring-the-sty
|
|
|
162
162
|
|
|
163
163
|
### no-unknown-css-properties
|
|
164
164
|
|
|
165
|
-
Disallow unknown CSS properties in camelCase within `css.create`, `css.keyframes`, and `css.
|
|
165
|
+
Disallow unknown CSS properties in camelCase within `css.create`, `css.keyframes`, `css.viewTransition`, and `css.positionTry`. Inside `css.positionTry`, it also reports properties that `@position-try` does not accept.
|
|
166
166
|
|
|
167
167
|
In `css.create`, a key that holds an object is reported with a hint that nested selectors start with `:`, `[` or `@`.
|
|
168
168
|
|
|
@@ -197,7 +197,7 @@ Validates CSS pseudo-classes and pseudo-elements inside `css.create()`. It check
|
|
|
197
197
|
|
|
198
198
|
### validate-at-rules
|
|
199
199
|
|
|
200
|
-
Validates at-rules inside `css.create()`. It accepts `@media`, `@container`,
|
|
200
|
+
Validates at-rules inside `css.create()`. It accepts `@media`, `@container`, and `@supports`, each with a prelude, whether or not a space separates it from the keyword, and `@starting-style` on its own, and supports validation of computed keys when TypeScript is available.
|
|
201
201
|
|
|
202
202
|
## Optional rules
|
|
203
203
|
|
|
@@ -41,6 +41,7 @@ exports.expandBorderShorthands = {
|
|
|
41
41
|
},
|
|
42
42
|
CallExpression(node) {
|
|
43
43
|
let isCssProperties = false;
|
|
44
|
+
let isPositionTry = false;
|
|
44
45
|
if (node.callee.type === 'MemberExpression') {
|
|
45
46
|
if (node.callee.object.type === 'Identifier' &&
|
|
46
47
|
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
@@ -49,8 +50,10 @@ exports.expandBorderShorthands = {
|
|
|
49
50
|
: null;
|
|
50
51
|
if (propertyName === 'create' ||
|
|
51
52
|
propertyName === 'keyframes' ||
|
|
52
|
-
propertyName === 'viewTransition'
|
|
53
|
+
propertyName === 'viewTransition' ||
|
|
54
|
+
propertyName === 'positionTry') {
|
|
53
55
|
isCssProperties = true;
|
|
56
|
+
isPositionTry = propertyName === 'positionTry';
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
}
|
|
@@ -58,13 +61,19 @@ exports.expandBorderShorthands = {
|
|
|
58
61
|
const aliasName = plumeriaAliases[node.callee.name];
|
|
59
62
|
if (aliasName === 'create' ||
|
|
60
63
|
aliasName === 'keyframes' ||
|
|
61
|
-
aliasName === 'viewTransition'
|
|
64
|
+
aliasName === 'viewTransition' ||
|
|
65
|
+
aliasName === 'positionTry') {
|
|
62
66
|
isCssProperties = true;
|
|
67
|
+
isPositionTry = aliasName === 'positionTry';
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
if (isCssProperties) {
|
|
66
71
|
node.arguments.forEach((arg) => {
|
|
67
72
|
if (arg.type === 'ObjectExpression') {
|
|
73
|
+
if (isPositionTry) {
|
|
74
|
+
checkStyleObject(arg);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
68
77
|
arg.properties.forEach((prop) => {
|
|
69
78
|
if (prop.type !== 'Property')
|
|
70
79
|
return;
|
|
@@ -140,6 +140,7 @@ exports.formatProperties = {
|
|
|
140
140
|
},
|
|
141
141
|
CallExpression(node) {
|
|
142
142
|
let isCssProperties = false;
|
|
143
|
+
let isPositionTry = false;
|
|
143
144
|
if (node.callee.type === 'MemberExpression') {
|
|
144
145
|
if (node.callee.object.type === 'Identifier' &&
|
|
145
146
|
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
@@ -148,8 +149,10 @@ exports.formatProperties = {
|
|
|
148
149
|
: null;
|
|
149
150
|
if (propertyName === 'create' ||
|
|
150
151
|
propertyName === 'keyframes' ||
|
|
151
|
-
propertyName === 'viewTransition'
|
|
152
|
+
propertyName === 'viewTransition' ||
|
|
153
|
+
propertyName === 'positionTry') {
|
|
152
154
|
isCssProperties = true;
|
|
155
|
+
isPositionTry = propertyName === 'positionTry';
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
}
|
|
@@ -157,13 +160,19 @@ exports.formatProperties = {
|
|
|
157
160
|
const alias = plumeriaAliases[node.callee.name];
|
|
158
161
|
if (alias === 'create' ||
|
|
159
162
|
alias === 'keyframes' ||
|
|
160
|
-
alias === 'viewTransition'
|
|
163
|
+
alias === 'viewTransition' ||
|
|
164
|
+
alias === 'positionTry') {
|
|
161
165
|
isCssProperties = true;
|
|
166
|
+
isPositionTry = alias === 'positionTry';
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
if (isCssProperties) {
|
|
165
170
|
node.arguments.forEach((arg) => {
|
|
166
171
|
if (arg.type === 'ObjectExpression') {
|
|
172
|
+
if (isPositionTry) {
|
|
173
|
+
checkStyleObject(arg);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
167
176
|
arg.properties.forEach((prop) => {
|
|
168
177
|
if (prop.type !== 'Property')
|
|
169
178
|
return;
|
|
@@ -80,7 +80,9 @@ exports.noInnerCall = {
|
|
|
80
80
|
propertyName === 'createStatic' ||
|
|
81
81
|
propertyName === 'createTheme' ||
|
|
82
82
|
propertyName === 'keyframes' ||
|
|
83
|
-
propertyName === 'viewTransition'
|
|
83
|
+
propertyName === 'viewTransition' ||
|
|
84
|
+
propertyName === 'positionTry' ||
|
|
85
|
+
propertyName === 'varType') {
|
|
84
86
|
context.report({
|
|
85
87
|
node,
|
|
86
88
|
messageId: 'noInnerCall',
|
|
@@ -115,6 +115,7 @@ exports.noOrderDependentOverlap = {
|
|
|
115
115
|
},
|
|
116
116
|
CallExpression(node) {
|
|
117
117
|
let isCssProperties = false;
|
|
118
|
+
let isPositionTry = false;
|
|
118
119
|
if (node.callee.type === 'MemberExpression') {
|
|
119
120
|
if (node.callee.object.type === 'Identifier' &&
|
|
120
121
|
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
@@ -123,8 +124,10 @@ exports.noOrderDependentOverlap = {
|
|
|
123
124
|
: null;
|
|
124
125
|
if (propertyName === 'create' ||
|
|
125
126
|
propertyName === 'keyframes' ||
|
|
126
|
-
propertyName === 'viewTransition'
|
|
127
|
+
propertyName === 'viewTransition' ||
|
|
128
|
+
propertyName === 'positionTry') {
|
|
127
129
|
isCssProperties = true;
|
|
130
|
+
isPositionTry = propertyName === 'positionTry';
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
}
|
|
@@ -132,13 +135,19 @@ exports.noOrderDependentOverlap = {
|
|
|
132
135
|
const aliasName = plumeriaAliases[node.callee.name];
|
|
133
136
|
if (aliasName === 'create' ||
|
|
134
137
|
aliasName === 'keyframes' ||
|
|
135
|
-
aliasName === 'viewTransition'
|
|
138
|
+
aliasName === 'viewTransition' ||
|
|
139
|
+
aliasName === 'positionTry') {
|
|
136
140
|
isCssProperties = true;
|
|
141
|
+
isPositionTry = aliasName === 'positionTry';
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
if (isCssProperties) {
|
|
140
145
|
node.arguments.forEach((arg) => {
|
|
141
146
|
if (arg.type === 'ObjectExpression') {
|
|
147
|
+
if (isPositionTry) {
|
|
148
|
+
checkStyleObject(arg);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
142
151
|
arg.properties.forEach((prop) => {
|
|
143
152
|
if (prop.type !== 'Property')
|
|
144
153
|
return;
|
|
@@ -5,15 +5,57 @@ const zss_engine_1 = require("zss-engine");
|
|
|
5
5
|
const styleObject_1 = require("../util/styleObject");
|
|
6
6
|
const cssPropertyData_1 = require("../util/cssPropertyData");
|
|
7
7
|
const knownProperties = new Set(cssPropertyData_1.cssPropertyData);
|
|
8
|
+
const positionTryProperties = new Set([
|
|
9
|
+
'position-anchor',
|
|
10
|
+
'position-area',
|
|
11
|
+
'inset',
|
|
12
|
+
'inset-block',
|
|
13
|
+
'inset-block-start',
|
|
14
|
+
'inset-block-end',
|
|
15
|
+
'inset-inline',
|
|
16
|
+
'inset-inline-start',
|
|
17
|
+
'inset-inline-end',
|
|
18
|
+
'top',
|
|
19
|
+
'right',
|
|
20
|
+
'bottom',
|
|
21
|
+
'left',
|
|
22
|
+
'place-self',
|
|
23
|
+
'align-self',
|
|
24
|
+
'justify-self',
|
|
25
|
+
'inline-size',
|
|
26
|
+
'min-inline-size',
|
|
27
|
+
'max-inline-size',
|
|
28
|
+
'width',
|
|
29
|
+
'min-width',
|
|
30
|
+
'max-width',
|
|
31
|
+
'block-size',
|
|
32
|
+
'min-block-size',
|
|
33
|
+
'max-block-size',
|
|
34
|
+
'height',
|
|
35
|
+
'min-height',
|
|
36
|
+
'max-height',
|
|
37
|
+
'margin',
|
|
38
|
+
'margin-block',
|
|
39
|
+
'margin-block-start',
|
|
40
|
+
'margin-block-end',
|
|
41
|
+
'margin-inline',
|
|
42
|
+
'margin-inline-start',
|
|
43
|
+
'margin-inline-end',
|
|
44
|
+
'margin-top',
|
|
45
|
+
'margin-right',
|
|
46
|
+
'margin-bottom',
|
|
47
|
+
'margin-left',
|
|
48
|
+
]);
|
|
8
49
|
exports.noUnknownCssProperties = {
|
|
9
50
|
meta: {
|
|
10
51
|
type: 'problem',
|
|
11
52
|
docs: {
|
|
12
|
-
description: 'Disallow unknown CSS properties in camelCase within css.create, css.keyframes, and css.
|
|
53
|
+
description: 'Disallow unknown CSS properties in camelCase within css.create, css.keyframes, css.viewTransition, and css.positionTry',
|
|
13
54
|
},
|
|
14
55
|
messages: {
|
|
15
56
|
unknownProperty: "Unknown CSS property '{{ name }}'.",
|
|
16
57
|
unknownNestedProperty: "Unknown CSS property '{{ name }}'. Nested selectors start with ':', '[' or '@'.",
|
|
58
|
+
positionTryProperty: "'{{ name }}' cannot be used in css.positionTry. @position-try only accepts inset, margin, sizing and self-alignment properties, positionAnchor and positionArea.",
|
|
17
59
|
},
|
|
18
60
|
schema: [],
|
|
19
61
|
},
|
|
@@ -39,6 +81,7 @@ exports.noUnknownCssProperties = {
|
|
|
39
81
|
},
|
|
40
82
|
CallExpression(node) {
|
|
41
83
|
let isCssProperties = false;
|
|
84
|
+
let isPositionTry = false;
|
|
42
85
|
let isCreate = false;
|
|
43
86
|
if (node.callee.type === 'MemberExpression') {
|
|
44
87
|
if (node.callee.object.type === 'Identifier' &&
|
|
@@ -48,8 +91,10 @@ exports.noUnknownCssProperties = {
|
|
|
48
91
|
: null;
|
|
49
92
|
if (propertyName === 'create' ||
|
|
50
93
|
propertyName === 'keyframes' ||
|
|
51
|
-
propertyName === 'viewTransition'
|
|
94
|
+
propertyName === 'viewTransition' ||
|
|
95
|
+
propertyName === 'positionTry') {
|
|
52
96
|
isCssProperties = true;
|
|
97
|
+
isPositionTry = propertyName === 'positionTry';
|
|
53
98
|
isCreate = propertyName === 'create';
|
|
54
99
|
}
|
|
55
100
|
}
|
|
@@ -58,14 +103,20 @@ exports.noUnknownCssProperties = {
|
|
|
58
103
|
const alias = plumeriaAliases[node.callee.name];
|
|
59
104
|
if (alias === 'create' ||
|
|
60
105
|
alias === 'keyframes' ||
|
|
61
|
-
alias === 'viewTransition'
|
|
106
|
+
alias === 'viewTransition' ||
|
|
107
|
+
alias === 'positionTry') {
|
|
62
108
|
isCssProperties = true;
|
|
109
|
+
isPositionTry = alias === 'positionTry';
|
|
63
110
|
isCreate = alias === 'create';
|
|
64
111
|
}
|
|
65
112
|
}
|
|
66
113
|
if (isCssProperties) {
|
|
67
114
|
node.arguments.forEach((arg) => {
|
|
68
115
|
if (arg.type === 'ObjectExpression') {
|
|
116
|
+
if (isPositionTry) {
|
|
117
|
+
checkPositionTry(arg);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
69
120
|
arg.properties.forEach((prop) => {
|
|
70
121
|
if (prop.type !== 'Property')
|
|
71
122
|
return;
|
|
@@ -78,6 +129,38 @@ exports.noUnknownCssProperties = {
|
|
|
78
129
|
}
|
|
79
130
|
},
|
|
80
131
|
};
|
|
132
|
+
function checkPositionTry(node) {
|
|
133
|
+
node.properties.forEach((prop) => {
|
|
134
|
+
if (prop.type !== 'Property')
|
|
135
|
+
return;
|
|
136
|
+
let keyName;
|
|
137
|
+
if (!prop.computed) {
|
|
138
|
+
keyName =
|
|
139
|
+
prop.key.type === 'Identifier'
|
|
140
|
+
? prop.key.name
|
|
141
|
+
: String(prop.key.value);
|
|
142
|
+
}
|
|
143
|
+
else if (prop.key.type === 'Literal' &&
|
|
144
|
+
typeof prop.key.value === 'string') {
|
|
145
|
+
keyName = prop.key.value;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const kebabName = (0, zss_engine_1.camelToKebabCase)(keyName);
|
|
151
|
+
if (positionTryProperties.has(kebabName))
|
|
152
|
+
return;
|
|
153
|
+
context.report({
|
|
154
|
+
node: prop.key,
|
|
155
|
+
messageId: /^[:[@]|^--/.test(keyName) || knownProperties.has(kebabName)
|
|
156
|
+
? 'positionTryProperty'
|
|
157
|
+
: 'unknownProperty',
|
|
158
|
+
data: {
|
|
159
|
+
name: keyName,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
81
164
|
function checkStyleObject(node, isCreate) {
|
|
82
165
|
node.properties.forEach((prop) => {
|
|
83
166
|
if (prop.type === 'Property') {
|
|
@@ -188,6 +188,7 @@ exports.sortProperties = {
|
|
|
188
188
|
},
|
|
189
189
|
CallExpression(node) {
|
|
190
190
|
let isCssProperties = false;
|
|
191
|
+
let isPositionTry = false;
|
|
191
192
|
if (node.callee.type === 'MemberExpression') {
|
|
192
193
|
if (node.callee.object.type === 'Identifier' &&
|
|
193
194
|
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
@@ -196,8 +197,10 @@ exports.sortProperties = {
|
|
|
196
197
|
: null;
|
|
197
198
|
if (propertyName === 'create' ||
|
|
198
199
|
propertyName === 'keyframes' ||
|
|
199
|
-
propertyName === 'viewTransition'
|
|
200
|
+
propertyName === 'viewTransition' ||
|
|
201
|
+
propertyName === 'positionTry') {
|
|
200
202
|
isCssProperties = true;
|
|
203
|
+
isPositionTry = propertyName === 'positionTry';
|
|
201
204
|
}
|
|
202
205
|
}
|
|
203
206
|
}
|
|
@@ -205,13 +208,19 @@ exports.sortProperties = {
|
|
|
205
208
|
const alias = plumeriaAliases[node.callee.name];
|
|
206
209
|
if (alias === 'create' ||
|
|
207
210
|
alias === 'keyframes' ||
|
|
208
|
-
alias === 'viewTransition'
|
|
211
|
+
alias === 'viewTransition' ||
|
|
212
|
+
alias === 'positionTry') {
|
|
209
213
|
isCssProperties = true;
|
|
214
|
+
isPositionTry = alias === 'positionTry';
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
if (isCssProperties) {
|
|
213
218
|
node.arguments.forEach((arg) => {
|
|
214
219
|
if (arg.type === 'ObjectExpression') {
|
|
220
|
+
if (isPositionTry) {
|
|
221
|
+
checkStyleObject(arg);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
215
224
|
arg.properties.forEach((prop) => {
|
|
216
225
|
if (prop.type !== 'Property')
|
|
217
226
|
return;
|
|
@@ -4,14 +4,10 @@ exports.validateAtRules = void 0;
|
|
|
4
4
|
exports.isValidAtRule = isValidAtRule;
|
|
5
5
|
const styleObject_1 = require("../util/styleObject");
|
|
6
6
|
const staticValue_1 = require("../util/staticValue");
|
|
7
|
-
const VALID_AT_RULES = [
|
|
8
|
-
'@media',
|
|
9
|
-
'@container',
|
|
10
|
-
'@supports',
|
|
11
|
-
'@layer',
|
|
12
|
-
'@scope',
|
|
13
|
-
];
|
|
7
|
+
const VALID_AT_RULES = ['@media', '@container', '@supports'];
|
|
14
8
|
function isValidAtRule(rule) {
|
|
9
|
+
if (rule === '@starting-style')
|
|
10
|
+
return true;
|
|
15
11
|
return VALID_AT_RULES.some((keyword) => {
|
|
16
12
|
if (!rule.startsWith(keyword))
|
|
17
13
|
return false;
|
|
@@ -899,34 +899,35 @@ function getFoldedValues(key) {
|
|
|
899
899
|
foldedValueCache.set(key, folded);
|
|
900
900
|
return folded;
|
|
901
901
|
}
|
|
902
|
+
const transformFunctionString = [
|
|
903
|
+
`matrix\\(${mathContentsString}\\)`,
|
|
904
|
+
`matrix3d\\(${mathContentsString}\\)`,
|
|
905
|
+
`perspective\\(${mathContentsString}\\)`,
|
|
906
|
+
`rotate\\(${mathContentsString}\\)`,
|
|
907
|
+
`rotate3d\\(${mathContentsString}\\)`,
|
|
908
|
+
`rotateX\\(${mathContentsString}\\)`,
|
|
909
|
+
`rotateY\\(${mathContentsString}\\)`,
|
|
910
|
+
`rotateZ\\(${mathContentsString}\\)`,
|
|
911
|
+
`scale\\(${mathContentsString}\\)`,
|
|
912
|
+
`scale3d\\(${mathContentsString}\\)`,
|
|
913
|
+
`scaleX\\(${mathContentsString}\\)`,
|
|
914
|
+
`scaleY\\(${mathContentsString}\\)`,
|
|
915
|
+
`scaleZ\\(${mathContentsString}\\)`,
|
|
916
|
+
`skew\\(${mathContentsString}\\)`,
|
|
917
|
+
`skewX\\(${mathContentsString}\\)`,
|
|
918
|
+
`skewY\\(${mathContentsString}\\)`,
|
|
919
|
+
`translate\\(${mathContentsString}\\)`,
|
|
920
|
+
`translate3d\\(${mathContentsString}\\)`,
|
|
921
|
+
`translateX\\(${mathContentsString}\\)`,
|
|
922
|
+
`translateY\\(${mathContentsString}\\)`,
|
|
923
|
+
`translateZ\\(${mathContentsString}\\)`,
|
|
924
|
+
].join('|');
|
|
902
925
|
function getValidator(key) {
|
|
903
926
|
if (validatorCache.has(key))
|
|
904
927
|
return validatorCache.get(key);
|
|
905
928
|
let validator = null;
|
|
906
929
|
const lvp = getLengthValuePattern(key);
|
|
907
|
-
const getTransformFunctions = () =>
|
|
908
|
-
`matrix\\(${mathContentsString}\\)`,
|
|
909
|
-
`matrix3d\\(${mathContentsString}\\)`,
|
|
910
|
-
`perspective\\(${mathContentsString}\\)`,
|
|
911
|
-
`rotate\\(${mathContentsString}\\)`,
|
|
912
|
-
`rotate3d\\(${mathContentsString}\\)`,
|
|
913
|
-
`rotateX\\(${mathContentsString}\\)`,
|
|
914
|
-
`rotateY\\(${mathContentsString}\\)`,
|
|
915
|
-
`rotateZ\\(${mathContentsString}\\)`,
|
|
916
|
-
`scale\\(${mathContentsString}\\)`,
|
|
917
|
-
`scale3d\\(${mathContentsString}\\)`,
|
|
918
|
-
`scaleX\\(${mathContentsString}\\)`,
|
|
919
|
-
`scaleY\\(${mathContentsString}\\)`,
|
|
920
|
-
`scaleZ\\(${mathContentsString}\\)`,
|
|
921
|
-
`skew\\(${mathContentsString}\\)`,
|
|
922
|
-
`skewX\\(${mathContentsString}\\)`,
|
|
923
|
-
`skewY\\(${mathContentsString}\\)`,
|
|
924
|
-
`translate\\(${mathContentsString}\\)`,
|
|
925
|
-
`translate3d\\(${mathContentsString}\\)`,
|
|
926
|
-
`translateX\\(${mathContentsString}\\)`,
|
|
927
|
-
`translateY\\(${mathContentsString}\\)`,
|
|
928
|
-
`translateZ\\(${mathContentsString}\\)`,
|
|
929
|
-
].join('|');
|
|
930
|
+
const getTransformFunctions = () => transformFunctionString;
|
|
930
931
|
const getGeometryBaseSet = () => 'content-box|padding-box|border-box|fill-box|stroke-box|view-box';
|
|
931
932
|
if (['translate'].includes(key)) {
|
|
932
933
|
const r = new RegExp(`^(${lvp}|${percentagePattern})(\\s(${lvp}|${percentagePattern}))?(\\s(${lvp}))?$`);
|
|
@@ -1848,6 +1849,109 @@ function getValidator(key) {
|
|
|
1848
1849
|
validatorCache.set(key, validator);
|
|
1849
1850
|
return validator;
|
|
1850
1851
|
}
|
|
1852
|
+
const contextDependentUnits = [
|
|
1853
|
+
'cap',
|
|
1854
|
+
'ch',
|
|
1855
|
+
'em',
|
|
1856
|
+
'ex',
|
|
1857
|
+
'ic',
|
|
1858
|
+
'lh',
|
|
1859
|
+
'rcap',
|
|
1860
|
+
'rch',
|
|
1861
|
+
'rem',
|
|
1862
|
+
'rex',
|
|
1863
|
+
'ric',
|
|
1864
|
+
'rlh',
|
|
1865
|
+
'cqw',
|
|
1866
|
+
'cqh',
|
|
1867
|
+
'cqi',
|
|
1868
|
+
'cqb',
|
|
1869
|
+
'cqmin',
|
|
1870
|
+
'cqmax',
|
|
1871
|
+
];
|
|
1872
|
+
const contextDependentRegex = new RegExp(`(?:var\\(|[\\d.](?:${contextDependentUnits.join('|')})(?![a-zA-Z0-9_-]))`, 'i');
|
|
1873
|
+
const quotedStringRegex = new RegExp(stringString, 'g');
|
|
1874
|
+
const timeString = `${pureNumber}(?:ms|s)`;
|
|
1875
|
+
const resolutionString = `${pureNumber}(?:dpi|dpcm|dppx|x)`;
|
|
1876
|
+
const customIdentRegex = /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
1877
|
+
const exactly = (pattern) => {
|
|
1878
|
+
const r = new RegExp(`^(?:${pattern})$`);
|
|
1879
|
+
return (v) => r.test(v);
|
|
1880
|
+
};
|
|
1881
|
+
const varTypeValidators = {
|
|
1882
|
+
'<length>': exactly(`${lengthPattern}|${calcString}`),
|
|
1883
|
+
'<number>': exactly(`${pureNumber}|${calcString}`),
|
|
1884
|
+
'<integer>': exactly(`${pureInteger}|${calcString}`),
|
|
1885
|
+
'<percentage>': exactly(`${percentagePattern}|${calcString}`),
|
|
1886
|
+
'<length-percentage>': exactly(`${lengthPattern}|${percentagePattern}|${calcString}`),
|
|
1887
|
+
'<angle>': exactly(`${pureAngle}|${calcString}`),
|
|
1888
|
+
'<time>': exactly(`${timeString}|${calcString}`),
|
|
1889
|
+
'<resolution>': exactly(`${resolutionString}|${calcString}`),
|
|
1890
|
+
'<color>': isColor,
|
|
1891
|
+
'<image>': exactly(`${urlString}|${gradientString}|${imageSetString}`),
|
|
1892
|
+
'<url>': exactly(urlString),
|
|
1893
|
+
'<string>': exactly(stringString),
|
|
1894
|
+
'<custom-ident>': (v) => customIdentRegex.test(v) &&
|
|
1895
|
+
!globalValues.includes(v.toLowerCase()) &&
|
|
1896
|
+
v.toLowerCase() !== 'default',
|
|
1897
|
+
'<transform-function>': exactly(transformFunctionString),
|
|
1898
|
+
'<transform-list>': exactly(`(?:${transformFunctionString})(?:\\s+(?:${transformFunctionString}))*`),
|
|
1899
|
+
};
|
|
1900
|
+
function splitTopLevelSpaces(value) {
|
|
1901
|
+
const parts = [];
|
|
1902
|
+
let depth = 0;
|
|
1903
|
+
let current = '';
|
|
1904
|
+
for (const char of value) {
|
|
1905
|
+
if (char === '(')
|
|
1906
|
+
depth++;
|
|
1907
|
+
else if (char === ')')
|
|
1908
|
+
depth--;
|
|
1909
|
+
if (depth === 0 && /\s/.test(char)) {
|
|
1910
|
+
if (current)
|
|
1911
|
+
parts.push(current);
|
|
1912
|
+
current = '';
|
|
1913
|
+
}
|
|
1914
|
+
else {
|
|
1915
|
+
current += char;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
if (current)
|
|
1919
|
+
parts.push(current);
|
|
1920
|
+
return parts;
|
|
1921
|
+
}
|
|
1922
|
+
function matchesSyntaxComponent(component, value) {
|
|
1923
|
+
const multiplier = /[+#]$/.test(component) ? component.slice(-1) : '';
|
|
1924
|
+
const name = multiplier ? component.slice(0, -1) : component;
|
|
1925
|
+
if (!name.startsWith('<'))
|
|
1926
|
+
return multiplier ? null : value === name;
|
|
1927
|
+
const validator = varTypeValidators[name];
|
|
1928
|
+
if (!validator)
|
|
1929
|
+
return null;
|
|
1930
|
+
if (!multiplier)
|
|
1931
|
+
return validator(value);
|
|
1932
|
+
const items = multiplier === '+'
|
|
1933
|
+
? splitTopLevelSpaces(value)
|
|
1934
|
+
: splitTopLevelCommas(value);
|
|
1935
|
+
return items.every((item) => item !== '' && validator(item));
|
|
1936
|
+
}
|
|
1937
|
+
function checkInitialValue(syntax, value) {
|
|
1938
|
+
const trimmedSyntax = syntax.trim();
|
|
1939
|
+
if (trimmedSyntax === '*')
|
|
1940
|
+
return null;
|
|
1941
|
+
if (contextDependentRegex.test(value.replace(quotedStringRegex, ''))) {
|
|
1942
|
+
return 'varTypeDependent';
|
|
1943
|
+
}
|
|
1944
|
+
const trimmedValue = value.trim();
|
|
1945
|
+
let unknown = false;
|
|
1946
|
+
for (const component of trimmedSyntax.split('|')) {
|
|
1947
|
+
const result = matchesSyntaxComponent(component.trim(), trimmedValue);
|
|
1948
|
+
if (result)
|
|
1949
|
+
return null;
|
|
1950
|
+
if (result === null)
|
|
1951
|
+
unknown = true;
|
|
1952
|
+
}
|
|
1953
|
+
return unknown ? null : 'varTypeMismatch';
|
|
1954
|
+
}
|
|
1851
1955
|
exports.validateValues = {
|
|
1852
1956
|
meta: {
|
|
1853
1957
|
type: 'problem',
|
|
@@ -1858,6 +1962,8 @@ exports.validateValues = {
|
|
|
1858
1962
|
validateValue: "'{{key}}' has an invalid value '{{value}}'. Valid values: {{validValues}}",
|
|
1859
1963
|
invalidPrimitive: "'{{key}}' cannot be assigned a {{type}} value ({{value}}). CSS properties require string or number values.",
|
|
1860
1964
|
invalidNumber: "'{{key}}' does not accept numeric values. Expected a string value.",
|
|
1965
|
+
varTypeMismatch: "css.varType initialValue '{{value}}' does not match the syntax '{{syntax}}'.",
|
|
1966
|
+
varTypeDependent: "css.varType initialValue '{{value}}' depends on the font, a container or var(), so the browser drops the @property. Use a value that does not depend on where it is used.",
|
|
1861
1967
|
},
|
|
1862
1968
|
schema: [],
|
|
1863
1969
|
},
|
|
@@ -1947,6 +2053,43 @@ exports.validateValues = {
|
|
|
1947
2053
|
}
|
|
1948
2054
|
});
|
|
1949
2055
|
}
|
|
2056
|
+
function checkVarType(arg) {
|
|
2057
|
+
if (!arg || arg.type !== 'ObjectExpression')
|
|
2058
|
+
return;
|
|
2059
|
+
let syntax = '*';
|
|
2060
|
+
let initialValue = null;
|
|
2061
|
+
for (const prop of arg.properties) {
|
|
2062
|
+
if (prop.type !== 'Property' ||
|
|
2063
|
+
prop.computed ||
|
|
2064
|
+
prop.key.type !== 'Identifier') {
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
2067
|
+
if (prop.key.name === 'syntax') {
|
|
2068
|
+
if (prop.value.type !== 'Literal')
|
|
2069
|
+
return;
|
|
2070
|
+
if (typeof prop.value.value !== 'string')
|
|
2071
|
+
return;
|
|
2072
|
+
syntax = prop.value.value;
|
|
2073
|
+
}
|
|
2074
|
+
else if (prop.key.name === 'initialValue') {
|
|
2075
|
+
initialValue = prop.value;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
if (!initialValue || initialValue.type !== 'Literal')
|
|
2079
|
+
return;
|
|
2080
|
+
const raw = initialValue.value;
|
|
2081
|
+
if (typeof raw !== 'string' && typeof raw !== 'number')
|
|
2082
|
+
return;
|
|
2083
|
+
const value = String(raw);
|
|
2084
|
+
const messageId = checkInitialValue(syntax, value);
|
|
2085
|
+
if (messageId) {
|
|
2086
|
+
context.report({
|
|
2087
|
+
node: initialValue,
|
|
2088
|
+
messageId,
|
|
2089
|
+
data: { value, syntax },
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
1950
2093
|
return {
|
|
1951
2094
|
ImportDeclaration(node) {
|
|
1952
2095
|
if (node.source.value === '@plumeria/core') {
|
|
@@ -1966,7 +2109,21 @@ exports.validateValues = {
|
|
|
1966
2109
|
}
|
|
1967
2110
|
},
|
|
1968
2111
|
CallExpression(node) {
|
|
2112
|
+
const calleeName = node.callee.type === 'MemberExpression'
|
|
2113
|
+
? node.callee.object.type === 'Identifier' &&
|
|
2114
|
+
plumeriaAliases[node.callee.object.name] === 'NAMESPACE' &&
|
|
2115
|
+
node.callee.property.type === 'Identifier'
|
|
2116
|
+
? node.callee.property.name
|
|
2117
|
+
: null
|
|
2118
|
+
: node.callee.type === 'Identifier'
|
|
2119
|
+
? plumeriaAliases[node.callee.name]
|
|
2120
|
+
: null;
|
|
2121
|
+
if (calleeName === 'varType') {
|
|
2122
|
+
checkVarType(node.arguments[0]);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
1969
2125
|
let isCssProperties = false;
|
|
2126
|
+
let isPositionTry = false;
|
|
1970
2127
|
if (node.callee.type === 'MemberExpression') {
|
|
1971
2128
|
if (node.callee.object.type === 'Identifier' &&
|
|
1972
2129
|
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
@@ -1975,8 +2132,10 @@ exports.validateValues = {
|
|
|
1975
2132
|
: null;
|
|
1976
2133
|
if (propertyName === 'create' ||
|
|
1977
2134
|
propertyName === 'keyframes' ||
|
|
1978
|
-
propertyName === 'viewTransition'
|
|
2135
|
+
propertyName === 'viewTransition' ||
|
|
2136
|
+
propertyName === 'positionTry') {
|
|
1979
2137
|
isCssProperties = true;
|
|
2138
|
+
isPositionTry = propertyName === 'positionTry';
|
|
1980
2139
|
}
|
|
1981
2140
|
}
|
|
1982
2141
|
}
|
|
@@ -1984,14 +2143,20 @@ exports.validateValues = {
|
|
|
1984
2143
|
const alias = plumeriaAliases[node.callee.name];
|
|
1985
2144
|
if (alias === 'create' ||
|
|
1986
2145
|
alias === 'keyframes' ||
|
|
1987
|
-
alias === 'viewTransition'
|
|
2146
|
+
alias === 'viewTransition' ||
|
|
2147
|
+
alias === 'positionTry') {
|
|
1988
2148
|
isCssProperties = true;
|
|
2149
|
+
isPositionTry = alias === 'positionTry';
|
|
1989
2150
|
}
|
|
1990
2151
|
}
|
|
1991
2152
|
if (!isCssProperties)
|
|
1992
2153
|
return;
|
|
1993
2154
|
node.arguments.forEach((arg) => {
|
|
1994
2155
|
if (arg.type === 'ObjectExpression') {
|
|
2156
|
+
if (isPositionTry) {
|
|
2157
|
+
checkStyleObject(arg);
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
1995
2160
|
arg.properties.forEach((prop) => {
|
|
1996
2161
|
if (prop.type !== 'Property')
|
|
1997
2162
|
return;
|
|
@@ -11,6 +11,14 @@ exports.propertyGroups = [
|
|
|
11
11
|
{
|
|
12
12
|
properties: [
|
|
13
13
|
'position',
|
|
14
|
+
'positionAnchor',
|
|
15
|
+
'positionArea',
|
|
16
|
+
'positionTry',
|
|
17
|
+
'positionTryOrder',
|
|
18
|
+
'positionTryFallbacks',
|
|
19
|
+
'positionVisibility',
|
|
20
|
+
'anchorName',
|
|
21
|
+
'anchorScope',
|
|
14
22
|
'inset',
|
|
15
23
|
'insetBlock',
|
|
16
24
|
'insetBlockStart',
|
|
@@ -53,6 +53,7 @@ const createSpellingRule = (reject) => ({
|
|
|
53
53
|
},
|
|
54
54
|
CallExpression(node) {
|
|
55
55
|
let isCssProperties = false;
|
|
56
|
+
let isPositionTry = false;
|
|
56
57
|
if (node.callee.type === 'MemberExpression') {
|
|
57
58
|
if (node.callee.object.type === 'Identifier' &&
|
|
58
59
|
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
@@ -61,8 +62,10 @@ const createSpellingRule = (reject) => ({
|
|
|
61
62
|
: null;
|
|
62
63
|
if (propertyName === 'create' ||
|
|
63
64
|
propertyName === 'keyframes' ||
|
|
64
|
-
propertyName === 'viewTransition'
|
|
65
|
+
propertyName === 'viewTransition' ||
|
|
66
|
+
propertyName === 'positionTry') {
|
|
65
67
|
isCssProperties = true;
|
|
68
|
+
isPositionTry = propertyName === 'positionTry';
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
}
|
|
@@ -70,13 +73,19 @@ const createSpellingRule = (reject) => ({
|
|
|
70
73
|
const aliasName = plumeriaAliases[node.callee.name];
|
|
71
74
|
if (aliasName === 'create' ||
|
|
72
75
|
aliasName === 'keyframes' ||
|
|
73
|
-
aliasName === 'viewTransition'
|
|
76
|
+
aliasName === 'viewTransition' ||
|
|
77
|
+
aliasName === 'positionTry') {
|
|
74
78
|
isCssProperties = true;
|
|
79
|
+
isPositionTry = aliasName === 'positionTry';
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
if (isCssProperties) {
|
|
78
83
|
node.arguments.forEach((arg) => {
|
|
79
84
|
if (arg.type === 'ObjectExpression') {
|
|
85
|
+
if (isPositionTry) {
|
|
86
|
+
checkStyleObject(arg);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
80
89
|
arg.properties.forEach((prop) => {
|
|
81
90
|
if (prop.type !== 'Property')
|
|
82
91
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumeria/eslint-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "20.0.0",
|
|
4
4
|
"description": "Plumeria ESLint plugin",
|
|
5
5
|
"author": "Refirst 11",
|
|
6
6
|
"license": "MIT",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"oxlint": "1.87.0",
|
|
61
|
-
"zss-engine": "
|
|
62
|
-
"@plumeria/compiler": "
|
|
61
|
+
"zss-engine": "3.0.1",
|
|
62
|
+
"@plumeria/compiler": "20.0.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "rimraf dist && pnpm cjs",
|