Skip to content

Commit 83dcfb3

Browse files
alan-agius4clydin
authored andcommitted
fix(@schematics/angular): prevent numbers from class names
With this change we prevent creating classes with invalid characters. Closes #12868 (cherry picked from commit e995bda)
1 parent 44c1808 commit 83dcfb3

File tree

9 files changed

+52
-7
lines changed

9 files changed

+52
-7
lines changed

packages/schematics/angular/class/index_spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,12 @@ describe('Class Schematic', () => {
109109
expect(tree.files).toContain('/projects/bar/src/app/foo.ts');
110110
expect(tree.files).not.toContain('/projects/bar/src/app/foo.spec.ts');
111111
});
112+
113+
it('should error when class name contains invalid characters', async () => {
114+
const options = { ...defaultOptions, name: '1Clazz' };
115+
116+
await expectAsync(
117+
schematicRunner.runSchematicAsync('class', options, appTree).toPromise(),
118+
).toBeRejectedWithError('Class name "1Clazz" is invalid.');
119+
});
112120
});

packages/schematics/angular/component/index_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe('Component Schematic', () => {
209209

210210
await expectAsync(
211211
schematicRunner.runSchematicAsync('component', options, appTree).toPromise(),
212-
).toBeRejectedWithError('Selector (app-1-one) is invalid.');
212+
).toBeRejectedWithError('Selector "app-1-one" is invalid.');
213213
});
214214

215215
it('should use the default project prefix if none is passed', async () => {

packages/schematics/angular/enum/index_spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,12 @@ describe('Enum Schematic', () => {
7373
const tree = await schematicRunner.runSchematicAsync('enum', options, appTree).toPromise();
7474
expect(tree.files).toContain('/projects/bar/src/app/foo.enum.ts');
7575
});
76+
77+
it('should error when class name contains invalid characters', async () => {
78+
const options = { ...defaultOptions, name: '1Clazz' };
79+
80+
await expectAsync(
81+
schematicRunner.runSchematicAsync('enum', options, appTree).toPromise(),
82+
).toBeRejectedWithError('Class name "1Clazz" is invalid.');
83+
});
7684
});

packages/schematics/angular/module/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
findModuleFromOptions,
3434
} from '../utility/find-module';
3535
import { parseName } from '../utility/parse-name';
36+
import { validateClassName } from '../utility/validation';
3637
import { createDefaultPath } from '../utility/workspace';
3738
import { Schema as ModuleOptions, RoutingScope } from './schema';
3839

