Skip to content

Add rule: ember/template-no-deprecated#2448

Merged
NullVoxPopuli merged 18 commits intoember-cli:masterfrom
wagenet:pwn/template-no-deprecated
Feb 27, 2026
Merged

Add rule: ember/template-no-deprecated#2448
NullVoxPopuli merged 18 commits intoember-cli:masterfrom
wagenet:pwn/template-no-deprecated

Conversation

@wagenet
Copy link
Contributor

@wagenet wagenet commented Feb 26, 2026

Summary

  • Adds ember/template-no-deprecated, a new type-aware rule that flags deprecated Glimmer components, helpers, and modifiers used in <template> blocks
  • Enabled in recommended-gts config; no-op without parserServices.program (i.e. in plain .gjs files)
  • Also fixes tests/rule-setup.js to filter subdirectories when comparing test filenames to rule names

How it works

The rule bridges the gap between Glimmer's synthetic AST nodes and TypeScript's type system:

  1. Walk GlimmerElementNode / GlimmerPathExpression → find ESLint scope reference
  2. Resolve to the import binding definition (def.type === 'ImportBinding')
  3. Map def.node → TypeScript AST node via esTreeNodeToTSNodeMap
  4. Walk the alias chain (checker.getImmediateAliasedSymbol) to reach the exported symbol
  5. Check symbol.getJsDocTags(checker) for a @deprecated tag

Covered syntax

