Skip to content

Commit efbcb69

Browse files
rafaelss95mgechev
authored andcommitted
fix(rule): 'contextual-decorator' - decorators with arguments, accessors and some missing decorators not being handled (#798)
1 parent 829c675 commit efbcb69

File tree

2 files changed

+1910
-608
lines changed

2 files changed

+1910
-608
lines changed

src/contextualDecoratorRule.ts

Lines changed: 66 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
import { sprintf } from 'sprintf-js';
2-
import { IRuleMetadata, RuleFailure } from 'tslint';
2+
import { IRuleMetadata, RuleFailure, WalkContext } from 'tslint';
33
import { AbstractRule } from 'tslint/lib/rules';
44
import { dedent } from 'tslint/lib/utils';
5-
import { createNodeArray, Decorator, isClassDeclaration, SourceFile } from 'typescript';
6-
import { NgWalker } from './angular/ngWalker';
5+
import {
6+
AccessorDeclaration,
7+
createNodeArray,
8+
Decorator,
9+
forEachChild,
10+
isAccessor,
11+
isMethodDeclaration,
12+
isParameterPropertyDeclaration,
13+
isPropertyDeclaration,
14+
MethodDeclaration,
15+
Node,
16+
ParameterPropertyDeclaration,
17+
PropertyDeclaration,
18+
SourceFile
19+
} from 'typescript';
20+
import { isNotNullOrUndefined } from './util/isNotNullOrUndefined';
721
import {
822
ANGULAR_CLASS_DECORATOR_MAPPER,
923
AngularClassDecoratorKeys,
1024
AngularClassDecorators,
11-
AngularInnerClassDecoratorKeys,
1225
AngularInnerClassDecorators,
1326
getDecoratorName,
1427
getNextToLastParentNode,
@@ -17,77 +30,83 @@ import {
1730
} from './util/utils';
1831

1932
interface FailureParameters {
20-
readonly className: string;
2133
readonly classDecoratorName: AngularClassDecoratorKeys;
22-
readonly innerClassDecoratorName: AngularInnerClassDecoratorKeys;
2334
}
2435

25-
export const getFailureMessage = (failureParameters: FailureParameters): string =>
26-
sprintf(
27-
Rule.FAILURE_STRING,
28-
failureParameters.innerClassDecoratorName,
29-
failureParameters.className,
30-
failureParameters.classDecoratorName
31-
);
36+
type DeclarationLike = AccessorDeclaration | MethodDeclaration | ParameterPropertyDeclaration | PropertyDeclaration;
37+
38+
export const getFailureMessage = (failureParameters: FailureParameters): string => {
39+
return sprintf(Rule.FAILURE_STRING, failureParameters.classDecoratorName);
40+
};
3241

3342
export class Rule extends AbstractRule {
3443
static readonly metadata: IRuleMetadata = {
35-
description: 'Ensures that classes use allowed decorator in its body.',
44+
description: 'Ensures that classes use contextual decorators in its body.',
3645
options: null,
3746
optionsDescription: 'Not configurable.',
3847
rationale: dedent`
39-
Some decorators can only be used in certain class types.
40-
For example, an @${AngularInnerClassDecorators.Input} should not be used
41-
in an @${AngularClassDecorators.Injectable} class.
48+
Some decorators should only be used in certain class types. For example,
49+
the decorator @${AngularInnerClassDecorators.Input}() should
50+
not be used in a class decorated with @${AngularClassDecorators.Injectable}().
4251
`,
4352
ruleName: 'contextual-decorator',
4453
type: 'functionality',
4554
typescriptOnly: true
4655
};
4756

48-
static readonly FAILURE_STRING = 'The decorator "%s" is not allowed for class "%s" because it is decorated with "%s"';
57+
static readonly FAILURE_STRING = 'Decorator out of context for "@%s()"';
4958

5059
apply(sourceFile: SourceFile): RuleFailure[] {
51-
const walker = new Walker(sourceFile, this.getOptions());
52-
53-
return this.applyWithWalker(walker);
60+
return this.applyWithFunction(sourceFile, walk);
5461
}
5562
}
5663

57-
class Walker extends NgWalker {
58-
protected visitMethodDecorator(decorator: Decorator): void {
59-
this.validateDecorator(decorator);
60-
super.visitMethodDecorator(decorator);
61-
}
64+
const callbackHandler = (walkContext: WalkContext, node: Node): void => {
65+
if (isDeclarationLike(node)) validateDeclaration(walkContext, node);
66+
};
6267

63-
protected visitPropertyDecorator(decorator: Decorator): void {
64-
this.validateDecorator(decorator);
65-
super.visitPropertyDecorator(decorator);
66-
}
68+
const getClassDecoratorName = (klass: Node): AngularClassDecoratorKeys | undefined => {
69+
return createNodeArray(klass.decorators)
70+
.map(getDecoratorName)
71+
.filter(isNotNullOrUndefined)
72+
.find(isAngularClassDecorator);
73+
};
6774

68-
private validateDecorator(decorator: Decorator): void {
69-
const klass = getNextToLastParentNode(decorator);
75+
const isDeclarationLike = (node: Node): node is DeclarationLike => {
76+
return isAccessor(node) || isMethodDeclaration(node) || isParameterPropertyDeclaration(node) || isPropertyDeclaration(node);
77+
};
7078

71-
if (!isClassDeclaration(klass) || !klass.name) return;
79+
const validateDeclaration = (walkContext: WalkContext, node: DeclarationLike): void => {
80+
const klass = getNextToLastParentNode(node);
81+
const classDecoratorName = getClassDecoratorName(klass);
7282

73-
const classDecoratorName = createNodeArray(klass.decorators)
74-
.map(x => x.expression.getText())
75-
.map(x => x.replace(/[^a-zA-Z]/g, ''))
76-
.find(isAngularClassDecorator);
83+
if (!classDecoratorName) return;
7784

78-
if (!classDecoratorName) return;
85+
createNodeArray(node.decorators).forEach(decorator => validateDecorator(walkContext, decorator, classDecoratorName));
86+
};
7987

80-
const innerClassDecoratorName = getDecoratorName(decorator);
88+
const validateDecorator = (walkContext: WalkContext, node: Decorator, classDecoratorName: AngularClassDecoratorKeys): void => {
89+
const decoratorName = getDecoratorName(node);
8190

82-
if (!innerClassDecoratorName || !isAngularInnerClassDecorator(innerClassDecoratorName)) return;
91+
if (!decoratorName || !isAngularInnerClassDecorator(decoratorName)) return;
8392

84-
const allowedDecorators = ANGULAR_CLASS_DECORATOR_MAPPER.get(classDecoratorName);
93+
const allowedDecorators = ANGULAR_CLASS_DECORATOR_MAPPER.get(classDecoratorName);
8594

86-
if (!allowedDecorators || allowedDecorators.has(innerClassDecoratorName)) return;
95+
if (!allowedDecorators || allowedDecorators.has(decoratorName)) return;
8796

88-
const className = klass.name.getText();
89-
const failure = getFailureMessage({ classDecoratorName, className, innerClassDecoratorName });
97+
const failure = getFailureMessage({ classDecoratorName });
9098

91-
this.addFailureAtNode(decorator, failure);
92-
}
93-
}
99+
walkContext.addFailureAtNode(node, failure);
100+
};
101+
102+
const walk = (walkContext: WalkContext): void => {
103+
const { sourceFile } = walkContext;
104+
105+
const callback = (node: Node): void => {
106+
callbackHandler(walkContext, node);
107+
108+
forEachChild(node, callback);
109+
};
110+
111+
forEachChild(sourceFile, callback);
112+
};

0 commit comments

Comments
 (0)