workspace: update eslint rule (#1027)

This commit is contained in:
undefined authored and GitHub committed 2025-08-06 18:51:25 +01:00
1 parent 05c3b8f2bd
commit cee3c00ddb
150 files changed
+1069 -1043

No files matched your search

+366 -385
View File
@@ -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;
+4 -10
View File
@@ -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",
+5 -6
View File
@@ -11,20 +11,20 @@ const REDIRECT = Symbol.for('hydro.api.response.redirect');
type MaybePromise<T> = T | Promise<T>;
export type ApiType = 'Query' | 'Mutation' | 'Subscription';
export type ApiCall<Type extends ApiType, Arg, Res, Progress = never> = {
export interface ApiCall<Type extends ApiType, Arg, Res, Progress = never> {
readonly type: Type;
readonly input: Schema<Arg>;
readonly func: (Type extends 'Subscription'
? (context: any, args: Arg, emit: (payload: Res) => void) => (() => MaybePromise<void>)
: (context: any, args: Arg) => MaybePromise<Res | AsyncGenerator<Progress, Res, never>>);
readonly hooks: ApiCall<'Query', Arg, void>[];
};
}
export const _get = <Type extends ApiType>(type: Type) => <Arg, Res, Progress>(
schema: Schema<Arg>,
func: ApiCall<Type, Arg, Res, Progress>['func'],
hooks: ApiCall<'Query', Arg, void, never>[] = [],
): ApiCall<Type, Arg, Res, Progress> => ({ input: schema, func, hooks, type } as const); // eslint-disable-line
): ApiCall<Type, Arg, Res, Progress> => ({ 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> = T extends any ? keyof T : never;
type MId<T> = { [K in MKeyOf<T>]: T[K] } & {};
type ProjectionSchema<T> = T extends Array<infer U>
? ProjectionSchema<U>
: | { [K in keyof T]?: ProjectionSchemaId | ProjectionSchema<T[K]> }
| Record<keyof any, ProjectionSchemaId | object>;
: { [K in keyof T]?: ProjectionSchemaId | ProjectionSchema<T[K]> } | Record<keyof any, ProjectionSchemaId | object>;
type AsKeys<T> = T extends Array<infer U extends string> ? Record<U, 1> : T;
type Projection<T, S> = S extends ProjectionSchemaId
? T : T extends Array<infer U> ? Array<MId<Projection<U, S>>> : {
+5 -5
View File
@@ -8,11 +8,11 @@ type MethodDecorator = (target: any, funcName: string, obj: any) => any;
type ClassDecorator = <T extends new (...args: any[]) => any>(Class: T) => T extends new (...args: infer R) => infer S
? new (...args: R) => S : never;
export interface ParamOption<T> {
name: string,
source: 'all' | 'get' | 'post' | 'route',
isOptional?: boolean | 'convert',
convert?: Converter<T>,
validate?: Validator,
name: string;
source: 'all' | 'get' | 'post' | 'route';
isOptional?: boolean | 'convert';
convert?: Converter<T>;
validate?: Validator;
}
const kSchema = Symbol.for('schemastery');
+2 -2
View File
@@ -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[]) {
+7 -7
View File
@@ -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';
+14 -14
View File
@@ -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<N extends string, H extends Handler<any>> =
@@ -24,19 +24,19 @@ export type Methods = 'get' | 'post' | 'put' | 'delete' | 'patch';
export type HookWithMethod = Exclude<HookType, 'before-operation'>;
export interface ServerEvents<C extends Context> extends KnownHandlerEvents, HandlerEvents<C> {
'handler/create': (thisArg: Handler<C> | ConnectionHandler<C>, type: 'ws' | 'http') => VoidReturn
'handler/create/http': (thisArg: Handler<C>) => VoidReturn
'handler/create/ws': (thisArg: ConnectionHandler<C>) => VoidReturn
'handler/init': (thisArg: Handler<C>) => VoidReturn
'handler/error': (thisArg: Handler<C> | ConnectionHandler<C>, e: Error) => VoidReturn
'handler/api/before': (thisArg: ApiHandler<C>) => 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<C>, e: Error) => VoidReturn
[k: `handler/api/before/${string}`]: (thisArg: ApiHandler<C>) => VoidReturn
[k: `api/before/${string}`]: (args: any) => VoidReturn
'handler/create': (thisArg: Handler<C> | ConnectionHandler<C>, type: 'ws' | 'http') => VoidReturn;
'handler/create/http': (thisArg: Handler<C>) => VoidReturn;
'handler/create/ws': (thisArg: ConnectionHandler<C>) => VoidReturn;
'handler/init': (thisArg: Handler<C>) => VoidReturn;
'handler/error': (thisArg: Handler<C> | ConnectionHandler<C>, e: Error) => VoidReturn;
'handler/api/before': (thisArg: ApiHandler<C>) => 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<C>, e: Error) => VoidReturn;
[k: `handler/api/before/${string}`]: (thisArg: ApiHandler<C>) => VoidReturn;
[k: `api/before/${string}`]: (args: any) => VoidReturn;
}
declare module 'cordis' {
+1 -1
View File
@@ -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",
+14 -11
View File
@@ -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<string, any>;
UiContext: Record<string, any>;
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<string, any>, context: Record<string, any>) => string | Promise<string>;
};
export type BinaryRenderer = {
}
export interface BinaryRenderer {
output: 'binary';
render: (name: string, args: Record<string, any>, context: Record<string, any>) => Buffer | Promise<Buffer>;
};
}
export type Renderer = (BinaryRenderer | TextRenderer) & {
name: string;
accept: readonly string[];
@@ -139,7 +141,7 @@ export interface UserModel {
_id: number;
}
export interface HandlerCommon<C> { } // eslint-disable-line @typescript-eslint/no-unused-vars
export interface HandlerCommon<C> { } // eslint-disable-line ts/no-unused-vars
export class HandlerCommon<C> {
static [kHandler]: string | boolean = 'HandlerCommon';
session: Record<string, any>;
@@ -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<C>, ...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<C>, ...permPrivChecker) {
return this.register('conn', name, path, RouteHandler, ...permPrivChecker);
}
+8 -8
View File
@@ -90,23 +90,23 @@ const saslprepString = <T = string>(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)],
+4 -3
View File
@@ -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,