Skip to content

Commit 4176a31

Browse files
committed
Merge pull request #19 from mgechev/pipe-name-rule
add pipe naming rule update README
2 parents 74319aa + 28222ef commit 4176a31

File tree

4 files changed

+177
-10
lines changed

4 files changed

+177
-10
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ Below you can find a recommended configuration which is based on the [Angular 2
3939
"output-property-directive": true,
4040
"call-forward-ref":true,
4141
"life-cycle-hook":true,
42-
"pipe-transform-interface":true
42+
"pipe-transform-interface":true,
43+
"pipe-naming": [true, "kebab-case","sg"]
4344
}
4445
```
4546

@@ -75,7 +76,7 @@ Below you can find a recommended configuration which is based on the [Angular 2
7576
- [ ] Do not manipulate elements referenced within the template.
7677
- [x] Implement life-cycle hooks explicitly.
7778
- [x] Implement Pipe transform interface for pipes.
78-
- [ ] Proper naming for pipes (kebab-case, optionally prefixed).
79+
- [x] Proper naming for pipes (kebab-case, optionally prefixed).
7980

8081
## License
8182

src/pipeNamingRule.ts

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as Lint from 'tslint/lib/lint';
2+
import * as ts from 'typescript';
3+
import {sprintf} from 'sprintf-js';
4+
import SyntaxKind = require('./util/syntaxKind');
5+
import {SelectorValidator} from "./util/selectorValidator";
6+
7+
export class Rule extends Lint.Rules.AbstractRule {
8+
9+
10+
public prefix:string;
11+
public hasPrefix:boolean;
12+
private prefixChecker:Function;
13+
private validator:Function;
14+
15+
constructor(ruleName:string, value:any, disabledIntervals:Lint.IDisabledInterval[]) {
16+
super(ruleName, value, disabledIntervals);
17+
if (value[1] === 'kebab-case') {
18+
this.validator = SelectorValidator.kebabCase;
19+
}
20+
if (value[2]) {
21+
this.hasPrefix = true;
22+
this.prefix = value[2];
23+
this.prefixChecker = SelectorValidator.prefix(value[2]);
24+
}
25+
}
26+
27+
public apply(sourceFile:ts.SourceFile):Lint.RuleFailure[] {
28+
return this.applyWithWalker(
29+
new ClassMetadataWalker(sourceFile, this));
30+
}
31+
32+
public validateName(name:string):boolean {
33+
return this.validator(name);
34+
}
35+
36+
public validatePrefix(prefix:string):boolean {
37+
return this.prefixChecker(prefix);
38+
}
39+
40+
static FAILURE_WITHOUT_PREFIX:string = 'The name of the Pipe decorator of class %s should' +
41+
' be named kebab-case, however its value is "%s".';
42+
43+
static FAILURE_WITH_PREFIX:string = 'The name of the Pipe decorator of class %s should' +
44+
' be named kebab-case with prefix %s, however its value is "%s".';
45+
}
46+
47+
export class ClassMetadataWalker extends Lint.RuleWalker {
48+
49+
constructor(sourceFile:ts.SourceFile, private rule:Rule) {
50+
super(sourceFile, rule.getOptions());
51+
}
52+
53+
visitClassDeclaration(node:ts.ClassDeclaration) {
54+
let className = node.name.text;
55+
let decorators = node.decorators || [];
56+
decorators.filter(d=> {
57+
let baseExpr = <any>d.expression || {};
58+
return baseExpr.expression.text === 'Pipe'
59+
}).forEach(this.validateProperties.bind(this, className));
60+
super.visitClassDeclaration(node);
61+
}
62+
63+
private validateProperties(className:string, pipe:any) {
64+
let argument = this.extractArgument(pipe);
65+
if (argument.kind === SyntaxKind.current().ObjectLiteralExpression) {
66+
argument.properties.filter(n=>n.name.text === 'name')
67+
.forEach(this.validateProperty.bind(this, className))
68+
}
69+
}
70+
71+
private extractArgument(pipe:any) {
72+
let baseExpr = <any>pipe.expression || {};
73+
let args = baseExpr.arguments || [];
74+
return args[0];
75+
}
76+
77+
private validateProperty(className:string, property:any) {
78+
let propName:string = property.initializer.text;
79+
let isValidName:boolean = this.rule.validateName(propName);
80+
let isValidPrefix:boolean = (this.rule.hasPrefix?this.rule.validatePrefix(propName):true);
81+
if (!isValidName || !isValidPrefix) {
82+
this.addFailure(
83+
this.createFailure(
84+
property.getStart(),
85+
property.getWidth(),
86+
sprintf.apply(this, this.createFailureArray(className, propName))));
87+
}
88+
}
89+
90+
private createFailureArray(className:string, pipeName:string):Array<string> {
91+
if (this.rule.hasPrefix) {
92+
return [Rule.FAILURE_WITH_PREFIX, className, this.rule.prefix, pipeName];
93+
}
94+
return [Rule.FAILURE_WITHOUT_PREFIX, className, pipeName];
95+
}
96+
97+
}

src/pipeTransformInterfaceRule.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ export class Rule extends Lint.Rules.AbstractRule {
1212
}
1313

1414
static FAILURE:string = 'The %s class has the Pipe decorator, so it should implement the PipeTransform interface';
15-
16-
static PIPE_INTERFACE_NAME = 'PipeTransform';
15+
static PIPE_INTERFACE_NAME='PipeTransform';
1716
}
1817

1918
export class ClassMetadataWalker extends Lint.RuleWalker {
@@ -24,28 +23,27 @@ export class ClassMetadataWalker extends Lint.RuleWalker {
2423
let pipes:Array<string> = decorators.map(d=>(<any>d.expression).expression.text).filter(t=>t === 'Pipe');
2524
if (pipes.length !== 0) {
2625
let className:string = node.name.text;
27-
if (!this.hasIPipeTransform(node)) {
26+
if(!this.hasIPipeTransform(node)){
2827
this.addFailure(
2928
this.createFailure(
3029
node.getStart(),
3130
node.getWidth(),
32-
sprintf.apply(this, [Rule.FAILURE, className])));
31+
sprintf.apply(this, [Rule.FAILURE,className])));
3332
}
3433
}
3534
}
3635
super.visitClassDeclaration(node);
3736
}
3837

39-
private hasIPipeTransform(node:ts.ClassDeclaration):boolean {
38+
private hasIPipeTransform(node:ts.ClassDeclaration):boolean{
4039
let interfaces = [];
4140
if (node.heritageClauses) {
4241
let interfacesClause = node.heritageClauses
4342
.filter(h=>h.token === SyntaxKind.current().ImplementsKeyword);
4443
if (interfacesClause.length !== 0) {
45-
interfaces = interfacesClause[0].types
46-
.map(t=>(<any>t.expression).text);
44+
interfaces = interfacesClause[0].types.map(t=>(<any>t.expression).text);
4745
}
4846
}
49-
return interfaces.indexOf(Rule.PIPE_INTERFACE_NAME) !== -1;
47+
return interfaces.indexOf(Rule.PIPE_INTERFACE_NAME)!==-1;
5048
}
5149
}

test/pipeNamingRule.spec.ts

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {assertFailure, assertSuccess} from './testHelper';
2+
3+
describe('pipe-naming', () => {
4+
describe('invalid pipe name', () => {
5+
it('should fail when Pipe is named camelCase with prefix ng', () => {
6+
let source = `
7+
@Pipe({
8+
name: 'fooBar'
9+
})
10+
class Test {}`;
11+
assertFailure('pipe-naming', source, {
12+
message: 'The name of the Pipe decorator of class Test should be named kebab-case with prefix ng, however its value is "fooBar".',
13+
startPosition: {
14+
line: 2,
15+
character: 24
16+
},
17+
endPosition: {
18+
line: 2,
19+
character: 38
20+
}
21+
}, ['kebab-case','ng']);
22+
});
23+
});
24+
describe('invalid pipe name', () => {
25+
it('should fail when Pipe is named camelCase without prefix', () => {
26+
let source = `
27+
@Pipe({
28+
name: 'fooBar'
29+
})
30+
class Test {}`;
31+
assertFailure('pipe-naming', source, {
32+
message: 'The name of the Pipe decorator of class Test should be named kebab-case, however its value is "fooBar".',
33+
startPosition: {
34+
line: 2,
35+
character: 24
36+
},
37+
endPosition: {
38+
line: 2,
39+
character: 38
40+
}
41+
}, 'kebab-case');
42+
});
43+
});
44+
describe('valid pipe name', () => {
45+
it('should succeed when set valid name with prefix ng in @Pipe', () => {
46+
let source = `
47+
@Pipe({
48+
name: 'ng-bar-foo'
49+
})
50+
class Test {}`;
51+
assertSuccess('pipe-naming', source, ['kebab-case','ng']);
52+
});
53+
});
54+
describe('valid pipe name', () => {
55+
it('should succeed when set valid name in @Pipe', () => {
56+
let source = `
57+
@Pipe({
58+
name: 'bar-foo'
59+
})
60+
class Test {}`;
61+
assertSuccess('pipe-naming', source, ['kebab-case']);
62+
});
63+
});
64+
describe('valid empty class', () => {
65+
it('should succeed when the class is not a Pipe', () => {
66+
let source = `
67+
class Test {}`;
68+
assertSuccess('pipe-naming', source, ['kebab-case']);
69+
});
70+
});
71+
});

0 commit comments

Comments
 (0)