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 helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
ktutnik committed Apr 18, 2020
1 parent 455b0d9 commit bc48ec0
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 200 deletions.
53 changes: 48 additions & 5 deletions src/decorators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
import "reflect-metadata"
import { Class, DecoratorOption, DecoratorTargetType, DecoratorId, ParamDecorator, CustomPropertyDecorator } from "./types"
import { isConstructor, addDecorator } from "./helpers"

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

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

function addDecorator<T extends Decorator>(target: any, decorator: T) {
const decorators: Decorator[] = Reflect.getOwnMetadata(DECORATOR_KEY, target) || []
decorators.push(decorator)
Reflect.defineMetadata(DECORATOR_KEY, decorators, target)
}

/* ---------------------------------------------------------------- */
/* --------------------------- DECORATORS ------------------------- */
Expand Down Expand Up @@ -39,7 +62,7 @@ export function decorate(data: any | ((...args: any[]) => any), targetTypes: Dec
return (...args: any[]) => {
const theData = typeof data === "function" ? data(...args) : data
if (!opt.allowMultiple && !theData[DecoratorId]) {
const ctorName = isConstructor(args[0]) ? args[0].name : args[0].constructor.name
const ctorName = metadata.isConstructor(args[0]) ? args[0].name : args[0].constructor.name
throw new Error(`Reflect Error: Decorator with allowMultiple set to false must have DecoratorId property in ${ctorName}`)
}
//class decorator
Expand All @@ -55,7 +78,7 @@ export function decorate(data: any | ((...args: any[]) => any), targetTypes: Dec
//parameter decorator
if (args.length === 3 && typeof args[2] === "number") {
throwIfNotOfType("Parameter")
const isCtorParam = isConstructor(args[0])
const isCtorParam = metadata.isConstructor(args[0])
const targetType = isCtorParam ? args[0] : args[0].constructor
const targetName = isCtorParam ? "constructor" : args[1]
return addDecorator<ParamDecorator>(targetType, {
Expand Down Expand Up @@ -90,4 +113,24 @@ export function mergeDecorator(...fn: (ClassDecorator | PropertyDecorator | Para
return (...args: any[]) => {
fn.forEach(x => (x as Function)(...args))
}
}
}

export function noop() {
return decorate({})
}

export function ignore() {
return decorate(<PrivateDecorator>{ [DecoratorId]: Symbol("ignore"), 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 })
}

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

export function parameterProperties() {
return decorateClass({ [DecoratorId]: Symbol("paramProp"), type: "ParameterProperties" }, { allowMultiple: false })
}
140 changes: 108 additions & 32 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,129 @@
import { parse, Node } from "acorn"
import { Class, DECORATOR_KEY, Decorator, DecoratorIterator, DecoratorTargetType, ParamDecorator, DecoratorOption, ArrayDecorator, TypeDecorator, PrivateDecorator } from "./types"
import { Node, parse } from "acorn"
import { ArrayDecorator, Class, Decorator, DECORATOR_KEY, TypeDecorator } from "./types"


/* ---------------------------------------------------------------- */
/* --------------------------- HELPERS ---------------------------- */
/* ---------------------------------------------------------------- */

function useCache<K, P extends any[], R>(cache: Map<K, R>, fn: (...args: P) => R, getKey: (...args: P) => K) {
return (...args: P) => {
const key = getKey(...args)
const result = cache.get(key)
if (!!result) return result
else {
const newResult = fn(...args)
cache.set(key, newResult)
return newResult
}
}
}

namespace metadata {

function isConstructor(value: Function) {
return ("" + value).indexOf("class") == 0
}
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)
}

function isCustomClass(type: Function | Function[]) {
switch (type) {
case Boolean:
case String:
case Array:
case Number:
case Object:
case Date:
return false
default:
return true
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 []
}
}

function addDecorator<T extends Decorator>(target: any, decorator: T) {
const decorators: Decorator[] = Reflect.getOwnMetadata(DECORATOR_KEY, target) || []
decorators.push(decorator)
Reflect.defineMetadata(DECORATOR_KEY, decorators, target)
}
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
}

function useCache<K, P extends any[], R>(cache: Map<K, R>, fn: (...args: P) => R, getKey: (...args: P) => K) {
return (...args: P) => {
const key = getKey(...args)
const result = cache.get(key)
if (!!result) return result
else {
const newResult = fn(...args)
cache.set(key, newResult)
return newResult
export function isCustomClass(type: Function | Function[]) {
switch (type) {
case Boolean:
case String:
case Array:
case Number:
case Object:
case Date:
return false
default:
return true
}
}
}



export { isConstructor, addDecorator, isCustomClass, useCache }
export { useCache, metadata }

0 comments on commit bc48ec0

Please sign in to comment.