diff options
Diffstat (limited to 'devtools/client/debugger/src/workers/parser/utils/helpers.js')
-rw-r--r-- | devtools/client/debugger/src/workers/parser/utils/helpers.js | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/workers/parser/utils/helpers.js b/devtools/client/debugger/src/workers/parser/utils/helpers.js new file mode 100644 index 0000000000..0045b54136 --- /dev/null +++ b/devtools/client/debugger/src/workers/parser/utils/helpers.js @@ -0,0 +1,232 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +import * as t from "@babel/types"; +import generate from "@babel/generator"; + +export function isFunction(node) { + return ( + t.isFunction(node) || + t.isArrowFunctionExpression(node) || + t.isObjectMethod(node) || + t.isClassMethod(node) + ); +} + +export function isAwaitExpression(path) { + const { node, parent } = path; + return ( + t.isAwaitExpression(node) || + t.isAwaitExpression(parent.init) || + t.isAwaitExpression(parent) + ); +} + +export function isYieldExpression(path) { + const { node, parent } = path; + return ( + t.isYieldExpression(node) || + t.isYieldExpression(parent.init) || + t.isYieldExpression(parent) + ); +} + +export function isObjectShorthand(parent) { + if (!t.isObjectProperty(parent)) { + return false; + } + + if (parent.value && parent.value.left) { + return ( + parent.value.type === "AssignmentPattern" && + parent.value.left.type === "Identifier" + ); + } + + return ( + parent.value && + parent.key.start == parent.value.start && + parent.key.loc.identifierName === parent.value.loc.identifierName + ); +} + +export function getObjectExpressionValue(node) { + const { value } = node; + + if (t.isIdentifier(value)) { + return value.name; + } + + if (t.isCallExpression(value) || t.isFunctionExpression(value)) { + return ""; + } + const code = generate(value).code; + + const shouldWrap = t.isObjectExpression(value); + return shouldWrap ? `(${code})` : code; +} + +export function getCode(node) { + return generate(node).code; +} + +export function getComments(ast) { + if (!ast || !ast.comments) { + return []; + } + return ast.comments.map(comment => ({ + name: comment.location, + location: comment.loc, + })); +} + +export function isComputedExpression(expression) { + return /^\[/m.test(expression); +} + +export function getMemberExpression(root) { + function _getMemberExpression(node, expr) { + if (t.isMemberExpression(node)) { + expr = [node.property.name].concat(expr); + return _getMemberExpression(node.object, expr); + } + + if (t.isCallExpression(node)) { + return []; + } + + if (t.isThisExpression(node)) { + return ["this"].concat(expr); + } + + return [node.name].concat(expr); + } + + const expr = _getMemberExpression(root, []); + return expr.join("."); +} + +export function getVariables(dec) { + if (!dec.id) { + return []; + } + + if (t.isArrayPattern(dec.id)) { + if (!dec.id.elements) { + return []; + } + + // NOTE: it's possible that an element is empty or has several variables + // e.g. const [, a] = arr + // e.g. const [{a, b }] = 2 + return dec.id.elements + .filter(Boolean) + .map(element => ({ + name: t.isAssignmentPattern(element) + ? element.left.name + : element.name || element.argument?.name, + location: element.loc, + })) + .filter(({ name }) => name); + } + + return [ + { + name: dec.id.name, + location: dec.loc, + }, + ]; +} + +/** + * Add the identifiers for a given object pattern. + * + * @param {Array.<Object>} identifiers + * the current list of identifiers where to push the new identifiers + * related to this path. + * @param {Set<String>} identifiersKeys + * List of currently registered identifier location key. + * @param {Object} pattern + */ +export function addPatternIdentifiers(identifiers, identifiersKeys, pattern) { + let items; + if (t.isObjectPattern(pattern)) { + items = pattern.properties.map(({ value }) => value); + } + + if (t.isArrayPattern(pattern)) { + items = pattern.elements; + } + + if (items) { + addIdentifiers(identifiers, identifiersKeys, items); + } +} + +function addIdentifiers(identifiers, identifiersKeys, items) { + for (const item of items) { + if (t.isObjectPattern(item) || t.isArrayPattern(item)) { + addPatternIdentifiers(identifiers, identifiersKeys, item); + } else if (t.isIdentifier(item)) { + if (!identifiersKeys.has(nodeLocationKey(item.loc))) { + identifiers.push({ + name: item.name, + expression: item.name, + location: item.loc, + }); + } + } + } +} + +// Top Level checks the number of "body" nodes in the ancestor chain +// if the node is top-level, then it shoul only have one body. +export function isTopLevel(ancestors) { + return ancestors.filter(ancestor => ancestor.key == "body").length == 1; +} + +export function nodeLocationKey({ start, end }) { + return `${start.line}:${start.column}:${end.line}:${end.column}`; +} + +export function getFunctionParameterNames(path) { + if (path.node.params != null) { + return path.node.params.map(param => { + if (param.type !== "AssignmentPattern") { + return param.name; + } + + // Parameter with default value + if ( + param.left.type === "Identifier" && + param.right.type === "Identifier" + ) { + return `${param.left.name} = ${param.right.name}`; + } else if ( + param.left.type === "Identifier" && + param.right.type === "StringLiteral" + ) { + return `${param.left.name} = ${param.right.value}`; + } else if ( + param.left.type === "Identifier" && + param.right.type === "ObjectExpression" + ) { + return `${param.left.name} = {}`; + } else if ( + param.left.type === "Identifier" && + param.right.type === "ArrayExpression" + ) { + return `${param.left.name} = []`; + } else if ( + param.left.type === "Identifier" && + param.right.type === "NullLiteral" + ) { + return `${param.left.name} = null`; + } + + return null; + }); + } + return []; +} |