diff --git a/packages/angular_devkit/schematics/src/formats/html-selector.ts b/packages/angular_devkit/schematics/src/formats/html-selector.ts index 6d7d9ada9b0b..b048482d2eb7 100644 --- a/packages/angular_devkit/schematics/src/formats/html-selector.ts +++ b/packages/angular_devkit/schematics/src/formats/html-selector.ts @@ -5,18 +5,47 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - import { schema } from '@angular-devkit/core'; +// As per https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name +// * Without mandatory `-` as the application prefix will generally cover its inclusion +// * And an allowance for upper alpha characters + +// NOTE: This should eventually be broken out into two formats: full and partial (allows for prefix) + +const unicodeRanges = [ + [0xC0, 0xD6], + [0xD8, 0xF6], + [0xF8, 0x37D], + [0x37F, 0x1FFF], + [0x200C, 0x200D], + [0x203F, 0x2040], + [0x2070, 0x218F], + [0x2C00, 0x2FEF], + [0x3001, 0xD7FF], + [0xF900, 0xFDCF], + [0xFDF0, 0xFFFD], + [0x10000, 0xEFFFF], +]; + +function isValidElementName(name: string) { + let regex = '^[a-zA-Z]['; + + regex += '-.0-9_a-zA-Z\\u{B7}'; + + for (const range of unicodeRanges) { + regex += `\\u{${range[0].toString(16)}}-\\u{${range[1].toString(16)}}`; + } + + regex += ']*$'; -// Must start with a letter, and must contain only alphanumeric characters or dashes. -// 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]*)*$/; + return new RegExp(regex, 'u').test(name); +} export const htmlSelectorFormat: schema.SchemaFormat = { name: 'html-selector', formatter: { async: false, - validate: (selector: string) => htmlSelectorRe.test(selector), + validate: name => typeof name === 'string' && isValidElementName(name), }, }; diff --git a/packages/angular_devkit/schematics/src/formats/html-selector_spec.ts b/packages/angular_devkit/schematics/src/formats/html-selector_spec.ts index c59ad62d6641..411d1df67df9 100644 --- a/packages/angular_devkit/schematics/src/formats/html-selector_spec.ts +++ b/packages/angular_devkit/schematics/src/formats/html-selector_spec.ts @@ -5,12 +5,10 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - import { map } from 'rxjs/operators'; import { formatValidator } from './format-validator'; import { htmlSelectorFormat } from './html-selector'; - describe('Schematics HTML selector format', () => { it('accepts correct selectors', done => { const data = { selector: 'my-selector' }; @@ -45,14 +43,25 @@ describe('Schematics HTML selector format', () => { .toPromise().then(done, done.fail); }); - it('rejects selectors with non-letter after dash', done => { + it('accepts selectors with non-letter after dash', done => { const data = { selector: 'my-1selector' }; const dataSchema = { properties: { selector: { type: 'string', format: 'html-selector' } }, }; formatValidator(data, dataSchema, [htmlSelectorFormat]) - .pipe(map(result => expect(result.success).toBe(false))) + .pipe(map(result => expect(result.success).toBe(true))) + .toPromise().then(done, done.fail); + }); + + it('accepts selectors with unicode', done => { + const data = { selector: 'app-root😀' }; + const dataSchema = { + properties: { selector: { type: 'string', format: 'html-selector' } }, + }; + + formatValidator(data, dataSchema, [htmlSelectorFormat]) + .pipe(map(result => expect(result.success).toBe(true))) .toPromise().then(done, done.fail); }); });