Add rule: ember/template-no-deprecated#2448
Conversation
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>
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. |
NullVoxPopuli
left a comment
There was a problem hiding this comment.
please undo the config changes
docs/rules/template-no-deprecated.md
Outdated
|
|
||
| ## 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`. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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>
- 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>
lib/rules/template-no-deprecated.js
Outdated
| @@ -0,0 +1,127 @@ | |||
| 'use strict'; | |||
|
|
|||
| // ts.SymbolFlags.Alias = 2097152 (1 << 21) — stable across all TypeScript versions | |||
There was a problem hiding this comment.
is this actually stable across all TS versions? do we have that guarantee?
There was a problem hiding this comment.
❯ "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:
- Add typescript as a peer dependency (already required for typed linting) and require('typescript') directly — this is what @typescript-eslint rules do.
- Keep the hardcoded value but fix the comment to say "stable in practice since TypeScript 1.x, not formally guaranteed" — honest and low-risk.
There was a problem hiding this comment.
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
NullVoxPopuli
left a comment
There was a problem hiding this comment.
seems a lot closer -- ci red tho
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
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>
|
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 |
There was a problem hiding this comment.
should also test the sub-expression syntax.
e.g.: {{fn (deprecatedFn 1 2)}}
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' }], |
There was a problem hiding this comment.
Summary
ember/template-no-deprecated, a new type-aware rule that flags deprecated Glimmer components, helpers, and modifiers used in<template>blocksrecommended-gtsconfig; no-op withoutparserServices.program(i.e. in plain.gjsfiles)tests/rule-setup.jsto filter subdirectories when comparing test filenames to rule namesHow it works
The rule bridges the gap between Glimmer's synthetic AST nodes and TypeScript's type system:
GlimmerElementNode/GlimmerPathExpression→ find ESLint scope referencedef.type === 'ImportBinding')def.node→ TypeScript AST node viaesTreeNodeToTSNodeMapchecker.getImmediateAliasedSymbol) to reach the exported symbolsymbol.getJsDocTags(checker)for a@deprecatedtagCovered syntax
<DeprecatedComponent />GlimmerElementNode{{deprecatedHelper}}GlimmerPathExpression{{#deprecatedBlock}}GlimmerPathExpression(block path)<div {{deprecatedModifier}}>GlimmerPathExpression<MyComp @deprecatedArg={{x}}>is not covered —@argnodes are not scope-registered byember-eslint-parser. Options and constraints are documented indocs/arg-deprecation-future-work.md.Test plan
pnpm testpasses (137/137 test files, 3696/3696 tests)parserServices.program🤖 Generated with Claude Code