diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 7053dc1..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -/coverage/ diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 04b2d68..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - extends: ["eslint:recommended", "prettier"], - - rules: { - "no-constant-condition": "off", - "no-unused-vars": [ - "error", - { varsIgnorePattern: "^_", argsIgnorePattern: "^_" }, - ], - }, - - env: { - node: true, - es2018: true, - }, -} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d68bcf4..d45c89a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,9 @@ jobs: - run: npm run lint - run: npm run format - run: node tools/toc.js check - - run: npx markdown-link-check -p README.md + # markdown-link-check disabled until https://github.com/tcort/markdown-link-check/issues/304 + # is fixed + # - run: npx markdown-link-check -p README.md integration: runs-on: ${{ matrix.os }} @@ -48,7 +50,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [16, 18, 20] + node: [18, 20] eslint: [latest] include: # ESLint 4.7 support: lowest version supported @@ -66,6 +68,12 @@ jobs: node: 20 eslint: next + # Node 16 support: use ESLint v8 becacuse they dropped node 16 support in v9. Will be + # dropped in next major. + - os: ubuntu-latest + node: 16 + eslint: "8" + # Windows support - os: windows-latest node: 20 diff --git a/CHANGELOG.md b/CHANGELOG.md index 801a008..2443ac1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +2024-04-10 v8.1.0 + +- Introduce the `html/ignore-tags-without-type` setting #249 +- Fix files with unknown extensions being considered as XML #257 +- Fix plugin applied even if it's not in the configuration #263 +- Update dependencies + 2024-02-09 v8.0.0 - **Breaking: drop Node 12 and 14 support** diff --git a/README.md b/README.md index c9acdeb..6a96b00 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

eslint-plugin-html

- NPM version - Tests Status + NPM version + Tests Status

A ESLint plugin to lint and fix inline scripts contained in HTML files.

