eslint-plugin-vuetify 1.1.0 → 2.0.0-beta.1

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
@@ -1,6 +1,4 @@
1
1
  # eslint-plugin-vuetify
2
- > An eslint plugin for Vuetify.
3
- > Built for https://github.com/vuetifyjs/vuetify/pull/7327, requires vuetify >=2.0.0
4
2
 
5
3
  <br>
6
4
 
@@ -45,20 +43,20 @@ These rules will help you avoid deprecated components, props, and classes. They
45
43
  - Prevent the use of components that have been removed from Vuetify ([`no-deprecated-components`])
46
44
  - Prevent the use of props that have been removed from Vuetify ([`no-deprecated-props`])
47
45
  - Prevent the use of classes that have been removed from Vuetify ([`no-deprecated-classes`])
46
+ - Prevent the use of the old theme class syntax ([`no-deprecated-colors`])
48
47
 
49
48
  ### Grid system
50
49
 
51
50
  These rules are designed to help migrate to the new grid system in Vuetify v2. They are included in the `plugin:vuetify/recommended` preset.
52
51
 
53
- - Prevent the use of legacy grid components and props ([`no-legacy-grid`])
54
52
  - Warn about unknown attributes not being converted to classes on new grid components ([`grid-unknown-attributes`])
55
53
 
56
54
 
57
- [`no-legacy-grid`]: ./docs/rules/no-legacy-grid.md
58
55
  [`grid-unknown-attributes`]: ./docs/rules/grid-unknown-attributes.md
59
56
  [`no-deprecated-components`]: ./docs/rules/no-deprecated-components.md
60
57
  [`no-deprecated-props`]: ./docs/rules/no-deprecated-props.md
61
58
  [`no-deprecated-classes`]: ./docs/rules/no-deprecated-classes.md
59
+ [`no-deprecated-colors`]: ./docs/rules/no-deprecated-colors.md
62
60
 
63
61
 
64
62
  ## 💪 Supporting Vuetify
@@ -1,16 +1,14 @@
1
- 'use strict'
1
+ 'use strict';
2
2
 
3
3
  module.exports = {
4
- plugins: [
5
- 'vuetify'
6
- ],
4
+ plugins: ['vuetify'],
7
5
  rules: {
8
6
  'vue/valid-v-slot': ['error', {
9
7
  allowModifiers: true
10
8
  }],
11
-
9
+ 'vuetify/no-deprecated-classes': 'error',
10
+ 'vuetify/no-deprecated-colors': 'error',
12
11
  'vuetify/no-deprecated-components': 'error',
13
- 'vuetify/no-deprecated-props': 'error',
14
- 'vuetify/no-deprecated-classes': 'error'
12
+ 'vuetify/no-deprecated-props': 'error'
15
13
  }
16
- }
14
+ };
@@ -1,9 +1,8 @@
1
- 'use strict'
1
+ 'use strict';
2
2
 
3
3
  module.exports = {
4
4
  extends: require.resolve('./base'),
5
5
  rules: {
6
- 'vuetify/no-legacy-grid': 'error',
7
6
  'vuetify/grid-unknown-attributes': 'error'
8
7
  }
9
- }
8
+ };
package/lib/index.js CHANGED
@@ -1,8 +1,8 @@
1
- 'use strict'
2
- const path = require('path')
3
- const requireindex = require('requireindex')
1
+ 'use strict';
4
2
 
3
+ const path = require('path');
4
+ const requireindex = require('requireindex');
5
5
  module.exports = {
6
6
  configs: requireindex(path.join(__dirname, './configs')),
7
7
  rules: requireindex(path.join(__dirname, './rules'))
8
- }
8
+ };
@@ -1,21 +1,29 @@
1
- 'use strict'
2
-
3
- const loadModule = require('../util/load-module')
4
- const { hyphenate, classify, getAttributes } = require('../util/helpers')
5
- const { isGridAttribute } = require('../util/grid-attributes')
6
- const { addClass, removeAttr } = require('../util/fixers')
1
+ 'use strict';
7
2
 
