eslint-plugin-vuetify 2.5.3 → 2.7.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.
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ classify,
5
+ isVueTemplate
6
+ } = require('../util/helpers');
7
+ module.exports = {
8
+ meta: {
9
+ docs: {
10
+ description: 'Disallow usage of specified component slots, with optional replacements'
11
+ },
12
+ fixable: 'code',
13
+ schema: [{
14
+ type: 'object',
15
+ additionalProperties: {
16
+ type: 'object',
17
+ additionalProperties: {
18
+ oneOf: [{
19
+ type: 'string'
20
+ }, {
21
+ type: 'boolean',
22
+ enum: [false]
23
+ }, {
24
+ type: 'object',
25
+ properties: {
26
+ message: {
27
+ type: 'string'
28
+ }
29
+ },
30
+ required: ['message'],
31
+ additionalProperties: false
32
+ }]
33
+ }
34
+ }
35
+ }],
36
+ messages: {
37
+ replacedWith: `{{ component }}'s '{{ slot }}' slot has been replaced with '{{ newSlot }}'`,
38
+ removed: `{{ component }}'s '{{ slot }}' slot has been removed`,
39
+ removedWithMessage: `{{ component }}'s '{{ slot }}' slot has been removed: {{ message }}`
40
+ }
41
+ },
42
+ create(context) {
43
+ if (!isVueTemplate(context)) return {};
44
+ const options = context.options[0];
45
+ if (!options || !Object.keys(options).length) return {};
46
+
47
+ // Normalize keys to PascalCase
48
+ const replacements = new Map();
49
+ for (const [component, slots] of Object.entries(options)) {
50
+ replacements.set(classify(component), slots);
51
+ }
52
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
53
+ VElement(node) {
54
+ if (node.name !== 'template' || node.parent.type !== 'VElement') return;
55
+ const tag = classify(node.parent.name);
56
+ if (!replacements.has(tag)) return;
57
+ const slots = replacements.get(tag);
58
+ const directive = node.startTag.attributes.find(attr => {
59
+ return attr.directive && attr.key.name.name === 'slot' && attr.key.argument?.name && slots[attr.key.argument.name] !== undefined;
60
+ });
61
+ if (!directive) return;
62
+ const slotName = directive.key.argument.name;
63
+ const replace = slots[slotName];
64
+ if (replace === false) {
65
+ context.report({
66
+ messageId: 'removed',
67
+ data: {
68
+ component: node.parent.name,
69
+ slot: slotName
70
+ },
71
+ node: directive
72
+ });
73
+ } else if (typeof replace === 'string') {
74
+ context.report({
75
+ messageId: 'replacedWith',
76
+ data: {
77
+ component: node.parent.name,
78
+ slot: slotName,
79
+ newSlot: replace
80
+ },
81
+ node: directive,
82
+ fix(fixer) {
83
+ return fixer.replaceText(directive.key.argument, replace);
84
+ }
85
+ });
86
+ } else if (typeof replace === 'object' && replace !== null) {
87
+ context.report({
88
+ messageId: 'removedWithMessage',
89
+ data: {
90
+ component: node.parent.name,
91
+ slot: slotName,
92
+ message: replace.message
93
+ },
94
+ node: directive
95
+ });
96
+ }
97
+ }
98
+ });
99
+ }
100
+ };
@@ -33,7 +33,7 @@ const tags = Object.keys(VGrid).reduce((t, k) => {
33
33
  module.exports = {
34
34
  meta: {
35
35
  docs: {
36
- description: 'warn about unknown attributes not being converted to classes on new grid components',
36
+ description: 'Warn about v1 grid attributes not being auto-converted to classes in v2.',
37
37
  category: 'recommended'
38
38
  },
39
39
  fixable: 'code',
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const {
7
+ addClass,
8
+ removeAttr
9
+ } = require('../util/fixers');
10
+ module.exports = {
11
+ meta: {
12
+ docs: {
13
+ description: 'Disallow the `border` prop; use Tailwind border utilities instead.',
14
+ category: 'tailwindcss'
15
+ },
16
+ fixable: 'code',
17
+ schema: [],
18
+ messages: {
19
+ replacedWith: `'border="{{ value }}"' should be replaced with class="{{ className }}"`,
20
+ noFix: `'border' prop should be replaced with Tailwind border utility classes`
21
+ }
22
+ },
23
+ create(context) {
24
+ if (!isVueTemplate(context)) return {};
25
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
26
+ VAttribute(attr) {
27
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
28
+ const propName = attr.directive ? attr.key.argument.rawName : attr.key.rawName;
29
+ if (propName !== 'border') return;
30
+ const element = attr.parent.parent;
31
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
32
+
33
+ // Boolean attribute (no value) — `border` with no `="..."`
34
+ if (!attr.directive && !attr.value) {
35
+ context.report({
36
+ messageId: 'replacedWith',
37
+ data: {
38
+ value: '',
39
+ className: 'border'
40
+ },
41
+ node: propNameNode,
42
+ fix(fixer) {
43
+ return [addClass(context, fixer, element, 'border'), removeAttr(context, fixer, attr)];
44
+ }
45
+ });
46
+ return;
47
+ }
48
+
49
+ // Get static value
50
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? String(attr.value.expression.value) : null : attr.value?.value;
51
+ if (value != null) {
52
+ const className = value.split(/\s+/).filter(Boolean).map(part => `border-${part}`).join(' ');
53
+ context.report({
54
+ messageId: 'replacedWith',
55
+ data: {
56
+ value,
57
+ className
58
+ },
59
+ node: propNameNode,
60
+ fix(fixer) {
61
+ return [addClass(context, fixer, element, className), removeAttr(context, fixer, attr)];
62
+ }
63
+ });
64
+ } else {
65
+ context.report({
66
+ messageId: 'noFix',
67
+ node: propNameNode
68
+ });
69
+ }
70
+ }
71
+ });
72
+ }
73
+ };
@@ -31,7 +31,6 @@ const replacements = {
31
31
  custom: '`v-list-item` with `icon` props, or `v-icon` in the list item append or prepend slot'
32
32
  },
33
33
  VOverflowBtn: false,
34
- VPicker: false,
35
34
  VSimpleCheckbox: 'v-checkbox-btn',
36
35
  VSubheader: {
37
36
  custom: 'v-list-subheader or class="text-subtitle-2"'
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ hyphenate,
5
+ classify,
6
+ isVueTemplate
7
+ } = require('../util/helpers');
8
+
9
+ // VSnackbarQueue: #default slot was renamed to #item in Vuetify 4
10
+ const slotRenames = [{
11
+ component: 'VSnackbarQueue',
12
+ from: 'default',
13
+ to: 'item'
14
+ }];
15
+
16
+ // VSnackbar props deprecated in Vuetify 4
17
+ const propReplacements = {
18
+ VSnackbar: {
19
+ multiLine: {
20
+ name: 'min-height',
21
+ value: '68'
22
+ }
23
+ }
24
+ };
25
+
26
+ // ------------------------------------------------------------------------------
27
+ // Rule Definition
28
+ // ------------------------------------------------------------------------------
29
+
30
+ module.exports = {
31
+ meta: {
32
+ docs: {
33
+ description: 'Disallow deprecated props and slots on Vuetify snackbar components.',
34
+ category: 'recommended'
35
+ },
36
+ fixable: 'code',
37
+ schema: [],
38
+ messages: {
39
+ replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`,
40
+ renamed: `{{ component }}'s '{{ slot }}' slot has been renamed to '{{ newSlot }}'`
41
+ }
42
+ },
43
+ create(context) {
44
+ if (!isVueTemplate(context)) return {};
45
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
46
+ VAttribute(attr) {
47
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
48
+ const tag = classify(attr.parent.parent.rawName);
49
+ if (!Object.keys(propReplacements).includes(tag)) return;
50
+ const propName = attr.directive ? hyphenate(attr.key.argument.rawName) : hyphenate(attr.key.rawName);
51
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
52
+ Object.entries(propReplacements[tag]).forEach(([test, replace]) => {
53
+ if (hyphenate(test) !== propName) return;
54
+ const oldValue = attr.directive ? context.sourceCode.getText(attr.value.expression) : attr.value?.value;
55
+ const value = typeof replace.value === 'function' ? replace.value(oldValue) : replace.value;
56
+ if (value == null || value === oldValue) return;
57
+ context.report({
58
+ messageId: 'replacedWith',
59
+ data: {
60
+ a: propName,
61
+ b: `${replace.name}="${value}"`
62
+ },
63
+ node: propNameNode,
64
+ fix(fixer) {
65
+ if (attr.directive && replace.bind !== false) {
66
+ if (replace.bind) {
67
+ return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${value}"`)];
68
+ } else {
69
+ const expression = context.sourceCode.getText(attr.value.expression);
70
+ return [fixer.replaceText(propNameNode, replace.name), fixer.replaceText(attr.value, `"${expression} ? '${value}' : undefined"`)];
71
+ }
72
+ } else {
73
+ return fixer.replaceText(attr, `${replace.bind ? ':' : ''}${replace.name}="${value}"`);
74
+ }
75
+ }
76
+ });
77
+ });
78
+ },
79
+ VElement(node) {
80
+ if (node.name !== 'template' || node.parent.type !== 'VElement') return;
81
+ const parentName = classify(node.parent.name);
82
+ for (const {
83
+ component,
84
+ from,
85
+ to
86
+ } of slotRenames) {
87
+ if (parentName !== component) continue;
88
+ const directive = node.startTag.attributes.find(attr => {
89
+ return attr.directive && attr.key.name.name === 'slot' && attr.key.argument?.name === from;
90
+ });
91
+ if (!directive) continue;
92
+ context.report({
93
+ node: directive,
94
+ messageId: 'renamed',
95
+ data: {
96
+ component: node.parent.name,
97
+ slot: from,
98
+ newSlot: to
99
+ },
100
+ fix(fixer) {
101
+ return fixer.replaceText(directive.key.argument, to);
102
+ }
103
+ });
104
+ }
105
+ }
106
+ });
107
+ }
108
+ };
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const md2 = {
7
+ 'display-4': 'text-h1',
8
+ 'display-3': 'text-h2',
9
+ 'display-2': 'text-h3',
10
+ 'display-1': 'text-h4',
11
+ headline: 'text-h5',
12
+ title: 'text-h6',
13
+ subheading: 'text-subtitle-1',
14
+ 'subtitle-1': 'text-subtitle-1',
15
+ 'subtitle-2': 'text-subtitle-2',
16
+ 'body-1': 'text-body-1',
17
+ 'body-2': 'text-body-2',
18
+ caption: 'text-caption',
19
+ overline: 'text-overline'
20
+ };
21
+ const md3 = {
22
+ 'text-h1': 'text-display-large',
23
+ 'text-h2': 'text-display-medium',
24
+ 'text-h3': 'text-display-small',
25
+ 'text-h4': 'text-headline-large',
26
+ 'text-h5': 'text-headline-medium',
27
+ 'text-h6': 'text-headline-small',
28
+ 'text-subtitle-1': 'text-body-large',
29
+ 'text-subtitle-2': 'text-label-large',
30
+ 'text-body-1': 'text-body-large',
31
+ 'text-body-2': 'text-body-medium',
32
+ 'text-caption': 'text-body-small',
33
+ 'text-button': 'text-label-large',
34
+ 'text-overline': 'text-label-small'
35
+ };
36
+
37
+ // ------------------------------------------------------------------------------
38
+ // Rule Definition
39
+ // ------------------------------------------------------------------------------
40
+
41
+ module.exports = {
42
+ md2,
43
+ md3,
44
+ meta: {
45
+ docs: {
46
+ description: 'Disallow deprecated MD2 typography classes, with configurable replacements.',
47
+ category: 'recommended'
48
+ },
49
+ fixable: 'code',
50
+ schema: [{
51
+ type: 'object',
52
+ additionalProperties: {
53
+ oneOf: [{
54
+ type: 'string'
55
+ }, {
56
+ type: 'boolean',
57
+ enum: [false]
58
+ }]
59
+ }
60
+ }],
61
+ messages: {
62
+ replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`,
63
+ removed: `'{{ name }}' has been removed`
64
+ }
65
+ },
66
+ create(context) {
67
+ if (!isVueTemplate(context)) return {};
68
+ const replacements = {
69
+ ...(context.options[0] || md3)
70
+ };
71
+
72
+ // Remove entries the user set to false
73
+ for (const key of Object.keys(replacements)) {
74
+ if (replacements[key] === false) delete replacements[key];
75
+ }
76
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
77
+ 'VAttribute[key.name="class"]'(node) {
78
+ if (!node.value || !node.value.value) return;
79
+ const classes = node.value.value.split(/\s+/).filter(Boolean);
80
+ classes.forEach(className => {
81
+ const replace = replacements[className];
82
+ if (replace == null) return;
83
+ const idx = node.value.value.indexOf(className) + 1;
84
+ const range = [node.value.range[0] + idx, node.value.range[0] + idx + className.length];
85
+ const loc = {
86
+ start: context.sourceCode.getLocFromIndex(range[0]),
87
+ end: context.sourceCode.getLocFromIndex(range[1])
88
+ };
89
+ if (typeof replace === 'string') {
90
+ context.report({
91
+ loc,
92
+ messageId: 'replacedWith',
93
+ data: {
94
+ a: className,
95
+ b: replace
96
+ },
97
+ fix(fixer) {
98
+ return fixer.replaceTextRange(range, replace);
99
+ }
100
+ });
101
+ }
102
+ });
103
+ }
104
+ });
105
+ }
106
+ };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const MAX_ELEVATION = 5;
7
+
8
+ // Match elevation-N in a class string, return the number
9
+ const elevationRegex = /\belevation-(\d+)\b/g;
10
+ function checkClassValue(context, node, value) {
11
+ let match;
12
+ elevationRegex.lastIndex = 0;
13
+ while ((match = elevationRegex.exec(value)) !== null) {
14
+ const level = parseInt(match[1], 10);
15
+ if (level > MAX_ELEVATION) {
16
+ context.report({
17
+ messageId: 'overflow',
18
+ data: {
19
+ level: String(level),
20
+ max: String(MAX_ELEVATION)
21
+ },
22
+ node
23
+ });
24
+ }
25
+ }
26
+ }
27
+
28
+ // ------------------------------------------------------------------------------
29
+ // Rule Definition
30
+ // ------------------------------------------------------------------------------
31
+
32
+ module.exports = {
33
+ meta: {
34
+ docs: {
35
+ description: 'Disallow elevation classes above the MD3 maximum (0–5).',
36
+ category: 'recommended'
37
+ },
38
+ fixable: null,
39
+ schema: [],
40
+ messages: {
41
+ overflow: 'Elevation level {{ level }} exceeds the MD3 maximum of {{ max }}.'
42
+ }
43
+ },
44
+ create(context) {
45
+ if (!isVueTemplate(context)) return {};
46
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
47
+ VAttribute(attr) {
48
+ // Static class="elevation-10"
49
+ if (!attr.directive && attr.key.rawName === 'class' && attr.value) {
50
+ checkClassValue(context, attr, attr.value.value);
51
+ return;
52
+ }
53
+
54
+ // :class="'elevation-10'" or :class="`elevation-${n}`" — check literal strings
55
+ if (attr.directive && attr.key.name.name === 'bind' && attr.key.argument?.rawName === 'class' && attr.value?.expression?.type === 'Literal' && typeof attr.value.expression.value === 'string') {
56
+ checkClassValue(context, attr, attr.value.expression.value);
57
+ return;
58
+ }
59
+
60
+ // :elevation="10" or elevation="10" on any component
61
+ const propName = attr.directive ? attr.key.argument?.rawName : attr.key.rawName;
62
+ if (propName !== 'elevation') return;
63
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? attr.value.expression.value : null : attr.value ? Number(attr.value.value) : null;
64
+ if (typeof value === 'number' && value > MAX_ELEVATION) {
65
+ context.report({
66
+ messageId: 'overflow',
67
+ data: {
68
+ level: String(value),
69
+ max: String(MAX_ELEVATION)
70
+ },
71
+ node: attr
72
+ });
73
+ }
74
+ }
75
+ });
76
+ }
77
+ };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ isVueTemplate
5
+ } = require('../util/helpers');
6
+ const {
7
+ addClass,
8
+ removeAttr
9
+ } = require('../util/fixers');
10
+ const validElevations = new Set(['0', '1', '2', '3', '4', '5']);
11
+ module.exports = {
12
+ meta: {
13
+ docs: {
14
+ description: 'Disallow the `elevation` prop; use Tailwind shadow utilities instead.',
15
+ category: 'tailwindcss'
16
+ },
17
+ fixable: 'code',
18
+ schema: [],
19
+ messages: {
20
+ replacedWith: `'elevation="{{ value }}"' should be replaced with class="{{ className }}"`,
21
+ noFix: `'elevation' prop should be replaced with a Tailwind shadow utility class`
22
+ }
23
+ },
24
+ create(context) {
25
+ if (!isVueTemplate(context)) return {};
26
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
27
+ VAttribute(attr) {
28
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
29
+ const propName = attr.directive ? attr.key.argument.rawName : attr.key.rawName;
30
+ if (propName !== 'elevation') return;
31
+ const element = attr.parent.parent;
32
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
33
+
34
+ // Get static value
35
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? String(attr.value.expression.value) : null : attr.value?.value;
36
+ if (value != null && validElevations.has(value)) {
37
+ const className = `elevation-${value}`;
38
+ context.report({
39
+ messageId: 'replacedWith',
40
+ data: {
41
+ value,
42
+ className
43
+ },
44
+ node: propNameNode,
45
+ fix(fixer) {
46
+ return [addClass(context, fixer, element, className), removeAttr(context, fixer, attr)];
47
+ }
48
+ });
49
+ } else {
50
+ context.report({
51
+ messageId: 'noFix',
52
+ node: propNameNode
53
+ });
54
+ }
55
+ }
56
+ });
57
+ }
58
+ };
@@ -0,0 +1,141 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ hyphenate,
5
+ classify,
6
+ isVueTemplate
7
+ } = require('../util/helpers');
8
+ const {
9
+ addClass,
10
+ removeAttr
11
+ } = require('../util/fixers');
12
+ const breakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];
13
+
14
+ // VRow props that should become utility classes
15
+ const rowPropToClass = {
16
+ align: value => `align-${value}`,
17
+ justify: value => `justify-${value}`,
18
+ 'align-content': value => `align-content-${value}`
19
+ };
20
+
21
+ // Generate responsive variants: align-sm, align-md, justify-sm, etc.
22
+ for (const [prop, fn] of Object.entries({
23
+ ...rowPropToClass
24
+ })) {
25
+ for (const bp of breakpoints) {
26
+ rowPropToClass[`${prop}-${bp}`] = value => `${fn(value).replace(value, '')}-${bp}-${value}`;
27
+ }
28
+ }
29
+ // Fix responsive class generation — the base fn closures captured value incorrectly, redo properly
30
+ for (const bp of breakpoints) {
31
+ rowPropToClass[`align-${bp}`] = value => `align-${bp}-${value}`;
32
+ rowPropToClass[`justify-${bp}`] = value => `justify-${bp}-${value}`;
33
+ rowPropToClass[`align-content-${bp}`] = value => `align-content-${bp}-${value}`;
34
+ }
35
+
36
+ // VCol props that should become utility classes
37
+ const colPropToClass = {
38
+ order: value => `order-${value}`,
39
+ 'align-self': value => `align-self-${value}`
40
+ };
41
+ for (const bp of breakpoints) {
42
+ colPropToClass[`order-${bp}`] = value => `order-${bp}-${value}`;
43
+ }
44
+ const replacements = {
45
+ VRow: {
46
+ props: rowPropToClass,
47
+ renamed: {
48
+ dense: {
49
+ name: 'density',
50
+ value: 'compact'
51
+ }
52
+ }
53
+ },
54
+ VCol: {
55
+ props: colPropToClass,
56
+ renamed: {}
57
+ }
58
+ };
59
+
60
+ // ------------------------------------------------------------------------------
61
+ // Rule Definition
62
+ // ------------------------------------------------------------------------------
63
+
64
+ module.exports = {
65
+ meta: {
66
+ docs: {
67
+ description: 'Prevent the use of removed grid props.',
68
+ category: 'recommended'
69
+ },
70
+ fixable: 'code',
71
+ schema: [],
72
+ messages: {
73
+ movedToClass: `'{{ prop }}' has been removed, use class="{{ className }}" instead`,
74
+ replacedWith: `'{{ a }}' has been replaced with '{{ b }}'`
75
+ }
76
+ },
77
+ create(context) {
78
+ if (!isVueTemplate(context)) return {};
79
+ return context.sourceCode.parserServices.defineTemplateBodyVisitor({
80
+ VAttribute(attr) {
81
+ if (attr.directive && (attr.key.name.name !== 'bind' || !attr.key.argument)) return;
82
+ const tag = classify(attr.parent.parent.rawName);
83
+ const config = replacements[tag];
84
+ if (!config) return;
85
+ const propName = attr.directive ? hyphenate(attr.key.argument.rawName) : hyphenate(attr.key.rawName);
86
+ const propNameNode = attr.directive ? attr.key.argument : attr.key;
87
+ const element = attr.parent.parent;
88
+
89
+ // Check for props that should become utility classes
90
+ if (config.props[propName]) {
91
+ const toClass = config.props[propName];
92
+
93
+ // Need a static value to auto-fix
94
+ const value = attr.directive ? attr.value?.expression?.type === 'Literal' ? String(attr.value.expression.value) : null : attr.value?.value;
95
+ if (value) {
96
+ const className = toClass(value);
97
+ context.report({
98
+ messageId: 'movedToClass',
99
+ data: {
100
+ prop: propName,
101
+ className
102
+ },
103
+ node: propNameNode,
104
+ fix(fixer) {
105
+ return [addClass(context, fixer, element, className), removeAttr(context, fixer, attr)];
106
+ }
107
+ });
108
+ } else {
109
+ // Dynamic value — can't auto-fix but still report
110
+ const className = toClass('<value>');
111
+ context.report({
112
+ messageId: 'movedToClass',
113
+ data: {
114
+ prop: propName,
115
+ className
116
+ },
117
+ node: propNameNode
118
+ });
119
+ }
120
+ return;
121
+ }
122
+
123
+ // Check for renamed props (e.g. dense -> density="compact")
124
+ const renamed = config.renamed[propName];
125
+ if (renamed) {
126
+ context.report({
127
+ messageId: 'replacedWith',
128
+ data: {
129
+ a: propName,
130
+ b: `${renamed.name}="${renamed.value}"`
131
+ },
132
+ node: propNameNode,
133
+ fix(fixer) {
134
+ return fixer.replaceText(attr, `${renamed.name}="${renamed.value}"`);
135
+ }
136
+ });
137
+ }
138
+ }
139
+ });
140
+ }
141
+ };