Skip to content

Commit 0ffa239

Browse files
chore: fix lint errors and run lint in CI (#348)
* chore: fix lint errors except non-null createXXXNode * chrore: suppress lint errors for createXXXNode * chore: add CI that runs the regular build and lint test would be nice too but to do separately * chore: remove unneeded non-null assertions Now `setCurNode` can take null we don't need them. * fix: use parameter not hardcoded value
1 parent 817e18d commit 0ffa239

25 files changed

+262
-188
lines changed

.github/workflows/build.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Build workflow
2+
name: Build
3+
4+
on: [pull_request, workflow_dispatch]
5+
6+
permissions:
7+
contents: read
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
- name: Setup Node
16+
uses: actions/setup-node@v4
17+
with:
18+
node-version: latest
19+
cache: 'npm'
20+
- run: npm ci
21+
- run: npm run build
22+
- run: npm run lint

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ module.exports = [
129129
},
130130
},
131131
{
132-
files: ['**/*.mocha.js'],
132+
files: ['**/*.mocha.js', 'test/webdriverio/test/*_test.mjs'],
133133
languageOptions: {
134134
globals: {
135135
...globals.mocha,

src/actions/action_menu.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
ContextMenu,
1111
ContextMenuRegistry,
1212
ShortcutRegistry,
13-
comments,
1413
utils as BlocklyUtils,
1514
WidgetDiv,
1615
} from 'blockly';
@@ -23,10 +22,7 @@ const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind(
2322
ShortcutRegistry.registry,
2423
);
2524

26-
export interface Scope {
27-
block?: BlockSvg;
28-
workspace?: WorkspaceSvg;
29-
comment?: comments.RenderedWorkspaceComment;
25+
export interface ScopeWithConnection extends ContextMenuRegistry.Scope {
3026
connection?: Connection;
3127
}
3228

@@ -100,6 +96,8 @@ export class ActionMenu {
10096
* Returns true if it is possible to open the action menu in the
10197
* current location, even if the menu was not opened due there being
10298
* no applicable menu items.
99+
*
100+
* @param workspace The workspace.
103101
*/
104102
private openActionMenu(workspace: WorkspaceSvg): boolean {
105103
let menuOptions: Array<
@@ -114,7 +112,7 @@ export class ActionMenu {
114112
if (!node) return false;
115113
const nodeType = node.getType();
116114
switch (nodeType) {
117-
case ASTNode.types.BLOCK:
115+
case ASTNode.types.BLOCK: {
118116
const block = node.getLocation() as BlockSvg;
119117
rtl = block.RTL;
120118
// Reimplement BlockSvg.prototype.generateContextMenu as that
@@ -130,11 +128,12 @@ export class ActionMenu {
130128
}
131129
// End reimplement.
132130
break;
131+
}
133132

134133
// case Blockly.ASTNode.types.INPUT:
135134
case ASTNode.types.NEXT:
136135
case ASTNode.types.PREVIOUS:
137-
case ASTNode.types.INPUT:
136+
case ASTNode.types.INPUT: {
138137
const connection = node.getLocation() as Connection;
139138
rtl = connection.getSourceBlock().RTL;
140139

@@ -143,6 +142,7 @@ export class ActionMenu {
143142
// a possible kind of scope.
144143
this.addConnectionItems(connection, menuOptions);
145144
break;
145+
}
146146

147147
default:
148148
console.info(`No action menu for ASTNode of type ${nodeType}`);
@@ -177,24 +177,21 @@ export class ActionMenu {
177177
*/
178178
private addConnectionItems(
179179
connection: Connection,
180-
menuOptions: (
180+
menuOptions: Array<
181181
| ContextMenuRegistry.ContextMenuOption
182182
| ContextMenuRegistry.LegacyContextMenuOption
183-
)[],
183+
>,
184184
) {
185-
const insertAction = ContextMenuRegistry.registry.getItem('insert');
186-
if (!insertAction) throw new Error("can't find insert action");
187-
188-
const pasteAction = ContextMenuRegistry.registry.getItem(
189-
'blockPasteFromContextMenu',
190-
);
191-
if (!pasteAction) throw new Error("can't find paste action");
192-
const possibleOptions = [insertAction, pasteAction /* etc.*/];
185+
const possibleOptions = [
186+
this.getContextMenuAction('insert'),
187+
this.getContextMenuAction('blockPasteFromContextMenu'),
188+
];
193189

194190
// Check preconditions and get menu texts.
195191
const scope = {
196192
connection,
197193
} as unknown as ContextMenuRegistry.Scope;
194+
198195
for (const option of possibleOptions) {
199196
const precondition = option.preconditionFn?.(scope);
200197
if (precondition === 'hidden') continue;
@@ -205,14 +202,33 @@ export class ActionMenu {
205202
menuOptions.push({
206203
text: displayText,
207204
enabled: precondition === 'enabled',
208-
callback: option.callback!,
205+
callback: option.callback,
209206
scope,
210207
weight: option.weight,
211208
});
212209
}
213210
return menuOptions;
214211
}
215212

213+
/**
214+
* Find a context menu action, throwing an `Error` if it is not present or
215+
* not an action. This usefully narrows the type to `ActionRegistryItem`
216+
* which is not exported from Blockly.
217+
*
218+
* @param id The id of the action.
219+
* @returns the action.
220+
*/
221+
private getContextMenuAction(id: string) {
222+
const item = ContextMenuRegistry.registry.getItem(id);
223+
if (!item) {
224+
throw new Error(`can't find context menu item ${id}`);
225+
}
226+
if (!item?.callback) {
227+
throw new Error(`context menu item unexpectedly not action ${id}`);
228+
}
229+
return item;
230+
}
231+
216232
/**
217233
* Create a fake PointerEvent for opening the action menu for the
218234
* given ASTNode.

src/actions/arrow_navigation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class ArrowNavigation {
5757
right: {
5858
name: Constants.SHORTCUT_NAMES.RIGHT,
5959
preconditionFn: (workspace) => this.canCurrentlyNavigate(workspace),
60-
callback: (workspace, _, shortcut) => {
60+
callback: (workspace, e, shortcut) => {
6161
const toolbox = workspace.getToolbox() as Toolbox;
6262
let isHandled = false;
6363
switch (this.navigation.getState(workspace)) {
@@ -94,7 +94,7 @@ export class ArrowNavigation {
9494
left: {
9595
name: Constants.SHORTCUT_NAMES.LEFT,
9696
preconditionFn: (workspace) => this.canCurrentlyNavigate(workspace),
97-
callback: (workspace, _, shortcut) => {
97+
callback: (workspace, e, shortcut) => {
9898
const toolbox = workspace.getToolbox() as Toolbox;
9999
let isHandled = false;
100100
switch (this.navigation.getState(workspace)) {
@@ -129,7 +129,7 @@ export class ArrowNavigation {
129129
down: {
130130
name: Constants.SHORTCUT_NAMES.DOWN,
131131
preconditionFn: (workspace) => this.canCurrentlyNavigate(workspace),
132-
callback: (workspace, _, shortcut) => {
132+
callback: (workspace, e, shortcut) => {
133133
const toolbox = workspace.getToolbox() as Toolbox;
134134
const flyout = workspace.getFlyout();
135135
let isHandled = false;
@@ -170,7 +170,7 @@ export class ArrowNavigation {
170170
up: {
171171
name: Constants.SHORTCUT_NAMES.UP,
172172
preconditionFn: (workspace) => this.canCurrentlyNavigate(workspace),
173-
callback: (workspace, _, shortcut) => {
173+
callback: (workspace, e, shortcut) => {
174174
const flyout = workspace.getFlyout();
175175
const toolbox = workspace.getToolbox() as Toolbox;
176176
let isHandled = false;

src/actions/clipboard.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as Constants from '../constants';
1818
import type {BlockSvg, WorkspaceSvg} from 'blockly';
1919
import {LineCursor} from '../line_cursor';
2020
import {Navigation} from '../navigation';
21+
import {ScopeWithConnection} from './action_menu';
2122

2223
const KeyCodes = blocklyUtils.KeyCodes;
2324
const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind(
@@ -238,21 +239,23 @@ export class Clipboard {
238239
private copyPrecondition(workspace: WorkspaceSvg) {
239240
if (!this.canCurrentlyEdit(workspace)) return false;
240241
switch (this.navigation.getState(workspace)) {
241-
case Constants.STATE.WORKSPACE:
242+
case Constants.STATE.WORKSPACE: {
242243
const curNode = workspace?.getCursor()?.getCurNode();
243244
const source = curNode?.getSourceBlock();
244245
return !!(
245246
source?.isDeletable() &&
246247
source?.isMovable() &&
247248
!Gesture.inProgress()
248249
);
249-
case Constants.STATE.FLYOUT:
250+
}
251+
case Constants.STATE.FLYOUT: {
250252
const flyoutWorkspace = workspace.getFlyout()?.getWorkspace();
251253
const sourceBlock = flyoutWorkspace
252254
?.getCursor()
253255
?.getCurNode()
254256
?.getSourceBlock();
255257
return !!(sourceBlock && !Gesture.inProgress());
258+
}
256259
default:
257260
return false;
258261
}
@@ -314,18 +317,15 @@ export class Clipboard {
314317
private registerPasteContextMenuAction() {
315318
const pasteAction: ContextMenuRegistry.RegistryItem = {
316319
displayText: (scope) => `Paste (${this.getPlatformPrefix()}V)`,
317-
preconditionFn: (scope) => {
318-
const ws =
319-
scope.block?.workspace ??
320-
(scope as any).connection?.getSourceBlock().workspace;
320+
preconditionFn: (scope: ScopeWithConnection) => {
321+
const block = scope.block ?? scope.connection?.getSourceBlock();
322+
const ws = block?.workspace as WorkspaceSvg | null;
321323
if (!ws) return 'hidden';
322-
323324
return this.pastePrecondition(ws) ? 'enabled' : 'disabled';
324325
},
325-
callback: (scope) => {
326-
const ws =
327-
scope.block?.workspace ??
328-
(scope as any).connection?.getSourceBlock().workspace;
326+
callback: (scope: ScopeWithConnection) => {
327+
const block = scope.block ?? scope.connection?.getSourceBlock();
328+
const ws = block?.workspace as WorkspaceSvg | null;
329329
if (!ws) return;
330330
return this.pasteCallback(ws);
331331
},
@@ -379,6 +379,7 @@ export class Clipboard {
379379
this.navigation.tryToConnectNodes(
380380
pasteWorkspace,
381381
targetNode,
382+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
382383
ASTNode.createBlockNode(block)!,
383384
);
384385
}

src/actions/delete.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export class DeleteAction {
113113
// Run the original precondition code, from the context menu option.
114114
// If the item would be hidden or disabled, respect it.
115115
const originalPreconditionResult =
116-
this.oldContextMenuItem!.preconditionFn?.(scope) ?? 'enabled';
116+
this.oldContextMenuItem?.preconditionFn?.(scope) ?? 'enabled';
117117
if (!ws || originalPreconditionResult !== 'enabled') {
118118
return originalPreconditionResult;
119119
}

src/actions/disconnect.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,17 @@ export class DisconnectAction {
117117
if (!curConnection.isConnected()) {
118118
return;
119119
}
120+
const targetConnection = curConnection.targetConnection;
121+
if (!targetConnection) {
122+
throw new Error('Must have target if connected');
123+
}
124+
120125
const superiorConnection = curConnection.isSuperior()
121126
? curConnection
122-
: curConnection.targetConnection!;
127+
: targetConnection;
123128

124129
const inferiorConnection = curConnection.isSuperior()
125-
? curConnection.targetConnection!
130+
? targetConnection
126131
: curConnection;
127132

128133
if (inferiorConnection.getSourceBlock().isShadow()) {
@@ -141,7 +146,7 @@ export class DisconnectAction {
141146

142147
if (wasVisitingConnection) {
143148
const connectionNode = ASTNode.createConnectionNode(superiorConnection);
144-
workspace.getCursor()!.setCurNode(connectionNode!);
149+
workspace.getCursor()?.setCurNode(connectionNode);
145150
}
146151
}
147152
}

src/actions/edit.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,9 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {
8-
Connection,
9-
ContextMenuRegistry,
10-
ShortcutRegistry,
11-
comments,
12-
utils as BlocklyUtils,
13-
} from 'blockly';
14-
import * as Constants from '../constants';
15-
import type {BlockSvg, WorkspaceSvg} from 'blockly';
7+
import {ContextMenuRegistry} from 'blockly';
8+
import type {WorkspaceSvg} from 'blockly';
169
import {LineCursor} from '../line_cursor';
17-
import {NavigationController} from '../navigation_controller';
18-
19-
const KeyCodes = BlocklyUtils.KeyCodes;
2010

2111
/**
2212
* Action to edit a block. This just moves the cursor to the first

src/actions/enter.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ export class EnterAction {
134134
!this.navigation.tryToConnectNodes(
135135
workspace,
136136
stationaryNode,
137+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
137138
ASTNode.createBlockNode(newBlock)!,
138139
)
139140
) {
@@ -144,7 +145,7 @@ export class EnterAction {
144145
}
145146

146147
this.navigation.focusWorkspace(workspace);
147-
workspace.getCursor()!.setCurNode(ASTNode.createBlockNode(newBlock)!);
148+
workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock));
148149
}
149150

150151
/**
@@ -155,17 +156,22 @@ export class EnterAction {
155156
*/
156157
private triggerButtonCallback(workspace: WorkspaceSvg) {
157158
const button = this.navigation
158-
.getFlyoutCursor(workspace)!
159-
.getCurNode()
159+
.getFlyoutCursor(workspace)
160+
?.getCurNode()
160161
?.getLocation() as FlyoutButton | undefined;
161162
if (!button) return;
162-
const buttonCallback = (workspace as any).flyoutButtonCallbacks.get(
163-
(button as any).callbackKey,
164-
);
165-
if (typeof buttonCallback === 'function') {
163+
164+
const flyoutButtonCallbacks: Map<string, (p1: FlyoutButton) => void> =
165+
// @ts-expect-error private field access
166+
workspace.flyoutButtonCallbacks;
167+
168+
const info = button.info;
169+
if ('callbackkey' in info) {
170+
const buttonCallback = flyoutButtonCallbacks.get(info.callbackkey);
171+
if (!buttonCallback) {
172+
throw new Error('No callback function found for flyout button.');
173+
}
166174
buttonCallback(button);
167-
} else if (!button.isLabel()) {
168-
throw new Error('No callback function found for flyout button.');
169175
}
170176
}
171177

@@ -208,8 +214,8 @@ export class EnterAction {
208214
}
209215

210216
const curBlock = this.navigation
211-
.getFlyoutCursor(workspace)!
212-
.getCurNode()
217+
.getFlyoutCursor(workspace)
218+
?.getCurNode()
213219
?.getLocation() as BlockSvg | undefined;
214220
if (!curBlock?.isEnabled()) {
215221
console.warn("Can't insert a disabled block.");

0 commit comments

Comments
 (0)