Skip to content

Commit

Permalink
Switch to ESLint v9 and flat config (#3558)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
KristjanESPERANTO authored Sep 25, 2024
1 parent 5ffdf9a commit d318768
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 1,149 deletions.
3 changes: 0 additions & 3 deletions .eslintignore

This file was deleted.

94 changes: 0 additions & 94 deletions .eslintrc.json

This file was deleted.

2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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`.

Expand Down
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
*.js
.eslintignore
*.mjs
.husky/pre-commit
.prettierignore
/config
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ _This release is scheduled to be released on 2024-10-01._

- [core] removed installer only files (#3492)
- [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

Expand All @@ -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] Detail optimizations in `config_check.js`
- [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

Expand Down
121 changes: 121 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -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;
57 changes: 35 additions & 22 deletions js/check_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ const path = require("node:path");
const fs = require("node:fs");
const Ajv = require("ajv");
const colors = require("ansis");
const globals = require("globals");
const { Linter } = require("eslint");

const rootPath = path.resolve(`${__dirname}/../`);
const Log = require(`${rootPath}/js/logger.js`);
const Utils = require(`${rootPath}/js/utils.js`);

const linter = new Linter();
const linter = new Linter({ configType: "flat" });
const ajv = new Ajv();

/**
Expand All @@ -29,16 +30,14 @@ function checkConfigFile () {

// Check if file is present
if (fs.existsSync(configFileName) === false) {
Log.error(`File not found: ${configFileName}`);
throw new Error("No config file present!");
throw new Error(`File not found: ${configFileName}\nNo config file present!`);
}

// Check permission
try {
fs.accessSync(configFileName, fs.F_OK);
} catch (error) {
Log.error(error);
throw new Error("No permission to access config file!");
throw new Error(`${error}\nNo permission to access config file!`);
}

// Validate syntax of the configuration file.
Expand All @@ -47,30 +46,39 @@ function checkConfigFile () {
// I'm not sure if all ever is utf-8
const configFile = fs.readFileSync(configFileName, "utf-8");

// Explicitly tell linter that he might encounter es2024 syntax ("let config = {...}")
const errors = linter.verify(configFile, {
env: {
es2024: true
}
});
const errors = linter.verify(
configFile,
{
languageOptions: {
ecmaVersion: "latest",
globals: {
...globals.node
}
}
},
configFileName
);

if (errors.length === 0) {
Log.info(colors.green("Your configuration file doesn't contain syntax errors :)"));
validateModulePositions(configFileName);
} else {
Log.error("Your configuration file contains syntax errors :(");
let errorMessage = "Your configuration file contains syntax 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}`;
}
process.exit(1);
throw new Error(errorMessage);
}
}

function validateModulePositions (configFileName) {
Log.info("Checking modules structure configuration ...");

const positionList = Utils.getModulePositions();

// Make Ajv schema configuration of modules config
// only scan "module" and "position"
// Only scan "module" and "position"
const schema = {
type: "object",
properties: {
Expand Down Expand Up @@ -103,16 +111,21 @@ function checkConfigFile () {
} else {
const module = validate.errors[0].instancePath.split("/")[2];
const position = validate.errors[0].instancePath.split("/")[3];

Log.error("This module configuration contains errors:");
Log.error(`\n${JSON.stringify(data.modules[module], null, 2)}`);
let errorMessage = "This module configuration contains errors:";
errorMessage += `\n${JSON.stringify(data.modules[module], null, 2)}`;
if (position) {
Log.error(`${position}: ${validate.errors[0].message}`);
Log.error(`\n${JSON.stringify(validate.errors[0].params.allowedValues, null, 2).slice(1, -1)}`);
errorMessage += `\n${position}: ${validate.errors[0].message}`;
errorMessage += `\n${JSON.stringify(validate.errors[0].params.allowedValues, null, 2).slice(1, -1)}`;
} else {
Log.error(validate.errors[0].message);
errorMessage += validate.errors[0].message;
}
Log.error(errorMessage);
}
}

checkConfigFile();
try {
checkConfigFile();
} catch (error) {
Log.error(error.message);
process.exit(1);
}
Loading

0 comments on commit d318768

Please sign in to comment.