Skip to content

Commit 7b1e7dc

Browse files
authored
Merge pull request #370 from wKoza/decorator_not_allowed
Decorator not allowed
2 parents 8a4cfb6 + 3a77d83 commit 7b1e7dc

File tree

5 files changed

+455
-22
lines changed

5 files changed

+455
-22
lines changed

src/angular/metadataReader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class MetadataReader {
6767
});
6868
}
6969

70-
protected readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator) {
70+
protected readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): ComponentMetadata {
7171
const expr = this.getDecoratorArgument(dec);
7272
const directiveMetadata = this.readDirectiveMetadata(d, dec);
7373

src/angular/ngWalker.ts

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import * as Lint from 'tslint';
22
import * as ts from 'typescript';
33
import * as compiler from '@angular/compiler';
4-
import { parseTemplate } from './templates/templateParser';
4+
import {parseTemplate} from './templates/templateParser';
55

66
import {parseCss} from './styles/parseCss';
77
import {CssAst} from './styles/cssAst';
88
import {BasicCssAstVisitor, CssAstVisitorCtrl} from './styles/basicCssAstVisitor';
99

10-
import {RecursiveAngularExpressionVisitorCtr, BasicTemplateAstVisitor, TemplateAstVisitorCtr} from './templates/basicTemplateAstVisitor';
10+
import {
11+
RecursiveAngularExpressionVisitorCtr,
12+
BasicTemplateAstVisitor,
13+
TemplateAstVisitorCtr
14+
} from './templates/basicTemplateAstVisitor';
1115
import {RecursiveAngularExpressionVisitor} from './templates/recursiveAngularExpressionVisitor';
1216
import {ReferenceCollectorVisitor} from './templates/referenceCollectorVisitor';
1317

@@ -34,9 +38,9 @@ export interface NgWalkerConfig {
3438

3539
export class NgWalker extends Lint.RuleWalker {
3640
constructor(sourceFile: ts.SourceFile,
37-
protected _originalOptions: Lint.IOptions,
38-
private _config?: NgWalkerConfig,
39-
protected _metadataReader?: MetadataReader) {
41+
protected _originalOptions: Lint.IOptions,
42+
private _config?: NgWalkerConfig,
43+
protected _metadataReader?: MetadataReader) {
4044

4145
super(sourceFile, _originalOptions);
4246

@@ -74,34 +78,55 @@ export class NgWalker extends Lint.RuleWalker {
7478
let name = getDecoratorName(decorator);
7579
if (name === 'HostListener') {
7680
this.visitNgHostListener(<ts.MethodDeclaration>decorator.parent,
77-
decorator, getDecoratorStringArgs(decorator));
81+
decorator, getDecoratorStringArgs(decorator));
7882
}
7983
}
8084

8185
protected visitPropertyDecorator(decorator: ts.Decorator) {
8286
let name = getDecoratorName(decorator);
8387
switch (name) {
8488
case 'Input':
85-
this.visitNgInput(<ts.PropertyDeclaration>decorator.parent,
89+
this.visitNgInput(<ts.PropertyDeclaration>decorator.parent,
8690
decorator, getDecoratorStringArgs(decorator));
87-
break;
91+
break;
8892
case 'Output':
89-
this.visitNgOutput(<ts.PropertyDeclaration>decorator.parent,
93+
this.visitNgOutput(<ts.PropertyDeclaration>decorator.parent,
9094
decorator, getDecoratorStringArgs(decorator));
91-
break;
95+
break;
9296
case 'HostBinding':
93-
this.visitNgHostBinding(<ts.PropertyDeclaration>decorator.parent,
97+
this.visitNgHostBinding(<ts.PropertyDeclaration>decorator.parent,
98+
decorator, getDecoratorStringArgs(decorator));
99+
break;
100+
case 'ContentChild':
101+
this.visitNgContentChild(<ts.PropertyDeclaration>decorator.parent,
102+
decorator, getDecoratorStringArgs(decorator));
103+
break;
104+
case 'ContentChildren':
105+
this.visitNgContentChildren(<ts.PropertyDeclaration>decorator.parent,
106+
decorator, getDecoratorStringArgs(decorator));
107+
break;
108+
case 'ViewChild':
109+
this.visitNgViewChild(<ts.PropertyDeclaration>decorator.parent,
94110
decorator, getDecoratorStringArgs(decorator));
95-
break;
111+
break;
112+
case 'ViewChildren':
113+
this.visitNgViewChildren(<ts.PropertyDeclaration>decorator.parent,
114+
decorator, getDecoratorStringArgs(decorator));
115+
break;
96116
}
97117
}
98118

99119
protected visitClassDecorator(decorator: ts.Decorator) {
100120
let name = getDecoratorName(decorator);
121+
122+
if (name === 'Injectable') {
123+
this.visitNgInjectable(<ts.ClassDeclaration>decorator.parent, decorator);
124+
}
125+
101126
// Not invoked @Component or @Pipe, or @Directive
102127
if (!(<ts.CallExpression>decorator.expression).arguments ||
103-
!(<ts.CallExpression>decorator.expression).arguments.length ||
104-
!(<ts.ObjectLiteralExpression>(<ts.CallExpression>decorator.expression).arguments[0]).properties) {
128+
!(<ts.CallExpression>decorator.expression).arguments.length ||
129+
!(<ts.ObjectLiteralExpression>(<ts.CallExpression>decorator.expression).arguments[0]).properties) {
105130
return;
106131
}
107132

@@ -118,7 +143,8 @@ export class NgWalker extends Lint.RuleWalker {
118143
pos = node.pos + 1;
119144
try {
120145
pos = node.getStart() + 1;
121-
} catch (e) {}
146+
} catch (e) {
147+
}
122148
}
123149
return pos;
124150
};
@@ -143,17 +169,38 @@ export class NgWalker extends Lint.RuleWalker {
143169
}
144170
}
145171

146-
protected visitNgDirective(metadata: DirectiveMetadata) {}
172+
protected visitNgDirective(metadata: DirectiveMetadata) {
173+
}
174+
175+
protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) {
176+
}
177+
178+
protected visitNgInjectable(classDeclaration: ts.ClassDeclaration, decorator: ts.Decorator) {
179+
}
147180

148-
protected visitNgPipe(controller: ts.ClassDeclaration, decorator: ts.Decorator) {}
181+
protected visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
182+
}
149183

150-
protected visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {}
184+
protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) {
185+
}
151186

152-
protected visitNgOutput(property: ts.PropertyDeclaration, output: ts.Decorator, args: string[]) {}
187+
protected visitNgHostBinding(property: ts.PropertyDeclaration, decorator: ts.Decorator, args: string[]) {
188+
}
153189

154-
protected visitNgHostBinding(property: ts.PropertyDeclaration, decorator: ts.Decorator, args: string[]) {}
190+
protected visitNgHostListener(method: ts.MethodDeclaration, decorator: ts.Decorator, args: string[]) {
191+
}
155192

156-
protected visitNgHostListener(method: ts.MethodDeclaration, decorator: ts.Decorator, args: string[]) {}
193+
protected visitNgContentChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
194+
}
195+
196+
protected visitNgContentChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
197+
}
198+
199+
protected visitNgViewChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
200+
}
201+
202+
protected visitNgViewChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
203+
}
157204

