or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility-rules.mdangular-framework.mdcomponent-rules.mddecorator-metadata-rules.mddirective-rules.mdindex.mdinput-output-rules.mdlifecycle-rules.mdpipe-rules.mdstyle-architecture-rules.mdtemplate-rules.md
tile.json

style-architecture-rules.mddocs/

Style and Architecture Rules

Validation rules for code style, architecture patterns, and Angular best practices that promote maintainable and performant applications.

Capabilities

Import Destructuring Spacing Rule

Enforces consistent spacing in import destructuring statements.

/**
 * Enforces consistent spacing in import destructuring statements
 * Maintains consistent code formatting across the application
 */
export class ImportDestructuringSpacingRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Good import spacing:

import { Component, Input, Output, EventEmitter } from '@angular/core';  // ✅ Consistent spacing
import { Observable, BehaviorSubject } from 'rxjs';  // ✅ Consistent spacing

No Unused CSS Rule

Detects unused CSS rules in component stylesheets.

/**
 * Detects unused CSS rules in component stylesheets
 * Helps maintain clean stylesheets by identifying unused styles
 */
export class NoUnusedCssRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Component with unused styles:

@Component({
  selector: 'app-user',
  template: `
    <div class="user-card">
      <h2 class="user-name">{{ user.name }}</h2>
    </div>
  `,
  styles: [`
    .user-card { padding: 16px; }      // ✅ Used in template
    .user-name { font-size: 18px; }    // ✅ Used in template
    .user-email { color: #666; }       // ❌ Not used in template
    .user-avatar { border-radius: 50%; } // ❌ Not used in template
  `]
})
export class UserComponent {
  @Input() user: User;
}

Prefer Inline Decorator Rule

Prefers inline decorator usage over multi-line decorator syntax.

/**
 * Prefers inline decorator usage for simple cases
 * Promotes concise code when decorator configuration is simple
 */
export class PreferInlineDecoratorRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Preferred inline style:

@Component({ selector: 'app-user', templateUrl: './user.component.html' })  // ✅ Inline for simple config
export class UserComponent {}

@Input() name: string;  // ✅ Simple decorator inline
@Output() change = new EventEmitter<string>();  // ✅ Simple decorator inline

Multi-line when complex:

@Component({  // ✅ Multi-line for complex config
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.ShadowDom
})
export class DashboardComponent {}

Prefer OnPush Component Change Detection Rule

Encourages use of OnPush change detection strategy for better performance.

/**
 * Encourages OnPush change detection strategy
 * Improves application performance by reducing change detection cycles
 */
export class PreferOnPushComponentChangeDetectionRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Preferred OnPush strategy:

@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush  // ✅ OnPush strategy
})
export class UserCardComponent {
  @Input() user: User;
  @Output() userSelected = new EventEmitter<User>();
  
  constructor(private cdr: ChangeDetectorRef) {}
  
  onUserClick() {
    this.userSelected.emit(this.user);
    // Manually trigger change detection when needed
    this.cdr.markForCheck();
  }
}

Prefer Output Readonly Rule

Prefers readonly EventEmitter properties for outputs.

/**
 * Prefers readonly EventEmitter properties for @Output decorators
 * Prevents accidental modification of output event emitters
 */
export class PreferOutputReadonlyRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Preferred readonly outputs:

export class UserComponent {
  @Output() readonly userSelected = new EventEmitter<User>();  // ✅ Readonly
  @Output() readonly dataChanged = new EventEmitter<any>();    // ✅ Readonly
  
  private selectUser(user: User) {
    this.userSelected.emit(user);
  }
}

Relative URL Prefix Rule

Validates relative URL prefixes in component templateUrl and styleUrls.

/**
 * Validates relative URL prefixes in component file references
 * Ensures consistent relative path usage in templateUrl and styleUrls
 */
export class RelativeUrlPrefixRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Good relative URLs:

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',  // ✅ Relative path with ./
  styleUrls: ['./user.component.scss']   // ✅ Relative path with ./
})
export class UserComponent {}

Use Component Selector Rule

Enforces use of component selector in component metadata.

/**
 * Enforces presence of selector in @Component decorator
 * Ensures components have proper selectors for template usage
 */
