@lvce-editor/eslint-plugin-virtual-dom 18.10.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.
Files changed (2) hide show
  1. package/dist/index.js +111 -6
  2. 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$u = {
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$u = context => {
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$u,
148
- meta: meta$u
147
+ create: create$v,
148
+ meta: meta$v
149
149
  };
150
150
 
151
- const meta$t = {
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$t = context => {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/eslint-plugin-virtual-dom",
3
- "version": "18.10.0",
3
+ "version": "18.11.0",
4
4
  "main": "dist/index.js",
5
5
  "repository": {
6
6
  "type": "git",