Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(angular): fix path and selector handling in directive generator #16017

Merged
merged 1 commit into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 40 additions & 26 deletions packages/angular/src/generators/directive/directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ describe('directive generator', () => {
addProjectConfiguration(tree, 'test', {
root: 'test',
sourceRoot: 'test/src',
projectType: 'application',
});

tree.write(
'test/src/test.module.ts',
'test/src/app/test.module.ts',
`import {NgModule} from "@angular/core";
@NgModule({
imports: [],
Expand All @@ -33,11 +34,13 @@ describe('directive generator', () => {
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')
tree.read('test/src/app/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(
tree.read('test/src/app/test.directive.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});

it('should import the directive correctly when flat=false', async () => {
Expand All @@ -48,12 +51,12 @@ describe('directive generator', () => {

// ASSERT
expect(
tree.read('test/src/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/test/test.directive.spec.ts', 'utf-8')
tree.read('test/src/app/test/test.directive.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});

it('should not import the directive when standalone=true', async () => {
Expand All @@ -63,11 +66,13 @@ describe('directive generator', () => {
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')
tree.read('test/src/app/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/app/test.directive.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});

it('should import the directive correctly when flat=false and path is nested deeper', async () => {
Expand All @@ -76,17 +81,20 @@ describe('directive generator', () => {
// ACT
await generateDirectiveWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-directives',
path: 'test/src/app/my-directives',
});

// ASSERT
expect(
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/my-directives/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
tree.read(
'test/src/app/my-directives/test/test.directive.spec.ts',
'utf-8'
)
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});

it('should export the directive correctly when flat=false and path is nested deeper', async () => {
Expand All @@ -95,18 +103,21 @@ describe('directive generator', () => {
// ACT
await generateDirectiveWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-directives',
path: 'test/src/app/my-directives',
export: true,
});

// ASSERT
expect(
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/my-directives/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
tree.read(
'test/src/app/my-directives/test/test.directive.spec.ts',
'utf-8'
)
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});

it('should not import the directive when skipImport=true', async () => {
Expand All @@ -115,18 +126,21 @@ describe('directive generator', () => {
// ACT
await generateDirectiveWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-directives',
path: 'test/src/app/my-directives',
skipImport: true,
});

// ASSERT
expect(
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/my-directives/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
tree.read(
'test/src/app/my-directives/test/test.directive.spec.ts',
'utf-8'
)
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});

it('should not generate test file when skipTests=true', async () => {
Expand All @@ -135,18 +149,18 @@ describe('directive generator', () => {
// ACT
await generateDirectiveWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-directives',
path: 'test/src/app/my-directives',
skipTests: true,
});

// ASSERT
expect(
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/my-directives/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.exists('test/src/my-directives/test/test.directive.spec.ts')
tree.exists('test/src/app/my-directives/test/test.directive.spec.ts')
).toBeFalsy();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});
});

Expand Down
65 changes: 20 additions & 45 deletions packages/angular/src/generators/directive/directive.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,65 @@
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
import type { 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 { addToNgModule, findModule } from '../utils';

let tsModule: typeof import('typescript');
import { normalizeOptions, validateOptions } from './lib';
import type { Schema } from './schema';

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);
validateOptions(tree, schema);
const options = normalizeOptions(tree, schema);

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 directiveNames = names(options.name);

const pathToGenerateFiles = schema.flat
const pathToGenerateFiles = options.flat
? './files/__directiveFileName__'
: './files';
await generateFiles(
generateFiles(
tree,
joinPathFragments(__dirname, pathToGenerateFiles),
path,
options.path,
{
selector,
selector: options.selector,
directiveClassName: directiveNames.className,
directiveFileName: directiveNames.fileName,
standalone: schema.standalone,
standalone: options.standalone,
tpl: '',
}
);

if (schema.skipTests) {
if (options.skipTests) {
const pathToSpecFile = joinPathFragments(
path,
`${!schema.flat ? `${directiveNames.fileName}/` : ``}${
options.path,
`${!options.flat ? `${directiveNames.fileName}/` : ``}${
directiveNames.fileName
}.directive.spec.ts`
);

tree.delete(pathToSpecFile);
}

if (!schema.skipImport && !schema.standalone) {
const modulePath = findModule(tree, path, schema.module);
if (!options.skipImport && !options.standalone) {
const modulePath = findModule(tree, options.path, options.module);
addToNgModule(
tree,
path,
options.path,
modulePath,
directiveNames.fileName,
`${directiveNames.className}Directive`,
`${directiveNames.fileName}.directive`,
'declarations',
schema.flat,
schema.export
options.flat,
options.export
);
}

if (!schema.skipFormat) {
if (!options.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;
}

export default directiveGenerator;
2 changes: 2 additions & 0 deletions packages/angular/src/generators/directive/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './normalize-options';
export * from './validate-options';
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
import {
joinPathFragments,
names,
readNxJson,
readProjectConfiguration,
} from '@nrwl/devkit';
import { parseName } from '../../utils/names';
import type { Schema } from '../schema';

export function normalizeOptions(tree: Tree, options: Schema) {
const { prefix, projectType, root, sourceRoot } = readProjectConfiguration(
tree,
options.project
) as ProjectConfiguration & { prefix?: string };

const projectSourceRoot = sourceRoot ?? joinPathFragments(root, 'src');
const { name, path: namePath } = parseName(options.name);

const path =
options.path ??
joinPathFragments(
projectSourceRoot,
projectType === 'application' ? 'app' : 'lib',
namePath
);

const selector =
options.selector ?? buildSelector(tree, name, options.prefix, prefix);
Coly010 marked this conversation as resolved.
Show resolved Hide resolved

return {
...options,
name,
path,
projectRoot: root,
projectSourceRoot,
selector,
};
}

function buildSelector(
tree: Tree,
name: string,
prefix: string,
projectPrefix: string
): string {
let selector = name;
prefix ??= projectPrefix ?? readNxJson(tree).npmScope;
if (prefix) {
selector = `${prefix}-${selector}`;
}

return names(selector).propertyName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Tree } from '@nrwl/devkit';
import { getProjects } from '@nrwl/devkit';
import { checkPathUnderProjectRoot } from '../../utils/path';
import type { Schema } from '../schema';

export function validateOptions(tree: Tree, options: Schema): void {
const projects = getProjects(tree);
if (!projects.has(options.project)) {
throw new Error(`Project "${options.project}" does not exist!`);
}

checkPathUnderProjectRoot(tree, options.project, options.path);
}
Coly010 marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 11 additions & 0 deletions packages/angular/src/generators/utils/names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { normalizePath } from '@nrwl/devkit';

export type NameInfo = { name: string; path: string };

export function parseName(rawName: string): NameInfo {
Coly010 marked this conversation as resolved.
Show resolved Hide resolved
const parsedName = normalizePath(rawName).split('/');
const name = parsedName.pop();
const path = parsedName.join('/');

return { name, path };
}