diff --git a/.gitignore b/.gitignore index 87c8978..ee6f715 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .DS_Store coverage -lib node_modules npm-debug.log package-lock.json diff --git a/lib/elementType.js b/lib/elementType.js new file mode 100644 index 0000000..c346e7e --- /dev/null +++ b/lib/elementType.js @@ -0,0 +1,44 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = elementType; +function resolveMemberExpressions() { + var object = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var property = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + if (object.type === 'JSXMemberExpression') { + return resolveMemberExpressions(object.object, object.property) + '.' + property.name; + } + + return object.name + '.' + property.name; +} + +/** + * Returns the tagName associated with a JSXElement. + */ +function elementType() { + var node = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var name = node.name; + + + if (!name) { + throw new Error('The argument provided is not a JSXElement node.'); + } + + if (name.type === 'JSXMemberExpression') { + var _name$object = name.object, + object = _name$object === undefined ? {} : _name$object, + _name$property = name.property, + property = _name$property === undefined ? {} : _name$property; + + return resolveMemberExpressions(object, property); + } + + if (name.type === 'JSXNamespacedName') { + return name.namespace.name + ':' + name.name.name; + } + + return node.name.name; +} \ No newline at end of file diff --git a/lib/eventHandlers.js b/lib/eventHandlers.js new file mode 100644 index 0000000..44a052d --- /dev/null +++ b/lib/eventHandlers.js @@ -0,0 +1,32 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +/** + * Common event handlers for JSX element event binding. + */ + +var eventHandlersByType = { + clipboard: ['onCopy', 'onCut', 'onPaste'], + composition: ['onCompositionEnd', 'onCompositionStart', 'onCompositionUpdate'], + keyboard: ['onKeyDown', 'onKeyPress', 'onKeyUp'], + focus: ['onFocus', 'onBlur'], + form: ['onChange', 'onInput', 'onSubmit'], + mouse: ['onClick', 'onContextMenu', 'onDblClick', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp'], + selection: ['onSelect'], + touch: ['onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart'], + ui: ['onScroll'], + wheel: ['onWheel'], + media: ['onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 'onEnded', 'onError', 'onLoadedData', 'onLoadedMetadata', 'onLoadStart', 'onPause', 'onPlay', 'onPlaying', 'onProgress', 'onRateChange', 'onSeeked', 'onSeeking', 'onStalled', 'onSuspend', 'onTimeUpdate', 'onVolumeChange', 'onWaiting'], + image: ['onLoad', 'onError'], + animation: ['onAnimationStart', 'onAnimationEnd', 'onAnimationIteration'], + transition: ['onTransitionEnd'] +}; + +var eventHandlers = Object.keys(eventHandlersByType).reduce(function (accumulator, type) { + return accumulator.concat(eventHandlersByType[type]); +}, []); + +exports.default = eventHandlers; +exports.eventHandlersByType = eventHandlersByType; \ No newline at end of file diff --git a/lib/getProp.js b/lib/getProp.js new file mode 100644 index 0000000..8f386e5 --- /dev/null +++ b/lib/getProp.js @@ -0,0 +1,122 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +exports.default = getProp; + +var _propName = require('./propName'); + +var _propName2 = _interopRequireDefault(_propName); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } + +var DEFAULT_OPTIONS = { + ignoreCase: true +}; + +/** + * Returns the JSXAttribute itself or undefined, indicating the prop + * is not present on the JSXOpeningElement. + * + */ +function getProp() { + var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var prop = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_OPTIONS; + + function getName(name) { + return options.ignoreCase ? name.toUpperCase() : name; + } + var propToFind = getName(prop); + function isPropToFind(property) { + return property.type === 'Property' && property.key.type === 'Identifier' && propToFind === getName(property.key.name); + } + + var foundAttribute = props.find(function (attribute) { + // If the props contain a spread prop, try to find the property in the object expression. + if (attribute.type === 'JSXSpreadAttribute') { + return attribute.argument.type === 'ObjectExpression' && propToFind !== getName('key') // https://github.com/reactjs/rfcs/pull/107 + && attribute.argument.properties.some(isPropToFind); + } + + return propToFind === getName((0, _propName2.default)(attribute)); + }); + + if (foundAttribute && foundAttribute.type === 'JSXSpreadAttribute') { + return propertyToJSXAttribute(foundAttribute.argument.properties.find(isPropToFind)); + } + + return foundAttribute; +} + +function propertyToJSXAttribute(node) { + var key = node.key, + value = node.value; + + return _extends({ + type: 'JSXAttribute', + name: _extends({ type: 'JSXIdentifier', name: key.name }, getBaseProps(key)), + value: value.type === 'Literal' ? adjustRangeStartAndEndOfNode(value) : _extends({ + type: 'JSXExpressionContainer', + expression: adjustExpressionRangeStartAndEnd(value) + }, getBaseProps(value)) + }, getBaseProps(node)); +} + +function adjustRangeStartAndEndOfNode(node) { + var _ref = node.range || [node.start, node.end], + _ref2 = _slicedToArray(_ref, 2), + start = _ref2[0], + end = _ref2[1]; + + return _extends({}, node, { + end: end, + range: [start, end], + start: start + }); +} + +function adjustExpressionRangeStartAndEnd(_ref3) { + var expressions = _ref3.expressions, + quasis = _ref3.quasis, + expression = _objectWithoutProperties(_ref3, ['expressions', 'quasis']); + + return _extends({}, adjustRangeStartAndEndOfNode(expression), expressions ? { expressions: expressions.map(adjustRangeStartAndEndOfNode) } : {}, quasis ? { quasis: quasis.map(adjustRangeStartAndEndOfNode) } : {}); +} + +function getBaseProps(_ref4) { + var loc = _ref4.loc, + node = _objectWithoutProperties(_ref4, ['loc']); + + var _adjustRangeStartAndE = adjustRangeStartAndEndOfNode(node), + end = _adjustRangeStartAndE.end, + range = _adjustRangeStartAndE.range, + start = _adjustRangeStartAndE.start; + + return { + end: end, + loc: getBaseLocation(loc), + range: range, + start: start + }; +} + +function getBaseLocation(_ref5) { + var start = _ref5.start, + end = _ref5.end, + source = _ref5.source, + filename = _ref5.filename; + + return _extends({ + start: start, + end: end + }, source !== undefined ? { source: source } : {}, filename !== undefined ? { filename: filename } : {}); +} \ No newline at end of file diff --git a/lib/getPropValue.js b/lib/getPropValue.js new file mode 100644 index 0000000..de4062f --- /dev/null +++ b/lib/getPropValue.js @@ -0,0 +1,57 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = getPropValue; +exports.getLiteralPropValue = getLiteralPropValue; + +var _values = require('./values'); + +var _values2 = _interopRequireDefault(_values); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var extractValue = function extractValue(attribute, extractor) { + if (attribute && attribute.type === 'JSXAttribute') { + if (attribute.value === null) { + // Null valued attributes imply truthiness. + // For example:
+ // See: https://facebook.github.io/react/docs/jsx-in-depth.html#boolean-attributes + return true; + } + + return extractor(attribute.value); + } + + return undefined; +}; + +/** + * Returns the value of a given attribute. + * Different types of attributes have their associated + * values in different properties on the object. + * + * This function should return the most *closely* associated + * value with the intention of the JSX. + * + * @param attribute - The JSXAttribute collected by AST parser. + */ +function getPropValue(attribute) { + return extractValue(attribute, _values2.default); +} + +/** + * Returns the value of a given attribute. + * Different types of attributes have their associated + * values in different properties on the object. + * + * This function should return a value only if we can extract + * a literal value from its attribute (i.e. values that have generic + * types in JavaScript - strings, numbers, booleans, etc.) + * + * @param attribute - The JSXAttribute collected by AST parser. + */ +function getLiteralPropValue(attribute) { + return extractValue(attribute, _values.getLiteralValue); +} \ No newline at end of file diff --git a/lib/hasProp.js b/lib/hasProp.js new file mode 100644 index 0000000..b54326b --- /dev/null +++ b/lib/hasProp.js @@ -0,0 +1,74 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = hasProp; +exports.hasAnyProp = hasAnyProp; +exports.hasEveryProp = hasEveryProp; + +var _propName = require('./propName'); + +var _propName2 = _interopRequireDefault(_propName); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var DEFAULT_OPTIONS = { + spreadStrict: true, + ignoreCase: true +}; + +/** + * Returns boolean indicating whether an prop exists on the props + * property of a JSX element node. + */ +function hasProp() { + var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var prop = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_OPTIONS; + + var propToCheck = options.ignoreCase ? prop.toUpperCase() : prop; + + return props.some(function (attribute) { + // If the props contain a spread prop, then refer to strict param. + if (attribute.type === 'JSXSpreadAttribute') { + return !options.spreadStrict; + } + + var currentProp = options.ignoreCase ? (0, _propName2.default)(attribute).toUpperCase() : (0, _propName2.default)(attribute); + + return propToCheck === currentProp; + }); +} + +/** + * Given the props on a node and a list of props to check, this returns a boolean + * indicating if any of them exist on the node. + */ +function hasAnyProp() { + var nodeProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_OPTIONS; + + var propsToCheck = typeof props === 'string' ? props.split(' ') : props; + + return propsToCheck.some(function (prop) { + return hasProp(nodeProps, prop, options); + }); +} + +/** + * Given the props on a node and a list of props to check, this returns a boolean + * indicating if all of them exist on the node + */ +function hasEveryProp() { + var nodeProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_OPTIONS; + + var propsToCheck = typeof props === 'string' ? props.split(' ') : props; + + return propsToCheck.every(function (prop) { + return hasProp(nodeProps, prop, options); + }); +} \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..2fe1119 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,40 @@ +'use strict'; + +var _hasProp = require('./hasProp'); + +var _hasProp2 = _interopRequireDefault(_hasProp); + +var _elementType = require('./elementType'); + +var _elementType2 = _interopRequireDefault(_elementType); + +var _eventHandlers = require('./eventHandlers'); + +var _eventHandlers2 = _interopRequireDefault(_eventHandlers); + +var _getProp = require('./getProp'); + +var _getProp2 = _interopRequireDefault(_getProp); + +var _getPropValue = require('./getPropValue'); + +var _getPropValue2 = _interopRequireDefault(_getPropValue); + +var _propName = require('./propName'); + +var _propName2 = _interopRequireDefault(_propName); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +module.exports = { + hasProp: _hasProp2.default, + hasAnyProp: _hasProp.hasAnyProp, + hasEveryProp: _hasProp.hasEveryProp, + elementType: _elementType2.default, + eventHandlers: _eventHandlers2.default, + eventHandlersByType: _eventHandlers.eventHandlersByType, + getProp: _getProp2.default, + getPropValue: _getPropValue2.default, + getLiteralPropValue: _getPropValue.getLiteralPropValue, + propName: _propName2.default +}; \ No newline at end of file diff --git a/lib/propName.js b/lib/propName.js new file mode 100644 index 0000000..5057886 --- /dev/null +++ b/lib/propName.js @@ -0,0 +1,22 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = propName; +/** + * Returns the name of the prop given the JSXAttribute object. + */ +function propName() { + var prop = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + if (!prop.type || prop.type !== 'JSXAttribute') { + throw new Error('The prop must be a JSXAttribute collected by the AST parser.'); + } + + if (prop.name.type === 'JSXNamespacedName') { + return prop.name.namespace.name + ':' + prop.name.name.name; + } + + return prop.name.name; +} \ No newline at end of file diff --git a/lib/values/JSXElement.js b/lib/values/JSXElement.js new file mode 100644 index 0000000..4fa044a --- /dev/null +++ b/lib/values/JSXElement.js @@ -0,0 +1,14 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromJSXElement; +/** + * Extractor function for a JSXElement type value node. + * + * Returns self-closing element with correct name. + */ +function extractValueFromJSXElement(value) { + return "<" + value.openingElement.name.name + " />"; +} \ No newline at end of file diff --git a/lib/values/Literal.js b/lib/values/Literal.js new file mode 100644 index 0000000..4233c82 --- /dev/null +++ b/lib/values/Literal.js @@ -0,0 +1,27 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromLiteral; +/** + * Extractor function for a Literal type value node. + * + * @param - value - AST Value object with type `Literal` + * @returns { String|Boolean } - The extracted value converted to correct type. + */ +function extractValueFromLiteral(value) { + var extractedValue = value.value; + + + var normalizedStringValue = typeof extractedValue === 'string' && extractedValue.toLowerCase(); + if (normalizedStringValue === 'true') { + return true; + } + + if (normalizedStringValue === 'false') { + return false; + } + + return extractedValue; +} \ No newline at end of file diff --git a/lib/values/expressions/ArrayExpression.js b/lib/values/expressions/ArrayExpression.js new file mode 100644 index 0000000..4e89af4 --- /dev/null +++ b/lib/values/expressions/ArrayExpression.js @@ -0,0 +1,19 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromArrayExpression; +/** + * Extractor function for an ArrayExpression type value node. + * An array expression is an expression with [] syntax. + * + * @returns - An array of the extracted elements. + */ +function extractValueFromArrayExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + return value.elements.map(function (element) { + return getValue(element); + }); +} \ No newline at end of file diff --git a/lib/values/expressions/BinaryExpression.js b/lib/values/expressions/BinaryExpression.js new file mode 100644 index 0000000..a58ea54 --- /dev/null +++ b/lib/values/expressions/BinaryExpression.js @@ -0,0 +1,78 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromBinaryExpression; +/** + * Extractor function for a BinaryExpression type value node. + * A binary expression has a left and right side separated by an operator + * such as `a + b`. + * + * @param - value - AST Value object with type `BinaryExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromBinaryExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + var operator = value.operator, + left = value.left, + right = value.right; + + var leftVal = getValue(left); + var rightVal = getValue(right); + + switch (operator) { + case '==': + return leftVal == rightVal; // eslint-disable-line + case '!=': + return leftVal != rightVal; // eslint-disable-line + case '===': + return leftVal === rightVal; + case '!==': + return leftVal !== rightVal; + case '<': + return leftVal < rightVal; + case '<=': + return leftVal <= rightVal; + case '>': + return leftVal > rightVal; + case '>=': + return leftVal >= rightVal; + case '<<': + return leftVal << rightVal; // eslint-disable-line no-bitwise + case '>>': + return leftVal >> rightVal; // eslint-disable-line no-bitwise + case '>>>': + return leftVal >>> rightVal; // eslint-disable-line no-bitwise + case '+': + return leftVal + rightVal; + case '-': + return leftVal - rightVal; + case '*': + return leftVal * rightVal; + case '/': + return leftVal / rightVal; + case '%': + return leftVal % rightVal; + case '|': + return leftVal | rightVal; // eslint-disable-line no-bitwise + case '^': + return leftVal ^ rightVal; // eslint-disable-line no-bitwise + case '&': + return leftVal & rightVal; // eslint-disable-line no-bitwise + case 'in': + try { + return leftVal in rightVal; + } catch (err) { + return false; + } + case 'instanceof': + if (typeof rightVal !== 'function') { + return false; + } + return leftVal instanceof rightVal; + default: + return undefined; + } +} \ No newline at end of file diff --git a/lib/values/expressions/BindExpression.js b/lib/values/expressions/BindExpression.js new file mode 100644 index 0000000..ab27192 --- /dev/null +++ b/lib/values/expressions/BindExpression.js @@ -0,0 +1,30 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromBindExpression; +/** + * Extractor function for a BindExpression type value node. + * A bind expression looks like `::this.foo` + * This will return `this.foo.bind(this)` as the value to indicate its existence, + * since we can not execute the function this.foo.bind(this) in a static environment. + * + * @param - value - AST Value object with type `BindExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromBindExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + var callee = getValue(value.callee); + + // If value.object === null, the callee must be a MemberExpression. + // https://github.com/babel/babylon/blob/master/ast/spec.md#bindexpression + var object = value.object === null ? getValue(value.callee.object) : getValue(value.object); + + if (value.object && value.object.property) { + return object + '.' + callee + '.bind(' + object + ')'; + } + + return callee + '.bind(' + object + ')'; +} \ No newline at end of file diff --git a/lib/values/expressions/CallExpression.js b/lib/values/expressions/CallExpression.js new file mode 100644 index 0000000..59f5fb8 --- /dev/null +++ b/lib/values/expressions/CallExpression.js @@ -0,0 +1,23 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromCallExpression; +/** + * Extractor function for a CallExpression type value node. + * A call expression looks like `bar()` + * This will return `bar` as the value to indicate its existence, + * since we can not execute the function bar in a static environment. + * + * @param - value - AST Value object with type `CallExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromCallExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + var args = Array.isArray(value.arguments) ? value.arguments.map(function (x) { + return getValue(x); + }).join(', ') : ''; + return '' + getValue(value.callee) + (value.optional ? '?.' : '') + '(' + args + ')'; +} \ No newline at end of file diff --git a/lib/values/expressions/ChainExpression.js b/lib/values/expressions/ChainExpression.js new file mode 100644 index 0000000..81c51a0 --- /dev/null +++ b/lib/values/expressions/ChainExpression.js @@ -0,0 +1,19 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromChainExpression; +/** + * Extractor function for a ChainExpression type value node. + * A member expression is accessing a property on an object `obj.property`. + * + * @param - value - AST Value object with type `ChainExpression` + * @returns - The extracted value converted to correct type + * and maintaing `obj?.property` convention. + */ +function extractValueFromChainExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + return getValue(value.expression); +} \ No newline at end of file diff --git a/lib/values/expressions/ConditionalExpression.js b/lib/values/expressions/ConditionalExpression.js new file mode 100644 index 0000000..04d67ae --- /dev/null +++ b/lib/values/expressions/ConditionalExpression.js @@ -0,0 +1,22 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromConditionalExpression; +/** + * Extractor function for a ConditionalExpression type value node. + * + * @param - value - AST Value object with type `ConditionalExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromConditionalExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + var test = value.test, + alternate = value.alternate, + consequent = value.consequent; + + + return getValue(test) ? getValue(consequent) : getValue(alternate); +} \ No newline at end of file diff --git a/lib/values/expressions/FunctionExpression.js b/lib/values/expressions/FunctionExpression.js new file mode 100644 index 0000000..15db995 --- /dev/null +++ b/lib/values/expressions/FunctionExpression.js @@ -0,0 +1,19 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromFunctionExpression; +/** + * Extractor function for a FunctionExpression type value node. + * Statically, we can't execute the given function, so just return a function + * to indicate that the value is present. + * + * @param - value - AST Value object with type `FunctionExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromFunctionExpression(value) { + return function () { + return value; + }; +} \ No newline at end of file diff --git a/lib/values/expressions/Identifier.js b/lib/values/expressions/Identifier.js new file mode 100644 index 0000000..bea6c94 --- /dev/null +++ b/lib/values/expressions/Identifier.js @@ -0,0 +1,35 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromIdentifier; +var JS_RESERVED = { + Array: Array, + Date: Date, + Infinity: Infinity, + Math: Math, + Number: Number, + Object: Object, + String: String, + undefined: undefined +}; + +/** + * Extractor function for a Identifier type value node. + * An Identifier is usually a reference to a variable. + * Just return variable name to determine its existence. + * + * @param - value - AST Value object with type `Identifier` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromIdentifier(value) { + var name = value.name; + + + if (Object.hasOwnProperty.call(JS_RESERVED, name)) { + return JS_RESERVED[name]; + } + + return name; +} \ No newline at end of file diff --git a/lib/values/expressions/LogicalExpression.js b/lib/values/expressions/LogicalExpression.js new file mode 100644 index 0000000..9d6616b --- /dev/null +++ b/lib/values/expressions/LogicalExpression.js @@ -0,0 +1,33 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromLogicalExpression; +/** + * Extractor function for a LogicalExpression type value node. + * A logical expression is `a && b` or `a || b`, so we evaluate both sides + * and return the extracted value of the expression. + * + * @param - value - AST Value object with type `LogicalExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromLogicalExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + var operator = value.operator, + left = value.left, + right = value.right; + + var leftVal = getValue(left); + var rightVal = getValue(right); + + if (operator === '&&') { + return leftVal && rightVal; + } + if (operator === '??') { + // return leftVal ?? rightVal; // TODO: update to babel 7 + return leftVal === null || typeof leftVal === 'undefined' ? rightVal : leftVal; + } + return leftVal || rightVal; +} \ No newline at end of file diff --git a/lib/values/expressions/MemberExpression.js b/lib/values/expressions/MemberExpression.js new file mode 100644 index 0000000..5a0face --- /dev/null +++ b/lib/values/expressions/MemberExpression.js @@ -0,0 +1,19 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromMemberExpression; +/** + * Extractor function for a MemberExpression type value node. + * A member expression is accessing a property on an object `obj.property`. + * + * @param - value - AST Value object with type `MemberExpression` + * @returns - The extracted value converted to correct type + * and maintaing `obj.property` convention. + */ +function extractValueFromMemberExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + return '' + getValue(value.object) + (value.optional ? '?.' : '.') + getValue(value.property); +} \ No newline at end of file diff --git a/lib/values/expressions/NewExpression.js b/lib/values/expressions/NewExpression.js new file mode 100644 index 0000000..3accd6f --- /dev/null +++ b/lib/values/expressions/NewExpression.js @@ -0,0 +1,15 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromNewExpression; +/** + * Extractor function for a NewExpression type value node. + * A new expression instantiates an object with `new` keyword. + * + * @returns - an empty object. + */ +function extractValueFromNewExpression() { + return new Object(); // eslint-disable-line +} \ No newline at end of file diff --git a/lib/values/expressions/ObjectExpression.js b/lib/values/expressions/ObjectExpression.js new file mode 100644 index 0000000..f99f741 --- /dev/null +++ b/lib/values/expressions/ObjectExpression.js @@ -0,0 +1,38 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +exports.default = extractValueFromObjectExpression; + +var _object = require('object.assign'); + +var _object2 = _interopRequireDefault(_object); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Extractor function for an ObjectExpression type value node. + * An object expression is using {}. + * + * @returns - a representation of the object + */ +function extractValueFromObjectExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + return value.properties.reduce(function (obj, property) { + var object = _extends({}, obj); + // Support types: SpreadProperty and ExperimentalSpreadProperty + if (/^(?:Experimental)?Spread(?:Property|Element)$/.test(property.type)) { + if (property.argument.type === 'ObjectExpression') { + return (0, _object2.default)(object, extractValueFromObjectExpression(property.argument)); + } + } else { + object[getValue(property.key)] = getValue(property.value); + } + return object; + }, {}); +} \ No newline at end of file diff --git a/lib/values/expressions/OptionalCallExpression.js b/lib/values/expressions/OptionalCallExpression.js new file mode 100644 index 0000000..235994c --- /dev/null +++ b/lib/values/expressions/OptionalCallExpression.js @@ -0,0 +1,21 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromOptionalCallExpression; +/** + * Extractor function for a OptionalCallExpression type value node. + * A member expression is accessing a property on an object `obj.property` and invoking it. + * + * @param - value - AST Value object with type `OptionalCallExpression` + * @returns - The extracted value converted to correct type + * and maintaing `obj.property?.()` convention. + */ +function extractValueFromOptionalCallExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + return getValue(value.callee) + '?.(' + value.arguments.map(function (x) { + return getValue(x); + }).join(', ') + ')'; +} \ No newline at end of file diff --git a/lib/values/expressions/OptionalMemberExpression.js b/lib/values/expressions/OptionalMemberExpression.js new file mode 100644 index 0000000..4fd1e82 --- /dev/null +++ b/lib/values/expressions/OptionalMemberExpression.js @@ -0,0 +1,19 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromOptionalMemberExpression; +/** + * Extractor function for a OptionalMemberExpression type value node. + * A member expression is accessing a property on an object `obj.property`. + * + * @param - value - AST Value object with type `OptionalMemberExpression` + * @returns - The extracted value converted to correct type + * and maintaing `obj?.property` convention. + */ +function extractValueFromOptionalMemberExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + return getValue(value.object) + '?.' + getValue(value.property); +} \ No newline at end of file diff --git a/lib/values/expressions/SpreadElement.js b/lib/values/expressions/SpreadElement.js new file mode 100644 index 0000000..cee0d48 --- /dev/null +++ b/lib/values/expressions/SpreadElement.js @@ -0,0 +1,17 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromSpreadElement; +/** + * Extractor function for a SpreadElement type value node. + * We can't statically evaluate an array spread, so just return + * undefined. + * + * @param - value - AST Value object with type `SpreadElement` + * @returns - An prototypeless object. + */ +function extractValueFromSpreadElement() { + return undefined; +} \ No newline at end of file diff --git a/lib/values/expressions/TaggedTemplateExpression.js b/lib/values/expressions/TaggedTemplateExpression.js new file mode 100644 index 0000000..9402746 --- /dev/null +++ b/lib/values/expressions/TaggedTemplateExpression.js @@ -0,0 +1,20 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromTaggedTemplateExpression; + +var _TemplateLiteral = require('./TemplateLiteral'); + +var _TemplateLiteral2 = _interopRequireDefault(_TemplateLiteral); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Returns the string value of a tagged template literal object. + * Redirects the bulk of the work to `TemplateLiteral`. + */ +function extractValueFromTaggedTemplateExpression(value) { + return (0, _TemplateLiteral2.default)(value.quasi); +} \ No newline at end of file diff --git a/lib/values/expressions/TemplateLiteral.js b/lib/values/expressions/TemplateLiteral.js new file mode 100644 index 0000000..339947b --- /dev/null +++ b/lib/values/expressions/TemplateLiteral.js @@ -0,0 +1,42 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromTemplateLiteral; +function sortStarts(a, b) { + return (a.range ? a.range[0] : a.start) - (b.range ? b.range[0] : b.start); +} + +/** + * Returns the string value of a template literal object. + * Tries to build it as best as it can based on the passed + * prop. For instance `This is a ${prop}` will return 'This is a {prop}'. + * + * If the template literal builds to undefined (`${undefined}`), then + * this should return "undefined". + */ +function extractValueFromTemplateLiteral(value) { + var quasis = value.quasis, + expressions = value.expressions; + + var partitions = quasis.concat(expressions); + + return partitions.sort(sortStarts).reduce(function (raw, part) { + var type = part.type; + + if (type === 'TemplateElement') { + return raw + part.value.raw; + } + + if (type === 'Identifier') { + return part.name === 'undefined' ? '' + raw + part.name : raw + '{' + part.name + '}'; + } + + if (type.indexOf('Expression') > -1) { + return raw + '{' + type + '}'; + } + + return raw; + }, ''); +} \ No newline at end of file diff --git a/lib/values/expressions/ThisExpression.js b/lib/values/expressions/ThisExpression.js new file mode 100644 index 0000000..185de46 --- /dev/null +++ b/lib/values/expressions/ThisExpression.js @@ -0,0 +1,15 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromThisExpression; +/** + * Extractor function for a ThisExpression type value node. + * A this expression is using `this` as an identifier. + * + * @returns - 'this' as a string. + */ +function extractValueFromThisExpression() { + return 'this'; +} \ No newline at end of file diff --git a/lib/values/expressions/TypeCastExpression.js b/lib/values/expressions/TypeCastExpression.js new file mode 100644 index 0000000..b1a19f8 --- /dev/null +++ b/lib/values/expressions/TypeCastExpression.js @@ -0,0 +1,19 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromTypeCastExpression; +/** + * Extractor function for a TypeCastExpression type value node. + * A type cast expression looks like `(this.handleClick: (event: MouseEvent) => void))` + * This will return the expression `this.handleClick`. + * + * @param - value - AST Value object with type `TypeCastExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromTypeCastExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + return getValue(value.expression); +} \ No newline at end of file diff --git a/lib/values/expressions/UnaryExpression.js b/lib/values/expressions/UnaryExpression.js new file mode 100644 index 0000000..318ec8c --- /dev/null +++ b/lib/values/expressions/UnaryExpression.js @@ -0,0 +1,39 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromUnaryExpression; +/** + * Extractor function for a UnaryExpression type value node. + * A unary expression is an expression with a unary operator. + * For example, !"foobar" will evaluate to false, so this will return false. + * + * @param - value - AST Value object with type `UnaryExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromUnaryExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + var operator = value.operator, + argument = value.argument; + + + switch (operator) { + case '-': + return -getValue(argument); + case '+': + return +getValue(argument); // eslint-disable-line no-implicit-coercion + case '!': + return !getValue(argument); + case '~': + return ~getValue(argument); // eslint-disable-line no-bitwise + case 'delete': + // I believe delete statements evaluate to true. + return true; + case 'typeof': + case 'void': + default: + return undefined; + } +} \ No newline at end of file diff --git a/lib/values/expressions/UpdateExpression.js b/lib/values/expressions/UpdateExpression.js new file mode 100644 index 0000000..a3a9c72 --- /dev/null +++ b/lib/values/expressions/UpdateExpression.js @@ -0,0 +1,33 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = extractValueFromUpdateExpression; +/** + * Extractor function for an UpdateExpression type value node. + * An update expression is an expression with an update operator. + * For example, foo++ will evaluate to foo + 1. + * + * @param - value - AST Value object with type `UpdateExpression` + * @returns - The extracted value converted to correct type. + */ +function extractValueFromUpdateExpression(value) { + // eslint-disable-next-line global-require + var getValue = require('./index.js').default; + var operator = value.operator, + argument = value.argument, + prefix = value.prefix; + + + var val = getValue(argument); + + switch (operator) { + case '++': + return prefix ? ++val : val++; // eslint-disable-line no-plusplus + case '--': + return prefix ? --val : val--; // eslint-disable-line no-plusplus + default: + return undefined; + } +} \ No newline at end of file diff --git a/lib/values/expressions/index.js b/lib/values/expressions/index.js new file mode 100644 index 0000000..c457798 --- /dev/null +++ b/lib/values/expressions/index.js @@ -0,0 +1,254 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +exports.default = extract; +exports.extractLiteral = extractLiteral; + +var _Literal = require('../Literal'); + +var _Literal2 = _interopRequireDefault(_Literal); + +var _JSXElement = require('../JSXElement'); + +var _JSXElement2 = _interopRequireDefault(_JSXElement); + +var _Identifier = require('./Identifier'); + +var _Identifier2 = _interopRequireDefault(_Identifier); + +var _TaggedTemplateExpression = require('./TaggedTemplateExpression'); + +var _TaggedTemplateExpression2 = _interopRequireDefault(_TaggedTemplateExpression); + +var _TemplateLiteral = require('./TemplateLiteral'); + +var _TemplateLiteral2 = _interopRequireDefault(_TemplateLiteral); + +var _FunctionExpression = require('./FunctionExpression'); + +var _FunctionExpression2 = _interopRequireDefault(_FunctionExpression); + +var _LogicalExpression = require('./LogicalExpression'); + +var _LogicalExpression2 = _interopRequireDefault(_LogicalExpression); + +var _MemberExpression = require('./MemberExpression'); + +var _MemberExpression2 = _interopRequireDefault(_MemberExpression); + +var _ChainExpression = require('./ChainExpression'); + +var _ChainExpression2 = _interopRequireDefault(_ChainExpression); + +var _OptionalCallExpression = require('./OptionalCallExpression'); + +var _OptionalCallExpression2 = _interopRequireDefault(_OptionalCallExpression); + +var _OptionalMemberExpression = require('./OptionalMemberExpression'); + +var _OptionalMemberExpression2 = _interopRequireDefault(_OptionalMemberExpression); + +var _CallExpression = require('./CallExpression'); + +var _CallExpression2 = _interopRequireDefault(_CallExpression); + +var _UnaryExpression = require('./UnaryExpression'); + +var _UnaryExpression2 = _interopRequireDefault(_UnaryExpression); + +var _ThisExpression = require('./ThisExpression'); + +var _ThisExpression2 = _interopRequireDefault(_ThisExpression); + +var _ConditionalExpression = require('./ConditionalExpression'); + +var _ConditionalExpression2 = _interopRequireDefault(_ConditionalExpression); + +var _BinaryExpression = require('./BinaryExpression'); + +var _BinaryExpression2 = _interopRequireDefault(_BinaryExpression); + +var _ObjectExpression = require('./ObjectExpression'); + +var _ObjectExpression2 = _interopRequireDefault(_ObjectExpression); + +var _NewExpression = require('./NewExpression'); + +var _NewExpression2 = _interopRequireDefault(_NewExpression); + +var _UpdateExpression = require('./UpdateExpression'); + +var _UpdateExpression2 = _interopRequireDefault(_UpdateExpression); + +var _ArrayExpression = require('./ArrayExpression'); + +var _ArrayExpression2 = _interopRequireDefault(_ArrayExpression); + +var _BindExpression = require('./BindExpression'); + +var _BindExpression2 = _interopRequireDefault(_BindExpression); + +var _SpreadElement = require('./SpreadElement'); + +var _SpreadElement2 = _interopRequireDefault(_SpreadElement); + +var _TypeCastExpression = require('./TypeCastExpression'); + +var _TypeCastExpression2 = _interopRequireDefault(_TypeCastExpression); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// Composition map of types to their extractor functions. +var TYPES = { + Identifier: _Identifier2.default, + Literal: _Literal2.default, + JSXElement: _JSXElement2.default, + TaggedTemplateExpression: _TaggedTemplateExpression2.default, + TemplateLiteral: _TemplateLiteral2.default, + ArrowFunctionExpression: _FunctionExpression2.default, + FunctionExpression: _FunctionExpression2.default, + LogicalExpression: _LogicalExpression2.default, + MemberExpression: _MemberExpression2.default, + ChainExpression: _ChainExpression2.default, + OptionalCallExpression: _OptionalCallExpression2.default, + OptionalMemberExpression: _OptionalMemberExpression2.default, + CallExpression: _CallExpression2.default, + UnaryExpression: _UnaryExpression2.default, + ThisExpression: _ThisExpression2.default, + ConditionalExpression: _ConditionalExpression2.default, + BinaryExpression: _BinaryExpression2.default, + ObjectExpression: _ObjectExpression2.default, + NewExpression: _NewExpression2.default, + UpdateExpression: _UpdateExpression2.default, + ArrayExpression: _ArrayExpression2.default, + BindExpression: _BindExpression2.default, + SpreadElement: _SpreadElement2.default, + TypeCastExpression: _TypeCastExpression2.default +}; + +var noop = function noop() { + return null; +}; + +var errorMessage = function errorMessage(expression) { + return 'The prop value with an expression type of ' + expression + ' could not be resolved. Please file issue to get this fixed immediately.'; +}; + +/** + * This function maps an AST value node + * to its correct extractor function for its + * given type. + * + * This will map correctly for *all* possible expression types. + * + * @param - value - AST Value object with type `JSXExpressionContainer` + * @returns The extracted value. + */ +function extract(value) { + // Value will not have the expression property when we recurse. + // The type for expression on ArrowFunctionExpression is a boolean. + var expression = void 0; + if (typeof value.expression !== 'boolean' && value.expression) { + expression = value.expression; // eslint-disable-line prefer-destructuring + } else { + expression = value; + } + var _expression = expression, + type = _expression.type; + + + while (type === 'TSNonNullExpression' || type === 'TSAsExpression') { + var _expression2 = expression; + type = _expression2.type; + + if (expression.expression) { + var _expression3 = expression; + expression = _expression3.expression; + } + } + + if (TYPES[type] === undefined) { + // eslint-disable-next-line no-console + console.error(errorMessage(type)); + return null; + } + + return TYPES[type](expression); +} + +// Composition map of types to their extractor functions to handle literals. +var LITERAL_TYPES = _extends({}, TYPES, { + Literal: function Literal(value) { + var extractedVal = TYPES.Literal.call(undefined, value); + var isNull = extractedVal === null; + // This will be convention for attributes that have null + // value explicitly defined (
maps to 'null'). + return isNull ? 'null' : extractedVal; + }, + Identifier: function Identifier(value) { + var isUndefined = TYPES.Identifier.call(undefined, value) === undefined; + return isUndefined ? undefined : null; + }, + JSXElement: noop, + ArrowFunctionExpression: noop, + FunctionExpression: noop, + LogicalExpression: noop, + MemberExpression: noop, + OptionalCallExpression: noop, + OptionalMemberExpression: noop, + CallExpression: noop, + UnaryExpression: function UnaryExpression(value) { + var extractedVal = TYPES.UnaryExpression.call(undefined, value); + return extractedVal === undefined ? null : extractedVal; + }, + UpdateExpression: function UpdateExpression(value) { + var extractedVal = TYPES.UpdateExpression.call(undefined, value); + return extractedVal === undefined ? null : extractedVal; + }, + ThisExpression: noop, + ConditionalExpression: noop, + BinaryExpression: noop, + ObjectExpression: noop, + NewExpression: noop, + ArrayExpression: function ArrayExpression(value) { + var extractedVal = TYPES.ArrayExpression.call(undefined, value); + return extractedVal.filter(function (val) { + return val !== null; + }); + }, + BindExpression: noop, + SpreadElement: noop, + TSNonNullExpression: noop, + TSAsExpression: noop, + TypeCastExpression: noop +}); + +/** + * This function maps an AST value node + * to its correct extractor function for its + * given type. + * + * This will map correctly for *some* possible types that map to literals. + * + * @param - value - AST Value object with type `JSXExpressionContainer` + * @returns The extracted value. + */ +function extractLiteral(value) { + // Value will not have the expression property when we recurse. + var expression = value.expression || value; + var type = expression.type; + + + if (LITERAL_TYPES[type] === undefined) { + // eslint-disable-next-line no-console + console.error(errorMessage(type)); + return null; + } + + return LITERAL_TYPES[type](expression); +} \ No newline at end of file diff --git a/lib/values/index.js b/lib/values/index.js new file mode 100644 index 0000000..496a78c --- /dev/null +++ b/lib/values/index.js @@ -0,0 +1,65 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +exports.default = getValue; +exports.getLiteralValue = getLiteralValue; + +var _Literal = require('./Literal'); + +var _Literal2 = _interopRequireDefault(_Literal); + +var _JSXElement = require('./JSXElement'); + +var _JSXElement2 = _interopRequireDefault(_JSXElement); + +var _expressions = require('./expressions'); + +var _expressions2 = _interopRequireDefault(_expressions); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// Composition map of types to their extractor functions. +var TYPES = { + Literal: _Literal2.default, + JSXElement: _JSXElement2.default, + JSXExpressionContainer: _expressions2.default +}; + +// Composition map of types to their extractor functions to handle literals. +var LITERAL_TYPES = _extends({}, TYPES, { + JSXElement: function JSXElement() { + return null; + }, + JSXExpressionContainer: _expressions.extractLiteral +}); + +/** + * This function maps an AST value node + * to its correct extractor function for its + * given type. + * + * This will map correctly for *all* possible types. + * + * @param value - AST Value object on a JSX Attribute. + */ +function getValue(value) { + return TYPES[value.type](value); +} + +/** + * This function maps an AST value node + * to its correct extractor function for its + * given type. + * + * This will map correctly for *some* possible types that map to literals. + * + * @param value - AST Value object on a JSX Attribute. + */ +function getLiteralValue(value) { + return LITERAL_TYPES[value.type](value); +} \ No newline at end of file