tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

lezer-utils.js (16987B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 // These are all the current lezer node types used in the source editor,
      7 // Add more here as are needed
      8 const nodeTypes = {
      9  AssignmentExpression: "AssignmentExpression",
     10  FunctionExpression: "FunctionExpression",
     11  FunctionDeclaration: "FunctionDeclaration",
     12  ArrowFunction: "ArrowFunction",
     13  MethodDeclaration: "MethodDeclaration",
     14  ClassDeclaration: "ClassDeclaration",
     15  ClassExpression: "ClassExpression",
     16  Property: "Property",
     17  PropertyDeclaration: "PropertyDeclaration",
     18  PropertyDefinition: "PropertyDefinition",
     19  PrivatePropertyDefinition: "PrivatePropertyDefinition",
     20  MemberExpression: "MemberExpression",
     21  VariableDeclaration: "VariableDeclaration",
     22  VariableDefinition: "VariableDefinition",
     23  VariableName: "VariableName",
     24  this: "this",
     25  PropertyName: "PropertyName",
     26  Equals: "Equals",
     27  ParamList: "ParamList",
     28  Spread: "Spread",
     29  Number: "Number",
     30  Script: "Script",
     31  Block: "Block",
     32 };
     33 
     34 const functionsSet = new Set([
     35  nodeTypes.FunctionExpression,
     36  nodeTypes.FunctionDeclaration,
     37  nodeTypes.ArrowFunction,
     38  nodeTypes.MethodDeclaration,
     39 ]);
     40 
     41 const nodeTypeSets = {
     42  functions: functionsSet,
     43  expressions: new Set([
     44    nodeTypes.MemberExpression,
     45    nodeTypes.VariableDefinition,
     46    nodeTypes.VariableName,
     47    nodeTypes.this,
     48    nodeTypes.PropertyName,
     49  ]),
     50  functionExpressions: new Set([
     51    nodeTypes.ArrowFunction,
     52    nodeTypes.FunctionExpression,
     53    nodeTypes.ParamList,
     54  ]),
     55  declarations: new Set([
     56    nodeTypes.MethodDeclaration,
     57    nodeTypes.PropertyDeclaration,
     58  ]),
     59  functionsDeclAndExpr: new Set([
     60    ...functionsSet,
     61    nodeTypes.Property,
     62    nodeTypes.PropertyDeclaration,
     63    nodeTypes.VariableDeclaration,
     64    nodeTypes.AssignmentExpression,
     65  ]),
     66  functionsVarDecl: new Set([
     67    ...functionsSet,
     68    // For anonymous functions we are using the variable name where the function is stored. See `getFunctionName`.
     69    nodeTypes.VariableDeclaration,
     70  ]),
     71  paramList: new Set([nodeTypes.ParamList]),
     72  variableDefinition: new Set([nodeTypes.VariableDefinition]),
     73  numberAndProperty: new Set([nodeTypes.PropertyDefinition, nodeTypes.Number]),
     74  memberExpression: new Set([nodeTypes.MemberExpression]),
     75  classes: new Set([nodeTypes.ClassDeclaration, nodeTypes.ClassExpression]),
     76  bindingReferences: new Set([
     77    nodeTypes.VariableDefinition,
     78    nodeTypes.VariableName,
     79    nodeTypes.PropertyName,
     80  ]),
     81  expressionProperty: new Set([nodeTypes.PropertyName]),
     82 };
     83 
     84 const ast = new Map();
     85 
     86 /**
     87 * Checks if a node has children with any of the node types specified
     88 *
     89 * @param {object} node
     90 * @param {Set} types
     91 * @returns
     92 */
     93 function hasChildNodeOfType(node, types) {
     94  let childNode = node.firstChild;
     95  while (childNode !== null) {
     96    if (types.has(childNode.name)) {
     97      return true;
     98    }
     99    childNode = childNode.nextSibling;
    100  }
    101  return false;
    102 }
    103 
    104 /**
    105 * Checks if a node has children with any of the node types specified
    106 *
    107 * @param {object} node
    108 * @param {Set} types
    109 * @returns
    110 */
    111 function findChildNodeOfType(node, types) {
    112  let childNode = node.firstChild;
    113  while (childNode !== null) {
    114    if (types.has(childNode.name)) {
    115      return childNode;
    116    }
    117    childNode = childNode.nextSibling;
    118  }
    119  return null;
    120 }
    121 
    122 /**
    123 * Gets a cached tree or parses the the source content
    124 *
    125 * @param {object} parserLanguage - The language parser used to parse the source
    126 * @param {string} id - A unique identifier for the source
    127 * @param {string} content - The source text
    128 * @returns {Tree} - https://lezer.codemirror.net/docs/ref/#common.Tree
    129 */
    130 function getTree(parserLanguage, id, content) {
    131  if (ast.has(id)) {
    132    return ast.get(id);
    133  }
    134  const tree = parserLanguage.parser.parse(content);
    135  ast.set(id, tree);
    136  return tree;
    137 }
    138 
    139 function clear() {
    140  ast.clear();
    141 }
    142 
    143 /**
    144 * Gets the node and the function name which immediately encloses the node (representing a location)
    145 *
    146 * @param {object} doc - The codemirror document used to retrive the part of content
    147 * @param {object} node - The parser syntax node https://lezer.codemirror.net/docs/ref/#common.SyntaxNode
    148 * @param {object} options
    149 * @param {boolean} options.includeAnonymousFunctions - if true, allow matching anonymous functions
    150 * @returns
    151 */
    152 function getEnclosingFunction(
    153  doc,
    154  node,
    155  options = { includeAnonymousFunctions: false }
    156 ) {
    157  let parentNode = node.parent;
    158  while (parentNode !== null) {
    159    if (nodeTypeSets.functionsVarDecl.has(parentNode.name)) {
    160      // For anonymous functions, we use variable declarations, but we only care about variable declarations which are part of function expressions
    161      if (
    162        parentNode.name == nodeTypes.VariableDeclaration &&
    163        !hasChildNodeOfType(parentNode.node, nodeTypeSets.functionExpressions)
    164      ) {
    165        parentNode = parentNode.parent;
    166        continue;
    167      }
    168      const funcName = getFunctionName(doc, parentNode);
    169      if (funcName || options.includeAnonymousFunctions) {
    170        return {
    171          node: parentNode,
    172          funcName,
    173        };
    174      }
    175    }
    176    parentNode = parentNode.parent;
    177  }
    178  return null;
    179 }
    180 
    181 /**
    182 * Gets the parent scope node for the specified node.
    183 * If neither is found then we fallback to the script node for the source.
    184 *
    185 * @param {object} node
    186 * @param {string} scopeType - The scope type specifies what kind of scope node to look for.
    187 * The types are defined by the platform. See https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Environment.html#type
    188 * @returns {object | null} scope node or null if none is found
    189 */
    190 function getParentScopeOfType(node, scopeType) {
    191  let parentNode = node.parent;
    192  let lastParentNode = parentNode;
    193  while (parentNode !== null) {
    194    if (scopeType == "block" || scopeType == "object") {
    195      if (parentNode.name == nodeTypes.Block) {
    196        return parentNode;
    197      }
    198    } else if (nodeTypeSets.functionsVarDecl.has(parentNode.name)) {
    199      if (
    200        parentNode.name == nodeTypes.VariableDeclaration &&
    201        !hasChildNodeOfType(parentNode.node, nodeTypeSets.functionExpressions)
    202      ) {
    203        parentNode = parentNode.parent;
    204        continue;
    205      }
    206      return parentNode;
    207    }
    208    lastParentNode = parentNode;
    209    parentNode = parentNode.parent;
    210  }
    211  // If no function node was found up to the root node
    212  if (lastParentNode?.name == nodeTypes.Script) {
    213    return lastParentNode;
    214  }
    215  return null;
    216 }
    217 
    218 /**
    219 * Gets the node at the specified location
    220 *
    221 * @param {object} doc - https://codemirror.net/docs/ref/#state.EditorState.doc
    222 * @param {object} tree - https://lezer.codemirror.net/docs/ref/#common.Tree
    223 * @param {object} location
    224 * @returns {object} node - https://lezer.codemirror.net/docs/ref/#common.SyntaxNodeRef
    225 */
    226 function getTreeNodeAtLocation(doc, tree, location) {
    227  try {
    228    const line = doc.line(location.line);
    229    const pos = line.from + location.column;
    230    return tree.resolve(pos, 1);
    231  } catch (e) {
    232    // if the line is not found in the document doc.line() will throw
    233    console.warn(e.message);
    234  }
    235  return null;
    236 }
    237 
    238 /**
    239 * Converts Codemirror position to valid source location. Used only for CM6
    240 *
    241 * @param {object} doc - The Codemirror document used to retrive the part of content
    242 * @param {number} pos - Codemirror offset
    243 * @returns
    244 */
    245 function positionToLocation(doc, pos) {
    246  if (pos == null) {
    247    return {
    248      line: null,
    249      column: null,
    250    };
    251  }
    252  const line = doc.lineAt(pos);
    253  return {
    254    line: line.number,
    255    column: pos - line.from,
    256  };
    257 }
    258 
    259 /**
    260 * Gets the name of the function if any exists, returns null
    261 * for anonymous functions.
    262 *
    263 * @param {object} doc - The codemirror document used to retrive the part of content
    264 * @param {object} node - The parser syntax node https://lezer.codemirror.net/docs/ref/#common.SyntaxNode
    265 * @returns {string | null}
    266 */
    267 function getFunctionName(doc, node) {
    268  /**
    269   * Examples:
    270   *  - Gets `foo` in `class ESClass { foo(a, b) {}}`
    271   *  - Gets `bar` in `class ESClass { bar = function () {}}`
    272   *  - Gets `boo` in `class ESClass { boo = () => {}}`
    273   *  - Gets `#pfoo` in `class ESClass { #pfoo() => {}}`
    274   */
    275  if (
    276    node.name == nodeTypes.MethodDeclaration ||
    277    (node.name == nodeTypes.PropertyDeclaration &&
    278      hasChildNodeOfType(node, nodeTypeSets.functionExpressions))
    279  ) {
    280    const propDefNode = findChildNodeOfType(
    281      node,
    282      new Set([
    283        nodeTypes.PropertyDefinition,
    284        nodeTypes.PrivatePropertyDefinition,
    285      ])
    286    );
    287 
    288    if (!propDefNode) {
    289      return null;
    290    }
    291    return doc.sliceString(propDefNode.from, propDefNode.to);
    292  } else if (
    293    /**
    294     * Examples:
    295     *  - Gets `foo` in `let foo = function () {};`
    296     *  - Gets `bar` in `const bar = () => {}`
    297     */
    298    node.name == nodeTypes.VariableDeclaration &&
    299    hasChildNodeOfType(node, nodeTypeSets.functionExpressions)
    300  ) {
    301    const varDefNode = findChildNodeOfType(
    302      node,
    303      nodeTypeSets.variableDefinition
    304    );
    305 
    306    if (!varDefNode) {
    307      return null;
    308    }
    309    return doc.sliceString(varDefNode.from, varDefNode.to);
    310  } else if (
    311    /**
    312     * Examples:
    313     *  - Gets `Foo` in `function Foo() {} - FunctionDeclaration`
    314     *  - Gets `bar` in `function bar(a) {} - Functionexpression`
    315     */
    316    node.name == nodeTypes.FunctionDeclaration ||
    317    node.name == nodeTypes.FunctionExpression
    318  ) {
    319    const varDefNode = findChildNodeOfType(
    320      node,
    321      nodeTypeSets.variableDefinition
    322    );
    323 
    324    if (!varDefNode) {
    325      return null;
    326    }
    327    return doc.sliceString(varDefNode.from, varDefNode.to);
    328  } else if (
    329    /**
    330     * Examples:
    331     *  - Gets `foo` in `const a = { foo(a, ...b) {} }`
    332     *  - Gets `bar` in `const a = { bar: function () {} }`
    333     *  - Gets `bla` in `const a = { bla: () => {} }`
    334     *  - Gets `1234` in `const a = { 1234: () => {} }`
    335     */
    336    node.name == nodeTypes.Property &&
    337    hasChildNodeOfType(node, nodeTypeSets.functionExpressions)
    338  ) {
    339    const propDefNode = findChildNodeOfType(
    340      node,
    341      nodeTypeSets.numberAndProperty
    342    );
    343 
    344    if (!propDefNode) {
    345      return null;
    346    }
    347    return doc.sliceString(propDefNode.from, propDefNode.to);
    348  } else if (
    349    /**
    350     * Examples:
    351     *  - Gets `bar` in `const foo = {}; foo.bar = function() {}`
    352     *  - Gets `bla` in `const foo = {}; foo.bla = () => {}`
    353     */
    354    node.name == nodeTypes.AssignmentExpression &&
    355    hasChildNodeOfType(node, nodeTypeSets.functionExpressions)
    356  ) {
    357    const memExprDefNode = findChildNodeOfType(
    358      node,
    359      nodeTypeSets.memberExpression
    360    );
    361 
    362    if (!memExprDefNode) {
    363      return null;
    364    }
    365    // Get the rightmost part of the member expression i.e for a.b.c get c
    366    const exprParts = doc
    367      .sliceString(memExprDefNode.from, memExprDefNode.to)
    368      .split(".");
    369    return exprParts.at(-1);
    370  }
    371 
    372  return null;
    373 }
    374 
    375 /**
    376 * Gets the parameter names of the function as an array
    377 *
    378 * @param {object} doc - The codemirror document used to retrieve the part of content
    379 * @param {object} node - The parser syntax node https://lezer.codemirror.net/docs/ref/#common.SyntaxNode
    380 * @returns {Array}
    381 */
    382 function getFunctionParameterNames(doc, node) {
    383  // Find the parameter list node
    384 
    385  let exprNode = node;
    386 
    387  if (
    388    // Example: Gets `(a)` in `const foo = {}; foo.bar = function(a) {}`
    389    node.name == nodeTypes.AssignmentExpression ||
    390    // Example: Gets `(a, b)` in `let foo = function (a, b) {};`
    391    node.name == nodeTypes.VariableDeclaration ||
    392    // Example: Gets `(x, y)` in `class ESClass { bar = function (x, y) {}}`
    393    node.name == nodeTypes.PropertyDeclaration ||
    394    // Example: Gets `(foo, ...bar)` in `const a = { foo: (foo, ...bar) {}}`
    395    (node.name == nodeTypes.Property &&
    396      !hasChildNodeOfType(node, nodeTypeSets.paramList))
    397  ) {
    398    exprNode = findChildNodeOfType(node, nodeTypeSets.functionExpressions);
    399  }
    400 
    401  /**
    402   * Others
    403   *  Function Declarations - Gets `(x, y)` in `function Foo(x, y) {}`
    404   *  Method Declarations - Gets `(a, b)` in `class ESClass { foo(a, b) {}}`
    405   */
    406  const paramListNode = findChildNodeOfType(exprNode, nodeTypeSets.paramList);
    407  if (paramListNode == null) {
    408    return [];
    409  }
    410 
    411  const names = [];
    412  let currNode = paramListNode.firstChild; // "("
    413  // Get all the parameter names
    414  while (currNode !== null && currNode.name !== ")") {
    415    if (currNode.name == nodeTypes.VariableDefinition) {
    416      // ignore spread operators i.e foo(...x)
    417      if (currNode.prevSibling?.name !== nodeTypes.Spread) {
    418        names.push(doc.sliceString(currNode.from, currNode.to));
    419      }
    420    }
    421    currNode = currNode.nextSibling;
    422  }
    423  return names;
    424 }
    425 
    426 function getFunctionClass(doc, node) {
    427  /**
    428   * Examples (Class Methods and Properties):
    429   *  Gets `ESClass` in `class ESClass { foo(a, b) {}}`
    430   *  Gets `ESClass` in `class ESClass { bar = function () {}}`
    431   */
    432  if (!nodeTypeSets.declarations.has(node.name)) {
    433    return null;
    434  }
    435  return doc.sliceString(
    436    node.parent.prevSibling.from,
    437    node.parent.prevSibling.to
    438  );
    439 }
    440 
    441 /**
    442 * Gets the meta data for member expression nodes
    443 *
    444 * @param {object} doc - The codemirror document used to retrieve the part of content
    445 * @param {object} node - The parser syntax node https://lezer.codemirror.net/docs/ref/#common.SyntaxNode
    446 * @returns
    447 */
    448 function getMetaBindings(doc, node) {
    449  if (!node || node.name !== nodeTypes.MemberExpression) {
    450    return null;
    451  }
    452 
    453  const memExpr = doc.sliceString(node.from, node.to).split(".");
    454  return {
    455    type: "member",
    456    start: positionToLocation(doc, node.from),
    457    end: positionToLocation(doc, node.to),
    458    property: memExpr.at(-1),
    459    parent: getMetaBindings(doc, node.parent),
    460  };
    461 }
    462 
    463 /**
    464 * Walk the syntax tree of the langauge provided
    465 *
    466 * @param {object}   view - Codemirror view (https://codemirror.net/docs/ref/#view)
    467 * @param {object}   language - Codemirror Language (https://codemirror.net/docs/ref/#language)
    468 * @param {object}   options
    469 *        {Boolean}  options.forceParseTo - Force parsing the document up to a certain point
    470 *        {Function} options.enterVisitor - A function that is called when a node is entered
    471 *        {Set}      options.filterSet - A set of node types which should be visited, all others should be ignored
    472 *        {Number}   options.walkFrom - Determine the location in the AST where the iteration of the syntax tree should start
    473 *        {Number}   options.walkTo - Determine the location in the AST where the iteration of the syntax tree should end
    474 */
    475 async function walkTree(view, language, options) {
    476  const { forceParsing, syntaxTree } = language;
    477  if (options.forceParseTo) {
    478    // Force parsing the source, up to the end of the current viewport,
    479    // Also increasing the timeout threshold so we make sure
    480    // all required content is parsed (this is mostly needed for larger sources).
    481    await forceParsing(view, options.forceParseTo, 10000);
    482  }
    483  await syntaxTree(view.state).iterate({
    484    enter: node => {
    485      if (options.filterSet?.has(node.name)) {
    486        options.enterVisitor(node);
    487      }
    488    },
    489    from: options.walkFrom,
    490    to: options.walkTo,
    491  });
    492 }
    493 
    494 /**
    495 * This enables walking a specific part of the syntax tree using the cursor
    496 * provided by the node (which is the parent)
    497 *
    498 * @param {object} cursor - https://lezer.codemirror.net/docs/ref/#common.TreeCursor
    499 * @param {object} options
    500 *        {Function} options.enterVisitor - A function that is called when a node is entered
    501 *        {Set}      options.filterSet - A set of node types which should be visited, all others should be ignored
    502 */
    503 async function walkCursor(cursor, options) {
    504  await cursor.iterate(node => {
    505    if (options.filterSet?.has(node.name)) {
    506      options.enterVisitor(node);
    507    }
    508  });
    509 }
    510 
    511 /**
    512 * Merge variables, arguments and child properties of member expressions
    513 * into a unique "bindings" objects where arguments overrides variables.
    514 *
    515 * @param {object} scopeBindings
    516 * @returns {object} bindings
    517 */
    518 function getScopeBindings(scopeBindings) {
    519  const bindings = { ...scopeBindings.variables };
    520  scopeBindings.arguments.forEach(argument => {
    521    Object.keys(argument).forEach(key => {
    522      bindings[key] = argument[key];
    523    });
    524  });
    525  // Find and add child properties of member expressions as bindings
    526  for (const v in scopeBindings.variables) {
    527    const ownProps = scopeBindings.variables[v]?.value?.preview?.ownProperties;
    528    if (ownProps) {
    529      Object.keys(ownProps).forEach(k => {
    530        bindings[k] = ownProps[k];
    531      });
    532    }
    533  }
    534  return bindings;
    535 }
    536 
    537 module.exports = {
    538  getFunctionName,
    539  getFunctionParameterNames,
    540  getFunctionClass,
    541  getEnclosingFunction,
    542  getTreeNodeAtLocation,
    543  getMetaBindings,
    544  nodeTypes,
    545  nodeTypeSets,
    546  walkTree,
    547  getTree,
    548  clear,
    549  walkCursor,
    550  positionToLocation,
    551  getParentScopeOfType,
    552  getScopeBindings,
    553 };