@lvce-editor/eslint-plugin-virtual-dom 18.9.0 → 18.11.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/dist/index.js +111 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -93,7 +93,7 @@ const controlsAllowingChildren = new Set(['A', 'Button']);
|
|
|
93
93
|
const hasDirectName = node => {
|
|
94
94
|
return Boolean(getProperty(node, 'ariaLabel') || getProperty(node, 'ariaLabelledBy') || getProperty(node, 'title'));
|
|
95
95
|
};
|
|
96
|
-
const meta$
|
|
96
|
+
const meta$v = {
|
|
97
97
|
docs: {
|
|
98
98
|
description: 'Require a statically determinable accessible name for virtual-dom controls'
|
|
99
99
|
},
|
|
@@ -102,7 +102,7 @@ const meta$u = {
|
|
|
102
102
|
},
|
|
103
103
|
type: 'problem'
|
|
104
104
|
};
|
|
105
|
-
const create$
|
|
105
|
+
const create$v = context => {
|
|
106
106
|
return {
|
|
107
107
|
ObjectExpression(node) {
|
|
108
108
|
if (!isVirtualDomNode(node) || hasDirectName(node)) {
|
|
@@ -144,11 +144,11 @@ const create$u = context => {
|
|
|
144
144
|
|
|
145
145
|
const accessibleControlName = {
|
|
146
146
|
__proto__: null,
|
|
147
|
-
create: create$
|
|
148
|
-
meta: meta$
|
|
147
|
+
create: create$v,
|
|
148
|
+
meta: meta$v
|
|
149
149
|
};
|
|
150
150
|
|
|
151
|
-
const meta$
|
|
151
|
+
const meta$u = {
|
|
152
152
|
docs: {
|
|
153
153
|
description: 'Require an explicit role on clickable virtual-dom div nodes'
|
|
154
154
|
},
|
|
@@ -157,7 +157,7 @@ const meta$t = {
|
|
|
157
157
|
},
|
|
158
158
|
type: 'problem'
|
|
159
159
|
};
|
|
160
|
-
const create$
|
|
160
|
+
const create$u = context => {
|
|
161
161
|
return {
|
|
162
162
|
ObjectExpression(node) {
|
|
163
163
|
if (!isVirtualDomNode(node)) {
|
|
@@ -178,6 +178,108 @@ const create$t = context => {
|
|
|
178
178
|
};
|
|
179
179
|
|
|
180
180
|
const clickableDivNeedsRole = {
|
|
181
|
+
__proto__: null,
|
|
182
|
+
create: create$u,
|
|
183
|
+
meta: meta$u
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const meta$t = {
|
|
187
|
+
docs: {
|
|
188
|
+
description: 'Require static mergeClassNames calls inside functions to be hoisted to module scope'
|
|
189
|
+
},
|
|
190
|
+
messages: {
|
|
191
|
+
hoistClassNames: 'Hoist this static mergeClassNames call to module scope.'
|
|
192
|
+
},
|
|
193
|
+
type: 'suggestion'
|
|
194
|
+
};
|
|
195
|
+
const findVariable$1 = (sourceCode, node) => {
|
|
196
|
+
let scope = sourceCode.getScope(node);
|
|
197
|
+
while (scope) {
|
|
198
|
+
const variable = scope.set.get(node.name);
|
|
199
|
+
if (variable) {
|
|
200
|
+
return variable;
|
|
201
|
+
}
|
|
202
|
+
scope = scope.upper;
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
};
|
|
206
|
+
const isMergeClassNames = (sourceCode, node) => {
|
|
207
|
+
if (node.type === 'Identifier') {
|
|
208
|
+
return Boolean(findVariable$1(sourceCode, node)?.defs.some(definition => {
|
|
209
|
+
return definition.type === 'ImportBinding' && definition.node.type === 'ImportSpecifier' && (definition.node.imported.type === 'Identifier' ? definition.node.imported.name : definition.node.imported.value) === 'mergeClassNames';
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
if (node.type !== 'MemberExpression' || node.computed || node.optional || node.object.type !== 'Identifier' || node.property.type !== 'Identifier' || node.property.name !== 'mergeClassNames') {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return Boolean(findVariable$1(sourceCode, node.object)?.defs.some(definition => {
|
|
216
|
+
return definition.type === 'ImportBinding' && definition.node.type === 'ImportNamespaceSpecifier';
|
|
217
|
+
}));
|
|
218
|
+
};
|
|
219
|
+
const isStatic = (sourceCode, node, seen = new Set()) => {
|
|
220
|
+
switch (node.type) {
|
|
221
|
+
case 'BinaryExpression':
|
|
222
|
+
return node.operator === '+' && isStatic(sourceCode, node.left, seen) && isStatic(sourceCode, node.right, seen);
|
|
223
|
+
case 'Identifier':
|
|
224
|
+
{
|
|
225
|
+
const variable = findVariable$1(sourceCode, node);
|
|
226
|
+
if (!variable || variable.scope.type !== 'module' || seen.has(variable)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const definition = variable.defs[0];
|
|
230
|
+
if (definition?.type !== 'Variable' || definition.parent.kind !== 'const' || !definition.node.init) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
return isStatic(sourceCode, definition.node.init, new Set([...seen, variable]));
|
|
234
|
+
}
|
|
235
|
+
case 'Literal':
|
|
236
|
+
return typeof node.value === 'string';
|
|
237
|
+
case 'MemberExpression':
|
|
238
|
+
{
|
|
239
|
+
// Class-name modules export constants. Do not assume arbitrary object properties are immutable.
|
|
240
|
+
if (node.computed || node.optional || node.object.type !== 'Identifier') {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const variable = findVariable$1(sourceCode, node.object);
|
|
244
|
+
return Boolean(variable?.defs.some(definition => {
|
|
245
|
+
if (definition.type !== 'ImportBinding') {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (definition.node.type === 'ImportNamespaceSpecifier') {
|
|
249
|
+
return /(?:^|\/)ClassNames\.(?:js|ts)$/.test(String(definition.parent.source.value));
|
|
250
|
+
}
|
|
251
|
+
return definition.node.type === 'ImportSpecifier' && definition.node.imported.type === 'Identifier' && definition.node.imported.name === 'ClassNames' && definition.parent.source.value === '@lvce-editor/virtual-dom-worker';
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
case 'TemplateLiteral':
|
|
255
|
+
return node.expressions.every(expression => isStatic(sourceCode, expression, seen));
|
|
256
|
+
default:
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
const create$t = context => {
|
|
261
|
+
return {
|
|
262
|
+
CallExpression(node) {
|
|
263
|
+
if (node.type !== 'CallExpression' || node.optional || !isMergeClassNames(context.sourceCode, node.callee)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (context.sourceCode.getAncestors(node).every(ancestor => {
|
|
267
|
+
return !['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(ancestor.type);
|
|
268
|
+
})) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (node.arguments.some(argument => !isStatic(context.sourceCode, argument))) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
context.report({
|
|
275
|
+
messageId: 'hoistClassNames',
|
|
276
|
+
node
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const hoistClassNames = {
|
|
181
283
|
__proto__: null,
|
|
182
284
|
create: create$t,
|
|
183
285
|
meta: meta$t
|
|
@@ -1664,6 +1766,7 @@ const plugin = {
|
|
|
1664
1766
|
rules: {
|
|
1665
1767
|
'accessible-control-name': accessibleControlName,
|
|
1666
1768
|
'clickable-div-needs-role': clickableDivNeedsRole,
|
|
1769
|
+
'hoist-class-names': hoistClassNames,
|
|
1667
1770
|
'hoist-static-nodes': hoistStaticNodes,
|
|
1668
1771
|
'no-conditional-spread': noConditionalSpread,
|
|
1669
1772
|
'no-element-helper': noElementHelper,
|
|
@@ -1702,6 +1805,7 @@ const recommended = [{
|
|
|
1702
1805
|
},
|
|
1703
1806
|
rules: {
|
|
1704
1807
|
'virtual-dom/clickable-div-needs-role': 'error',
|
|
1808
|
+
'virtual-dom/hoist-class-names': 'error',
|
|
1705
1809
|
'virtual-dom/hoist-static-nodes': 'error',
|
|
1706
1810
|
'virtual-dom/no-conditional-spread': 'error',
|
|
1707
1811
|
'virtual-dom/no-element-helper': 'error',
|
|
@@ -1727,6 +1831,7 @@ const recommended = [{
|
|
|
1727
1831
|
}, {
|
|
1728
1832
|
files: ['**/test/**/*.{js,mjs,cjs,ts,mts,cts}', '**/tests/**/*.{js,mjs,cjs,ts,mts,cts}', '**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}'],
|
|
1729
1833
|
rules: {
|
|
1834
|
+
'virtual-dom/hoist-class-names': 'off',
|
|
1730
1835
|
'virtual-dom/hoist-static-nodes': 'off',
|
|
1731
1836
|
'virtual-dom/prefer-merge-class-names': 'off'
|
|
1732
1837
|
}
|