Skip to content

Commit

Permalink
Merge pull request #407 from mgechev/minko/fix-397
Browse files Browse the repository at this point in the history
fix: support validation directives and extra suffixes
  • Loading branch information
mgechev authored Sep 11, 2017
2 parents 027af3d + cdfd3d0 commit 184da6b
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 67 deletions.
33 changes: 28 additions & 5 deletions src/directiveClassSuffixRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ import { NgWalker } from './angular/ngWalker';

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

const getInterfaceName = (t: any): string => {
if (!t.expression) {
return '';
}
if (t.expression.name) {
return t.expression.name.text;
}
return t.expression.text;
};

const ValidatorSuffix = 'Validator';

export class Rule extends Lint.Rules.AbstractRule {

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

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

static validate(className: string, suffix: string): boolean {
return className.endsWith(suffix);
static validate(className: string, suffixes: string[]): boolean {
return suffixes.some(s => className.endsWith(s));
}

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
Expand All @@ -44,13 +56,24 @@ export class ClassMetadataWalker extends NgWalker {
visitNgDirective(meta: DirectiveMetadata) {
let name = meta.controller.name;
let className: string = name.text;
const suffix = this.getOptions()[0] || 'Directive';
if (!Rule.validate(className, suffix)) {
const options = this.getOptions();
const suffixes: string[] = options.length ? options : ['Directive'];
const heritageClauses = meta.controller.heritageClauses;
if (heritageClauses) {
const i = heritageClauses.filter(h => h.token === ts.SyntaxKind.ImplementsKeyword);
if (i.length !== 0 &&
i[0].types.map(getInterfaceName)
.filter(name => !!name)
.some(name => name.endsWith(ValidatorSuffix))) {
suffixes.push(ValidatorSuffix);
}
}
if (!Rule.validate(className, suffixes)) {
this.addFailure(
this.createFailure(
name.getStart(),
name.getWidth(),
sprintf.apply(this, [Rule.FAILURE, className, suffix])));
sprintf.apply(this, [Rule.FAILURE, className, suffixes.join(', ')])));
}
}
}
159 changes: 97 additions & 62 deletions test/directiveClassSuffix.spec.ts
Original file line number Diff line number Diff line change
@@ -1,122 +1,157 @@
import { assertAnnotated, assertSuccess } from './testHelper';

describe('directive-class-suffix', () => {
describe('invalid directive class suffix', () => {
it('should fail when directive class is with the wrong suffix', () => {
let source = `
describe('invalid directive class suffix', () => {
it('should fail when directive class is with the wrong suffix', () => {
let source = `
@Directive({
selector: 'sgBarFoo'
})
class Test {}
~~~~
`;
assertAnnotated({
ruleName: 'directive-class-suffix',
message: 'The name of the class Test should end with the suffix Directive (https://angular.io/styleguide#style-02-03)',
source
});
});
assertAnnotated({
ruleName: 'directive-class-suffix',
message: 'The name of the class Test should end with the suffix Directive (https://angular.io/styleguide#style-02-03)',
source
});
});

describe('valid directive class name', () => {
it('should succeed when the directive class name ends with Directive', () => {
let source = `
it('should fail when directive class is with the wrong suffix', () => {
let source = `
@Directive({
selector: 'sgBarFoo'
})
class Test {}
~~~~
`;
assertAnnotated({
ruleName: 'directive-class-suffix',
message: 'The name of the class Test should end with the suffix ' +
'Directive, Page, Validator (https://angular.io/styleguide#style-02-03)',
source,
options: ['Directive', 'Page', 'Validator']
});
});
});

describe('valid directive class name', () => {
it('should succeed when the directive class name ends with Directive', () => {
let source = `
@Directive({
selector: 'sgBarFoo'
})
class TestDirective {}`;
assertSuccess('directive-class-suffix', source);
});
assertSuccess('directive-class-suffix', source);
});

it('should succeed when the directive class name ends with Validator and implements Validator', () => {
const source = `
@Directive({
selector: 'sgBarFoo'
})
class TestValidator implements Validator {}`;
assertSuccess('directive-class-suffix', source);
});

describe('not called decorator', () => {
it('should not fail when @Directive is not called', () => {
let source = `
it('should succeed when the directive class name ends with Validator and implements AsyncValidator', () => {
const source = `
@Directive({
selector: 'sgBarFoo'
})
class TestValidator implements AsyncValidator {}`;
assertSuccess('directive-class-suffix', source);
});
});

describe('not called decorator', () => {
it('should not fail when @Directive is not called', () => {
let source = `
@Directive
class TestDirective {}`;
assertSuccess('directive-class-suffix', source);
});
assertSuccess('directive-class-suffix', source);
});
});

describe('valid directive class', () => {
it('should succeed when is used @Component decorator', () => {
let source = `
describe('valid directive class', () => {
it('should succeed when is used @Component decorator', () => {
let source = `
@Component({
selector: 'sg-foo-bar'
})
class TestComponent {}`;
assertSuccess('directive-class-suffix', source);
});
assertSuccess('directive-class-suffix', source);
});
});

describe('valid pipe class', () => {
it('should succeed when is used @Pipe decorator', () => {
let source = `
describe('valid pipe class', () => {
it('should succeed when is used @Pipe decorator', () => {
let source = `
@Pipe({
selector: 'sg-test-pipe'
})
class TestPipe {}`;
assertSuccess('directive-class-suffix', source);
});
assertSuccess('directive-class-suffix', source);
});
});

describe('valid service class', () => {
it('should succeed when is used @Injectable decorator', () => {
let source = `
describe('valid service class', () => {
it('should succeed when is used @Injectable decorator', () => {
let source = `
@Injectable()
class TestService {}`;
assertSuccess('directive-class-suffix', source);
});
assertSuccess('directive-class-suffix', source);
});
});

describe('valid empty class', () => {
it('should succeed when the class is empty', () => {
let source = `
describe('valid empty class', () => {
it('should succeed when the class is empty', () => {
let source = `
class TestEmpty {}`;
assertSuccess('directive-class-suffix', source);
});
assertSuccess('directive-class-suffix', source);
});
});

describe('changed suffix', () => {
it('should suceed when different sufix is set', () => {
let source = `
describe('changed suffix', () => {
it('should suceed when different sufix is set', () => {
let source = `
@Directive({
selector: 'sgBarFoo'
})
class TestPage {}`;
assertSuccess('directive-class-suffix', source, ['Page']);
});
assertSuccess('directive-class-suffix', source, ['Page']);
});

it('should fail when different sufix is set and doesnt match', () => {
let source = `
it('should fail when different sufix is set and doesnt match', () => {
let source = `
@Directive({
selector: 'sgBarFoo'
})
class TestPage {}
~~~~~~~~
`;
assertAnnotated({
ruleName: 'directive-class-suffix',
message: 'The name of the class TestPage should end with the suffix Directive (https://angular.io/styleguide#style-02-03)',
source,
options: ['Directive']
});
});
assertAnnotated({
ruleName: 'directive-class-suffix',
message: 'The name of the class TestPage should end with the suffix Directive (https://angular.io/styleguide#style-02-03)',
source,
options: ['Directive']
});
});

it('should fail when different sufix is set and doesnt match', () => {
let source = `
it('should fail when different sufix is set and doesnt match', () => {
let source = `
@Directive({
selector: 'sgBarFoo'
})
class TestDirective {}
~~~~~~~~~~~~~
`;
assertAnnotated({
ruleName: 'directive-class-suffix',
message: 'The name of the class TestDirective should end with the suffix Page (https://angular.io/styleguide#style-02-03)',
source,
options: ['Page']
});
});
assertAnnotated({
ruleName: 'directive-class-suffix',
message: 'The name of the class TestDirective should end with the suffix Page (https://angular.io/styleguide#style-02-03)',
source,
options: ['Page']
});
});
});
});

0 comments on commit 184da6b

Please sign in to comment.