R: Upgrade to eslint9

This commit is contained in:
Ivan 2024-08-06 14:38:10 +03:00
parent be0dfdefd8
commit edc728fe00
38 changed files with 1906 additions and 330 deletions

View File

@ -28,7 +28,9 @@
"editor.tabSize": 4, "editor.tabSize": 4,
"editor.insertSpaces": true, "editor.insertSpaces": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": "explicit" "source.organizeImports": "explicit",
"source.fixAll.ts": "never",
"source.fixAll.eslint": "never"
} }
}, },
"python.testing.unittestArgs": ["-v", "-s", "./tests", "-p", "test*.py"], "python.testing.unittestArgs": ["-v", "-s", "./tests", "-p", "test*.py"],

View File

@ -1,2 +0,0 @@
**/parser.ts
**/node_modules/**

View File

@ -1,25 +0,0 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended-type-checked",
"plugin:react-hooks/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": ["tsconfig.json", "tsconfig.node.json"]
},
"plugins": ["react-refresh", "simple-import-sort", "eslint-plugin-tsdoc"],
"rules": {
"no-console": "off",
"require-jsdoc": "off",
"react-refresh/only-export-components": ["off", { "allowConstantExport": true }],
"simple-import-sort/imports": "warn",
"tsdoc/syntax": "warn"
}
}

View File

@ -10,4 +10,4 @@
"quoteProps": "consistent", "quoteProps": "consistent",
"bracketSameLine": false, "bracketSameLine": false,
"bracketSpacing": true "bracketSpacing": true
} }

View File

@ -0,0 +1,61 @@
import globals from 'globals';
import typescriptPlugin from 'typescript-eslint';
import typescriptParser from '@typescript-eslint/parser';
import reactPlugin from 'eslint-plugin-react';
// import { fixupPluginRules } from '@eslint/compat';
// import reactHooksPlugin from 'eslint-plugin-react-hooks';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
export default [
...typescriptPlugin.configs.recommendedTypeChecked,
...typescriptPlugin.configs.stylisticTypeChecked,
{
ignores: ['**/parser.ts', '**/node_modules/**', '**/public/**', 'eslint.config.js']
},
{
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: { ...globals.browser, ...globals.es2020, ...globals.jest },
project: ['./tsconfig.json', './tsconfig.node.json']
}
}
},
{
plugins: {
'react': reactPlugin,
// 'react-hooks': fixupPluginRules(reactHooksPlugin),
'simple-import-sort': simpleImportSort
},
settings: { react: { version: 'detect' } },
rules: {
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_'
}
],
'react-refresh/only-export-components': ['off', { allowConstantExport: true }],
'simple-import-sort/imports': 'warn',
'simple-import-sort/exports': 'error'
}
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'no-console': 'off',
'require-jsdoc': 'off'
}
}
];

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
"test": "jest", "test": "jest",
"dev": "vite --host", "dev": "vite --host",
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
@ -48,16 +48,16 @@
"@typescript-eslint/parser": "^8.0.1", "@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^8.57.0", "eslint": "^9.8.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-refresh": "^0.4.9",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-tsdoc": "^0.3.0", "globals": "^15.9.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"postcss": "^8.4.40", "postcss": "^8.4.40",
"tailwindcss": "^3.4.7", "tailwindcss": "^3.4.7",
"ts-jest": "^29.2.4", "ts-jest": "^29.2.4",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"typescript-eslint": "^8.0.1",
"vite": "^5.3.5" "vite": "^5.3.5"
}, },
"jest": { "jest": {

View File

@ -1,6 +1,7 @@
// Search new icons at https://reactsvgicons.com/ // Search new icons at https://reactsvgicons.com/
// Note: save this file using Ctrl + K, Ctrl + Shift + S to disable autoformat // Note: save this file using Ctrl + K, Ctrl + Shift + S to disable autoformat
/* eslint-disable simple-import-sort/exports */
// ==== General actions ======= // ==== General actions =======
export { BiMenu as IconMenu } from 'react-icons/bi'; export { BiMenu as IconMenu } from 'react-icons/bi';
export { LuLogOut as IconLogout } from 'react-icons/lu'; export { LuLogOut as IconLogout } from 'react-icons/lu';

View File

