Skip to content

Commit e5dc232

Browse files
committed
More coverage from template-lint
1 parent 10255f4 commit e5dc232

3 files changed

Lines changed: 37 additions & 37 deletions

File tree

docs/rules/template-no-outlet-outside-routes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ Examples of **correct** code for this rule:
3434

3535
- [Ember guides/routing](https://guides.emberjs.com/release/routing/rendering-a-template/)
3636
- [Ember api/outlet helper](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/outlet?anchor=outlet)
37+
- [ember-template-lint no-outlet-outside-routes](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-outlet-outside-routes.md)

lib/rules/template-no-outlet-outside-routes.js

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,27 @@ const path = require('path');
55
* Mirrors ember-template-lint's is-route-template.js heuristic:
66
* - If the path is unknown, assume it could be a route (don't report).
77
* - Partials (basename starts with '-') are not routes.
8-
* - Classic component templates (templates/components/) are not routes.
9-
* - Co-located component templates (app/components/) are not routes.
8+
* - Classic component templates (<app>/templates/components/) are not routes.
9+
* - Co-located component templates (<app>/components/) are not routes.
10+
*
11+
* Note: GJS/GTS files can be route templates (e.g. app/routes/foo.gjs).
1012
*/
1113
function isRouteTemplate(filePath) {
1214
if (typeof filePath !== 'string') {
1315
return true; // unknown — assume it could be a route
1416
}
1517

16-
// GJS/GTS files are always components, never route templates
17-
if (filePath.endsWith('.gjs') || filePath.endsWith('.gts')) {
18-
return false;
19-
}
20-
21-
const baseName = path.basename(filePath);
22-
23-
// Partials start with '-'
24-
if (baseName.startsWith('-')) {
25-
return false;
26-
}
27-
2818
const normalized = filePath.replaceAll('\\', '/');
19+
const baseName = path.basename(normalized);
2920

30-
// Classic component template: <app>/templates/components/...
31-
if (
32-
/\/templates\/components\//.test(normalized) ||
33-
/^[^/]+\/templates\/components\//.test(normalized)
34-
) {
35-
return false;
36-
}
37-
38-
// Co-located component: <app>/components/...
39-
if (/\/components\//.test(normalized) && !/\/templates\//.test(normalized)) {
21+
if (baseName.startsWith('-')) {
4022
return false;
4123
}
4224

43-
return true;
25+
return (
26+
!/^[^/]+\/templates\/components\//.test(normalized) && // classic component
27+
!/^[^/]+\/components\//.test(normalized) // co-located component template
28+
);
4429
}
4530

4631
/** @type {import('eslint').Rule.RuleModule} */
@@ -68,9 +53,7 @@ module.exports = {
6853
create(context) {
6954
const filename = context.filename || context.getFilename();
7055

71-
// In GJS/GTS (strict) mode, templates are always in components — never route templates.
72-
// Only .hbs files can be route templates, so apply the filePath heuristic only for those.
73-
const routeTemplate = filename.endsWith('.hbs') ? isRouteTemplate(filename) : false;
56+
const routeTemplate = isRouteTemplate(filename);
7457

7558
return {
7659
GlimmerMustacheStatement(node) {

tests/lib/rules/template-no-outlet-outside-routes.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,33 @@ ruleTester.run('template-no-outlet-outside-routes', rule, {
1616
'<template><div>Content</div></template>',
1717
'<template>{{foo}}</template>',
1818
'<template>{{button}}</template>',
19+
// GJS route templates — outlet is allowed
20+
{
21+
filename: 'app/routes/foo.gjs',
22+
code: '<template>{{outlet}}</template>',
23+
},
24+
{
25+
filename: 'app/routes/foo.gts',
26+
code: '<template>{{outlet}}</template>',
27+
},
1928
],
2029
invalid: [
21-
// GJS files are always components — outlet should be flagged
30+
// Co-located component (explicit filename)
2231
{
32+
filename: 'app/components/my-component.gjs',
2333
code: '<template>{{outlet}}</template>',
2434
output: null,
2535
errors: [{ messageId: 'noOutletOutsideRoutes' }],
2636
},
2737
{
28-
code: '<template><div>{{outlet}}</div></template>',
38+
filename: 'app/components/my-component.gts',
39+
code: '<template>{{outlet}}</template>',
2940
output: null,
3041
errors: [{ messageId: 'noOutletOutsideRoutes' }],
3142
},
32-
// Co-located component (explicit filename)
3343
{
3444
filename: 'app/components/my-component.gjs',
35-
code: '<template>{{outlet}}</template>',
45+
code: '<template><div>{{outlet}}</div></template>',
3646
output: null,
3747
errors: [{ messageId: 'noOutletOutsideRoutes' }],
3848
},
@@ -52,9 +62,6 @@ hbsRuleTester.run('template-no-outlet-outside-routes', rule, {
5262
// Non-outlet usage
5363
'{{foo}}',
5464
'{{button}}',
55-
// Block form is ambiguous (could be a component named "outlet")
56-
'{{#outlet}}Why?!{{/outlet}}',
57-
'{{#outlet}}Works because ambiguous{{/outlet}}',
5865
// Route templates — outlet is allowed
5966
{
6067
filename: 'app/templates/foo/route.hbs',
@@ -64,12 +71,21 @@ hbsRuleTester.run('template-no-outlet-outside-routes', rule, {
6471
filename: 'app/templates/routes/foo.hbs',
6572
code: '{{outlet}}',
6673
},
74+
// Block form in route templates
75+
{
76+
filename: 'app/templates/foo/route.hbs',
77+
code: '{{#outlet}}Why?!{{/outlet}}',
78+
},
79+
{
80+
filename: 'app/templates/routes/foo.hbs',
81+
code: '{{#outlet}}Why?!{{/outlet}}',
82+
},
6783
// Ambiguous path — not clearly a component, so allowed
6884
{
6985
filename: 'app/templates/something/foo.hbs',
70-
code: '{{outlet}}',
86+
code: '{{#outlet}}Works because ambiguous{{/outlet}}',
7187
},
72-
// "components" in the prefix but not under templates/components/ or app/components/
88+
// "components" appears in prefix but not under <app>/templates/components/ or <app>/components/
7389
{
7490
filename: 'components/templates/application.hbs',
7591
code: '{{outlet}}',

0 commit comments

Comments
 (0)