Skip to content

Commit

Permalink
refactor: replace LoDash isPlainObject, mapValues, and isMap
Browse files Browse the repository at this point in the history
- `isMap` was added to `util.types` in Node 10.0.
  https://nodejs.org/api/util.html#util_util_types_ismap_value
- `isPlainObject` and `mapValue` both came from LoDash's master branch, which is the pending v5.
- Replace LoDash dependency with `lodash.set` to cover the one use we still have.

Ref: nock#1285
  • Loading branch information
mastermatt committed Mar 29, 2020
1 parent c11eec7 commit b85f914
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 44 deletions.
118 changes: 84 additions & 34 deletions lib/common.js
@@ -1,9 +1,10 @@
'use strict'

const _ = require('lodash')
const debug = require('debug')('nock.common')
const url = require('url')
const set = require('lodash.set')
const timers = require('timers')
const url = require('url')
const util = require('util')

/**
* Normalizes the request options so that it always has `host` property.
Expand Down Expand Up @@ -194,7 +195,7 @@ function isJSONContent(headers) {
* Duplicates throw an error.
*/
function headersFieldNamesToLowerCase(headers) {
if (!_.isPlainObject(headers)) {
if (!isPlainObject(headers)) {
throw Error('Headers must be provided as an object')
}

Expand Down Expand Up @@ -243,11 +244,11 @@ function headersInputToRawArray(headers) {
}

// [].concat(...) is used instead of Array.flat until v11 is the minimum Node version
if (_.isMap(headers)) {
if (util.types.isMap(headers)) {
return [].concat(...Array.from(headers, ([k, v]) => [k.toString(), v]))
}

if (_.isPlainObject(headers)) {
if (isPlainObject(headers)) {
return [].concat(...Object.entries(headers))
}

Expand Down Expand Up @@ -359,7 +360,7 @@ function addHeaderLine(headers, name, value) {
* @fieldName {String} field name - string with the case-insensitive field name
*/
function deleteHeadersField(headers, fieldNameToDelete) {
if (!_.isPlainObject(headers)) {
if (!isPlainObject(headers)) {
throw Error('headers must be an object')
}

Expand Down Expand Up @@ -559,7 +560,7 @@ const dataEqual = (expected, actual) =>
* { 'foo[bar][0]': 'baz' } -> { foo: { bar: [ 'baz' ] } }
*/
const expand = input =>
Object.entries(input).reduce((acc, [k, v]) => _.set(acc, k, v), {})
Object.entries(input).reduce((acc, [k, v]) => set(acc, k, v), {})

/**
* Performs a recursive strict comparison between two values.
Expand All @@ -572,7 +573,7 @@ function deepEqual(expected, actual) {
return expected.test(actual)
}

if (Array.isArray(expected) || _.isPlainObject(expected)) {
if (Array.isArray(expected) || isPlainObject(expected)) {
if (actual === undefined) {
return false
}
Expand All @@ -588,6 +589,51 @@ function deepEqual(expected, actual) {
return expected === actual
}

/**
* Checks if `value` is a plain object, that is, an object created by the
* `Object` constructor or one with a `[[Prototype]]` of `null`.
* https://github.com/lodash/lodash/blob/588bf3e20db0ae039a822a14a8fa238c5b298e65/isPlainObject.js
*
* @param {*} value The value to check.
* @return {boolean}
*/
function isPlainObject(value) {
const isObjectLike = typeof value === 'object' && value !== null
const tag = Object.prototype.toString.call(value)
if (!isObjectLike || tag !== '[object Object]') {
return false
}
if (Object.getPrototypeOf(value) === null) {
return true
}
let proto = value
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(value) === proto
}

/**
* Creates an object with the same keys as `object` and values generated
* by running each own enumerable string keyed property of `object` thru
* `iteratee`. (iteration order is not guaranteed)
* The iteratee is invoked with three arguments: (value, key, object).
* https://github.com/lodash/lodash/blob/588bf3e20db0ae039a822a14a8fa238c5b298e65/mapValue.js
*
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Object} Returns the new mapped object.
*/
function mapValue(object, iteratee) {
object = Object(object)
const result = {}

Object.keys(object).forEach(key => {
result[key] = iteratee(object[key], key, object)
})
return result
}

const timeouts = []
const intervals = []
const immediates = []
Expand All @@ -614,29 +660,33 @@ function removeAllTimers() {
clearTimer(clearImmediate, immediates)
}

exports.normalizeClientRequestArgs = normalizeClientRequestArgs
exports.normalizeRequestOptions = normalizeRequestOptions
exports.normalizeOrigin = normalizeOrigin
exports.isUtf8Representable = isUtf8Representable
exports.overrideRequests = overrideRequests
exports.restoreOverriddenRequests = restoreOverriddenRequests
exports.stringifyRequest = stringifyRequest
exports.isContentEncoded = isContentEncoded
exports.contentEncoding = contentEncoding
exports.isJSONContent = isJSONContent
exports.headersFieldNamesToLowerCase = headersFieldNamesToLowerCase
exports.headersFieldsArrayToLowerCase = headersFieldsArrayToLowerCase
exports.headersArrayToObject = headersArrayToObject
exports.headersInputToRawArray = headersInputToRawArray
exports.deleteHeadersField = deleteHeadersField
exports.forEachHeader = forEachHeader
exports.percentEncode = percentEncode
exports.percentDecode = percentDecode
exports.matchStringOrRegexp = matchStringOrRegexp
exports.formatQueryValue = formatQueryValue
exports.isStream = isStream
exports.dataEqual = dataEqual
exports.setTimeout = setTimeout
exports.setInterval = setInterval
exports.setImmediate = setImmediate
exports.removeAllTimers = removeAllTimers
module.exports = {
contentEncoding,
dataEqual,
deleteHeadersField,
forEachHeader,
formatQueryValue,
headersArrayToObject,
headersFieldNamesToLowerCase,
headersFieldsArrayToLowerCase,
headersInputToRawArray,
isContentEncoded,
isJSONContent,
isPlainObject,
isStream,
isUtf8Representable,
mapValue,
matchStringOrRegexp,
normalizeClientRequestArgs,
normalizeOrigin,
normalizeRequestOptions,
overrideRequests,
percentDecode,
percentEncode,
removeAllTimers,
restoreOverriddenRequests,
setImmediate,
setInterval,
setTimeout,
stringifyRequest,
}
3 changes: 1 addition & 2 deletions lib/interceptor.js
Expand Up @@ -2,7 +2,6 @@

const debug = require('debug')('nock.interceptor')
const stringify = require('json-stringify-safe')
const _ = require('lodash')
const querystring = require('querystring')
const { URL, URLSearchParams } = require('url')

Expand Down Expand Up @@ -483,7 +482,7 @@ module.exports = class Interceptor {
// Normalize the data into the shape that is matched against.
// Duplicate keys are handled by combining the values into an array.
queries = querystring.parse(queries.toString())
} else if (!_.isPlainObject(queries)) {
} else if (!common.isPlainObject(queries)) {
throw Error(`Argument Error: ${queries}`)
}

Expand Down
5 changes: 2 additions & 3 deletions lib/match_body.js
@@ -1,6 +1,5 @@
'use strict'

const _ = require('lodash')
const querystring = require('querystring')

const common = require('./common')
Expand Down Expand Up @@ -70,8 +69,8 @@ function mapValuesDeep(obj, cb) {
if (Array.isArray(obj)) {
return obj.map(v => mapValuesDeep(v, cb))
}
if (_.isPlainObject(obj)) {
return _.mapValues(obj, v => mapValuesDeep(v, cb))
if (common.isPlainObject(obj)) {
return common.mapValue(obj, v => mapValuesDeep(v, cb))
}
return cb(obj)
}
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -24,7 +24,7 @@
"dependencies": {
"debug": "^4.1.0",
"json-stringify-safe": "^5.0.1",
"lodash": "^4.17.13",
"lodash.set": "^4.3.2",
"propagate": "^2.0.0"
},
"devDependencies": {
Expand Down

0 comments on commit b85f914

Please sign in to comment.