text: "use strict"
  
  const path = require("path")
  const extract = require("./extract")
  
  const defaultHTMLExtensions = [
    ".erb",
    ".handlebars",
    ".hbs",
    ".htm",
    ".html",
    ".mustache",
    ".nunjucks",
    ".php",
    ".tag",
    ".twig",
    ".vue",
    ".we",
  ]
  
  const defaultXMLExtensions = [
    ".xhtml",
    ".xml",
  ]
  
  // Disclaimer:
  //
  // This is not a long term viable solution. ESLint needs to improve its processor API to
  // provide access to the configuration before actually preprocess files, but it's not
  // planed yet. This solution is quite ugly but shouldn't alter eslint process.
  //
  // Related github issues:
  // https://github.com/eslint/eslint/issues/3422
  // https://github.com/eslint/eslint/issues/4153
  
  iterateESLintModules(patch)
  
  function iterateESLintModules(fn) {
    if (!require.cache || Object.keys(require.cache).length === 0) {
      // Jest is replacing the node "require" function, and "require.cache" isn't available here.
      return fn({
        eslint: require("eslint/lib/eslint"),
        SourceCodeFixer: require("eslint/lib/util/source-code-fixer"),
      })
    }
  
    let found = false
    const needle = path.join("lib", "eslint.js")
    for (const key in require.cache) {
      if (key.endsWith(needle)) {
        const sourceCodeFixerKey = path.join(key, "..", "util", "source-code-fixer.js")
  
        const eslint = require.cache[key]
        const SourceCodeFixer = require.cache[sourceCodeFixerKey]
  
        if (eslint.exports && typeof eslint.exports.verify === "function" &&
          SourceCodeFixer && SourceCodeFixer.exports) {
          fn({ eslint: eslint.exports, SourceCodeFixer: SourceCodeFixer.exports })
          found = true
        }
      }
    }
  
    if (!found) {
      throw new Error("eslint-plugin-html error: It seems that eslint is not loaded. " +
                      "If you think it is a bug, please file a report at " +
                      "https://github.com/BenoitZugmeyer/eslint-plugin-html/issues")
    }
  }
  
  function filterOut(array, excludeArray) {
    if (!excludeArray) return array
    return array.filter((item) => excludeArray.indexOf(item) < 0)
  }
  
  function compileRegExp(re) {
    const tokens = re.split("/")
    if (tokens.shift()) { // Ignore first token
      throw new Error(`Invalid regexp: ${re}`)
    }
    const flags = tokens.pop()
    return new RegExp(tokens.join("/"), flags)
  }
  
  function getPluginSettings(settings) {
  
    const htmlExtensions = settings["html/html-extensions"] ||
        filterOut(defaultHTMLExtensions, settings["html/xml-extensions"])
  
    const xmlExtensions = settings["html/xml-extensions"] ||
        filterOut(defaultXMLExtensions, settings["html/html-extensions"])
  
    let reportBadIndent
    switch (settings["html/report-bad-indent"]) {
      case undefined: case false: case 0: case "off":
        reportBadIndent = 0
        break
      case true: case 1: case "warn":
        reportBadIndent = 1
        break
      case 2: case "error":
        reportBadIndent = 2
        break
      default:
        throw new Error("Invalid value for html/report-bad-indent, " +
          "expected one of 0, 1, 2, \"off\", \"warn\" or \"error\"")
    }
  
    const parsedIndent = /^(\+)?(tab|\d+)$/.exec(settings["html/indent"])
    const indent = parsedIndent && {
      relative: parsedIndent[1] === "+",
      spaces: parsedIndent[2] === "tab" ? "\t" : " ".repeat(parsedIndent[2]),
    }
  
    const javaScriptMIMETypes = settings["html/javascript-mime-types"] ?
      (
        Array.isArray(settings["html/javascript-mime-types"]) ?
          settings["html/javascript-mime-types"] :
          [settings["html/javascript-mime-types"]]
      ).map((s) => s.startsWith("/") ? compileRegExp(s) : s) :
      [/^(application|text)\/(x-)?(javascript|babel|ecmascript-6)$/i]
  
    function isJavaScriptMIMEType(type) {
      return javaScriptMIMETypes.some((o) => typeof o === "string" ? type === o : o.test(type))
    }
  
    return {
      htmlExtensions,
      xmlExtensions,
      indent,
      reportBadIndent,
      isJavaScriptMIMEType,
    }
  }
  
  function patch(modules) {
    const eslint = modules.eslint
    const SourceCodeFixer = modules.SourceCodeFixer
  
    const sourceCodeForMessages = new WeakMap()
  
    const verify = eslint.verify
    eslint.verify = function (textOrSourceCode, config, filenameOrOptions, saveState) {
      const localVerify = (code) => verify.call(this, code, config, filenameOrOptions, saveState)
  
      let messages
      const filename = typeof filenameOrOptions === "object" ?
        filenameOrOptions.filename :
        filenameOrOptions
      const extension = path.extname(filename)
  
      const pluginSettings = getPluginSettings(config.settings || {})
      const isHTML = pluginSettings.htmlExtensions.indexOf(extension) >= 0
      const isXML = !isHTML && pluginSettings.xmlExtensions.indexOf(extension) >= 0
  
      if (typeof textOrSourceCode === "string" && (isHTML || isXML)) {
        const currentInfos = extract(
          textOrSourceCode,
          pluginSettings.indent,
          isXML,
          pluginSettings.isJavaScriptMIMEType
        )
  
        messages = remapMessages(
          localVerify(String(currentInfos.code)),
          currentInfos.code,
          pluginSettings.reportBadIndent,
          currentInfos.badIndentationLines
        )
        sourceCodeForMessages.set(messages, textOrSourceCode)
      }
      else {
        messages = localVerify(textOrSourceCode)
      }
  
      return messages
    }
  
    const applyFixes = SourceCodeFixer.applyFixes
    SourceCodeFixer.applyFixes = function (sourceCode, messages) {
      const originalSourceCode = sourceCodeForMessages.get(messages)
      // The BOM is always included in the HTML, which is removed by the extract process
      return applyFixes.call(
        this,
        originalSourceCode === undefined ? sourceCode : { text: originalSourceCode, hasBOM: false },
        messages
      )
    }
  
  }
  
  function remapMessages(messages, code, reportBadIndent, badIndentationLines) {
    const newMessages = []
  
    for (const message of messages) {
      const location = code.originalLocation(message)
  
      // Ignore messages if they were in transformed code
      if (location) {
        Object.assign(message, location)
  
        // Map fix range
        if (message.fix && message.fix.range) {
          message.fix.range = [
            code.originalIndex(message.fix.range[0]),
            code.originalIndex(message.fix.range[1]),
          ]
        }
  
        // Map end location
        if (message.endLine && message.endColumn) {
          const endLocation = code.originalLocation({
            line: message.endLine,
            column: message.endColumn,
          })
          if (endLocation) {
            message.endLine = endLocation.line
            message.endColumn = endLocation.column
          }
        }
  
        newMessages.push(message)
      }
    }
  
    if (reportBadIndent) {
      badIndentationLines.forEach((line) => {
        newMessages.push({
          message: "Bad line indentation.",
          line,
          column: 1,
          ruleId: "(html plugin)",
          severity: reportBadIndent === true ? 2 : reportBadIndent,
        })
      })
    }
  
    newMessages.sort((ma, mb) => {
      return ma.line - mb.line || ma.column - mb.column
    })
  
    return newMessages
  }
  