Spaces:
Running
Running
/** | |
* Pokemon Showdown standard style | |
* | |
* This is Showdown's shared ESLint configuration. Each project overrides | |
* at least a little of it here and there, but these are the rules we use | |
* unless there's a good reason otherwise. | |
*/ | |
// @ts-check | |
import eslint from '@eslint/js'; | |
import globals from 'globals'; | |
import tseslint from 'typescript-eslint'; | |
import stylistic from '@stylistic/eslint-plugin'; | |
/** @typedef {import('typescript-eslint').Config} ConfigFile */ | |
/** @typedef {Awaited<ConfigFile>[number]} Config */ | |
/** @typedef {NonNullable<Config['rules']>} Rules */ | |
export { eslint, globals, tseslint, stylistic }; | |
/** @type {Config} */ | |
export const plugin = { | |
plugins: { | |
'@stylistic': stylistic, | |
'@typescript-eslint': tseslint.plugin, | |
}, | |
}; | |
/** @type {typeof tseslint.config} */ | |
export const configure = (...args) => [ | |
plugin, | |
...tseslint.config(...args), | |
]; | |
/** @type {NonNullable<Config['rules']>} */ | |
export const defaultRules = { | |
...stylistic.configs.customize({ | |
braceStyle: '1tbs', | |
indent: 'tab', | |
semi: true, | |
jsx: true, | |
// ... | |
}).rules, | |
// TODO rules to revisit | |
// ===================== | |
// nice to have but we mostly know && || precedence so not urgent to fix | |
"@stylistic/no-mixed-operators": "off", | |
// test only (should never be committed, but useful when testing) | |
// ============================================================== | |
// do we want unused args/destructures to start with _? unsure | |
"no-unused-vars": ["warn", { | |
args: "all", | |
argsIgnorePattern: ".", | |
caughtErrors: "all", | |
destructuredArrayIgnorePattern: ".", | |
ignoreRestSiblings: true, | |
}], | |
// "no-unused-vars": ["warn", { | |
// args: "all", | |
// argsIgnorePattern: "^_", | |
// caughtErrors: "all", | |
// destructuredArrayIgnorePattern: "^_", | |
// ignoreRestSiblings: true | |
// }], | |
"@stylistic/max-len": ["warn", { | |
"code": 120, "tabWidth": 0, | |
// DO NOT EDIT DIRECTLY: see bottom of file for source | |
"ignorePattern": "^\\s*(?:\\/\\/ \\s*)?(?:(?:export )?(?:let |const |readonly )?[a-zA-Z0-9_$.]+(?: \\+?=>? )|[a-zA-Z0-9$]+: \\[?|(?:return |throw )?(?:new )?(?:[a-zA-Z0-9$.]+\\()?)?(?:Utils\\.html|(?:this\\.)?(?:room\\.)?tr|\\$\\()?['\"`/]", | |
}], | |
"prefer-const": ["warn", { "destructuring": "all" }], | |
// PS code (code specific to PS) | |
// ============================= | |
"@stylistic/new-parens": "off", // used for the `new class {...}` pattern | |
"no-prototype-builtins": "off", | |
// defaults too strict | |
// =================== | |
"no-empty": ["error", { "allowEmptyCatch": true }], | |
"no-case-declarations": "off", | |
// probably bugs | |
// ============= | |
"array-callback-return": "error", | |
"no-constructor-return": "error", | |
"no-dupe-class-members": "error", | |
"no-extend-native": "error", | |
"no-extra-bind": "warn", | |
"no-extra-label": "warn", | |
"no-eval": "error", | |
"no-implied-eval": "error", | |
"no-inner-declarations": ["error", "functions"], | |
"no-iterator": "error", | |
"no-fallthrough": ["error", { allowEmptyCase: true, reportUnusedFallthroughComment: true }], | |
"no-promise-executor-return": ["error", { allowVoid: true }], | |
"no-return-assign": "error", | |
"no-self-compare": "error", | |
"no-sequences": "error", | |
"no-shadow": "error", | |
"no-template-curly-in-string": "error", | |
"no-throw-literal": "warn", | |
"no-unmodified-loop-condition": "error", | |
// best way to read first key of object | |
// "no-unreachable-loop": "error", | |
// ternary is used to convert callbacks to Promises | |
// tagged templates are used for the SQL library | |
"no-unused-expressions": ["error", { allowTernary: true, allowTaggedTemplates: true, enforceForJSX: true }], | |
"no-useless-call": "error", | |
// "no-useless-assignment": "error", | |
"require-atomic-updates": "error", | |
// syntax style (local syntactical, usually autofixable formatting decisions) | |
// =========================================================================== | |
"@stylistic/member-delimiter-style": ["error", { | |
multiline: { delimiter: "comma", requireLast: true }, | |
singleline: { delimiter: "comma", requireLast: false }, | |
overrides: { interface: { | |
multiline: { delimiter: "semi", requireLast: true }, | |
singleline: { delimiter: "semi", requireLast: false }, | |
} }, | |
}], | |
"default-case-last": "error", | |
"eqeqeq": ["error", "always", { null: "ignore" }], | |
"no-array-constructor": "error", | |
"no-duplicate-imports": "error", | |
"no-implicit-coercion": ["error", { allow: ["!!", "+"] }], | |
"no-multi-str": "error", | |
"no-object-constructor": "error", | |
"no-proto": "error", | |
"no-unneeded-ternary": "error", | |
"no-useless-computed-key": "error", | |
"no-useless-constructor": "error", | |
"no-useless-rename": "error", | |
"no-useless-return": "error", | |
"no-var": "error", | |
"object-shorthand": ["error", "always"], | |
"operator-assignment": ["error", "always"], | |
"prefer-arrow-callback": "error", | |
"prefer-exponentiation-operator": "error", | |
"prefer-numeric-literals": "error", | |
"prefer-object-has-own": "error", | |
"prefer-object-spread": "error", | |
"prefer-promise-reject-errors": "error", | |
"prefer-regex-literals": "error", | |
"prefer-rest-params": "error", | |
"prefer-spread": "error", | |
"radix": ["error", "as-needed"], | |
// syntax style, overriding base | |
// ============================= | |
"@stylistic/quotes": "off", | |
"@stylistic/quote-props": "off", | |
"@stylistic/function-call-spacing": "error", | |
"@stylistic/arrow-parens": ["error", "as-needed"], | |
"@stylistic/comma-dangle": ["error", { | |
"arrays": "always-multiline", | |
"objects": "always-multiline", | |
"imports": "always-multiline", | |
"exports": "always-multiline", | |
"functions": "never", | |
"importAttributes": "always-multiline", | |
"dynamicImports": "always-multiline", | |
"enums": "always-multiline", | |
"generics": "always-multiline", | |
"tuples": "always-multiline", | |
}], | |
"@stylistic/jsx-wrap-multilines": "off", | |
"@stylistic/jsx-closing-bracket-location": ["error", "line-aligned"], | |
// "@stylistic/jsx-closing-tag-location": ["error", "line-aligned"], | |
"@stylistic/jsx-closing-tag-location": "off", | |
"@stylistic/jsx-one-expression-per-line": "off", | |
"@stylistic/jsx-max-props-per-line": "off", | |
"@stylistic/jsx-function-call-newline": "off", | |
"no-restricted-syntax": ["error", | |
{ selector: "CallExpression[callee.name='Symbol']", message: "Annoying to serialize, just use a string" }, | |
], | |
// whitespace | |
// ========== | |
"@stylistic/block-spacing": "error", | |
"@stylistic/operator-linebreak": ["error", "after"], | |
"@stylistic/max-statements-per-line": ["error", { max: 3, ignoredNodes: ['BreakStatement'] }], | |
"@stylistic/lines-between-class-members": "off", | |
"@stylistic/multiline-ternary": "off", | |
"@stylistic/object-curly-spacing": ["error", "always"], | |
"@stylistic/indent": ["error", "tab", { "flatTernaryExpressions": true }], | |
}; | |
/** @type {NonNullable<Config['rules']>} */ | |
export const defaultRulesTS = { | |
...defaultRules, | |
// TODO: revisit | |
// we should do this someday but it'd have to be a gradual manual process | |
// "@typescript-eslint/explicit-module-boundary-types": "off", | |
// like above but slightly harder, so do that one first | |
// "@typescript-eslint/explicit-function-return-type": "off", | |
// probably we should settle on a standard someday | |
// "@typescript-eslint/member-ordering": "off", | |
// "@typescript-eslint/no-extraneous-class": "error", | |
// maybe we should consider this | |
"@typescript-eslint/consistent-indexed-object-style": "off", | |
// typescript-eslint specific | |
// ========================== | |
"no-unused-vars": "off", | |
"@typescript-eslint/no-unused-vars": defaultRules["no-unused-vars"], | |
"no-shadow": "off", | |
"@typescript-eslint/no-shadow": defaultRules["no-shadow"], | |
"no-dupe-class-members": "off", | |
"@typescript-eslint/no-dupe-class-members": defaultRules["no-dupe-class-members"], | |
"no-unused-expressions": "off", | |
"@typescript-eslint/no-unused-expressions": defaultRules["no-unused-expressions"], | |
// defaults too strict | |
// =================== | |
"@typescript-eslint/no-empty-function": "off", | |
"@typescript-eslint/no-explicit-any": "off", | |
"@typescript-eslint/no-non-null-assertion": "off", | |
// probably bugs | |
// ============= | |
"@typescript-eslint/no-empty-object-type": "error", | |
"@typescript-eslint/no-extra-non-null-assertion": "error", | |
"@typescript-eslint/no-misused-new": "error", | |
// no way to get it to be less strict unfortunately | |
// "@typescript-eslint/no-misused-spread": "error", | |
"@typescript-eslint/no-non-null-asserted-optional-chain": "error", | |
// naming style | |
// ============ | |
"@typescript-eslint/naming-convention": ["error", { | |
"selector": ["class", "interface", "typeAlias"], | |
"format": ["PascalCase"], | |
}], | |
// syntax style (local syntactical, usually autofixable formatting decisions) | |
// =========================================================================== | |
"@typescript-eslint/no-namespace": ["error", { allowDeclarations: true }], | |
"@typescript-eslint/prefer-namespace-keyword": "error", | |
"@typescript-eslint/adjacent-overload-signatures": "error", | |
"@typescript-eslint/array-type": "error", | |
"@typescript-eslint/consistent-type-assertions": ["error", { "assertionStyle": "as" }], | |
"@typescript-eslint/consistent-type-definitions": "off", | |
"@typescript-eslint/consistent-type-imports": ["error", { fixStyle: "inline-type-imports" }], | |
"@typescript-eslint/explicit-member-accessibility": ["error", { "accessibility": "no-public" }], | |
"@typescript-eslint/parameter-properties": "error", | |
// `source` and `target` are frequently used as variables that may point to `this` | |
// or to another `Pokemon` object, depending on how the given method is invoked | |
"@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["source", "target"] }], | |
// unfortunately this has lots of false positives without strict array/object property access | |
// "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", | |
"@typescript-eslint/prefer-as-const": "error", | |
"@typescript-eslint/prefer-for-of": "error", | |
"@typescript-eslint/prefer-function-type": "error", | |
"@typescript-eslint/prefer-return-this-type": "error", | |
"@typescript-eslint/triple-slash-reference": "error", | |
"@typescript-eslint/unified-signatures": "error", | |
}; | |
/** @type {NonNullable<Config['rules']>} */ | |
export const defaultRulesTSChecked = { | |
...defaultRulesTS, | |
// style | |
// ===== | |
"@typescript-eslint/no-unnecessary-type-arguments": "error", | |
"@typescript-eslint/restrict-plus-operands": ["error", { | |
allowBoolean: false, allowNullish: false, allowNumberAndString: false, allowRegExp: false, | |
}], | |
"@typescript-eslint/restrict-template-expressions": ["error", { | |
allow: [{ name: ['Error', 'URL', 'URLSearchParams'], from: 'lib' }], | |
allowBoolean: false, allowNever: false, allowNullish: false, allowRegExp: false, | |
}], | |
// we use `any` | |
// ============ | |
"@typescript-eslint/no-unsafe-assignment": "off", | |
"@typescript-eslint/no-unsafe-call": "off", | |
"@typescript-eslint/no-unsafe-member-access": "off", | |
"@typescript-eslint/no-unsafe-return": "off", | |
"@typescript-eslint/no-unsafe-argument": "off", | |
// yes-types syntax style, overriding base | |
// ======================================= | |
"@typescript-eslint/prefer-includes": "error", | |
"@typescript-eslint/prefer-nullish-coalescing": "off", | |
"@typescript-eslint/dot-notation": "off", | |
"@typescript-eslint/no-confusing-non-null-assertion": "off", | |
}; | |
/** @type {NonNullable<Config['rules']>} */ | |
export const defaultRulesES3 = { | |
...defaultRules, | |
// required in ES3 | |
// ================ | |
"no-var": "off", | |
"object-shorthand": ["error", "never"], | |
"prefer-arrow-callback": "off", | |
"prefer-exponentiation-operator": "off", | |
"prefer-object-has-own": "off", | |
"prefer-object-spread": "off", | |
"prefer-rest-params": "off", | |
"prefer-spread": "off", | |
"radix": "off", | |
"@stylistic/comma-dangle": "error", | |
"no-unused-vars": ["warn", { | |
args: "all", | |
argsIgnorePattern: ".", | |
caughtErrors: "all", | |
caughtErrorsIgnorePattern: "^e(rr)?$", | |
destructuredArrayIgnorePattern: ".", | |
ignoreRestSiblings: true, | |
}], | |
"no-restricted-syntax": ["error", | |
{ selector: "TaggedTemplateExpression", message: "Hard to compile down to ES3" }, | |
{ selector: "CallExpression[callee.name='Symbol']", message: "Annoying to serialize, just use a string" }, | |
], | |
// with no block scoping, coming up with original variable names is too hard | |
"no-redeclare": "off", | |
// treat var as let | |
// unfortunately doesn't actually let me redeclare | |
// "block-scoped-var": "error", | |
"no-caller": "error", | |
"no-invalid-this": "error", | |
"no-new-wrappers": "error", | |
// Map/Set can be polyfilled but it's nontrivial and it's easier just to use bare objects | |
"no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet", "WeakMap", "Set", "Map"], | |
"unicode-bom": "error", | |
}; | |
/** | |
* Actually very different from defaultRulesES3, because we don't have to | |
* worry about syntax that's easy to transpile to ES3 (which is basically | |
* all syntax). | |
* @type {NonNullable<Config['rules']>} | |
*/ | |
export const defaultRulesES3TSChecked = { | |
...defaultRulesTSChecked, | |
"radix": "off", | |
"no-restricted-globals": ["error", "Proxy", "Reflect", "Symbol", "WeakSet", "WeakMap", "Set", "Map"], | |
"no-restricted-syntax": ["error", "TaggedTemplateExpression", "YieldExpression", "AwaitExpression", "BigIntLiteral"], | |
}; | |
/** | |
* @param {Config[]} configs | |
* @returns {Config} | |
*/ | |
function extractPlugin(configs) { | |
return configs.find(config => !config.rules) || | |
(() => { throw new Error('No plugin found'); })(); | |
} | |
/** | |
* @param {Config[]} configs | |
* @returns {Rules} | |
*/ | |
function extractRules(configs) { | |
const rules = {}; | |
for (const config of configs.filter(c => c.rules)) { | |
Object.assign(rules, config.rules); | |
} | |
return rules; | |
} | |
const tseslintPlugin = extractPlugin(tseslint.configs.stylisticTypeChecked); | |
/** @type {{[k: string]: Config[]}} */ | |
export const configs = { | |
js: [{ | |
rules: { | |
...eslint.configs.recommended.rules, | |
...defaultRules, | |
}, | |
}], | |
ts: [tseslintPlugin, { | |
rules: { | |
...eslint.configs.recommended.rules, | |
...extractRules(tseslint.configs.recommendedTypeChecked), | |
...extractRules(tseslint.configs.stylisticTypeChecked), | |
...defaultRulesTSChecked, | |
}, | |
}], | |
es3: [{ | |
rules: { | |
...eslint.configs.recommended.rules, | |
...defaultRulesES3, | |
}, | |
}], | |
es3ts: [tseslintPlugin, { | |
rules: { | |
...eslint.configs.recommended.rules, | |
...extractRules(tseslint.configs.recommendedTypeChecked), | |
...extractRules(tseslint.configs.stylisticTypeChecked), | |
...defaultRulesES3TSChecked, | |
}, | |
}], | |
}; | |
/* | |
SOURCE FOR IGNOREPATTERN (compile with https://regexfree.k55.io/ ) | |
# indentation | |
^\s* | |
# possibly commented out | |
(\/\/\ \s*)? | |
( | |
# define a variable, append to a variable, or define a single-arg arrow function | |
(export\ )? (let\ |const\ |readonly\ )? [a-zA-Z0-9_$.]+ (\ \+?=>?\ ) | |
| | |
# define a property (oversize arrays are only allowed in properties) | |
[a-zA-Z0-9$]+:\ \[? | |
| | |
# optionally return or throw | |
(return\ |throw\ )? | |
# call a function or constructor | |
(new\ )?([a-zA-Z0-9$.]+\()? | |
)? | |
( | |
Utils\.html | |
| | |
(this\.)?(room\.)?tr | |
| | |
\$\( | |
)? | |
# start of string or regex | |
['"`\/] | |
*/ | |