Skip to content

Commit 9f66a51

Browse files
committed
Fix template-link-rel-noopener: fix fixer and regex matching
- Use word-boundary regex to prevent substring matches - When rel already exists with partial values, append missing values instead of inserting a duplicate rel attribute - Add test cases for reversed order, additional values, and partial rel
1 parent e20ba32 commit 9f66a51

2 files changed

Lines changed: 50 additions & 5 deletions

File tree

lib/rules/template-link-rel-noopener.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,34 @@ module.exports = {
3131
}
3232

3333
const relAttr = node.attributes?.find((a) => a.name === 'rel');
34-
const hasProperRel =
35-
relAttr?.value?.type === 'GlimmerTextNode' &&
36-
/noopener/.test(relAttr.value.chars) &&
37-
/noreferrer/.test(relAttr.value.chars);
34+
const relValue =
35+
relAttr?.value?.type === 'GlimmerTextNode' ? relAttr.value.chars : '';
36+
const hasNoopener = /(?:^|\s)noopener(?:\s|$)/.test(relValue);
37+
const hasNoreferrer = /(?:^|\s)noreferrer(?:\s|$)/.test(relValue);
38+
const hasProperRel = hasNoopener && hasNoreferrer;
3839

3940
if (!hasProperRel) {
4041
context.report({
4142
node: targetAttr,
4243
messageId: 'missingRel',
4344
fix(fixer) {
45+
if (relAttr && relAttr.value?.type === 'GlimmerTextNode') {
46+
// rel attribute exists with a text value — append missing values
47+
const oldValue = relAttr.value.chars;
48+
const parts = oldValue.trim().split(/\s+/).filter(Boolean);
49+
if (!hasNoopener) {
50+
parts.push('noopener');
51+
}
52+
if (!hasNoreferrer) {
53+
parts.push('noreferrer');
54+
}
55+
const newValue = parts.join(' ');
56+
return fixer.replaceTextRange(
57+
[relAttr.value.range[0], relAttr.value.range[1]],
58+
newValue
59+
);
60+
}
61+
// No rel attribute — insert one before the closing >
4462
const sourceCode = context.sourceCode;
4563
const openTag = sourceCode.getText(node).match(/^<a[^>]*/)[0];
4664
const insertPos = node.range[0] + openTag.length;

tests/lib/rules/template-link-rel-noopener.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,39 @@ const ruleTester = new RuleTester({
66
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
77
});
88
ruleTester.run('template-link-rel-noopener', rule, {
9-
valid: ['<template><a href="/" target="_blank" rel="noopener noreferrer">Link</a></template>'],
9+
valid: [
10+
'<template><a href="/" target="_blank" rel="noopener noreferrer">Link</a></template>',
11+
// reversed order
12+
'<template><a href="/" target="_blank" rel="noreferrer noopener">Link</a></template>',
13+
// with additional values
14+
'<template><a href="/" target="_blank" rel="nofollow noreferrer noopener">Link</a></template>',
15+
// no target="_blank" means no rel required
16+
'<template><a href="/">Link</a></template>',
17+
],
1018
invalid: [
19+
// no rel attribute at all
1120
{
1221
code: '<template><a href="/" target="_blank">Link</a></template>',
1322
output: '<template><a href="/" target="_blank" rel="noopener noreferrer">Link</a></template>',
1423
errors: [{ messageId: 'missingRel' }],
1524
},
25+
// rel="noopener" only — missing noreferrer
26+
{
27+
code: '<template><a href="/" target="_blank" rel="noopener">Link</a></template>',
28+
output: '<template><a href="/" target="_blank" rel="noopener noreferrer">Link</a></template>',
29+
errors: [{ messageId: 'missingRel' }],
30+
},
31+
// rel="noreferrer" only — missing noopener
32+
{
33+
code: '<template><a href="/" target="_blank" rel="noreferrer">Link</a></template>',
34+
output: '<template><a href="/" target="_blank" rel="noreferrer noopener">Link</a></template>',
35+
errors: [{ messageId: 'missingRel' }],
36+
},
37+
// rel="nofollow" — present but wrong values
38+
{
39+
code: '<template><a href="/" target="_blank" rel="nofollow">Link</a></template>',
40+
output: '<template><a href="/" target="_blank" rel="nofollow noopener noreferrer">Link</a></template>',
41+
errors: [{ messageId: 'missingRel' }],
42+
},
1643
],
1744
});

0 commit comments

Comments
 (0)