Browse Source

Merge pull request #6537 from abpframework/fix/6503

Proxy Generator: Fixed creation of generics with defaults
pull/6551/head
Bunyamin Coskuner 6 years ago
committed by GitHub
parent
commit
785676f98a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      npm/ng-packs/packages/schematics/src/constants/volo.ts
  2. 94
      npm/ng-packs/packages/schematics/src/models/model.ts
  3. 14
      npm/ng-packs/packages/schematics/src/models/util.ts
  4. 100
      npm/ng-packs/packages/schematics/src/utils/generics.ts
  5. 1
      npm/ng-packs/packages/schematics/src/utils/index.ts
  6. 49
      npm/ng-packs/packages/schematics/src/utils/model.ts
  7. 4
      npm/ng-packs/packages/schematics/src/utils/service.ts
  8. 25
      npm/ng-packs/packages/schematics/src/utils/type.ts

21
npm/ng-packs/packages/schematics/src/constants/volo.ts

@ -1,22 +1 @@
import { Interface, Property } from '../models';
export const VOLO_REGEX = /^Volo\.Abp\.(Application\.Dtos|ObjectExtending)/;
export const VOLO_NAME_VALUE = new Interface({
base: null,
identifier: 'NameValue<T = string>',
ref: 'Volo.Abp.NameValue',
namespace: 'Volo.Abp',
properties: [
new Property({
name: 'name',
type: 'string',
refs: ['System.String'],
}),
new Property({
name: 'value',
type: 'T',
refs: ['T'],
}),
],
});

94
npm/ng-packs/packages/schematics/src/models/model.ts

