eslint-plugin-vuetify 2.3.0 → 2.5.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
@@ -15,7 +15,7 @@ This package is for migrating from Vuetify v2 to v3, use [eslint-plugin-vuetify@
15
15
 
16
16
  ## 💿 Install
17
17
 
18
- You should have [`eslint`](https://eslint.org/docs/user-guide/getting-started) and [`eslint-plugin-vue`](https://eslint.vuejs.org/user-guide/#installation) set up first.
18
+ You should have [`eslint`](https://eslint.org/docs/latest/use/getting-started) and [`eslint-plugin-vue`](https://eslint.vuejs.org/user-guide/#installation) set up first.
19
19
 
20
20
  ```bash
21
21
  yarn add eslint-plugin-vuetify -D
@@ -23,6 +23,19 @@ yarn add eslint-plugin-vuetify -D
23
23
  npm install eslint-plugin-vuetify --save-dev
24
24
  ```
25
25
 
26
+ ```js
27
+ // eslint.config.js
28
+ import vue from 'eslint-plugin-vue'
29
+ import vuetify from 'eslint-plugin-vuetify'
30
+
31
+ export default [
32
+ ...vue.configs['flat/base'],
33
+ ...vuetify.configs['flat/base'],
34
+ ]
35
+ ```
36
+
37
+ Eslint 8 can alternatively use the older configuration format:
38
+
26
39
  ```js
27
40
  // .eslintrc.js
28
41
  module.exports = {
@@ -40,7 +53,7 @@ module.exports = {
40
53
 
41
54
  ### Deprecations
42
55
 
43
- These rules will help you avoid deprecated components, props, and classes. They are included in the `plugin:vuetify/base` preset.
56
+ These rules will help you avoid deprecated components, props, and classes. They are included in the `base` preset.
44
57
 
45
58
  - Prevent the use of components that have been removed from Vuetify ([`no-deprecated-components`])
46
59
  - Prevent the use of props that have been removed from Vuetify ([`no-deprecated-props`])
@@ -48,10 +61,11 @@ These rules will help you avoid deprecated components, props, and classes. They
48
61
  - Prevent the use of classes that have been removed from Vuetify ([`no-deprecated-classes`])
49
62
  - Prevent the use of the old theme class syntax ([`no-deprecated-colors`])
50
63
  - Prevent the use of deprecated import paths ([`no-deprecated-imports`])
64
+ - Ensure icon buttons have a variant defined ([`icon-button-variant`])
51
65
 
52
66
  ### Grid system
53
67
 
54
- These rules are designed to help migrate to the new grid system in Vuetify v2. They are included in the `plugin:vuetify/recommended` preset.
68
+ These rules are designed to help migrate to the new grid system in Vuetify v2. They are included in the `recommended` preset.
55
69
 
56
70
  - Warn about unknown attributes not being converted to classes on new grid components ([`grid-unknown-attributes`])
57
71
 
@@ -63,6 +77,7 @@ These rules are designed to help migrate to the new grid system in Vuetify v2. T
63
77
  [`no-deprecated-classes`]: ./docs/rules/no-deprecated-classes.md
64
78
  [`no-deprecated-colors`]: ./docs/rules/no-deprecated-colors.md
65
79
  [`no-deprecated-imports`]: ./docs/rules/no-deprecated-imports.md
80
+ [`icon-button-variant`]: ./docs/rules/icon-button-variant.md
66
81
 
67
82
 
68
83
  ## 💪 Supporting Vuetify
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ module.exports = [{
4
+ plugins: {
5
+ vue: require('eslint-plugin-vue'),
6
+ get vuetify() {
7
+ return require('../../index');
8
+ }
9
+ },
10
+ rules: require('../base').rules
11
+ }];
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ module.exports = [...require('./base'), {
4
+ plugins: {
5
+ get vuetify() {
6
+ return require('../../index');
7
+ }
8
+ },
9
+ rules: require('../recommended').rules
10
+ }];
package/lib/index.js CHANGED
@@ -3,6 +3,11 @@
3
3
  const path = require('path');
4
4
  const requireindex = require('requireindex');
5
5
  module.exports = {
6
- configs: requireindex(path.join(__dirname, './configs')),
6
+ configs: {
7
+ base: require('./configs/base'),
8
+ recommended: require('./configs/recommended'),
9
+ 'flat/base': require('./configs/flat/base'),
10
+ 'flat/recommended': require('./configs/flat/recommended')
11
+ },
7
12
  rules: requireindex(path.join(__dirname, './rules'))
8
13
  };
@@ -39,7 +39,7 @@ module.exports = {
39
39
  schema: []
40
40
  },
41
41
  create(context) {
42
- return context.parserServices.defineTemplateBodyVisitor({
42
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
43
43
  VElement(element) {
44
44
  const tag = classify(element.rawName);
45
45
  if (!Object.keys(tags).includes(tag)) return;
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ classify,
5
+ getAttributes
6
+ } = require('../util/helpers');
7
+
8
+ // ------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ // ------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ docs: {
15
+ description: 'Ensure icon buttons have a variant defined.',
16
+ category: 'recommended'
17
+ },
18
+ fixable: 'code',
19
+ schema: [{
20
+ type: 'string'
21
+ }],
22
+ messages: {
23
+ needsVariant: 'Icon buttons should have {{ a }} defined.'
24
+ }
25
+ },
26
+ create(context) {
27
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
28
+ VElement(element) {
29
+ const tag = classify(element.rawName);
30
+ if (tag !== 'VBtn') return;
31
+ const attributes = getAttributes(element);
32
+ const iconAttribute = attributes.find(attr => attr.name === 'icon');
33
+ if (!iconAttribute) return;
34
+ if (attributes.some(attr => attr.name === 'variant')) return;
35
+ const variant = `variant="${context.options[0] || 'text'}"`;
36
+ context.report({
37
+ node: iconAttribute.node,
38
+ messageId: 'needsVariant',
39
+ data: {
40
+ a: variant
41
+ },
42
+ fix(fixer) {
43
+ return fixer.insertTextAfter(iconAttribute.node, ' ' + variant);
44
+ }
45
+ });
46
+ }
47
+ });
48
+ }
49
+ };
@@ -11,13 +11,7 @@ const replacements = new Map([[/^rounded-(r|l|tr|tl|br|bl)(-.*)?$/, ([side, rest
11
11
  bl: 'bs'
12
12
  }[side];
13
13
  return `rounded-${side}${rest || ''}`;
14
- }], [/^border-([rl])(.*)$/, ([side, rest]) => {
15
- side = {
16
- r: 'e',
17
- l: 's'
18
- }[side];
19
- return `border-${side}${rest}`;
20
- }], [/^text-xs-(left|right|center|justify)$/, ([align]) => `text-${align}`], [/hidden-(xs|sm|md|lg|xl)-only/, ([breakpoint]) => `hidden-${breakpoint}`], ['scroll-y', 'overflow-y-auto'], ['hide-overflow', 'overflow-hidden'], ['show-overflow', 'overflow-visible'], ['no-wrap', 'text-no-wrap'], ['ellipsis', 'text-truncate'], ['left', 'float-left'], ['right', 'float-right'], ['display-4', 'text-h1'], ['display-3', 'text-h2'], ['display-2', 'text-h3'], ['display-1', 'text-h4'], ['headline', 'text-h5'], ['title', 'text-h6'], ['subtitle-1', 'text-subtitle-1'], ['subtitle-2', 'text-subtitle-2'], ['body-1', 'text-body-1'], ['body-2', 'text-body-2'], ['caption', 'text-caption'], ['caption', 'text-caption'], ['overline', 'text-overline'], [/^transition-(fast-out-slow-in|linear-out-slow-in|fast-out-linear-in|ease-in-out|fast-in-fast-out|swing)$/, false]]);
14
+ }], [/^text-xs-(left|right|center|justify)$/, ([align]) => `text-${align}`], [/^hidden-(xs|sm|md|lg|xl)-only$/, ([breakpoint]) => `hidden-${breakpoint}`], ['scroll-y', 'overflow-y-auto'], ['hide-overflow', 'overflow-hidden'], ['show-overflow', 'overflow-visible'], ['no-wrap', 'text-no-wrap'], ['ellipsis', 'text-truncate'], ['left', 'float-left'], ['right', 'float-right'], ['display-4', 'text-h1'], ['display-3', 'text-h2'], ['display-2', 'text-h3'], ['display-1', 'text-h4'], ['headline', 'text-h5'], ['title', 'text-h6'], ['subtitle-1', 'text-subtitle-1'], ['subtitle-2', 'text-subtitle-2'], ['body-1', 'text-body-1'], ['body-2', 'text-body-2'], ['caption', 'text-caption'], ['caption', 'text-caption'], ['overline', 'text-overline'], [/^transition-(fast-out-slow-in|linear-out-slow-in|fast-out-linear-in|ease-in-out|fast-in-fast-out|swing)$/, false]]);
21
15
 
22
16
  // ------------------------------------------------------------------------------
23
17
  // Rule Definition
@@ -36,11 +30,10 @@ module.exports = {
36
30
  }
37
31
  },
38
32
  create(context) {
39
- return context.parserServices.defineTemplateBodyVisitor({
33
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
40
34
  'VAttribute[key.name="class"]'(node) {
41
35
  if (!node.value || !node.value.value) return;
42
36
  const classes = node.value.value.split(/\s+/).filter(s => !!s);
43
- const source = context.getSourceCode();
44
37
  const changed = [];
45
38
  classes.forEach(className => {
46
39
  for (const replacer of replacements) {
@@ -64,8 +57,8 @@ module.exports = {
64
57
  const idx = node.value.value.indexOf(change[0]) + 1;
65
58
  const range = [node.value.range[0] + idx, node.value.range[0] + idx + change[0].length];
66
59
  const loc = {
67
- start: source.getLocFromIndex(range[0]),
68
- end: source.getLocFromIndex(range[1])
60
+ start: context.sourceCode.getLocFromIndex(range[0]),
61
+ end: context.sourceCode.getLocFromIndex(range[1])
69
62
  };
70
63
  if (change[1]) {
71
64
  context.report({
@@ -45,7 +45,7 @@ module.exports = {
45
45
  const variant = classes.findIndex(t => textVariants.includes(t));
46
46
  return [base, variant];
47
47
  }
48
- return context.parserServices.defineTemplateBodyVisitor({
48
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
49
49
  'VAttribute[key.name="color"]'(node) {
50
50
  if (!node.value || !node.value.value) return;
51
51
  const color = node.value.value.split(/\s+/).filter(s => !!s);
@@ -38,8 +38,7 @@ const replacements = {
38
38
  VSimpleTable: 'v-table',
39
39
  VTabsSlider: false,
40
40
  VTabsItems: false,
41
- VTabItem: false,
42
- VTimePicker: false
41
+ VTabItem: false
43
42
  };
44
43
 
45
44
  // ------------------------------------------------------------------------------
@@ -61,10 +60,10 @@ module.exports = {
61
60
  }
62
61
  },
63
62
  create(context) {
64
- return context.parserServices.defineTemplateBodyVisitor({
63
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
65
64
  VElement(element) {
66
65
  const tag = classify(element.rawName);
67
- const tokens = context.parserServices.getTemplateBodyTokenStore();
66
+ const tokens = context.sourceCode.parserServices.getTemplateBodyTokenStore();
68
67
  if (Object.prototype.hasOwnProperty.call(replacements, tag)) {
69
68
  const replacement = replacements[tag];
70
69
  if (typeof replacement === 'object' && 'custom' in replacement) {
@@ -156,7 +156,7 @@ module.exports = {
156
156
  }
157
157
  },
158
158
  create(context) {
159
- return context.parserServices.defineTemplateBodyVisitor({
159
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
160
160
  VAttribute(attr) {
161
161
  if (!(attr.directive && attr.key.name.name === 'on' && attr.key.argument?.type === 'VIdentifier')) return;
162
162
  const tag = classify(attr.parent.parent.rawName);
@@ -89,9 +89,6 @@ const select = {
89
89
  cacheItems: false,
90
90
  deletableChips: 'closable-chips',
91
91
  disableLookup: false,
92
- itemColor: {
93
- custom: 'item-props.color'
94
- },
95
92
  itemDisabled: {
96
93
  custom: 'item-props.disabled'
97
94
  },
@@ -316,10 +313,7 @@ const replacements = {
316
313
  VCard: {
317
314
  activeClass: false,
318
315
  loaderHeight: false,
319
- outlined: {
320
- name: 'variant',
321
- value: 'outlined'
322
- },
316
+ outlined: 'border',
323
317
  raised: {
324
318
  name: 'elevation',
325
319
  value: 8
@@ -545,7 +539,8 @@ const replacements = {
545
539
  ...size
546
540
  },
547
541
  VForm: {
548
- value: 'model-value'
542
+ value: 'model-value',
543
+ lazyValidation: false
549
544
  },
550
545
  VHover: {
551
546
  value: 'model-value'
@@ -943,7 +938,7 @@ module.exports = {
943
938
  }
944
939
  },
945
940
  create(context) {
946
- return context.parserServices.defineTemplateBodyVisitor({
941
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
947
942
  VStartTag(tag) {
948
943
  const attrGroups = {};
949
944
  tag.attributes.forEach(attr => {
@@ -999,7 +994,7 @@ module.exports = {
999
994
  }
1000
995
  });
1001
996
  } else if (typeof replace === 'object' && 'name' in replace && 'value' in replace) {
1002
- const oldValue = attr.directive ? context.getSourceCode().getText(attr.value.expression) : attr.value?.value;
997
+ const oldValue = attr.directive ? context.sourceCode.getText(attr.value.expression) : attr.value?.value;
1003
998
  const value = typeof replace.value === 'function' ? replace.value(oldValue) : replace.value;
1004
999
  if (value == null || value === oldValue) return;
1005
1000
  context.report({
@@ -1017,8 +1012,8 @@ module.exports = {
1017
1012
  }
1018
1013
  return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${value}"`)];
1019
1014
  } else {
1020
- const expression = context.getSourceCode().getText(attr.value.expression);
1021
- return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${expression} && '${value}'"`)];
1015
+ const expression = context.sourceCode.getText(attr.value.expression);
1016
+ return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${expression} ? '${value}' : undefined"`)];
1022
1017
  }
