Skip to content

Commit dc75c5f

Browse files
author
Guillaume Chau
authored
Search improvements (#572)
* Search improvements * Comments + some cleaning up * Typo * Missing depth argument * Clear seen Map
1 parent 28df5c4 commit dc75c5f

File tree

3 files changed

+117
-37
lines changed

3 files changed

+117
-37
lines changed

shells/dev/target/NativeTypes.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export default {
9999
100100
theStore () {
101101
return this.$store
102-
},
102+
}
103103
},
104104
105105
mounted () {

src/devtools/components/DataField.vue

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ import {
166166
isPlainObject,
167167
sortByKey,
168168
openInEditor,
169-
escape
169+
escape,
170+
specialTokenToString
170171
} from 'src/util'
171172
172173
import DataFieldEdit from '../mixins/data-field-edit'
@@ -269,18 +270,11 @@ export default {
269270
270271
formattedValue () {
271272
const value = this.field.value
273+
let result
272274
if (this.fieldOptions.abstract) {
273275
return ''
274-
} else if (value === null) {
275-
return 'null'
276-
} else if (value === UNDEFINED) {
277-
return 'undefined'
278-
} else if (value === NAN) {
279-
return 'NaN'
280-
} else if (value === INFINITY) {
281-
return 'Infinity'
282-
} else if (value === NEGATIVE_INFINITY) {
283-
return '-Infinity'
276+
} else if ((result = specialTokenToString(value))) {
277+
return result
284278
} else if (this.valueType === 'custom') {
285279
return value._custom.display
286280
} else if (this.valueType === 'array') {

src/util.js

Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ export const SPECIAL_TOKENS = {
5858
'NaN': NAN
5959
}
6060

61+
export function specialTokenToString (value) {
62+
if (value === null) {
63+
return 'null'
64+
} else if (value === UNDEFINED) {
65+
return 'undefined'
66+
} else if (value === NAN) {
67+
return 'NaN'
68+
} else if (value === INFINITY) {
69+
return 'Infinity'
70+
} else if (value === NEGATIVE_INFINITY) {
71+
return '-Infinity'
72+
}
73+
return false
74+
}
75+
6176
/**
6277
* Needed to prevent stack overflow
6378
* while replacing complex objects
@@ -313,45 +328,116 @@ function isPrimitive (data) {
313328
)
314329
}
315330

331+
/**
332+
* Searches a key or value in the object, with a maximum deepness
333+
* @param {*} obj Search target
334+
* @param {string} searchTerm Search string
335+
* @returns {boolean} Search match
336+
*/
316337
export function searchDeepInObject (obj, searchTerm) {
317-
var match = false
338+
const seen = new Map()
339+
const result = internalSearchObject(obj, searchTerm.toLowerCase(), seen, 0)
340+
seen.clear()
341+
return result
342+
}
343+
344+
const SEARCH_MAX_DEPTH = 10
345+
346+
/**
347+
* Executes a search on each field of the provided object
348+
* @param {*} obj Search target
349+
* @param {string} searchTerm Search string
350+
* @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times
351+
* @param {number} depth Deep search depth level, which is capped to prevent performance issues
352+
* @returns {boolean} Search match
353+
*/
354+
function internalSearchObject (obj, searchTerm, seen, depth) {
355+
if (depth > SEARCH_MAX_DEPTH) {
356+
return false
357+
}
358+
let match = false
318359
const keys = Object.keys(obj)
360+
let key, value
319361
for (let i = 0; i < keys.length; i++) {
320-
const key = keys[i]
321-
const value = obj[key]
322-
if (compare(key, searchTerm) || compare(value, searchTerm)) {
323-
match = true
362+
key = keys[i]
363+
value = obj[key]
364+
match = interalSearchCheck(searchTerm, key, value, seen, depth + 1)
365+
if (match) {
324366
break
325367
}
326-
if (isPlainObject(value)) {
327-
match = searchDeepInObject(value, searchTerm)
328-
if (match) {
329-
break
330-
}
331-
}
332368
}
333369
return match
334370
}
335371

336-
function compare (mixedValue, stringValue) {
337-
if (Array.isArray(mixedValue) && searchInArray(mixedValue, stringValue.toLowerCase())) {
338-
return true
372+
/**
373+
* Executes a search on each value of the provided array
374+
* @param {*} array Search target
375+
* @param {string} searchTerm Search string
376+
* @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times
377+
* @param {number} depth Deep search depth level, which is capped to prevent performance issues
378+
* @returns {boolean} Search match
379+
*/
380+
function internalSearchArray (array, searchTerm, seen, depth) {
381+
if (depth > SEARCH_MAX_DEPTH) {
382+
return false
339383
}
340-
if (('' + mixedValue).toLowerCase().indexOf(stringValue.toLowerCase()) !== -1) {
341-
return true
384+
let match = false
385+
let value
386+
for (let i = 0; i < array.length; i++) {
387+
value = array[i]
388+
match = interalSearchCheck(searchTerm, null, value, seen, depth + 1)
389+
if (match) {
390+
break
391+
}
342392
}
343-
return false
393+
return match
344394
}
345395

346-
function searchInArray (arr, searchTerm) {
347-
let found = false
348-
for (let i = 0; i < arr.length; i++) {
349-
if (('' + arr[i]).toLowerCase().indexOf(searchTerm) !== -1) {
350-
found = true
351-
break
352-
}
396+
/**
397+
* Checks if the provided field matches the search terms
398+
* @param {string} searchTerm Search string
399+
* @param {string} key Field key (null if from array)
400+
* @param {*} value Field value
401+
* @param {Map<any,boolean>} seen Map containing the search result to prevent stack overflow by walking on the same object multiple times
402+
* @param {number} depth Deep search depth level, which is capped to prevent performance issues
403+
* @returns {boolean} Search match
404+
*/
405+
function interalSearchCheck (searchTerm, key, value, seen, depth) {
406+
let match = false
407+
let result
408+
if (key === '_custom') {
409+
key = value.display
410+
value = value.value
353411
}
354-
return found
412+
(result = specialTokenToString(value)) && (value = result)
413+
if (key && compare(key, searchTerm)) {
414+
match = true
415+
seen.set(value, true)
416+
} else if (seen.has(value)) {
417+
match = seen.get(value)
418+
} else if (Array.isArray(value)) {
419+
seen.set(value, null)
420+
match = internalSearchArray(value, searchTerm, seen, depth)
421+
seen.set(value, match)
422+
} else if (isPlainObject(value)) {
423+
seen.set(value, null)
424+
match = internalSearchObject(value, searchTerm, seen, depth)
425+
seen.set(value, match)
426+
} else if (compare(value, searchTerm)) {
427+
match = true
428+
seen.set(value, true)
429+
}
430+
return match
431+
}
432+
433+
/**
434+
* Compares two values
435+
* @param {*} value Mixed type value that will be cast to string
436+
* @param {string} searchTerm Search string
437+
* @returns {boolean} Search match
438+
*/
439+
function compare (value, searchTerm) {
440+
return ('' + value).toLowerCase().indexOf(searchTerm) !== -1
355441
}
356442

357443
export function sortByKey (state) {

0 commit comments

Comments
 (0)