├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .npmrc ├── eslint.config.js ├── index.js ├── license ├── package.json ├── readme.md ├── space.js ├── test └── test.js └── tsconfig.json /.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/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 | - 20 14 | - 18 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: npm install 21 | - run: npm test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | export {default} from './index.js'; 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import typescriptEslint from 'typescript-eslint'; 2 | import jsConfig from 'eslint-config-xo'; 3 | import stylistic from '@stylistic/eslint-plugin'; 4 | 5 | const getNamingConventionRule = ({isTsx}) => ({ 6 | '@typescript-eslint/naming-convention': [ 7 | 'error', 8 | { 9 | /// selector: ['variableLike', 'memberLike', 'property', 'method'], 10 | // Note: Leaving out `parameter` and `typeProperty` because of the mentioned known issues. 11 | // Note: We are intentionally leaving out `enumMember` as it's usually pascal-case or upper-snake-case. 12 | selector: ['variable', 'function', 'classProperty', 'objectLiteralProperty', 'parameterProperty', 'classMethod', 'objectLiteralMethod', 'typeMethod', 'accessor'], 13 | format: [ 14 | 'strictCamelCase', 15 | isTsx && 'StrictPascalCase', 16 | ].filter(Boolean), 17 | // We allow double underscore because of GraphQL type names and some React names. 18 | leadingUnderscore: 'allowSingleOrDouble', 19 | trailingUnderscore: 'allow', 20 | // Ignore `{'Retry-After': retryAfter}` type properties. 21 | filter: { 22 | regex: '[- ]', 23 | match: false 24 | } 25 | }, 26 | { 27 | selector: 'typeLike', 28 | format: [ 29 | 'StrictPascalCase' 30 | ] 31 | }, 32 | { 33 | selector: 'variable', 34 | types: [ 35 | 'boolean' 36 | ], 37 | format: [ 38 | 'StrictPascalCase' 39 | ], 40 | prefix: [ 41 | 'is', 42 | 'has', 43 | 'can', 44 | 'should', 45 | 'will', 46 | 'did' 47 | ] 48 | }, 49 | { 50 | // Interface name should not be prefixed with `I`. 51 | selector: 'interface', 52 | filter: /^(?!I)[A-Z]/.source, 53 | format: [ 54 | 'StrictPascalCase' 55 | ] 56 | }, 57 | { 58 | // Type parameter name should either be `T` or a descriptive name. 59 | selector: 'typeParameter', 60 | filter: /^T$|^[A-Z][a-zA-Z]+$/.source, 61 | format: [ 62 | 'StrictPascalCase' 63 | ] 64 | }, 65 | // Allow these in non-camel-case when quoted. 66 | { 67 | selector: [ 68 | 'classProperty', 69 | 'objectLiteralProperty' 70 | ], 71 | format: null, 72 | modifiers: [ 73 | 'requiresQuotes' 74 | ] 75 | } 76 | ] 77 | }); 78 | 79 | const rules = { 80 | '@typescript-eslint/adjacent-overload-signatures': 'error', 81 | '@typescript-eslint/array-type': [ 82 | 'error', 83 | { 84 | default: 'array-simple' 85 | } 86 | ], 87 | '@typescript-eslint/await-thenable': 'error', 88 | '@typescript-eslint/ban-ts-comment': [ 89 | 'error', 90 | { 91 | 'ts-expect-error': 'allow-with-description', 92 | minimumDescriptionLength: 4 93 | } 94 | ], 95 | '@typescript-eslint/ban-tslint-comment': 'error', 96 | '@typescript-eslint/no-restricted-types': [ 97 | 'error', 98 | { 99 | types: { 100 | object: { 101 | message: 'The `object` type is hard to use. Use `Record` instead. See: https://github.com/typescript-eslint/typescript-eslint/pull/848', 102 | fixWith: 'Record' 103 | }, 104 | null: { 105 | message: 'Use `undefined` instead. See: https://github.com/sindresorhus/meta/issues/7', 106 | fixWith: 'undefined' 107 | }, 108 | Buffer: { 109 | message: 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer', 110 | suggest: [ 111 | 'Uint8Array' 112 | ] 113 | }, 114 | '[]': 'Don\'t use the empty array type `[]`. It only allows empty arrays. Use `SomeType[]` instead.', 115 | '[[]]': 'Don\'t use `[[]]`. It only allows an array with a single element which is an empty array. Use `SomeType[][]` instead.', 116 | '[[[]]]': 'Don\'t use `[[[]]]`. Use `SomeType[][][]` instead.', 117 | '[[[[]]]]': 'ur drunk 🤡', 118 | '[[[[[]]]]]': '🦄💥' 119 | } 120 | } 121 | ], 122 | '@typescript-eslint/class-literal-property-style': [ 123 | 'error', 124 | 'getters' 125 | ], 126 | '@typescript-eslint/consistent-generic-constructors': [ 127 | 'error', 128 | 'constructor' 129 | ], 130 | '@typescript-eslint/consistent-indexed-object-style': 'error', 131 | 'brace-style': 'off', 132 | '@stylistic/brace-style': [ 133 | 'error', 134 | '1tbs', 135 | { 136 | allowSingleLine: false 137 | } 138 | ], 139 | 'comma-dangle': 'off', 140 | '@stylistic/comma-dangle': [ 141 | 'error', 142 | 'always-multiline' 143 | ], 144 | 'comma-spacing': 'off', 145 | '@stylistic/comma-spacing': [ 146 | 'error', 147 | { 148 | before: false, 149 | after: true 150 | } 151 | ], 152 | 'default-param-last': 'off', 153 | '@typescript-eslint/default-param-last': 'error', 154 | 'dot-notation': 'off', 155 | '@typescript-eslint/dot-notation': 'error', 156 | '@typescript-eslint/consistent-type-assertions': [ 157 | 'error', 158 | { 159 | assertionStyle: 'as', 160 | objectLiteralTypeAssertions: 'allow-as-parameter' 161 | } 162 | ], 163 | '@typescript-eslint/consistent-type-definitions': [ 164 | 'error', 165 | 'type' 166 | ], 167 | '@typescript-eslint/consistent-type-exports': [ 168 | 'error', 169 | { 170 | fixMixedExportsWithInlineTypeSpecifier: true 171 | } 172 | ], 173 | '@typescript-eslint/consistent-type-imports': [ 174 | 'error', 175 | { 176 | fixStyle: 'inline-type-imports' 177 | } 178 | ], 179 | 180 | // Disabled because it's too annoying. Enable it when it's more mature, smarter, and more flexible. 181 | // https://github.com/typescript-eslint/typescript-eslint/search?q=%22explicit-function-return-type%22&state=open&type=Issues 182 | // '@typescript-eslint/explicit-function-return-type': [ 183 | // 'error', 184 | // { 185 | // allowExpressions: true, 186 | // allowTypedFunctionExpressions: true, 187 | // allowHigherOrderFunctions: true, 188 | // allowConciseArrowFunctionExpressionsStartingWithVoid: false, 189 | // allowIIFE: true 190 | // } 191 | // ], 192 | 193 | // TODO: This rule should be removed if/when we enable `@typescript-eslint/explicit-function-return-type`. 194 | // Disabled for now as it has too many false-positives. 195 | // https://github.com/typescript-eslint/typescript-eslint/search?q=%22explicit-module-boundary-types%22&state=open&type=Issues 196 | // '@typescript-eslint/explicit-module-boundary-types': [ 197 | // 'error', 198 | // { 199 | // allowTypedFunctionExpressions: true, 200 | // allowHigherOrderFunctions: true, 201 | // allowDirectConstAssertionInArrowFunctions: true, 202 | // shouldTrackReferences: true 203 | // } 204 | // ], 205 | 206 | 'func-call-spacing': 'off', 207 | '@stylistic/func-call-spacing': [ 208 | 'error', 209 | 'never' 210 | ], 211 | indent: 'off', 212 | '@stylistic/indent': [ 213 | 'error', 214 | 'tab', 215 | { 216 | SwitchCase: 1 217 | } 218 | ], 219 | 'keyword-spacing': 'off', 220 | '@stylistic/keyword-spacing': 'error', 221 | 'lines-between-class-members': 'off', 222 | '@stylistic/lines-between-class-members': [ 223 | 'error', 224 | 'always', 225 | { 226 | // Workaround to allow class fields to not have lines between them. 227 | // TODO: Get ESLint to add an option to ignore class fields. 228 | exceptAfterSingleLine: true 229 | } 230 | ], 231 | '@stylistic/member-delimiter-style': [ 232 | 'error', 233 | { 234 | multiline: { 235 | delimiter: 'semi', 236 | requireLast: true 237 | }, 238 | singleline: { 239 | delimiter: 'semi', 240 | requireLast: false 241 | } 242 | } 243 | ], 244 | '@typescript-eslint/member-ordering': [ 245 | 'error', 246 | { 247 | default: [ 248 | 'signature', 249 | 250 | 'public-static-field', 251 | 'public-static-method', 252 | 253 | 'protected-static-field', 254 | 'protected-static-method', 255 | 256 | 'private-static-field', 257 | 'private-static-method', 258 | 259 | 'static-field', 260 | 'static-method', 261 | 262 | 'public-decorated-field', 263 | 'public-instance-field', 264 | 'public-abstract-field', 265 | 'public-field', 266 | 267 | 'protected-decorated-field', 268 | 'protected-instance-field', 269 | 'protected-abstract-field', 270 | 'protected-field', 271 | 272 | 'private-decorated-field', 273 | 'private-instance-field', 274 | 'private-field', 275 | 276 | 'instance-field', 277 | 'abstract-field', 278 | 'decorated-field', 279 | 'field', 280 | 281 | 'public-constructor', 282 | 'protected-constructor', 283 | 'private-constructor', 284 | 'constructor', 285 | 286 | 'public-decorated-method', 287 | 'public-instance-method', 288 | 'public-abstract-method', 289 | 'public-method', 290 | 291 | 'protected-decorated-method', 292 | 'protected-instance-method', 293 | 'protected-abstract-method', 294 | 'protected-method', 295 | 296 | 'private-decorated-method', 297 | 'private-instance-method', 298 | 'private-method', 299 | 300 | 'instance-method', 301 | 'abstract-method', 302 | 'decorated-method', 303 | 'method' 304 | ] 305 | } 306 | ], 307 | 308 | // Disabled for now as it causes too many weird TypeScript issues. I'm not sure whether the problems are caused by bugs in TS or problems in my types. 309 | // TODO: Try to re-enable this again in 2026. 310 | // '@typescript-eslint/method-signature-style': 'error', 311 | 312 | // We use `@typescript-eslint/naming-convention` in favor of `camelcase`. 313 | camelcase: 'off', 314 | // Known issues: 315 | // - https://github.com/typescript-eslint/typescript-eslint/issues/1485 316 | // - https://github.com/typescript-eslint/typescript-eslint/issues/1484 317 | // TODO: Prevent `_` prefix on private fields when TypeScript 3.8 is out. 318 | ...getNamingConventionRule({isTsx: false}), 319 | '@typescript-eslint/no-base-to-string': 'error', 320 | 'no-array-constructor': 'off', 321 | '@typescript-eslint/no-array-constructor': 'error', 322 | '@typescript-eslint/no-array-delete': 'error', 323 | 'no-dupe-class-members': 'off', 324 | '@typescript-eslint/no-dupe-class-members': 'error', 325 | '@typescript-eslint/no-confusing-void-expression': 'error', 326 | '@typescript-eslint/no-deprecated': 'error', 327 | '@typescript-eslint/no-duplicate-enum-values': 'error', 328 | '@typescript-eslint/no-duplicate-type-constituents': 'error', 329 | '@typescript-eslint/no-dynamic-delete': 'error', 330 | 'no-empty-function': 'off', 331 | '@typescript-eslint/no-empty-function': 'error', 332 | '@typescript-eslint/no-empty-interface': [ 333 | 'error', 334 | { 335 | allowSingleExtends: true 336 | } 337 | ], 338 | '@typescript-eslint/no-empty-object-type': 'error', 339 | 340 | // TODO: Try to enable this again in 2025. 341 | // Disabled for now. This is a great rule. It's just that TypeScript is not good enough yet to not use `any` in many places. 342 | // For example: https://github.com/sindresorhus/refined-github/pull/2391#discussion_r318995182 343 | // '@typescript-eslint/no-explicit-any': [ 344 | // 'error', 345 | // { 346 | // fixToUnknown: true, 347 | // ignoreRestArgs: true 348 | // } 349 | // ], 350 | 351 | '@typescript-eslint/no-extra-non-null-assertion': 'error', 352 | 353 | // Disabled because it's buggy. It transforms `...(personalToken ? {Authorization: `token ${personalToken}`} : {})` into `...personalToken ? {Authorization: `token ${personalToken}`} : {}` which is not valid. 354 | // https://github.com/typescript-eslint/typescript-eslint/search?q=%22no-extra-parens%22&state=open&type=Issues 355 | 'no-extra-parens': 'off', 356 | // '@typescript-eslint/no-extra-parens': [ 357 | // 'error', 358 | // 'all', 359 | // { 360 | // conditionalAssign: false, 361 | // nestedBinaryExpressions: false, 362 | // ignoreJSX: 'multi-line', 363 | // nestedConditionalExpressions: false, 364 | // } 365 | // ], 366 | 367 | 'no-extra-semi': 'off', 368 | '@stylistic/no-extra-semi': 'error', 369 | 'no-loop-func': 'off', 370 | '@typescript-eslint/no-loop-func': 'error', 371 | '@typescript-eslint/no-extraneous-class': [ 372 | 'error', 373 | { 374 | allowConstructorOnly: false, 375 | allowEmpty: false, 376 | allowStaticOnly: false, 377 | allowWithDecorator: true 378 | } 379 | ], 380 | 'no-void': [ 381 | 'error', 382 | { 383 | allowAsStatement: true // To allow `ignoreVoid` in `@typescript-eslint/no-floating-promises` 384 | } 385 | ], 386 | '@typescript-eslint/no-floating-promises': [ 387 | 'error', 388 | { 389 | checkThenables: true, 390 | ignoreVoid: true, // Prepend a function call with `void` to mark it as not needing to be await'ed, which silences this rule. 391 | ignoreIIFE: true 392 | } 393 | ], 394 | '@typescript-eslint/no-for-in-array': 'error', 395 | '@typescript-eslint/no-inferrable-types': 'error', 396 | 397 | // Disabled for now as it has too many false-positives. 398 | // '@typescript-eslint/no-invalid-void-type': 'error', 399 | 400 | '@typescript-eslint/no-meaningless-void-operator': 'error', 401 | '@typescript-eslint/no-misused-new': 'error', 402 | '@typescript-eslint/no-misused-promises': [ 403 | 'error', 404 | { 405 | checksConditionals: true, 406 | 407 | // TODO: I really want this to be `true`, but it makes it inconvenient to use 408 | // async functions as event handlers... I need to find a good way to handle that. 409 | // https://github.com/sindresorhus/refined-github/pull/2391#discussion_r318990466 410 | checksVoidReturn: false 411 | } 412 | ], 413 | '@typescript-eslint/no-namespace': 'error', 414 | '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error', 415 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', 416 | 417 | // Disabled for now. There are just too many places where you need to use it because of incorrect types, for example, the Node.js types. 418 | // TODO: Try to enable this again in 2023. 419 | // '@typescript-eslint/no-non-null-assertion': 'error', 420 | 421 | 'no-redeclare': 'off', 422 | '@typescript-eslint/no-redeclare': 'error', 423 | 'no-restricted-imports': 'off', 424 | '@typescript-eslint/no-restricted-imports': [ 425 | 'error', 426 | { 427 | paths: [ 428 | 'error', 429 | 'domain', 430 | 'freelist', 431 | 'smalloc', 432 | 'punycode', 433 | 'sys', 434 | 'querystring', 435 | 'colors' 436 | ], 437 | }, 438 | ], 439 | 440 | // The rule is buggy and keeps inferring `any` for types that are not `any`. Just a lot of false-positives. 441 | // '@typescript-eslint/no-redundant-type-constituents': 'error', 442 | 443 | '@typescript-eslint/no-require-imports': 'error', 444 | '@typescript-eslint/no-this-alias': [ 445 | 'error', 446 | { 447 | allowDestructuring: true 448 | } 449 | ], 450 | 'no-throw-literal': 'off', 451 | '@typescript-eslint/only-throw-error': [ 452 | 'error', 453 | { 454 | // This should ideally be `false`, but it makes rethrowing errors inconvenient. There should be a separate `allowRethrowingUnknown` option. 455 | allowThrowingUnknown: true, 456 | allowThrowingAny: false 457 | } 458 | ], 459 | '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', 460 | 461 | // `no-unnecessary-condition` is essentially a stricter version of `no-constant-condition`, but that isn't currently enabled 462 | 'no-constant-condition': 'error', 463 | 464 | // TODO: Try to enable this again in 2025 *if* the following are resolved: 465 | // - https://github.com/microsoft/TypeScript/issues/36393 466 | // - The rule needs a way to ignore runtime type-checks: https://github.com/sindresorhus/refined-github/pull/3168 467 | // - Run the rule on https://github.com/sindresorhus/refined-github and ensure there are no false-positives 468 | // 469 | // Also related: https://github.com/typescript-eslint/typescript-eslint/issues/1798 470 | // Also disable `no-constant-condition` when this is enabled 471 | // '@typescript-eslint/no-unnecessary-condition': [ 472 | // 'error', 473 | // { 474 | // checkTypePredicates: true 475 | // } 476 | // ], 477 | 478 | '@typescript-eslint/no-unnecessary-parameter-property-assignment': 'error', 479 | '@typescript-eslint/no-unnecessary-qualifier': 'error', 480 | '@typescript-eslint/no-unnecessary-type-arguments': 'error', 481 | '@typescript-eslint/no-unnecessary-type-assertion': 'error', 482 | '@typescript-eslint/no-unnecessary-type-constraint': 'error', 483 | 484 | // TODO: Currently documented as flawed. Enable it if fixed in 2026. 485 | // '@typescript-eslint/no-unnecessary-type-conversion': 'error', 486 | 487 | // TODO: Enable at some point. Currently disabled because it's marked as unstable. 488 | // https://typescript-eslint.io/rules/no-unnecessary-type-parameters/ 489 | // '@typescript-eslint/no-unnecessary-type-parameters': 'error', 490 | 491 | '@typescript-eslint/no-unsafe-argument': 'error', 492 | '@typescript-eslint/no-unsafe-assignment': 'error', 493 | '@typescript-eslint/no-unsafe-call': 'error', 494 | '@typescript-eslint/no-unsafe-declaration-merging': 'error', 495 | '@typescript-eslint/no-unsafe-enum-comparison': 'error', 496 | '@typescript-eslint/no-unsafe-function-type': 'error', 497 | '@typescript-eslint/no-unsafe-member-access': 'error', 498 | '@typescript-eslint/no-unsafe-return': 'error', 499 | '@typescript-eslint/no-unsafe-type-assertion': 'error', 500 | '@typescript-eslint/no-useless-empty-export': 'error', 501 | 'no-unused-expressions': 'off', 502 | '@typescript-eslint/no-unused-expressions': 'error', 503 | 'no-unused-vars': 'off', 504 | // NOTE: TypeScript already catches unused variables. Let us know if there's something this rule catches that TypeScript does not. 505 | // '@typescript-eslint/no-unused-vars': [ 506 | // 'error', 507 | // { 508 | // vars: 'all', 509 | // args: 'after-used', 510 | // ignoreRestSiblings: true, 511 | // argsIgnorePattern: /^_/.source, 512 | // caughtErrors: 'all', 513 | // caughtErrorsIgnorePattern: /^_$/.source 514 | // } 515 | // ], 516 | 'no-useless-constructor': 'off', 517 | '@typescript-eslint/no-useless-constructor': 'error', 518 | 'object-curly-spacing': 'off', 519 | '@stylistic/object-curly-spacing': [ 520 | 'error', 521 | 'never' 522 | ], 523 | 'padding-line-between-statements': 'off', 524 | '@stylistic/padding-line-between-statements': [ 525 | 'error', 526 | { 527 | blankLine: 'always', 528 | prev: 'multiline-block-like', 529 | next: '*' 530 | } 531 | ], 532 | '@typescript-eslint/no-wrapper-object-types': 'error', 533 | '@typescript-eslint/non-nullable-type-assertion-style': 'error', 534 | '@typescript-eslint/parameter-properties': [ 535 | 'error', 536 | { 537 | prefer: 'parameter-property' 538 | } 539 | ], 540 | '@typescript-eslint/prefer-as-const': 'error', 541 | '@typescript-eslint/prefer-find': 'error', 542 | '@typescript-eslint/prefer-for-of': 'error', 543 | '@typescript-eslint/prefer-function-type': 'error', 544 | '@typescript-eslint/prefer-includes': 'error', 545 | '@typescript-eslint/prefer-literal-enum-member': 'error', 546 | '@typescript-eslint/prefer-namespace-keyword': 'error', 547 | '@typescript-eslint/prefer-nullish-coalescing': [ 548 | 'error', 549 | { 550 | ignoreTernaryTests: false, 551 | ignoreConditionalTests: false, 552 | ignoreMixedLogicalExpressions: false 553 | } 554 | ], 555 | '@typescript-eslint/prefer-optional-chain': 'error', 556 | 'prefer-promise-reject-errors': 'off', 557 | '@typescript-eslint/prefer-promise-reject-errors': 'error', 558 | '@typescript-eslint/prefer-readonly': 'error', 559 | 560 | // TODO: Try to enable this again in 2023. 561 | // Disabled for now as it's too annoying and will cause too much churn. It also has bugs: https://github.com/typescript-eslint/typescript-eslint/search?q=%22prefer-readonly-parameter-types%22+is:issue&state=open&type=issues 562 | // '@typescript-eslint/prefer-readonly-parameter-types': [ 563 | // 'error', 564 | // { 565 | // checkParameterProperties: true, 566 | // ignoreInferredTypes: true 567 | // } 568 | // ], 569 | 570 | '@typescript-eslint/prefer-reduce-type-parameter': 'error', 571 | '@typescript-eslint/prefer-string-starts-ends-with': 'error', 572 | '@typescript-eslint/promise-function-async': 'error', 573 | '@typescript-eslint/related-getter-setter-pairs': 'error', 574 | quotes: 'off', 575 | '@stylistic/quotes': [ 576 | 'error', 577 | 'single' 578 | ], 579 | '@typescript-eslint/restrict-plus-operands': [ 580 | 'error', 581 | { 582 | allowAny: false 583 | } 584 | ], 585 | '@typescript-eslint/restrict-template-expressions': [ 586 | 'error', 587 | { 588 | allowNumber: true 589 | } 590 | ], 591 | '@typescript-eslint/return-await': 'error', 592 | '@typescript-eslint/require-array-sort-compare': [ 593 | 'error', 594 | { 595 | ignoreStringArrays: true 596 | } 597 | ], 598 | 599 | // Disabled for now. It's too buggy. It fails to detect when try/catch is used, await inside blocks, etc. It's also common to have async functions without await for various reasons. 600 | // 'require-await': 'off', 601 | // '@typescript-eslint/require-await': 'error', 602 | 603 | 'space-before-function-paren': 'off', 604 | '@stylistic/space-before-function-paren': [ 605 | 'error', 606 | { 607 | anonymous: 'always', 608 | named: 'never', 609 | asyncArrow: 'always' 610 | } 611 | ], 612 | 'space-infix-ops': 'off', 613 | '@stylistic/space-infix-ops': 'error', 614 | semi: 'off', 615 | '@stylistic/semi': [ 616 | 'error', 617 | 'always' 618 | ], 619 | 'space-before-blocks': 'off', 620 | '@stylistic/space-before-blocks': [ 621 | 'error', 622 | 'always' 623 | ], 624 | 625 | // TODO: Reconsider enabling it again in 2023. 626 | // NOTE: The rule was complete redone in typescript-eslint v3, so this config needs to be changed before this is enabled. 627 | // Disabled for now as it's too strict. 628 | // Relevant discussion: https://github.com/sindresorhus/refined-github/pull/2521#discussion_r343013852 629 | // '@typescript-eslint/strict-boolean-expressions': [ 630 | // 'error', 631 | // { 632 | // allowNullable: true, 633 | // allowSafe: true 634 | // } 635 | // ], 636 | 637 | 'default-case': 'off', // It conflicts with `@typescript-eslint/switch-exhaustiveness-check`. It would still be nice to have this rule for non-exhaustive switches though. 638 | '@typescript-eslint/switch-exhaustiveness-check': [ 639 | 'error', 640 | { 641 | allowDefaultCaseForExhaustiveSwitch: false, 642 | requireDefaultForNonUnion: true 643 | } 644 | ], 645 | '@typescript-eslint/triple-slash-reference': [ 646 | 'error', 647 | { 648 | path: 'never', 649 | types: 'never', 650 | lib: 'never' 651 | } 652 | ], 653 | '@stylistic/type-annotation-spacing': 'error', 654 | 655 | // Disabled as it crashes on most code. 656 | // https://github.com/typescript-eslint/typescript-eslint/search?q=%22unbound-method%22&state=open&type=Issues 657 | // '@typescript-eslint/unbound-method': [ 658 | // 'error', 659 | // { 660 | // ignoreStatic: true 661 | // } 662 | // ], 663 | 664 | '@typescript-eslint/prefer-regexp-exec': 'error', 665 | '@typescript-eslint/prefer-return-this-type': 'error', 666 | '@typescript-eslint/unified-signatures': [ 667 | 'error', 668 | { 669 | ignoreDifferentlyNamedParameters: true 670 | } 671 | ], 672 | '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error', 673 | '@stylistic/type-generic-spacing': 'error', 674 | '@stylistic/type-named-tuple-spacing': 'error', 675 | 676 | // Disabled per typescript-eslint recommendation: https://github.com/typescript-eslint/typescript-eslint/blob/e26e43ffba96f6d46198b22f1c8dd5c814db2652/docs/getting-started/linting/FAQ.md#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors 677 | 'no-undef': 'off', 678 | 679 | // TypeScript might have features not supported in a specific Node.js version. 680 | 'node/no-unsupported-features/es-syntax': 'off', 681 | 'node/no-unsupported-features/es-builtins': 'off', 682 | 683 | // Even though we already use `@typescript-eslint/no-restricted-types`, `unicorn/no-null` is useful for catching literal usage. 684 | // https://github.com/xojs/eslint-config-xo-typescript/issues/69 685 | // 'unicorn/no-null': 'off', 686 | 687 | // The rule is buggy with TS and it's not needed as TS already enforces valid imports and references at compile-time. 688 | 'import/namespace': 'off', 689 | 690 | // TypeScript already does a better job at this. 691 | 'import/named': 'off', 692 | 693 | // `import/no-duplicates` works better with TypeScript. 694 | 'no-duplicate-imports': 'off' 695 | }; 696 | 697 | export default typescriptEslint.config( 698 | ...jsConfig, 699 | { 700 | plugins: { 701 | '@typescript-eslint': typescriptEslint.plugin, 702 | '@stylistic': stylistic, 703 | }, 704 | languageOptions: { 705 | sourceType: 'module', 706 | parser: typescriptEslint.parser, 707 | parserOptions: { 708 | projectService: true, 709 | warnOnUnsupportedTypeScriptVersion: false, 710 | ecmaFeatures: { 711 | jsx: true 712 | } 713 | } 714 | }, 715 | // Disabled temporarily. 716 | // settings: { 717 | // 'import/resolver': { 718 | // node: { 719 | // extensions: [ 720 | // '.js', 721 | // '.jsx', 722 | // '.ts', 723 | // '.tsx' 724 | // ] 725 | // } 726 | // }, 727 | // 'import/parsers': { 728 | // [require.resolve('@typescript-eslint/parser')]: [ 729 | // '.ts', 730 | // '.tsx' 731 | // ] 732 | // } 733 | // }, 734 | rules 735 | }, 736 | { 737 | files: [ 738 | '**/*.d.ts' 739 | ], 740 | rules: { 741 | '@typescript-eslint/no-unused-vars': 'off' 742 | } 743 | }, 744 | { 745 | files: [ 746 | '**/*.test-d.ts' 747 | ], 748 | rules: { 749 | '@typescript-eslint/no-unsafe-call': 'off', 750 | '@typescript-eslint/no-confusing-void-expression': 'off' // Conflicts with `expectError` assertion. 751 | } 752 | }, 753 | { 754 | files: [ 755 | '**/*.tsx' 756 | ], 757 | rules: { 758 | ...getNamingConventionRule({isTsx: true}) 759 | } 760 | } 761 | ); 762 | -------------------------------------------------------------------------------- /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-typescript", 3 | "version": "8.0.1", 4 | "description": "ESLint shareable config for TypeScript to be used with eslint-config-xo", 5 | "license": "MIT", 6 | "repository": "xojs/eslint-config-xo-typescript", 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 | "./space": "./space.js" 17 | }, 18 | "sideEffects": false, 19 | "engines": { 20 | "node": ">=18.18" 21 | }, 22 | "scripts": { 23 | "test": "ava" 24 | }, 25 | "files": [ 26 | "index.js", 27 | "space.js" 28 | ], 29 | "keywords": [ 30 | "typescript", 31 | "eslintconfig", 32 | "xo", 33 | "code", 34 | "quality", 35 | "style", 36 | "lint", 37 | "linter", 38 | "jscs", 39 | "jshint", 40 | "jslint", 41 | "eslint", 42 | "validate", 43 | "standard", 44 | "strict", 45 | "check", 46 | "checker", 47 | "verify", 48 | "enforce", 49 | "hint", 50 | "simple" 51 | ], 52 | "dependencies": { 53 | "@stylistic/eslint-plugin": "^4.2.0", 54 | "eslint-config-xo": "^0.47.0", 55 | "typescript-eslint": "^8.32.0" 56 | }, 57 | "devDependencies": { 58 | "ava": "^6.3.0", 59 | "eslint": "^9.26.0", 60 | "typescript": "^5.8.3" 61 | }, 62 | "peerDependencies": { 63 | "eslint": ">=9.8.0", 64 | "typescript": ">=5.5.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # eslint-config-xo-typescript 2 | 3 | > ESLint [shareable config](https://eslint.org/docs/developer-guide/shareable-configs.html) for TypeScript 4 | 5 | This config also includes [eslint-config-xo](https://github.com/xojs/eslint-config-xo). 6 | 7 | **This config assumes your project is [ESM](https://nodejs.org/api/esm.html) and that you use a [strict config](https://github.com/sindresorhus/tsconfig/blob/main/tsconfig.json).** 8 | 9 | ## Install 10 | 11 | ```sh 12 | npm install --save-dev eslint-config-xo-typescript 13 | ``` 14 | 15 | ## Use with XO 16 | 17 | [XO has built-in support for TypeScript](https://github.com/xojs/xo#typescript), using this package under the hood, so you do not have to configure anything. 18 | 19 | ## Standalone Usage 20 | 21 | Add some ESLint config to your package.json (or `.eslintrc`): 22 | 23 | ```js 24 | // eslint.config.js 25 | import xoTypeScript from 'eslint-config-xo-typescript'; 26 | 27 | export default [ 28 | ...xoTypeScript, 29 | ]; 30 | ``` 31 | 32 | Use the `space` sub-config if you want 2 space indentation instead of tabs: 33 | 34 | ```js 35 | import xoTypeScriptSpace from 'eslint-config-xo-typescript/space'; 36 | 37 | export default [ 38 | ...xoTypeScriptSpace, 39 | ]; 40 | ``` 41 | 42 | ## Related 43 | 44 | - [XO](https://github.com/xojs/xo) 45 | -------------------------------------------------------------------------------- /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 config from '../index.js'; 4 | 5 | const hasRule = (errors, ruleId) => errors.some(error => error.ruleId === ruleId); 6 | 7 | async function runEslint(string, config) { 8 | const eslint = new ESLint({ 9 | overrideConfigFile: true, 10 | overrideConfig: config, 11 | }); 12 | 13 | const [firstResult] = await eslint.lintText(string, {filePath: 'test/test.js'}); 14 | 15 | return firstResult.messages; 16 | } 17 | 18 | test('main', async t => { 19 | const errors = await runEslint('const foo: number = 5;\n', config); 20 | t.true(hasRule(errors, '@typescript-eslint/no-inferrable-types'), JSON.stringify(errors)); 21 | t.is(errors.length, 1); 22 | }); 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true 4 | }, 5 | "files": [ 6 | "test/test.js" 7 | ], 8 | "include": [ 9 | "test" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------