Skip to content

Commit e6083b5

Browse files
committed
fix(@schematics/angular): generate pipes with a dash type separator
To align with the updated style guide, Angular v20 will generate pipes with file extension `pipe` type prefixed with a `-` separator instead of a `.` by default. Projects will automatically use this naming convention. Projects can however opt-out by setting the `typeSeparator` option to `.` for the pipe schematic. This can be done as a default in the `angular.json` or directly on the commandline via `--type-separator=.` when executing `ng generate`. As an example, `example.pipe.ts` will now be named `example-pipe.ts`. The TypeScript class name will continue to contain `Pipe` such as with `ExamplePipe`.
1 parent d8a5647 commit e6083b5

File tree

7 files changed

+64
-40
lines changed

7 files changed

+64
-40
lines changed

Diff for: packages/schematics/angular/pipe/files/__name@dasherize@if-flat__/__name@dasherize__.pipe.spec.ts.template renamed to packages/schematics/angular/pipe/files/__name@dasherize____typeSeparator__pipe.spec.ts.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { <%= classify(name) %>Pipe } from './<%= dasherize(name) %>.pipe';
1+
import { <%= classify(name) %>Pipe } from './<%= dasherize(name) %><%= typeSeparator %>pipe';
22

33
describe('<%= classify(name) %>Pipe', () => {
44
it('create an instance', () => {

Diff for: packages/schematics/angular/pipe/index.ts

+3-25
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,10 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {
10-
Rule,
11-
Tree,
12-
apply,
13-
applyTemplates,
14-
chain,
15-
filter,
16-
mergeWith,
17-
move,
18-
noop,
19-
strings,
20-
url,
21-
} from '@angular-devkit/schematics';
9+
import { Rule, Tree, chain, strings } from '@angular-devkit/schematics';
2210
import { addDeclarationToNgModule } from '../utility/add-declaration-to-ng-module';
2311
import { findModuleFromOptions } from '../utility/find-module';
12+
import { generateFromFiles } from '../utility/generate-from-files';
2413
import { parseName } from '../utility/parse-name';
2514
import { validateClassName } from '../utility/validation';
2615
import { createDefaultPath } from '../utility/workspace';
@@ -36,23 +25,12 @@ export default function (options: PipeOptions): Rule {
3625
options.path = parsedPath.path;
3726
validateClassName(strings.classify(options.name));
3827

39-
const templateSource = apply(url('./files'), [
40-
options.skipTests ? filter((path) => !path.endsWith('.spec.ts.template')) : noop(),
41-
applyTemplates({
42-
...strings,
43-
'if-flat': (s: string) => (options.flat ? '' : s),
44-
...options,
45-
}),
46-
move(parsedPath.path),
47-
]);
48-
4928
return chain([
5029
addDeclarationToNgModule({
5130
type: 'pipe',
52-
5331
...options,
5432
}),
55-
mergeWith(templateSource),
33+
generateFromFiles(options),
5634
]);
5735
};
5836
}

Diff for: packages/schematics/angular/pipe/index_spec.ts

+48-10
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,25 @@ describe('Pipe Schematic', () => {
5656
it('should create a pipe', async () => {
5757
const tree = await schematicRunner.runSchematic('pipe', defaultNonStandaloneOptions, appTree);
5858
const files = tree.files;
59+
expect(files).toContain('/projects/bar/src/app/foo-pipe.spec.ts');
60+
expect(files).toContain('/projects/bar/src/app/foo-pipe.ts');
61+
const moduleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts');
62+
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo-pipe'/);
63+
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooPipe\r?\n/m);
64+
const fileContent = tree.readContent('/projects/bar/src/app/foo-pipe.ts');
65+
expect(fileContent).toContain('transform(value: unknown, ...args: unknown[])');
66+
});
67+
68+
it('should use a `.` type separator when specified', async () => {
69+
const tree = await schematicRunner.runSchematic(
70+
'pipe',
71+
{
72+
...defaultNonStandaloneOptions,
73+
typeSeparator: '.',
74+
},
75+
appTree,
76+
);
77+
const files = tree.files;
5978
expect(files).toContain('/projects/bar/src/app/foo.pipe.spec.ts');
6079
expect(files).toContain('/projects/bar/src/app/foo.pipe.ts');
6180
const moduleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts');
@@ -65,13 +84,32 @@ describe('Pipe Schematic', () => {
6584
expect(fileContent).toContain('transform(value: unknown, ...args: unknown[])');
6685
});
6786

87+
it('should use a `-` type separator when specified', async () => {
88+
const tree = await schematicRunner.runSchematic(
89+
'pipe',
90+
{
91+
...defaultNonStandaloneOptions,
92+
typeSeparator: '-',
93+
},
94+
appTree,
95+
);
96+
const files = tree.files;
97+
expect(files).toContain('/projects/bar/src/app/foo-pipe.spec.ts');
98+
expect(files).toContain('/projects/bar/src/app/foo-pipe.ts');
99+
const moduleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts');
100+
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo-pipe'/);
101+
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooPipe\r?\n/m);
102+
const fileContent = tree.readContent('/projects/bar/src/app/foo-pipe.ts');
103+
expect(fileContent).toContain('transform(value: unknown, ...args: unknown[])');
104+
});
105+
68106
it('should import into a specified module', async () => {
69107
const options = { ...defaultNonStandaloneOptions, module: 'app.module.ts' };
70108

71109
const tree = await schematicRunner.runSchematic('pipe', options, appTree);
72110
const appModule = getFileContent(tree, '/projects/bar/src/app/app.module.ts');
73111

74-
expect(appModule).toMatch(/import { FooPipe } from '.\/foo.pipe'/);
112+
expect(appModule).toMatch(/import { FooPipe } from '.\/foo-pipe'/);
75113
});
76114

77115
it('should fail if specified module does not exist', async () => {
@@ -94,7 +132,7 @@ describe('Pipe Schematic', () => {
94132
appTree = await schematicRunner.runSchematic('pipe', options, appTree);
95133

96134
const content = appTree.readContent('/projects/bar/src/app/admin/module/module.module.ts');
97-
expect(content).toMatch(/import { FooPipe } from '\.\.\/\.\.\/foo.pipe'/);
135+
expect(content).toMatch(/import { FooPipe } from '\.\.\/\.\.\/foo-pipe'/);
98136
});
99137

100138
it('should export the pipe', async () => {
@@ -110,10 +148,10 @@ describe('Pipe Schematic', () => {
110148

111149
const tree = await schematicRunner.runSchematic('pipe', options, appTree);
112150
const files = tree.files;
113-
expect(files).toContain('/projects/bar/src/app/foo/foo.pipe.spec.ts');
114-
expect(files).toContain('/projects/bar/src/app/foo/foo.pipe.ts');
151+
expect(files).toContain('/projects/bar/src/app/foo/foo-pipe.spec.ts');
152+
expect(files).toContain('/projects/bar/src/app/foo/foo-pipe.ts');
115153
const moduleContent = getFileContent(tree, '/projects/bar/src/app/app.module.ts');
116-
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.pipe'/);
154+
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo-pipe'/);
117155
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooPipe\r?\n/m);
118156
});
119157

@@ -124,7 +162,7 @@ describe('Pipe Schematic', () => {
124162
const options = { ...defaultNonStandaloneOptions, module: routingFileName };
125163
const tree = await schematicRunner.runSchematic('pipe', options, newTree);
126164
const content = getFileContent(tree, routingModulePath);
127-
expect(content).toMatch(/import { FooPipe } from '.\/foo.pipe/);
165+
expect(content).toMatch(/import { FooPipe } from '.\/foo-pipe/);
128166
});
129167

130168
it('should respect the sourceRoot value', async () => {
@@ -143,7 +181,7 @@ describe('Pipe Schematic', () => {
143181
'/projects/bar/custom/app/app.module.ts',
144182
);
145183
appTree = await schematicRunner.runSchematic('pipe', defaultNonStandaloneOptions, appTree);
146-
expect(appTree.files).toContain('/projects/bar/custom/app/foo.pipe.ts');
184+
expect(appTree.files).toContain('/projects/bar/custom/app/foo-pipe.ts');
147185
});
148186
});
149187

@@ -155,7 +193,7 @@ describe('Pipe Schematic', () => {
155193
it('should create a standalone pipe', async () => {
156194
const tree = await schematicRunner.runSchematic('pipe', defaultOptions, appTree);
157195
const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts');
158-
const pipeContent = tree.readContent('/projects/bar/src/app/foo.pipe.ts');
196+
const pipeContent = tree.readContent('/projects/bar/src/app/foo-pipe.ts');
159197
expect(pipeContent).not.toContain('standalone');
160198
expect(pipeContent).toContain('class FooPipe');
161199
expect(moduleContent).not.toContain('FooPipe');
@@ -166,8 +204,8 @@ describe('Pipe Schematic', () => {
166204

167205
const tree = await schematicRunner.runSchematic('pipe', options, appTree);
168206
const files = tree.files;
169-
expect(files).not.toContain('/projects/bar/src/app/foo.pipe.spec.ts');
170-
expect(files).toContain('/projects/bar/src/app/foo.pipe.ts');
207+
expect(files).not.toContain('/projects/bar/src/app/foo-pipe.spec.ts');
208+
expect(files).toContain('/projects/bar/src/app/foo-pipe.ts');
171209
});
172210

173211
it('should error when class name contains invalid characters', async () => {

Diff for: packages/schematics/angular/pipe/schema.json

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@
6161
"type": "boolean",
6262
"default": false,
6363
"description": "Automatically export the pipe from the specified NgModule, making it accessible to other modules in the application."
64+
},
65+
"typeSeparator": {
66+
"type": "string",
67+
"default": "-",
68+
"enum": ["-", "."]
6469
}
6570
},
6671
"required": ["name", "project"]

Diff for: packages/schematics/angular/utility/add-declaration-to-ng-module.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface DeclarationToNgModuleOptions {
1919
flat?: boolean;
2020
export?: boolean;
2121
type: string;
22+
typeSeparator?: '.' | '-';
2223
skipImport?: boolean;
2324
standalone?: boolean;
2425
}
@@ -30,14 +31,16 @@ export function addDeclarationToNgModule(options: DeclarationToNgModuleOptions):
3031
return host;
3132
}
3233

34+
const typeSeparator = options.typeSeparator ?? '.';
35+
3336
const sourceText = host.readText(modulePath);
3437
const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
3538

3639
const filePath =
3740
`/${options.path}/` +
3841
(options.flat ? '' : strings.dasherize(options.name) + '/') +
3942
strings.dasherize(options.name) +
40-
(options.type ? '.' + strings.dasherize(options.type) : '');
43+
(options.type ? typeSeparator + strings.dasherize(options.type) : '');
4144

4245
const importPath = buildRelativePath(modulePath, filePath);
4346
const classifiedName =

Diff for: tests/legacy-cli/e2e/tests/generate/pipe/pipe-basic.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export default function () {
88
const pipeDir = join('src', 'app');
99

1010
return (
11-
ng('generate', 'pipe', 'test-pipe')
11+
ng('generate', 'pipe', 'test')
1212
.then(() => expectFileToExist(pipeDir))
13-
.then(() => expectFileToExist(join(pipeDir, 'test-pipe.pipe.ts')))
14-
.then(() => expectFileToExist(join(pipeDir, 'test-pipe.pipe.spec.ts')))
13+
.then(() => expectFileToExist(join(pipeDir, 'test-pipe.ts')))
14+
.then(() => expectFileToExist(join(pipeDir, 'test-pipe.spec.ts')))
1515

1616
// Try to run the unit tests.
1717
.then(() => ng('test', '--watch=false'))

0 commit comments

Comments
 (0)