UNPKG

@noneforge/eslint-config-angular

Version:

Angular ESLint configuration with TypeScript, component rules, and accessibility

415 lines (340 loc) 11.7 kB
# @noneforge/eslint-config-angular Comprehensive Angular ESLint configuration with TypeScript support, component/template rules, accessibility, and CSS linting. Built on [@noneforge/eslint-config](https://www.npmjs.com/package/@noneforge/eslint-config) ESLint 9+ flat config with Angular 20+ best practices and modern reactive patterns. ## Features -**ESLint 9 Flat Config** - Modern configuration format with better performance - 🎯 **Angular 20+ Optimized** - Specific rules for components, directives, pipes, and services - 🔒 **Accessibility First** - Built-in a11y rules for templates with ARIA support -**Signals & Control Flow** - Support for Angular's latest reactive features - 📦 **Full TypeScript Support** - Extends @noneforge/eslint-config base with Angular specifics - 🎨 **CSS/SCSS Linting** - Integrated CSS validation for component styles - 🚀 **Standalone Components** - Promotes modern Angular architecture patterns - 🔧 **Smart Detection** - Different rules for tests, stories, schematics, and modules ## Related Packages - [@noneforge/eslint-config](https://www.npmjs.com/package/@noneforge/eslint-config) - TypeScript/Javascript base configuration - [@noneforge/eslint-config-node](https://www.npmjs.com/package/@noneforge/eslint-config-node) - Node.js backend configuration ## Requirements - Node.js >=20.19.0 - ESLint >=9.22.0 - TypeScript >=5.9.0 - Angular >=20.0.0 - RxJS >=7.4.0 ## Installation ```bash npm install --save-dev @noneforge/eslint-config-angular eslint typescript ``` or with Yarn: ```bash yarn add --dev @noneforge/eslint-config-angular eslint typescript ``` ## Usage Create an `eslint.config.js` file in your project root: ```javascript import config from '@noneforge/eslint-config-angular'; export default [ ...config, // Your custom rules here ]; ``` ### With Custom Rules ```javascript import config from '@noneforge/eslint-config-angular'; export default [ ...config, { rules: { // Override or add custom rules '@angular-eslint/component-selector': ['error', { type: 'element', prefix: 'my-app', style: 'kebab-case', }], } } ]; ``` ### For Nx Monorepos ```javascript import config from '@noneforge/eslint-config-angular'; export default [ ...config, { languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, project: ['./apps/*/tsconfig.json', './libs/*/tsconfig.json'], } } } ]; ``` ## Rule Categories ### 🎯 Angular Component Rules **Modern Angular patterns and best practices:** - **Standalone Components**: `prefer-standalone` - Enforces standalone architecture - **Signals**: `prefer-signals`, `no-uncalled-signals` - Reactive state management - **Change Detection**: `prefer-on-push-component-change-detection` - Performance optimization - **Lifecycle**: `contextual-lifecycle`, `no-empty-lifecycle-method`, `no-async-lifecycle-method` - **Component Structure**: `component-max-inline-declarations` - Limits inline templates/styles - **Dependency Injection**: `prefer-inject` - Modern DI patterns over constructor injection - **Naming Conventions**: `component-class-suffix`, `directive-class-suffix` - **Selectors**: Enforces `app-` prefix with kebab-case for components, camelCase for directives ```typescript // ❌ Legacy patterns @Component({ selector: 'myComponent', template: `...very long template...`, standalone: false, // Explicitly false triggers the rule changeDetection: ChangeDetectionStrategy.Default }) export class MyComp { constructor(private service: MyService) {} value = 0; } // ✅ Modern Angular patterns @Component({ selector: 'app-my-component', templateUrl: './my-component.html', changeDetection: ChangeDetectionStrategy.OnPush // standalone: true is default in Angular 20, can be omitted }) export class MyComponent { private service = inject(MyService); value = signal(0); } ``` ### 📝 Template Rules **Comprehensive template validation and best practices:** - **Control Flow**: `prefer-control-flow` - New @if/@for/@switch syntax - **Two-Way Binding**: `banana-in-box` - Correct [(ngModel)] syntax - **Type Safety**: `no-any` - Prevents any types in templates - **Performance**: `use-track-by-function` - Required for @for loops - **Image Optimization**: `prefer-ngsrc` - NgOptimizedImage for better performance - **Self-Closing Tags**: `prefer-self-closing-tags` - Cleaner template syntax - **Complexity**: `conditional-complexity`, `cyclomatic-complexity` - Maintainable templates ```html <!-- ❌ Old patterns --> <div *ngIf="condition">Content</div> <img src="{{imageUrl}}" /> <div *ngFor="let item of items">{{item}}</div> <!-- ✅ Modern patterns --> @if (condition) { <div>Content</div> } <img [ngSrc]="imageUrl" width="200" height="100" /> @for (item of items; track item.id) { <div>{{item}}</div> } ``` ### ♿ Accessibility Rules **Built-in a11y compliance for inclusive applications:** - **Images**: `alt-text` - Alternative text required - **Keyboard Navigation**: `click-events-have-key-events`, `mouse-events-have-key-events` - **Interactive Elements**: `interactive-supports-focus` - Focusable interactive elements - **Forms**: `label-has-associated-control` - Proper form labeling - **ARIA**: `role-has-required-aria`, `valid-aria` - Correct ARIA usage - **Focus Management**: `no-autofocus`, `no-positive-tabindex` - **Tables**: `table-scope` - Proper scope attributes - **Distracting Elements**: `no-distracting-elements` - No marquee/blink ```html <!-- ❌ Accessibility issues --> <img [src]="imageUrl"> <div (click)="action()">Click me</div> <input type="text"> <!-- ✅ Accessible patterns --> <img [src]="imageUrl" [alt]="imageAlt"> <button (click)="action()">Click me</button> <label for="name">Name</label> <input id="name" type="text"> ``` ### 🎨 CSS/SCSS Rules **Style validation for component styles:** - **Import Management**: `no-duplicate-imports` - Prevents duplicate @import - **Selectors**: `no-duplicate-keyframe-selectors` - Unique keyframe names - **At-Rules**: `no-invalid-at-rules`, `no-invalid-at-rule-placement` - **Grid**: `no-invalid-named-grid-areas` - Valid CSS Grid areas - **Properties**: `no-invalid-properties` - Catches typos in CSS properties - **Quality**: `no-empty-blocks`, `font-family-fallbacks` - Best practices ```scss // ❌ CSS issues @import 'theme'; @import 'theme'; // Duplicate .empty {} // Empty block .text { font-family: 'CustomFont'; // No fallback colr: red; // Typo } // ✅ Clean styles @import 'theme'; .text { font-family: 'CustomFont', sans-serif; color: red; } ``` ### 🧩 Input/Output Rules **Modern Angular communication patterns:** - **Input Signals**: Preferred over decorators for reactive inputs - **Output Events**: `no-output-native`, `no-output-on-prefix` - Avoids conflicts - **Naming**: `no-input-rename`, `no-output-rename` - Consistent API - **Metadata**: `no-inputs-metadata-property`, `no-outputs-metadata-property` - **Readonly Outputs**: `prefer-output-readonly` - Immutable event emitters ```typescript // ❌ Old patterns @Component({ inputs: ['value'], outputs: ['onChange'] }) export class OldComponent { @Input('aliased') value: string; @Output() onClick = new EventEmitter(); } // ✅ Modern patterns @Component({...}) export class ModernComponent { value = input<string>(); readonly changed = output<string>(); } ``` ### 📁 Smart File Detection #### Test Files (`*.spec.ts`, `*.test.ts`, `*.e2e-spec.ts`) **Relaxed rules for testing:** - Lifecycle calls allowed in tests - OnPush change detection not required - Injectable providedIn requirements relaxed #### Storybook Stories (`*.stories.ts`) **Flexible rules for component documentation:** - Console output allowed - Indentation and line length unrestricted - Component selector requirements relaxed - Type safety warnings instead of errors #### Angular Schematics (`schematics/**/*.ts`) **Practical rules for code generation:** - Console output allowed for CLI feedback - Any types allowed for dynamic operations - Unsafe operations permitted for metaprogramming #### Angular Modules (`*.module.ts`) **Migration support:** - Standalone preference as warning, not error - Supports gradual migration to standalone #### Router Components (`**/+*.ts`) **Special routing patterns:** - Component/directive selector rules disabled - Supports Angular's file-based routing conventions ## Base Configuration This package extends [@noneforge/eslint-config](https://github.com/noneforge/eslint-config) which provides: - Comprehensive TypeScript type checking - Built-in formatting (Prettier replacement) - Import organization and sorting - Modern JavaScript best practices - JSDoc documentation rules See the [base configuration README](https://github.com/noneforge/eslint-config) for details on inherited rules. ## Common Patterns ### Signals and Effects ```typescript // ❌ Traditional reactive patterns export class OldComponent { value = 0; valueSubject = new BehaviorSubject(0); ngOnInit() { this.valueSubject.subscribe(v => this.value = v); } } // ✅ Modern signals export class ModernComponent { value = signal(0); computed = computed(() => this.value() * 2); constructor() { effect(() => { console.log('Value changed:', this.value()); }); } } ``` ### Dependency Injection ```typescript // ❌ Constructor injection export class OldService { constructor( private http: HttpClient, private router: Router, @Inject(CONFIG) private config: Config ) {} } // ✅ inject() function export class ModernService { private http = inject(HttpClient); private router = inject(Router); private config = inject(CONFIG); } ``` ### Control Flow Syntax ```typescript // Template file @if (user()) { <app-user-profile [user]="user()" /> } @else if (loading()) { <app-spinner /> } @else { <p>No user found</p> } @for (item of items(); track item.id) { <app-item [data]="item" /> } @empty { <p>No items available</p> } @switch (status()) { @case ('active') { <app-active /> } @case ('pending') { <app-pending /> } @default { <app-inactive /> } } ``` ## VSCode Integration Add to `.vscode/settings.json`: ```json { "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, "eslint.experimental.useFlatConfig": true, "eslint.validate": [ "javascript", "typescript", "html", "css", "scss" ] } ``` ## Package.json Scripts ```json { "scripts": { "lint": "eslint .", "lint:fix": "eslint . --fix", "lint:debug": "eslint . --debug", "type-check": "tsc --noEmit" } } ``` ## Migration from ESLint 8 1. Remove `.eslintrc.*` files 2. Create `eslint.config.js` with flat config 3. Update VSCode settings for flat config 4. Install this package and its peer dependencies 5. Update scripts to use ESLint 9 CLI ## Performance Tips - Use `projectService: true` for better TypeScript performance - Enable ESLint cache: `eslint . --cache` - Exclude `dist` and `.angular` in your tsconfig.json - Consider `--max-warnings 0` in CI/CD pipelines ## Philosophy This configuration prioritizes: 1. **Modern Angular** - Signals, standalone components, and latest APIs 2. **Accessibility** - Built-in a11y rules for inclusive applications 3. **Performance** - OnPush change detection and optimization patterns 4. **Type Safety** - Leverage TypeScript for runtime error prevention 5. **Developer Experience** - Clear errors with practical escape hatches ## License MIT ## Contributing Issues and PRs welcome at [GitHub](https://github.com/noneforge/eslint-config-angular)