@ -1,43 +1,97 @@
import { Import } from './import';
import { Omissible } from './util';
import { Options } from './util';
export class Model {
imports: Import[] = [];
interfaces: Interface[] = [];
namespace: string;
path: string;
readonly imports: Import[] = [];
readonly interfaces: Interface[] = [];
readonly namespace: string;
readonly path: string;
constructor(options: ModelOptions) {
Object.assign(this, options);
}
}
export type ModelOptions = Omissible<Model, 'imports' | 'interfaces'>;
export type ModelOptions = Options<Model, 'imports' | 'interfaces'>;
export class Interface {
base: string | null;
identifier: string;
namespace: string;
properties: Property[] = [];
ref: string;
readonly base: string | null;
readonly identifier: string;
readonly namespace: string;
readonly generics: Generic[] = [];
readonly properties: Property[] = [];
readonly ref: string;
constructor(options: InterfaceOptions) {
Object.assign(this, options);
}
}
export type InterfaceOptions = Omissible<Interface, 'properties'>;
export type InterfaceOptions = Options<Interface, 'generics' | 'properties'>;
export class Property {
name: string;
type: string;
default = '';
optional: '' | '?' = '';
refs: string[] = [];
abstract class TypeRef {
readonly refs: string[] = [];
constructor(options: PropertyOptions) {
protected _type = '';
get type() {
return this._type;
}
set type(value: string) {
if (!value) return;
this._type = value;
}
protected _default = '';
get default() {
return this._default;
}
set default(value: string) {
if (!value) return;
this._default = ` = ${value}`;
}
constructor(options: TypeRefOptions) {
Object.assign(this, options);
}
setDefault(value: string) {
this.default = value;
}
setType(value: string) {
this.type = value;
}
}
type TypeRefOptionalKeys = 'default' | 'refs';
type TypeRefOptions = Options<TypeRef, TypeRefOptionalKeys>;
export class Generic extends TypeRef {
constructor(options: GenericOptions) {
super(options);
}
}
export type GenericOptions = Options<Generic, TypeRefOptionalKeys>;
export class Property extends TypeRef {
readonly name: string;
private _optional: '' | '?' = '';
get optional() {
return this.default ? '' : this._optional;
}
set optional(value: '' | '?') {
this._optional = value;
}
constructor(options: PropertyOptions) {
super(options);
}
setOptional(isOptional: boolean) {
this.optional = isOptional ? '?' : '';
}
}
export type PropertyOptions = Omissible<Property, 'default' | 'optional' | 'refs'>;
export type PropertyOptions = Options<Property, TypeRefOptionalKeys | 'optional'>;

14
npm/ng-packs/packages/schematics/src/models/util.ts

@ -1,2 +1,16 @@
// Omissible (given keys will become optional)
export type Omissible<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
// ExcludeKeys (keys will be excluded based on their type)
type ExcludeKeys<Type, Excluded> = Exclude<
{
[Key in keyof Type]: Type[Key] extends Excluded ? never : Key;
}[keyof Type],
never
>;
// tslint:disable-next-line: ban-types
type ExcludeMethods<Type> = Pick<Type, ExcludeKeys<Type, Function>>;
// Options (methods will be omitted, given keys will become optional)
export type Options<T, K extends keyof ExcludeMethods<T>> = Omissible<ExcludeMethods<T>, K>;

100
npm/ng-packs/packages/schematics/src/utils/generics.ts

@ -0,0 +1,100 @@
import { Generic } from '../models';
export class GenericsCollector {
private _generics: Generic[] = [];
get generics() {
return this._generics;
}
apply = (value: string, index: number) => {
const generic = this.get(index);
if (generic) {
if (!generic.type) generic.setType(value);
return value + generic.default;
}
return value;
};
constructor(private getTypeIdentifier = (type: string) => type) {}
private createGeneric(type: string, ref: string, defaultValue: string) {
const _default = this.getTypeIdentifier(defaultValue);
const refs = [generateRefWithPlaceholders(ref)];
const generic = new Generic({ type, default: _default, refs });
return generic;
}
private register(index: number, generic: Generic) {
const existing = this.get(index);
if (existing) {
existing.setDefault(generic.default);
existing.setType(generic.type);
} else this.set(index, generic);
}
collect(generics: string[], genericArguments: string[]) {
generics.forEach((ref, i) => {
const generic = this.createGeneric(
genericArguments[i],
ref,
genericArguments.includes(ref) ? '' : ref,
);
this.register(i, generic);
});
}
get(index: number) {
return this.generics[index];
}
set(index: number, value: Generic) {
this.generics[index] = value;
}
reset() {
this._generics = [];
}
}
export function generateRefWithPlaceholders(sourceType: string) {
let { identifier, generics } = extractGenerics(sourceType);
identifier = identifier;
generics = generics.map((_, i) => `T${i}`);
return generics.length ? `${identifier}<${generics}>` : identifier;
}
export function extractSimpleGenerics(sourceType: string) {
const { identifier, generics } = extractGenerics(sourceType);
return {
identifier: getLastSegment(identifier),
generics: generics.map(getLastSegment),
};
}
export function extractGenerics(sourceType: string) {
const regex = /(?<identifier>[^<]+)(<(?<generics>.+)>)?/g;
const { identifier = '', generics = '' } = regex.exec(sourceType)?.groups ?? {};
return {
identifier,
generics: generics.split(/,\s*/).filter(Boolean),
};
}
function getLastSegment(str: string) {
return str.split('.').pop()!;
}
export function replacePlaceholdersWithGenerics(
type: string,
generics: string[],
genericsCollector: GenericsCollector,
) {
return generics
.map(genericsCollector.apply)
.reduce((acc, v, i) => acc.replace(new RegExp(`([<, ])T${i}([,>])`, 'g'), `$1${v}$2`), type);
}

1
npm/ng-packs/packages/schematics/src/utils/index.ts

@ -5,6 +5,7 @@ export * from './barrel';
export * from './common';
export * from './enum';
export * from './file';
export * from './generics';
export * from './import';
export * from './model';
export * from './namespace';

49
npm/ng-packs/packages/schematics/src/utils/model.ts

@ -1,5 +1,11 @@
import { VOLO_NAME_VALUE, VOLO_REGEX } from '../constants';
import { VOLO_REGEX } from '../constants';
import { Interface, Model, Property, PropertyDef, Type, TypeWithEnum } from '../models';
import {
extractGenerics,
generateRefWithPlaceholders,
GenericsCollector,
replacePlaceholdersWithGenerics,
} from './generics';
import { parseNamespace } from './namespace';
import { relativePathToModel } from './path';
import { camel } from './text';
@ -8,6 +14,7 @@ import {
createTypeParser,
createTypeSimplifier,
createTypesToImportsReducer,
extendsSelf,
removeTypeModifiers,
} from './type';
@ -27,6 +34,7 @@ export function createImportRefsToModelReducer(params: ModelGeneratorParams) {
return (models: Model[], importRefs: string[]) => {
const enums: string[] = [];
const interfaces = importRefs.reduce(reduceImportRefsToInterfaces, []);
sortInterfaces(interfaces);
interfaces.forEach(_interface => {
@ -40,12 +48,9 @@ export function createImportRefsToModelReducer(params: ModelGeneratorParams) {
const index = models.findIndex(m => m.namespace === _interface.namespace);
if (index > -1) {
if (models[index].interfaces.some(i => i.identifier === _interface.identifier)) return;
if (_interface.ref.startsWith(VOLO_NAME_VALUE.ref)) return;
models[index].interfaces.push(_interface);
} else {
if (_interface.ref.startsWith(VOLO_NAME_VALUE.ref)) _interface = VOLO_NAME_VALUE;
const { namespace } = _interface;
models.push(
@ -70,7 +75,7 @@ export function createImportRefsToModelReducer(params: ModelGeneratorParams) {
isEnum: false,
});
_interface.properties.forEach(prop => {
[..._interface.properties, ..._interface.generics].forEach(prop => {
prop.refs.forEach(ref => {
const propType = types[ref];
if (!propType) return;
@ -100,38 +105,50 @@ export function createImportRefToInterfaceReducerCreator(params: ModelGeneratorP
const parseType = createTypeParser(removeTypeModifiers);
const simplifyType = createTypeSimplifier();
const getIdentifier = (type: string) => removeTypeModifiers(simplifyType(type));
const genericsCollector = new GenericsCollector(getIdentifier);
return reduceRefsToInterfaces;
function reduceRefsToInterfaces(interfaces: Interface[], ref: string) {
function reduceRefsToInterfaces(interfaces: Interface[], ref: string): Interface[] {
const typeDef = types[ref];
if (!typeDef) return interfaces;
const namespace = parseNamespace(solution, ref);
const identifier = (typeDef.genericArguments ?? []).reduce(
(acc, t, i) => acc.replace(`T${i}`, t),
getIdentifier(ref),
);
let { baseType: base, genericArguments } = typeDef;
genericArguments = genericArguments || [];
let identifier = getIdentifier(ref);
identifier = replacePlaceholdersWithGenerics(identifier, genericArguments, genericsCollector);
if (base) {
if (extendsSelf(ref, base)) {
genericsCollector.collect(extractGenerics(base).generics, genericArguments);
return reduceRefsToInterfaces(interfaces, generateRefWithPlaceholders(base));
} else {
base = getIdentifier(base);
}
}
const base = typeDef.baseType ? getIdentifier(typeDef.baseType) : null;
const _interface = new Interface({ identifier, base, namespace, ref });
const { generics } = genericsCollector;
const _interface = new Interface({ identifier, base, namespace, ref, generics });
genericsCollector.reset();
typeDef.properties?.forEach(prop => {
const name = camel(prop.name);
const optional = isOptionalProperty(prop) ? '?' : '';
const type = simplifyType(prop.typeSimple);
const refs = parseType(prop.type).reduce(
(acc: string[], r) => acc.concat(parseGenerics(r).toGenerics()),
[],
);
const property = new Property({ name, type, refs });
property.setOptional(isOptionalProperty(prop));
_interface.properties.push(new Property({ name, optional, type, refs }));
_interface.properties.push(property);
});
interfaces.push(_interface);
return _interface.properties
return [..._interface.properties, ..._interface.generics]
.reduce<string[]>((refs, prop) => {
prop.refs.forEach(type => {
if (types[type]?.isEnum) return;
@ -142,7 +159,7 @@ export function createImportRefToInterfaceReducerCreator(params: ModelGeneratorP
return refs;
}, [])
.concat(base ? parseGenerics(typeDef.baseType!).toGenerics() : [])
.reduce<Interface[]>(reduceRefsToInterfaces, interfaces);
.reduce(reduceRefsToInterfaces, interfaces);
}
}

4
npm/ng-packs/packages/schematics/src/utils/service.ts

@ -83,8 +83,8 @@ export function createActionToSignatureMapper() {
signature.parameters = action.parametersOnMethod.map(p => {
const type = adaptType(p.typeSimple);
const parameter = new Property({ name: p.name, type });
if (p.defaultValue) parameter.default = ` = ${p.defaultValue}`;
else if (p.isOptional) parameter.optional = '?';
parameter.setDefault(p.defaultValue);
parameter.setOptional(p.isOptional);
return parameter;
});

25
npm/ng-packs/packages/schematics/src/utils/type.ts

@ -1,7 +1,7 @@
import { SYSTEM_TYPES } from '../constants';
import { VOLO_REGEX } from '../constants/volo';
import { SYSTEM_TYPES, VOLO_REGEX } from '../constants';
import { eImportKeyword } from '../enums';
import { Import, TypeWithEnum } from '../models';
import { extractSimpleGenerics } from './generics';
import { parseNamespace } from './namespace';
import { relativePathToEnum, relativePathToModel } from './path';
import { parseGenerics } from './tree';
@ -15,14 +15,9 @@ export function createTypeSimplifier() {
type = /any</.test(type) ? 'any' : type;
const regexp = new RegExp(/.*(?<=\.)(?<generic>.+)<.*(?<=[\.<])(?<genericType>.+)>/gm);
const { generic, genericType } = regexp.exec(type)?.groups ?? {};
const { identifier, generics } = extractSimpleGenerics(type);
return generic
? generic === 'any'
? 'any'
: `${generic}<${genericType}>`
: type.split('.').pop()!;
return generics.length ? `${identifier}<${generics.join(', ')}>` : identifier;
});
return (type: string) => {
@ -40,9 +35,7 @@ export function createTypeParser(replacerFn = (t: string) => t) {
export function createTypeNormalizer(replacerFn = (t: string) => t) {
return (type: string) => {
type = normalizeTypeAnnotations(type);
return replacerFn(type);
return replacerFn(normalizeTypeAnnotations(type));
};
}
@ -56,8 +49,7 @@ export function flattenDictionaryTypes(types: string[], type: string) {
}
export function normalizeTypeAnnotations(type: string) {
type = type.replace(/\[(.+)+\]/g, '$1[]');
return type.replace(/\?/g, '');
return type.replace(/\[(.+)+\]/g, '$1[]').replace(/\?/g, '');
}
export function removeGenerics(type: string) {
@ -115,3 +107,8 @@ export function createTypeAdapter() {
const simplifyType = createTypeSimplifier();
return (type: string) => parseGenerics(type, node => simplifyType(node.data)).toString();
}
// naming here is depictive only
export function extendsSelf(type: string, base: string) {
return removeGenerics(base) === removeGenerics(type);
}

Loading…
Cancel
Save