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

input-output-rules.mddocs/

Input/Output Rules

Angular component and directive input/output validation rules that enforce best practices for property binding and event handling.

Capabilities

No Input Prefix Rule

Prevents specific prefixes on input properties that could cause confusion.

/**
 * Prevents specific prefixes on @Input properties
 * Avoids naming conflicts and maintains clear property naming
 */
export class NoInputPrefixRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

TSLint configuration:

{
  "rules": {
    "no-input-prefix": [true, "is", "has", "should"]
  }
}

Bad input prefixes:

export class UserComponent {
  @Input() isActive: boolean;    // ❌ 'is' prefix
  @Input() hasPermissions: boolean;  // ❌ 'has' prefix
  @Input() shouldShow: boolean;  // ❌ 'should' prefix
}

Good input names:

export class UserComponent {
  @Input() active: boolean;      // ✅ Clear, direct property name
  @Input() permissions: string[]; // ✅ Noun describing the data
  @Input() visible: boolean;     // ✅ Descriptive without problematic prefix
}

No Input Rename Rule

Prevents renaming of input properties in the @Input decorator.

/**
 * Prevents renaming @Input properties in the decorator
 * Maintains consistency between property names and template usage
 */
export class NoInputRenameRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad input renaming:

export class UserComponent {
  @Input('userName') name: string;     // ❌ Renamed input
  @Input('isActive') active: boolean;  // ❌ Renamed input
}

Good input naming:

export class UserComponent {
  @Input() userName: string;    // ✅ Property name matches input name
  @Input() active: boolean;     // ✅ No renaming
}

Template usage:

<app-user [userName]="user.name" [active]="user.isActive"></app-user>

No Inputs Metadata Property Rule

Prevents defining inputs in the component/directive metadata instead of using @Input decorator.

/**
 * Prevents defining inputs in component metadata
 * Enforces use of @Input decorator for better type safety and clarity
 */
export class NoInputsMetadataPropertyRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad metadata inputs:

@Component({
  selector: 'app-user',
  template: '...',
  inputs: ['name', 'active']  // ❌ Inputs in metadata
})
export class UserComponent {
  name: string;
  active: boolean;
}

Good decorator inputs:

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

No Output On Prefix Rule

Prevents "on" prefix on output event properties.

/**
 * Prevents 'on' prefix on @Output event properties
 * Follows Angular naming conventions for event properties
 */
export class NoOutputOnPrefixRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad output prefixes:

export class UserComponent {
  @Output() onClick = new EventEmitter<void>();    // ❌ 'on' prefix
  @Output() onSave = new EventEmitter<User>();     // ❌ 'on' prefix
  @Output() onDelete = new EventEmitter<number>(); // ❌ 'on' prefix
}

Good output names:

export class UserComponent {
  @Output() click = new EventEmitter<void>();    // ✅ Direct event name
  @Output() save = new EventEmitter<User>();     // ✅ Action-based name
  @Output() delete = new EventEmitter<number>(); // ✅ Clear event name
}

Template usage:

<app-user (click)="handleClick()" (save)="handleSave($event)" (delete)="handleDelete($event)">
</app-user>

No Output Rename Rule

Prevents renaming of output properties in the @Output decorator.

/**
 * Prevents renaming @Output properties in the decorator
 * Maintains consistency between property names and template usage
 */
export class NoOutputRenameRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad output renaming:

export class UserComponent {
  @Output('userClick') click = new EventEmitter<void>();     // ❌ Renamed output
  @Output('saveUser') save = new EventEmitter<User>();       // ❌ Renamed output
}

Good output naming:

export class UserComponent {
  @Output() userClick = new EventEmitter<void>();  // ✅ Property name matches output name
  @Output() save = new EventEmitter<User>();       // ✅ No renaming
}

No Outputs Metadata Property Rule

Prevents defining outputs in the component/directive metadata instead of using @Output decorator.

