text: "use strict"
  
  const path = require("path")
  const semver = require("semver")
  const CLIEngine = require("eslint").CLIEngine
  const plugin = require("..")
  
  const eslintVersion = require("eslint/package.json").version
  
  function isESLintVersion(descriptor) {
    return semver.satisfies(eslintVersion, descriptor)
  }
  
  function execute(file, baseConfig) {
    if (!baseConfig) baseConfig = {}
  
    const cli = new CLIEngine({
      extensions: ["html"],
      baseConfig: {
        settings: baseConfig.settings,
        rules: Object.assign(
          {
            "no-console": 2,
          },
          baseConfig.rules
        ),
      },
      ignore: false,
      useEslintrc: false,
      fix: baseConfig.fix,
    })
    cli.addPlugin("html", plugin)
    const results = cli.executeOnFiles([path.join(__dirname, "fixtures", file)])
      .results[0]
    return baseConfig.fix ? results : results && results.messages
  }
  
  it("should extract and remap messages", () => {
    const messages = execute("simple.html")
  
    expect(messages.length, 5)
  
    const hasEndPosition = messages[0].endLine !== undefined
  
    expect(messages[0].message).toBe("Unexpected console statement.")
    expect(messages[0].line).toBe(8)
    expect(messages[0].column).toBe(7)
    if (hasEndPosition) {
      expect(messages[0].endLine).toBe(8)
      expect(messages[0].endColumn).toBe(18)
    }
  
    expect(messages[1].message).toBe("Unexpected console statement.")
    expect(messages[1].line).toBe(14)
    expect(messages[1].column).toBe(7)
    if (hasEndPosition) {
      expect(messages[1].endLine).toBe(14)
      expect(messages[1].endColumn).toBe(18)
    }
  
    expect(messages[2].message).toBe("Unexpected console statement.")
    expect(messages[2].line).toBe(20)
    expect(messages[2].column).toBe(3)
    if (hasEndPosition) {
      expect(messages[2].endLine).toBe(20)
      expect(messages[2].endColumn).toBe(14)
    }
  
    expect(messages[3].message).toBe("Unexpected console statement.")
    expect(messages[3].line).toBe(25)
    expect(messages[3].column).toBe(11)
    if (hasEndPosition) {
      expect(messages[3].endLine).toBe(25)
      expect(messages[3].endColumn).toBe(22)
    }
  
    expect(messages[4].message).toBe("Unexpected console statement.")
    expect(messages[4].line).toBe(28)
    expect(messages[4].column).toBe(13)
    if (hasEndPosition) {
      expect(messages[4].endLine).toBe(28)
      expect(messages[4].endColumn).toBe(24)
    }
  })
  
  it("should report correct line numbers with crlf newlines", () => {
    const messages = execute("crlf-newlines.html")
  
    expect(messages.length).toBe(1)
  
    expect(messages[0].message).toBe("Unexpected console statement.")
    expect(messages[0].line).toBe(8)
    expect(messages[0].column).toBe(7)
  })
  
  describe("html/indent setting", () => {
    it("should automatically compute indent when nothing is specified", () => {
      const messages = execute("indent-setting.html", {
        rules: {
          indent: [2, 2],
        },
      })
  
      expect(messages.length).toBe(0)
    })
  
    it("should work with a zero absolute indentation descriptor", () => {
      const messages = execute("indent-setting.html", {
        rules: {
          indent: [2, 2],
        },
  
        settings: {
          "html/indent": 0,
        },
      })
  
      if (isESLintVersion(">= 4")) {
        expect(messages.length).toBe(9)
  
        // Only the first script is correctly indented (aligned on the first column)
  
        expect(messages[0].message).toMatch(
          /Expected indentation of 0 .* but found 2\./
        )
        expect(messages[0].line).toBe(16)
  
        expect(messages[1].message).toMatch(
          /Expected indentation of 2 .* but found 4\./
        )
        expect(messages[1].line).toBe(17)
  
        expect(messages[2].message).toMatch(
          /Expected indentation of 0 .* but found 2\./
        )
        expect(messages[2].line).toBe(18)
  
        expect(messages[3].message).toMatch(
          /Expected indentation of 0 .* but found 6\./
        )
        expect(messages[3].line).toBe(22)
  
        expect(messages[4].message).toMatch(
          /Expected indentation of 2 .* but found 8\./
        )
        expect(messages[4].line).toBe(23)
  
        expect(messages[5].message).toMatch(
          /Expected indentation of 0 .* but found 6\./
        )
        expect(messages[5].line).toBe(24)
  
        expect(messages[6].message).toMatch(
          /Expected indentation of 0 .* but found 10\./
        )
        expect(messages[6].line).toBe(28)
  
        expect(messages[7].message).toMatch(
          /Expected indentation of 2 .* but found 12\./
        )
        expect(messages[7].line).toBe(29)
  
        expect(messages[8].message).toMatch(
          /Expected indentation of 0 .* but found 10\./
        )
        expect(messages[8].line).toBe(30)
      }
      else {
        // ESlint < 4 indentation was always checked relatively to the previous line. Since every line
        // is correctly indented relatively to the previous one, there is no error.
  
        expect(messages.length).toBe(0)
      }
    })
  
    it("should work with a non-zero absolute indentation descriptor", () => {
      const messages = execute("indent-setting.html", {
        rules: {
          indent: [2, 2],
        },
  
        settings: {
          "html/indent": 2,
        },
      })
  
      if (isESLintVersion(">= 4")) {
        expect(messages.length).toBe(7)
  
        // The first script is incorrect since the second line gets dedented
        expect(messages[0].message).toMatch(
          /Expected indentation of 2 .* but found 0\./
        )
        expect(messages[0].line).toBe(11)
  
        // The second script is correct.
  
        expect(messages[1].message).toMatch(
          /Expected indentation of 0 .* but found 6\./
        )
        expect(messages[1].line).toBe(22)
  
        expect(messages[2].message).toMatch(
          /Expected indentation of .* but found 6\./
        )
        expect(messages[2].line).toBe(23)
  
        expect(messages[3].message).toMatch(
          /Expected indentation of .* but found 4\./
        )
        expect(messages[3].line).toBe(24)
  
        expect(messages[4].message).toMatch(
          /Expected indentation of 0 .* but found 10\./
        )
        expect(messages[4].line).toBe(28)
  
        expect(messages[5].message).toMatch(
          /Expected indentation of .* but found 10\./
        )
        expect(messages[5].line).toBe(29)
  
        expect(messages[6].message).toMatch(
          /Expected indentation of .* but found 8\./
        )
        expect(messages[6].line).toBe(30)
      }
      else {
        // ESlint < 4 indentation was always checked relatively to the previous line. Since some lines
        // are correctly indented relatively to the previous one, there is less errors.
  
        expect(messages.length).toBe(5)
  
        // The first script is incorrect since the second line gets dedented
        expect(messages[0].message).toMatch(
          /Expected indentation of 2 .* but found 0\./
        )
        expect(messages[0].line).toBe(11)
  
        // The second script is correct.
  
        expect(messages[1].message).toMatch(
          /Expected indentation of .* but found 6\./
        )
        expect(messages[1].line).toBe(23)
  
        expect(messages[2].message).toMatch(
          /Expected indentation of .* but found 4\./
        )
        expect(messages[2].line).toBe(24)
  
        expect(messages[3].message).toMatch(
          /Expected indentation of .* but found 10\./
        )
        expect(messages[3].line).toBe(29)
  
        expect(messages[4].message).toMatch(
          /Expected indentation of .* but found 8\./
        )
        expect(messages[4].line).toBe(30)
      }
    })
  
    it("should work with relative indentation descriptor", () => {
      const messages = execute("indent-setting.html", {
        rules: {
          indent: [2, 2],
        },
  
        settings: {
          "html/indent": "+2",
        },
      })
  
      if (isESLintVersion(">= 4")) {
        expect(messages.length).toBe(6)
  
        // The first script is correct since it can't be dedented, but follows the indent
        // rule anyway.
  
        expect(messages[0].message).toMatch(
          /Expected indentation of 0 .* but found 2\./
        )
        expect(messages[0].line).toBe(16)
  
        expect(messages[1].message).toMatch(
          /Expected indentation of 2 .* but found 4\./
        )
        expect(messages[1].line).toBe(17)
  
        expect(messages[2].message).toMatch(
          /Expected indentation of 0 .* but found 2\./
        )
        expect(messages[2].line).toBe(18)
  
        // The third script is correct.
  
        expect(messages[3].message).toMatch(
          /Expected indentation of 0 .* but found 10\./
        )
        expect(messages[3].line).toBe(28)
  
        expect(messages[4].message).toMatch(
          /Expected indentation of 2 .* but found 4\./
        )
        expect(messages[4].line).toBe(29)
  
        expect(messages[5].message).toMatch(
          /Expected indentation of 0 .* but found 2\./
        )
        expect(messages[5].line).toBe(30)
      }
      else {
        // ESlint < 4 indentation was always checked relatively to the previous line. Since some lines
        // are correctly indented relatively to the previous one, there is less errors.
  
        expect(messages.length).toBe(2)
  
        expect(messages[0].message).toMatch(
          /Expected indentation of 12 .* but found 4\./
        )
        expect(messages[0].line).toBe(29)
  
        expect(messages[1].message).toMatch(
          /Expected indentation of 10 .* but found 2\./
        )
        expect(messages[1].line).toBe(30)
      }
    })
  
    it("should report messages at the beginning of the file", () => {
      const messages = execute("error-at-the-beginning.html", {
        rules: {
          "max-lines": [2, { max: 1 }],
          "max-len": [2, { code: 35 }],
          "no-console": 0,
        },
      })
  
      expect(messages.length).toBe(2)
      expect(messages[0].message).toBe(
        "Line 1 exceeds the maximum line length of 35."
      )
      expect(messages[0].line).toBe(1)
      expect(messages[0].column).toBe(9)
  
      if (isESLintVersion(">= 3")) {
        expect(messages[1].message).toBe(
          "File must be at most 1 lines long. It's 7 lines long."
        )
      }
      else {
        expect(messages[1].message).toBe("File must be at most 1 lines long")
      }
      expect(messages[1].line).toBe(1)
      expect(messages[1].column).toBe(9)
    })
  })
  
  describe("html/report-bad-indent setting", () => {
    it("should report under-indented code with auto indent setting", () => {
      const messages = execute("report-bad-indent-setting.html", {
        settings: {
          "html/report-bad-indent": true,
        },
      })
  
      expect(messages.length).toBe(1)
  
      expect(messages[0].message).toBe("Bad line indentation.")
      expect(messages[0].line).toBe(10)
      expect(messages[0].column).toBe(1)
    })
  
    it("should report under-indented code with provided indent setting", () => {
      const messages = execute("report-bad-indent-setting.html", {
        settings: {
          "html/report-bad-indent": true,
          "html/indent": "+4",
        },
      })
  
      expect(messages.length).toBe(3)
  
      expect(messages[0].message).toBe("Bad line indentation.")
      expect(messages[0].line).toBe(9)
      expect(messages[0].column).toBe(1)
  
      expect(messages[1].message).toBe("Bad line indentation.")
      expect(messages[1].line).toBe(10)
      expect(messages[1].column).toBe(1)
  
      expect(messages[2].message).toBe("Bad line indentation.")
      expect(messages[2].line).toBe(11)
      expect(messages[2].column).toBe(1)
    })
  })
  
  describe("xml support", () => {
    it("consider .html files as HTML", () => {
      const messages = execute("cdata.html")
  
      expect(messages.length).toBe(1)
  
      expect(messages[0].message).toBe("Parsing error: Unexpected token <")
      expect(messages[0].fatal).toBe(true)
      expect(messages[0].line).toBe(10)
      expect(messages[0].column).toBe(7)
    })
  
    it("can be forced to consider .html files as XML", () => {
      const messages = execute("cdata.html", {
        settings: {
          "html/xml-extensions": [".html"],
        },
      })
  
      expect(messages.length).toBe(1)
  
      expect(messages[0].message).toBe("Unexpected console statement.")
      expect(messages[0].line).toBe(11)
      expect(messages[0].column).toBe(9)
    })
  
    it("consider .xhtml files as XML", () => {
      const messages = execute("cdata.xhtml")
  
      expect(messages.length).toBe(1)
  
      expect(messages[0].message).toBe("Unexpected console statement.")
      expect(messages[0].line).toBe(13)
      expect(messages[0].column).toBe(9)
    })
  
    it("can be forced to consider .xhtml files as HTML", () => {
      const messages = execute("cdata.xhtml", {
        settings: {
          "html/html-extensions": [".xhtml"],
        },
      })
  
      expect(messages.length).toBe(1)
  
      expect(messages[0].message).toBe("Parsing error: Unexpected token <")
      expect(messages[0].fatal).toBe(true)
      expect(messages[0].line).toBe(12)
      expect(messages[0].column).toBe(7)
    })
  
    it("should support self closing script tags", () => {
      let messages
      expect(() => {
        messages = execute("self-closing-tags.xhtml")
      }).not.toThrow()
      expect(messages.length).toBe(0)
    })
  })
  
  describe("lines-around-comment and multiple scripts", () => {
    it("should not warn with lines-around-comment if multiple scripts", () => {
      const messages = execute("simple.html", {
        rules: {
          "lines-around-comment": ["error", { beforeLineComment: true }],
        },
      })
  
      expect(messages.length).toBe(5)
    })
  })
  
  describe("fix", () => {
    it("should remap fix ranges", () => {
      const messages = execute("fix.html", {
        rules: {
          "no-extra-semi": ["error"],
        },
      })
  
      if (isESLintVersion(">= 3.17.0")) {
        // Since v3.17.0, no-extra-semi replaces all semicolons by a single semi colon instead of
        // removing extra semi colons. See https://github.com/eslint/eslint/pull/8067 .
        expect(messages[0].fix.range).toEqual([53, 55])
      }
      else {
        expect(messages[0].fix.range).toEqual([54, 55])
      }
    })
  
    it("should fix errors", () => {
      const result = execute("fix.html", {
        rules: {
          "no-extra-semi": ["error"],
        },
        fix: true,
      })
  
      expect(result.output).toBe(`<!DOCTYPE html>
  <html lang="en">
    <script>
      foo();
    </script>
  </html>
  `)
      expect(result.messages.length).toBe(0)
    })
  
    it("should fix errors in files with BOM", () => {
      const result = execute("fix-bom.html", {
        rules: {
          "no-extra-semi": ["error"],
        },
        fix: true,
      })
  
      expect(result.output).toBe(`\uFEFF<!DOCTYPE html>
  <html lang="en">
    <script>
      foo();
    </script>
  </html>
  `)
      expect(result.messages.length).toBe(0)
    })
  
    describe("eol-last rule", () => {
      it("should work with eol-last always", () => {
        const result = execute("fix.html", {
          rules: {
            "eol-last": ["error"],
            "no-extra-semi": ["error"],
          },
          fix: true,
        })
  
        expect(result.output).toBe(`<!DOCTYPE html>
  <html lang="en">
    <script>
      foo();
    </script>
  </html>
  `)
        expect(result.messages.length).toBe(0)
      })
  
      it("should work with eol-last never", () => {
        // ESLint 2 did not remove the last new line if any
        if (isESLintVersion("2")) return
        const result = execute("fix.html", {
          rules: {
            "eol-last": ["error", "never"],
          },
          fix: true,
        })
  
        expect(result.output).toBe(`<!DOCTYPE html>
  <html lang="en">
    <script>
      foo();;  </script>
  </html>
  `)
        expect(result.messages.length).toBe(0)
      })
    })
  })
  
  describe("html/javascript-mime-types", () => {
    it("ignores unknown mime types by default", () => {
      const messages = execute("javascript-mime-types.html")
  
      expect(messages.length).toBe(2)
  
      expect(messages[0].ruleId).toBe("no-console")
      expect(messages[0].line).toBe(8)
  
      expect(messages[1].ruleId).toBe("no-console")
      expect(messages[1].line).toBe(12)
    })
  
    it("specifies a list of valid mime types", () => {
      const messages = execute("javascript-mime-types.html", {
        settings: {
          "html/javascript-mime-types": ["text/foo"],
        },
      })
  
      expect(messages.length, 2)
  
      expect(messages[0].ruleId).toBe("no-console")
      expect(messages[0].line).toBe(8)
  
      expect(messages[1].ruleId).toBe("no-console")
      expect(messages[1].line).toBe(16)
    })
  
    it("specifies a regexp of valid mime types", () => {
      const messages = execute("javascript-mime-types.html", {
        settings: {
          "html/javascript-mime-types": "/^(application|text)/foo$/",
        },
      })
  
      expect(messages.length).toBe(3)
  
      expect(messages[0].ruleId).toBe("no-console")
      expect(messages[0].line).toBe(8)
  
      expect(messages[1].ruleId).toBe("no-console")
      expect(messages[1].line).toBe(16)
  
      expect(messages[2].ruleId).toBe("no-console")
      expect(messages[2].line).toBe(20)
    })
  })
  