npx skills add ...
npx skills add bitwarden/clients --skill fix-angular-fixmes
npx skills add bitwarden/clients --skill fix-angular-fixmes
Resolves eslint-disable suppression comments throughout the Bitwarden clients codebase by fixing the underlying issue. Use when the user asks to "fix FIXMEs", "fix eslint suppressions", "clean up eslint-disable-next-line", "resolve CL-764", "resolve CL-903", "fix OnPush eslint suppressions", "fix Signals eslint suppressions", or reduce linting suppressions.
angular-modernization skill is the authoritative source — this skill only owns the ESLint suppression mechanics.Use the Grep tool to find suppressions in the target path:
eslint-disable — finds all eslint suppressionsFIXME.*CL- — finds Angular FIXME-tracked ones specificallyGroup results by rule name. Two forms appear in this codebase:
Form A — FIXME-paired (a FIXME tracking comment sits above the disable):
Form B — Standalone (disable without a FIXME, or with a CLI skip comment):
Both forms must be fixed the same way.
| Category | Rule | Section below |
|---|---|---|
| Angular | @angular-eslint/prefer-on-push-component-change-detection | OnPush |
| Angular | @angular-eslint/prefer-signals | Signals |
| Angular | @angular-eslint/prefer-output-emitter-ref | Signals |
| Angular template | @angular-eslint/template/button-has-type | HTML rules |
| TypeScript | @typescript-eslint/no-floating-promises | no-floating-promises |
| TypeScript | @typescript-eslint/no-unused-vars | no-unused-vars |
| TypeScript | @typescript-eslint/no-unsafe-function-type | no-unsafe-function-type |
| RxJS | rxjs/no-async-subscribe | rxjs rules |
| RxJS | rxjs-angular/prefer-takeuntil | rxjs rules |
| Bitwarden | @bitwarden/platform/no-enums | no-enums |
| Bitwarden | @bitwarden/components/no-bwi-class-usage | HTML rules |
| General | no-restricted-imports | no-restricted-imports |
| General | no-console | no-console |
| General | no-empty | no-empty |
| General | bare // eslint-disable-next-line | bare disable |
| Tailwind | tailwindcss/no-custom-classname | HTML rules |
Rule: @angular-eslint/prefer-on-push-component-change-detection
Follow the OnPush guidance in the angular-modernization skill (add changeDetection: ChangeDetectionStrategy.OnPush, remove ChangeDetectorRef if only used for detectChanges()). Then remove the FIXME + eslint-disable-next-line lines.
@Directivedoes not supportchangeDetection— skip OnPush for pure directives.
Rules: @angular-eslint/prefer-signals, @angular-eslint/prefer-output-emitter-ref
Applies to @Input(), @Output(), @ViewChild, @ContentChild.
Follow the Signal Inputs, Outputs, and Queries guidance in the angular-modernization skill (prefer CLI schematics, then manual conversion). After each migration, manually remove the FIXME and eslint-disable-next-line lines, and any // TODO: Skipped for signal migration because: comment blocks.
Do NOT convert service observables to signals — only component-local state and decorator bindings (ADR-0027).
Rule: @typescript-eslint/no-floating-promises
A returned Promise is not handled. Pick one fix:
Use void for navigation or toast calls that genuinely don't need awaiting. Use await when the result matters or you're already in an async context.
Rule: @typescript-eslint/no-unused-vars
Rule: @typescript-eslint/no-unsafe-function-type
Replace the generic Function type with a specific signature:
Before
After — use the actual signature
Rules: rxjs/no-async-subscribe, rxjs-angular/prefer-takeuntil
rxjs/no-async-subscribe — async callback inside .subscribe() swallows errors:
Before
After — move async work into the pipe
rxjs-angular/prefer-takeuntil — subscription without cleanup:
Before
After — add takeUntilDestroyed() (call in constructor or use destroyRef)
Rule: @bitwarden/platform/no-enums
Convert TypeScript enums to const objects with type aliases (ADR-0025):
Before
After
Update all import sites — the usage (CipherType.Login) stays the same.
Rule: no-restricted-imports
The import is from a path that the ESLint config forbids. Steps:
eslint.config.mjs at the repo root (or the nearest config) for the no-restricted-imports rule to find the allowed alternative path.Common cases: importing platform-internal modules directly instead of through the public API, or test-only helpers in non-test files.
Rule: no-console
In test files (*.spec.ts), a console.error or console.warn spy may be intentional — in that case, set up the spy properly rather than suppressing:
Rule: no-empty
Empty catch blocks silently swallow errors:
Before
After — handle or log the error
Rule: bare // eslint-disable-next-line (no rule specified)
This disables ALL rules for the next line, which is always wrong. Steps:
npm run lint:fix to see which specific rule triggers.@angular-eslint/template/button-has-type — Add an explicit type to every <button>:
Before
After
@bitwarden/components/no-bwi-class-usage — Replace raw bwi-* icon classes with the <bit-icon> component or the appropriate icon token.
tailwindcss/no-custom-classname — Use a Tailwind utility class with the tw- prefix, or register the class in the Tailwind safelist. Never use arbitrary custom class names.
// FIXME(…) line removed (if present)// TODO: Skipped for signal migration because: … block removed (all lines, if present)// eslint-disable-next-line … line removed())Fix any errors that remain. Run npm run test if behaviour-critical code was changed.
// Remove unused variable
const unused = computeSomething(); // delete this line
// Or prefix with _ if it must be declared (e.g. destructuring)
const [_first, second] = array;
// Or suppress a catch variable (TypeScript 4.0+)
try { ... } catch { ... } // omit the variable entirelyprivate callback: Function;private callback: () => void;
// or for unknown signatures:
private callback: (...args: unknown[]) => unknown;this.service.data$.subscribe(async (value) => {
await this.process(value);
});this.service.data$
.pipe(
switchMap((value) => this.process(value)),
takeUntilDestroyed(),
)
.subscribe();this.service.data$.subscribe((value) => {
this.data = value;
});constructor() {
this.service.data$
.pipe(takeUntilDestroyed())
.subscribe((value) => { this.data = value; });
}enum CipherType {
Login = 1,
SecureNote = 2,
}export const CipherType = Object.freeze({ Login: 1, SecureNote: 2 } as const);
export type CipherType = (typeof CipherType)[keyof typeof CipherType];// Remove debug statements
console.log("debug"); // delete
// Replace with the application logging service
this.logService.error("Something failed", error);jest.spyOn(console, "error").mockImplementation(() => {});try {
await something();
// eslint-disable-next-line no-empty
} catch {}try {
await something();
} catch (e) {
// Intentionally ignored — operation is best-effort
}
// Or log it
try {
await something();
} catch (e) {
this.logService.warning("Operation failed", e);
}<button (click)="save()">Save</button><button type="button" (click)="save()">Save</button>
<!-- or type="submit" inside a <form> -->npm run lint:fix