Skip to content

Commit b4ab37a

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 b10dfa0 commit b4ab37a

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

0 commit comments

Comments
 (0)