|
1 |
| -import * as Lint from 'tslint'; |
2 |
| -import * as ts from 'typescript'; |
3 |
| -import { sprintf } from 'sprintf-js'; |
| 1 | +import { IRuleMetadata, RuleFailure, Rules } from 'tslint/lib'; |
| 2 | +import { Decorator, PropertyDeclaration, SourceFile } from 'typescript/lib/typescript'; |
| 3 | +import { DirectiveMetadata } from './angular/metadata'; |
4 | 4 | import { NgWalker } from './angular/ngWalker';
|
5 | 5 |
|
6 |
| -export class Rule extends Lint.Rules.AbstractRule { |
7 |
| - public static metadata: Lint.IRuleMetadata = { |
8 |
| - ruleName: 'no-output-rename', |
9 |
| - type: 'maintainability', |
| 6 | +export class Rule extends Rules.AbstractRule { |
| 7 | + static readonly metadata: IRuleMetadata = { |
10 | 8 | description: 'Disallows renaming directive outputs by providing a string to the decorator.',
|
11 | 9 | descriptionDetails: 'See more at https://angular.io/styleguide#style-05-13.',
|
12 |
| - rationale: 'Two names for the same property (one private, one public) is inherently confusing.', |
13 | 10 | options: null,
|
14 | 11 | optionsDescription: 'Not configurable.',
|
| 12 | + rationale: 'Two names for the same property (one private, one public) is inherently confusing.', |
| 13 | + ruleName: 'no-output-rename', |
| 14 | + type: 'maintainability', |
15 | 15 | typescriptOnly: true
|
16 | 16 | };
|
17 | 17 |
|
18 |
| - static FAILURE_STRING: string = 'In the class "%s", the directive output ' + |
19 |
| - 'property "%s" should not be renamed.' + |
20 |
| - 'Please, consider the following use "@Output() %s = new EventEmitter();"'; |
| 18 | + static readonly FAILURE_STRING = '@Outputs should not be renamed'; |
21 | 19 |
|
22 |
| - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { |
| 20 | + apply(sourceFile: SourceFile): RuleFailure[] { |
23 | 21 | return this.applyWithWalker(new OutputMetadataWalker(sourceFile, this.getOptions()));
|
24 | 22 | }
|
25 | 23 | }
|
26 | 24 |
|
| 25 | +export const getFailureMessage = (): string => { |
| 26 | + return Rule.FAILURE_STRING; |
| 27 | +}; |
| 28 | + |
27 | 29 | export class OutputMetadataWalker extends NgWalker {
|
28 |
| - protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) { |
29 |
| - let className = (<any>property).parent.name.text; |
30 |
| - let memberName = (<any>property.name).text; |
31 |
| - if (args.length !== 0 && memberName !== args[0]) { |
32 |
| - let failureConfig: string[] = [className, memberName, memberName]; |
33 |
| - failureConfig.unshift(Rule.FAILURE_STRING); |
34 |
| - this.addFailure(this.createFailure(property.getStart(), property.getWidth(), sprintf.apply(this, failureConfig))); |
35 |
| - } |
| 30 | + private directiveSelectors: ReadonlySet<DirectiveMetadata['selector']>; |
36 | 31 |
|
| 32 | + visitNgDirective(metadata: DirectiveMetadata): void { |
| 33 | + this.directiveSelectors = new Set((metadata.selector || '').replace(/[\[\]\s]/g, '').split(',')); |
| 34 | + super.visitNgDirective(metadata); |
| 35 | + } |
| 36 | + |
| 37 | + protected visitNgOutput(property: PropertyDeclaration, output: Decorator, args: string[]) { |
| 38 | + this.validateOutput(property, output, args); |
37 | 39 | super.visitNgOutput(property, output, args);
|
38 | 40 | }
|
| 41 | + |
| 42 | + private canPropertyBeAliased(propertyAlias: string, propertyName: string): boolean { |
| 43 | + return !!(this.directiveSelectors && this.directiveSelectors.has(propertyAlias) && propertyAlias !== propertyName); |
| 44 | + } |
| 45 | + |
| 46 | + private validateOutput(property: PropertyDeclaration, output: Decorator, args: string[]) { |
| 47 | + const propertyName = property.name.getText(); |
| 48 | + |
| 49 | + if (args.length === 0 || this.canPropertyBeAliased(args[0], propertyName)) { |
| 50 | + return; |
| 51 | + } |
| 52 | + |
| 53 | + this.addFailureAtNode(property, getFailureMessage()); |
| 54 | + } |
39 | 55 | }
|
0 commit comments