From cbfb316d0220660992e1252be7cf82a5cd9ffeda Mon Sep 17 00:00:00 2001 From: Alexey Morozov Date: Sun, 15 Jun 2025 02:31:15 +0300 Subject: [PATCH] Enable and fix type-checked linting --- eslint.config.mjs | 21 +++++++++++- examples/classicWorkspace.tsx | 2 +- examples/i18n.tsx | 2 +- examples/resources/common.tsx | 6 ++-- examples/resources/exampleMetadata.ts | 2 +- examples/sparql.tsx | 2 +- examples/stressTest.tsx | 2 +- examples/styleCustomization.tsx | 6 ++-- examples/wikidata.tsx | 4 +-- src/coreUtils/events.ts | 2 +- src/coreUtils/scheduler.ts | 9 +++-- src/data/indexedDb/indexedDbCachedProvider.ts | 9 ++--- src/data/sparql/responseHandler.ts | 11 +++++-- src/data/sparql/sparqlDataProvider.ts | 8 ++--- src/diagram/commands.ts | 2 +- src/diagram/elementLayer.tsx | 4 ++- src/diagram/layout.ts | 4 +-- src/diagram/layoutShared.ts | 12 +++---- src/diagram/paperArea.tsx | 6 ++-- src/diagram/toSvg.ts | 4 +-- src/editor/dataDiagramModel.ts | 8 ++--- src/editor/overlayController.tsx | 4 +-- src/editor/validation.ts | 2 +- src/forms/editRelationForm.tsx | 2 +- src/forms/elementTypeSelector.tsx | 10 +++--- src/forms/findOrCreateEntityForm.tsx | 4 +-- src/forms/linkTypeSelector.tsx | 4 +-- src/layout.worker.ts | 30 ++++++++--------- src/legacy-styles.tsx | 18 +++++----- src/templates/standardTemplate.tsx | 2 +- src/widgets/classTree/classTree.tsx | 20 +++++++---- src/widgets/connectionsMenu.tsx | 8 ++--- src/widgets/dialog.tsx | 2 +- src/widgets/dropOnCanvas.tsx | 2 +- src/widgets/instancesSearch.tsx | 2 +- src/widgets/linkAction.tsx | 2 +- src/widgets/navigator.tsx | 4 +-- src/widgets/selectionAction.tsx | 6 ++-- src/widgets/toolbarAction.tsx | 33 +++++++++---------- .../visualAuthoring/authoredEntity.tsx | 2 +- src/widgets/visualAuthoring/dragEditLayer.tsx | 2 +- src/workspace/workspace.tsx | 6 ++-- tsconfig.json | 1 + 43 files changed, 162 insertions(+), 130 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 76e9283f..623d47b4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,9 +5,19 @@ import react from 'eslint-plugin-react'; export default tseslint.config( eslint.configs.recommended, - tseslint.configs.recommended, + tseslint.configs.recommendedTypeChecked, react.configs.flat.recommended, react.configs.flat['jsx-runtime'], + { + languageOptions: { + parserOptions: { + projectService: { + allowDefaultProject: ['*.js', '*.mjs', 'vitest.config.mts'], + }, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, { ignores: ['dist/'], }, @@ -35,6 +45,9 @@ export default tseslint.config( 'no-control-regex': 'off', 'quotes': ['warn', 'single'], 'semi': ['warn', 'always'], + '@typescript-eslint/no-misused-promises': ['warn', { + checksVoidReturn: false, + }], '@typescript-eslint/no-empty-object-type': ['warn', { allowInterfaces: 'with-single-extends', allowWithName: 'Props$', @@ -46,6 +59,10 @@ export default tseslint.config( }], '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', + '@typescript-eslint/unbound-method': ['warn', { + ignoreStatic: true, + }], }, }, { @@ -69,6 +86,8 @@ export default tseslint.config( SwitchCase: 1, }], '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', }, } ); diff --git a/examples/classicWorkspace.tsx b/examples/classicWorkspace.tsx index f0d20403..4baaf314 100644 --- a/examples/classicWorkspace.tsx +++ b/examples/classicWorkspace.tsx @@ -28,7 +28,7 @@ function ClassicWorkspaceExample() { } const diagram = tryLoadLayoutFromLocalStorage(); - model.importLayout({ + await model.importLayout({ diagram, dataProvider, validateLinks: true, diff --git a/examples/i18n.tsx b/examples/i18n.tsx index 76c2f182..ca15a29b 100644 --- a/examples/i18n.tsx +++ b/examples/i18n.tsx @@ -5,7 +5,7 @@ import * as Reactodia from '../src/workspace'; import { ExampleToolbarMenu, mountOnLoad, tryLoadLayoutFromLocalStorage } from './resources/common'; -const TURTLE_DATA = require('./resources/orgOntology.ttl'); +const TURTLE_DATA = require('./resources/orgOntology.ttl') as string; const Layouts = Reactodia.defineLayoutWorker(() => new Worker('layout.worker.js')); diff --git a/examples/resources/common.tsx b/examples/resources/common.tsx index 994d0e92..6cc5b93f 100644 --- a/examples/resources/common.tsx +++ b/examples/resources/common.tsx @@ -43,7 +43,7 @@ export function ExampleToolbarMenu() { const task = overlay.startTask({title: 'Importing a layout from file'}); try { const json = await file.text(); - const diagramLayout = JSON.parse(json); + const diagramLayout = JSON.parse(json) as Reactodia.SerializedDiagram; await model.importLayout({ dataProvider: model.dataProvider, diagram: diagramLayout, @@ -136,7 +136,9 @@ export function tryLoadLayoutFromLocalStorage(): Reactodia.SerializedDiagram | u if (layoutKey) { try { const unparsedLayout = localStorage.getItem(layoutKey); - const entry = unparsedLayout && JSON.parse(unparsedLayout); + const entry = unparsedLayout + ? JSON.parse(unparsedLayout) as Reactodia.SerializedDiagram + : undefined; return entry; } catch (e) { /* ignore */ diff --git a/examples/resources/exampleMetadata.ts b/examples/resources/exampleMetadata.ts index 5febc42c..f7cddda9 100644 --- a/examples/resources/exampleMetadata.ts +++ b/examples/resources/exampleMetadata.ts @@ -233,7 +233,7 @@ type Vocabulary = { }; function vocabulary(prefix: string, keys: Keys): Vocabulary { - const result: { [key: string]: string } = Object.create(null); + const result = Object.create(null) as { [key: string]: string }; for (const key of keys) { result[key] = prefix + key; } diff --git a/examples/sparql.tsx b/examples/sparql.tsx index f95be9c1..26752ba4 100644 --- a/examples/sparql.tsx +++ b/examples/sparql.tsx @@ -41,7 +41,7 @@ function SparqlExample() { imagePropertyUris: ['http://xmlns.com/foaf/0.1/img'], }, Reactodia.OwlStatsSettings); - model.importLayout({ + await model.importLayout({ diagram, dataProvider: dataProvider, validateLinks: true, diff --git a/examples/stressTest.tsx b/examples/stressTest.tsx index a35fafb6..47f2f749 100644 --- a/examples/stressTest.tsx +++ b/examples/stressTest.tsx @@ -53,7 +53,7 @@ function StressTestExample() { const canvas = view.findAnyCanvas(); if (canvas) { canvas.renderingState.syncUpdate(); - canvas.zoomToFit(); + await canvas.zoomToFit(); } } }, []); diff --git a/examples/styleCustomization.tsx b/examples/styleCustomization.tsx index 4b9361fd..82e49ae1 100644 --- a/examples/styleCustomization.tsx +++ b/examples/styleCustomization.tsx @@ -5,9 +5,9 @@ import * as Reactodia from '../src/workspace'; import { ExampleToolbarMenu, mountOnLoad, tryLoadLayoutFromLocalStorage } from './resources/common'; -const BOOK_ICON = require('@vscode/codicons/src/icons/book.svg'); -const CERTIFICATE_ICON = require('@vscode/codicons/src/icons/symbol-class.svg'); -const COG_ICON = require('@vscode/codicons/src/icons/gear.svg'); +const BOOK_ICON = require('@vscode/codicons/src/icons/book.svg') as string; +const CERTIFICATE_ICON = require('@vscode/codicons/src/icons/symbol-class.svg') as string; +const COG_ICON = require('@vscode/codicons/src/icons/gear.svg') as string; const EXAMPLE_DIAGRAM = require('./resources/exampleDiagram.json') as Reactodia.SerializedDiagram; const TURTLE_DATA = require('./resources/orgOntology.ttl') as string; diff --git a/examples/wikidata.tsx b/examples/wikidata.tsx index d44ce3d3..16bec118 100644 --- a/examples/wikidata.tsx +++ b/examples/wikidata.tsx @@ -70,10 +70,10 @@ function WikidataExample() { { + onSelect={async () => { const {model: {dataProvider}} = getContext(); if (dataProvider instanceof Reactodia.IndexedDbCachedProvider) { - dataProvider.clearCache(); + await dataProvider.clearCache(); } }}> Clear Wikidata cache diff --git a/src/coreUtils/events.ts b/src/coreUtils/events.ts index 00c77595..31aa9265 100644 --- a/src/coreUtils/events.ts +++ b/src/coreUtils/events.ts @@ -135,7 +135,7 @@ export class EventSource implements Events, EventTrigger { if (this.anyListeners) { for (const anyListener of this.anyListeners) { - anyListener({[eventKey]: data} as any); + anyListener({[eventKey]: data} as unknown as Partial); } } } diff --git a/src/coreUtils/scheduler.ts b/src/coreUtils/scheduler.ts index f9b53ead..1bc3dc2f 100644 --- a/src/coreUtils/scheduler.ts +++ b/src/coreUtils/scheduler.ts @@ -9,8 +9,7 @@ * @category Utilities */ export class Debouncer { - // TODO: fix - private scheduled: any; + private scheduled: number | undefined; private _timeout: number | 'frame'; private callback: (() => void) | undefined; @@ -38,7 +37,7 @@ export class Debouncer { if (this.timeout === 'frame') { this.scheduled = requestAnimationFrame(this.runSynchronously); } else { - this.scheduled = setTimeout(this.runSynchronously, this.timeout); + this.scheduled = setTimeout(this.runSynchronously, this.timeout) as unknown as number; } } } @@ -48,12 +47,12 @@ export class Debouncer { callback?.(); } - runSynchronously() { + runSynchronously = () => { const wasScheduled = this.cancelScheduledTimeout(); if (wasScheduled) { this.run(); } - } + }; dispose() { this.cancelScheduledTimeout(); diff --git a/src/data/indexedDb/indexedDbCachedProvider.ts b/src/data/indexedDb/indexedDbCachedProvider.ts index de772226..b5fc7c97 100644 --- a/src/data/indexedDb/indexedDbCachedProvider.ts +++ b/src/data/indexedDb/indexedDbCachedProvider.ts @@ -275,7 +275,7 @@ export class IndexedDbCachedProvider implements DataProvider { private onClose = async () => { this.closeSignal.removeEventListener('abort', this.onClose); - this.closeDatabase(); + await this.closeDatabase(); }; private async closeDatabase(): Promise { @@ -410,7 +410,7 @@ export class IndexedDbCachedProvider implements DataProvider { const lock = await this.linkLock.acquire(); try { const ranges = await this.readLinkRanges(db, request); - const blocks = await this.selectMissingLinkBlocks(request, ranges); + const blocks = this.selectMissingLinkBlocks(request, ranges); if (blocks.length > 0) { await this.fetchAndCacheLinks(db, blocks, params.signal); await this.updateLinkRanges(db, ranges, request); @@ -799,7 +799,8 @@ async function fetchSingleWithDbCache( { const readTx = db.transaction(storeName, 'readonly'); const readStore = readTx.objectStore(storeName); - const cached: V | undefined = await indexedDbRequestAsPromise( + const cached = await indexedDbRequestAsPromise( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument readStore.get(key) ); readTx.commit(); @@ -1068,7 +1069,7 @@ function indexedDbSilentAbort(tx: IDBTransaction): void { } function serializeForDb(value: T): T { - return JSON.parse(JSON.stringify(value)); + return JSON.parse(JSON.stringify(value)) as T; } function isMissingRecord(value: { readonly id: K }): value is MissingRecord { diff --git a/src/data/sparql/responseHandler.ts b/src/data/sparql/responseHandler.ts index 864e3273..30d4e755 100644 --- a/src/data/sparql/responseHandler.ts +++ b/src/data/sparql/responseHandler.ts @@ -221,7 +221,9 @@ interface MutableElementModel { export function getElementsInfo( response: SparqlResponse, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment types: ReadonlyMap> = EMPTY_MAP, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment propertyByPredicate: ReadonlyMap = EMPTY_MAP, labelPredicate: PropertyTypeIri, openWorldProperties: boolean @@ -316,7 +318,9 @@ interface MutableLinkModel { export function getLinksInfo( bindings: ReadonlyArray, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment types: ReadonlyMap> = EMPTY_MAP, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment linkByPredicateType: ReadonlyMap = EMPTY_MAP, openWorldLinks: boolean = true ): LinkModel[] { @@ -365,6 +369,7 @@ export interface ConnectedLinkType { export function getConnectedLinkTypes( response: SparqlResponse, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment linkByPredicateType: ReadonlyMap = EMPTY_MAP, openWorldLinks: boolean = true ): ConnectedLinkType[] { @@ -419,7 +424,7 @@ export function getFilteredData( labelPredicate: PropertyTypeIri, openWorldLinks: boolean ): DataProviderLookupItem[] { - const predicateToConfig = linkByPredicateType ?? EMPTY_MAP; + const predicateToConfig: typeof linkByPredicateType = linkByPredicateType ?? EMPTY_MAP; const instances = new Map(); const resultTypes = new Map>(); @@ -466,13 +471,13 @@ export function getFilteredData( model.types.sort(); const outLinks = new Set(translateLinkPredicates( sourceTypes, - outPredicates.get(model.id) ?? EMPTY_SET, + outPredicates.get(model.id) ?? (EMPTY_SET as ReadonlySet), predicateToConfig, openWorldLinks )); const inLinks = new Set(translateLinkPredicates( targetTypes, - inPredicates.get(model.id) ?? EMPTY_SET, + inPredicates.get(model.id) ?? (EMPTY_SET as ReadonlySet), predicateToConfig, openWorldLinks )); diff --git a/src/data/sparql/sparqlDataProvider.ts b/src/data/sparql/sparqlDataProvider.ts index 3c8421bc..f0c9a2ce 100644 --- a/src/data/sparql/sparqlDataProvider.ts +++ b/src/data/sparql/sparqlDataProvider.ts @@ -1160,11 +1160,11 @@ async function executeSparqlQuery( } const response = await internalQuery; if (response.ok) { - const sparqlResponse: SparqlResponse = await response.json(); + const sparqlResponse = await response.json() as SparqlResponse; return mapSparqlResponseIntoRdfJs(sparqlResponse, factory); } else { const error = new Error(response.statusText); - (error as any).response = response; + (error as { response?: Response }).response = response; throw error; } } @@ -1205,7 +1205,7 @@ async function executeSparqlConstruct( return parser.parse(turtleText); } else { const error = new Error(response.statusText); - (error as any).response = response; + (error as { response?: Response }).response = response; throw error; } } @@ -1221,7 +1221,7 @@ function appendQueryParams(endpoint: string, queryParams: { [key: string]: strin function queryInternal(params: { url: string; body?: string; - headers: any; + headers: { [header: string]: string }; method: string; signal?: AbortSignal; }) { diff --git a/src/diagram/commands.ts b/src/diagram/commands.ts index c2610cbf..82f9e745 100644 --- a/src/diagram/commands.ts +++ b/src/diagram/commands.ts @@ -210,7 +210,7 @@ export function restoreViewport(canvas: CanvasApi): Command { return {center, scale}; } function apply({center, scale}: CapturedViewport): void { - canvas.centerTo(center, {scale}); + void canvas.centerTo(center, {scale}); } const initialViewport = capture(); const command = Command.create(TranslatedText.text('commands.restore_viewport.title'), () => { diff --git a/src/diagram/elementLayer.tsx b/src/diagram/elementLayer.tsx index d1ec4c9a..046f30fe 100644 --- a/src/diagram/elementLayer.tsx +++ b/src/diagram/elementLayer.tsx @@ -181,7 +181,7 @@ export class ElementLayer extends React.Component { } private requestRedraw = (element: Element, request: RedrawFlags) => { - const flagsWithForAll = this.redrawBatch.forAll | request; + const flagsWithForAll: RedrawFlags = this.redrawBatch.forAll | request; if (flagsWithForAll === this.redrawBatch.forAll) { // forAll flags already include the request return; @@ -254,9 +254,11 @@ function applyRedrawRequests( state = { element, templateProps: + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison (request & RedrawFlags.RecomputeTemplate) === RedrawFlags.RecomputeTemplate ? computeTemplateProps(state.element) : state.templateProps, blurred: + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison (request & RedrawFlags.RecomputeBlurred) === RedrawFlags.RecomputeBlurred ? computeIsBlurred(state.element, view) : state.blurred, }; diff --git a/src/diagram/layout.ts b/src/diagram/layout.ts index 7f9bb642..56e7a9dc 100644 --- a/src/diagram/layout.ts +++ b/src/diagram/layout.ts @@ -145,8 +145,8 @@ export async function calculateLayout(params: { elements = elements.filter(el => selectedElements.has(el)); } - const nodes: { [id: string]: LayoutNode } = Object.create(null); - const bounds: { [id: string]: Rect } = Object.create(null); + const nodes = Object.create(null) as { [id: string]: LayoutNode }; + const bounds = Object.create(null) as { [id: string]: Rect }; for (const element of elements) { nodes[element.id] = { diff --git a/src/diagram/layoutShared.ts b/src/diagram/layoutShared.ts index ea0db109..db3fa4a7 100644 --- a/src/diagram/layoutShared.ts +++ b/src/diagram/layoutShared.ts @@ -40,7 +40,7 @@ export function evaluateColaLayout( evaluateLayout(nodes, links); - const bounds: { [id: string]: Rect } = Object.create(null); + const bounds = Object.create(null) as { [id: string]: Rect }; for (const [id, node] of nodes) { bounds[id] = { x: node.x, @@ -136,7 +136,7 @@ export function colaRemoveOverlaps(state: LayoutState): LayoutState { cola.removeOverlaps(Array.from(nodeRectangles.values())); - const bounds: { [id: string]: Rect } = Object.create(null); + const bounds = Object.create(null) as { [id: string]: Rect }; for (const [id, {x, y}] of nodeRectangles) { const nodeBounds = Object.hasOwn(state.bounds, id) ? state.bounds[id] : undefined; bounds[id] = { @@ -164,7 +164,7 @@ export function layoutPadded( if (!padding) { return {state, unwrap: transformed => transformed}; } - const paddedBounds: { [id: string]: Rect } = Object.create(null); + const paddedBounds = Object.create(null) as { [id: string]: Rect }; for (const [id, {x, y, width, height}] of Object.entries(state.bounds)) { paddedBounds[id] = { x: x - padding.x, @@ -177,7 +177,7 @@ export function layoutPadded( return { state: paddedState, unwrap: transformed => { - const shrinkBounds: { [id: string]: Rect } = Object.create(null); + const shrinkBounds = Object.create(null) as { [id: string]: Rect }; for (const [id, {x, y, width, height}] of Object.entries(transformed.bounds)) { shrinkBounds[id] = { x: x + padding.x, @@ -203,7 +203,7 @@ export function layoutPaddedBiasFree( state: LayoutState, padding: Vector | undefined ): PaddedBiasFreeLayoutState { - const extendedBounds: { [id: string]: Rect } = Object.create(null); + const extendedBounds = Object.create(null) as { [id: string]: Rect }; let compressX = Infinity; let compressY = Infinity; @@ -225,7 +225,7 @@ export function layoutPaddedBiasFree( const withoutPadding = padded.unwrap(transformed); const fittingBox = getContentFittingBoxForLayout(withoutPadding); - const uncompressedBounds: { [id: string]: Rect } = Object.create(null); + const uncompressedBounds = Object.create(null) as { [id: string]: Rect }; for (const [id, bounds] of Object.entries(withoutPadding.bounds)) { const initialBounds = Object.hasOwn(state.bounds, id) ? state.bounds[id] : undefined; uncompressedBounds[id] = { diff --git a/src/diagram/paperArea.tsx b/src/diagram/paperArea.tsx index 2a11373e..7ee90ab2 100644 --- a/src/diagram/paperArea.tsx +++ b/src/diagram/paperArea.tsx @@ -278,7 +278,7 @@ export class PaperArea extends React.Component implements }; componentDidMount() { - this.adjustPaper(() => this.centerTo()); + this.adjustPaper(() => void this.centerTo()); const {model, renderingState} = this.props; const delayedAdjust = () => this.delayedPaperAdjust.call(this.adjustPaper); @@ -612,7 +612,7 @@ export class PaperArea extends React.Component implements ); const scale = originMetrics.getTransform().scale * scaleMultiplier; - this.centerTo(movedCenter, {scale}); + void this.centerTo(movedCenter, {scale}); return true; } @@ -689,7 +689,7 @@ export class PaperArea extends React.Component implements e.preventDefault(); const delta = Math.max(-1, Math.min(1, e.deltaY || e.deltaX)); const pivot = this.metrics.pageToPaperCoords(e.pageX, e.pageY); - this.zoomBy(-delta * 0.1, {pivot}); + void this.zoomBy(-delta * 0.1, {pivot}); } }; diff --git a/src/diagram/toSvg.ts b/src/diagram/toSvg.ts index f9548a95..07001cf7 100644 --- a/src/diagram/toSvg.ts +++ b/src/diagram/toSvg.ts @@ -164,7 +164,7 @@ function extractCSSFromDocument(targetSubtree: Element): string { for (let i = 0; i < document.styleSheets.length; i++) { let rules: CSSRuleList; try { - const cssSheet = document.styleSheets[i] as CSSStyleSheet; + const cssSheet = document.styleSheets[i]; rules = cssSheet.cssRules || cssSheet.rules; if (!rules) { continue; @@ -190,7 +190,7 @@ function composeExportedSvg(layers: ToSVGOptions['layers'], viewBox: Rect): { composedViewport.setAttribute('class', 'reactodia-exported-canvas'); composedSvg.appendChild(composedViewport); - const imageBounds: { [path: string]: Bounds } = Object.create(null); + const imageBounds = Object.create(null) as { [path: string]: Bounds }; for (const layer of layers) { if (layer instanceof SVGSVGElement) { diff --git a/src/editor/dataDiagramModel.ts b/src/editor/dataDiagramModel.ts index d353538a..d0809520 100644 --- a/src/editor/dataDiagramModel.ts +++ b/src/editor/dataDiagramModel.ts @@ -575,7 +575,7 @@ export class DataDiagramModel extends DiagramModel implements DataGraphStructure */ createElement(elementIriOrModel: ElementIri | ElementModel): EntityElement { const elementIri = typeof elementIriOrModel === 'string' - ? elementIriOrModel : (elementIriOrModel as ElementModel).id; + ? elementIriOrModel : elementIriOrModel.id; const elements = this.elements.filter((el): el is EntityElement => el instanceof EntityElement && el.iri === elementIri @@ -587,7 +587,7 @@ export class DataDiagramModel extends DiagramModel implements DataGraphStructure let data = typeof elementIriOrModel === 'string' ? EntityElement.placeholderData(elementIri) - : elementIriOrModel as ElementModel; + : elementIriOrModel; data = {...data, id: data.id}; const element = new EntityElement({data}); this.addElement(element); @@ -1015,7 +1015,7 @@ export interface RequestLinksOptions { export function requestElementData(model: DataDiagramModel, elementIris: ReadonlyArray): Command { return Command.effect( TranslatedText.text('data_diagram_model.request_entities.command'), - () => model.requestElementData(elementIris) + () => void model.requestElementData(elementIris) ); } @@ -1032,6 +1032,6 @@ export function restoreLinksBetweenElements( ): Command { return Command.effect( TranslatedText.text('data_diagram_model.request_relations.command'), - () => model.requestLinks(options) + () => void model.requestLinks(options) ); } diff --git a/src/editor/overlayController.tsx b/src/editor/overlayController.tsx index 7ca67bf6..8f7680ad 100644 --- a/src/editor/overlayController.tsx +++ b/src/editor/overlayController.tsx @@ -202,7 +202,7 @@ export class OverlayController { this.updateTaskSpinner(); if (delayed) { - delay(delayMs).then(() => { + void delay(delayMs).then(() => { if (this._tasks.has(createdTask)) { // Activate all tasks if any activates for (const task of this._tasks) { @@ -224,7 +224,7 @@ export class OverlayController { showSpinnerWhile(operation: Promise): void { const {translation: t} = this; const task = this.startTask(); - (async () => { + void (async () => { try { await operation; } catch (err) { diff --git a/src/editor/validation.ts b/src/editor/validation.ts index b23e2317..b2b581fd 100644 --- a/src/editor/validation.ts +++ b/src/editor/validation.ts @@ -185,7 +185,7 @@ export function validateElements( newState.elements.set(entity.id, loadingElement); outboundLinks.forEach(link => newState.links.set(link, loadingLink)); - processValidationResult(result, loadingElement, loadingLink, event, editor); + void processValidationResult(result, loadingElement, loadingLink, event, editor); } else { // use previous state for element and outbound links newState.elements.set(entity.id, previousState.elements.get(entity.id)!); diff --git a/src/forms/editRelationForm.tsx b/src/forms/editRelationForm.tsx index 9dc312cb..7163d368 100644 --- a/src/forms/editRelationForm.tsx +++ b/src/forms/editRelationForm.tsx @@ -68,7 +68,7 @@ function EditRelationFormInner(props: EditRelationFormProps) { if (!lastValidated.current || !equalLinks(toValidate, lastValidated.current)) { const cancellation = new AbortController(); setValidating(true); - validateLinkType( + void validateLinkType( toValidate, originalLink.data, workspace, diff --git a/src/forms/elementTypeSelector.tsx b/src/forms/elementTypeSelector.tsx index 0b989417..b3ce798d 100644 --- a/src/forms/elementTypeSelector.tsx +++ b/src/forms/elementTypeSelector.tsx @@ -93,16 +93,16 @@ export class ElementTypeSelectorInner extends React.Component { if (source.value.length === 0) { // Clear the search - this.searchExistingElements(''); + void this.searchExistingElements(''); } else { this.forceUpdate(); } }); this.listener.listen(searchStore.events, 'executeSearch', ({value}) => { - this.searchExistingElements(value); + void this.searchExistingElements(value); }); - this.fetchPossibleElementTypes(); + void this.fetchPossibleElementTypes(); } componentWillUnmount() { @@ -244,7 +244,7 @@ export class ElementTypeSelectorInner extends React.Component + onChange={e => void this.onElementTypeChange(e)}> @@ -284,7 +284,7 @@ export class ElementTypeSelectorInner extends React.Component this.onSelectExistingItem(model)} + onClick={(e, model) => void this.onSelectExistingItem(model)} /> ); }); diff --git a/src/forms/findOrCreateEntityForm.tsx b/src/forms/findOrCreateEntityForm.tsx index 890da1f0..a3b4917e 100644 --- a/src/forms/findOrCreateEntityForm.tsx +++ b/src/forms/findOrCreateEntityForm.tsx @@ -90,7 +90,7 @@ export class FindOrCreateEntityForm extends React.Component { + void Promise.all([validateElement, validateLink]).then(([elementError, linkError]) => { if (signal.aborted) { return; } this.setState({isValidating: false}); this.setElementOrLink({ @@ -238,7 +238,7 @@ export class FindOrCreateEntityForm extends React.Component this.forceUpdate(); componentDidMount() { - this.fetchPossibleLinkTypes(); + void this.fetchPossibleLinkTypes(); } componentDidUpdate(prevProps: LinkTypeSelectorProps) { @@ -64,7 +64,7 @@ export class LinkTypeSelector extends React.Component { + ): Promise => { return blockingDefaultLayout(graph, state, options); - } + }; /** * Force-directed layout algorithm from [cola.js](https://ialab.it.monash.edu/webcola/). */ - async forceLayout( + forceLayout = async ( graph: LayoutGraph, state: LayoutState, options?: ColaForceLayoutOptions - ): Promise { - return colaForceLayout(graph, state, options); - } + ): Promise => { + return Promise.resolve(colaForceLayout(graph, state, options)); + }; /** * Flow layout algorithm from [cola.js](https://ialab.it.monash.edu/webcola/). */ - async flowLayout( + flowLayout = async ( graph: LayoutGraph, state: LayoutState, options?: ColaFlowLayoutOptions - ): Promise { - return colaFlowLayout(graph, state, options); - } + ): Promise => { + return Promise.resolve(colaFlowLayout(graph, state, options)); + }; /** * Remove overlaps algorithm from [cola.js](https://ialab.it.monash.edu/webcola/). */ - async removeOverlaps( + removeOverlaps = async ( graph: LayoutGraph, state: LayoutState - ): Promise { - return colaRemoveOverlaps(state); - } + ): Promise => { + return Promise.resolve(colaRemoveOverlaps(state)); + }; } export type { DefaultLayouts }; diff --git a/src/legacy-styles.tsx b/src/legacy-styles.tsx index ed1b2be1..8c707122 100644 --- a/src/legacy-styles.tsx +++ b/src/legacy-styles.tsx @@ -1,14 +1,14 @@ import type { TypeStyleResolver, LinkTemplate, LinkTemplateResolver } from './diagram/customization'; -const classIcon = require('@images/semantic/class.svg'); -const objectPropertyIcon = require('@images/semantic/objectProperty.svg'); -const datatypePropertyIcon = require('@images/semantic/datatypeProperty.svg'); -const personIcon = require('@images/semantic/person.svg'); -const countryIcon = require('@images/semantic/country.svg'); -const organizationIcon = require('@images/semantic/organization.svg'); -const locationIcon = require('@images/semantic/location.svg'); -const eventIcon = require('@images/semantic/event.svg'); -const objectIcon = require('@images/semantic/object.svg'); +const classIcon = require('@images/semantic/class.svg') as string; +const objectPropertyIcon = require('@images/semantic/objectProperty.svg') as string; +const datatypePropertyIcon = require('@images/semantic/datatypeProperty.svg') as string; +const personIcon = require('@images/semantic/person.svg') as string; +const countryIcon = require('@images/semantic/country.svg') as string; +const organizationIcon = require('@images/semantic/organization.svg') as string; +const locationIcon = require('@images/semantic/location.svg') as string; +const eventIcon = require('@images/semantic/event.svg') as string; +const objectIcon = require('@images/semantic/object.svg') as string; /** * Built-in type style provider for Semantic Web element types: diff --git a/src/templates/standardTemplate.tsx b/src/templates/standardTemplate.tsx index 7875f0dc..26002391 100644 --- a/src/templates/standardTemplate.tsx +++ b/src/templates/standardTemplate.tsx @@ -385,7 +385,7 @@ function StandardEntityGroupItem(props: StandardEntityGroupItemProps) { )} data-reactodia-no-export='true' title={t.text('standard_template.ungroup.title')} - onClick={() => ungroupSome({ + onClick={() => void ungroupSome({ group: target, entities: new Set([data.id]), canvas, diff --git a/src/widgets/classTree/classTree.tsx b/src/widgets/classTree/classTree.tsx index c4b5682a..5874fb85 100644 --- a/src/widgets/classTree/classTree.tsx +++ b/src/widgets/classTree/classTree.tsx @@ -274,7 +274,7 @@ class ClassTreeInner extends React.Component { this.createElementCancellation.abort(); } - private async initClassTree() { + private initClassTree() { const {workspace: {model}} = this.props; const {fetchedGraph} = this.state; const {dataProvider} = model; @@ -282,7 +282,7 @@ class ClassTreeInner extends React.Component { this.refreshClassTree(); } else if (dataProvider) { this.setState({fetchedGraph: undefined}, () => { - this.fetchTypeGraph(dataProvider); + void this.fetchTypeGraph(dataProvider); }); } else { this.refreshClassTree(); @@ -340,13 +340,13 @@ class ClassTreeInner extends React.Component { }; private onCreateInstance = (node: TreeNode) => { - this.createInstanceAt(node.iri); + void this.createInstanceAt(node.iri); }; private onDragCreate = (node: TreeNode) => { const {workspace: {view}} = this.props; view.setHandlerForNextDropOnPaper(e => { - this.createInstanceAt(node.iri, e); + void this.createInstanceAt(node.iri, e); }); }; @@ -368,7 +368,7 @@ class ClassTreeInner extends React.Component { if (newIris.size > 0) { refreshingState = 'loading'; - this.queryCreatableTypes(newIris, cancellation.signal); + void this.queryCreatableTypes(newIris, cancellation.signal); } } @@ -377,7 +377,10 @@ class ClassTreeInner extends React.Component { }); }; - private async queryCreatableTypes(typeIris: Set, signal: AbortSignal) { + private async queryCreatableTypes( + typeIris: Set, + signal: AbortSignal + ): Promise { const {workspace: {editor}} = this.props; const {metadataProvider} = editor; if (!metadataProvider) { @@ -404,7 +407,10 @@ class ClassTreeInner extends React.Component { } } - private async createInstanceAt(elementType: ElementTypeIri, dropEvent?: CanvasDropEvent) { + private async createInstanceAt( + elementType: ElementTypeIri, + dropEvent?: CanvasDropEvent + ): Promise { const {workspace: {model, view, editor, getCommandBus}} = this.props; const batch = model.history.startBatch(); diff --git a/src/widgets/connectionsMenu.tsx b/src/widgets/connectionsMenu.tsx index be00a790..e327ef18 100644 --- a/src/widgets/connectionsMenu.tsx +++ b/src/widgets/connectionsMenu.tsx @@ -367,11 +367,11 @@ class ConnectionsMenuInner extends React.Component { - this.loadSuggestions(value); + void this.loadSuggestions(value); }); this.handler.listen(objectSearch.events, 'changeValue', this.updateAll); - this.loadLinks(); + void this.loadLinks(); } getSnapshotBeforeUpdate( @@ -465,7 +465,7 @@ class ConnectionsMenuInner extends React.Component { this.resubscribeOnLinkTypeEvents(); triggerWorkspaceEvent(WorkspaceEventKey.connectionsLoadLinks); - this.loadSuggestions(''); + void this.loadSuggestions(''); }); } @@ -621,7 +621,7 @@ class ConnectionsMenuInner extends React.Component { const paperCenter = canvas.metrics.scrollablePaneToPaperCoords( newScrollableCenter.x, newScrollableCenter.y, ); - canvas.centerTo(paperCenter); + void canvas.centerTo(paperCenter); } private onStartDragging = (e: React.MouseEvent) => { diff --git a/src/widgets/dropOnCanvas.tsx b/src/widgets/dropOnCanvas.tsx index 114654e2..285d6eb2 100644 --- a/src/widgets/dropOnCanvas.tsx +++ b/src/widgets/dropOnCanvas.tsx @@ -71,7 +71,7 @@ function tryParseDefaultDragAndDropData(e: DragEvent): ElementIri[] { if (!iriString) { return undefined; } let iris: ElementIri[]; try { - iris = JSON.parse(iriString); + iris = JSON.parse(iriString) as ElementIri[]; } catch (e) { iris = [(decode ? decodeURI(iriString) : iriString)]; } diff --git a/src/widgets/instancesSearch.tsx b/src/widgets/instancesSearch.tsx index 078ef2b0..aba1de88 100644 --- a/src/widgets/instancesSearch.tsx +++ b/src/widgets/instancesSearch.tsx @@ -553,7 +553,7 @@ class InstancesSearchInner extends React.Component { if (signal.aborted) { return; } console.error(error); - this.setState({querying: false, error}); + this.setState({querying: false, error: error as unknown}); }); } diff --git a/src/widgets/linkAction.tsx b/src/widgets/linkAction.tsx index 07fa3c93..3c87bc9a 100644 --- a/src/widgets/linkAction.tsx +++ b/src/widgets/linkAction.tsx @@ -309,7 +309,7 @@ function useCanModifyLink( const source = graph.getElement(link.sourceId) as EntityElement; const target = graph.getElement(link.targetId) as EntityElement; const signal = cancellation.signal; - mapAbortedToNull( + void mapAbortedToNull( editor.metadataProvider.canModifyRelation( link.data, source.data, target.data, {signal} ), diff --git a/src/widgets/navigator.tsx b/src/widgets/navigator.tsx index e24125cf..18b72cdf 100644 --- a/src/widgets/navigator.tsx +++ b/src/widgets/navigator.tsx @@ -540,7 +540,7 @@ class NavigatorInner extends React.Component { const canvasCoords = this.canvasFromPageCoords(e.pageX, e.pageY); const paperTransform = canvas.metrics.getTransform(); const paperCoords = paperFromCanvasCoords(canvasCoords, paperTransform, this.transform); - canvas.centerTo(paperCoords); + void canvas.centerTo(paperCoords); } }; @@ -552,7 +552,7 @@ class NavigatorInner extends React.Component { e.preventDefault(); const {canvas} = this.props; const delta = Math.max(-1, Math.min(1, e.deltaY || e.deltaX)); - canvas.zoomBy(-delta * 0.1); + void canvas.zoomBy(-delta * 0.1); }; private onToggleClick = () => { diff --git a/src/widgets/selectionAction.tsx b/src/widgets/selectionAction.tsx index 8a161e0f..593906bf 100644 --- a/src/widgets/selectionAction.tsx +++ b/src/widgets/selectionAction.tsx @@ -265,7 +265,7 @@ export function SelectionActionZoomToFit(props: SelectionActionZoomToFitProps) { } } const fittingBox = getContentFittingBox(elements, links, canvas.renderingState); - canvas.zoomToFitRect(fittingBox, {animate: true}); + void canvas.zoomToFitRect(fittingBox, {animate: true}); }} /> ); @@ -299,7 +299,7 @@ export function SelectionActionLayout(props: SelectionActionLayoutProps) { className={cx(className, `${CLASS_NAME}__layout`)} title={title ?? t.text('selection_action.layout.title')} onSelect={() => { - performLayout({ + void performLayout({ canvas, selectedElements: new Set(elements), animate: true, @@ -728,7 +728,7 @@ function useCanEstablishLink( } else { setCanLink(undefined); const signal = cancellation.signal; - mapAbortedToNull( + void mapAbortedToNull( editor.metadataProvider.canConnect(targetData, undefined, linkType, {signal}), signal ).then(connections => { diff --git a/src/widgets/toolbarAction.tsx b/src/widgets/toolbarAction.tsx index 6248ae89..12ed0d4e 100644 --- a/src/widgets/toolbarAction.tsx +++ b/src/widgets/toolbarAction.tsx @@ -303,14 +303,13 @@ export function ToolbarActionExport(props: ToolbarActionExportProps) { { + onSelect={async () => { const exportOptions: ExportRasterOptions = rasterOptions ?? { backgroundColor: 'white', }; - canvas.exportRaster(exportOptions).then(dataUri => { - const blob = dataURLToBlob(dataUri); - saveAs(blob, `${fileName}.png`); - }); + const dataUri = await canvas.exportRaster(exportOptions); + const blob = dataURLToBlob(dataUri); + saveAs(blob, `${fileName}.png`); }}> {children ?? t.text('toolbar_action.export_raster.label')} @@ -320,11 +319,10 @@ export function ToolbarActionExport(props: ToolbarActionExportProps) { { - canvas.exportSvg({addXmlHeader: true}).then(svg => { - const blob = new Blob([svg], {type: 'image/svg+xml'}); - saveAs(blob, `${fileName}.svg`); - }); + onSelect={async () => { + const svg = await canvas.exportSvg({addXmlHeader: true}); + const blob = new Blob([svg], {type: 'image/svg+xml'}); + saveAs(blob, `${fileName}.svg`); }}> {children ?? t.text('toolbar_action.export_svg.label')} @@ -334,13 +332,12 @@ export function ToolbarActionExport(props: ToolbarActionExportProps) { { + onSelect={async () => { const printWindow = window.open('', undefined, 'width=1280,height=720')!; - canvas.exportSvg().then(svg => { - printWindow.document.write(svg); - printWindow.document.close(); - printWindow.print(); - }); + const svg = await canvas.exportSvg(); + printWindow.document.write(svg); + printWindow.document.close(); + printWindow.print(); }}> {children ?? t.text('toolbar_action.export_print.label')} @@ -482,8 +479,8 @@ export function ToolbarActionLayout(props: ToolbarActionLayoutProps) { className={cx(className, `${CLASS_NAME}__layout`)} title={title ?? t.text('toolbar_action.layout.title')} disabled={diagramIsEmpty} - onSelect={() => { - performLayout({ + onSelect={async () => { + await performLayout({ canvas, animate: true, }); diff --git a/src/widgets/visualAuthoring/authoredEntity.tsx b/src/widgets/visualAuthoring/authoredEntity.tsx index 62c7333a..d223f6e2 100644 --- a/src/widgets/visualAuthoring/authoredEntity.tsx +++ b/src/widgets/visualAuthoring/authoredEntity.tsx @@ -82,7 +82,7 @@ export function useAuthoredEntity( if (!editor.metadataProvider || (authoringEvent && authoringEvent.type === 'entityDelete')) { setAllowedActions(AllowedActions.None); } else { - mapAbortedToNull( + void mapAbortedToNull( editor.metadataProvider.canModifyEntity(entity, {signal: cancellation.signal}), cancellation.signal ).then(canModify => { diff --git a/src/widgets/visualAuthoring/dragEditLayer.tsx b/src/widgets/visualAuthoring/dragEditLayer.tsx index fe14e649..ae0be4d4 100644 --- a/src/widgets/visualAuthoring/dragEditLayer.tsx +++ b/src/widgets/visualAuthoring/dragEditLayer.tsx @@ -327,7 +327,7 @@ class DragEditLayerInner extends React.Component // show spinner while waiting for additional MetadataApi queries this.setState({waitingForMetadata: true}); const selectedPosition = canvas.metrics.pageToPaperCoords(e.pageX, e.pageY); - this.executeEditOperation(selectedPosition); + void this.executeEditOperation(selectedPosition); }; private async executeEditOperation(selectedPosition: Vector): Promise { diff --git a/src/workspace/workspace.tsx b/src/workspace/workspace.tsx index c7def4c2..61c000c9 100644 --- a/src/workspace/workspace.tsx +++ b/src/workspace/workspace.tsx @@ -243,7 +243,7 @@ export class Workspace extends React.Component { this.listener.listen(model.events, 'loadingSuccess', () => { for (const canvas of view.findAllCanvases()) { canvas.renderingState.syncUpdate(); - canvas.zoomToFit(); + void canvas.zoomToFit(); } }); @@ -388,7 +388,7 @@ export class Workspace extends React.Component { applyLayout(calculatedLayout, model); batch.store(); if (zoomToFit) { - canvas.zoomToFit(); + await canvas.zoomToFit(); } } }; @@ -517,7 +517,7 @@ export function useLoadedWorkspace( const latestOnLoad = stateRef.current!.latestOnLoad; const controller = new AbortController(); - (async () => { + void (async () => { const task = context.overlay.startTask(); try { // Move execution into a microtask to avoid React warnings diff --git a/tsconfig.json b/tsconfig.json index efdc93c9..25bfade6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "./examples/**/*.tsx", "./src/**/*.ts", "./src/**/*.tsx", + "./test/**/*.ts", "./typings/typings.d.ts" ] }