diff --git a/eslint.config.mjs b/eslint.config.mjs index e4e7e6bcc..2d77b0eee 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,20 +1,31 @@ /* eslint-disable max-len */ -/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable ts/naming-convention */ import path from 'node:path'; -import { defineConfig, globalIgnores } from 'eslint/config'; import globals from 'globals'; import react from '@hydrooj/eslint-config'; -export default defineConfig([globalIgnores([ - '**/{public,files}/**/*.js', - '**/dist', - '**/*.d.ts', - '**/node_modules', - '**/.*.js', - 'packages/ui-default/public', -]), { - extends: [react], +const config = react({ + ignores: [ + '**/{public,files}/**/*.js', + '**/dist', + '**/*.d.ts', + '**/node_modules', + '**/.*.js', + 'packages/ui-default/public', + 'packages/hydrojudge/vendor', + ], + stylistic: { + indent: 4, + }, + jsonc: false, + rules: { + 'yaml/indent': ['warn', 2], + // There are too many `global` and `Function` in codebase already + 'no-restricted-globals': 'off', + 'ts/no-unsafe-function-type': 'off', + }, +}, { languageOptions: { ecmaVersion: 5, sourceType: 'module', @@ -105,16 +116,17 @@ export default defineConfig([globalIgnores([ rules: { 'github/array-foreach': 0, - '@typescript-eslint/no-invalid-this': 0, + 'ts/no-invalid-this': 0, // FIXME A bug with eslint-parser // 'template-curly-spacing': 'off', - '@stylistic/indent': [ + 'style/indent': [ 'warn', 2, { SwitchCase: 1 }, ], + 'style/indent-binary-ops': ['warn', 2], 'function-paren-newline': 'off', 'no-mixed-operators': 'off', 'no-await-in-loop': 'off', @@ -139,14 +151,21 @@ export default defineConfig([globalIgnores([ }, }, { files: ['packages/ui-next/src/**/*.{ts,tsx}'], - languageOptions: { globals: { ...globals.browser, }, }, - rules: { - '@stylistic/indent': ['warn', 2], + 'style/indent': ['warn', 2], + 'style/indent-binary-ops': ['warn', 2], }, -}]); +}, { + files: ['**/*.yaml', '**/*.yml'], + rules: { + 'yaml/plain-scalar': 'off', + 'style/no-trailing-spaces': 'off', + 'max-len': 'off', + }, +}); +export default config; diff --git a/framework/eslint-config/base.mjs b/framework/eslint-config/base.mjs index 1385d33a5..573046bb4 100644 --- a/framework/eslint-config/base.mjs +++ b/framework/eslint-config/base.mjs @@ -1,409 +1,144 @@ -import { FlatCompat } from '@eslint/eslintrc'; +/* eslint perfectionist/sort-objects: error */ +import antfu from '@antfu/eslint-config'; import eslintReact from '@eslint-react/eslint-plugin'; -import stylistic from '@stylistic/eslint-plugin'; -import typescriptEslint from '@typescript-eslint/eslint-plugin'; import tsParser from '@typescript-eslint/parser'; -import { defineConfig, globalIgnores } from 'eslint/config'; import deMorgan from 'eslint-plugin-de-morgan'; import github from 'eslint-plugin-github'; -import jsxA11Y from 'eslint-plugin-jsx-a11y'; import reactRefresh from 'eslint-plugin-react-refresh'; import simpleImportSort from 'eslint-plugin-simple-import-sort'; -const compat = new FlatCompat({}); - -export default defineConfig([ - globalIgnores([ - '**/*.d.ts', - '**/node_modules', - '**/.git', - ]), +/** @type {typeof antfu} */ +const base = (option, ...args) => antfu( { - extends: [ - compat.extends('airbnb-base'), - compat.extends('airbnb/hooks'), - github.getFlatConfigs().react, - deMorgan.configs.recommended, - eslintReact.configs['recommended-typescript'], + markdown: false, + typescript: true, + vue: true, + ...option, + gitignore: false, + ignores: [ + '**/*.d.ts', + '**/.git', + '**/.pnp.*', + ...(option.ignores || []), ], - - settings: { - 'import/parsers': { - '@typescript-eslint/parser': ['.ts', '.tsx', '.d.ts'], - }, - - 'import/resolver': { - node: { - extensions: ['.mjs', '.js', '.json', '.ts', '.d.ts'], - }, - }, - - 'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts'], - 'import/external-module-folders': ['node_modules', 'node_modules/@types'], - }, - - plugins: { - '@typescript-eslint': typescriptEslint, - 'simple-import-sort': simpleImportSort, - // import: fixupPluginRules(_import), - 'react-refresh': reactRefresh, - 'jsx-a11y': jsxA11Y, - '@stylistic': stylistic, - }, - languageOptions: { globals: { Atomics: 'readonly', - SharedArrayBuffer: 'readonly', BigInt: 'readonly', + SharedArrayBuffer: 'readonly', + ...(option.languageOptions?.globals || {}), }, - - parser: tsParser, + ...(option.languageOptions || {}), + }, + lessOpinionated: true, + plugins: { + '@eslint-react': eslintReact, + 'react-refresh': reactRefresh, + 'simple-import-sort': simpleImportSort, + ...(option.plugins || {}), }, - rules: { - 'brace-style': 'off', + '@eslint-react/dom/no-missing-button-type': 0, + // to allow `javascript:;` + '@eslint-react/dom/no-script-url': 0, + '@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': 0, + '@eslint-react/hooks-extra/no-unnecessary-use-prefix': 0, + '@eslint-react/no-array-index-key': 0, - '@stylistic/brace-style': ['error', '1tbs', { - allowSingleLine: true, - }], + // ui-default is currently on react 18 + '@eslint-react/no-prop-types': 0, + '@eslint-react/no-string-refs': 0, - camelcase: 'off', - 'comma-dangle': 'off', - - '@stylistic/comma-dangle': ['error', { - arrays: 'always-multiline', - objects: 'always-multiline', - imports: 'always-multiline', - exports: 'always-multiline', - functions: 'always-multiline', - enums: 'always-multiline', - generics: 'always-multiline', - tuples: 'always-multiline', - }], - - 'comma-spacing': 'off', - - '@stylistic/comma-spacing': ['error', { - before: false, - after: true, - }], + 'antfu/consistent-chaining': 'off', + 'antfu/consistent-list-newline': 'off', + 'class-methods-use-this': 0, + 'consistent-return': 1, + curly: ['error', 'multi-line'], 'default-param-last': 'off', - 'func-call-spacing': 'off', - 'function-call-spacing': 'off', - '@stylistic/function-call-spacing': ['error', 'never'], - indent: 'off', - - '@stylistic/indent': ['warn', 4, { - SwitchCase: 1, - VariableDeclarator: 1, - outerIIFEBody: 1, - - FunctionDeclaration: { - parameters: 1, - body: 1, - }, - - FunctionExpression: { - parameters: 1, - body: 1, - }, - - CallExpression: { - arguments: 1, - }, - - ArrayExpression: 1, - ObjectExpression: 1, - ImportDeclaration: 1, - flatTernaryExpressions: false, - - ignoredNodes: [ - 'JSXElement', - 'JSXElement > *', - 'JSXAttribute', - 'JSXIdentifier', - 'JSXNamespacedName', - 'JSXMemberExpression', - 'JSXSpreadAttribute', - 'JSXExpressionContainer', - 'JSXOpeningElement', - 'JSXClosingElement', - 'JSXFragment', - 'JSXOpeningFragment', - 'JSXClosingFragment', - 'JSXText', - 'JSXEmptyExpression', - 'JSXSpreadChild', - ], - - ignoreComments: false, - }], - - 'keyword-spacing': 'off', - - '@stylistic/keyword-spacing': ['error', { - before: true, - after: true, - - overrides: { - return: { - after: true, - }, - - throw: { - after: true, - }, - - case: { - after: true, - }, - }, - }], - - 'lines-between-class-members': 'off', - - '@stylistic/lines-between-class-members': ['error', 'always', { - exceptAfterSingleLine: true, - }], - - 'no-array-constructor': 'off', - '@typescript-eslint/no-array-constructor': 'error', - 'no-dupe-class-members': 'off', - '@typescript-eslint/no-dupe-class-members': 'error', - 'no-empty-function': 'off', - - '@typescript-eslint/no-empty-function': ['error', { - allow: ['arrowFunctions', 'functions', 'methods'], - }], - - 'no-extra-parens': 'off', - - '@typescript-eslint/no-extra-parens': ['off', 'all', { - conditionalAssign: true, - nestedBinaryExpressions: false, - returnAssign: false, - ignoreJSX: 'all', - enforceForArrowConditionals: false, - }], - - 'no-extra-semi': 'off', - '@stylistic/no-extra-semi': 'error', - 'no-new-func': 'off', - 'no-loss-of-precision': 'off', - '@typescript-eslint/no-loss-of-precision': 'error', - 'no-loop-func': 'off', - '@typescript-eslint/no-loop-func': 'error', - 'no-magic-numbers': 'off', - - '@typescript-eslint/no-magic-numbers': ['off', { - ignore: [], - ignoreArrayIndexes: true, - enforceConst: true, - detectObjects: false, - }], - - 'no-redeclare': 'off', - '@typescript-eslint/no-redeclare': 'error', - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': 'error', - 'space-before-blocks': 'off', - '@stylistic/space-before-blocks': 'error', - 'no-unused-expressions': 'off', - - '@typescript-eslint/no-unused-expressions': ['error', { - allowShortCircuit: false, - allowTernary: false, - allowTaggedTemplates: false, - }], - - 'no-unused-vars': 'off', - - '@typescript-eslint/no-unused-vars': ['error', { - vars: 'all', - args: 'after-used', - ignoreRestSiblings: true, - caughtErrorsIgnorePattern: '^_|e', - argsIgnorePattern: '^_', - }], - - 'no-use-before-define': 'off', - - '@typescript-eslint/no-use-before-define': ['error', { - functions: true, - classes: true, - variables: true, - }], - - 'no-useless-constructor': 'off', - '@typescript-eslint/no-useless-constructor': 'error', - quotes: 'off', - - '@stylistic/quotes': ['warn', 'single', { - avoidEscape: true, - }], - - semi: 'off', - '@stylistic/semi': ['error', 'always'], - 'space-before-function-paren': 'off', - - '@stylistic/space-before-function-paren': ['error', { - anonymous: 'always', - named: 'never', - asyncArrow: 'always', - }], - - 'require-await': 'off', - '@typescript-eslint/require-await': 'off', - 'space-infix-ops': 'off', - '@stylistic/space-infix-ops': 'error', - 'object-curly-spacing': 'off', - '@stylistic/object-curly-spacing': ['error', 'always'], - - 'function-call-argument-newline': 0, - - 'no-implicit-coercion': ['warn', { - allow: ['!!', '+'], - }], - - 'no-extra-boolean-cast': ['warn', { - enforceForLogicalOperands: true, - }], - - 'no-empty-pattern': 0, - 'no-multi-str': 0, - 'dot-notation': 0, - '@typescript-eslint/naming-convention': [ - 'warn', - { - selector: 'default', - format: ['camelCase'], - leadingUnderscore: 'allowSingleOrDouble', - }, { - selector: 'default', - - filter: { - regex: '^([A-Z]|_+id|__call__)$', - match: true, - }, - - format: null, - }, { - selector: 'variable', - modifiers: ['destructured'], - format: null, - }, { - selector: 'import', - format: null, - }, { - selector: 'variable', - format: ['camelCase', 'PascalCase', 'UPPER_CASE'], - modifiers: ['const'], - leadingUnderscore: 'allowSingleOrDouble', - }, { - selector: 'property', - format: ['camelCase', 'PascalCase', 'UPPER_CASE', 'snake_case'], - leadingUnderscore: 'allowSingleOrDouble', - }, { - selector: ['function', 'parameter', 'parameterProperty', 'objectLiteralMethod'], - - filter: { - regex: '__call__', - match: false, - }, - - format: ['camelCase', 'PascalCase'], - leadingUnderscore: 'allowSingleOrDouble', - }, { - selector: ['objectLiteralProperty'], - - filter: { - regex: '^[\\d]+$', - match: true, - }, - - format: null, - }, { - selector: ['typeProperty', 'typeMethod', 'objectLiteralProperty'], - modifiers: ['requiresQuotes'], - format: null, - }, { - selector: ['typeMethod'], - format: ['camelCase', 'PascalCase'], - }, { - selector: ['enum', 'class', 'interface'], - format: ['PascalCase'], - }, { - selector: ['typeAlias', 'enumMember', 'typeParameter'], - format: ['PascalCase', 'camelCase', 'UPPER_CASE'], - }, - ], - - '@typescript-eslint/no-invalid-this': 0, - - 'class-methods-use-this': 0, + 'eslint-comments/no-unlimited-disable': 'off', + 'func-names': 0, + 'function-call-argument-newline': 0, + 'function-paren-newline': 0, + 'github/a11y-svg-has-accessible-name': 0, + 'github/array-foreach': 1, 'global-require': 0, 'guard-for-in': 0, 'implicit-arrow-linebreak': 0, + + 'import/consistent-type-specifier-style': 'off', 'import/extensions': 0, - 'import/order': 0, - 'consistent-return': 1, - 'func-names': 0, 'import/newline-after-import': 0, 'import/no-cycle': 0, 'import/no-extraneous-dependencies': 0, 'import/no-named-as-default': 0, + 'import/order': 0, 'import/prefer-default-export': 0, + 'jsx-a11y/anchor-is-valid': 0, + 'jsx-a11y/click-events-have-key-events': 0, + 'jsx-a11y/label-has-associated-control': 0, + 'jsx-a11y/no-static-element-interactions': 0, + 'logical-assignment-operators': ['warn', 'always', { enforceForIfStatements: true, }], - 'max-classes-per-file': 0, 'max-len': ['warn', 150], 'newline-per-chained-call': 0, + 'no-await-in-loop': 1, 'no-bitwise': 0, 'no-console': 0, 'no-continue': 0, - 'no-extend-native': 0, - 'no-empty': ['warn', { allowEmptyCatch: true, }], - - 'no-await-in-loop': 1, + 'no-empty-pattern': 0, + 'no-extend-native': 0, + 'no-extra-boolean-cast': ['warn', { + enforceForLogicalOperands: true, + }], + 'no-implicit-coercion': ['warn', { + allow: ['!!', '+'], + }], 'no-multi-assign': 0, - 'no-return-await': 0, + 'no-multi-str': 0, 'no-nested-ternary': 0, 'no-param-reassign': 0, 'no-plusplus': 0, - 'no-underscore-dangle': 0, - 'no-unmodified-loop-condition': 1, - 'prefer-destructuring': 0, - 'function-paren-newline': 0, - 'simple-import-sort/exports': 0, - 'no-restricted-syntax': [ 'error', { - selector: 'LabeledStatement', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', + selector: 'LabeledStatement', }, { - selector: 'WithStatement', message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', + selector: 'WithStatement', }, ], + 'no-return-await': 0, + 'no-underscore-dangle': 0, + 'no-unmodified-loop-condition': 1, - 'generator-star-spacing': 0, + 'node/prefer-global/buffer': 'off', + 'node/prefer-global/process': 'off', + 'perfectionist/sort-imports': 'off', + 'perfectionist/sort-named-imports': 'off', - 'github/array-foreach': 1, - 'github/a11y-svg-has-accessible-name': 0, + 'prefer-destructuring': 0, 'quote-props': 0, - '@stylistic/quote-props': [0, 'as-needed'], + 'react-hooks/exhaustive-deps': 0, + 'react-refresh/only-export-components': ['warn', { + allowConstantExport: true, + }], + 'regexp/prefer-d': 'off', + 'regexp/prefer-w': 'off', + 'regexp/use-ignore-case': 'off', + + 'simple-import-sort/exports': 0, 'simple-import-sort/imports': ['warn', { groups: [ ['^\\u0000'], @@ -416,50 +151,290 @@ export default defineConfig([ ], }], - // ui-default is currently on react 18 - '@eslint-react/no-prop-types': 0, - '@eslint-react/no-string-refs': 0, - - // to allow `javascript:;` - '@eslint-react/dom/no-script-url': 0, - - '@eslint-react/no-array-index-key': 0, - '@eslint-react/dom/no-missing-button-type': 0, - '@eslint-react/dom/no-dangerously-set-innerhtml': 0, - '@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': 0, - '@eslint-react/hooks-extra/no-unnecessary-use-prefix': 0, - 'react-hooks/exhaustive-deps': 0, - // 'react/prefer-stateless-function': 0, - // 'react/function-component-definition': 0, - // 'react/static-property-placement': 0, - // 'react/self-closing-comp': 0, - // 'react/prop-types': 0, - // 'react/jsx-filename-extension': 0, - // 'react/jsx-one-expression-per-line': 0, - // 'react/jsx-props-no-spreading': 0, - // 'react/no-string-refs': 0, - // 'react/require-default-props': 0, - // 'react/react-in-jsx-scope': 0, - // 'react/destructuring-assignment': 0, - // 'react/button-has-type': 0, - // 'react/forbid-prop-types': 0, - 'jsx-a11y/no-static-element-interactions': 0, - 'jsx-a11y/anchor-is-valid': 0, - 'jsx-a11y/click-events-have-key-events': 0, - 'jsx-a11y/label-has-associated-control': 0, - - 'react-refresh/only-export-components': ['warn', { - allowConstantExport: true, + 'style/arrow-parens': ['error', 'always'], + 'style/brace-style': ['error', '1tbs', { + allowSingleLine: true, }], + 'style/comma-dangle': ['error', { + arrays: 'always-multiline', + enums: 'always-multiline', + exports: 'always-multiline', + functions: 'always-multiline', + generics: 'always-multiline', + imports: 'always-multiline', + objects: 'always-multiline', + tuples: 'always-multiline', + }], + 'style/comma-spacing': ['error', { + after: true, + before: false, + }], + 'style/function-call-spacing': ['error', 'never'], + 'style/generator-star-spacing': ['error', { + after: true, + anonymous: { after: true, before: false }, + before: false, + method: { after: false, before: true }, + }], + 'style/indent': ['warn', option.stylistic?.indent ?? 4, { + ArrayExpression: 1, + CallExpression: { + arguments: 1, + }, + flatTernaryExpressions: false, + FunctionDeclaration: { + body: 1, + parameters: 1, + }, + FunctionExpression: { + body: 1, + parameters: 1, + }, + ignoreComments: false, + ignoredNodes: [ + // 'JSXElement', + // 'JSXElement > *', + // 'JSXAttribute', + // 'JSXIdentifier', + // 'JSXNamespacedName', + // 'JSXMemberExpression', + // 'JSXSpreadAttribute', + // 'JSXExpressionContainer', + // 'JSXOpeningElement', + // 'JSXClosingElement', + // 'JSXFragment', + // 'JSXOpeningFragment', + // 'JSXClosingFragment', + 'JSXText', + // 'JSXEmptyExpression', + // 'JSXSpreadChild', + ], + ImportDeclaration: 1, + ObjectExpression: 1, + outerIIFEBody: 1, + SwitchCase: 1, + VariableDeclarator: 1, + }], + 'style/jsx-closing-bracket-location': 'off', + 'style/jsx-closing-tag-location': 'off', + 'style/jsx-function-call-newline': 'off', + 'style/jsx-indent-props': 'off', + 'style/jsx-one-expression-per-line': 'off', + 'style/jsx-wrap-multilines': 'off', + 'style/keyword-spacing': ['error', { + after: true, + before: true, + overrides: { + case: { + after: true, + }, + return: { + after: true, + }, + throw: { + after: true, + }, + }, + }], + 'style/lines-between-class-members': ['error', 'always', { + exceptAfterSingleLine: true, + }], + 'style/max-statements-per-line': ['error', { max: 2 }], + 'style/member-delimiter-style': ['error', { + multiline: { + delimiter: 'semi', + requireLast: true, + }, + multilineDetection: 'brackets', + singleline: { + delimiter: 'comma', + requireLast: false, + }, + }], + 'style/multiline-ternary': 'off', + 'style/no-extra-semi': 'error', + 'style/object-curly-spacing': ['error', 'always'], + 'style/operator-linebreak': ['warn', 'before', { + overrides: { + '=': 'after', + }, + }], + 'style/quote-props': ['warn', 'as-needed'], + 'style/quotes': ['warn', 'single', { + avoidEscape: true, + }], + 'style/space-before-blocks': 'error', + 'style/space-before-function-paren': ['error', { + anonymous: 'always', + asyncArrow: 'always', + named: 'never', + }], + 'style/space-infix-ops': 'error', + + 'test/no-import-node-test': 'off', + + 'ts/ban-ts-comment': 'off', + 'ts/consistent-type-imports': 'off', + 'ts/method-signature-style': 'off', + 'ts/no-array-constructor': 'error', + 'ts/no-dupe-class-members': 'error', + 'ts/no-empty-function': ['error', { + allow: ['arrowFunctions', 'functions', 'methods'], + }], + 'ts/no-extra-parens': ['off', 'all', { + conditionalAssign: true, + enforceForArrowConditionals: false, + ignoreJSX: 'all', + nestedBinaryExpressions: false, + returnAssign: false, + }], + 'ts/no-invalid-this': 'off', + 'ts/no-loop-func': 'error', + 'ts/no-loss-of-precision': 'error', + 'ts/no-magic-numbers': 'off', + 'ts/no-namespace': 'off', + 'ts/no-redeclare': 'off', + 'ts/no-require-imports': 'off', + 'ts/no-shadow': 'error', + 'ts/no-this-alias': 'off', + 'ts/no-unused-expressions': ['error', { + allowShortCircuit: false, + allowTaggedTemplates: false, + allowTernary: false, + }], + 'ts/no-unused-vars': ['error', { + args: 'after-used', + argsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_|e', + ignoreRestSiblings: true, + vars: 'all', + }], + 'ts/no-use-before-define': ['error', { + classes: true, + functions: true, + variables: true, + }], + 'ts/no-useless-constructor': 'error', + 'ts/prefer-as-const': 'off', + 'ts/require-await': 'off', + + 'unicorn/error-message': 'off', + 'unicorn/no-instanceof-builtins': 'off', + 'unicorn/prefer-node-protocol': 'off', + + 'unused-imports/no-unused-vars': 'off', + + ...(option.rules || {}), + }, + settings: { + 'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts'], + 'import/external-module-folders': ['node_modules', 'node_modules/@types'], + 'import/resolver': { + node: { + extensions: ['.mjs', '.js', '.json', '.ts', '.d.ts'], + }, + }, + ...(option.settings || {}), + }, + stylistic: { + indent: 4, + quotes: undefined, + semi: true, + ...(typeof option.stylistic === 'object' ? option.stylistic : {}), + }, + }, + github.getFlatConfigs().react, + deMorgan.configs.recommended, + { + files: ['**/*.vue'], + rules: { + 'vue/block-order': ['warn', { order: ['template', 'script[setup]', 'script:not([setup])', 'style[scoped]', 'style:not([scoped])'] }], + 'vue/singleline-html-element-content-newline': 'off', }, }, { - files: ['**/*.ts', '**/*.tsx'], + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.cjs', '**/*.cts', '**/*.mjs', '**/*.mts'], + + languageOptions: { + parser: tsParser, + }, + + rules: { + 'ts/naming-convention': [ + 'warn', + { + format: ['camelCase'], + leadingUnderscore: 'allowSingleOrDouble', + selector: 'default', + }, { + filter: { + match: true, + regex: '^([A-Z]|_+id|__call__)$', + }, + format: null, + selector: 'default', + }, { + format: null, + modifiers: ['destructured'], + selector: 'variable', + }, { + format: null, + selector: 'import', + }, { + format: ['camelCase', 'PascalCase', 'UPPER_CASE'], + leadingUnderscore: 'allowSingleOrDouble', + modifiers: ['const'], + selector: 'variable', + }, { + format: ['camelCase', 'PascalCase', 'UPPER_CASE', 'snake_case'], + leadingUnderscore: 'allowSingleOrDouble', + selector: 'property', + }, { + filter: { + match: false, + regex: '__call__', + }, + format: ['camelCase', 'PascalCase'], + leadingUnderscore: 'allowSingleOrDouble', + selector: ['function', 'parameter', 'parameterProperty', 'objectLiteralMethod'], + }, { + filter: { + match: true, + regex: '^[\\d]+$', + }, + format: null, + selector: ['objectLiteralProperty'], + }, { + format: null, + modifiers: ['requiresQuotes'], + selector: ['typeProperty', 'typeMethod', 'objectLiteralProperty'], + }, { + format: ['camelCase', 'PascalCase'], + selector: ['typeMethod'], + }, { + format: ['PascalCase'], + selector: ['enum', 'class', 'interface'], + }, { + format: ['PascalCase', 'camelCase', 'UPPER_CASE'], + selector: ['typeAlias', 'enumMember', 'typeParameter'], + }, + ], + }, + }, + { + files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], + + languageOptions: { + parser: tsParser, + }, // Hand over to tsc rules: { 'constructor-super': 'off', 'getter-return': 'off', + 'import/named': 'off', + 'import/no-named-as-default-member': 'off', + 'import/no-unresolved': 'off', 'no-const-assign': 'off', 'no-dupe-args': 'off', 'no-dupe-class-members': 'off', @@ -475,9 +450,15 @@ export default defineConfig([ 'no-unreachable': 'off', 'no-unsafe-negation': 'off', 'valid-typeof': 'off', - 'import/named': 'off', - 'import/no-named-as-default-member': 'off', - 'import/no-unresolved': 'off', }, }, -]); + { + files: ['**/*.yaml', '**/*.yml'], + rules: { + 'style/no-multi-spaces': 'off', + }, + }, + ...args, +); + +export default base; diff --git a/framework/eslint-config/package.json b/framework/eslint-config/package.json index 90970783c..776ecfd17 100644 --- a/framework/eslint-config/package.json +++ b/framework/eslint-config/package.json @@ -1,22 +1,16 @@ { "name": "@hydrooj/eslint-config", - "version": "2.0.3", + "version": "3.0.0", "license": "MIT", "main": "base.mjs", "repository": "https://github.com/hydro-dev/Hydro", "dependencies": { + "@antfu/eslint-config": "^5.2.0", "@eslint-react/eslint-plugin": "^1.52.3", - "@eslint/compat": "^1.3.1", - "@eslint/eslintrc": "^3.3.1", - "@stylistic/eslint-plugin": "^5.2.2", - "@typescript-eslint/eslint-plugin": "^8.38.0", - "@typescript-eslint/parser": "^8.38.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-base": "^15.0.0", + "@typescript-eslint/eslint-plugin": "^8.39.0", + "@typescript-eslint/parser": "^8.39.0", "eslint-plugin-de-morgan": "^1.3.1", "eslint-plugin-github": "^6.0.0", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-simple-import-sort": "^12.1.1", diff --git a/framework/framework/api.ts b/framework/framework/api.ts index 930a29b84..d4391b8fa 100644 --- a/framework/framework/api.ts +++ b/framework/framework/api.ts @@ -11,20 +11,20 @@ const REDIRECT = Symbol.for('hydro.api.response.redirect'); type MaybePromise = T | Promise; export type ApiType = 'Query' | 'Mutation' | 'Subscription'; -export type ApiCall = { +export interface ApiCall { readonly type: Type; readonly input: Schema; readonly func: (Type extends 'Subscription' ? (context: any, args: Arg, emit: (payload: Res) => void) => (() => MaybePromise) : (context: any, args: Arg) => MaybePromise>); readonly hooks: ApiCall<'Query', Arg, void>[]; -}; +} export const _get = (type: Type) => ( schema: Schema, func: ApiCall['func'], hooks: ApiCall<'Query', Arg, void, never>[] = [], -): ApiCall => ({ input: schema, func, hooks, type } as const); // eslint-disable-line +): ApiCall => ({ input: schema, func, hooks, type } as const); export const Query = _get('Query'); export const Mutation = _get('Mutation'); @@ -57,7 +57,7 @@ export interface Apis { builtin: { 'query.batch': ApiCall<'Query', { op: string, args: any }[], { [key: string]: any }>; 'mutation.batch': ApiCall<'Mutation', { op: string, args: any }[], { [key: string]: any }>; - } + }; test: typeof TestApis; } export type FlattenedApis = Apis[keyof Apis]; @@ -68,8 +68,7 @@ type MKeyOf = T extends any ? keyof T : never; type MId = { [K in MKeyOf]: T[K] } & {}; type ProjectionSchema = T extends Array ? ProjectionSchema - : | { [K in keyof T]?: ProjectionSchemaId | ProjectionSchema } - | Record; + : { [K in keyof T]?: ProjectionSchemaId | ProjectionSchema } | Record; type AsKeys = T extends Array ? Record : T; type Projection = S extends ProjectionSchemaId ? T : T extends Array ? Array>> : { diff --git a/framework/framework/decorators.ts b/framework/framework/decorators.ts index 1e9ef6f65..1235bcf34 100644 --- a/framework/framework/decorators.ts +++ b/framework/framework/decorators.ts @@ -8,11 +8,11 @@ type MethodDecorator = (target: any, funcName: string, obj: any) => any; type ClassDecorator = any>(Class: T) => T extends new (...args: infer R) => infer S ? new (...args: R) => S : never; export interface ParamOption { - name: string, - source: 'all' | 'get' | 'post' | 'route', - isOptional?: boolean | 'convert', - convert?: Converter, - validate?: Validator, + name: string; + source: 'all' | 'get' | 'post' | 'route'; + isOptional?: boolean | 'convert'; + convert?: Converter; + validate?: Validator; } const kSchema = Symbol.for('schemastery'); diff --git a/framework/framework/error.ts b/framework/framework/error.ts index 65483a9b0..9ce2a8b37 100644 --- a/framework/framework/error.ts +++ b/framework/framework/error.ts @@ -1,5 +1,5 @@ interface IHydroError { - new(...args: any[]): HydroError + new(...args: any[]): HydroError; } export class HydroError extends Error { @@ -32,7 +32,7 @@ const Err = (name: string, Class: IHydroError, ...info: Array<(() => string) | s msg = item; } } - // eslint-disable-next-line @typescript-eslint/no-shadow + // eslint-disable-next-line ts/no-shadow return class HydroError extends Class { name = name; constructor(...args: any[]) { diff --git a/framework/framework/index.ts b/framework/framework/index.ts index 154afd26d..c68310108 100644 --- a/framework/framework/index.ts +++ b/framework/framework/index.ts @@ -1,8 +1,8 @@ -export * from './server'; -export { default as serializer } from './serializer'; -export * from './decorators'; -export * from './validator'; -export * from './error'; -export * from './router'; -export * from './interface'; export * from './api'; +export * from './decorators'; +export * from './error'; +export * from './interface'; +export * from './router'; +export { default as serializer } from './serializer'; +export * from './server'; +export * from './validator'; diff --git a/framework/framework/interface.ts b/framework/framework/interface.ts index 1085416f9..1aa0a5f94 100644 --- a/framework/framework/interface.ts +++ b/framework/framework/interface.ts @@ -3,7 +3,7 @@ import type { ApiHandler } from './api'; import type { ConnectionHandler, Handler, NotFoundHandler } from './server'; export interface KnownHandlers { - 'NotFound': NotFoundHandler; + NotFound: NotFoundHandler; } type MapHandlerEvents> = @@ -24,19 +24,19 @@ export type Methods = 'get' | 'post' | 'put' | 'delete' | 'patch'; export type HookWithMethod = Exclude; export interface ServerEvents extends KnownHandlerEvents, HandlerEvents { - 'handler/create': (thisArg: Handler | ConnectionHandler, type: 'ws' | 'http') => VoidReturn - 'handler/create/http': (thisArg: Handler) => VoidReturn - 'handler/create/ws': (thisArg: ConnectionHandler) => VoidReturn - 'handler/init': (thisArg: Handler) => VoidReturn - 'handler/error': (thisArg: Handler | ConnectionHandler, e: Error) => VoidReturn - 'handler/api/before': (thisArg: ApiHandler) => VoidReturn - 'api/before': (args: any) => VoidReturn - [k: `handler/${HookType}/${string}`]: (thisArg: any) => VoidReturn - [k: `handler/${HookWithMethod}/${string}/${Methods}`]: (thisArg: any) => VoidReturn - [k: `handler/register/${string}`]: (HandlerClass: new (...args: any[]) => any) => VoidReturn - [k: `handler/error/${string}`]: (thisArg: Handler, e: Error) => VoidReturn - [k: `handler/api/before/${string}`]: (thisArg: ApiHandler) => VoidReturn - [k: `api/before/${string}`]: (args: any) => VoidReturn + 'handler/create': (thisArg: Handler | ConnectionHandler, type: 'ws' | 'http') => VoidReturn; + 'handler/create/http': (thisArg: Handler) => VoidReturn; + 'handler/create/ws': (thisArg: ConnectionHandler) => VoidReturn; + 'handler/init': (thisArg: Handler) => VoidReturn; + 'handler/error': (thisArg: Handler | ConnectionHandler, e: Error) => VoidReturn; + 'handler/api/before': (thisArg: ApiHandler) => VoidReturn; + 'api/before': (args: any) => VoidReturn; + [k: `handler/${HookType}/${string}`]: (thisArg: any) => VoidReturn; + [k: `handler/${HookWithMethod}/${string}/${Methods}`]: (thisArg: any) => VoidReturn; + [k: `handler/register/${string}`]: (HandlerClass: new (...args: any[]) => any) => VoidReturn; + [k: `handler/error/${string}`]: (thisArg: Handler, e: Error) => VoidReturn; + [k: `handler/api/before/${string}`]: (thisArg: ApiHandler) => VoidReturn; + [k: `api/before/${string}`]: (args: any) => VoidReturn; } declare module 'cordis' { diff --git a/framework/framework/package.json b/framework/framework/package.json index fb44a8519..36e8f0837 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -10,7 +10,7 @@ "dependencies": { "@hydrooj/utils": "workspace:^", "emoji-regex": "^10.4.0", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "koa": "^3.0.1", "koa-body": "^6.0.1", "koa-compress": "^5.1.1", diff --git a/framework/framework/server.ts b/framework/framework/server.ts index ca0e258c8..1d2500ccb 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -1,3 +1,4 @@ +/* eslint-disable ts/no-unsafe-declaration-merging */ import http from 'http'; import { tmpdir } from 'os'; import { join } from 'path'; @@ -77,7 +78,8 @@ export interface HydroResponse { type: string; status: number; template?: string; - /** If set, and pjax content was request from client, + /** + * If set, and pjax content was request from client, * The template will be used for rendering. */ pjax?: string; @@ -87,14 +89,14 @@ export interface HydroResponse { attachment: (name: string, stream?: any) => void; addHeader: (name: string, value: string) => void; } -type HydroContext = { +interface HydroContext { request: HydroRequest; response: HydroResponse; args: Record; UiContext: Record; domain: { _id: string }; user: { _id: number }; -}; +} export type KoaContext = Koa.Context & { HydroContext: HydroContext; handler: any; @@ -103,14 +105,14 @@ export type KoaContext = Koa.Context & { holdFiles: (string | File)[]; }; -export type TextRenderer = { +export interface TextRenderer { output: 'html' | 'json' | 'text'; render: (name: string, args: Record, context: Record) => string | Promise; -}; -export type BinaryRenderer = { +} +export interface BinaryRenderer { output: 'binary'; render: (name: string, args: Record, context: Record) => Buffer | Promise; -}; +} export type Renderer = (BinaryRenderer | TextRenderer) & { name: string; accept: readonly string[]; @@ -139,7 +141,7 @@ export interface UserModel { _id: number; } -export interface HandlerCommon { } // eslint-disable-line @typescript-eslint/no-unused-vars +export interface HandlerCommon { } // eslint-disable-line ts/no-unused-vars export class HandlerCommon { static [kHandler]: string | boolean = 'HandlerCommon'; session: Record; @@ -507,7 +509,8 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); this.activeHandlers.set(h, { start: Date.now(), name }); try { const operation = (method === 'post' && ctx.request.body?.operation) - ? `_${ctx.request.body.operation}`.replace(/_([a-z])/gm, (s) => s[1].toUpperCase()) + // eslint-disable-next-line regexp/no-unused-capturing-group + ? `_${ctx.request.body.operation}`.replace(/_([a-z])/g, (s) => s[1].toUpperCase()) : ''; // FIXME: should pass type check @@ -777,7 +780,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); this.ctx.on(`handler/register/${name}`, callback as any); } - // eslint-disable-next-line @typescript-eslint/naming-convention + // eslint-disable-next-line ts/naming-convention public Route(name: string, path: string, RouteHandler: typeof Handler, ...permPrivChecker) { // if (name === 'contest_scoreboard') { // console.log('+++', this.ctx); @@ -786,7 +789,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); return this.register('route', name, path, RouteHandler, ...permPrivChecker); } - // eslint-disable-next-line @typescript-eslint/naming-convention + // eslint-disable-next-line ts/naming-convention public Connection(name: string, path: string, RouteHandler: typeof ConnectionHandler, ...permPrivChecker) { return this.register('conn', name, path, RouteHandler, ...permPrivChecker); } diff --git a/framework/framework/validator.ts b/framework/framework/validator.ts index 036665304..26a539340 100644 --- a/framework/framework/validator.ts +++ b/framework/framework/validator.ts @@ -90,23 +90,23 @@ const saslprepString = (regex?: RegExp, cb?: (i: string) => boolean, export const Types = { Content: [(v) => v.toString().trim(), (v) => v?.toString()?.trim() && v.toString().trim().length < 65536], - Key: saslprepString(/^[a-zA-Z0-9-_]{1,255}$/), + Key: saslprepString(/^[\w-]{1,255}$/), /** @deprecated */ Name: saslprepString(/^.{1,255}$/), Filename: saslprepString(/^[^\\/?#~!|*]{1,255}$/, (i) => sanitize(i) === i), - UidOrName: saslprepString(/^(.{3,31}|[\u4e00-\u9fa5]{2}|-?[0-9]+)$/), - Username: saslprepString(/^(.{3,31}|[\u4e00-\u9fa5]{2})$/), + UidOrName: saslprepString(/^(?:.{3,31}|[\u4E00-\u9FA5]{2}|-?[0-9]+)$/), + Username: saslprepString(/^(?:.{3,31}|[\u4E00-\u9FA5]{2})$/), Password: basicString(/^.{6,255}$/), - ProblemId: saslprepString(/^([a-zA-Z0-9]{1,10}-)?[a-zA-Z0-9]+$/i, () => true, (s) => (Number.isSafeInteger(+s) ? +s : s)), - Email: saslprepString(/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i), - DomainId: saslprepString(/^[a-zA-Z][a-zA-Z0-9_]{3,31}$/), - Role: saslprepString(/^[_0-9A-Za-z\u4e00-\u9fa5]{1,31}$/i), + ProblemId: saslprepString(/^(?:[a-z0-9]{1,10}-)?[a-z0-9]+$/i, () => true, (s) => (Number.isSafeInteger(+s) ? +s : s)), + Email: saslprepString(/^[\w.+-]+@[a-z0-9-]+(?:\.[a-z0-9-]+)+$/i), + DomainId: saslprepString(/^[a-zA-Z]\w{3,31}$/), + Role: saslprepString(/^[\w\u4E00-\u9FA5]{1,31}$/), Title: basicString(/^.{1,64}$/, (i) => !!i.trim()), ShortString: basicString(/^.{1,255}$/), String: basicString(), Int: [(v) => +v, (v) => /^[+-]?[0-9]+$/.test(v.toString().trim()) && Number.isSafeInteger(+v)], - UnsignedInt: [(v) => +v, (v) => /^(-0|\+?[0-9]+)$/.test(v.toString().trim()) && Number.isSafeInteger(+v)], + UnsignedInt: [(v) => +v, (v) => /^(?:-0|\+?[0-9]+)$/.test(v.toString().trim()) && Number.isSafeInteger(+v)], PositiveInt: [(v) => +v, (v) => /^\+?[1-9][0-9]*$/.test(v.toString().trim()) && Number.isSafeInteger(+v)], Float: [(v) => +v, (v) => Number.isFinite(+v)], diff --git a/framework/register/index.js b/framework/register/index.js index 0fc8e53fb..03f0d0a84 100644 --- a/framework/register/index.js +++ b/framework/register/index.js @@ -1,3 +1,4 @@ +/* eslint-disable node/no-deprecated-api */ const map = {}; require('source-map-support').install({ handleUncaughtExceptions: false, @@ -27,7 +28,7 @@ const remove = [ /(const|let|var)\s+__dirname\s*=\s*(path\.)?dirname\s*\(\s*__filename\s*\)\s*;?/g, // by tsdown /(const|let|var)\s+getFilename\s*=\s*\(\s*\)\s*=>\s*fileURLToPath\s*\(\s*import\.meta\.url\s*\)\s*;?/g, - /(const|let|var)\s+__filename\s*=\s*(\/\*\s*@__PURE__\s*\*\/)?\s*getFilename\s*\(\s*\)\s*;?/g, + /(const|let|var)\s+__filename\s*=\s*(?:(\/\*\s*@__PURE__\s*\*\/)\s*)?getFilename\s*\(\s*\)\s*;?/g, ]; function tryTransform(filename, content, tsx = true) { for (const regex of remove) content = content.replace(regex, ''); @@ -93,10 +94,10 @@ require.extensions['.jsc'] = function loader(module, filename) { if (![12, 13, 14, 15, 16, 17].filter((i) => process.version.startsWith(`v${i}`)).length) { bytecode.subarray(16, 20).copy(buf, 16); } - // eslint-disable-next-line no-return-assign + const length = buf.subarray(8, 12).reduce((sum, number, power) => sum += number * (256 ** power), 0); let dummyCode = ''; - if (length > 1) dummyCode = `"${'\u200b'.repeat(length - 2)}"`; + if (length > 1) dummyCode = `"${'\u200B'.repeat(length - 2)}"`; const script = new vm.Script(dummyCode, { filename, lineOffset: 0, diff --git a/package.json b/package.json index 72908aa7a..b60861f71 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "test": "node -r @hydrooj/register packages/common/tests/subtask.spec.ts && node test/entry.js", "benchmark": "cross-env BENCHMARK=true node test/entry.js", "oxlint": "cd framework/eslint-config && node -r @hydrooj/register build.ts && cd ../../ && oxlint --config framework/eslint-config/.oxlintrc.json", - "lint": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx --fix", - "lint:ci": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx --max-warnings=0", + "lint": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx,.vue,.yaml,.yml --fix", + "lint:ci": "eslint --flag v10_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx,.vue,.yaml,.yml --max-warnings=0", "debug": "node --trace-warnings --inspect=192.168.99.234:1111 --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template", "start": "packages/hydrooj/bin/hydrooj.js", "postinstall": "node build/prepare.js", @@ -46,7 +46,7 @@ "eslint": "^9.32.0", "eslint-import-resolver-typescript": "^4.4.4", "eslint-import-resolver-webpack": "^0.13.10", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "globals": "^16.3.0", "globby": "~14.0.2", "inspectpack": "^4.7.1", diff --git a/packages/a11y/performance-test.ts b/packages/a11y/performance-test.ts index 36edd86ad..e93f2cadc 100644 --- a/packages/a11y/performance-test.ts +++ b/packages/a11y/performance-test.ts @@ -167,7 +167,7 @@ export async function startPerformanceTest(args: { enable5: boolean }, report) { await ProblemModel.addTestdata('system', docId, 'config.yaml', Buffer.from(yaml.dump({ time: '3s', memory: args.enable5 ? '2g' : '512m', - cases: new Array(20).fill({ + cases: Array.from({ length: 20 }).fill({ input: '1.in', output: '1.out', }), diff --git a/packages/common/cases.ts b/packages/common/cases.ts index da2d7c537..38345d50e 100644 --- a/packages/common/cases.ts +++ b/packages/common/cases.ts @@ -1,7 +1,7 @@ import { findFileSync, parseMemoryMB, parseTimeMS } from '@hydrooj/utils'; import { CompilableSource, ProblemConfigFile } from './types'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars +// eslint-disable-next-line ts/no-unused-vars export async function readYamlCases(cfg: ProblemConfigFile = {}, checkFile = (s: CompilableSource, errMsg: string) => s) { const config: any = { checker_type: cfg.checker_type || 'default', diff --git a/packages/common/index.ts b/packages/common/index.ts index 78ec077c1..eda2728de 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -1,4 +1,4 @@ export * from './lang'; export * from './status'; -export * from './types'; export * from './subtask'; +export * from './types'; diff --git a/packages/common/subtask.ts b/packages/common/subtask.ts index 4e554fbc9..6a1fa5747 100644 --- a/packages/common/subtask.ts +++ b/packages/common/subtask.ts @@ -2,18 +2,18 @@ import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils/lib/common'; export function convertIniConfig(ini: string) { const f = ini.split('\n'); - const count = parseInt(f[0], 10); + const count = Number.parseInt(f[0], 10); const res = { subtasks: [] }; for (let i = 1; i <= count; i++) { if (!f[i]?.trim()) throw new Error('Testdata count incorrect.'); const [input, output, time, score, memory] = f[i].split('|'); const cur = { cases: [{ input, output }], - score: parseInt(score, 10), + score: Number.parseInt(score, 10), time: `${time}s`, memory: '256m', }; - if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`; + if (!Number.isNaN(Number.parseInt(memory, 10))) cur.memory = `${Math.floor(Number.parseInt(memory, 10) / 1024)}m`; res.subtasks.push(cur); } return res; @@ -29,7 +29,8 @@ interface MatchRule { const SubtaskMatcher: MatchRule[] = [ { - regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/, + // eslint-disable-next-line regexp/no-super-linear-backtracking + regex: /^(([\w.-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/, output: (a) => ['out', 'ans'] .flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`]) .flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]) @@ -39,7 +40,7 @@ const SubtaskMatcher: MatchRule[] = [ preferredScorerType: (a) => (a[3] ? 'min' : 'sum'), }, { - regex: /^([^\d]*)\.(in|IN)(\d+)$/, + regex: /^(\D*)\.(in|IN)(\d+)$/, output: (a) => [ `${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`, `${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`, @@ -49,14 +50,14 @@ const SubtaskMatcher: MatchRule[] = [ preferredScorerType: () => 'sum', }, { - regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/, + regex: /^(\D*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/, output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`), id: (a) => +a[4], subtask: (a) => +a[2], preferredScorerType: () => 'min', }, { - regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/, + regex: /^(([0-9]+)[-_].*)\.(in|IN)$/, output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`), id: (a) => +a[2], subtask: () => 1, diff --git a/packages/common/types.ts b/packages/common/types.ts index 4d123bd40..a1300ff86 100644 --- a/packages/common/types.ts +++ b/packages/common/types.ts @@ -66,13 +66,13 @@ export interface ProblemConfigFile { export interface FileInfo { /** storage path */ - _id: string, + _id: string; /** filename */ - name: string, + name: string; /** file size (in bytes) */ - size: number, - etag: string, - lastModified: Date, + size: number; + etag: string; + lastModified: Date; } export interface JudgeMeta { @@ -113,7 +113,7 @@ export interface RecordPayload extends RecordJudgeInfo { /** 0 if pretest&script */ contest?: string; - files?: Record + files?: Record; } export interface JudgeRequest extends Omit { diff --git a/packages/components/frontend/ConfigEditor.tsx b/packages/components/frontend/ConfigEditor.tsx index 5df323611..b84baa311 100644 --- a/packages/components/frontend/ConfigEditor.tsx +++ b/packages/components/frontend/ConfigEditor.tsx @@ -91,7 +91,7 @@ function MonacoContainer({ if (!editor) return; const current = editor.getValue({ lineEnding: '\n', preserveBOM: false }); const diff = diffLines(current, config); - const ops: { range: any; text: string }[] = []; + const ops: { range: any, text: string }[] = []; let cursor = 1; for (const line of diff) { if (line.added) { @@ -109,11 +109,7 @@ function MonacoContainer({ } model.pushEditOperations([], ops, undefined); }, [editor, config]); - return ( -
- ); + return
; } export default function ConfigEditor({ @@ -166,8 +162,12 @@ export default function ConfigEditor({ diff --git a/packages/components/frontend/autocomplete/AutoComplete.tsx b/packages/components/frontend/autocomplete/AutoComplete.tsx index 419e7eaa3..bc12deff7 100644 --- a/packages/components/frontend/autocomplete/AutoComplete.tsx +++ b/packages/components/frontend/autocomplete/AutoComplete.tsx @@ -79,7 +79,7 @@ function DraggableSelection({ const AutoComplete = forwardRef(function Impl(props: AutoCompleteProps, ref: React.Ref>) { const { multi = false, width = '100%', height = 'auto', - freeSolo = false, allowEmptyQuery = false, listStyle = {}, // eslint-disable-line @eslint-react/no-unstable-default-props + freeSolo = false, allowEmptyQuery = false, listStyle = {}, disabled = false, disabledHint = '', draggable = multi, } = props; const queryItems = props.queryItems ?? (() => []); @@ -87,7 +87,7 @@ const AutoComplete = forwardRef(function Impl(props: AutoCompleteProps, re const itemText = props.itemText ?? ((item) => item.toString()); const itemKey = props.itemKey ?? itemText; const onChange = props.onChange ?? (() => { }); - const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : ((i) => i); + const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : (i) => i; const [focused, setFocused] = useState(false); // is focused const [selectedKeys, setSelectedKeys] = useState(props.selectedKeys || []); // keys of selected items diff --git a/packages/components/frontend/index.ts b/packages/components/frontend/index.ts index f8410925b..914065a99 100644 --- a/packages/components/frontend/index.ts +++ b/packages/components/frontend/index.ts @@ -1,6 +1,6 @@ -export { default as ComponentsProvider } from './provider'; +export { default as AutoComplete, type AutoCompleteHandle, type AutoCompleteProps } from './autocomplete/AutoComplete'; +export { default as CustomSelectAutoComplete } from './autocomplete/CustomSelectAutoComplete'; export { default as ConfigEditor } from './ConfigEditor'; export { default as Icon } from './Icon'; export { default as Notification } from './Notification'; -export { default as AutoComplete, type AutoCompleteHandle, type AutoCompleteProps } from './autocomplete/AutoComplete'; -export { default as CustomSelectAutoComplete } from './autocomplete/CustomSelectAutoComplete'; +export { default as ComponentsProvider } from './provider'; diff --git a/packages/components/package.json b/packages/components/package.json index 400a2c003..f2afc5d88 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -21,7 +21,7 @@ "@types/diff": "^8.0.0", "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.17.20", - "sass": "^1.89.2" + "sass": "^1.90.0" }, "peerDependencies": { "react": "*", diff --git a/packages/fps-importer/index.ts b/packages/fps-importer/index.ts index 13af0f886..49a84fda9 100644 --- a/packages/fps-importer/index.ts +++ b/packages/fps-importer/index.ts @@ -27,7 +27,7 @@ class FpsProblemImportHandler extends Handler { samples: p.sample_input?.map((input, i) => [input, p.sample_output[i]]), hint: p.hint?.[0], source: p.source?.join(' '), - }, 'html', (s) => this.translate(s)).replace(/lns=/g, ' this.translate(s)).replace(/lns=/g, '/dev/null" - code_file: Main.java - target: Main.jar - execute: /usr/bin/java -Dfile.encoding="UTF-8" -cp Main.jar Main - time_limit_rate: 2 - compile_time_limit: 30000 - compile_memory_limit: 1024 + compile: /usr/bin/bash -c "javac -d /w -encoding utf8 ./Main.java && jar cvf Main.jar *.class >/dev/null" + code_file: Main.java + target: Main.jar + execute: /usr/bin/java -Dfile.encoding="UTF-8" -cp Main.jar Main + time_limit_rate: 2 + compile_time_limit: 30000 + compile_memory_limit: 1024 py3: - compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)" - code_file: foo.py - execute: /usr/bin/python3 foo + compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)" + code_file: foo.py + execute: /usr/bin/python3 foo php: - execute: /usr/bin/php foo.php + execute: /usr/bin/php foo.php rs: - compile: /usr/bin/rustc -O -o /w/foo /w/foo.rs - compile_time_limit: 30000 + compile: /usr/bin/rustc -O -o /w/foo /w/foo.rs + compile_time_limit: 30000 hs: - compile: /usr/bin/ghc -O -outputdir /tmp -o /w/foo /w/foo.hs - compile_time_limit: 30000 + compile: /usr/bin/ghc -O -outputdir /tmp -o /w/foo /w/foo.hs + compile_time_limit: 30000 js: - execute: /usr/bin/jsc foo.js + execute: /usr/bin/jsc foo.js go: - compile: /usr/bin/go build -o /w/foo /w/foo.go - compile_time_limit: 30000 + compile: /usr/bin/go build -o /w/foo /w/foo.go + compile_time_limit: 30000 rb: - execute: /usr/bin/ruby foo.rb - compile_time_limit: 30000 + execute: /usr/bin/ruby foo.rb + compile_time_limit: 30000 cs: - compile: /usr/bin/mcs csc -optimize+ -out:/w/foo.exe /w/foo.cs - execute: /usr/bin/mono foo.exe - compile_time_limit: 30000 + compile: /usr/bin/mcs csc -optimize+ -out:/w/foo.exe /w/foo.cs + execute: /usr/bin/mono foo.exe + compile_time_limit: 30000 diff --git a/packages/hydrojudge/locales/zh.yaml b/packages/hydrojudge/locales/zh.yaml index 7ad955502..08ad37691 100644 --- a/packages/hydrojudge/locales/zh.yaml +++ b/packages/hydrojudge/locales/zh.yaml @@ -36,4 +36,4 @@ Unknown language type.: 未知的语言类型。 Unsupported language {0}.: 不支持的语言 {0}。 User output longer than standard answer.: 选手输出比标准输出长。 You did not specify a checker.: 你没有指定比较器文件。 -Your program returned {0}.: 您的程序返回了{0}。 \ No newline at end of file +Your program returned {0}.: 您的程序返回了{0}。 diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 076365d46..68e5f8a45 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -8,7 +8,7 @@ "dependencies": { "@hydrooj/common": "workspace:^", "@hydrooj/utils": "workspace:^", - "@zip.js/zip.js": "^2.7.71", + "@zip.js/zip.js": "^2.7.72", "cac": "^6.7.14", "p-queue": "^8.1.0", "schemastery": "^3.16.1", diff --git a/packages/hydrojudge/src/checkers.ts b/packages/hydrojudge/src/checkers.ts index 0e8832586..52ac15ca6 100644 --- a/packages/hydrojudge/src/checkers.ts +++ b/packages/hydrojudge/src/checkers.ts @@ -17,9 +17,9 @@ export interface CheckConfig { } type Checker = (config: CheckConfig) => Promise<{ - status: number, - score: number, - message: string, + status: number; + score: number; + message: string; }>; function parseDiffMsg(msg: string) { @@ -206,7 +206,6 @@ const checkers: Record = new Proxy({ * stderr:输出错误报告 */ async syzoj(config) { - // eslint-disable-next-line prefer-const let { status, stdout, stderr } = await runQueued(config.execute, { copyIn: { input: config.input, diff --git a/packages/hydrojudge/src/error.ts b/packages/hydrojudge/src/error.ts index 2ef05a560..342eaedb5 100644 --- a/packages/hydrojudge/src/error.ts +++ b/packages/hydrojudge/src/error.ts @@ -1,9 +1,9 @@ import { STATUS_TEXTS } from '@hydrooj/common'; interface CompileErrorInfo { - stdout?: string, - stderr?: string, - status?: number + stdout?: string; + stderr?: string; + status?: number; } export class CompileError extends Error { diff --git a/packages/hydrojudge/src/flow.ts b/packages/hydrojudge/src/flow.ts index 2ef44b7e3..bdfa69074 100644 --- a/packages/hydrojudge/src/flow.ts +++ b/packages/hydrojudge/src/flow.ts @@ -10,7 +10,7 @@ interface Task { compile: () => Promise; judgeCase: (c: NormalizedCase) => ( (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise - ) + ); } const Score = { diff --git a/packages/hydrojudge/src/judge/objective.ts b/packages/hydrojudge/src/judge/objective.ts index dbbe9ca9b..977c904a6 100644 --- a/packages/hydrojudge/src/judge/objective.ts +++ b/packages/hydrojudge/src/judge/objective.ts @@ -33,7 +33,7 @@ export async function judge({ if (!Object.keys(config.answers).length) throw new FormatError('Invalid standard answer.'); for (const key in config.answers) { const ansInfo = config.answers[key] as [string | string[], number] | Record; - // eslint-disable-next-line @typescript-eslint/no-loop-func + // eslint-disable-next-line ts/no-loop-func const report = (status: STATUS, score: number, message: string) => { const [subtaskId, caseId] = key.split('-').map(Number); totalScore += score; diff --git a/packages/hydrojudge/src/sandbox/client.ts b/packages/hydrojudge/src/sandbox/client.ts index df960e103..b7b8cb179 100644 --- a/packages/hydrojudge/src/sandbox/client.ts +++ b/packages/hydrojudge/src/sandbox/client.ts @@ -27,8 +27,8 @@ export class Stream extends EventEmitter { break; case 2: this.emit('output', { - index: (data[1] >> 4) & 0xf, - fd: (data[1]) & 0xf, + index: (data[1] >> 4) & 0xF, + fd: (data[1]) & 0xF, content: data.subarray(2), }); break; diff --git a/packages/hydrojudge/src/testlib.ts b/packages/hydrojudge/src/testlib.ts index 7e76f360d..caee2f6df 100644 --- a/packages/hydrojudge/src/testlib.ts +++ b/packages/hydrojudge/src/testlib.ts @@ -1,6 +1,6 @@ import { DetailType, STATUS } from '@hydrooj/common'; -const operation = /^\s*(status|score)\((\d+)\)\s*(.*)$/m; +const operation = /^\s*(status|score)\((\d+)\)\s*((\S|$).*)$/m; export function parse(output: string, fullscore: number, detail: DetailType) { let status = STATUS.STATUS_WRONG_ANSWER; diff --git a/packages/hydrojudge/src/utils.ts b/packages/hydrojudge/src/utils.ts index f8a5fc310..ab0378538 100644 --- a/packages/hydrojudge/src/utils.ts +++ b/packages/hydrojudge/src/utils.ts @@ -4,7 +4,7 @@ import { CompilableSource } from '@hydrooj/common'; import { fs } from '@hydrooj/utils'; import { FormatError } from './error'; -const EMPTY_STR = /^[ \r\n\t]*$/i; +const EMPTY_STR = /^[ \r\n\t]*$/; export const cmd = parse; @@ -34,7 +34,7 @@ export function compilerText(...messages: string[]) { function restrictFile(p: string) { if (!p) return '/'; if (p[0] === '/') p = ''; - return p.replace(/\.\./gmi, ''); + return p.replace(/\.\./g, ''); } export function ensureFile(folder: string) { diff --git a/packages/hydrooj/bin/commands.ts b/packages/hydrooj/bin/commands.ts index 94c93b684..947c9df0d 100644 --- a/packages/hydrooj/bin/commands.ts +++ b/packages/hydrooj/bin/commands.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-dynamic-require */ import cac from 'cac'; import { getAddons } from '../src/options'; diff --git a/packages/hydrooj/locales/en.yaml b/packages/hydrooj/locales/en.yaml index 62caaac29..e33197b61 100644 --- a/packages/hydrooj/locales/en.yaml +++ b/packages/hydrooj/locales/en.yaml @@ -114,4 +114,4 @@ user_lostpass: Lost Password user_register: Register user_sudo: Entering Sudo Mode wiki_about: About -wiki_help: Help \ No newline at end of file +wiki_help: Help diff --git a/packages/hydrooj/locales/ko.yaml b/packages/hydrooj/locales/ko.yaml index c2b01ebe4..2193be48a 100644 --- a/packages/hydrooj/locales/ko.yaml +++ b/packages/hydrooj/locales/ko.yaml @@ -41,9 +41,6 @@ __langname: 한국어 (All Problems): (전체 문제) (None): (N/A) (Not changed): 수정되지 않음 -#Sample Input: Sample Input -#Sample Output: Sample Output -#Star: Star Aborted: 중지됨 About {0}: '{0}에 대하여' About Markdown: Markdown에 대하여 diff --git a/packages/hydrooj/locales/zh.yaml b/packages/hydrooj/locales/zh.yaml index 988e63af5..8a09af1fb 100644 --- a/packages/hydrooj/locales/zh.yaml +++ b/packages/hydrooj/locales/zh.yaml @@ -3,7 +3,7 @@ __id: zh __interface: yes __langname: 简体中文 '"{data}" copied to clipboard!': “{data}”已复制到剪贴板! -'{0} {1} delete your discussion reply {2} in "{3}"({4}).' : '{0} {1} 从讨论 {3}({4}) 删除了你的回复 {2}。' +'{0} {1} delete your discussion reply {2} in "{3}"({4}).': '{0} {1} 从讨论 {3}({4}) 删除了你的回复 {2}。' '{0} comments': '{0} 条评论' '{0} discussions': '{0} 条讨论' '{0} is a moderator of this domain.': '{}是这个域的管理者之一。' @@ -38,7 +38,7 @@ __langname: 简体中文 'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。' 'Yes': 是 "Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。 -"Or upload a file:": 或者上传一个文件: +'Or upload a file:': 或者上传一个文件: "Split by ',' '.'": 由“, ”或“,”分隔。 "What's file?": 什么是文件? (All Problems): (所有题目) @@ -64,8 +64,8 @@ Add new data: 添加新数据 Add User: 添加用户 Add: 添加 Additional File: 附加文件 -Admin {0} delete your discussion "{1}". : 管理员 {0} 删除了你的讨论 "{1}"。 -Admin {0} delete your discussion tail reply {1} in "{2}"({3}). : 管理员 {0} 从讨论 {2}({3}) 中删除了你的楼中楼回复 {1}。 +Admin {0} delete your discussion "{1}".: 管理员 {0} 删除了你的讨论 "{1}"。 +Admin {0} delete your discussion tail reply {1} in "{2}"({3}).: 管理员 {0} 从讨论 {2}({3}) 中删除了你的楼中楼回复 {1}。 All {0} Contests: 所有 {0} 比赛 All Contests: 所有比赛 All Homeworks: 所有作业 @@ -947,4 +947,4 @@ You've already attended this contest.: 您已经参加本次比赛。 You've already claimed this homework.: 您已认领过该作业。 You've already voted.: 您已经投过票。 Your permission: 您的权限 -Your role: 您的角色 \ No newline at end of file +Your role: 您的角色 diff --git a/packages/hydrooj/locales/zh_TW.yaml b/packages/hydrooj/locales/zh_TW.yaml index e5b87ed13..b8bf89c34 100644 --- a/packages/hydrooj/locales/zh_TW.yaml +++ b/packages/hydrooj/locales/zh_TW.yaml @@ -563,4 +563,4 @@ You've already attended this contest.: 您已經參加本次比賽。 You've already claimed this homework.: 您已認領該功課。 You've already voted.: 您已經投過票。 Your permission: 您的許可權 -Your role: 您的角色 \ No newline at end of file +Your role: 您的角色 diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 6d1b9d1f0..637c862ad 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -23,14 +23,14 @@ "@hydrooj/utils": "workspace:^", "@simplewebauthn/server": "13.1.2", "@undefined-moe/require-resolve-hook": "^1.1.0", - "@zip.js/zip.js": "^2.7.71", + "@zip.js/zip.js": "^2.7.72", "adm-zip": "0.5.16", "cac": "^6.7.14", "cordis": "4.0.0-beta.5", "csv-stringify": "^6.6.0", "emoji-regex": "^10.4.0", "emojis-list": "2.1.0", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "js-yaml": "^4.1.0", "koa-proxies": "^0.12.4", "koa-static-cache": "^5.1.4", diff --git a/packages/hydrooj/setting.yaml b/packages/hydrooj/setting.yaml index cfbcda3b0..f4087d9c8 100644 --- a/packages/hydrooj/setting.yaml +++ b/packages/hydrooj/setting.yaml @@ -22,13 +22,13 @@ langs: __hint__: - ~ All keys starting with _ will be ignored - ~ Entries named like [foo], [foo].[bar] [foo].[baz] - - will be merged into one category. + - . will be merged into one category. - ~ A entry named [foo].[bar] will inhert all options - - defined in entry [foo] if not overwritten + - . defined in entry [foo] if not overwritten - ~ code_file option defaults to foo.[name] - ~ highlight option defaults to [name] - ~ if highlight option was set, monaco option defaults - - to [highlight], otherwise defaults to [name] + - . to [highlight], otherwise defaults to [name] - ~ Compiling be be ignored if compile option wasn't set - ~ Key marked with a ? means the option is optional - (name): diff --git a/packages/hydrooj/src/context.ts b/packages/hydrooj/src/context.ts index 0433a3211..6a50d1256 100644 --- a/packages/hydrooj/src/context.ts +++ b/packages/hydrooj/src/context.ts @@ -1,3 +1,4 @@ +/* eslint-disable ts/no-unsafe-declaration-merging */ import LoggerService from '@cordisjs/plugin-logger'; import { TimerService } from '@cordisjs/plugin-timer'; import * as cordis from 'cordis'; @@ -25,7 +26,7 @@ function provideModule(type: T, id: string, mo export type Fiber = cordis.Fiber; -export { Disposable, Plugin, FiberState } from 'cordis'; +export { Disposable, FiberState, Plugin } from 'cordis'; export interface Context extends cordis.Context { [Context.events]: EventMap & cordis.Events; diff --git a/packages/hydrooj/src/entry/cli.ts b/packages/hydrooj/src/entry/cli.ts index 5f5c11fb6..cf771cf64 100644 --- a/packages/hydrooj/src/entry/cli.ts +++ b/packages/hydrooj/src/entry/cli.ts @@ -13,8 +13,8 @@ import { const argv = cac().parse(); const tmpdir = path.resolve(os.tmpdir(), 'hydro'); -const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; -const ARR = /=>.*$/mg; +const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm; +const ARR = /=>.*$/gm; function parseParameters(fn: Function) { const code = fn.toString() .replace(COMMENTS, '') diff --git a/packages/hydrooj/src/entry/common.ts b/packages/hydrooj/src/entry/common.ts index 9091dc0d1..66d958478 100644 --- a/packages/hydrooj/src/entry/common.ts +++ b/packages/hydrooj/src/entry/common.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-dynamic-require */ /* eslint-disable no-await-in-loop */ import '../lib/index'; diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 31d3235a7..42a7a8230 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -42,7 +42,7 @@ export class ContestListHandler extends Handler { if (group && !groups.includes(group)) throw new NotAssignedError(group); const rules = Object.keys(contest.RULES).filter((i) => !contest.RULES[i].hidden); const escaped = escapeRegExp(q.toLowerCase()); - const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi'); + const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gim'); const filter = { ...(this.user.hasPerm(PERM.PERM_VIEW_HIDDEN_CONTEST) && !group) ? {} @@ -626,10 +626,10 @@ export class ContestBalloonHandler extends ContestManagementBaseHandler { } } -type BuiltinInput = { +interface BuiltinInput { tdoc: Tdoc; groups: any[]; -}; +} type AnyFunction = (...args: any) => any; type ParseArgs }> = { [key in keyof T]: T[key] extends keyof BuiltinInput ? BuiltinInput[T[key]] : T[key] extends AnyFunction ? ReturnType : any @@ -696,9 +696,9 @@ class ScoreboardService extends Service { addView }>( id: string, name: string, args: T, { display, supportedRules, cacheTime }: { - display: (this: ContestScoreboardHandler, args: ParseArgs) => Promise, - supportedRules: string[], - cacheTime?: number, + display: (this: ContestScoreboardHandler, args: ParseArgs) => Promise; + supportedRules: string[]; + cacheTime?: number; }, ) { if (this.views[id]) throw new Error(`View ${id} already exists`); @@ -766,7 +766,7 @@ export async function apply(ctx: Context) { config.lockAt = this.tdoc.lockAt; } const [, rows, udict, pdict] = await contest.getScoreboard.call(this, tdoc.domainId, tdoc._id, config); - // eslint-disable-next-line @typescript-eslint/naming-convention + // eslint-disable-next-line ts/naming-convention const page_name = tdoc.rule === 'homework' ? 'homework_scoreboard' : 'contest_scoreboard'; diff --git a/packages/hydrooj/src/handler/discussion.ts b/packages/hydrooj/src/handler/discussion.ts index 75baaf9ec..32658249f 100644 --- a/packages/hydrooj/src/handler/discussion.ts +++ b/packages/hydrooj/src/handler/discussion.ts @@ -95,7 +95,7 @@ class DiscussionNodeHandler extends DiscussionHandler { async get(domainId: string, type: string, _name: string, page = 1) { let name: ObjectId | string | number; if (ObjectId.isValid(_name)) name = new ObjectId(_name); - else if (isSafeInteger(parseInt(_name, 10))) name = parseInt(_name, 10); + else if (isSafeInteger(Number.parseInt(_name, 10))) name = Number.parseInt(_name, 10); else name = _name; const hidden = this.user.own(this.vnode) || this.user.hasPerm(PERM.PERM_EDIT_DISCUSSION) ? {} : { hidden: false }; const [ddocs, dpcount] = await this.paginate( diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index c4d9f8262..268356823 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -534,7 +534,7 @@ class HomeDomainCreateHandler extends Handler { @param('name', Types.Title) @param('bulletin', Types.Content) @param('avatar', Types.Content, true) - // eslint-disable-next-line @typescript-eslint/no-shadow + // eslint-disable-next-line ts/no-shadow async post(_: string, id: string, name: string, bulletin: string, avatar: string) { const doc = await domain.get(id); if (doc) throw new DomainAlreadyExistsError(id); diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index 033708717..f19f54f1f 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -52,7 +52,7 @@ class HomeworkMainHandler extends Handler { ], }, ...group ? { assign: { $in: [group] } } : {}, - ...q ? { title: { $regex: new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi') } } : {}, + ...q ? { title: { $regex: new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gim') } } : {}, }).sort({ penaltySince: -1, endAt: -1, beginAt: -1, _id: -1, }); diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index 98f5e13f6..5b89fc664 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -79,7 +79,7 @@ export class JudgeResultCallbackContext { private relatedId = new ObjectId(); private meta: { rejudge?: JudgeMeta['rejudge'] }; - constructor(public ctx: Context, public readonly task: Omit & { type: string }) { // eslint-disable-line @typescript-eslint/no-shadow + constructor(public ctx: Context, public readonly task: Omit & { type: string }) { // eslint-disable-line ts/no-shadow this.meta = task.meta as JudgeMeta || {}; this.finishPromise = new Promise((resolve) => { this.resolve = resolve; diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index d72944c77..8e6f7909e 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -239,7 +239,7 @@ class SystemUserImportHandler extends SystemHandler { @param('draft', Types.Boolean) async post(domainId: string, _users: string, draft: boolean) { const users = _users.split('\n'); - const udocs: { email: string, username: string, password: string, displayName?: string, [key: string]: any; }[] = []; + const udocs: { email: string, username: string, password: string, displayName?: string, [key: string]: any }[] = []; const messages = []; const mapping = Object.create(null); const groups: Record = Object.create(null); diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 22af71a36..3a993eb1f 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -56,7 +56,7 @@ function buildQuery(udoc: User) { const defaultSearch = async (domainId: string, q: string, options?: ProblemSearchOptions) => { const escaped = escapeRegExp(q.toLowerCase()); const projection: (keyof ProblemDoc)[] = ['domainId', 'docId', 'pid']; - const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi'); + const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gim'); const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }, { tag: q }] }; const pdocs = await problem.getMulti(domainId, filter, projection) .skip(options.skip || 0).limit(options.limit || system.get('pagination.problem')).toArray(); @@ -109,7 +109,7 @@ export class ProblemMainHandler extends Handler { this.response.template = 'problem_main.html'; if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem'); this.queryContext.query = buildQuery(this.user); - // eslint-disable-next-line @typescript-eslint/no-shadow + // eslint-disable-next-line ts/no-shadow const query = this.queryContext.query; const psdict = {}; const search = Object.values(global.Hydro.module.problemSearch)[0] || defaultSearch; @@ -607,7 +607,7 @@ export class ProblemEditHandler extends ProblemManageHandler { @route('pid', Types.ProblemId) @post('title', Types.Title) @post('content', Types.Content) - @post('pid', Types.ProblemId, true, (i) => /^([a-zA-Z0-9]{1,10}-)?[a-zA-Z]+[a-zA-Z0-9]*$/i.test(i)) + @post('pid', Types.ProblemId, true, (i) => /^(?:[a-z0-9]{1,10}-)?[a-z][a-z0-9]*$/i.test(i)) @post('hidden', Types.Boolean) @post('tag', Types.Content, true, null, parseCategory) @post('difficulty', Types.PositiveInt, (i) => +i <= 10, true) @@ -837,7 +837,7 @@ export class ProblemSolutionHandler extends ProblemDetailHandler { if (!accepted || !this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION_ACCEPT)) { this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION); } - // eslint-disable-next-line prefer-const + let [psdocs, pcount, pscount] = await this.paginate( solution.getMulti(domainId, this.pdoc.docId), page, @@ -995,7 +995,7 @@ export class ProblemCreateHandler extends Handler { @post('title', Types.Title) @post('content', Types.Content) - @post('pid', Types.ProblemId, true, (i) => /^([a-zA-Z0-9]{1,10}-)?[a-zA-Z]+[a-zA-Z0-9]*$/i.test(i)) + @post('pid', Types.ProblemId, true, (i) => /^(?:[a-z0-9]{1,10}-)?[a-z][a-z0-9]*$/i.test(i)) @post('hidden', Types.Boolean) @post('difficulty', Types.PositiveInt, (i) => +i <= 10, true) @post('tag', Types.Content, true, null, parseCategory) @@ -1006,7 +1006,7 @@ export class ProblemCreateHandler extends Handler { if (typeof pid !== 'string') pid = `P${pid}`; if (pid && await problem.get(domainId, pid)) throw new ProblemAlreadyExistError(pid); const docId = await problem.add(domainId, pid, title, content, this.user._id, tag ?? [], { hidden, difficulty }); - const files = new Set(Array.from(content.matchAll(/file:\/\/([a-zA-Z0-9_-]+\.[a-zA-Z0-9]+)/g)).map((i) => i[1])); + const files = new Set(Array.from(content.matchAll(/file:\/\/([\w-]+\.[a-zA-Z0-9]+)/g)).map((i) => i[1])); const tasks = []; for (const file of files) { if (this.user._files.find((i) => i.name === file)) { diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index 264e80cfd..0ff7afe9c 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -70,7 +70,7 @@ class RecordListHandler extends ContestDetailBaseHandler { } if (pid) { if (typeof pid === 'string' && tdoc && /^[A-Z]$/.test(pid)) { - pid = tdoc.pids[parseInt(pid, 36) - 10]; + pid = tdoc.pids[Number.parseInt(pid, 36) - 10]; } const pdoc = await problem.get(domainId, pid); if (pdoc) q.pid = pdoc.docId; @@ -341,7 +341,6 @@ class RecordMainConnectionHandler extends ConnectionHandler { if (typeof this.pid === 'number' && rdoc.pid !== this.pid) return; if (typeof this.uid === 'number' && rdoc.uid !== this.uid) return; - // eslint-disable-next-line prefer-const let [udoc, pdoc] = await Promise.all([ user.getById(this.args.domainId, rdoc.uid), problem.get(rdoc.domainId, rdoc.pid), diff --git a/packages/hydrooj/src/init.ts b/packages/hydrooj/src/init.ts index 931b52c26..e6ede192b 100644 --- a/packages/hydrooj/src/init.ts +++ b/packages/hydrooj/src/init.ts @@ -1,4 +1,4 @@ -const versionNum = +process.version.replace(/v/gim, '').split('.')[0]; +const versionNum = +process.version.replace(/v/gi, '').split('.')[0]; if (versionNum < 22) throw new Error('NodeJS >=22 required'); console.log('Process', process.pid, 'running as', process.env.NODE_APP_INSTANCE === '0' ? 'master' : 'worker'); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index de35a43f0..05f4136db 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -1,3 +1,4 @@ +/* eslint-disable perfectionist/sort-exports */ import type { AttestationFormat, CredentialDeviceType } from '@simplewebauthn/server'; import type { ParsedAuthenticatorData } from '@simplewebauthn/server/helpers'; import type fs from 'fs'; @@ -16,43 +17,43 @@ export * from '@hydrooj/common/types'; type document = typeof import('./model/document'); export interface System { - _id: string, - value: any, + _id: string; + value: any; } export interface SystemKeys { - 'smtp.user': string, - 'smtp.from': string, - 'smtp.pass': string, - 'smtp.host': string, - 'smtp.port': number, - 'smtp.secure': boolean, - 'installid': string, - 'server.name': string, - 'server.url': string, - 'server.xff': string, - 'server.xhost': string, - 'server.host': string, - 'server.port': number, - 'server.language': string, - 'limit.problem_files_max': number, - 'problem.categories': string, - 'session.keys': string[], - 'session.saved_expire_seconds': number, - 'session.unsaved_expire_seconds': number, - 'user.quota': number, + 'smtp.user': string; + 'smtp.from': string; + 'smtp.pass': string; + 'smtp.host': string; + 'smtp.port': number; + 'smtp.secure': boolean; + installid: string; + 'server.name': string; + 'server.url': string; + 'server.xff': string; + 'server.xhost': string; + 'server.host': string; + 'server.port': number; + 'server.language': string; + 'limit.problem_files_max': number; + 'problem.categories': string; + 'session.keys': string[]; + 'session.saved_expire_seconds': number; + 'session.unsaved_expire_seconds': number; + 'user.quota': number; } export interface Setting { - family: string, - key: string, - range: [string, string][] | Record, - value: any, - type: string, - subType?: string, - name: string, - desc: string, - flag: number, + family: string; + key: string; + range: [string, string][] | Record; + value: any; + type: string; + subType?: string; + name: string; + desc: string; + flag: number; } export interface Authenticator { @@ -119,7 +120,7 @@ export interface UserPreferenceDoc { content: string; } -export type ownerInfo = { owner: number, maintainer?: number[] }; +export interface OwnerInfo { owner: number, maintainer?: number[] } export type User = import('./model/user').User; export type Udict = Record; @@ -190,11 +191,11 @@ export type { ProblemDoc } from './model/problem'; export type ProblemDict = NumericDictionary; export interface StatusDocBase { - _id: ObjectId, - docId: any, - docType: number, - domainId: string, - uid: number, + _id: ObjectId; + docId: any; + docType: number; + domainId: string; + uid: number; } export interface ProblemStatusDoc extends StatusDocBase { @@ -241,10 +242,10 @@ export type ScoreboardRow = ScoreboardNode[] & { raw?: any }; export type PenaltyRules = Dictionary; export interface TrainingNode { - _id: number, - title: string, - requireNids: number[], - pids: number[], + _id: number; + title: string; + requireNids: number[]; + pids: number[]; } export interface Tdoc extends Document { @@ -287,28 +288,28 @@ export interface Tdoc extends Document { } export interface TrainingDoc extends Omit { - docType: document['TYPE_TRAINING'], + docType: document['TYPE_TRAINING']; description: string; pin?: number; dag: TrainingNode[]; } export interface DomainDoc extends Record { - _id: string, - owner: number, - roles: Dictionary, - avatar: string, - bulletin: string, - _join?: any, - host?: string[], + _id: string; + owner: number; + roles: Dictionary; + avatar: string; + bulletin: string; + _join?: any; + host?: string[]; } // Message export interface MessageDoc { - from: number, - to: number, - content: string, - flag: number, + from: number; + to: number; + content: string; + flag: number; } // Blacklist @@ -382,22 +383,22 @@ export interface ContestClarificationDoc extends Document { } export interface TokenDoc { - _id: string, - tokenType: number, - createAt: Date, - updateAt: Date, - expireAt: Date, - [key: string]: any, + _id: string; + tokenType: number; + createAt: Date; + updateAt: Date; + expireAt: Date; + [key: string]: any; } export interface OplogDoc extends Record { - _id: ObjectId, - type: string, + _id: ObjectId; + type: string; } export interface ContestStat extends Record { - detail: Record>, - unrank?: boolean, + detail: Record>; + unrank?: boolean; } export interface ScoreboardConfig { @@ -438,9 +439,9 @@ export type ContestRules = Dictionary; export type ProblemImporter = (url: string, handler: any) => Promise<[ProblemDoc, fs.ReadStream?]> | [ProblemDoc, fs.ReadStream?]; export interface Script { - run: (args: any, report: Function) => any, - description: string, - validate: any, + run: (args: any, report: Function) => any; + description: string; + validate: any; } export interface Task { @@ -528,66 +529,66 @@ export interface LockDoc { declare module './service/db' { interface Collections { - 'blacklist': BlacklistDoc; - 'domain': DomainDoc; + blacklist: BlacklistDoc; + domain: DomainDoc; 'domain.user': any; - 'record': RecordDoc; + record: RecordDoc; 'record.stat': RecordStatDoc; 'record.history': RecordHistoryDoc; - 'document': any; + document: any; 'document.status': StatusDocBase & { [K in keyof DocStatusType]: { docType: K } & DocStatusType[K]; }[keyof DocStatusType]; 'discussion.history': DiscussionHistoryDoc; - 'user': Udoc; + user: Udoc; 'user.preference': UserPreferenceDoc; - 'vuser': VUdoc; + vuser: VUdoc; 'user.group': GDoc; - 'check': System; - 'message': MessageDoc; - 'token': TokenDoc; - 'status': any; - 'oauth': OauthMap; - 'system': System; - 'task': Task; - 'storage': FileNode; - 'oplog': OplogDoc; - 'event': EventDoc; - 'opcount': OpCountDoc; - 'schedule': Schedule; + check: System; + message: MessageDoc; + token: TokenDoc; + status: any; + oauth: OauthMap; + system: System; + task: Task; + storage: FileNode; + oplog: OplogDoc; + event: EventDoc; + opcount: OpCountDoc; + schedule: Schedule; 'contest.balloon': ContestBalloonDoc; - 'lock': LockDoc; + lock: LockDoc; } } export interface Model { - blacklist: typeof import('./model/blacklist').default, - builtin: typeof import('./model/builtin'), - contest: typeof import('./model/contest'), - discussion: typeof import('./model/discussion'), - document: Omit, - domain: typeof import('./model/domain').default, - message: typeof import('./model/message').default, - opcount: typeof import('./model/opcount'), - problem: typeof import('./model/problem').default, - record: typeof import('./model/record').default, - setting: typeof import('./model/setting'), - solution: typeof import('./model/solution').default, - system: typeof import('./model/system'), - task: typeof import('./model/task').default, + blacklist: typeof import('./model/blacklist').default; + builtin: typeof import('./model/builtin'); + contest: typeof import('./model/contest'); + discussion: typeof import('./model/discussion'); + document: Omit; + domain: typeof import('./model/domain').default; + message: typeof import('./model/message').default; + opcount: typeof import('./model/opcount'); + problem: typeof import('./model/problem').default; + record: typeof import('./model/record').default; + setting: typeof import('./model/setting'); + solution: typeof import('./model/solution').default; + system: typeof import('./model/system'); + task: typeof import('./model/task').default; schedule: typeof import('./model/schedule').default; - oplog: typeof import('./model/oplog'), - token: typeof import('./model/token').default, - training: typeof import('./model/training'), - user: typeof import('./model/user').default, - oauth: typeof import('./model/oauth').default, - storage: typeof import('./model/storage').default, - rp: typeof import('./script/rating').RpTypes, + oplog: typeof import('./model/oplog'); + token: typeof import('./model/token').default; + training: typeof import('./model/training'); + user: typeof import('./model/user').default; + oauth: typeof import('./model/oauth').default; + storage: typeof import('./model/storage').default; + rp: typeof import('./script/rating').RpTypes; } export interface GeoIP { - provider: string, - lookup: (ip: string, locale?: string) => any, + provider: string; + lookup: (ip: string, locale?: string) => any; } export interface ProblemSearchResponse { @@ -604,9 +605,9 @@ export type ProblemSearch = (domainId: string, q: string, options?: ProblemSearc export type UIInjectableFields = 'ProblemAdd' | 'Notification' | 'Nav' | 'UserDropdown' | 'DomainManage' | 'ControlPanel'; export interface UI { - nodes: Record, - getNodes: typeof import('./lib/ui').getNodes, - inject: typeof import('./lib/ui').inject, + nodes: Record; + getNodes: typeof import('./lib/ui').getNodes; + inject: typeof import('./lib/ui').inject; } export interface ModuleInterfaces { @@ -629,8 +630,8 @@ export interface HydroGlobal { declare global { namespace NodeJS { interface Global { - Hydro: HydroGlobal, - addons: string[], + Hydro: HydroGlobal; + addons: string[]; } } /** @deprecated */ diff --git a/packages/hydrooj/src/lib/avatar.ts b/packages/hydrooj/src/lib/avatar.ts index ea962f997..86f82fcb4 100644 --- a/packages/hydrooj/src/lib/avatar.ts +++ b/packages/hydrooj/src/lib/avatar.ts @@ -6,7 +6,7 @@ type AvatarProvider = (src: string, size: number) => string; export const providers: Record = { gravatar: (email, size) => `${global.Hydro.model.system.get('avatar.gravatar_url')}` + `${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size || 32}`, - qq: (id) => `//q1.qlogo.cn/g?b=qq&nk=${(/(\d+)/g.exec(id) || ['', ''])[1]}&s=160`, + qq: (id) => `//q1.qlogo.cn/g?b=qq&nk=${(/(\d+)/.exec(id) || ['', ''])[1]}&s=160`, github: (id, size) => `//github.com/${id}.png?size=${Math.min(size, 460)}`, url: (url) => url, }; diff --git a/packages/hydrooj/src/lib/content.ts b/packages/hydrooj/src/lib/content.ts index f79078635..f31891c15 100644 --- a/packages/hydrooj/src/lib/content.ts +++ b/packages/hydrooj/src/lib/content.ts @@ -1,11 +1,11 @@ export interface ProblemSource { - description?: string, - input?: string, - output?: string, - samples?: [string, string][], - samplesRaw?: string, - hint?: string, - source?: string, + description?: string; + input?: string; + output?: string; + samples?: [string, string][]; + samplesRaw?: string; + hint?: string; + source?: string; } export function buildContent(source: ProblemSource, type: 'markdown' | 'html' = 'markdown', translate?: (s: string) => string) { diff --git a/packages/hydrooj/src/lib/rating.ts b/packages/hydrooj/src/lib/rating.ts index bb4f14207..99d31495d 100644 --- a/packages/hydrooj/src/lib/rating.ts +++ b/packages/hydrooj/src/lib/rating.ts @@ -91,14 +91,14 @@ class RatingCalculator { } interface RatingInputUser { - old: number, - uid: number, - rank: number, + old: number; + uid: number; + rank: number; } interface RatingOutputUser { - new: number, - uid: number, + new: number; + uid: number; } function calculate(users: RatingInputUser[]): RatingOutputUser[] { diff --git a/packages/hydrooj/src/libs.ts b/packages/hydrooj/src/libs.ts index 7dbb92373..b29d55266 100644 --- a/packages/hydrooj/src/libs.ts +++ b/packages/hydrooj/src/libs.ts @@ -5,13 +5,13 @@ import { ObjectId as ObjectID } from 'mongodb'; import Schema from 'schemastery'; import superagent from 'superagent'; import { Context } from './context'; -export { ObjectId, Filter } from 'mongodb'; -export { WebSocket, WebSocketServer } from '@hydrooj/framework'; -export * from './utils'; export * from './interface'; export * from './typeutils'; +export * from './utils'; +export { WebSocket, WebSocketServer } from '@hydrooj/framework'; +export { Filter, ObjectId } from 'mongodb'; export { - Schema, superagent, _, ObjectID, Zip, + _, ObjectID, Schema, superagent, Zip, }; /** @deprecated Use ZipReader/ZipWriter instead */ export const AdmZip = _AdmZip; diff --git a/packages/hydrooj/src/loader.ts b/packages/hydrooj/src/loader.ts index 21d8cdebe..c868b55a9 100644 --- a/packages/hydrooj/src/loader.ts +++ b/packages/hydrooj/src/loader.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-dynamic-require */ /* eslint-disable consistent-return */ /* eslint-disable simple-import-sort/imports */ import './init'; @@ -181,7 +180,7 @@ export async function load() { if (process.env.DEV) { const q = await simpleGit().listRemote(['--get-url']); if (!q.includes('hydro-dev/Hydro')) { - console.warn('\x1b[93m'); + console.warn('\x1B[93m'); console.warn('DISCLAIMER:'); console.warn(' You are under development mode.'); console.warn(' The Hydro project is licensed under AGPL3,'); @@ -195,7 +194,7 @@ export async function load() { console.warn(' 这意味着除非你获得了原作者的其他授权,'); console.warn(' 你需要同样以 AGPL3 协议开源所有的修改,'); console.warn(' 并保留所有的版权声明。'); - console.warn('\x1b[39m'); + console.warn('\x1B[39m'); console.log(''); console.log('Hydro will start in 5s.'); console.log('Hydro 将在五秒后继续启动。'); diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index b86f95b66..24c10d29f 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -641,7 +641,7 @@ const homework = buildContestRule({ ); if (exceedSeconds < 0) return rate * jdoc.score; let coefficient = 1; - const keys = Object.keys(tdoc.penaltyRules).map(parseFloat).sort((a, b) => a - b); + const keys = Object.keys(tdoc.penaltyRules).map(Number.parseFloat).sort((a, b) => a - b); for (const i of keys) { if (i * 3600 <= exceedSeconds) coefficient = tdoc.penaltyRules[i]; else break; diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index 647984ade..73f941231 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -1,4 +1,3 @@ -/* eslint-disable object-curly-newline */ import assert from 'assert'; import { Filter, FindCursor, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter, @@ -43,9 +42,9 @@ export interface DocType { } export interface DocStatusType { - [TYPE_PROBLEM]: ProblemStatusDoc, + [TYPE_PROBLEM]: ProblemStatusDoc; // FIXME: this need to be typed - [key: number]: any + [key: number]: any; } export async function add( diff --git a/packages/hydrooj/src/model/domain.ts b/packages/hydrooj/src/model/domain.ts index fb3ca8448..12ee7f4bb 100644 --- a/packages/hydrooj/src/model/domain.ts +++ b/packages/hydrooj/src/model/domain.ts @@ -15,8 +15,8 @@ const collUser = db.collection('domain.user'); const cache = new LRUCache({ max: 1000, ttl: 300 * 1000 }); interface DomainUserArg { - _id: number, - priv: number, + _id: number; + priv: number; } class DomainModel { @@ -282,7 +282,7 @@ class DomainModel { @ArgMethod static async getPrefixSearch(prefix: string, limit: number = 50) { - const $regex = new RegExp(escapeRegExp(prefix), 'mi'); + const $regex = new RegExp(escapeRegExp(prefix), 'im'); const ddocs = await coll.find({ $or: [{ _id: { $regex } }, { name: { $regex } }], }).limit(limit).toArray(); diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index da19759e8..2e7cf0e56 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -1,4 +1,3 @@ -/* eslint-disable object-curly-newline */ import { pick, sum } from 'lodash'; import moment from 'moment-timezone'; import { @@ -133,11 +132,11 @@ export default class RecordModel { domainId: string, pid: number, uid: number, lang: string, code: string, addTask: boolean, args: { - contest?: ObjectId, - input?: string, - files?: Record, - hackTarget?: ObjectId, - type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate', + contest?: ObjectId; + input?: string; + files?: Record; + hackTarget?: ObjectId; + type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate'; } = { type: 'judge' }, ) { const data: RecordDoc = { diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 02939cb3e..3f0669038 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -69,7 +69,7 @@ export const Setting = ( declare global { namespace Schemastery { - interface Meta { // eslint-disable-line @typescript-eslint/no-unused-vars + interface Meta { // eslint-disable-line ts/no-unused-vars family?: string; secret?: boolean; } diff --git a/packages/hydrooj/src/model/training.ts b/packages/hydrooj/src/model/training.ts index 76ba73b85..b01e70bd8 100644 --- a/packages/hydrooj/src/model/training.ts +++ b/packages/hydrooj/src/model/training.ts @@ -97,8 +97,8 @@ export async function get(domainId: string, tid: ObjectId) { if (!tdoc) throw new TrainingNotFoundError(domainId, tid); for (const i in tdoc.dag) { for (const j in tdoc.dag[i].pids) { - if (Number.isSafeInteger(parseInt(tdoc.dag[i].pids[j], 10))) { - tdoc.dag[i].pids[j] = parseInt(tdoc.dag[i].pids[j], 10); + if (Number.isSafeInteger(Number.parseInt(tdoc.dag[i].pids[j], 10))) { + tdoc.dag[i].pids[j] = Number.parseInt(tdoc.dag[i].pids[j], 10); } } } diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 094d5bbbc..7409fc288 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -5,7 +5,7 @@ import { serializer } from '@hydrooj/framework'; import { LoginError, UserAlreadyExistError, UserNotFoundError } from '../error'; import { Authenticator, BaseUserDict, FileInfo, GDoc, - ownerInfo, Udict, Udoc, VUdoc, + OwnerInfo, Udict, Udoc, VUdoc, } from '../interface'; import avatar from '../lib/avatar'; import pwhash from '../lib/hash.hydro'; @@ -116,9 +116,9 @@ export class User { return this; } - own(doc: T, checkPerm: bigint): boolean; - own(doc: T, exact: boolean): boolean; - own(doc: T): boolean; + own(doc: T, checkPerm: bigint): boolean; + own(doc: T, exact: boolean): boolean; + own(doc: T): boolean; own(doc: T): boolean; own(doc: any, arg1: any = false): boolean { if (typeof arg1 === 'bigint' && !this.hasPerm(arg1)) return false; @@ -143,7 +143,7 @@ export class User { throw new LoginError(this.uname); } if (this.hashType !== 'hydro') { - // eslint-disable-next-line @typescript-eslint/no-use-before-define + // eslint-disable-next-line ts/no-use-before-define UserModel.setPassword(this._id, password); } } diff --git a/packages/hydrooj/src/pipelineUtils.ts b/packages/hydrooj/src/pipelineUtils.ts index 249517527..071c7b18b 100644 --- a/packages/hydrooj/src/pipelineUtils.ts +++ b/packages/hydrooj/src/pipelineUtils.ts @@ -43,7 +43,7 @@ export async function iterateAllPsdoc(filter: Filter, cb: (psd } interface PartialProblemDoc extends ProblemDoc { - [key: string]: any, + [key: string]: any; } export async function iterateAllProblemInDomain( diff --git a/packages/hydrooj/src/plugin-api.ts b/packages/hydrooj/src/plugin-api.ts index e70bbf508..a1a5d421c 100644 --- a/packages/hydrooj/src/plugin-api.ts +++ b/packages/hydrooj/src/plugin-api.ts @@ -1,12 +1,13 @@ +/* eslint-disable perfectionist/sort-exports */ import pwsh from './lib/hash.hydro'; import db from './service/db'; export { nanoid } from 'nanoid'; -export { default as moment, isMoment } from 'moment-timezone'; +export { isMoment, default as moment } from 'moment-timezone'; export { - WebService, Router, HandlerCommon, httpServer, - Query, Mutation, Subscription, Apis, APIS, + Apis, APIS, HandlerCommon, httpServer, + Mutation, Query, Router, Subscription, WebService, } from '@hydrooj/framework'; export * from './pipelineUtils'; @@ -37,10 +38,10 @@ export { default as TaskModel } from './model/task'; export * from './model/builtin'; /** @deprecated */ export * as JudgeHandler from './handler/judge'; -export { postJudge, JudgeResultCallbackContext } from './handler/judge'; +export { JudgeResultCallbackContext, postJudge } from './handler/judge'; export { Collections } from './service/db'; -export { Handler, ConnectionHandler, requireSudo } from './service/server'; -export { Service, Context } from './context'; +export { ConnectionHandler, Handler, requireSudo } from './service/server'; +export { Context, Service } from './context'; export { buildContent } from './lib/content'; export { default as mime } from './lib/mime'; export { default as difficultyAlgorithm } from './lib/difficulty'; diff --git a/packages/hydrooj/src/script/blacklist.ts b/packages/hydrooj/src/script/blacklist.ts index 1fcfad896..b4924f8d7 100644 --- a/packages/hydrooj/src/script/blacklist.ts +++ b/packages/hydrooj/src/script/blacklist.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ +/* eslint-disable ts/no-use-before-define */ import { ObjectId } from 'mongodb'; import Schema from 'schemastery'; import blacklist from '../model/blacklist'; diff --git a/packages/hydrooj/src/script/checkUpdate.ts b/packages/hydrooj/src/script/checkUpdate.ts index 1a18a7b98..034c84ae1 100644 --- a/packages/hydrooj/src/script/checkUpdate.ts +++ b/packages/hydrooj/src/script/checkUpdate.ts @@ -14,7 +14,7 @@ async function getRemoteVersion(id: string) { } } -export async function run(_: {}, report: Function) { +export async function run(_: void, report: (data: any) => void) { const current = global.Hydro.version; const message = ['']; for (const name in current) { diff --git a/packages/hydrooj/src/script/rating.ts b/packages/hydrooj/src/script/rating.ts index 2ec3fa0af..43cbe94ab 100644 --- a/packages/hydrooj/src/script/rating.ts +++ b/packages/hydrooj/src/script/rating.ts @@ -17,9 +17,10 @@ import db from '../service/db'; export const description = 'Calculate rp of a domain, or all domains'; type ND = NumericDictionary; +type Report = (data: any) => void; interface RpDef { - run(domainIds: string[], udict: ND, report: Function): Promise; + run(domainIds: string[], udict: ND, report: Report): Promise; hidden: boolean; base: number; } @@ -101,7 +102,7 @@ export const RpTypes: Record = { }; global.Hydro.model.rp = RpTypes; -export async function calcLevel(domainId: string, report: Function) { +export async function calcLevel(domainId: string, report: Report) { await domain.setMultiUserInDomain(domainId, {}, { level: 0, rank: null }); let last = { rp: null }; let rank = 0; @@ -135,7 +136,7 @@ export async function calcLevel(domainId: string, report: Function) { await bulk.execute(); } -async function runInDomain(domainId: string, report: Function) { +async function runInDomain(domainId: string, report: Report) { const results: Record = {}; const udict = Counter(); await db.collection('domain.user').updateMany({ domainId }, { $set: { rpInfo: {} } }); @@ -160,7 +161,7 @@ async function runInDomain(domainId: string, report: Function) { await calcLevel(domainId, report); } -export async function run({ domainId }, report: Function) { +export async function run({ domainId }, report: Report) { if (!domainId) { const domains = await domain.getMulti().toArray(); await report({ message: `Found ${domains.length} domains` }); diff --git a/packages/hydrooj/src/service/bus.ts b/packages/hydrooj/src/service/bus.ts index 1ecde1fb7..8e1ec1233 100644 --- a/packages/hydrooj/src/service/bus.ts +++ b/packages/hydrooj/src/service/bus.ts @@ -15,95 +15,95 @@ export type Disposable = () => void; export type VoidReturn = Promise | any; export interface EventMap { - 'app/listen': () => void - 'app/started': () => void - 'app/ready': () => VoidReturn - 'app/exit': () => VoidReturn - 'app/before-reload': (entries: Set) => VoidReturn - 'app/reload': (entries: Set) => VoidReturn + 'app/listen': () => void; + 'app/started': () => void; + 'app/ready': () => VoidReturn; + 'app/exit': () => VoidReturn; + 'app/before-reload': (entries: Set) => VoidReturn; + 'app/reload': (entries: Set) => VoidReturn; - 'subscription/init': (h: ConnectionHandler, privileged: boolean) => VoidReturn - 'subscription/subscribe': (channel: string, user: User, metadata: Record) => VoidReturn + 'subscription/init': (h: ConnectionHandler, privileged: boolean) => VoidReturn; + 'subscription/subscribe': (channel: string, user: User, metadata: Record) => VoidReturn; 'subscription/enable': ( channel: string, h: ConnectionHandler, privileged: boolean, onDispose: (disposable: () => void) => void - ) => VoidReturn + ) => VoidReturn; - 'app/watch/change': (path: string) => VoidReturn - 'app/watch/unlink': (path: string) => VoidReturn + 'app/watch/change': (path: string) => VoidReturn; + 'app/watch/unlink': (path: string) => VoidReturn; - 'database/connect': (db: Db) => void - 'database/config': () => VoidReturn + 'database/connect': (db: Db) => void; + 'database/config': () => VoidReturn; - 'system/setting': (args: Record) => VoidReturn - 'bus/broadcast': (event: keyof EventMap, payload: any, trace?: string) => VoidReturn - 'monitor/update': (type: 'server' | 'judge', $set: any) => VoidReturn - 'monitor/collect': (info: any) => VoidReturn + 'system/setting': (args: Record) => VoidReturn; + 'bus/broadcast': (event: keyof EventMap, payload: any, trace?: string) => VoidReturn; + 'monitor/update': (type: 'server' | 'judge', $set: any) => VoidReturn; + 'monitor/collect': (info: any) => VoidReturn; 'api/update': () => void; 'task/daily': () => void; 'task/daily/finish': (pref: Record) => void; - 'user/message': (uid: number[], mdoc: Omit) => void - 'user/get': (udoc: User) => void - 'user/delcache': (content: string | true) => void + 'user/message': (uid: number[], mdoc: Omit) => void; + 'user/get': (udoc: User) => void; + 'user/delcache': (content: string | true) => void; - 'user/import/parse': (payload: any) => VoidReturn - 'user/import/create': (uid: number, udoc: any) => VoidReturn + 'user/import/parse': (payload: any) => VoidReturn; + 'user/import/create': (uid: number, udoc: any) => VoidReturn; - 'domain/create': (ddoc: DomainDoc) => VoidReturn - 'domain/before-get': (query: Filter) => VoidReturn - 'domain/get': (ddoc: DomainDoc) => VoidReturn - 'domain/before-update': (domainId: string, $set: Partial) => VoidReturn - 'domain/update': (domainId: string, $set: Partial, ddoc: DomainDoc) => VoidReturn - 'domain/delete': (domainId: string) => VoidReturn - 'domain/delete-cache': (domainId: string) => VoidReturn + 'domain/create': (ddoc: DomainDoc) => VoidReturn; + 'domain/before-get': (query: Filter) => VoidReturn; + 'domain/get': (ddoc: DomainDoc) => VoidReturn; + 'domain/before-update': (domainId: string, $set: Partial) => VoidReturn; + 'domain/update': (domainId: string, $set: Partial, ddoc: DomainDoc) => VoidReturn; + 'domain/delete': (domainId: string) => VoidReturn; + 'domain/delete-cache': (domainId: string) => VoidReturn; - 'document/add': (doc: any) => VoidReturn + 'document/add': (doc: any) => VoidReturn; 'document/set': ( domainId: string, docType: T, docId: DocType[T], $set: any, $unset: OnlyFieldsOfType - ) => VoidReturn + ) => VoidReturn; - 'discussion/before-add': (payload: Partial) => VoidReturn - 'discussion/add': (payload: Partial) => VoidReturn + 'discussion/before-add': (payload: Partial) => VoidReturn; + 'discussion/add': (payload: Partial) => VoidReturn; - 'problem/before-add': (domainId: string, content: string, owner: number, docId: number, doc: Partial) => VoidReturn - 'problem/add': (doc: Partial, docId: number) => VoidReturn - 'problem/before-edit': (doc: Partial, $unset: OnlyFieldsOfType) => VoidReturn - 'problem/edit': (doc: ProblemDoc) => VoidReturn - 'problem/before-del': (domainId: string, docId: number) => VoidReturn - 'problem/del': (domainId: string, docId: number) => VoidReturn - 'problem/list': (query: Filter, handler: any, sort?: string[]) => VoidReturn - 'problem/get': (doc: ProblemDoc, handler: any) => VoidReturn - 'problem/delete': (domainId: string, docId: number) => VoidReturn - 'problem/addTestdata': (domainId: string, docId: number, name: string, payload: Omit) => VoidReturn - 'problem/renameTestdata': (domainId: string, docId: number, name: string, newName: string) => VoidReturn - 'problem/delTestdata': (domainId: string, docId: number, name: string[]) => VoidReturn - 'problem/addAdditionalFile': (domainId: string, docId: number, name: string, payload: Omit) => VoidReturn - 'problem/renameAdditionalFile': (domainId: string, docId: number, name: string, newName: string) => VoidReturn - 'problem/delAdditionalFile': (domainId: string, docId: number, name: string[]) => VoidReturn + 'problem/before-add': (domainId: string, content: string, owner: number, docId: number, doc: Partial) => VoidReturn; + 'problem/add': (doc: Partial, docId: number) => VoidReturn; + 'problem/before-edit': (doc: Partial, $unset: OnlyFieldsOfType) => VoidReturn; + 'problem/edit': (doc: ProblemDoc) => VoidReturn; + 'problem/before-del': (domainId: string, docId: number) => VoidReturn; + 'problem/del': (domainId: string, docId: number) => VoidReturn; + 'problem/list': (query: Filter, handler: any, sort?: string[]) => VoidReturn; + 'problem/get': (doc: ProblemDoc, handler: any) => VoidReturn; + 'problem/delete': (domainId: string, docId: number) => VoidReturn; + 'problem/addTestdata': (domainId: string, docId: number, name: string, payload: Omit) => VoidReturn; + 'problem/renameTestdata': (domainId: string, docId: number, name: string, newName: string) => VoidReturn; + 'problem/delTestdata': (domainId: string, docId: number, name: string[]) => VoidReturn; + 'problem/addAdditionalFile': (domainId: string, docId: number, name: string, payload: Omit) => VoidReturn; + 'problem/renameAdditionalFile': (domainId: string, docId: number, name: string, newName: string) => VoidReturn; + 'problem/delAdditionalFile': (domainId: string, docId: number, name: string[]) => VoidReturn; - 'contest/before-add': (payload: Partial) => VoidReturn - 'contest/add': (payload: Partial, id: ObjectId) => VoidReturn - 'contest/edit': (payload: Tdoc) => VoidReturn - 'contest/list': (query: Filter, handler: any) => VoidReturn - 'contest/scoreboard': (tdoc: Tdoc, rows: ScoreboardRow[], udict: BaseUserDict, pdict: ProblemDict) => VoidReturn - 'contest/balloon': (domainId: string, tid: ObjectId, bdoc: ContestBalloonDoc) => VoidReturn - 'contest/del': (domainId: string, tid: ObjectId) => VoidReturn + 'contest/before-add': (payload: Partial) => VoidReturn; + 'contest/add': (payload: Partial, id: ObjectId) => VoidReturn; + 'contest/edit': (payload: Tdoc) => VoidReturn; + 'contest/list': (query: Filter, handler: any) => VoidReturn; + 'contest/scoreboard': (tdoc: Tdoc, rows: ScoreboardRow[], udict: BaseUserDict, pdict: ProblemDict) => VoidReturn; + 'contest/balloon': (domainId: string, tid: ObjectId, bdoc: ContestBalloonDoc) => VoidReturn; + 'contest/del': (domainId: string, tid: ObjectId) => VoidReturn; 'oplog/log': (type: string, handler: Handler | ConnectionHandler, args: any, data: any) => VoidReturn; - 'training/list': (query: Filter, handler: any) => VoidReturn - 'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn + 'training/list': (query: Filter, handler: any) => VoidReturn; + 'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn; - 'record/change': (rdoc: RecordDoc, $set?: any, $push?: any, body?: any) => void - 'record/judge': (rdoc: RecordDoc, updated: boolean, pdoc?: ProblemDoc, updater?: any) => VoidReturn + 'record/change': (rdoc: RecordDoc, $set?: any, $push?: any, body?: any) => void; + 'record/judge': (rdoc: RecordDoc, updated: boolean, pdoc?: ProblemDoc, updater?: any) => VoidReturn; } export function apply(ctx: Context) { try { - if (!process.send || !pm2 || process.env.exec_mode !== 'cluster_mode') throw new Error(); + if (!process.send || !pm2 || process.env.exec_mode !== 'cluster_mode') throw new Error('not in cluster mode'); pm2.launchBus((err, bus) => { - if (err) throw new Error(); + if (err) throw new Error('cannot launch pm2 bus'); bus.on('hydro:broadcast', (packet) => { (app.parallel as any)(packet.data.event, ...BSON.EJSON.parse(packet.data.payload)); }); diff --git a/packages/hydrooj/src/service/db.ts b/packages/hydrooj/src/service/db.ts index 1696ea347..626229360 100644 --- a/packages/hydrooj/src/service/db.ts +++ b/packages/hydrooj/src/service/db.ts @@ -14,16 +14,16 @@ const logger = new Logger('mongo'); export interface Collections { } interface MongoConfig { - protocol?: string, - username?: string, - password?: string, - host?: string, - port?: string, - name?: string, - url?: string, - uri?: string, - prefix?: string, - collectionMap?: Record, + protocol?: string; + username?: string; + password?: string; + host?: string; + port?: string; + name?: string; + url?: string; + uri?: string; + prefix?: string; + collectionMap?: Record; } declare module '../context' { diff --git a/packages/hydrooj/src/service/hmr.ts b/packages/hydrooj/src/service/hmr.ts index 54022192d..1673ef395 100644 --- a/packages/hydrooj/src/service/hmr.ts +++ b/packages/hydrooj/src/service/hmr.ts @@ -1,7 +1,6 @@ // This file was adapted from @koishijs, MIT licensed. /* eslint-disable consistent-return */ -/* eslint-disable import/no-dynamic-require */ -/* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable ts/no-shadow */ import { readFileSync } from 'fs'; import { relative } from 'path'; import { codeFrameColumns } from '@babel/code-frame'; diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 58895fd9a..0b5dc130b 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -28,7 +28,7 @@ const ignoredLimit = `,${argv.options.ignoredLimit},`; const logger = new Logger('server'); declare module '@hydrooj/framework' { - export interface HandlerCommon { // eslint-disable-line @typescript-eslint/no-unused-vars + export interface HandlerCommon { // eslint-disable-line ts/no-unused-vars domain: DomainDoc; paginate(cursor: FindCursor, page: number, key: string): Promise<[docs: T[], numPages: number, count: number]>; @@ -39,9 +39,9 @@ declare module '@hydrooj/framework' { } } +export { Mutation, Query } from '@hydrooj/framework/api'; export * from '@hydrooj/framework/decorators'; export * from '@hydrooj/framework/validator'; -export { Query, Mutation } from '@hydrooj/framework/api'; /* * For security concern, some API requires sudo privilege to access. @@ -153,7 +153,7 @@ export async function apply(ctx: Context) { ? (!host.includes(this.request.host)) : this.request.host !== host) )) withDomainId ||= domainId; - res = server.router.url.call(server.router, name, args, { query }).toString(); + res = server.router.url(name, args, { query }).toString(); if (anchor) res = `${res}#${anchor}`; if (withDomainId) res = `/d/${withDomainId}${res}`; } catch (e) { diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index 5b63cc9c7..2e73875dd 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -1,7 +1,7 @@ /* eslint-disable consistent-return */ -/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable ts/no-unused-vars */ /* eslint-disable no-await-in-loop */ -/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable ts/naming-convention */ import yaml from 'js-yaml'; import { ObjectId } from 'mongodb'; import { randomstring, sleep } from '@hydrooj/utils'; @@ -51,7 +51,7 @@ export const coreScripts: MigrationScript[] = [ return true; }, // Init - ...new Array(47).fill(unsupportedUpgrade), // oxlint-disable-line no-new-array + ...Array.from({ length: 47 }).fill(unsupportedUpgrade) as any, async function _48_49() { await RecordModel.coll.updateMany({ input: { $exists: true } }, { $set: { contest: new ObjectId('000000000000000000000000') } }); return true; @@ -221,7 +221,7 @@ export const coreScripts: MigrationScript[] = [ config.answers[cnt] = l; } - function processSingleLanguage(content: string) { // eslint-disable-line no-inner-declarations + function processSingleLanguage(content: string) { let text = ''; try { let scnt = 0; diff --git a/packages/login-with-google/index.ts b/packages/login-with-google/index.ts index d12553d7a..8b44a3280 100644 --- a/packages/login-with-google/index.ts +++ b/packages/login-with-google/index.ts @@ -3,7 +3,7 @@ import { } from 'hydrooj'; function unescapedString(escapedString: string) { - escapedString += new Array(5 - (escapedString.length % 4)).join('='); + escapedString += Array.from({ length: 5 - (escapedString.length % 4) }).join('='); return escapedString.replace(/-/g, '+').replace(/_/g, '/'); } diff --git a/packages/migrate/scripts/hustoj.ts b/packages/migrate/scripts/hustoj.ts index 9db9ef97e..7f1617f9a 100644 --- a/packages/migrate/scripts/hustoj.ts +++ b/packages/migrate/scripts/hustoj.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-tabs */ +/* eslint-disable style/no-tabs */ /* eslint-disable no-await-in-loop */ import path from 'path'; import mariadb from 'mariadb'; @@ -70,7 +70,7 @@ export async function run({ host = 'localhost', port = 3306, name = 'jol', username, password, domainId, contestType = 'oi', dataDir, uploadDir = '/home/judge/src/web/upload/', rerun = true, randomMail = false, -}, report: Function) { +}, report: (data: any) => void) { let remoteUsed = false; const src = await mariadb.createConnection({ host, @@ -191,7 +191,7 @@ export async function run({ samples: [[pdoc.sample_input.trim(), pdoc.sample_output.trim()]], hint: pdoc.hint, source: pdoc.source, - }, 'html').replace(/lns=/g, 'lns=/g, ' addContestFile(domainId, tid, filename, files[filename]))); if (Object.keys(files).length) report({ message: `move ${Object.keys(files).length} file for contest ${tidMap[tdoc.contest_id]}` }); - const allowedUser:{ user_id:string }[] = await query(`SELECT * FROM privilege WHERE rightstr = 'c${tdoc.contest_id}';`); + const allowedUser: { user_id: string }[] = await query(`SELECT * FROM privilege WHERE rightstr = 'c${tdoc.contest_id}';`); const assignUserList = allowedUser.map((i) => uidMap[i.user_id]).filter((i) => i); if (isAssignMode) { await ContestModel.edit(domainId, tid, { diff --git a/packages/migrate/scripts/poj.ts b/packages/migrate/scripts/poj.ts index 295eb8a0a..505f8a25e 100644 --- a/packages/migrate/scripts/poj.ts +++ b/packages/migrate/scripts/poj.ts @@ -1,5 +1,6 @@ -/* eslint-disable no-tabs */ /* eslint-disable no-await-in-loop */ +/* eslint-disable style/no-tabs */ + import path from 'path'; import zlib from 'zlib'; import mariadb from 'mariadb'; @@ -34,7 +35,7 @@ export async function run({ host = 'localhost', port = 3306, name, username, password, domainId, contestType = 'oi', dataDir, imageDir, rerun = true, randomMail = false, -}, report: Function) { +}, report: (data: any) => void) { const src = await mariadb.createConnection({ host, port, @@ -150,6 +151,7 @@ export async function run({ hint: pdoc.hint, source: pdoc.source, }, 'html'); + // eslint-disable-next-line regexp/no-super-linear-backtracking const uploadFiles = content.matchAll(/(?:src|href)="\/images\/([^"]+\/([^"]+))"/g); for (const file of uploadFiles) { try { diff --git a/packages/migrate/scripts/syzoj.ts b/packages/migrate/scripts/syzoj.ts index 61967f8a7..fb2ffb0e1 100644 --- a/packages/migrate/scripts/syzoj.ts +++ b/packages/migrate/scripts/syzoj.ts @@ -75,7 +75,7 @@ export async function run({ host = 'localhost', port = 3306, name = 'syzoj', username, password, domainId, dataDir, rerun = true, randomMail = 'never', -}, report: Function) { +}, report: (data: any) => void) { const src = await mariadb.createConnection({ host, port, @@ -199,7 +199,7 @@ export async function run({ `publicize_time` datetime NULL DEFAULT NULL, 公开时间 `type` enum('traditional','submit-answer','interaction') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'traditional', */ - const fileReg = /\[.*\]\((\/problem\/(\d+)\/testdata\/download\/(.*))\)/gm; + const fileReg = /\[.*\]\((\/problem\/(\d+)\/testdata\/download\/(.*))\)/g; const pidMap: Record = {}; const configMap: Record = {}; const problemAdditionalFile = {}; diff --git a/packages/migrate/scripts/universaloj.ts b/packages/migrate/scripts/universaloj.ts index e9f48cdeb..ea33a7971 100644 --- a/packages/migrate/scripts/universaloj.ts +++ b/packages/migrate/scripts/universaloj.ts @@ -45,7 +45,7 @@ export async function run({ host = '172.17.0.2', port = 3306, name = 'app_uoj233', username, password, domainId, dataDir, rerun = true, randomMail = false, -}, report: Function) { +}, report: (data: any) => void) { const src = await mariadb.createConnection({ host, port, @@ -506,10 +506,10 @@ export async function run({ if (confInfo.n_subtasks && Object.keys(confInfo.subtask_end).length === +confInfo.n_subtasks && Object.keys(confInfo.subtask_score).length === +confInfo.n_subtasks) { - config.subtasks.push(...[...new Array(+confInfo.n_subtasks)].map((v, i) => i + 1).map((i) => ({ + config.subtasks.push(...[...Array.from({ length: +confInfo.n_subtasks })].map((v, i) => i + 1).map((i) => ({ id: +i, score: confInfo.subtask_score[i], - cases: [...new Array(confInfo.subtask_end[i] - (confInfo.subtask_end[i - 1] || 0))].map((v, j) => ({ + cases: [...Array.from({ length: confInfo.subtask_end[i] - (confInfo.subtask_end[i - 1] || 0) })].map((v, j) => ({ input: `${confInfo.input_pre}${j + (confInfo.subtask_end[i - 1] || 0) + 1}.${confInfo.input_suf}`, output: `${confInfo.output_pre}${j + (confInfo.subtask_end[i - 1] || 0) + 1}.${confInfo.output_suf}`, })), @@ -521,7 +521,7 @@ export async function run({ id: 1, score: 97, type: 'sum' as SubtaskType, - cases: [...new Array(+confInfo.n_tests)].map((v, i) => i + 1).map((i) => ({ + cases: [...Array.from({ length: +confInfo.n_tests })].map((v, i) => i + 1).map((i) => ({ input: `${confInfo.input_pre}${i}.${confInfo.input_suf}`, output: `${confInfo.output_pre}${i}.${confInfo.output_suf}`, })), @@ -530,7 +530,7 @@ export async function run({ config.subtasks.push({ id: Math.max(...config.subtasks.map((i) => i.id)) + 1, score: 3, - cases: [...new Array(+confInfo.n_ex_tests)].map((v, i) => i + 1).map((i) => ({ + cases: [...Array.from({ length: +confInfo.n_ex_tests })].map((v, i) => i + 1).map((i) => ({ input: `ex_${confInfo.input_pre}${i}.${confInfo.input_suf}`, output: `ex_${confInfo.output_pre}${i}.${confInfo.output_suf}`, })), diff --git a/packages/migrate/scripts/vijos.ts b/packages/migrate/scripts/vijos.ts index 2dfc05043..12270ce0b 100644 --- a/packages/migrate/scripts/vijos.ts +++ b/packages/migrate/scripts/vijos.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable ts/naming-convention */ /* eslint-disable no-await-in-loop */ import mongodb, { Db, FindCursor } from 'mongodb'; import { @@ -13,6 +13,8 @@ const pid = (id) => { return id; }; +type Report = (args: { progress?: number, message?: string }) => void; + const tasks = { user: async (doc) => ({ _id: doc._id, @@ -93,7 +95,7 @@ const tasks = { processer: (rule) => { const n = {}; for (const key in rule) { - n[parseInt(key, 10) / 3600] = rule; + n[Number.parseInt(key, 10) / 3600] = rule; } return n; }, @@ -282,7 +284,7 @@ const cursor: NodeJS.Dict = { file: (s) => s.collection('fs.files').find(), }; -async function discussionNode(src: Db, report: Function) { +async function discussionNode(src: Db, report: Report) { const count = await src.collection('document').countDocuments({ doc_type: 20 }); await report({ progress: 1, message: `discussion.node: ${count}` }); const total = Math.floor(count / 5); @@ -327,7 +329,7 @@ async function fix(doc) { ); } -async function fixProblem(report: Function) { +async function fixProblem(report: Report) { const count = await dst.collection('document').countDocuments({ docType: 10 }); await report({ progress: 1, message: `Fix pid: ${count}` }); const total = Math.floor(count / 50); @@ -349,7 +351,7 @@ function objid(ts: Date) { } // FIXME this seems not working -async function message(src: Db, report: Function) { +async function message(src: Db, report: Report) { const count = await src.collection('message').countDocuments(); await report({ progress: 1, message: `Messages: ${count}` }); const total = Math.floor(count / 50); @@ -373,7 +375,7 @@ async function message(src: Db, report: Function) { } } -async function removeInvalidPid(report: Function) { +async function removeInvalidPid(report: Report) { const count = await dst.collection('document').countDocuments({ docType: 10 }); const bulk = dst.collection('document').initializeUnorderedBulkOp(); await report({ progress: 1, message: `Remove pid: ${count}` }); @@ -383,7 +385,7 @@ async function removeInvalidPid(report: Function) { .find({ docType: 10 }).skip(i * 50).limit(50) .toArray(); for (const doc of docs) { - const id = parseInt(doc.pid, 10); + const id = Number.parseInt(doc.pid, 10); if (Number.isSafeInteger(id)) { bulk.find({ _id: doc._id }).updateOne({ $unset: { pid: '' } }); } @@ -392,7 +394,7 @@ async function removeInvalidPid(report: Function) { } } -async function task(name: any, src: Db, report: Function) { +async function task(name: any, src: Db, report: Report) { await report({ progress: 1, message: `${name}` }); let lastProgress = -1; for (let i = 0; ; i++) { @@ -466,7 +468,7 @@ async function task(name: any, src: Db, report: Function) { export async function run({ host = 'localhost', port = 27017, name = 'vijos4', username, password, -}, report: Function) { +}, report: Report) { let mongourl = 'mongodb://'; if (username) mongourl += `${username}:${password}@`; mongourl += `${host}:${port}/${name}`; diff --git a/packages/onsite-toolkit/index.ts b/packages/onsite-toolkit/index.ts index 228469cfe..7322c892f 100644 --- a/packages/onsite-toolkit/index.ts +++ b/packages/onsite-toolkit/index.ts @@ -139,25 +139,25 @@ export function apply(ctx: Context) { solved: +i === STATUS.STATUS_ACCEPTED, })), getFeed('languages', { - id: 'c', name: 'C', 'entry_point_required': false, extensions: ['c'], + id: 'c', name: 'C', entry_point_required: false, extensions: ['c'], }), getFeed('languages', { - id: 'cpp', name: 'C++', 'entry_point_required': false, extensions: ['cpp', 'cc', 'cxx', 'c++'], + id: 'cpp', name: 'C++', entry_point_required: false, extensions: ['cpp', 'cc', 'cxx', 'c++'], }), getFeed('languages', { - id: 'java', name: 'Java', 'entry_point_required': true, extensions: ['java'], + id: 'java', name: 'Java', entry_point_required: true, extensions: ['java'], }), getFeed('languages', { - id: 'python3', name: 'Python 3', 'entry_point_required': false, 'entry_point_name': 'Main file', extensions: ['py', 'py3'], + id: 'python3', name: 'Python 3', entry_point_required: false, entry_point_name: 'Main file', extensions: ['py', 'py3'], }), getFeed('languages', { - id: 'kotlin', name: 'Kotlin', 'entry_point_required': true, extensions: ['kt'], + id: 'kotlin', name: 'Kotlin', entry_point_required: true, extensions: ['kt'], }), getFeed('languages', { - id: 'rust', name: 'Rust', 'entry_point_required': true, extensions: ['rs'], + id: 'rust', name: 'Rust', entry_point_required: true, extensions: ['rs'], }), getFeed('languages', { - id: 'go', name: 'Go', 'entry_point_required': true, extensions: ['go'], + id: 'go', name: 'Go', entry_point_required: true, extensions: ['go'], }), getFeed('groups', { id: 'participants', name: '正式队伍' }), getFeed('groups', { id: 'observers', name: '打星队伍' }), diff --git a/packages/scoreboard-xcpcio/index.ts b/packages/scoreboard-xcpcio/index.ts index aa3621120..009b3e38e 100644 --- a/packages/scoreboard-xcpcio/index.ts +++ b/packages/scoreboard-xcpcio/index.ts @@ -4,8 +4,8 @@ import { } from 'hydrooj'; const file = fs.readFileSync(path.join(__dirname, 'public/assets/board.html'), 'utf8'); -const indexJs = file.match(/index-([A-Za-z0-9_-]+)\.js"/)?.[1]; -const indexCss = file.match(/index-([A-Za-z0-9_-]+)\.css"/)?.[1]; +const indexJs = file.match(/index-([\w-]+)\.js"/)?.[1]; +const indexCss = file.match(/index-([\w-]+)\.css"/)?.[1]; const status = { [STATUS.STATUS_WRONG_ANSWER]: 'WRONG_ANSWER', [STATUS.STATUS_ACCEPTED]: 'CORRECT', @@ -72,9 +72,9 @@ export async function apply(ctx: Context) { }, organization: 'School', status_time_display: { - 'correct': true, - 'incorrect': true, - 'pending': true, + correct: true, + incorrect: true, + pending: true, }, medal: { official: { diff --git a/packages/ui-default/api.ts b/packages/ui-default/api.ts index 98b5bcda5..fd86dcd4c 100644 --- a/packages/ui-default/api.ts +++ b/packages/ui-default/api.ts @@ -1,23 +1,23 @@ /* eslint-disable import/first */ -export * from './utils'; -export { default as Notification } from './components/notification'; export * from './components/dialog'; -export { Context, Service, ctx } from './context'; -export { AnsiUp } from 'ansi_up'; export { default as loadMonaco } from './components/monaco/loader'; +export { default as Notification } from './components/notification'; export { default as Socket } from './components/socket/index'; export { default as uploadFiles } from './components/upload'; -export * as redux from 'react-redux'; export * from './components/zipDownloader'; -export * from '@hydrooj/utils/lib/common'; export { default as download } from './components/zipDownloader'; +export { Context, ctx, Service } from './context'; +export { initPageLoader } from './hydro'; +export * from './lazyload'; +export * from './misc/Page'; +export * from './utils'; +export * from '@hydrooj/utils/lib/common'; +export { AnsiUp } from 'ansi_up'; export { default as $ } from 'jquery'; export { default as _ } from 'lodash'; export { default as React } from 'react'; export { default as ReactDOM } from 'react-dom/client'; -export * from './misc/Page'; -export { initPageLoader } from './hydro'; -export * from './lazyload'; +export * as redux from 'react-redux'; import { load } from './lazyload'; export default load; @@ -30,7 +30,7 @@ import ProblemSelectAutoComplete from './components/autocomplete/ProblemSelectAu import UserSelectAutoComplete from './components/autocomplete/UserSelectAutoComplete'; export { - AutoComplete, UserSelectAutoComplete, ProblemSelectAutoComplete, DomainSelectAutoComplete, CustomSelectAutoComplete, + AutoComplete, CustomSelectAutoComplete, DomainSelectAutoComplete, ProblemSelectAutoComplete, UserSelectAutoComplete, }; export function addPage(page: import('./misc/Page').Page | (() => Promise | void)) { window.Hydro.extraPages.push(page); diff --git a/packages/ui-default/backendlib/builder.ts b/packages/ui-default/backendlib/builder.ts index 94fb2ca1f..82a782cf9 100644 --- a/packages/ui-default/backendlib/builder.ts +++ b/packages/ui-default/backendlib/builder.ts @@ -10,7 +10,7 @@ import esbuild from 'esbuild'; declare module 'hydrooj' { interface UI { - esbuildPlugins?: esbuild.Plugin[] + esbuildPlugins?: esbuild.Plugin[]; } interface SystemKeys { 'ui-default.nav_logo_dark': string; @@ -91,7 +91,7 @@ export async function buildUI() { const entryPoints: string[] = []; const lazyModules: string[] = []; const newFiles = ['entry.js']; - for (const addon of Object.values(global.addons)) { + for (const addon of Object.values(global.addons) as string[]) { let publicPath = resolve(addon, 'frontend'); if (!fs.existsSync(publicPath)) publicPath = resolve(addon, 'public'); if (!fs.existsSync(publicPath)) continue; diff --git a/packages/ui-default/backendlib/markdown-it-imsize.ts b/packages/ui-default/backendlib/markdown-it-imsize.ts index 4ada0d7be..59b940be8 100644 --- a/packages/ui-default/backendlib/markdown-it-imsize.ts +++ b/packages/ui-default/backendlib/markdown-it-imsize.ts @@ -8,7 +8,7 @@ function parseNextNumber(str, pos, max) { value: '', }; let code = str.charCodeAt(pos); - while (pos < max && (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || code === 0x25 /* % */) { + while (pos < max && ((code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || code === 0x25 /* % */)) { code = str.charCodeAt(++pos); } result.ok = true; @@ -24,9 +24,9 @@ function parseImageSize(str, pos, max) { width: '', height: '', }; - if (pos >= max) { return result; } + if (pos >= max) return result; let code = str.charCodeAt(pos); - if (code !== 0x3d /* = */) { return result; } + if (code !== 0x3D /* = */) return result; pos++; code = str.charCodeAt(pos); if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) { @@ -67,14 +67,14 @@ export default function plugin(md) { const oldPos = state.pos; const max = state.posMax; - if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } - if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) return false; + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) return false; const labelStart = state.pos + 2; const labelEnd = md.helpers.parseLinkLabel(state, state.pos + 1, false); // parser failed to find ']', so it's not a valid link - if (labelEnd < 0) { return false; } + if (labelEnd < 0) return false; pos = labelEnd + 1; if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { @@ -89,7 +89,7 @@ export default function plugin(md) { code = state.src.charCodeAt(pos); if (code !== 0x20 && code !== 0x0A) { break; } } - if (pos >= max) { return false; } + if (pos >= max) return false; // [link]( "title" ) // ^^^^^^ parsing link destination @@ -162,7 +162,7 @@ export default function plugin(md) { // // Link reference // - if (typeof state.env.references === 'undefined') { return false; } + if (typeof state.env.references === 'undefined') return false; // [foo] [bar] // ^^ optional whitespace (can include newlines) diff --git a/packages/ui-default/backendlib/markdown-it-katex.ts b/packages/ui-default/backendlib/markdown-it-katex.ts index 46c9de279..e6067be97 100644 --- a/packages/ui-default/backendlib/markdown-it-katex.ts +++ b/packages/ui-default/backendlib/markdown-it-katex.ts @@ -112,7 +112,7 @@ export default function plugin(md) { const render = (latex: string, displayMode = false) => { options.displayMode = displayMode; try { - latex = latex.replace(/\\def{\\([a-zA-Z0-9]+)}/g, '\\def\\$1'); + latex = latex.replace(/\\def\{\\([a-zA-Z0-9]+)\}/g, '\\def\\$1'); return katex.renderToString(latex, options); } catch (error) { if (options.throwOnError) console.error(error); diff --git a/packages/ui-default/backendlib/markdown-it-media.ts b/packages/ui-default/backendlib/markdown-it-media.ts index 9a192e2e2..5410b9b2f 100644 --- a/packages/ui-default/backendlib/markdown-it-media.ts +++ b/packages/ui-default/backendlib/markdown-it-media.ts @@ -1,4 +1,7 @@ /* eslint-disable max-len */ +/* eslint-disable regexp/prefer-question-quantifier */ +/* eslint-disable regexp/no-useless-non-capturing-group */ +/* eslint-disable regexp/optimal-quantifier-concatenation */ import type MarkdownIt from 'markdown-it'; import { v4 as uuid } from 'uuid'; @@ -10,6 +13,7 @@ function youtubeParser(url: string) { const match = url.match(ytRegex); return match && match[7].length === 11 ? match[7] : url; } +// eslint-disable-next-line regexp/no-empty-alternative const vimeoRegex = /https?:\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|album\/(\d+)\/video\/|)(\d+)(?:$|\/|\?)/; function vimeoParser(url: string) { const match = url.match(vimeoRegex); @@ -25,7 +29,7 @@ function preziParser(url: string) { const match = url.match(preziRegex); return match ? match[1] : url; } -const EMBED_REGEX = /@\[([a-zA-Z].+?)]\((.*?)[)]/im; +const EMBED_REGEX = /@\[([a-zA-Z].+?)\]\((.*?)\)/; function extractVideoParameters(url: string) { const parameterMap = new Map(); const params = url.replace(/&/gi, '&').split(/[#?&]/); @@ -83,7 +87,7 @@ declare module 'hydrooj' { interface ModuleInterfaces { richmedia: { get: (service: string, src: string, md: MarkdownIt) => string | null; - } + }; } } diff --git a/packages/ui-default/backendlib/markdown-it-xss.ts b/packages/ui-default/backendlib/markdown-it-xss.ts index 5f35b6e6b..7e372919c 100644 --- a/packages/ui-default/backendlib/markdown-it-xss.ts +++ b/packages/ui-default/backendlib/markdown-it-xss.ts @@ -19,7 +19,7 @@ const depedentTags = { dt: ['dl'], dd: ['dl'], }; -const whitelistClasses = ['row', 'columns', 'typo', 'note', 'warn'].concat(new Array(12).fill(0).map((_, i) => `medium-${i + 1}`)); +const whitelistClasses = ['row', 'columns', 'typo', 'note', 'warn'].concat(Array.from({ length: 12 }).fill(0).map((_, i) => `medium-${i + 1}`)); const tagCheck = new FilterXSS({ css: false, diff --git a/packages/ui-default/backendlib/misc.ts b/packages/ui-default/backendlib/misc.ts index ec6eeba64..0c0ff9e8f 100644 --- a/packages/ui-default/backendlib/misc.ts +++ b/packages/ui-default/backendlib/misc.ts @@ -1,7 +1,7 @@ import { moment, ObjectId } from 'hydrooj'; import { AnsiUp } from 'ansi_up'; -export { size, formatSeconds } from 'hydrooj'; +export { formatSeconds, size } from 'hydrooj'; const AU = new AnsiUp(); diff --git a/packages/ui-default/backendlib/template.ts b/packages/ui-default/backendlib/template.ts index e4bd8850c..0a15a09ff 100644 --- a/packages/ui-default/backendlib/template.ts +++ b/packages/ui-default/backendlib/template.ts @@ -30,7 +30,7 @@ async function getFiles(folder: string, base = ''): Promise { files.push(...await getFiles(path.join(folder, i), path.join(base, i))); } else files.push(path.join(base, i)); } - return files.map((item) => item.replace(/\\/gmi, '/')); + return files.map((item) => item.replace(/\\/g, '/')); } function locateFile(basePath: string, filenames: string[]) { @@ -47,7 +47,7 @@ nunjucks.runtime.memberLookup = function memberLookup(obj, val) { if (obj === undefined || obj === null) return undefined; if (typeof obj[val] === 'function') { const fn = function (...args) { - return obj[val].call(obj, ...args); + return obj[val].call(obj, ...args); // eslint-disable-line no-useless-call }; fn._original = obj[val]; return fn; @@ -255,7 +255,7 @@ export class TemplateService extends Service { const pending = Object.values(global.addons); const logger = this.ctx.logger('template'); for (const i of pending) { - const p = locateFile(i, ['template', 'templates']); + const p = locateFile(i as string, ['template', 'templates']); if (p && (await fs.stat(p)).isDirectory()) { try { const files = await getFiles(p); diff --git a/packages/ui-default/build/config/webpack.ts b/packages/ui-default/build/config/webpack.ts index 7f701da9c..40152690a 100644 --- a/packages/ui-default/build/config/webpack.ts +++ b/packages/ui-default/build/config/webpack.ts @@ -108,7 +108,7 @@ export default async function (env: { watch?: boolean, production?: boolean, mea devtool: env.production ? 'source-map' : false, entry: { [`hydro-${version}`]: './entry.js', - 'sentry': './sentry.ts', + sentry: './sentry.ts', 'default.theme': './theme/default.js', 'service-worker': './service-worker.ts', }, diff --git a/packages/ui-default/build/main.ts b/packages/ui-default/build/main.ts index 8fbbed1c7..4bf54e7c8 100644 --- a/packages/ui-default/build/main.ts +++ b/packages/ui-default/build/main.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/no-import-module-exports */ import path from 'path'; import { size } from '@hydrooj/utils/lib/utils'; import cac from 'cac'; @@ -106,7 +105,7 @@ async function main() { classNamePrefix: 'icon', fontName: 'hydro-icons', css: true, - startUnicode: 0xea01, + startUnicode: 0xEA01, svg2ttf: { timestamp: 1577836800, // 2020-1-1 }, diff --git a/packages/ui-default/components/DOMAttachedObject.ts b/packages/ui-default/components/DOMAttachedObject.ts index 05707afa7..1a7c1c39e 100644 --- a/packages/ui-default/components/DOMAttachedObject.ts +++ b/packages/ui-default/components/DOMAttachedObject.ts @@ -118,7 +118,6 @@ export default class DOMAttachedObject { } constructor($dom: JQuery, monitorDetach = false) { - // eslint-disable-next-line no-constructor-return if ($dom == null) return null; this.$dom = $dom; this.id = ++DOMAttachedObject.uniqueIdCounter; diff --git a/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx index 1dcb1905f..b0fef65d3 100644 --- a/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx @@ -2,7 +2,7 @@ import { CustomSelectAutoComplete as CustomSelectAutoCompleteFC } from '@hydrooj import AutoComplete, { AutoCompleteOptions } from '.'; interface CustomSelectOptions { - data: any[] + data: any[]; } export default class CustomSelectAutoComplete extends AutoComplete { diff --git a/packages/ui-default/components/autocomplete/index.tsx b/packages/ui-default/components/autocomplete/index.tsx index 6f67a9124..4f3a0513c 100644 --- a/packages/ui-default/components/autocomplete/index.tsx +++ b/packages/ui-default/components/autocomplete/index.tsx @@ -19,7 +19,7 @@ export interface AutoCompleteOptions { text?: () => string; } -export default class AutoComplete = {}, Multi extends boolean = boolean> extends DOMAttachedObject { +export default class AutoComplete = object, Multi extends boolean = boolean> extends DOMAttachedObject { static DOMAttachKey = 'ucwAutoCompleteInstance'; ref = null; container = document.createElement('div'); diff --git a/packages/ui-default/components/calendar/index.js b/packages/ui-default/components/calendar/index.js index 7ab0bea02..af4354615 100644 --- a/packages/ui-default/components/calendar/index.js +++ b/packages/ui-default/components/calendar/index.js @@ -215,7 +215,7 @@ export default class Calendar { const daysByWeek = _.chunk(days, 7); const numberOfWeeks = days.length / 7; - const bannersByWeek = _.fill(new Array(numberOfWeeks), 1).map(() => []); + const bannersByWeek = _.fill(Array.from({ length: numberOfWeeks }), 1).map(() => []); const beginDate = days[0].date.clone(); const endDate = _.last(days).date.clone(); @@ -268,7 +268,7 @@ export default class Calendar { ])) .map((banners) => { const dayBitmap = _ - .fill(new Array(7), 1) + .fill(Array.from({ length: 7 }), 1) .map(() => []); banners.forEach((banner) => { const beginDay = banner.beginAt.day(); @@ -292,7 +292,7 @@ export default class Calendar { // merge adjacent cells and arrange banners by vertical index const vMaxLength = _.max(_.range(0, 7).map((day) => dayBitmap[day].length)); const weekBanners = _ - .fill(new Array(vMaxLength), 1) + .fill(Array.from({ length: vMaxLength }), 1) .map(() => []); for (let vIndex = 0; vIndex < vMaxLength; ++vIndex) { let last = { span: 1, banner: dayBitmap[0][vIndex] }; diff --git a/packages/ui-default/components/customFont.page.tsx b/packages/ui-default/components/customFont.page.tsx index 4fc72190a..defbb1abe 100644 --- a/packages/ui-default/components/customFont.page.tsx +++ b/packages/ui-default/components/customFont.page.tsx @@ -27,12 +27,14 @@ export default new AutoloadPage('customFont', (pageName) => { if (!dialog) { const fonts = await getAvailableFonts(); dialog = new ActionDialog({ - $body: $(tpl( { handle = el; }} - allowEmptyQuery={false} - data={fonts} - renderItem={(item) =>
{item.name}
} - />, true)), + $body: $(tpl( + { handle = el; }} + allowEmptyQuery={false} + data={fonts} + renderItem={(item) =>
{item.name}
} + />, true, + )), onDispatch: (action) => { selection = handle.getSelectedItems()?.[0]?.id; return action !== 'ok' || selection; diff --git a/packages/ui-default/components/dialog/index.tsx b/packages/ui-default/components/dialog/index.tsx index 0ac1e3bdd..6d8b44819 100644 --- a/packages/ui-default/components/dialog/index.tsx +++ b/packages/ui-default/components/dialog/index.tsx @@ -144,24 +144,29 @@ export async function prompt(title: string, fields: Record {field.type === 'text' ? setValues({ ...values, [name]: e.target.value })} /> : field.type === 'user' ? setValues({ ...values, [name]: +e })} byId /> : setValues({ ...values, [name]: e })} />}
} {field.type === 'checkbox' &&
- - }> + } + > ); diff --git a/packages/ui-default/components/editor/index.tsx b/packages/ui-default/components/editor/index.tsx index ec81627bd..97da3f71f 100644 --- a/packages/ui-default/components/editor/index.tsx +++ b/packages/ui-default/components/editor/index.tsx @@ -169,9 +169,9 @@ export default class Editor extends DOMAttachedObject { const [val, setVal] = React.useState(value); that.setMarkdownEditorValue = setVal; return /// import ReconnectingWebsocket from 'reconnecting-websocket'; @@ -64,10 +63,10 @@ function initConn(path: string, port: MessagePort, cookie: any) { console.log('Connected'); broadcastMsg({ type: 'open' }); conn.send(JSON.stringify({ - 'operation': 'subscribe', - 'request_id': Math.random().toString(16).substring(2), - 'credential': lcookie, - 'channels': ['message'], + operation: 'subscribe', + request_id: Math.random().toString(16).substring(2), + credential: lcookie, + channels: ['message'], })); }; conn.onerror = () => broadcastMsg({ type: 'error' }); diff --git a/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx b/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx index c7da4613e..4e1466ac0 100644 --- a/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx +++ b/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx @@ -94,11 +94,11 @@ export default connect(mapStateToProps)(class MessagePadDialogueContentContainer <>
    {this.renderInner()} diff --git a/packages/ui-default/components/messagepad/MessagePadInputContainer.jsx b/packages/ui-default/components/messagepad/MessagePadInputContainer.jsx index 369003a98..2fa49bda1 100644 --- a/packages/ui-default/components/messagepad/MessagePadInputContainer.jsx +++ b/packages/ui-default/components/messagepad/MessagePadInputContainer.jsx @@ -51,7 +51,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MessagePadInpu componentDidUpdate(prevProps) { this.focusInput = ( this.props.activeId !== prevProps.activeId - || prevProps.isPosting !== this.props.isPosting && this.props.isPosting === false + || (prevProps.isPosting !== this.props.isPosting && this.props.isPosting === false) ); if (this.focusInput) { const { scrollX, scrollY } = window; diff --git a/packages/ui-default/components/monaco/nls.js b/packages/ui-default/components/monaco/nls.js index 1eddfaf32..647743d72 100644 --- a/packages/ui-default/components/monaco/nls.js +++ b/packages/ui-default/components/monaco/nls.js @@ -12,7 +12,7 @@ function format(message, args) { return result; } -let CURRENT_LOCALE_DATA = {}; // eslint-disable-line @typescript-eslint/naming-convention +let CURRENT_LOCALE_DATA = {}; // eslint-disable-line ts/naming-convention export function localize(path, message, ...args) { return format(CURRENT_LOCALE_DATA[path.key || path] || CURRENT_LOCALE_DATA[message] || message, args); diff --git a/packages/ui-default/components/preview/preview.page.ts b/packages/ui-default/components/preview/preview.page.ts index 1246e9a61..a67361e54 100644 --- a/packages/ui-default/components/preview/preview.page.ts +++ b/packages/ui-default/components/preview/preview.page.ts @@ -159,7 +159,7 @@ export async function previewFile(ev?, type = '') { Notification.info(i18n('Loading file...')); try { const { url } = await request.get(link, {}, { headers: { Pragma: 'no-cache' } }); - if (/^(doc|xls|ppt)x?$/.test(ext)) return previewOffice(link, url); + if (/^(?:doc|xls|ppt)x?$/.test(ext)) return previewOffice(link, url); content = await request.get(url, undefined, { dataType: 'text' }); } catch (e) { Notification.error(i18n('Failed to load file: {0}', e.message)); diff --git a/packages/ui-default/components/problemconfig/ProblemConfigEditor.tsx b/packages/ui-default/components/problemconfig/ProblemConfigEditor.tsx index 1d4561f5e..d419aaef4 100644 --- a/packages/ui-default/components/problemconfig/ProblemConfigEditor.tsx +++ b/packages/ui-default/components/problemconfig/ProblemConfigEditor.tsx @@ -24,7 +24,7 @@ const mapDispatchToProps = (dispatch) => ({ interface Props { config: any; - handleUpdateCode: Function; + handleUpdateCode: (val: string) => void; } const configKey = [ diff --git a/packages/ui-default/components/problemconfig/index.tsx b/packages/ui-default/components/problemconfig/index.tsx index d3124f222..f95338e9c 100644 --- a/packages/ui-default/components/problemconfig/index.tsx +++ b/packages/ui-default/components/problemconfig/index.tsx @@ -10,7 +10,7 @@ import { ProblemConfigTree } from './ProblemConfigTree'; import { RootState } from './reducer'; interface Props { - onSave: () => void + onSave: () => void; } export default function ProblemConfig(props: Props) { @@ -31,8 +31,12 @@ export default function ProblemConfig(props: Props) { (t !== 'errors' && setSelected(t.toString()))} selectedTabId={valid ? selected : 'errors'}> } /> } /> - {errors.map((i) => (
    {i}
    ))}} /> + {errors.map((i) => (
    {i}
    ))}} + />
    diff --git a/packages/ui-default/components/problemconfig/tree/SelectionManager.tsx b/packages/ui-default/components/problemconfig/tree/SelectionManager.tsx index 53602da3b..84a79f449 100644 --- a/packages/ui-default/components/problemconfig/tree/SelectionManager.tsx +++ b/packages/ui-default/components/problemconfig/tree/SelectionManager.tsx @@ -42,9 +42,9 @@ export function SelectionManager(props: SelectionManagerProps) { for (const el of selected) { if (collide(el, { ...pos, width: 1, height: 1 })) return; } - // eslint-disable-next-line @typescript-eslint/no-use-before-define + // eslint-disable-next-line ts/no-use-before-define document.body.addEventListener('mousemove', handleMouseMove); - // eslint-disable-next-line @typescript-eslint/no-use-before-define + // eslint-disable-next-line ts/no-use-before-define document.body.addEventListener('mouseup', handleMouseUp); $('body').css('cursor', 'crosshair') .append('
    '); diff --git a/packages/ui-default/components/problemconfig/tree/Testcase.tsx b/packages/ui-default/components/problemconfig/tree/Testcase.tsx index 909cefd79..64b45104b 100644 --- a/packages/ui-default/components/problemconfig/tree/Testcase.tsx +++ b/packages/ui-default/components/problemconfig/tree/Testcase.tsx @@ -30,7 +30,7 @@ export function TestcaseNode(props: TestcaseNodeProps) { data-selected={selected} content={ - + {subtaskIds.filter((i) => i !== subtaskId).map((i) => ( ))} diff --git a/packages/ui-default/components/rotator/index.js b/packages/ui-default/components/rotator/index.js index cec77a709..d53102d19 100644 --- a/packages/ui-default/components/rotator/index.js +++ b/packages/ui-default/components/rotator/index.js @@ -56,7 +56,7 @@ export default class Rotator extends DOMAttachedObject { return; } let fromPosition; - if (parseFloat(value) > parseFloat(this.value)) { + if (Number.parseFloat(value) > Number.parseFloat(this.value)) { fromPosition = POS_BELOW; } else { fromPosition = POS_ABOVE; diff --git a/packages/ui-default/components/upload.tsx b/packages/ui-default/components/upload.tsx index dac5bf75f..abbb4d12f 100644 --- a/packages/ui-default/components/upload.tsx +++ b/packages/ui-default/components/upload.tsx @@ -20,15 +20,21 @@ interface UploadOptions { export default async function uploadFiles(endpoint = '', files: File[] | FileList = [], options: UploadOptions = {}) { const dialog = new Dialog({ $body: $(tpl(<> -
    +
    -
    +
    diff --git a/packages/ui-default/components/zipDownloader/index.ts b/packages/ui-default/components/zipDownloader/index.ts index a143ad89b..526bbb2cd 100644 --- a/packages/ui-default/components/zipDownloader/index.ts +++ b/packages/ui-default/components/zipDownloader/index.ts @@ -84,13 +84,13 @@ export default async function download(filename, targets) { declare module '../../api' { interface EventMap { - 'problemset/download': (pids: number[], name: string, targets: { filename: string; url?: string; content?: string }[]) => void; + 'problemset/download': (pids: number[], name: string, targets: { filename: string, url?: string, content?: string }[]) => void; } } export async function downloadProblemSet(pids, name = 'Export') { Notification.info(i18n('Downloading...')); - const targets: { filename: string; url?: string; content?: string }[] = []; + const targets: { filename: string, url?: string, content?: string }[] = []; try { await ctx.serial('problemset/download', pids, name, targets); for (const pid of pids) { @@ -122,7 +122,7 @@ export async function downloadProblemSet(pids, name = 'Export') { }); try { const c = JSON.parse(pdoc.content); - if (c instanceof Array || typeof c === 'string') throw new Error(); + if (c instanceof Array || typeof c === 'string') throw new Error('Invalid content'); for (const key of Object.keys(c)) { targets.push({ filename: `${pid}/problem_${key}.md`, diff --git a/packages/ui-default/context.ts b/packages/ui-default/context.ts index 8eafbe0ea..022dce826 100644 --- a/packages/ui-default/context.ts +++ b/packages/ui-default/context.ts @@ -1,3 +1,5 @@ +/* eslint-disable ts/no-unsafe-declaration-merging */ + import * as cordis from 'cordis'; import type { EventMap } from './api'; diff --git a/packages/ui-default/entry.js b/packages/ui-default/entry.js index 869a0bf00..b75744a90 100644 --- a/packages/ui-default/entry.js +++ b/packages/ui-default/entry.js @@ -29,7 +29,9 @@ console.log( window.UiContext = JSON.parse(window.UiContext); window.UserContext = JSON.parse(window.UserContext); -try { __webpack_public_path__ = UiContext.cdn_prefix; } catch (e) { } +try { + __webpack_public_path__ = UiContext.cdn_prefix; +} catch (e) { } if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js').then((registration) => { console.log('SW registered: ', registration); diff --git a/packages/ui-default/hydro.ts b/packages/ui-default/hydro.ts index 05630d0b0..77e36618e 100644 --- a/packages/ui-default/hydro.ts +++ b/packages/ui-default/hydro.ts @@ -21,7 +21,7 @@ const start = new Date(); function buildSequence(pages, type) { if (process.env.NODE_ENV !== 'production') { - if (['before', 'after'].indexOf(type) === -1) { + if (!['before', 'after'].includes(type)) { throw new Error("'type' should be one of 'before' or 'after'"); } } diff --git a/packages/ui-default/index.ts b/packages/ui-default/index.ts index e22b4d2b3..b3e903ffb 100644 --- a/packages/ui-default/index.ts +++ b/packages/ui-default/index.ts @@ -22,7 +22,7 @@ class WikiAboutHandler extends Handler { async get() { let raw = SystemModel.get('ui-default.about') || ''; // TODO template engine - raw = raw.replace(/{{ name }}/g, this.domain.ui?.name || SystemModel.get('server.name')).trim(); + raw = raw.replace(/\{\{ name \}\}/g, this.domain.ui?.name || SystemModel.get('server.name')).trim(); const lines = raw.split('\n'); const sections: { id: string, title: string, content: string }[] = []; for (const line of lines) { @@ -131,6 +131,7 @@ class RichMediaHandler extends Handler { } } +/* eslint-disable style/quote-props */ const fontRange = { 'Open Sans': 'Open Sans', 'Seravek': 'Seravek', diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index 964537e0f..4d6b50635 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -1102,4 +1102,4 @@ Your account has two factor authentication enabled. Please choose an authenticat Your browser does not support WebAuthn or you are not in secure context.: 您的浏览器不支持WebAuthn或是您不在安全环境中。 Your Device: 你的设备 Your permission: 您的权限 -Your role: 您的角色 \ No newline at end of file +Your role: 您的角色 diff --git a/packages/ui-default/misc/Page.ts b/packages/ui-default/misc/Page.ts index 12c5b78a0..54ea4f6d9 100644 --- a/packages/ui-default/misc/Page.ts +++ b/packages/ui-default/misc/Page.ts @@ -16,7 +16,7 @@ export class Page { } if (process.env.NODE_ENV !== 'production') { if (typeof name !== 'string' && !(name instanceof Array)) { - throw new Error("'name' should be a string or string[]"); + throw new TypeError("'name' should be a string or string[]"); } if (typeof this.afterLoading !== 'function' && this.afterLoading != null) { throw new Error("'afterLoading' should be a function"); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 121a1b251..c96e378c2 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -21,7 +21,7 @@ "@fontsource/source-code-pro": "^5.2.6", "@fontsource/ubuntu-mono": "^5.2.6", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^10.0.0", + "@sentry/browser": "^10.2.0", "@sentry/webpack-plugin": "^4.0.2", "@simplewebauthn/browser": "13.1.2", "@svgr/webpack": "^8.1.0", @@ -44,14 +44,14 @@ "allotment": "^1.20.4", "autoprefixer": "^10.4.21", "browser-update": "^3.3.60", - "chalk": "^5.4.1", + "chalk": "^5.5.0", "classnames": "^2.5.1", "clean-webpack-plugin": "^4.0.0", "clipboard": "^2.0.11", "copy-webpack-plugin": "^13.0.0", "cordis": "4.0.0-beta.5", - "core-js": "^3.44.0", - "core-js-compat": "^3.44.0", + "core-js": "^3.45.0", + "core-js-compat": "^3.45.0", "css-loader": "^7.1.2", "diff": "^8.0.2", "diff-dom": "^5.2.0", @@ -67,7 +67,7 @@ "jquery.transit": "^0.9.12", "matchmedia-polyfill": "^0.3.2", "md-editor-rt": "^5.8.4", - "mini-css-extract-plugin": "^2.9.2", + "mini-css-extract-plugin": "^2.9.3", "moment": "^2.30.1", "monaco-editor": "0.52.2", "monaco-editor-nls": "^3.1.0", @@ -98,7 +98,7 @@ "redux-promise-middleware": "^6.2.0", "redux-thunk": "^3.1.0", "rupture": "^0.7.1", - "sass": "^1.89.2", + "sass": "^1.90.0", "sass-loader": "^16.0.5", "schemastery": "^3.16.1", "shorty.js": "^1.0.1", @@ -125,7 +125,7 @@ "ansi_up": "^6.0.6", "cssfilter": "^0.0.11", "esbuild": "0.25.2", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "html-to-text": "^9.0.5", "js-yaml": "^4.1.0", "jsesc": "^3.1.0", diff --git a/packages/ui-default/pages/contest.page.ts b/packages/ui-default/pages/contest.page.ts index 112fb4866..32d89c7c0 100644 --- a/packages/ui-default/pages/contest.page.ts +++ b/packages/ui-default/pages/contest.page.ts @@ -24,10 +24,10 @@ export default new NamedPage(['contest_detail', 'contest_problemlist', 'contest_ addSpeculationRules({ prerender: [{ - 'where': { - 'or': [ - { 'href_matches': '/p/*' }, - { 'href_matches': '/d/*/p/*' }, + where: { + or: [ + { href_matches: '/p/*' }, + { href_matches: '/d/*/p/*' }, ], }, }], diff --git a/packages/ui-default/pages/contest_balloon.page.tsx b/packages/ui-default/pages/contest_balloon.page.tsx index 29c4aca2a..8696404d7 100644 --- a/packages/ui-default/pages/contest_balloon.page.tsx +++ b/packages/ui-default/pages/contest_balloon.page.tsx @@ -37,7 +37,7 @@ function Balloon({ tdoc, val }) { { setNow(pid); setColor(c); }} onChange={(e) => { val[+pid].color = e; setColor(e); }} diff --git a/packages/ui-default/pages/problem_detail.page.tsx b/packages/ui-default/pages/problem_detail.page.tsx index b77d444d5..94b502608 100644 --- a/packages/ui-default/pages/problem_detail.page.tsx +++ b/packages/ui-default/pages/problem_detail.page.tsx @@ -194,15 +194,15 @@ const page = new NamedPage(['problem_detail', 'contest_detail_problem', 'homewor const ans = {}; const pids = []; let cnt = 0; - const reg = /{{ (input|select|multiselect|textarea)\(\d+(-\d+)?\) }}/g; + const reg = /\{\{ (input|select|multiselect|textarea)\(\d+(-\d+)?\) \}\}/g; $('.problem-content .typo').children().each((i, e) => { if (e.tagName === 'PRE' && !e.children[0].className.includes('#input')) return; const questions = []; let q; - while (q = reg.exec(e.innerText)) questions.push(q); // eslint-disable-line no-cond-assign + while (q = reg.exec(e.textContent)) questions.push(q); // eslint-disable-line no-cond-assign for (const [info, type] of questions) { cnt++; - const id = info.replace(/{{ (input|select|multiselect|textarea)\((\d+(-\d+)?)\) }}/, '$2'); + const id = info.replace(/\{\{ (input|select|multiselect|textarea)\((\d+(-\d+)?)\) \}\}/, '$2'); pids.push(id); if (type === 'input') { $(e).html($(e).html().replace(info, tpl` diff --git a/packages/ui-default/pages/problem_edit.page.js b/packages/ui-default/pages/problem_edit.page.js index 5bd6fec1a..dec63b918 100644 --- a/packages/ui-default/pages/problem_edit.page.js +++ b/packages/ui-default/pages/problem_edit.page.js @@ -37,7 +37,7 @@ async function updateSelection() { } } const requestCategoryTags = _.uniq(selections - .filter((s) => s.indexOf(',') !== -1) + .filter((s) => s.includes(',')) .map((s) => s.split(',')[0])); // drop the category if its subcategory is selected const requestTags = _.uniq(_.pullAll(selections, requestCategoryTags)); @@ -267,7 +267,7 @@ export default new NamedPage(['problem_create', 'problem_edit'], () => { val = JSON.parse(val); if (!(val instanceof Array)) val = JSON.stringify(val); } catch { } - const empty = /^\s*$/g.test(val); + const empty = /^\s*$/.test(val); if (empty) delete content[activeTab]; else content[activeTab] = val; if (!Object.keys(content).length) $field.text(''); diff --git a/packages/ui-default/pages/problem_files.page.tsx b/packages/ui-default/pages/problem_files.page.tsx index c2123a469..6a24ca760 100644 --- a/packages/ui-default/pages/problem_files.page.tsx +++ b/packages/ui-default/pages/problem_files.page.tsx @@ -175,14 +175,21 @@ const page = new NamedPage('problem_files', () => { style={{ ...style, ...(highlight ? { color: 'transparent', background: 'transparent', caretColor: 'black' } : {}) }} value={original} onChange={(e) => setOriginal(e.currentTarget.value)} - > + /> -
    - {highlight && } +
    + {highlight && }
    @@ -237,7 +244,7 @@ const page = new NamedPage('problem_files', () => { } - ; + ; } const promise = new ActionDialog({ diff --git a/packages/ui-default/pages/problem_main.page.tsx b/packages/ui-default/pages/problem_main.page.tsx index 2ecec72a8..cd9017142 100644 --- a/packages/ui-default/pages/problem_main.page.tsx +++ b/packages/ui-default/pages/problem_main.page.tsx @@ -456,19 +456,20 @@ const page = new NamedPage(['problem_main'], () => { const selection = document.getElementById('problem_selection'); if (selection) { - ReactDOM.createRoot(selection) - .render( { selectedPids = pids; }} onClear={(handler) => { clearSelectionHandler = handler; }} - />); + />, + ); } addSpeculationRules({ prerender: [{ - 'where': { - 'or': [ - { 'href_matches': '/p/*' }, - { 'href_matches': '/d/*/p/*' }, + where: { + or: [ + { href_matches: '/p/*' }, + { href_matches: '/d/*/p/*' }, ], }, }], diff --git a/packages/ui-default/pages/problem_submit.page.tsx b/packages/ui-default/pages/problem_submit.page.tsx index f429e4edb..53db1456e 100644 --- a/packages/ui-default/pages/problem_submit.page.tsx +++ b/packages/ui-default/pages/problem_submit.page.tsx @@ -49,7 +49,8 @@ const page = new NamedPage(['problem_submit', 'contest_detail_problem_submit', ' root.render(

    {i18n('This page is only for pasting code from other sources.')}

    {i18n("To get a better editing experience, with code highlighting and test runs, \ -please go back to the problem detail page and use 'Open Scratchpad' button.")}

    +please go back to the problem detail page and use 'Open Scratchpad' button.")} +

    root.unmount()}>{i18n('Dismiss')} / {i18n("Don't show again")}
    ); }); diff --git a/packages/ui-default/pages/setting.page.tsx b/packages/ui-default/pages/setting.page.tsx index a9c9d4a1b..d4c30c863 100644 --- a/packages/ui-default/pages/setting.page.tsx +++ b/packages/ui-default/pages/setting.page.tsx @@ -57,7 +57,8 @@ const page = new NamedPage('manage_config', async () => { .omnibar-toggle { display: none; } - `} + `} + , ); }); diff --git a/packages/ui-default/service-worker.ts b/packages/ui-default/service-worker.ts index 37c112ff8..059cf0cdc 100644 --- a/packages/ui-default/service-worker.ts +++ b/packages/ui-default/service-worker.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-restricted-globals */ /// /// export { }; // make it a module so that declare self works @@ -27,7 +26,7 @@ self.onmessage = (event) => { const data = event.data; const downloadUrl = data.url || `${self.registration.scope + Math.random()}/${typeof data === 'string' ? data : data.filename}`; const port = event.ports[0]; - const metadata = new Array(3); // [stream, data, port] + const metadata = Array.from({ length: 3 }); // [stream, data, port] metadata[1] = data; metadata[2] = port; if (event.data.readableStream) { diff --git a/packages/ui-default/utils/base64.js b/packages/ui-default/utils/base64.js index 0b53eb715..523e470dd 100644 --- a/packages/ui-default/utils/base64.js +++ b/packages/ui-default/utils/base64.js @@ -23,8 +23,8 @@ const Base64 = { if (Number.isNaN(chr2)) enc3 = enc4 = 64; else if (Number.isNaN(chr3)) enc4 = 64; output = output - + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) - + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); + + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); } return output; }, diff --git a/packages/ui-default/utils/index.ts b/packages/ui-default/utils/index.ts index 35e61c60a..2b75b0e1f 100644 --- a/packages/ui-default/utils/index.ts +++ b/packages/ui-default/utils/index.ts @@ -36,7 +36,6 @@ export async function pipeStream(read, write, abort) { const writer = write.getWriter(); if (abort) abort.abort = writer.abort.bind(writer); const reader = read.getReader(); - // eslint-disable-next-line no-constant-condition while (1) { const readResult = await reader.read(); if (readResult.done) { @@ -51,10 +50,10 @@ export async function pipeStream(read, write, abort) { export function mongoId(idstring: string) { if (typeof idstring !== 'string') idstring = String(idstring); return { - timestamp: parseInt(idstring.slice(0, 0 + 8), 16), - machineid: parseInt(idstring.slice(8, 8 + 6), 16), - pid: parseInt(idstring.slice(14, 14 + 4), 16), - sequence: parseInt(idstring.slice(18, 18 + 6), 16), + timestamp: Number.parseInt(idstring.slice(0, 0 + 8), 16), + machineid: Number.parseInt(idstring.slice(8, 8 + 6), 16), + pid: Number.parseInt(idstring.slice(14, 14 + 4), 16), + sequence: Number.parseInt(idstring.slice(18, 18 + 6), 16), }; } @@ -66,12 +65,12 @@ export function emulateAnchorClick(ev: KeyboardEvent, targetUrl: string, alwaysO else window.location.href = targetUrl; } -export { default as pjax } from './pjax'; +export * from './base'; export { default as base64 } from './base64'; export { default as loadReactRedux } from './loadReactRedux'; export * as mediaQuery from './mediaQuery'; +export { default as pjax } from './pjax'; export * from './slide'; -export * from './base'; const zip = { createZipStream, createZipBlob }; Object.assign(window.Hydro.utils, { diff --git a/packages/utils/lib/common.ts b/packages/utils/lib/common.ts index 99f1bd23c..c81669b40 100644 --- a/packages/utils/lib/common.ts +++ b/packages/utils/lib/common.ts @@ -166,7 +166,7 @@ export function parseTimeMS(str: string | number, throwOnError = true) { const match = TIME_RE.exec(str); if (!match && throwOnError) throw new Error(`${str} error parsing time`); if (!match) return 1000; - return Math.floor(parseFloat(match[1]) * TIME_UNITS[match[2].toLowerCase()]); + return Math.floor(Number.parseFloat(match[1]) * TIME_UNITS[match[2].toLowerCase()]); } export function parseMemoryMB(str: string | number, throwOnError = true) { @@ -174,7 +174,7 @@ export function parseMemoryMB(str: string | number, throwOnError = true) { const match = MEMORY_RE.exec(str); if (!match && throwOnError) throw new Error(`${str} error parsing memory`); if (!match) return 256; - return Math.ceil(parseFloat(match[1]) * MEMORY_UNITS[match[2].toLowerCase()]); + return Math.ceil(Number.parseFloat(match[1]) * MEMORY_UNITS[match[2].toLowerCase()]); } function _digit2(number: number) { @@ -209,7 +209,7 @@ export function randomPick(arr: T[]): T { export type StringKeys = { [K in keyof O]: string extends O[K] ? K : never }[keyof O]; -const fSortR = /[^\d]+|\d+/g; +const fSortR = /\D+|\d+/g; export function sortFiles(files: string[]): string[]; export function sortFiles(files: T[], key?: '_id'): T[]; export function sortFiles>(files: T[], key: StringKeys): T[]; diff --git a/packages/utils/lib/locate-pm2.ts b/packages/utils/lib/locate-pm2.ts index 1d36692ad..520fd3217 100644 --- a/packages/utils/lib/locate-pm2.ts +++ b/packages/utils/lib/locate-pm2.ts @@ -3,7 +3,6 @@ import path from 'path'; const PATH = process.env.PATH?.split(':') || []; -/* eslint-disable import/no-dynamic-require */ // @ts-ignore const pm2: typeof import('pm2') | null = (() => { for (const dir of PATH) { diff --git a/packages/utils/lib/search.ts b/packages/utils/lib/search.ts index a7681ca85..3342b5a10 100644 --- a/packages/utils/lib/search.ts +++ b/packages/utils/lib/search.ts @@ -7,7 +7,7 @@ export function stringify(queryObject: SearchParserResult, options: SearchParser if (!Object.keys(queryObject || {}).length) return ''; prefix ||= ''; const toArray = (val: string | string[]) => (typeof val === 'string' ? [val] : val); - const addQuotes = (s: string) => (s.indexOf(' ') > -1 ? JSON.stringify(s) : s); + const addQuotes = (s: string) => (s.includes(' ') ? JSON.stringify(s) : s); const addPrefix = (s: string) => prefix + s; const parts = []; if (queryObject.text) { diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index b44a74404..c0bc6408a 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -8,7 +8,8 @@ import fs from 'fs-extra'; import type { Moment } from 'moment'; import { Exporter, Factory, Logger as Reggol } from 'reggol'; import type * as superagent from 'superagent'; -export * as yaml from 'js-yaml'; + +export * from '@hydrooj/utils/lib/common'; export * as fs from 'fs-extra'; Factory.formatters['d'] = (value, exporter) => Reggol.color(exporter, 3, value); @@ -252,10 +253,10 @@ export function findFileSync(pathname: string, doThrow: boolean | Error = true) return null; } -export async function retry(func: Function, ...args: any[]): Promise; -export async function retry(times: number, func: Function, ...args: any[]): Promise; +export async function retry(func: (...args: Arg) => Ret, ...args: Arg): Promise; +export async function retry(times: number, func: (...args: Arg) => Ret, ...args: Arg): Promise; // eslint-disable-next-line consistent-return -export async function retry(arg0: number | Function, func: any, ...args: any[]) { +export async function retry(arg0: number | ((...args: any[]) => any), func: any, ...args: any[]): Promise { let res; if (typeof arg0 !== 'number') { args = [func, ...args]; @@ -397,4 +398,4 @@ export async function pipeRequest(req: superagent.Request, w: fs.WriteStream, ti } } -export * from '@hydrooj/utils/lib/common'; +export * as yaml from 'js-yaml'; diff --git a/packages/utils/package.json b/packages/utils/package.json index 03d4ea56d..ccd9a130d 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -14,7 +14,7 @@ "dependencies": { "@hydrooj/register": "workspace:^", "cac": "^6.7.14", - "fs-extra": "^11.3.0", + "fs-extra": "^11.3.1", "js-yaml": "^4.1.0", "reggol": "^2.1.0", "search-query-parser": "^1.6.0", diff --git a/packages/vjudge/src/fetch.ts b/packages/vjudge/src/fetch.ts index 63150302f..dbd3e4258 100644 --- a/packages/vjudge/src/fetch.ts +++ b/packages/vjudge/src/fetch.ts @@ -8,8 +8,8 @@ proxy(superagent); interface FetchOptions { headers?: { [key: string]: string }; - post?: Omit - get?: Omit + post?: Omit; + get?: Omit; } const defaultUA = `Hydro/${global.Hydro.version.hydrooj} VJudge/${global.Hydro.version.vjudge}`; diff --git a/packages/vjudge/src/index.ts b/packages/vjudge/src/index.ts index 7156b4ec2..03e446d96 100644 --- a/packages/vjudge/src/index.ts +++ b/packages/vjudge/src/index.ts @@ -262,8 +262,8 @@ class VJudgeService extends Service { } export { BasicFetcher } from './fetch'; -export { VERDICT } from './verdict'; export * from './interface'; +export { VERDICT } from './verdict'; export const name = 'vjudge'; export async function apply(ctx: Context) { diff --git a/packages/vjudge/src/interface.ts b/packages/vjudge/src/interface.ts index 24b82edab..56e7169ec 100644 --- a/packages/vjudge/src/interface.ts +++ b/packages/vjudge/src/interface.ts @@ -27,8 +27,8 @@ declare module 'hydrooj' { } type NextFunction = (body: Partial) => void; export interface IBasicProvider { - ensureLogin(): Promise; - getProblem(id: string, meta: Record): Promise<{ + ensureLogin: () => Promise; + getProblem: (id: string, meta: Record) => Promise<{ title: string; data: Record; files: Record; @@ -37,9 +37,9 @@ export interface IBasicProvider { difficulty?: number; }>; entryProblemLists?: string[]; - listProblem(page: number, resync: boolean, listId: string): Promise; - submitProblem(id: string, lang: string, code: string, info: any, next: NextFunction, end: NextFunction): Promise; - waitForSubmission(id: string, next: NextFunction, end: NextFunction): Promise; + listProblem: (page: number, resync: boolean, listId: string) => Promise; + submitProblem: (id: string, lang: string, code: string, info: any, next: NextFunction, end: NextFunction) => Promise; + waitForSubmission: (id: string, next: NextFunction, end: NextFunction) => Promise; checkStatus?: (onCheckFunc: boolean) => Promise; stop?: () => Promise; } diff --git a/packages/vjudge/src/providers/codeforces.ts b/packages/vjudge/src/providers/codeforces.ts index 08dd488d2..9923568b5 100644 --- a/packages/vjudge/src/providers/codeforces.ts +++ b/packages/vjudge/src/providers/codeforces.ts @@ -191,8 +191,8 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr ele.setAttribute('src', `file://${fid}.png`); } const title = document.querySelector('.title').innerHTML.trim().split('. ')[1]; - const time = parseInt(document.querySelector('.time-limit').innerHTML.substr(53, 2), 10); - const memory = parseInt(document.querySelector('.memory-limit').innerHTML.substr(55, 4), 10); + const time = Number.parseInt(document.querySelector('.time-limit').innerHTML.substr(53, 2), 10); + const memory = Number.parseInt(document.querySelector('.memory-limit').innerHTML.substr(55, 4), 10); document.body.firstChild.remove(); document.querySelector('.input-specification')?.firstChild.remove(); document.querySelector('.output-specification')?.firstChild.remove(); diff --git a/packages/vjudge/src/providers/hduoj.ts b/packages/vjudge/src/providers/hduoj.ts index 0b8d996d2..0aacc3402 100644 --- a/packages/vjudge/src/providers/hduoj.ts +++ b/packages/vjudge/src/providers/hduoj.ts @@ -146,7 +146,7 @@ export default class HDUOJProvider extends BasicFetcher implements IBasicProvide return res; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars + // eslint-disable-next-line ts/no-unused-vars async submitProblem(id: string, lang: string, code: string, info) { await this.ensureLogin(); const language = lang.includes('hduoj.') ? lang.split('hduoj.')[1] : '0'; @@ -163,7 +163,7 @@ export default class HDUOJProvider extends BasicFetcher implements IBasicProvide throw new Error(text.split('
  1. ')[1].split('
  2. ')[0]); } // eslint-disable-next-line max-len - const { text: status } = await this.get(`/status.php?first=&pid=${id}&user=${this.account.handle}&lang=${parseInt(language, 10) + 1}&status=0`); + const { text: status } = await this.get(`/status.php?first=&pid=${id}&user=${this.account.handle}&lang=${Number.parseInt(language, 10) + 1}&status=0`); const $dom = new JSDOM(status); const res = $dom.window.document.querySelector('.table_text>tbody'); return res.children[2].children[0].innerHTML; diff --git a/packages/vjudge/src/providers/hustoj.ts b/packages/vjudge/src/providers/hustoj.ts index 4f7902220..1563e4bb1 100644 --- a/packages/vjudge/src/providers/hustoj.ts +++ b/packages/vjudge/src/providers/hustoj.ts @@ -47,7 +47,7 @@ interface HustOJRemoteConfig { endpoint?: string; matcher?: RegExp; selector?: string; - }, + }; server?: string; } @@ -68,7 +68,7 @@ const defaultConfig: HustOJRemoteConfig = { // }, ceInfo: { endpoint: '/ceinfo.php?sid={rid}', - matcher: /
    (.*?)<\/pre>/gmi,
    +        matcher: /
    (.*?)<\/pre>/gi,
         },
         monit: {
             endpoint: '/status.php?pid={pid}&user_id={uid}',
    @@ -87,7 +87,7 @@ const defaultConfig: HustOJRemoteConfig = {
             codeField: 'source',
             extra: {},
             tooFrequent: 'UNKNOWN',
    -        rid: /\n([0-9]+)<\/td>/gmi,
    +        rid: /\n([0-9]+)<\/td>/gi,
         },
         server: 'https://acm.hust.edu.cn',
     };
    @@ -188,7 +188,7 @@ export class HUSTOJ extends BasicFetcher implements IBasicProvider {
         async waitForSubmission(rid, next, end) {
             let url = this.config.monit.endpoint.replace('{uid}', this.state.username).replace('{pid}', this.state.pid);
             // eslint-disable-next-line max-len
    -        const RE = new RegExp(`${rid}.*?.*?(.*?)(.*?)kb(.*?)ms`, 'gmi');
    +        const RE = new RegExp(`${rid}.*?.*?(.*?)(.*?)kb(.*?)ms`, 'gim');
             const res = await this.get(url);
             let [, status, time, memory] = RE.exec(res.text);
             while (isProcessing(status)) {
    @@ -249,14 +249,15 @@ export class XJOI extends HUSTOJ {
                 codeField: 'source',
                 tooFrequent: '请稍后再提交',
                 extra: {},
    -            rid: /  ([\s\S]*?)<\/textarea>/igm,
    -            /time: ([0-9]+)ms, memory: ([0-9]+)kb, points: ([0-9]+), status: (.*?)/gmi,
    +            // eslint-disable-next-line regexp/no-super-linear-backtracking
    +            /