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 656816f

Browse filesBrowse files
mohammedzamakhanmgechev
authored andcommitted
feat(rule): add use-injectable-provided-in (#814)
1 parent 3b82574 commit 656816f
Copy full SHA for 656816f

5 files changed

+89-2Lines changed: 89 additions & 2 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/angular/metadata.ts‎

Copy file name to clipboardExpand all lines: src/angular/metadata.ts
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@ export class ModuleMetadata {
5555
}
5656

5757
export class InjectableMetadata {
58-
constructor(readonly controller: ts.ClassDeclaration, readonly decorator: ts.Decorator) {}
58+
constructor(readonly controller: ts.ClassDeclaration, readonly decorator: ts.Decorator, readonly providedIn?: string | ts.Expression) {}
5959
}
Collapse file

‎src/angular/metadataReader.ts‎

Copy file name to clipboardExpand all lines: src/angular/metadataReader.ts
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ export class MetadataReader {
107107
}
108108

109109
protected readInjectableMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
110-
return new InjectableMetadata(d, dec);
110+
const providedInExpression = getDecoratorPropertyInitializer(dec, 'providedIn');
111+
112+
return new InjectableMetadata(d, dec, providedInExpression);
111113
}
112114

113115
protected readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): ComponentMetadata {
Collapse file

‎src/index.ts‎

Copy file name to clipboardExpand all lines: src/index.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export { Rule as TemplateNoNegatedAsyncRule } from './templateNoNegatedAsyncRule
4747
export { Rule as TemplateUseTrackByFunctionRule } from './templateUseTrackByFunctionRule';
4848
export { Rule as UseComponentSelectorRule } from './useComponentSelectorRule';
4949
export { Rule as UseComponentViewEncapsulationRule } from './useComponentViewEncapsulationRule';
50+
export { Rule as UseInjectableProvidedInRule } from './useInjectableProvidedInRule';
5051
export { Rule as UseLifecycleInterfaceRule } from './useLifecycleInterfaceRule';
5152
export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule';
5253
export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule';
Collapse file

‎src/useInjectableProvidedInRule.ts‎

Copy file name to clipboard
+38Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { IRuleMetadata, RuleFailure } from 'tslint';
2+
import { AbstractRule } from 'tslint/lib/rules';
3+
import { SourceFile } from 'typescript';
4+
import { InjectableMetadata } from './angular';
5+
import { NgWalker } from './angular/ngWalker';
6+
7+
export class Rule extends AbstractRule {
8+
static readonly metadata: IRuleMetadata = {
9+
description: "Enforces classes decorated with @Injectable to use the 'providedIn' property.",
10+
options: null,
11+
optionsDescription: 'Not configurable.',
12+
rationale: "Using the 'providedIn' property makes classes decorated with @Injectable tree shakeable.",
13+
ruleName: 'use-injectable-provided-in',
14+
type: 'functionality',
15+
typescriptOnly: true
16+
};
17+
18+
static readonly FAILURE_STRING = "Classes decorated with @Injectable should use the 'providedIn' property";
19+
20+
apply(sourceFile: SourceFile): RuleFailure[] {
21+
const walker = new Walker(sourceFile, this.getOptions());
22+
23+
return this.applyWithWalker(walker);
24+
}
25+
}
26+
27+
class Walker extends NgWalker {
28+
protected visitNgInjectable(metadata: InjectableMetadata): void {
29+
this.validateInjectable(metadata);
30+
super.visitNgInjectable(metadata);
31+
}
32+
33+
private validateInjectable(metadata: InjectableMetadata): void {
34+
if (metadata.providedIn) return;
35+
36+
this.addFailureAtNode(metadata.decorator, Rule.FAILURE_STRING);
37+
}
38+
}
Collapse file
+46Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Rule } from '../src/useInjectableProvidedInRule';
2+
import { assertAnnotated, assertSuccess } from './testHelper';
3+
4+
const {
5+
metadata: { ruleName },
6+
FAILURE_STRING
7+
} = Rule;
8+
9+
describe(ruleName, () => {
10+
describe('failures', () => {
11+
it('should fail if providedIn property is not set', () => {
12+
const source = `
13+
@Injectable()
14+
~~~~~~~~~~~~~
15+
class Test {}
16+
`;
17+
assertAnnotated({
18+
message: FAILURE_STRING,
19+
ruleName,
20+
source
21+
});
22+
});
23+
});
24+
25+
describe('success', () => {
26+
it('should succeed if providedIn property is set to a literal string', () => {
27+
const source = `
28+
@Injectable({
29+
providedIn: 'root'
30+
})
31+
class Test {}
32+
`;
33+
assertSuccess(ruleName, source);
34+
});
35+
36+
it('should succeed if providedIn property is set to a module', () => {
37+
const source = `
38+
@Injectable({
39+
providedIn: SomeModule
40+
})
41+
class Test {}
42+
`;
43+
assertSuccess(ruleName, source);
44+
});
45+
});
46+
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.