Skip to content

Commit eb5b7d1

Browse files
Merge pull request #2652 from johanrd/night_fix/template-no-link-to-tagname
Post-merge-review: Fix `template-no-link-to-tagname`: only flag `@tagName`, not bare `tagName`, on angle-bracket `<LinkTo>`
2 parents 4f5da70 + 5990adc commit eb5b7d1

File tree

2 files changed

+98
-22
lines changed

2 files changed

+98
-22
lines changed

lib/rules/template-no-link-to-tagname.js

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,3 @@
1-
/**
2-
* @param {any} node
3-
* @returns {boolean}
4-
*/
5-
function isLinkToComponent(node) {
6-
if (node.type === 'GlimmerElementNode') {
7-
return node.tag === 'LinkTo' || node.tag === 'link-to';
8-
}
9-
return false;
10-
}
11-
121
/** @type {import('eslint').Rule.RuleModule} */
132
module.exports = {
143
meta: {
@@ -21,7 +10,8 @@ module.exports = {
2110
},
2211
schema: [],
2312
messages: {
24-
noLinkToTagname: 'tagName attribute on LinkTo is deprecated',
13+
noLinkToTagname:
14+
'@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',
2515
},
2616
originallyFrom: {
2717
name: 'ember-template-lint',
@@ -32,6 +22,27 @@ module.exports = {
3222
},
3323

3424
create(context) {
25+
const filename = context.filename;
26+
const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');
27+
28+
// In HBS, `LinkTo` almost always refers to Ember's router link component; a
29+
// user-defined `link-to` could shadow it, but detecting that in classic HBS
30+
// would require resolver-level info the rule doesn't have.
31+
// In GJS/GTS, LinkTo must be explicitly imported from '@ember/routing'
32+
// (and may be renamed, e.g. `import { LinkTo as Link } from '@ember/routing'`).
33+
// local alias → true
34+
const importedLinkComponents = new Map();
35+
36+
function isLinkToComponent(node) {
37+
if (node.type !== 'GlimmerElementNode') {
38+
return false;
39+
}
40+
if (isStrictMode) {
41+
return importedLinkComponents.has(node.tag);
42+
}
43+
return node.tag === 'LinkTo' || node.tag === 'link-to';
44+
}
45+
3546
function checkHashPairsForTagName(node) {
3647
if (!node.hash || !node.hash.pairs) {
3748
return;
@@ -46,14 +57,27 @@ module.exports = {
4657
}
4758

4859
return {
60+
ImportDeclaration(node) {
61+
if (!isStrictMode) {
62+
return;
63+
}
64+
if (node.source.value !== '@ember/routing') {
65+
return;
66+
}
67+
for (const specifier of node.specifiers) {
68+
if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'LinkTo') {
69+
importedLinkComponents.set(specifier.local.name, true);
70+
}
71+
}
72+
},
73+
4974
GlimmerElementNode(node) {
5075
if (!isLinkToComponent(node)) {
5176
return;
5277
}
5378

5479
const tagNameAttr = node.attributes.find(
55-
(attr) =>
56-
attr.type === 'GlimmerAttrNode' && (attr.name === 'tagName' || attr.name === '@tagName')
80+
(attr) => attr.type === 'GlimmerAttrNode' && attr.name === '@tagName'
5781
);
5882

5983
if (tagNameAttr) {

tests/lib/rules/template-no-link-to-tagname.js

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ ruleTester.run('template-no-link-to-tagname', rule, {
1010
valid: [
1111
{
1212
filename: 'test.gjs',
13-
code: '<template><LinkTo @route="index">Home</LinkTo></template>',
13+
code: 'import { LinkTo } from \'@ember/routing\';\n<template><LinkTo @route="index">Home</LinkTo></template>',
1414
output: null,
1515
},
1616
{
@@ -24,6 +24,24 @@ ruleTester.run('template-no-link-to-tagname', rule, {
2424
output: null,
2525
},
2626

27+
// User-authored LinkTo component (no @ember/routing import) — should NOT be flagged
28+
{
29+
filename: 'test.gjs',
30+
code: '<template><LinkTo @tagName="button">Home</LinkTo></template>',
31+
output: null,
32+
},
33+
{
34+
filename: 'test.gts',
35+
code: '<template><LinkTo @tagName="button">Home</LinkTo></template>',
36+
output: null,
37+
},
38+
39+
// Bare tagName (without @) is just an HTML attribute, not flagged
40+
{
41+
filename: 'test.gjs',
42+
code: 'import { LinkTo } from \'@ember/routing\';\n<template><LinkTo @route="index" tagName="button">Home</LinkTo></template>',
43+
output: null,
44+
},
2745
'<template><Foo @route="routeName" @tagName="button">Link text</Foo></template>',
2846
'<template><LinkTo @route="routeName">Link text</LinkTo></template>',
2947
'<template>{{#link-to "routeName"}}Link text{{/link-to}}</template>',
@@ -35,19 +53,26 @@ ruleTester.run('template-no-link-to-tagname', rule, {
3553
invalid: [
3654
{
3755
filename: 'test.gjs',
38-
code: '<template><LinkTo @route="index" tagName="button">Home</LinkTo></template>',
56+
code: 'import { LinkTo } from \'@ember/routing\';\n<template><LinkTo @route="about" @tagName="span">About</LinkTo></template>',
3957
output: null,
4058
errors: [{ messageId: 'noLinkToTagname' }],
4159
},
4260
{
43-
filename: 'test.gjs',
44-
code: '<template><LinkTo @route="about" @tagName="span">About</LinkTo></template>',
61+
filename: 'test.gts',
62+
code: 'import { LinkTo } from \'@ember/routing\';\n<template><LinkTo @route="about" @tagName="span">About</LinkTo></template>',
4563
output: null,
4664
errors: [{ messageId: 'noLinkToTagname' }],
4765
},
66+
// Renamed import
4867
{
4968
filename: 'test.gjs',
50-
code: '<template><link-to @route="contact" tagName="div">Contact</link-to></template>',
69+
code: 'import { LinkTo as Link } from \'@ember/routing\';\n<template><Link @tagName="button">x</Link></template>',
70+
output: null,
71+
errors: [{ messageId: 'noLinkToTagname' }],
72+
},
73+
{
74+
filename: 'test.gts',
75+
code: 'import { LinkTo as Link } from \'@ember/routing\';\n<template><Link @route="index" @tagName="button">x</Link></template>',
5176
output: null,
5277
errors: [{ messageId: 'noLinkToTagname' }],
5378
},
@@ -80,6 +105,8 @@ const hbsRuleTester = new RuleTester({
80105

81106
hbsRuleTester.run('template-no-link-to-tagname', rule, {
82107
valid: [
108+
// Bare tagName (without @) is just an HTML attribute, not flagged
109+
'<LinkTo @route="index" tagName="button">Home</LinkTo>',
83110
'<Foo @route="routeName" @tagName="button">Link text</Foo>',
84111
'<LinkTo @route="routeName">Link text</LinkTo>',
85112
'{{#link-to "routeName"}}Link text{{/link-to}}',
@@ -91,17 +118,42 @@ hbsRuleTester.run('template-no-link-to-tagname', rule, {
91118
{
92119
code: '<LinkTo @route="routeName" @tagName="button">Link text</LinkTo>',
93120
output: null,
94-
errors: [{ message: 'tagName attribute on LinkTo is deprecated' }],
121+
errors: [
122+
{
123+
message:
124+
'@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',
125+
},
126+
],
127+
},
128+
{
129+
code: '<link-to @route="contact" @tagName="div">Contact</link-to>',
130+
output: null,
131+
errors: [
132+
{
133+
message:
134+
'@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',
135+
},
136+
],
95137
},
96138
{
97139
code: '{{#link-to "routeName" tagName="button"}}Link text{{/link-to}}',
98140
output: null,
99-
errors: [{ message: 'tagName attribute on LinkTo is deprecated' }],
141+
errors: [
142+
{
143+
message:
144+
'@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',
145+
},
146+
],
100147
},
101148
{
102149
code: '{{link-to "Link text" "routeName" tagName="button"}}',
103150
output: null,
104-
errors: [{ message: 'tagName attribute on LinkTo is deprecated' }],
151+
errors: [
152+
{
153+
message:
154+
'@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',
155+
},
156+
],
105157
},
106158
],
107159
});

0 commit comments

Comments
 (0)