Skip to content

Commit b9c899b

Browse files
authored
Add rule in order to enforce the declaration of a selector (#557)
* Add a new rule in order to enforce the declaration of a selector
1 parent 3e10013 commit b9c899b

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

src/enforceComponentSelectorRule.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as Lint from 'tslint';
2+
import * as ts from 'typescript';
3+
import { sprintf } from 'sprintf-js';
4+
import { NgWalker } from './angular/ngWalker';
5+
import { ComponentMetadata } from './angular/metadata';
6+
7+
export class Rule extends Lint.Rules.AbstractRule {
8+
9+
public static metadata: Lint.IRuleMetadata = {
10+
ruleName: 'enforce-component-selector',
11+
type: 'style',
12+
description: 'Component selector must be declared.',
13+
rationale: 'Omit the component selector makes debugging difficult.',
14+
options: null,
15+
optionsDescription: 'Not configurable.',
16+
typescriptOnly: true
17+
};
18+
19+
20+
static SELECTOR_FAILURE: string = 'The selector of the component "%s" is mandatory.';
21+
22+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
23+
return this.applyWithWalker(
24+
new EnforceComponentSelectorValidatorWalker(sourceFile, this));
25+
}
26+
}
27+
28+
export class EnforceComponentSelectorValidatorWalker extends NgWalker {
29+
30+
constructor(sourceFile: ts.SourceFile, private rule: Rule) {
31+
super(sourceFile, rule.getOptions());
32+
}
33+
34+
visitNgComponent(metadata: ComponentMetadata) {
35+
if (!metadata.selector) {
36+
const failureConfig: string[] = [metadata.controller.name.text];
37+
failureConfig.unshift(Rule.SELECTOR_FAILURE);
38+
this.generateFailure(metadata.decorator.getStart(), metadata.decorator.getWidth(), failureConfig);
39+
}
40+
super.visitNgComponent(metadata);
41+
}
42+
43+
private generateFailure(start: number, width: number, failureConfig: string[]) {
44+
this.addFailure(
45+
this.createFailure(
46+
start,
47+
width,
48+
sprintf.apply(this, failureConfig)));
49+
}
50+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export { Rule as ContextualLifeCycleRule } from './contextualLifeCycleRule';
66
export { Rule as DecoratorNotAllowedRule } from './decoratorNotAllowedRule';
77
export { Rule as DirectiveClassSuffixRule } from './directiveClassSuffixRule';
88
export { Rule as DirectiveSelectorRule } from './directiveSelectorRule';
9+
export { Rule as EnforceComponentSelectorRule} from './enforceComponentSelectorRule';
910
export { Rule as I18nRule } from './i18nRule';
1011
export { Rule as ImportDestructuringSpacingRule } from './importDestructuringSpacingRule';
1112
export { Rule as MaxInlineDeclarationsRule } from './maxInlineDeclarationsRule';
@@ -31,6 +32,7 @@ export { Rule as UseOutputPropertyDecoratorRule } from './useOutputPropertyDecor
3132
export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule';
3233
export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule';
3334
export { Rule as UseViewEncapsulationRule } from './useViewEncapsulationRule';
35+
3436
export * from './angular/config';
3537

3638
// this file exists for tslint to resolve the rules directory
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { assertAnnotated, assertSuccess } from './testHelper';
2+
3+
describe('enforceComoponentSelectorRule', () => {
4+
it('should fail when selector is not given in @Component', () => {
5+
let source = `
6+
@Component()
7+
~~~~~~~~~~~~
8+
class Test {}`;
9+
assertAnnotated({
10+
ruleName: 'enforce-component-selector',
11+
message: 'The selector of the component "Test" is mandatory.',
12+
source
13+
});
14+
});
15+
16+
it('should fail when selector is empty in @Component', () => {
17+
let source = `
18+
@Component({
19+
~~~~~~~~~~~~
20+
selector: ''
21+
})
22+
~~
23+
class Test {}`;
24+
assertAnnotated({
25+
ruleName: 'enforce-component-selector',
26+
message: 'The selector of the component "Test" is mandatory.',
27+
source
28+
});
29+
});
30+
31+
32+
it('should fail when selector equals 0 in @Component', () => {
33+
let source = `
34+
@Component({
35+
~~~~~~~~~~~~
36+
selector: 0
37+
})
38+
~~
39+
class Test {}`;
40+
assertAnnotated({
41+
ruleName: 'enforce-component-selector',
42+
message: 'The selector of the component "Test" is mandatory.',
43+
source
44+
});
45+
});
46+
47+
it('should fail when selector equals null in @Component', () => {
48+
let source = `
49+
@Component({
50+
~~~~~~~~~~~~
51+
selector: null
52+
})
53+
~~
54+
class Test {}`;
55+
assertAnnotated({
56+
ruleName: 'enforce-component-selector',
57+
message: 'The selector of the component "Test" is mandatory.',
58+
source
59+
});
60+
});
61+
62+
it('should succeed when selector is given in @Component', () => {
63+
let source = `
64+
@Component({
65+
selector: 'sg-bar-foo'
66+
})
67+
class Test {}`;
68+
assertSuccess('enforce-component-selector', source);
69+
});
70+
});

0 commit comments

Comments
 (0)