Skip to content
This repository has been archived by the owner on Dec 12, 2020. It is now read-only.

Commit

Permalink
fix: Fix generic inheritance issue when passed generic template
Browse files Browse the repository at this point in the history
  • Loading branch information
ktutnik committed May 27, 2020
1 parent f321a45 commit 0a572ac
Show file tree
Hide file tree
Showing 23 changed files with 800 additions and 6,440 deletions.
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ const metadata = reflect(Awesome)
Above code showing that we add a specialized decorator `@generic.template()` to define generic template type. We also defined data type of the generic parameters using `@reflect.type()` decorator. Next on the inherited class we specify `@generic.type()` to define types replace the generic template. Note that the order of the parameter on `@generic.template()` and `@generic.type()` is important.




## Inspect Parameter Properties
TypeScript has parameter properties feature, which make it possible to use constructor parameter as property. tinspector able to extract parameter properties type information by using `@reflect.parameterProperties()` decorator.

Expand Down
15 changes: 9 additions & 6 deletions src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function mergeDecorator(...fn: (ClassDecorator | PropertyDecorator | Nati
}
}


const symIgnore = Symbol("ignore")
const symOverride = Symbol("override")
const symArray = Symbol("array")
Expand All @@ -129,16 +130,19 @@ export function ignore() {
return decorate(<PrivateDecorator>{ [DecoratorId]: symIgnore, kind: "Ignore" }, ["Parameter", "Method", "Property"], { allowMultiple: false })
}

