@plumeria/eslint-plugin 11.0.1 → 11.1.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
CHANGED
|
@@ -12,6 +12,7 @@ The `plugin:@plumeria/recommended` config enables the following:
|
|
|
12
12
|
- `@plumeria/no-destructure`: **error**
|
|
13
13
|
- `@plumeria/no-inline-object`: **error**
|
|
14
14
|
- `@plumeria/no-inner-call`: **error**
|
|
15
|
+
- `@plumeria/no-invalid-selector-nesting`: **error**
|
|
15
16
|
- `@plumeria/no-unknown-css-properties`: **error**
|
|
16
17
|
- `@plumeria/no-unused-keys`: **warn**
|
|
17
18
|
- `@plumeria/sort-properties`: **warn**
|
|
@@ -46,6 +47,14 @@ Disallow passing inline object to `styleName` and `css.use()`. Only compiled sty
|
|
|
46
47
|
|
|
47
48
|
Disallow calling APIs inside functions.
|
|
48
49
|
|
|
50
|
+
### no-invalid-selector-nesting
|
|
51
|
+
|
|
52
|
+
Disallow invalid selector nesting inside `css.create()`. (e.g. Pseudo -> Query, Query -> Query)
|
|
53
|
+
|
|
54
|
+
### no-mixed-styling-props
|
|
55
|
+
|
|
56
|
+
Disallow mixing `styleName` with `className` or `style`. `styleName` can handle both `className` and `style`.
|
|
57
|
+
|
|
49
58
|
### no-unknown-css-properties
|
|
50
59
|
|
|
51
60
|
Disallow unknown CSS properties in camelCase within `css.create`, `css.keyframes`, and `css.viewTransition`.
|
package/dist/index.js
CHANGED
|
@@ -6,17 +6,21 @@ const no_combinator_1 = require("./rules/no-combinator");
|
|
|
6
6
|
const no_destructure_1 = require("./rules/no-destructure");
|
|
7
7
|
const no_inline_object_1 = require("./rules/no-inline-object");
|
|
8
8
|
const no_inner_call_1 = require("./rules/no-inner-call");
|
|
9
|
+
const no_invalid_selector_nesting_1 = require("./rules/no-invalid-selector-nesting");
|
|
10
|
+
const no_mixed_styling_props_1 = require("./rules/no-mixed-styling-props");
|
|
11
|
+
const no_unknown_css_properties_1 = require("./rules/no-unknown-css-properties");
|
|
9
12
|
const no_unused_keys_1 = require("./rules/no-unused-keys");
|
|
10
13
|
const sort_properties_1 = require("./rules/sort-properties");
|
|
11
14
|
const format_properties_1 = require("./rules/format-properties");
|
|
12
15
|
const validate_values_1 = require("./rules/validate-values");
|
|
13
|
-
const no_unknown_css_properties_1 = require("./rules/no-unknown-css-properties");
|
|
14
16
|
const rules = {
|
|
15
17
|
'style-name-requires-import': style_name_requires_import_1.styleNameRequiresImport,
|
|
16
18
|
'no-combinator': no_combinator_1.noCombinator,
|
|
17
19
|
'no-destructure': no_destructure_1.noDestructure,
|
|
18
20
|
'no-inline-object': no_inline_object_1.noInlineObject,
|
|
19
21
|
'no-inner-call': no_inner_call_1.noInnerCall,
|
|
22
|
+
'no-invalid-selector-nesting': no_invalid_selector_nesting_1.noInvalidSelectorNesting,
|
|
23
|
+
'no-mixed-styling-props': no_mixed_styling_props_1.noMixedStylingProps,
|
|
20
24
|
'no-unknown-css-properties': no_unknown_css_properties_1.noUnknownCssProperties,
|
|
21
25
|
'no-unused-keys': no_unused_keys_1.noUnusedKeys,
|
|
22
26
|
'sort-properties': sort_properties_1.sortProperties,
|
|
@@ -32,6 +36,8 @@ const configs = {
|
|
|
32
36
|
'@plumeria/no-destructure': 'error',
|
|
33
37
|
'@plumeria/no-inline-object': 'error',
|
|
34
38
|
'@plumeria/no-inner-call': 'error',
|
|
39
|
+
'@plumeria/no-invalid-selector-nesting': 'error',
|
|
40
|
+
'@plumeria/no-mixed-styling-props': 'error',
|
|
35
41
|
'@plumeria/no-unknown-css-properties': 'error',
|
|
36
42
|
'@plumeria/no-unused-keys': 'warn',
|
|
37
43
|
'@plumeria/sort-properties': 'warn',
|
|
@@ -53,6 +59,8 @@ const flatConfigs = {
|
|
|
53
59
|
'@plumeria/no-destructure': 'error',
|
|
54
60
|
'@plumeria/no-inline-object': 'error',
|
|
55
61
|
'@plumeria/no-inner-call': 'error',
|
|
62
|
+
'@plumeria/no-invalid-selector-nesting': 'error',
|
|
63
|
+
'@plumeria/no-mixed-styling-props': 'error',
|
|
56
64
|
'@plumeria/no-unknown-css-properties': 'error',
|
|
57
65
|
'@plumeria/no-unused-keys': 'warn',
|
|
58
66
|
'@plumeria/sort-properties': 'warn',
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noInvalidSelectorNesting = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
exports.noInvalidSelectorNesting = {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'problem',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Disallow invalid selector nesting (e.g. Pseudo -> Query, Query -> Query) based on Plumeria rules.',
|
|
10
|
+
},
|
|
11
|
+
messages: {
|
|
12
|
+
noQueryInsidePseudo: 'Media/Container queries cannot be nested inside pseudo-selectors.',
|
|
13
|
+
noQueryInsideQuery: 'Media/Container queries cannot be nested inside other queries.',
|
|
14
|
+
noPseudoInsidePseudo: 'Pseudo-selectors cannot be nested inside other pseudo-selectors.',
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
const plumeriaAliases = {};
|
|
20
|
+
const parserServices = context.sourceCode.parserServices;
|
|
21
|
+
const checker = parserServices?.program?.getTypeChecker();
|
|
22
|
+
function getSelectorType(node) {
|
|
23
|
+
if (node.type === utils_1.TSESTree.AST_NODE_TYPES.Literal &&
|
|
24
|
+
typeof node.value === 'string') {
|
|
25
|
+
if (node.value.startsWith('@'))
|
|
26
|
+
return 'QUERY';
|
|
27
|
+
if (node.value.startsWith(':'))
|
|
28
|
+
return 'PSEUDO';
|
|
29
|
+
return 'PROPERTY';
|
|
30
|
+
}
|
|
31
|
+
if (node.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
32
|
+
!(node.parent?.type === utils_1.TSESTree.AST_NODE_TYPES.Property &&
|
|
33
|
+
node.parent.computed)) {
|
|
34
|
+
return 'PROPERTY';
|
|
35
|
+
}
|
|
36
|
+
if (checker && parserServices?.esTreeNodeToTSNodeMap) {
|
|
37
|
+
try {
|
|
38
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
39
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
40
|
+
if (type.isStringLiteral()) {
|
|
41
|
+
if (type.value.startsWith('@'))
|
|
42
|
+
return 'QUERY';
|
|
43
|
+
if (type.value.startsWith(':'))
|
|
44
|
+
return 'PSEUDO';
|
|
45
|
+
}
|
|
46
|
+
if (type.isUnion()) {
|
|
47
|
+
const types = type.types;
|
|
48
|
+
if (types.every((t) => t.isStringLiteral() && t.value.startsWith('@')))
|
|
49
|
+
return 'QUERY';
|
|
50
|
+
if (types.every((t) => t.isStringLiteral() && t.value.startsWith(':')))
|
|
51
|
+
return 'PSEUDO';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return 'UNKNOWN';
|
|
58
|
+
}
|
|
59
|
+
function checkNesting(node, parentType) {
|
|
60
|
+
for (const prop of node.properties) {
|
|
61
|
+
if (prop.type !== utils_1.TSESTree.AST_NODE_TYPES.Property)
|
|
62
|
+
continue;
|
|
63
|
+
const currentType = getSelectorType(prop.key);
|
|
64
|
+
if (parentType === 'PSEUDO' && currentType === 'QUERY') {
|
|
65
|
+
context.report({
|
|
66
|
+
node: prop.key,
|
|
67
|
+
messageId: 'noQueryInsidePseudo',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else if (parentType === 'QUERY' && currentType === 'QUERY') {
|
|
71
|
+
context.report({ node: prop.key, messageId: 'noQueryInsideQuery' });
|
|
72
|
+
}
|
|
73
|
+
else if (parentType === 'PSEUDO' && currentType === 'PSEUDO') {
|
|
74
|
+
context.report({
|
|
75
|
+
node: prop.key,
|
|
76
|
+
messageId: 'noPseudoInsidePseudo',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (prop.value.type === utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
80
|
+
checkNesting(prop.value, currentType === 'PROPERTY' ? parentType : currentType);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
ImportDeclaration(node) {
|
|
86
|
+
if (node.source.value === '@plumeria/core') {
|
|
87
|
+
node.specifiers.forEach((specifier) => {
|
|
88
|
+
if (specifier.type ===
|
|
89
|
+
utils_1.TSESTree.AST_NODE_TYPES.ImportNamespaceSpecifier ||
|
|
90
|
+
specifier.type === utils_1.TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier) {
|
|
91
|
+
plumeriaAliases[specifier.local.name] = 'NAMESPACE';
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const importedName = specifier.imported.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier
|
|
95
|
+
? specifier.imported.name
|
|
96
|
+
: String(specifier.imported.value);
|
|
97
|
+
plumeriaAliases[specifier.local.name] = importedName;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
CallExpression(node) {
|
|
103
|
+
let isCssCreate = false;
|
|
104
|
+
if (node.callee.type === utils_1.TSESTree.AST_NODE_TYPES.MemberExpression) {
|
|
105
|
+
if (node.callee.object.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
106
|
+
plumeriaAliases[node.callee.object.name] === 'NAMESPACE') {
|
|
107
|
+
const propertyName = node.callee.property.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier
|
|
108
|
+
? node.callee.property.name
|
|
109
|
+
: null;
|
|
110
|
+
if (propertyName === 'create')
|
|
111
|
+
isCssCreate = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (node.callee.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier) {
|
|
115
|
+
const alias = plumeriaAliases[node.callee.name];
|
|
116
|
+
if (alias === 'create')
|
|
117
|
+
isCssCreate = true;
|
|
118
|
+
}
|
|
119
|
+
if (isCssCreate &&
|
|
120
|
+
node.arguments[0]?.type === utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
121
|
+
const styleObj = node.arguments[0];
|
|
122
|
+
styleObj.properties.forEach((prop) => {
|
|
123
|
+
if (prop.type === utils_1.TSESTree.AST_NODE_TYPES.Property &&
|
|
124
|
+
prop.value.type === utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
|
125
|
+
checkNesting(prop.value, 'CLASS');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noMixedStylingProps = void 0;
|
|
4
|
+
exports.noMixedStylingProps = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'problem',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Disallow className and style props when styleName is present',
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
noMixedStylingProps: '"styleName" handles both "className" and "style". Avoid mixing them.',
|
|
12
|
+
},
|
|
13
|
+
schema: [],
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
return {
|
|
17
|
+
JSXOpeningElement(node) {
|
|
18
|
+
const attributes = node.attributes;
|
|
19
|
+
const hasStyleName = attributes.some((attr) => attr.type === 'JSXAttribute' &&
|
|
20
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
21
|
+
attr.name.name === 'styleName');
|
|
22
|
+
if (hasStyleName) {
|
|
23
|
+
for (const attr of attributes) {
|
|
24
|
+
if (attr.type === 'JSXAttribute' &&
|
|
25
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
26
|
+
(attr.name.name === 'className' || attr.name.name === 'style')) {
|
|
27
|
+
context.report({
|
|
28
|
+
node: attr,
|
|
29
|
+
messageId: 'noMixedStylingProps',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumeria/eslint-plugin",
|
|
3
|
-
"version": "11.0
|
|
3
|
+
"version": "11.1.0",
|
|
4
4
|
"description": "Plumeria ESLint plugin",
|
|
5
5
|
"author": "Refirst 11",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/eslint": "^9.6.1",
|
|
38
38
|
"@types/estree": "^1.0.8",
|
|
39
|
+
"@typescript-eslint/parser": "^8.59.2",
|
|
39
40
|
"eslint": "^9.39.0"
|
|
40
41
|
},
|
|
41
42
|
"publishConfig": {
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"provenance": true
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
47
|
+
"@typescript-eslint/utils": "^8.59.2",
|
|
46
48
|
"known-css-properties": "^0.37.0"
|
|
47
49
|
},
|
|
48
50
|
"scripts": {
|