@@ -4,55 +4,108 @@ const babel = require('@babel/parser');
44const expression = require ( 'eval-estree-expression' ) ;
55const { evaluate } = expression ;
66
7- const isObject = v => v !== null && typeof v === 'object' && ! Array . isArray ( v ) ;
7+ const isAST = value => isObject ( value ) && hasOwnProperty . call ( value , 'type' ) ;
8+
9+ const isObject = value => {
10+ return value !== null && typeof value === 'object' && ! Array . isArray ( value ) ;
11+ } ;
812
913const isPrimitive = value => {
1014 return value == null || ( typeof value !== 'object' && typeof value !== 'function' ) ;
1115} ;
1216
17+ const LITERALS = {
18+ 'undefined' : undefined ,
19+ 'null' : null ,
20+ 'true' : true ,
21+ 'false' : false
22+ } ;
23+
1324/**
14- * Returns true if the given value is truthy, or the `left` value is contained within
15- * the `right` value.
25+ * Returns true if the given value is truthy, or the `value` ("left") is
26+ * equal to or contained within the `context` ("right") value. This method is
27+ * used by the `whence()` function (the main export), but you can use this
28+ * method directly if you don't want the values to be evaluated.
1629 *
1730 * @name equal
18- * @param {any } `left ` The value to test.
19- * @param {Object } `right ` The value to compare against.
31+ * @param {any } `value ` The value to test.
32+ * @param {Object } `context ` The value to compare against.
2033 * @param {[type] } `parent`
2134 * @return {Boolean } Returns true or false.
2235 * @api public
2336 */
2437
25- const equal = ( left , right , parent ) => {
26- if ( left === right ) return true ;
38+ const equal = ( value , context , options = { } ) => {
39+ const eq = ( a , b , parent , depth = 0 ) => {
40+ if ( a === b ) return true ;
2741
28- if ( typeof left === 'boolean' && ! parent ) {
29- if ( isPrimitive ( right ) ) return left === right ;
30- return left ;
31- }
42+ if ( a === 'undefined' || a === 'null' ) {
43+ return a === b ;
44+ }
3245
33- if ( isPrimitive ( left ) && isObject ( right ) ) {
34- return Boolean ( right [ left ] ) ;
35- }
46+ if ( typeof a === 'boolean' && ( ! parent || parent === context ) ) {
47+ return typeof b === 'boolean' ? a === b : a ;
48+ }
3649
37- if ( isPrimitive ( left ) && Array . isArray ( right ) ) {
38- return right . includes ( left ) ;
39- }
50+ if ( ( a === 'true' || a === 'false' ) && ( ! parent || parent === context ) ) {
51+ return a === 'true' ;
52+ }
4053
41- if ( Array . isArray ( left ) ) {
42- if ( isObject ( right ) ) {
43- return left . every ( ele => equal ( ele , right , left ) ) ;
54+ // only call function values at the root
55+ if ( typeof a === 'function' && depth === 0 ) {
56+ return a . call ( b , b , options ) ;
4457 }
4558
46- if ( Array . isArray ( right ) ) {
47- return left . every ( ( ele , i ) => equal ( ele , right [ i ] , left ) ) ;
59+ if ( typeof a === 'string' && ( isObject ( b ) && b === context || ( depth === 0 && context === undefined ) ) ) {
60+ if ( options . castBoolean === false || ( b && hasOwnProperty . call ( b , a ) ) ) {
61+ return Boolean ( b [ a ] ) ;
62+ }
63+
64+ return whence . sync ( a , b , options ) ;
4865 }
49- }
5066
51- if ( isObject ( left ) ) {
52- return isObject ( right ) && Object . entries ( left ) . every ( ( [ k , v ] ) => equal ( v , right [ k ] , left ) ) ;
53- }
67+ if ( isPrimitive ( a ) && isObject ( b ) ) {
68+ return Boolean ( b [ a ] ) ;
69+ }
70+
71+ if ( isPrimitive ( a ) && Array . isArray ( b ) ) {
72+ return b . includes ( a ) ;
73+ }
74+
75+ if ( a instanceof RegExp ) {
76+ return ! ( b instanceof RegExp ) ? false : a . toString ( ) === b . toString ( ) ;
77+ }
5478
55- return false ;
79+ if ( a instanceof Date ) {
80+ return ! ( b instanceof Date ) ? false : a . toString ( ) === b . toString ( ) ;
81+ }
82+
83+ if ( a instanceof Set ) {
84+ return b instanceof Set && eq ( [ ...a ] , [ ...b ] , a , depth + 1 ) ;
85+ }
86+
87+ if ( a instanceof Map ) {
88+ return b instanceof Map && [ ...a ] . every ( ( [ k , v ] ) => eq ( v , b . get ( k ) , a , depth + 1 ) ) ;
89+ }
90+
91+ if ( Array . isArray ( a ) ) {
92+ if ( isObject ( b ) ) {
93+ return a . every ( ele => eq ( ele , b , a , depth + 1 ) ) ;
94+ }
95+
96+ if ( Array . isArray ( b ) ) {
97+ return a . every ( ( ele , i ) => eq ( ele , b [ i ] , a , depth + 1 ) ) ;
98+ }
99+ }
100+
101+ if ( isObject ( a ) ) {
102+ return isObject ( b ) && Object . entries ( a ) . every ( ( [ k , v ] ) => eq ( v , b [ k ] , a , depth + 1 ) ) ;
103+ }
104+
105+ return false ;
106+ } ;
107+
108+ return eq ( value , context ) ;
56109} ;
57110
58111/**
@@ -66,9 +119,9 @@ const equal = (left, right, parent) => {
66119 * // Resuls in something like this:
67120 * // Node {
68121 * // type: 'BinaryExpression',
69- * // left : Node { type: 'Identifier', name: 'platform' },
122+ * // value : Node { type: 'Identifier', name: 'platform' },
70123 * // operator: '===',
71- * // right : Node {
124+ * // context : Node {
72125 * // type: 'StringLiteral',
73126 * // extra: { rawValue: 'darwin', raw: '"darwin"' },
74127 * // value: 'darwin'
@@ -115,7 +168,22 @@ const parse = (source, options = {}) => {
115168 * @api public
116169 */
117170
118- const whence = ( source , context = { } , options = { } ) => compile ( source , options ) ( context ) ;
171+ const whence = async ( source , context , options = { } ) => {
172+ if ( isAST ( source ) ) {
173+ return compile ( source , options ) ( context ) ;
174+ }
175+
176+ if ( typeof source !== 'string' || ( isPrimitive ( context ) && context !== undefined ) ) {
177+ return equal ( source , context , options ) ;
178+ }
179+
180+ if ( hasOwnProperty . call ( LITERALS , source ) ) {
181+ return options . castBoolean !== false ? Boolean ( LITERALS [ source ] ) : LITERALS [ source ] ;
182+ }
183+
184+ const result = compile ( source , options ) ( context ) ;
185+ return options . castBoolean !== false ? Boolean ( await result ) : result ;
186+ } ;
119187
120188/**
121189 * Synchronous version of [whence](#whence). Aliased as `whence.sync()`.
@@ -133,7 +201,22 @@ const whence = (source, context = {}, options = {}) => compile(source, options)(
133201 * @api public
134202 */
135203
136- const whenceSync = ( source , context = { } , options = { } ) => compileSync ( source , options ) ( context ) ;
204+ const whenceSync = ( source , context , options = { } ) => {
205+ if ( isAST ( source ) ) {
206+ return compile . sync ( source , options ) ( context ) ;
207+ }
208+
209+ if ( typeof source !== 'string' || ( isPrimitive ( context ) && context !== undefined ) ) {
210+ return equal ( source , context , options ) ;
211+ }
212+
213+ if ( hasOwnProperty . call ( LITERALS , source ) ) {
214+ return options . castBoolean !== false ? Boolean ( LITERALS [ source ] ) : LITERALS [ source ] ;
215+ }
216+
217+ const result = compile . sync ( source , options ) ( context ) ;
218+ return options . castBoolean !== false ? Boolean ( result ) : result ;
219+ } ;
137220
138221/**
139222 * Compiles the given expression and returns an async function.
@@ -153,10 +236,11 @@ const whenceSync = (source, context = {}, options = {}) => compileSync(source, o
153236 */
154237
155238const compile = ( source , options ) => {
156- const ast = parse ( source , options ) ;
239+ const opts = { strictVariables : false , booleanLogicalOperators : true , ...options } ;
240+ const ast = parse ( source , opts ) ;
157241
158242 return context => {
159- return evaluate ( ast , context , options ) ;
243+ return evaluate ( ast , context , opts ) ;
160244 } ;
161245} ;
162246
@@ -178,10 +262,11 @@ const compile = (source, options) => {
178262 */
179263
180264const compileSync = ( source , options ) => {
181- const ast = parse ( source , options ) ;
265+ const opts = { strictVariables : false , booleanLogicalOperators : true , ...options } ;
266+ const ast = parse ( source , opts ) ;
182267
183268 return context => {
184- return evaluate . sync ( ast , context , options ) ;
269+ return evaluate . sync ( ast , context , opts ) ;
185270 } ;
186271} ;
187272
0 commit comments