diff options
Diffstat (limited to 'node_modules/call-matcher/index.js')
-rw-r--r-- | node_modules/call-matcher/index.js | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/node_modules/call-matcher/index.js b/node_modules/call-matcher/index.js new file mode 100644 index 000000000..24163e354 --- /dev/null +++ b/node_modules/call-matcher/index.js @@ -0,0 +1,195 @@ +/** + * call-matcher: + * ECMAScript CallExpression matcher made from function/method signature + * + * https://github.com/twada/call-matcher + * + * Copyright (c) 2015-2016 Takuto Wada + * Licensed under the MIT license. + * https://github.com/twada/call-matcher/blob/master/MIT-LICENSE.txt + */ +'use strict'; +/* jshint -W024 */ + +var estraverse = require('estraverse'); +var espurify = require('espurify'); +var syntax = estraverse.Syntax; +var hasOwn = Object.prototype.hasOwnProperty; +var forEach = require('core-js/library/fn/array/for-each'); +var map = require('core-js/library/fn/array/map'); +var filter = require('core-js/library/fn/array/filter'); +var reduce = require('core-js/library/fn/array/reduce'); +var indexOf = require('core-js/library/fn/array/index-of'); +var deepEqual = require('deep-equal'); +var notCallExprMessage = 'Argument should be in the form of CallExpression'; +var duplicatedArgMessage = 'Duplicate argument name: '; +var invalidFormMessage = 'Argument should be in the form of `name` or `[name]`'; + +function CallMatcher (signatureAst, options) { + validateApiExpression(signatureAst); + options = options || {}; + this.visitorKeys = options.visitorKeys || estraverse.VisitorKeys; + if (options.astWhiteList) { + this.purifyAst = espurify.cloneWithWhitelist(options.astWhiteList); + } else { + this.purifyAst = espurify; + } + this.signatureAst = signatureAst; + this.signatureCalleeDepth = astDepth(signatureAst.callee, this.visitorKeys); + this.numMaxArgs = this.signatureAst.arguments.length; + this.numMinArgs = filter(this.signatureAst.arguments, identifiers).length; +} + +CallMatcher.prototype.test = function (currentNode) { + var calleeMatched = this.isCalleeMatched(currentNode); + var numArgs; + if (calleeMatched) { + numArgs = currentNode.arguments.length; + return this.numMinArgs <= numArgs && numArgs <= this.numMaxArgs; + } + return false; +}; + +CallMatcher.prototype.matchArgument = function (currentNode, parentNode) { + if (isCalleeOfParent(currentNode, parentNode)) { + return null; + } + if (this.test(parentNode)) { + var indexOfCurrentArg = indexOf(parentNode.arguments, currentNode); + var numOptional = parentNode.arguments.length - this.numMinArgs; + var matchedSignatures = reduce(this.argumentSignatures(), function (accum, argSig) { + if (argSig.kind === 'mandatory') { + accum.push(argSig); + } + if (argSig.kind === 'optional' && 0 < numOptional) { + numOptional -= 1; + accum.push(argSig); + } + return accum; + }, []); + return matchedSignatures[indexOfCurrentArg]; + } + return null; +}; + +CallMatcher.prototype.calleeAst = function () { + return this.purifyAst(this.signatureAst.callee); +}; + +CallMatcher.prototype.argumentSignatures = function () { + return map(this.signatureAst.arguments, toArgumentSignature); +}; + +CallMatcher.prototype.isCalleeMatched = function (node) { + if (!isCallExpression(node)) { + return false; + } + if (!this.isSameDepthAsSignatureCallee(node.callee)) { + return false; + } + return deepEqual(this.purifyAst(this.signatureAst.callee), this.purifyAst(node.callee)); +}; + +CallMatcher.prototype.isSameDepthAsSignatureCallee = function (ast) { + var depth = this.signatureCalleeDepth; + var currentDepth = 0; + estraverse.traverse(ast, { + keys: this.visitorKeys, + enter: function (currentNode, parentNode) { + var path = this.path(); + var pathDepth = path ? path.length : 0; + if (currentDepth < pathDepth) { + currentDepth = pathDepth; + } + if (depth < currentDepth) { + this['break'](); + } + } + }); + return (depth === currentDepth); +}; + +function toArgumentSignature (argSignatureNode) { + switch(argSignatureNode.type) { + case syntax.Identifier: + return { + name: argSignatureNode.name, + kind: 'mandatory' + }; + case syntax.ArrayExpression: + return { + name: argSignatureNode.elements[0].name, + kind: 'optional' + }; + default: + return null; + } +} + +function astDepth (ast, visitorKeys) { + var maxDepth = 0; + estraverse.traverse(ast, { + keys: visitorKeys, + enter: function (currentNode, parentNode) { + var path = this.path(); + var pathDepth = path ? path.length : 0; + if (maxDepth < pathDepth) { + maxDepth = pathDepth; + } + } + }); + return maxDepth; +} + +function isCallExpression (node) { + return node && node.type === syntax.CallExpression; +} + +function isCalleeOfParent(currentNode, parentNode) { + return parentNode && currentNode && + parentNode.type === syntax.CallExpression && + parentNode.callee === currentNode; +} + +function identifiers (node) { + return node.type === syntax.Identifier; +} + +function validateApiExpression (callExpression) { + if (!callExpression || !callExpression.type) { + throw new Error(notCallExprMessage); + } + if (callExpression.type !== syntax.CallExpression) { + throw new Error(notCallExprMessage); + } + var names = {}; + forEach(callExpression.arguments, function (arg) { + var name = validateArg(arg); + if (hasOwn.call(names, name)) { + throw new Error(duplicatedArgMessage + name); + } else { + names[name] = name; + } + }); +} + +function validateArg (arg) { + var inner; + switch(arg.type) { + case syntax.Identifier: + return arg.name; + case syntax.ArrayExpression: + if (arg.elements.length !== 1) { + throw new Error(invalidFormMessage); + } + inner = arg.elements[0]; + if (inner.type !== syntax.Identifier) { + throw new Error(invalidFormMessage); + } + return inner.name; + default: + throw new Error(invalidFormMessage); + } +} + +module.exports = CallMatcher; |