/**
 * Prevents defining outputs in component metadata
 * Enforces use of @Output decorator for better type safety and clarity
 */
export class NoOutputsMetadataPropertyRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

No Output Native Rule

Prevents using native DOM event names as output properties.

/**
 * Prevents using native DOM event names as @Output properties
 * Avoids conflicts with built-in DOM events
 */
export class NoOutputNativeRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad native event names:

export class CustomComponent {
  @Output() click = new EventEmitter<void>();    // ❌ Native DOM event
  @Output() focus = new EventEmitter<void>();    // ❌ Native DOM event
  @Output() blur = new EventEmitter<void>();     // ❌ Native DOM event
  @Output() change = new EventEmitter<any>();    // ❌ Native DOM event
}

Good custom event names:

export class CustomComponent {
  @Output() itemClick = new EventEmitter<void>();     // ✅ Custom event name
  @Output() itemSelected = new EventEmitter<any>();   // ✅ Descriptive custom name
  @Output() valueChanged = new EventEmitter<any>();   // ✅ Custom variation
  @Output() customAction = new EventEmitter<void>();  // ✅ Unique event name
}

Input/Output Best Practices

Input Property Design

export class DataComponent {
  // ✅ Simple primitive inputs
  @Input() title: string;
  @Input() count: number;
  @Input() enabled: boolean;

  // ✅ Complex object inputs with proper typing
  @Input() user: User;
  @Input() config: ComponentConfig;

  // ✅ Optional inputs with default values
  @Input() size: 'small' | 'medium' | 'large' = 'medium';
  @Input() theme: string = 'default';
}

Output Event Design

export class DataComponent {
  // ✅ Simple event notifications
  @Output() loaded = new EventEmitter<void>();
  @Output() error = new EventEmitter<string>();

  // ✅ Events with meaningful data
  @Output() itemSelected = new EventEmitter<User>();
  @Output() dataChanged = new EventEmitter<DataChangeEvent>();

  // ✅ Action-based events
  @Output() save = new EventEmitter<User>();
  @Output() cancel = new EventEmitter<void>();
  @Output() delete = new EventEmitter<number>();
}

interface DataChangeEvent {
  oldValue: any;
  newValue: any;
  field: string;
}

Two-way Binding Pattern

export class CustomInputComponent {
  @Input() value: string;
  @Output() valueChange = new EventEmitter<string>();

  // Internal method to emit changes
  onValueChange(newValue: string): void {
    this.value = newValue;
    this.valueChange.emit(newValue);
  }
}

Template usage:

<app-custom-input [(value)]="formData.name"></app-custom-input>

Rule Configuration

Example Input/Output Rules Configuration

{
  "rules": {
    "no-input-prefix": [true, "is", "has", "should"],
    "no-input-rename": true,
    "no-inputs-metadata-property": true,
    "no-output-on-prefix": true,
    "no-output-rename": true,
    "no-outputs-metadata-property": true,
    "no-output-native": true
  }
}

Common Patterns

Form Component Integration

export class FormFieldComponent implements ControlValueAccessor {
  @Input() label: string;
  @Input() placeholder: string;
  @Input() disabled: boolean = false;
  @Input() required: boolean = false;

  @Output() focus = new EventEmitter<void>();    // ❌ Native event name
  @Output() fieldFocus = new EventEmitter<void>(); // ✅ Custom name
  @Output() valueChange = new EventEmitter<any>();
  @Output() validationChange = new EventEmitter<boolean>();
}

Data Display Components

export class DataTableComponent {
  @Input() data: any[];
  @Input() columns: TableColumn[];
  @Input() sortable: boolean = true;
  @Input() filterable: boolean = false;

  @Output() rowSelected = new EventEmitter<any>();
  @Output() sortChanged = new EventEmitter<SortEvent>();
  @Output() filterChanged = new EventEmitter<FilterEvent>();
}

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