Skip to content
This repository was archived by the owner on Apr 4, 2025. It is now read-only.

Commit e7f3663

Browse files
filipesilvahansl
authored andcommitted
feat(@angular-devkit/schematics): add custom schema formats
1 parent eb87ca4 commit e7f3663

File tree

11 files changed

+287
-3
lines changed

11 files changed

+287
-3
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { schema } from '@angular-devkit/core';
10+
import { htmlSelectorRe } from './html-selector';
11+
12+
13+
const unsupportedProjectNames = ['test', 'ember', 'ember-cli', 'vendor', 'app'];
14+
15+
export const appNameFormat: schema.SchemaFormat = {
16+
name: 'app-name',
17+
formatter: {
18+
async: false,
19+
validate: (appName: string) => htmlSelectorRe.test(appName)
20+
&& unsupportedProjectNames.indexOf(appName) === -1,
21+
},
22+
};
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { appNameFormat } from './app-name';
10+
import { formatValidator } from './format-validator';
11+
12+
13+
describe('Schematics app name format', () => {
14+
it('accepts correct app name', done => {
15+
const data = { appName: 'my-app' };
16+
const dataSchema = {
17+
properties: { appName: { type: 'string', format: 'app-name' } },
18+
};
19+
20+
formatValidator(data, dataSchema, [appNameFormat])
21+
.map(result => expect(result.success).toBe(true))
22+
.subscribe(done, done.fail);
23+
});
24+
25+
it('rejects app name starting with invalid characters', done => {
26+
const data = { appName: 'my-app$' };
27+
const dataSchema = {
28+
properties: { appName: { type: 'string', format: 'app-name' } },
29+
};
30+
31+
formatValidator(data, dataSchema, [appNameFormat])
32+
.map(result => expect(result.success).toBe(false))
33+
.subscribe(done, done.fail);
34+
});
35+
36+
it('rejects app name starting with number', done => {
37+
const data = { appName: '1app' };
38+
const dataSchema = {
39+
properties: { appName: { type: 'string', format: 'app-name' } },
40+
};
41+
42+
formatValidator(data, dataSchema, [appNameFormat])
43+
.map(result => expect(result.success).toBe(false))
44+
.subscribe(done, done.fail);
45+
});
46+
47+
it('rejects unsupported app names', done => {
48+
const data = {
49+
appName1: 'test',
50+
appName2: 'ember',
51+
appName3: 'ember-cli',
52+
appName4: 'vendor',
53+
appName5: 'app',
54+
};
55+
const dataSchema = {
56+
properties: {
57+
appName1: { type: 'string', format: 'app-name' },
58+
appName2: { type: 'string', format: 'app-name' },
59+
appName3: { type: 'string', format: 'app-name' },
60+
appName4: { type: 'string', format: 'app-name' },
61+
appName5: { type: 'string', format: 'app-name' },
62+
},
63+
};
64+
65+
formatValidator(data, dataSchema, [appNameFormat])
66+
.map(result => expect(result.success).toBe(false))
67+
.subscribe(done, done.fail);
68+
});
69+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { schema } from '@angular-devkit/core';
10+
import { Observable } from 'rxjs/Observable';
11+
import 'rxjs/add/operator/mergeMap';
12+
13+
14+
export function formatValidator(
15+
data: Object,
16+
dataSchema: Object,
17+
formats: schema.SchemaFormat[],
18+
): Observable<schema.SchemaValidatorResult> {
19+
const registry = new schema.CoreSchemaRegistry();
20+
21+
for (const format of formats) {
22+
registry.addFormat(format);
23+
}
24+
25+
return registry
26+
.compile(dataSchema)
27+
.mergeMap(validator => validator(data));
28+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { schema } from '@angular-devkit/core';
10+
11+
12+
// Must start with a letter, and must contain only alphanumeric characters or dashes.
13+
// When adding a dash the segment after the dash must also start with a letter.
14+
export const htmlSelectorRe = /^[a-zA-Z][.0-9a-zA-Z]*(:?-[a-zA-Z][.0-9a-zA-Z]*)*$/;
15+
16+
export const htmlSelectorFormat: schema.SchemaFormat = {
17+
name: 'html-selector',
18+
formatter: {
19+
async: false,
20+
validate: (selector: string) => htmlSelectorRe.test(selector),
21+
},
22+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { formatValidator } from './format-validator';
10+
import { htmlSelectorFormat } from './html-selector';
11+
12+
13+
describe('Schematics HTML selector format', () => {
14+
it('accepts correct selectors', done => {
15+
const data = { selector: 'my-selector' };
16+
const dataSchema = {
17+
properties: { selector: { type: 'string', format: 'html-selector' } },
18+
};
19+
20+
formatValidator(data, dataSchema, [htmlSelectorFormat])
21+
.map(result => expect(result.success).toBe(true))
22+
.subscribe(done, done.fail);
23+
});
24+
25+
it('rejects selectors starting with invalid characters', done => {
26+
const data = { selector: 'my-selector$' };
27+
const dataSchema = {
28+
properties: { selector: { type: 'string', format: 'html-selector' } },
29+
};
30+
31+
formatValidator(data, dataSchema, [htmlSelectorFormat])
32+
.map(result => expect(result.success).toBe(false))
33+
.subscribe(done, done.fail);
34+
});
35+
36+
it('rejects selectors starting with number', done => {
37+
const data = { selector: '1selector' };
38+
const dataSchema = {
39+
properties: { selector: { type: 'string', format: 'html-selector' } },
40+
};
41+
42+
formatValidator(data, dataSchema, [htmlSelectorFormat])
43+
.map(result => expect(result.success).toBe(false))
44+
.subscribe(done, done.fail);
45+
});
46+
47+
it('rejects selectors with non-letter after dash', done => {
48+
const data = { selector: 'my-1selector' };
49+
const dataSchema = {
50+
properties: { selector: { type: 'string', format: 'html-selector' } },
51+
};
52+
53+
formatValidator(data, dataSchema, [htmlSelectorFormat])
54+
.map(result => expect(result.success).toBe(false))
55+
.subscribe(done, done.fail);
56+
});
57+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './app-name';
10+
export * from './html-selector';
11+
export * from './path';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { normalize, schema } from '@angular-devkit/core';
10+
11+
12+
export const pathFormat: schema.SchemaFormat = {
13+
name: 'path',
14+
formatter: {
15+
async: false,
16+
validate: (path: string) => {
17+
// Check path is normalized already.
18+
return path === normalize(path);
19+
// TODO: check if path is valid (is that just checking if it's normalized?)
20+
// TODO: check path is from root of schematics even if passed absolute
21+
// TODO: error out if path is outside of host
22+
},
23+
},
24+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { formatValidator } from './format-validator';
10+
import { pathFormat } from './path';
11+
12+
13+
describe('Schematics Path format', () => {
14+
it('accepts correct Paths', done => {
15+
const data = { path: 'a/b/c' };
16+
const dataSchema = {
17+
properties: { path: { type: 'string', format: 'path' } },
18+
};
19+
20+
formatValidator(data, dataSchema, [pathFormat])
21+
.map(result => expect(result.success).toBe(true))
22+
.subscribe(done, done.fail);
23+
});
24+
25+
it('rejects Paths that are not normalized', done => {
26+
const data = { path: 'a/b/c/../' };
27+
const dataSchema = {
28+
properties: { path: { type: 'string', format: 'path' } },
29+
};
30+
31+
formatValidator(data, dataSchema, [pathFormat])
32+
.map(result => expect(result.success).toBe(false))
33+
.subscribe(done, done.fail);
34+
});
35+
});

packages/angular_devkit/schematics/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export {UpdateRecorder} from './tree/interface';
3333
export * from './engine/schematic';
3434
export * from './sink/dryrun';
3535
export {FileSystemSink} from './sink/filesystem';
36+
import * as formats from './formats';
37+
export { formats };
3638

3739

3840
export interface TreeConstructor {

packages/angular_devkit/schematics/testing/schematic-test-runner.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
SchematicEngine,
1616
Tree,
1717
VirtualTree,
18+
formats,
1819
} from '@angular-devkit/schematics';
1920
import {
2021
NodeModulesTestEngineHost,
@@ -43,8 +44,14 @@ export class SchematicTestRunner {
4344
this._engineHost.registerCollection(_collectionName, collectionPath);
4445
this._logger = new logging.Logger('test');
4546

46-
this._engineHost.registerOptionsTransform(
47-
validateOptionsWithSchema(new schema.CoreSchemaRegistry()));
47+
const schemaFormats = [
48+
formats.appNameFormat,
49+
formats.htmlSelectorFormat,
50+
formats.pathFormat,
51+
];
52+
const registry = new schema.CoreSchemaRegistry(schemaFormats);
53+
54+
this._engineHost.registerOptionsTransform(validateOptionsWithSchema(registry));
4855
this._collection = this._engine.createCollection(this._collectionName);
4956
}
5057

packages/angular_devkit/schematics_cli/bin/schematics.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
FileSystemTree,
2020
SchematicEngine,
2121
Tree,
22+
formats,
2223
} from '@angular-devkit/schematics';
2324
import {
2425
FileSystemHost,
@@ -128,7 +129,13 @@ const engine = new SchematicEngine(engineHost);
128129

129130

130131
// Add support for schemaJson.
131-
engineHost.registerOptionsTransform(validateOptionsWithSchema(new schema.CoreSchemaRegistry()));
132+
const schemaFormats = [
133+
formats.appNameFormat,
134+
formats.htmlSelectorFormat,
135+
formats.pathFormat,
136+
];
137+
const registry = new schema.CoreSchemaRegistry(schemaFormats);
138+
engineHost.registerOptionsTransform(validateOptionsWithSchema(registry));
132139

133140

134141
/**

0 commit comments

Comments
 (0)