diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 000000000..3cd72b90e --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,18 @@ +name: ESLint Check + +on: + pull_request: + push: + branches: [main] + +jobs: + eslint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + - run: npm ci + - run: npx eslint --format gha diff --git a/eslint.config.js b/eslint.config.js index 43d4f5881..575e2122c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,13 +1,36 @@ +import { includeIgnoreFile } from "@eslint/compat"; import pluginJs from "@eslint/js"; import eslintConfigPrettier from "eslint-config-prettier/flat"; import globals from "globals"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; import tseslint from "typescript-eslint"; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const gitignorePath = path.resolve(__dirname, ".gitignore"); + /** @type {import('eslint').Linter.Config[]} */ export default [ + includeIgnoreFile(gitignorePath), { files: ["**/*.{js,mjs,cjs,ts}"] }, { languageOptions: { globals: { ...globals.browser, ...globals.node } } }, pluginJs.configs.recommended, ...tseslint.configs.recommended, eslintConfigPrettier, + { + rules: { + // Disable rules that would fail. The failures should be fixed, and the entries here removed. + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-empty-object-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-unused-vars": "off", + "no-case-declarations": "off", + "no-useless-escape": "off", + "prefer-const": "off", + }, + }, ]; diff --git a/package-lock.json b/package-lock.json index d47e7401f..44f0f6d50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/preset-typescript": "^7.24.7", + "@eslint/compat": "^1.2.7", "@eslint/js": "^9.21.0", "@types/chai": "^4.3.17", "@types/d3": "^7.4.3", @@ -85,6 +86,8 @@ "css-loader": "^7.1.2", "eslint": "^9.21.0", "eslint-config-prettier": "^10.1.1", + "eslint-formatter-gha": "^1.5.2", + "eslint-webpack-plugin": "^5.0.0", "file-loader": "^6.2.0", "globals": "^16.0.0", "html-inline-script-webpack-plugin": "^3.2.1", @@ -3555,6 +3558,24 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.7.tgz", + "integrity": "sha512-xvv7hJE32yhegJ8xNAnb62ggiAwTYHBpUCWhRxEj/ksvgDJuSXfoDkBcRYaYNFiJ+jH0IE3K16hd+xXzhBgNbg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, "node_modules/@eslint/config-array": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", @@ -6608,6 +6629,17 @@ "@types/trusted-types": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -10446,6 +10478,42 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-formatter-gha": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/eslint-formatter-gha/-/eslint-formatter-gha-1.5.2.tgz", + "integrity": "sha512-1TY8AYbrIP9DCcbydYW467nTP67eW79bT+oVKdUehO3WPMP8pn2oOZVkDc8yDcWf35+t2Li1elY5Dh6jTjJ3/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-formatter-json": "^8.40.0", + "eslint-formatter-stylish": "^8.40.0" + } + }, + "node_modules/eslint-formatter-json": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-json/-/eslint-formatter-json-8.40.0.tgz", + "integrity": "sha512-0bXo4At1EoEU23gFfN7wcDeqRXDHLJnvDOuQKD3Q6FkBlk7L2oVNPYg/sciIWdYrUnCBcKuMit3IWXkdSfzChg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-formatter-stylish": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-stylish/-/eslint-formatter-stylish-8.40.0.tgz", + "integrity": "sha512-blbD5ZSQnjNEUaG38VCO4WG9nfDQWE8/IOmt8DFRHXUIfZikaIXmsQTdWNFk0/e0j7RgIVRza86MpsJ+aHgFLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -10472,6 +10540,88 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-webpack-plugin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-5.0.0.tgz", + "integrity": "sha512-iDhXf2r55KO1UhMfpus8oGp93wdNF+934q5kEkwa7qn3BH9f51QEC11xQidt+8jfqRnEYYZa2/8lhac7U/vqWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "^9.6.1", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "schema-utils": "^4.3.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/eslint/node_modules/eslint-scope": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", @@ -18193,6 +18343,13 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", diff --git a/package.json b/package.json index fdf62679d..29be008c6 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ }, "lint-staged": { "**/*": [ + "eslint --fix", "prettier --ignore-unknown --write" ] }, @@ -24,6 +25,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/preset-typescript": "^7.24.7", + "@eslint/compat": "^1.2.7", "@eslint/js": "^9.21.0", "@types/chai": "^4.3.17", "@types/d3": "^7.4.3", @@ -45,6 +47,8 @@ "css-loader": "^7.1.2", "eslint": "^9.21.0", "eslint-config-prettier": "^10.1.1", + "eslint-formatter-gha": "^1.5.2", + "eslint-webpack-plugin": "^5.0.0", "file-loader": "^6.2.0", "globals": "^16.0.0", "html-inline-script-webpack-plugin": "^3.2.1", diff --git a/webpack.config.js b/webpack.config.js index 66de6c77c..5bb60050c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ import CopyPlugin from "copy-webpack-plugin"; +import ESLintPlugin from "eslint-webpack-plugin"; import HtmlWebpackPlugin from "html-webpack-plugin"; import path from "path"; import { fileURLToPath } from "url"; @@ -129,6 +130,9 @@ export default async (env, argv) => { ], options: { concurrency: 100 }, }), + new ESLintPlugin({ + context: __dirname, + }), ], optimization: { // Add optimization configuration for better caching