File tree Expand file tree Collapse file tree
Open diff view settings
Expand file tree Collapse file tree
Open diff view settings
Original file line number Diff line number Diff line change @@ -55,5 +55,5 @@ export class ModuleMetadata {
5555}
5656
5757export 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}
Original file line number Diff line number Diff 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 {
Original file line number Diff line number Diff line change @@ -47,6 +47,7 @@ export { Rule as TemplateNoNegatedAsyncRule } from './templateNoNegatedAsyncRule
4747export { Rule as TemplateUseTrackByFunctionRule } from './templateUseTrackByFunctionRule' ;
4848export { Rule as UseComponentSelectorRule } from './useComponentSelectorRule' ;
4949export { Rule as UseComponentViewEncapsulationRule } from './useComponentViewEncapsulationRule' ;
50+ export { Rule as UseInjectableProvidedInRule } from './useInjectableProvidedInRule' ;
5051export { Rule as UseLifecycleInterfaceRule } from './useLifecycleInterfaceRule' ;
5152export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule' ;
5253export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule' ;
Original file line number Diff line number Diff line change 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+ }
Original file line number Diff line number Diff line change 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+ } ) ;
You can’t perform that action at this time.
0 commit comments