Skip to content

Commit c61a059

Browse files
committed
Merge pull request #1 from mgechev/attribute
Add: attribute rule and constructor walker, Update: attribute rule ad…
2 parents bd8eca5 + aef46fc commit c61a059

4 files changed

+128
-2
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ Below you can find a recommended configuration which is based on the [Angular 2
1616
"component-selector-prefix": [true, "sg"],
1717
"host-parameter-decorator": true,
1818
"input-parameter-decorator": true,
19-
"output-parameter-decorator": true
19+
"output-parameter-decorator": true,
20+
"attribute-parameter-decorator":true
2021
}
2122
```
2223

@@ -35,7 +36,7 @@ Below you can find a recommended configuration which is based on the [Angular 2
3536
- [ ] Do not rename inputs.
3637
- [ ] Do not rename outputs.
3738
- [ ] Externalize template above *n* lines of code.
38-
- [ ] Do not use the `@Attribute` decorator.
39+
- [x] Do not use the `@Attribute` decorator.
3940
- [ ] Do not use `forwardRef`.
4041
- [ ] Rise a warning for impure pipes.
4142
- [ ] Do not declare global providers.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as Lint from 'tslint/lib/lint';
2+
import * as ts from 'typescript';
3+
import {ConstructorRule} from "./parameterConstructorBase";
4+
5+
const attributeValidator = (parameter:ts.ParameterDeclaration)=>{
6+
let isValid = true;
7+
if (parameter.decorators) {
8+
parameter.decorators.forEach((decorator:ts.Decorator)=> {
9+
let baseExpr = <any>decorator.expression || {};
10+
let expr = baseExpr.expression || {};
11+
let name = expr.text;
12+
if (name==='Attribute') {
13+
isValid = false
14+
}
15+
});
16+
}
17+
return isValid;
18+
};
19+
20+
const FAILURE_STRING = 'In the constructor of class "%s", the parameter "%s" uses the @Attribute decorator, ' +
21+
'which is considered as a bad practice. Please, consider construction of type "@Input() %s: string"';
22+
23+
export class Rule extends ConstructorRule {
24+
25+
constructor(ruleName:string, value:any, disabledIntervals:Lint.IDisabledInterval[]) {
26+
super(ruleName, value, disabledIntervals,attributeValidator,FAILURE_STRING);
27+
}
28+
29+
}

src/parameterConstructorBase.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as Lint from 'tslint/lib/lint';
2+
import * as ts from 'typescript';
3+
import {sprintf} from 'sprintf-js';
4+
5+
export class ConstructorRule extends Lint.Rules.AbstractRule {
6+
7+
constructor(ruleName:string, value:any, disabledIntervals:Lint.IDisabledInterval[], private validator:Function,
8+
private errorMessage:string) {
9+
super(ruleName, value, disabledIntervals);
10+
}
11+
12+
public apply(sourceFile:ts.SourceFile):Lint.RuleFailure[] {
13+
let documentRegistry = ts.createDocumentRegistry();
14+
let languageServiceHost = Lint.createLanguageServiceHost('file.ts', sourceFile.getFullText());
15+
let languageService:ts.LanguageService = ts.createLanguageService(languageServiceHost, documentRegistry);
16+
return this.applyWithWalker(
17+
new ConstructorMetadataWalker(
18+
sourceFile,
19+
languageService,
20+
this));
21+
}
22+
23+
public validate(parameter:ts.ParameterDeclaration) {
24+
return this.validator(parameter);
25+
}
26+
27+
public getFailureString(failureConfig) {
28+
return sprintf(this.errorMessage, failureConfig.className, failureConfig.parameterName, failureConfig.parameterName);
29+
}
30+
}
31+
32+
export class ConstructorMetadataWalker extends Lint.RuleWalker {
33+
34+
private languageService:ts.LanguageService;
35+
private typeChecker:ts.TypeChecker;
36+
37+
constructor(sourceFile:ts.SourceFile, languageService:ts.LanguageService, private rule:ConstructorRule) {
38+
super(sourceFile, rule.getOptions());
39+
this.languageService = languageService;
40+
this.typeChecker = languageService.getProgram().getTypeChecker();
41+
}
42+
43+
visitConstructorDeclaration(node:ts.ConstructorDeclaration) {
44+
let parentName = (<ts.ClassDeclaration>node.parent).name.text;
45+
(node.parameters || []).forEach(this.validateParameter.bind(this, parentName));
46+
super.visitConstructorDeclaration(node);
47+
}
48+
49+
validateParameter(className:string, parameter:ts.ParameterDeclaration) {
50+
let parameterName = (<ts.Identifier>parameter.name).text;
51+
if (!this.rule.validate(parameter)) {
52+
this.addFailure(
53+
this.createFailure(
54+
parameter.getStart(),
55+
parameter.getWidth(),
56+
this.rule.getFailureString({className, parameterName})));
57+
}
58+
}
59+
60+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {assertFailure, assertSuccess} from './testHelper';
2+
3+
describe('attribute-parameter-decorator', () => {
4+
describe('invalid parameter decorator', () => {
5+
it(`should fail, when it's used attribute decorator`, () => {
6+
let source = `
7+
class ButtonComponent {
8+
label: string;
9+
constructor(public @Attribute('label') label) {
10+
this.label = label;
11+
}
12+
}`;
13+
assertFailure('decorator-attribute', source, {
14+
message: 'In the constructor of class "ButtonComponent", the parameter "label" uses the @Attribute decorator, ' +
15+
'which is considered as a bad practice. Please, consider construction of type "@Input() label: string"',
16+
startPosition: {
17+
line: 3,
18+
character: 35
19+
},
20+
endPosition: {
21+
line: 3,
22+
character: 60
23+
}
24+
});
25+
});
26+
});
27+
describe('valid class constructor', () => {
28+
it('should succeed, when is not used attribute decorator', () => {
29+
let source = `
30+
class ButtonComponent {
31+
constructor(){}
32+
}`;
33+
assertSuccess('attribute-parameter-decorator', source);
34+
});
35+
});
36+
});

0 commit comments

Comments
 (0)