158205
protected visitNgTemplateHelper(roots: compiler.TemplateAst[], context: ComponentMetadata, baseStart: number) {
159206
if (!roots || !roots.length) {

src/decoratorNotAllowedRule.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import * as Lint from 'tslint';
2+
import * as ts from 'typescript';
3+
import {sprintf} from 'sprintf-js';
4+
import SyntaxKind = require('./util/syntaxKind');
5+
import {NgWalker} from './angular/ngWalker';
6+
7+
8+
export class Rule extends Lint.Rules.AbstractRule {
9+
public static metadata: Lint.IRuleMetadata = {
10+
ruleName: 'decorator-not-allowed',
11+
type: 'functionality',
12+
description: `Ensure that classes use allowed decorator in its body`,
13+
rationale: `Some decorators can only be used in certain class types.
14+
For example, an @Input should not be used in an @Injectable class.`,
15+
options: null,
16+
optionsDescription: `Not configurable.`,
17+
typescriptOnly: true,
18+
};
19+
20+
21+
static INJECTABLE_FAILURE_STRING: string = 'In the class "%s" which have the "%s" decorator, the ' +
22+
'"%s" decorator is not allowed. ' +
23+
'Please, drop it.';
24+
25+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
26+
return this.applyWithWalker(
27+
new ClassMetadataWalker(sourceFile,
28+
this));
29+
}
30+
}
31+
32+
33+
export class ClassMetadataWalker extends NgWalker {
34+
35+
className: string;
36+
isInjectable = false;
37+
38+
constructor(sourceFile: ts.SourceFile, private rule: Rule) {
39+
super(sourceFile, rule.getOptions());
40+
}
41+
42+
visitNgInjectable(classDeclaration: ts.ClassDeclaration, decorator: ts.Decorator) {
43+
this.className = classDeclaration.name.text;
44+
this.isInjectable = true;
45+
}
46+
47+
protected visitNgInput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
48+
if (this.isInjectable) {
49+
let failureConfig: string[] = [this.className, '@Injectable', '@Input'];
50+
failureConfig.unshift(Rule.INJECTABLE_FAILURE_STRING);
51+
this.generateFailure(property.getStart(), property.getWidth(), failureConfig);
52+
}
53+
54+
}
55+
56+
protected visitNgOutput(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
57+
if (this.isInjectable) {
58+
let failureConfig: string[] = [this.className, '@Injectable', '@Output'];
59+
failureConfig.unshift(Rule.INJECTABLE_FAILURE_STRING);
60+
this.generateFailure(property.getStart(), property.getWidth(), failureConfig);
61+
}
62+
63+
}
64+
65+
protected visitNgHostBinding(property: ts.PropertyDeclaration, decorator: ts.Decorator, args: string[]) {
66+
if (this.isInjectable) {
67+
let failureConfig: string[] = [this.className, '@Injectable', '@HostBinding'];
68+
failureConfig.unshift(Rule.INJECTABLE_FAILURE_STRING);
69+
this.generateFailure(property.getStart(), property.getWidth(), failureConfig);
70+
}
71+
}
72+
73+
protected visitNgHostListener(method: ts.MethodDeclaration, decorator: ts.Decorator, args: string[]) {
74+
if (this.isInjectable) {
75+
let failureConfig: string[] = [this.className, '@Injectable', '@HostListener'];
76+
failureConfig.unshift(Rule.INJECTABLE_FAILURE_STRING);
77+
this.generateFailure(method.getStart(), method.getWidth(), failureConfig);
78+
}
79+
}
80+
81+
protected visitNgContentChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
82+
if (this.isInjectable) {
83+
let failureConfig: string[] = [this.className, '@Injectable', '@ContentChild'];
84+
failureConfig.unshift(Rule.INJECTABLE_FAILURE_STRING);
85+
this.generateFailure(property.getStart(), property.getWidth(), failureConfig);
86+
}
87+
}
88+
89+
protected visitNgContentChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
90+
if (this.isInjectable) {
91+
let failureConfig: string[] = [this.className, '@Injectable', '@ContentChildren'];
92+
failureConfig.unshift(Rule.INJECTABLE_FAILURE_STRING);
93+
this.generateFailure(property.getStart(), property.getWidth(), failureConfig);
94+
}
95+
}
96+
97+
protected visitNgViewChild(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
98+
if (this.isInjectable) {
99+
let failureConfig: string[] = [this.className, '@Injectable', '@ViewChild'];
100+
failureConfig.unshift(Rule.INJECTABLE_FAILURE_STRING);
101+
this.generateFailure(property.getStart(), property.getWidth(), failureConfig);
102+
}
103+
}
104+
105+
protected visitNgViewChildren(property: ts.PropertyDeclaration, input: ts.Decorator, args: string[]) {
106+
if (this.isInjectable) {
107+
let failureConfig: string[] = [this.className, '@Injectable', '@ViewChildren'];
108+
failureConfig.unshift(Rule.INJECTABLE_FAILURE_STRING);
109+
this.generateFailure(property.getStart(), property.getWidth(), failureConfig);
110+
}
111+
}
112+
113+
private generateFailure(start: number, width: number, failureConfig: string[]) {
114+
this.addFailure(
115+
this.createFailure(
116+
start,
117+
width,
118+
sprintf.apply(this, failureConfig)));
119+
}
120+
121+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ export { Rule as UseViewEncapsulationRule } from './useViewEncapsulationRule';
2424
export { Rule as BananaInBoxRule } from './bananaInBoxRule';
2525
export { Rule as AngularWhitespaceRule } from './angularWhitespaceRule';
2626
export { Rule as TemplatesNoNegatedAsync } from './templatesNoNegatedAsyncRule';
27+
export { Rule as DecoratorNotAllowedRule } from './decoratorNotAllowedRule';
2728
export * from './angular/config';
2829

0 commit comments

Comments
 (0)