Template syntax AST node
<DeprecatedComponent /> GlimmerElementNode
{{deprecatedHelper}} GlimmerPathExpression
{{#deprecatedBlock}} GlimmerPathExpression (block path)
<div {{deprecatedModifier}}> GlimmerPathExpression

<MyComp @deprecatedArg={{x}}> is not covered — @arg nodes are not scope-registered by ember-eslint-parser. Options and constraints are documented in docs/arg-deprecation-future-work.md.

Test plan

  • pnpm test passes (137/137 test files, 3696/3696 tests)
  • Typed test block exercises deprecated component (element), deprecated helper (mustache), deprecated component (block) — all reported correctly
  • Non-typed test block confirms rule is a no-op without parserServices.program

🤖 Generated with Claude Code

Flags deprecated Glimmer components, helpers, and modifiers used in
<template> by walking from the template reference → ESLint scope variable
→ import binding → TypeScript symbol → JSDoc @deprecated tag.

Requires TypeScript (parserServices.program); no-op in plain .gjs files.
Enabled in recommended-gts config.

Covers:
- <DeprecatedComponent /> (GlimmerElementNode)
- {{deprecatedHelper}} (GlimmerPathExpression)
- {{#deprecatedBlock}} (GlimmerPathExpression block path)
- <div {{deprecatedModifier}}> (GlimmerPathExpression modifier)

@arg deprecation is not yet implemented; see docs/arg-deprecation-future-work.md.

Also fixes tests/rule-setup.js to filter out subdirectories when
checking that test file names match rule names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NullVoxPopuli
Copy link
Contributor

Enabled in recommended-gts config;

Rules that add new rules are not allowed to also add to the recommended set.

Changing the recommended set is a breaking change, and we're currently queuing up > 100 new rules for the next release, which should not be a breaking change release.

Copy link
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.

please undo the config changes


## Rule Details

The rule resolves template references through ESLint's scope analysis: a `<Component>` or `{{helper}}` reference is traced back to its import declaration, then the TypeScript type checker inspects the exported symbol's JSDoc tags for `@deprecated`.

Choose a reason for hiding this comment

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

this worries me as not all coomponents/helpers modifiers will be imported
gjs format allows for multiple components in a single file as well as in scope helpers modifiers or declared on the class itself

Copy link
Contributor

Choose a reason for hiding this comment

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

The scope tracking should handle that

- Remove `require('ts-api-utils')` and `require('typescript')` (not declared deps); inline TS_ALIAS_FLAG constant and displayPartsToString logic
- Remove `recommendedGts: true` and `requiresTypeChecking` from meta (breaking change, per review; fixes meta-property-ordering lint error)
- Fix `eqeqeq`: use `!== undefined` instead of `!= null`
- Fix `no-param-reassign`: use `current` variable instead of reassigning `symbol` param
- Add `output: null` to invalid test cases (consistent-output lint rule)
- Regenerate config files and docs (rule no longer in recommended configs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

Choose a reason for hiding this comment

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

PR needs cleanup

Copy link
Contributor

Choose a reason for hiding this comment

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

why's this here?

wagenet and others added 3 commits February 26, 2026 11:02
- Remove template-no-deprecated from recommended config snapshot
- Revert unrelated semicolon change in recommended-rules.js
- Delete docs/arg-deprecation-future-work.md (not part of this PR)
- Replace box-drawing characters in test comments with plain ASCII
- Revert rule-setup.js filter change (no .ts files in tests/lib/rules/)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The .ts fixture files and usage.gts placeholder are required because:
1. The tsconfig glob must match a physically-existing file for TypeScript
   to include it in the project.
2. The rule only checks ImportBinding definitions, so TypeScript must
   resolve the imports and read @deprecated from the source files.

Added a comment in the test explaining why these files are needed and
why other rules can use virtual filenames without real files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove leading newlines and indentation from template literal test cases
so character positions in error messages are not offset by invisible whitespace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@@ -0,0 +1,127 @@
'use strict';

// ts.SymbolFlags.Alias = 2097152 (1 << 21) — stable across all TypeScript versions
Copy link
Contributor

Choose a reason for hiding this comment

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

is this actually stable across all TS versions? do we have that guarantee?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

❯ "ts.SymbolFlags.Alias = 2097152 (1 << 21) — stable across all TypeScript versions" is this guaranteed?

⏺ No. TypeScript's compiler API has no stability guarantee — SymbolFlags is a const enum and values can shift if flags are inserted. The comment overstates it.

In practice, Alias = 1 << 21 has been unchanged since TypeScript was open-sourced (~2014) and is very unlikely to move because it would silently break every @typescript-eslint/* rule that bit-tests it. But "unlikely to change" ≠ "guaranteed".

The defensible fix is to read the value from the TypeScript module the user has installed rather than hardcoding it:

// Get ts from the program's internal reference to avoid adding a direct dependency
const ts = services.program.getCompilerOptions().configFilePath
? require('typescript')
: undefined;
const TS_ALIAS_FLAG = ts?.SymbolFlags?.Alias ?? 2_097_152;

But that's awkward. The cleaner options are:

  1. Add typescript as a peer dependency (already required for typed linting) and require('typescript') directly — this is what @typescript-eslint rules do.
  2. Keep the hardcoded value but fix the comment to say "stable in practice since TypeScript 1.x, not formally guaranteed" — honest and low-risk.

Copy link
Contributor

Choose a reason for hiding this comment

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

let's keep the hard-coded value, but add this context into the file, as it's important historical context, and may one day requires us require the user's typescript

Copy link
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.

seems a lot closer -- ci red tho

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NullVoxPopuli
Copy link
Contributor

lints!

Explains why it's hardcoded, its stability history, and what to do
if it ever changes in a future TypeScript release.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NullVoxPopuli
Copy link
Contributor

lints!

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove implementation detail from Rule Details; drop broken "see future
work" reference since that doc was removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detects deprecated component arguments in Glimmer templates via a new
GlimmerAttrNode visitor. Navigates the TypeScript type chain from the
component's import symbol through getBaseTypes/getTypeArguments to the
Args object, then checks getJsDocTags on the specific arg's symbol.
Components without typed Args are silently skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
output: null,
errors: [{ messageId: 'deprecatedWithReason', type: 'GlimmerElementNodePart' }],
},
// Deprecated helper in mustache position
Copy link
Contributor

Choose a reason for hiding this comment

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

should also test the sub-expression syntax.

e.g.: {{fn (deprecatedFn 1 2)}}

Copilot AI and others added 4 commits February 27, 2026 19:26
Co-authored-by: wagenet <9835+wagenet@users.noreply.github.com>
…xpression test

Co-authored-by: wagenet <9835+wagenet@users.noreply.github.com>
Co-authored-by: wagenet <9835+wagenet@users.noreply.github.com>
Fix review comments on ember/template-no-deprecated
filename: path.join(FIXTURES_DIR, 'usage.gts'),
code: "import { deprecatedHelper } from './deprecated-helper';\n<template>{{fn (deprecatedHelper)}}</template>",
output: null,
errors: [{ messageId: 'deprecated', type: 'VarHead' }],
Copy link
Contributor

Choose a reason for hiding this comment

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

@NullVoxPopuli NullVoxPopuli merged commit 4629cbb into ember-cli:master Feb 27, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants