Skip to content

Commit 176831f

Browse files
NullVoxPopuliclaude
andcommitted
Add hbs file support for template-lint-disable and tests
Apply the processor to hbs files in base configs so template-lint-disable works in standalone .hbs templates, not just gjs/gts files. Also handle template AST edge case where TextNodes start on the comment line. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent cfc13ae commit 176831f

5 files changed

Lines changed: 159 additions & 3 deletions

File tree

lib/config-legacy/base.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,10 @@ module.exports = {
2323
parser: 'ember-eslint-parser',
2424
processor: 'ember/noop',
2525
},
26+
{
27+
files: ['**/*.hbs'],
28+
parser: 'ember-eslint-parser/hbs',
29+
processor: 'ember/noop',
30+
},
2631
],
2732
};

lib/config/base.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const plugin = require('../index');
22
const emberEslintParser = require('ember-eslint-parser');
3+
const emberEslintParserHbs = require('ember-eslint-parser/hbs');
34

45
module.exports = [
56
{
@@ -17,4 +18,11 @@ module.exports = [
1718
},
1819
processor: 'ember/noop',
1920
},
21+
{
22+
files: ['**/*.hbs'],
23+
languageOptions: {
24+
parser: emberEslintParserHbs,
25+
},
26+
processor: 'ember/noop',
27+
},
2028
];

