diff --git a/javascript/ql/src/semmle/javascript/frameworks/LodashUnderscore.qll b/javascript/ql/src/semmle/javascript/frameworks/LodashUnderscore.qll index 7ab3e2f8d20b..97bfeea16818 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/LodashUnderscore.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/LodashUnderscore.qll @@ -21,10 +21,14 @@ module LodashUnderscore { string name; DefaultMember() { - this = DataFlow::moduleMember("underscore", name) or - this = DataFlow::moduleMember("lodash", name) or - this = DataFlow::moduleImport("lodash/" + name) or - this = DataFlow::moduleImport("lodash." + name) or + this = DataFlow::moduleMember("underscore", name) + or + this = DataFlow::moduleMember("lodash", name) + or + this = DataFlow::moduleImport("lodash/" + name) + or + this = DataFlow::moduleImport("lodash." + name.toLowerCase()) and isLodashMember(name) + or this = DataFlow::globalVarRef("_").getAPropertyRead(name) } @@ -38,6 +42,319 @@ module LodashUnderscore { * In addition, the global variable `_` is assumed to refer to `lodash` or `underscore`. */ DataFlow::SourceNode member(string name) { result.(Member).getName() = name } + + /** + * Holds if `name` is the name of a member exported from the `lodash` package + * which has a corresponding `lodash.xxx` NPM package. + */ + private predicate isLodashMember(string name) { // Can be generated using Object.keys(require('lodash')) + name = "templateSettings" or + name = "after" or + name = "ary" or + name = "assign" or + name = "assignIn" or + name = "assignInWith" or + name = "assignWith" or + name = "at" or + name = "before" or + name = "bind" or + name = "bindAll" or + name = "bindKey" or + name = "castArray" or + name = "chain" or + name = "chunk" or + name = "compact" or + name = "concat" or + name = "cond" or + name = "conforms" or + name = "constant" or + name = "countBy" or + name = "create" or + name = "curry" or + name = "curryRight" or + name = "debounce" or + name = "defaults" or + name = "defaultsDeep" or + name = "defer" or + name = "delay" or + name = "difference" or + name = "differenceBy" or + name = "differenceWith" or + name = "drop" or + name = "dropRight" or + name = "dropRightWhile" or + name = "dropWhile" or + name = "fill" or + name = "filter" or + name = "flatMap" or + name = "flatMapDeep" or + name = "flatMapDepth" or + name = "flatten" or + name = "flattenDeep" or + name = "flattenDepth" or + name = "flip" or + name = "flow" or + name = "flowRight" or + name = "fromPairs" or + name = "functions" or + name = "functionsIn" or + name = "groupBy" or + name = "initial" or + name = "intersection" or + name = "intersectionBy" or + name = "intersectionWith" or + name = "invert" or + name = "invertBy" or + name = "invokeMap" or + name = "iteratee" or + name = "keyBy" or + name = "keys" or + name = "keysIn" or + name = "map" or + name = "mapKeys" or + name = "mapValues" or + name = "matches" or + name = "matchesProperty" or + name = "memoize" or + name = "merge" or + name = "mergeWith" or + name = "method" or + name = "methodOf" or + name = "mixin" or + name = "negate" or + name = "nthArg" or + name = "omit" or + name = "omitBy" or + name = "once" or + name = "orderBy" or + name = "over" or + name = "overArgs" or + name = "overEvery" or + name = "overSome" or + name = "partial" or + name = "partialRight" or + name = "partition" or + name = "pick" or + name = "pickBy" or + name = "property" or + name = "propertyOf" or + name = "pull" or + name = "pullAll" or + name = "pullAllBy" or + name = "pullAllWith" or + name = "pullAt" or + name = "range" or + name = "rangeRight" or + name = "rearg" or + name = "reject" or + name = "remove" or + name = "rest" or + name = "reverse" or + name = "sampleSize" or + name = "set" or + name = "setWith" or + name = "shuffle" or + name = "slice" or + name = "sortBy" or + name = "sortedUniq" or + name = "sortedUniqBy" or + name = "split" or + name = "spread" or + name = "tail" or + name = "take" or + name = "takeRight" or + name = "takeRightWhile" or + name = "takeWhile" or + name = "tap" or + name = "throttle" or + name = "thru" or + name = "toArray" or + name = "toPairs" or + name = "toPairsIn" or + name = "toPath" or + name = "toPlainObject" or + name = "transform" or + name = "unary" or + name = "union" or + name = "unionBy" or + name = "unionWith" or + name = "uniq" or + name = "uniqBy" or + name = "uniqWith" or + name = "unset" or + name = "unzip" or + name = "unzipWith" or + name = "update" or + name = "updateWith" or + name = "values" or + name = "valuesIn" or + name = "without" or + name = "words" or + name = "wrap" or + name = "xor" or + name = "xorBy" or + name = "xorWith" or + name = "zip" or + name = "zipObject" or + name = "zipObjectDeep" or + name = "zipWith" or + name = "entries" or + name = "entriesIn" or + name = "extend" or + name = "extendWith" or + name = "add" or + name = "attempt" or + name = "camelCase" or + name = "capitalize" or + name = "ceil" or + name = "clamp" or + name = "clone" or + name = "cloneDeep" or + name = "cloneDeepWith" or + name = "cloneWith" or + name = "conformsTo" or + name = "deburr" or + name = "defaultTo" or + name = "divide" or + name = "endsWith" or + name = "eq" or + name = "escape" or + name = "escapeRegExp" or + name = "every" or + name = "find" or + name = "findIndex" or + name = "findKey" or + name = "findLast" or + name = "findLastIndex" or + name = "findLastKey" or + name = "floor" or + name = "forEach" or + name = "forEachRight" or + name = "forIn" or + name = "forInRight" or + name = "forOwn" or + name = "forOwnRight" or + name = "get" or + name = "gt" or + name = "gte" or + name = "has" or + name = "hasIn" or + name = "head" or + name = "identity" or + name = "includes" or + name = "indexOf" or + name = "inRange" or + name = "invoke" or + name = "isArguments" or + name = "isArray" or + name = "isArrayBuffer" or + name = "isArrayLike" or + name = "isArrayLikeObject" or + name = "isBoolean" or + name = "isBuffer" or + name = "isDate" or + name = "isElement" or + name = "isEmpty" or + name = "isEqual" or + name = "isEqualWith" or + name = "isError" or + name = "isFinite" or + name = "isFunction" or + name = "isInteger" or + name = "isLength" or + name = "isMap" or + name = "isMatch" or + name = "isMatchWith" or + name = "isNaN" or + name = "isNative" or + name = "isNil" or + name = "isNull" or + name = "isNumber" or + name = "isObject" or + name = "isObjectLike" or + name = "isPlainObject" or + name = "isRegExp" or + name = "isSafeInteger" or + name = "isSet" or + name = "isString" or + name = "isSymbol" or + name = "isTypedArray" or + name = "isUndefined" or + name = "isWeakMap" or + name = "isWeakSet" or + name = "join" or + name = "kebabCase" or + name = "last" or + name = "lastIndexOf" or + name = "lowerCase" or + name = "lowerFirst" or + name = "lt" or + name = "lte" or + name = "max" or + name = "maxBy" or + name = "mean" or + name = "meanBy" or + name = "min" or + name = "minBy" or + name = "stubArray" or + name = "stubFalse" or + name = "stubObject" or + name = "stubString" or + name = "stubTrue" or + name = "multiply" or + name = "nth" or + name = "noConflict" or + name = "noop" or + name = "now" or + name = "pad" or + name = "padEnd" or + name = "padStart" or + name = "parseInt" or + name = "random" or + name = "reduce" or + name = "reduceRight" or + name = "repeat" or + name = "replace" or + name = "result" or + name = "round" or + name = "runInContext" or + name = "sample" or + name = "size" or + name = "snakeCase" or + name = "some" or + name = "sortedIndex" or + name = "sortedIndexBy" or + name = "sortedIndexOf" or + name = "sortedLastIndex" or + name = "sortedLastIndexBy" or + name = "sortedLastIndexOf" or + name = "startCase" or + name = "startsWith" or + name = "subtract" or + name = "sum" or + name = "sumBy" or + name = "template" or + name = "times" or + name = "toFinite" or + name = "toInteger" or + name = "toLength" or + name = "toLower" or + name = "toNumber" or + name = "toSafeInteger" or + name = "toString" or + name = "toUpper" or + name = "trim" or + name = "trimEnd" or + name = "trimStart" or + name = "truncate" or + name = "unescape" or + name = "uniqueId" or + name = "upperCase" or + name = "upperFirst" or + name = "each" or + name = "eachRight" or + name = "first" + } } /** diff --git a/javascript/ql/test/library-tests/Extend/ExtendCalls.expected b/javascript/ql/test/library-tests/Extend/ExtendCalls.expected index d930a1880760..89449bc4ff1b 100644 --- a/javascript/ql/test/library-tests/Extend/ExtendCalls.expected +++ b/javascript/ql/test/library-tests/Extend/ExtendCalls.expected @@ -23,19 +23,21 @@ | tst.js:55:1:55:49 | checkDe ... arg())) | OK | | tst.js:56:1:56:53 | checkDe ... arg())) | OK | | tst.js:57:1:57:56 | checkDe ... arg())) | OK | -| tst.js:61:1:61:42 | checkSh ... arg())) | OK | -| tst.js:62:1:62:48 | checkSh ... arg())) | OK | -| tst.js:63:1:63:54 | checkSh ... arg())) | OK | -| tst.js:64:1:64:45 | checkSh ... arg())) | OK | -| tst.js:65:1:65:52 | checkSh ... arg())) | OK | -| tst.js:66:1:66:53 | checkSh ... arg())) | OK | -| tst.js:67:1:67:53 | checkSh ... arg())) | OK | -| tst.js:68:1:68:55 | checkSh ... arg())) | OK | -| tst.js:69:1:69:52 | checkSh ... arg())) | OK | -| tst.js:70:1:70:51 | checkSh ... arg())) | OK | -| tst.js:71:1:71:51 | checkSh ... arg())) | OK | -| tst.js:72:1:72:53 | checkSh ... arg())) | OK | -| tst.js:73:1:73:53 | checkSh ... arg())) | OK | -| tst.js:77:1:77:45 | checkSh ... arg())) | OK | -| tst.js:78:1:78:55 | checkSh ... arg())) | OK | -| tst.js:79:1:79:51 | checkSh ... arg())) | OK | +| tst.js:58:1:58:53 | checkDe ... arg())) | OK | +| tst.js:59:1:59:56 | checkDe ... arg())) | OK | +| tst.js:63:1:63:42 | checkSh ... arg())) | OK | +| tst.js:64:1:64:48 | checkSh ... arg())) | OK | +| tst.js:65:1:65:54 | checkSh ... arg())) | OK | +| tst.js:66:1:66:45 | checkSh ... arg())) | OK | +| tst.js:67:1:67:52 | checkSh ... arg())) | OK | +| tst.js:68:1:68:53 | checkSh ... arg())) | OK | +| tst.js:69:1:69:53 | checkSh ... arg())) | OK | +| tst.js:70:1:70:55 | checkSh ... arg())) | OK | +| tst.js:71:1:71:52 | checkSh ... arg())) | OK | +| tst.js:72:1:72:51 | checkSh ... arg())) | OK | +| tst.js:73:1:73:51 | checkSh ... arg())) | OK | +| tst.js:74:1:74:53 | checkSh ... arg())) | OK | +| tst.js:75:1:75:53 | checkSh ... arg())) | OK | +| tst.js:79:1:79:45 | checkSh ... arg())) | OK | +| tst.js:80:1:80:55 | checkSh ... arg())) | OK | +| tst.js:81:1:81:51 | checkSh ... arg())) | OK | diff --git a/javascript/ql/test/library-tests/Extend/package.json b/javascript/ql/test/library-tests/Extend/package.json index 92d84b2ed4f5..b140801b8982 100644 --- a/javascript/ql/test/library-tests/Extend/package.json +++ b/javascript/ql/test/library-tests/Extend/package.json @@ -14,6 +14,8 @@ "js-extend": "^1.0.1", "just-extend": "^1.1.27", "lodash": "^4.17.10", + "lodash.defaultsdeep": "^4.6.0", + "lodash.mergewith": "^4.6.1", "merge": "^1.2.0", "merge-deep": "^3.0.2", "merge-options": "^1.0.1", diff --git a/javascript/ql/test/library-tests/Extend/tst.js b/javascript/ql/test/library-tests/Extend/tst.js index b2cd428183c1..287fe57cdba7 100644 --- a/javascript/ql/test/library-tests/Extend/tst.js +++ b/javascript/ql/test/library-tests/Extend/tst.js @@ -55,6 +55,8 @@ checkDeep(require("smart-extend").deep(base(), arg())); checkDeep(require("lodash").merge(base(), arg())); checkDeep(require("lodash").mergeWith(base(), arg())); checkDeep(require("lodash").defaultsDeep(base(), arg())); +checkDeep(require("lodash.mergewith")(base(), arg())); +checkDeep(require("lodash.defaultsdeep")(base(), arg())); // Always shallow