3
+ const {
4
+ hyphenate,
5
+ classify,
6
+ getAttributes
7
+ } = require('../util/helpers');
8
+ const {
9
+ isGridAttribute
10
+ } = require('../util/grid-attributes');
11
+ const {
12
+ addClass,
13
+ removeAttr
14
+ } = require('../util/fixers');
15
+ const {
16
+ components
17
+ } = require('vuetify/dist/vuetify.js');
8
18
  const VGrid = {
9
- VContainer: loadModule('vuetify/es5/components/VGrid/VContainer').default,
10
- VRow: loadModule('vuetify/es5/components/VGrid/VRow').default,
11
- VCol: loadModule('vuetify/es5/components/VGrid/VCol').default
12
- }
13
-
19
+ VContainer: components.VContainer,
20
+ VRow: components.VRow,
21
+ VCol: components.VCol
22
+ };
14
23
  const tags = Object.keys(VGrid).reduce((t, k) => {
15
- t[classify(k)] = Object.keys(VGrid[k].options.props).map(p => hyphenate(p)).sort()
16
-
17
- return t
18
- }, {})
24
+ t[classify(k)] = Object.keys(VGrid[k].props).map(p => hyphenate(p)).sort();
25
+ return t;
26
+ }, {});
19
27
 
20
28
  // ------------------------------------------------------------------------------
21
29
  // Rule Definition
@@ -30,16 +38,16 @@ module.exports = {
30
38
  fixable: 'code',
31
39
  schema: []
32
40
  },
33
- create (context) {
41
+ create(context) {
34
42
  return context.parserServices.defineTemplateBodyVisitor({
35
- VElement (element) {
36
- const tag = classify(element.rawName)
37
- if (!Object.keys(tags).includes(tag)) return
38
-
39
- const attributes = getAttributes(element).filter(({ name }) => {
40
- return !tags[tag].includes(name) && !isGridAttribute(tag, name)
41
- })
42
-
43
+ VElement(element) {
44
+ const tag = classify(element.rawName);
45
+ if (!Object.keys(tags).includes(tag)) return;
46
+ const attributes = getAttributes(element).filter(({
47
+ name
48
+ }) => {
49
+ return !tags[tag].includes(name) && !isGridAttribute(tag, name);
50
+ });
43
51
  if (attributes.length) {
44
52
  context.report({
45
53
  node: element.startTag,
@@ -48,21 +56,17 @@ module.exports = {
48
56
  end: attributes[attributes.length - 1].node.loc.end
49
57
  },
50
58
  message: 'Attributes are no longer converted into classes',
51
- fix (fixer) {
52
- const fixableAttrs = attributes.map(({ node }) => node)
53
- .filter(attr => !attr.directive)
54
-
55
- if (!fixableAttrs.length) return
56
-
57
- const className = fixableAttrs.map(node => node.key.rawName).join(' ')
58
- return [
59
- addClass(context, fixer, element, className),
60
- ...fixableAttrs.map(removeAttr.bind(this, context, fixer))
61
- ]
59
+ fix(fixer) {
60
+ const fixableAttrs = attributes.map(({
61
+ node
62
+ }) => node).filter(attr => !attr.directive);
63
+ if (!fixableAttrs.length) return;
64
+ const className = fixableAttrs.map(node => node.key.rawName).join(' ');
65
+ return [addClass(context, fixer, element, className), ...fixableAttrs.map(removeAttr.bind(this, context, fixer))];
62
66
  }
63
- })
67
+ });
64
68
  }
65
69
  }
66
- })
70
+ });
67
71
  }
68
- }
72
+ };
@@ -1,53 +1,23 @@
1
- 'use strict'
2
-
3
- const { classify, getAttributes } = require('../util/helpers')
4
- const { removeAttr } = require('../util/fixers')
5
- const { getInstalledVuetifyVersion } = require('../util/get-installed-vuetify-version')
6
-
7
- // const spacers = {
8
- // 0: 0,
9
- // 1: 1,
10
- // 2: 2,
11
- // 3: 4,
12
- // 4: 6,
13
- // 5: 12
14
- // }
1
+ 'use strict';
15
2
 
