| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166 |
1×
158×
1×
1×
59×
1×
1×
133×
133×
41×
41×
92×
1×
1×
91×
5×
5×
86×
3×
3×
83×
2×
2×
81×
1×
1×
80×
2×
2×
2×
78×
1×
1×
77×
1×
1×
76×
4×
4×
72×
1×
1×
71×
1×
1×
70×
1×
1×
69×
1×
1×
68×
2×
2×
66×
10×
10×
56×
4×
4×
52×
6×
6×
3×
3×
3×
1×
46×
46×
46×
46×
46×
44×
44×
44×
43×
43×
43×
43×
72×
72×
72×
34×
34×
34×
34×
1×
| 'use strict'
function isArguments (obj) {
return Object.prototype.toString.call(obj) === '[object Arguments]'
}
module.exports = match
function match (obj, pattern) {
return match_(obj, pattern, [], [])
}
/**
1. If the object is a string, and the pattern is a RegExp, then return
true if `pattern.test(object)`.
2. Use loose equality (`==`) only for all other value types
(non-objects). `tmatch` cares more about shape and contents than
type. This step will also catch functions, with the useful
(default) property that only references to the same function are
considered equal. 'Ware the halting problem!
3. `null` *is* an object – a singleton value object, in fact – so if
either is `null`, return object == pattern.
4. Since the only way to make it this far is for `object` or `pattern`
to be an object, if `object` or `pattern` is *not* an object,
they're clearly not a match.
5. It's much faster to compare dates by numeric value (`.getTime()`)
than by lexical value.
6. Compare RegExps by their components, not the objects themselves.
7. The parts of an arguments list most people care about are the
arguments themselves, not the callee, which you shouldn't be
looking at anyway.
8. Objects are more complex:
1. Return `true` if `object` and `pattern` both have no properties.
2. Ensure that cyclical references don't blow up the stack.
3. Ensure that all the key names in `pattern` exist in `object`.
4. Ensure that all of the associated values match, recursively.
*/
/* istanbul ignore next */
var log = (/\btmatch\b/.test(process.env.NODE_DEBUG || '')) ?
console.error : function () {}
function match_ (obj, pattern, ca, cb) {
log('TMATCH', typeof obj, pattern)
if (obj == pattern) {
log('TMATCH same object or simple value, true')
return true
} else if (obj === null || pattern === null) {
log('TMATCH null test, already failed ==')
return false
} else if (typeof obj === 'string' && pattern instanceof RegExp) {
log('TMATCH string~=regexp test')
return pattern.test(obj)
} else if (typeof obj === 'string' && typeof pattern === 'string' && pattern) {
log('TMATCH string~=string test')
return obj.indexOf(pattern) !== -1
} else if (obj instanceof Date && pattern instanceof Date) {
log('TMATCH date test')
return obj.getTime() === pattern.getTime()
} else if (obj instanceof Date && typeof pattern === 'string') {
log('TMATCH date~=string test')
return obj.getTime() === new Date(pattern).getTime()
} else if (isArguments(obj) || isArguments(pattern)) {
log('TMATCH arguments test')
var slice = Array.prototype.slice
return match_(slice.call(obj), slice.call(pattern), ca, cb)
} else if (pattern === Buffer) {
log('TMATCH Buffer ctor')
return Buffer.isBuffer(obj)
} else if (pattern === Function) {
log('TMATCH Function ctor')
return typeof obj === 'function'
} else if (pattern === Number) {
log('TMATCH Number ctor (finite, not NaN)')
return typeof obj === 'number' && obj === obj && isFinite(obj)
} else if (pattern !== pattern) {
log('TMATCH NaN')
return obj !== obj
} else if (pattern === String) {
log('TMATCH String ctor')
return typeof obj === 'string'
} else if (pattern === Boolean) {
log('TMATCH Boolean ctor')
return typeof obj === 'boolean'
} else if (pattern === Array) {
log('TMATCH Array ctor', pattern, Array.isArray(obj))
return Array.isArray(obj)
} else if (typeof pattern === 'function' && typeof obj === 'object') {
log('TMATCH object~=function')
return obj instanceof pattern
} else if (typeof obj !== 'object' || typeof pattern !== 'object') {
log('TMATCH obj is not object, pattern is not object, false')
return false
} else if (obj instanceof RegExp && pattern instanceof RegExp) {
log('TMATCH regexp~=regexp test')
return obj.source === pattern.source &&
obj.global === pattern.global &&
obj.multiline === pattern.multiline &&
obj.lastIndex === pattern.lastIndex &&
obj.ignoreCase === pattern.ignoreCase
} else if (Buffer.isBuffer(obj) && Buffer.isBuffer(pattern)) {
log('TMATCH buffer test')
if (obj.equals) {
return obj.equals(pattern)
} else {
if (obj.length !== pattern.length) return false
for (var j = 0; j < obj.length; j++) if (obj[j] != pattern[j]) return false
return true
}
} else {
// both are objects. interesting case!
log('TMATCH object~=object test')
var kobj = Object.keys(obj)
var kpat = Object.keys(pattern)
log(' TMATCH patternkeys=%j objkeys=%j', kpat, kobj)
// don't bother with stack acrobatics if there's nothing there
if (kobj.length === 0 && kpat.length === 0) return true
// if we've seen this exact pattern and object already, then
// it means that pattern and obj have matching cyclicalness
// however, non-cyclical patterns can match cyclical objects
log(' TMATCH check seen objects...')
var cal = ca.length
while (cal--) if (ca[cal] === obj && cb[cal] === pattern) return true
ca.push(obj); cb.push(pattern)
log(' TMATCH not seen previously')
var key
for (var l = kpat.length - 1; l >= 0; l--) {
key = kpat[l]
log(' TMATCH test obj[%j]', key, obj[key], pattern[key])
if (!match_(obj[key], pattern[key], ca, cb)) return false
}
ca.pop()
cb.pop()
log(' TMATCH object pass')
return true
}
/* istanbul ignore next */
throw new Error('impossible to reach this point')
}
|