Skip to content

Commit

Permalink
feat(rule): add use-injectable-provided-in (#814)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohammedzamakhan authored and mgechev committed May 1, 2019
1 parent 3b82574 commit 656816f
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/angular/metadata.ts
Expand Up @@ -55,5 +55,5 @@ export class ModuleMetadata {
}

export class InjectableMetadata {
constructor(readonly controller: ts.ClassDeclaration, readonly decorator: ts.Decorator) {}
constructor(readonly controller: ts.ClassDeclaration, readonly decorator: ts.Decorator, readonly providedIn?: string | ts.Expression) {}
}
4 changes: 3 additions & 1 deletion src/angular/metadataReader.ts
Expand Up @@ -107,7 +107,9 @@ export class MetadataReader {
}

protected readInjectableMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): DirectiveMetadata {
return new InjectableMetadata(d, dec);
const providedInExpression = getDecoratorPropertyInitializer(dec, 'providedIn');

return new InjectableMetadata(d, dec, providedInExpression);
}

protected readComponentMetadata(d: ts.ClassDeclaration, dec: ts.Decorator): ComponentMetadata {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -47,6 +47,7 @@ export { Rule as TemplateNoNegatedAsyncRule } from './templateNoNegatedAsyncRule
export { Rule as TemplateUseTrackByFunctionRule } from './templateUseTrackByFunctionRule';
export { Rule as UseComponentSelectorRule } from './useComponentSelectorRule';
export { Rule as UseComponentViewEncapsulationRule } from './useComponentViewEncapsulationRule';
export { Rule as UseInjectableProvidedInRule } from './useInjectableProvidedInRule';
export { Rule as UseLifecycleInterfaceRule } from './useLifecycleInterfaceRule';
export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule';
export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule';
Expand Down
38 changes: 38 additions & 0 deletions src/useInjectableProvidedInRule.ts
@@ -0,0 +1,38 @@
import { IRuleMetadata, RuleFailure } from 'tslint';
import { AbstractRule } from 'tslint/lib/rules';
import { SourceFile } from 'typescript';
import { InjectableMetadata } from './angular';
import { NgWalker } from './angular/ngWalker';

export class Rule extends AbstractRule {
static readonly metadata: IRuleMetadata = {
description: "Enforces classes decorated with @Injectable to use the 'providedIn' property.",
options: null,
optionsDescription: 'Not configurable.',
rationale: "Using the 'providedIn' property makes classes decorated with @Injectable tree shakeable.",
ruleName: 'use-injectable-provided-in',
type: 'functionality',
typescriptOnly: true
};

static readonly FAILURE_STRING = "Classes decorated with @Injectable should use the 'providedIn' property";

apply(sourceFile: SourceFile): RuleFailure[] {
const walker = new Walker(sourceFile, this.getOptions());

return this.applyWithWalker(walker);
}
}

class Walker extends NgWalker {
protected visitNgInjectable(metadata: InjectableMetadata): void {
this.validateInjectable(metadata);
super.visitNgInjectable(metadata);
}

private validateInjectable(metadata: InjectableMetadata): void {
if (metadata.providedIn) return;

this.addFailureAtNode(metadata.decorator, Rule.FAILURE_STRING);
}
}
46 changes: 46 additions & 0 deletions test/useInjectableProvidedInRule.spec.ts
@@ -0,0 +1,46 @@
import { Rule } from '../src/useInjectableProvidedInRule';
import { assertAnnotated, assertSuccess } from './testHelper';

const {
metadata: { ruleName },
FAILURE_STRING
} = Rule;

describe(ruleName, () => {
describe('failures', () => {
it('should fail if providedIn property is not set', () => {
const source = `
@Injectable()
~~~~~~~~~~~~~
class Test {}
`;
assertAnnotated({
message: FAILURE_STRING,
ruleName,
source
});
});
});

describe('success', () => {
it('should succeed if providedIn property is set to a literal string', () => {
const source = `
@Injectable({
providedIn: 'root'
})
class Test {}
`;
assertSuccess(ruleName, source);
});

it('should succeed if providedIn property is set to a module', () => {
const source = `
@Injectable({
providedIn: SomeModule
})
class Test {}
`;
assertSuccess(ruleName, source);
});
});
});

0 comments on commit 656816f

Please sign in to comment.