/
pipePrefixRule.ts
99 lines (83 loc) · 3 KB
/
pipePrefixRule.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { sprintf } from 'sprintf-js';
import * as Lint from 'tslint';
import * as ts from 'typescript';
import { NgWalker } from './angular/ngWalker';
import { SelectorValidator } from './util/selectorValidator';
import { getDecoratorArgument } from './util/utils';
import { PipeMetadata } from './angular';
export class Rule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata = {
description: 'Enforce consistent prefix for pipes.',
optionExamples: [[true, 'myPrefix'], [true, 'myPrefix', 'myOtherPrefix']],
options: {
items: [
{
type: 'string'
}
],
minLength: 1,
type: 'array'
},
optionsDescription: Lint.Utils.dedent`
* The list of arguments are supported prefixes (given as strings).
`,
rationale: 'Consistent conventions make it easy to quickly identify and reference assets of different types.',
ruleName: 'pipe-prefix',
type: 'style',
typescriptOnly: true
};
static FAILURE_STRING = `The name of the Pipe decorator of class %s should start with prefix %s, however its value is "%s"`;
prefix: string;
private prefixChecker: Function;
constructor(options: Lint.IOptions) {
super(options);
let args = options.ruleArguments;
if (!(args instanceof Array)) {
args = [args];
}
this.prefix = args.join(',');
let prefixExpression = args.join('|');
this.prefixChecker = SelectorValidator.prefix(prefixExpression, 'camelCase');
}
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new ClassMetadataWalker(sourceFile, this));
}
isEnabled(): boolean {
const {
metadata: {
options: { minLength }
}
} = Rule;
const { length } = this.ruleArguments;
return super.isEnabled() && length >= minLength;
}
validatePrefix(prefix: string): boolean {
return this.prefixChecker(prefix);
}
}
export class ClassMetadataWalker extends NgWalker {
constructor(sourceFile: ts.SourceFile, private rule: Rule) {
super(sourceFile, rule.getOptions());
}
protected visitNgPipe(metadata: PipeMetadata) {
let className = metadata.controller.name!.text;
this.validateProperties(className, metadata.decorator);
super.visitNgPipe(metadata);
}
private validateProperties(className: string, pipe: ts.Decorator) {
const argument = getDecoratorArgument(pipe)!;
argument.properties
.filter(p => p.name && ts.isIdentifier(p.name) && p.name.text === 'name')
.forEach(this.validateProperty.bind(this, className));
}
private validateProperty(className: string, property: ts.Node) {
const initializer = ts.isPropertyAssignment(property) ? property.initializer : undefined;
if (initializer && ts.isStringLiteral(initializer)) {
const propName = initializer.text;
const isValid = this.rule.validatePrefix(propName);
if (!isValid) {
this.addFailureAtNode(property, sprintf(Rule.FAILURE_STRING, className, this.rule.prefix, propName));
}
}
}
}