@plumeria/eslint-plugin 19.2.15 → 19.3.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 +8 -4
- package/dist/index.js +3 -3
- package/dist/rules/custom-props-require-import.d.ts +2 -0
- package/dist/rules/custom-props-require-import.js +185 -0
- package/dist/rules/sort-properties.js +1 -1
- package/oxlint.json +1 -1
- package/package.json +3 -2
- package/dist/rules/props-require-import.d.ts +0 -2
- package/dist/rules/props-require-import.js +0 -52
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Below are the available rules and the recommended configuration.
|
|
|
7
7
|
|
|
8
8
|
The `plugin:@plumeria/recommended` config enables the following:
|
|
9
9
|
|
|
10
|
-
- `@plumeria/props-require-import`: **error**
|
|
10
|
+
- `@plumeria/custom-props-require-import`: **error**
|
|
11
11
|
- `@plumeria/no-combinator`: **error**
|
|
12
12
|
- `@plumeria/no-destructure`: **error**
|
|
13
13
|
- `@plumeria/no-inline-object`: **error**
|
|
@@ -31,7 +31,7 @@ export default [plumeria.configs.recommended];
|
|
|
31
31
|
|
|
32
32
|
## Configuring the styling prop
|
|
33
33
|
|
|
34
|
-
Both `props-require-import` and `no-mixed-styling-props` need to know which JSX
|
|
34
|
+
Both `custom-props-require-import` and `no-mixed-styling-props` need to know which JSX
|
|
35
35
|
prop carries styles. It is `classStyle` unless you changed it, so most projects
|
|
36
36
|
configure nothing.
|
|
37
37
|
|
|
@@ -70,9 +70,13 @@ the lint rules report against a prop the compiler never transforms.
|
|
|
70
70
|
The following rules are enabled by `plumeria.configs.recommended` with the
|
|
71
71
|
severity shown in [Recommended configuration](#recommended-configuration).
|
|
72
72
|
|
|
73
|
-
### props-require-import
|
|
73
|
+
### custom-props-require-import
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
Require the `@plumeria/core` import in a file that passes a `css.create` style to a
|
|
76
|
+
component through a prop other than the styling prop. The compiler cannot tell that
|
|
77
|
+
such a prop carries a style, so it compiles the file only when the file imports
|
|
78
|
+
`@plumeria/core`. A file that writes the styling prop itself is compiled without
|
|
79
|
+
the import.
|
|
76
80
|
|
|
77
81
|
Accepts `{ styleProp }`; see [Configuring the styling prop](#configuring-the-styling-prop).
|
|
78
82
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const
|
|
2
|
+
const custom_props_require_import_1 = require("./rules/custom-props-require-import");
|
|
3
3
|
const expand_border_shorthands_1 = require("./rules/expand-border-shorthands");
|
|
4
4
|
const no_combinator_1 = require("./rules/no-combinator");
|
|
5
5
|
const no_destructure_1 = require("./rules/no-destructure");
|
|
@@ -18,7 +18,7 @@ const validate_values_1 = require("./rules/validate-values");
|
|
|
18
18
|
const validate_pseudos_1 = require("./rules/validate-pseudos");
|
|
19
19
|
const validate_at_rules_1 = require("./rules/validate-at-rules");
|
|
20
20
|
const rules = {
|
|
21
|
-
'props-require-import':
|
|
21
|
+
'custom-props-require-import': custom_props_require_import_1.customPropsRequireImport,
|
|
22
22
|
'expand-border-shorthands': expand_border_shorthands_1.expandBorderShorthands,
|
|
23
23
|
'no-combinator': no_combinator_1.noCombinator,
|
|
24
24
|
'no-destructure': no_destructure_1.noDestructure,
|
|
@@ -45,7 +45,7 @@ const configs = {
|
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
rules: {
|
|
48
|
-
'@plumeria/props-require-import': 'error',
|
|
48
|
+
'@plumeria/custom-props-require-import': 'error',
|
|
49
49
|
'@plumeria/no-combinator': 'error',
|
|
50
50
|
'@plumeria/no-destructure': 'error',
|
|
51
51
|
'@plumeria/no-inline-object': 'error',
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.customPropsRequireImport = void 0;
|
|
4
|
+
const utils_1 = require("@plumeria/utils");
|
|
5
|
+
const style_prop_1 = require("../util/style-prop");
|
|
6
|
+
const CORE = '@plumeria/core';
|
|
7
|
+
const styleRoots = (node, roots) => {
|
|
8
|
+
const expression = node;
|
|
9
|
+
if (!expression)
|
|
10
|
+
return roots;
|
|
11
|
+
switch (expression.type) {
|
|
12
|
+
case 'Identifier':
|
|
13
|
+
roots.push({ name: expression.name, member: null });
|
|
14
|
+
break;
|
|
15
|
+
case 'MemberExpression': {
|
|
16
|
+
let object = expression;
|
|
17
|
+
let member = null;
|
|
18
|
+
while (object.type === 'MemberExpression') {
|
|
19
|
+
member =
|
|
20
|
+
!object.computed && object.property.type === 'Identifier'
|
|
21
|
+
? object.property.name
|
|
22
|
+
: null;
|
|
23
|
+
object = object.object;
|
|
24
|
+
}
|
|
25
|
+
if (object.type === 'Identifier')
|
|
26
|
+
roots.push({ name: object.name, member });
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case 'ArrayExpression':
|
|
30
|
+
expression.elements.forEach((element) => styleRoots(element, roots));
|
|
31
|
+
break;
|
|
32
|
+
case 'ConditionalExpression':
|
|
33
|
+
styleRoots(expression.consequent, roots);
|
|
34
|
+
styleRoots(expression.alternate, roots);
|
|
35
|
+
break;
|
|
36
|
+
case 'LogicalExpression':
|
|
37
|
+
styleRoots(expression.left, roots);
|
|
38
|
+
styleRoots(expression.right, roots);
|
|
39
|
+
break;
|
|
40
|
+
case 'ChainExpression':
|
|
41
|
+
case 'TSAsExpression':
|
|
42
|
+
case 'TSNonNullExpression':
|
|
43
|
+
case 'TSSatisfiesExpression':
|
|
44
|
+
styleRoots(expression.expression, roots);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
return roots;
|
|
48
|
+
};
|
|
49
|
+
const SCAN_REUSE_MS = 1000;
|
|
50
|
+
const MAX_ALIAS_DEPTH = 4;
|
|
51
|
+
let recentScan;
|
|
52
|
+
const scanTables = (cwd, styleProp) => {
|
|
53
|
+
const key = `${cwd}\0${styleProp}`;
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
if (!recentScan ||
|
|
56
|
+
recentScan.key !== key ||
|
|
57
|
+
now - recentScan.at > SCAN_REUSE_MS) {
|
|
58
|
+
recentScan = { key, at: now, tables: (0, utils_1.scanAll)(cwd, styleProp) };
|
|
59
|
+
}
|
|
60
|
+
return recentScan.tables;
|
|
61
|
+
};
|
|
62
|
+
exports.customPropsRequireImport = {
|
|
63
|
+
meta: {
|
|
64
|
+
type: 'problem',
|
|
65
|
+
docs: {
|
|
66
|
+
description: 'Require the @plumeria/core import in files that pass a style through a prop other than the styling prop',
|
|
67
|
+
},
|
|
68
|
+
fixable: 'code',
|
|
69
|
+
messages: {
|
|
70
|
+
requiresImport: '{{prop}} passes a style from "{{source}}", so this file must import "@plumeria/core".',
|
|
71
|
+
},
|
|
72
|
+
schema: style_prop_1.stylePropSchema,
|
|
73
|
+
},
|
|
74
|
+
create(context) {
|
|
75
|
+
const styleProp = (0, style_prop_1.resolveStyleProp)(context);
|
|
76
|
+
const filename = context.filename;
|
|
77
|
+
const imports = new Map();
|
|
78
|
+
const candidates = [];
|
|
79
|
+
let hasPlumeriaImport = false;
|
|
80
|
+
const findVariable = (scope, name) => {
|
|
81
|
+
for (let current = scope; current; current = current.upper) {
|
|
82
|
+
const variable = current.set.get(name);
|
|
83
|
+
if (variable)
|
|
84
|
+
return variable;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
};
|
|
88
|
+
const importedRoots = (roots, scope, depth) => roots.flatMap((root) => {
|
|
89
|
+
const definition = findVariable(scope, root.name)?.defs[0];
|
|
90
|
+
if (!definition)
|
|
91
|
+
return [];
|
|
92
|
+
if (definition.type === 'ImportBinding')
|
|
93
|
+
return [root];
|
|
94
|
+
const declarator = definition.node;
|
|
95
|
+
if (definition.type === 'Variable' &&
|
|
96
|
+
definition.parent?.kind === 'const' &&
|
|
97
|
+
declarator.init &&
|
|
98
|
+
depth < MAX_ALIAS_DEPTH) {
|
|
99
|
+
return importedRoots(styleRoots(declarator.init, []), context.sourceCode.getScope(definition.node), depth + 1);
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
102
|
+
});
|
|
103
|
+
const isStyle = (root) => {
|
|
104
|
+
const binding = imports.get(root.name);
|
|
105
|
+
const exportName = binding.importedName ?? root.member;
|
|
106
|
+
if (!exportName)
|
|
107
|
+
return null;
|
|
108
|
+
const actualPath = (0, utils_1.resolveImportPath)(binding.source, filename);
|
|
109
|
+
if (!actualPath)
|
|
110
|
+
return null;
|
|
111
|
+
const tables = scanTables(context.cwd, styleProp);
|
|
112
|
+
const resolved = (0, utils_1.resolveExport)(actualPath, exportName);
|
|
113
|
+
if (!resolved)
|
|
114
|
+
return null;
|
|
115
|
+
const key = `${resolved.filePath}-${resolved.localName}`;
|
|
116
|
+
return tables.createHashTable[key] !== undefined ? binding.source : null;
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
ImportDeclaration(node) {
|
|
120
|
+
const source = node.source.value;
|
|
121
|
+
if (source.startsWith(CORE)) {
|
|
122
|
+
hasPlumeriaImport = true;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const specifier of node.specifiers) {
|
|
126
|
+
if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
127
|
+
imports.set(specifier.local.name, { source, importedName: null });
|
|
128
|
+
}
|
|
129
|
+
else if (specifier.type === 'ImportDefaultSpecifier') {
|
|
130
|
+
imports.set(specifier.local.name, {
|
|
131
|
+
source,
|
|
132
|
+
importedName: 'default',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const imported = specifier.imported;
|
|
137
|
+
imports.set(specifier.local.name, {
|
|
138
|
+
source,
|
|
139
|
+
importedName: imported.name ?? String(imported.value),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
JSXAttribute(node) {
|
|
145
|
+
if (node.name.type !== 'JSXIdentifier')
|
|
146
|
+
return;
|
|
147
|
+
const prop = node.name.name;
|
|
148
|
+
if (prop === styleProp)
|
|
149
|
+
return;
|
|
150
|
+
if (node.value?.type !== 'JSXExpressionContainer')
|
|
151
|
+
return;
|
|
152
|
+
const roots = styleRoots(node.value.expression, []);
|
|
153
|
+
if (roots.length > 0)
|
|
154
|
+
candidates.push({
|
|
155
|
+
node,
|
|
156
|
+
prop,
|
|
157
|
+
roots,
|
|
158
|
+
scope: context.sourceCode.getScope(node),
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
'Program:exit'() {
|
|
162
|
+
if (hasPlumeriaImport || candidates.length === 0)
|
|
163
|
+
return;
|
|
164
|
+
let fixed = false;
|
|
165
|
+
for (const { node, prop, roots, scope } of candidates) {
|
|
166
|
+
const source = importedRoots(roots, scope, 0)
|
|
167
|
+
.map(isStyle)
|
|
168
|
+
.find((found) => found !== null);
|
|
169
|
+
if (!source)
|
|
170
|
+
continue;
|
|
171
|
+
const first = !fixed;
|
|
172
|
+
fixed = true;
|
|
173
|
+
context.report({
|
|
174
|
+
node,
|
|
175
|
+
messageId: 'requiresImport',
|
|
176
|
+
data: { prop, source },
|
|
177
|
+
fix: first
|
|
178
|
+
? (fixer) => fixer.insertTextBefore(context.sourceCode.ast, 'import "@plumeria/core";\n')
|
|
179
|
+
: null,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
};
|
|
@@ -144,7 +144,7 @@ exports.sortProperties = {
|
|
|
144
144
|
node: prop.key,
|
|
145
145
|
messageId: 'sortProperties',
|
|
146
146
|
data: {
|
|
147
|
-
position:
|
|
147
|
+
position: propertyToIndexInSorted.get(prop) + 1,
|
|
148
148
|
property: getPropertyName(prop),
|
|
149
149
|
},
|
|
150
150
|
fix(fixer) {
|
package/oxlint.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"ignorePatterns": ["**/*.svelte", "**/*.vue"],
|
|
10
10
|
|
|
11
11
|
"rules": {
|
|
12
|
-
"@plumeria/props-require-import": "error",
|
|
12
|
+
"@plumeria/custom-props-require-import": "error",
|
|
13
13
|
"@plumeria/no-combinator": "error",
|
|
14
14
|
"@plumeria/no-destructure": "error",
|
|
15
15
|
"@plumeria/no-inline-object": "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumeria/eslint-plugin",
|
|
3
|
-
"version": "19.
|
|
3
|
+
"version": "19.3.0",
|
|
4
4
|
"description": "Plumeria ESLint plugin",
|
|
5
5
|
"author": "Refirst 11",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@typescript-eslint/utils": "^8.60.0",
|
|
56
56
|
"known-css-properties": "^0.37.0",
|
|
57
|
-
"zss-engine": "2.8.0"
|
|
57
|
+
"zss-engine": "2.8.0",
|
|
58
|
+
"@plumeria/utils": "19.3.0"
|
|
58
59
|
},
|
|
59
60
|
"scripts": {
|
|
60
61
|
"build": "rimraf dist && pnpm cjs",
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.propsRequireImport = void 0;
|
|
4
|
-
const style_prop_1 = require("../util/style-prop");
|
|
5
|
-
exports.propsRequireImport = {
|
|
6
|
-
meta: {
|
|
7
|
-
type: 'problem',
|
|
8
|
-
docs: {
|
|
9
|
-
description: 'Disallow the styling prop in files without a @plumeria/core import',
|
|
10
|
-
},
|
|
11
|
-
fixable: 'code',
|
|
12
|
-
messages: {
|
|
13
|
-
requiresImport: '{{prop}} requires importing "@plumeria/core".',
|
|
14
|
-
},
|
|
15
|
-
schema: style_prop_1.stylePropSchema,
|
|
16
|
-
},
|
|
17
|
-
create(context) {
|
|
18
|
-
const styleProp = (0, style_prop_1.resolveStyleProp)(context);
|
|
19
|
-
let hasPlumeriaImport = false;
|
|
20
|
-
const stylePropNodes = [];
|
|
21
|
-
return {
|
|
22
|
-
ImportDeclaration(node) {
|
|
23
|
-
const source = node.source.value;
|
|
24
|
-
if (typeof source === 'string' && source.startsWith('@plumeria/core')) {
|
|
25
|
-
hasPlumeriaImport = true;
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
JSXAttribute(node) {
|
|
29
|
-
if (node.name && node.name.name === styleProp) {
|
|
30
|
-
stylePropNodes.push(node);
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
'Program:exit'() {
|
|
34
|
-
if (!hasPlumeriaImport) {
|
|
35
|
-
stylePropNodes.forEach((node, index) => {
|
|
36
|
-
context.report({
|
|
37
|
-
node,
|
|
38
|
-
messageId: 'requiresImport',
|
|
39
|
-
data: { prop: styleProp },
|
|
40
|
-
fix(fixer) {
|
|
41
|
-
if (index === 0) {
|
|
42
|
-
return fixer.insertTextBefore(context.sourceCode.ast, 'import "@plumeria/core";\n');
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
};
|