Skip to content

Commit ae417aa

Browse files
authored
feat(angular): generate pipes with a - type separator (#31243)
- Update pipe and scam pipe generators to use a `-` type separator by default for Angular v20 - Generate pipes with a `.` type separator for Angular versions below v20 - Add the `typeSeparator` option to allow choosing between both Note: a migration will be provided in a separate PR so existing workspaces continue generating pipes with the `.` type separator.
1 parent 1070e18 commit ae417aa

File tree

20 files changed

+300
-53
lines changed

20 files changed

+300
-53
lines changed

docs/generated/packages/angular/generators/pipe.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515
"command": "nx g @nx/angular:pipe mylib/src/lib/foo.pipe.ts"
1616
},
1717
{
18-
"description": "Generate a pipe without providing the file extension. It results in the pipe `FooPipe` at `mylib/src/lib/foo.pipe.ts`",
18+
"description": "Generate a pipe without providing the file extension. It results in the pipe `FooPipe` at `mylib/src/lib/foo-pipe.ts`",
1919
"command": "nx g @nx/angular:pipe mylib/src/lib/foo"
2020
},
2121
{
22-
"description": "Generate a pipe with the exported symbol different from the file name. It results in the pipe `CustomPipe` at `mylib/src/lib/foo.pipe.ts`",
22+
"description": "Generate a pipe with a different type separator. It results in the pipe `FooPipe` at `mylib/src/lib/foo.pipe.ts`",
23+
"command": "nx g @nx/angular:pipe mylib/src/lib/foo --typeSeparator=."
24+
},
25+
{
26+
"description": "Generate a pipe with the exported symbol different from the file name. It results in the pipe `CustomPipe` at `mylib/src/lib/foo-pipe.ts`",
2327
"command": "nx g @nx/angular:pipe mylib/src/lib/foo --name=custom"
2428
}
2529
],
@@ -59,6 +63,11 @@
5963
"default": false,
6064
"description": "The declaring NgModule exports this pipe."
6165
},
66+
"typeSeparator": {
67+
"type": "string",
68+
"enum": ["-", "."],
69+
"description": "The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.pipe.ts`. It defaults to '-' for Angular v20+. For versions below v20, it defaults to '.'."
70+
},
6271
"skipFormat": {
6372
"type": "boolean",
6473
"default": false,

docs/generated/packages/angular/generators/scam-pipe.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@
5151
"default": true,
5252
"x-priority": "important"
5353
},
54+
"typeSeparator": {
55+
"type": "string",
56+
"enum": ["-", "."],
57+
"description": "The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.pipe.ts`. It defaults to '-' for Angular v20+. For versions below v20, it defaults to '.'."
58+
},
5459
"skipFormat": {
5560
"description": "Skip formatting files.",
5661
"type": "boolean",

packages/angular/src/generators/add-linting/__snapshots__/add-linting.spec.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ exports[`addLinting generator should correctly generate the .eslintrc.json file
1818
"*.ts",
1919
],
2020
"rules": {
21+
"@angular-eslint/component-class-suffix": "off",
2122
"@angular-eslint/component-selector": [
2223
"error",
2324
{
@@ -26,6 +27,7 @@ exports[`addLinting generator should correctly generate the .eslintrc.json file
2627
"type": "element",
2728
},
2829
],
30+
"@angular-eslint/directive-class-suffix": "off",
2931
"@angular-eslint/directive-selector": [
3032
"error",
3133
{

packages/angular/src/generators/add-linting/add-linting.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ describe('addLinting generator', () => {
291291
"prefix": "my-org",
292292
"style": "kebab-case"
293293
}
294-
]
294+
],
295+
"@angular-eslint/component-class-suffix": "off",
296+
"@angular-eslint/directive-class-suffix": "off"
295297
}
296298
},
297299
{

packages/angular/src/generators/add-linting/add-linting.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ export async function addLintingGenerator(
125125
style: 'kebab-case',
126126
},
127127
],
128+
// Temporary disable these rules until Angular ESLint recommended
129+
// rules are updated with the new Style Guide
130+
'@angular-eslint/component-class-suffix': 'off',
131+
'@angular-eslint/directive-class-suffix': 'off',
128132
},
129133
},
130134
{

packages/angular/src/generators/application/application.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ describe('app', () => {
672672
"*.ts",
673673
],
674674
"rules": {
675+
"@angular-eslint/component-class-suffix": "off",
675676
"@angular-eslint/component-selector": [
676677
"error",
677678
{
@@ -680,6 +681,7 @@ describe('app', () => {
680681
"type": "element",
681682
},
682683
],
684+
"@angular-eslint/directive-class-suffix": "off",
683685
"@angular-eslint/directive-selector": [
684686
"error",
685687
{

packages/angular/src/generators/library/library.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,9 @@ describe('lib', () => {
681681
"prefix": "lib",
682682
"style": "kebab-case"
683683
}
684-
]
684+
],
685+
"@angular-eslint/component-class-suffix": "off",
686+
"@angular-eslint/directive-class-suffix": "off"
685687
}
686688
},
687689
{
@@ -1272,6 +1274,7 @@ describe('lib', () => {
12721274
"*.ts",
12731275
],
12741276
"rules": {
1277+
"@angular-eslint/component-class-suffix": "off",
12751278
"@angular-eslint/component-selector": [
12761279
"error",
12771280
{
@@ -1280,6 +1283,7 @@ describe('lib', () => {
12801283
"type": "element",
12811284
},
12821285
],
1286+
"@angular-eslint/directive-class-suffix": "off",
12831287
"@angular-eslint/directive-selector": [
12841288
"error",
12851289
{
@@ -1332,6 +1336,7 @@ describe('lib', () => {
13321336
"*.ts",
13331337
],
13341338
"rules": {
1339+
"@angular-eslint/component-class-suffix": "off",
13351340
"@angular-eslint/component-selector": [
13361341
"error",
13371342
{
@@ -1340,6 +1345,7 @@ describe('lib', () => {
13401345
"type": "element",
13411346
},
13421347
],
1348+
"@angular-eslint/directive-class-suffix": "off",
13431349
"@angular-eslint/directive-selector": [
13441350
"error",
13451351
{

packages/angular/src/generators/pipe/__snapshots__/pipe.spec.ts.snap

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
exports[`pipe generator --no-standalone should export the pipe correctly when directory is nested deeper 1`] = `
44
"import { NgModule } from '@angular/core';
5-
import { TestPipe } from './my-pipes/test/test.pipe';
5+
import { TestPipe } from './my-pipes/test/test-pipe';
66
@NgModule({
77
imports: [],
88
declarations: [TestPipe],
@@ -28,7 +28,7 @@ export class TestPipe implements PipeTransform {
2828
`;
2929

3030
exports[`pipe generator --no-standalone should generate a pipe with test files and attach to the NgModule automatically 2`] = `
31-
"import { TestPipe } from './test.pipe';
31+
"import { TestPipe } from './test-pipe';
3232
3333
describe('TestPipe', () => {
3434
it('create an instance', () => {
@@ -41,7 +41,7 @@ describe('TestPipe', () => {
4141

4242
exports[`pipe generator --no-standalone should generate a pipe with test files and attach to the NgModule automatically 3`] = `
4343
"import { NgModule } from '@angular/core';
44-
import { TestPipe } from './test.pipe';
44+
import { TestPipe } from './test-pipe';
4545
@NgModule({
4646
imports: [],
4747
declarations: [TestPipe],
@@ -67,7 +67,7 @@ export class TestPipe implements PipeTransform {
6767
`;
6868

6969
exports[`pipe generator --no-standalone should import the pipe correctly when files are flat 2`] = `
70-
"import { TestPipe } from './test.pipe';
70+
"import { TestPipe } from './test-pipe';
7171
7272
describe('TestPipe', () => {
7373
it('create an instance', () => {
@@ -80,7 +80,7 @@ describe('TestPipe', () => {
8080

8181
exports[`pipe generator --no-standalone should import the pipe correctly when files are flat 3`] = `
8282
"import { NgModule } from '@angular/core';
83-
import { TestPipe } from './test/test.pipe';
83+
import { TestPipe } from './test/test-pipe';
8484
@NgModule({
8585
imports: [],
8686
declarations: [TestPipe],
@@ -106,7 +106,7 @@ export class TestPipe implements PipeTransform {
106106
`;
107107

108108
exports[`pipe generator --no-standalone should import the pipe correctly when files are flat but deeply nested 2`] = `
109-
"import { TestPipe } from './test.pipe';
109+
"import { TestPipe } from './test-pipe';
110110
111111
describe('TestPipe', () => {
112112
it('create an instance', () => {
@@ -119,7 +119,7 @@ describe('TestPipe', () => {
119119

120120
exports[`pipe generator --no-standalone should import the pipe correctly when files are flat but deeply nested 3`] = `
121121
"import { NgModule } from '@angular/core';
122-
import { TestPipe } from './my-pipes/test/test.pipe';
122+
import { TestPipe } from './my-pipes/test/test-pipe';
123123
@NgModule({
124124
imports: [],
125125
declarations: [TestPipe],
@@ -144,7 +144,7 @@ export class TestPipe implements PipeTransform {
144144
`;
145145

146146
exports[`pipe generator should generate correctly 2`] = `
147-
"import { TestPipe } from './test.pipe';
147+
"import { TestPipe } from './test-pipe';
148148
149149
describe('TestPipe', () => {
150150
it('create an instance', () => {

packages/angular/src/generators/pipe/lib/normalize-options.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import type { Tree } from '@nx/devkit';
22
import { names } from '@nx/devkit';
33
import { determineArtifactNameAndDirectoryOptions } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
44
import { validateClassName } from '../../utils/validations';
5+
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
56
import type { NormalizedSchema, Schema } from '../schema';
67

78
export async function normalizeOptions(
89
tree: Tree,
910
options: Schema
1011
): Promise<NormalizedSchema> {
12+
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
13+
options.typeSeparator ??= angularMajorVersion < 20 ? '.' : '-';
14+
1115
const {
1216
artifactName: name,
1317
directory,
@@ -18,6 +22,7 @@ export async function normalizeOptions(
1822
name: options.name,
1923
path: options.path,
2024
suffix: 'pipe',
25+
suffixSeparator: options.typeSeparator,
2126
allowedFileExtensions: ['ts'],
2227
fileExtension: 'ts',
2328
});

packages/angular/src/generators/pipe/pipe.spec.ts

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addProjectConfiguration, Tree } from '@nx/devkit';
1+
import { addProjectConfiguration, type Tree, updateJson } from '@nx/devkit';
22
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
33
import { pipeGenerator } from './pipe';
44
import type { Schema } from './schema';
@@ -21,12 +21,46 @@ describe('pipe generator', () => {
2121
await generatePipeWithDefaultOptions(tree, { skipFormat: false });
2222

2323
// ASSERT
24-
expect(tree.read('test/src/app/test.pipe.ts', 'utf-8')).toMatchSnapshot();
24+
expect(tree.read('test/src/app/test-pipe.ts', 'utf-8')).toMatchSnapshot();
2525
expect(
26-
tree.read('test/src/app/test.pipe.spec.ts', 'utf-8')
26+
tree.read('test/src/app/test-pipe.spec.ts', 'utf-8')
2727
).toMatchSnapshot();
2828
});
2929

30+
it('should generate files with the provided type separator', async () => {
31+
await generatePipeWithDefaultOptions(tree, {
32+
typeSeparator: '.',
33+
skipFormat: false,
34+
});
35+
36+
expect(tree.read('test/src/app/test.pipe.ts', 'utf-8'))
37+
.toMatchInlineSnapshot(`
38+
"import { Pipe, PipeTransform } from '@angular/core';
39+
40+
@Pipe({
41+
name: 'test',
42+
})
43+
export class TestPipe implements PipeTransform {
44+
transform(value: unknown, ...args: unknown[]): unknown {
45+
return null;
46+
}
47+
}
48+
"
49+
`);
50+
expect(tree.read('test/src/app/test.pipe.spec.ts', 'utf-8'))
51+
.toMatchInlineSnapshot(`
52+
"import { TestPipe } from './test.pipe';
53+
54+
describe('TestPipe', () => {
55+
it('create an instance', () => {
56+
const pipe = new TestPipe();
57+
expect(pipe).toBeTruthy();
58+
});
59+
});
60+
"
61+
`);
62+
});
63+
3064
it('should handle path with file extension', async () => {
3165
await generatePipeWithDefaultOptions(tree, {
3266
path: 'test/src/app/test.pipe.ts',
@@ -61,7 +95,7 @@ describe('pipe generator', () => {
6195

6296
// ASSERT
6397
expect(
64-
tree.exists('test/src/app/my-pipes/test/test.pipe.spec.ts')
98+
tree.exists('test/src/app/my-pipes/test/test-pipe.spec.ts')
6599
).toBeFalsy();
66100
});
67101

@@ -86,9 +120,9 @@ describe('pipe generator', () => {
86120
});
87121

88122
// ASSERT
89-
expect(tree.read('test/src/app/test.pipe.ts', 'utf-8')).toMatchSnapshot();
123+
expect(tree.read('test/src/app/test-pipe.ts', 'utf-8')).toMatchSnapshot();
90124
expect(
91-
tree.read('test/src/app/test.pipe.spec.ts', 'utf-8')
125+
tree.read('test/src/app/test-pipe.spec.ts', 'utf-8')
92126
).toMatchSnapshot();
93127
expect(
94128
tree.read('test/src/app/test.module.ts', 'utf-8')
@@ -106,10 +140,10 @@ describe('pipe generator', () => {
106140

107141
// ASSERT
108142
expect(
109-
tree.read('test/src/app/test/test.pipe.ts', 'utf-8')
143+
tree.read('test/src/app/test/test-pipe.ts', 'utf-8')
110144
).toMatchSnapshot();
111145
expect(
112-
tree.read('test/src/app/test/test.pipe.spec.ts', 'utf-8')
146+
tree.read('test/src/app/test/test-pipe.spec.ts', 'utf-8')
113147
).toMatchSnapshot();
114148
expect(
115149
tree.read('test/src/app/test.module.ts', 'utf-8')
@@ -127,10 +161,10 @@ describe('pipe generator', () => {
127161

128162
// ASSERT
129163
expect(
130-
tree.read('test/src/app/my-pipes/test/test.pipe.ts', 'utf-8')
164+
tree.read('test/src/app/my-pipes/test/test-pipe.ts', 'utf-8')
131165
).toMatchSnapshot();
132166
expect(
133-
tree.read('test/src/app/my-pipes/test/test.pipe.spec.ts', 'utf-8')
167+
tree.read('test/src/app/my-pipes/test/test-pipe.spec.ts', 'utf-8')
134168
).toMatchSnapshot();
135169
expect(
136170
tree.read('test/src/app/test.module.ts', 'utf-8')
@@ -169,6 +203,47 @@ describe('pipe generator', () => {
169203
);
170204
});
171205
});
206+
207+
describe('compat', () => {
208+
it('should generate the files with the "." type separator for versions below v20', async () => {
209+
updateJson(tree, 'package.json', (json) => {
210+
json.dependencies = {
211+
...json.dependencies,
212+
'@angular/core': '~19.2.0',
213+
};
214+
return json;
215+
});
216+
217+
await generatePipeWithDefaultOptions(tree, { skipFormat: false });
218+
219+
expect(tree.read('test/src/app/test.pipe.ts', 'utf-8'))
220+
.toMatchInlineSnapshot(`
221+
"import { Pipe, PipeTransform } from '@angular/core';
222+
223+
@Pipe({
224+
name: 'test',
225+
})
226+
export class TestPipe implements PipeTransform {
227+
transform(value: unknown, ...args: unknown[]): unknown {
228+
return null;
229+
}
230+
}
231+
"
232+
`);
233+
expect(tree.read('test/src/app/test.pipe.spec.ts', 'utf-8'))
234+
.toMatchInlineSnapshot(`
235+
"import { TestPipe } from './test.pipe';
236+
237+
describe('TestPipe', () => {
238+
it('create an instance', () => {
239+
const pipe = new TestPipe();
240+
expect(pipe).toBeTruthy();
241+
});
242+
});
243+
"
244+
`);
245+
});
246+
});
172247
});
173248

174249
function addModule(tree: Tree) {

0 commit comments

Comments
 (0)