blob: 8181f0b38b947f80ee8d8429bb02959cfdc48db3 [file] [edit]
import eslintConfigXo from 'eslint-config-xo'
import unicorn from 'eslint-plugin-unicorn'
import importXPlugin from 'eslint-plugin-import-x'
import html from 'eslint-plugin-html'
import markdown from 'eslint-plugin-markdown'
import globals from 'globals'
const importRules = {
'import/extensions': [
'error',
'ignorePackages',
{
js: 'always'
}
],
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-absolute-path': 'error',
'import/no-amd': 'error',
'import/no-cycle': [
'error',
{
ignoreExternal: true
}
],
'import/no-duplicates': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-mutable-exports': 'error',
'import/no-named-as-default': 'error',
'import/no-named-as-default-member': 'error',
'import/no-named-default': 'error',
'import/no-self-import': 'error',
'import/no-unassigned-import': [
'error'
],
'import/no-useless-path-segments': 'error',
'import/order': 'error'
}
const localRules = {
'arrow-body-style': 'off',
'capitalized-comments': 'off',
'@stylistic/comma-dangle': [
'error',
'never'
],
...importRules,
'@stylistic/indent': [
'error',
2,
{
MemberExpression: 'off',
SwitchCase: 1
}
],
'logical-assignment-operators': 'off',
'max-params': [
'warn',
5
],
'@stylistic/multiline-ternary': [
'error',
'always-multiline'
],
'new-cap': [
'error',
{
properties: false
}
],
'no-console': 'error',
'no-negated-condition': 'off',
'@stylistic/object-curly-spacing': [
'error',
'always'
],
'@stylistic/operator-linebreak': [
'error',
'after'
],
'prefer-object-has-own': 'off',
'prefer-template': 'error',
'@stylistic/semi': [
'error',
'never'
],
strict: 'error',
'unicorn/explicit-length-check': 'off',
'unicorn/filename-case': 'off',
'unicorn/no-anonymous-default-export': 'off',
'unicorn/no-array-callback-reference': 'off',
'unicorn/no-array-method-this-argument': 'off',
'unicorn/no-null': 'off',
'unicorn/no-typeof-undefined': 'off',
'unicorn/no-unused-properties': 'error',
'unicorn/numeric-separators-style': 'off',
'unicorn/prefer-array-flat': 'off',
'unicorn/prefer-at': 'error',
'unicorn/prefer-dom-node-dataset': 'off',
'unicorn/prefer-global-this': 'off',
'unicorn/prefer-module': 'off',
'unicorn/prefer-query-selector': 'off',
'unicorn/prefer-spread': 'error',
'unicorn/prefer-string-raw': 'off',
'unicorn/prefer-string-replace-all': 'off',
'unicorn/prefer-structured-clone': 'off',
'unicorn/prevent-abbreviations': 'off',
// Rules new/changed in ESLint 10 / updated plugins — disable to preserve old behaviour
'xo/import-specifier-newline': 'off',
'@eslint-community/eslint-comments/require-description': 'off',
'import-x/no-anonymous-default-export': 'off',
'import-x/no-extraneous-dependencies': 'off',
'import-x/no-unassigned-import': 'off',
'n/file-extension-in-import': 'off',
'n/no-deprecated-api': 'off',
'n/no-extraneous-import': 'off',
'n/prefer-global/buffer': 'off',
'n/prefer-global/process': 'off',
'n/prefer-promises/fs': 'off',
'no-shadow': 'off',
'require-unicode-regexp': 'off',
'@stylistic/indent-binary-ops': 'off',
'@stylistic/curly-newline': 'off',
'@stylistic/function-paren-newline': 'off',
'unicorn/better-dom-traversing': 'off',
'unicorn/class-reference-in-static-methods': 'off',
'unicorn/consistent-boolean-name': 'off',
'unicorn/consistent-class-member-order': 'off',
'unicorn/consistent-compound-words': 'off',
'unicorn/consistent-conditional-object-spread': 'off',
'unicorn/default-export-style': 'off',
'unicorn/dom-node-dataset': 'off',
'unicorn/explicit-timer-delay': 'off',
'unicorn/logical-assignment-operators': 'off',
'unicorn/max-nested-calls': 'off',
'unicorn/name-replacements': 'off',
'unicorn/no-break-in-nested-loop': 'off',
'unicorn/no-computed-property-existence-check': 'off',
'unicorn/no-declarations-before-early-exit': 'off',
'unicorn/no-duplicate-if-branches': 'off',
'unicorn/no-for-each': 'off',
'unicorn/no-global-object-property-assignment': 'off',
'unicorn/no-incorrect-query-selector': 'off',
'unicorn/no-return-array-push': 'off',
'unicorn/no-this-outside-of-class': 'off',
'unicorn/no-unnecessary-splice': 'off',
'unicorn/no-unused-array-method-return': 'off',
'unicorn/no-unreadable-for-of-expression': 'off',
'unicorn/no-subtraction-comparison': 'off',
'unicorn/prefer-add-event-listener-options': 'off',
'unicorn/prefer-array-from-map': 'off',
'unicorn/prefer-await': 'off',
'unicorn/prefer-classlist-toggle': 'off',
'unicorn/prefer-continue': 'off',
'unicorn/prefer-early-return': 'off',
'unicorn/prefer-global-number-constants': 'off',
'unicorn/prefer-includes-over-repeated-comparisons': 'off',
'unicorn/prefer-iterator-to-array': 'off',
'unicorn/prefer-minimal-ternary': 'off',
'unicorn/prefer-number-coercion': 'off',
'unicorn/prefer-object-define-properties': 'off',
'unicorn/prefer-object-iterable-methods': 'off',
'unicorn/prefer-private-class-fields': 'off',
'unicorn/prefer-scoped-selector': 'off',
'unicorn/prefer-split-limit': 'off',
'unicorn/prefer-string-repeat': 'off',
'unicorn/prefer-ternary': 'off',
'unicorn/prefer-unicode-code-point-escapes': 'off',
'unicorn/no-array-sort': 'off',
'unicorn/no-top-level-assignment-in-function': 'off',
'unicorn/no-top-level-side-effects': 'off',
'unicorn/no-useless-coercion': 'off',
'unicorn/no-useless-template-literals': 'off',
'unicorn/no-unsafe-string-replacement': 'off',
'unicorn/require-array-sort-compare': 'off',
'unicorn/require-css-escape': 'off'
}
// Base config: xo/browser + unicorn + import-x for all JS/HTML/MD files
const xoBrowserConfigs = eslintConfigXo({ browser: true, space: false })
const xoBrowserConfig = xoBrowserConfigs.find(config => config.name === 'xo/base') ?? xoBrowserConfigs[0]
const unicornConfig = unicorn.configs['flat/recommended']
const eslintConfig = [
// Global ignores (replaces .eslintignore)
{
ignores: [
'**/*.min.js',
'**/dist/**',
'**/vendor/**',
'_site/**',
'site/public/**',
'js/coverage/**',
'site/static/sw.js',
'site/static/docs/**/assets/sw.js',
'site/layouts/partials/**',
// TypeScript, declaration and Astro files are not linted
'**/*.ts',
'**/*.d.ts',
'**/*.astro',
// Meteor metadata file (uses unsupported eslint-env comment)
'package.js',
// Claude worktrees
'.claude/worktrees/**'
]
},
// Base config for all JS files
{
...xoBrowserConfig,
plugins: {
...xoBrowserConfig.plugins,
unicorn,
import: importXPlugin
},
rules: {
...xoBrowserConfig.rules,
...unicornConfig.rules,
...localRules
}
},
// build/** — node environment, no browser
{
files: ['build/**'],
languageOptions: {
globals: {
...globals.node
},
sourceType: 'module'
},
rules: {
'no-console': 'off',
'unicorn/prefer-top-level-await': 'off'
}
},
// js/** — ESM source
{
files: ['js/**'],
languageOptions: {
sourceType: 'module'
}
},
// CJS files — Node.js environment, script mode
{
files: ['**/*.cjs'],
languageOptions: {
globals: {
...globals.node,
...globals.commonjs
},
sourceType: 'script'
},
rules: {
strict: 'off',
'unicorn/prefer-module': 'off'
}
},
// js/tests/*.js + integration rollup configs — browser ESM
{
files: [
'js/tests/integration/bundle*.js',
'js/tests/integration/rollup*.js'
],
languageOptions: {
globals: {
...globals.browser
},
sourceType: 'module'
}
},
// js/tests/unit/** — jasmine
{
files: ['js/tests/unit/**'],
languageOptions: {
globals: {
...globals.jasmine
}
},
rules: {
'no-console': 'off',
'unicorn/consistent-function-scoping': 'off',
'unicorn/no-useless-undefined': 'off',
'unicorn/prefer-add-event-listener': 'off'
}
},
// js/tests/visual/** — HTML files with inline scripts
{
files: ['js/tests/visual/**'],
plugins: {
html
},
settings: {
'html/html-extensions': ['.html']
},
rules: {
'no-console': 'off',
'no-new': 'off',
'unicorn/no-array-for-each': 'off'
}
},
// scss/tests/** — node, script mode
{
files: ['scss/tests/**'],
languageOptions: {
globals: {
...globals.node
},
sourceType: 'script'
}
},
// site/** — browser, script mode
{
files: ['site/**'],
languageOptions: {
globals: {
...globals.browser
},
sourceType: 'script'
},
rules: {
'no-new': 'off',
'unicorn/no-array-for-each': 'off',
strict: 'off'
}
},
// site specific module files
{
files: [
'site/src/assets/application.js',
'site/src/assets/partials/*.js',
'site/src/assets/search.js',
'site/src/assets/stackblitz.js',
'site/src/plugins/*.js'
],
languageOptions: {
sourceType: 'module'
},
// These files may have eslint-disable directives for the old import plugin
linterOptions: {
reportUnusedDisableDirectives: 'off'
}
},
// site/static/**/*.js — older scripts with 'use strict', not modules
{
files: ['site/static/**/*.js'],
rules: {
strict: 'off'
}
},
// site cheatsheet/sidebars — module, no-unresolved off
{
files: [
'site/src/assets/examples/cheatsheet/cheatsheet.js',
'site/src/assets/examples/sidebars/sidebars.js'
],
languageOptions: {
sourceType: 'module'
},
rules: {
'import/no-unresolved': 'off'
}
},
// Markdown files — use processor
...markdown.configs.recommended,
// Markdown code blocks
{
files: ['**/*.md/*.js', '**/*.md/*.mjs'],
rules: {
'unicorn/prefer-node-protocol': 'off'
}
}
]
export default eslintConfig