lib/processors/template-lint-disable.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@ function parseDisableDirectives(text) {
3737
? cleaned.split(/[\s,]+/).filter(Boolean)
3838
: []; // empty = disable all
3939

40+
// Comment is on line i+1 (1-indexed), next line is i+2.
41+
// In template ASTs, TextNodes can start on the same line as the comment
42+
// (e.g. the newline after {{! template-lint-disable }} is part of the
43+
// following TextNode), so we suppress both the comment line and the next.
4044
directives.push({
41-
// comment is on line i+1 (1-indexed), next line is i+2
42-
line: i + 2,
45+
commentLine: i + 1,
46+
nextLine: i + 2,
4347
rules,
4448
});
4549
}
@@ -67,7 +71,7 @@ function matchesRule(ruleId, disableRuleName) {
6771

6872
function shouldSuppressMessage(message, directives) {
6973
for (const directive of directives) {
70-
if (message.line !== directive.line) {
74+
if (message.line !== directive.commentLine && message.line !== directive.nextLine) {
7175
continue;
7276
}
7377
// No rules specified = suppress all

lib/recommended.mjs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import baseRules from './recommended-rules.js';
33
import gjsRules from './recommended-rules-gjs.js';
44
import gtsRules from './recommended-rules-gts.js';
55
import emberParser from 'ember-eslint-parser';
6+
import emberParserHbs from 'ember-eslint-parser/hbs';
67

78
export const plugin = emberPlugin;
89
export const parser = emberParser;
10+
export const hbsParser = emberParserHbs;
911

1012
export const base = {
1113
name: 'ember:base',
@@ -53,14 +55,29 @@ export const gts = {
5355
},
5456
};
5557

58+
export const hbs = {
59+
name: 'ember:hbs',
60+
plugins: { ember: emberPlugin },
61+
files: ['**/*.hbs'],
62+
languageOptions: {
63+
parser: emberParserHbs,
64+
},
65+
processor: 'ember/noop',
66+
rules: {
67+
...base.rules,
68+
},
69+
};
70+
5671
export default {
5772
// Helpful utility exports
5873
plugin,
5974
parser,
75+
hbsParser,
6076
// Recommended config sets
6177
configs: {
6278
base,
6379
gjs,
6480
gts,
81+
hbs,
6582
},
6683
};

tests/lib/rules-preprocessor/gjs-gts-parser-test.js

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const { writeFileSync, readFileSync } = require('node:fs');
1313
const { join } = require('node:path');
1414

1515
const gjsGtsParser = require.resolve('ember-eslint-parser');
16+
const hbsParser = require.resolve('ember-eslint-parser/hbs');
1617

1718
/**
1819
* Helper function which creates ESLint instance with enabled/disabled autofix feature.
@@ -1020,3 +1021,124 @@ describe('supports template-lint-disable directive', () => {
10201021
expect(resultErrors).toHaveLength(0);
10211022
});
10221023
});
1024+
1025+
describe('supports template-lint-disable directive in hbs files', () => {
1026+
function initHbsESLint() {
1027+
return new ESLint({
1028+
ignore: false,
1029+
useEslintrc: false,
1030+
plugins: { ember: plugin },
1031+
overrideConfig: {
1032+
root: true,
1033+
parserOptions: {
1034+
ecmaVersion: 2022,
1035+
sourceType: 'module',
1036+
},
1037+
plugins: ['ember'],
1038+
overrides: [
1039+
{
1040+
files: ['**/*.hbs'],
1041+
parser: hbsParser,
1042+
processor: 'ember/noop',
1043+
rules: {
1044+
'ember/template-no-bare-strings': 'error',
1045+
},
1046+
},
1047+
],
1048+
},
1049+
});
1050+
}
1051+
1052+
it('disables all rules on the next line with mustache comment', async () => {
1053+
const eslint = initHbsESLint();
1054+
const code = `<div>
1055+
{{! template-lint-disable }}
1056+
Hello world
1057+
</div>`;
1058+
const results = await eslint.lintText(code, { filePath: 'my-template.hbs' });
1059+
const resultErrors = results.flatMap((result) => result.messages);
1060+
expect(resultErrors).toHaveLength(0);
1061+
});
1062+
1063+
it('disables all rules on the next line with mustache block comment', async () => {
1064+
const eslint = initHbsESLint();
1065+
const code = `<div>
1066+
{{!-- template-lint-disable --}}
1067+
Hello world
1068+
</div>`;
1069+
const results = await eslint.lintText(code, { filePath: 'my-template.hbs' });
1070+
const resultErrors = results.flatMap((result) => result.messages);
1071+
expect(resultErrors).toHaveLength(0);
1072+
});
1073+
1074+
it('only disables the next line, not subsequent lines', async () => {
1075+
const eslint = initHbsESLint();
1076+
const code = `{{! template-lint-disable }}
1077+
<div>Hello world</div>
1078+
<div>Bare string here too</div>`;
1079+
const results = await eslint.lintText(code, { filePath: 'my-template.hbs' });
1080+
const resultErrors = results.flatMap((result) => result.messages);
1081+
// Line 2 "Hello world" suppressed, but line 3 "Bare string here too" should still error
1082+
expect(resultErrors).toHaveLength(1);
1083+
expect(resultErrors[0].line).toBe(3);
1084+
});
1085+
1086+
it('disables a specific rule by name', async () => {
1087+
const eslint = initHbsESLint();
1088+
const code = `<div>
1089+
{{! template-lint-disable ember/template-no-bare-strings }}
1090+
Hello world
1091+
</div>`;
1092+
const results = await eslint.lintText(code, { filePath: 'my-template.hbs' });
1093+
const resultErrors = results.flatMap((result) => result.messages);
1094+
expect(resultErrors).toHaveLength(0);
1095+
});
1096+
1097+
it('supports template-lint rule name format (maps to ember/ prefix)', async () => {
1098+
const eslint = initHbsESLint();
1099+
const code = `<div>
1100+
{{! template-lint-disable no-bare-strings }}
1101+
Hello world
1102+
</div>`;
1103+
const results = await eslint.lintText(code, { filePath: 'my-template.hbs' });
1104+
const resultErrors = results.flatMap((result) => result.messages);
1105+
expect(resultErrors).toHaveLength(0);
1106+
});
1107+
1108+
it('does not suppress unrelated rules when a specific rule is named', async () => {
1109+
const eslint = initHbsESLint();
1110+
const code = `<div>
1111+
{{! template-lint-disable ember/template-no-html-comments }}
1112+
Hello world
1113+
</div>`;
1114+
const results = await eslint.lintText(code, { filePath: 'my-template.hbs' });
1115+
const resultErrors = results.flatMap((result) => result.messages);
1116+
// no-bare-strings should still fire since we only disabled no-html-comments
1117+
expect(resultErrors).toHaveLength(1);
1118+
expect(resultErrors[0].ruleId).toBe('ember/template-no-bare-strings');
1119+
});
1120+
1121+
it('works with multiple disable comments in the same file', async () => {
1122+
const eslint = initHbsESLint();
1123+
const code = `<div>
1124+
{{! template-lint-disable }}
1125+
Hello world
1126+
{{! template-lint-disable }}
1127+
Another bare string
1128+
</div>`;
1129+
const results = await eslint.lintText(code, { filePath: 'my-template.hbs' });
1130+
const resultErrors = results.flatMap((result) => result.messages);
1131+
expect(resultErrors).toHaveLength(0);
1132+
});
1133+
1134+
it('bare strings without disable comment still trigger errors', async () => {
1135+
const eslint = initHbsESLint();
1136+
const code = `<div>
1137+
Hello world
1138+
</div>`;
1139+
const results = await eslint.lintText(code, { filePath: 'my-template.hbs' });
1140+
const resultErrors = results.flatMap((result) => result.messages);
1141+
expect(resultErrors).toHaveLength(1);
1142+
expect(resultErrors[0].ruleId).toBe('ember/template-no-bare-strings');
1143+
});
1144+
});

0 commit comments

Comments
 (0)