From 5f5c1fb1083573ea511d0dae7913651db0dca772 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 25 May 2022 13:42:24 +0200 Subject: [PATCH] chore: lint eleventy config file (#15904) * chore: lint eleventy config file * fix wrong override for eslint-plugin/prefer-output-null * update `lint` command to exclude linting JS files in the docs dir * add separate script from linting docs js files * use internal mutliline comment style for sections --- .eslintignore | 1 + .eslintrc.js | 30 +++-- .github/workflows/ci.yml | 5 + Makefile.js | 26 +++- docs/.eleventy.js | 283 +++++++++++++++++++-------------------- docs/README.md | 12 ++ docs/package.json | 3 + package.json | 2 + 8 files changed, 209 insertions(+), 153 deletions(-) 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", () => `
🛠 Fixable

if some problems reported by the rule are automatically fixable by the --fix command line option

-
`; - }); + `); - eleventyConfig.addShortcode("recommended", function() { - return ` + eleventyConfig.addShortcode("recommended", () => `
✅ Recommended

if the "extends": "eslint:recommended" property in a configuration file enables the rule.

-
`; - }); + `); - eleventyConfig.addShortcode("hasSuggestions", function() { - return ` + eleventyConfig.addShortcode("hasSuggestions", () => `
💡 hasSuggestions

if some problems reported by the rule are manually fixable by editor suggestions

-
`; - }); + `); - eleventyConfig.addShortcode("related_rules", function(arr) { - let rules = arr, - items = ""; + eleventyConfig.addShortcode("related_rules", arr => { + const rules = arr; + let items = ""; - rules.forEach(function(rule) { - let list_item = ``; - items += list_item; - }) + + items += listItem; + }); return ` `; }); - eleventyConfig.addShortcode("important", function(text, url) { - return ` + eleventyConfig.addShortcode("important", (text, url) => `
${text}
Learn more
- `; - }); + `); - eleventyConfig.addShortcode("warning", function(text, url) { - return ` + eleventyConfig.addShortcode("warning", (text, url) => `
${text}
Learn more
- `; - }); + `); - eleventyConfig.addShortcode("tip", function(text, url) { - return ` + eleventyConfig.addShortcode("tip", (text, url) => `
${text}
Learn more
- `; - }); + `); eleventyConfig.addWatchTarget("./src/assets/"); - - - /***************************************************************************************** - * File PassThroughs - * ***************************************************************************************/ + //------------------------------------------------------------------------------ + // File PassThroughs + //------------------------------------------------------------------------------ eleventyConfig.addPassthroughCopy({ "./src/static": "/" }); - eleventyConfig.addPassthroughCopy('./src/assets/'); + eleventyConfig.addPassthroughCopy("./src/assets/"); eleventyConfig.addPassthroughCopy({ - './src/content/**/*.png': "/assets/images" + "./src/content/**/*.png": "/assets/images" }); eleventyConfig.addPassthroughCopy({ - './src/content/**/*.jpg': "/assets/images" + "./src/content/**/*.jpg": "/assets/images" }); eleventyConfig.addPassthroughCopy({ - './src/content/**/*.jpeg': "/assets/images" + "./src/content/**/*.jpeg": "/assets/images" }); eleventyConfig.addPassthroughCopy({ - './src/content/**/*.svg': "/assets/images" + "./src/content/**/*.svg": "/assets/images" }); eleventyConfig.addPassthroughCopy({ - './src/content/**/*.mp4': "/assets/videos" + "./src/content/**/*.mp4": "/assets/videos" }); eleventyConfig.addPassthroughCopy({ - './src/content/**/*.pdf': "/assets/documents" + "./src/content/**/*.pdf": "/assets/documents" }); eleventyConfig.addPassthroughCopy({ - './node_modules/algoliasearch/dist/algoliasearch-lite.esm.browser.js': "/assets/js/algoliasearch.js" + "./node_modules/algoliasearch/dist/algoliasearch-lite.esm.browser.js": "/assets/js/algoliasearch.js" }); - /***************************************************************************************** - * Collections - * ***************************************************************************************/ + //------------------------------------------------------------------------------ + // Collections + //------------------------------------------------------------------------------ - eleventyConfig.addCollection("docs", function(collection) { - return collection.getFilteredByGlob("./src/**/**/*.md"); - }); + eleventyConfig.addCollection("docs", collection => collection.getFilteredByGlob("./src/**/**/*.md")); - eleventyConfig.addCollection("library", function(collection) { - return collection.getFilteredByGlob("./src/library/**/*.md"); - }); + eleventyConfig.addCollection("library", collection => collection.getFilteredByGlob("./src/library/**/*.md")); - // START, eleventy-img - function imageShortcode(src, alt, cls, sizes = "(max-width: 768px) 100vw, 50vw") { - const source = src; - - let options = { + // START, eleventy-img (https://www.11ty.dev/docs/plugins/image/) + /* eslint-disable-next-line jsdoc/require-jsdoc + -- + This shortcode is currently unused. If we are going to use it, add JSDoc + and describe what exactly is this doing. + */ + function imageShortcode(source, alt, cls, sizes = "(max-width: 768px) 100vw, 50vw") { + const options = { widths: [600, 900, 1500], formats: ["webp", "jpeg"], urlPath: "/assets/images/", outputDir: "./_site/assets/images/", - filenameFormat: function(id, src, width, format, options) { - const extension = path.extname(src) - const name = path.basename(src, extension) - return `${name}-${width}w.${format}` + filenameFormat(id, src, width, format) { + const extension = path.extname(src); + const name = path.basename(src, extension); + + return `${name}-${width}w.${format}`; } - } + }; + /** + * Resolves source + * @returns {string} URL or a local file path + */ function getSRC() { - if (source.indexOf("http://") == 0 || source.indexOf("https://") == 0) { + if (source.startsWith("http://") || source.startsWith("https://")) { return source; - } else { - // for convenience, you only need to use the image's name in the shortcode, - // and this will handle appending the full path to it - src = path.join('./src/assets/images/', source); - return src; } + + /* + * for convenience, you only need to use the image's name in the shortcode, + * and this will handle appending the full path to it + */ + return path.join("./src/assets/images/", source); } - var fullSrc = getSRC(); + const fullSrc = getSRC(); + + // generate images - Image(fullSrc, options) + Image(fullSrc, options); // eslint-disable-line new-cap -- `Image` is a function - let imageAttributes = { + const imageAttributes = { alt, class: cls, sizes, loading: "lazy", - decoding: "async", - } + decoding: "async" + }; + // get metadata - metadata = Image.statsSync(fullSrc, options) - return Image.generateHTML(metadata, imageAttributes) + const metadata = Image.statsSync(fullSrc, options); + + return Image.generateHTML(metadata, imageAttributes); } - eleventyConfig.addShortcode("image", imageShortcode) + eleventyConfig.addShortcode("image", imageShortcode); + // END, eleventy-img @@ -352,9 +351,9 @@ module.exports = function(eleventyConfig) { pathPrefix, - markdownTemplateEngine: 'njk', - dataTemplateEngine: 'njk', - htmlTemplateEngine: 'njk', + markdownTemplateEngine: "njk", + dataTemplateEngine: "njk", + htmlTemplateEngine: "njk", dir: { input: "src", diff --git a/docs/README.md b/docs/README.md index ac6484a189f..ee5f64e100e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,3 +13,15 @@ To update the links data file, run this from the root folder (not the `docs` fol ```shell npm run docs:update-links ``` + +To lint JS files, run this from the root folder (not the `docs` folder): + +```shell +npm run lint:docsjs +``` + +To autofix JS files, run this from the root folder (not the `docs` folder): + +```shell +npm run fix:docsjs +``` diff --git a/docs/package.json b/docs/package.json index aae609e17a2..ba963f8e767 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,11 +1,13 @@ { "name": "docs-eslint", + "private": true, "version": "1.0.0", "description": "", "main": "index.js", "keywords": [], "author": "", "license": "ISC", + "files": [], "scripts": { "images": "imagemin '_site/assets/images' --out-dir='_site/assets/images'", "watch:sass": "sass --watch --poll src/assets/scss:src/assets/css", @@ -28,6 +30,7 @@ "eleventy-plugin-reading-time": "^0.0.1", "imagemin": "^8.0.1", "imagemin-cli": "^7.0.0", + "luxon": "^2.4.0", "markdown-it": "^12.2.0", "markdown-it-anchor": "^8.1.2", "netlify-cli": "^10.3.1", diff --git a/package.json b/package.json index 9a565f2f9e4..f1010b32ace 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "test": "node Makefile.js test", "test:cli": "mocha", "lint": "node Makefile.js lint", + "lint:docsjs": "node Makefile.js lintDocsJS", "fix": "node Makefile.js lint -- fix", + "fix:docsjs": "node Makefile.js lintDocsJS -- fix", "fuzz": "node Makefile.js fuzz", "generate-release": "node Makefile.js generateRelease", "generate-alpharelease": "node Makefile.js generatePrerelease -- alpha",