16
3
  /** @type {Map<RegExp, (args: string[]) => string> | Map<string, string>} */
17
- const replacements = new Map([
18
- // ['shrink', 'flex-grow-0'],
19
- // ['grow', 'flex-shrink-0'],
20
- [/^text-xs-(left|right|center|justify)$/, ([align]) => `text-${align}`],
21
- // ['child-flex', false],
22
- ['scroll-y', 'overflow-y-auto'],
23
- ['hide-overflow', 'overflow-hidden'],
24
- ['show-overflow', 'overflow-visible'],
25
- ['no-wrap', 'text-no-wrap'],
26
- ['ellipsis', 'text-truncate'],
27
- ['left', 'float-left'],
28
- ['right', 'float-right']
29
- // TODO: only run fixer once
30
- // [/([mp][axytblr])-(\d)/, (type, n) => `${type}-${spacers[n]}`]
31
- ])
32
-
33
- if (getInstalledVuetifyVersion() >= '2.3.0') {
34
- replacements
35
- .set('display-4', 'text-h1')
36
- .set('display-3', 'text-h2')
37
- .set('display-2', 'text-h3')
38
- .set('display-1', 'text-h4')
39
- .set('headline', 'text-h5')
40
- .set('title', 'text-h6')
41
- .set('subtitle-1', 'text-subtitle-1')
42
- .set('subtitle-2', 'text-subtitle-2')
43
- .set('body-1', 'text-body-1')
44
- .set('body-2', 'text-body-2')
45
- .set('caption', 'text-caption')
46
- .set('overline', 'text-overline')
47
- }
48
-
49
- // These components treat attributes like classes
50
- const gridComponents = ['VContainer', 'VLayout', 'VFlex', 'VSpacer']
4
+ const replacements = new Map([[/^rounded-(r|l|tr|tl|br|bl)(.*)$/, ([side, rest]) => {
5
+ side = {
6
+ r: 'e',
7
+ l: 's',
8
+ tr: 'te',
9
+ tl: 'ts',
10
+ br: 'be',
11
+ bl: 'bs'
12
+ }[side];
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}`], ['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']]);
51
21
 
52
22
  // ------------------------------------------------------------------------------
53
23
  // Rule Definition
@@ -65,95 +35,34 @@ module.exports = {
65
35
  removed: `'{{ name }}' has been removed`
66
36
  }
67
37
  },
68
-
69
- create (context) {
38
+ create(context) {
70
39
  return context.parserServices.defineTemplateBodyVisitor({
71
- VElement (element) {
72
- const tag = classify(element.rawName)
73
-
74
- if (!gridComponents.includes(tag)) return
75
-
76
- getAttributes(element).forEach(attr => {
77
- for (const replacer of replacements) {
78
- if (typeof replacer[0] === 'string' && replacer[0] === attr.name) {
79
- const replacement = replacer[1]
80
- return context.report({
81
- messageId: 'replacedWith',
82
- data: {
83
- a: attr.name,
84
- b: replacement
85
- },
86
- node: attr.node,
87
- fix (fixer) {
88
- return fixer.replaceText(attr.node, replacement)
89
- }
90
- })
91
- }
92
- if (replacer[0] instanceof RegExp) {
93
- const matches = (replacer[0].exec(attr.name) || []).slice(1)
94
- const replace = replacer[1]
95
- if (matches.length && typeof replace === 'function') {
96
- const replacement = replace(matches)
97
- return context.report({
98
- messageId: 'replacedWith',
99
- data: {
100
- a: attr.name,
101
- b: replacement
102
- },
103
- node: attr.node,
104
- fix (fixer) {
105
- return fixer.replaceText(attr.node, replacement)
106
- }
107
- })
108
- }
109
- }
110
- }
111
-
112
- // Remove <v-layout row> as it conflicts with <v-row> styles
113
- // https://github.com/vuetifyjs/vuetify/commit/3f435b5a
114
- if (tag === 'VLayout' && attr.name === 'row') {
115
- return context.report({
116
- node: attr.node,
117
- message: `Don't use "row" on <v-layout>, see https://github.com/vuetifyjs/vuetify/commit/3f435b5a`,
118
- fix (fixer) {
119
- if (!attr.node.directive) return removeAttr(context, fixer, attr.node)
120
- }
121
- })
122
- }
123
- })
124
- },
125
- 'VAttribute[key.name="class"]' (node) {
126
- if (!node.value || !node.value.value) return
127
-
128
- const classes = node.value.value.split(/\s+/).filter(s => !!s)
129
- const source = context.getSourceCode()
130
-
131
- const changed = []
40
+ 'VAttribute[key.name="class"]'(node) {
41
+ if (!node.value || !node.value.value) return;
42
+ const classes = node.value.value.split(/\s+/).filter(s => !!s);
43
+ const source = context.getSourceCode();
44
+ const changed = [];
132
45
  classes.forEach(className => {
133
46
  for (const replacer of replacements) {
134
47
  if (typeof replacer[0] === 'string' && replacer[0] === className) {
135
- return changed.push([className, replacer[1]])
48
+ return changed.push([className, replacer[1]]);
136
49
  }
137
50
  if (replacer[0] instanceof RegExp) {
138
- const matches = (replacer[0].exec(className) || []).slice(1)
139
- const replace = replacer[1]
51
+ const matches = (replacer[0].exec(className) || []).slice(1);
52
+ const replace = replacer[1];
140
53
  if (matches.length && typeof replace === 'function') {
141
- return changed.push([className, replace(matches)])
54
+ return changed.push([className, replace(matches)]);
142
55
  }
143
56
  }
144
57
  }
145
- })
146
-
58
+ });
147
59
  changed.forEach(change => {
148
- const idx = node.value.value.indexOf(change[0]) + 1
149
- const range = [
150
- node.value.range[0] + idx,
151
- node.value.range[0] + idx + change[0].length
152
- ]
60
+ const idx = node.value.value.indexOf(change[0]) + 1;
61
+ const range = [node.value.range[0] + idx, node.value.range[0] + idx + change[0].length];
153
62
  const loc = {
154
63
  start: source.getLocFromIndex(range[0]),
155
64
  end: source.getLocFromIndex(range[1])
156
- }
65
+ };
157
66
  context.report({
158
67
  loc,
159
68
  messageId: 'replacedWith',
@@ -161,12 +70,12 @@ module.exports = {
161
70
  a: change[0],
162
71
  b: change[1]
163
72
  },
164
- fix (fixer) {
165
- return fixer.replaceTextRange(range, change[1])
73
+ fix(fixer) {
74
+ return fixer.replaceTextRange(range, change[1]);
166
75
  }
167
- })
168
- })
76
+ });
77
+ });
169
78
  }
