"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.findGeneratedReference = findGeneratedReference;
exports.findGeneratedImportReference = findGeneratedImportReference;
exports.findGeneratedImportDeclaration = findGeneratedImportDeclaration;
loader.lazyRequireGetter(this, "_locColumn", "devtools/client/debugger/src/utils/pause/mapScopes/locColumn");
loader.lazyRequireGetter(this, "_mappingContains", "devtools/client/debugger/src/utils/pause/mapScopes/mappingContains");
loader.lazyRequireGetter(this, "_firefox", "devtools/client/debugger/src/client/firefox");

/* 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/>. */
// eslint-disable-next-line max-len

/**
 * Given a mapped range over the generated source, attempt to resolve a real
 * binding descriptor that can be used to access the value.
 */
async function findGeneratedReference(applicableBindings) {
  // We can adjust this number as we go, but these are a decent start as a
  // general heuristic to assume the bindings were bad or just map a chunk of
  // whole line or something.
  if (applicableBindings.length > 4) {
    // Babel's for..of generates at least 3 bindings inside one range for
    // block-scoped loop variables, so we shouldn't go below that.
    applicableBindings = [];
  }

  for (const applicable of applicableBindings) {
    const result = await mapBindingReferenceToDescriptor(applicable);

    if (result) {
      return result;
    }
  }

  return null;
}

async function findGeneratedImportReference(applicableBindings) {
  // When wrapped, for instance as `Object(ns.default)`, the `Object` binding
  // will be the first in the list. To avoid resolving `Object` as the
  // value of the import itself, we potentially skip the first binding.
  applicableBindings = applicableBindings.filter((applicable, i) => {
    if (!applicable.firstInRange || applicable.binding.loc.type !== "ref" || applicable.binding.loc.meta) {
      return true;
    }

    const next = i + 1 < applicableBindings.length ? applicableBindings[i + 1] : null;
    return !next || next.binding.loc.type !== "ref" || !next.binding.loc.meta;
  }); // We can adjust this number as we go, but these are a decent start as a
  // general heuristic to assume the bindings were bad or just map a chunk of
  // whole line or something.

  if (applicableBindings.length > 2) {
    // Babel's for..of generates at least 3 bindings inside one range for
    // block-scoped loop variables, so we shouldn't go below that.
    applicableBindings = [];
  }

  for (const applicable of applicableBindings) {
    const result = await mapImportReferenceToDescriptor(applicable);

    if (result) {
      return result;
    }
  }

  return null;
}
/**
 * Given a mapped range over the generated source and the name of the imported
 * value that is referenced, attempt to resolve a binding descriptor for
 * the import's value.
 */


async function findGeneratedImportDeclaration(applicableBindings, importName) {
  // We can adjust this number as we go, but these are a decent start as a
  // general heuristic to assume the bindings were bad or just map a chunk of
  // whole line or something.
  if (applicableBindings.length > 10) {
    // Import declarations tend to have a large number of bindings for
    // for things like 'require' and 'interop', so this number is larger
    // than other binding count checks.
    applicableBindings = [];
  }

  let result = null;

  for (const {
    binding
  } of applicableBindings) {
    if (binding.loc.type === "ref") {
      continue;
    }

    const namespaceDesc = await binding.desc();

    if (isPrimitiveValue(namespaceDesc)) {
      continue;
    }

    if (!isObjectValue(namespaceDesc)) {
      // We want to handle cases like
      //
      //   var _mod = require(...);
      //   var _mod2 = _interopRequire(_mod);
      //
      // where "_mod" is optimized out because it is only referenced once. To
      // allow that, we track the optimized-out value as a possible result,
      // but allow later binding values to overwrite the result.
      result = {
        name: binding.name,
        desc: namespaceDesc,
        expression: binding.name
      };
      continue;
    }

    const desc = await readDescriptorProperty(namespaceDesc, importName);
    const expression = `${binding.name}.${importName}`;

    if (desc) {
      result = {
        name: binding.name,
        desc,
        expression
      };
      break;
    }
  }

  return result;
}
/**
 * Given a generated binding, and a range over the generated code, statically
 * check if the given binding matches the range.
 */


