"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.0.0-alpha.0")) { 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 so there were // less errors. expect(messages.length).toBe(3) // 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 0 .* but found 6\./) expect(messages[1].line).toBe(22) expect(messages[2].message).toMatch(/Expected indentation of 0 .* but found 10\./) expect(messages[2].line).toBe(28) } }) it("should work with a non-zero absolute indentation descriptor", () => { const messages = execute("indent-setting.html", { rules: { indent: [2, 2], }, settings: { "html/indent": 2, }, }) 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) }) it("should work with relative indentation descriptor", () => { const messages = execute("indent-setting.html", { rules: { indent: [2, 2], }, settings: { "html/indent": "+2", }, }) if (isESLintVersion(">= 4.0.0-alpha.0")) { 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 { expect(messages.length).toBe(4) // 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) // The third script is correct. expect(messages[1].message).toMatch(/Expected indentation of 0 .* but found 10\./) expect(messages[1].line).toBe(28) expect(messages[2].message).toMatch(/Expected indentation of 12 .* but found 4\./) expect(messages[2].line).toBe(29) expect(messages[3].message).toMatch(/Expected indentation of 10 .* but found 2\./) expect(messages[3].line).toBe(30) } }) }) 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 || >= 4.0.0-alpha.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(` `) 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 `) 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(` `) 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(` `) 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) }) })