Skip to content

Commit 184da6b

Browse files
authored
Merge pull request #407 from mgechev/minko/fix-397
fix: support validation directives and extra suffixes
2 parents 027af3d + cdfd3d0 commit 184da6b

File tree

2 files changed

+125
-67
lines changed

2 files changed

+125
-67
lines changed

src/directiveClassSuffixRule.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ import { NgWalker } from './angular/ngWalker';
55

66
import { DirectiveMetadata } from './angular/metadata';
77

8+
const getInterfaceName = (t: any): string => {
9+
if (!t.expression) {
10+
return '';
11+
}
12+
if (t.expression.name) {
13+
return t.expression.name.text;
14+
}
15+
return t.expression.text;
16+
};
17+
18+
const ValidatorSuffix = 'Validator';
19+
820
export class Rule extends Lint.Rules.AbstractRule {
921

1022
public static metadata: Lint.IRuleMetadata = {
@@ -29,8 +41,8 @@ export class Rule extends Lint.Rules.AbstractRule {
2941

3042
static FAILURE: string = 'The name of the class %s should end with the suffix %s (https://angular.io/styleguide#style-02-03)';
3143

32-
static validate(className: string, suffix: string): boolean {
33-
return className.endsWith(suffix);
44+
static validate(className: string, suffixes: string[]): boolean {
45+
return suffixes.some(s => className.endsWith(s));
3446
}
3547

3648
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
@@ -44,13 +56,24 @@ export class ClassMetadataWalker extends NgWalker {
4456
visitNgDirective(meta: DirectiveMetadata) {
4557
let name = meta.controller.name;
4658
let className: string = name.text;
47-
const suffix = this.getOptions()[0] || 'Directive';
48-
if (!Rule.validate(className, suffix)) {
59+
const options = this.getOptions();
60+
const suffixes: string[] = options.length ? options : ['Directive'];
61+
const heritageClauses = meta.controller.heritageClauses;
62+
if (heritageClauses) {
63+
const i = heritageClauses.filter(h => h.token === ts.SyntaxKind.ImplementsKeyword);
64+
if (i.length !== 0 &&
65+
i[0].types.map(getInterfaceName)
66+
.filter(name => !!name)
67+
.some(name => name.endsWith(ValidatorSuffix))) {
68+
suffixes.push(ValidatorSuffix);
69+
}
70+
}
71+
if (!Rule.validate(className, suffixes)) {
4972
this.addFailure(
5073
this.createFailure(
5174
name.getStart(),
5275
name.getWidth(),
53-
sprintf.apply(this, [Rule.FAILURE, className, suffix])));
76+
sprintf.apply(this, [Rule.FAILURE, className, suffixes.join(', ')])));
5477
}
5578
}
5679
}

test/directiveClassSuffix.spec.ts

Lines changed: 97 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,157 @@
11
import { assertAnnotated, assertSuccess } from './testHelper';
22

33
describe('directive-class-suffix', () => {
4-
describe('invalid directive class suffix', () => {
5-
it('should fail when directive class is with the wrong suffix', () => {
6-
let source = `
4+
describe('invalid directive class suffix', () => {
5+
it('should fail when directive class is with the wrong suffix', () => {
6+
let source = `
77
@Directive({
88
selector: 'sgBarFoo'
99
})
1010
class Test {}
1111
~~~~
1212
`;
13-
assertAnnotated({
14-
ruleName: 'directive-class-suffix',
15-
message: 'The name of the class Test should end with the suffix Directive (https://angular.io/styleguide#style-02-03)',
16-
source
17-
});
18-
});
13+
assertAnnotated({
14+
ruleName: 'directive-class-suffix',
15+
message: 'The name of the class Test should end with the suffix Directive (https://angular.io/styleguide#style-02-03)',
16+
source
17+
});
1918
});
2019

21-
describe('valid directive class name', () => {
22-
it('should succeed when the directive class name ends with Directive', () => {
23-
let source = `
20+
it('should fail when directive class is with the wrong suffix', () => {
21+
let source = `
22+
@Directive({
23+
selector: 'sgBarFoo'
24+
})
25+
class Test {}
26+
~~~~
27+
`;
28+
assertAnnotated({
29+
ruleName: 'directive-class-suffix',
30+
message: 'The name of the class Test should end with the suffix ' +
31+
'Directive, Page, Validator (https://angular.io/styleguide#style-02-03)',
32+
source,
33+
options: ['Directive', 'Page', 'Validator']
34+
});
35+
});
36+
});
37+
38+
describe('valid directive class name', () => {
39+
it('should succeed when the directive class name ends with Directive', () => {
40+
let source = `
2441
@Directive({
2542
selector: 'sgBarFoo'
2643
})
2744
class TestDirective {}`;
28-
assertSuccess('directive-class-suffix', source);
29-
});
45+
assertSuccess('directive-class-suffix', source);
46+
});
47+
48+
it('should succeed when the directive class name ends with Validator and implements Validator', () => {
49+
const source = `
50+
@Directive({
51+
selector: 'sgBarFoo'
52+
})
53+
class TestValidator implements Validator {}`;
54+
assertSuccess('directive-class-suffix', source);
3055
});
3156

32-
describe('not called decorator', () => {
33-
it('should not fail when @Directive is not called', () => {
34-
let source = `
57+
it('should succeed when the directive class name ends with Validator and implements AsyncValidator', () => {
58+
const source = `
59+
@Directive({
60+
selector: 'sgBarFoo'
61+
})
62+
class TestValidator implements AsyncValidator {}`;
63+
assertSuccess('directive-class-suffix', source);
64+
});
65+
});
66+
67+
describe('not called decorator', () => {
68+
it('should not fail when @Directive is not called', () => {
69+
let source = `
3570
@Directive
3671
class TestDirective {}`;
37-
assertSuccess('directive-class-suffix', source);
38-
});
72+
assertSuccess('directive-class-suffix', source);
3973
});
74+
});
4075

41-
describe('valid directive class', () => {
42-
it('should succeed when is used @Component decorator', () => {
43-
let source = `
76+
describe('valid directive class', () => {
77+
it('should succeed when is used @Component decorator', () => {
78+
let source = `
4479
@Component({
4580
selector: 'sg-foo-bar'
4681
})
4782
class TestComponent {}`;
48-
assertSuccess('directive-class-suffix', source);
49-
});
83+
assertSuccess('directive-class-suffix', source);
5084
});
85+
});
5186

52-
describe('valid pipe class', () => {
53-
it('should succeed when is used @Pipe decorator', () => {
54-
let source = `
87+
describe('valid pipe class', () => {
88+
it('should succeed when is used @Pipe decorator', () => {
89+
let source = `
5590
@Pipe({
5691
selector: 'sg-test-pipe'
5792
})
5893
class TestPipe {}`;
59-
assertSuccess('directive-class-suffix', source);
60-
});
94+
assertSuccess('directive-class-suffix', source);
6195
});
96+
});
6297

63-
describe('valid service class', () => {
64-
it('should succeed when is used @Injectable decorator', () => {
65-
let source = `
98+
describe('valid service class', () => {
99+
it('should succeed when is used @Injectable decorator', () => {
100+
let source = `
66101
@Injectable()
67102
class TestService {}`;
68-
assertSuccess('directive-class-suffix', source);
69-
});
103+
assertSuccess('directive-class-suffix', source);
70104
});
105+
});
71106

72-
describe('valid empty class', () => {
73-
it('should succeed when the class is empty', () => {
74-
let source = `
107+
describe('valid empty class', () => {
108+
it('should succeed when the class is empty', () => {
109+
let source = `
75110
class TestEmpty {}`;
76-
assertSuccess('directive-class-suffix', source);
77-
});
111+
assertSuccess('directive-class-suffix', source);
78112
});
113+
});
79114

80-
describe('changed suffix', () => {
81-
it('should suceed when different sufix is set', () => {
82-
let source = `
115+
describe('changed suffix', () => {
116+
it('should suceed when different sufix is set', () => {
117+
let source = `
83118
@Directive({
84119
selector: 'sgBarFoo'
85120
})
86121
class TestPage {}`;
87-
assertSuccess('directive-class-suffix', source, ['Page']);
88-
});
122+
assertSuccess('directive-class-suffix', source, ['Page']);
123+
});
89124

90-
it('should fail when different sufix is set and doesnt match', () => {
91-
let source = `
125+
it('should fail when different sufix is set and doesnt match', () => {
126+
let source = `
92127
@Directive({
93128
selector: 'sgBarFoo'
94129
})
95130
class TestPage {}
96131
~~~~~~~~
97132
`;
98-
assertAnnotated({
99-
ruleName: 'directive-class-suffix',
100-
message: 'The name of the class TestPage should end with the suffix Directive (https://angular.io/styleguide#style-02-03)',
101-
source,
102-
options: ['Directive']
103-
});
104-
});
133+
assertAnnotated({
134+
ruleName: 'directive-class-suffix',
135+
message: 'The name of the class TestPage should end with the suffix Directive (https://angular.io/styleguide#style-02-03)',
136+
source,
137+
options: ['Directive']
138+
});
139+
});
105140

106-
it('should fail when different sufix is set and doesnt match', () => {
107-
let source = `
141+
it('should fail when different sufix is set and doesnt match', () => {
142+
let source = `
108143
@Directive({
109144
selector: 'sgBarFoo'
110145
})
111146
class TestDirective {}
112147
~~~~~~~~~~~~~
113148
`;
114-
assertAnnotated({
115-
ruleName: 'directive-class-suffix',
116-
message: 'The name of the class TestDirective should end with the suffix Page (https://angular.io/styleguide#style-02-03)',
117-
source,
118-
options: ['Page']
119-
});
120-
});
149+
assertAnnotated({
150+
ruleName: 'directive-class-suffix',
151+
message: 'The name of the class TestDirective should end with the suffix Page (https://angular.io/styleguide#style-02-03)',
152+
source,
153+
options: ['Page']
154+
});
121155
});
156+
});
122157
});

0 commit comments

Comments
 (0)