A comprehensive static code analysis tool for Angular TypeScript projects that implements TSLint rules enforcing Angular's official style guide.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Angular pipe validation rules that enforce naming conventions, decoration requirements, and purity best practices for custom pipes.
Enforces naming conventions for Angular pipes with specified prefixes.
/**
* Enforces naming conventions for Angular pipes
* Ensures pipes follow consistent naming patterns with required prefixes
*/
export class PipePrefixRule 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": {
"pipe-prefix": [true, "app", "custom"]
}
}Good pipe names:
@Pipe({
name: 'appFormatCurrency' // ✅ Has 'app' prefix
})
export class FormatCurrencyPipe implements PipeTransform {
transform(value: number): string {
return `$${value.toFixed(2)}`;
}
}
@Pipe({
name: 'customDateFormat' // ✅ Has 'custom' prefix
})
export class DateFormatPipe implements PipeTransform {
transform(value: Date, format: string): string {
// Custom date formatting logic
return value.toLocaleDateString();
}
}Bad pipe names:
@Pipe({
name: 'formatCurrency' // ❌ Missing required prefix
})
export class FormatCurrencyPipe implements PipeTransform {
transform(value: number): string {
return `$${value.toFixed(2)}`;
}
}Prevents creation of impure pipes that can impact performance.
/**
* Prevents creation of impure pipes
* Impure pipes execute on every change detection cycle and can impact performance
*/
export class NoPipeImpureRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Bad impure pipe:
@Pipe({
name: 'appFilter',
pure: false // ❌ Impure pipe
})
export class FilterPipe implements PipeTransform {
transform(items: any[], filter: any): any[] {
return items.filter(item => /* filtering logic */);
}
}Good pure pipe (default):
@Pipe({
name: 'appFilter' // ✅ Pure by default
})
export class FilterPipe implements PipeTransform {
transform(items: any[], filter: any): any[] {
return items.filter(item => /* filtering logic */);
}
}
@Pipe({
name: 'appFormat',
pure: true // ✅ Explicitly pure
})
export class FormatPipe implements PipeTransform {
transform(value: string): string {
return value.toUpperCase();
}
}Enforces use of @Pipe decorator on pipe classes.
/**
* Enforces @Pipe decorator usage on pipe classes
* Ensures pipes are properly decorated for Angular to recognize them
*/
export class UsePipeDecoratorRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Good pipe with decorator:
@Pipe({ // ✅ Proper @Pipe decorator
name: 'appCapitalize'
})
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1);
}
}Bad pipe without decorator:
// ❌ Missing @Pipe decorator
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1);
}
}Enforces implementation of PipeTransform interface on pipe classes.
/**
* Enforces PipeTransform interface implementation on pipe classes
* Ensures pipes implement the required transform method with proper typing
*/
export class UsePipeTransformInterfaceRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Good pipe with interface:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'appReverse'
})
export class ReversePipe implements PipeTransform { // ✅ Implements PipeTransform
transform(value: string): string {
return value.split('').reverse().join('');
}
}Bad pipe without interface:
@Pipe({
name: 'appReverse'
})
export class ReversePipe { // ❌ Missing PipeTransform interface
transform(value: string): string {
return value.split('').reverse().join('');
}
}// ✅ Pure pipe - cached result, better performance
@Pipe({
name: 'appExpensive',
pure: true
})
export class ExpensivePipe implements PipeTransform {
transform(value: any): any {
// Expensive computation - result will be cached
return this.performExpensiveOperation(value);
}
}
// ❌ Impure pipe - runs on every change detection
@Pipe({
name: 'appExpensive',
pure: false
})
export class ExpensivePipe implements PipeTransform {
transform(value: any): any {
// This runs on every change detection cycle
return this.performExpensiveOperation(value);
}
}@Pipe({
name: 'appFormatDate'
})
export class FormatDatePipe implements PipeTransform {
transform(value: Date | string, format: string = 'short'): string {
if (!value) return '';
const date = typeof value === 'string' ? new Date(value) : value;
switch (format) {
case 'short':
return date.toLocaleDateString();
case 'long':
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
default:
return date.toString();
}
}
}Template usage:
<p>{{ user.birthDate | appFormatDate }}</p>
<p>{{ user.birthDate | appFormatDate:'long' }}</p>{
"rules": {
"pipe-prefix": [true, "app", "shared"],
"no-pipe-impure": true,
"use-pipe-decorator": true,
"use-pipe-transform-interface": true
}
}@Pipe({ name: 'appCurrency' })
export class CurrencyPipe implements PipeTransform {
transform(value: number, currency: string = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency
}).format(value);
}
}
@Pipe({ name: 'appTruncate' })
export class TruncatePipe implements PipeTransform {
transform(value: string, length: number = 100): string {
if (!value || value.length <= length) return value;
return value.substring(0, length) + '...';
}
}// ⚠️ Consider using component methods instead for filtering
@Pipe({
name: 'appFilter',
pure: false // Required for filtering dynamic data
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchTerm: string): any[] {
if (!items || !searchTerm) return items;
return items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}
}Better approach for filtering:
// Component method approach (better performance)
export class UserListComponent {
users: User[] = [];
searchTerm: string = '';
get filteredUsers(): User[] {
if (!this.searchTerm) return this.users;
return this.users.filter(user =>
user.name.toLowerCase().includes(this.searchTerm.toLowerCase())
);
}
}Install with Tessl CLI
npx tessl i tessl/npm-codelyzer