refactor(blocks): Migrate blocks/math.js to TypeScript (#6900)

* refactor(blocks): Auto-migration of blocks/math.js to ts

  This is just the result of running js2ts on this file.

* fix(blocks): Manually migrate & fix types in math.ts

* chore(blocks): clang-format math.ts

* fix(blocks): Corrections for comments on PR #6900

* refactor(blocks): Define types for mixin-ed blocks, etc.

  Define types to represent the union of Block and each of the
  *_MIXINs, and use these types for the type of this in mixin
  methods.

* refactor(blocks): Misc minor changes

  Make sure validator functions explicitly return undefined.
  Field.prototype.setValidator takes a FieldValidator<T>, which
  must be a non-void function.  I'm not sure why tsc was not
  objecting to the void implementation here, but it does in
  other very similar situations so for consistency explicitly
  return undefined to signal the value should be used unchanged.

  Also undo previous change of !. to ?.: I think it wisest to
  try to preserve the existing behaviour as exactly as possible
  for now, and make behaviour changes (i.e., ones that affect
  the generated code) separately.

* fix(blocks): Fix erroneous typing of mixins

  It turns out that the previous types for these were completely
  wrong, for two reasons:

  - They should be intersection types (which have the union of the
    properties), not union types (which have the interseciton of the
    properties).
  - The *_MIXIN types were already declared as having type
    BlockDefinition, which is ultimately an alias for any.

  TypeScript doesn't like (some) kinds of circularly defined types,
  so fixing the above necessitates declaring a few auxiliary types
  just to make the type checker happy - but the end result is
  excellent and caught an actual type error in the code.
This commit is contained in:
Christopher Allen
2023-03-17 20:24:09 +00:00
committed by GitHub
parent a7c342ec29
commit bb9f31853d

View File

@@ -6,37 +6,28 @@
/**
* @fileoverview Math blocks for Blockly.
* @suppress {checkTypes}
*/
'use strict';
goog.module('Blockly.libraryBlocks.math');
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.libraryBlocks.math');
const Extensions = goog.require('Blockly.Extensions');
// N.B.: Blockly.FieldDropdown needed for type AND side-effects.
/* eslint-disable-next-line no-unused-vars */
const FieldDropdown = goog.require('Blockly.FieldDropdown');
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 {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
/** @suppress {extraRequire} */
goog.require('Blockly.FieldLabel');
/** @suppress {extraRequire} */
goog.require('Blockly.FieldNumber');
/** @suppress {extraRequire} */
goog.require('Blockly.FieldVariable');
import * as Extensions from '../core/extensions.js';
import type {Field} from '../core/field.js';
import type {FieldDropdown} from '../core/field_dropdown.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Block} from '../core/block.js';
import type {BlockDefinition} from '../core/blocks.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';
/**
* A dictionary of the block definitions provided by this module.
* @type {!Object<string, !BlockDefinition>}
*/
const blocks = createBlockDefinitionsFromJsonArray([
export const blocks = createBlockDefinitionsFromJsonArray([
// Block for numeric value.
{
'type': 'math_number',
@@ -391,11 +382,11 @@ const blocks = createBlockDefinitionsFromJsonArray([
'helpUrl': '%{BKY_MATH_ATAN2_HELPURL}',
},
]);
exports.blocks = blocks;
/**
* Mapping of math block OP value to tooltip message for blocks
* math_arithmetic, math_simple, math_trig, and math_on_lists.
*
* @see {Extensions#buildTooltipForDropdown}
* @package
* @readonly
@@ -440,10 +431,15 @@ Extensions.register(
'math_op_tooltip',
Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP));
/** Type of a block that has IS_DIVISBLEBY_MUTATOR_MIXIN */
type DivisiblebyBlock = Block&DivisiblebyMixin;
interface DivisiblebyMixin extends DivisiblebyMixinType {};
type DivisiblebyMixinType = typeof IS_DIVISIBLEBY_MUTATOR_MIXIN;
/**
* Mixin for mutator functions in the 'math_is_divisibleby_mutator'
* extension.
*
* @mixin
* @augments Block
* @package
@@ -452,22 +448,22 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
/**
* Create XML to represent whether the 'divisorInput' should be present.
* Backwards compatible serialization implementation.
* @return {!Element} XML storage element.
* @this {Block}
*
* @returns XML storage element.
*/
mutationToDom: function() {
mutationToDom: function(this: DivisiblebyBlock): Element {
const container = xmlUtils.createElement('mutation');
const divisorInput = (this.getFieldValue('PROPERTY') === 'DIVISIBLE_BY');
container.setAttribute('divisor_input', divisorInput);
container.setAttribute('divisor_input', String(divisorInput));
return container;
},
/**
* Parse XML to restore the 'divisorInput'.
* Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element.
* @this {Block}
*
* @param xmlElement XML storage element.
*/
domToMutation: function(xmlElement) {
domToMutation: function(this: DivisiblebyBlock, xmlElement: Element) {
const divisorInput = (xmlElement.getAttribute('divisor_input') === 'true');
this.updateShape_(divisorInput);
},
@@ -479,11 +475,10 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
/**
* Modify this block to have (or not have) an input for 'is divisible by'.
* @param {boolean} divisorInput True if this block has a divisor input.
* @private
* @this {Block}
*
* @param divisorInput True if this block has a divisor input.
*/
updateShape_: function(divisorInput) {
updateShape_: function(this: DivisiblebyBlock, divisorInput: boolean) {
// Add or remove a Value Input.
const inputExists = this.getInput('DIVISOR');
if (divisorInput) {
@@ -500,20 +495,15 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
* 'math_is_divisibleby_mutator' extension to the 'math_property' block that
* can update the block shape (add/remove divisor input) based on whether
* property is "divisible by".
* @this {Block}
* @package
*/
const IS_DIVISIBLE_MUTATOR_EXTENSION = function() {
this.getField('PROPERTY')
.setValidator(
/**
* @this {FieldDropdown}
* @param {*} option The selected dropdown option.
*/
function(option) {
const divisorInput = (option === 'DIVISIBLE_BY');
this.getSourceBlock().updateShape_(divisorInput);
});
const IS_DIVISIBLE_MUTATOR_EXTENSION = function(this: DivisiblebyBlock) {
this.getField('PROPERTY')!.setValidator(
/** @param option The selected dropdown option. */
function(this: FieldDropdown, option: string) {
const divisorInput = (option === 'DIVISIBLE_BY');
(this.getSourceBlock() as DivisiblebyBlock).updateShape_(divisorInput);
return undefined; // FieldValidators can't be void. Use option as-is.
});
};
Extensions.registerMutator(
@@ -525,35 +515,35 @@ Extensions.register(
'math_change_tooltip',
Extensions.buildTooltipWithFieldText('%{BKY_MATH_CHANGE_TOOLTIP}', 'VAR'));
/** Type of a block that has LIST_MODES_MUTATOR_MIXIN */
type ListModesBlock = Block&ListModesMixin;
interface ListModesMixin extends ListModesMixinType {};
type ListModesMixinType = typeof LIST_MODES_MUTATOR_MIXIN;
/**
* Mixin with mutator methods to support alternate output based if the
* 'math_on_list' block uses the 'MODE' operation.
* @mixin
* @augments Block
* @package
* @readonly
*/
const LIST_MODES_MUTATOR_MIXIN = {
/**
* Modify this block to have the correct output type.
* @param {string} newOp Either 'MODE' or some op than returns a number.
* @private
* @this {Block}
*
* @param newOp Either 'MODE' or some op than returns a number.
*/
updateType_: function(newOp) {
updateType_: function(this: ListModesBlock, newOp: string) {
if (newOp === 'MODE') {
this.outputConnection.setCheck('Array');
this.outputConnection!.setCheck('Array');
} else {
this.outputConnection.setCheck('Number');
this.outputConnection!.setCheck('Number');
}
},
/**
* Create XML to represent the output type.
* Backwards compatible serialization implementation.
* @return {!Element} XML storage element.
* @this {Block}
*
* @returns XML storage element.
*/
mutationToDom: function() {
mutationToDom: function(this: ListModesBlock): Element {
const container = xmlUtils.createElement('mutation');
container.setAttribute('op', this.getFieldValue('OP'));
return container;
@@ -561,11 +551,13 @@ const LIST_MODES_MUTATOR_MIXIN = {
/**
* Parse XML to restore the output type.
* Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element.
* @this {Block}
*
* @param xmlElement XML storage element.
*/
domToMutation: function(xmlElement) {
this.updateType_(xmlElement.getAttribute('op'));
domToMutation: function(this: ListModesBlock, xmlElement: Element) {
const op = xmlElement.getAttribute('op');
if (op === null) throw new TypeError('xmlElement had no op attribute');
this.updateType_(op);
},
// This block does not need JSO serialization hooks (saveExtraState and
@@ -577,13 +569,13 @@ const LIST_MODES_MUTATOR_MIXIN = {
/**
* Extension to 'math_on_list' blocks that allows support of
* modes operation (outputs a list of numbers).
* @this {Block}
* @package
*/
const LIST_MODES_MUTATOR_EXTENSION = function() {
this.getField('OP').setValidator(function(newOp) {
this.updateType_(newOp);
}.bind(this));
const LIST_MODES_MUTATOR_EXTENSION = function(this: ListModesBlock) {
this.getField('OP')!.setValidator(
function(this: ListModesBlock, newOp: string) {
this.updateType_(newOp);
return undefined;
}.bind(this));
};
Extensions.registerMutator(