170
- })
79
+ });
171
80
  }
172
- }
81
+ };
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ const cssColors = ['red', 'pink', 'purple', 'deep-purple', 'indigo', 'blue', 'light-blue', 'cyan', 'teal', 'green', 'light-green', 'lime', 'yellow', 'amber', 'orange', 'deep-orange', 'brown', 'blue-grey', 'grey', 'black', 'white', 'transparent'];
4
+ const cssTextColors = cssColors.map(v => `${v}--text`);
5
+ const variants = ['lighten-1', 'lighten-2', 'lighten-3', 'lighten-4', 'lighten-5', 'darken-1', 'darken-2', 'darken-3', 'darken-4', 'accent-1', 'accent-2', 'accent-3', 'accent-4'];
6
+ const textVariants = variants.map(v => `text--${v}`);
7
+
8
+ // ------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ // ------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ docs: {
15
+ description: 'Disallow the use of classes that have been removed from Vuetify'
16
+ },
17
+ fixable: 'code',
18
+ schema: [{
19
+ type: 'object',
20
+ properties: {
21
+ themeColors: {
22
+ type: 'array',
23
+ items: {
24
+ type: 'string'
25
+ }
26
+ }
27
+ },
28
+ additionalProperties: false
29
+ }],
30
+ messages: {
31
+ replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`,
32
+ removed: `'{{ name }}' cannot be used alone, it must be combined with a color`
33
+ }
34
+ },
35
+ create(context) {
36
+ const themeColors = ['primary', 'secondary', 'accent', 'error', 'warning', 'info', 'success', ...(context.options[0]?.themeColors || [])];
37
+ const themeTextColors = themeColors.map(v => `${v}--text`);
38
+ function findColor(classes) {
39
+ const base = classes.findIndex(t => themeColors.includes(t) || cssColors.includes(t));
40
+ const variant = classes.findIndex(t => variants.includes(t));
41
+ return [base, variant];
42
+ }
43
+ function findTextColor(classes) {
44
+ const base = classes.findIndex(t => themeTextColors.includes(t) || cssTextColors.includes(t));
45
+ const variant = classes.findIndex(t => textVariants.includes(t));
46
+ return [base, variant];
47
+ }
48
+ return context.parserServices.defineTemplateBodyVisitor({
49
+ 'VAttribute[key.name="color"]'(node) {
50
+ if (!node.value || !node.value.value) return;
51
+ const color = node.value.value.split(/\s+/).filter(s => !!s);
52
+ const [base, variant] = findColor(color);
53
+ if (~base && ~variant) {
54
+ context.report({
55
+ node,
56
+ messageId: 'replacedWith',
57
+ data: {
58
+ a: node.value.value,
59
+ b: `${color[base]}-${color[variant]}`
60
+ },
61
+ fix: fixer => fixer.replaceTextRange(node.value.range, `"${color[base]}-${color[variant]}"`)
62
+ });
63
+ }
64
+ },
65
+ 'VAttribute[key.name="class"]'(node) {
66
+ if (!node.value || !node.value.value) return;
67
+ const classes = node.value.value.split(/\s+/).filter(s => !!s);
68
+ for (const [find, prefix] of [[findColor, 'bg'], [findTextColor, 'text']]) {
69
+ const [base, variant] = find(classes);
70
+ if (~base || ~base && ~variant) {
71
+ const newColor = ~variant ? `${prefix}-${classes[base].replace('--text', '')}-${classes[variant].replace('text--', '')}` : `${prefix}-${classes[base].replace('--text', '')}`;
72
+ context.report({
73
+ node,
74
+ messageId: 'replacedWith',
75
+ data: {
76
+ a: ~variant ? `${classes[base]} ${classes[variant]}` : classes[base],
77
+ b: newColor
78
+ },
79
+ fix: fixer => {
80
+ const newClasses = classes.slice();
81
+ newClasses.splice(base, 1, newColor);
82
+ if (~variant) newClasses.splice(variant, 1);
83
+ return fixer.replaceTextRange(node.value.range, `"${newClasses.join(' ')}"`);
84
+ }
85
+ });
86
+ } else if (~variant) {
87
+ context.report({
88
+ node,
89
+ messageId: 'removed',
90
+ data: {
91
+ name: classes[variant]
92
+ }
93
+ });
94
+ }
95
+ }
96
+ }
97
+ });
98
+ }
99
+ };
@@ -1,27 +1,43 @@
1
- 'use strict'
2
-
3
- const { hyphenate, classify } = require('../util/helpers')
4
- const { getInstalledVuetifyVersion } = require('../util/get-installed-vuetify-version')
1
+ 'use strict';
5
2
 
3
+ const {
4
+ hyphenate,
5
+ classify
6
+ } = require('../util/helpers');
6
7
  const replacements = {
7
8
  VListTile: 'v-list-item',
8
9
  VListTileAction: 'v-list-item-action',
9
- VListTileAvatar: 'v-list-item-avatar',
10
+ VListTileAvatar: false,
10
11
  VListTileActionText: 'v-list-item-action-text',
11
- VListTileContent: 'v-list-item-content',
12
+ VListTileContent: false,
12
13
  VListTileTitle: 'v-list-item-title',
13
14
  VListTileSubTitle: 'v-list-item-subtitle',
14
15
  VJumbotron: false,
15
16
  VToolbarSideIcon: 'v-app-bar-nav-icon',
16
-
17
17
  // Possible typos
18
18
  VListItemSubTitle: 'v-list-item-subtitle',
19
- VListTileSubtitle: 'v-list-item-subtitle'
20
- }
21
-
22
- if (getInstalledVuetifyVersion() >= '2.3.0') {
23
- replacements.VContent = 'v-main'
24
- }
19
+ VListTileSubtitle: 'v-list-item-subtitle',
20
+ VContent: 'v-main',
21
+ VBannerActions: false,
22
+ VBannerText: false,
23
+ VBottomSheet: false,
24
+ VCalendar: false,
25
+ VData: false,
26
+ VDataIterator: false,
27
+ VDataTable: false,
28
+ VDatePicker: false,
29
+ VOtpInput: false,
30
+ VOverflowBtn: false,
31
+ VPicker: false,
32
+ VSimpleCheckbox: 'v-checkbox-btn',
33
+ VSkeletonLoader: false,
34
+ VSparkline: false,
35
+ VSpeedDial: false,
36
+ VStepper: false,
37
+ VTimePicker: false,
38
+ VTreeview: false,
39
+ VVirtualScroll: false
40
+ };
25
41
 
26
42
  // ------------------------------------------------------------------------------
27
43
  // Rule Definition
@@ -40,15 +56,13 @@ module.exports = {
40
56
  removed: `'{{ name }}' has been removed`
41
57
  }
42
58
  },
43
- create (context) {
59
+ create(context) {
44
60
  return context.parserServices.defineTemplateBodyVisitor({
45
- VElement (element) {
46
- const tag = classify(element.rawName)
47
-
48
- const tokens = context.parserServices.getTemplateBodyTokenStore()
49
-
61
+ VElement(element) {
62
+ const tag = classify(element.rawName);
63
+ const tokens = context.parserServices.getTemplateBodyTokenStore();
50
64
  if (Object.prototype.hasOwnProperty.call(replacements, tag)) {
51
- const replacement = replacements[tag]
65
+ const replacement = replacements[tag];
52
66
  if (replacement) {
53
67
  context.report({
54
68
  node: element,
@@ -57,28 +71,27 @@ module.exports = {
57
71
  a: hyphenate(tag),
58
72
  b: replacement
59
73
  },
60
- fix (fixer) {
61
- const open = tokens.getFirstToken(element.startTag)
62
- const endTag = element.endTag
74
+ fix(fixer) {
75
+ const open = tokens.getFirstToken(element.startTag);
76
+ const endTag = element.endTag;
63
77
  if (!endTag) {
64
- return fixer.replaceText(open, `<${replacement}`)
78
+ return fixer.replaceText(open, `<${replacement}`);
65
79
  }
66
- const endTagOpen = tokens.getFirstToken(endTag)
67
- return [
68
- fixer.replaceText(open, `<${replacement}`),
69
- fixer.replaceText(endTagOpen, `</${replacement}`)
70
- ]
80
+ const endTagOpen = tokens.getFirstToken(endTag);
81
+ return [fixer.replaceText(open, `<${replacement}`), fixer.replaceText(endTagOpen, `</${replacement}`)];
71
82
  }
72
- })
83
+ });
73
84
  } else {
74
85
  context.report({
75
86
  node: element,
76
87
  messageId: 'removed',
77
- data: { name: hyphenate(tag) }
78
- })
88
+ data: {
89
+ name: hyphenate(tag)
90
+ }
91
+ });
79
92
  }
80
93
  }
81
94
  }
82
- })
95
+ });
83
96
  }
84
- }
97
+ };