async function mapBindingReferenceToDescriptor({
  binding,
  range,
  firstInRange,
  firstOnLine
}) {
  // Allow the mapping to point anywhere within the generated binding
  // location to allow for less than perfect sourcemaps. Since you also
  // need at least one character between identifiers, we also give one
  // characters of space at the front the generated binding in order
  // to increase the probability of finding the right mapping.
  if (range.start.line === binding.loc.start.line && ( // If a binding is the first on a line, Babel will extend the mapping to
  // include the whitespace between the newline and the binding. To handle
  // that, we skip the range requirement for starting location.
  firstInRange || firstOnLine || (0, _locColumn.locColumn)(range.start) >= (0, _locColumn.locColumn)(binding.loc.start)) && (0, _locColumn.locColumn)(range.start) <= (0, _locColumn.locColumn)(binding.loc.end)) {
    return {
      name: binding.name,
      desc: await binding.desc(),
      expression: binding.name
    };
  }

  return null;
}
/**
 * Given an generated binding, and a range over the generated code, statically
 * evaluate accessed properties within the mapped range to resolve the actual
 * imported value.
 */


async function mapImportReferenceToDescriptor({
  binding,
  range
}) {
  if (binding.loc.type !== "ref") {
    return null;
  } // Expression matches require broader searching because sourcemaps usage
  // varies in how they map certain things. For instance given
  //
  //   import { bar } from "mod";
  //   bar();
  //
  // The "bar()" expression is generally expanded into one of two possibly
  // forms, both of which map the "bar" identifier in different ways. See
  // the "^^" markers below for the ranges.
  //
  //   (0, foo.bar)()    // Babel
  //       ^^^^^^^       // mapping
  //       ^^^           // binding
  // vs
  //
  //   __webpack_require__.i(foo.bar)() // Webpack 2
  //   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   // mapping
  //                         ^^^        // binding
  // vs
  //
  //   Object(foo.bar)() // Webpack >= 3
  //   ^^^^^^^^^^^^^^^   // mapping
  //          ^^^        // binding
  //
  // Unfortunately, Webpack also has a tendancy to over-map past the call
  // expression to the start of the next line, at least when there isn't
  // anything else on that line that is mapped, e.g.
  //
  //   Object(foo.bar)()
  //   ^^^^^^^^^^^^^^^^^
  //   ^                 // wrapped to column 0 of next line


  if (!(0, _mappingContains.mappingContains)(range, binding.loc)) {
    return null;
  } // Webpack 2's import declarations wrap calls with an identity fn, so we
  // need to make sure to skip that binding because it is mapped to the
  // location of the original binding usage.


  if (binding.name === "__webpack_require__" && binding.loc.meta && binding.loc.meta.type === "member" && binding.loc.meta.property === "i") {
    return null;
  }

  let expression = binding.name;
  let desc = await binding.desc();

  if (binding.loc.type === "ref") {
    const {
      meta
    } = binding.loc; // Limit to 2 simple property or inherits operartions, since it would
    // just be more work to search more and it is very unlikely that
    // bindings would be mapped to more than a single member + inherits
    // wrapper.

    for (let op = meta, index = 0; op && (0, _mappingContains.mappingContains)(range, op) && desc && index < 2; index++, op = op?.parent) {
      // Calling could potentially trigger side-effects, which would not
      // be ideal for this case.
      if (op.type === "call") {
        return null;
      }

      if (op.type === "inherit") {
        continue;
      }

      desc = await readDescriptorProperty(desc, op.property);
      expression += `.${op.property}`;
    }
  }

  return desc ? {
    name: binding.name,
    desc,
    expression
  } : null;
}

function isPrimitiveValue(desc) {
  return desc && (!desc.value || typeof desc.value !== "object");
}

function isObjectValue(desc) {
  return desc && !isPrimitiveValue(desc) && desc.value.type === "object" && // Note: The check for `.type` might already cover the optimizedOut case
  // but not 100% sure, so just being cautious.
  !desc.value.optimizedOut;
}

async function readDescriptorProperty(desc, property) {
  if (!desc) {
    return null;
  }

  if (typeof desc.value !== "object" || !desc.value) {
    // If accessing a property on a primitive type, just return 'undefined'
    // as the value.
    return {
      value: {
        type: "undefined"
      }
    };
  }

  if (!isObjectValue(desc)) {
    // If we got a non-primitive descriptor but it isn't an object, then
    // it's definitely not the namespace and it is probably an error.
    return desc;
  }

  const objectFront = _firefox.clientCommands.createObjectFront(desc.value);

  return (await objectFront.getProperty(property)).descriptor;
}