Skip to content

Commit a522476

Browse files
[Fix] no-invalid-html-attribute: convert autofix to suggestion
Fixes #3458. Co-authored-by: himanshu007-creator <[email protected]> Co-authored-by: Jordan Harband <[email protected]>
1 parent 01ab399 commit a522476

File tree

7 files changed

+605
-126
lines changed

7 files changed

+605
-126
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1717
* [`no-unknown-property`]: do not check `fbs` elements ([#3494][] @brianogilvie)
1818
* [`jsx-newline`]: No newline between comments and jsx elements ([#3493][] @justmejulian)
1919
* [`jsx-no-leaked-render`]: Don't report errors on empty strings if React >= v18 ([#3488][] @himanshu007-creator)
20+
* [`no-invalid-html-attribute`]: convert autofix to suggestion ([#3474][] @himanshu007-creator @ljharb)
2021

2122
### Changed
2223
* [Docs] [`jsx-no-leaked-render`]: Remove mentions of empty strings for React 18 ([#3468][] @karlhorky)
2324

2425
[#3494]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3494
2526
[#3493]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3493
2627
[#3488]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3488
28+
[#3474]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3474
2729
[#3471]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3471
2830
[#3468]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3468
2931
[#3461]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3461

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ module.exports = [
352352
| [no-did-update-set-state](docs/rules/no-did-update-set-state.md) | Disallow usage of setState in componentDidUpdate | | | | |
353353
| [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md) | Disallow direct mutation of this.state | 💼 | | | |
354354
| [no-find-dom-node](docs/rules/no-find-dom-node.md) | Disallow usage of findDOMNode | 💼 | | | |
355-
| [no-invalid-html-attribute](docs/rules/no-invalid-html-attribute.md) | Disallow usage of invalid attributes | | 🔧 | | |
355+
| [no-invalid-html-attribute](docs/rules/no-invalid-html-attribute.md) | Disallow usage of invalid attributes | | | 💡 | |
356356
| [no-is-mounted](docs/rules/no-is-mounted.md) | Disallow usage of isMounted | 💼 | | | |
357357
| [no-multi-comp](docs/rules/no-multi-comp.md) | Disallow multiple component definition per file | | | | |
358358
| [no-namespace](docs/rules/no-namespace.md) | Enforce that namespaces are not used in React elements | | | | |

docs/rules/no-invalid-html-attribute.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Disallow usage of invalid attributes (`react/no-invalid-html-attribute`)
22

3-
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
3+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
44

55
<!-- end auto-generated rule header -->
66

lib/rules/hook-use-state.js

+5-9
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,12 @@ module.exports = {
114114
getMessageData('suggestPair', messages.suggestPair),
115115
{
116116
fix(fixer) {
117-
if (expectedSetterVariableNames.length === 0) {
118-
return;
117+
if (expectedSetterVariableNames.length > 0) {
118+
return fixer.replaceTextRange(
119+
node.parent.id.range,
120+
`[${valueVariableName}, ${expectedSetterVariableNames[0]}]`
121+
);
119122
}
120-
121-
const fix = fixer.replaceTextRange(
122-
node.parent.id.range,
123-
`[${valueVariableName}, ${expectedSetterVariableNames[0]}]`
124-
);
125-
126-
return fix;
127123
},
128124
}
129125
),

lib/rules/no-invalid-html-attribute.js

+85-39
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
const matchAll = require('string.prototype.matchall');
99
const docsUrl = require('../util/docsUrl');
1010
const report = require('../util/report');
11+
const getMessageData = require('../util/message');
1112

1213
// ------------------------------------------------------------------------------
1314
// Rule Definition
@@ -232,6 +233,11 @@ const messages = {
232233
onlyMeaningfulFor: 'The ”{{attributeName}}“ attribute only has meaning on the tags: {{tagNames}}',
233234
onlyStrings: '“{{attributeName}}” attribute only supports strings.',
234235
spaceDelimited: '”{{attributeName}}“ attribute values should be space delimited.',
236+
suggestRemoveDefault: '"remove {{attributeName}}"',
237+
suggestRemoveEmpty: '"remove empty attribute {{attributeName}}"',
238+
suggestRemoveInvalid: '“remove invalid attribute {{reportingValue}}”',
239+
suggestRemoveWhitespaces: 'remove whitespaces in “{{reportingValue}}”',
240+
suggestRemoveNonString: 'remove non-string value in “{{reportingValue}}”',
235241
};
236242

237243
function splitIntoRangedParts(node, regex) {
@@ -254,9 +260,12 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
254260
report(context, messages.onlyStrings, 'onlyStrings', {
255261
node,
256262
data: { attributeName },
257-
fix(fixer) {
258-
return fixer.remove(parentNode);
259-
},
263+
suggest: [
264+
Object.assign(
265+
getMessageData('suggestRemoveNonString', messages.suggestRemoveNonString),
266+
{ fix(fixer) { return fixer.remove(parentNode); } }
267+
),
268+
],
260269
});
261270
return;
262271
}
@@ -265,9 +274,12 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
265274
report(context, messages.noEmpty, 'noEmpty', {
266275
node,
267276
data: { attributeName },
268-
fix(fixer) {
269-
return fixer.remove(parentNode);
270-
},
277+
suggest: [
278+
Object.assign(
279+
getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty),
280+
{ fix(fixer) { return fixer.remove(node.parent); } }
281+
),
282+
],
271283
});
272284
return;
273285
}
@@ -276,16 +288,23 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
276288
for (const singlePart of singleAttributeParts) {
277289
const allowedTags = VALID_VALUES.get(attributeName).get(singlePart.value);
278290
const reportingValue = singlePart.reportingValue;
291+
292+
const suggest = [
293+
Object.assign(
294+
getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid),
295+
{ fix(fixer) { return fixer.removeRange(singlePart.range); } }
296+
),
297+
];
298+
279299
if (!allowedTags) {
300+
const data = {
301+
attributeName,
302+
reportingValue,
303+
};
280304
report(context, messages.neverValid, 'neverValid', {
281305
node,
282-
data: {
283-
attributeName,
284-
reportingValue,
285-
},
286-
fix(fixer) {
287-
return fixer.removeRange(singlePart.range);
288-
},
306+
data,
307+
suggest,
289308
});
290309
} else if (!allowedTags.has(parentNodeName)) {
291310
report(context, messages.notValidFor, 'notValidFor', {
@@ -295,9 +314,7 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
295314
reportingValue,
296315
elementName: parentNodeName,
297316
},
298-
fix(fixer) {
299-
return fixer.removeRange(singlePart.range);
300-
},
317+
suggest,
301318
});
302319
}
303320
}
@@ -324,6 +341,7 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
324341
secondValue,
325342
missingValue: Array.from(siblings).join(', '),
326343
},
344+
suggest: false,
327345
});
328346
}
329347
}
@@ -337,17 +355,23 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
337355
report(context, messages.spaceDelimited, 'spaceDelimited', {
338356
node,
339357
data: { attributeName },
340-
fix(fixer) {
341-
return fixer.removeRange(whitespacePart.range);
342-
},
358+
suggest: [
359+
Object.assign(
360+
getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces),
361+
{ fix(fixer) { return fixer.removeRange(whitespacePart.range); } }
362+
),
363+
],
343364
});
344365
} else if (whitespacePart.value !== '\u0020') {
345366
report(context, messages.spaceDelimited, 'spaceDelimited', {
346367
node,
347368
data: { attributeName },
348-
fix(fixer) {
349-
return fixer.replaceTextRange(whitespacePart.range, '\u0020');
350-
},
369+
suggest: [
370+
Object.assign(
371+
getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces),
372+
{ fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); } }
373+
),
374+
],
351375
});
352376
}
353377
}
@@ -358,10 +382,6 @@ const DEFAULT_ATTRIBUTES = ['rel'];
358382
function checkAttribute(context, node) {
359383
const attribute = node.name.name;
360384

361-
function fix(fixer) {
362-
return fixer.remove(node);
363-
}
364-
365385
const parentNodeName = node.parent.name.name;
366386
if (!COMPONENT_ATTRIBUTE_MAP.has(attribute) || !COMPONENT_ATTRIBUTE_MAP.get(attribute).has(parentNodeName)) {
367387
const tagNames = Array.from(
@@ -374,16 +394,28 @@ function checkAttribute(context, node) {
374394
attributeName: attribute,
375395
tagNames,
376396
},
377-
fix,
397+
suggest: [
398+
Object.assign(
399+
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
400+
{ fix(fixer) { return fixer.remove(node); } }
401+
),
402+
],
378403
});
379404
return;
380405
}
381406

407+
function fix(fixer) { return fixer.remove(node); }
408+
382409
if (!node.value) {
383410
report(context, messages.emptyIsMeaningless, 'emptyIsMeaningless', {
384411
node,
385412
data: { attributeName: attribute },
386-
fix,
413+
suggest: [
414+
Object.assign(
415+
getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty),
416+
{ fix }
417+
),
418+
],
387419
});
388420
return;
389421
}
@@ -404,16 +436,23 @@ function checkAttribute(context, node) {
404436
report(context, messages.onlyStrings, 'onlyStrings', {
405437
node,
406438
data: { attributeName: attribute },
407-
fix,
439+
suggest: [
440+
Object.assign(
441+
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
442+
{ fix }
443+
),
444+
],
408445
});
409-
return;
410-
}
411-
412-
if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') {
446+
} else if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') {
413447
report(context, messages.onlyStrings, 'onlyStrings', {
414448
node,
415449
data: { attributeName: attribute },
416-
fix,
450+
suggest: [
451+
Object.assign(
452+
getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
453+
{ fix }
454+
),
455+
],
417456
});
418457
}
419458
}
@@ -441,18 +480,22 @@ function checkPropValidValue(context, node, value, attribute) {
441480
attributeName: attribute,
442481
reportingValue: value.value,
443482
},
483+
suggest: [
484+
Object.assign(
485+
getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid),
486+
{ fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); } }
487+
),
488+
],
444489
});
445-
return;
446-
}
447-
448-
if (!validTagSet.has(node.arguments[0].value)) {
490+
} else if (!validTagSet.has(node.arguments[0].value)) {
449491
report(context, messages.notValidFor, 'notValidFor', {
450492
node: value,
451493
data: {
452494
attributeName: attribute,
453495
reportingValue: value.raw,
454496
elementName: node.arguments[0].value,
455497
},
498+
suggest: false,
456499
});
457500
}
458501
}
@@ -493,6 +536,7 @@ function checkCreateProps(context, node, attribute) {
493536
attributeName: attribute,
494537
tagNames,
495538
},
539+
suggest: false,
496540
});
497541

498542
// eslint-disable-next-line no-continue
@@ -505,6 +549,7 @@ function checkCreateProps(context, node, attribute) {
505549
data: {
506550
attributeName: attribute,
507551
},
552+
suggest: false,
508553
});
509554

510555
// eslint-disable-next-line no-continue
@@ -531,7 +576,6 @@ function checkCreateProps(context, node, attribute) {
531576

532577
module.exports = {
533578
meta: {
534-
fixable: 'code',
535579
docs: {
536580
description: 'Disallow usage of invalid attributes',
537581
category: 'Possible Errors',
@@ -545,6 +589,8 @@ module.exports = {
545589
enum: ['rel'],
546590
},
547591
}],
592+
type: 'suggestion',
593+
hasSuggestions: true, // eslint-disable-line eslint-plugin/require-meta-has-suggestions
548594
},
549595

550596
create(context) {

tests/helpers/parsers.js

+2
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ const parsers = {
174174
tsNew ? addComment(Object.assign({}, test, { parser: parsers['@TYPESCRIPT_ESLINT'] }), '@typescript-eslint/parser') : []
175175
);
176176
});
177+
178+
// console.log(require('util').inspect(t, { depth: null }));
177179
return t;
178180
},
179181
};

0 commit comments

Comments
 (0)