export function type(type: Class[] | Class | string | string[] | ((x:any) => Class[] | Class | string | string[]), info?: string) {
return decorate((target: any) => <TypeDecorator>{ [DecoratorId]: symOverride, kind: "Override", type: type, info, target }, ["Parameter", "Method", "Property"], { allowMultiple: false })
export function type(type: Class[] | Class | string | string[] | ((x: any) => Class[] | Class | string | string[]), info?: string) {
// type is not inheritable because derived class can define their own type override
return decorate((target: any) => <TypeDecorator>{ [DecoratorId]: symOverride, kind: "Override", type: type, info, target }, ["Parameter", "Method", "Property"], { inherit: false, allowMultiple: false })
}

export function noop(type?: (x: any) => string | string[] | Class | Class[]) {
return decorate((target:any) => <NoopDecorator>{ [DecoratorId]: symNoop, kind: "Noop", type, target }, undefined, { allowMultiple: false })
// type is not inheritable because derived class can define their own type override
return decorate((target: any) => <NoopDecorator>{ [DecoratorId]: symNoop, kind: "Noop", type, target }, undefined, { inherit: false, allowMultiple: false })
}

export function array(type: Class | string) {
return decorate((target:any) => <ArrayDecorator>{ [DecoratorId]: symArray, kind: "Array", type: type, target }, ["Parameter", "Method", "Property"], { allowMultiple: false })
// type is not inheritable because derived class can define their own type override
return decorate((target: any) => <ArrayDecorator>{ [DecoratorId]: symArray, kind: "Array", type: type, target }, ["Parameter", "Method", "Property"], { inherit: false, allowMultiple: false })
}

export function parameterProperties() {
Expand All @@ -151,10 +155,9 @@ export namespace generic {
export function template(...templates: string[]) {
return decorateClass(target => <GenericTemplateDecorator>{ [DecoratorId]: symGenericTemplate, kind: "GenericTemplate", templates, target }, { inherit: false, allowMultiple: false })
}
export function type(...types: (Class | Class[])[]) {
export function type(...types: (Class[] | Class | string | string[])[]) {
return decorateClass(target => <GenericTypeDecorator>{ [DecoratorId]: symGenericType, kind: "GenericType", types, target }, { inherit: false, allowMultiple: false })
}

/**
* Create generic class dynamically
* @param parent Super class that the class inherited from
Expand Down
76 changes: 76 additions & 0 deletions src/extends.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
ClassReflection,
DecoratorId,
DecoratorOption,
DecoratorOptionId,
MethodReflection,
ParameterReflection,
PropertyReflection,
} from "./types"

type MemberReflection = PropertyReflection | MethodReflection | ParameterReflection

// --------------------------------------------------------------------- //
// ------------------------------ EXTENDER ----------------------------- //
// --------------------------------------------------------------------- //

function mergeDecorators(ownDecorators: any[], parentDecorators: any[]) {
const result = [...ownDecorators]
for (const decorator of parentDecorators) {
const options: DecoratorOption = decorator[DecoratorOptionId]!
// continue, if the decorator is not inheritable
if (!options.inherit) continue
// continue, if allow multiple and already has decorator with the same ID
if (!options.allowMultiple && ownDecorators.some(x => x[DecoratorId] === decorator[DecoratorId])) continue
result.push(decorator)
}
return result
}

function mergeMember(child: MemberReflection | undefined, parent: MemberReflection): MemberReflection {
const decorators = mergeDecorators(child?.decorators ?? [], parent.decorators)
if (parent.kind === "Method") {
const childParameters = (child as MethodReflection)?.parameters ?? []
const merged = (child ?? parent) as MethodReflection
// copy parent parameters if number of current parameters = 0, else just merge existing parameters with parent
const copyParentParameters = childParameters.length === 0
const parameters = mergeMembers(childParameters, parent.parameters, copyParentParameters) as ParameterReflection[]
return { ...merged, returnType: parent.returnType, typeClassification: parent.typeClassification, decorators, parameters }
}
else {
const merged = (child ?? parent) as PropertyReflection | ParameterReflection
return { ...merged, type: parent.type, typeClassification: parent.typeClassification, decorators }
}
}

function mergeMembers(children: MemberReflection[], parents: MemberReflection[], copy = true): MemberReflection[] {
const result: MemberReflection[] = []
const isExists: { [key: string]: true } = {}
for (const child of children) {
const parent = parents.find(x => x.name === child.name)
if (parent) {
result.push(mergeMember(child, parent))
isExists[child.name] = true
}
else
result.push(child)
}
if(copy){
for (const parent of parents) {
if (isExists[parent.name]) continue
result.push(mergeMember(undefined, parent))
}
}
return result
}

function extendsMetadata(child: ClassReflection, parent: ClassReflection): ClassReflection {
return {
...child,
decorators: mergeDecorators(child.decorators, parent.decorators),
methods: mergeMembers(child.methods, parent.methods) as MethodReflection[],
properties: mergeMembers(child.properties, parent.properties) as PropertyReflection[]
}
}

export { extendsMetadata }
26 changes: 23 additions & 3 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/* --------------------------- HELPERS ---------------------------- */
/* ---------------------------------------------------------------- */

import { Class } from "./types"
import { Class, ParameterPropertyReflection, ClassReflection } from "./types"

function useCache<K, P extends any[], R>(cache: Map<K, R>, fn: (...args: P) => R, getKey: (...args: P) => K) {
return (...args: P) => {
Expand All @@ -20,8 +20,12 @@ function useCache<K, P extends any[], R>(cache: Map<K, R>, fn: (...args: P) => R
}

namespace metadata {
export function isCallback(type: Function) : type is ((x:any) => Class[] | Class | string | string[]){
return typeof type === "function" && !type.prototype
export function isParameterProperties(meta: any): meta is ParameterPropertyReflection {
return meta && meta.kind === "Property" && (meta as ParameterPropertyReflection).isParameter
}

export function isCallback(type: Function): type is ((x: any) => Class[] | Class | string | string[]) {
return typeof type === "function" && !type.prototype
}

export function isConstructor(value: any) {
Expand All @@ -41,6 +45,22 @@ namespace metadata {
return true
}
}

export function getMethods(meta: ClassReflection) {
return meta.methods.map(x => ({
name: x.name,
type: x.returnType,
pars: x.parameters
.map(p => ({ name: p.name, type: p.type }))
}))
}

export function getProperties(meta: ClassReflection) {
return meta.properties.map(x => ({
name: x.name,
type: x.type
}))
}
}


Expand Down
16 changes: 8 additions & 8 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function printDestruct(params: any[]) {
// ------------------------------- PARSER ------------------------------ //
// --------------------------------------------------------------------- //

function parseParameter(name: string | { [key: string]: string[] }, index: number, owner?: Class): ParameterReflection {
function parseParameter(name: string | { [key: string]: string[] }, index: number): ParameterReflection {
let parName
let fields: { [key: string]: any[] } = {}
if (typeof name === "object") {
Expand All @@ -120,7 +120,7 @@ function parseParameter(name: string | { [key: string]: string[] }, index: numbe
else {
parName = name
}
return { kind: "Parameter", name: parName, decorators: [], fields, owner: owner ? [owner] : undefined as any, index, type: undefined }
return { kind: "Parameter", name: parName, decorators: [], fields, index, type: undefined }
}

function parseFunction(fn: Function): FunctionReflection {
Expand All @@ -134,8 +134,8 @@ function parseMethods(owner: Class): MethodReflection[] {
for (const name of members) {
const des = Reflect.getOwnPropertyDescriptor(owner.prototype, name)
if (des && typeof des.value === "function" && !des.get && !des.set) {
const parameters = getMethodParameters(owner, name).map((x, i) => parseParameter(x, i, owner))
result.push({ kind: "Method", name, parameters, decorators: [], returnType: undefined, owner: [owner] })
const parameters = getMethodParameters(owner, name).map((x, i) => parseParameter(x, i))
result.push({ kind: "Method", name, parameters, decorators: [], returnType: undefined })
}
}
return result
Expand All @@ -147,13 +147,13 @@ function parseProperties(owner: Class): PropertyReflection[] {
for (const name of members) {
const des = Reflect.getOwnPropertyDescriptor(owner.prototype, name)
if (!des || des.get || des.set) {
result.push({ kind: "Property", name, decorators: [], owner: [owner], get: des?.get, set: des?.set })
result.push({ kind: "Property", name, decorators: [], get: des?.get, set: des?.set })
}
}
// include constructor parameter
const params = getConstructorParameters(owner)
for (const [i, name] of params.entries()) {
const { fields, ...par } = parseParameter(name, i, owner)
const { fields, ...par } = parseParameter(name, i)
result.push(<ParameterPropertyReflection>{ ...par, kind: "Property", isParameter: true, get: undefined, set: undefined })
}
return result
Expand All @@ -164,14 +164,14 @@ function parseConstructor(fn: Class): ConstructorReflection {
return {
kind: "Constructor",
name: "constructor",
parameters: params.map((x, i) => parseParameter(x, i, fn)),
parameters: params.map((x, i) => parseParameter(x, i)),
}
}

function parseClass(fn: Class): ClassReflection {
const proto = Object.getPrototypeOf(fn)
return {
kind: "Class", name: fn.name, type: fn, decorators: [], owner: [fn],
kind: "Class", name: fn.name, type: fn, decorators: [],
methods: parseMethods(fn),
properties: parseProperties(fn),
ctor: parseConstructor(fn),
Expand Down
Loading

0 comments on commit 0a572ac

Please sign in to comment.