guy
Version:
npm dependencies checker
395 lines (346 loc) • 19.1 kB
text/coffeescript
'use strict'
############################################################################################################
GUY_props = @
no_such_value = Symbol 'no_such_value'
H = require './_helpers'
builtins = require './_builtins'
= Symbol 'misfit'
= Symbol 'misfit2'
{ rpr, } = require './trm'
### see https://nodejs.org/dist/latest-v18.x/docs/api/util.html#utilinspectcustom ###
node_inspect = Symbol.for 'nodejs.util.inspect.custom'
#-----------------------------------------------------------------------------------------------------------
H.types.declare 'guy_props_keys_cfg', tests:
"@isa.object x": ( x ) -> .object x
"@isa_optional.cardinal x.depth": ( x ) -> .cardinal x.depth
"@isa.boolean x.allow_any": ( x ) -> .boolean x.allow_any
"@isa.boolean x.symbols": ( x ) -> .boolean x.symbols
"@isa.boolean x.hidden": ( x ) -> .boolean x.hidden
"@isa.boolean x.depth_first": ( x ) -> .boolean x.depth_first
"@isa.boolean x.builtins": ( x ) -> .boolean x.builtins
"@isa.boolean x.builtins implies x.hidden": ( x ) ->
return ( not x.builtins ) or ( x.hidden )
#...........................................................................................................
H.types.defaults.guy_props_keys_cfg =
allow_any: true
symbols: false
builtins: false
hidden: false
depth: null
depth_first: false
#-----------------------------------------------------------------------------------------------------------
H.types.declare 'guy_props_crossmerge_cfg', tests:
"@isa.guy_props_keys_cfg x": ( x ) -> .guy_props_keys_cfg x
"x.keys?": ( x ) -> x.keys?
"x.values?": ( x ) -> x.values?
"x.fallback can be anything": ( x ) -> true
#...........................................................................................................
### TAINT code duplication ###
H.types.defaults.guy_props_crossmerge_cfg =
allow_any: true
symbols: false
builtins: false
hidden: false
depth: null
depth_first: false
keys: null
values: null
fallback:
#-----------------------------------------------------------------------------------------------------------
H.types.declare 'guy_props_tree_cfg', tests:
"@isa.guy_props_keys_cfg x": ( x ) -> .guy_props_keys_cfg x
"@isa_optional.function x.evaluate": ( x ) -> .function x.evaluate
"@isa_optional.text x.sep": ( x ) -> .text x.sep
#...........................................................................................................
### TAINT code duplication ###
H.types.defaults.guy_props_tree_cfg =
allow_any: true
symbols: false
builtins: false
hidden: false
depth: null
depth_first: null
evaluate: null
sep: null
#-----------------------------------------------------------------------------------------------------------
H.types.declare 'guy_props_tree_verdict', ( x ) ->
return true if .boolean x
return false unless .text x
return true
#-----------------------------------------------------------------------------------------------------------
H.types.declare 'guy_props_strict_owner', ( x ) -> x instanceof Strict_owner
#-----------------------------------------------------------------------------------------------------------
H.types.declare 'guy_props_strict_owner_cfg', tests:
"@isa.object x": ( x ) -> .object x
"x.target?": ( x ) -> x.target?
"@isa.boolean x.locked": ( x ) -> .boolean x.locked
"@isa.boolean x.seal": ( x ) -> .boolean x.seal
"@isa.boolean x.freeze": ( x ) -> .boolean x.freeze
"@isa.boolean x.oneshot": ( x ) -> .boolean x.oneshot
"@isa_optional.boolean x.reset": ( x ) -> .boolean x.reset
# "reassignable object cannot be frozen": ( x ) ->
# return false if x.oneshot and x.freeze
# return true
"x.reset is deprecated": ( x ) ->
return true if x.reset?
return true if x.reset is true
throw new Error "^guy.props.Strict_owner@1^ `cfg.reset: false` is deprecated; use `cfg.seal: true` instead"
#...........................................................................................................
H.types.defaults.guy_props_strict_owner_cfg =
target: null
reset: true
locked: true
seal: false
freeze: false
oneshot: false
#-----------------------------------------------------------------------------------------------------------
= def = Object.defineProperty
= hide = ( object, name, value ) => Object.defineProperty object, name,
enumerable: false
writable: true
configurable: true
value: value
#-----------------------------------------------------------------------------------------------------------
= def_oneoff = ( object, name, cfg, method ) =>
get = ->
R = method.apply object
delete cfg.get
def object, name,
configurable: ( cfg.configurable ? true )
enumerable: ( cfg.enumerable ? true )
value: R
return R
def object, name, { enumerable: true, configurable: true, get, }
return null
#-----------------------------------------------------------------------------------------------------------
= ( d, fallback, keys... ) ->
R = {}
keys = keys.flat Infinity
for key in keys
R[ key ] = if ( value = d[ key ] ) is undefined then fallback else value
return [ R, keys, ]
#-----------------------------------------------------------------------------------------------------------
= ( d, fallback, keys... ) -> ( d, fallback, keys )[ 0 ]
#-----------------------------------------------------------------------------------------------------------
= ( d, fallback, keys... ) ->
[ R, keys, ] = d, fallback, keys...
delete d[ key ] for key in keys
return R
#-----------------------------------------------------------------------------------------------------------
= ( d ) ->
R = {}
R[ k ] = ( v ? null ) for k, v of d
return R
#-----------------------------------------------------------------------------------------------------------
= ( d ) ->
R = {}
R[ k ] = v for k, v of d when v?
return R
#-----------------------------------------------------------------------------------------------------------
= ( first, others... ) ->
return Object.assign {}, first, ( other for other in others )...
#-----------------------------------------------------------------------------------------------------------
= ( cfg ) ->
H.types.validate.guy_props_crossmerge_cfg ( cfg = { H.types.defaults.guy_props_crossmerge_cfg..., cfg..., } )
R = {}
for key from cfg.keys, cfg
if ( value = cfg.values, key, ) isnt
R[ key ] = value
continue
if cfg.fallback is
throw new Error "^guy.props.crossmerge@1^ missing key #{H.rpr key} in values"
R[ key ] = cfg.fallback
return R
#-----------------------------------------------------------------------------------------------------------
= ( owner, property_chain ) ->
R = owner
R = R[ term ] for term in property_chain
return R
#-----------------------------------------------------------------------------------------------------------
class
#---------------------------------------------------------------------------------------------------------
Strict_owner_cfg = Symbol 'Strict_owner_cfg'
#---------------------------------------------------------------------------------------------------------
: ( instance, cfg ) ->
R =
#.....................................................................................................
ownKeys: ( target ) -> Reflect.ownKeys target
#.....................................................................................................
get: ( target, key ) ->
return "#{instance.constructor.name}" if key is Symbol.toStringTag
return target.constructor if key is 'constructor'
return target.toString if key is 'toString'
return target.call if key is 'call'
return target.apply if key is 'apply'
return target.then if key is 'then'
return target[ Symbol.iterator ] if key is Symbol.iterator
return target[ node_inspect ] if key is node_inspect
### NOTE necessitated by behavior of `node:util.inspect()`: ###
return target[ 0 ] if key is '0'
if ( value = GUY_props.get target, key, no_such_value ) is no_such_value
return undefined unless cfg.locked
throw new Error "^guy.props.Strict_owner@1^ #{instance.constructor.name} instance does not have property #{H.rpr key}"
return value
#.....................................................................................................
set: ( target, key, value ) ->
if GUY_props.has target, key
throw new Error "^guy.props.Strict_owner@1^ #{instance.constructor.name} instance already has property #{H.rpr key}"
return Reflect.set target, key, value
#.......................................................................................................
return R
#---------------------------------------------------------------------------------------------------------
: ( self ) ->
return self[ Strict_owner_cfg ].locked
#---------------------------------------------------------------------------------------------------------
: ( self, flag ) ->
H.types.validate.boolean flag
self[ Strict_owner_cfg ].locked = flag
return flag
#---------------------------------------------------------------------------------------------------------
constructor: ( cfg ) ->
### thx to https://stackoverflow.com/a/40714458/7568091 ###
cfg = { target: @, cfg..., }
H.types.validate.guy_props_strict_owner_cfg cfg = { H.types.defaults.guy_props_strict_owner_cfg..., cfg..., }
proxy_cfg = ._get_proxy_cfg @, cfg
delete proxy_cfg.set unless cfg.oneshot
R = new Proxy cfg.target, proxy_cfg
GUY_props.hide R, Strict_owner_cfg, cfg
Object.freeze R if cfg.freeze
Object.seal R if cfg.seal
return R
#---------------------------------------------------------------------------------------------------------
: ( cfg ) ->
cfg = { target: {}, cfg..., }
H.types.validate.guy_props_strict_owner_cfg cfg = { H.types.defaults.guy_props_strict_owner_cfg..., cfg..., }
proxy_cfg = cfg.target, cfg
delete proxy_cfg.set unless cfg.oneshot
R = new Proxy cfg.target, proxy_cfg
GUY_props.hide R, Strict_owner_cfg, cfg
### TAINT consider to freeze, seal target instread of proxy ###
Object.freeze R if cfg.freeze
Object.seal R if cfg.seal
return R
#-----------------------------------------------------------------------------------------------------------
Strict_owner =
#===========================================================================================================
# KEY TESTING, RETRIEVAL, CATALOGUING
#-----------------------------------------------------------------------------------------------------------
= ( target, key ) =>
### safe version of `Reflect.has()` that never throws an error ###
### try to use `Reflect.has()` on given target; will fail for `null`, `undefined`, primitive values: ###
try return Reflect.has target, key catch error then null
### try to retrieve prototype of target; will fail for `null`, `undefined`: ###
try return false unless ( prototype = Object.getPrototypeOf target )? catch error then null
### give up if no prototye has been returned (may happen e.g. with `target = Object.create null`): ###
return false unless prototype?
### apply `Reflect.has()` to prototype of target; this should be the definitive answer: ###
return Reflect.has prototype, key
#-----------------------------------------------------------------------------------------------------------
get = ( target, key, fallback ) ->
switch arity = arguments.length
when 2 then fallback =
when 3 then null
else throw new Error "^guy.props.get@1^ expected 2 or 3 arguments, got #{arity}"
return target[ key ] if target, key
return fallback unless fallback is
throw new Error "^guy.props.get@1^ no such property #{H.rpr key}"
= get.bind @ ### avoiding fat-arrow function so we can use `arguments` ###
#-----------------------------------------------------------------------------------------------------------
= ( cfg ) ->
has_hidden = ( cfg ? {} ).hidden?
cfg = { H.types.defaults.guy_props_keys_cfg..., cfg..., }
cfg.hidden = true if not has_hidden and cfg.builtins
H.types.validate.guy_props_keys_cfg cfg
return cfg
#-----------------------------------------------------------------------------------------------------------
= ( owner, cfg ) => [ ( owner, ( cfg ) )..., ]
#-----------------------------------------------------------------------------------------------------------
= ( owner, cfg ) =>
return true for key from owner, ( cfg )
return false
#-----------------------------------------------------------------------------------------------------------
= ( owner, cfg ) =>
return owner, ( cfg )
#-----------------------------------------------------------------------------------------------------------
= ( owner, cfg ) ->
seen = new Set()
if cfg.depth_first
cache = new Map()
for { key, owner, } from owner, cfg
unless ( collector = cache.get owner )?
cache.set owner, collector = []
collector.push key
for owner in [ cache.keys()..., ].reverse()
for key in cache.get owner
continue if seen.has key
seen.add key
yield key
else
for { key, } from owner, cfg
continue if seen.has key
seen.add key
yield key
return null
#-----------------------------------------------------------------------------------------------------------
= ( x ) ->
return [] unless x?
R = [ x, ]
loop
break unless ( x = Object.getPrototypeOf x )?
R.push x
return R
#-----------------------------------------------------------------------------------------------------------
= ( owner, cfg, current_depth = 0 ) ->
return null if cfg.depth? and current_depth > cfg.depth
return null if ( not cfg.builtins ) and builtins.has owner
try
for key in Reflect.ownKeys owner
continue if ( not cfg.symbols ) and ( H.types.isa.symbol key )
d = Object.getOwnPropertyDescriptor owner, key
continue if ( not cfg.hidden ) and ( not d.enumerable )
yield { key, owner, }
catch error
return null if cfg.allow_any and ( error.message is 'Reflect.ownKeys called on non-object' )
throw new Error "^guy.props._walk_keyowners@1^ Reflect.ownKeys called on non-object #{rpr owner}"
#.........................................................................................................
if ( proto_owner = Object.getPrototypeOf owner )?
yield from proto_owner, cfg, current_depth + 1
return null
#-----------------------------------------------------------------------------------------------------------
= ( owner, base = {} ) =>
base[ k ] = owner[ k ] for k from owner, { hidden: true, symbols: true, builtins: false, }
return base
#-----------------------------------------------------------------------------------------------------------
### thx to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors#creating_a_shallow_copy ###
= ( d ) -> Object.create ( Object.getPrototypeOf d ), ( Object.getOwnPropertyDescriptors d )
#===========================================================================================================
# TREE
#-----------------------------------------------------------------------------------------------------------
= ( owner, cfg ) -> [ ( owner, cfg )..., ]
#-----------------------------------------------------------------------------------------------------------
= ( owner, cfg ) ->
H.types.validate.guy_props_tree_cfg ( cfg = { H.types.defaults.guy_props_tree_cfg..., cfg..., } )
unless cfg.sep? then return yield from owner, cfg
yield ( x.toString() for x in p ).join cfg.sep for p from owner, cfg
return null
#-----------------------------------------------------------------------------------------------------------
= ( owner, cfg, seen ) ->
seen ?= new Map()
for { key, owner: subowner, } from owner, cfg
seen.set subowner, new Set() unless seen.has subowner
continue if ( seen_keys = seen.get subowner ).has key
seen_keys.add key
try
value = subowner[ key ]
catch error
continue if cfg.allow_any and (
/'caller', 'callee', and 'arguments' properties may not be accessed/.test error.message)
verdict = if cfg.evaluate? then ( cfg.evaluate { owner: subowner, key, value, } ) else 'take,descend'
H.types.validate.guy_props_tree_verdict verdict
continue if verdict is false
verdict = 'take,descend' if verdict is true
yield [ key ] if /\btake\b/.test verdict
continue unless /\bdescend\b/.test verdict
for subkey from value, cfg, seen
yield [ key, subkey, ].flat()
return null