Skip to content

Commit ec2712e

Browse files
Merge pull request #2576 from NullVoxPopuli/nvp/template-lint-extract-rule-template-no-trailing-spaces
Extract rule: template-no-trailing-spaces
2 parents 8c8af7c + 91e07cd commit ec2712e

4 files changed

Lines changed: 307 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ rules in templates can be disabled with eslint directives with mustache or html
256256
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
257257
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
258258
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
259+
| [template-no-trailing-spaces](docs/rules/template-no-trailing-spaces.md) | disallow trailing whitespace at the end of lines in templates | | 🔧 | |
259260
| [template-no-unavailable-this](docs/rules/template-no-unavailable-this.md) | disallow `this` in templates that are not inside a class or function | | | |
260261
| [template-no-unnecessary-component-helper](docs/rules/template-no-unnecessary-component-helper.md) | disallow unnecessary component helper | | 🔧 | |
261262
| [template-no-unnecessary-concat](docs/rules/template-no-unnecessary-concat.md) | disallow unnecessary string concatenation | | 🔧 | |
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# ember/template-no-trailing-spaces
2+
3+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Disallow trailing whitespace at the end of lines.
8+
9+
## Examples
10+
11+
In examples below, `` represents a trailing space character.
12+
13+
This rule **forbids** the following:
14+
15+
```hbs
16+
<div>test</div>•• •••••
17+
```
18+
19+
```gjs
20+
<template>
21+
<div>Hello</div>••
22+
</template>
23+
```
24+
25+
This rule **allows** the following:
26+
27+
```hbs
28+
<div>test</div>
29+
```
30+
31+
```gjs
32+
<template>
33+
<div>Hello</div>
34+
</template>
35+
```
36+
37+
## Related Rules
38+
39+
- [no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces) from eslint
40+
41+
## References
42+
43+
- [git/formatting and whitespace](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_formatting_and_whitespace)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'suggestion',
5+
docs: {
6+
description: 'disallow trailing whitespace at the end of lines in templates',
7+
category: 'Best Practices',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-trailing-spaces.md',
9+
templateMode: 'both',
10+
},
11+
fixable: 'whitespace',
12+
schema: [],
13+
messages: {
14+
unexpected: 'Trailing whitespace detected.',
15+
},
16+
originallyFrom: {
17+
name: 'ember-template-lint',
18+
rule: 'lib/rules/no-trailing-spaces.js',
19+
docs: 'docs/rule/no-trailing-spaces.md',
20+
tests: 'test/unit/rules/no-trailing-spaces-test.js',
21+
},
22+
},
23+
24+
create(context) {
25+
const sourceCode = context.sourceCode || context.getSourceCode();
26+
const glimmerTemplateRanges = [];
27+
28+
return {
29+
GlimmerTemplate(node) {
30+
glimmerTemplateRanges.push(node.range);
31+
},
32+
33+
'Program:exit'() {
34+
const text = sourceCode.getText();
35+
const lines = text.split('\n');
36+
let lineOffset = 0;
37+
38+
for (const [index, line] of lines.entries()) {
39+
if (line.endsWith(' ') || line.endsWith('\t')) {
40+
const trimmedLength = line.trimEnd().length;
41+
const trailingStart = lineOffset + trimmedLength;
42+
const lineEnd = lineOffset + line.length;
43+
44+
const isInTemplate = glimmerTemplateRanges.some(
45+
([start, end]) => trailingStart >= start && lineEnd <= end
46+
);
47+
48+
if (isInTemplate) {
49+
context.report({
50+
loc: {
51+
start: { line: index + 1, column: trimmedLength },
52+
end: { line: index + 1, column: line.length },
53+
},
54+
messageId: 'unexpected',
55+
fix(fixer) {
56+
return fixer.removeRange([trailingStart, lineEnd]);
57+
},
58+
});
59+
}
60+
}
61+
62+
lineOffset += line.length + 1; // +1 for the \n
63+
}
64+
},
65+
};
66+
},
67+
};
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
//------------------------------------------------------------------------------
2+
// Requirements
3+
//------------------------------------------------------------------------------
4+
5+
const rule = require('../../../lib/rules/template-no-trailing-spaces');
6+
const RuleTester = require('eslint').RuleTester;
7+
8+
//------------------------------------------------------------------------------
9+
// Tests
10+
//------------------------------------------------------------------------------
11+
12+
const ruleTester = new RuleTester({
13+
parser: require.resolve('ember-eslint-parser'),
14+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
15+
});
16+
17+
ruleTester.run('template-no-trailing-spaces', rule, {
18+
valid: [
19+
`<template>
20+
<div>Hello World</div>
21+
</template>`,
22+
`<template>
23+
<div>
24+
Content
25+
</div>
26+
</template>`,
27+
28+
'<template>test</template>',
29+
'<template> test</template>',
30+
`<template>test
31+
</template>`,
32+
`<template>{{#my-component}}
33+
{{/my-component}}</template>`,
34+
`<template> test
35+
</template>`,
36+
37+
// JS code with trailing spaces outside <template> must NOT be flagged
38+
'const foo = "bar"; \n\n<template><div>Hello</div></template>',
39+
],
40+
41+
invalid: [
42+
{
43+
code: `<template>
44+
<div>Hello</div>
45+
</template>`,
46+
output: `<template>
47+
<div>Hello</div>
48+
</template>`,
49+
errors: [
50+
{
51+
message: 'Trailing whitespace detected.',
52+
},
53+
],
54+
},
55+
{
56+
code: `<template>
57+
<div>Hello</div>
58+
</template>`,
59+
output: `<template>
60+
<div>Hello</div>
61+
</template>`,
62+
errors: [
63+
{
64+
message: 'Trailing whitespace detected.',
65+
},
66+
],
67+
},
68+
69+
{
70+
code: `<template>test
71+
</template>`,
72+
output: `<template>test
73+
</template>`,
74+
errors: [{ message: 'Trailing whitespace detected.' }],
75+
},
76+
{
77+
code: `<template>import { hbs } from 'ember-cli-htmlbars';
78+
79+
test('it renders', async (assert) => {
80+
await render(hbs\`
81+
<div class="parent">
82+
<div class="child"></div>
83+
</div>
84+
\`);
85+
});</template>`,
86+
output: `<template>import { hbs } from 'ember-cli-htmlbars';
87+
88+
test('it renders', async (assert) => {
89+
await render(hbs\`
90+
<div class="parent">
91+
<div class="child"></div>
92+
</div>
93+
\`);
94+
});</template>`,
95+
errors: [{ message: 'Trailing whitespace detected.' }],
96+
},
97+
{
98+
code: `<template>import { hbs } from 'ember-cli-htmlbars';
99+
100+
test('it renders', async (assert) => {
101+
await render(hbs\`
102+
<div></div>
103+
104+
<div></div>
105+
\`);
106+
});</template>`,
107+
output: `<template>import { hbs } from 'ember-cli-htmlbars';
108+
109+
test('it renders', async (assert) => {
110+
await render(hbs\`
111+
<div></div>
112+
113+
<div></div>
114+
\`);
115+
});</template>`,
116+
errors: [{ message: 'Trailing whitespace detected.' }],
117+
},
118+
{
119+
code: '<template>test\n \n</template>',
120+
output: '<template>test\n\n</template>',
121+
errors: [{ message: 'Trailing whitespace detected.' }],
122+
},
123+
{
124+
code: `<template>{{#my-component}}
125+
test
126+
{{/my-component}}</template>`,
127+
output: `<template>{{#my-component}}
128+
test
129+
{{/my-component}}</template>`,
130+
errors: [{ message: 'Trailing whitespace detected.' }],
131+
},
132+
],
133+
});
134+
135+
const hbsRuleTester = new RuleTester({
136+
parser: require.resolve('ember-eslint-parser/hbs'),
137+
parserOptions: {
138+
ecmaVersion: 2022,
139+
sourceType: 'module',
140+
},
141+
});
142+
143+
hbsRuleTester.run('template-no-trailing-spaces', rule, {
144+
valid: [
145+
'test',
146+
' test',
147+
'test\n',
148+
'test\n\n',
149+
'{{#my-component}}\n test\n{{/my-component}}',
150+
`import { hbs } from 'ember-cli-htmlbars';
151+
152+
test('it renders', async (assert) => {
153+
await render(hbs\`
154+
<div class="parent">
155+
<div class="child"></div>
156+
</div>
157+
\`);
158+
});`,
159+
],
160+
invalid: [
161+
{
162+
code: 'test ',
163+
output: 'test',
164+
errors: [{ message: 'Trailing whitespace detected.' }],
165+
},
166+
{
167+
code: `test
168+
`,
169+
output: `test
170+
`,
171+
errors: [{ message: 'Trailing whitespace detected.' }],
172+
},
173+
{
174+
code: 'test\n \n',
175+
output: 'test\n\n',
176+
errors: [{ message: 'Trailing whitespace detected.' }],
177+
},
178+
{
179+
code: '{{#my-component}}\n test \n{{/my-component}}',
180+
output: '{{#my-component}}\n test\n{{/my-component}}',
181+
errors: [{ message: 'Trailing whitespace detected.' }],
182+
},
183+
{
184+
code: 'import { hbs } from \'ember-cli-htmlbars\';\n\ntest(\'it renders\', async (assert) => {\n await render(hbs` \n <div class="parent">\n <div class="child"></div>\n </div>\n `);\n});',
185+
output:
186+
'import { hbs } from \'ember-cli-htmlbars\';\n\ntest(\'it renders\', async (assert) => {\n await render(hbs`\n <div class="parent">\n <div class="child"></div>\n </div>\n `);\n});',
187+
errors: [{ message: 'Trailing whitespace detected.' }],
188+
},
189+
{
190+
code: "import { hbs } from 'ember-cli-htmlbars';\n\ntest('it renders', async (assert) => {\n await render(hbs`\n <div></div>\n \n <div></div>\n `);\n});",
191+
output:
192+
"import { hbs } from 'ember-cli-htmlbars';\n\ntest('it renders', async (assert) => {\n await render(hbs`\n <div></div>\n\n <div></div>\n `);\n});",
193+
errors: [{ message: 'Trailing whitespace detected.' }],
194+
},
195+
],
196+
});

0 commit comments

Comments
 (0)