Skip to content

Commit fbda3e5

Browse files
Merge pull request #2691 from johanrd/day_fix/template-no-model-argument-in-route-templates
Post-merge-review: Fix template-no-model-argument-in-route-templates: lint .gjs/.gts and unknown paths
2 parents c37c5ad + a31a752 commit fbda3e5

File tree

2 files changed

+86
-19
lines changed

2 files changed

+86
-19
lines changed

lib/rules/template-no-model-argument-in-route-templates.js

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
const path = require('path');
2+
3+
/**
4+
* Determine whether a file path corresponds to a route template.
5+
* Mirrors ember-template-lint's is-route-template.js heuristic (and the
6+
* duplicate in template-no-outlet-outside-routes.js):
7+
* - If the path is unknown, assume it could be a route (default-lint).
8+
* - Partials (basename starts with '-') are not routes.
9+
* - Classic component templates (<app>/templates/components/) are not routes.
10+
* - Co-located component templates (<app>/components/) are not routes.
11+
*
12+
* Note: GJS/GTS files can be route templates (e.g. app/routes/foo.gjs), so
13+
* we do not gate on file extension.
14+
*/
15+
function isRouteTemplate(filePath) {
16+
if (typeof filePath !== 'string') {
17+
return true; // unknown — assume it could be a route
18+
}
19+
20+
const normalized = filePath.replaceAll('\\', '/');
21+
const baseName = path.basename(normalized);
22+
23+
if (baseName.startsWith('-')) {
24+
return false;
25+
}
26+
27+
return (
28+
!/^[^/]+\/templates\/components\//.test(normalized) && // classic component
29+
!/^[^/]+\/components\//.test(normalized) // co-located component template
30+
);
31+
}
32+
133
/** @type {import('eslint').Rule.RuleModule} */
234
module.exports = {
335
meta: {
@@ -23,28 +55,24 @@ module.exports = {
2355
},
2456

2557
create(context) {
26-
const filename = context.filename;
27-
const isRouteTemplate =
28-
filename.includes('/templates/') &&
29-
!filename.includes('/components/') &&
30-
filename.endsWith('.hbs');
31-
const sourceCode = context.sourceCode;
58+
const routeTemplate = isRouteTemplate(context.filename);
59+
60+
if (!routeTemplate) {
61+
return {};
62+
}
3263

3364
return {
3465
GlimmerPathExpression(node) {
3566
// Check for @model usage
3667
if (node.original === '@model' || node.original.startsWith('@model.')) {
37-
// Only report in route templates (hbs files in templates/ directory)
38-
if (isRouteTemplate) {
39-
const replacement = node.original.replace('@model', 'this.model');
40-
context.report({
41-
node,
42-
messageId: 'noModelArgumentInRouteTemplates',
43-
fix(fixer) {
44-
return fixer.replaceText(node, replacement);
45-
},
46-
});
47-
}
68+
const replacement = node.original.replace('@model', 'this.model');
69+
context.report({
70+
node,
71+
messageId: 'noModelArgumentInRouteTemplates',
72+
fix(fixer) {
73+
return fixer.replaceText(node, replacement);
74+
},
75+
});
4876
}
4977
},
5078
};

tests/lib/rules/template-no-model-argument-in-route-templates.js

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ ruleTester.run('template-no-model-argument-in-route-templates', rule, {
2424
filename: 'app/components/user-card.gjs',
2525
code: '<template>{{@model}}</template>',
2626
},
27+
// Partial templates (basename starts with '-') are skipped.
28+
{
29+
filename: 'app/templates/-partial.hbs',
30+
code: '<template>{{@model.foo}}</template>',
31+
},
2732

2833
'<template>{{model}}</template>',
2934
'<template>{{@modelythingy}}</template>',
@@ -60,6 +65,19 @@ ruleTester.run('template-no-model-argument-in-route-templates', rule, {
6065
output: '<template>{{this.model.foo.bar}}</template>',
6166
errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],
6267
},
68+
// .gjs route templates are also linted (not gated to .hbs).
69+
{
70+
filename: 'app/routes/posts.gjs',
71+
code: '<template>{{@model.foo}}</template>',
72+
output: '<template>{{this.model.foo}}</template>',
73+
errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],
74+
},
75+
// Unknown path defaults to lint (matches upstream).
76+
{
77+
code: '<template>{{@model.foo}}</template>',
78+
output: '<template>{{this.model.foo}}</template>',
79+
errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],
80+
},
6381
],
6482
});
6583

@@ -72,6 +90,27 @@ const hbsRuleTester = new RuleTester({
7290
});
7391

7492
hbsRuleTester.run('template-no-model-argument-in-route-templates', rule, {
75-
valid: ['{{model}}', '{{this.model}}', '{{@modelythingy}}', '{{@model}}'],
76-
invalid: [],
93+
valid: [
94+
'{{model}}',
95+
'{{this.model}}',
96+
'{{@modelythingy}}',
97+
// Component templates are not routes.
98+
{
99+
filename: 'app/components/user-card.hbs',
100+
code: '{{@model}}',
101+
},
102+
// Partials (basename starts with '-') are not routes.
103+
{
104+
filename: 'app/templates/-partial.hbs',
105+
code: '{{@model.foo}}',
106+
},
107+
],
108+
invalid: [
109+
// Unknown path defaults to lint.
110+
{
111+
code: '{{@model}}',
112+
output: '{{this.model}}',
113+
errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],
114+
},
115+
],
77116
});

0 commit comments

Comments
 (0)