feat(angular): add directive generator (#15630)
This commit is contained in:
parent
636a74b62a
commit
481899ce9f
@ -3699,6 +3699,14 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "directive",
|
||||||
|
"path": "/packages/angular/generators/directive",
|
||||||
|
"name": "directive",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "init",
|
"id": "init",
|
||||||
"path": "/packages/angular/generators/init",
|
"path": "/packages/angular/generators/init",
|
||||||
|
|||||||
@ -189,6 +189,15 @@
|
|||||||
"path": "/packages/angular/generators/convert-tslint-to-eslint",
|
"path": "/packages/angular/generators/convert-tslint-to-eslint",
|
||||||
"type": "generator"
|
"type": "generator"
|
||||||
},
|
},
|
||||||
|
"/packages/angular/generators/directive": {
|
||||||
|
"description": "Generate an Angular directive.",
|
||||||
|
"file": "generated/packages/angular/generators/directive.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "directive",
|
||||||
|
"originalFilePath": "/packages/angular/src/generators/directive/schema.json",
|
||||||
|
"path": "/packages/angular/generators/directive",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
"/packages/angular/generators/init": {
|
"/packages/angular/generators/init": {
|
||||||
"description": "Initializes the `@nrwl/angular` plugin.",
|
"description": "Initializes the `@nrwl/angular` plugin.",
|
||||||
"file": "generated/packages/angular/generators/init.json",
|
"file": "generated/packages/angular/generators/init.json",
|
||||||
|
|||||||
@ -183,6 +183,15 @@
|
|||||||
"path": "angular/generators/convert-tslint-to-eslint",
|
"path": "angular/generators/convert-tslint-to-eslint",
|
||||||
"type": "generator"
|
"type": "generator"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Generate an Angular directive.",
|
||||||
|
"file": "generated/packages/angular/generators/directive.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "directive",
|
||||||
|
"originalFilePath": "/packages/angular/src/generators/directive/schema.json",
|
||||||
|
"path": "angular/generators/directive",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Initializes the `@nrwl/angular` plugin.",
|
"description": "Initializes the `@nrwl/angular` plugin.",
|
||||||
"file": "generated/packages/angular/generators/init.json",
|
"file": "generated/packages/angular/generators/init.json",
|
||||||
|
|||||||
90
docs/generated/packages/angular/generators/directive.json
Normal file
90
docs/generated/packages/angular/generators/directive.json
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"name": "directive",
|
||||||
|
"factory": "./src/generators/directive/directive",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"$id": "GeneratorAngularDirective",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Nx Angular Directive Options Schema",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Creates a new, generic directive definition in the given project.",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the new directive.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What name would you like to use for the directive?"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "path",
|
||||||
|
"$default": { "$source": "workingDirectory" },
|
||||||
|
"description": "The path at which to create the interface that defines the directive, relative to the workspace root.",
|
||||||
|
"visible": false
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"x-dropdown": "projects"
|
||||||
|
},
|
||||||
|
"prefix": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A prefix to apply to generated selectors.",
|
||||||
|
"alias": "p",
|
||||||
|
"oneOf": [
|
||||||
|
{ "maxLength": 0 },
|
||||||
|
{ "minLength": 1, "format": "html-selector" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"skipTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not create \"spec.ts\" test files for the new class.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipImport": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not import this directive into the owning NgModule.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "html-selector",
|
||||||
|
"description": "The HTML selector to use for this directive."
|
||||||
|
},
|
||||||
|
"standalone": {
|
||||||
|
"description": "Whether the generated directive is standalone.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"flat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "When true (the default), creates the new files at the top level of the current project.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"module": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The filename of the declaring NgModule.",
|
||||||
|
"alias": "m"
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "The declaring NgModule exports this directive."
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Skip formatting of files."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "project"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"aliases": ["d"],
|
||||||
|
"description": "Generate an Angular directive.",
|
||||||
|
"implementation": "/packages/angular/src/generators/directive/directive.ts",
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/angular/src/generators/directive/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
@ -341,7 +341,7 @@ describe('create-nx-workspace', () => {
|
|||||||
process.env.SELECTED_PM = packageManager;
|
process.env.SELECTED_PM = packageManager;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should store package manager preference for angular cli', () => {
|
it('should store package manager preference for angular', () => {
|
||||||
const wsName = uniq('pm');
|
const wsName = uniq('pm');
|
||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
|
|
||||||
|
|||||||
@ -195,6 +195,12 @@
|
|||||||
"schema": "./src/generators/convert-tslint-to-eslint/schema.json",
|
"schema": "./src/generators/convert-tslint-to-eslint/schema.json",
|
||||||
"description": "Converts a project from TSLint to ESLint."
|
"description": "Converts a project from TSLint to ESLint."
|
||||||
},
|
},
|
||||||
|
"directive": {
|
||||||
|
"factory": "./src/generators/directive/directive",
|
||||||
|
"schema": "./src/generators/directive/schema.json",
|
||||||
|
"aliases": ["d"],
|
||||||
|
"description": "Generate an Angular directive."
|
||||||
|
},
|
||||||
"init": {
|
"init": {
|
||||||
"factory": "./src/generators/init/init",
|
"factory": "./src/generators/init/init",
|
||||||
"schema": "./src/generators/init/schema.json",
|
"schema": "./src/generators/init/schema.json",
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export * from './src/generators/component-cypress-spec/component-cypress-spec';
|
|||||||
export * from './src/generators/component-story/component-story';
|
export * from './src/generators/component-story/component-story';
|
||||||
export * from './src/generators/component/component';
|
export * from './src/generators/component/component';
|
||||||
export * from './src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint';
|
export * from './src/generators/convert-tslint-to-eslint/convert-tslint-to-eslint';
|
||||||
|
export * from './src/generators/directive/directive';
|
||||||
export * from './src/generators/host/host';
|
export * from './src/generators/host/host';
|
||||||
export * from './src/generators/init/init';
|
export * from './src/generators/init/init';
|
||||||
export * from './src/generators/library-secondary-entry-point/library-secondary-entry-point';
|
export * from './src/generators/library-secondary-entry-point/library-secondary-entry-point';
|
||||||
|
|||||||
@ -1,17 +1,11 @@
|
|||||||
import type { Tree } from '@nrwl/devkit';
|
import type { Tree } from '@nrwl/devkit';
|
||||||
import {
|
import { formatFiles, stripIndents } from '@nrwl/devkit';
|
||||||
formatFiles,
|
|
||||||
normalizePath,
|
|
||||||
readNxJson,
|
|
||||||
readProjectConfiguration,
|
|
||||||
stripIndents,
|
|
||||||
} from '@nrwl/devkit';
|
|
||||||
import { pathStartsWith } from '../utils/path';
|
|
||||||
import { exportComponentInEntryPoint } from './lib/component';
|
import { exportComponentInEntryPoint } from './lib/component';
|
||||||
import { normalizeOptions } from './lib/normalize-options';
|
import { normalizeOptions } from './lib/normalize-options';
|
||||||
import type { NormalizedSchema, Schema } from './schema';
|
import type { Schema } from './schema';
|
||||||
import { getInstalledAngularVersionInfo } from '../utils/version-utils';
|
import { getInstalledAngularVersionInfo } from '../utils/version-utils';
|
||||||
import { lt } from 'semver';
|
import { lt } from 'semver';
|
||||||
|
import { checkPathUnderProjectRoot } from '../utils/path';
|
||||||
|
|
||||||
export async function componentGenerator(tree: Tree, rawOptions: Schema) {
|
export async function componentGenerator(tree: Tree, rawOptions: Schema) {
|
||||||
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
|
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
|
||||||
@ -27,7 +21,7 @@ export async function componentGenerator(tree: Tree, rawOptions: Schema) {
|
|||||||
const options = await normalizeOptions(tree, rawOptions);
|
const options = await normalizeOptions(tree, rawOptions);
|
||||||
const { projectSourceRoot, ...schematicOptions } = options;
|
const { projectSourceRoot, ...schematicOptions } = options;
|
||||||
|
|
||||||
checkPathUnderProjectRoot(tree, options);
|
checkPathUnderProjectRoot(tree, options.project, options.path);
|
||||||
|
|
||||||
const { wrapAngularDevkitSchematic } = require('@nrwl/devkit/ngcli-adapter');
|
const { wrapAngularDevkitSchematic } = require('@nrwl/devkit/ngcli-adapter');
|
||||||
const angularComponentSchematic = wrapAngularDevkitSchematic(
|
const angularComponentSchematic = wrapAngularDevkitSchematic(
|
||||||
@ -41,25 +35,4 @@ export async function componentGenerator(tree: Tree, rawOptions: Schema) {
|
|||||||
await formatFiles(tree);
|
await formatFiles(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPathUnderProjectRoot(tree: Tree, schema: NormalizedSchema): void {
|
|
||||||
if (!schema.path) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const project = schema.project ?? readNxJson(tree).defaultProject;
|
|
||||||
const { root } = readProjectConfiguration(tree, project);
|
|
||||||
|
|
||||||
let pathToComponent = normalizePath(schema.path);
|
|
||||||
pathToComponent = pathToComponent.startsWith('/')
|
|
||||||
? pathToComponent.slice(1)
|
|
||||||
: pathToComponent;
|
|
||||||
|
|
||||||
if (!pathStartsWith(pathToComponent, root)) {
|
|
||||||
throw new Error(
|
|
||||||
`The path provided for the component (${schema.path}) does not exist under the project root (${root}). ` +
|
|
||||||
`Please make sure to provide a path that exists under the project root.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default componentGenerator;
|
export default componentGenerator;
|
||||||
|
|||||||
@ -0,0 +1,247 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`directive generator should export the directive correctly when flat=false and path is nested deeper 1`] = `
|
||||||
|
"import { Directive } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[projTest]'
|
||||||
|
})
|
||||||
|
export class TestDirective {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should export the directive correctly when flat=false and path is nested deeper 2`] = `
|
||||||
|
"import { TestDirective } from './test.directive';
|
||||||
|
|
||||||
|
describe('TestDirective', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
const directive = new TestDirective();
|
||||||
|
expect(directive).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should export the directive correctly when flat=false and path is nested deeper 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestDirective } from './my-directives/test/test.directive';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestDirective],
|
||||||
|
exports: [, TestDirective]
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should generate a directive with test files and attach to the NgModule automatically 1`] = `
|
||||||
|
"import { Directive } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[projTest]'
|
||||||
|
})
|
||||||
|
export class TestDirective {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should generate a directive with test files and attach to the NgModule automatically 2`] = `
|
||||||
|
"import { TestDirective } from './test.directive';
|
||||||
|
|
||||||
|
describe('TestDirective', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
const directive = new TestDirective();
|
||||||
|
expect(directive).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should generate a directive with test files and attach to the NgModule automatically 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestDirective } from './test.directive';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestDirective],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should import the directive correctly when flat=false 1`] = `
|
||||||
|
"import { Directive } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[projTest]'
|
||||||
|
})
|
||||||
|
export class TestDirective {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should import the directive correctly when flat=false 2`] = `
|
||||||
|
"import { TestDirective } from './test.directive';
|
||||||
|
|
||||||
|
describe('TestDirective', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
const directive = new TestDirective();
|
||||||
|
expect(directive).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should import the directive correctly when flat=false 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestDirective } from './test/test.directive';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestDirective],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should import the directive correctly when flat=false and path is nested deeper 1`] = `
|
||||||
|
"import { Directive } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[projTest]'
|
||||||
|
})
|
||||||
|
export class TestDirective {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should import the directive correctly when flat=false and path is nested deeper 2`] = `
|
||||||
|
"import { TestDirective } from './test.directive';
|
||||||
|
|
||||||
|
describe('TestDirective', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
const directive = new TestDirective();
|
||||||
|
expect(directive).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should import the directive correctly when flat=false and path is nested deeper 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestDirective } from './my-directives/test/test.directive';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestDirective],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should not generate test file when skipTests=true 1`] = `
|
||||||
|
"import { Directive } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[projTest]'
|
||||||
|
})
|
||||||
|
export class TestDirective {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should not generate test file when skipTests=true 2`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestDirective } from './my-directives/test/test.directive';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestDirective],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should not import the directive when skipImport=true 1`] = `
|
||||||
|
"import { Directive } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[projTest]'
|
||||||
|
})
|
||||||
|
export class TestDirective {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should not import the directive when skipImport=true 2`] = `
|
||||||
|
"import { TestDirective } from './test.directive';
|
||||||
|
|
||||||
|
describe('TestDirective', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
const directive = new TestDirective();
|
||||||
|
expect(directive).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should not import the directive when skipImport=true 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should not import the directive when standalone=true 1`] = `
|
||||||
|
"import { Directive } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[projTest]',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class TestDirective {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should not import the directive when standalone=true 2`] = `
|
||||||
|
"import { TestDirective } from './test.directive';
|
||||||
|
|
||||||
|
describe('TestDirective', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
const directive = new TestDirective();
|
||||||
|
expect(directive).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`directive generator should not import the directive when standalone=true 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
163
packages/angular/src/generators/directive/directive.spec.ts
Normal file
163
packages/angular/src/generators/directive/directive.spec.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { addProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||||
|
import { directiveGenerator } from './directive';
|
||||||
|
import type { Schema } from './schema';
|
||||||
|
|
||||||
|
describe('directive generator', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
addProjectConfiguration(tree, 'test', {
|
||||||
|
root: 'test',
|
||||||
|
sourceRoot: 'test/src',
|
||||||
|
});
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
'test/src/test.module.ts',
|
||||||
|
`import {NgModule} from "@angular/core";
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a directive with test files and attach to the NgModule automatically', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateDirectiveWithDefaultOptions(tree);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('test/src/test.directive.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/test.directive.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import the directive correctly when flat=false', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateDirectiveWithDefaultOptions(tree, { flat: false });
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/test/test.directive.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/test/test.directive.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not import the directive when standalone=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateDirectiveWithDefaultOptions(tree, { standalone: true });
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('test/src/test.directive.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/test.directive.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import the directive correctly when flat=false and path is nested deeper', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateDirectiveWithDefaultOptions(tree, {
|
||||||
|
flat: false,
|
||||||
|
path: 'test/src/my-directives',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export the directive correctly when flat=false and path is nested deeper', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateDirectiveWithDefaultOptions(tree, {
|
||||||
|
flat: false,
|
||||||
|
path: 'test/src/my-directives',
|
||||||
|
export: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not import the directive when skipImport=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateDirectiveWithDefaultOptions(tree, {
|
||||||
|
flat: false,
|
||||||
|
path: 'test/src/my-directives',
|
||||||
|
skipImport: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not generate test file when skipTests=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateDirectiveWithDefaultOptions(tree, {
|
||||||
|
flat: false,
|
||||||
|
path: 'test/src/my-directives',
|
||||||
|
skipTests: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.exists('test/src/my-directives/test/test.directive.spec.ts')
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function generateDirectiveWithDefaultOptions(
|
||||||
|
tree: Tree,
|
||||||
|
overrides: Partial<Schema> = {}
|
||||||
|
) {
|
||||||
|
await directiveGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
project: 'test',
|
||||||
|
flat: true,
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
}
|
||||||
160
packages/angular/src/generators/directive/directive.ts
Normal file
160
packages/angular/src/generators/directive/directive.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||||
|
import {
|
||||||
|
formatFiles,
|
||||||
|
generateFiles,
|
||||||
|
getProjects,
|
||||||
|
joinPathFragments,
|
||||||
|
names,
|
||||||
|
readNxJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import type { Schema } from './schema';
|
||||||
|
import { checkPathUnderProjectRoot } from '../utils/path';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import { insertNgModuleProperty } from '../utils';
|
||||||
|
import { insertImport } from '@nrwl/js';
|
||||||
|
import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescript';
|
||||||
|
|
||||||
|
let tsModule: typeof import('typescript');
|
||||||
|
|
||||||
|
export async function directiveGenerator(tree: Tree, schema: Schema) {
|
||||||
|
const projects = getProjects(tree);
|
||||||
|
if (!projects.has(schema.project)) {
|
||||||
|
throw new Error(`Project "${schema.project}" does not exist!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPathUnderProjectRoot(tree, schema.project, schema.path);
|
||||||
|
|
||||||
|
const project = readProjectConfiguration(
|
||||||
|
tree,
|
||||||
|
schema.project
|
||||||
|
) as ProjectConfiguration & { prefix?: string };
|
||||||
|
|
||||||
|
const path = schema.path ?? `${project.sourceRoot}`;
|
||||||
|
const directiveNames = names(schema.name);
|
||||||
|
const selector =
|
||||||
|
schema.selector ??
|
||||||
|
buildSelector(tree, schema.name, schema.prefix ?? project.prefix);
|
||||||
|
|
||||||
|
const pathToGenerateFiles = schema.flat
|
||||||
|
? './files/__directiveFileName__'
|
||||||
|
: './files';
|
||||||
|
await generateFiles(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(__dirname, pathToGenerateFiles),
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
selector,
|
||||||
|
directiveClassName: directiveNames.className,
|
||||||
|
directiveFileName: directiveNames.fileName,
|
||||||
|
standalone: schema.standalone,
|
||||||
|
tpl: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (schema.skipTests) {
|
||||||
|
const pathToSpecFile = joinPathFragments(
|
||||||
|
path,
|
||||||
|
`${!schema.flat ? `${directiveNames.fileName}/` : ``}${
|
||||||
|
directiveNames.fileName
|
||||||
|
}.directive.spec.ts`
|
||||||
|
);
|
||||||
|
|
||||||
|
tree.delete(pathToSpecFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schema.skipImport && !schema.standalone) {
|
||||||
|
const modulePath = findModule(tree, path, schema.module);
|
||||||
|
addImportToNgModule(path, modulePath, schema, directiveNames, tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schema.skipFormat) {
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSelector(tree: Tree, name: string, prefix: string) {
|
||||||
|
let selector = names(name).fileName;
|
||||||
|
const selectorPrefix = names(prefix ?? readNxJson(tree).npmScope).fileName;
|
||||||
|
|
||||||
|
return names(`${selectorPrefix}-${selector}`).propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findModule(tree: Tree, path: string, module?: string) {
|
||||||
|
let modulePath = '';
|
||||||
|
let pathToSearch = path;
|
||||||
|
while (pathToSearch !== '/') {
|
||||||
|
if (module) {
|
||||||
|
const pathToModule = joinPathFragments(pathToSearch, module);
|
||||||
|
if (tree.exists(pathToModule)) {
|
||||||
|
modulePath = pathToModule;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const potentialOptions = tree
|
||||||
|
.children(pathToSearch)
|
||||||
|
.filter((f) => f.endsWith('.module.ts'));
|
||||||
|
if (potentialOptions.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
`More than one NgModule was found. Please provide the NgModule you wish to use.`
|
||||||
|
);
|
||||||
|
} else if (potentialOptions.length === 1) {
|
||||||
|
modulePath = joinPathFragments(pathToSearch, potentialOptions[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pathToSearch = dirname(pathToSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleContents = tree.read(modulePath, 'utf-8');
|
||||||
|
if (!moduleContents.includes('@NgModule')) {
|
||||||
|
throw new Error(
|
||||||
|
`Declaring module file (${modulePath}) does not contain an @NgModule Declaration.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modulePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addImportToNgModule(
|
||||||
|
path: string,
|
||||||
|
modulePath: string,
|
||||||
|
schema: Schema,
|
||||||
|
directiveNames: {
|
||||||
|
name: string;
|
||||||
|
className: string;
|
||||||
|
propertyName: string;
|
||||||
|
constantName: string;
|
||||||
|
fileName: string;
|
||||||
|
},
|
||||||
|
tree: Tree
|
||||||
|
) {
|
||||||
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
let relativePath = `${joinPathFragments(
|
||||||
|
path.replace(dirname(modulePath), ''),
|
||||||
|
!schema.flat ? directiveNames.fileName : '',
|
||||||
|
`${directiveNames.fileName}.directive`
|
||||||
|
)}`;
|
||||||
|
relativePath = relativePath.startsWith('/')
|
||||||
|
? `.${relativePath}`
|
||||||
|
: `./${relativePath}`;
|
||||||
|
const directiveClassName = `${directiveNames.className}Directive`;
|
||||||
|
|
||||||
|
const moduleContents = tree.read(modulePath, 'utf-8');
|
||||||
|
const source = tsModule.createSourceFile(
|
||||||
|
modulePath,
|
||||||
|
moduleContents,
|
||||||
|
tsModule.ScriptTarget.Latest,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
insertImport(tree, source, modulePath, directiveClassName, relativePath);
|
||||||
|
insertNgModuleProperty(tree, modulePath, directiveClassName, 'declarations');
|
||||||
|
if (schema.export) {
|
||||||
|
insertNgModuleProperty(tree, modulePath, directiveClassName, 'exports');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default directiveGenerator;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { <%= directiveClassName %>Directive } from './<%= directiveFileName %>.directive';
|
||||||
|
|
||||||
|
describe('<%= directiveClassName %>Directive', () => {
|
||||||
|
it('should create an instance', () => {
|
||||||
|
const directive = new <%= directiveClassName %>Directive();
|
||||||
|
expect(directive).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { Directive } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[<%= selector %>]'<% if(standalone) {%>,
|
||||||
|
standalone: true<%}%>
|
||||||
|
})
|
||||||
|
export class <%= directiveClassName %>Directive {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
}
|
||||||
14
packages/angular/src/generators/directive/schema.d.ts
vendored
Normal file
14
packages/angular/src/generators/directive/schema.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export interface Schema {
|
||||||
|
name: string;
|
||||||
|
project: string;
|
||||||
|
path?: string;
|
||||||
|
prefix?: string;
|
||||||
|
skipTests?: boolean;
|
||||||
|
skipImport?: boolean;
|
||||||
|
selector?: string;
|
||||||
|
standalone?: boolean;
|
||||||
|
flat?: boolean;
|
||||||
|
module?: string;
|
||||||
|
export?: boolean;
|
||||||
|
skipFormat?: boolean;
|
||||||
|
}
|
||||||
89
packages/angular/src/generators/directive/schema.json
Normal file
89
packages/angular/src/generators/directive/schema.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"$id": "GeneratorAngularDirective",
|
||||||
|
"cli": "nx",
|
||||||
|
"title": "Nx Angular Directive Options Schema",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Creates a new, generic directive definition in the given project.",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the new directive.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What name would you like to use for the directive?"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "path",
|
||||||
|
"$default": {
|
||||||
|
"$source": "workingDirectory"
|
||||||
|
},
|
||||||
|
"description": "The path at which to create the interface that defines the directive, relative to the workspace root.",
|
||||||
|
"visible": false
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"x-dropdown": "projects"
|
||||||
|
},
|
||||||
|
"prefix": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A prefix to apply to generated selectors.",
|
||||||
|
"alias": "p",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"maxLength": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minLength": 1,
|
||||||
|
"format": "html-selector"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"skipTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not create \"spec.ts\" test files for the new class.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipImport": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not import this directive into the owning NgModule.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "html-selector",
|
||||||
|
"description": "The HTML selector to use for this directive."
|
||||||
|
},
|
||||||
|
"standalone": {
|
||||||
|
"description": "Whether the generated directive is standalone.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"flat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "When true (the default), creates the new files at the top level of the current project.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"module": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The filename of the declaring NgModule.",
|
||||||
|
"alias": "m"
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "The declaring NgModule exports this directive."
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Skip formatting of files."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "project"]
|
||||||
|
}
|
||||||
@ -1,25 +1,17 @@
|
|||||||
import { applyChangesToString, ChangeType, Tree } from '@nrwl/devkit';
|
import { applyChangesToString, ChangeType, Tree } from '@nrwl/devkit';
|
||||||
import {
|
import type {
|
||||||
__String,
|
__String,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
ClassDeclaration,
|
ClassDeclaration,
|
||||||
createSourceFile,
|
|
||||||
Decorator,
|
Decorator,
|
||||||
getDecorators,
|
|
||||||
ImportDeclaration,
|
ImportDeclaration,
|
||||||
isArrayLiteralExpression,
|
|
||||||
isCallExpression,
|
|
||||||
isClassDeclaration,
|
|
||||||
isIdentifier,
|
|
||||||
isImportDeclaration,
|
|
||||||
isNamedImports,
|
|
||||||
isObjectLiteralExpression,
|
|
||||||
isPropertyAssignment,
|
|
||||||
ObjectLiteralExpression,
|
ObjectLiteralExpression,
|
||||||
PropertyAssignment,
|
PropertyAssignment,
|
||||||
ScriptTarget,
|
|
||||||
SourceFile,
|
SourceFile,
|
||||||
} from 'typescript';
|
} from 'typescript';
|
||||||
|
import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescript';
|
||||||
|
|
||||||
|
let tsModule: typeof import('typescript');
|
||||||
|
|
||||||
type ngModuleDecoratorProperty =
|
type ngModuleDecoratorProperty =
|
||||||
| 'imports'
|
| 'imports'
|
||||||
@ -33,12 +25,15 @@ export function insertNgModuleProperty(
|
|||||||
name: string,
|
name: string,
|
||||||
property: ngModuleDecoratorProperty
|
property: ngModuleDecoratorProperty
|
||||||
) {
|
) {
|
||||||
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
const contents = tree.read(modulePath).toString('utf-8');
|
const contents = tree.read(modulePath).toString('utf-8');
|
||||||
|
|
||||||
const sourceFile = createSourceFile(
|
const sourceFile = tsModule.createSourceFile(
|
||||||
modulePath,
|
modulePath,
|
||||||
contents,
|
contents,
|
||||||
ScriptTarget.ESNext
|
tsModule.ScriptTarget.ESNext
|
||||||
);
|
);
|
||||||
|
|
||||||
const coreImport = findImport(sourceFile, '@angular/core');
|
const coreImport = findImport(sourceFile, '@angular/core');
|
||||||
@ -63,10 +58,12 @@ export function insertNgModuleProperty(
|
|||||||
let ngModuleDecorator: Decorator;
|
let ngModuleDecorator: Decorator;
|
||||||
try {
|
try {
|
||||||
ngModuleClassDeclaration = findDecoratedClass(sourceFile, ngModuleName);
|
ngModuleClassDeclaration = findDecoratedClass(sourceFile, ngModuleName);
|
||||||
ngModuleDecorator = getDecorators(ngModuleClassDeclaration).find(
|
ngModuleDecorator = tsModule
|
||||||
|
.getDecorators(ngModuleClassDeclaration)
|
||||||
|
.find(
|
||||||
(decorator) =>
|
(decorator) =>
|
||||||
isCallExpression(decorator.expression) &&
|
tsModule.isCallExpression(decorator.expression) &&
|
||||||
isIdentifier(decorator.expression.expression) &&
|
tsModule.isIdentifier(decorator.expression.expression) &&
|
||||||
decorator.expression.expression.escapedText === ngModuleName
|
decorator.expression.expression.escapedText === ngModuleName
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
@ -78,8 +75,8 @@ export function insertNgModuleProperty(
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
ngModuleDecorator = ngModuleClassDeclaration.decorators.find(
|
ngModuleDecorator = ngModuleClassDeclaration.decorators.find(
|
||||||
(decorator) =>
|
(decorator) =>
|
||||||
isCallExpression(decorator.expression) &&
|
tsModule.isCallExpression(decorator.expression) &&
|
||||||
isIdentifier(decorator.expression.expression) &&
|
tsModule.isIdentifier(decorator.expression.expression) &&
|
||||||
decorator.expression.expression.escapedText === ngModuleName
|
decorator.expression.expression.escapedText === ngModuleName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -96,7 +93,7 @@ export function insertNgModuleProperty(
|
|||||||
]);
|
]);
|
||||||
tree.write(modulePath, newContents);
|
tree.write(modulePath, newContents);
|
||||||
} else {
|
} else {
|
||||||
if (!isObjectLiteralExpression(ngModuleCall.arguments[0])) {
|
if (!tsModule.isObjectLiteralExpression(ngModuleCall.arguments[0])) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The NgModule options for ${ngModuleClassDeclaration.name.escapedText} in ${modulePath} is not an object literal`
|
`The NgModule options for ${ngModuleClassDeclaration.name.escapedText} in ${modulePath} is not an object literal`
|
||||||
);
|
);
|
||||||
@ -123,7 +120,7 @@ export function insertNgModuleProperty(
|
|||||||
]);
|
]);
|
||||||
tree.write(modulePath, newContents);
|
tree.write(modulePath, newContents);
|
||||||
} else {
|
} else {
|
||||||
if (!isArrayLiteralExpression(typeProperty.initializer)) {
|
if (!tsModule.isArrayLiteralExpression(typeProperty.initializer)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The NgModule ${property} for ${ngModuleClassDeclaration.name.escapedText} in ${modulePath} is not an array literal`
|
`The NgModule ${property} for ${ngModuleClassDeclaration.name.escapedText} in ${modulePath} is not an array literal`
|
||||||
);
|
);
|
||||||
@ -156,7 +153,13 @@ export function insertNgModuleImport(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findImport(sourceFile: SourceFile, importPath: string) {
|
function findImport(sourceFile: SourceFile, importPath: string) {
|
||||||
const importStatements = sourceFile.statements.filter(isImportDeclaration);
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
|
||||||
|
const importStatements = sourceFile.statements.filter(
|
||||||
|
tsModule.isImportDeclaration
|
||||||
|
);
|
||||||
|
|
||||||
return importStatements.find(
|
return importStatements.find(
|
||||||
(statement) =>
|
(statement) =>
|
||||||
@ -168,7 +171,11 @@ function findImport(sourceFile: SourceFile, importPath: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getNamedImport(coreImport: ImportDeclaration, importName: string) {
|
function getNamedImport(coreImport: ImportDeclaration, importName: string) {
|
||||||
if (!isNamedImports(coreImport.importClause.namedBindings)) {
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tsModule.isNamedImports(coreImport.importClause.namedBindings)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The import from ${coreImport.moduleSpecifier} does not have named imports.`
|
`The import from ${coreImport.moduleSpecifier} does not have named imports.`
|
||||||
);
|
);
|
||||||
@ -176,9 +183,9 @@ function getNamedImport(coreImport: ImportDeclaration, importName: string) {
|
|||||||
|
|
||||||
return coreImport.importClause.namedBindings.elements.find((namedImport) =>
|
return coreImport.importClause.namedBindings.elements.find((namedImport) =>
|
||||||
namedImport.propertyName
|
namedImport.propertyName
|
||||||
? isIdentifier(namedImport.propertyName) &&
|
? tsModule.isIdentifier(namedImport.propertyName) &&
|
||||||
namedImport.propertyName.escapedText === importName
|
namedImport.propertyName.escapedText === importName
|
||||||
: isIdentifier(namedImport.name) &&
|
: tsModule.isIdentifier(namedImport.name) &&
|
||||||
namedImport.name.escapedText === importName
|
namedImport.name.escapedText === importName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -187,14 +194,20 @@ function findDecoratedClass(
|
|||||||
sourceFile: SourceFile,
|
sourceFile: SourceFile,
|
||||||
ngModuleName: __String
|
ngModuleName: __String
|
||||||
): ClassDeclaration | undefined {
|
): ClassDeclaration | undefined {
|
||||||
const classDeclarations = sourceFile.statements.filter(isClassDeclaration);
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
|
||||||
|
const classDeclarations = sourceFile.statements.filter(
|
||||||
|
tsModule.isClassDeclaration
|
||||||
|
);
|
||||||
return classDeclarations.find((declaration) => {
|
return classDeclarations.find((declaration) => {
|
||||||
const decorators = getDecorators(declaration);
|
const decorators = tsModule.getDecorators(declaration);
|
||||||
if (decorators) {
|
if (decorators) {
|
||||||
return decorators.some(
|
return decorators.some(
|
||||||
(decorator) =>
|
(decorator) =>
|
||||||
isCallExpression(decorator.expression) &&
|
tsModule.isCallExpression(decorator.expression) &&
|
||||||
isIdentifier(decorator.expression.expression) &&
|
tsModule.isIdentifier(decorator.expression.expression) &&
|
||||||
decorator.expression.expression.escapedText === ngModuleName
|
decorator.expression.expression.escapedText === ngModuleName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -206,14 +219,20 @@ function findDecoratedClassLegacy(
|
|||||||
sourceFile: SourceFile,
|
sourceFile: SourceFile,
|
||||||
ngModuleName: __String
|
ngModuleName: __String
|
||||||
) {
|
) {
|
||||||
const classDeclarations = sourceFile.statements.filter(isClassDeclaration);
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
|
||||||
|
const classDeclarations = sourceFile.statements.filter(
|
||||||
|
tsModule.isClassDeclaration
|
||||||
|
);
|
||||||
return classDeclarations.find(
|
return classDeclarations.find(
|
||||||
(declaration) =>
|
(declaration) =>
|
||||||
declaration.decorators &&
|
declaration.decorators &&
|
||||||
(declaration.decorators as any[]).some(
|
(declaration.decorators as any[]).some(
|
||||||
(decorator) =>
|
(decorator) =>
|
||||||
isCallExpression(decorator.expression) &&
|
tsModule.isCallExpression(decorator.expression) &&
|
||||||
isIdentifier(decorator.expression.expression) &&
|
tsModule.isIdentifier(decorator.expression.expression) &&
|
||||||
decorator.expression.expression.escapedText === ngModuleName
|
decorator.expression.expression.escapedText === ngModuleName
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -223,10 +242,14 @@ function findPropertyAssignment(
|
|||||||
ngModuleOptions: ObjectLiteralExpression,
|
ngModuleOptions: ObjectLiteralExpression,
|
||||||
propertyName: ngModuleDecoratorProperty
|
propertyName: ngModuleDecoratorProperty
|
||||||
) {
|
) {
|
||||||
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
|
||||||
return ngModuleOptions.properties.find(
|
return ngModuleOptions.properties.find(
|
||||||
(property) =>
|
(property) =>
|
||||||
isPropertyAssignment(property) &&
|
tsModule.isPropertyAssignment(property) &&
|
||||||
isIdentifier(property.name) &&
|
tsModule.isIdentifier(property.name) &&
|
||||||
property.name.escapedText === propertyName
|
property.name.escapedText === propertyName
|
||||||
) as PropertyAssignment;
|
) as PropertyAssignment;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { joinPathFragments, workspaceRoot } from '@nrwl/devkit';
|
import {
|
||||||
|
joinPathFragments,
|
||||||
|
normalizePath,
|
||||||
|
readNxJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
workspaceRoot,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
import { basename, dirname, relative } from 'path';
|
import { basename, dirname, relative } from 'path';
|
||||||
|
|
||||||
export function pathStartsWith(path1: string, path2: string): boolean {
|
export function pathStartsWith(path1: string, path2: string): boolean {
|
||||||
@ -22,3 +29,28 @@ export function getRelativeImportToFile(
|
|||||||
basename(targetFilePath, '.ts')
|
basename(targetFilePath, '.ts')
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkPathUnderProjectRoot(
|
||||||
|
tree: Tree,
|
||||||
|
projectName: string,
|
||||||
|
path: string
|
||||||
|
): void {
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const project = projectName ?? readNxJson(tree).defaultProject;
|
||||||
|
const { root } = readProjectConfiguration(tree, project);
|
||||||
|
|
||||||
|
let pathToComponent = normalizePath(path);
|
||||||
|
pathToComponent = pathToComponent.startsWith('/')
|
||||||
|
? pathToComponent.slice(1)
|
||||||
|
: pathToComponent;
|
||||||
|
|
||||||
|
if (!pathStartsWith(pathToComponent, root)) {
|
||||||
|
throw new Error(
|
||||||
|
`The path provided (${path}) does not exist under the project root (${root}). ` +
|
||||||
|
`Please make sure to provide a path that exists under the project root.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user