mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-27 19:53:36 +00:00
Switch to ESLint v9 and flat config (#3558)
Since PR #3551 was not yet complete, I made my own attempt. 1. Update to ESLint v9. 2. Replace deprecated `.eslintrc.json` and `.eslintignore` by flat config `eslint.config.mjs`. 3. Adapt `check_config.js` to use flat config. 4. Since `eslint-plugin-import` still doesn't support ESLint v9 I removed it. We can add it back when it does support v9. 5. Run tests `npm run check:js` and `npm run config:check`. 6. In order not to overload this PR, I have not yet activated more additional rules - there are some useful ones in the new plugin `@eslint/js`. @bugsounet, please don't take it as an offence that I have created a competing PR. The migration to ESLint v9 has been burning under my nails for some time.
This commit is contained in:
parent
5ffdf9af09
commit
d3187689f0
@ -1,3 +0,0 @@
|
|||||||
modules/*
|
|
||||||
!modules/default/
|
|
||||||
js/positions.js
|
|
@ -1,94 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["eslint:recommended", "plugin:@stylistic/all-extends", "plugin:import/recommended", "plugin:jest/recommended", "plugin:jsdoc/recommended"],
|
|
||||||
"plugins": ["unicorn"],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2024": true,
|
|
||||||
"jest/globals": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"config": true,
|
|
||||||
"Log": true,
|
|
||||||
"MM": true,
|
|
||||||
"Module": true,
|
|
||||||
"moment": true
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"globalReturn": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"eqeqeq": "error",
|
|
||||||
"import/order": "error",
|
|
||||||
"import/extensions": "error",
|
|
||||||
"import/newline-after-import": "error",
|
|
||||||
"jest/consistent-test-it": "warn",
|
|
||||||
"jest/expect-expect": "warn",
|
|
||||||
"jest/no-done-callback": "warn",
|
|
||||||
"jest/prefer-expect-resolves": "warn",
|
|
||||||
"jest/prefer-mock-promise-shorthand": "warn",
|
|
||||||
"jest/prefer-to-be": "warn",
|
|
||||||
"jest/prefer-to-have-length": "warn",
|
|
||||||
"no-param-reassign": "error",
|
|
||||||
"no-prototype-builtins": "off",
|
|
||||||
"no-throw-literal": "error",
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"no-useless-return": "error",
|
|
||||||
"object-shorthand": ["error", "methods"],
|
|
||||||
"prefer-template": "error",
|
|
||||||
"@stylistic/array-element-newline": ["error", "consistent"],
|
|
||||||
"@stylistic/arrow-parens": ["error", "always"],
|
|
||||||
"@stylistic/brace-style": "off",
|
|
||||||
"@stylistic/comma-dangle": ["error", "never"],
|
|
||||||
"@stylistic/dot-location": ["error", "property"],
|
|
||||||
"@stylistic/function-call-argument-newline": ["error", "consistent"],
|
|
||||||
"@stylistic/function-paren-newline": ["error", "consistent"],
|
|
||||||
"@stylistic/implicit-arrow-linebreak": ["error", "beside"],
|
|
||||||
"@stylistic/max-statements-per-line": ["error", { "max": 2 }],
|
|
||||||
"@stylistic/multiline-comment-style": "off",
|
|
||||||
"@stylistic/multiline-ternary": ["error", "always-multiline"],
|
|
||||||
"@stylistic/newline-per-chained-call": ["error", { "ignoreChainWithDepth": 4 }],
|
|
||||||
"@stylistic/no-extra-parens": "off",
|
|
||||||
"@stylistic/no-tabs": "off",
|
|
||||||
"@stylistic/object-curly-spacing": ["error", "always"],
|
|
||||||
"@stylistic/object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }],
|
|
||||||
"@stylistic/operator-linebreak": ["error", "before"],
|
|
||||||
"@stylistic/padded-blocks": "off",
|
|
||||||
"@stylistic/quote-props": ["error", "as-needed"],
|
|
||||||
"@stylistic/quotes": ["error", "double"],
|
|
||||||
"@stylistic/indent": ["error", "tab"],
|
|
||||||
"@stylistic/semi": ["error", "always"],
|
|
||||||
"@stylistic/space-before-function-paren": ["error", "always"],
|
|
||||||
"@stylistic/spaced-comment": "off",
|
|
||||||
"unicorn/prefer-node-protocol": "error"
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["config/config.js*"],
|
|
||||||
"rules": {
|
|
||||||
"@stylistic/comma-dangle": "off",
|
|
||||||
"@stylistic/indent": "off",
|
|
||||||
"@stylistic/no-multi-spaces": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["tests/configs/modules/weather/*.js"],
|
|
||||||
"rules": {
|
|
||||||
"@stylistic/quotes": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"extends": ["plugin:package-json/recommended"],
|
|
||||||
"files": ["package.json"],
|
|
||||||
"parser": "jsonc-eslint-parser",
|
|
||||||
"plugins": ["package-json"],
|
|
||||||
"rules": {
|
|
||||||
"package-json/sort-collections": ["error", ["devDependencies", "dependencies", "peerDependencies", "config"]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@ -12,7 +12,7 @@ We use prettier for automatic linting of all our files: `npm run lint:prettier`.
|
|||||||
|
|
||||||
We use [ESLint](https://eslint.org) on our JavaScript files.
|
We use [ESLint](https://eslint.org) on our JavaScript files.
|
||||||
|
|
||||||
Our ESLint configuration is in our `.eslintrc.json` and `.eslintignore` files.
|
The ESLint configuration is in our `eslint.config.mjs` file.
|
||||||
|
|
||||||
To run ESLint, use `npm run lint:js`.
|
To run ESLint, use `npm run lint:js`.
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
*.js
|
*.js
|
||||||
.eslintignore
|
*.mjs
|
||||||
.husky/pre-commit
|
.husky/pre-commit
|
||||||
.prettierignore
|
.prettierignore
|
||||||
/config
|
/config
|
||||||
|
@ -23,6 +23,7 @@ _This release is scheduled to be released on 2024-10-01._
|
|||||||
|
|
||||||
- [core] removed installer only files (#3492)
|
- [core] removed installer only files (#3492)
|
||||||
- [core] removed raspberry object from systeminformation (#3505)
|
- [core] removed raspberry object from systeminformation (#3505)
|
||||||
|
- [linter] removed `eslint-plugin-import`, because it doesn't support ESLint v9. We will reenter it later when it does.
|
||||||
|
|
||||||
### Updated
|
### Updated
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ _This release is scheduled to be released on 2024-10-01._
|
|||||||
- [core] Allow custom modules positions by scanning index.html for the defined regions, instead of hard coded (PR #3518 fixes issue #3504)
|
- [core] Allow custom modules positions by scanning index.html for the defined regions, instead of hard coded (PR #3518 fixes issue #3504)
|
||||||
- [core] Detail optimizations in `config_check.js`
|
- [core] Detail optimizations in `config_check.js`
|
||||||
- [core] Updated minimal needed node version in `package.json` (currently v20.9.0)
|
- [core] Updated minimal needed node version in `package.json` (currently v20.9.0)
|
||||||
|
- [linter] Switch to ESLint v9 and flat config and replace `eslint-plugin-unicorn` by `@eslint/js`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
121
eslint.config.mjs
Normal file
121
eslint.config.mjs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import eslintPluginJest from "eslint-plugin-jest";
|
||||||
|
import eslintPluginJs from "@eslint/js";
|
||||||
|
import eslintPluginStylistic from "@stylistic/eslint-plugin";
|
||||||
|
import globals from "globals";
|
||||||
|
|
||||||
|
const config = [
|
||||||
|
eslintPluginJs.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ["**/*.js"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
...globals.jest,
|
||||||
|
Log: "readonly",
|
||||||
|
MM: "readonly",
|
||||||
|
Module: "readonly",
|
||||||
|
config: "readonly",
|
||||||
|
moment: "readonly"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
...eslintPluginStylistic.configs["all-flat"].plugins,
|
||||||
|
...eslintPluginJest.configs["flat/recommended"].plugins
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...eslintPluginStylistic.configs["all-flat"].rules,
|
||||||
|
...eslintPluginJest.configs["flat/recommended"].rules,
|
||||||
|
"@stylistic/array-element-newline": ["error", "consistent"],
|
||||||
|
"@stylistic/arrow-parens": ["error", "always"],
|
||||||
|
"@stylistic/brace-style": "off",
|
||||||
|
"@stylistic/comma-dangle": ["error", "never"],
|
||||||
|
"@stylistic/dot-location": ["error", "property"],
|
||||||
|
"@stylistic/function-call-argument-newline": ["error", "consistent"],
|
||||||
|
"@stylistic/function-paren-newline": ["error", "consistent"],
|
||||||
|
"@stylistic/implicit-arrow-linebreak": ["error", "beside"],
|
||||||
|
"@stylistic/indent": ["error", "tab"],
|
||||||
|
"@stylistic/max-statements-per-line": ["error", {max: 2}],
|
||||||
|
"@stylistic/multiline-comment-style": "off",
|
||||||
|
"@stylistic/multiline-ternary": ["error", "always-multiline"],
|
||||||
|
"@stylistic/newline-per-chained-call": ["error", {ignoreChainWithDepth: 4}],
|
||||||
|
"@stylistic/no-extra-parens": "off",
|
||||||
|
"@stylistic/no-tabs": "off",
|
||||||
|
"@stylistic/object-curly-spacing": ["error", "always"],
|
||||||
|
"@stylistic/object-property-newline": ["error", {allowAllPropertiesOnSameLine: true}],
|
||||||
|
"@stylistic/operator-linebreak": ["error", "before"],
|
||||||
|
"@stylistic/padded-blocks": "off",
|
||||||
|
"@stylistic/quote-props": ["error", "as-needed"],
|
||||||
|
"@stylistic/quotes": ["error", "double"],
|
||||||
|
"@stylistic/semi": ["error", "always"],
|
||||||
|
"@stylistic/space-before-function-paren": ["error", "always"],
|
||||||
|
"@stylistic/spaced-comment": "off",
|
||||||
|
eqeqeq: "error",
|
||||||
|
"id-length": "off",
|
||||||
|
"init-declarations": "off",
|
||||||
|
"jest/consistent-test-it": "warn",
|
||||||
|
"jest/no-done-callback": "warn",
|
||||||
|
"jest/prefer-expect-resolves": "warn",
|
||||||
|
"jest/prefer-mock-promise-shorthand": "warn",
|
||||||
|
"jest/prefer-to-be": "warn",
|
||||||
|
"jest/prefer-to-have-length": "warn",
|
||||||
|
"max-lines-per-function": ["warn", 350],
|
||||||
|
"max-statements": "off",
|
||||||
|
"no-global-assign": "off",
|
||||||
|
"no-inline-comments": "off",
|
||||||
|
"no-magic-numbers": "off",
|
||||||
|
"no-param-reassign": "error",
|
||||||
|
"no-plusplus": "off",
|
||||||
|
"no-prototype-builtins": "off",
|
||||||
|
"no-ternary": "off",
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-undefined": "off",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"no-useless-return": "error",
|
||||||
|
"no-warning-comments": "off",
|
||||||
|
"object-shorthand": ["error", "methods"],
|
||||||
|
"one-var": "off",
|
||||||
|
"prefer-destructuring": "off",
|
||||||
|
"prefer-template": "error",
|
||||||
|
"sort-keys": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.mjs"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
globals: {
|
||||||
|
...globals.node
|
||||||
|
},
|
||||||
|
sourceType: "module"
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
...eslintPluginStylistic.configs["all-flat"].plugins
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...eslintPluginStylistic.configs["all-flat"].rules,
|
||||||
|
"@stylistic/array-element-newline": "off",
|
||||||
|
"@stylistic/indent": ["error", "tab"],
|
||||||
|
"@stylistic/padded-blocks": ["error", "never"],
|
||||||
|
"@stylistic/quote-props": ["error", "as-needed"],
|
||||||
|
"func-style": "off",
|
||||||
|
"import/namespace": "off",
|
||||||
|
"max-lines-per-function": ["error", 100],
|
||||||
|
"no-magic-numbers": "off",
|
||||||
|
"one-var": "off",
|
||||||
|
"prefer-destructuring": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["tests/configs/modules/weather/*.js"],
|
||||||
|
rules: {
|
||||||
|
"@stylistic/quotes": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ["config/**", "modules/**", "!modules/default/**", "js/positions.js"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default config;
|
@ -2,13 +2,14 @@ const path = require("node:path");
|
|||||||
const fs = require("node:fs");
|
const fs = require("node:fs");
|
||||||
const Ajv = require("ajv");
|
const Ajv = require("ajv");
|
||||||
const colors = require("ansis");
|
const colors = require("ansis");
|
||||||
|
const globals = require("globals");
|
||||||
const { Linter } = require("eslint");
|
const { Linter } = require("eslint");
|
||||||
|
|
||||||
const rootPath = path.resolve(`${__dirname}/../`);
|
const rootPath = path.resolve(`${__dirname}/../`);
|
||||||
const Log = require(`${rootPath}/js/logger.js`);
|
const Log = require(`${rootPath}/js/logger.js`);
|
||||||
const Utils = require(`${rootPath}/js/utils.js`);
|
const Utils = require(`${rootPath}/js/utils.js`);
|
||||||
|
|
||||||
const linter = new Linter();
|
const linter = new Linter({ configType: "flat" });
|
||||||
const ajv = new Ajv();
|
const ajv = new Ajv();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,16 +30,14 @@ function checkConfigFile () {
|
|||||||
|
|
||||||
// Check if file is present
|
// Check if file is present
|
||||||
if (fs.existsSync(configFileName) === false) {
|
if (fs.existsSync(configFileName) === false) {
|
||||||
Log.error(`File not found: ${configFileName}`);
|
throw new Error(`File not found: ${configFileName}\nNo config file present!`);
|
||||||
throw new Error("No config file present!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check permission
|
// Check permission
|
||||||
try {
|
try {
|
||||||
fs.accessSync(configFileName, fs.F_OK);
|
fs.accessSync(configFileName, fs.F_OK);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Log.error(error);
|
throw new Error(`${error}\nNo permission to access config file!`);
|
||||||
throw new Error("No permission to access config file!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate syntax of the configuration file.
|
// Validate syntax of the configuration file.
|
||||||
@ -47,30 +46,39 @@ function checkConfigFile () {
|
|||||||
// I'm not sure if all ever is utf-8
|
// I'm not sure if all ever is utf-8
|
||||||
const configFile = fs.readFileSync(configFileName, "utf-8");
|
const configFile = fs.readFileSync(configFileName, "utf-8");
|
||||||
|
|
||||||
// Explicitly tell linter that he might encounter es2024 syntax ("let config = {...}")
|
const errors = linter.verify(
|
||||||
const errors = linter.verify(configFile, {
|
configFile,
|
||||||
env: {
|
{
|
||||||
es2024: true
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
globals: {
|
||||||
|
...globals.node
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
configFileName
|
||||||
|
);
|
||||||
|
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
Log.info(colors.green("Your configuration file doesn't contain syntax errors :)"));
|
Log.info(colors.green("Your configuration file doesn't contain syntax errors :)"));
|
||||||
|
validateModulePositions(configFileName);
|
||||||
} else {
|
} else {
|
||||||
Log.error("Your configuration file contains syntax errors :(");
|
let errorMessage = "Your configuration file contains syntax errors :(";
|
||||||
|
|
||||||
for (const error of errors) {
|
for (const error of errors) {
|
||||||
Log.error(`Line ${error.line} column ${error.column}: ${error.message}`);
|
errorMessage += `\nLine ${error.line} column ${error.column}: ${error.message}`;
|
||||||
|
}
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateModulePositions (configFileName) {
|
||||||
Log.info("Checking modules structure configuration ...");
|
Log.info("Checking modules structure configuration ...");
|
||||||
|
|
||||||
const positionList = Utils.getModulePositions();
|
const positionList = Utils.getModulePositions();
|
||||||
|
|
||||||
// Make Ajv schema configuration of modules config
|
// Make Ajv schema configuration of modules config
|
||||||
// only scan "module" and "position"
|
// Only scan "module" and "position"
|
||||||
const schema = {
|
const schema = {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@ -103,16 +111,21 @@ function checkConfigFile () {
|
|||||||
} else {
|
} else {
|
||||||
const module = validate.errors[0].instancePath.split("/")[2];
|
const module = validate.errors[0].instancePath.split("/")[2];
|
||||||
const position = validate.errors[0].instancePath.split("/")[3];
|
const position = validate.errors[0].instancePath.split("/")[3];
|
||||||
|
let errorMessage = "This module configuration contains errors:";
|
||||||
Log.error("This module configuration contains errors:");
|
errorMessage += `\n${JSON.stringify(data.modules[module], null, 2)}`;
|
||||||
Log.error(`\n${JSON.stringify(data.modules[module], null, 2)}`);
|
|
||||||
if (position) {
|
if (position) {
|
||||||
Log.error(`${position}: ${validate.errors[0].message}`);
|
errorMessage += `\n${position}: ${validate.errors[0].message}`;
|
||||||
Log.error(`\n${JSON.stringify(validate.errors[0].params.allowedValues, null, 2).slice(1, -1)}`);
|
errorMessage += `\n${JSON.stringify(validate.errors[0].params.allowedValues, null, 2).slice(1, -1)}`;
|
||||||
} else {
|
} else {
|
||||||
Log.error(validate.errors[0].message);
|
errorMessage += validate.errors[0].message;
|
||||||
}
|
}
|
||||||
|
Log.error(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
checkConfigFile();
|
checkConfigFile();
|
||||||
|
} catch (error) {
|
||||||
|
Log.error(error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
1238
package-lock.json
generated
1238
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -59,7 +59,7 @@
|
|||||||
"ansis": "^3.3.2",
|
"ansis": "^3.3.2",
|
||||||
"console-stamp": "^3.1.2",
|
"console-stamp": "^3.1.2",
|
||||||
"envsub": "^4.1.0",
|
"envsub": "^4.1.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^9.11.1",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"express-ipfilter": "^1.3.2",
|
"express-ipfilter": "^1.3.2",
|
||||||
"feedme": "^2.0.2",
|
"feedme": "^2.0.2",
|
||||||
@ -75,13 +75,12 @@
|
|||||||
"systeminformation": "^5.23.5"
|
"systeminformation": "^5.23.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.11.1",
|
||||||
"@stylistic/eslint-plugin": "^2.8.0",
|
"@stylistic/eslint-plugin": "^2.8.0",
|
||||||
"cspell": "^8.14.4",
|
"cspell": "^8.14.4",
|
||||||
"eslint-plugin-import": "^2.30.0",
|
|
||||||
"eslint-plugin-jest": "^28.8.3",
|
"eslint-plugin-jest": "^28.8.3",
|
||||||
"eslint-plugin-jsdoc": "^50.2.4",
|
"eslint-plugin-jsdoc": "^50.2.4",
|
||||||
"eslint-plugin-package-json": "^0.15.3",
|
"eslint-plugin-package-json": "^0.15.3",
|
||||||
"eslint-plugin-unicorn": "^55.0.0",
|
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"husky": "^9.1.6",
|
"husky": "^9.1.6",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
@ -19,7 +19,7 @@ describe("server_functions tests", () => {
|
|||||||
},
|
},
|
||||||
text: fetchResponseHeadersText
|
text: fetchResponseHeadersText
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line
|
|
||||||
fetch = jest.fn();
|
fetch = jest.fn();
|
||||||
fetch.mockImplementation(() => fetchResponse);
|
fetch.mockImplementation(() => fetchResponse);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user