diff options
Diffstat (limited to 'node_modules/babel-plugin-espower/lib')
8 files changed, 679 insertions, 0 deletions
diff --git a/node_modules/babel-plugin-espower/lib/babel-assertion-visitor.js b/node_modules/babel-plugin-espower/lib/babel-assertion-visitor.js new file mode 100644 index 000000000..6135f5766 --- /dev/null +++ b/node_modules/babel-plugin-espower/lib/babel-assertion-visitor.js @@ -0,0 +1,341 @@ +'use strict'; + +var EspowerLocationDetector = require('espower-location-detector'); +var estraverse = require('estraverse'); +var cloneWithWhitelist = require('espurify').cloneWithWhitelist; +var babelgen = require('babel-generator'); +var define = require('./define-properties'); +var toBeCaptured = require('./to-be-captured'); +var toBeSkipped = require('./to-be-skipped'); +var fs = require('fs'); +var helperCode = '(' + + fs.readFileSync(require.resolve('./power-assert-recorder.js'), 'utf8') + .split('\n') + .slice(2) + .join('\n') + + ')()'; + +function BabelAssertionVisitor (babel, matcher, options) { + this.babel = babel; + this.matcher = matcher; + this.options = options; + this.currentArgumentNodePath = null; + this.argumentModified = false; + this.valueRecorder = null; + this.locationDetector = new EspowerLocationDetector(this.options); + var babelTemplate = babel.template; + this.helperTemplate = babelTemplate(helperCode); + var whiteListWithRange = Object.keys(options.astWhiteList).reduce(function (acc, key) { + acc[key] = options.astWhiteList[key].concat(['range']); + return acc; + }, {}); + this.purifyAst = cloneWithWhitelist(whiteListWithRange); +} + +BabelAssertionVisitor.prototype.enter = function (nodePath) { + this.assertionNodePath = nodePath; + var currentNode = nodePath.node; + this.location = this.locationDetector.locationFor(currentNode); + var enclosingFunc = this.findEnclosingFunction(nodePath); + this.withinGenerator = enclosingFunc && enclosingFunc.generator; + this.withinAsync = enclosingFunc && enclosingFunc.async; + this.generateCanonicalCode(nodePath, currentNode); // should be next to enclosingFunc detection + // store original espath for each node + var visitorKeys = this.options.visitorKeys; + estraverse.traverse(currentNode, { + keys: visitorKeys, + enter: function (node) { + if (this.path()) { + var espath = this.path().join('/'); + define(node, { _espowerEspath: espath }); + } + } + }); +}; + +BabelAssertionVisitor.prototype.enterArgument = function (nodePath) { + var currentNode = nodePath.node; + var parentNode = nodePath.parent; + var argMatchResult = this.matcher.matchArgument(currentNode, parentNode); + if (!argMatchResult) { + return; + } + if (argMatchResult.name === 'message' && argMatchResult.kind === 'optional') { + // skip optional message argument + return; + } + this.verifyNotInstrumented(currentNode); + // create recorder per argument + this.valueRecorder = this.createNewRecorder(nodePath); + // entering target argument + this.currentArgumentNodePath = nodePath; +}; + +BabelAssertionVisitor.prototype.leave = function (nodePath) { + var currentNode = nodePath.node; + var visitorKeys = this.options.visitorKeys; + estraverse.traverse(currentNode, { + keys: visitorKeys, + enter: function (node) { + delete node._espowerEspath; + } + }); +}; + +BabelAssertionVisitor.prototype.leaveArgument = function (resultTree) { + try { + return this.argumentModified ? this.captureArgument(resultTree) : resultTree; + } finally { + this.currentArgumentNodePath = null; + this.argumentModified = false; + this.valueRecorder = null; + } +}; + +BabelAssertionVisitor.prototype.captureNode = function (nodePath) { + var currentNode = nodePath.node; + var t = this.babel.types; + this.argumentModified = true; + var relativeEsPath = currentNode._espowerEspath; + var newNode = t.callExpression( + t.memberExpression(this.valueRecorder, t.identifier('_capt')), + [ + currentNode, + t.valueToNode(relativeEsPath) + ]); + define(newNode, { _generatedByEspower: true }); + return newNode; +}; + +BabelAssertionVisitor.prototype.toBeSkipped = function (nodePath) { + return toBeSkipped(this.babel.types, nodePath); +}; + +BabelAssertionVisitor.prototype.toBeCaptured = function (nodePath) { + return toBeCaptured(this.babel.types, nodePath); +}; + +BabelAssertionVisitor.prototype.isArgumentModified = function () { + return !!this.argumentModified; +}; + +BabelAssertionVisitor.prototype.isCapturingArgument = function () { + return !!this.currentArgumentNodePath; +}; + +BabelAssertionVisitor.prototype.isLeavingAssertion = function (nodePath) { + return this.assertionNodePath === nodePath; +}; + +BabelAssertionVisitor.prototype.isLeavingArgument = function (nodePath) { + return this.currentArgumentNodePath === nodePath; +}; + +BabelAssertionVisitor.prototype.isGeneratedNode = function (nodePath) { + var currentNode = nodePath.node; + return !!currentNode._generatedByEspower; +}; + +// internal + +BabelAssertionVisitor.prototype.generateCanonicalCode = function (nodePath, node) { + var file = nodePath.hub.file; + var gen = new babelgen.CodeGenerator(node, { concise: true, comments: false }); + var output = gen.generate(); + this.canonicalCode = output.code; + if (!this.options.embedAst) { + return; + } + var astAndTokens = this.parseCanonicalCode(file, this.canonicalCode); + this.ast = JSON.stringify(this.purifyAst(astAndTokens.expression)); + this.tokens = JSON.stringify(astAndTokens.tokens); + var _this = this; + var types = this.babel.types; + this.visitorKeys = this.getOrCreateNode(nodePath, 'powerAssertVisitorKeys', function () { + return types.stringLiteral(JSON.stringify(_this.options.visitorKeys)); + }); +}; + +BabelAssertionVisitor.prototype.parseCanonicalCode = function (file, code) { + var ast, tokens; + + function doParse(wrapper) { + var content = wrapper ? wrapper(code) : code; + var output = file.parse(content); + if (wrapper) { + ast = output.program.body[0].body; + tokens = output.tokens.slice(6, -2); + } else { + ast = output.program; + tokens = output.tokens.slice(0, -1); + } + } + + if (this.withinAsync) { + doParse(wrappedInAsync); + } else if (this.withinGenerator) { + doParse(wrappedInGenerator); + } else { + doParse(); + } + + var exp = ast.body[0].expression; + var columnOffset = exp.loc.start.column; + var offsetTree = estraverse.replace(exp, { + keys: this.options.visitorKeys, + enter: function (eachNode) { + eachNode.range = [ + eachNode.loc.start.column - columnOffset, + eachNode.loc.end.column - columnOffset + ]; + delete eachNode.loc; + return eachNode; + } + }); + + return { + tokens: offsetAndSlimDownTokens(tokens), + expression: offsetTree + }; +}; + +function wrappedInGenerator (jsCode) { + return 'function *wrapper() { ' + jsCode + ' }'; +} + +function wrappedInAsync (jsCode) { + return 'async function wrapper() { ' + jsCode + ' }'; +} + +function offsetAndSlimDownTokens (tokens) { + var i, token, newToken, result = []; + var columnOffset; + for(i = 0; i < tokens.length; i += 1) { + token = tokens[i]; + if (i === 0) { + columnOffset = token.loc.start.column; + } + newToken = { + type: { + label: token.type.label + } + }; + if (typeof token.value !== 'undefined') { + newToken.value = token.value; + } + newToken.range = [ + token.loc.start.column - columnOffset, + token.loc.end.column - columnOffset + ]; + result.push(newToken); + } + return result; +} + +BabelAssertionVisitor.prototype.captureArgument = function (node) { + var t = this.babel.types; + var props = { + content: this.canonicalCode, + filepath: this.location.source, + line: this.location.line + }; + if (this.withinAsync) { + props.async = true; + } + if (this.withinGenerator) { + props.generator = true; + } + if (this.ast) { + props.ast = this.ast; + } + if (this.tokens) { + props.tokens = this.tokens; + } + var propsNode = t.valueToNode(props); + if (this.visitorKeys) { + var visitorKeysNode = t.objectProperty(t.identifier('visitorKeys'), this.visitorKeys); + propsNode.properties.push(visitorKeysNode); + } + var newNode = t.callExpression( + t.memberExpression(this.valueRecorder, t.identifier('_expr')), + [ + node, + propsNode + ] + ); + define(newNode, { _generatedByEspower: true }); + return newNode; +}; + +BabelAssertionVisitor.prototype.verifyNotInstrumented = function (currentNode) { + var types = this.babel.types; + if (!types.isCallExpression(currentNode)) { + return; + } + if (!types.isMemberExpression(currentNode.callee)) { + return; + } + var prop = currentNode.callee.property; + if (types.isIdentifier(prop) && prop.name === '_expr') { + var errorMessage = '[espower] Attempted to transform AST twice.'; + if (this.options.path) { + errorMessage += ' path: ' + this.options.path; + } + throw new Error(errorMessage); + } +}; + +BabelAssertionVisitor.prototype.createNewRecorder = function (nodePath) { + var _this = this; + var types = this.babel.types; + var helperNameNode = this.getOrCreateNode(nodePath, 'powerAssertRecorder', function () { + return types.toExpression(_this.helperTemplate()); + }); + var recorderIdent = nodePath.scope.generateUidIdentifier('rec'); + define(recorderIdent, { _generatedByEspower: true }); + var init = types.newExpression(helperNameNode, []); + define(init, { _generatedByEspower: true }); + nodePath.scope.push({ id: recorderIdent, init: init }); + return recorderIdent; +}; + +BabelAssertionVisitor.prototype.getOrCreateNode = function (nodePath, keyName, generateNode) { + var file = nodePath.hub.file; + var ident = file.get(keyName); + if (!ident) { + ident = this.createNode(nodePath, keyName, generateNode); + // helperNameNode = file.addImport('power-assert-runtime/recorder', 'default', 'recorder'); + } + return ident; +}; + +BabelAssertionVisitor.prototype.createNode = function (nodePath, keyName, generateNode) { + var file = nodePath.hub.file; + var programScope = nodePath.scope.getProgramParent(); + var ident = programScope.generateUidIdentifier(keyName); + define(ident, { _generatedByEspower: true }); + file.set(keyName, ident); + var generatedNode = generateNode(); + var visitorKeys = this.options.visitorKeys; + estraverse.traverse(generatedNode, { + keys: visitorKeys, + enter: function (node) { + define(node, { _generatedByEspower: true }); + } + }); + generatedNode._compact = true; + programScope.push({ id: ident, init: generatedNode }); + return ident; +}; + +BabelAssertionVisitor.prototype.findEnclosingFunction = function (nodePath) { + if (!nodePath) { + return null; + } + if (this.babel.types.isFunction(nodePath.node)) { + return nodePath.node; + } + return this.findEnclosingFunction(nodePath.parentPath); +}; + +module.exports = BabelAssertionVisitor; diff --git a/node_modules/babel-plugin-espower/lib/babel-espower-visitor.js b/node_modules/babel-plugin-espower/lib/babel-espower-visitor.js new file mode 100644 index 000000000..61a7e37be --- /dev/null +++ b/node_modules/babel-plugin-espower/lib/babel-espower-visitor.js @@ -0,0 +1,86 @@ +'use strict'; + +var CallMatcher = require('call-matcher'); +var babylon = require('babylon'); +var assign = require('core-js/library/fn/object/assign'); +var find = require('core-js/library/fn/array/find'); +var BabelAssertionVisitor = require('./babel-assertion-visitor'); + +function BabelEspowerVisitor (babel, opts) { + this.babel = babel; + this.matchers = opts.patterns.map(function (pattern) { + var signatureAst = babylon.parse(pattern); + var expression = signatureAst.program.body[0].expression; + return new CallMatcher(expression, opts); + }); + this.options = opts; +} + +BabelEspowerVisitor.prototype.enter = function (nodePath) { + var currentNode = nodePath.node; + var file = nodePath.hub.file; + var assertionVisitor = file.get('espowerAssertionVisitor'); + if (assertionVisitor) { + if (assertionVisitor.isGeneratedNode(nodePath) || assertionVisitor.toBeSkipped(nodePath)) { + // skipping this Node + // MEMO: exit() will not be called when skip() is called + nodePath.skip(); + return; + } + if (!assertionVisitor.isCapturingArgument() && !this.isCalleeOfParentCallExpression(nodePath)) { + // entering argument + assertionVisitor.enterArgument(nodePath); + } + } else if (nodePath.isCallExpression()) { + var matcher = find(this.matchers, function (m) { return m.test(currentNode); }); + if (matcher) { + // entering assertion + var espowerOptions = assign({ + path: file.opts.filename, // or opts.sourceFileName? + sourceMap: file.opts.inputSourceMap + }, this.options); + assertionVisitor = new BabelAssertionVisitor(this.babel, matcher, espowerOptions); + assertionVisitor.enter(nodePath); + file.set('espowerAssertionVisitor', assertionVisitor); + } + } +}; + +BabelEspowerVisitor.prototype.exit = function (nodePath) { + var currentNode = nodePath.node; + var resultTree = currentNode; + var file = nodePath.hub.file; + var assertionVisitor = file.get('espowerAssertionVisitor'); + if (!assertionVisitor) { + return; + } + if (assertionVisitor.isLeavingAssertion(nodePath)) { + // leaving assertion + assertionVisitor.leave(nodePath); + file.delete('espowerAssertionVisitor'); + return; + } + if (!assertionVisitor.isCapturingArgument()) { + return; + } + if (assertionVisitor.toBeCaptured(nodePath)) { + // capturing Node + resultTree = assertionVisitor.captureNode(nodePath); + } + if (assertionVisitor.isLeavingArgument(nodePath)) { + // capturing whole argument on leaving argument + resultTree = assertionVisitor.leaveArgument(resultTree); + } + if (resultTree !== currentNode) { + nodePath.replaceWith(resultTree); + } +}; + +BabelEspowerVisitor.prototype.isCalleeOfParentCallExpression = function (nodePath) { + var currentKey = nodePath.key; + var parentNode = nodePath.parent; + var types = this.babel.types; + return types.isCallExpression(parentNode) && currentKey === 'callee'; +}; + +module.exports = BabelEspowerVisitor; diff --git a/node_modules/babel-plugin-espower/lib/create-espower-visitor.js b/node_modules/babel-plugin-espower/lib/create-espower-visitor.js new file mode 100644 index 000000000..eab911ab8 --- /dev/null +++ b/node_modules/babel-plugin-espower/lib/create-espower-visitor.js @@ -0,0 +1,32 @@ +'use strict'; + +var defaultOptions = require('./default-options'); +var assign = require('core-js/library/fn/object/assign'); +var BabelEspowerVisitor = require('./babel-espower-visitor'); + +module.exports = function createEspowerVisitor (babel, options) { + return { + visitor: { + Program: function (path, state) { + var opts = assign(defaultOptions(), { + astWhiteList: babel.types.BUILDER_KEYS, + visitorKeys: babel.types.VISITOR_KEYS, + sourceRoot: process.cwd() + }, options, state.opts); + var espowerVisitor = new BabelEspowerVisitor(babel, opts); + var innerVisitor = Object.keys(opts.visitorKeys).reduce(function (handlers, nodeType) { + handlers[nodeType] = { + enter: function (nodePath, pluginPass) { + espowerVisitor.enter(nodePath); + }, + exit: function (nodePath, pluginPass) { + espowerVisitor.exit(nodePath); + } + }; + return handlers; + }, {}); + path.traverse(innerVisitor, state); + } + } + }; +}; diff --git a/node_modules/babel-plugin-espower/lib/default-options.js b/node_modules/babel-plugin-espower/lib/default-options.js new file mode 100644 index 000000000..d9d6cc1c6 --- /dev/null +++ b/node_modules/babel-plugin-espower/lib/default-options.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = function defaultOptions () { + return { + embedAst: false, + patterns: [ + 'assert(value, [message])', + 'assert.ok(value, [message])', + 'assert.equal(actual, expected, [message])', + 'assert.notEqual(actual, expected, [message])', + 'assert.strictEqual(actual, expected, [message])', + 'assert.notStrictEqual(actual, expected, [message])', + 'assert.deepEqual(actual, expected, [message])', + 'assert.notDeepEqual(actual, expected, [message])', + 'assert.deepStrictEqual(actual, expected, [message])', + 'assert.notDeepStrictEqual(actual, expected, [message])' + ] + }; +}; diff --git a/node_modules/babel-plugin-espower/lib/define-properties.js b/node_modules/babel-plugin-espower/lib/define-properties.js new file mode 100644 index 000000000..9f86150fb --- /dev/null +++ b/node_modules/babel-plugin-espower/lib/define-properties.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = function defineProperties (obj, map) { + Object.keys(map).forEach(function (name) { + Object.defineProperty(obj, name, { + configurable: true, + enumerable: false, + value: map[name], + writable: true + }); + }); +}; diff --git a/node_modules/babel-plugin-espower/lib/power-assert-recorder.js b/node_modules/babel-plugin-espower/lib/power-assert-recorder.js new file mode 100644 index 000000000..0e59999f5 --- /dev/null +++ b/node_modules/babel-plugin-espower/lib/power-assert-recorder.js @@ -0,0 +1,26 @@ +'use strict'; +module.exports = /* intentional newline */ + function () { + function PowerAssertRecorder() { + this.captured = []; + } + + PowerAssertRecorder.prototype._capt = function _capt (value, espath) { + this.captured.push({value: value, espath: espath}); + return value; + }; + + PowerAssertRecorder.prototype._expr = function _expr (value, source) { + var capturedValues = this.captured; + this.captured = []; + return { + powerAssertContext: { + value: value, + events: capturedValues + }, + source: source + }; + }; + + return PowerAssertRecorder; +} diff --git a/node_modules/babel-plugin-espower/lib/to-be-captured.js b/node_modules/babel-plugin-espower/lib/to-be-captured.js new file mode 100644 index 000000000..248fe5366 --- /dev/null +++ b/node_modules/babel-plugin-espower/lib/to-be-captured.js @@ -0,0 +1,56 @@ +'use strict'; + +var typesToBeCaptured = [ + 'Identifier', + 'BinaryExpression', + 'MemberExpression', + 'CallExpression', + 'UnaryExpression', + 'LogicalExpression', + 'ArrayExpression', + 'ObjectExpression', + 'TemplateLiteral', + 'YieldExpression', + 'AwaitExpression', + 'NewExpression', + 'AssignmentExpression', + 'UpdateExpression', + 'TaggedTemplateExpression' +]; + +function isCaputuringTargetType (types, nodePath) { + var currentNode = nodePath.node; + return typesToBeCaptured.some(function (nodeType) { + return types['is' + nodeType](currentNode); + }); +} + +function isCalleeOfParent(types, nodePath) { + var currentKey = nodePath.key; + var parentNode = nodePath.parent; + return (types.isCallExpression(parentNode) || types.isNewExpression(parentNode)) && currentKey === 'callee'; +} + +function isChildOfTaggedTemplateExpression(types, nodePath) { + var parentNode = nodePath.parent; + return types.isTaggedTemplateExpression(parentNode); +} + +function isYieldOrAwaitArgument(types, nodePath) { + var currentKey = nodePath.key; + var parentNode = nodePath.parent; + // capture the yielded/await result, not the promise + return (types.isYieldExpression(parentNode) || types.isAwaitExpression(parentNode)) && currentKey === 'argument'; +} + +var blacklist = [ + isYieldOrAwaitArgument, + isCalleeOfParent, + isChildOfTaggedTemplateExpression +]; + +module.exports = function toBeCaptured (types, nodePath) { + return isCaputuringTargetType(types, nodePath) && !blacklist.some(function (predicate) { + return predicate(types, nodePath); + }); +}; diff --git a/node_modules/babel-plugin-espower/lib/to-be-skipped.js b/node_modules/babel-plugin-espower/lib/to-be-skipped.js new file mode 100644 index 000000000..92b2528c8 --- /dev/null +++ b/node_modules/babel-plugin-espower/lib/to-be-skipped.js @@ -0,0 +1,107 @@ +'use strict'; + +var typesNotToBeSkippedDuringCapturing = [ + 'Identifier', + 'BinaryExpression', + 'MemberExpression', + 'CallExpression', + 'UnaryExpression', + 'LogicalExpression', + 'ArrayExpression', + 'ObjectExpression', + 'SequenceExpression', + 'TemplateLiteral', + 'YieldExpression', + 'AwaitExpression', + 'NewExpression', + 'AssignmentExpression', + 'UpdateExpression', + 'TaggedTemplateExpression', + 'ConditionalExpression', + 'SpreadElement', + 'Property' +]; + +function isTypeNotToBeSkippedDuringCapturing (types, nodePath) { + var currentNode = nodePath.node; + return typesNotToBeSkippedDuringCapturing.some(function (nodeType) { + return types['is' + nodeType](currentNode); + }); +} + +function isTypeToBeSkippedDuringCapturing (types, nodePath) { + return !isTypeNotToBeSkippedDuringCapturing(types, nodePath); +} + +function isLeftHandSideOfAssignment(types, nodePath) { + var currentKey = nodePath.key; + var parentNode = nodePath.parent; + // Do not instrument left due to 'Invalid left-hand side in assignment' + return types.isAssignmentExpression(parentNode) && currentKey === 'left'; +} + +function isChildOfObjectLiteral (types, parentNode) { + return types.isObjectProperty(parentNode) || + types.isObjectMethod(parentNode) || + types.isSpreadProperty(parentNode); +} + +function isObjectLiteralKey (types, parentNode, currentKey) { + return isChildOfObjectLiteral(types, parentNode) && currentKey === 'key'; +} + +function isObjectLiteralValue (types, parentNode, currentKey) { + return isChildOfObjectLiteral(types, parentNode) && currentKey === 'value'; +} + +function isNonComputedObjectLiteralKey(types, nodePath) { + var currentKey = nodePath.key; + var parentNode = nodePath.parent; + // Do not instrument non-computed Object literal key + return isObjectLiteralKey(types, parentNode, currentKey) && !parentNode.computed; +} + +function isShorthandedValueOfObjectLiteral(types, nodePath) { + var currentKey = nodePath.key; + var parentNode = nodePath.parent; + // Do not instrument shorthanded Object literal value + return isObjectLiteralValue(types, parentNode, currentKey) && parentNode.shorthand; +} + +function isUpdateExpression(types, nodePath) { + var parentNode = nodePath.parent; + // Just wrap UpdateExpression, not digging in. + return types.isUpdateExpression(parentNode); +} + +function isCallExpressionWithNonComputedMemberExpression(types, nodePath) { + var currentKey = nodePath.key; + var currentNode = nodePath.node; + var parentNode = nodePath.parent; + // Do not instrument non-computed property of MemberExpression within CallExpression. + return types.isIdentifier(currentNode) && types.isMemberExpression(parentNode) && !parentNode.computed && currentKey === 'property'; +} + +function isTypeOfOrDeleteUnaryExpression(types, nodePath) { + var currentKey = nodePath.key; + var currentNode = nodePath.node; + var parentNode = nodePath.parent; + // 'typeof Identifier' or 'delete Identifier' is not instrumented + return types.isIdentifier(currentNode) && types.isUnaryExpression(parentNode) && (parentNode.operator === 'typeof' || parentNode.operator === 'delete') && currentKey === 'argument'; +} + +var criteriaForSkipping = [ + isTypeToBeSkippedDuringCapturing, + isLeftHandSideOfAssignment, + isNonComputedObjectLiteralKey, + isShorthandedValueOfObjectLiteral, + isUpdateExpression, + isCallExpressionWithNonComputedMemberExpression, + isTypeOfOrDeleteUnaryExpression, +]; + +module.exports = function toBeSkipped (types, nodePath) { + return criteriaForSkipping.some(function (predicate) { + return predicate(types, nodePath); + }); +}; |