mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-04-26 01:57:21 -05:00
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
This finally removes the tslint dependency and switches to eslint. There are a lot of other changes here, too, to bring the codebase up to server standards. TSLint never had much in the way of indentation enforcement. Not very happy about eslint splitting itself up over 6 dependencies, or its documentation over three websites, nor how poorly documented the new flat config is, but I mean, eslint's gonna eslint. Customizing would be even harder if we tried to use Biome or something. They mostly seem to go full Prettier. Also here are some changes to our style rules. In particular: - Curly brackets (for objects etc) now have spaces inside them. Sorry for the huge change. ESLint doesn't support our old style, and most projects use Prettier style, so we might as well match them in this way. See https://github.com/eslint-stylistic/eslint-stylistic/issues/415 - String + number concatenation is no longer allowed (except in ES3 code). We otherwise now consistently use template strings for this.
436 lines
15 KiB
JavaScript
436 lines
15 KiB
JavaScript
/**
|
|
* 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
|
|
['"`\/]
|
|
|
|
*/
|