diff --git a/.eslintignore b/.eslintignore index b8414c9c832..905f2a39ddf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,7 @@ /build/** /coverage/** /docs/** +!/docs/.eleventy.js /jsdoc/** /templates/** /tests/bench/** diff --git a/.eslintrc.js b/.eslintrc.js index 33d895f5c11..4df24ee7349 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -46,8 +46,7 @@ module.exports = { "internal-rules" ], extends: [ - "eslint", - "plugin:eslint-plugin/recommended" + "eslint" ], parserOptions: { ecmaVersion: 2021 @@ -63,14 +62,6 @@ module.exports = { } }, rules: { - "eslint-plugin/prefer-message-ids": "error", - "eslint-plugin/prefer-output-null": "error", - "eslint-plugin/prefer-placeholders": "error", - "eslint-plugin/prefer-replace-text": "error", - "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], - "eslint-plugin/require-meta-docs-description": "error", - "eslint-plugin/test-case-property-ordering": "error", - "eslint-plugin/test-case-shorthand-strings": "error", "internal-rules/multiline-comment-style": "error" }, overrides: [ @@ -83,7 +74,15 @@ module.exports = { { files: ["lib/rules/*", "tools/internal-rules/*"], excludedFiles: ["index.js"], + extends: [ + "plugin:eslint-plugin/rules-recommended" + ], rules: { + "eslint-plugin/prefer-message-ids": "error", + "eslint-plugin/prefer-placeholders": "error", + "eslint-plugin/prefer-replace-text": "error", + "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], + "eslint-plugin/require-meta-docs-description": "error", "internal-rules/no-invalid-meta": "error" } }, @@ -94,6 +93,17 @@ module.exports = { "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/rules/{{name}}" }] } }, + { + files: ["tests/lib/rules/*", "tests/tools/internal-rules/*"], + extends: [ + "plugin:eslint-plugin/tests-recommended" + ], + rules: { + "eslint-plugin/prefer-output-null": "error", + "eslint-plugin/test-case-property-ordering": "error", + "eslint-plugin/test-case-shorthand-strings": "error" + } + }, { files: ["tests/**/*"], env: { mocha: true }, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bbaacfca60..a61886ab522 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,11 @@ jobs: run: node Makefile checkRuleFiles - name: Check Licenses run: node Makefile checkLicenses + - name: Install Docs Packages + working-directory: docs + run: npm install + - name: Lint Docs JS Files + run: node Makefile lintDocsJS test_on_node: name: Test diff --git a/Makefile.js b/Makefile.js index de786148c65..bdd3aca75bc 100644 --- a/Makefile.js +++ b/Makefile.js @@ -482,8 +482,17 @@ target.lint = function([fix = false] = []) { let errors = 0, lastReturn; + /* + * In order to successfully lint JavaScript files in the `docs` directory, dependencies declared in `docs/package.json` + * would have to be installed in `docs/node_modules`. In particular, eslint-plugin-node rules examine `docs/node_modules` + * when analyzing `require()` calls from CJS modules in the `docs` directory. Since our release process does not run `npm install` + * in the `docs` directory, linting would fail and break the release. Also, working on the main `eslint` package does not require + * installing dependencies declared in `docs/package.json`, so most contributors will not have `docs/node_modules` locally. + * Therefore, we add `--ignore-pattern docs` to exclude linting the `docs` directory from this command. + * There is a separate command `target.lintDocsJS` for linting JavaScript files in the `docs` directory. + */ echo("Validating JavaScript files"); - lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} .`); + lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} . --ignore-pattern docs`); if (lastReturn.code !== 0) { errors++; } @@ -502,6 +511,21 @@ target.lint = function([fix = false] = []) { } }; +target.lintDocsJS = function([fix = false] = []) { + let errors = 0; + + echo("Validating JavaScript files in the docs directory"); + const lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} docs`); + + if (lastReturn.code !== 0) { + errors++; + } + + if (errors) { + exit(1); + } +}; + target.fuzz = function({ amount = 1000, fuzzBrokenAutofixes = false } = {}) { const fuzzerRunner = require("./tools/fuzzer-runner"); const fuzzResults = fuzzerRunner.run({ amount, fuzzBrokenAutofixes }); diff --git a/docs/.eleventy.js b/docs/.eleventy.js index ecda440722e..ba0c98d5a34 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -1,19 +1,19 @@ +"use strict"; + const eleventyNavigationPlugin = require("@11ty/eleventy-navigation"); const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight"); const pluginRss = require("@11ty/eleventy-plugin-rss"); -const pluginTOC = require('eleventy-plugin-nesting-toc'); +const pluginTOC = require("eleventy-plugin-nesting-toc"); const slugify = require("slugify"); -const markdownIt = require("markdown-it"); -const markdownItAnchor = require('markdown-it-anchor'); +const markdownItAnchor = require("markdown-it-anchor"); const Image = require("@11ty/eleventy-img"); -const path = require('path'); +const path = require("path"); const { DateTime } = require("luxon"); module.exports = function(eleventyConfig) { - let now = new Date(); /* * The site is loaded from /docs on eslint.org and so we need to adjust @@ -21,109 +21,111 @@ module.exports = function(eleventyConfig) { * * The path prefix is turned off for deploy previews so we can properly * see changes before deployed. - */ + */ const pathPrefix = process.env.CONTEXT === "deploy-preview" ? "" : "/docs"; - /***************************************************************************************** - * Filters - * ***************************************************************************************/ - eleventyConfig.addFilter("limitTo", function(arr, limit) { - return arr.slice(0, limit); - }); + //------------------------------------------------------------------------------ + // Filters + //------------------------------------------------------------------------------ - eleventyConfig.addFilter('jsonify', function(variable) { - return JSON.stringify(variable); - }); + eleventyConfig.addFilter("limitTo", (arr, limit) => arr.slice(0, limit)); + + eleventyConfig.addFilter("jsonify", variable => JSON.stringify(variable)); - eleventyConfig.addFilter('slugify', function(str) { + eleventyConfig.addFilter("slugify", str => { if (!str) { - return; + return ""; } return slugify(str, { lower: true, strict: true, - remove: /["]/g, + remove: /["]/gu }); }); - eleventyConfig.addFilter('URIencode', function(str) { + eleventyConfig.addFilter("URIencode", str => { if (!str) { - return; + return ""; } return encodeURI(str); }); /* order collection by the order specified in the front matter */ - eleventyConfig.addFilter("sortByPageOrder", function(values) { - return values.slice().sort((a, b) => a.data.order - b.data.order); - }); + eleventyConfig.addFilter("sortByPageOrder", values => values.slice().sort((a, b) => a.data.order - b.data.order)); - eleventyConfig.addFilter("readableDate", (dateObj) => { - //turn it into a JS Date string - date = new Date(dateObj); - //pass it to luxon for formatting - return DateTime.fromJSDate(date).toFormat('dd MMM, yyyy'); - }); + eleventyConfig.addFilter("readableDate", dateObj => { - eleventyConfig.addFilter("blogPermalinkDate", (dateObj) => { - //turn it into a JS Date string - date = new Date(dateObj); - //pass it to luxon for formatting - return DateTime.fromJSDate(dateObj).toFormat('yyyy/MM'); - }); + // turn it into a JS Date string + const date = new Date(dateObj); - eleventyConfig.addFilter("readableDateFromISO", (ISODate) => { - return DateTime.fromISO(ISODate).toUTC().toLocaleString(DateTime.DATE_FULL); + // pass it to luxon for formatting + return DateTime.fromJSDate(date).toFormat("dd MMM, yyyy"); }); - eleventyConfig.addFilter('dollars', value => { - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD" - }).format(value); + eleventyConfig.addFilter("blogPermalinkDate", dateObj => { + + // turn it into a JS Date string + const date = new Date(dateObj); + + // pass it to luxon for formatting + return DateTime.fromJSDate(date).toFormat("yyyy/MM"); }); - // parse markdown from includes, used for author bios - // Source: https://github.com/11ty/eleventy/issues/658 - eleventyConfig.addFilter('markdown', function(value) { - let markdown = require('markdown-it')({ + eleventyConfig.addFilter("readableDateFromISO", ISODate => DateTime.fromISO(ISODate).toUTC().toLocaleString(DateTime.DATE_FULL)); + + eleventyConfig.addFilter("dollars", value => new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD" + }).format(value)); + + /* + * parse markdown from includes, used for author bios + * Source: https://github.com/11ty/eleventy/issues/658 + */ + eleventyConfig.addFilter("markdown", value => { + const markdown = require("markdown-it")({ html: true }); + return markdown.render(value); }); - /***************************************************************************************** - * Plugins - * ***************************************************************************************/ + //------------------------------------------------------------------------------ + // Plugins + //------------------------------------------------------------------------------ + eleventyConfig.addPlugin(eleventyNavigationPlugin); eleventyConfig.addPlugin(syntaxHighlight, { - alwaysWrapLineHighlights: true, + alwaysWrapLineHighlights: true }); eleventyConfig.addPlugin(pluginRss); eleventyConfig.addPlugin(pluginTOC, { - tags: ['h2', 'h3', 'h4'], - wrapper: 'nav', // Element to put around the root `ol` - wrapperClass: 'c-toc', // Class for the element around the root `ol` - headingText: '', // Optional text to show in heading above the wrapper element - headingTag: 'h2' // Heading tag when showing heading above the wrapper element + tags: ["h2", "h3", "h4"], + wrapper: "nav", // Element to put around the root `ol` + wrapperClass: "c-toc", // Class for the element around the root `ol` + headingText: "", // Optional text to show in heading above the wrapper element + headingTag: "h2" // Heading tag when showing heading above the wrapper element }); + // add IDs to the headers - const markdownIt = require('markdown-it'); + const markdownIt = require("markdown-it"); + eleventyConfig.setLibrary("md", markdownIt({ html: true, linkify: true, - typographer: true, + typographer: true + + }).use(markdownItAnchor, {}).disable("code")); - }).use(markdownItAnchor, {}).disable('code') - ); + //------------------------------------------------------------------------------ + // Shortcodes + //------------------------------------------------------------------------------ - /********************************************************************** - * Shortcodes - * ********************************************************************/ eleventyConfig.addNunjucksShortcode("link", function(url) { + // eslint-disable-next-line no-invalid-this -- Eleventy API const urlData = this.ctx.further_reading_links[url]; if (!urlData) { @@ -151,42 +153,36 @@ module.exports = function(eleventyConfig) { `; }); - eleventyConfig.addShortcode("fixable", function() { - return ` + eleventyConfig.addShortcode("fixable", () => `
if some problems reported by the rule are automatically fixable by the --fix
command line option
if the "extends": "eslint:recommended"
property in a configuration file enables the rule.
if some problems reported by the rule are manually fixable by editor suggestions
-