refactor: move generation rules to separate functions

pull/5137/head
Arman Ozak 5 years ago
parent 5bfc5dcf94
commit a3108f86ab

@ -1,9 +1,33 @@
import { normalize, strings } from '@angular-devkit/core';
import { applyTemplates, branchAndMerge, chain, move, SchematicContext, SchematicsException, Tree, url } from '@angular-devkit/schematics';
import {
applyTemplates,
branchAndMerge,
chain,
move,
Rule,
SchematicContext,
SchematicsException,
Tree,
url,
} from '@angular-devkit/schematics';
import { Exception } from '../../enums';
import { applyWithOverwrite, buildDefaultPath, createApiDefinitionReader, createControllerToServiceMapper, createImportRefsToModelMapper, createImportRefToEnumMapper, getEnumNamesFromImports, interpolate, resolveProject, serializeParameters } from '../../utils';
import { ServiceGeneratorParams } from '../../models';
import {
applyWithOverwrite,
buildDefaultPath,
createApiDefinitionReader,
createControllerToServiceMapper,
createImportRefsToModelMapper,
createImportRefToEnumMapper,
EnumGeneratorParams,
getEnumNamesFromImports,
interpolate,
ModelGeneratorParams,
resolveProject,
serializeParameters,
} from '../../utils';
import * as cases from '../../utils/text';
import type { Schema as GenerateProxySchema } from './schema';
import { Schema as GenerateProxySchema } from './schema';
export default function(params: GenerateProxySchema) {
const solution = params.solution;
@ -13,79 +37,124 @@ export default function(params: GenerateProxySchema) {
async (tree: Tree, _context: SchematicContext) => {
const target = await resolveProject(tree, params.target!);
const targetPath = buildDefaultPath(target.definition);
const readApiDefinition = createApiDefinitionReader(`${targetPath}/shared/api-definition.json`);
const readApiDefinition = createApiDefinitionReader(
`${targetPath}/shared/api-definition.json`,
);
const data = readApiDefinition(tree);
const types = data.types;
const definition = data.modules[moduleName];
if (!definition) throw new SchematicsException(interpolate(Exception.InvalidModule, moduleName));
if (!definition)
throw new SchematicsException(interpolate(Exception.InvalidModule, moduleName));
const mapControllerToService = createControllerToServiceMapper(solution, types, definition.remoteServiceName);
const apiName = definition.remoteServiceName;
const controllers = Object.values(definition.controllers || {});
const serviceImports: Record<string, string[]> = {};
const generateServices = createServiceGenerator({
targetPath,
solution,
types,
apiName,
controllers,
serviceImports,
});
const createServiceFiles = chain(
controllers.map(controller => {
const service = mapControllerToService(controller);
service.imports.forEach(({refs, path}) => refs.forEach(ref => {
if (path === '@abp/ng.core') return;
if (!serviceImports[path]) return (serviceImports[path] = [ref]);
serviceImports[path] = [...new Set([...serviceImports[path], ref])];
}));
const modelImports: Record<string, string[]> = {};
const generateModels = createModelGenerator({
targetPath,
solution,
types,
serviceImports,
modelImports,
});
return applyWithOverwrite(url('./files-service'), [
applyTemplates({
...cases,
serializeParameters,
...service,
}),
move(normalize(targetPath)),
]);
}
),
);
const generateEnums = createEnumGenerator({
targetPath,
solution,
types,
serviceImports,
modelImports,
});
const mapImportRefsToModel = createImportRefsToModelMapper(solution, types);
const modelImports: Record<string, string[]> = {};
return branchAndMerge(chain([generateServices, generateModels, generateEnums]));
},
]);
}
const createModelFiles = chain(
Object.values(serviceImports).map(refs => {
const model = mapImportRefsToModel(refs);
model.imports.forEach(({refs, path}) => refs.forEach(ref => {
if (path === '@abp/ng.core') return;
if (!modelImports[path]) return (modelImports[path] = [ref]);
modelImports[path] = [...new Set([...modelImports[path], ref])];
}));
function createEnumGenerator(params: EnumGeneratorParams) {
const { targetPath, serviceImports, modelImports } = params;
const mapImportRefToEnum = createImportRefToEnumMapper(params);
const enumRefs = [
...new Set([
...getEnumNamesFromImports(serviceImports),
...getEnumNamesFromImports(modelImports),
]),
];
return applyWithOverwrite(url('./files-model'), [
applyTemplates({
...cases,
...model,
}),
move(normalize(targetPath)),
]);
return chain(
enumRefs.map(ref => {
return applyWithOverwrite(url('./files-enum'), [
applyTemplates({
...cases,
...mapImportRefToEnum(ref),
}),
);
move(normalize(targetPath)),
]);
}),
);
}
const mapImportRefToEnum = createImportRefToEnumMapper(solution, types);
const enumRefs = [...new Set([
...getEnumNamesFromImports(serviceImports),
...getEnumNamesFromImports(modelImports),
])];
function createModelGenerator(params: ModelGeneratorParams) {
const { targetPath, serviceImports, modelImports } = params;
const mapImportRefsToModel = createImportRefsToModelMapper(params);
const createEnumFiles = chain(
enumRefs.map(ref => {
return applyWithOverwrite(url('./files-enum'), [
applyTemplates({
...cases,
...mapImportRefToEnum(ref),
}),
move(normalize(targetPath)),
]);
return chain(
Object.values(serviceImports).reduce((rules: Rule[], refs) => {
const model = mapImportRefsToModel(refs);
model.imports.forEach(({ refs, path }) =>
refs.forEach(ref => {
if (path === '@abp/ng.core') return;
if (!modelImports[path]) return (modelImports[path] = [ref]);
modelImports[path] = [...new Set([...modelImports[path], ref])];
}),
);
return branchAndMerge(chain([createServiceFiles, createModelFiles, createEnumFiles]));
},
]);
const rule = applyWithOverwrite(url('./files-model'), [
applyTemplates({
...cases,
...model,
}),
move(normalize(targetPath)),
]);
rules.push(rule);
return rules;
}, []),
);
}
function createServiceGenerator(params: ServiceGeneratorParams) {
const { targetPath, controllers, serviceImports } = params;
const mapControllerToService = createControllerToServiceMapper(params);
return chain(
controllers.map(controller => {
const service = mapControllerToService(controller);
service.imports.forEach(({ refs, path }) =>
refs.forEach(ref => {
if (path === '@abp/ng.core') return;
if (!serviceImports[path]) return (serviceImports[path] = [ref]);
serviceImports[path] = [...new Set([...serviceImports[path], ref])];
}),
);
return applyWithOverwrite(url('./files-service'), [
applyTemplates({
...cases,
serializeParameters,
...service,
}),
move(normalize(targetPath)),
]);
}),
);
}

@ -1,9 +1,21 @@
import { strings } from '@angular-devkit/core';
import { branchAndMerge, chain, schematic, SchematicContext, Tree } from '@angular-devkit/schematics';
import {
branchAndMerge,
chain,
schematic,
SchematicContext,
Tree,
} from '@angular-devkit/schematics';
import { API_DEFINITION_ENDPOINT } from '../../constants';
import { ApiDefinition } from '../../models';
import { buildDefaultPath, createApiDefinitionSaver, getApiDefinition, getSourceUrl, resolveProject } from '../../utils';
import type { Schema as GenerateProxySchema } from './schema';
import {
buildDefaultPath,
createApiDefinitionSaver,
getApiDefinition,
getSourceUrl,
resolveProject,
} from '../../utils';
import { Schema as GenerateProxySchema } from './schema';
export default function(params: GenerateProxySchema) {
const moduleName = strings.camelize(params.module || 'app');
@ -16,7 +28,10 @@ export default function(params: GenerateProxySchema) {
const targetPath = buildDefaultPath(target.definition);
const data: ApiDefinition = await getApiDefinition(sourceUrl + API_DEFINITION_ENDPOINT);
const saveApiDefinition = createApiDefinitionSaver(data, `${targetPath}/shared/api-definition.json`)
const saveApiDefinition = createApiDefinitionSaver(
data,
`${targetPath}/shared/api-definition.json`,
);
const createApi = schematic('api', params);
return branchAndMerge(chain([saveApiDefinition, createApi]));

@ -1,7 +1,17 @@
import { Controller, Type } from './api-definition';
import { Import } from './import';
import { Method } from './method';
import { Omissible } from './util';
export interface ServiceGeneratorParams {
targetPath: string;
solution: string;
types: Record<string, Type>;
apiName: string;
controllers: Controller[];
serviceImports: Record<string, string[]>;
}
export class Service {
apiName: string;
imports: Import[] = [];

@ -4,6 +4,14 @@ import { Type } from '../models';
import { interpolate } from './common';
import { parseNamespace } from './namespace';
export interface EnumGeneratorParams {
targetPath: string;
solution: string;
types: Record<string, Type>;
serviceImports: Record<string, string[]>;
modelImports: Record<string, string[]>;
}
export function getEnumNamesFromImports(serviceImports: Record<string, string[]>) {
return Object.keys(serviceImports)
.filter(path => path.includes('/enums/'))
@ -13,7 +21,7 @@ export function getEnumNamesFromImports(serviceImports: Record<string, string[]>
}, []);
}
export function createImportRefToEnumMapper(solution: string, types: Record<string, Type>) {
export function createImportRefToEnumMapper({ solution, types }: EnumGeneratorParams) {
return (ref: string) => {
const { enumNames, enumValues } = types[ref];
if (!enumNames || !enumValues)

@ -9,10 +9,19 @@ import {
createTypesToImportsReducer,
flattenUnionTypes,
normalizeTypeAnnotations,
removeTypeModifiers,
} from './type';
export function createImportRefsToModelMapper(solution: string, types: Record<string, Type>) {
const mapImportRefToInterface = createImportRefToInterfaceMapper(solution, types);
export interface ModelGeneratorParams {
targetPath: string;
solution: string;
types: Record<string, Type>;
serviceImports: Record<string, string[]>;
modelImports: Record<string, string[]>;
}
export function createImportRefsToModelMapper({ solution, types }: ModelGeneratorParams) {
const mapImportRefToInterface = createImportRefToInterfaceMapper(types);
const createImportRefToImportReducer = createImportRefToImportReducerCreator(solution, types);
return (importRefs: string[]) => {
@ -53,8 +62,9 @@ function sortInterfaces(interfaces: Interface[]) {
interfaces.sort((a, b) => (a.identifier > b.identifier ? 1 : -1));
}
export function createImportRefToInterfaceMapper(solution: string, types: Record<string, Type>) {
const simplifyType = createTypeSimplifier(solution);
export function createImportRefToInterfaceMapper(types: Record<string, Type>) {
const simplifyType = createTypeSimplifier();
const getIdentifier = (type: string) => removeTypeModifiers(simplifyType(type));
return (ref: string) => {
const typeDef = types[ref];
@ -62,10 +72,10 @@ export function createImportRefToInterfaceMapper(solution: string, types: Record
const identifier = (typeDef.genericArguments ?? []).reduce(
(acc, t, i) => acc.replace(`T${i}`, t),
simplifyType(ref),
getIdentifier(ref),
);
const base = typeDef.baseType ? simplifyType(typeDef.baseType) : null;
const base = typeDef.baseType ? getIdentifier(typeDef.baseType) : null;
const _interface = new Interface({ identifier, base, ref });
typeDef.properties?.forEach(({ name, typeSimple }) => {

@ -6,6 +6,7 @@ import {
Method,
Property,
Service,
ServiceGeneratorParams,
Signature,
Type,
TypeWithEnum,
@ -19,12 +20,12 @@ export function serializeParameters(parameters: Property[]) {
return parameters.map(p => p.name + p.optional + ': ' + p.type + p.default, '').join(', ');
}
export function createControllerToServiceMapper(
solution: string,
types: Record<string, Type>,
apiName: string,
) {
const mapActionToMethod = createActionToMethodMapper(solution);
export function createControllerToServiceMapper({
solution,
types,
apiName,
}: ServiceGeneratorParams) {
const mapActionToMethod = createActionToMethodMapper();
return (controller: Controller) => {
const name = controller.controllerName;
@ -44,9 +45,9 @@ function sortMethods(methods: Method[]) {
methods.sort((a, b) => (a.signature.name > b.signature.name ? 1 : -1));
}
export function createActionToMethodMapper(solution: string) {
const mapActionToBody = createActionToBodyMapper(solution);
const mapActionToSignature = createActionToSignatureMapper(solution);
export function createActionToMethodMapper() {
const mapActionToBody = createActionToBodyMapper();
const mapActionToSignature = createActionToSignatureMapper();
return (action: Action) => {
const body = mapActionToBody(action);
@ -55,8 +56,8 @@ export function createActionToMethodMapper(solution: string) {
};
}
export function createActionToBodyMapper(solution: string) {
const adaptType = createTypeAdapter(solution);
export function createActionToBodyMapper() {
const adaptType = createTypeAdapter();
return ({ httpMethod, parameters, returnValue, url }: Action) => {
const responseType = adaptType(returnValue.typeSimple);
@ -68,8 +69,8 @@ export function createActionToBodyMapper(solution: string) {
};
}
export function createActionToSignatureMapper(solution: string) {
const adaptType = createTypeAdapter(solution);
export function createActionToSignatureMapper() {
const adaptType = createTypeAdapter();
return (action: Action) => {
const signature = new Signature({ name: getMethodNameFromAction(action) });

@ -6,25 +6,15 @@ import { parseNamespace } from './namespace';
import { relativePathToEnum, relativePathToModel } from './path';
import { parseGenerics } from './tree';
export function createTypeSimplifier(solution: string) {
const solutionRegex = new RegExp(solution.replace(/\./g, `\.`) + `\.`);
const voloRegex = /^Volo\.(Abp\.?)(Application|ObjectExtending\.?)/;
return createTypeParser(
type =>
type
.replace(voloRegex, '')
.replace(solutionRegex, '')
.split('.')
.pop()!,
);
export function createTypeSimplifier() {
return createTypeParser(type => type.split('.').pop()!);
}
export function createTypeParser(replacerFn = (t: string) => t) {
return (originalType: string) =>
flattenUnionTypes([], originalType)
.map(type => {
type = removeTypeModifiers(normalizeTypeAnnotations(type));
type = normalizeTypeAnnotations(type);
type = type.replace(
/System\.([0-9A-Za-z]+)/g,
(_, match) => SYSTEM_TYPES.get(match) ?? strings.camelize(match),
@ -78,15 +68,15 @@ export function createTypesToImportsReducer(solution: string, namespace: string)
}
export function createTypeToImportMapper(solution: string, namespace: string) {
const adaptType = createTypeAdapter(solution);
const simplifyType = createTypeSimplifier(solution);
const adaptType = createTypeAdapter();
const simplifyType = createTypeSimplifier();
return (type: string, isEnum: boolean) => {
if (!type || type.startsWith('System')) return;
const modelNamespace = parseNamespace(solution, type);
const refs = [removeTypeModifiers(type)];
const specifiers = [adaptType(simplifyType(type).split('<')[0])];
const specifiers = [adaptType(simplifyType(refs[0]).split('<')[0])];
const path = /^Volo\.Abp\.(Application\.Dtos|ObjectExtending)/.test(type)
? '@abp/ng.core'
: isEnum
@ -97,7 +87,7 @@ export function createTypeToImportMapper(solution: string, namespace: string) {
};
}
export function createTypeAdapter(solution: string) {
const simplifyType = createTypeSimplifier(solution);
export function createTypeAdapter() {
const simplifyType = createTypeSimplifier();
return (type: string) => parseGenerics(type, node => simplifyType(node.data)).toString();
}

@ -1,6 +1,5 @@
import { experimental, strings, workspaces } from '@angular-devkit/core';
import { SchematicsException } from '@angular-devkit/schematics';
import type { Tree } from '@angular-devkit/schematics';
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { Exception } from '../enums';
import { Project } from '../models';
import { getWorkspace, ProjectType } from './angular';
@ -35,10 +34,7 @@ export function readWorkspaceSchema(tree: Tree) {
return workspaceSchema;
}
export async function resolveProject(
tree: Tree,
name: string,
): Promise<Project> {
export async function resolveProject(tree: Tree, name: string): Promise<Project> {
name = name || readWorkspaceSchema(tree).defaultProject!;
const workspace = await getWorkspace(tree);
let definition: Project['definition'] | undefined;

Loading…
Cancel
Save