Files
MagicMirror/eslint.config.mjs
Kristjan ESPERANTO 2850e14172 Unify linting: replace Stylelint and markdownlint with ESLint (#4148)
We were running three separate lint tools (ESLint, Stylelint,
markdownlint-cli2) and a separate Prettier step. This PR consolidates
everything: `@eslint/css` and `@eslint/markdown` bring CSS and Markdown
linting into ESLint, so one tool now covers JS, CSS, and Markdown. The
Prettier step got merged into the lint scripts too, so there's now just
`test:lint` to check things and `lint:fix` to fix them.

Outcome: fewer tools to maintain, simpler/fewer scripts, fewer config
files.

As a side effect, the dependabot alerts
https://github.com/MagicMirrorOrg/MagicMirror/security/dependabot/140
and
https://github.com/MagicMirrorOrg/MagicMirror/security/dependabot/141
are resolved, as we no longer use the vulnerable `fast-uri` package
(which was a dependency of `stylelint`).
2026-05-09 12:01:18 +02:00

197 lines
5.7 KiB
JavaScript

import {defineConfig, globalIgnores} from "eslint/config";
import css from "@eslint/css";
import globals from "globals";
import {flatConfigs as importX} from "eslint-plugin-import-x";
import js from "@eslint/js";
import jsdocPlugin from "eslint-plugin-jsdoc";
import markdown from "@eslint/markdown";
import {configs as packageJsonConfigs} from "eslint-plugin-package-json";
import playwright from "eslint-plugin-playwright";
import stylistic from "@stylistic/eslint-plugin";
import vitest from "@vitest/eslint-plugin";
export default defineConfig([
globalIgnores(["config/**", "modules/**/*", "js/positions.js", "tests/configs/config_variables.js"]),
{
files: ["**/*.css"],
language: "css/css",
plugins: {css},
extends: ["css/recommended"],
rules: {
"css/no-invalid-properties": ["error", {allowUnknownVariables: true}],
"css/use-baseline": "off"
}
},
{
files: ["**/*.md"],
plugins: {markdown},
language: "markdown/gfm",
extends: ["markdown/recommended"],
rules: {
"markdown/no-missing-label-refs": "off"
}
},
{
files: ["**/*.js"],
languageOptions: {
ecmaVersion: "latest",
globals: {
...globals.browser,
...globals.node,
Log: "readonly",
MM: "readonly",
Module: "readonly",
config: "readonly",
moment: "readonly"
}
},
extends: [importX.recommended, js.configs.recommended, jsdocPlugin.configs["flat/recommended"], stylistic.configs.all],
rules: {
"@stylistic/array-element-newline": ["error", "consistent"],
"@stylistic/arrow-parens": ["error", "always"],
"@stylistic/brace-style": "off",
"@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",
"dot-notation": "error",
eqeqeq: ["error", "always", {null: "ignore"}],
"id-length": "off",
"import-x/extensions": "error",
"import-x/newline-after-import": "error",
"import-x/order": "error",
"init-declarations": "off",
"max-lines-per-function": ["warn", 400],
"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-unneeded-ternary": "error",
"no-useless-return": "error",
"no-warning-comments": "off",
"object-shorthand": ["error", "methods"],
"one-var": "off",
"prefer-template": "error",
"require-await": "error",
"sort-keys": "off"
}
},
{
files: ["**/*.js"],
ignores: [
"clientonly/index.js",
"js/logger.js",
"tests/**/*.js"
],
rules: {"no-console": "error"}
},
{
files: ["**/package.json"],
extends: [packageJsonConfigs.recommended]
},
{
files: ["**/*.mjs"],
languageOptions: {
ecmaVersion: "latest",
globals: {
...globals.node
},
sourceType: "module"
},
extends: [importX.recommended, js.configs.all, stylistic.configs.all],
rules: {
"@stylistic/array-element-newline": "off",
"@stylistic/indent": ["error", "tab"],
"@stylistic/object-property-newline": ["error", {allowAllPropertiesOnSameLine: true}],
"@stylistic/padded-blocks": ["error", "never"],
"@stylistic/quote-props": ["error", "as-needed"],
"import-x/no-unresolved": ["error", {ignore: ["eslint/config"]}],
"max-lines-per-function": ["error", 100],
"no-magic-numbers": "off",
"one-var": ["error", "never"],
"sort-keys": "off"
}
},
{
files: ["tests/**/*.js"],
languageOptions: {
globals: {
...vitest.environments.env.globals
}
},
extends: [vitest.configs.recommended],
rules: {
"vitest/consistent-test-it": "error",
"vitest/expect-expect": [
"error",
{
assertFunctionNames: [
"expect",
"testElementLength",
"testTextContain",
"doTest",
"runAnimationTest",
"waitForAnimationClass",
"assertNoAnimationWithin"
]
}
],
"vitest/max-nested-describe": ["error", {max: 3}],
"vitest/prefer-to-be": "error",
"vitest/prefer-to-have-length": "error",
"max-lines-per-function": "off"
}
},
{
files: ["tests/unit/modules/default/weather/providers/*.js"],
rules: {
"import-x/namespace": "off",
"import-x/named": "off",
"import-x/default": "off",
"import-x/extensions": "off"
}
},
{
files: ["tests/configs/modules/weather/*.js"],
rules: {
"@stylistic/quotes": "off"
}
},
{
files: ["tests/e2e/**/*.js"],
extends: [playwright.configs["flat/recommended"]],
rules: {
/*
* Tests use Vitest-style plain beforeAll()/afterAll() calls, not Playwright's
* test.beforeAll() style. The rule incorrectly treats all plain hook calls
* as the same unnamed type, flagging the second hook as a duplicate.
*/
"playwright/no-duplicate-hooks": "off",
"playwright/no-standalone-expect": "off"
}
}
]);