diff --git a/docs/rules/jsx-no-invalid-rel.md b/docs/rules/jsx-no-invalid-rel.md index 7bdf973fa6..3792aa6249 100644 --- a/docs/rules/jsx-no-invalid-rel.md +++ b/docs/rules/jsx-no-invalid-rel.md @@ -1,6 +1,8 @@ # Prevent usage of invalid `rel` (react/jsx-no-invalid-rel) -The JSX elements: `a`, `area`, `link`, or `form` all have a attribute called `rel`. There is is fixed list of values that have any meaning on these tags (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel)). To help with minimizing confusion while reading code, only the appropriate values should be on each attribute. +The JSX elements: `a`, `area`, `link`, or `form` all have an attribute called `rel`. +There is is fixed list of values that have any meaning on these tags (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel)). +To help with minimizing confusion while reading code, only the appropriate values should be on each attribute. ## Rule Details diff --git a/lib/rules/jsx-no-invalid-rel.js b/lib/rules/jsx-no-invalid-rel.js index 7c203c2228..271e9d0412 100644 --- a/lib/rules/jsx-no-invalid-rel.js +++ b/lib/rules/jsx-no-invalid-rel.js @@ -40,9 +40,8 @@ const standardValues = new Map(Object.entries({ })); const components = new Set(['link', 'a', 'area', 'form']); -function splitIntoRangedParts(node) { +function splitIntoRangedParts(node, regex) { const res = []; - const regex = /\s*([^\s]+)/g; const valueRangeStart = node.range[0] + 1; // the plus one is for the initial quote let match; @@ -64,7 +63,7 @@ function checkLiteralValueNode(context, node, parentNodeName) { if (typeof node.value !== 'string') { return context.report({ node, - messageId: 'onlyStrings', + message: '"rel" attribute only supports strings.', fix(fixer) { return fixer.remove(node.parent.parent); } @@ -74,23 +73,20 @@ function checkLiteralValueNode(context, node, parentNodeName) { if (!node.value.trim()) { return context.report({ node, - messageId: 'emptyRel', + message: 'An empty "rel" attribute is meaningless.', fix(fixer) { return fixer.remove(node); } }); } - const parts = splitIntoRangedParts(node); + const parts = splitIntoRangedParts(node, /([^\s]+)/g); for (const part of parts) { const allowedTags = standardValues.get(part.value); if (!allowedTags) { context.report({ node, - messageId: 'realRelValues', - data: { - value: part.reportingValue - }, + message: `${part.reportingValue} is never a valid "rel" attribute value.`, fix(fixer) { return fixer.removeRange(part.range); } @@ -98,17 +94,26 @@ function checkLiteralValueNode(context, node, parentNodeName) { } else if (!allowedTags.has(parentNodeName)) { context.report({ node, - messageId: 'matchingRelValues', - data: { - value: part.reportingValue, - tag: parentNodeName - }, + message: `${part.reportingValue} is not a valid "rel" attribute value for <${parentNodeName}>.`, fix(fixer) { return fixer.removeRange(part.range); } }); } } + + const whitespaceParts = splitIntoRangedParts(node, /(\s+)/g); + for (const whitespacePart of whitespaceParts) { + if (whitespacePart.value !== ' ' || whitespacePart.range[0] === (node.range[0] + 1) || whitespacePart.range[1] === (node.range[1] - 1)) { + context.report({ + node, + message: '"rel" attribute values should be space delimited.', + fix(fixer) { + return fixer.removeRange(whitespacePart.range); + } + }); + } + } } module.exports = { @@ -118,13 +123,6 @@ module.exports = { description: 'Forbid `rel` attribute with an invalid value`', category: 'Possible Errors', url: docsUrl('jsx-no-invalid-rel') - }, - messages: { - relOnlyOnSpecific: 'The "rel" attribute only has meaning on ``, ``, ``, and `
` tags.', - emptyRel: 'An empty "rel" attribute is meaningless.', - onlyStrings: '"rel" attribute only supports strings', - realRelValues: '{{ value }} is never a valid "rel" attribute value.', - matchingRelValues: '"{{ value }}" is not a valid "rel" attribute value for <{{ tag }}>.' } }, @@ -132,7 +130,7 @@ module.exports = { return { JSXAttribute(node) { // ignore attributes that aren't "rel" - if (node.type !== 'JSXIdentifier' && node.name.name !== 'rel') { + if (node.name.name !== 'rel') { return; } @@ -140,7 +138,7 @@ module.exports = { if (!components.has(parentNodeName)) { return context.report({ node, - messageId: 'relOnlyOnSpecific', + message: 'The "rel" attribute only has meaning on ``, ``, ``, and `` tags.', fix(fixer) { return fixer.remove(node); } @@ -150,7 +148,7 @@ module.exports = { if (!node.value) { return context.report({ node, - messageId: 'emptyRel', + message: 'An empty "rel" attribute is meaningless.', fix(fixer) { return fixer.remove(node); } @@ -165,10 +163,14 @@ module.exports = { return checkLiteralValueNode(context, node.value.expression, parentNodeName); } + if (node.value.type !== 'JSXExpressionContainer') { + return; + } + if (node.value.expression.type === 'ObjectExpression') { return context.report({ node, - messageId: 'onlyStrings', + message: '"rel" attribute only supports strings.', fix(fixer) { return fixer.remove(node); } @@ -178,7 +180,7 @@ module.exports = { if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') { return context.report({ node, - messageId: 'onlyStrings', + message: '"rel" attribute only supports strings.', fix(fixer) { return fixer.remove(node); } diff --git a/tests/lib/rules/jsx-no-invalid-rel.js b/tests/lib/rules/jsx-no-invalid-rel.js index accf326bb5..be92dced97 100644 --- a/tests/lib/rules/jsx-no-invalid-rel.js +++ b/tests/lib/rules/jsx-no-invalid-rel.js @@ -93,371 +93,447 @@ ruleTester.run('jsx-no-invalid-rel', rule, { code: '', output: '', errors: [{ - messageId: 'relOnlyOnSpecific' + message: 'The "rel" attribute only has meaning on ``, ``, ``, and `` tags.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: 'The "rel" attribute only has meaning on ``, ``, ``, and `` tags.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: 'An empty "rel" attribute is meaningless.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: 'The "rel" attribute only has meaning on ``, ``, ``, and `` tags.' }] }, { code: '', output: '', errors: [{ - messageId: 'onlyStrings' + message: '"rel" attribute only supports strings.' }] }, { code: '', output: '', errors: [{ - messageId: 'onlyStrings' + message: '"rel" attribute only supports strings.' }] }, { code: '', output: '', errors: [{ - messageId: 'onlyStrings' + message: '"rel" attribute only supports strings.' }] }, { code: '', output: '', errors: [{ - messageId: 'onlyStrings' + message: '"rel" attribute only supports strings.' }] }, { code: '', output: '', errors: [{ - messageId: 'onlyStrings' + message: '"rel" attribute only supports strings.' }] }, { code: '', + output: '', + errors: [{ + message: '"foobar" is never a valid "rel" attribute value.' + }] + }, + { + code: '', output: '', errors: [{ - messageId: 'realRelValues' + message: '"rel" attribute values should be space delimited.' }] }, { code: '', - output: '', + output: '', errors: [{ - messageId: 'realRelValues' + message: '"foobar" is never a valid "rel" attribute value.' }] }, { code: '', output: '', errors: [{ - messageId: 'realRelValues' + message: '"foobar" is never a valid "rel" attribute value.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: '"foobar" is never a valid "rel" attribute value.' + }, { + message: '"batgo" is never a valid "rel" attribute value.' + }, { + message: '"rel" attribute values should be space delimited.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: '"rel" attribute values should be space delimited.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: '"rel" attribute values should be space delimited.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: '"batgo" is never a valid "rel" attribute value.' + }, { + message: '"rel" attribute values should be space delimited.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: '"batgo" is never a valid "rel" attribute value.' + }] + }, + { + code: '', + output: '', + errors: [{ + message: '"rel" attribute values should be space delimited.' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"canonical" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"dns-prefetch" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"icon" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"manifest" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"modulepreload" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"pingback" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"preconnect" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"prefetch" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"preload" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"prerender" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"stylesheet" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"canonical" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"dns-prefetch" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"icon" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"manifest" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"modulepreload" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"pingback" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"preconnect" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"prefetch" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"preload" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"prerender" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"stylesheet" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"bookmark" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"external" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"nofollow" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"noopener" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"noreferrer" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"opener" is not a valid "rel" attribute value for .' }] }, { code: '', output: '', errors: [{ - messageId: 'matchingRelValues' + message: '"tag" is not a valid "rel" attribute value for .' }] }, { code: '', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"alternate" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"author" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"bookmark" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"canonical" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"dns-prefetch" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"icon" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"manifest" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"modulepreload" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"pingback" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"preconnect" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"prefetch" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"preload" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"prerender" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"stylesheet" is not a valid "rel" attribute value for
.' }] }, { code: '
', output: '
', errors: [{ - messageId: 'matchingRelValues' + message: '"tag" is not a valid "rel" attribute value for
.' }] } ]