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.insertSpaces": true,
"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"],

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",
"bracketSameLine": false,
"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",
"dev": "vite --host",
"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"
},
"dependencies": {
@ -48,16 +48,16 @@
"@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.9",
"eslint": "^9.8.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-tsdoc": "^0.3.0",
"globals": "^15.9.0",
"jest": "^29.7.0",
"postcss": "^8.4.40",
"tailwindcss": "^3.4.7",
"ts-jest": "^29.2.4",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.1",
"vite": "^5.3.5"
},
"jest": {

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@ function SelectUser({
options={options}
value={value ? { value: value, label: getUserLabel(value) } : null}
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
filterOption={filter}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -72,7 +72,7 @@ export class FolderNode {
*
*/
export class FolderTree {
roots: Map<string, FolderNode> = new Map();
roots = new Map<string, FolderNode>();
constructor(arr?: string[]) {
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.
*/
export class Graph {
nodes: Map<number, GraphNode> = new Map();
nodes = new Map<number, GraphNode>();
constructor(arr?: number[][]) {
if (!arr) {

View File

@ -20,7 +20,7 @@ import {
export class OssLoader {
private oss: IOperationSchemaData;
private graph: Graph = new Graph();
private operationByID: Map<OperationID, IOperation> = new Map();
private operationByID = new Map<OperationID, IOperation>();
private schemas: LibraryItemID[] = [];
constructor(input: IOperationSchemaData) {
@ -53,7 +53,7 @@ export class OssLoader {
}
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() {

View File

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

View File

@ -108,7 +108,7 @@ export enum HelpTopic {
/**
* Manual topics hierarchy.
*/
export const topicParent: Map<HelpTopic, HelpTopic> = new Map([
export const topicParent = new Map<HelpTopic, HelpTopic>([
[HelpTopic.MAIN, HelpTopic.MAIN],
[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}.
*/
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;
}
const mapping: { [key: string]: string } = {};
const mapping: Record<string, string> = {};
args
.filter(arg => !!arg.value)
.forEach(arg => {

View File

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

View File

@ -196,7 +196,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
const openOperationSchema = useCallback(
(target: OperationID) => {
const node = model.schema?.operationByID.get(target);
if (!node || !node.result) {
if (!node?.result) {
return;
}
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)}
/>
) : null}
{activeCst && activeCst.is_inherited ? (
{activeCst?.is_inherited ? (
<MiniButton
title='Перейти к исходной конституенте в ОСС'
onClick={() => controller.viewPredecessor(activeCst.id)}

View File

@ -117,7 +117,7 @@ function EditorRSExpression({
);
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;
}
const text = new RSTextWrapper(rsInput.current as Required<ReactCodeMirrorRef>);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,14 +29,14 @@ export class TextMatcher {
}
try {
this.query = new RegExp(query, isCaseSensitive ? '' : 'i');
} catch (exception: unknown) {
} catch (_exception: unknown) {
this.query = query;
}
}
test(text: string): boolean {
if (typeof this.query === 'string') {
return text.indexOf(this.query) !== -1;
return text.includes(this.query);
} else {
return !!text.match(this.query);
}
@ -46,7 +46,7 @@ export class TextMatcher {
/**
* 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) {
return text;
}