tor-browser

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

ast.js (5073B)


      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 import { parseScriptTags } from "./parse-script-tags";
      6 import * as babelParser from "@babel/parser";
      7 import * as t from "@babel/types";
      8 import { getSource } from "../sources";
      9 
     10 const ASTs = new Map();
     11 
     12 function _parse(code, opts) {
     13  return babelParser.parse(code, {
     14    ...opts,
     15    tokens: true,
     16  });
     17 }
     18 
     19 const sourceOptions = {
     20  generated: {
     21    sourceType: "unambiguous",
     22    tokens: true,
     23    plugins: [
     24      "classStaticBlock",
     25      "classPrivateProperties",
     26      "classPrivateMethods",
     27      "classProperties",
     28      "explicitResourceManagement",
     29      "importAttributes",
     30      "objectRestSpread",
     31      "optionalChaining",
     32      "privateIn",
     33      "nullishCoalescingOperator",
     34      "regexpUnicodeSets",
     35    ],
     36  },
     37  original: {
     38    sourceType: "unambiguous",
     39    tokens: true,
     40    plugins: [
     41      "asyncGenerators",
     42      "classPrivateMethods",
     43      "classPrivateProperties",
     44      "classProperties",
     45      "classStaticBlock",
     46      "decorators-legacy",
     47      "doExpressions",
     48      "dynamicImport",
     49      "explicitResourceManagement",
     50      "exportDefaultFrom",
     51      "exportNamespaceFrom",
     52      "flow",
     53      "functionBind",
     54      "functionSent",
     55      "importAttributes",
     56      "jsx",
     57      "nullishCoalescingOperator",
     58      "objectRestSpread",
     59      "optionalChaining",
     60      "react-jsx",
     61      "regexpUnicodeSets",
     62    ],
     63  },
     64 };
     65 
     66 export function parse(text, opts) {
     67  let ast = {};
     68  if (!text) {
     69    return ast;
     70  }
     71 
     72  try {
     73    ast = _parse(text, opts);
     74  } catch (error) {
     75    console.error(error);
     76  }
     77 
     78  return ast;
     79 }
     80 
     81 // Custom parser for parse-script-tags that adapts its input structure to
     82 // our parser's signature
     83 function htmlParser({ source, line }) {
     84  return parse(source, { startLine: line, ...sourceOptions.generated });
     85 }
     86 
     87 const VUE_COMPONENT_START = /^\s*</;
     88 function vueParser({ source, line }) {
     89  return parse(source, {
     90    startLine: line,
     91    ...sourceOptions.original,
     92  });
     93 }
     94 function parseVueScript(code) {
     95  if (typeof code !== "string") {
     96    return {};
     97  }
     98 
     99  let ast;
    100 
    101  // .vue files go through several passes, so while there is a
    102  // single-file-component Vue template, there are also generally .vue files
    103  // that are still just JS as well.
    104  if (code.match(VUE_COMPONENT_START)) {
    105    ast = parseScriptTags(code, vueParser);
    106    if (t.isFile(ast)) {
    107      // parseScriptTags is currently hard-coded to return scripts, but Vue
    108      // always expects ESM syntax, so we just hard-code it.
    109      ast.program.sourceType = "module";
    110    }
    111  } else {
    112    ast = parse(code, sourceOptions.original);
    113  }
    114  return ast;
    115 }
    116 
    117 export function parseConsoleScript(text) {
    118  try {
    119    return _parse(text, {
    120      plugins: [
    121        "classStaticBlock",
    122        "classPrivateProperties",
    123        "classPrivateMethods",
    124        "explicitResourceManagement",
    125        "objectRestSpread",
    126        "dynamicImport",
    127        "nullishCoalescingOperator",
    128        "optionalChaining",
    129        "regexpUnicodeSets",
    130        "topLevelAwait",
    131      ],
    132      sourceType: "module",
    133      strictMode: false,
    134    });
    135  } catch (e) {
    136    return null;
    137  }
    138 }
    139 
    140 export function parseScript(text, opts) {
    141  return _parse(text, opts);
    142 }
    143 
    144 export function getAst(sourceId) {
    145  if (ASTs.has(sourceId)) {
    146    return ASTs.get(sourceId);
    147  }
    148 
    149  const source = getSource(sourceId);
    150 
    151  if (source.isWasm) {
    152    return null;
    153  }
    154 
    155  let ast = {};
    156  const { contentType } = source;
    157  if (contentType == "text/html") {
    158    ast = parseScriptTags(source.text, htmlParser) || {};
    159  } else if (contentType && contentType === "text/vue") {
    160    ast = parseVueScript(source.text) || {};
    161  } else if (
    162    contentType &&
    163    contentType.match(/(javascript|jsx)/) &&
    164    !contentType.match(/typescript-jsx/)
    165  ) {
    166    const type = source.id.includes("original") ? "original" : "generated";
    167    const options = sourceOptions[type];
    168    ast = parse(source.text, options);
    169  } else if (contentType && contentType.match(/typescript/)) {
    170    const options = {
    171      ...sourceOptions.original,
    172      plugins: [
    173        ...sourceOptions.original.plugins.filter(
    174          p =>
    175            p !== "flow" &&
    176            p !== "decorators" &&
    177            p !== "decorators2" &&
    178            (p !== "jsx" || contentType.match(/typescript-jsx/))
    179        ),
    180        "decorators-legacy",
    181        "typescript",
    182      ],
    183    };
    184    ast = parse(source.text, options);
    185  }
    186 
    187  ASTs.set(source.id, ast);
    188  return ast;
    189 }
    190 
    191 export function clearASTs(sourceIds) {
    192  for (const sourceId of sourceIds) {
    193    ASTs.delete(sourceId);
    194  }
    195 }
    196 
    197 export function replaceNode(ancestors, node) {
    198  const parent = ancestors[ancestors.length - 1];
    199 
    200  if (typeof parent.index === "number") {
    201    if (Array.isArray(node)) {
    202      parent.node[parent.key].splice(parent.index, 1, ...node);
    203    } else {
    204      parent.node[parent.key][parent.index] = node;
    205    }
    206  } else {
    207    parent.node[parent.key] = node;
    208  }
    209 }