mirror of
https://github.com/github/codeql-action.git
synced 2025-12-27 17:50:07 +08:00
Co-authored-by: Andrew Eisenberg <aeisenberg@github.com> Co-authored-by: Henry Mercer <henrymercer@github.com>
586 lines
13 KiB
JavaScript
586 lines
13 KiB
JavaScript
'use strict'
|
|
|
|
function _process (v, mod) {
|
|
var i
|
|
var r
|
|
|
|
if (typeof mod === 'function') {
|
|
r = mod(v)
|
|
if (r !== undefined) {
|
|
v = r
|
|
}
|
|
} else if (Array.isArray(mod)) {
|
|
for (i = 0; i < mod.length; i++) {
|
|
r = mod[i](v)
|
|
if (r !== undefined) {
|
|
v = r
|
|
}
|
|
}
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
function parseKey (key, val) {
|
|
// detect negative index notation
|
|
if (key[0] === '-' && Array.isArray(val) && /^-\d+$/.test(key)) {
|
|
return val.length + parseInt(key, 10)
|
|
}
|
|
return key
|
|
}
|
|
|
|
function isIndex (k) {
|
|
return /^\d+$/.test(k)
|
|
}
|
|
|
|
function isObject (val) {
|
|
return Object.prototype.toString.call(val) === '[object Object]'
|
|
}
|
|
|
|
function isArrayOrObject (val) {
|
|
return Object(val) === val
|
|
}
|
|
|
|
function isEmptyObject (val) {
|
|
return Object.keys(val).length === 0
|
|
}
|
|
|
|
var blacklist = ['__proto__', 'prototype', 'constructor']
|
|
var blacklistFilter = function (part) { return blacklist.indexOf(part) === -1 }
|
|
|
|
function parsePath (path, sep) {
|
|
if (path.indexOf('[') >= 0) {
|
|
path = path.replace(/\[/g, sep).replace(/]/g, '')
|
|
}
|
|
|
|
var parts = path.split(sep)
|
|
|
|
var check = parts.filter(blacklistFilter)
|
|
|
|
if (check.length !== parts.length) {
|
|
throw Error('Refusing to update blacklisted property ' + path)
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
var hasOwnProperty = Object.prototype.hasOwnProperty
|
|
|
|
function DotObject (separator, override, useArray, useBrackets) {
|
|
if (!(this instanceof DotObject)) {
|
|
return new DotObject(separator, override, useArray, useBrackets)
|
|
}
|
|
|
|
if (typeof override === 'undefined') override = false
|
|
if (typeof useArray === 'undefined') useArray = true
|
|
if (typeof useBrackets === 'undefined') useBrackets = true
|
|
this.separator = separator || '.'
|
|
this.override = override
|
|
this.useArray = useArray
|
|
this.useBrackets = useBrackets
|
|
this.keepArray = false
|
|
|
|
// contains touched arrays
|
|
this.cleanup = []
|
|
}
|
|
|
|
var dotDefault = new DotObject('.', false, true, true)
|
|
function wrap (method) {
|
|
return function () {
|
|
return dotDefault[method].apply(dotDefault, arguments)
|
|
}
|
|
}
|
|
|
|
DotObject.prototype._fill = function (a, obj, v, mod) {
|
|
var k = a.shift()
|
|
|
|
if (a.length > 0) {
|
|
obj[k] = obj[k] || (this.useArray && isIndex(a[0]) ? [] : {})
|
|
|
|
if (!isArrayOrObject(obj[k])) {
|
|
if (this.override) {
|
|
obj[k] = {}
|
|
} else {
|
|
if (!(isArrayOrObject(v) && isEmptyObject(v))) {
|
|
throw new Error(
|
|
'Trying to redefine `' + k + '` which is a ' + typeof obj[k]
|
|
)
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
this._fill(a, obj[k], v, mod)
|
|
} else {
|
|
if (!this.override && isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) {
|
|
if (!(isArrayOrObject(v) && isEmptyObject(v))) {
|
|
throw new Error("Trying to redefine non-empty obj['" + k + "']")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
obj[k] = _process(v, mod)
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Converts an object with dotted-key/value pairs to it's expanded version
|
|
*
|
|
* Optionally transformed by a set of modifiers.
|
|
*
|
|
* Usage:
|
|
*
|
|
* var row = {
|
|
* 'nr': 200,
|
|
* 'doc.name': ' My Document '
|
|
* }
|
|
*
|
|
* var mods = {
|
|
* 'doc.name': [_s.trim, _s.underscored]
|
|
* }
|
|
*
|
|
* dot.object(row, mods)
|
|
*
|
|
* @param {Object} obj
|
|
* @param {Object} mods
|
|
*/
|
|
DotObject.prototype.object = function (obj, mods) {
|
|
var self = this
|
|
|
|
Object.keys(obj).forEach(function (k) {
|
|
var mod = mods === undefined ? null : mods[k]
|
|
// normalize array notation.
|
|
var ok = parsePath(k, self.separator).join(self.separator)
|
|
|
|
if (ok.indexOf(self.separator) !== -1) {
|
|
self._fill(ok.split(self.separator), obj, obj[k], mod)
|
|
delete obj[k]
|
|
} else {
|
|
obj[k] = _process(obj[k], mod)
|
|
}
|
|
})
|
|
|
|
return obj
|
|
}
|
|
|
|
/**
|
|
* @param {String} path dotted path
|
|
* @param {String} v value to be set
|
|
* @param {Object} obj object to be modified
|
|
* @param {Function|Array} mod optional modifier
|
|
*/
|
|
DotObject.prototype.str = function (path, v, obj, mod) {
|
|
var ok = parsePath(path, this.separator).join(this.separator)
|
|
|
|
if (path.indexOf(this.separator) !== -1) {
|
|
this._fill(ok.split(this.separator), obj, v, mod)
|
|
} else {
|
|
obj[path] = _process(v, mod)
|
|
}
|
|
|
|
return obj
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Pick a value from an object using dot notation.
|
|
*
|
|
* Optionally remove the value
|
|
*
|
|
* @param {String} path
|
|
* @param {Object} obj
|
|
* @param {Boolean} remove
|
|
*/
|
|
DotObject.prototype.pick = function (path, obj, remove, reindexArray) {
|
|
var i
|
|
var keys
|
|
var val
|
|
var key
|
|
var cp
|
|
|
|
keys = parsePath(path, this.separator)
|
|
for (i = 0; i < keys.length; i++) {
|
|
key = parseKey(keys[i], obj)
|
|
if (obj && typeof obj === 'object' && key in obj) {
|
|
if (i === keys.length - 1) {
|
|
if (remove) {
|
|
val = obj[key]
|
|
if (reindexArray && Array.isArray(obj)) {
|
|
obj.splice(key, 1)
|
|
} else {
|
|
delete obj[key]
|
|
}
|
|
if (Array.isArray(obj)) {
|
|
cp = keys.slice(0, -1).join('.')
|
|
if (this.cleanup.indexOf(cp) === -1) {
|
|
this.cleanup.push(cp)
|
|
}
|
|
}
|
|
return val
|
|
} else {
|
|
return obj[key]
|
|
}
|
|
} else {
|
|
obj = obj[key]
|
|
}
|
|
} else {
|
|
return undefined
|
|
}
|
|
}
|
|
if (remove && Array.isArray(obj)) {
|
|
obj = obj.filter(function (n) {
|
|
return n !== undefined
|
|
})
|
|
}
|
|
return obj
|
|
}
|
|
/**
|
|
*
|
|
* Delete value from an object using dot notation.
|
|
*
|
|
* @param {String} path
|
|
* @param {Object} obj
|
|
* @return {any} The removed value
|
|
*/
|
|
DotObject.prototype.delete = function (path, obj) {
|
|
return this.remove(path, obj, true)
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Remove value from an object using dot notation.
|
|
*
|
|
* Will remove multiple items if path is an array.
|
|
* In this case array indexes will be retained until all
|
|
* removals have been processed.
|
|
*
|
|
* Use dot.delete() to automatically re-index arrays.
|
|
*
|
|
* @param {String|Array<String>} path
|
|
* @param {Object} obj
|
|
* @param {Boolean} reindexArray
|
|
* @return {any} The removed value
|
|
*/
|
|
DotObject.prototype.remove = function (path, obj, reindexArray) {
|
|
var i
|
|
|
|
this.cleanup = []
|
|
if (Array.isArray(path)) {
|
|
for (i = 0; i < path.length; i++) {
|
|
this.pick(path[i], obj, true, reindexArray)
|
|
}
|
|
if (!reindexArray) {
|
|
this._cleanup(obj)
|
|
}
|
|
return obj
|
|
} else {
|
|
return this.pick(path, obj, true, reindexArray)
|
|
}
|
|
}
|
|
|
|
DotObject.prototype._cleanup = function (obj) {
|
|
var ret
|
|
var i
|
|
var keys
|
|
var root
|
|
if (this.cleanup.length) {
|
|
for (i = 0; i < this.cleanup.length; i++) {
|
|
keys = this.cleanup[i].split('.')
|
|
root = keys.splice(0, -1).join('.')
|
|
ret = root ? this.pick(root, obj) : obj
|
|
ret = ret[keys[0]].filter(function (v) {
|
|
return v !== undefined
|
|
})
|
|
this.set(this.cleanup[i], ret, obj)
|
|
}
|
|
this.cleanup = []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Alias method for `dot.remove`
|
|
*
|
|
* Note: this is not an alias for dot.delete()
|
|
*
|
|
* @param {String|Array<String>} path
|
|
* @param {Object} obj
|
|
* @param {Boolean} reindexArray
|
|
* @return {any} The removed value
|
|
*/
|
|
DotObject.prototype.del = DotObject.prototype.remove
|
|
|
|
/**
|
|
*
|
|
* Move a property from one place to the other.
|
|
*
|
|
* If the source path does not exist (undefined)
|
|
* the target property will not be set.
|
|
*
|
|
* @param {String} source
|
|
* @param {String} target
|
|
* @param {Object} obj
|
|
* @param {Function|Array} mods
|
|
* @param {Boolean} merge
|
|
*/
|
|
DotObject.prototype.move = function (source, target, obj, mods, merge) {
|
|
if (typeof mods === 'function' || Array.isArray(mods)) {
|
|
this.set(target, _process(this.pick(source, obj, true), mods), obj, merge)
|
|
} else {
|
|
merge = mods
|
|
this.set(target, this.pick(source, obj, true), obj, merge)
|
|
}
|
|
|
|
return obj
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Transfer a property from one object to another object.
|
|
*
|
|
* If the source path does not exist (undefined)
|
|
* the property on the other object will not be set.
|
|
*
|
|
* @param {String} source
|
|
* @param {String} target
|
|
* @param {Object} obj1
|
|
* @param {Object} obj2
|
|
* @param {Function|Array} mods
|
|
* @param {Boolean} merge
|
|
*/
|
|
DotObject.prototype.transfer = function (
|
|
source,
|
|
target,
|
|
obj1,
|
|
obj2,
|
|
mods,
|
|
merge
|
|
) {
|
|
if (typeof mods === 'function' || Array.isArray(mods)) {
|
|
this.set(
|
|
target,
|
|
_process(this.pick(source, obj1, true), mods),
|
|
obj2,
|
|
merge
|
|
)
|
|
} else {
|
|
merge = mods
|
|
this.set(target, this.pick(source, obj1, true), obj2, merge)
|
|
}
|
|
|
|
return obj2
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Copy a property from one object to another object.
|
|
*
|
|
* If the source path does not exist (undefined)
|
|
* the property on the other object will not be set.
|
|
*
|
|
* @param {String} source
|
|
* @param {String} target
|
|
* @param {Object} obj1
|
|
* @param {Object} obj2
|
|
* @param {Function|Array} mods
|
|
* @param {Boolean} merge
|
|
*/
|
|
DotObject.prototype.copy = function (source, target, obj1, obj2, mods, merge) {
|
|
if (typeof mods === 'function' || Array.isArray(mods)) {
|
|
this.set(
|
|
target,
|
|
_process(
|
|
// clone what is picked
|
|
JSON.parse(JSON.stringify(this.pick(source, obj1, false))),
|
|
mods
|
|
),
|
|
obj2,
|
|
merge
|
|
)
|
|
} else {
|
|
merge = mods
|
|
this.set(target, this.pick(source, obj1, false), obj2, merge)
|
|
}
|
|
|
|
return obj2
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Set a property on an object using dot notation.
|
|
*
|
|
* @param {String} path
|
|
* @param {any} val
|
|
* @param {Object} obj
|
|
* @param {Boolean} merge
|
|
*/
|
|
DotObject.prototype.set = function (path, val, obj, merge) {
|
|
var i
|
|
var k
|
|
var keys
|
|
var key
|
|
|
|
// Do not operate if the value is undefined.
|
|
if (typeof val === 'undefined') {
|
|
return obj
|
|
}
|
|
keys = parsePath(path, this.separator)
|
|
|
|
for (i = 0; i < keys.length; i++) {
|
|
key = keys[i]
|
|
if (i === keys.length - 1) {
|
|
if (merge && isObject(val) && isObject(obj[key])) {
|
|
for (k in val) {
|
|
if (hasOwnProperty.call(val, k)) {
|
|
obj[key][k] = val[k]
|
|
}
|
|
}
|
|
} else if (merge && Array.isArray(obj[key]) && Array.isArray(val)) {
|
|
for (var j = 0; j < val.length; j++) {
|
|
obj[keys[i]].push(val[j])
|
|
}
|
|
} else {
|
|
obj[key] = val
|
|
}
|
|
} else if (
|
|
// force the value to be an object
|
|
!hasOwnProperty.call(obj, key) ||
|
|
(!isObject(obj[key]) && !Array.isArray(obj[key]))
|
|
) {
|
|
// initialize as array if next key is numeric
|
|
if (/^\d+$/.test(keys[i + 1])) {
|
|
obj[key] = []
|
|
} else {
|
|
obj[key] = {}
|
|
}
|
|
}
|
|
obj = obj[key]
|
|
}
|
|
return obj
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Transform an object
|
|
*
|
|
* Usage:
|
|
*
|
|
* var obj = {
|
|
* "id": 1,
|
|
* "some": {
|
|
* "thing": "else"
|
|
* }
|
|
* }
|
|
*
|
|
* var transform = {
|
|
* "id": "nr",
|
|
* "some.thing": "name"
|
|
* }
|
|
*
|
|
* var tgt = dot.transform(transform, obj)
|
|
*
|
|
* @param {Object} recipe Transform recipe
|
|
* @param {Object} obj Object to be transformed
|
|
* @param {Array} mods modifiers for the target
|
|
*/
|
|
DotObject.prototype.transform = function (recipe, obj, tgt) {
|
|
obj = obj || {}
|
|
tgt = tgt || {}
|
|
Object.keys(recipe).forEach(
|
|
function (key) {
|
|
this.set(recipe[key], this.pick(key, obj), tgt)
|
|
}.bind(this)
|
|
)
|
|
return tgt
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Convert object to dotted-key/value pair
|
|
*
|
|
* Usage:
|
|
*
|
|
* var tgt = dot.dot(obj)
|
|
*
|
|
* or
|
|
*
|
|
* var tgt = {}
|
|
* dot.dot(obj, tgt)
|
|
*
|
|
* @param {Object} obj source object
|
|
* @param {Object} tgt target object
|
|
* @param {Array} path path array (internal)
|
|
*/
|
|
DotObject.prototype.dot = function (obj, tgt, path) {
|
|
tgt = tgt || {}
|
|
path = path || []
|
|
var isArray = Array.isArray(obj)
|
|
|
|
Object.keys(obj).forEach(
|
|
function (key) {
|
|
var index = isArray && this.useBrackets ? '[' + key + ']' : key
|
|
if (
|
|
isArrayOrObject(obj[key]) &&
|
|
((isObject(obj[key]) && !isEmptyObject(obj[key])) ||
|
|
(Array.isArray(obj[key]) && !this.keepArray && obj[key].length !== 0))
|
|
) {
|
|
if (isArray && this.useBrackets) {
|
|
var previousKey = path[path.length - 1] || ''
|
|
return this.dot(
|
|
obj[key],
|
|
tgt,
|
|
path.slice(0, -1).concat(previousKey + index)
|
|
)
|
|
} else {
|
|
return this.dot(obj[key], tgt, path.concat(index))
|
|
}
|
|
} else {
|
|
if (isArray && this.useBrackets) {
|
|
tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key]
|
|
} else {
|
|
tgt[path.concat(index).join(this.separator)] = obj[key]
|
|
}
|
|
}
|
|
}.bind(this)
|
|
)
|
|
return tgt
|
|
}
|
|
|
|
DotObject.pick = wrap('pick')
|
|
DotObject.move = wrap('move')
|
|
DotObject.transfer = wrap('transfer')
|
|
DotObject.transform = wrap('transform')
|
|
DotObject.copy = wrap('copy')
|
|
DotObject.object = wrap('object')
|
|
DotObject.str = wrap('str')
|
|
DotObject.set = wrap('set')
|
|
DotObject.delete = wrap('delete')
|
|
DotObject.del = DotObject.remove = wrap('remove')
|
|
DotObject.dot = wrap('dot');
|
|
['override', 'overwrite'].forEach(function (prop) {
|
|
Object.defineProperty(DotObject, prop, {
|
|
get: function () {
|
|
return dotDefault.override
|
|
},
|
|
set: function (val) {
|
|
dotDefault.override = !!val
|
|
}
|
|
})
|
|
});
|
|
['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) {
|
|
Object.defineProperty(DotObject, prop, {
|
|
get: function () {
|
|
return dotDefault[prop]
|
|
},
|
|
set: function (val) {
|
|
dotDefault[prop] = val
|
|
}
|
|
})
|
|
})
|
|
|
|
DotObject._process = _process
|
|
|
|
module.exports = DotObject
|