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

Commit

Permalink
refactor: Refactor reflect class logic for extensibility
Browse files Browse the repository at this point in the history
  • Loading branch information
ktutnik committed Apr 24, 2020
1 parent b858f49 commit abe0d4e
Show file tree
Hide file tree
Showing 21 changed files with 5,836 additions and 607 deletions.
30 changes: 18 additions & 12 deletions src/decorators.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import "reflect-metadata"

import { metadata } from "./helpers"
import { metadata } from "./helpers"
import {
ArrayDecorator,
Class,
CustomPropertyDecorator,
DECORATOR_KEY,
DecoratorId,
DecoratorOption,
DecoratorTargetType,
ParamDecorator,
NativeDecorator,
NativeParameterDecorator,
PrivateDecorator,
TypeDecorator,
Decorator,
DECORATOR_KEY,
ParameterPropertiesDecorator,
} from "./types"

// --------------------------------------------------------------------- //
// ------------------------------- HELPER ------------------------------ //
// --------------------------------------------------------------------- //

function addDecorator<T extends Decorator>(target: any, decorator: T) {
const decorators: Decorator[] = Reflect.getOwnMetadata(DECORATOR_KEY, target) || []
function addDecorator<T extends NativeDecorator>(target: any, decorator: T) {
const decorators: NativeDecorator[] = Reflect.getOwnMetadata(DECORATOR_KEY, target) || []
decorators.push(decorator)
Reflect.defineMetadata(DECORATOR_KEY, decorators, target)
}
Expand Down Expand Up @@ -81,7 +82,7 @@ export function decorate(data: any | ((...args: any[]) => any), targetTypes: Dec
const isCtorParam = metadata.isConstructor(args[0])
const targetType = isCtorParam ? args[0] : args[0].constructor
const targetName = isCtorParam ? "constructor" : args[1]
return addDecorator<ParamDecorator>(targetType, {
return addDecorator<NativeParameterDecorator>(targetType, {
targetType: "Parameter",
target: targetName,
targetIndex: args[2],
Expand Down Expand Up @@ -109,7 +110,7 @@ export function decorate(data: any | ((...args: any[]) => any), targetTypes: Dec
}
}

export function mergeDecorator(...fn: (ClassDecorator | PropertyDecorator | ParamDecorator | MethodDecorator)[]) {
export function mergeDecorator(...fn: (ClassDecorator | PropertyDecorator | NativeParameterDecorator | MethodDecorator)[]) {
return (...args: any[]) => {
fn.forEach(x => (x as Function)(...args))
}
Expand All @@ -119,18 +120,23 @@ export function noop() {
return decorate({})
}

const symIgnore = Symbol("ignore")
const symOverride = Symbol("override")
const symArray = Symbol("array")
const symParamProp = Symbol("paramProp")

export function ignore() {
return decorate(<PrivateDecorator>{ [DecoratorId]: Symbol("ignore"), kind: "Ignore" }, ["Parameter", "Method", "Property"], { allowMultiple: false })
return decorate(<PrivateDecorator>{ [DecoratorId]: symIgnore, kind: "Ignore" }, ["Parameter", "Method", "Property"], { allowMultiple: false })
}

export function type(type: Class | Class[], info?: string) {
return decorate(<TypeDecorator>{ [DecoratorId]: Symbol("override"), kind: "Override", type: type, info }, ["Parameter", "Method", "Property"], { allowMultiple: false })
return decorate(<TypeDecorator>{ [DecoratorId]: symOverride, kind: "Override", type: type, info }, ["Parameter", "Method", "Property"], { allowMultiple: false })
}

export function array(type: Class) {
return decorate(<ArrayDecorator>{ [DecoratorId]: Symbol("array"), kind: "Array", type: type }, ["Parameter", "Method", "Property"], { allowMultiple: false })
return decorate(<ArrayDecorator>{ [DecoratorId]: symArray, kind: "Array", type: type }, ["Parameter", "Method", "Property"], { allowMultiple: false })
}

export function parameterProperties() {
return decorateClass({ [DecoratorId]: Symbol("paramProp"), type: "ParameterProperties" }, { allowMultiple: false })
return decorateClass(<ParameterPropertiesDecorator>{ [DecoratorId]: symParamProp, type: "ParameterProperties" }, { allowMultiple: false })
}
78 changes: 0 additions & 78 deletions src/extends.ts

This file was deleted.

87 changes: 0 additions & 87 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Node, parse } from "acorn"
import { ArrayDecorator, Class, Decorator, DECORATOR_KEY, TypeDecorator } from "./types"


/* ---------------------------------------------------------------- */
Expand All @@ -20,91 +18,6 @@ function useCache<K, P extends any[], R>(cache: Map<K, R>, fn: (...args: P) => R
}

namespace metadata {

function getNode(node: Node, criteria: (x: any) => boolean): Node | undefined {
if (criteria(node)) return node
if (!(node as any).body) return
if (Array.isArray((node as any).body)) {
for (const child of (node as any).body) {
const result = getNode(child, criteria)
if (result) return result
}
}
return getNode((node as any).body, criteria)
}

function getNamesFromAst(nodes: any[]) {
const getName = (node: any): undefined | string | { [key: string]: string[] } => {
if (node.type === "Identifier") return node.name
if (node.type === "AssignmentPattern") return node.left.name
if (node.type === "RestElement") return node.argument.name
if (node.type === "Property") {
if (node.value.type === "Identifier") return node.value.name
else {
const result: { [key: string]: any } = {}
result[node.key.name] = getName(node.value)
return result
}
}
//if (node.type === "ObjectPattern") {
return node.properties.map((x: any) => getName(x))
//}
}
return nodes.map(x => getName(x)).filter((x): x is string | { [key: string]: string[] } => !!x)
}

export function getMethodParameters(fn: Class, method: string) {
const body = fn.toString()
const ast = parse(body)
const ctor = getNode(ast, x => x.type === "MethodDefinition" && x.kind === "method" && x.key.name === method)
return getNamesFromAst(ctor ? (ctor as any).value.params : [])
}

export function getConstructorParameters(fn: Class) {
const body = fn.toString()
const ast = parse(body)
const ctor = getNode(ast, x => x.type === "MethodDefinition" && x.kind === "constructor")
return getNamesFromAst(ctor ? (ctor as any).value.params : [])
}

export function getParameterNames(fn: Function) {
try {
const body = fn.toString()
const ast = parse(body)
return getNamesFromAst((ast as any).body[0].params)
}
catch {
return []
}
}

export function getClassMembers(fun: Function) {
const isGetter = (name: string) => Object.getOwnPropertyDescriptor(fun.prototype, name)!.get
const isSetter = (name: string) => Object.getOwnPropertyDescriptor(fun.prototype, name)!.set
const isFunction = (name: string) => typeof fun.prototype[name] === "function";
const members = Object.getOwnPropertyNames(fun.prototype)
.filter(name => isGetter(name) || isSetter(name) || isFunction(name))
const properties = (Reflect.getOwnMetadata(DECORATOR_KEY, fun) || [])
.filter((x: Decorator) => x.targetType === "Property")
.map((x: Decorator) => x.target)
const names = members.concat(properties)
.filter(name => name !== "constructor" && !~name.indexOf("__"))
return [...new Set(names)]
}

export function getType(decorators: any[], type: any) {
const array = decorators.find((x: ArrayDecorator): x is ArrayDecorator => x.kind === "Array")
const override = decorators.find((x: TypeDecorator): x is TypeDecorator => x.kind === "Override")
if (override)
return override.type
else if (array)
return [array.type]
else if (type === Array)
return [Object]
else
return type
}

export function isConstructor(value: Function) {
return ("" + value).indexOf("class") == 0
}
Expand Down

0 comments on commit abe0d4e

Please sign in to comment.