From a4d0b679852cc5b95a0ffa73187acae255ae57fc Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Tue, 18 Apr 2023 15:58:21 -0700 Subject: [PATCH] refactor(blocks): Migrate `blocks/loops.js` to TypeScript (#6957) * refactor(blocks): Auto-migration of blocks/loops.js to ts * fix(blocks): Manually migrate types and fix imports in loops.ts * chore: respond to PR comments --- blocks/{loops.js => loops.ts} | 116 ++++++++++++++++------------------ 1 file changed, 53 insertions(+), 63 deletions(-) rename blocks/{loops.js => loops.ts} (77%) diff --git a/blocks/loops.js b/blocks/loops.ts similarity index 77% rename from blocks/loops.js rename to blocks/loops.ts index 220c17d67..605128c64 100644 --- a/blocks/loops.js +++ b/blocks/loops.ts @@ -6,44 +6,33 @@ /** * @fileoverview Loop blocks for Blockly. - * @suppress {checkTypes} */ -'use strict'; -goog.module('Blockly.libraryBlocks.loops'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.loops'); -/* eslint-disable-next-line no-unused-vars */ -const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); -const ContextMenu = goog.require('Blockly.ContextMenu'); -const Events = goog.require('Blockly.Events'); -const Extensions = goog.require('Blockly.Extensions'); -const Variables = goog.require('Blockly.Variables'); -const xmlUtils = goog.require('Blockly.utils.xml'); -/* eslint-disable-next-line no-unused-vars */ -const {Block} = goog.requireType('Blockly.Block'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; -const {Msg} = goog.require('Blockly.Msg'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldDropdown'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldLabel'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldNumber'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldVariable'); -/** @suppress {extraRequire} */ -goog.require('Blockly.Warning'); +import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js'; +import type {Block} from '../core/block.js'; +import * as ContextMenu from '../core/contextmenu.js'; +import * as Events from '../core/events/events.js'; +import * as Extensions from '../core/extensions.js'; +import * as Variables from '../core/variables.js'; +import * as xmlUtils from '../core/utils/xml.js'; +import {Msg} from '../core/msg.js'; +import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js'; +import '../core/field_dropdown.js'; +import '../core/field_label.js'; +import '../core/field_number.js'; +import '../core/field_variable.js'; +import '../core/warning.js'; +import {FieldVariable} from '../core/field_variable.js'; +import {WorkspaceSvg} from '../core/workspace_svg.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for repeat n times (external number). { 'type': 'controls_repeat_ext', @@ -213,12 +202,11 @@ const blocks = createBlockDefinitionsFromJsonArray([ ], }, ]); -exports.blocks = blocks; /** * Tooltips for the 'controls_whileUntil' block, keyed by MODE value. + * * @see {Extensions#buildTooltipForDropdown} - * @readonly */ const WHILE_UNTIL_TOOLTIPS = { 'WHILE': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}', @@ -231,8 +219,8 @@ Extensions.register( /** * Tooltips for the 'controls_flow_statements' block, keyed by FLOW value. + * * @see {Extensions#buildTooltipForDropdown} - * @readonly */ const BREAK_CONTINUE_TOOLTIPS = { 'BREAK': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}', @@ -243,36 +231,42 @@ Extensions.register( 'controls_flow_tooltip', Extensions.buildTooltipForDropdown('FLOW', BREAK_CONTINUE_TOOLTIPS)); +/** Type of a block that has CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN */ +type CustomContextMenuBlock = Block&CustomContextMenuMixin; +interface CustomContextMenuMixin extends CustomContextMenuMixinType {} +type CustomContextMenuMixinType = + typeof CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN; + /** * Mixin to add a context menu item to create a 'variables_get' block. * Used by blocks 'controls_for' and 'controls_forEach'. - * @mixin - * @augments Block - * @package - * @readonly */ const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = { /** * Add context menu option to create getter block for the loop's variable. * (customContextMenu support limited to web BlockSvg.) - * @param {!Array} options List of menu options to add to. - * @this {Block} + * + * @param options List of menu options to add to. */ - customContextMenu: function(options) { + customContextMenu: function( + this: CustomContextMenuBlock, options: Array) { if (this.isInFlyout) { return; } - const variable = this.getField('VAR').getVariable(); + const varField = this.getField('VAR') as FieldVariable; + const variable = varField.getVariable()!; const varName = variable.name; if (!this.isCollapsed() && varName !== null) { - const option = {enabled: true}; - option.text = Msg['VARIABLES_SET_CREATE_GET'].replace('%1', varName); const xmlField = Variables.generateVariableFieldDom(variable); const xmlBlock = xmlUtils.createElement('block'); xmlBlock.setAttribute('type', 'variables_get'); xmlBlock.appendChild(xmlField); - option.callback = ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); + + options.push({ + enabled: true, + text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', varName), + callback: ContextMenu.callbackFactory(this, xmlBlock) + }); } }, }; @@ -304,34 +298,32 @@ Extensions.register( * * // Else if using blockly_compressed + blockss_compressed.js in browser: * Blockly.libraryBlocks.loopTypes.add('custom_loop'); - * - * @type {!Set} */ -const loopTypes = new Set([ +export const loopTypes: Set = new Set([ 'controls_repeat', 'controls_repeat_ext', 'controls_forEach', 'controls_for', 'controls_whileUntil', ]); -exports.loopTypes = loopTypes; + +/** Type of a block that has CONTROL_FLOW_IN_LOOP_CHECK_MIXIN */ +type ControlFlowInLoopBlock = Block&ControlFlowInLoopMixin; +interface ControlFlowInLoopMixin extends ControlFlowInLoopMixinType {} +type ControlFlowInLoopMixinType = typeof CONTROL_FLOW_IN_LOOP_CHECK_MIXIN; /** * This mixin adds a check to make sure the 'controls_flow_statements' block * is contained in a loop. Otherwise a warning is added to the block. - * @mixin - * @augments Block - * @public - * @readonly */ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { /** * Is this block enclosed (at any level) by a loop? - * @return {Block} The nearest surrounding loop, or null if none. - * @this {Block} + * + * @returns The nearest surrounding loop, or null if none. */ - getSurroundLoop: function() { - let block = this; + getSurroundLoop: function(this: ControlFlowInLoopBlock): Block | null { + let block: Block|null = this; do { if (loopTypes.has(block.type)) { return block; @@ -344,18 +336,16 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { /** * Called whenever anything on the workspace changes. * Add warning if this flow block is not nested inside a loop. - * @param {!AbstractEvent} e Move event. - * @this {Block} */ - onchange: function(e) { + onchange: function(this: ControlFlowInLoopBlock, e: AbstractEvent) { + const ws = this.workspace as WorkspaceSvg; // Don't change state if: // * It's at the start of a drag. // * It's not a move event. - if (!this.workspace.isDragging || this.workspace.isDragging() || - e.type !== Events.BLOCK_MOVE) { + if (!ws.isDragging || ws.isDragging() || e.type !== Events.BLOCK_MOVE) { return; } - const enabled = this.getSurroundLoop(this); + const enabled = !!this.getSurroundLoop(); this.setWarningText( enabled ? null : Msg['CONTROLS_FLOW_STATEMENTS_WARNING']); if (!this.isInFlyout) {