diff --git a/e2e/schematics/application.test.ts b/e2e/schematics/application.test.ts index 1cc6de6a77c1c..4d331bd8f2846 100644 --- a/e2e/schematics/application.test.ts +++ b/e2e/schematics/application.test.ts @@ -5,19 +5,19 @@ describe('Nrwl Workspace', () => { 'should work', () => { newProject(); - newApp('myapp'); - newLib('mylib --ngmodule'); + newApp('myApp --directory=myDir'); + newLib('myLib --directory=myDir --ngmodule'); updateFile( - 'apps/myapp/src/app/app.module.ts', + 'apps/my-dir/my-app/src/app/app.module.ts', ` import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; - import { MylibModule } from '@nrwl/mylib'; + import { MyLibModule } from '@nrwl/my-dir/my-lib'; import { AppComponent } from './app.component'; @NgModule({ - imports: [BrowserModule, MylibModule], + imports: [BrowserModule, MyLibModule], declarations: [AppComponent], bootstrap: [AppComponent] }) @@ -35,8 +35,8 @@ describe('Nrwl Workspace', () => { 'should support router config generation (lazy)', () => { newProject(); - newApp('myapp --routing'); - newLib('mylib --routing --lazy --parentModule=apps/myapp/src/app/app.module.ts'); + newApp('myApp --directory=myDir --routing'); + newLib('myLib --directory=myDir --routing --lazy --parentModule=apps/my-dir/my-app/src/app/app.module.ts'); runCLI('build --aot'); expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS'); @@ -48,8 +48,8 @@ describe('Nrwl Workspace', () => { 'should support router config generation (eager)', () => { newProject(); - newApp('myapp --routing'); - newLib('mylib --routing --parentModule=apps/myapp/src/app/app.module.ts'); + newApp('myApp --directory=myDir --routing'); + newLib('myLib --directory=myDir --routing --parentModule=apps/my-dir/my-app/src/app/app.module.ts'); runCLI('build --aot'); expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS'); diff --git a/e2e/schematics/tslint.test.ts b/e2e/schematics/tslint.test.ts index 85a27d926efca..34fdd2fbbf246 100644 --- a/e2e/schematics/tslint.test.ts +++ b/e2e/schematics/tslint.test.ts @@ -18,12 +18,14 @@ describe('Lint', () => { ` import '../../../libs/mylib'; import '@nrwl/lazylib'; + import '@nrwl/mylib/deep'; ` ); const out = runCLI('lint --type-check', { silenceError: true }); expect(out).toContain('relative imports of libraries are forbidden'); expect(out).toContain('import of lazy-loaded libraries are forbidden'); + expect(out).toContain('deep imports into libraries are forbidden'); }, 1000000 ); diff --git a/packages/schematics/migrations/20171202-change-schema.ts b/packages/schematics/migrations/20171202-change-schema.ts index 25bb9ac4df2c3..3ae1d395c3f9d 100644 --- a/packages/schematics/migrations/20171202-change-schema.ts +++ b/packages/schematics/migrations/20171202-change-schema.ts @@ -9,8 +9,8 @@ export default { const rule = ruleName in json.rules ? json.rules[ruleName] : null; // Only modify when the rule is configured with optional arguments - if (Array.isArray(rule) && typeof rule[2] === 'object' && rule[2] !== null) { - rule[2][ruleOptionName] = []; + if (Array.isArray(rule) && typeof rule[1] === 'object' && rule[1] !== null) { + rule[1][ruleOptionName] = []; } }); } diff --git a/packages/schematics/migrations/20171205-remove-npmscope-from-tslintjson.ts b/packages/schematics/migrations/20171205-remove-npmscope-from-tslintjson.ts new file mode 100644 index 0000000000000..9e472d94abbcd --- /dev/null +++ b/packages/schematics/migrations/20171205-remove-npmscope-from-tslintjson.ts @@ -0,0 +1,16 @@ +import { updateJsonFile } from '../src/collection/utility/fileutils'; + +export default { + description: 'Remove npmScope from tslint.json', + run: () => { + updateJsonFile('tslint.json', json => { + const ruleName = 'nx-enforce-module-boundaries'; + const rule = ruleName in json.rules ? json.rules[ruleName] : null; + + // Only modify when the rule is configured with optional arguments + if (Array.isArray(rule) && typeof rule[1] === 'object' && rule[1] !== null) { + rule[1].npmScope = undefined; + } + }); + } +}; diff --git a/packages/schematics/src/collection/app/app.spec.ts b/packages/schematics/src/collection/app/app.spec.ts index c20f2327d343a..f00231ba929b7 100644 --- a/packages/schematics/src/collection/app/app.spec.ts +++ b/packages/schematics/src/collection/app/app.spec.ts @@ -14,49 +14,86 @@ describe('app', () => { appTree = createEmptyWorkspace(appTree); }); - it('should update angular-cli.json', () => { - const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree); - const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json')); - expect(updatedAngularCLIJson.apps).toEqual([ - { - assets: ['assets', 'favicon.ico'], - environmentSource: 'environments/environment.ts', - environments: { dev: 'environments/environment.ts', prod: 'environments/environment.prod.ts' }, - index: 'index.html', - main: 'main.ts', - name: 'my-app', - outDir: 'dist/apps/my-app', - polyfills: 'polyfills.ts', - prefix: 'app', - root: 'apps/my-app/src', - scripts: [], - styles: ['styles.css'], - test: '../../../test.js', - testTsconfig: '../../../tsconfig.spec.json', - tsconfig: '../../../tsconfig.app.json' - } - ]); + describe('not nested', () => { + it('should update angular-cli.json', () => { + const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree); + const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json')); + expect(updatedAngularCLIJson.apps).toEqual([ + { + assets: ['assets', 'favicon.ico'], + environmentSource: 'environments/environment.ts', + environments: { dev: 'environments/environment.ts', prod: 'environments/environment.prod.ts' }, + index: 'index.html', + main: 'main.ts', + name: 'my-app', + outDir: 'dist/apps/my-app', + polyfills: 'polyfills.ts', + prefix: 'app', + root: 'apps/my-app/src', + scripts: [], + styles: ['styles.css'], + test: '../../../test.js', + testTsconfig: '../../../tsconfig.spec.json', + tsconfig: '../../../tsconfig.app.json' + } + ]); + }); + + it('should generate files', () => { + const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree); + expect(tree.exists('apps/my-app/src/main.ts')).toBeTruthy(); + expect(tree.exists('apps/my-app/src/app/app.module.ts')).toBeTruthy(); + expect(tree.exists('apps/my-app/src/app/app.component.ts')).toBeTruthy(); + expect(tree.exists('apps/my-app/e2e/app.po.ts')).toBeTruthy(); + expect(getFileContent(tree, 'apps/my-app/src/app/app.module.ts')).toContain('class AppModule'); + }); }); - it('should generate files', () => { - const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree); - expect(tree.exists('apps/my-app/src/main.ts')).toBeTruthy(); - expect(tree.exists('apps/my-app/src/app/app.module.ts')).toBeTruthy(); - expect(tree.exists('apps/my-app/src/app/app.component.ts')).toBeTruthy(); - expect(tree.exists('apps/my-app/e2e/app.po.ts')).toBeTruthy(); - expect(getFileContent(tree, 'apps/my-app/src/app/app.module.ts')).toContain('class AppModule'); + describe('nested', () => { + it('should update angular-cli.json', () => { + const tree = schematicRunner.runSchematic('app', { name: 'myApp', directory: 'myDir' }, appTree); + const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json')); + expect(updatedAngularCLIJson.apps).toEqual([ + { + assets: ['assets', 'favicon.ico'], + environmentSource: 'environments/environment.ts', + environments: { dev: 'environments/environment.ts', prod: 'environments/environment.prod.ts' }, + index: 'index.html', + main: 'main.ts', + name: 'my-dir/my-app', + outDir: 'dist/apps/my-dir/my-app', + polyfills: 'polyfills.ts', + prefix: 'app', + root: 'apps/my-dir/my-app/src', + scripts: [], + styles: ['styles.css'], + test: '../../../../test.js', + testTsconfig: '../../../../tsconfig.spec.json', + tsconfig: '../../../../tsconfig.app.json' + } + ]); + }); + + it('should generate files', () => { + const tree = schematicRunner.runSchematic('app', { name: 'myApp', directory: 'myDir' }, appTree); + expect(tree.exists('apps/my-dir/my-app/src/main.ts')).toBeTruthy(); + expect(tree.exists('apps/my-dir/my-app/src/app/app.module.ts')).toBeTruthy(); + expect(tree.exists('apps/my-dir/my-app/src/app/app.component.ts')).toBeTruthy(); + expect(tree.exists('apps/my-dir/my-app/e2e/app.po.ts')).toBeTruthy(); + expect(getFileContent(tree, 'apps/my-dir/my-app/src/app/app.module.ts')).toContain('class AppModule'); + }); }); it('should import NgModule', () => { - const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree); - expect(getFileContent(tree, 'apps/my-app/src/app/app.module.ts')).toContain('NxModule.forRoot()'); + const tree = schematicRunner.runSchematic('app', { name: 'myApp', directory: 'myDir' }, appTree); + expect(getFileContent(tree, 'apps/my-dir/my-app/src/app/app.module.ts')).toContain('NxModule.forRoot()'); }); describe('routing', () => { it('should include RouterTestingModule', () => { - const tree = schematicRunner.runSchematic('app', { name: 'myApp', routing: true }, appTree); - expect(getFileContent(tree, 'apps/my-app/src/app/app.module.ts')).toContain('RouterModule.forRoot'); - expect(getFileContent(tree, 'apps/my-app/src/app/app.component.spec.ts')).toContain( + const tree = schematicRunner.runSchematic('app', { name: 'myApp', directory: 'myDir', routing: true }, appTree); + expect(getFileContent(tree, 'apps/my-dir/my-app/src/app/app.module.ts')).toContain('RouterModule.forRoot'); + expect(getFileContent(tree, 'apps/my-dir/my-app/src/app/app.component.spec.ts')).toContain( 'imports: [RouterTestingModule]' ); }); diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/assets/__dot__gitkeep b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/assets/__dot__gitkeep similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/assets/__dot__gitkeep rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/assets/__dot__gitkeep diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/assets/nx-logo.png b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/assets/nx-logo.png similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/assets/nx-logo.png rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/assets/nx-logo.png diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/environments/environment.prod.ts__tmpl__ b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/environments/environment.prod.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/environments/environment.prod.ts__tmpl__ rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/environments/environment.prod.ts__tmpl__ diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/environments/environment.ts__tmpl__ b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/environments/environment.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/environments/environment.ts__tmpl__ rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/environments/environment.ts__tmpl__ diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/favicon.ico b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/favicon.ico similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/favicon.ico rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/favicon.ico diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/index.html__tmpl__ b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/index.html__tmpl__ similarity index 97% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/index.html__tmpl__ rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/index.html__tmpl__ index 63b8497cace2b..cd8a591155806 100644 --- a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/index.html__tmpl__ +++ b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/index.html__tmpl__ @@ -11,4 +11,4 @@ <<%= prefix %>-root>-root> - + \ No newline at end of file diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/main.ts__tmpl__ b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/main.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/main.ts__tmpl__ rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/main.ts__tmpl__ diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/polyfills.ts__tmpl__ b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/polyfills.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/polyfills.ts__tmpl__ rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/polyfills.ts__tmpl__ diff --git a/packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/styles.__style__ b/packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/styles.__style__ similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/__sourceDir__/styles.__style__ rename to packages/schematics/src/collection/app/files/apps/__fullName__/__sourceDir__/styles.__style__ diff --git a/packages/schematics/src/collection/app/files/apps/__name__/e2e/app.e2e-spec.ts__tmpl__ b/packages/schematics/src/collection/app/files/apps/__fullName__/e2e/app.e2e-spec.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/e2e/app.e2e-spec.ts__tmpl__ rename to packages/schematics/src/collection/app/files/apps/__fullName__/e2e/app.e2e-spec.ts__tmpl__ diff --git a/packages/schematics/src/collection/app/files/apps/__name__/e2e/app.po.ts__tmpl__ b/packages/schematics/src/collection/app/files/apps/__fullName__/e2e/app.po.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/app/files/apps/__name__/e2e/app.po.ts__tmpl__ rename to packages/schematics/src/collection/app/files/apps/__fullName__/e2e/app.po.ts__tmpl__ diff --git a/packages/schematics/src/collection/app/index.ts b/packages/schematics/src/collection/app/index.ts index c3dc09aa684f0..d4b382a908b19 100644 --- a/packages/schematics/src/collection/app/index.ts +++ b/packages/schematics/src/collection/app/index.ts @@ -21,6 +21,12 @@ import { addBootstrapToModule } from '@schematics/angular/utility/ast-utils'; import { insertImport } from '@schematics/angular/utility/route-utils'; import { addApp, serializeJson } from '../utility/fileutils'; import { addImportToTestBed } from '../utility/ast-utils'; +import { offsetFromRoot } from '../utility/common'; + +interface NormalizedSchema extends Schema { + fullName: string; + fullPath: string; +} function addBootstrap(path: string): Rule { return (host: Tree) => { @@ -48,7 +54,7 @@ function addNxModule(path: string): Rule { return host; }; } -function addAppToAngularCliJson(options: Schema): Rule { +function addAppToAngularCliJson(options: NormalizedSchema): Rule { return (host: Tree) => { if (!host.exists('.angular-cli.json')) { throw new Error('Missing .angular-cli.json'); @@ -57,16 +63,16 @@ function addAppToAngularCliJson(options: Schema): Rule { const sourceText = host.read('.angular-cli.json')!.toString('utf-8'); const json = JSON.parse(sourceText); json.apps = addApp(json.apps, { - name: options.name, - root: fullPath(options), - outDir: `dist/apps/${options.name}`, + name: options.fullName, + root: options.fullPath, + outDir: `dist/apps/${options.fullName}`, assets: ['assets', 'favicon.ico'], index: 'index.html', main: 'main.ts', polyfills: 'polyfills.ts', - test: '../../../test.js', - tsconfig: '../../../tsconfig.app.json', - testTsconfig: '../../../tsconfig.spec.json', + test: `${offsetFromRoot(options)}test.js`, + tsconfig: `${offsetFromRoot(options)}tsconfig.app.json`, + testTsconfig: `${offsetFromRoot(options)}tsconfig.spec.json`, prefix: options.prefix, styles: [`styles.${options.style}`], scripts: [], @@ -109,7 +115,8 @@ function addRouterRootConfiguration(path: string): Rule { } export default function(schema: Schema): Rule { - const options = { ...schema, name: toFileName(schema.name) }; + const options = normalizeOptions(schema); + const templateSource = apply(url('./files'), [ template({ utils: stringUtils, dot: '.', tmpl: '', ...(options as object) }) ]); @@ -122,13 +129,13 @@ export default function(schema: Schema): Rule { commonModule: false, flat: true, routing: false, - sourceDir: fullPath(options), + sourceDir: options.fullPath, spec: false }), externalSchematic('@schematics/angular', 'component', { name: 'app', selector: selector, - sourceDir: fullPath(options), + sourceDir: options.fullPath, flat: true, inlineStyle: options.inlineStyle, inlineTemplate: options.inlineTemplate, @@ -142,17 +149,20 @@ export default function(schema: Schema): Rule { apply(url('./component-files'), [ options.inlineTemplate ? filter(path => !path.endsWith('.html')) : noop(), template({ ...options, tmpl: '' }), - move(`${fullPath(options)}/app`) + move(`${options.fullPath}/app`) ]), MergeStrategy.Overwrite ), - addBootstrap(fullPath(options)), - addNxModule(fullPath(options)), + addBootstrap(options.fullPath), + addNxModule(options.fullPath), addAppToAngularCliJson(options), - options.routing ? addRouterRootConfiguration(fullPath(options)) : noop() + options.routing ? addRouterRootConfiguration(options.fullPath) : noop() ]); } -function fullPath(options: Schema) { - return `apps/${options.name}/${options.sourceDir}`; +function normalizeOptions(options: Schema): NormalizedSchema { + const name = toFileName(options.name); + const fullName = options.directory ? `${toFileName(options.directory)}/${name}` : name; + const fullPath = `apps/${fullName}/${options.sourceDir}`; + return { ...options, name, fullName, fullPath }; } diff --git a/packages/schematics/src/collection/app/schema.d.ts b/packages/schematics/src/collection/app/schema.d.ts index 8271c6333e9e9..64c3ef3f714a1 100644 --- a/packages/schematics/src/collection/app/schema.d.ts +++ b/packages/schematics/src/collection/app/schema.d.ts @@ -1,5 +1,6 @@ export interface Schema { name: string; + directory?: string; sourceDir?: string; inlineStyle?: boolean; inlineTemplate?: boolean; diff --git a/packages/schematics/src/collection/app/schema.json b/packages/schematics/src/collection/app/schema.json index 899645a905453..bfbff040ce4f7 100644 --- a/packages/schematics/src/collection/app/schema.json +++ b/packages/schematics/src/collection/app/schema.json @@ -8,6 +8,10 @@ "type": "string", "description": "Application name" }, + "directory": { + "type": "string", + "description": "A directory where the app is placed" + }, "sourceDir": { "type": "string", "default": "src", diff --git a/packages/schematics/src/collection/application/application.spec.ts b/packages/schematics/src/collection/application/application.spec.ts index 32201aa0fd581..beb4912ec3f35 100644 --- a/packages/schematics/src/collection/application/application.spec.ts +++ b/packages/schematics/src/collection/application/application.spec.ts @@ -54,11 +54,5 @@ describe('application', () => { const tsconfigJson = JSON.parse(getFileContent(tree, '/my-app/tsconfig.json')); expect(tsconfigJson.compilerOptions.paths).toEqual({ '@myApp/*': ['libs/*'] }); - - const tslintJson = JSON.parse(getFileContent(tree, '/my-app/tslint.json')); - expect(tslintJson.rules['nx-enforce-module-boundaries']).toEqual([ - true, - { allow: [], lazyLoad: [], npmScope: 'myApp' } - ]); }); }); diff --git a/packages/schematics/src/collection/application/files/__directory__/tslint.json b/packages/schematics/src/collection/application/files/__directory__/tslint.json index cd88d82203ee8..6ea54dbca98ea 100644 --- a/packages/schematics/src/collection/application/files/__directory__/tslint.json +++ b/packages/schematics/src/collection/application/files/__directory__/tslint.json @@ -113,7 +113,6 @@ "nx-enforce-module-boundaries": [ true, { - "npmScope": "<%= npmScope %>", "lazyLoad": [], "allow": [] } diff --git a/packages/schematics/src/collection/lib/files/libs/__name__/__sourceDir__/__fileName__.spec.ts__tmpl__ b/packages/schematics/src/collection/lib/files/libs/__fullName__/__sourceDir__/__fileName__.spec.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/lib/files/libs/__name__/__sourceDir__/__fileName__.spec.ts__tmpl__ rename to packages/schematics/src/collection/lib/files/libs/__fullName__/__sourceDir__/__fileName__.spec.ts__tmpl__ diff --git a/packages/schematics/src/collection/lib/files/libs/__name__/__sourceDir__/__fileName__.ts__tmpl__ b/packages/schematics/src/collection/lib/files/libs/__fullName__/__sourceDir__/__fileName__.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/lib/files/libs/__name__/__sourceDir__/__fileName__.ts__tmpl__ rename to packages/schematics/src/collection/lib/files/libs/__fullName__/__sourceDir__/__fileName__.ts__tmpl__ diff --git a/packages/schematics/src/collection/lib/files/libs/__name__/index.ts__tmpl__ b/packages/schematics/src/collection/lib/files/libs/__fullName__/index.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/lib/files/libs/__name__/index.ts__tmpl__ rename to packages/schematics/src/collection/lib/files/libs/__fullName__/index.ts__tmpl__ diff --git a/packages/schematics/src/collection/lib/index.ts b/packages/schematics/src/collection/lib/index.ts index 74d368e3c7446..368e860ca6c5a 100644 --- a/packages/schematics/src/collection/lib/index.ts +++ b/packages/schematics/src/collection/lib/index.ts @@ -18,14 +18,21 @@ import { serializeJson, addApp, cliConfig } from '../utility/fileutils'; import { insertImport } from '@schematics/angular/utility/route-utils'; import * as ts from 'typescript'; import { addGlobal, addReexport, addRoute } from '../utility/ast-utils'; +import { offsetFromRoot } from '../utility/common'; -function addLibToAngularCliJson(options: Schema): Rule { +interface NormalizedSchema extends Schema { + name: string; + fullName: string; + fullPath: string; +} + +function addLibToAngularCliJson(options: NormalizedSchema): Rule { return (host: Tree) => { const json = cliConfig(host); json.apps = addApp(json.apps, { - name: options.name, - root: fullPath(options), - test: '../../../test.js', + name: options.fullName, + root: options.fullPath, + test: `${offsetFromRoot(options)}test.js`, appRoot: '' }); @@ -54,7 +61,7 @@ function addLazyLoadedRouterConfiguration(modulePath: string): Rule { } function addRouterConfiguration( - schema: Schema, + schema: NormalizedSchema, indexFilePath: string, moduleFileName: string, modulePath: string @@ -76,14 +83,14 @@ function addRouterConfiguration( }; } -function addLoadChildren(schema: Schema): Rule { +function addLoadChildren(schema: NormalizedSchema): Rule { return (host: Tree) => { const json = cliConfig(host); const moduleSource = host.read(schema.parentModule)!.toString('utf-8'); const sourceFile = ts.createSourceFile(schema.parentModule, moduleSource, ts.ScriptTarget.Latest, true); - const loadChildren = `@${json.project.npmScope}/${toFileName(schema.name)}#${toClassName(schema.name)}Module`; + const loadChildren = `@${json.project.npmScope}/${toFileName(schema.fullName)}#${toClassName(schema.name)}Module`; insert(host, schema.parentModule, [ ...addRoute( schema.parentModule, @@ -95,14 +102,14 @@ function addLoadChildren(schema: Schema): Rule { }; } -function addChildren(schema: Schema): Rule { +function addChildren(schema: NormalizedSchema): Rule { return (host: Tree) => { const json = cliConfig(host); const moduleSource = host.read(schema.parentModule)!.toString('utf-8'); const sourceFile = ts.createSourceFile(schema.parentModule, moduleSource, ts.ScriptTarget.Latest, true); const constName = `${toPropertyName(schema.name)}Routes`; - const importPath = `@${json.project.npmScope}/${toFileName(schema.name)}`; + const importPath = `@${json.project.npmScope}/${toFileName(schema.fullName)}`; insert(host, schema.parentModule, [ insertImport(sourceFile, schema.parentModule, constName, importPath), @@ -112,7 +119,7 @@ function addChildren(schema: Schema): Rule { }; } -function updateTsLint(schema: Schema): Rule { +function updateTsLint(schema: NormalizedSchema): Rule { return (host: Tree) => { const tsLint = JSON.parse(host.read('tslint.json')!.toString('utf-8')); if ( @@ -121,7 +128,7 @@ function updateTsLint(schema: Schema): Rule { tsLint['rules']['nx-enforce-module-boundaries'][1] && tsLint['rules']['nx-enforce-module-boundaries'][1]['lazyLoad'] ) { - tsLint['rules']['nx-enforce-module-boundaries'][1]['lazyLoad'].push(toFileName(schema.name)); + tsLint['rules']['nx-enforce-module-boundaries'][1]['lazyLoad'].push(toFileName(schema.fullName)); host.overwrite('tslint.json', serializeJson(tsLint)); } return host; @@ -129,16 +136,16 @@ function updateTsLint(schema: Schema): Rule { } export default function(schema: Schema): Rule { - const options = { ...schema, name: toFileName(schema.name) }; - const moduleFileName = `${toFileName(schema.name)}.module`; - const modulePath = `${fullPath(schema)}/${moduleFileName}.ts`; - const indexFile = `libs/${toFileName(options.name)}/index.ts`; + const options = normalizeOptions(schema); + const moduleFileName = `${toFileName(options.name)}.module`; + const modulePath = `${options.fullPath}/${moduleFileName}.ts`; + const indexFile = `libs/${toFileName(options.fullName)}/index.ts`; - if (schema.routing && schema.nomodule) { + if (options.routing && options.nomodule) { throw new Error(`nomodule and routing cannot be used together`); } - if (!schema.routing && schema.lazy) { + if (!options.routing && options.lazy) { throw new Error(`routing must be set`); } @@ -154,15 +161,18 @@ export default function(schema: Schema): Rule { return chain([ branchAndMerge(chain([mergeWith(templateSource)])), addLibToAngularCliJson(options), - schema.routing && schema.lazy ? addLazyLoadedRouterConfiguration(modulePath) : noop(), - schema.routing && schema.lazy ? updateTsLint(schema) : noop(), - schema.routing && schema.lazy && schema.parentModule ? addLoadChildren(schema) : noop(), + options.routing && options.lazy ? addLazyLoadedRouterConfiguration(modulePath) : noop(), + options.routing && options.lazy ? updateTsLint(options) : noop(), + options.routing && options.lazy && options.parentModule ? addLoadChildren(options) : noop(), - schema.routing && !schema.lazy ? addRouterConfiguration(schema, indexFile, moduleFileName, modulePath) : noop(), - schema.routing && !schema.lazy && schema.parentModule ? addChildren(schema) : noop() + options.routing && !options.lazy ? addRouterConfiguration(options, indexFile, moduleFileName, modulePath) : noop(), + options.routing && !options.lazy && options.parentModule ? addChildren(options) : noop() ]); } -function fullPath(options: Schema) { - return `libs/${toFileName(options.name)}/${options.sourceDir}`; +function normalizeOptions(options: Schema): NormalizedSchema { + const name = toFileName(options.name); + const fullName = options.directory ? `${toFileName(options.directory)}/${name}` : name; + const fullPath = `libs/${fullName}/${options.sourceDir}`; + return { ...options, name, fullName, fullPath }; } diff --git a/packages/schematics/src/collection/lib/lib.spec.ts b/packages/schematics/src/collection/lib/lib.spec.ts index 9b30f1c4939f6..63a11cd11ba91 100644 --- a/packages/schematics/src/collection/lib/lib.spec.ts +++ b/packages/schematics/src/collection/lib/lib.spec.ts @@ -16,33 +16,58 @@ describe('lib', () => { schematicRunner.logger.subscribe(s => console.log(s)); }); - it('should update angular-cli.json', () => { - const tree = schematicRunner.runSchematic('lib', { name: 'myLib' }, appTree); - const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json')); - expect(updatedAngularCLIJson.apps).toEqual([ - { - appRoot: '', - name: 'my-lib', - root: 'libs/my-lib/src', - test: '../../../test.js' - } - ]); - }); + describe('not nested', () => { + it('should update angular-cli.json', () => { + const tree = schematicRunner.runSchematic('lib', { name: 'myLib' }, appTree); + const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json')); + expect(updatedAngularCLIJson.apps).toEqual([ + { + appRoot: '', + name: 'my-lib', + root: 'libs/my-lib/src', + test: '../../../test.js' + } + ]); + }); - it('should generate files', () => { - const tree = schematicRunner.runSchematic('lib', { name: 'myLib', nomodule: true }, appTree); - expect(tree.exists('libs/my-lib/src/my-lib.ts')).toBeTruthy(); - expect(tree.exists('libs/my-lib/src/my-lib.spec.ts')).toBeTruthy(); - expect(tree.exists('libs/my-lib/index.ts')).toBeTruthy(); - expect(getFileContent(tree, 'libs/my-lib/src/my-lib.ts')).toContain('class MyLib'); + it('should generate files', () => { + const tree = schematicRunner.runSchematic('lib', { name: 'myLib', nomodule: true }, appTree); + expect(tree.exists('libs/my-lib/src/my-lib.ts')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/my-lib.spec.ts')).toBeTruthy(); + expect(tree.exists('libs/my-lib/index.ts')).toBeTruthy(); + expect(getFileContent(tree, 'libs/my-lib/src/my-lib.ts')).toContain('class MyLib'); + }); + + it('should generate files', () => { + const tree = schematicRunner.runSchematic('lib', { name: 'myLib' }, appTree); + expect(tree.exists('libs/my-lib/src/my-lib.module.ts')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/my-lib.module.spec.ts')).toBeTruthy(); + expect(tree.exists('libs/my-lib/index.ts')).toBeTruthy(); + expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('class MyLibModule'); + }); }); - it('should generate files', () => { - const tree = schematicRunner.runSchematic('lib', { name: 'myLib' }, appTree); - expect(tree.exists('libs/my-lib/src/my-lib.module.ts')).toBeTruthy(); - expect(tree.exists('libs/my-lib/src/my-lib.module.spec.ts')).toBeTruthy(); - expect(tree.exists('libs/my-lib/index.ts')).toBeTruthy(); - expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('class MyLibModule'); + describe('nested', () => { + it('should update angular-cli.json', () => { + const tree = schematicRunner.runSchematic('lib', { name: 'myLib', directory: 'myDir' }, appTree); + const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json')); + expect(updatedAngularCLIJson.apps).toEqual([ + { + appRoot: '', + name: 'my-dir/my-lib', + root: 'libs/my-dir/my-lib/src', + test: '../../../../test.js' + } + ]); + }); + + it('should generate files', () => { + const tree = schematicRunner.runSchematic('lib', { name: 'myLib', directory: 'myDir', nomodule: true }, appTree); + expect(tree.exists('libs/my-dir/my-lib/src/my-lib.ts')).toBeTruthy(); + expect(tree.exists('libs/my-dir/my-lib/src/my-lib.spec.ts')).toBeTruthy(); + expect(tree.exists('libs/my-dir/my-lib/index.ts')).toBeTruthy(); + expect(getFileContent(tree, 'libs/my-dir/my-lib/src/my-lib.ts')).toContain('class MyLib'); + }); }); describe('router', () => { @@ -60,53 +85,75 @@ describe('lib', () => { describe('lazy', () => { it('should add RouterModule.forChild', () => { - const tree = schematicRunner.runSchematic('lib', { name: 'myLib', routing: true, lazy: true }, appTree); - expect(tree.exists('libs/my-lib/src/my-lib.module.ts')).toBeTruthy(); - expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('RouterModule.forChild'); + const tree = schematicRunner.runSchematic( + 'lib', + { name: 'myLib', directory: 'myDir', routing: true, lazy: true }, + appTree + ); + expect(tree.exists('libs/my-dir/my-lib/src/my-lib.module.ts')).toBeTruthy(); + expect(getFileContent(tree, 'libs/my-dir/my-lib/src/my-lib.module.ts')).toContain('RouterModule.forChild'); }); it('should update the parent module', () => { appTree = createApp(appTree, 'myapp'); const tree = schematicRunner.runSchematic( 'lib', - { name: 'myLib', routing: true, lazy: true, parentModule: 'apps/myapp/src/app/app.module.ts' }, + { + name: 'myLib', + directory: 'myDir', + routing: true, + lazy: true, + parentModule: 'apps/myapp/src/app/app.module.ts' + }, appTree ); expect(getFileContent(tree, 'apps/myapp/src/app/app.module.ts')).toContain( - `RouterModule.forRoot([{path: 'my-lib', loadChildren: '@proj/my-lib#MyLibModule'}])` + `RouterModule.forRoot([{path: 'my-lib', loadChildren: '@proj/my-dir/my-lib#MyLibModule'}])` ); const tree2 = schematicRunner.runSchematic( 'lib', - { name: 'myLib2', routing: true, lazy: true, parentModule: 'apps/myapp/src/app/app.module.ts' }, + { + name: 'myLib2', + directory: 'myDir', + routing: true, + lazy: true, + parentModule: 'apps/myapp/src/app/app.module.ts' + }, tree ); expect(getFileContent(tree2, 'apps/myapp/src/app/app.module.ts')).toContain( - `RouterModule.forRoot([{path: 'my-lib', loadChildren: '@proj/my-lib#MyLibModule'}, {path: 'my-lib2', loadChildren: '@proj/my-lib2#MyLib2Module'}])` + `RouterModule.forRoot([{path: 'my-lib', loadChildren: '@proj/my-dir/my-lib#MyLibModule'}, {path: 'my-lib2', loadChildren: '@proj/my-dir/my-lib2#MyLib2Module'}])` ); }); it('should register the module as lazy loaded in tslint.json', () => { - const tree = schematicRunner.runSchematic('lib', { name: 'myLib', routing: true, lazy: true }, appTree); + const tree = schematicRunner.runSchematic( + 'lib', + { name: 'myLib', directory: 'myDir', routing: true, lazy: true }, + appTree + ); const tslint = JSON.parse(getFileContent(tree, 'tslint.json')); - expect(tslint['rules']['nx-enforce-module-boundaries'][1]['lazyLoad']).toEqual(['my-lib']); + expect(tslint['rules']['nx-enforce-module-boundaries'][1]['lazyLoad']).toEqual(['my-dir/my-lib']); }); }); describe('eager', () => { it('should add RouterModule and define an array of routes', () => { - const tree = schematicRunner.runSchematic('lib', { name: 'myLib', routing: true }, appTree); - expect(tree.exists('libs/my-lib/src/my-lib.module.ts')).toBeTruthy(); - expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('RouterModule]'); - expect(getFileContent(tree, 'libs/my-lib/src/my-lib.module.ts')).toContain('const myLibRoutes: Route[] = '); - expect(getFileContent(tree, 'libs/my-lib/index.ts')).toContain('myLibRoutes'); + const tree = schematicRunner.runSchematic('lib', { name: 'myLib', directory: 'myDir', routing: true }, appTree); + expect(tree.exists('libs/my-dir/my-lib/src/my-lib.module.ts')).toBeTruthy(); + expect(getFileContent(tree, 'libs/my-dir/my-lib/src/my-lib.module.ts')).toContain('RouterModule]'); + expect(getFileContent(tree, 'libs/my-dir/my-lib/src/my-lib.module.ts')).toContain( + 'const myLibRoutes: Route[] = ' + ); + expect(getFileContent(tree, 'libs/my-dir/my-lib/index.ts')).toContain('myLibRoutes'); }); it('should update the parent module', () => { appTree = createApp(appTree, 'myapp'); const tree = schematicRunner.runSchematic( 'lib', - { name: 'myLib', routing: true, parentModule: 'apps/myapp/src/app/app.module.ts' }, + { name: 'myLib', directory: 'myDir', routing: true, parentModule: 'apps/myapp/src/app/app.module.ts' }, appTree ); expect(getFileContent(tree, 'apps/myapp/src/app/app.module.ts')).toContain( @@ -115,7 +162,7 @@ describe('lib', () => { const tree2 = schematicRunner.runSchematic( 'lib', - { name: 'myLib2', routing: true, parentModule: 'apps/myapp/src/app/app.module.ts' }, + { name: 'myLib2', directory: 'myDir', routing: true, parentModule: 'apps/myapp/src/app/app.module.ts' }, tree ); expect(getFileContent(tree2, 'apps/myapp/src/app/app.module.ts')).toContain( diff --git a/packages/schematics/src/collection/lib/ngfiles/libs/__name__/__sourceDir__/__fileName__.module.spec.ts__tmpl__ b/packages/schematics/src/collection/lib/ngfiles/libs/__fullName__/__sourceDir__/__fileName__.module.spec.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/lib/ngfiles/libs/__name__/__sourceDir__/__fileName__.module.spec.ts__tmpl__ rename to packages/schematics/src/collection/lib/ngfiles/libs/__fullName__/__sourceDir__/__fileName__.module.spec.ts__tmpl__ diff --git a/packages/schematics/src/collection/lib/ngfiles/libs/__name__/__sourceDir__/__fileName__.module.ts__tmpl__ b/packages/schematics/src/collection/lib/ngfiles/libs/__fullName__/__sourceDir__/__fileName__.module.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/lib/ngfiles/libs/__name__/__sourceDir__/__fileName__.module.ts__tmpl__ rename to packages/schematics/src/collection/lib/ngfiles/libs/__fullName__/__sourceDir__/__fileName__.module.ts__tmpl__ diff --git a/packages/schematics/src/collection/lib/ngfiles/libs/__name__/index.ts__tmpl__ b/packages/schematics/src/collection/lib/ngfiles/libs/__fullName__/index.ts__tmpl__ similarity index 100% rename from packages/schematics/src/collection/lib/ngfiles/libs/__name__/index.ts__tmpl__ rename to packages/schematics/src/collection/lib/ngfiles/libs/__fullName__/index.ts__tmpl__ diff --git a/packages/schematics/src/collection/lib/schema.d.ts b/packages/schematics/src/collection/lib/schema.d.ts index 11cf317c67c1c..0f62e3cbd1fac 100644 --- a/packages/schematics/src/collection/lib/schema.d.ts +++ b/packages/schematics/src/collection/lib/schema.d.ts @@ -1,5 +1,6 @@ export interface Schema { name: string; + directory?: string; sourceDir?: string; nomodule: boolean; diff --git a/packages/schematics/src/collection/lib/schema.json b/packages/schematics/src/collection/lib/schema.json index 75cef36f2eb70..851f89ef6bf3d 100644 --- a/packages/schematics/src/collection/lib/schema.json +++ b/packages/schematics/src/collection/lib/schema.json @@ -8,6 +8,10 @@ "type": "string", "description": "Library name" }, + "directory": { + "type": "string", + "description": "A directory where the app is placed" + }, "sourceDir": { "type": "string", "default": "src", diff --git a/packages/schematics/src/collection/utility/common.ts b/packages/schematics/src/collection/utility/common.ts index 037a1677b798c..801ec329938c5 100644 --- a/packages/schematics/src/collection/utility/common.ts +++ b/packages/schematics/src/collection/utility/common.ts @@ -1,6 +1,7 @@ import { Tree, Rule } from '@angular-devkit/schematics'; import { angularJsVersion } from './lib-versions'; import { serializeJson } from './fileutils'; +import { Schema } from '../app/schema'; export function addUpgradeToPackageJson(): Rule { return (host: Tree) => { @@ -23,3 +24,14 @@ export function addUpgradeToPackageJson(): Rule { return host; }; } + +export function offsetFromRoot(options: Schema): string { + let offset = '../../../'; + if (options.directory) { + const parts = options.directory.split('/').length; + for (let i = 0; i < parts; ++i) { + offset += '../'; + } + } + return offset; +} diff --git a/packages/schematics/src/collection/utility/lib-versions.ts b/packages/schematics/src/collection/utility/lib-versions.ts index 7307afe519158..df2c91c963097 100644 --- a/packages/schematics/src/collection/utility/lib-versions.ts +++ b/packages/schematics/src/collection/utility/lib-versions.ts @@ -4,7 +4,7 @@ export const angularJsVersion = '1.6.6'; export const ngrxVersion = '^4.1.0'; export const nxVersion = '*'; export const schematicsVersion = '*'; -export const latestMigration = '20171129-change-schema'; +export const latestMigration = '20171205-remove-npmscope-from-tslintjson'; export const prettierVersion = '1.8.2'; export const libVersions = { diff --git a/packages/schematics/src/collection/workspace/index.ts b/packages/schematics/src/collection/workspace/index.ts index 3ba9441d2eeba..0a1eac37ab9d4 100644 --- a/packages/schematics/src/collection/workspace/index.ts +++ b/packages/schematics/src/collection/workspace/index.ts @@ -13,7 +13,14 @@ import { } from '@angular-devkit/schematics'; import { Schema } from './schema'; import * as path from 'path'; -import { angularCliVersion, ngrxVersion, nxVersion, prettierVersion, schematicsVersion } from '../utility/lib-versions'; +import { + angularCliVersion, + latestMigration, + ngrxVersion, + nxVersion, + prettierVersion, + schematicsVersion +} from '../utility/lib-versions'; import * as fs from 'fs'; import { join } from 'path'; import { serializeJson, updateJsonFile } from '../utility/fileutils'; @@ -71,6 +78,7 @@ function updateAngularCLIJson(options: Schema) { } const angularCliJson = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8')); angularCliJson.project.npmScope = npmScope(options); + angularCliJson.project.latestMigration = latestMigration; if (angularCliJson.apps.length !== 1) { throw new Error('Can only convert projects with one app'); @@ -141,7 +149,7 @@ function updateTsLintJson(options: Schema) { ['no-trailing-whitespace', 'one-line', 'quotemark', 'typedef-whitespace', 'whitespace'].forEach(key => { json[key] = undefined; }); - json['nx-enforce-module-boundaries'] = [true, { npmScope: npmScope(options), lazyLoad: [], allow: [] }]; + json['nx-enforce-module-boundaries'] = [true, { lazyLoad: [], allow: [] }]; }); return host; }; diff --git a/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.spec.ts b/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.spec.ts index d013b02f66d26..b46347f34836e 100644 --- a/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.spec.ts +++ b/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.spec.ts @@ -6,7 +6,7 @@ import { Rule } from './nxEnforceModuleBoundariesRule'; describe('Enforce Module Boundaries', () => { it('should not error when everything is in order', () => { const failures = runRule( - { npmScope: 'mycompany', allow: ['@mycompany/mylib/deep'] }, + { allow: ['@mycompany/mylib/deep'] }, ` import '@mycompany/mylib'; import '@mycompany/mylib/deep'; @@ -25,14 +25,14 @@ describe('Enforce Module Boundaries', () => { }); it('should error about deep imports into libraries', () => { - const failures = runRule({ npmScope: 'mycompany' }, `import '@mycompany/mylib/blah';`); + const failures = runRule({}, `import '@mycompany/mylib/blah';`); expect(failures.length).toEqual(1); expect(failures[0].getFailure()).toEqual('deep imports into libraries are forbidden'); }); it('should error on importing a lazy-loaded library', () => { - const failures = runRule({ npmScope: 'mycompany', lazyLoad: ['mylib'] }, `import '@mycompany/mylib';`); + const failures = runRule({ lazyLoad: ['mylib'] }, `import '@mycompany/mylib';`); expect(failures.length).toEqual(1); expect(failures[0].getFailure()).toEqual('import of lazy-loaded libraries are forbidden'); @@ -47,6 +47,6 @@ function runRule(ruleArguments: any, content: string): RuleFailure[] { }; const sourceFile = ts.createSourceFile('proj/apps/myapp/src/main.ts', content, ts.ScriptTarget.Latest, true); - const rule = new Rule(options, 'proj'); + const rule = new Rule(options, 'proj', 'mycompany', ['mylib']); return rule.apply(sourceFile); } diff --git a/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts b/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts index 1efe32dec95ad..625fdf54aa5a5 100644 --- a/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts +++ b/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts @@ -2,40 +2,71 @@ import * as path from 'path'; import * as Lint from 'tslint'; import { IOptions } from 'tslint'; import * as ts from 'typescript'; +import { readFileSync } from 'fs'; export class Rule extends Lint.Rules.AbstractRule { - constructor(options: IOptions, private path?: string) { + constructor(options: IOptions, private path?: string, private npmScope?: string, private appNames?: string[]) { super(options); if (!path) { + const cliConfig = this.readCliConfig(); this.path = process.cwd(); + this.npmScope = cliConfig.project.npmScope; + this.appNames = cliConfig.apps.map(a => a.name); } } public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithWalker(new EnforceModuleBoundariesWalker(sourceFile, this.getOptions(), this.path)); + return this.applyWithWalker( + new EnforceModuleBoundariesWalker(sourceFile, this.getOptions(), this.path, this.npmScope, this.appNames) + ); + } + + private readCliConfig(): any { + return JSON.parse(readFileSync(`.angular-cli.json`, 'UTF-8')); } } class EnforceModuleBoundariesWalker extends Lint.RuleWalker { - constructor(sourceFile: ts.SourceFile, options: IOptions, private projectPath: string) { + constructor( + sourceFile: ts.SourceFile, + options: IOptions, + private projectPath: string, + private npmScope: string, + private appNames: string[] + ) { super(sourceFile, options); } public visitImportDeclaration(node: ts.ImportDeclaration) { - const npmScope = `@${this.getOptions()[0].npmScope}`; - const lazyLoad = this.getOptions()[0].lazyLoad; + const imp = node.moduleSpecifier.getText().substring(1, node.moduleSpecifier.getText().length - 1); const allow: string[] = Array.isArray(this.getOptions()[0].allow) ? this.getOptions()[0].allow.map(a => `${a}`) : []; - const imp = node.moduleSpecifier.getText().substring(1, node.moduleSpecifier.getText().length - 1); - const impParts = imp.split(path.sep); + const lazyLoad: string[] = Array.isArray(this.getOptions()[0].lazyLoad) + ? this.getOptions()[0].lazyLoad.map(a => `${a}`) + : []; - if (impParts[0] === npmScope && allow.indexOf(imp) === -1 && impParts.length > 2) { - this.addFailureAt(node.getStart(), node.getWidth(), 'deep imports into libraries are forbidden'); - } else if (impParts[0] === npmScope && impParts.length === 2 && lazyLoad && lazyLoad.indexOf(impParts[1]) > -1) { + // whitelisted import => return + if (allow.indexOf(imp) > -1) { + super.visitImportDeclaration(node); + return; + } + + const lazyLoaded = lazyLoad.filter(a => imp.startsWith(`@${this.npmScope}/${a}`))[0]; + if (lazyLoaded) { this.addFailureAt(node.getStart(), node.getWidth(), 'import of lazy-loaded libraries are forbidden'); - } else if (this.isRelative(imp) && this.isRelativeImportIntoAnotherProject(imp)) { + return; + } + + if (this.isRelative(imp) && this.isRelativeImportIntoAnotherProject(imp)) { this.addFailureAt(node.getStart(), node.getWidth(), 'relative imports of libraries are forbidden'); + return; + } + + const app = this.appNames.filter(a => imp.startsWith(`@${this.npmScope}/${a}`))[0]; + if (app && imp !== `@${this.npmScope}/${app}`) { + this.addFailureAt(node.getStart(), node.getWidth(), 'deep imports into libraries are forbidden'); + return; } super.visitImportDeclaration(node);