mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Minor
- Fix `/help` for server commands
- Fix multiline DMs
- Redesign collapsed userlists
- Refactor topbar to use display:table and inline-blocks, instead of
floats and manual margin calculations
- I nearly used flexbox, but fake tables work just as well so I
figure why not. The old client actually manually calculates margin
to do the thing where the right tabbar expands to the right and
then to the left and it's nice to not to need that anymore.
- Document `.gray` in STYLING
- Convert all buttons that make sense as links to <a class="button">
- That would be all the main menu links, plus the
usercount/battlecount in Rooms.
- Fix dismissing popups in iOS Safari
- Add an icon for being disconnected
Trivial
- Fix stars in the old client format dropdown
- Use "Cmd" instead of "Ctrl" on Macs in the chat formatting panel
- Fix some code style in panel-popups
- Update supported browser list in README
- Add Preact keys where they make sense
- For the most part I think requiring keys is too strict. I still
think they're unnecessary or actively detrimental for any list
that isn't long/complex and also rearranged ever.
- ESLint: Set no-shadow to warn
437 lines
15 KiB
JavaScript
437 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$.]+\\()?)?(?:[A-Za-z0-9.]+|\\$\\()?['\"`/]",
|
|
}],
|
|
"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-constant-condition": "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": "warn",
|
|
"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",
|
|
"@stylistic/jsx-child-element-spacing": "error",
|
|
"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: "Not supported by 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", "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$.]+\()?
|
|
)?
|
|
|
|
(
|
|
# tagged template
|
|
[A-Za-z0-9\.]+
|
|
|
|
|
\$\(
|
|
)?
|
|
|
|
# start of string or regex
|
|
['"`\/]
|
|
|
|
*/
|