Skip to content

Commit

Permalink
fix(@schematics/angular): prevent numbers from class names
Browse files Browse the repository at this point in the history
With this change we prevent creating classes with invalid characters.

Closes #12868

(cherry picked from commit e995bda)
  • Loading branch information
alan-agius4 authored and clydin committed Jul 21, 2022
1 parent 44c1808 commit 83dcfb3
Show file tree
Hide file tree
Showing 9 changed files with 52 additions and 7 deletions.
8 changes: 8 additions & 0 deletions packages/schematics/angular/class/index_spec.ts
Expand Up @@ -109,4 +109,12 @@ describe('Class Schematic', () => {
expect(tree.files).toContain('/projects/bar/src/app/foo.ts');
expect(tree.files).not.toContain('/projects/bar/src/app/foo.spec.ts');
});

it('should error when class name contains invalid characters', async () => {
const options = { ...defaultOptions, name: '1Clazz' };

await expectAsync(
schematicRunner.runSchematicAsync('class', options, appTree).toPromise(),
).toBeRejectedWithError('Class name "1Clazz" is invalid.');
});
});
2 changes: 1 addition & 1 deletion packages/schematics/angular/component/index_spec.ts
Expand Up @@ -209,7 +209,7 @@ describe('Component Schematic', () => {

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

it('should use the default project prefix if none is passed', async () => {
Expand Down
8 changes: 8 additions & 0 deletions packages/schematics/angular/enum/index_spec.ts
Expand Up @@ -73,4 +73,12 @@ describe('Enum Schematic', () => {
const tree = await schematicRunner.runSchematicAsync('enum', options, appTree).toPromise();
expect(tree.files).toContain('/projects/bar/src/app/foo.enum.ts');
});

it('should error when class name contains invalid characters', async () => {
const options = { ...defaultOptions, name: '1Clazz' };

await expectAsync(
schematicRunner.runSchematicAsync('enum', options, appTree).toPromise(),
).toBeRejectedWithError('Class name "1Clazz" is invalid.');
});
});
2 changes: 2 additions & 0 deletions packages/schematics/angular/module/index.ts
Expand Up @@ -33,6 +33,7 @@ import {
findModuleFromOptions,
} from '../utility/find-module';
import { parseName } from '../utility/parse-name';
import { validateClassName } from '../utility/validation';
import { createDefaultPath } from '../utility/workspace';
import { Schema as ModuleOptions, RoutingScope } from './schema';

Expand Down Expand Up @@ -149,6 +150,7 @@ export default function (options: ModuleOptions): Rule {
const parsedPath = parseName(options.path, options.name);
options.name = parsedPath.name;
options.path = parsedPath.path;
validateClassName(strings.classify(options.name));

const templateSource = apply(url('./files'), [
options.routing || (isLazyLoadedModuleGen && routingModulePath)
Expand Down
9 changes: 9 additions & 0 deletions packages/schematics/angular/module/index_spec.ts
Expand Up @@ -71,6 +71,15 @@ describe('Module Schematic', () => {
expect(content).toMatch(/imports: \[[^\]]*FooModule[^\]]*\]/m);
});

it('should import into another module when using flat', async () => {
const options = { ...defaultOptions, flat: true, module: 'app.module.ts' };

const tree = await schematicRunner.runSchematicAsync('module', options, appTree).toPromise();
const content = tree.readContent('/projects/bar/src/app/app.module.ts');
expect(content).toMatch(/import { FooModule } from '.\/foo.module'/);
expect(content).toMatch(/imports: \[[^\]]*FooModule[^\]]*\]/m);
});

it('should import into another module (deep)', async () => {
let tree = appTree;

Expand Down
8 changes: 3 additions & 5 deletions packages/schematics/angular/pipe/index.ts
Expand Up @@ -8,7 +8,6 @@

import {
Rule,
SchematicsException,
Tree,
apply,
applyTemplates,
Expand All @@ -25,6 +24,7 @@ import { addDeclarationToModule, addExportToModule } from '../utility/ast-utils'
import { InsertChange } from '../utility/change';
import { buildRelativePath, findModuleFromOptions } from '../utility/find-module';
import { parseName } from '../utility/parse-name';
import { validateClassName } from '../utility/validation';
import { createDefaultPath } from '../utility/workspace';
import { Schema as PipeOptions } from './schema';

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

export default function (options: PipeOptions): Rule {
return async (host: Tree) => {
if (options.path === undefined) {
options.path = await createDefaultPath(host, options.project as string);
}

options.path ??= await createDefaultPath(host, options.project as string);
options.module = findModuleFromOptions(host, options);

const parsedPath = parseName(options.path, options.name);
options.name = parsedPath.name;
options.path = parsedPath.path;
validateClassName(strings.classify(options.name));

const templateSource = apply(url('./files'), [
options.skipTests ? filter((path) => !path.endsWith('.spec.ts.template')) : noop(),
Expand Down
8 changes: 8 additions & 0 deletions packages/schematics/angular/pipe/index_spec.ts
Expand Up @@ -154,4 +154,12 @@ describe('Pipe Schematic', () => {
expect(pipeContent).toContain('class FooPipe');
expect(moduleContent).not.toContain('FooPipe');
});

it('should error when class name contains invalid characters', async () => {
const options = { ...defaultOptions, name: '1Clazz' };

await expectAsync(
schematicRunner.runSchematicAsync('pipe', options, appTree).toPromise(),
).toBeRejectedWithError('Class name "1Clazz" is invalid.');
});
});
3 changes: 3 additions & 0 deletions packages/schematics/angular/utility/generate-from-files.ts
Expand Up @@ -20,6 +20,7 @@ import {
url,
} from '@angular-devkit/schematics';
import { parseName } from './parse-name';
import { validateClassName } from './validation';
import { createDefaultPath } from './workspace';

export interface GenerateFromFilesOptions {
Expand All @@ -44,6 +45,8 @@ export function generateFromFiles(
options.name = parsedPath.name;
options.path = parsedPath.path;

validateClassName(strings.classify(options.name));

const templateSource = apply(url('./files'), [
options.skipTests ? filter((path) => !path.endsWith('.spec.ts.template')) : noop(),
applyTemplates({
Expand Down
11 changes: 10 additions & 1 deletion packages/schematics/angular/utility/validation.ts
Expand Up @@ -12,8 +12,17 @@ import { SchematicsException } from '@angular-devkit/schematics';
// When adding a dash the segment after the dash must also start with a letter.
export const htmlSelectorRe = /^[a-zA-Z][.0-9a-zA-Z]*(:?-[a-zA-Z][.0-9a-zA-Z]*)*$/;

// See: https://github.com/tc39/proposal-regexp-unicode-property-escapes/blob/fe6d07fad74cd0192d154966baa1e95e7cda78a1/README.md#other-examples
const ecmaIdentifierNameRegExp = /^(?:[$_\p{ID_Start}])(?:[$_\u200C\u200D\p{ID_Continue}])*$/u;

export function validateHtmlSelector(selector: string): void {
if (selector && !htmlSelectorRe.test(selector)) {
throw new SchematicsException(`Selector (${selector}) is invalid.`);
throw new SchematicsException(`Selector "${selector}" is invalid.`);
}
}

export function validateClassName(className: string): void {
if (!ecmaIdentifierNameRegExp.test(className)) {
throw new SchematicsException(`Class name "${className}" is invalid.`);
}
}

0 comments on commit 83dcfb3

Please sign in to comment.