From 1ba01f1f151e3b21327923168774ebd70f69894d Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 28 Mar 2025 11:34:49 +0000 Subject: [PATCH 1/5] chore: fix lint errors except non-null createXXXNode --- eslint.config.js | 2 +- src/actions/action_menu.ts | 52 +++++++++----- src/actions/arrow_navigation.ts | 8 +-- src/actions/clipboard.ts | 22 +++--- src/actions/delete.ts | 2 +- src/actions/disconnect.ts | 13 ++-- src/actions/edit.ts | 14 +--- src/actions/enter.ts | 27 ++++--- src/actions/insert.ts | 12 ++-- src/actions/ws_movement.ts | 14 ++-- src/constants.ts | 5 +- src/flyout_cursor.ts | 2 + src/index.ts | 5 +- src/{keynames.js => keynames.ts} | 42 ++++++----- src/line_cursor.ts | 39 +++++++---- src/navigation.ts | 101 ++++++++++++++------------- src/navigation_controller.ts | 12 ++-- src/passive_focus.ts | 2 + src/shortcut_dialog.ts | 12 ++-- test/blocks/p5_blocks.js | 1 + test/blocks/p5_generators.js | 1 + test/index.ts | 7 +- test/webdriverio/index.ts | 10 ++- test/webdriverio/test/test_setup.mjs | 8 +-- 24 files changed, 232 insertions(+), 181 deletions(-) rename src/{keynames.js => keynames.ts} (78%) diff --git a/eslint.config.js b/eslint.config.js index d351d595..4b6119cb 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -129,7 +129,7 @@ module.exports = [ }, }, { - files: ['**/*.mocha.js'], + files: ['**/*.mocha.js', 'test/webdriverio/test/*_test.mjs'], languageOptions: { globals: { ...globals.mocha, diff --git a/src/actions/action_menu.ts b/src/actions/action_menu.ts index 584624d7..d70d4299 100644 --- a/src/actions/action_menu.ts +++ b/src/actions/action_menu.ts @@ -10,7 +10,6 @@ import { ContextMenu, ContextMenuRegistry, ShortcutRegistry, - comments, utils as BlocklyUtils, WidgetDiv, } from 'blockly'; @@ -23,10 +22,7 @@ const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind( ShortcutRegistry.registry, ); -export interface Scope { - block?: BlockSvg; - workspace?: WorkspaceSvg; - comment?: comments.RenderedWorkspaceComment; +export interface ScopeWithConnection extends ContextMenuRegistry.Scope { connection?: Connection; } @@ -100,6 +96,8 @@ export class ActionMenu { * Returns true if it is possible to open the action menu in the * current location, even if the menu was not opened due there being * no applicable menu items. + * + * @param workspace The workspace. */ private openActionMenu(workspace: WorkspaceSvg): boolean { let menuOptions: Array< @@ -114,7 +112,7 @@ export class ActionMenu { if (!node) return false; const nodeType = node.getType(); switch (nodeType) { - case ASTNode.types.BLOCK: + case ASTNode.types.BLOCK: { const block = node.getLocation() as BlockSvg; rtl = block.RTL; // Reimplement BlockSvg.prototype.generateContextMenu as that @@ -130,11 +128,12 @@ export class ActionMenu { } // End reimplement. break; + } // case Blockly.ASTNode.types.INPUT: case ASTNode.types.NEXT: case ASTNode.types.PREVIOUS: - case ASTNode.types.INPUT: + case ASTNode.types.INPUT: { const connection = node.getLocation() as Connection; rtl = connection.getSourceBlock().RTL; @@ -143,6 +142,7 @@ export class ActionMenu { // a possible kind of scope. this.addConnectionItems(connection, menuOptions); break; + } default: console.info(`No action menu for ASTNode of type ${nodeType}`); @@ -177,24 +177,21 @@ export class ActionMenu { */ private addConnectionItems( connection: Connection, - menuOptions: ( + menuOptions: Array< | ContextMenuRegistry.ContextMenuOption | ContextMenuRegistry.LegacyContextMenuOption - )[], + >, ) { - const insertAction = ContextMenuRegistry.registry.getItem('insert'); - if (!insertAction) throw new Error("can't find insert action"); - - const pasteAction = ContextMenuRegistry.registry.getItem( - 'blockPasteFromContextMenu', - ); - if (!pasteAction) throw new Error("can't find paste action"); - const possibleOptions = [insertAction, pasteAction /* etc.*/]; + const possibleOptions = [ + this.getContextMenuAction('insert'), + this.getContextMenuAction('blockPasteFromContextMenu'), + ]; // Check preconditions and get menu texts. const scope = { connection, } as unknown as ContextMenuRegistry.Scope; + for (const option of possibleOptions) { const precondition = option.preconditionFn?.(scope); if (precondition === 'hidden') continue; @@ -205,7 +202,7 @@ export class ActionMenu { menuOptions.push({ text: displayText, enabled: precondition === 'enabled', - callback: option.callback!, + callback: option.callback, scope, weight: option.weight, }); @@ -213,6 +210,25 @@ export class ActionMenu { return menuOptions; } + /** + * Find a context menu action, throwing an `Error` if it is not present or + * not an action. This usefully narrows the type to `ActionRegistryItem` + * which is not exported from Blockly. + * + * @param id The id of the action. + * @returns the action. + */ + private getContextMenuAction(id: string) { + const item = ContextMenuRegistry.registry.getItem('insert'); + if (!item) { + throw new Error(`can't find context menu item ${id}`); + } + if (!item?.callback) { + throw new Error(`context menu item unexpectedly not action ${id}`); + } + return item; + } + /** * Create a fake PointerEvent for opening the action menu for the * given ASTNode. diff --git a/src/actions/arrow_navigation.ts b/src/actions/arrow_navigation.ts index 7832c5ff..16cc3828 100644 --- a/src/actions/arrow_navigation.ts +++ b/src/actions/arrow_navigation.ts @@ -57,7 +57,7 @@ export class ArrowNavigation { right: { name: Constants.SHORTCUT_NAMES.RIGHT, preconditionFn: (workspace) => this.canCurrentlyNavigate(workspace), - callback: (workspace, _, shortcut) => { + callback: (workspace, e, shortcut) => { const toolbox = workspace.getToolbox() as Toolbox; let isHandled = false; switch (this.navigation.getState(workspace)) { @@ -94,7 +94,7 @@ export class ArrowNavigation { left: { name: Constants.SHORTCUT_NAMES.LEFT, preconditionFn: (workspace) => this.canCurrentlyNavigate(workspace), - callback: (workspace, _, shortcut) => { + callback: (workspace, e, shortcut) => { const toolbox = workspace.getToolbox() as Toolbox; let isHandled = false; switch (this.navigation.getState(workspace)) { @@ -129,7 +129,7 @@ export class ArrowNavigation { down: { name: Constants.SHORTCUT_NAMES.DOWN, preconditionFn: (workspace) => this.canCurrentlyNavigate(workspace), - callback: (workspace, _, shortcut) => { + callback: (workspace, e, shortcut) => { const toolbox = workspace.getToolbox() as Toolbox; const flyout = workspace.getFlyout(); let isHandled = false; @@ -170,7 +170,7 @@ export class ArrowNavigation { up: { name: Constants.SHORTCUT_NAMES.UP, preconditionFn: (workspace) => this.canCurrentlyNavigate(workspace), - callback: (workspace, _, shortcut) => { + callback: (workspace, e, shortcut) => { const flyout = workspace.getFlyout(); const toolbox = workspace.getToolbox() as Toolbox; let isHandled = false; diff --git a/src/actions/clipboard.ts b/src/actions/clipboard.ts index 46e8733d..ecc8d816 100644 --- a/src/actions/clipboard.ts +++ b/src/actions/clipboard.ts @@ -18,6 +18,7 @@ import * as Constants from '../constants'; import type {BlockSvg, WorkspaceSvg} from 'blockly'; import {LineCursor} from '../line_cursor'; import {Navigation} from '../navigation'; +import {ScopeWithConnection} from './action_menu'; const KeyCodes = blocklyUtils.KeyCodes; const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind( @@ -238,7 +239,7 @@ export class Clipboard { private copyPrecondition(workspace: WorkspaceSvg) { if (!this.canCurrentlyEdit(workspace)) return false; switch (this.navigation.getState(workspace)) { - case Constants.STATE.WORKSPACE: + case Constants.STATE.WORKSPACE: { const curNode = workspace?.getCursor()?.getCurNode(); const source = curNode?.getSourceBlock(); return !!( @@ -246,13 +247,15 @@ export class Clipboard { source?.isMovable() && !Gesture.inProgress() ); - case Constants.STATE.FLYOUT: + } + case Constants.STATE.FLYOUT: { const flyoutWorkspace = workspace.getFlyout()?.getWorkspace(); const sourceBlock = flyoutWorkspace ?.getCursor() ?.getCurNode() ?.getSourceBlock(); return !!(sourceBlock && !Gesture.inProgress()); + } default: return false; } @@ -314,18 +317,15 @@ export class Clipboard { private registerPasteContextMenuAction() { const pasteAction: ContextMenuRegistry.RegistryItem = { displayText: (scope) => `Paste (${this.getPlatformPrefix()}V)`, - preconditionFn: (scope) => { - const ws = - scope.block?.workspace ?? - (scope as any).connection?.getSourceBlock().workspace; + preconditionFn: (scope: ScopeWithConnection) => { + const block = scope.block ?? scope.connection?.getSourceBlock(); + const ws = block?.workspace as WorkspaceSvg | null; if (!ws) return 'hidden'; - return this.pastePrecondition(ws) ? 'enabled' : 'disabled'; }, - callback: (scope) => { - const ws = - scope.block?.workspace ?? - (scope as any).connection?.getSourceBlock().workspace; + callback: (scope: ScopeWithConnection) => { + const block = scope.block ?? scope.connection?.getSourceBlock(); + const ws = block?.workspace as WorkspaceSvg | null; if (!ws) return; return this.pasteCallback(ws); }, diff --git a/src/actions/delete.ts b/src/actions/delete.ts index 33498444..ba341e2a 100644 --- a/src/actions/delete.ts +++ b/src/actions/delete.ts @@ -113,7 +113,7 @@ export class DeleteAction { // Run the original precondition code, from the context menu option. // If the item would be hidden or disabled, respect it. const originalPreconditionResult = - this.oldContextMenuItem!.preconditionFn?.(scope) ?? 'enabled'; + this.oldContextMenuItem?.preconditionFn?.(scope) ?? 'enabled'; if (!ws || originalPreconditionResult !== 'enabled') { return originalPreconditionResult; } diff --git a/src/actions/disconnect.ts b/src/actions/disconnect.ts index 5da40fab..e3451e12 100644 --- a/src/actions/disconnect.ts +++ b/src/actions/disconnect.ts @@ -117,12 +117,17 @@ export class DisconnectAction { if (!curConnection.isConnected()) { return; } + const targetConnection = curConnection.targetConnection; + if (!targetConnection) { + throw new Error('Must have target if connected'); + } + const superiorConnection = curConnection.isSuperior() ? curConnection - : curConnection.targetConnection!; + : targetConnection; const inferiorConnection = curConnection.isSuperior() - ? curConnection.targetConnection! + ? targetConnection : curConnection; if (inferiorConnection.getSourceBlock().isShadow()) { @@ -140,8 +145,8 @@ export class DisconnectAction { rootBlock.bringToFront(); if (wasVisitingConnection) { - const connectionNode = ASTNode.createConnectionNode(superiorConnection); - workspace.getCursor()!.setCurNode(connectionNode!); + const connectionNode = ASTNode.createConnectionNode(superiorConnection)!; + workspace.getCursor()?.setCurNode(connectionNode); } } } diff --git a/src/actions/edit.ts b/src/actions/edit.ts index 9d6f7254..be997fdf 100644 --- a/src/actions/edit.ts +++ b/src/actions/edit.ts @@ -4,19 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - Connection, - ContextMenuRegistry, - ShortcutRegistry, - comments, - utils as BlocklyUtils, -} from 'blockly'; -import * as Constants from '../constants'; -import type {BlockSvg, WorkspaceSvg} from 'blockly'; +import {ContextMenuRegistry} from 'blockly'; +import type {WorkspaceSvg} from 'blockly'; import {LineCursor} from '../line_cursor'; -import {NavigationController} from '../navigation_controller'; - -const KeyCodes = BlocklyUtils.KeyCodes; /** * Action to edit a block. This just moves the cursor to the first diff --git a/src/actions/enter.ts b/src/actions/enter.ts index c168a627..046f0292 100644 --- a/src/actions/enter.ts +++ b/src/actions/enter.ts @@ -144,7 +144,7 @@ export class EnterAction { } this.navigation.focusWorkspace(workspace); - workspace.getCursor()!.setCurNode(ASTNode.createBlockNode(newBlock)!); + workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock)!); } /** @@ -155,17 +155,22 @@ export class EnterAction { */ private triggerButtonCallback(workspace: WorkspaceSvg) { const button = this.navigation - .getFlyoutCursor(workspace)! - .getCurNode() + .getFlyoutCursor(workspace) + ?.getCurNode() ?.getLocation() as FlyoutButton | undefined; if (!button) return; - const buttonCallback = (workspace as any).flyoutButtonCallbacks.get( - (button as any).callbackKey, - ); - if (typeof buttonCallback === 'function') { + + const flyoutButtonCallbacks: Map void> = + // @ts-expect-error private field access + workspace.flyoutButtonCallbacks; + + const info = button.info; + if ('callbackkey' in info) { + const buttonCallback = flyoutButtonCallbacks.get(info.callbackkey); + if (!buttonCallback) { + throw new Error('No callback function found for flyout button.'); + } buttonCallback(button); - } else if (!button.isLabel()) { - throw new Error('No callback function found for flyout button.'); } } @@ -208,8 +213,8 @@ export class EnterAction { } const curBlock = this.navigation - .getFlyoutCursor(workspace)! - .getCurNode() + .getFlyoutCursor(workspace) + ?.getCurNode() ?.getLocation() as BlockSvg | undefined; if (!curBlock?.isEnabled()) { console.warn("Can't insert a disabled block."); diff --git a/src/actions/insert.ts b/src/actions/insert.ts index 0f8f4a42..84c60117 100644 --- a/src/actions/insert.ts +++ b/src/actions/insert.ts @@ -5,16 +5,14 @@ */ import { - Connection, ContextMenuRegistry, ShortcutRegistry, - comments, utils as BlocklyUtils, } from 'blockly'; import * as Constants from '../constants'; -import type {BlockSvg, WorkspaceSvg} from 'blockly'; +import type {WorkspaceSvg} from 'blockly'; import {Navigation} from '../navigation'; -import {Scope} from './action_menu'; +import {ScopeWithConnection} from './action_menu'; const KeyCodes = BlocklyUtils.KeyCodes; @@ -87,15 +85,15 @@ export class InsertAction { return 'Insert Block (I)'; } }, - preconditionFn: (scope: Scope) => { + preconditionFn: (scope: ScopeWithConnection) => { const block = scope.block ?? scope.connection?.getSourceBlock(); const ws = block?.workspace as WorkspaceSvg | null; if (!ws) return 'hidden'; return this.insertPrecondition(ws) ? 'enabled' : 'hidden'; }, - callback: (scope: Scope) => { - let ws = + callback: (scope: ScopeWithConnection) => { + const ws = scope.block?.workspace ?? (scope.connection?.getSourceBlock().workspace as WorkspaceSvg); if (!ws) return false; diff --git a/src/actions/ws_movement.ts b/src/actions/ws_movement.ts index 25700c9d..369d8c4c 100644 --- a/src/actions/ws_movement.ts +++ b/src/actions/ws_movement.ts @@ -13,6 +13,11 @@ const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind( ShortcutRegistry.registry, ); +/** + * The distance to move the cursor when the cursor is on the workspace. + */ +const WS_MOVE_DISTANCE = 40; + /** * Logic for free movement of the cursor on the workspace with keyboard * shortcuts. @@ -24,11 +29,6 @@ export class WorkspaceMovement { */ private canCurrentlyEdit: (ws: WorkspaceSvg) => boolean; - /** - * The distance to move the cursor when the cursor is on the workspace. - */ - WS_MOVE_DISTANCE = 40; - constructor(canEdit: (ws: WorkspaceSvg) => boolean) { this.canCurrentlyEdit = canEdit; } @@ -111,8 +111,8 @@ export class WorkspaceMovement { if (!curNode || curNode.getType() !== ASTNode.types.WORKSPACE) return false; const wsCoord = curNode.getWsCoordinate(); - const newX = xDirection * this.WS_MOVE_DISTANCE + wsCoord.x; - const newY = yDirection * this.WS_MOVE_DISTANCE + wsCoord.y; + const newX = xDirection * WS_MOVE_DISTANCE + wsCoord.x; + const newY = yDirection * WS_MOVE_DISTANCE + wsCoord.y; cursor.setCurNode( ASTNode.createWorkspaceNode( diff --git a/src/constants.ts b/src/constants.ts index 4fb5cedd..5884cabf 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +// The rules expect camel or pascal case enum members and record properties. + /** * @license * Copyright 2021 Google LLC @@ -38,13 +41,11 @@ export enum SHORTCUT_NAMES { CUT = 'keyboard_nav_cut', PASTE = 'keyboard_nav_paste', DELETE = 'keyboard_nav_delete', - /* eslint-disable @typescript-eslint/naming-convention */ MOVE_WS_CURSOR_UP = 'workspace_up', MOVE_WS_CURSOR_DOWN = 'workspace_down', MOVE_WS_CURSOR_LEFT = 'workspace_left', MOVE_WS_CURSOR_RIGHT = 'workspace_right', CREATE_WS_CURSOR = 'to_workspace', - /* eslint-enable @typescript-eslint/naming-convention */ LIST_SHORTCUTS = 'list_shortcuts', CLEAN_UP = 'clean_up_workspace', } diff --git a/src/flyout_cursor.ts b/src/flyout_cursor.ts index e4abdf10..fb0c73fe 100644 --- a/src/flyout_cursor.ts +++ b/src/flyout_cursor.ts @@ -20,6 +20,8 @@ import {scrollBoundsIntoView} from './workspace_utilities'; export class FlyoutCursor extends Blockly.Cursor { /** * The constructor for the FlyoutCursor. + * + * @param flyout The flyout this cursor is for. */ constructor(private readonly flyout: Blockly.IFlyout) { super(); diff --git a/src/index.ts b/src/index.ts index 63efa4e0..e0773444 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,9 +10,9 @@ import {CursorOptions, LineCursor} from './line_cursor'; import {getFlyoutElement, getToolboxElement} from './workspace_utilities'; /** Options object for KeyboardNavigation instances. */ -export type NavigationOptions = { +export interface NavigationOptions { cursor: Partial; -}; +} /** Default options for LineCursor instances. */ const defaultOptions: NavigationOptions = { @@ -73,6 +73,7 @@ export class KeyboardNavigation { * * @param workspace The workspace that the plugin will * be added to. + * @param options Options. */ constructor( workspace: Blockly.WorkspaceSvg, diff --git a/src/keynames.js b/src/keynames.ts similarity index 78% rename from src/keynames.js rename to src/keynames.ts index d2e78bfa..f8c5b0d6 100644 --- a/src/keynames.js +++ b/src/keynames.ts @@ -19,7 +19,8 @@ * * Copied from goog.events.keynames */ -const keyNames = { +const keyNames: Record = { + /* eslint-disable @typescript-eslint/naming-convention */ 8: 'backspace', 9: 'tab', 13: 'enter', @@ -118,6 +119,7 @@ const keyNames = { 221: 'close-square-bracket', 222: 'single-quote', 224: 'win', + /* eslint-enable @typescript-eslint/naming-convention */ }; const modifierKeys = ['control', 'alt', 'meta']; @@ -126,12 +128,20 @@ const modifierKeys = ['control', 'alt', 'meta']; * Assign the appropriate class names for the key. * Modifier keys are indicated so they can be switched to a platform specific * key. + * + * @param keyName The key name. */ -function getKeyClassName(keyName) { +function getKeyClassName(keyName: string) { return modifierKeys.includes(keyName.toLowerCase()) ? 'key modifier' : 'key'; } -export function toTitleCase(str) { +/** + * Naive title case conversion. Uppercases first and lowercases remainder. + * + * @param str String. + * @returns The string in title case. + */ +export function toTitleCase(str: string) { return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); } @@ -139,11 +149,13 @@ export function toTitleCase(str) { * Convert from a serialized key code to a HTML string. * This should be the inverse of ShortcutRegistry.createSerializedKey, but * should also convert ascii characters to strings. - * @param {string} keycode The key code as a string of characters separated + * + * @param keycode The key code as a string of characters separated * by the + character. - * @returns {string} A single string representing the key code. + * @param index Which key code this is in sequence. + * @returns A single string representing the key code. */ -function keyCodeToString(keycode, index) { +function keyCodeToString(keycode: string, index: number) { let result = ``; const pieces = keycode.split('+'); @@ -154,29 +166,23 @@ function keyCodeToString(keycode, index) { piece = pieces[i]; strrep = keyNames[piece] ?? piece; const className = getKeyClassName(strrep); - - if (i.length === 1) { - strrep = strrep.toUpperCase(); - } else { - strrep = toTitleCase(strrep); - } - if (i > 0) { result += '+'; } - result += `${strrep}`; + result += `${toTitleCase(strrep)}`; } result += ''; return result; } /** - * Convert an array of key codes into a comma-separated list of strings - * @param {Array} keycodeArr The array of key codes to convert. - * @returns {string} The input array as a comma-separated list of + * Convert an array of key codes into a comma-separated list of strings. + * + * @param keycodeArr The array of key codes to convert. + * @returns The input array as a comma-separated list of * human-readable strings wrapped in HTML. */ -export function keyCodeArrayToString(keycodeArr) { +export function keyCodeArrayToString(keycodeArr: string[]): string { const stringified = keycodeArr.map((keycode, index) => keyCodeToString(keycode, index), ); diff --git a/src/line_cursor.ts b/src/line_cursor.ts index 54508590..afef6f30 100644 --- a/src/line_cursor.ts +++ b/src/line_cursor.ts @@ -18,13 +18,13 @@ import {ASTNode, Marker} from 'blockly/core'; import {scrollBoundsIntoView} from './workspace_utilities'; /** Options object for LineCursor instances. */ -export type CursorOptions = { +export interface CursorOptions { /** * Can the cursor visit all stack connections (next/previous), or * (if false) only unconnected next connections? */ stackConnections: boolean; -}; +} /** Default options for LineCursor instances. */ const defaultOptions: CursorOptions = { @@ -50,13 +50,14 @@ export class LineCursor extends Marker { private potentialNodes: Blockly.ASTNode[] | null = null; /** Whether the renderer is zelos-style. */ - private isZelos: boolean = false; + private isZelos = false; /** * @param workspace The workspace this cursor belongs to. + * @param options Cursor options. */ constructor( - public readonly workspace: Blockly.WorkspaceSvg, + private readonly workspace: Blockly.WorkspaceSvg, options?: Partial, ) { super(); @@ -111,7 +112,7 @@ export class LineCursor extends Marker { if (!curNode) { return null; } - let newNode = this.getNextNode(curNode, this.validLineNode.bind(this)); + const newNode = this.getNextNode(curNode, this.validLineNode.bind(this)); if (newNode) { this.setCurNode(newNode); @@ -150,7 +151,10 @@ export class LineCursor extends Marker { if (!curNode) { return null; } - let newNode = this.getPreviousNode(curNode, this.validLineNode.bind(this)); + const newNode = this.getPreviousNode( + curNode, + this.validLineNode.bind(this), + ); if (newNode) { this.setCurNode(newNode); @@ -187,7 +191,7 @@ export class LineCursor extends Marker { * - in effect, if the LineCursor is at the end of the 'current * line' of the program. */ - public atEndOfLine(): boolean { + atEndOfLine(): boolean { const curNode = this.getCurNode(); if (!curNode) return false; const rightNode = this.getNextNode( @@ -227,12 +231,13 @@ export class LineCursor extends Marker { switch (type) { case ASTNode.types.BLOCK: return !(location as Blockly.Block).outputConnection?.isConnected(); - case ASTNode.types.INPUT: + case ASTNode.types.INPUT: { const connection = location as Blockly.Connection; return ( connection.type === Blockly.NEXT_STATEMENT && (this.options.stackConnections || !connection.isConnected()) ); + } case ASTNode.types.NEXT: return ( this.options.stackConnections || @@ -388,13 +393,17 @@ export class LineCursor extends Marker { * @returns The right most child of the given node, or the node if no child * exists. */ - private getRightMostChild(node: ASTNode | null): ASTNode | null { - if (!node!.in()) { + private getRightMostChild(node: ASTNode): ASTNode | null { + let newNode = node.in(); + if (!newNode) { return node; } - let newNode = node!.in(); - while (newNode && newNode.next()) { - newNode = newNode.next(); + for ( + let nextNode: ASTNode | null = newNode; + nextNode; + nextNode = newNode.next() + ) { + newNode = nextNode; } return this.getRightMostChild(newNode); } @@ -573,6 +582,7 @@ export class LineCursor extends Marker { * * @param oldNode The previous node. * @param curNode The current node. + * @param realDrawer The object ~in charge of drawing the marker. */ private drawMarker( oldNode: ASTNode | null, @@ -620,7 +630,8 @@ export class LineCursor extends Marker { // Call MarkerSvg.prototype.fireMarkerEvent like // MarkerSvg.prototype.draw would (even though it's private). - (realDrawer as any)?.fireMarkerEvent?.(oldNode, curNode); + // @ts-expect-error calling protected method + realDrawer?.fireMarkerEvent?.(oldNode, curNode); } /** diff --git a/src/navigation.ts b/src/navigation.ts index fdd115fe..82264bfd 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -24,6 +24,22 @@ import { } from './workspace_utilities'; import {PassiveFocus} from './passive_focus'; +/** + * The default coordinate to use when focusing on the workspace and no + * blocks are present. In pixel coordinates, but will be converted to + * workspace coordinates when used to position the cursor. + */ +const DEFAULT_WS_COORDINATE: Blockly.utils.Coordinate = + new Blockly.utils.Coordinate(100, 100); + +/** + * The default coordinate to use when moving the cursor to the workspace + * after a block has been deleted. In pixel coordinates, but will be + * converted to workspace coordinates when used to position the cursor. + */ +const WS_COORDINATE_ON_DELETE: Blockly.utils.Coordinate = + new Blockly.utils.Coordinate(100, 100); + /** * Class that holds all methods necessary for keyboard navigation to work. */ @@ -34,22 +50,6 @@ export class Navigation { */ workspaceStates: {[index: string]: Constants.STATE} = {}; - /** - * The default coordinate to use when focusing on the workspace and no - * blocks are present. In pixel coordinates, but will be converted to - * workspace coordinates when used to position the cursor. - */ - DEFAULT_WS_COORDINATE: Blockly.utils.Coordinate = - new Blockly.utils.Coordinate(100, 100); - - /** - * The default coordinate to use when moving the cursor to the workspace - * after a block has been deleted. In pixel coordinates, but will be - * converted to workspace coordinates when used to position the cursor. - */ - WS_COORDINATE_ON_DELETE: Blockly.utils.Coordinate = - new Blockly.utils.Coordinate(100, 100); - /** * Wrapper for method that deals with workspace changes. * Used for removing change listener. @@ -263,15 +263,21 @@ export class Navigation { e.type === Blockly.Events.CLICK && (e as Blockly.Events.Click).targetType === 'block' ) { - const block = flyoutWorkspace.getBlockById( - (e as Blockly.Events.Click).blockId!, - ); - this.handleBlockClickInFlyout(mainWorkspace, block!); + const {blockId} = e as Blockly.Events.Click; + if (blockId) { + const block = flyoutWorkspace.getBlockById(blockId); + if (block) { + this.handleBlockClickInFlyout(mainWorkspace, block); + } + } } else if (e.type === Blockly.Events.SELECTED) { - const block = flyoutWorkspace.getBlockById( - (e as Blockly.Events.Selected).newElementId!, - ); - this.handleBlockClickInFlyout(mainWorkspace, block!); + const {newElementId} = e as Blockly.Events.Selected; + if (newElementId) { + const block = flyoutWorkspace.getBlockById(newElementId); + if (block) { + this.handleBlockClickInFlyout(mainWorkspace, block); + } + } } } else if ( e.type === Blockly.Events.BLOCK_CREATE && @@ -333,23 +339,17 @@ export class Navigation { const deletedBlockId = e.blockId; const ids = e.ids ?? []; const cursor = workspace.getCursor(); + if (!cursor) return; // Make sure the cursor is on a block. - if ( - !cursor || - !cursor.getCurNode() || - !cursor.getCurNode()?.getSourceBlock() - ) { - return; - } + const sourceBlock = cursor.getCurNode()?.getSourceBlock(); + if (!sourceBlock) return; - const curNode = cursor.getCurNode(); - const sourceBlock = curNode?.getSourceBlock()!; - if (sourceBlock?.id === deletedBlockId || ids.includes(sourceBlock?.id)) { + if (sourceBlock.id === deletedBlockId || ids.includes(sourceBlock.id)) { cursor.setCurNode( Blockly.ASTNode.createWorkspaceNode( workspace, - this.WS_COORDINATE_ON_DELETE, + WS_COORDINATE_ON_DELETE, )!, ); } @@ -369,12 +369,12 @@ export class Navigation { if (!block) { return; } - if (block.isShadow()) { - block = block.getParent()!; + const curNodeBlock = block.isShadow() ? block : block.getParent(); + if (curNodeBlock) { + this.getFlyoutCursor(mainWorkspace)?.setCurNode( + Blockly.ASTNode.createStackNode(curNodeBlock)!, + ); } - this.getFlyoutCursor(mainWorkspace)!.setCurNode( - Blockly.ASTNode.createStackNode(block)!, - ); this.focusFlyout(mainWorkspace); } @@ -488,9 +488,9 @@ export class Navigation { } this.setState(workspace, Constants.STATE.TOOLBOX); - if (!toolbox.getSelectedItem()) { + if (!toolbox.getSelectedItem() && toolbox instanceof Blockly.Toolbox) { // Find the first item that is selectable. - const toolboxItems = (toolbox as any).getToolboxItems(); + const toolboxItems = toolbox.getToolboxItems(); for (let i = 0, toolboxItem; (toolboxItem = toolboxItems[i]); i++) { if (toolboxItem.isSelectable()) { toolbox.selectItemByPosition(i); @@ -602,14 +602,14 @@ export class Navigation { if (defaultFlyoutItemElement instanceof Blockly.FlyoutButton) { const astNode = Blockly.ASTNode.createButtonNode( defaultFlyoutItemElement as Blockly.FlyoutButton, - ); - flyoutCursor.setCurNode(astNode!); + )!; + flyoutCursor.setCurNode(astNode); return true; } else if (defaultFlyoutItemElement instanceof Blockly.BlockSvg) { const astNode = Blockly.ASTNode.createStackNode( defaultFlyoutItemElement as Blockly.BlockSvg, - ); - flyoutCursor.setCurNode(astNode!); + )!; + flyoutCursor.setCurNode(astNode); return true; } return false; @@ -642,8 +642,8 @@ export class Navigation { return false; } const wsCoordinates = new Blockly.utils.Coordinate( - this.DEFAULT_WS_COORDINATE.x / workspace.scale, - this.DEFAULT_WS_COORDINATE.y / workspace.scale, + DEFAULT_WS_COORDINATE.x / workspace.scale, + DEFAULT_WS_COORDINATE.y / workspace.scale, ); if (topBlocks.length > 0) { cursor.setCurNode( @@ -1077,9 +1077,9 @@ export class Navigation { workspace.keyboardAccessibilityMode ) { workspace.keyboardAccessibilityMode = false; - workspace.getCursor()!.hide(); + workspace.getCursor()?.hide(); if (this.getFlyoutCursor(workspace)) { - this.getFlyoutCursor(workspace)!.hide(); + this.getFlyoutCursor(workspace)?.hide(); } } } @@ -1117,6 +1117,7 @@ export class Navigation { /** * Save the current cursor location and open the toolbox or flyout * to select and insert a block. + * * @param workspace The active workspace. */ openToolboxOrFlyout(workspace: Blockly.WorkspaceSvg) { diff --git a/src/navigation_controller.ts b/src/navigation_controller.ts index 9d9d2c62..2dc62479 100644 --- a/src/navigation_controller.ts +++ b/src/navigation_controller.ts @@ -149,13 +149,17 @@ export class NavigationController { } switch (shortcut.name) { case Constants.SHORTCUT_NAMES.UP: - return (this as any).selectPrevious(); + // @ts-expect-error private method + return this.selectPrevious(); case Constants.SHORTCUT_NAMES.LEFT: - return (this as any).selectParent(); + // @ts-expect-error private method + return this.selectParent(); case Constants.SHORTCUT_NAMES.DOWN: - return (this as any).selectNext(); + // @ts-expect-error private method + return this.selectNext(); case Constants.SHORTCUT_NAMES.RIGHT: - return (this as any).selectChild(); + // @ts-expect-error private method + return this.selectChild(); default: return false; } diff --git a/src/passive_focus.ts b/src/passive_focus.ts index 17ebbff5..08719211 100644 --- a/src/passive_focus.ts +++ b/src/passive_focus.ts @@ -74,6 +74,8 @@ export class PassiveFocus { /** * Show the passive focus indicator at the specified location. * Implementation varies based on location type. + * + * @param node The node to show passive focus for. */ show(node: ASTNode) { // Hide last shown. diff --git a/src/shortcut_dialog.ts b/src/shortcut_dialog.ts index 3883640c..80078b82 100644 --- a/src/shortcut_dialog.ts +++ b/src/shortcut_dialog.ts @@ -7,7 +7,6 @@ import * as Blockly from 'blockly/core'; import * as Constants from './constants'; import {ShortcutRegistry} from 'blockly/core'; -// @ts-expect-error No types in js file import {keyCodeArrayToString, toTitleCase} from './keynames'; /** @@ -88,12 +87,13 @@ export class ShortcutDialog { } /** - * @param {string} shortcutName Shortcut name to convert. - * @returns {string} + * Munges a shortcut name into human readable text. + * + * @param shortcutName Shortcut name to convert. + * @returns A title case version of the name. */ getReadableShortcutName(shortcutName: string) { - shortcutName = toTitleCase(shortcutName.replace(/_/gi, ' ')); - return shortcutName; + return toTitleCase(shortcutName.replace(/_/gi, ' ')); } /** @@ -188,7 +188,7 @@ export class ShortcutDialog { /** * Register classes used by the shortcuts modal * Alt: plugin exports a register() function that updates the registry - **/ + */ Blockly.Css.register(` :root { --divider-border-color: #eee; diff --git a/test/blocks/p5_blocks.js b/test/blocks/p5_blocks.js index 9e2d5e1c..83654f74 100644 --- a/test/blocks/p5_blocks.js +++ b/test/blocks/p5_blocks.js @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ /** * @license * Copyright 2024 Google LLC diff --git a/test/blocks/p5_generators.js b/test/blocks/p5_generators.js index a33660b4..f642dc88 100644 --- a/test/blocks/p5_generators.js +++ b/test/blocks/p5_generators.js @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ /** * @license * Copyright 2023 Google LLC diff --git a/test/index.ts b/test/index.ts index 80784c1d..7af7ca27 100644 --- a/test/index.ts +++ b/test/index.ts @@ -6,7 +6,7 @@ import * as Blockly from 'blockly'; // Import the default blocks. -import * as libraryBlocks from 'blockly/blocks'; +import 'blockly/blocks'; import {installAllBlocks as installColourBlocks} from '@blockly/field-colour'; import {KeyboardNavigation} from '../src/index'; // @ts-expect-error No types in js file @@ -89,7 +89,10 @@ function createWorkspace(): Blockly.WorkspaceSvg { toolbox, renderer, }; - const blocklyDiv = document.getElementById('blocklyDiv')!; + const blocklyDiv = document.getElementById('blocklyDiv'); + if (!blocklyDiv) { + throw new Error('Missing blocklyDiv'); + } const workspace = Blockly.inject(blocklyDiv, injectOptions); const navigationOptions = { diff --git a/test/webdriverio/index.ts b/test/webdriverio/index.ts index c3078e2c..d9999790 100644 --- a/test/webdriverio/index.ts +++ b/test/webdriverio/index.ts @@ -6,7 +6,7 @@ import * as Blockly from 'blockly'; // Import the default blocks. -import * as libraryBlocks from 'blockly/blocks'; +import 'blockly/blocks'; import {installAllBlocks as installColourBlocks} from '@blockly/field-colour'; import {KeyboardNavigation} from '../../src/index'; // @ts-expect-error No types in js file @@ -73,7 +73,10 @@ function createWorkspace(): Blockly.WorkspaceSvg { toolbox, renderer, }; - const blocklyDiv = document.getElementById('blocklyDiv')!; + const blocklyDiv = document.getElementById('blocklyDiv'); + if (!blocklyDiv) { + throw new Error('Missing blocklyDiv'); + } const workspace = Blockly.inject(blocklyDiv, injectOptions); const navigationOptions = { @@ -105,6 +108,7 @@ document.addEventListener('DOMContentLoaded', () => { createWorkspace(); // Add Blockly to the global scope so that test code can access it to // verify state after keypresses. - // @ts-ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error window.Blockly = Blockly; }); diff --git a/test/webdriverio/test/test_setup.mjs b/test/webdriverio/test/test_setup.mjs index f3b54c20..b4c59f40 100644 --- a/test/webdriverio/test/test_setup.mjs +++ b/test/webdriverio/test/test_setup.mjs @@ -40,7 +40,7 @@ export const PAUSE_TIME = 50; /** * Start up the test page. This should only be done once, to avoid * constantly popping browser windows open and closed. - * @return A Promise that resolves to a webdriverIO browser that tests can manipulate. + * @returns {Promise} A Promise that resolves to a webdriverIO browser that tests can manipulate. */ export async function driverSetup() { const options = { @@ -90,7 +90,7 @@ export async function driverTeardown() { * Navigate to the correct URL for the test, using the shared driver. * @param {string} playgroundUrl The URL to open for the test, which should be * a Blockly playground with a workspace. - * @return A Promsie that resolves to a webdriverIO browser that tests can manipulate. + * @returns {Promise} A Promsie that resolves to a webdriverIO browser that tests can manipulate. */ export async function testSetup(playgroundUrl) { if (!driver) { @@ -106,11 +106,11 @@ export async function testSetup(playgroundUrl) { /** * Replaces OS-specific path with POSIX style path. + * * Simplified implementation based on * https://stackoverflow.com/a/63251716/4969945 - * * @param {string} target target path - * @return {string} posix path + * @returns {string} posix path */ function posixPath(target) { const result = target.split(path.sep).join(path.posix.sep); From 07cdf4874c48dde32ee853854e0bf0eec6c2394d Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 28 Mar 2025 12:09:05 +0000 Subject: [PATCH 2/5] chrore: suppress lint errors for createXXXNode --- src/actions/clipboard.ts | 1 + src/actions/disconnect.ts | 1 + src/actions/enter.ts | 2 ++ src/actions/ws_movement.ts | 1 + src/line_cursor.ts | 1 + src/navigation.ts | 12 ++++++++++-- 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/actions/clipboard.ts b/src/actions/clipboard.ts index ecc8d816..6088c2af 100644 --- a/src/actions/clipboard.ts +++ b/src/actions/clipboard.ts @@ -379,6 +379,7 @@ export class Clipboard { this.navigation.tryToConnectNodes( pasteWorkspace, targetNode, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion ASTNode.createBlockNode(block)!, ); } diff --git a/src/actions/disconnect.ts b/src/actions/disconnect.ts index e3451e12..438372aa 100644 --- a/src/actions/disconnect.ts +++ b/src/actions/disconnect.ts @@ -145,6 +145,7 @@ export class DisconnectAction { rootBlock.bringToFront(); if (wasVisitingConnection) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const connectionNode = ASTNode.createConnectionNode(superiorConnection)!; workspace.getCursor()?.setCurNode(connectionNode); } diff --git a/src/actions/enter.ts b/src/actions/enter.ts index 046f0292..6d158d56 100644 --- a/src/actions/enter.ts +++ b/src/actions/enter.ts @@ -134,6 +134,7 @@ export class EnterAction { !this.navigation.tryToConnectNodes( workspace, stationaryNode, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion ASTNode.createBlockNode(newBlock)!, ) ) { @@ -144,6 +145,7 @@ export class EnterAction { } this.navigation.focusWorkspace(workspace); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock)!); } diff --git a/src/actions/ws_movement.ts b/src/actions/ws_movement.ts index 369d8c4c..1660db4a 100644 --- a/src/actions/ws_movement.ts +++ b/src/actions/ws_movement.ts @@ -115,6 +115,7 @@ export class WorkspaceMovement { const newY = yDirection * WS_MOVE_DISTANCE + wsCoord.y; cursor.setCurNode( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion ASTNode.createWorkspaceNode( workspace, new BlocklyUtils.Coordinate(newX, newY), diff --git a/src/line_cursor.ts b/src/line_cursor.ts index afef6f30..2f4eeb75 100644 --- a/src/line_cursor.ts +++ b/src/line_cursor.ts @@ -744,6 +744,7 @@ export class LineCursor extends Marker { block = block.getParent(); } if (block) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.setCurNode(Blockly.ASTNode.createBlockNode(block)!, true); } } diff --git a/src/navigation.ts b/src/navigation.ts index 82264bfd..7d63016c 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -320,6 +320,7 @@ export class Navigation { const curNode = cursor.getCurNode(); const block = curNode ? curNode.getSourceBlock() : null; if (block && block.id === mutatedBlockId) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion cursor.setCurNode(Blockly.ASTNode.createBlockNode(block)!); } } @@ -347,6 +348,7 @@ export class Navigation { if (sourceBlock.id === deletedBlockId || ids.includes(sourceBlock.id)) { cursor.setCurNode( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion Blockly.ASTNode.createWorkspaceNode( workspace, WS_COORDINATE_ON_DELETE, @@ -372,6 +374,7 @@ export class Navigation { const curNodeBlock = block.isShadow() ? block : block.getParent(); if (curNodeBlock) { this.getFlyoutCursor(mainWorkspace)?.setCurNode( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion Blockly.ASTNode.createStackNode(curNodeBlock)!, ); } @@ -600,12 +603,14 @@ export class Navigation { if (!defaultFlyoutItem) return; const defaultFlyoutItemElement = defaultFlyoutItem.getElement(); if (defaultFlyoutItemElement instanceof Blockly.FlyoutButton) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const astNode = Blockly.ASTNode.createButtonNode( defaultFlyoutItemElement as Blockly.FlyoutButton, )!; flyoutCursor.setCurNode(astNode); return true; } else if (defaultFlyoutItemElement instanceof Blockly.BlockSvg) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const astNode = Blockly.ASTNode.createStackNode( defaultFlyoutItemElement as Blockly.BlockSvg, )!; @@ -647,16 +652,18 @@ export class Navigation { ); if (topBlocks.length > 0) { cursor.setCurNode( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion Blockly.ASTNode.createTopNode( topBlocks[prefer === 'first' ? 0 : topBlocks.length - 1], )!, ); } else { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const wsNode = Blockly.ASTNode.createWorkspaceNode( workspace, wsCoordinates, - ); - cursor.setCurNode(wsNode!); + )!; + cursor.setCurNode(wsNode); } return true; } @@ -1150,6 +1157,7 @@ export class Navigation { this.tryToConnectNodes( workspace, targetNode, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion Blockly.ASTNode.createBlockNode(block)!, ); } From 42e8cd7b56d802b063cdd613ddb91904e28fc15b Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 28 Mar 2025 12:17:23 +0000 Subject: [PATCH 3/5] chore: add CI that runs the regular build and lint test would be nice too but to do separately --- .github/workflows/build.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..80063ce2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,22 @@ +# Build workflow +name: Build + +on: [pull_request, workflow_dispatch] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: latest + cache: 'npm' + - run: npm ci + - run: npm run build + - run: npm run lint From eaff1ae050be7b32152f31096171504c7e688cf5 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 28 Mar 2025 13:33:48 +0000 Subject: [PATCH 4/5] chore: remove unneeded non-null assertions Now `setCurNode` can take null we don't need them. --- src/actions/disconnect.ts | 3 +-- src/actions/enter.ts | 3 +-- src/actions/ws_movement.ts | 3 +-- src/flyout_cursor.ts | 5 +++-- src/line_cursor.ts | 3 +-- src/navigation.ts | 24 +++++++----------------- 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/actions/disconnect.ts b/src/actions/disconnect.ts index 438372aa..bbf91a7d 100644 --- a/src/actions/disconnect.ts +++ b/src/actions/disconnect.ts @@ -145,8 +145,7 @@ export class DisconnectAction { rootBlock.bringToFront(); if (wasVisitingConnection) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const connectionNode = ASTNode.createConnectionNode(superiorConnection)!; + const connectionNode = ASTNode.createConnectionNode(superiorConnection); workspace.getCursor()?.setCurNode(connectionNode); } } diff --git a/src/actions/enter.ts b/src/actions/enter.ts index 6d158d56..9071fa54 100644 --- a/src/actions/enter.ts +++ b/src/actions/enter.ts @@ -145,8 +145,7 @@ export class EnterAction { } this.navigation.focusWorkspace(workspace); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock)!); + workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock)); } /** diff --git a/src/actions/ws_movement.ts b/src/actions/ws_movement.ts index 1660db4a..048a0b90 100644 --- a/src/actions/ws_movement.ts +++ b/src/actions/ws_movement.ts @@ -115,11 +115,10 @@ export class WorkspaceMovement { const newY = yDirection * WS_MOVE_DISTANCE + wsCoord.y; cursor.setCurNode( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion ASTNode.createWorkspaceNode( workspace, new BlocklyUtils.Coordinate(newX, newY), - )!, + ), ); return true; } diff --git a/src/flyout_cursor.ts b/src/flyout_cursor.ts index fb0c73fe..1ca7bbb1 100644 --- a/src/flyout_cursor.ts +++ b/src/flyout_cursor.ts @@ -83,12 +83,13 @@ export class FlyoutCursor extends Blockly.Cursor { return null; } - override setCurNode(node: Blockly.ASTNode) { + override setCurNode(node: Blockly.ASTNode | null) { super.setCurNode(node); - const location = node.getLocation(); + const location = node?.getLocation(); let bounds: Blockly.utils.Rect | undefined; if ( + location && 'getBoundingRectangle' in location && typeof location.getBoundingRectangle === 'function' ) { diff --git a/src/line_cursor.ts b/src/line_cursor.ts index 2f4eeb75..b810ddf9 100644 --- a/src/line_cursor.ts +++ b/src/line_cursor.ts @@ -744,8 +744,7 @@ export class LineCursor extends Marker { block = block.getParent(); } if (block) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.setCurNode(Blockly.ASTNode.createBlockNode(block)!, true); + this.setCurNode(Blockly.ASTNode.createBlockNode(block), true); } } } diff --git a/src/navigation.ts b/src/navigation.ts index 7d63016c..29cf84cb 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -320,8 +320,7 @@ export class Navigation { const curNode = cursor.getCurNode(); const block = curNode ? curNode.getSourceBlock() : null; if (block && block.id === mutatedBlockId) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - cursor.setCurNode(Blockly.ASTNode.createBlockNode(block)!); + cursor.setCurNode(Blockly.ASTNode.createBlockNode(block)); } } } @@ -348,11 +347,7 @@ export class Navigation { if (sourceBlock.id === deletedBlockId || ids.includes(sourceBlock.id)) { cursor.setCurNode( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - Blockly.ASTNode.createWorkspaceNode( - workspace, - WS_COORDINATE_ON_DELETE, - )!, + Blockly.ASTNode.createWorkspaceNode(workspace, WS_COORDINATE_ON_DELETE), ); } } @@ -374,8 +369,7 @@ export class Navigation { const curNodeBlock = block.isShadow() ? block : block.getParent(); if (curNodeBlock) { this.getFlyoutCursor(mainWorkspace)?.setCurNode( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - Blockly.ASTNode.createStackNode(curNodeBlock)!, + Blockly.ASTNode.createStackNode(curNodeBlock), ); } this.focusFlyout(mainWorkspace); @@ -603,17 +597,15 @@ export class Navigation { if (!defaultFlyoutItem) return; const defaultFlyoutItemElement = defaultFlyoutItem.getElement(); if (defaultFlyoutItemElement instanceof Blockly.FlyoutButton) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const astNode = Blockly.ASTNode.createButtonNode( defaultFlyoutItemElement as Blockly.FlyoutButton, - )!; + ); flyoutCursor.setCurNode(astNode); return true; } else if (defaultFlyoutItemElement instanceof Blockly.BlockSvg) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const astNode = Blockly.ASTNode.createStackNode( defaultFlyoutItemElement as Blockly.BlockSvg, - )!; + ); flyoutCursor.setCurNode(astNode); return true; } @@ -652,17 +644,15 @@ export class Navigation { ); if (topBlocks.length > 0) { cursor.setCurNode( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion Blockly.ASTNode.createTopNode( topBlocks[prefer === 'first' ? 0 : topBlocks.length - 1], - )!, + ), ); } else { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const wsNode = Blockly.ASTNode.createWorkspaceNode( workspace, wsCoordinates, - )!; + ); cursor.setCurNode(wsNode); } return true; From a264d3533a4b90b3421049aad974a7dedaec302e Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 28 Mar 2025 18:38:35 +0000 Subject: [PATCH 5/5] fix: use parameter not hardcoded value --- src/actions/action_menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/action_menu.ts b/src/actions/action_menu.ts index d70d4299..349da56f 100644 --- a/src/actions/action_menu.ts +++ b/src/actions/action_menu.ts @@ -219,7 +219,7 @@ export class ActionMenu { * @returns the action. */ private getContextMenuAction(id: string) { - const item = ContextMenuRegistry.registry.getItem('insert'); + const item = ContextMenuRegistry.registry.getItem(id); if (!item) { throw new Error(`can't find context menu item ${id}`); }