Skip to content

Commit ebf2a2a

Browse files
Merge pull request #2503 from johanrd/fix/2396
Post-merge review of #2396 (`template-link-rel-noopener`)
2 parents 6aaf941 + 454833c commit ebf2a2a

2 files changed

Lines changed: 45 additions & 5 deletions

File tree

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,28 @@ 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 = relAttr?.value?.type === 'GlimmerTextNode' ? relAttr.value.chars : '';
35+
const hasNoopener = /(?:^|\s)noopener(?:\s|$)/.test(relValue);
36+
const hasNoreferrer = /(?:^|\s)noreferrer(?:\s|$)/.test(relValue);
37+
const hasProperRel = hasNoopener && hasNoreferrer;
3838

3939
if (!hasProperRel) {
4040
context.report({
4141
node: targetAttr,
4242
messageId: 'missingRel',
4343
fix(fixer) {
44+
if (relAttr && relAttr.value?.type === 'GlimmerTextNode') {
45+
// Strip existing noopener/noreferrer tokens, then re-add in canonical order
46+
// (matches ember-template-lint behavior)
47+
const oldValue = relAttr.value.chars.trim().replaceAll(/\s+/g, ' ');
48+
const filtered = oldValue
49+
.split(' ')
50+
.filter((t) => t !== 'noopener' && t !== 'noreferrer')
51+
.join(' ');
52+
const newValue = `${filtered} noopener noreferrer`.trim();
53+
return fixer.replaceText(relAttr.value, `"${newValue}"`);
54+
}
55+
// No rel attribute — insert one before the closing >
4456
const sourceCode = context.sourceCode;
4557
const openTag = sourceCode.getText(node).match(/^<a[^>]*/)[0];
4658
const insertPos = node.range[0] + openTag.length;

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,40 @@ 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="noopener noreferrer">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:
41+
'<template><a href="/" target="_blank" rel="nofollow noopener noreferrer">Link</a></template>',
42+
errors: [{ messageId: 'missingRel' }],
43+
},
1644
],
1745
});

0 commit comments

Comments
 (0)