Skip to content

Commit 91ee4d5

Browse files
V2dhaljharb
authored andcommitted
[Fix] jsx-no-target-blank: False negative when rel attribute is assigned using ConditionalExpression
1 parent b3a3937 commit 91ee4d5

File tree

3 files changed

+75
-8
lines changed

3 files changed

+75
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
1919
* [`display-name`]: fix identifying `_` as a capital letter ([#3335][] @apbarrero)
2020
* [`require-default-props`]: avoid a crash when function has no props param ([#3350][] @noahnu)
2121
* [`display-name`], component detection: fix HOF returning null as Components ([#3347][] @jxm-math)
22+
* [`jsx-no-target-blank`]: False negative when rel attribute is assigned using ConditionalExpression ([#3332][] @V2dha)
2223

2324
### Changed
2425
* [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223)
@@ -35,6 +36,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
3536
[#3339]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3339
3637
[#3338]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3338
3738
[#3335]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3335
39+
[#3332]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3332
3840
[#3331]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3331
3941
[#3328]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3328
4042
[#3327]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3327

lib/rules/jsx-no-target-blank.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ function hasDynamicLink(node, linkAttribute) {
6565
}
6666
}
6767

68+
function attributeValuePossiblyRel(value) {
69+
if (typeof value === 'string') {
70+
return true;
71+
}
72+
}
73+
6874
function getStringFromValue(value) {
6975
if (value) {
7076
if (value.type === 'Literal') {
@@ -74,6 +80,15 @@ function getStringFromValue(value) {
7480
if (value.expression.type === 'TemplateLiteral') {
7581
return value.expression.quasis[0].value.cooked;
7682
}
83+
const expr = value.expression;
84+
if (expr.type === 'ConditionalExpression') {
85+
if (
86+
attributeValuePossiblyRel(expr.consequent.value)
87+
&& attributeValuePossiblyRel(expr.alternate.value)
88+
) {
89+
return [expr.consequent.value, expr.alternate.value];
90+
}
91+
}
7792
return value.expression && value.expression.value;
7893
}
7994
}
@@ -88,12 +103,15 @@ function hasSecureRel(node, allowReferrer, warnOnSpreadAttributes, spreadAttribu
88103

89104
const relAttribute = node.attributes[relIndex];
90105
const value = getStringFromValue(relAttribute.value);
91-
const tags = value && typeof value === 'string' && value.toLowerCase().split(' ');
92-
const noreferrer = tags && tags.indexOf('noreferrer') >= 0;
93-
if (noreferrer) {
94-
return true;
95-
}
96-
return allowReferrer && tags && tags.indexOf('noopener') >= 0;
106+
return [].concat(value).filter(Boolean).every((item) => {
107+
const tags = typeof item === 'string' && item.toLowerCase().split(' ');
108+
const noreferrer = tags && tags.indexOf('noreferrer') >= 0;
109+
const noopener = tags && tags.indexOf('noopener') >= 0;
110+
if (noreferrer) {
111+
return true;
112+
}
113+
return allowReferrer && noopener;
114+
});
97115
}
98116

99117
const messages = {

tests/lib/rules/jsx-no-target-blank.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const parserOptions = {
2828

2929
const ruleTester = new RuleTester({ parserOptions });
3030
const defaultErrors = [{ messageId: 'noTargetBlankWithoutNoreferrer' }];
31+
const allowReferrerErrors = [{ messageId: 'noTargetBlankWithoutNoopener' }];
3132

3233
ruleTester.run('jsx-no-target-blank', rule, {
3334
valid: parsers.all([
@@ -141,6 +142,19 @@ ruleTester.run('jsx-no-target-blank', rule, {
141142
{
142143
code: '<a href target="_blank"/>',
143144
},
145+
{
146+
code: '<a href={href} target={isExternal ? "_blank" : undefined} rel="noopener noreferrer" />',
147+
},
148+
{
149+
code: '<a href={href} target={isExternal ? undefined : "_blank"} rel={isExternal ? "noreferrer" : "noopener noreferrer"} />',
150+
},
151+
{
152+
code: '<a href={href} target={isExternal ? undefined : "_blank"} rel={isExternal ? "noreferrer noopener" : "noreferrer"} />',
153+
},
154+
{
155+
code: '<a href={href} target="_blank" rel={isExternal ? "noreferrer" : "noopener"} />',
156+
options: [{ allowReferrer: true }],
157+
},
144158
]),
145159
invalid: parsers.all([
146160
{
@@ -251,13 +265,13 @@ ruleTester.run('jsx-no-target-blank', rule, {
251265
code: '<a href="https://example.com/20" target="_blank" rel></a>',
252266
output: '<a href="https://example.com/20" target="_blank" rel="noopener"></a>',
253267
options: [{ allowReferrer: true }],
254-
errors: [{ messageId: 'noTargetBlankWithoutNoopener' }],
268+
errors: allowReferrerErrors,
255269
},
256270
{
257271
code: '<a href="https://example.com/20" target="_blank"></a>',
258272
output: '<a href="https://example.com/20" target="_blank" rel="noopener"></a>',
259273
options: [{ allowReferrer: true }],
260-
errors: [{ messageId: 'noTargetBlankWithoutNoopener' }],
274+
errors: allowReferrerErrors,
261275
},
262276
{
263277
code: '<a target="_blank" href={ dynamicLink }></a>',
@@ -352,5 +366,38 @@ ruleTester.run('jsx-no-target-blank', rule, {
352366
options: [{ forms: true, links: false }],
353367
errors: defaultErrors,
354368
},
369+
{
370+
code: '<a href={href} target="_blank" rel={isExternal ? "undefined" : "undefined"} />',
371+
errors: defaultErrors,
372+
},
373+
{
374+
code: '<a href={href} target="_blank" rel={isExternal ? "noopener" : undefined} />',
375+
errors: defaultErrors,
376+
},
377+
{
378+
code: '<a href={href} target="_blank" rel={isExternal ? "undefined" : "noopener"} />',
379+
errors: defaultErrors,
380+
},
381+
{
382+
code: '<a href={href} target={isExternal ? "_blank" : undefined} rel={isExternal ? "noopener noreferrer" : undefined} />',
383+
errors: defaultErrors,
384+
},
385+
{
386+
code: '<a href={href} target={isExternal ? "_blank" : undefined} rel={isExternal ? undefined : "noopener noreferrer"} />',
387+
errors: defaultErrors,
388+
},
389+
{
390+
code: '<a href={href} target="_blank" rel={isExternal ? 3 : "noopener noreferrer"} />',
391+
errors: defaultErrors,
392+
},
393+
{
394+
code: '<a href={href} target="_blank" rel={isExternal ? "noopener noreferrer" : "3"} />',
395+
errors: defaultErrors,
396+
},
397+
{
398+
code: '<a href={href} target="_blank" rel={isExternal ? "noopener" : "2"} />',
399+
options: [{ allowReferrer: true }],
400+
errors: allowReferrerErrors,
401+
},
355402
]),
356403
});

0 commit comments

Comments
 (0)