Skip to content

Add template-lint-disable pragma for template regions#2627

Open
NullVoxPopuli-ai-agent wants to merge 19 commits intoember-cli:masterfrom
NullVoxPopuli-ai-agent:feat/template-lint-disable-pragma
Open

Add template-lint-disable pragma for template regions#2627
NullVoxPopuli-ai-agent wants to merge 19 commits intoember-cli:masterfrom
NullVoxPopuli-ai-agent:feat/template-lint-disable-pragma

Conversation

@NullVoxPopuli-ai-agent
Copy link
Copy Markdown

@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent commented Mar 22, 2026

Summary

Adds a template-lint-disable comment directive that suppresses lint errors on the next line in template regions of gjs/gts/hbs files. Works like eslint-disable-next-line but uses the template-lint-disable naming convention familiar to ember-template-lint users.

This processor is opt-in — it is registered as ember/template-lint-disable and must be explicitly configured. The existing ember/noop processor is unchanged.

Setup

Add the processor to your ESLint config for the file types you want:

// flat config
import ember from 'eslint-plugin-ember';

export default [
  {
    files: ['**/*.gjs', '**/*.gts'],
    processor: 'ember/template-lint-disable',
    // ... other config
  },
  {
    files: ['**/*.hbs'],
    processor: 'ember/template-lint-disable',
    // ... other config
  },
];

Usage

{{! disable all rules on the next line }}
{{! template-lint-disable }}
{{test}}

{{! disable a specific rule }}
{{!-- template-lint-disable no-undef --}}
{{test}}

Rule names can be specified in three forms:

  • Direct ESLint rule IDs: no-undef, ember/template-no-bare-strings
  • Plugin-qualified names: template-no-bare-strings (maps to ember/template-no-bare-strings)
  • Template-lint style names: no-bare-strings (maps to ember/template-no-bare-strings)

Differences from ember-template-lint

  • Next-line only: template-lint-disable suppresses the next line (and the comment line itself). It does NOT disable for the rest of the scope — there is no template-lint-enable counterpart.
  • Only template-lint-disable is recognized: template-lint-disable-next-line and template-lint-disable-tree are not matched and will silently do nothing.
  • HTML comments (<!-- -->) are not supported — only Handlebars mustache comments ({{! }} and {{!-- --}}).

Implementation

Wraps the existing noop processor with a new processor that:

  1. In preprocess: fast-path skips files without template-lint-disable in the text; otherwise parses comments and records which lines/rules should be suppressed
  2. In postprocess: delegates to noop.postprocess for gjs/gts files (config validation), then filters out suppressed messages

Follow-up

  • README documentation for the new processor
  • Consider wiring into default configs for .hbs files (which have no processor config yet)

Test plan

  • Disables all rules on next line with mustache comment {{! template-lint-disable }}
  • Disables all rules on next line with mustache block comment {{!-- template-lint-disable --}}
  • Disables a specific rule by name (all three forms)
  • Only disables the next line, not subsequent lines
  • Does not suppress unrelated rules when a specific rule is named
  • Supports multiple rule names
  • Works with multiple disable comments in the same file
  • Suppresses errors on the comment line itself
  • Does NOT match template-lint-disable-next-line or template-lint-disable-tree
  • Matches template-no-bare-strings middle form (ember/ prefix mapping)
  • Error messages on .hbs files are not corrupted with gjs/gts setup instructions
  • Parses @-scoped rule names without breaking the regex
  • All existing parser tests continue to pass
  • Self-lint passes
  • Benchmarks show no measurable performance regression

🤖 Generated with Claude Code

Comment thread lib/processors/template-lint-disable.js Fixed
Comment thread lib/processors/template-lint-disable.js Fixed
Comment thread lib/recommended.mjs
Comment thread lib/index.js Outdated
Comment thread tests/lib/rules-preprocessor/gjs-gts-parser-test.js Outdated
Comment thread lib/config-legacy/base.js
Comment thread lib/config/base.js
Copy link
Copy Markdown
Contributor