@@ -19,6 +19,7 @@ - [`html/report-bad-indent`](#htmlreport-bad-indent) - [`html/javascript-tag-names`](#htmljavascript-tag-names) - [`html/javascript-mime-types`](#htmljavascript-mime-types) + - [`html/ignore-tags-without-type`](#htmlignore-tags-without-type) - [Troubleshooting](#troubleshooting) - [No file linted when running `eslint` on a directory](#no-file-linted-when-running-eslint-on-a-directory) - [Linting templates (or PHP)](#linting-templates-or-php) @@ -232,6 +233,21 @@ If a MIME type starts with a `/`, it will be considered as a regular expression. } ``` +### `html/ignore-tags-without-type` + +By default, the code between `", + ignoreTagsWithoutType: true, + }) +}) + it("extract multiple tags types", () => { test({ input: ` diff --git a/src/__tests__/fixtures/javascript.js b/src/__tests__/fixtures/javascript.js new file mode 100644 index 0000000..4ccc01b --- /dev/null +++ b/src/__tests__/fixtures/javascript.js @@ -0,0 +1 @@ +console.log("aaa") diff --git a/src/__tests__/plugin.js b/src/__tests__/plugin.js index a08d5e1..eb37d2b 100644 --- a/src/__tests__/plugin.js +++ b/src/__tests__/plugin.js @@ -4,7 +4,7 @@ const path = require("path") const eslint = require("eslint") const semver = require("semver") const eslintVersion = require("eslint/package.json").version -require("..") +const eslintPluginHtml = require("..") function matchVersion(versionSpec) { return semver.satisfies(eslintVersion, versionSpec, { @@ -23,11 +23,14 @@ async function execute(file, options = {}) { let eslintOptions if (matchVersion(">= 9")) { eslintOptions = { - plugins: { - html: require(".."), - }, + plugins: + options.usePlugin === false + ? {} + : { + html: eslintPluginHtml, + }, baseConfig: { - files: ["**/*.html", "**/*.xhtml"], + files: ["**/*.*"], settings: options.settings || {}, rules: Object.assign( { @@ -148,6 +151,23 @@ it("should extract and remap messages", async () => { } }) +ifVersion( + ">= 9", + it, + "does not apply the plugin if it is not used in the configuration", + async () => { + const messages = await execute("simple.html", { + usePlugin: false, + rules: { + "no-console": "error", + }, + }) + + expect(messages.length).toBe(1) + expect(messages[0].message).toBe("Parsing error: Unexpected token <") + } +) + it("should report correct line numbers with crlf newlines", async () => { const messages = await execute("crlf-newlines.html") @@ -420,6 +440,16 @@ describe("xml support", () => { expect(messages[0].column).toBe(7) }) + it("consider .js files as JS", async () => { + const messages = await execute("javascript.js") + + expect(messages.length).toBe(1) + + expect(messages[0].message).toBe("Unexpected console statement.") + expect(messages[0].line).toBe(1) + expect(messages[0].column).toBe(1) + }) + it("can be forced to consider .html files as XML", async () => { const messages = await execute("cdata.html", { settings: { @@ -601,6 +631,18 @@ ifVersion(">= 4.8.0", describe, "reportUnusedDisableDirectives", () => { }) }) +describe("html/ignore-tags-without-type", () => { + it("ignores tags without type attribute", async () => { + const messages = await execute("javascript-mime-types.html", { + settings: { + "html/ignore-tags-without-type": true, + }, + }) + + expect(messages.length).toBe(2) + }) +}) + describe("html/javascript-mime-types", () => { it("ignores unknown mime types by default", async () => { const messages = await execute("javascript-mime-types.html") diff --git a/src/extract.js b/src/extract.js index abb0ab7..880013f 100644 --- a/src/extract.js +++ b/src/extract.js @@ -7,12 +7,12 @@ const NO_IGNORE = 0 const IGNORE_NEXT = 1 const IGNORE_UNTIL_ENABLE = 2 -function iterateScripts(code, options, onChunk) { +function iterateScripts(code, xmlMode, options, onChunk) { if (!code) return - const xmlMode = options.xmlMode const isJavaScriptMIMEType = options.isJavaScriptMIMEType || (() => true) const javaScriptTagNames = options.javaScriptTagNames || ["script"] + const ignoreTagsWithoutType = options.ignoreTagsWithoutType || false let index = 0 let inScript = false let cdata = [] @@ -33,7 +33,11 @@ function iterateScripts(code, options, onChunk) { return } - if (attrs.type && !isJavaScriptMIMEType(attrs.type)) { + if (attrs.type) { + if (!isJavaScriptMIMEType(attrs.type)) { + return + } + } else if (ignoreTagsWithoutType) { return } @@ -205,57 +209,47 @@ function* dedent(indent, slice) { } } -function extract( - code, - indentDescriptor, - xmlMode, - javaScriptTagNames, - isJavaScriptMIMEType -) { +function extract(code, xmlMode, options) { const badIndentationLines = [] const codeParts = [] let lineNumber = 1 let previousHTML = "" - iterateScripts( - code, - { xmlMode, javaScriptTagNames, isJavaScriptMIMEType }, - (chunk) => { - const slice = code.slice(chunk.start, chunk.end) - if (chunk.type === "html") { - const match = slice.match(/\r\n|\n|\r/g) - if (match) lineNumber += match.length - previousHTML = slice - } else if (chunk.type === "script") { - const transformedCode = new TransformableString(code) - let indentSlice = slice - for (const cdata of chunk.cdata) { - transformedCode.replace(cdata.start, cdata.end, "") - if (cdata.end === chunk.end) { - indentSlice = code.slice(chunk.start, cdata.start) - } + iterateScripts(code, xmlMode, options, (chunk) => { + const slice = code.slice(chunk.start, chunk.end) + if (chunk.type === "html") { + const match = slice.match(/\r\n|\n|\r/g) + if (match) lineNumber += match.length + previousHTML = slice + } else if (chunk.type === "script") { + const transformedCode = new TransformableString(code) + let indentSlice = slice + for (const cdata of chunk.cdata) { + transformedCode.replace(cdata.start, cdata.end, "") + if (cdata.end === chunk.end) { + indentSlice = code.slice(chunk.start, cdata.start) } - transformedCode.replace(0, chunk.start, "") - transformedCode.replace(chunk.end, code.length, "") - for (const action of dedent( - computeIndent(indentDescriptor, previousHTML, indentSlice), - indentSlice - )) { - lineNumber += 1 - if (action.type === "dedent") { - transformedCode.replace( - chunk.start + action.from, - chunk.start + action.to, - "" - ) - } else if (action.type === "bad-indent") { - badIndentationLines.push(lineNumber) - } + } + transformedCode.replace(0, chunk.start, "") + transformedCode.replace(chunk.end, code.length, "") + for (const action of dedent( + computeIndent(options.indent, previousHTML, indentSlice), + indentSlice + )) { + lineNumber += 1 + if (action.type === "dedent") { + transformedCode.replace( + chunk.start + action.from, + chunk.start + action.to, + "" + ) + } else if (action.type === "bad-indent") { + badIndentationLines.push(lineNumber) } - codeParts.push(transformedCode) } + codeParts.push(transformedCode) } - ) + }) return { code: codeParts, diff --git a/src/index.js b/src/index.js index 762fa13..b110a30 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,9 @@ const { createVerifyPatch } = require("./verifyPatch") const { createVerifyWithFlatConfigPatch, } = require("./verifyWithFlatConfigPatch") +const pluginReference = require("./pluginReference") + +module.exports = pluginReference const LINTER_ISPATCHED_PROPERTY_NAME = "__eslint-plugin-html-verify-function-is-patched" @@ -72,12 +75,12 @@ function iterateESLintModules(fn) { let eslintPath, eslintVersion try { eslintPath = require.resolve("eslint") - } catch (e) { + } catch { eslintPath = "(not found)" } try { eslintVersion = require("eslint/package.json").version - } catch (e) { + } catch { eslintVersion = "n/a" } diff --git a/src/pluginReference.js b/src/pluginReference.js new file mode 100644 index 0000000..4ba52ba --- /dev/null +++ b/src/pluginReference.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/src/settings.js b/src/settings.js index 73fc9ea..f4c633b 100644 --- a/src/settings.js +++ b/src/settings.js @@ -54,6 +54,9 @@ function getSettings(settings) { "script", ] + const ignoreTagsWithoutType = + getSetting(settings, "ignore-tags-without-type") || false + let reportBadIndent switch (getSetting(settings, "report-bad-indent")) { case undefined: @@ -110,6 +113,7 @@ function getSettings(settings) { indent, reportBadIndent, isJavaScriptMIMEType, + ignoreTagsWithoutType, } } diff --git a/src/verifyPatch.js b/src/verifyPatch.js index 008a1d7..5abbf61 100644 --- a/src/verifyPatch.js +++ b/src/verifyPatch.js @@ -43,10 +43,8 @@ function createVerifyPatch(verify) { const extractResult = extract( textOrSourceCode, - pluginSettings.indent, mode === "xml", - pluginSettings.javaScriptTagNames, - pluginSettings.isJavaScriptMIMEType + pluginSettings ) if (pluginSettings.reportBadIndent) { diff --git a/src/verifyWithFlatConfigPatch.js b/src/verifyWithFlatConfigPatch.js index 12b8f9a..3219be1 100644 --- a/src/verifyWithFlatConfigPatch.js +++ b/src/verifyWithFlatConfigPatch.js @@ -3,6 +3,7 @@ const getFileMode = require("./getFileMode") const extract = require("./extract") const { verifyWithSharedScopes } = require("./verifyWithSharedScopes") const { remapMessages } = require("./remapMessages") +const pluginReference = require("./pluginReference") const PREPARE_RULE_NAME = "__eslint-plugin-html-prepare" const PREPARE_PLUGIN_NAME = "__eslint-plugin-html-prepare" @@ -19,9 +20,17 @@ function createVerifyWithFlatConfigPatch(verifyWithFlatConfig) { providedOptions ) + if (!Object.values(providedConfig.plugins).includes(pluginReference)) { + return callOriginalVerify() + } + const pluginSettings = getSettings(providedConfig.settings || {}) const mode = getFileMode(pluginSettings, providedOptions.filename) + if (!mode) { + return callOriginalVerify() + } + let messages ;[messages, providedConfig] = verifyExternalHtmlPlugin( providedConfig, @@ -30,10 +39,8 @@ function createVerifyWithFlatConfigPatch(verifyWithFlatConfig) { const extractResult = extract( textOrSourceCode, - pluginSettings.indent, mode === "xml", - pluginSettings.javaScriptTagNames, - pluginSettings.isJavaScriptMIMEType + pluginSettings ) if (pluginSettings.reportBadIndent) { @@ -113,7 +120,7 @@ const externalHtmlPlugins = [ function tryRequire(name) { try { return require(name) - } catch (e) { + } catch { return undefined } } diff --git a/tools/release.js b/tools/release.js index 65aa38e..2711ae9 100644 --- a/tools/release.js +++ b/tools/release.js @@ -2,6 +2,7 @@ const { execSync: exec } = require("child_process") const { readFileSync: read } = require("fs") const { request } = require("https") +const { version: currentVersion } = require("../package.json") const REPO = "BenoitZugmeyer/eslint-plugin-html" const PACKAGE_FILES = [ @@ -9,6 +10,7 @@ const PACKAGE_FILES = [ "src/extract.js", "src/getFileMode.js", "src/index.js", + "src/pluginReference.js", "src/settings.js", "src/remapMessages.js", "src/verifyPatch.js", @@ -115,6 +117,11 @@ function verifyPackageContent() { } function createVersion(version) { + if (version === currentVersion) { + console.log(`Version ${version} already exists, skipping creation`) + return + } + console.log(`Creating version ${version}`) exec(`npm version ${version}`, { @@ -144,7 +151,7 @@ async function verifyBuild() { console.log(`Workflow run ${run.html_url} ${run.status}`) } } - await new Promise((resolve) => setTimeout(resolve, 3000)) + await new Promise((resolve) => setTimeout(resolve, 30_000)) } } @@ -160,11 +167,21 @@ function fetchWorkflowRuns() { ) req.on("error", reject) req.on("response", (response) => { + // `${response.headers['x-ratelimit-used']}/${response.headers['x-ratelimit-limit']} (reset: ${new Date(response.headers['x-ratelimit-reset'] * 1000).toLocaleTimeString()})` const datum = [] response.on("error", reject) response.on("data", (data) => datum.push(data)) response.on("end", () => { - resolve(JSON.parse(Buffer.concat(datum))) + const body = JSON.parse(Buffer.concat(datum)) + if (response.statusCode !== 200) { + reject( + new Error( + `HTTP ${response.statusCode}: ${body.message || response.statusMessage}` + ) + ) + } else { + resolve(body) + } }) }) req.end()