@@ -149,6 +150,7 @@ export default function (options: ModuleOptions): Rule {
149150
const parsedPath = parseName(options.path, options.name);
150151
options.name = parsedPath.name;
151152
options.path = parsedPath.path;
153+
validateClassName(strings.classify(options.name));
152154

153155
const templateSource = apply(url('./files'), [
154156
options.routing || (isLazyLoadedModuleGen && routingModulePath)

packages/schematics/angular/module/index_spec.ts

+9
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ describe('Module Schematic', () => {
7171
expect(content).toMatch(/imports: \[[^\]]*FooModule[^\]]*\]/m);
7272
});
7373

74+
it('should import into another module when using flat', async () => {
75+
const options = { ...defaultOptions, flat: true, module: 'app.module.ts' };
76+
77+
const tree = await schematicRunner.runSchematicAsync('module', options, appTree).toPromise();
78+
const content = tree.readContent('/projects/bar/src/app/app.module.ts');
79+
expect(content).toMatch(/import { FooModule } from '.\/foo.module'/);
80+
expect(content).toMatch(/imports: \[[^\]]*FooModule[^\]]*\]/m);
81+
});
82+
7483
it('should import into another module (deep)', async () => {
7584
let tree = appTree;
7685

packages/schematics/angular/pipe/index.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import {
1010
Rule,
11-
SchematicsException,
1211
Tree,
1312
apply,
1413
applyTemplates,
@@ -25,6 +24,7 @@ import { addDeclarationToModule, addExportToModule } from '../utility/ast-utils'
2524
import { InsertChange } from '../utility/change';
2625
import { buildRelativePath, findModuleFromOptions } from '../utility/find-module';
2726
import { parseName } from '../utility/parse-name';
27+
import { validateClassName } from '../utility/validation';
2828
import { createDefaultPath } from '../utility/workspace';
2929
import { Schema as PipeOptions } from './schema';
3030

@@ -84,15 +84,13 @@ function addDeclarationToNgModule(options: PipeOptions): Rule {
8484

8585
export default function (options: PipeOptions): Rule {
8686
return async (host: Tree) => {
87-
if (options.path === undefined) {
88-
options.path = await createDefaultPath(host, options.project as string);
89-
}
90-
87+
options.path ??= await createDefaultPath(host, options.project as string);
9188
options.module = findModuleFromOptions(host, options);
9289

9390
const parsedPath = parseName(options.path, options.name);
9491
options.name = parsedPath.name;
9592
options.path = parsedPath.path;
93+
validateClassName(strings.classify(options.name));
9694

9795
const templateSource = apply(url('./files'), [
9896
options.skipTests ? filter((path) => !path.endsWith('.spec.ts.template')) : noop(),

packages/schematics/angular/pipe/index_spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,12 @@ describe('Pipe Schematic', () => {
154154
expect(pipeContent).toContain('class FooPipe');
155155
expect(moduleContent).not.toContain('FooPipe');
156156
});
157+
158+
it('should error when class name contains invalid characters', async () => {
159+
const options = { ...defaultOptions, name: '1Clazz' };
160+
161+
await expectAsync(
162+
schematicRunner.runSchematicAsync('pipe', options, appTree).toPromise(),
163+
).toBeRejectedWithError('Class name "1Clazz" is invalid.');
164+
});
157165
});

packages/schematics/angular/utility/generate-from-files.ts

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
url,
2121
} from '@angular-devkit/schematics';
2222
import { parseName } from './parse-name';
23+
import { validateClassName } from './validation';
2324
import { createDefaultPath } from './workspace';
2425

2526
export interface GenerateFromFilesOptions {
@@ -44,6 +45,8 @@ export function generateFromFiles(
4445
options.name = parsedPath.name;
4546
options.path = parsedPath.path;
4647

48+
validateClassName(strings.classify(options.name));
49+
4750
const templateSource = apply(url('./files'), [
4851
options.skipTests ? filter((path) => !path.endsWith('.spec.ts.template')) : noop(),
4952
applyTemplates({

packages/schematics/angular/utility/validation.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,17 @@ import { SchematicsException } from '@angular-devkit/schematics';
1212
// When adding a dash the segment after the dash must also start with a letter.
1313
export const htmlSelectorRe = /^[a-zA-Z][.0-9a-zA-Z]*(:?-[a-zA-Z][.0-9a-zA-Z]*)*$/;
1414

15+
// See: https://github.com/tc39/proposal-regexp-unicode-property-escapes/blob/fe6d07fad74cd0192d154966baa1e95e7cda78a1/README.md#other-examples
16+
const ecmaIdentifierNameRegExp = /^(?:[$_\p{ID_Start}])(?:[$_\u200C\u200D\p{ID_Continue}])*$/u;
17+
1518
export function validateHtmlSelector(selector: string): void {
1619
if (selector && !htmlSelectorRe.test(selector)) {
17-
throw new SchematicsException(`Selector (${selector}) is invalid.`);
20+
throw new SchematicsException(`Selector "${selector}" is invalid.`);
21+
}
22+
}
23+
24+
export function validateClassName(className: string): void {
25+
if (!ecmaIdentifierNameRegExp.test(className)) {
26+
throw new SchematicsException(`Class name "${className}" is invalid.`);
1827
}
1928
}

0 commit comments

Comments
 (0)