@NullVoxPopuli NullVoxPopuli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will need a perf test / benchmark

Copy link
Copy Markdown
Contributor

@johanrd johanrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Review of template-lint-disable processor

Claude reviewed the final state across all 9 commits. The core approach — wrapping the noop processor to filter messages in postprocess — is sound. A few issues to address:

Must fix

1. Add early-exit in preprocess for files without directives

Every file pays the cost of text.split('\n') + regex matching even when no template-lint-disable comment exists. Possible fix:

preprocess(text, fileName) {
  if (!text.includes('template-lint-disable')) {
    fileDisableDirectives.delete(fileName);
    return [text];
  }
  // ... existing parsing logic
}

This makes the overhead near-zero for the common case (no disable comments).

2. commentLine suppression is over-broad

if (message.line !== directive.commentLine && message.line !== directive.nextLine) {

This suppresses errors on the comment line itself, not just the next line. That means:

Hello world {{! template-lint-disable }}

Would suppress the no-bare-strings error for "Hello world" — the error comes before the comment but is on the same line. The comment explains this is for a TextNode edge case where the newline after the comment belongs to the next TextNode. But it's a blunt instrument.

Options:

  • (a) Accept this and document it (ESLint's own eslint-disable-line also suppresses the whole line)
  • (b) Only suppress commentLine when message.column > commentEndColumn (would need to track comment position)

I'd go with (a) and document it, but it should be a conscious choice.

3. Misleading test name in gjs tests

it('supports template-lint rule name format (maps to ember/ prefix)', async () => {
    // ...
    {{! template-lint-disable no-undef }}

This test passes because no-undef matches directly as an ESLint rule ID (ruleId === disableRuleName), not because of the ember/template- prefix mapping. The test name claims it's testing the mapping but it isn't. The hbs test with no-bare-stringsember/template-no-bare-strings correctly tests this. Fix the gjs test name or use an actual ember/template-* rule.

Should fix

4. Semantic mismatch with ember-template-lint

In ember-template-lint, {{! template-lint-disable }} disables from that point for the rest of the scope (until template-lint-enable). This PR gives it "next line only" semantics (like eslint-disable-next-line).

Users migrating from ember-template-lint will expect disable/enable pairs to work:

{{! template-lint-disable }}
  line 1
  line 2
  line 3
{{! template-lint-enable }}

But only line 1 would be suppressed here.

Consider either:

  • (a) Rename to template-lint-disable-next-line to avoid confusion, or
  • (b) Implement range-based disable/enable to match ember-template-lint behavior, or
  • (c) Document the difference prominently

5. Regex doesn't support @ for scoped plugin rules

[\s\w,/-] can't match @typescript-eslint/no-unused-vars or @ember/no-classic-classes. Adding @ to the character class would fix this:

/{{!\s+template-lint-disable([\s\w,/@-]*)}}/g

NullVoxPopuli and others added 9 commits April 11, 2026 08:31
Implements a `template-lint-disable` comment directive that suppresses
lint errors on the next line in template regions of gjs/gts/hbs files.
This works like `eslint-disable-next-line` but uses the template-lint
naming convention familiar to ember-template-lint users.

Supported comment formats:
  {{! template-lint-disable }}
  {{!-- template-lint-disable rule-name --}}
  <!-- template-lint-disable rule-name -->

Rule names can be specified as either eslint rule IDs (e.g. `no-undef`)
or with `ember/template-` prefix (e.g. `ember/template-no-bare-strings`).
When no rule is specified, all rules are suppressed on the next line.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Only mustache comments ({{! ... }} and {{!-- ... --}}) are supported
for the template-lint-disable directive.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
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]>
Split the single regex with overlapping quantifiers into two separate
patterns for mustache comments and mustache block comments. Each uses
a specific character class ([\w\s,/-]*) that cannot cause polynomial
backtracking. Also removes the -+$ cleanup regex since the block
comment pattern now captures only the rules content without trailing
dashes.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Reorder regex char class per unicorn/better-regex
- Use for-of loop per unicorn/no-for-loop
- Move initHbsESLint to outer scope per unicorn/consistent-function-scoping
- Disable import/no-unresolved for ember-eslint-parser/hbs (valid export)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Revert recommended.mjs to original (cannot change recommended config)
- Keep noop processor unchanged; export template-lint-disable as a
  separate processor (ember/template-lint-disable) to avoid breaking
  existing gjs/gts users
- Wire hbs files to use the new processor by default in base configs
  (hbs had no prior config, so this is additive)
- Move hbs tests to a separate hbs-parser-test.js file
- Update gjs template-lint-disable tests to explicitly use the new
  processor via initESLintWithTemplateLintDisable()

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Cannot change existing configs; the template-lint-disable processor
is available for users to opt into via their own config.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
No config changes in this PR; the template-lint-disable processor
is exported for users to opt into in their own config.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent force-pushed the feat/template-lint-disable-pragma branch from fcc9ee7 to 2df802f Compare April 11, 2026 12:31
@NullVoxPopuli
Copy link
Copy Markdown
Contributor

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
js small 14.35 ms 14.39 ms +0.3%
🟢 js medium 7.29 ms 7.09 ms -2.8%
🟢 js large 2.90 ms 2.83 ms -2.4%
gjs small 1.24 ms 1.25 ms +0.5%
gjs medium 624.14 µs 621.74 µs -0.4%
gjs large 246.88 µs 246.91 µs +0.0%
gts small 1.24 ms 1.24 ms +0.2%
gts medium 619.99 µs 621.22 µs +0.2%
gts large 246.83 µs 246.23 µs -0.2%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~3.07 GHz
cpu: AMD EPYC 7763 64-Core Processor
runtime: node 24.14.1 (x64-linux)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
js small (control)            17.06 ms/iter  18.00 ms █ ▂                  
                      (12.60 ms … 28.74 ms)  27.90 ms █▇█ ▂  ▂             
                    (  5.76 mb …  10.13 mb)   7.27 mb ███▁█▇▁█▁▄▁▁▄▁▁▄▁▄▄▄▇

js small (experiment)         14.99 ms/iter  15.67 ms     █                
                      (13.14 ms … 21.11 ms)  18.96 ms   ▆ █▃               
                    (  6.71 mb …   8.23 mb)   6.83 mb ▆██▆███▁▆█▆▄▁▄▄▁▁▁▄▁▄

                             ┌                                            ┐
                             ╷┌───────────┬──┐                            ╷
          js small (control) ├┤           │  ├────────────────────────────┤
                             ╵└───────────┴──┘                            ╵
                               ╷ ┌──┬─┐         ╷
       js small (experiment)   ├─┤  │ ├─────────┤
                               ╵ └──┴─┘         ╵
                             └                                            ┘
                             12.60 ms           20.25 ms           27.90 ms

summary
  js small (experiment)
   1.14x faster than js small (control)
                    (212.91 kb …   1.62 mb)   1.06 mb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)         1.35 ms/iter   1.28 ms █                    
                        (1.21 ms … 5.25 ms)   4.72 ms █                    
                    (340.34 kb …   1.79 mb)   1.06 mb █▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
         gjs small (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌─┬                                        ╷
      gjs small (experiment) │ │────────────────────────────────────────┤
                             └─┴                                        ╵
                             └                                            ┘
                             1.20 ms            3.04 ms             4.88 ms

summary
  gjs small (experiment)
   1.02x faster than gjs small (control)

------------------------------------------- -------------------------------
gjs medium (control)         673.00 µs/iter 643.99 µs █                    
                      (592.68 µs … 4.91 ms)   3.39 ms █                    
                    (405.43 kb …   1.38 mb) 543.55 kb █▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      674.22 µs/iter 637.95 µs ▂█                   
                      (587.86 µs … 4.84 ms)   1.81 ms ██                   
                    ( 66.36 kb …   1.13 mb) 541.39 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
        gjs medium (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌┬                  ╷
     gjs medium (experiment) ││──────────────────┤
                             └┴                  ╵
                             └                                            ┘
                             587.86 µs           1.99 ms            3.39 ms

summary
  gjs medium (control)
   1x faster than gjs medium (experiment)

------------------------------------------- -------------------------------
gjs large (control)          269.42 µs/iter 263.41 µs  █                   
                      (236.17 µs … 4.19 ms) 384.57 µs ▃█▄                  
                    (  7.73 kb …   1.27 mb) 217.33 kb ███▄█▆▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       266.27 µs/iter 262.73 µs  █▂                  
                      (235.97 µs … 4.01 ms) 363.18 µs  ██                  
                    (215.70 kb …   1.06 mb) 216.95 kb ███▄▅▇▆▂▂▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌───────┬                                  ╷
         gjs large (control) ├─┤       │──────────────────────────────────┤
                             ╵ └───────┴                                  ╵
                             ╷┌───────┬                             ╷
      gjs large (experiment) ├┤       │─────────────────────────────┤
                             ╵└───────┴                             ╵
                             └                                            ┘
                             235.97 µs         310.27 µs          384.57 µs

summary
  gjs large (experiment)
   1.01x faster than gjs large (control)

------------------------------------------- -------------------------------
gts small (control)            1.33 ms/iter   1.27 ms █                    
                        (1.20 ms … 5.51 ms)   4.71 ms █                    
                    (616.85 kb …   1.70 mb)   1.06 mb █▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)         1.32 ms/iter   1.26 ms █                    
                        (1.20 ms … 5.23 ms)   4.55 ms █                    
                    (574.16 kb …   1.76 mb)   1.05 mb █▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
         gts small (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌─┬                                        ╷
      gts small (experiment) │ │────────────────────────────────────────┤
                             └─┴                                        ╵
                             └                                            ┘
                             1.20 ms            2.96 ms             4.71 ms

summary
  gts small (experiment)
   1x faster than gts small (control)

------------------------------------------- -------------------------------
gts medium (control)         661.81 µs/iter 637.99 µs █                    
                      (590.10 µs … 4.46 ms)   1.84 ms ██                   
                    (110.10 kb …   1.08 mb) 540.76 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      664.23 µs/iter 637.73 µs ▃█                   
                      (589.37 µs … 4.50 ms)   1.79 ms ██                   
                    ( 19.38 kb …   1.25 mb) 540.86 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─┬                                         ╷
        gts medium (control) ├┤ │─────────────────────────────────────────┤
                             ╵└─┴                                         ╵
                             ╷┌─┬                                       ╷
     gts medium (experiment) ├┤ │───────────────────────────────────────┤
                             ╵└─┴                                       ╵
                             └                                            ┘
                             589.37 µs           1.22 ms            1.84 ms

summary
  gts medium (control)
   1x faster than gts medium (experiment)

------------------------------------------- -------------------------------
gts large (control)          267.07 µs/iter 263.14 µs  █▅                  
                      (236.56 µs … 3.99 ms) 359.70 µs ▆██                  
                    ( 28.74 kb … 777.30 kb) 216.77 kb ███▄▆█▆▃▂▂▂▁▁▁▁▁▁▁▁▁▁

gts large (experiment)       267.17 µs/iter 263.26 µs  █                   
                      (235.04 µs … 4.17 ms) 344.83 µs  ██                  
                    (183.82 kb …   1.31 mb) 216.85 kb ▂██▇▃▇▆▅▂▂▂▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                              ╷┌─────────┬                                ╷
         gts large (control)  ├┤         │────────────────────────────────┤
                              ╵└─────────┴                                ╵
                             ╷ ┌─────────┬                           ╷
      gts large (experiment) ├─┤         │───────────────────────────┤
                             ╵ └─────────┴                           ╵
                             └                                            ┘
                             235.04 µs         297.37 µs          359.70 µs

summary
  gts large (control)
   1x faster than gts large (experiment)

- Add early-exit in preprocess for files without directives (near-zero
  overhead for common case)
- Add `@` to regex character classes to support scoped plugin rules
  like @typescript-eslint/no-unused-vars
- Document semantic difference from ember-template-lint (next-line-only
  vs scope-based disable) and comment-line suppression behavior
- Fix misleading test name that claimed to test ember/ prefix mapping
  but was actually testing exact rule ID matching

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@johanrd
Copy link
Copy Markdown
Contributor

johanrd commented Apr 11, 2026

Re-review by claude:

Round-1 items 1–5 all look addressed ✓. Re-running the review surfaced one thing we missed the first time.

🔴 noop.postprocess corrupts error messages on .hbs files

lib/processors/template-lint-disable.js delegates to noop.postprocess(messages, fileName) unconditionally. But the upstream noop processor (in ember-eslint-parser) is gjs/gts-specific: its postprocess
appends a "To lint Gjs/Gts files please follow the setup guide…" blurb whenever parsedFiles.has(fileName) is false, and only the gjs-gts parser calls registerParsedFile — the hbs parser never does.

Result: on every .hbs file using this processor, the first real lint message has its .message field mutated to include the gjs/gts setup blurb. Verified by instrumenting postprocess and running
hbs-parser-test.js:

inputMessages = [{ ruleId: "ember/template-no-bare-strings",
                   message: "Non-translated string used", … }]
                                                                                                                                                                                                                
after noop.postprocess msgs =
  [{ ruleId: "ember/template-no-bare-strings",                                                                                                                                                                  
     message: "Non-translated string used\n
               To lint Gjs/Gts files please follow the setup guide
               at https://github.com/ember-cli/eslint-plugin-ember#gtsgjs\n                                                                                                                                     
               Note that this error can also happen if you have multiple
               versions of eslint-plugin-ember in your node_modules",                                                                                                                                           
     … }]                                                                                                                                                                                                       

The hbs tests pass because none assert on .message — only on toHaveLength(...) and ruleId — but real hbs users will see every first error suffixed with irrelevant gjs/gts setup instructions.

Suggested fix — only run noop's validation for gjs/gts:

postprocess(messages, fileName) {
  const msgs = /\.(gjs|gts)$/.test(fileName)                                                                                                                                                                    
    ? noop.postprocess(messages, fileName)
    : messages.flat();                                                                                                                                                                                          
  // … existing filter logic                                                                                                                                                                                    
}              

While at it, it'd be worth adding an assertion on .message in at least one hbs test so this kind of regression is caught next time.

🟡 Should fix — missing test coverage for behavior added in the last commit

  • @-scoped rule names: @ was added to the regex character classes to support @typescript-eslint/... / @ember/..., but no test exercises it.
  • Comment-line suppression: the doc comment says "errors on the comment line itself are also suppressed (like eslint-disable-line)", but no test pins this down. A case like {{test}} {{! template-lint-disable }} on a single line would lock it in.

🟡 Minor — fileDisableDirectives Map can leak on parse errors

Module-level Map keyed by fileName. If ESLint ever calls preprocess without a matching postprocess (parse error, crash), entries linger. Slow leak in long-running editor sessions. Not a biggie.

NullVoxPopuli and others added 3 commits April 11, 2026 16:53
- Only delegate to noop.postprocess for gjs/gts files — the hbs parser
  never calls registerParsedFile, so noop appends irrelevant gjs/gts
  setup instructions to hbs error messages
- Add .message assertion in hbs test to catch future regressions
- Add test for comment-line suppression (eslint-disable-line behavior)
- Add test for @-scoped plugin rule names in regex

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Hoist gjs/gts extension regex to module level to avoid recompiling
  on every postprocess call
- Remove duplicate "disables a rule matched by exact ESLint rule ID"
  test — identical to "disables a specific rule by eslint rule name"
- Rename @-scoped test to accurately describe what it tests (regex
  parsing, not rule suppression)
- Clarify doc comment: suppression covers both comment line and next
  line (not just "like eslint-disable-line")

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Explain why MUSTACHE_COMMENT_REGEX uses \s+ (required to avoid
  ambiguity with {{!text}} syntax) while MUSTACHE_BLOCK_COMMENT_REGEX
  uses \s* (dashes already delimit the comment)
- Drop misleading "like eslint-disable-line" from test name

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@johanrd
Copy link
Copy Markdown
Contributor

johanrd commented Apr 11, 2026

re-review dump:

🔴 {{! template-lint-disable-next-line }} silently no-ops

The regex capture class [\s\w,/@-]* accepts -, so -next-line slides into the capture group, gets split as a bogus rule name '-next-line', matches nothing, and no suppression happens. Verified:

BUG | template-lint-disable-next-line form | expected 0 got 1 — [L2] ember/template-no-bare-strings
BUG | template-lint-disable-tree form | expected 0 got 1 — [L2] ember/template-no-bare-strings

This is the spelling users migrating from ember-template-lint will reach for first, and they'll get silent failure with no feedback. Simplest fix: anchor the match with a lookahead so anything other than
whitespace or } after disable fails to match:

const MUSTACHE_COMMENT_REGEX = /{{!\s+template-lint-disable(?=[\s}])([\s\w,/@-]*)}}/g;
const MUSTACHE_BLOCK_COMMENT_REGEX = /{{!--\s*template-lint-disable(?=[\s-])([\s\w,/@-]*)--}}/g;                                                                                                                

That way template-lint-disable-next-line stops matching entirely, the pragma visibly does nothing, and the user has a chance to notice.

🔴 The template-no-bare-strings middle form is silently ignored

matchesRule handles no-bare-strings (short → prefixed) and ember/template-no-bare-strings (already qualified), but not template-no-bare-strings — which is literally the filename in lib/rules/ and the name
used in the plugin's own docs. Users will copy that name and get silent failure. Three-line fix:

function matchesRule(ruleId, disableRuleName) {
if (ruleId === disableRuleName) return true;
if (ruleId === ember/${disableRuleName}) return true; // ← add this
if (ruleId === ember/template-${disableRuleName}) return true;
return false;
}

🔴 The feature is not wired up anywhere and has no docs

f48d3e6 and c981dfa reverted both lib/config/base.js and lib/config-legacy/base.js, so the recommended configs still set processor: 'ember/noop'. The new processor sits at
plugin.processors['template-lint-disable'] but no shipped config references it, and there's no README update in the diff. Effect: the feature does nothing unless a user reads the PR and manually adds
processor: 'ember/template-lint-disable' to their flat config.

The PR description still says "The processor is registered under the same noop key so all existing config references (ember/noop) continue to work unchanged" — that was the original design but it's no longer
what the code does. Either (a) wire the new processor into the default configs in place of noop (which was the original intent), or (b) keep the separate-key approach but add README docs explaining how to opt
in and update the PR description to match. Right now code and description disagree.

🟡 template-lint-enable is a silent no-op (compounds finding #1)

Next-line-only semantics is an explicit design choice — fine — but combined with #1, a user who writes either of the two forms they already know from ember-template-lint

{{! template-lint-disable-next-line }} ← silent no-op (#1)
{{! template-lint-disable }} ... {{! template-lint-enable }} ← only line 1 suppressed

has no obvious path to a working form without reading the processor source. Worth at least a paragraph in whatever docs accompany this (see #3). An alternative worth considering: rename the directive to
template-lint-disable-next-line (matching ember-template-lint's own next-line directive exactly) and reject bare template-lint-disable. That would fix #1 and #5 in one move and eliminate the ETL semantic
mismatch entirely.

🟡 Dead code: registerParsedFile re-export

lib/processors/template-lint-disable.js:98 re-exports noop.registerParsedFile on the processor object. ESLint's processor API doesn't use it, and ember-eslint-parser's gjs-gts parser imports
registerParsedFile directly from its own noop module — nothing in this repo references the re-export. Safe to delete.

🟡 JS string literals containing the directive text match the regex

The processor runs on the entire file text, not just template regions. In a gjs file, a string like const docs = '{{! template-lint-disable }}'; would match and suppress any errors on the same/next line.
Unlikely in practice but impossible to debug when it happens. Fixing properly would require region awareness the processor doesn't have, so probably accept-and-document in the JSDoc.

- Add regex lookahead to reject template-lint-disable-next-line and
  template-lint-disable-tree — these ember-template-lint directives
  previously matched silently and suppressed nothing
- Add ember/ prefix mapping to matchesRule so template-no-bare-strings
  (the form used in plugin docs and filenames) matches correctly
- Remove dead registerParsedFile re-export (parser imports it directly
  from ember-eslint-parser, not from the processor object)
- Fix template literal lint violation in hbs test
- Document unsupported directives (enable, disable-next-line, disable-tree)
  and JS string literal caveat in JSDoc

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@johanrd
Copy link
Copy Markdown
Contributor

johanrd commented Apr 11, 2026

Everything seems to be in great shape, now, with two comments:

The new processor still sits at plugin.processors['template-lint-disable'] with no shipped config referencing it (both lib/config/base.js and lib/config-legacy/base.js are unchanged from master and still set processor: 'ember/noop'), and there's no README update. A user following the plugin's setup guide will never discover this feature exists.

The PR description still says "The processor is registered under the same noop key so all existing config references (ember/noop) continue to work unchanged", which hasn't matched the code since commits
f48d3e6/c981dfa8 reverted the configs. Either wire the new processor into the default configs in place of noop (which was the original intent and is functionally a superset — the new processor is noop plus
optional directive handling), or keep the separate-key approach but add README docs and update the PR description.

@NullVoxPopuli
Copy link
Copy Markdown
Contributor

I'm not changing the configs without an RFC 🙈

I don't think the new processor will be used unless the configs are updated (which is ideal).

I'll add some README stuff, but but with lots of disclaimers, I don't know if we want to have the same semver guarantees with the template-lint replacements until we have configs for them just yet

Documents the experimental opt-in processor with setup instructions
for both flat and legacy configs, usage examples, supported rule name
forms, and differences from ember-template-lint semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@johanrd
Copy link
Copy Markdown
Contributor

johanrd commented Apr 11, 2026

okay, i think it is fine.

NullVoxPopuli and others added 4 commits April 11, 2026 18:32
- Add parser: 'ember-eslint-parser/hbs' to README config examples —
  required for hbs files but was missing
- Document eslint-disable/eslint-enable as the way to suppress ranges
  (works natively via mustache comments, no special processor needed)
- Add test verifying eslint-disable/eslint-enable range suppression
  works in hbs templates

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Standard ESLint directives (eslint-disable-next-line, eslint-disable/
eslint-enable ranges) work natively in mustache comments alongside
the template-lint-disable processor. Add both forms to the README
and add a test for eslint-disable-next-line in hbs.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Verifies that standard ESLint range directives via mustache comments
work correctly in gjs templates — errors inside the disable/enable
range are suppressed, errors after the range still fire.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@johanrd
Copy link
Copy Markdown
Contributor

johanrd commented Apr 13, 2026

@NullVoxPopuli added an experiment of range-version here: NullVoxPopuli-ai-agent#3 – Supporting range disable could be important to be a simple migration for candidate for ember-template-lint

Or maybe it is enough with next line + whole file disable?

@johanrd
Copy link
Copy Markdown
Contributor

johanrd commented Apr 19, 2026

prototype of alternative codemod johanrd#3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants