@@ -45,21 +45,33 @@ module.exports = {
4545 } ,
4646
4747 create ( context ) {
48+ const filename = context . filename ;
49+ const isStrictMode = filename . endsWith ( '.gjs' ) || filename . endsWith ( '.gts' ) ;
50+
51+ // In HBS, <LinkTo> always refers to Ember's router link component.
52+ // In GJS/GTS, LinkTo must be explicitly imported from '@ember/routing'.
53+ // local alias → true (any truthy value marks it as a tracked link component)
54+ const importedLinkComponents = new Map ( ) ;
55+
56+ const linkTags = new Set ( [ 'a' ] ) ;
57+ if ( ! isStrictMode ) {
58+ linkTags . add ( 'LinkTo' ) ;
59+ }
60+
4861 // eslint-disable-next-line complexity
4962 function checkElementNode ( node ) {
50- if ( node . tag !== 'a' && node . tag !== 'LinkTo' ) {
63+ if ( ! linkTags . has ( node . tag ) ) {
5164 return ;
5265 }
66+ // Determine if this tag should be treated as <LinkTo> for @title handling
67+ const isLinkTo = node . tag === 'LinkTo' || importedLinkComponents . has ( node . tag ) ;
5368
5469 const titleAttr = node . attributes . find (
5570 ( attr ) => attr . type === 'GlimmerAttrNode' && attr . name === 'title'
5671 ) ;
57- const titleArgAttr =
58- node . tag === 'LinkTo'
59- ? node . attributes . find (
60- ( attr ) => attr . type === 'GlimmerAttrNode' && attr . name === '@title'
61- )
62- : null ;
72+ const titleArgAttr = isLinkTo
73+ ? node . attributes . find ( ( attr ) => attr . type === 'GlimmerAttrNode' && attr . name === '@title' )
74+ : null ;
6375
6476 // Get title attribute text value
6577 let titleAttrValue ;
@@ -74,12 +86,12 @@ module.exports = {
7486 }
7587
7688 // Collect all title values (lowercased, trimmed)
77- const titleValues = [ titleAttrValue , node . tag === 'LinkTo' ? titleArgValue : null ]
89+ const titleValues = [ titleAttrValue , isLinkTo ? titleArgValue : null ]
7890 . filter ( ( v ) => typeof v === 'string' )
7991 . map ( ( v ) => v . toLowerCase ( ) . trim ( ) ) ;
8092
8193 // Error if both title and @title are specified on LinkTo
82- if ( node . tag === 'LinkTo' && titleAttrValue !== undefined && titleArgValue !== undefined ) {
94+ if ( isLinkTo && titleAttrValue !== undefined && titleArgValue !== undefined ) {
8395 context . report ( {
8496 node : titleAttr || node ,
8597 messageId : 'noInvalidLinkTitle' ,
@@ -150,6 +162,19 @@ module.exports = {
150162 }
151163
152164 return {
165+ ImportDeclaration ( node ) {
166+ if ( ! isStrictMode ) {
167+ return ;
168+ }
169+ if ( node . source . value === '@ember/routing' ) {
170+ for ( const specifier of node . specifiers ) {
171+ if ( specifier . type === 'ImportSpecifier' && specifier . imported . name === 'LinkTo' ) {
172+ importedLinkComponents . set ( specifier . local . name , true ) ;
173+ linkTags . add ( specifier . local . name ) ;
174+ }
175+ }
176+ }
177+ } ,
153178 GlimmerElementNode : checkElementNode ,
154179 GlimmerBlockStatement : checkBlockStatement ,
155180 } ;
0 commit comments