|
|
@ -1,14 +1,13 @@ |
|
|
import { strings } from '@angular-devkit/core'; |
|
|
import { strings } from '@angular-devkit/core'; |
|
|
import { Import, Interface, Model, Property, Type, TypeWithEnum } from '../models'; |
|
|
import { VOLO_REGEX } from '../constants'; |
|
|
import { isEnumImport } from './enum'; |
|
|
import { Interface, Model, Property, Type, TypeWithEnum } from '../models'; |
|
|
import { parseNamespace } from './namespace'; |
|
|
import { parseNamespace } from './namespace'; |
|
|
import { relativePathToModel } from './path'; |
|
|
import { relativePathToModel } from './path'; |
|
|
import { parseGenerics } from './tree'; |
|
|
import { parseGenerics } from './tree'; |
|
|
import { |
|
|
import { |
|
|
|
|
|
createTypeParser, |
|
|
createTypeSimplifier, |
|
|
createTypeSimplifier, |
|
|
createTypesToImportsReducer, |
|
|
createTypesToImportsReducer, |
|
|
flattenUnionTypes, |
|
|
|
|
|
normalizeTypeAnnotations, |
|
|
|
|
|
removeTypeModifiers, |
|
|
removeTypeModifiers, |
|
|
} from './type'; |
|
|
} from './type'; |
|
|
|
|
|
|
|
|
@ -20,38 +19,69 @@ export interface ModelGeneratorParams { |
|
|
modelImports: Record<string, string[]>; |
|
|
modelImports: Record<string, string[]>; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function createImportRefsToModelMapper({ solution, types }: ModelGeneratorParams) { |
|
|
export function createImportRefsToModelReducer(params: ModelGeneratorParams) { |
|
|
const mapImportRefToInterface = createImportRefToInterfaceMapper(types); |
|
|
const reduceImportRefsToInterfaces = createImportRefToInterfaceReducerCreator(params); |
|
|
const createImportRefToImportReducer = createImportRefToImportReducerCreator(solution, types); |
|
|
const createRefToImportReducer = createRefToImportReducerCreator(params); |
|
|
|
|
|
const { solution, types } = params; |
|
|
return (importRefs: string[]) => { |
|
|
|
|
|
const namespace = parseNamespace(solution, importRefs[0]); |
|
|
return (models: Model[], importRefs: string[]) => { |
|
|
const path = relativePathToModel(namespace, namespace); |
|
|
const enums: string[] = []; |
|
|
const model = new Model({ namespace, path }); |
|
|
const interfaces = importRefs.reduce(reduceImportRefsToInterfaces, []); |
|
|
const imports: Import[] = []; |
|
|
sortInterfaces(interfaces); |
|
|
|
|
|
|
|
|
const reduceImportRefToImport = createImportRefToImportReducer(namespace, _import => { |
|
|
interfaces.forEach(_interface => { |
|
|
if (_import.path !== model.path) return true; |
|
|
if (VOLO_REGEX.test(_interface.ref)) return; |
|
|
|
|
|
|
|
|
_import.refs.reduce(reduceImportRefsToImportsAndInterfaces, imports); |
|
|
if (types[_interface.ref]!.isEnum) { |
|
|
return false; |
|
|
if (!enums.includes(_interface.ref)) enums.push(_interface.ref); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const index = models.findIndex(m => m.namespace === _interface.namespace); |
|
|
|
|
|
if (index > -1) { |
|
|
|
|
|
if (models[index].interfaces.some(i => i.identifier === _interface.identifier)) return; |
|
|
|
|
|
models[index].interfaces.push(_interface); |
|
|
|
|
|
} else { |
|
|
|
|
|
const { namespace } = _interface; |
|
|
|
|
|
|
|
|
|
|
|
models.push( |
|
|
|
|
|
new Model({ |
|
|
|
|
|
interfaces: [_interface], |
|
|
|
|
|
namespace, |
|
|
|
|
|
path: relativePathToModel(namespace, namespace), |
|
|
|
|
|
}), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
importRefs |
|
|
models.forEach(model => { |
|
|
.reduce(reduceImportRefsToImportsAndInterfaces, imports) |
|
|
let toBeImported: TypeWithEnum[] = []; |
|
|
.forEach(_import => model.imports.push(_import)); |
|
|
|
|
|
|
|
|
model.interfaces.forEach(_interface => { |
|
|
sortInterfaces(model.interfaces); |
|
|
const { baseType } = types[_interface.ref]; |
|
|
|
|
|
if (baseType && parseNamespace(solution, baseType) !== model.namespace) |
|
|
|
|
|
toBeImported.push({ |
|
|
|
|
|
type: parseGenerics(baseType) |
|
|
|
|
|
.toGenerics() |
|
|
|
|
|
.join(''), |
|
|
|
|
|
isEnum: false, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
_interface.properties.forEach(prop => { |
|
|
|
|
|
prop.refs.forEach(ref => { |
|
|
|
|
|
if (parseNamespace(solution, ref) !== model.namespace) |
|
|
|
|
|
toBeImported.push({ type: ref, isEnum: types[ref]?.isEnum }); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
return model; |
|
|
if (!toBeImported.length) return; |
|
|
|
|
|
|
|
|
function reduceImportRefsToImportsAndInterfaces(accumulatedImports: Import[], ref: string) { |
|
|
const reduceRefToImport = createRefToImportReducer(model.namespace); |
|
|
if (model.interfaces.some(i => i.ref === ref)) return accumulatedImports; |
|
|
reduceRefToImport(model.imports, toBeImported); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const _interface = mapImportRefToInterface(ref); |
|
|
return models; |
|
|
if (_interface && !types[ref].isEnum) model.interfaces.push(_interface); |
|
|
|
|
|
return reduceImportRefToImport(accumulatedImports, ref); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -59,13 +89,19 @@ function sortInterfaces(interfaces: Interface[]) { |
|
|
interfaces.sort((a, b) => (a.identifier > b.identifier ? 1 : -1)); |
|
|
interfaces.sort((a, b) => (a.identifier > b.identifier ? 1 : -1)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function createImportRefToInterfaceMapper(types: Record<string, Type>) { |
|
|
export function createImportRefToInterfaceReducerCreator(params: ModelGeneratorParams) { |
|
|
|
|
|
const { solution, types } = params; |
|
|
|
|
|
const parseType = createTypeParser(removeTypeModifiers); |
|
|
const simplifyType = createTypeSimplifier(); |
|
|
const simplifyType = createTypeSimplifier(); |
|
|
const getIdentifier = (type: string) => removeTypeModifiers(simplifyType(type)); |
|
|
const getIdentifier = (type: string) => removeTypeModifiers(simplifyType(type)); |
|
|
|
|
|
|
|
|
return (ref: string) => { |
|
|
return reduceRefsToInterfaces; |
|
|
|
|
|
|
|
|
|
|
|
function reduceRefsToInterfaces(interfaces: Interface[], ref: string) { |
|
|
const typeDef = types[ref]; |
|
|
const typeDef = types[ref]; |
|
|
if (!typeDef) return; |
|
|
if (!typeDef) return interfaces; |
|
|
|
|
|
|
|
|
|
|
|
const namespace = parseNamespace(solution, ref); |
|
|
|
|
|
|
|
|
const identifier = (typeDef.genericArguments ?? []).reduce( |
|
|
const identifier = (typeDef.genericArguments ?? []).reduce( |
|
|
(acc, t, i) => acc.replace(`T${i}`, t), |
|
|
(acc, t, i) => acc.replace(`T${i}`, t), |
|
|
@ -73,74 +109,33 @@ export function createImportRefToInterfaceMapper(types: Record<string, Type>) { |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
const base = typeDef.baseType ? getIdentifier(typeDef.baseType) : null; |
|
|
const base = typeDef.baseType ? getIdentifier(typeDef.baseType) : null; |
|
|
const _interface = new Interface({ identifier, base, ref }); |
|
|
const _interface = new Interface({ identifier, base, namespace, ref }); |
|
|
|
|
|
|
|
|
typeDef.properties?.forEach(({ name, typeSimple }) => { |
|
|
typeDef.properties?.forEach(prop => { |
|
|
name = strings.camelize(name); |
|
|
const name = strings.camelize(prop.name); |
|
|
const optional = typeSimple.endsWith('?') ? '?' : ''; |
|
|
const optional = prop.typeSimple.endsWith('?') ? '?' : ''; |
|
|
const type = simplifyType(typeSimple); |
|
|
const type = simplifyType(prop.typeSimple); |
|
|
|
|
|
const refs = parseType(prop.type).reduce( |
|
|
_interface.properties.push(new Property({ name, optional, type })); |
|
|
(acc: string[], r) => acc.concat(parseGenerics(r).toGenerics()), |
|
|
|
|
|
[], |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
_interface.properties.push(new Property({ name, optional, type, refs })); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
return _interface; |
|
|
interfaces.push(_interface); |
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function createImportRefToImportReducerCreator( |
|
|
|
|
|
solution: string, |
|
|
|
|
|
types: Record<string, Type>, |
|
|
|
|
|
) { |
|
|
|
|
|
return (namespace: string, filterFn: (imports: Import) => boolean) => { |
|
|
|
|
|
const reduceTypesToImport = createTypesToImportsReducer(solution, namespace); |
|
|
|
|
|
|
|
|
|
|
|
return (imports: Import[], importRef: string) => |
|
|
|
|
|
reduceTypesToImport( |
|
|
|
|
|
imports, |
|
|
|
|
|
mergeBaseTypeWithProperties(types[importRef]).reduce((acc: TypeWithEnum[], typeName) => { |
|
|
|
|
|
parseGenerics(typeName) |
|
|
|
|
|
.toGenerics() |
|
|
|
|
|
.forEach(type => acc.push({ type, isEnum: types[type]?.isEnum })); |
|
|
|
|
|
|
|
|
|
|
|
return acc; |
|
|
|
|
|
}, []), |
|
|
|
|
|
).filter(filterFn); |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function mergeBaseTypeWithProperties({ baseType, genericArguments, properties }: Type) { |
|
|
return _interface.properties |
|
|
const removeGenerics = createGenericRemover(genericArguments); |
|
|
.reduce<string[]>((refs, prop) => { |
|
|
const clearTypes = (type: string) => normalizeTypeAnnotations(removeGenerics(type)); |
|
|
prop.refs.forEach(type => !types[type]?.isEnum && refs.push(type)); |
|
|
const baseTypes = baseType ? [baseType] : []; |
|
|
return refs; |
|
|
const propTypes = (properties ?? []).map(({ type }) => type); |
|
|
}, []) |
|
|
|
|
|
.concat(base || []) |
|
|
return [...baseTypes, ...propTypes].reduce(flattenUnionTypes, []).map(clearTypes); |
|
|
.reduce<Interface[]>(reduceRefsToInterfaces, interfaces); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function createGenericRemover(genericArguments: string[] | null) { |
|
|
|
|
|
if (!genericArguments) return (type: string) => type; |
|
|
|
|
|
|
|
|
|
|
|
return (type: string) => |
|
|
|
|
|
genericArguments.includes(type) |
|
|
|
|
|
? '' |
|
|
|
|
|
: type.replace(/<([^<>]+)>/, (_, match) => { |
|
|
|
|
|
return match |
|
|
|
|
|
.split(/,\s*/) |
|
|
|
|
|
.filter((t: string) => !genericArguments.includes(t)) |
|
|
|
|
|
.join(','); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function filterModelRefsToGenerate( |
|
|
export function createRefToImportReducerCreator(params: ModelGeneratorParams) { |
|
|
modelImports: Record<string, string[]>, |
|
|
const { solution } = params; |
|
|
modelsCreated: Model[], |
|
|
return (namespace: string) => createTypesToImportsReducer(solution, namespace); |
|
|
) { |
|
|
|
|
|
const created = modelsCreated.map(m => m.path); |
|
|
|
|
|
|
|
|
|
|
|
return Object.entries(modelImports).reduce((acc: string[][], [path, refs]) => { |
|
|
|
|
|
if (isEnumImport(path)) return acc; |
|
|
|
|
|
if (created.includes(path)) return acc; |
|
|
|
|
|
acc.push(refs); |
|
|
|
|
|
return acc; |
|
|
|
|
|
}, []); |
|
|
|
|
|
} |
|
|
} |
|
|
|