mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-18 03:23:57 +08:00
Code related to decoding buffers into strings and parsing content into JSON can now be removed by using the support provided directly from the Tree instance for the executing schematic.
158 lines
4.9 KiB
TypeScript
158 lines
4.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* 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 {
|
|
FileOperator,
|
|
Rule,
|
|
SchematicsException,
|
|
Tree,
|
|
apply,
|
|
applyTemplates,
|
|
chain,
|
|
filter,
|
|
forEach,
|
|
mergeWith,
|
|
move,
|
|
noop,
|
|
strings,
|
|
url,
|
|
} from '@angular-devkit/schematics';
|
|
import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript';
|
|
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 { validateHtmlSelector } from '../utility/validation';
|
|
import { buildDefaultPath, getWorkspace } from '../utility/workspace';
|
|
import { Schema as ComponentOptions, Style } from './schema';
|
|
|
|
function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile {
|
|
const sourceText = host.readText(modulePath);
|
|
|
|
return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
|
|
}
|
|
|
|
function addDeclarationToNgModule(options: ComponentOptions): Rule {
|
|
return (host: Tree) => {
|
|
if (options.skipImport || !options.module) {
|
|
return host;
|
|
}
|
|
|
|
options.type = options.type != null ? options.type : 'Component';
|
|
|
|
const modulePath = options.module;
|
|
const source = readIntoSourceFile(host, modulePath);
|
|
|
|
const componentPath =
|
|
`/${options.path}/` +
|
|
(options.flat ? '' : strings.dasherize(options.name) + '/') +
|
|
strings.dasherize(options.name) +
|
|
(options.type ? '.' : '') +
|
|
strings.dasherize(options.type);
|
|
const relativePath = buildRelativePath(modulePath, componentPath);
|
|
const classifiedName = strings.classify(options.name) + strings.classify(options.type);
|
|
const declarationChanges = addDeclarationToModule(
|
|
source,
|
|
modulePath,
|
|
classifiedName,
|
|
relativePath,
|
|
);
|
|
|
|
const declarationRecorder = host.beginUpdate(modulePath);
|
|
for (const change of declarationChanges) {
|
|
if (change instanceof InsertChange) {
|
|
declarationRecorder.insertLeft(change.pos, change.toAdd);
|
|
}
|
|
}
|
|
host.commitUpdate(declarationRecorder);
|
|
|
|
if (options.export) {
|
|
// Need to refresh the AST because we overwrote the file in the host.
|
|
const source = readIntoSourceFile(host, modulePath);
|
|
|
|
const exportRecorder = host.beginUpdate(modulePath);
|
|
const exportChanges = addExportToModule(
|
|
source,
|
|
modulePath,
|
|
strings.classify(options.name) + strings.classify(options.type),
|
|
relativePath,
|
|
);
|
|
|
|
for (const change of exportChanges) {
|
|
if (change instanceof InsertChange) {
|
|
exportRecorder.insertLeft(change.pos, change.toAdd);
|
|
}
|
|
}
|
|
host.commitUpdate(exportRecorder);
|
|
}
|
|
|
|
return host;
|
|
};
|
|
}
|
|
|
|
function buildSelector(options: ComponentOptions, projectPrefix: string) {
|
|
let selector = strings.dasherize(options.name);
|
|
if (options.prefix) {
|
|
selector = `${options.prefix}-${selector}`;
|
|
} else if (options.prefix === undefined && projectPrefix) {
|
|
selector = `${projectPrefix}-${selector}`;
|
|
}
|
|
|
|
return selector;
|
|
}
|
|
|
|
export default function (options: ComponentOptions): Rule {
|
|
return async (host: Tree) => {
|
|
const workspace = await getWorkspace(host);
|
|
const project = workspace.projects.get(options.project as string);
|
|
|
|
if (!project) {
|
|
throw new SchematicsException(`Project "${options.project}" does not exist.`);
|
|
}
|
|
|
|
if (options.path === undefined) {
|
|
options.path = buildDefaultPath(project);
|
|
}
|
|
|
|
options.module = findModuleFromOptions(host, options);
|
|
|
|
const parsedPath = parseName(options.path as string, options.name);
|
|
options.name = parsedPath.name;
|
|
options.path = parsedPath.path;
|
|
options.selector =
|
|
options.selector || buildSelector(options, (project && project.prefix) || '');
|
|
|
|
validateHtmlSelector(options.selector);
|
|
|
|
const skipStyleFile = options.inlineStyle || options.style === Style.None;
|
|
const templateSource = apply(url('./files'), [
|
|
options.skipTests ? filter((path) => !path.endsWith('.spec.ts.template')) : noop(),
|
|
skipStyleFile ? filter((path) => !path.endsWith('.__style__.template')) : noop(),
|
|
options.inlineTemplate ? filter((path) => !path.endsWith('.html.template')) : noop(),
|
|
applyTemplates({
|
|
...strings,
|
|
'if-flat': (s: string) => (options.flat ? '' : s),
|
|
...options,
|
|
}),
|
|
!options.type
|
|
? forEach(((file) => {
|
|
return file.path.includes('..')
|
|
? {
|
|
content: file.content,
|
|
path: file.path.replace('..', '.'),
|
|
}
|
|
: file;
|
|
}) as FileOperator)
|
|
: noop(),
|
|
move(parsedPath.path),
|
|
]);
|
|
|
|
return chain([addDeclarationToNgModule(options), mergeWith(templateSource)]);
|
|
};
|
|
}
|