@ -2,11 +2,11 @@
import { HTMLMotionProps } from 'framer-motion'; import { HTMLMotionProps } from 'framer-motion';
export namespace CProps { export namespace CProps {
export type Titled = { export interface Titled {
title?: string; title?: string;
titleHtml?: string; titleHtml?: string;
hideTitle?: boolean; hideTitle?: boolean;
}; }
export type Control = Titled & { export type Control = Titled & {
disabled?: boolean; disabled?: boolean;
@ -14,18 +14,18 @@ export namespace CProps {
noOutline?: boolean; noOutline?: boolean;
}; };
export type Styling = { export interface Styling {
style?: React.CSSProperties; style?: React.CSSProperties;
className?: string; className?: string;
}; }
export type Editor = Control & { export type Editor = Control & {
label?: string; label?: string;
}; };
export type Colors = { export interface Colors {
colors?: string; colors?: string;
}; }
export type Div = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>; export type Div = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
export type Button = Titled & export type Button = Titled &

View File

@ -25,7 +25,7 @@ function SelectLocation({ value, folderTree, dense, prefix, onClick, className,
const [folded, setFolded] = useState<FolderNode[]>(items); const [folded, setFolded] = useState<FolderNode[]>(items);
useLayoutEffect(() => { useLayoutEffect(() => {
setFolded(items.filter(item => item !== activeNode && (!activeNode || !activeNode.hasPredecessor(item)))); setFolded(items.filter(item => item !== activeNode && !activeNode?.hasPredecessor(item)));
}, [items, activeNode]); }, [items, activeNode]);
const onFoldItem = useCallback( const onFoldItem = useCallback(

View File

@ -51,7 +51,7 @@ function SelectUser({
options={options} options={options}
value={value ? { value: value, label: getUserLabel(value) } : null} value={value ? { value: value, label: getUserLabel(value) } : null}
onChange={data => { onChange={data => {
if (data !== null && data.value !== undefined) onSelectValue(data.value); if (data?.value !== undefined) onSelectValue(data.value);
}} }}
// @ts-expect-error: TODO: use type definitions from react-select in filter object // @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter} filterOption={filter}

View File

@ -23,7 +23,7 @@ import TableBody from './TableBody';
import TableFooter from './TableFooter'; import TableFooter from './TableFooter';
import TableHeader from './TableHeader'; import TableHeader from './TableHeader';
export { createColumnHelper, type ColumnSort, type RowSelectionState, type VisibilityState }; export { type ColumnSort, createColumnHelper, type RowSelectionState, type VisibilityState };
export interface IConditionalStyle<TData> { export interface IConditionalStyle<TData> {
when: (rowData: TData) => boolean; when: (rowData: TData) => boolean;

View File

@ -44,7 +44,7 @@ function TableBody<TData>({
lastIndex > currentIndex ? currentIndex : lastIndex + 1, lastIndex > currentIndex ? currentIndex : lastIndex + 1,
lastIndex > currentIndex ? lastIndex : currentIndex + 1 lastIndex > currentIndex ? lastIndex : currentIndex + 1
); );
const newSelection: { [key: string]: boolean } = {}; const newSelection: Record<string, boolean> = {};
toggleRows.forEach(row => { toggleRows.forEach(row => {
newSelection[row.id] = !target.getIsSelected(); newSelection[row.id] = !target.getIsSelected();
}); });

View File

@ -1,6 +1,6 @@
export { export {
default,
createColumnHelper, createColumnHelper,
default,
type IConditionalStyle, type IConditionalStyle,
type RowSelectionState, type RowSelectionState,
type VisibilityState type VisibilityState

View File

@ -4,12 +4,12 @@
import { GraphCanvas as GraphUI } from 'reagraph'; import { GraphCanvas as GraphUI } from 'reagraph';
export { export {
type CollapseProps,
type GraphCanvasRef,
type GraphEdge, type GraphEdge,
type GraphNode, type GraphNode,
type GraphCanvasRef,
Sphere, Sphere,
useSelection, useSelection
type CollapseProps
} from 'reagraph'; } from 'reagraph';
export { type LayoutTypes as GraphLayout } from 'reagraph'; export { type LayoutTypes as GraphLayout } from 'reagraph';

View File

@ -274,7 +274,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
onError: setProcessingError, onError: setProcessingError,
onSuccess: () => onSuccess: () =>
reloadItems(() => { reloadItems(() => {
if (user && user.subscriptions.includes(target)) { if (user?.subscriptions.includes(target)) {
user.subscriptions.splice( user.subscriptions.splice(
user.subscriptions.findIndex(item => item === target), user.subscriptions.findIndex(item => item === target),
1 1

View File

@ -102,7 +102,6 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
return false; return false;
} }
return schema.subscribers.includes(user.id); return schema.subscribers.includes(user.id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user, schema, toggleTracking]); }, [user, schema, toggleTracking]);
useEffect(() => { useEffect(() => {

View File

@ -143,7 +143,6 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
return false; return false;
} }
return schema.subscribers.includes(user.id); return schema.subscribers.includes(user.id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user, schema, toggleTracking]); }, [user, schema, toggleTracking]);
const update = useCallback( const update = useCallback(

View File

@ -61,7 +61,6 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
useEffect(() => { useEffect(() => {
cache.preload(schemasIDs); cache.preload(schemasIDs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [schemasIDs]); }, [schemasIDs]);
const handleSubmit = () => { const handleSubmit = () => {

View File

@ -73,7 +73,6 @@ function useRSFormCache() {
} }
}) })
); );
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pending]); }, [pending]);
return { preload, getSchema, getConstituenta, getSchemaByCst, loading, error, setError }; return { preload, getSchema, getConstituenta, getSchemaByCst, loading, error, setError };

View File

@ -26,7 +26,6 @@ function useWindowSize() {
} }
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
return windowSize; return windowSize;

View File

@ -72,7 +72,7 @@ export class FolderNode {
* *
*/ */
export class FolderTree { export class FolderTree {
roots: Map<string, FolderNode> = new Map(); roots = new Map<string, FolderNode>();
constructor(arr?: string[]) { constructor(arr?: string[]) {
arr?.forEach(path => this.addPath(path)); arr?.forEach(path => this.addPath(path));

View File

@ -48,7 +48,7 @@ export class GraphNode {
* This class is optimized for TermGraph use case and not supposed to be used as generic graph implementation. * This class is optimized for TermGraph use case and not supposed to be used as generic graph implementation.
*/ */
export class Graph { export class Graph {
nodes: Map<number, GraphNode> = new Map(); nodes = new Map<number, GraphNode>();
constructor(arr?: number[][]) { constructor(arr?: number[][]) {
if (!arr) { if (!arr) {

View File

@ -20,7 +20,7 @@ import {
export class OssLoader { export class OssLoader {
private oss: IOperationSchemaData; private oss: IOperationSchemaData;
private graph: Graph = new Graph(); private graph: Graph = new Graph();
private operationByID: Map<OperationID, IOperation> = new Map(); private operationByID = new Map<OperationID, IOperation>();
private schemas: LibraryItemID[] = []; private schemas: LibraryItemID[] = [];
constructor(input: IOperationSchemaData) { constructor(input: IOperationSchemaData) {
@ -53,7 +53,7 @@ export class OssLoader {
} }
private extractSchemas() { private extractSchemas() {
this.schemas = this.oss.items.map(operation => operation.result as LibraryItemID).filter(item => item !== null); this.schemas = this.oss.items.map(operation => operation.result).filter(item => item !== null);
} }
private inferOperationAttributes() { private inferOperationAttributes() {

View File

@ -18,8 +18,8 @@ import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './r
export class RSFormLoader { export class RSFormLoader {
private schema: IRSFormData; private schema: IRSFormData;
private graph: Graph = new Graph(); private graph: Graph = new Graph();
private cstByAlias: Map<string, IConstituenta> = new Map(); private cstByAlias = new Map<string, IConstituenta>();
private cstByID: Map<ConstituentaID, IConstituenta> = new Map(); private cstByID = new Map<ConstituentaID, IConstituenta>();
constructor(input: IRSFormData) { constructor(input: IRSFormData) {
this.schema = input; this.schema = input;
@ -116,7 +116,7 @@ export class RSFormLoader {
} }
private extractSources(target: IConstituenta): Set<ConstituentaID> { private extractSources(target: IConstituenta): Set<ConstituentaID> {
const sources: Set<ConstituentaID> = new Set(); const sources = new Set<ConstituentaID>();
if (!isFunctional(target.cst_type)) { if (!isFunctional(target.cst_type)) {
const node = this.graph.at(target.id)!; const node = this.graph.at(target.id)!;
node.inputs.forEach(id => { node.inputs.forEach(id => {

View File

@ -108,7 +108,7 @@ export enum HelpTopic {
/** /**
* Manual topics hierarchy. * Manual topics hierarchy.
*/ */
export const topicParent: Map<HelpTopic, HelpTopic> = new Map([ export const topicParent = new Map<HelpTopic, HelpTopic>([
[HelpTopic.MAIN, HelpTopic.MAIN], [HelpTopic.MAIN, HelpTopic.MAIN],
[HelpTopic.INTERFACE, HelpTopic.INTERFACE], [HelpTopic.INTERFACE, HelpTopic.INTERFACE],

View File

@ -254,7 +254,7 @@ export function isFunctional(type: CstType): boolean {
* Validate new alias against {@link CstType} and {@link IRSForm}. * Validate new alias against {@link CstType} and {@link IRSForm}.
*/ */
export function validateNewAlias(alias: string, type: CstType, schema: IRSForm): boolean { export function validateNewAlias(alias: string, type: CstType, schema: IRSForm): boolean {
return alias.length >= 2 && alias[0] == getCstTypePrefix(type) && !schema.cstByAlias.has(alias); return alias.length >= 2 && alias.startsWith(getCstTypePrefix(type)) && !schema.cstByAlias.has(alias);
} }
/** /**

View File

@ -91,7 +91,7 @@ export function substituteTemplateArgs(expression: string, args: IArgumentValue[
return expression; return expression;
} }
const mapping: { [key: string]: string } = {}; const mapping: Record<string, string> = {};
args args
.filter(arg => !!arg.value) .filter(arg => !!arg.value)
.forEach(arg => { .forEach(arg => {

View File

@ -1,6 +1,5 @@
import { HelpTopic } from '@/models/miscellaneous';
import LinkTopic from '@/components/ui/LinkTopic'; import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous';
function HelpCstAttributes() { function HelpCstAttributes() {
return ( return (

View File

@ -196,7 +196,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
const openOperationSchema = useCallback( const openOperationSchema = useCallback(
(target: OperationID) => { (target: OperationID) => {
const node = model.schema?.operationByID.get(target); const node = model.schema?.operationByID.get(target);
if (!node || !node.result) { if (!node?.result) {
return; return;
} }
router.push(urls.schema(node.result)); router.push(urls.schema(node.result));

View File

@ -56,7 +56,7 @@ function ToolbarConstituenta({
onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)} onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
/> />
) : null} ) : null}
{activeCst && activeCst.is_inherited ? ( {activeCst?.is_inherited ? (
<MiniButton <MiniButton
title='Перейти к исходной конституенте в ОСС' title='Перейти к исходной конституенте в ОСС'
onClick={() => controller.viewPredecessor(activeCst.id)} onClick={() => controller.viewPredecessor(activeCst.id)}

View File

@ -117,7 +117,7 @@ function EditorRSExpression({
); );
const handleEdit = useCallback((id: TokenID, key?: string) => { const handleEdit = useCallback((id: TokenID, key?: string) => {
if (!rsInput.current || !rsInput.current.editor || !rsInput.current.state || !rsInput.current.view) { if (!rsInput.current?.editor || !rsInput.current.state || !rsInput.current.view) {
return; return;
} }
const text = new RSTextWrapper(rsInput.current as Required<ReactCodeMirrorRef>); const text = new RSTextWrapper(rsInput.current as Required<ReactCodeMirrorRef>);

View File

@ -387,7 +387,7 @@ export const RSEditState = ({
const oldCount = model.schema.items.length; const oldCount = model.schema.items.length;
model.inlineSynthesis(data, newSchema => { model.inlineSynthesis(data, newSchema => {
setSelected([]); setSelected([]);
toast.success(information.addedConstituents(newSchema['items'].length - oldCount)); toast.success(information.addedConstituents(newSchema.items.length - oldCount));
}); });
}, },
[model, setSelected] [model, setSelected]

View File

@ -75,7 +75,7 @@ function RSTabs() {
setIsModified(false); setIsModified(false);
if (activeTab === RSTabID.CST_EDIT) { if (activeTab === RSTabID.CST_EDIT) {
const cstID = Number(cstQuery); const cstID = Number(cstQuery);
if (cstID && schema && schema.cstByID.has(cstID)) { if (cstID && schema?.cstByID.has(cstID)) {
setSelected([cstID]); setSelected([cstID]);
} else if (schema && schema?.items.length > 0) { } else if (schema && schema?.items.length > 0) {
setSelected([schema.items[0].id]); setSelected([schema.items[0].id]);

View File

@ -53,7 +53,7 @@ function TableSideConstituents({
useLayoutEffect(() => { useLayoutEffect(() => {
setColumnVisibility(prev => { setColumnVisibility(prev => {
const newValue = (windowSize.width ?? 0) >= denseThreshold; const newValue = (windowSize.width ?? 0) >= denseThreshold;
if (newValue === prev['expression']) { if (newValue === prev.expression) {
return prev; return prev;
} else { } else {
return { expression: newValue }; return { expression: newValue };

View File

@ -37,11 +37,11 @@ function cursorNode({ type, from, to }: TreeCursor, isLeaf = false): CursorNode
return { type, from, to, isLeaf }; return { type, from, to, isLeaf };
} }
type TreeTraversalOptions = { interface TreeTraversalOptions {
beforeEnter?: (cursor: TreeCursor) => void; beforeEnter?: (cursor: TreeCursor) => void;
onEnter: (node: CursorNode) => false | void; onEnter: (node: CursorNode) => false | void;
onLeave?: (node: CursorNode) => false | void; onLeave?: (node: CursorNode) => false | void;
}; }
/** /**
* Implements depth-first traversal. * Implements depth-first traversal.

View File

@ -296,7 +296,7 @@ export function describeLocationHead(head: LocationHead): string {
/** /**
* Retrieves label for graph layout mode. * Retrieves label for graph layout mode.
*/ */
export const mapLabelLayout: Map<GraphLayout, string> = new Map([ export const mapLabelLayout = new Map<GraphLayout, string>([
['treeTd2d', 'Граф: ДеревоВ 2D'], ['treeTd2d', 'Граф: ДеревоВ 2D'],
['treeTd3d', 'Граф: ДеревоВ 3D'], ['treeTd3d', 'Граф: ДеревоВ 3D'],
['forceatlas2', 'Граф: Атлас 2D'], ['forceatlas2', 'Граф: Атлас 2D'],
@ -312,7 +312,7 @@ export const mapLabelLayout: Map<GraphLayout, string> = new Map([
/** /**
* Retrieves label for {@link GraphColoring}. * Retrieves label for {@link GraphColoring}.
*/ */
export const mapLabelColoring: Map<GraphColoring, string> = new Map([ export const mapLabelColoring = new Map<GraphColoring, string>([
['none', 'Цвет: Моно'], ['none', 'Цвет: Моно'],
['status', 'Цвет: Статус'], ['status', 'Цвет: Статус'],
['type', 'Цвет: Класс'] ['type', 'Цвет: Класс']
@ -321,7 +321,7 @@ export const mapLabelColoring: Map<GraphColoring, string> = new Map([
/** /**
* Retrieves label for {@link GraphSizing}. * Retrieves label for {@link GraphSizing}.
*/ */
export const mapLabelSizing: Map<GraphSizing, string> = new Map([ export const mapLabelSizing = new Map<GraphSizing, string>([
['none', 'Узлы: Моно'], ['none', 'Узлы: Моно'],
['derived', 'Узлы: Порожденные'], ['derived', 'Узлы: Порожденные'],
['complex', 'Узлы: Простые'] ['complex', 'Узлы: Простые']

View File

@ -29,14 +29,14 @@ export class TextMatcher {
} }
try { try {
this.query = new RegExp(query, isCaseSensitive ? '' : 'i'); this.query = new RegExp(query, isCaseSensitive ? '' : 'i');
} catch (exception: unknown) { } catch (_exception: unknown) {
this.query = query; this.query = query;
} }
} }
test(text: string): boolean { test(text: string): boolean {
if (typeof this.query === 'string') { if (typeof this.query === 'string') {
return text.indexOf(this.query) !== -1; return text.includes(this.query);
} else { } else {
return !!text.match(this.query); return !!text.match(this.query);
} }
@ -46,7 +46,7 @@ export class TextMatcher {
/** /**
* Text substitution guided by mapping and regular expression. * Text substitution guided by mapping and regular expression.
*/ */
export function applyPattern(text: string, mapping: { [key: string]: string }, pattern: RegExp): string { export function applyPattern(text: string, mapping: Record<string, string>, pattern: RegExp): string {
if (text === '' || pattern === null) { if (text === '' || pattern === null) {
return text; return text;
} }