@plumeria/eslint-plugin 21.1.1 → 21.1.3
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/LICENSE +1 -1
- package/dist/rules/no-style-prop-relay.js +125 -16
- package/dist/util/scan.d.ts +4 -1
- package/dist/util/scan.js +6 -1
- package/dist/util/selector.js +30 -0
- package/package.json +4 -4
package/LICENSE
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.noStylePropRelay = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const compiler_1 = require("@plumeria/compiler");
|
|
4
39
|
const style_prop_1 = require("../util/style-prop");
|
|
5
40
|
const scan_1 = require("../util/scan");
|
|
6
41
|
const isFunction = (node) => node?.type === 'FunctionDeclaration' ||
|
|
@@ -20,6 +55,14 @@ const componentFunctionOf = (node) => {
|
|
|
20
55
|
}
|
|
21
56
|
return undefined;
|
|
22
57
|
};
|
|
58
|
+
const realPathOf = (filename) => {
|
|
59
|
+
try {
|
|
60
|
+
return fs.realpathSync.native(filename);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return filename;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
23
66
|
const unwrapDefault = (pattern) => pattern?.type === 'AssignmentPattern' ? pattern.left : pattern;
|
|
24
67
|
const keyName = (key) => key.type === 'Identifier'
|
|
25
68
|
? key.name
|
|
@@ -35,6 +78,7 @@ exports.noStylePropRelay = {
|
|
|
35
78
|
schema: style_prop_1.stylePropSchema,
|
|
36
79
|
messages: {
|
|
37
80
|
relay: "'{{prop}}' is never applied here. Apply it to {{styleProp}} or css.use() on an element this component renders.",
|
|
81
|
+
receiver: "'{{prop}}' is a style received through a prop and cannot be passed on to a component that declares {{styleProp}}. Apply it on an element this component renders.",
|
|
38
82
|
},
|
|
39
83
|
},
|
|
40
84
|
create(context) {
|
|
@@ -120,13 +164,14 @@ exports.noStylePropRelay = {
|
|
|
120
164
|
}
|
|
121
165
|
return into;
|
|
122
166
|
}
|
|
123
|
-
function applied(fn) {
|
|
167
|
+
function applied(fn, receives) {
|
|
124
168
|
const found = [];
|
|
125
|
-
const
|
|
169
|
+
const passed = [];
|
|
170
|
+
const visit = (node, owner) => {
|
|
126
171
|
if (!node || typeof node !== 'object')
|
|
127
172
|
return;
|
|
128
173
|
if (Array.isArray(node)) {
|
|
129
|
-
node.forEach(visit);
|
|
174
|
+
node.forEach((item) => visit(item, owner));
|
|
130
175
|
return;
|
|
131
176
|
}
|
|
132
177
|
const current = node;
|
|
@@ -135,26 +180,74 @@ exports.noStylePropRelay = {
|
|
|
135
180
|
if (current.type === 'JSXAttribute' &&
|
|
136
181
|
current.name?.name === styleProp &&
|
|
137
182
|
current.value?.type === 'JSXExpressionContainer') {
|
|
138
|
-
|
|
183
|
+
const expression = current.value.expression;
|
|
184
|
+
if (owner !== undefined && receives(owner)) {
|
|
185
|
+
passed.push({ attribute: current, roots: roots(expression, []) });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
roots(expression, found);
|
|
189
|
+
}
|
|
139
190
|
}
|
|
140
191
|
if (isUse(current) && current.type === 'CallExpression') {
|
|
141
192
|
current.arguments.forEach((argument) => roots(argument, found));
|
|
142
193
|
}
|
|
194
|
+
const next = current.type === 'JSXOpeningElement'
|
|
195
|
+
? current.name?.type === 'JSXIdentifier'
|
|
196
|
+
? current.name.name
|
|
197
|
+
: undefined
|
|
198
|
+
: owner;
|
|
143
199
|
for (const key of Object.keys(current)) {
|
|
144
200
|
if (key === 'parent')
|
|
145
201
|
continue;
|
|
146
|
-
visit(current[key]);
|
|
202
|
+
visit(current[key], next);
|
|
147
203
|
}
|
|
148
204
|
};
|
|
149
205
|
visit(fn.body);
|
|
150
|
-
return found;
|
|
206
|
+
return { found, passed };
|
|
151
207
|
}
|
|
152
208
|
return {
|
|
153
209
|
Program(program) {
|
|
154
210
|
const tables = (0, scan_1.scanTables)(context.cwd, styleProp);
|
|
155
211
|
const table = tables.componentPropsTable ?? {};
|
|
212
|
+
const filename = realPathOf(context.filename);
|
|
213
|
+
const imports = new Map();
|
|
214
|
+
for (const node of program.body ?? []) {
|
|
215
|
+
if (node.type !== 'ImportDeclaration')
|
|
216
|
+
continue;
|
|
217
|
+
for (const specifier of node.specifiers) {
|
|
218
|
+
const name = specifier.type === 'ImportDefaultSpecifier'
|
|
219
|
+
? 'default'
|
|
220
|
+
: specifier.type === 'ImportSpecifier'
|
|
221
|
+
? keyName(specifier.imported)
|
|
222
|
+
: null;
|
|
223
|
+
if (name === null)
|
|
224
|
+
continue;
|
|
225
|
+
imports.set(specifier.local.name, {
|
|
226
|
+
source: node.source.value,
|
|
227
|
+
name,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const receivers = tables.styleReceiverTable ?? {};
|
|
232
|
+
const known = new Map();
|
|
233
|
+
const receives = (name) => {
|
|
234
|
+
if (known.has(name))
|
|
235
|
+
return known.get(name);
|
|
236
|
+
const binding = imports.get(name);
|
|
237
|
+
let key = `${filename}-${name}`;
|
|
238
|
+
if (binding) {
|
|
239
|
+
const actualPath = (0, compiler_1.resolveImportPath)(binding.source, filename);
|
|
240
|
+
const site = actualPath
|
|
241
|
+
? (0, compiler_1.resolveExport)(actualPath, binding.name)
|
|
242
|
+
: null;
|
|
243
|
+
key = site ? `${site.filePath}-${site.localName}` : null;
|
|
244
|
+
}
|
|
245
|
+
const result = key !== null && (receivers[key]?.includes(styleProp) ?? false);
|
|
246
|
+
known.set(name, result);
|
|
247
|
+
return result;
|
|
248
|
+
};
|
|
156
249
|
for (const { name, fn } of components(program)) {
|
|
157
|
-
const props = table[`${
|
|
250
|
+
const props = table[`${filename}-${name}`];
|
|
158
251
|
if (!props)
|
|
159
252
|
continue;
|
|
160
253
|
const param = unwrapDefault(fn.params[0]);
|
|
@@ -174,18 +267,34 @@ exports.noStylePropRelay = {
|
|
|
174
267
|
locals.set(local.name, prop);
|
|
175
268
|
}
|
|
176
269
|
}
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
done.add(locals.get(root.name) ?? root.name);
|
|
181
|
-
}
|
|
182
|
-
else if (root.type === 'MemberExpression' &&
|
|
270
|
+
const propOf = (root) => root.type === 'Identifier'
|
|
271
|
+
? (locals.get(root.name) ?? root.name)
|
|
272
|
+
: root.type === 'MemberExpression' &&
|
|
183
273
|
root.object.type === 'Identifier' &&
|
|
184
274
|
root.object.name === paramName &&
|
|
185
275
|
!root.computed &&
|
|
186
|
-
root.property.type === 'Identifier'
|
|
187
|
-
|
|
188
|
-
|
|
276
|
+
root.property.type === 'Identifier'
|
|
277
|
+
? root.property.name
|
|
278
|
+
: null;
|
|
279
|
+
const done = new Set();
|
|
280
|
+
const { found, passed } = applied(fn, receives);
|
|
281
|
+
for (const root of found) {
|
|
282
|
+
const prop = propOf(root);
|
|
283
|
+
if (prop !== null)
|
|
284
|
+
done.add(prop);
|
|
285
|
+
}
|
|
286
|
+
for (const { attribute, roots: passedRoots } of passed) {
|
|
287
|
+
const prop = passedRoots
|
|
288
|
+
.map(propOf)
|
|
289
|
+
.find((name) => name !== null && name in props);
|
|
290
|
+
if (!prop)
|
|
291
|
+
continue;
|
|
292
|
+
done.add(prop);
|
|
293
|
+
context.report({
|
|
294
|
+
node: attribute,
|
|
295
|
+
messageId: 'receiver',
|
|
296
|
+
data: { prop, styleProp },
|
|
297
|
+
});
|
|
189
298
|
}
|
|
190
299
|
for (const prop of Object.keys(props)) {
|
|
191
300
|
if (done.has(prop))
|
package/dist/util/scan.d.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { styleReceivers } from '@plumeria/compiler';
|
|
2
|
+
export declare const scanTables: (cwd: string, styleProp: string) => import("@plumeria/compiler").ScanSummary & {
|
|
3
|
+
styleReceiverTable: ReturnType<typeof styleReceivers>;
|
|
4
|
+
};
|
package/dist/util/scan.js
CHANGED
|
@@ -10,7 +10,12 @@ const scanTables = (cwd, styleProp) => {
|
|
|
10
10
|
if (!recentScan ||
|
|
11
11
|
recentScan.key !== key ||
|
|
12
12
|
now - recentScan.at > SCAN_REUSE_MS) {
|
|
13
|
-
|
|
13
|
+
const summary = (0, compiler_1.scanAll)(cwd, styleProp);
|
|
14
|
+
recentScan = {
|
|
15
|
+
key,
|
|
16
|
+
at: now,
|
|
17
|
+
tables: { ...summary, styleReceiverTable: (0, compiler_1.styleReceivers)() },
|
|
18
|
+
};
|
|
14
19
|
}
|
|
15
20
|
return recentScan.tables;
|
|
16
21
|
};
|
package/dist/util/selector.js
CHANGED
|
@@ -20,6 +20,13 @@ const EXCLUSIVE_PAIRS = [
|
|
|
20
20
|
[':valid', ':invalid'],
|
|
21
21
|
[':in-range', ':out-of-range'],
|
|
22
22
|
[':required', ':optional'],
|
|
23
|
+
[':user-valid', ':user-invalid'],
|
|
24
|
+
[':playing', ':paused'],
|
|
25
|
+
[':dir(ltr)', ':dir(rtl)'],
|
|
26
|
+
[':nth-child(odd)', ':nth-child(even)'],
|
|
27
|
+
[':nth-last-child(odd)', ':nth-last-child(even)'],
|
|
28
|
+
[':nth-of-type(odd)', ':nth-of-type(even)'],
|
|
29
|
+
[':nth-last-of-type(odd)', ':nth-last-of-type(even)'],
|
|
23
30
|
].map((pair) => pair.slice().sort().join('\0'));
|
|
24
31
|
const isNameChar = (char) => /[\w-]/.test(char);
|
|
25
32
|
const skipName = (selector, from) => {
|
|
@@ -153,6 +160,24 @@ const negationOf = (state) => {
|
|
|
153
160
|
return null;
|
|
154
161
|
return state.slice(5, -1).trim();
|
|
155
162
|
};
|
|
163
|
+
const exactAttributeOf = (state) => {
|
|
164
|
+
const match = /^\[\s*([\w-]+)\s*=\s*(?:"([^"\\]*)"|'([^'\\]*)'|([\w-]+))\s*([is])?\s*\]$/i.exec(state);
|
|
165
|
+
if (!match)
|
|
166
|
+
return null;
|
|
167
|
+
return {
|
|
168
|
+
name: match[1],
|
|
169
|
+
value: (match[2] ?? match[3] ?? match[4]).toLowerCase(),
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
const attributeNameOf = (state) => /^\[\s*([\w-]+)/.exec(state)?.[1] ?? null;
|
|
173
|
+
const absentAttribute = (negated, other) => negated !== null &&
|
|
174
|
+
/^\[\s*[\w-]+\s*\]$/.test(negated) &&
|
|
175
|
+
attributeNameOf(negated) === attributeNameOf(other);
|
|
176
|
+
const differentAttributeValues = (first, second) => {
|
|
177
|
+
const a = exactAttributeOf(first);
|
|
178
|
+
const b = exactAttributeOf(second);
|
|
179
|
+
return a !== null && b !== null && a.name === b.name && a.value !== b.value;
|
|
180
|
+
};
|
|
156
181
|
function areExclusive(first, second) {
|
|
157
182
|
const left = new Set(statesOf(first));
|
|
158
183
|
const right = new Set(statesOf(second));
|
|
@@ -164,6 +189,11 @@ function areExclusive(first, second) {
|
|
|
164
189
|
return true;
|
|
165
190
|
if (EXCLUSIVE_PAIRS.includes([a, b].sort().join('\0')))
|
|
166
191
|
return true;
|
|
192
|
+
if (differentAttributeValues(a, b))
|
|
193
|
+
return true;
|
|
194
|
+
if (absentAttribute(negationOf(a), b) ||
|
|
195
|
+
absentAttribute(negationOf(b), a))
|
|
196
|
+
return true;
|
|
167
197
|
}
|
|
168
198
|
}
|
|
169
199
|
return false;
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plumeria/eslint-plugin",
|
|
3
|
-
"version": "21.1.
|
|
3
|
+
"version": "21.1.3",
|
|
4
4
|
"description": "Plumeria ESLint plugin",
|
|
5
5
|
"author": "Refirst 11",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://plumeria.dev",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/zss-
|
|
10
|
+
"url": "git+https://github.com/zss-labs/plumeria.git",
|
|
11
11
|
"directory": "packages/eslint-plugin"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/zss-
|
|
14
|
+
"url": "https://github.com/zss-labs/plumeria/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"plumeria",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"oxlint": "1.87.0",
|
|
61
61
|
"zss-engine": "3.0.1",
|
|
62
|
-
"@plumeria/compiler": "21.1.
|
|
62
|
+
"@plumeria/compiler": "21.1.3"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "rimraf dist && pnpm cjs",
|