@plumeria/eslint-plugin 18.5.3 → 19.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 +5 -0
- package/dist/index.js +3 -0
- package/dist/rules/validate-at-rules.d.ts +3 -0
- package/dist/rules/validate-at-rules.js +111 -0
- package/oxlint.json +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ The `plugin:@plumeria/recommended` config enables the following:
|
|
|
21
21
|
- `@plumeria/format-properties`: **warn**
|
|
22
22
|
- `@plumeria/validate-values`: **warn**
|
|
23
23
|
- `@plumeria/validate-pseudos`: **error**
|
|
24
|
+
- `@plumeria/validate-at-rules`: **error**
|
|
24
25
|
|
|
25
26
|
```js
|
|
26
27
|
import plumeria from '@plumeria/eslint-plugin';
|
|
@@ -148,6 +149,10 @@ Validates CSS property values for correctness. Only standard CSS properties are
|
|
|
148
149
|
|
|
149
150
|
Validates CSS pseudo-classes and pseudo-elements inside `css.create()`. It checks for typos and structural correctness and supports validation of computed keys when TypeScript is available.
|
|
150
151
|
|
|
152
|
+
### validate-at-rules
|
|
153
|
+
|
|
154
|
+
Validates at-rules inside `css.create()`. It accepts `@media`, `@container`, `@supports`, `@layer`, and `@scope`, and supports validation of computed keys when TypeScript is available.
|
|
155
|
+
|
|
151
156
|
## Optional rules
|
|
152
157
|
|
|
153
158
|
These rules are not enabled by `plumeria.configs.recommended`. Enable only the
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const sort_properties_1 = require("./rules/sort-properties");
|
|
|
16
16
|
const format_properties_1 = require("./rules/format-properties");
|
|
17
17
|
const validate_values_1 = require("./rules/validate-values");
|
|
18
18
|
const validate_pseudos_1 = require("./rules/validate-pseudos");
|
|
19
|
+
const validate_at_rules_1 = require("./rules/validate-at-rules");
|
|
19
20
|
const rules = {
|
|
20
21
|
'props-require-import': props_require_import_1.propsRequireImport,
|
|
21
22
|
'expand-border-shorthands': expand_border_shorthands_1.expandBorderShorthands,
|
|
@@ -34,6 +35,7 @@ const rules = {
|
|
|
34
35
|
'format-properties': format_properties_1.formatProperties,
|
|
35
36
|
'validate-values': validate_values_1.validateValues,
|
|
36
37
|
'validate-pseudos': validate_pseudos_1.validatePseudos,
|
|
38
|
+
'validate-at-rules': validate_at_rules_1.validateAtRules,
|
|
37
39
|
};
|
|
38
40
|
const configs = {
|
|
39
41
|
recommended: {
|
|
@@ -57,6 +59,7 @@ const configs = {
|
|
|
57
59
|
'@plumeria/format-properties': 'warn',
|
|
58
60
|
'@plumeria/validate-values': 'warn',
|
|
59
61
|
'@plumeria/validate-pseudos': 'error',
|
|
62
|
+
'@plumeria/validate-at-rules': 'error',
|
|
60
63
|
},
|
|
61
64
|
},
|
|
62
65
|
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateAtRules = void 0;
|
|
4
|
+
exports.isValidAtRule = isValidAtRule;
|
|
5
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
6
|
+
const styleObject_1 = require("../util/styleObject");
|
|
7
|
+
const VALID_AT_RULES = [
|
|
8
|
+
'@media ',
|
|
9
|
+
'@container ',
|
|
10
|
+
'@supports ',
|
|
11
|
+
'@layer ',
|
|
12
|
+
'@scope ',
|
|
13
|
+
];
|
|
14
|
+
function isValidAtRule(rule) {
|
|
15
|
+
return VALID_AT_RULES.some((prefix) => rule.startsWith(prefix) && rule.length > prefix.length);
|
|
16
|
+
}
|
|
17
|
+
exports.validateAtRules = {
|
|
18
|
+
meta: {
|
|
19
|
+
type: 'problem',
|
|
20
|
+
docs: {
|
|
21
|
+
description: 'Validate at-rules inside css.create.',
|
|
22
|
+
},
|
|
23
|
+
messages: {
|
|
24
|
+
invalidAtRule: 'Invalid at-rule: "{{ rule }}".',
|
|
25
|
+
},
|
|
26
|
+
schema: [],
|
|
27
|
+
},
|
|
28
|
+
create(context) {
|
|
29
|
+
const plumeriaAliases = {};
|
|
30
|
+
const parserServices = context.sourceCode.parserServices;
|
|
31
|
+
const checker = parserServices?.program?.getTypeChecker();
|
|
32
|
+
function getKeyString(node) {
|
|
33
|
+
if (node.type === utils_1.TSESTree.AST_NODE_TYPES.Literal &&
|
|
34
|
+
typeof node.value === 'string') {
|
|
35
|
+
return node.value;
|
|
36
|
+
}
|
|
37
|
+
if (checker && parserServices?.esTreeNodeToTSNodeMap) {
|
|
38
|
+
try {
|
|
39
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
40
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
41
|
+
if (type.isStringLiteral())
|
|
42
|
+
return type.value;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function checkProperties(node) {
|
|
50
|
+
for (const prop of node.properties) {
|
|
51
|
+
if (prop.type !== utils_1.TSESTree.AST_NODE_TYPES.Property)
|
|
52
|
+
continue;
|
|
53
|
+
const key = getKeyString(prop.key);
|
|
54
|
+
if (key?.startsWith('@') && !isValidAtRule(key)) {
|
|
55
|
+
context.report({
|
|
56
|
+
node: prop.key,
|
|
57
|
+
messageId: 'invalidAtRule',
|
|
58
|
+
data: { rule: key },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (prop.value.type === utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
62
|
+
checkProperties(prop.value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
ImportDeclaration(node) {
|
|
68
|
+
if (node.source.value !== '@plumeria/core')
|
|
69
|
+
return;
|
|
70
|
+
node.specifiers.forEach((specifier) => {
|
|
71
|
+
if (specifier.type ===
|
|
72
|
+
utils_1.TSESTree.AST_NODE_TYPES.ImportNamespaceSpecifier ||
|
|
73
|
+
specifier.type === utils_1.TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
74
|
+
plumeriaAliases[specifier.local.name] = 'NAMESPACE';
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const importedName = specifier.imported.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier
|
|
78
|
+
? specifier.imported.name
|
|
79
|
+
: String(specifier.imported.value);
|
|
80
|
+
plumeriaAliases[specifier.local.name] = importedName;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
CallExpression(node) {
|
|
85
|
+
let isCssCreate = false;
|
|
86
|
+
if (node.callee.type === utils_1.TSESTree.AST_NODE_TYPES.MemberExpression &&
|
|
87
|
+
node.callee.object.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
88
|
+
plumeriaAliases[node.callee.object.name] === 'NAMESPACE' &&
|
|
89
|
+
node.callee.property.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
90
|
+
node.callee.property.name === 'create') {
|
|
91
|
+
isCssCreate = true;
|
|
92
|
+
}
|
|
93
|
+
else if (node.callee.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
94
|
+
plumeriaAliases[node.callee.name] === 'create') {
|
|
95
|
+
isCssCreate = true;
|
|
96
|
+
}
|
|
97
|
+
if (!isCssCreate ||
|
|
98
|
+
node.arguments[0]?.type !== utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const prop of node.arguments[0].properties) {
|
|
102
|
+
if (prop.type !== utils_1.TSESTree.AST_NODE_TYPES.Property)
|
|
103
|
+
continue;
|
|
104
|
+
const style = (0, styleObject_1.styleObjectFromValue)(prop.value);
|
|
105
|
+
if (style)
|
|
106
|
+
checkProperties(style);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
package/oxlint.json
CHANGED