CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-codelyzer

A comprehensive static code analysis tool for Angular TypeScript projects that implements TSLint rules enforcing Angular's official style guide.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

decorator-metadata-rules.mddocs/

Decorator and Metadata Rules

Angular decorator and metadata validation rules that prevent problematic decorator usage and enforce proper metadata configuration patterns.

Capabilities

No Attribute Decorator Rule

Prevents use of @Attribute decorator which has specific limitations and use cases.

/**
 * Prevents use of @Attribute decorator
 * @Attribute decorator has limitations and should generally be avoided in favor of @Input
 */
export class NoAttributeDecoratorRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad usage with @Attribute:

import { Component, Attribute } from '@angular/core';

@Component({
  selector: 'app-user',
  template: '...'
})
export class UserComponent {
  constructor(
    @Attribute('data-id') private dataId: string  // ❌ @Attribute decorator
  ) {}
}

Good usage with @Input:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user',
  template: '...'
})
export class UserComponent {
  @Input() dataId: string;  // ✅ @Input decorator instead
}

No Forward Ref Rule

Prevents use of forwardRef which can indicate circular dependency issues.

/**
 * Prevents use of forwardRef
 * forwardRef often indicates circular dependencies that should be resolved through refactoring
 */
export class NoForwardRefRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad usage with forwardRef:

import { Component, forwardRef, Inject } from '@angular/core';

@Component({
  selector: 'app-child',
  template: '...'
})
export class ChildComponent {
  constructor(
    @Inject(forwardRef(() => ParentComponent)) private parent: ParentComponent  // ❌ forwardRef usage
  ) {}
}

Better approaches:

// ✅ Use service for communication
@Injectable()
export class CommunicationService {
  // Shared communication logic
}

@Component({
  selector: 'app-child',
  template: '...'
})
export class ChildComponent {
  constructor(private commService: CommunicationService) {}
}

// ✅ Use ViewChild or ContentChild for parent-child communication
@Component({
  selector: 'app-parent',
  template: '<app-child #childRef></app-child>'
})
export class ParentComponent {
  @ViewChild('childRef') child: ChildComponent;
}

No Host Metadata Property Rule

Prevents defining host properties in component/directive metadata instead of using @HostBinding and @HostListener.

/**
 * Prevents defining host properties in component metadata
 * Enforces use of @HostBinding and @HostListener decorators for better type safety
 */
export class NoHostMetadataPropertyRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad usage in metadata:

@Component({
  selector: 'app-button',
  template: '...',
  host: {  // ❌ Host in metadata
    '[class.active]': 'isActive',
    '(click)': 'onClick()',
    '[attr.disabled]': 'disabled'
  }
})
export class ButtonComponent {
  isActive = false;
  disabled = false;
  
  onClick() {
    this.isActive = !this.isActive;
  }
}

Good usage with decorators:

@Component({
  selector: 'app-button',
  template: '...'
})
export class ButtonComponent {
  @HostBinding('class.active') isActive = false;  // ✅ @HostBinding decorator
  @HostBinding('attr.disabled') disabled = false;  // ✅ @HostBinding decorator
  
  @HostListener('click')  // ✅ @HostListener decorator
  onClick() {
    this.isActive = !this.isActive;
  }
}

No Queries Metadata Property Rule

Prevents defining queries (ViewChild, ContentChild, etc.) in component metadata instead of using decorators.

/**
 * Prevents defining queries in component metadata
 * Enforces use of @ViewChild, @ContentChild, etc. decorators for better type safety
 */
export class NoQueriesMetadataPropertyRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad usage in metadata:

@Component({
  selector: 'app-parent',
  template: '<app-child #childRef></app-child>',
  queries: {  // ❌ Queries in metadata
    child: new ViewChild('childRef'),
    children: new ViewChildren(ChildComponent)
  }
})
export class ParentComponent {
  child: ChildComponent;
  children: QueryList<ChildComponent>;
}

Good usage with decorators:

@Component({
  selector: 'app-parent',
  template: '<app-child #childRef></app-child>'
})
export class ParentComponent {
  @ViewChild('childRef') child: ChildComponent;  // ✅ @ViewChild decorator
  @ViewChildren(ChildComponent) children: QueryList<ChildComponent>;  // ✅ @ViewChildren decorator
}

Advanced Decorator Patterns

Host Binding Examples

@Component({
  selector: 'app-card',
  template: '...'
})
export class CardComponent {
  @Input() elevation: number = 1;
  @Input() disabled: boolean = false;
  
  // Dynamic class binding
  @HostBinding('class') 
  get cssClasses() {
    return {
      [`elevation-${this.elevation}`]: true,
      'disabled': this.disabled
    };
  }
  
  // Attribute binding
  @HostBinding('attr.aria-disabled')
  get ariaDisabled() {
    return this.disabled ? 'true' : null;
  }
  
  // Style binding
  @HostBinding('style.opacity')
  get opacity() {
    return this.disabled ? 0.5 : 1;
  }
}

Host Listener Examples

@Directive({
  selector: '[appClickOutside]'
})
export class ClickOutsideDirective {
  @Output() clickOutside = new EventEmitter<void>();
  
  @HostListener('document:click', ['$event'])
  onDocumentClick(event: MouseEvent) {
    if (!this.elementRef.nativeElement.contains(event.target)) {
      this.clickOutside.emit();
    }
  }
  
  @HostListener('keydown.escape')
  onEscapeKey() {
    this.clickOutside.emit();
  }
  
  constructor(private elementRef: ElementRef) {}
}

Query Examples

@Component({
  selector: 'app-tabs',
  template: `
    <div class="tab-headers">
      <ng-content select="[slot=header]"></ng-content>
    </div>
    <div class="tab-content">
      <ng-content></ng-content>
    </div>
  `
})
export class TabsComponent implements AfterViewInit {
  // Single element query
  @ViewChild('tabContent', { static: true }) 
  tabContent: ElementRef;
  
  // Multiple elements query
  @ViewChildren('tabHeader') 
  tabHeaders: QueryList<ElementRef>;
  
  // Component query
  @ContentChildren(TabComponent) 
  tabs: QueryList<TabComponent>;
  
  // Static query (available in ngOnInit)
  @ViewChild('staticElement', { static: true }) 
  staticElement: ElementRef;
  
  ngAfterViewInit() {
    // Access query results here
    this.tabs.forEach((tab, index) => {
      tab.tabIndex = index;
    });
  }
}

Rule Configuration

Example Configuration

{
  "rules": {
    "no-attribute-decorator": true,
    "no-forward-ref": true,
    "no-host-metadata-property": true,
    "no-queries-metadata-property": true
  }
}

Benefits of Decorator Approach

Type Safety

  • Decorators provide better TypeScript integration
  • Compile-time checking of property names and types
  • IntelliSense support in IDEs

Readability

  • Decorators are closer to the properties they affect
  • Clearer intent and easier to understand
  • Better code organization

Maintainability

  • Easier to refactor and update
  • Less prone to typos in property names
  • Better IDE support for renaming

Performance

  • Slightly better performance in some cases
  • More efficient change detection
  • Better tree-shaking support

Install with Tessl CLI

npx tessl i tessl/npm-codelyzer

docs

accessibility-rules.md

angular-framework.md

component-rules.md

decorator-metadata-rules.md

directive-rules.md

index.md

input-output-rules.md

lifecycle-rules.md

pipe-rules.md

style-architecture-rules.md

template-rules.md

tile.json