forked from mgechev/codelyzer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pipeNamingRule.ts
133 lines (113 loc) · 4.47 KB
/
pipeNamingRule.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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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';
const OPTION_ATTRIBUTE = 'attribute';
const OPTION_CAMEL_CASE = 'camelCase';
const OPTION_KEBAB_CASE = 'kebab-case';
export class Rule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata = {
deprecationMessage: `You can name your pipes only ${OPTION_CAMEL_CASE}. If you try to use snake-case then your application will not compile. For prefix validation use pipe-prefix rule.`,
description: 'Enforce consistent case and prefix for pipes.',
optionExamples: [
[true, OPTION_CAMEL_CASE, 'myPrefix'],
[true, OPTION_CAMEL_CASE, 'myPrefix', 'myOtherPrefix'],
[true, OPTION_KEBAB_CASE, 'my-prefix']
],
options: {
items: [
{
enum: [OPTION_KEBAB_CASE, OPTION_ATTRIBUTE]
},
{
type: 'string'
}
],
minLength: 1,
type: 'array'
},
optionsDescription: Lint.Utils.dedent`
* The first item in the array is \`${OPTION_CAMEL_CASE}\` or \`${OPTION_KEBAB_CASE}\`, which allows you to pick a case.
* The rest of the arguments are supported prefixes (given as strings). They are optional.
`,
rationale: 'Consistent conventions make it easy to quickly identify and reference assets of different types.',
ruleName: 'pipe-naming',
type: 'style',
typescriptOnly: true
};
static FAILURE_WITHOUT_PREFIX = `The name of the Pipe decorator of class %s should be named ${OPTION_CAMEL_CASE}, however its value is "%s"`;
static FAILURE_WITH_PREFIX = `The name of the Pipe decorator of class %s should be named ${OPTION_CAMEL_CASE} with prefix %s, however its value is "%s"`;
prefix!: string;
hasPrefix!: boolean;
private prefixChecker!: Function;
private validator!: Function;
constructor(options: Lint.IOptions) {
super(options);
let args = options.ruleArguments;
if (!(args instanceof Array)) {
args = [args];
}
if (args[0] === OPTION_CAMEL_CASE) {
this.validator = SelectorValidator.camelCase;
}
if (args.length > 1) {
this.hasPrefix = true;
let prefixExpression = (args.slice(1) || []).join('|');
this.prefix = (args.slice(1) || []).join(',');
this.prefixChecker = SelectorValidator.prefix(prefixExpression, OPTION_CAMEL_CASE);
}
}
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;
}
validateName(name: string): boolean {
return this.validator(name);
}
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(controller: ts.ClassDeclaration, decorator: ts.Decorator) {
let className = controller.name!.text;
this.validateProperties(className, decorator);
super.visitNgPipe(controller, decorator);
}
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 isValidName = this.rule.validateName(propName);
const isValidPrefix = this.rule.hasPrefix ? this.rule.validatePrefix(propName) : true;
if (!isValidName || !isValidPrefix) {
this.addFailureAtNode(property, this.getFailureMessage(className, propName));
}
}
}
private getFailureMessage(className: string, pipeName: string): string {
if (this.rule.hasPrefix) {
return sprintf(Rule.FAILURE_WITH_PREFIX, className, this.rule.prefix, pipeName);
}
return sprintf(Rule.FAILURE_WITHOUT_PREFIX, className, pipeName);
}
}