Skip to content

Commit 0338b6c

Browse files
committed
Fix template-no-link-to-tagname: only flag @TagName, with @ember/routing import tracking in strict mode
What was broken on master: - The rule matched every `<LinkTo>` tag by bare name. In strict-mode templates (.gjs/.gts) a component named `LinkTo` is only Ember's router link if it is imported from `@ember/routing`. This caused two bugs: 1. False positive: a user-authored `<LinkTo @TagName="button">` component (no import) was flagged even though it has nothing to do with Ember's router LinkTo. 2. False negative: a renamed import such as `import { LinkTo as Link } from '@ember/routing'` used as `<Link @TagName="button">` was not detected because the tag name no longer matches the bare string `LinkTo`. - Additionally: on angle-bracket `<LinkTo>` the rule previously flagged the bare HTML-attribute `tagName` as well as the Ember argument `@tagName`. Bare `tagName` on angle-bracket invocation is just a passthrough HTML attribute and is not deprecated; only `@tagName` is. Fix: - Detect strict mode via `context.filename` (.gjs / .gts). - Add an `ImportDeclaration` visitor (strict mode only) that records every local alias imported as `LinkTo` from `@ember/routing` into `importedLinkComponents`. Mirrors the pattern used in `template-no-invalid-link-text`. - In strict mode the angle-bracket visitor only flags elements whose tag is in the tracked set. In classic HBS it keeps the previous behavior (`LinkTo` / `link-to`), since HBS has no imports. - The curly (`{{link-to}}` / `{{#link-to}}`) visitor is unchanged: there are no imports in HBS curly form. - Only flag `@tagName` (not bare `tagName`) on angle-bracket `<LinkTo>`. Test plan: - Valid: `<LinkTo @TagName="button">` in .gjs and .gts with no `@ember/routing` import (user-authored component must not be flagged). - Invalid: `import { LinkTo } from '@ember/routing';` + `<LinkTo @TagName=...>` in both .gjs and .gts. - Invalid: renamed import `import { LinkTo as Link } from '@ember/routing';` + `<Link @TagName="button">` flags `<Link>` in both .gjs and .gts. - Existing HBS cases still covered (including `<link-to @TagName>` via angle-bracket kebab form in classic templates).
1 parent b705850 commit 0338b6c

2 files changed

Lines changed: 71 additions & 18 deletions

File tree

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

Lines changed: 34 additions & 13 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: {
@@ -32,6 +21,25 @@ module.exports = {
3221
},
3322

3423
create(context) {
24+
const filename = context.filename;
25+
const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');
26+
27+
// In HBS, LinkTo always refers to Ember's router link component.
28+
// In GJS/GTS, LinkTo must be explicitly imported from '@ember/routing'
29+
// (and may be renamed, e.g. `import { LinkTo as Link } from '@ember/routing'`).
30+
// local alias → true
31+
const importedLinkComponents = new Map();
32+
33+
function isLinkToComponent(node) {
34+
if (node.type !== 'GlimmerElementNode') {
35+
return false;
36+
}
37+
if (isStrictMode) {
38+
return importedLinkComponents.has(node.tag);
39+
}
40+
return node.tag === 'LinkTo' || node.tag === 'link-to';
41+
}
42+
3543
function checkHashPairsForTagName(node) {
3644
if (!node.hash || !node.hash.pairs) {
3745
return;
@@ -46,14 +54,27 @@ module.exports = {
4654
}
4755

4856
return {
57+
ImportDeclaration(node) {
58+
if (!isStrictMode) {
59+
return;
60+
}
61+
if (node.source.value !== '@ember/routing') {
62+
return;
63+
}
64+
for (const specifier of node.specifiers) {
65+
if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'LinkTo') {
66+
importedLinkComponents.set(specifier.local.name, true);
67+
}
68+
}
69+
},
70+
4971
GlimmerElementNode(node) {
5072
if (!isLinkToComponent(node)) {
5173
return;
5274
}
5375

5476
const tagNameAttr = node.attributes.find(
55-
(attr) =>
56-
attr.type === 'GlimmerAttrNode' && (attr.name === 'tagName' || attr.name === '@tagName')
77+
(attr) => attr.type === 'GlimmerAttrNode' && attr.name === '@tagName'
5778
);
5879

5980
if (tagNameAttr) {

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

Lines changed: 37 additions & 5 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}}',
@@ -93,6 +120,11 @@ hbsRuleTester.run('template-no-link-to-tagname', rule, {
93120
output: null,
94121
errors: [{ message: 'tagName attribute on LinkTo is deprecated' }],
95122
},
123+
{
124+
code: '<link-to @route="contact" @tagName="div">Contact</link-to>',
125+
output: null,
126+
errors: [{ message: 'tagName attribute on LinkTo is deprecated' }],
127+
},
96128
{
97129
code: '{{#link-to "routeName" tagName="button"}}Link text{{/link-to}}',
98130
output: null,

0 commit comments

Comments
 (0)