Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 45 additions & 17 deletions lib/rules/template-no-model-argument-in-route-templates.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
const path = require('path');

/**
* Determine whether a file path corresponds to a route template.
* Mirrors ember-template-lint's is-route-template.js heuristic (and the
* duplicate in template-no-outlet-outside-routes.js):
* - If the path is unknown, assume it could be a route (default-lint).
* - Partials (basename starts with '-') are not routes.
* - Classic component templates (<app>/templates/components/) are not routes.
* - Co-located component templates (<app>/components/) are not routes.
*
* Note: GJS/GTS files can be route templates (e.g. app/routes/foo.gjs), so
* we do not gate on file extension.
*/
function isRouteTemplate(filePath) {
if (typeof filePath !== 'string') {
return true; // unknown — assume it could be a route
}

const normalized = filePath.replaceAll('\\', '/');
const baseName = path.basename(normalized);

if (baseName.startsWith('-')) {
return false;
}

return (
!/^[^/]+\/templates\/components\//.test(normalized) && // classic component
!/^[^/]+\/components\//.test(normalized) // co-located component template
);
}

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
Expand All @@ -23,28 +55,24 @@ module.exports = {
},

create(context) {
const filename = context.filename;
const isRouteTemplate =
filename.includes('/templates/') &&
!filename.includes('/components/') &&
filename.endsWith('.hbs');
const sourceCode = context.sourceCode;
const routeTemplate = isRouteTemplate(context.filename);

if (!routeTemplate) {
return {};
}

return {
GlimmerPathExpression(node) {
// Check for @model usage
if (node.original === '@model' || node.original.startsWith('@model.')) {
// Only report in route templates (hbs files in templates/ directory)
if (isRouteTemplate) {
const replacement = node.original.replace('@model', 'this.model');
context.report({
node,
messageId: 'noModelArgumentInRouteTemplates',
fix(fixer) {
return fixer.replaceText(node, replacement);
},
});
}
const replacement = node.original.replace('@model', 'this.model');
context.report({
node,
messageId: 'noModelArgumentInRouteTemplates',
fix(fixer) {
return fixer.replaceText(node, replacement);
},
});
}
},
};
Expand Down
43 changes: 41 additions & 2 deletions tests/lib/rules/template-no-model-argument-in-route-templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ ruleTester.run('template-no-model-argument-in-route-templates', rule, {
filename: 'app/components/user-card.gjs',
code: '<template>{{@model}}</template>',
},
// Partial templates (basename starts with '-') are skipped.
{
filename: 'app/templates/-partial.hbs',
code: '<template>{{@model.foo}}</template>',
},

'<template>{{model}}</template>',
'<template>{{@modelythingy}}</template>',
Expand Down Expand Up @@ -60,6 +65,19 @@ ruleTester.run('template-no-model-argument-in-route-templates', rule, {
output: '<template>{{this.model.foo.bar}}</template>',
errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],
},
// .gjs route templates are also linted (not gated to .hbs).
{
filename: 'app/routes/posts.gjs',
code: '<template>{{@model.foo}}</template>',
output: '<template>{{this.model.foo}}</template>',
errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],
},
// Unknown path defaults to lint (matches upstream).
{
code: '<template>{{@model.foo}}</template>',
output: '<template>{{this.model.foo}}</template>',
errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],
},
],
});

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

hbsRuleTester.run('template-no-model-argument-in-route-templates', rule, {
valid: ['{{model}}', '{{this.model}}', '{{@modelythingy}}', '{{@model}}'],
invalid: [],
valid: [
'{{model}}',
'{{this.model}}',
'{{@modelythingy}}',
// Component templates are not routes.
{
filename: 'app/components/user-card.hbs',
code: '{{@model}}',
},
// Partials (basename starts with '-') are not routes.
{
filename: 'app/templates/-partial.hbs',
code: '{{@model.foo}}',
},
],
invalid: [
// Unknown path defaults to lint.
{
code: '{{@model}}',
output: '{{this.model}}',
errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],
},
],
});
Loading