diff --git a/schematics/src/store/files/__name@dasherize__/__name@dasherize__.effects.ts.template b/schematics/src/store/files/__name@dasherize__/__name@dasherize__.effects.ts.template index ba8d6b7a35..55ce7bc749 100644 --- a/schematics/src/store/files/__name@dasherize__/__name@dasherize__.effects.ts.template +++ b/schematics/src/store/files/__name@dasherize__/__name@dasherize__.effects.ts.template @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { filter, tap } from 'rxjs/operators'; -import * as actions from './<%= dasherize(name) %>.actions'; +import { Load<%= classify(name) %>, <%= classify(name) %>ActionTypes } from './<%= dasherize(name) %>.actions'; @Injectable() export class <%= classify(name) %>Effects { @@ -10,7 +10,7 @@ export class <%= classify(name) %>Effects { @Effect() load<%= classify(name) %>$ = this.actions$.pipe( - ofType>(actions.<%= classify(name) %>ActionTypes.Load<%= classify(name) %>), + ofType>(<%= classify(name) %>ActionTypes.Load<%= classify(name) %>), // tslint:disable-next-line:no-console tap(() => console.log('got Load<%= classify(name) %> in <%= classify(name) %>Effects.load<%= classify(name) %>$')), filter(() => false) diff --git a/schematics/src/store/files/__name@dasherize__/__name@dasherize__.selectors.spec.ts.template b/schematics/src/store/files/__name@dasherize__/__name@dasherize__.selectors.spec.ts.template index adf063d99d..7ab6b65fc4 100644 --- a/schematics/src/store/files/__name@dasherize__/__name@dasherize__.selectors.spec.ts.template +++ b/schematics/src/store/files/__name@dasherize__/__name@dasherize__.selectors.spec.ts.template @@ -6,7 +6,7 @@ import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ng <% if(entity) { %>import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { <%= classify(entity) %> } from '<% if(!extension) { %>ish-core<% } else { %>../..<% } %>/models/<%= dasherize(entity) %>/<%= dasherize(entity) %>.model';<% } %> -import * as actions from './<%= dasherize(name) %>.actions'; +import { Load<%= classify(name) %><% if (entity) { %>, Load<%= classify(name) %>Fail, Load<%= classify(name) %>Success<% } %> } from './<%= dasherize(name) %>.actions'; import { <% if (entity) { %>getNumberOf<%= classify(name) %>, get<%= classify(name) %>, get<%= classify(name) %>Entities, get<%= classify(name) %>Error, <% } %>get<%= classify(name) %>Loading } from './<%= dasherize(name) %>.selectors'; describe('<%= classify(name) %> Selectors', () => { @@ -41,7 +41,7 @@ describe('<%= classify(name) %> Selectors', () => { <% } %> }); describe('Load<%= classify(name) %>', () =>{ - const action = new actions.Load<%= classify(name) %>(); + const action = new Load<%= classify(name) %>(); beforeEach(() => { store$.dispatch(action); @@ -53,7 +53,7 @@ describe('<%= classify(name) %> Selectors', () => { <% if (entity) { %> describe('Load<%= classify(name) %>Success', () => { const <%= camelize(name) %> = [{ id: '1' }, { id: '2' }] as <%= classify(entity) %>[]; - const successAction = new actions.Load<%= classify(name) %>Success({ <%= camelize(name) %> }); + const successAction = new Load<%= classify(name) %>Success({ <%= camelize(name) %> }); beforeEach(() => { store$.dispatch(successAction); @@ -76,7 +76,7 @@ describe('<%= classify(name) %> Selectors', () => { describe('Load<%= classify(name) %>Fail', () => { const error = { error: 'ERROR' } as HttpError; - const failAction = new actions.Load<%= classify(name) %>Fail({ error }); + const failAction = new Load<%= classify(name) %>Fail({ error }); beforeEach(() => { store$.dispatch(failAction); diff --git a/tslint-rules/src/noStarImportsInStoreRule.ts b/tslint-rules/src/noStarImportsInStoreRule.ts new file mode 100644 index 0000000000..7f4ea9bbad --- /dev/null +++ b/tslint-rules/src/noStarImportsInStoreRule.ts @@ -0,0 +1,53 @@ +import { tsquery } from '@phenomnomnominal/tsquery'; +import * as Lint from 'tslint'; +import * as ts from 'typescript'; + +export class Rule extends Lint.Rules.AbstractRule { + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + if (/^.*\.(effects|reducer|actions|selectors)\.(ts|spec\.ts)$/.test(sourceFile.fileName)) { + return this.applyWithFunction(sourceFile, ctx => { + sourceFile.statements + .filter(ts.isImportDeclaration) + .filter(importStatement => + /^\..*\.(actions|selectors)$/.test(importStatement.moduleSpecifier.getText().replace(/['"]/g, '')) + ) + .map(importStatement => importStatement.importClause.namedBindings) + .filter(x => !!x) + .filter(ts.isNamespaceImport) + .forEach(node => { + this.visitNamespaceImportDeclaration(ctx, node); + }); + }); + } + return []; + } + + private visitNamespaceImportDeclaration(ctx: Lint.WalkContext, importStatement: ts.NamespaceImport) { + const importString = importStatement.name.text; + + // get all Nodes that use the star import + const importNodes = tsquery( + ctx.sourceFile, + `PropertyAccessExpression[expression.name=${importString}],TypeReference > QualifiedName[left.text=${importString}]` + ).sort((a, b) => b.getStart() - a.getStart()); + + // replace all star import references + importNodes.forEach(node => { + const fix = Lint.Replacement.deleteText(node.getStart(), importString.length + 1); + ctx.addFailureAtNode(node, 'star imports are banned', fix); + }); + + // replace import itself + const newImportStrings = importNodes + .map(node => node.getText().replace(`${importString}.`, '').split('.')[0]) + .filter((node, index, array) => array.indexOf(node) === index) + .sort(); + + const importFix = new Lint.Replacement( + importStatement.getStart(), + importStatement.getWidth(), + `{\n ${newImportStrings.join(',\n ')},\n}` + ); + ctx.addFailureAtNode(importStatement, `Star imports in ngrx store files are banned.`, importFix); + } +} diff --git a/tslint.json b/tslint.json index fd56211916..5a14b56741 100644 --- a/tslint.json +++ b/tslint.json @@ -532,6 +532,7 @@ } } }, + "no-star-imports-in-store": { "severity": "warning" }, "initialize-observables-in-ngoninit": true, "project-structure": { "severity": "warning",