From e0da2214cc06fd441def1844135f3c9fef74e940 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Tue, 23 Jul 2024 05:00:44 -0400 Subject: [PATCH] feat!: Convert to ESM (#259) * feat!: Switch to ESM fixes #218 * Clean up * Update docs * Fix lint errors * Update package.json Co-authored-by: Milos Djermanovic * Update package.json Co-authored-by: Milos Djermanovic * Fix extra-files names * Update package.json Co-authored-by: Milos Djermanovic * Apply feedback * Remove ESLint 8 from CI --------- Co-authored-by: Milos Djermanovic --- .github/workflows/ci.yml | 2 +- .github/workflows/release-please.yml | 5 +- README.md | 61 ------------------- eslint.config.js | 24 ++++---- .../{eslint.config.js => eslint.config.mjs} | 12 ++-- .../{eslint.config.js => eslint.config.mjs} | 10 ++- index.js | 8 --- npm-prepare.js => npm-prepare.cjs | 0 package.json | 31 ++++++---- release-please-config.json | 4 +- {lib => src}/index.js | 6 +- {lib => src}/processor.js | 8 +-- tests/examples/{all.js => all.test.js} | 17 +++--- tests/fixtures/eslint.config.js | 6 +- tests/fixtures/recommended.js | 6 +- tests/{lib/plugin.js => plugin.test.js} | 41 ++++++++----- tests/{lib/processor.js => processor.test.js} | 25 ++++++-- 17 files changed, 115 insertions(+), 151 deletions(-) rename examples/react/{eslint.config.js => eslint.config.mjs} (76%) rename examples/typescript/{eslint.config.js => eslint.config.mjs} (62%) delete mode 100644 index.js rename npm-prepare.js => npm-prepare.cjs (100%) rename {lib => src}/index.js (97%) rename {lib => src}/processor.js (99%) rename tests/examples/{all.js => all.test.js} (80%) rename tests/{lib/plugin.js => plugin.test.js} (98%) rename tests/{lib/processor.js => processor.test.js} (96%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3104e5ef..2545cdf9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - eslint: [9, 8] + eslint: [9] node: [22.x, 21.x, 20.x, 18.x, "18.18.0"] include: - os: windows-latest diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 6bc1f651..b857d80d 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -20,7 +20,10 @@ jobs: node-version: lts/* registry-url: https://registry.npmjs.org if: ${{ steps.release.outputs.release_created }} - - run: npm publish --provenance + - run: | + npm install + npm run build --if-present + npm publish --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} if: ${{ steps.release.outputs.release_created }} diff --git a/README.md b/README.md index bd2c497e..43c1a44c 100644 --- a/README.md +++ b/README.md @@ -38,15 +38,6 @@ export default [ ]; ``` -If you are still using the deprecated `.eslintrc.js` file format for ESLint, you can extend the `plugin:markdown/recommended-legacy` config to enable the Markdown processor on all `.md` files: - -```js -// .eslintrc.js -module.exports = { - extends: "plugin:markdown/recommended-legacy" -}; -``` - #### Advanced Configuration You can manually include the Markdown processor by setting the `processor` option in your configuration file for all `.md` files. @@ -96,32 +87,6 @@ export default [ ]; ``` -In the deprecated `.eslintrc.js` format: - -```js -// .eslintrc.js -module.exports = { - // 1. Add the plugin. - plugins: ["markdown"], - overrides: [ - { - // 2. Enable the Markdown processor for all .md files. - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - // 3. Optionally, customize the configuration ESLint uses for ```js - // fenced code blocks inside .md files. - files: ["**/*.md/*.js"], - // ... - rules: { - // ... - } - } - ] -}; -``` - #### Frequently-Disabled Rules Some rules that catch mistakes in regular code are less helpful in documentation. @@ -163,30 +128,6 @@ export default [ ]; ``` -And in the deprecated `.eslintrc.js` format: - -```js -// .eslintrc.js -module.exports = { - plugins: ["markdown"], - overrides: [ - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - // 1. Target ```js code blocks in .md files. - files: ["**/*.md/*.js"], - rules: { - // 2. Disable other rules. - "no-console": "off", - "import/no-unresolved": "off" - } - } - ] -}; -``` - #### Strict Mode `"use strict"` directives in every code block would be annoying. @@ -205,8 +146,6 @@ The `markdown.configs.recommended` config disables these rules in Markdown files If you are using an `eslint.config.js` file, then you can run ESLint as usual and it will pick up file patterns in your config file. The `--ext` option is not available when using flat config. -If you are using an `.eslintrc.*` file, then you can run ESLint as usual and it will pick up file extensions specified in `overrides[].files` patterns in config files. - ### Autofixing diff --git a/eslint.config.js b/eslint.config.js index fb37936b..dae8360f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,17 +1,14 @@ -"use strict"; +import globals from "globals"; +import eslintConfigESLint from "eslint-config-eslint"; +import eslintConfigESLintFormatting from "eslint-config-eslint/formatting"; +import markdown from "./src/index.js"; -module.exports = [ - ...require("eslint-config-eslint/cjs").map(config => ({ - ...config, - files: ["**/*.js"] - })), - { - ...require("eslint-config-eslint/formatting"), - files: ["**/*.js"] - }, +export default [ + ...eslintConfigESLint, + eslintConfigESLintFormatting, { plugins: { - markdown: require(".") + markdown } }, { @@ -25,8 +22,11 @@ module.exports = [ files: ["tests/**/*.js"], languageOptions: { globals: { - ...require("globals").mocha + ...globals.mocha } + }, + rules: { + "no-underscore-dangle": "off" } }, { diff --git a/examples/react/eslint.config.js b/examples/react/eslint.config.mjs similarity index 76% rename from examples/react/eslint.config.js rename to examples/react/eslint.config.mjs index efd097da..5f8b3947 100644 --- a/examples/react/eslint.config.js +++ b/examples/react/eslint.config.mjs @@ -1,13 +1,13 @@ -"use strict"; -const markdown = require("eslint-plugin-markdown"); -const js = require("@eslint/js"); -const globals = require("globals"); +import js from "@eslint/js"; +import markdown from "../../src/index.js"; +import globals from "globals"; +import reactRecommended from "eslint-plugin-react/configs/recommended.js"; -module.exports = [ +export default [ js.configs.recommended, ...markdown.configs.recommended, - require("eslint-plugin-react/configs/recommended"), + reactRecommended, { settings: { react: { diff --git a/examples/typescript/eslint.config.js b/examples/typescript/eslint.config.mjs similarity index 62% rename from examples/typescript/eslint.config.js rename to examples/typescript/eslint.config.mjs index e1409236..a36c941d 100644 --- a/examples/typescript/eslint.config.js +++ b/examples/typescript/eslint.config.mjs @@ -1,10 +1,8 @@ -"use strict"; +import js from "@eslint/js"; +import markdown from "../../src/index.js"; +import tseslint from "typescript-eslint"; -const markdown = require("eslint-plugin-markdown"); -const js = require("@eslint/js") -const tseslint = require("typescript-eslint"); - -module.exports = tseslint.config( +export default tseslint.config( js.configs.recommended, ...markdown.configs.recommended, { diff --git a/index.js b/index.js deleted file mode 100644 index 1638f11e..00000000 --- a/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @fileoverview Exports the processor. - * @author Brandon Mills - */ - -"use strict"; - -module.exports = require("./lib"); diff --git a/npm-prepare.js b/npm-prepare.cjs similarity index 100% rename from npm-prepare.js rename to npm-prepare.cjs diff --git a/package.json b/package.json index c1ace70e..655f84a4 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,16 @@ "name": "Brandon Mills", "url": "https://github.com/btmills" }, + "type": "module", + "main": "src/index.js", + "exports": { + "import": { + "default": "./src/index.js" + } + }, + "files": [ + "src" + ], "repository": "eslint/eslint-plugin-markdown", "bugs": { "url": "https://github.com/eslint/eslint-plugin-markdown/issues" @@ -21,35 +31,30 @@ ], "scripts": { "lint": "eslint .", - "prepare": "node ./npm-prepare.js", + "prepare": "node ./npm-prepare.cjs", "release:generate:latest": "eslint-generate-release", "release:generate:alpha": "eslint-generate-prerelease alpha", "release:generate:beta": "eslint-generate-prerelease beta", "release:generate:rc": "eslint-generate-prerelease rc", "release:publish": "eslint-publish-release", - "test": "nyc _mocha -- -c tests/{examples,lib}/**/*.js --timeout 30000" + "test": "c8 mocha \"tests/**/*.test.js\" --timeout 30000" }, - "main": "index.js", - "files": [ - "index.js", - "lib/index.js", - "lib/processor.js" - ], "devDependencies": { + "@eslint/core": "^0.2.0", "@eslint/js": "^9.4.0", - "chai": "^4.2.0", + "c8": "^10.1.2", + "chai": "^5.1.1", "eslint": "^9.4.0", "eslint-config-eslint": "^11.0.0", "eslint-release": "^3.1.2", "globals": "^15.1.0", - "mocha": "^6.2.2", - "nyc": "^14.1.1" + "mocha": "^10.6.0" }, "dependencies": { - "mdast-util-from-markdown": "^0.8.5" + "mdast-util-from-markdown": "^2.0.1" }, "peerDependencies": { - "eslint": ">=8" + "eslint": ">=9" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/release-please-config.json b/release-please-config.json index dd505362..d551e111 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -5,8 +5,8 @@ "pull-request-title-pattern": "chore: release ${version} 🚀", "include-component-in-tag": false, "extra-files": [ - "lib/index.js", - "lib/processor.js" + "src/index.js", + "src/processor.js" ] } } diff --git a/lib/index.js b/src/index.js similarity index 97% rename from lib/index.js rename to src/index.js index b210d72d..5c375274 100644 --- a/lib/index.js +++ b/src/index.js @@ -3,9 +3,7 @@ * @author Brandon Mills */ -"use strict"; - -const processor = require("./processor"); +import { processor } from "./processor.js"; const rulesConfig = { @@ -100,4 +98,4 @@ plugin.configs.recommended = [ } ]; -module.exports = plugin; +export default plugin; diff --git a/lib/processor.js b/src/processor.js similarity index 99% rename from lib/processor.js rename to src/processor.js index 93f56ada..43fa6618 100644 --- a/lib/processor.js +++ b/src/processor.js @@ -25,9 +25,7 @@ * @typedef {ASTNode & BlockBase} Block */ -"use strict"; - -const parse = require("mdast-util-from-markdown"); +import { fromMarkdown } from "mdast-util-from-markdown"; const UNSATISFIABLE_RULES = new Set([ "eol-last", // The Markdown parser strips trailing newlines in code fences @@ -248,7 +246,7 @@ const languageToFileExtension = { * @returns {Array<{ filename: string, text: string }>} Source code blocks to lint. */ function preprocess(text, filename) { - const ast = parse(text); + const ast = fromMarkdown(text); const blocks = []; blocksCache.set(filename, blocks); @@ -409,7 +407,7 @@ function postprocess(messages, filename) { }); } -module.exports = { +export const processor = { meta: { name: "eslint-plugin-markdown/markdown", version: "5.1.0" // x-release-please-version diff --git a/tests/examples/all.js b/tests/examples/all.test.js similarity index 80% rename from tests/examples/all.js rename to tests/examples/all.test.js index 2efdf192..98cb7529 100644 --- a/tests/examples/all.js +++ b/tests/examples/all.test.js @@ -1,10 +1,13 @@ -"use strict"; - -const assert = require("chai").assert; -const fs = require("fs"); -const path = require("path"); -const semver = require("semver"); +import { assert } from "chai"; +import fs from "node:fs"; +import path from "node:path"; +import semver from "semver"; +import { createRequire } from "node:module"; +import { fileURLToPath } from "node:url"; +const require = createRequire(import.meta.url); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const examplesDir = path.resolve(__dirname, "../../examples/"); const examples = fs.readdirSync(examplesDir) .filter(exampleDir => fs.statSync(path.join(examplesDir, exampleDir)).isDirectory()) @@ -14,7 +17,7 @@ for (const example of examples) { const cwd = path.join(examplesDir, example); // The plugin officially supports ESLint as early as v6, but the examples - // use ESLint v7, which has a higher minimum Node.js version than does v6. + // use ESLint v8, which has a higher minimum Node.js version than does v6. // Only exercise the example if the running Node.js version satisfies the // minimum version constraint. In CI, this will skip these tests in Node.js // v8 and run them on all other Node.js versions. diff --git a/tests/fixtures/eslint.config.js b/tests/fixtures/eslint.config.js index 0cd96371..d8c4ecb7 100644 --- a/tests/fixtures/eslint.config.js +++ b/tests/fixtures/eslint.config.js @@ -1,7 +1,7 @@ -const markdown = require("../../"); -const globals = require("globals"); +import markdown from "../../src/index.js"; +import globals from "globals"; -module.exports = [ +export default [ { plugins: { markdown diff --git a/tests/fixtures/recommended.js b/tests/fixtures/recommended.js index d2d72b84..d04e32b1 100644 --- a/tests/fixtures/recommended.js +++ b/tests/fixtures/recommended.js @@ -1,7 +1,7 @@ -const markdown = require("../../"); -const js = require("@eslint/js") +import markdown from "../../src/index.js"; +import js from "@eslint/js"; -module.exports = [ +export default [ js.configs.recommended, ...markdown.configs.recommended, { diff --git a/tests/lib/plugin.js b/tests/plugin.test.js similarity index 98% rename from tests/lib/plugin.js rename to tests/plugin.test.js index d7e91ae3..94edb62c 100644 --- a/tests/lib/plugin.js +++ b/tests/plugin.test.js @@ -3,17 +3,28 @@ * @author Brandon Mills */ -"use strict"; +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import { assert } from "chai"; +import api from "eslint"; +import unsupportedAPI from "eslint/use-at-your-own-risk"; +import path from "node:path"; +import plugin from "../src/index.js"; +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; + +const ESLint = api.ESLint; +const LegacyESLint = unsupportedAPI.LegacyESLint; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); //----------------------------------------------------------------------------- -// Requirements +// Data //----------------------------------------------------------------------------- -const assert = require("chai").assert; -const { LegacyESLint, FlatESLint } = require("eslint/use-at-your-own-risk"); -const path = require("node:path"); -const plugin = require("../.."); -const pkg = require("../../package.json"); +const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../package.json"), "utf8")); //----------------------------------------------------------------------------- // Helpers @@ -27,10 +38,10 @@ const pkg = require("../../package.json"); */ function initLegacyESLint(fixtureConfigName, options = {}) { return new LegacyESLint({ - cwd: path.resolve(__dirname, "../fixtures/"), + cwd: path.resolve(__dirname, "./fixtures/"), ignore: false, useEslintrc: false, - overrideConfigFile: path.resolve(__dirname, "../fixtures/", fixtureConfigName), + overrideConfigFile: path.resolve(__dirname, "./fixtures/", fixtureConfigName), plugins: { markdown: plugin }, ...options }); @@ -40,13 +51,13 @@ function initLegacyESLint(fixtureConfigName, options = {}) { * Helper function which creates ESLint instance with enabled/disabled autofix feature. * @param {string} fixtureConfigName ESLint config filename. * @param {Object} [options={}] Whether to enable autofix feature. - * @returns {FlatESLint} ESLint instance to execute in tests. + * @returns {ESLint} ESLint instance to execute in tests. */ function initFlatESLint(fixtureConfigName, options = {}) { - return new FlatESLint({ - cwd: path.resolve(__dirname, "../fixtures/"), + return new ESLint({ + cwd: path.resolve(__dirname, "./fixtures/"), ignore: false, - overrideConfigFile: path.resolve(__dirname, "../fixtures/", fixtureConfigName), + overrideConfigFile: path.resolve(__dirname, "./fixtures/", fixtureConfigName), ...options }); } @@ -245,7 +256,7 @@ describe("LegacyESLint", () => { }); it("should extract blocks and remap messages", async () => { - const results = await eslint.lintFiles([path.resolve(__dirname, "../fixtures/long.md")]); + const results = await eslint.lintFiles([path.resolve(__dirname, "./fixtures/long.md")]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 5); @@ -1207,7 +1218,7 @@ describe("FlatESLint", () => { }); it("should extract blocks and remap messages", async () => { - const results = await eslint.lintFiles([path.resolve(__dirname, "../fixtures/long.md")]); + const results = await eslint.lintFiles([path.resolve(__dirname, "./fixtures/long.md")]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 5); diff --git a/tests/lib/processor.js b/tests/processor.test.js similarity index 96% rename from tests/lib/processor.js rename to tests/processor.test.js index 23433be2..bb00eb33 100644 --- a/tests/lib/processor.js +++ b/tests/processor.test.js @@ -3,11 +3,28 @@ * @author Brandon Mills */ -"use strict"; +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- -const assert = require("chai").assert; -const processor = require("../../lib/processor"); -const pkg = require("../../package.json"); +import { assert } from "chai"; +import path from "node:path"; +import { processor } from "../src/processor.js"; +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +//----------------------------------------------------------------------------- +// Data +//----------------------------------------------------------------------------- + +const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../package.json"), "utf8")); + +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- describe("processor", () => {