feat(angular): add pipe generator (#15659)
This commit is contained in:
parent
ca8d24b3d4
commit
9cd28583f6
@ -3779,6 +3779,14 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "pipe",
|
||||||
|
"path": "/packages/angular/generators/pipe",
|
||||||
|
"name": "pipe",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "scam-to-standalone",
|
"id": "scam-to-standalone",
|
||||||
"path": "/packages/angular/generators/scam-to-standalone",
|
"path": "/packages/angular/generators/scam-to-standalone",
|
||||||
|
|||||||
@ -279,6 +279,15 @@
|
|||||||
"path": "/packages/angular/generators/ngrx",
|
"path": "/packages/angular/generators/ngrx",
|
||||||
"type": "generator"
|
"type": "generator"
|
||||||
},
|
},
|
||||||
|
"/packages/angular/generators/pipe": {
|
||||||
|
"description": "Generate an Angular Pipe",
|
||||||
|
"file": "generated/packages/angular/generators/pipe.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "pipe",
|
||||||
|
"originalFilePath": "/packages/angular/src/generators/pipe/schema.json",
|
||||||
|
"path": "/packages/angular/generators/pipe",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
"/packages/angular/generators/scam-to-standalone": {
|
"/packages/angular/generators/scam-to-standalone": {
|
||||||
"description": "Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.",
|
"description": "Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.",
|
||||||
"file": "generated/packages/angular/generators/scam-to-standalone.json",
|
"file": "generated/packages/angular/generators/scam-to-standalone.json",
|
||||||
|
|||||||
@ -273,6 +273,15 @@
|
|||||||
"path": "angular/generators/ngrx",
|
"path": "angular/generators/ngrx",
|
||||||
"type": "generator"
|
"type": "generator"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Generate an Angular Pipe",
|
||||||
|
"file": "generated/packages/angular/generators/pipe.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "pipe",
|
||||||
|
"originalFilePath": "/packages/angular/src/generators/pipe/schema.json",
|
||||||
|
"path": "angular/generators/pipe",
|
||||||
|
"type": "generator"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.",
|
"description": "Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.",
|
||||||
"file": "generated/packages/angular/generators/scam-to-standalone.json",
|
"file": "generated/packages/angular/generators/scam-to-standalone.json",
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
"project": {
|
"project": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of the project.",
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
"x-dropdown": "projects"
|
"x-dropdown": "projects"
|
||||||
},
|
},
|
||||||
"prefix": {
|
"prefix": {
|
||||||
|
|||||||
77
docs/generated/packages/angular/generators/pipe.json
Normal file
77
docs/generated/packages/angular/generators/pipe.json
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"name": "pipe",
|
||||||
|
"factory": "./src/generators/pipe/pipe",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"$id": "SchematicsAngularPipe",
|
||||||
|
"title": "Angular Pipe Options Schema",
|
||||||
|
"type": "object",
|
||||||
|
"cli": "nx",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Creates a new, generic pipe definition in the given project.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the pipe.",
|
||||||
|
"$default": { "$source": "argv", "index": 0 },
|
||||||
|
"x-prompt": "What name would you like to use for the pipe?"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "path",
|
||||||
|
"$default": { "$source": "workingDirectory" },
|
||||||
|
"description": "The path at which to create the pipe, relative to the workspace root.",
|
||||||
|
"visible": false
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": { "$source": "projectName" },
|
||||||
|
"x-dropdown": "projects"
|
||||||
|
},
|
||||||
|
"flat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "When true (the default) creates files at the top level of the project."
|
||||||
|
},
|
||||||
|
"skipTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not create \"spec.ts\" test files for the new pipe.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipImport": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Do not import this pipe into the owning NgModule."
|
||||||
|
},
|
||||||
|
"standalone": {
|
||||||
|
"description": "Whether the generated pipe is standalone.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"module": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The filename of the declaring NgModule.",
|
||||||
|
"alias": "m"
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "The declaring NgModule exports this pipe."
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Skip formatting of files."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "project"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"description": "Generate an Angular Pipe",
|
||||||
|
"aliases": ["p"],
|
||||||
|
"implementation": "/packages/angular/src/generators/pipe/pipe.ts",
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/angular/src/generators/pipe/schema.json",
|
||||||
|
"type": "generator"
|
||||||
|
}
|
||||||
@ -254,6 +254,12 @@
|
|||||||
"schema": "./src/generators/ngrx/schema.json",
|
"schema": "./src/generators/ngrx/schema.json",
|
||||||
"description": "Adds NgRx support to an application or library."
|
"description": "Adds NgRx support to an application or library."
|
||||||
},
|
},
|
||||||
|
"pipe": {
|
||||||
|
"factory": "./src/generators/pipe/pipe",
|
||||||
|
"schema": "./src/generators/pipe/schema.json",
|
||||||
|
"description": "Generate an Angular Pipe",
|
||||||
|
"aliases": ["p"]
|
||||||
|
},
|
||||||
"scam-to-standalone": {
|
"scam-to-standalone": {
|
||||||
"factory": "./src/generators/scam-to-standalone/scam-to-standalone",
|
"factory": "./src/generators/scam-to-standalone/scam-to-standalone",
|
||||||
"schema": "./src/generators/scam-to-standalone/schema.json",
|
"schema": "./src/generators/scam-to-standalone/schema.json",
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export * from './src/generators/library-secondary-entry-point/library-secondary-
|
|||||||
export * from './src/generators/library/library';
|
export * from './src/generators/library/library';
|
||||||
export * from './src/generators/move/move';
|
export * from './src/generators/move/move';
|
||||||
export * from './src/generators/ngrx/ngrx';
|
export * from './src/generators/ngrx/ngrx';
|
||||||
|
export * from './src/generators/pipe/pipe';
|
||||||
export * from './src/generators/remote/remote';
|
export * from './src/generators/remote/remote';
|
||||||
export * from './src/generators/scam-directive/scam-directive';
|
export * from './src/generators/scam-directive/scam-directive';
|
||||||
export * from './src/generators/scam-pipe/scam-pipe';
|
export * from './src/generators/scam-pipe/scam-pipe';
|
||||||
|
|||||||
@ -10,10 +10,7 @@ import {
|
|||||||
} from '@nrwl/devkit';
|
} from '@nrwl/devkit';
|
||||||
import type { Schema } from './schema';
|
import type { Schema } from './schema';
|
||||||
import { checkPathUnderProjectRoot } from '../utils/path';
|
import { checkPathUnderProjectRoot } from '../utils/path';
|
||||||
import { dirname } from 'path';
|
import { addToNgModule, findModule } from '../utils';
|
||||||
import { insertNgModuleProperty } from '../utils';
|
|
||||||
import { insertImport } from '@nrwl/js';
|
|
||||||
import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescript';
|
|
||||||
|
|
||||||
let tsModule: typeof import('typescript');
|
let tsModule: typeof import('typescript');
|
||||||
|
|
||||||
@ -65,7 +62,17 @@ export async function directiveGenerator(tree: Tree, schema: Schema) {
|
|||||||
|
|
||||||
if (!schema.skipImport && !schema.standalone) {
|
if (!schema.skipImport && !schema.standalone) {
|
||||||
const modulePath = findModule(tree, path, schema.module);
|
const modulePath = findModule(tree, path, schema.module);
|
||||||
addImportToNgModule(path, modulePath, schema, directiveNames, tree);
|
addToNgModule(
|
||||||
|
tree,
|
||||||
|
path,
|
||||||
|
modulePath,
|
||||||
|
directiveNames.fileName,
|
||||||
|
`${directiveNames.className}Directive`,
|
||||||
|
`${directiveNames.fileName}.directive`,
|
||||||
|
'declarations',
|
||||||
|
schema.flat,
|
||||||
|
schema.export
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!schema.skipFormat) {
|
if (!schema.skipFormat) {
|
||||||
@ -80,81 +87,4 @@ function buildSelector(tree: Tree, name: string, prefix: string) {
|
|||||||
return names(`${selectorPrefix}-${selector}`).propertyName;
|
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;
|
export default directiveGenerator;
|
||||||
|
|||||||
@ -28,6 +28,9 @@
|
|||||||
"project": {
|
"project": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of the project.",
|
"description": "The name of the project.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "projectName"
|
||||||
|
},
|
||||||
"x-dropdown": "projects"
|
"x-dropdown": "projects"
|
||||||
},
|
},
|
||||||
"prefix": {
|
"prefix": {
|
||||||
|
|||||||
@ -0,0 +1,261 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`pipe generator should export the pipe correctly when flat=false and path is nested deeper 1`] = `
|
||||||
|
"import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'test'
|
||||||
|
})
|
||||||
|
export class TestPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: unknown, ...args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should export the pipe correctly when flat=false and path is nested deeper 2`] = `
|
||||||
|
"import { TestPipe } from './test.pipe';
|
||||||
|
|
||||||
|
describe('TestPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new TestPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should export the pipe correctly when flat=false and path is nested deeper 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestPipe } from './my-pipes/test/test.pipe';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestPipe],
|
||||||
|
exports: [, TestPipe]
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should generate a pipe with test files and attach to the NgModule automatically 1`] = `
|
||||||
|
"import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'test'
|
||||||
|
})
|
||||||
|
export class TestPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: unknown, ...args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should generate a pipe with test files and attach to the NgModule automatically 2`] = `
|
||||||
|
"import { TestPipe } from './test.pipe';
|
||||||
|
|
||||||
|
describe('TestPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new TestPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should generate a pipe with test files and attach to the NgModule automatically 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestPipe } from './test.pipe';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestPipe],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should import the pipe correctly when flat=false 1`] = `
|
||||||
|
"import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'test'
|
||||||
|
})
|
||||||
|
export class TestPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: unknown, ...args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should import the pipe correctly when flat=false 2`] = `
|
||||||
|
"import { TestPipe } from './test.pipe';
|
||||||
|
|
||||||
|
describe('TestPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new TestPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should import the pipe correctly when flat=false 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestPipe } from './test/test.pipe';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestPipe],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should import the pipe correctly when flat=false and path is nested deeper 1`] = `
|
||||||
|
"import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'test'
|
||||||
|
})
|
||||||
|
export class TestPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: unknown, ...args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should import the pipe correctly when flat=false and path is nested deeper 2`] = `
|
||||||
|
"import { TestPipe } from './test.pipe';
|
||||||
|
|
||||||
|
describe('TestPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new TestPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should import the pipe correctly when flat=false and path is nested deeper 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestPipe } from './my-pipes/test/test.pipe';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestPipe],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should not generate test file when skipTests=true 1`] = `
|
||||||
|
"import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'test'
|
||||||
|
})
|
||||||
|
export class TestPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: unknown, ...args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should not generate test file when skipTests=true 2`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
import { TestPipe } from './my-pipes/test/test.pipe';
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [, TestPipe],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should not import the pipe when skipImport=true 1`] = `
|
||||||
|
"import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'test'
|
||||||
|
})
|
||||||
|
export class TestPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: unknown, ...args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should not import the pipe when skipImport=true 2`] = `
|
||||||
|
"import { TestPipe } from './test.pipe';
|
||||||
|
|
||||||
|
describe('TestPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new TestPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should not import the pipe when skipImport=true 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should not import the pipe when standalone=true 1`] = `
|
||||||
|
"import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'test',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class TestPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: unknown, ...args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should not import the pipe when standalone=true 2`] = `
|
||||||
|
"import { TestPipe } from './test.pipe';
|
||||||
|
|
||||||
|
describe('TestPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new TestPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`pipe generator should not import the pipe when standalone=true 3`] = `
|
||||||
|
"import {NgModule} from \\"@angular/core\\";
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class TestModule {}"
|
||||||
|
`;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { <%= pipeClassName %>Pipe } from './<%= pipeFileName %>.pipe';
|
||||||
|
|
||||||
|
describe('<%= pipeClassName %>Pipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new <%= pipeClassName %>Pipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: '<%= pipePropertyName %>'<% if(standalone) {%>,
|
||||||
|
standalone: true<%}%>
|
||||||
|
})
|
||||||
|
export class <%= pipeClassName %>Pipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: unknown, ...args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
155
packages/angular/src/generators/pipe/pipe.spec.ts
Normal file
155
packages/angular/src/generators/pipe/pipe.spec.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import { addProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||||
|
import { pipeGenerator } from './pipe';
|
||||||
|
import type { Schema } from './schema';
|
||||||
|
|
||||||
|
describe('pipe 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 pipe with test files and attach to the NgModule automatically', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generatePipeWithDefaultOptions(tree);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('test/src/test.pipe.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.pipe.spec.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import the pipe correctly when flat=false', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generatePipeWithDefaultOptions(tree, { flat: false });
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('test/src/test/test.pipe.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/test/test.pipe.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not import the pipe when standalone=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generatePipeWithDefaultOptions(tree, { standalone: true });
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('test/src/test.pipe.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.pipe.spec.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import the pipe correctly when flat=false and path is nested deeper', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generatePipeWithDefaultOptions(tree, {
|
||||||
|
flat: false,
|
||||||
|
path: 'test/src/my-pipes',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-pipes/test/test.pipe.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-pipes/test/test.pipe.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export the pipe correctly when flat=false and path is nested deeper', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generatePipeWithDefaultOptions(tree, {
|
||||||
|
flat: false,
|
||||||
|
path: 'test/src/my-pipes',
|
||||||
|
export: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-pipes/test/test.pipe.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-pipes/test/test.pipe.spec.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not import the pipe when skipImport=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generatePipeWithDefaultOptions(tree, {
|
||||||
|
flat: false,
|
||||||
|
path: 'test/src/my-pipes',
|
||||||
|
skipImport: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-pipes/test/test.pipe.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-pipes/test/test.pipe.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 generatePipeWithDefaultOptions(tree, {
|
||||||
|
flat: false,
|
||||||
|
path: 'test/src/my-pipes',
|
||||||
|
skipTests: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(
|
||||||
|
tree.read('test/src/my-pipes/test/test.pipe.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.exists('test/src/my-pipes/test/test.pipe.spec.ts')).toBeFalsy();
|
||||||
|
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function generatePipeWithDefaultOptions(
|
||||||
|
tree: Tree,
|
||||||
|
overrides: Partial<Schema> = {}
|
||||||
|
) {
|
||||||
|
await pipeGenerator(tree, {
|
||||||
|
name: 'test',
|
||||||
|
project: 'test',
|
||||||
|
flat: true,
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
}
|
||||||
79
packages/angular/src/generators/pipe/pipe.ts
Normal file
79
packages/angular/src/generators/pipe/pipe.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||||
|
import {
|
||||||
|
formatFiles,
|
||||||
|
generateFiles,
|
||||||
|
getProjects,
|
||||||
|
joinPathFragments,
|
||||||
|
names,
|
||||||
|
readProjectConfiguration,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import type { Schema } from './schema';
|
||||||
|
import { checkPathUnderProjectRoot } from '../utils/path';
|
||||||
|
import { addToNgModule, findModule } from '../utils';
|
||||||
|
|
||||||
|
let tsModule: typeof import('typescript');
|
||||||
|
|
||||||
|
export async function pipeGenerator(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 pipeNames = names(schema.name);
|
||||||
|
|
||||||
|
const pathToGenerateFiles = schema.flat
|
||||||
|
? './files/__pipeFileName__'
|
||||||
|
: './files';
|
||||||
|
await generateFiles(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(__dirname, pathToGenerateFiles),
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
pipeClassName: pipeNames.className,
|
||||||
|
pipeFileName: pipeNames.fileName,
|
||||||
|
pipePropertyName: pipeNames.propertyName,
|
||||||
|
standalone: schema.standalone,
|
||||||
|
tpl: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (schema.skipTests) {
|
||||||
|
const pathToSpecFile = joinPathFragments(
|
||||||
|
path,
|
||||||
|
`${!schema.flat ? `${pipeNames.fileName}/` : ``}${
|
||||||
|
pipeNames.fileName
|
||||||
|
}.pipe.spec.ts`
|
||||||
|
);
|
||||||
|
|
||||||
|
tree.delete(pathToSpecFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schema.skipImport && !schema.standalone) {
|
||||||
|
const modulePath = findModule(tree, path, schema.module);
|
||||||
|
addToNgModule(
|
||||||
|
tree,
|
||||||
|
path,
|
||||||
|
modulePath,
|
||||||
|
pipeNames.fileName,
|
||||||
|
`${pipeNames.className}Pipe`,
|
||||||
|
`${pipeNames.fileName}.pipe`,
|
||||||
|
'declarations',
|
||||||
|
schema.flat,
|
||||||
|
schema.export
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!schema.skipFormat) {
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default pipeGenerator;
|
||||||
12
packages/angular/src/generators/pipe/schema.d.ts
vendored
Normal file
12
packages/angular/src/generators/pipe/schema.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface Schema {
|
||||||
|
name: string;
|
||||||
|
project: string;
|
||||||
|
path?: string;
|
||||||
|
flat?: boolean;
|
||||||
|
skipTests?: boolean;
|
||||||
|
skipImport?: boolean;
|
||||||
|
standalone?: boolean;
|
||||||
|
module?: string;
|
||||||
|
export?: boolean;
|
||||||
|
skipFormat?: boolean;
|
||||||
|
}
|
||||||
73
packages/angular/src/generators/pipe/schema.json
Normal file
73
packages/angular/src/generators/pipe/schema.json
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"$id": "SchematicsAngularPipe",
|
||||||
|
"title": "Angular Pipe Options Schema",
|
||||||
|
"type": "object",
|
||||||
|
"cli": "nx",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Creates a new, generic pipe definition in the given project.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the pipe.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"x-prompt": "What name would you like to use for the pipe?"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "path",
|
||||||
|
"$default": {
|
||||||
|
"$source": "workingDirectory"
|
||||||
|
},
|
||||||
|
"description": "The path at which to create the pipe, relative to the workspace root.",
|
||||||
|
"visible": false
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the project.",
|
||||||
|
"$default": {
|
||||||
|
"$source": "projectName"
|
||||||
|
},
|
||||||
|
"x-dropdown": "projects"
|
||||||
|
},
|
||||||
|
"flat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "When true (the default) creates files at the top level of the project."
|
||||||
|
},
|
||||||
|
"skipTests": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do not create \"spec.ts\" test files for the new pipe.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"skipImport": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Do not import this pipe into the owning NgModule."
|
||||||
|
},
|
||||||
|
"standalone": {
|
||||||
|
"description": "Whether the generated pipe is standalone.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"module": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The filename of the declaring NgModule.",
|
||||||
|
"alias": "m"
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "The declaring NgModule exports this pipe."
|
||||||
|
},
|
||||||
|
"skipFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Skip formatting of files."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "project"]
|
||||||
|
}
|
||||||
86
packages/angular/src/generators/utils/find-module.ts
Normal file
86
packages/angular/src/generators/utils/find-module.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import type { Tree } from '@nrwl/devkit';
|
||||||
|
import { joinPathFragments } from '@nrwl/devkit';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescript';
|
||||||
|
import { insertImport } from '@nrwl/js';
|
||||||
|
import {
|
||||||
|
insertNgModuleProperty,
|
||||||
|
ngModuleDecoratorProperty,
|
||||||
|
} from './insert-ngmodule-import';
|
||||||
|
|
||||||
|
let tsModule: typeof import('typescript');
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addToNgModule(
|
||||||
|
tree: Tree,
|
||||||
|
path: string,
|
||||||
|
modulePath: string,
|
||||||
|
name: string,
|
||||||
|
className: string,
|
||||||
|
fileName: string,
|
||||||
|
ngModuleProperty: ngModuleDecoratorProperty,
|
||||||
|
isFlat = true,
|
||||||
|
isExported = false
|
||||||
|
) {
|
||||||
|
if (!tsModule) {
|
||||||
|
tsModule = ensureTypescript();
|
||||||
|
}
|
||||||
|
|
||||||
|
let relativePath = `${joinPathFragments(
|
||||||
|
path.replace(dirname(modulePath), ''),
|
||||||
|
!isFlat ? name : '',
|
||||||
|
`${fileName}`
|
||||||
|
)}`;
|
||||||
|
relativePath = relativePath.startsWith('/')
|
||||||
|
? `.${relativePath}`
|
||||||
|
: `./${relativePath}`;
|
||||||
|
|
||||||
|
const moduleContents = tree.read(modulePath, 'utf-8');
|
||||||
|
const source = tsModule.createSourceFile(
|
||||||
|
modulePath,
|
||||||
|
moduleContents,
|
||||||
|
tsModule.ScriptTarget.Latest,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
insertImport(tree, source, modulePath, className, relativePath);
|
||||||
|
insertNgModuleProperty(tree, modulePath, className, ngModuleProperty);
|
||||||
|
if (isExported) {
|
||||||
|
insertNgModuleProperty(tree, modulePath, className, 'exports');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from './insert-ngmodule-import';
|
export * from './insert-ngmodule-import';
|
||||||
|
export * from './find-module';
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescrip
|
|||||||
|
|
||||||
let tsModule: typeof import('typescript');
|
let tsModule: typeof import('typescript');
|
||||||
|
|
||||||
type ngModuleDecoratorProperty =
|
export type ngModuleDecoratorProperty =
|
||||||
| 'imports'
|
| 'imports'
|
||||||
| 'providers'
|
| 'providers'
|
||||||
| 'declarations'
|
| 'declarations'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user