export class UseComponentSelectorRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Good component with selector:

@Component({
  selector: 'app-user-profile',  // ✅ Has selector
  templateUrl: './user-profile.component.html'
})
export class UserProfileComponent {}

Bad component without selector:

@Component({  // ❌ Missing selector
  templateUrl: './dialog.component.html'
})
export class DialogComponent {}  // Should have selector unless it's a dialog/modal

Use Component View Encapsulation Rule

Enforces explicit view encapsulation strategy in components.

/**
 * Enforces explicit view encapsulation strategy
 * Ensures consistent styling encapsulation across components
 */
export class UseComponentViewEncapsulationRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Good explicit encapsulation:

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.scss'],
  encapsulation: ViewEncapsulation.Emulated  // ✅ Explicit encapsulation
})
export class CardComponent {}

@Component({
  selector: 'app-global-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
  encapsulation: ViewEncapsulation.None  // ✅ None for global styles
})
export class HeaderComponent {}

Use Injectable Provided In Rule

Enforces use of providedIn for injectable services.

/**
 * Enforces providedIn property for @Injectable decorators
 * Promotes tree-shakable services and proper dependency injection
 */
export class UseInjectableProvidedInRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Good injectable with providedIn:

@Injectable({
  providedIn: 'root'  // ✅ Tree-shakable root service
})
export class UserService {
  private users: User[] = [];
  
  getUsers(): Observable<User[]> {
    return of(this.users);
  }
}

@Injectable({
  providedIn: 'platform'  // ✅ Platform-level service
})
export class ConfigService {}

@Injectable()  // ✅ Module-provided service (acceptable in some cases)
export class FeatureService {}

Bad injectable without providedIn (when it should have it):

@Injectable()  // ❌ Should specify providedIn for root services
export class GlobalUserService {
  // Global service logic
}

Architecture Best Practices

Change Detection Strategy

// ✅ OnPush for performance
@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserListComponent {
  @Input() users: User[];  // Immutable inputs work well with OnPush
  
  constructor(private cdr: ChangeDetectorRef) {}
  
  trackByUser(index: number, user: User): number {
    return user.id;  // TrackBy function for *ngFor performance
  }
}

Service Architecture

// ✅ Tree-shakable services
@Injectable({ providedIn: 'root' })
export class ApiService {
  constructor(private http: HttpClient) {}
}

// ✅ Feature-specific services
@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private api: ApiService) {}
}

// ✅ Module-provided when needed
@Injectable()
export class FeatureSpecificService {
  // Service logic
}

// In feature module:
@NgModule({
  providers: [FeatureSpecificService]
})
export class FeatureModule {}

Component Organization

@Component({
  selector: 'app-product-card',
  templateUrl: './product-card.component.html',
  styleUrls: ['./product-card.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.Emulated
})
export class ProductCardComponent {
  @Input() product: Product;
  @Output() readonly productSelected = new EventEmitter<Product>();
  @Output() readonly addToCart = new EventEmitter<Product>();
  
  @HostBinding('class.featured')
  get isFeatured() {
    return this.product?.featured;
  }
  
  @HostListener('click')
  onCardClick() {
    this.productSelected.emit(this.product);
  }
}

Rule Configuration

Example Configuration

{
  "rules": {
    "import-destructuring-spacing": true,
    "no-unused-css": true,
    "prefer-inline-decorator": [true, { "maxLineLength": 80 }],
    "prefer-on-push-component-change-detection": true,
    "prefer-output-readonly": true,
    "relative-url-prefix": [true, "./"],
    "use-component-selector": true,
    "use-component-view-encapsulation": [true, "Emulated"],
    "use-injectable-provided-in": [true, "root", "platform"]
  }
}

Performance Benefits

OnPush Strategy Benefits

  • Reduces change detection cycles
  • Improves large list performance
  • Forces immutable data patterns
  • Better performance with observables

Tree-shakable Services

  • Smaller bundle sizes
  • Only included when used
  • Better lazy loading support
  • Cleaner dependency graphs

Proper Encapsulation

  • Predictable styling behavior
  • No style leakage between components
  • Better maintainability
  • Clearer component boundaries