├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── config.yml ├── security.md └── workflows │ └── main.yml ├── .gitignore ├── .npmrc ├── browser.js ├── eslint.config.js ├── index.js ├── license ├── package.json ├── readme.md ├── space-browser.js ├── space.js └── test └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: ❌ Do not open new issues on this repo 4 | url: https://github.com/xojs/xo/issues 5 | about: Use the XO issue tracker instead of this one. 6 | -------------------------------------------------------------------------------- /.github/security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 22 14 | - 20 15 | - 18 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm install 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import confusingBrowserGlobals from 'confusing-browser-globals'; 3 | import eslintConfigXo from './index.js'; 4 | 5 | const [config] = eslintConfigXo; 6 | 7 | export default [ 8 | { 9 | ...config, 10 | languageOptions: { 11 | ...config.languageOptions, 12 | globals: { 13 | ...globals.es2021, 14 | ...globals.browser, 15 | }, 16 | }, 17 | rules: { 18 | ...config.rules, 19 | 'no-restricted-globals': [ 20 | 'error', 21 | ...confusingBrowserGlobals, 22 | ], 23 | }, 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | export {default} from './index.js'; 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import stylistic from '@stylistic/eslint-plugin'; 3 | import json from '@eslint/json'; 4 | import css from '@eslint/css'; 5 | 6 | const rules = { 7 | '@stylistic/comma-dangle': [ 8 | 'error', 9 | 'always-multiline', 10 | ], 11 | 'for-direction': 'error', 12 | 'getter-return': 'error', 13 | 'no-async-promise-executor': 'error', 14 | 'no-await-in-loop': 'error', 15 | 'no-compare-neg-zero': 'error', 16 | 'no-cond-assign': 'error', 17 | 'no-constant-condition': 'error', 18 | 'no-control-regex': 'error', 19 | 'no-debugger': 'error', 20 | 'no-dupe-args': 'error', 21 | 'no-dupe-else-if': 'error', 22 | 'no-dupe-keys': 'error', 23 | 'no-duplicate-case': 'error', 24 | 'no-empty-character-class': 'error', 25 | 'no-empty': [ 26 | 'error', 27 | { 28 | allowEmptyCatch: true, 29 | }, 30 | ], 31 | 'no-empty-static-block': 'error', 32 | 'no-ex-assign': 'error', 33 | 'no-extra-boolean-cast': 'error', 34 | // Disabled because of https://github.com/xojs/eslint-config-xo/pull/87 35 | // 'no-extra-boolean-cast': [ 36 | // 'error', 37 | // { 38 | // enforceForInnerExpressions: true 39 | // }, 40 | // ], 41 | // Disabled because of https://github.com/eslint/eslint/issues/6028 42 | // '@stylistic/no-extra-parens': [ 43 | // 'error', 44 | // 'all', 45 | // { 46 | // conditionalAssign: false, 47 | // nestedBinaryExpressions: false, 48 | // ignoreJSX: 'multi-line' 49 | // } 50 | // ], 51 | '@stylistic/no-extra-semi': 'error', 52 | 'no-func-assign': 'error', 53 | 'no-import-assign': 'error', 54 | 'no-inner-declarations': 'error', 55 | 'no-invalid-regexp': 'error', 56 | 'no-irregular-whitespace': 'error', 57 | 'no-loss-of-precision': 'error', 58 | 'no-misleading-character-class': 'error', 59 | 'no-obj-calls': 'error', 60 | 'no-promise-executor-return': 'error', 61 | 'no-prototype-builtins': 'error', 62 | 'no-regex-spaces': 'error', 63 | 'no-setter-return': 'error', 64 | 'no-sparse-arrays': 'error', 65 | 'no-template-curly-in-string': 'error', 66 | 'no-unreachable': 'error', 67 | 'no-unreachable-loop': 'error', 68 | 'no-unsafe-finally': 'error', 69 | 'no-unsafe-negation': [ 70 | 'error', 71 | { 72 | enforceForOrderingRelations: true, 73 | }, 74 | ], 75 | 'no-unsafe-optional-chaining': [ 76 | 'error', 77 | { 78 | disallowArithmeticOperators: true, 79 | }, 80 | ], 81 | 'no-useless-backreference': 'error', 82 | 'use-isnan': 'error', 83 | 'valid-typeof': [ 84 | 'error', 85 | { 86 | requireStringLiterals: false, 87 | }, 88 | ], 89 | 'no-unexpected-multiline': 'error', 90 | 'accessor-pairs': [ 91 | 'error', 92 | { 93 | enforceForClassMembers: true, 94 | }, 95 | ], 96 | 'array-callback-return': [ 97 | 'error', 98 | { 99 | allowImplicit: true, 100 | }, 101 | ], 102 | 'block-scoped-var': 'error', 103 | complexity: 'warn', 104 | curly: 'error', 105 | 'default-case': 'error', 106 | 'default-case-last': 'error', 107 | 'dot-notation': 'error', 108 | '@stylistic/curly-newline': [ 109 | 'error', 110 | 'always', 111 | ], 112 | '@stylistic/dot-location': [ 113 | 'error', 114 | 'property', 115 | ], 116 | eqeqeq: 'error', 117 | 'grouped-accessor-pairs': [ 118 | 'error', 119 | 'getBeforeSet', 120 | ], 121 | 'guard-for-in': 'error', 122 | 'no-alert': 'error', 123 | 'no-caller': 'error', 124 | 'no-case-declarations': 'error', 125 | 'no-constructor-return': 'error', 126 | 'no-else-return': [ 127 | 'error', 128 | { 129 | allowElseIf: false, 130 | }, 131 | ], 132 | 'no-empty-pattern': 'error', 133 | 'no-eq-null': 'error', 134 | 'no-eval': 'error', 135 | 'no-extend-native': 'error', 136 | 'no-extra-bind': 'error', 137 | 'no-extra-label': 'error', 138 | 'no-fallthrough': 'error', 139 | '@stylistic/no-floating-decimal': 'error', 140 | 'no-global-assign': 'error', 141 | 'no-implicit-coercion': 'error', 142 | 'no-implicit-globals': 'error', 143 | 'no-implied-eval': 'error', 144 | 'no-iterator': 'error', 145 | 'no-labels': 'error', 146 | 'no-lone-blocks': 'error', 147 | '@stylistic/no-multi-spaces': 'error', 148 | 'no-multi-str': 'error', 149 | 'no-new-func': 'error', 150 | 'no-new-wrappers': 'error', 151 | 'no-nonoctal-decimal-escape': 'error', 152 | 'no-object-constructor': 'error', 153 | 'no-new': 'error', 154 | 'no-octal-escape': 'error', 155 | 'no-octal': 'error', 156 | 'no-proto': 'error', 157 | 'no-redeclare': 'error', 158 | 'no-return-assign': [ 159 | 'error', 160 | 'always', 161 | ], 162 | 'no-return-await': 'error', 163 | 'no-script-url': 'error', 164 | 'no-self-assign': [ 165 | 'error', 166 | { 167 | props: true, 168 | }, 169 | ], 170 | 'no-self-compare': 'error', 171 | 'no-sequences': 'error', 172 | 'no-throw-literal': 'error', 173 | 'no-unmodified-loop-condition': 'error', 174 | 'no-unused-expressions': [ 175 | 'error', 176 | { 177 | enforceForJSX: true, 178 | }, 179 | ], 180 | 'no-unused-labels': 'error', 181 | 'no-useless-call': 'error', 182 | 'no-useless-catch': 'error', 183 | 'no-useless-concat': 'error', 184 | 'no-useless-escape': 'error', 185 | 'no-useless-return': 'error', 186 | 'no-void': 'error', 187 | 'no-warning-comments': 'warn', 188 | 'no-with': 'error', 189 | 190 | // Disabled for now as Firefox doesn't support named capture groups and I'm tired of getting issues about the use of named capture groups... 191 | // 'prefer-named-capture-group': 'error' 192 | 193 | 'prefer-promise-reject-errors': [ 194 | 'error', 195 | { 196 | allowEmptyReject: true, 197 | }, 198 | ], 199 | 'prefer-regex-literals': [ 200 | 'error', 201 | { 202 | disallowRedundantWrapping: true, 203 | }, 204 | ], 205 | radix: 'error', 206 | 207 | // Disabled for now as it causes too much churn 208 | // TODO: Enable it in the future when I have time to deal with 209 | // the churn and the rule is stable and has an autofixer. 210 | // Still doesn't have a fixer as of ESLint 7.24.0. 211 | // 'require-unicode-regexp': 'error', 212 | 213 | '@stylistic/wrap-iife': [ 214 | 'error', 215 | 'inside', 216 | { 217 | functionPrototypeMethods: true, 218 | }, 219 | ], 220 | yoda: 'error', 221 | 'no-delete-var': 'error', 222 | 'no-label-var': 'error', 223 | 'no-restricted-globals': [ 224 | 'error', 225 | 'event', 226 | // TODO: Enable this in 2025. 227 | // { 228 | // name: 'Buffer', 229 | // message: 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer', 230 | // }, 231 | { 232 | name: 'atob', 233 | message: 'This API is deprecated. Use https://github.com/sindresorhus/uint8array-extras instead.', 234 | }, 235 | { 236 | name: 'btoa', 237 | message: 'This API is deprecated. Use https://github.com/sindresorhus/uint8array-extras instead.', 238 | }, 239 | ], 240 | 'no-shadow-restricted-names': 'error', 241 | 'no-undef-init': 'error', 242 | 'no-undef': [ 243 | 'error', 244 | { 245 | typeof: true, 246 | }, 247 | ], 248 | 'no-unused-vars': [ 249 | 'error', 250 | { 251 | vars: 'all', 252 | varsIgnorePattern: /^_/.source, 253 | args: 'after-used', 254 | ignoreRestSiblings: true, 255 | argsIgnorePattern: /^_/.source, 256 | caughtErrors: 'all', 257 | caughtErrorsIgnorePattern: /^_$/.source, 258 | }, 259 | ], 260 | 'no-buffer-constructor': 'error', 261 | 'no-restricted-imports': [ 262 | 'error', 263 | 'domain', 264 | 'freelist', 265 | 'smalloc', 266 | 'punycode', 267 | 'sys', 268 | 'querystring', 269 | 'colors', 270 | // TODO: Enable this in 2025. 271 | // { 272 | // name: 'buffer', 273 | // message: 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer', 274 | // }, 275 | // { 276 | // name: 'node:buffer', 277 | // message: 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer', 278 | // }, 279 | ], 280 | '@stylistic/array-bracket-newline': [ 281 | 'error', 282 | 'consistent', 283 | ], 284 | '@stylistic/array-bracket-spacing': [ 285 | 'error', 286 | 'never', 287 | ], 288 | '@stylistic/array-element-newline': [ 289 | 'error', 290 | 'consistent', 291 | ], 292 | '@stylistic/brace-style': [ 293 | 'error', 294 | '1tbs', 295 | { 296 | allowSingleLine: false, 297 | }, 298 | ], 299 | camelcase: [ 300 | 'error', 301 | { 302 | properties: 'always', 303 | }, 304 | ], 305 | 'capitalized-comments': [ 306 | 'error', 307 | 'always', 308 | { 309 | // You can also ignore this rule by wrapping the first word in quotes. 310 | // c8 => https://github.com/bcoe/c8 311 | ignorePattern: /pragma|ignore|prettier-ignore|biome-ignore|webpack\w+:|c8|v8|type-coverage:/.source, 312 | ignoreInlineComments: true, 313 | ignoreConsecutiveComments: true, 314 | }, 315 | ], 316 | '@stylistic/comma-spacing': [ 317 | 'error', 318 | { 319 | before: false, 320 | after: true, 321 | }, 322 | ], 323 | '@stylistic/comma-style': [ 324 | 'error', 325 | 'last', 326 | ], 327 | '@stylistic/computed-property-spacing': [ 328 | 'error', 329 | 'never', 330 | { 331 | enforceForClassMembers: true, 332 | }, 333 | ], 334 | '@stylistic/eol-last': 'error', 335 | '@stylistic/function-call-spacing': [ 336 | 'error', 337 | 'never', 338 | ], 339 | '@stylistic/function-paren-newline': [ 340 | 'error', 341 | 'multiline', 342 | ], 343 | 'func-name-matching': [ 344 | 'error', 345 | { 346 | considerPropertyDescriptor: true, 347 | }, 348 | ], 349 | 'func-names': [ 350 | 'error', 351 | 'never', 352 | ], 353 | '@stylistic/function-call-argument-newline': [ 354 | 'error', 355 | 'consistent', 356 | ], 357 | '@stylistic/indent': [ 358 | 'error', 359 | 'tab', 360 | { 361 | SwitchCase: 1, 362 | }, 363 | ], 364 | '@stylistic/jsx-quotes': [ 365 | 'error', 366 | 'prefer-single', 367 | ], 368 | '@stylistic/key-spacing': [ 369 | 'error', 370 | { 371 | beforeColon: false, 372 | afterColon: true, 373 | }, 374 | ], 375 | '@stylistic/keyword-spacing': 'error', 376 | '@stylistic/linebreak-style': [ 377 | process.platform === 'win32' ? 'off' : 'error', 378 | 'unix', 379 | ], 380 | '@stylistic/lines-between-class-members': [ 381 | 'error', 382 | { 383 | enforce: [ 384 | { 385 | blankLine: 'always', 386 | prev: '*', 387 | next: 'method', 388 | }, 389 | { 390 | blankLine: 'always', 391 | prev: 'method', 392 | next: 'field', 393 | }, 394 | { 395 | blankLine: 'never', 396 | prev: 'field', 397 | next: 'field', 398 | }, 399 | ], 400 | }, 401 | ], 402 | 'logical-assignment-operators': [ 403 | 'error', 404 | 'always', 405 | { 406 | enforceForIfStatements: true, 407 | }, 408 | ], 409 | 'max-depth': 'warn', 410 | '@stylistic/max-len': [ 411 | 'warn', 412 | { 413 | code: 200, 414 | ignoreComments: true, 415 | ignoreUrls: true, 416 | }, 417 | ], 418 | 'max-lines': [ 419 | 'warn', 420 | { 421 | max: 1500, 422 | skipComments: true, 423 | }, 424 | ], 425 | 'max-nested-callbacks': [ 426 | 'warn', 427 | 4, 428 | ], 429 | 'max-params': [ 430 | 'warn', 431 | { 432 | max: 4, 433 | }, 434 | ], 435 | '@stylistic/max-statements-per-line': 'error', 436 | 'new-cap': [ 437 | 'error', 438 | { 439 | newIsCap: true, 440 | capIsNew: true, 441 | }, 442 | ], 443 | '@stylistic/multiline-ternary': [ 444 | 'error', 445 | 'always-multiline', 446 | ], 447 | '@stylistic/new-parens': 'error', 448 | 'no-array-constructor': 'error', 449 | 'no-bitwise': 'error', 450 | 'no-lonely-if': 'error', 451 | '@stylistic/no-mixed-operators': 'error', 452 | '@stylistic/no-mixed-spaces-and-tabs': 'error', 453 | 'no-multi-assign': 'error', 454 | '@stylistic/no-multiple-empty-lines': [ 455 | 'error', 456 | { 457 | max: 1, 458 | }, 459 | ], 460 | 'no-negated-condition': 'error', 461 | '@stylistic/no-whitespace-before-property': 'error', 462 | '@stylistic/no-trailing-spaces': 'error', 463 | 'no-unneeded-ternary': 'error', 464 | '@stylistic/object-curly-spacing': [ 465 | 'error', 466 | 'never', 467 | ], 468 | '@stylistic/object-curly-newline': [ 469 | 'error', 470 | { 471 | ObjectExpression: { 472 | multiline: true, 473 | minProperties: 4, 474 | consistent: true, 475 | }, 476 | ObjectPattern: { 477 | multiline: true, 478 | consistent: true, 479 | }, 480 | ImportDeclaration: { 481 | multiline: true, 482 | minProperties: 4, 483 | consistent: true, 484 | }, 485 | ExportDeclaration: { 486 | multiline: true, 487 | minProperties: 4, 488 | consistent: true, 489 | }, 490 | }, 491 | ], 492 | 'one-var': [ 493 | 'error', 494 | 'never', 495 | ], 496 | '@stylistic/one-var-declaration-per-line': 'error', 497 | 'operator-assignment': [ 498 | 'error', 499 | 'always', 500 | ], 501 | '@stylistic/operator-linebreak': [ 502 | 'error', 503 | 'before', 504 | ], 505 | '@stylistic/padded-blocks': [ 506 | 'error', 507 | 'never', 508 | { 509 | allowSingleLineBlocks: false, 510 | }, 511 | ], 512 | '@stylistic/padding-line-between-statements': [ 513 | 'error', 514 | { 515 | blankLine: 'always', 516 | prev: 'multiline-block-like', 517 | next: '*', 518 | }, 519 | ], 520 | 'prefer-exponentiation-operator': 'error', 521 | 'prefer-object-spread': 'error', 522 | '@stylistic/quote-props': [ 523 | 'error', 524 | 'as-needed', 525 | ], 526 | '@stylistic/quotes': [ 527 | 'error', 528 | 'single', 529 | ], 530 | '@stylistic/semi-spacing': [ 531 | 'error', 532 | { 533 | before: false, 534 | after: true, 535 | }, 536 | ], 537 | '@stylistic/semi-style': [ 538 | 'error', 539 | 'last', 540 | ], 541 | '@stylistic/semi': [ 542 | 'error', 543 | 'always', 544 | ], 545 | '@stylistic/space-before-blocks': [ 546 | 'error', 547 | 'always', 548 | ], 549 | '@stylistic/space-before-function-paren': [ 550 | 'error', 551 | { 552 | anonymous: 'always', 553 | named: 'never', 554 | asyncArrow: 'always', 555 | }, 556 | ], 557 | '@stylistic/space-in-parens': [ 558 | 'error', 559 | 'never', 560 | ], 561 | '@stylistic/space-infix-ops': 'error', 562 | '@stylistic/space-unary-ops': 'error', 563 | '@stylistic/spaced-comment': [ 564 | 'error', 565 | 'always', 566 | { 567 | line: { 568 | exceptions: [ 569 | '-', 570 | '+', 571 | '*', 572 | ], 573 | markers: [ 574 | '!', 575 | '/', 576 | '=>', 577 | ], 578 | }, 579 | block: { 580 | exceptions: [ 581 | '-', 582 | '+', 583 | '*', 584 | ], 585 | markers: [ 586 | '!', 587 | '*', 588 | ], 589 | balanced: true, 590 | }, 591 | }, 592 | ], 593 | '@stylistic/switch-colon-spacing': [ 594 | 'error', 595 | { 596 | after: true, 597 | before: false, 598 | }, 599 | ], 600 | '@stylistic/template-tag-spacing': [ 601 | 'error', 602 | 'never', 603 | ], 604 | 'unicode-bom': [ 605 | 'error', 606 | 'never', 607 | ], 608 | 'arrow-body-style': 'error', 609 | '@stylistic/arrow-parens': [ 610 | 'error', 611 | 'as-needed', 612 | ], 613 | '@stylistic/arrow-spacing': [ 614 | 'error', 615 | { 616 | before: true, 617 | after: true, 618 | }, 619 | ], 620 | '@stylistic/block-spacing': [ 621 | 'error', 622 | 'never', 623 | ], 624 | 'constructor-super': 'error', 625 | '@stylistic/generator-star-spacing': [ 626 | 'error', 627 | 'both', 628 | ], 629 | 'no-class-assign': 'error', 630 | 'no-const-assign': 'error', 631 | 'no-constant-binary-expression': 'error', 632 | 'no-dupe-class-members': 'error', 633 | 'no-new-native-nonconstructor': 'error', 634 | 'no-this-before-super': 'error', 635 | 'no-unassigned-vars': 'error', 636 | 'no-useless-computed-key': [ 637 | 'error', 638 | { 639 | enforceForClassMembers: true, 640 | }, 641 | ], 642 | 'no-useless-constructor': 'error', 643 | 'no-useless-rename': 'error', 644 | 'no-var': 'error', 645 | 'object-shorthand': [ 646 | 'error', 647 | 'always', 648 | { 649 | avoidExplicitReturnArrows: true, 650 | }, 651 | ], 652 | 'prefer-arrow-callback': [ 653 | 'error', 654 | { 655 | allowNamedFunctions: true, 656 | }, 657 | ], 658 | 'prefer-const': [ 659 | 'error', 660 | { 661 | destructuring: 'all', 662 | }, 663 | ], 664 | 'prefer-destructuring': [ 665 | 'error', 666 | { 667 | // `array` is disabled because it forces destructuring on 668 | // stupid stuff like `foo.bar = process.argv[2];` 669 | // TODO: Open ESLint issue about this 670 | VariableDeclarator: { 671 | array: false, 672 | object: true, 673 | }, 674 | AssignmentExpression: { 675 | array: false, 676 | 677 | // Disabled because object assignment destructuring requires parens wrapping: 678 | // `let foo; ({foo} = object);` 679 | object: false, 680 | }, 681 | }, 682 | { 683 | enforceForRenamedProperties: false, 684 | }, 685 | ], 686 | 'prefer-numeric-literals': 'error', 687 | 'prefer-object-has-own': 'error', 688 | 'prefer-rest-params': 'error', 689 | 'prefer-spread': 'error', 690 | 'require-yield': 'error', 691 | '@stylistic/rest-spread-spacing': [ 692 | 'error', 693 | 'never', 694 | ], 695 | 'symbol-description': 'error', 696 | '@stylistic/template-curly-spacing': 'error', 697 | '@stylistic/yield-star-spacing': [ 698 | 'error', 699 | 'both', 700 | ], 701 | '@stylistic/indent-binary-ops': [ 702 | 'error', 703 | 'tab', 704 | ], 705 | }; 706 | 707 | const TYPESCRIPT_EXTENSION = [ 708 | 'ts', 709 | 'tsx', 710 | 'mts', 711 | 'cts', 712 | ]; 713 | 714 | const DEFAULT_EXTENSION = [ 715 | 'js', 716 | 'jsx', 717 | 'mjs', 718 | 'cjs', 719 | ...TYPESCRIPT_EXTENSION, 720 | ]; 721 | 722 | const config = { 723 | languageOptions: { 724 | globals: { 725 | ...globals.es2021, 726 | ...globals.nodeBuiltin, 727 | }, 728 | ecmaVersion: 'latest', 729 | sourceType: 'module', 730 | parserOptions: { 731 | ecmaFeatures: { 732 | jsx: true, 733 | }, 734 | }, 735 | }, 736 | linterOptions: { 737 | reportUnusedDisableDirectives: 'error', 738 | reportUnusedInlineConfigs: 'error', 739 | }, 740 | plugins: { 741 | '@stylistic': stylistic, 742 | }, 743 | files: [ 744 | `**/*.{${DEFAULT_EXTENSION.join(',')}}`, 745 | ], 746 | rules, 747 | }; 748 | 749 | const jsonRules = { 750 | 'json/no-duplicate-keys': 'error', 751 | 'json/no-empty-keys': 'error', 752 | 'json/no-unsafe-values': 'error', 753 | 'json/no-unnormalized-keys': 'error', 754 | }; 755 | 756 | const jsonConfig = { 757 | plugins: { 758 | json, 759 | }, 760 | files: [ 761 | '**/*.json', 762 | ], 763 | language: 'json/json', 764 | rules: jsonRules, 765 | }; 766 | 767 | const jsoncConfig = { 768 | plugins: { 769 | json, 770 | }, 771 | files: [ 772 | '**/*.jsonc', 773 | '**/tsconfig.json', 774 | '.vscode/*.json', 775 | ], 776 | language: 'json/jsonc', 777 | languageOptions: { 778 | allowTrailingCommas: true, 779 | }, 780 | rules: jsonRules, 781 | }; 782 | 783 | const json5Config = { 784 | plugins: { 785 | json, 786 | }, 787 | files: [ 788 | '**/*.json5', 789 | ], 790 | language: 'json/json5', 791 | rules: jsonRules, 792 | }; 793 | 794 | const cssRules = { 795 | 'css/no-duplicate-imports': 'error', 796 | 'css/no-empty-blocks': 'error', 797 | 'css/no-invalid-at-rules': 'error', 798 | 'cs//no-invalid-at-rules': 'error', 799 | 'css/no-invalid-properties': 'error', 800 | }; 801 | 802 | // eslint-disable-next-line no-unused-vars 803 | const cssConfig = { 804 | plugins: { 805 | css, 806 | }, 807 | files: [ 808 | '**/*.css', 809 | ], 810 | language: 'css/css', 811 | rules: cssRules, 812 | }; 813 | 814 | export default [ 815 | config, 816 | jsoncConfig, 817 | json5Config, 818 | jsonConfig, // Placed last so non-standard JSONs match first. 819 | 820 | // Disabled for now until it becomes more stable. 821 | // cssConfig, 822 | ]; 823 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-xo", 3 | "version": "0.47.0", 4 | "description": "ESLint shareable config for XO", 5 | "license": "MIT", 6 | "repository": "xojs/eslint-config-xo", 7 | "funding": "https://github.com/sponsors/sindresorhus", 8 | "author": { 9 | "name": "Sindre Sorhus", 10 | "email": "sindresorhus@gmail.com", 11 | "url": "https://sindresorhus.com" 12 | }, 13 | "type": "module", 14 | "exports": { 15 | ".": "./index.js", 16 | "./browser": "./browser.js", 17 | "./space": "./space.js", 18 | "./space/browser": "./space-browser.js" 19 | }, 20 | "sideEffects": false, 21 | "engines": { 22 | "node": ">=18.18" 23 | }, 24 | "scripts": { 25 | "test": "eslint && ava" 26 | }, 27 | "files": [ 28 | "index.js", 29 | "browser.js", 30 | "space.js", 31 | "space-browser.js" 32 | ], 33 | "keywords": [ 34 | "eslintconfig", 35 | "xo", 36 | "xoxo", 37 | "hugs", 38 | "kisses", 39 | "happy", 40 | "happiness", 41 | "code", 42 | "quality", 43 | "style", 44 | "lint", 45 | "linter", 46 | "jscs", 47 | "jshint", 48 | "jslint", 49 | "eslint", 50 | "validate", 51 | "code style", 52 | "standard", 53 | "strict", 54 | "check", 55 | "checker", 56 | "verify", 57 | "enforce", 58 | "hint", 59 | "simple" 60 | ], 61 | "dependencies": { 62 | "@eslint/css": "^0.7.0", 63 | "@eslint/json": "^0.12.0", 64 | "@stylistic/eslint-plugin": "^4.2.0", 65 | "confusing-browser-globals": "1.0.11", 66 | "globals": "^16.0.0" 67 | }, 68 | "devDependencies": { 69 | "ava": "^6.2.0", 70 | "eslint": "^9.27.0" 71 | }, 72 | "peerDependencies": { 73 | "eslint": ">=9.27.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # eslint-config-xo 2 | 3 | > ESLint [shareable config](https://eslint.org/docs/developer-guide/shareable-configs.html) for [XO](https://github.com/xojs/xo) 4 | 5 | This is for advanced users. [You probably want to use XO directly.](#use-the-xo-cli-instead) 6 | 7 | See [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) for some additional useful rules. 8 | 9 | **Use the [XO issue tracker](https://github.com/xojs/xo/issues) instead of this one.** 10 | 11 | ## Install 12 | 13 | ```sh 14 | npm install --save-dev eslint-config-xo 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```js 20 | // eslint.config.js 21 | import xo from 'eslint-config-xo'; 22 | 23 | export default [ 24 | ...xo, 25 | ]; 26 | ``` 27 | 28 | This package also exposes [`eslint-config-xo/browser`](browser.js) if you're in the browser: 29 | 30 | ```js 31 | import xoBrowser from 'eslint-config-xo/browser'; 32 | 33 | export default [ 34 | ...xoBrowser, 35 | ]; 36 | ``` 37 | 38 | This package also exposes [`eslint-config-xo/space`](space.js) if you're in favor of 2-space indent: 39 | 40 | ```js 41 | import xoSpace from 'eslint-config-xo/space'; 42 | 43 | export default [ 44 | ...xoSpace, 45 | ]; 46 | ``` 47 | 48 | This package also exposes [`eslint-config-xo/space/browser`](space-browser.js) if you're in favor of 2-space indent and in browser: 49 | 50 | ```js 51 | import xoSpaceBrowser from 'eslint-config-xo/space/browser'; 52 | 53 | export default [ 54 | ...xoSpaceBrowser, 55 | ]; 56 | ``` 57 | 58 | ## Use the XO CLI instead 59 | 60 | XO is an ESLint wrapper with great defaults. 61 | 62 | Here are some reason why you should use the [XO CLI](https://github.com/xojs/xo) instead of this config: 63 | 64 | - XO comes bundled with this config. 65 | - [Beautiful output.](https://github.com/sindresorhus/eslint-formatter-pretty) 66 | - Bundles many useful plugins, like [`eslint-plugin-unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn), [`eslint-plugin-import`](https://github.com/benmosher/eslint-plugin-import), [`eslint-plugin-ava`](https://github.com/avajs/eslint-plugin-ava), and more. 67 | - No need to specify file paths to lint. It will lint all JS files except [commonly ignored paths](https://github.com/xojs/xo#ignores). 68 | - Super simple to add XO to a project with [`$ npm init xo`](https://github.com/xojs/create-xo). 69 | - Specify `indent` and `semicolon` preferences easily without messing with the rule config. 70 | - Config/rule overrides per files/globs. 71 | - Can open all files with errors at the correct line in your editor. *(See the `--open` flag)* 72 | - The [editor plugins](https://github.com/xojs/xo#editor-plugins) are IMHO better than the ESLint ones. *(Subjective)* 73 | 74 | tl;dr You miss out on a lot by just using this config. 75 | 76 | ## Related 77 | 78 | - [eslint-config-xo-space](https://github.com/xojs/eslint-config-xo-space) - ESLint shareable config for XO with 2-space indent 79 | - [eslint-config-xo-typescript](https://github.com/xojs/eslint-config-xo-typescript) - ESLint shareable config for TypeScript to be used with this config 80 | - [eslint-config-xo-react](https://github.com/xojs/eslint-config-xo-react) - ESLint shareable config for React to be used with this config 81 | -------------------------------------------------------------------------------- /space-browser.js: -------------------------------------------------------------------------------- 1 | import eslintConfigXoBrowser from './browser.js'; 2 | 3 | const [config] = eslintConfigXoBrowser; 4 | 5 | export default [ 6 | { 7 | ...config, 8 | rules: { 9 | ...config.rules, 10 | '@stylistic/indent': [ 11 | 'error', 12 | 2, 13 | { 14 | SwitchCase: 1, 15 | }, 16 | ], 17 | }, 18 | }, 19 | ]; 20 | -------------------------------------------------------------------------------- /space.js: -------------------------------------------------------------------------------- 1 | import eslintConfigXo from './index.js'; 2 | 3 | const [config] = eslintConfigXo; 4 | 5 | export default [ 6 | { 7 | ...config, 8 | rules: { 9 | ...config.rules, 10 | '@stylistic/indent': [ 11 | 'error', 12 | 2, 13 | { 14 | SwitchCase: 1, 15 | }, 16 | ], 17 | }, 18 | }, 19 | ]; 20 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {ESLint} from 'eslint'; 3 | import configXoNode from '../index.js'; 4 | import configXoBrowser from '../browser.js'; 5 | import configXoSpaceNode from '../space.js'; 6 | import configXoSpaceBrowser from '../space-browser.js'; 7 | 8 | const hasRule = (errors, ruleId) => errors.some(error => error.ruleId === ruleId); 9 | 10 | async function runEslint(string, config) { 11 | const eslint = new ESLint({ 12 | overrideConfigFile: true, 13 | overrideConfig: config, 14 | }); 15 | 16 | const [firstResult] = await eslint.lintText(string); 17 | 18 | return firstResult.messages; 19 | } 20 | 21 | test('node', async t => { 22 | for (const config of [configXoNode, configXoSpaceNode]) { 23 | t.true(Array.isArray(config)); 24 | 25 | // eslint-disable-next-line no-await-in-loop 26 | const errors = await runEslint('\'use strict\';\nconsole.log("unicorn")\n', config); 27 | t.true(hasRule(errors, '@stylistic/quotes'), JSON.stringify(errors)); 28 | } 29 | }); 30 | 31 | test('browser', async t => { 32 | for (const config of [configXoBrowser, configXoSpaceBrowser]) { 33 | t.true(Array.isArray(config)); 34 | 35 | // eslint-disable-next-line no-await-in-loop 36 | const errors = await runEslint('\'use strict\';\nprocess.exit();\n', config); 37 | t.true(hasRule(errors, 'no-undef'), JSON.stringify(errors)); 38 | } 39 | }); 40 | 41 | test('space', async t => { 42 | const fixture = ` 43 | export function foo() { 44 | \treturn true; 45 | } 46 | `.trim(); 47 | 48 | for (const { 49 | expected, 50 | config, 51 | } of [ 52 | { 53 | config: configXoSpaceNode, 54 | expected: true, 55 | }, 56 | { 57 | config: configXoSpaceBrowser, 58 | expected: true, 59 | }, 60 | { 61 | config: configXoNode, 62 | expected: false, 63 | }, 64 | { 65 | config: configXoBrowser, 66 | expected: false, 67 | }, 68 | ]) { 69 | // eslint-disable-next-line no-await-in-loop 70 | const errors = await runEslint(fixture, config); 71 | t.is(hasRule(errors, '@stylistic/indent'), expected, JSON.stringify(errors)); 72 | } 73 | }); 74 | --------------------------------------------------------------------------------