1023
1018
  } else {
1024
1019
  return fixer.replaceText(attr, `${replace.bind ? ':' : ''}${replace.name}="${value}"`);
@@ -96,7 +96,7 @@ const groups = [{
96
96
  yield fixer.replaceText(ref.id.parent.parent, `v-bind="props"`);
97
97
  }
98
98
  if (boundVariables.attrs) {
99
- const template = context.parserServices.getTemplateBodyTokenStore();
99
+ const template = context.sourceCode.parserServices.getTemplateBodyTokenStore();
100
100
  const ref = boundVariables.attrs;
101
101
  const isLast = ref.prop === param.properties.at(-1);
102
102
  if (isLast) {
@@ -188,7 +188,7 @@ module.exports = {
188
188
  },
189
189
  create(context) {
190
190
  let scopeStack;
191
- return context.parserServices.defineTemplateBodyVisitor({
191
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
192
192
  VElement(node) {
193
193
  scopeStack = {
194
194
  parent: scopeStack,
@@ -10,11 +10,11 @@ function addClass(context, fixer, element, className) {
10
10
  return fixer.insertTextAfter(classNode, `="${className}"`);
11
11
  } else {
12
12
  // nothing
13
- return fixer.insertTextAfter(context.parserServices.getTemplateBodyTokenStore().getFirstToken(element.startTag), ` class="${className}"`);
13
+ return fixer.insertTextAfter(context.sourceCode.parserServices.getTemplateBodyTokenStore().getFirstToken(element.startTag), ` class="${className}"`);
14
14
  }
15
15
  }
16
16
  function removeAttr(context, fixer, node) {
17
- const source = context.getSourceCode().text;
17
+ const source = context.sourceCode.text;
18
18
  let [start, end] = node.range;
19
19
  // Remove extra whitespace before attributes
20
20
  start -= /\s*$/g.exec(source.substring(0, start))[0].length;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- function hyphenate( /* istanbul ignore next */
3
+ function hyphenate(/* istanbul ignore next */
4
4
  str = '') {
5
5
  return str.replace(/\B([A-Z])/g, '-$1').toLowerCase();
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-vuetify",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "An eslint plugin for Vuetify",
5
5
  "main": "lib/index.js",
6
6
  "author": "Kael Watts-Deuchar <kaelwd@gmail.com>",
@@ -9,6 +9,7 @@
9
9
  "scripts": {
10
10
  "build": "rimraf lib && babel src --out-dir lib",
11
11
  "test": "mocha tests --recursive --reporter dot",
12
+ "test:8": "ESLINT8=true mocha tests --recursive --reporter dot",
12
13
  "test:coverage": "nyc mocha tests --recursive --reporter dot",
13
14
  "test:ci": "nyc --reporter=lcov mocha tests --recursive --reporter dot",
14
15
  "lint": "eslint src tests",
@@ -26,23 +27,24 @@
26
27
  "@babel/cli": "^7.19.3",
27
28
  "@babel/core": "^7.19.6",
28
29
  "@babel/preset-env": "^7.19.4",
30
+ "@stylistic/eslint-plugin": "^2.10.1",
29
31
  "conventional-changelog-cli": "^2.2.2",
30
32
  "conventional-changelog-vuetify": "^1.1.0",
31
33
  "conventional-github-releaser": "^3.1.5",
32
- "eslint": "^8.26.0",
33
- "eslint-config-standard": "^17.0.0",
34
- "eslint-plugin-import": "^2.26.0",
35
- "eslint-plugin-n": "^15.3.0",
36
- "eslint-plugin-promise": "^6.1.1",
34
+ "eslint": "^9.14.0",
35
+ "eslint8": "npm:eslint@8.57.1",
37
36
  "husky": "^8.0.1",
38
37
  "mocha": "^10.1.0",
38
+ "neostandard": "^0.11.8",
39
39
  "nyc": "^15.1.0",
40
40
  "rimraf": "^3.0.2",
41
- "vue": "^3.2.41",
42
- "vuetify": "^3.0.0"
41
+ "vue": "^3.5.12",
42
+ "vue-eslint-parser": "^9.4.3",
43
+ "vuetify": "^3.7.4"
43
44
  },
44
45
  "peerDependencies": {
45
- "eslint": "^8.0.0",
46
+ "eslint": "^8.0.0 || ^9.0.0",
46
47
  "vuetify": "^3.0.0"
47
- }
48
+ },
49
+ "packageManager": "pnpm@9.13.2"
48
50
  }