Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit efbcb69

Browse filesBrowse 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
Copy full SHA for efbcb69

2 files changed

+1,910-608Lines changed: 1910 additions & 608 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/contextualDecoratorRule.ts‎

Copy file name to clipboard
+66-47Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.