Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(generators): Migrate JavaScript generators to TypeScript #7602

Merged
merged 8 commits into from
Oct 30, 2023
8 changes: 6 additions & 2 deletions blocks/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,12 @@ export const blocks = createBlockDefinitionsFromJsonArray([
},
]);

/** Type of a 'lists_create_with' block. */
type CreateWithBlock = Block & ListCreateWithMixin;
/**
* Type of a 'lists_create_with' block.
*
* @internal
*/
export type CreateWithBlock = Block & ListCreateWithMixin;
interface ListCreateWithMixin extends ListCreateWithMixinType {
itemCount_: number;
}
Expand Down
8 changes: 6 additions & 2 deletions blocks/loops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,12 @@ export const loopTypes: Set<string> = new Set([
'controls_whileUntil',
]);

/** Type of a block that has CONTROL_FLOW_IN_LOOP_CHECK_MIXIN */
type ControlFlowInLoopBlock = Block & ControlFlowInLoopMixin;
/**
* Type of a block that has CONTROL_FLOW_IN_LOOP_CHECK_MIXIN
*
* @internal
BeksOmega marked this conversation as resolved.
Show resolved Hide resolved
*/
export type ControlFlowInLoopBlock = Block & ControlFlowInLoopMixin;
interface ControlFlowInLoopMixin extends ControlFlowInLoopMixinType {}
type ControlFlowInLoopMixinType = typeof CONTROL_FLOW_IN_LOOP_CHECK_MIXIN;

Expand Down
8 changes: 6 additions & 2 deletions blocks/procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1209,8 +1209,12 @@ blocks['procedures_callreturn'] = {
defType_: 'procedures_defreturn',
};

/** Type of a procedures_ifreturn block. */
type IfReturnBlock = Block & IfReturnMixin;
/**
* Type of a procedures_ifreturn block.
*
* @internal
*/
export type IfReturnBlock = Block & IfReturnMixin;
interface IfReturnMixin extends IfReturnMixinType {
hasReturnValue_: boolean;
}
Expand Down
8 changes: 6 additions & 2 deletions blocks/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,8 +725,12 @@ const QUOTES_EXTENSION = function (this: QuoteImageBlock) {
this.quoteField_('TEXT');
};

/** Type of a block that has TEXT_JOIN_MUTATOR_MIXIN */
type JoinMutatorBlock = BlockSvg & JoinMutatorMixin & QuoteImageMixin;
/**
* Type of a block that has TEXT_JOIN_MUTATOR_MIXIN
*
* @internal
*/
export type JoinMutatorBlock = BlockSvg & JoinMutatorMixin & QuoteImageMixin;
interface JoinMutatorMixin extends JoinMutatorMixinType {}
type JoinMutatorMixinType = typeof JOIN_MUTATOR_MIXIN;

Expand Down
30 changes: 17 additions & 13 deletions generators/javascript/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// Former goog.module ID: Blockly.JavaScript.lists

import type {Block} from '../../core/block.js';
import type {CreateWithBlock} from '../../blocks/lists.js';
import type {JavascriptGenerator} from './javascript_generator.js';
import {NameType} from '../../core/names.js';
import {Order} from './javascript_generator.js';
Expand All @@ -23,9 +24,10 @@ export function lists_create_empty(block: Block, generator: JavascriptGenerator)
};

export function lists_create_with(block: Block, generator: JavascriptGenerator): [string, Order] {
const createWithBlock = block as CreateWithBlock;
// Create a list with any number of elements of any type.
const elements = new Array(block.itemCount_);
for (let i = 0; i < block.itemCount_; i++) {
const elements = new Array(createWithBlock.itemCount_);
for (let i = 0; i < createWithBlock.itemCount_; i++) {
elements[i] =
generator.valueToCode(block, 'ADD' + i, Order.NONE) ||
'null';
Expand Down Expand Up @@ -182,8 +184,8 @@ export function lists_setIndex(block: Block, generator: JavascriptGenerator) {
return '';
}
const listVar =
generator.nameDB_.getDistinctName(
'tmpList', NameType.VARIABLE);
generator.nameDB_!.getDistinctName(
'tmpList', NameType.VARIABLE)!;
const code = 'var ' + listVar + ' = ' + list + ';\n';
list = listVar;
return code;
Expand Down Expand Up @@ -231,7 +233,7 @@ export function lists_setIndex(block: Block, generator: JavascriptGenerator) {
case ('RANDOM'): {
let code = cacheList();
const xVar =
generator.nameDB_.getDistinctName(
generator.nameDB_!.getDistinctName(
'tmpX', NameType.VARIABLE);
code += 'var ' + xVar + ' = Math.floor(Math.random() * ' + list +
'.length);\n';
Expand Down Expand Up @@ -268,11 +270,19 @@ const getSubstringIndex = function(listName: string, where: string, opt_at?: str
};

export function lists_getSublist(block: Block, generator: JavascriptGenerator): [string, Order] {
// Dictionary of WHEREn field choices and their CamelCase equivalents.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: WHERE field choices and their PascalCase equiavlents, or just delete comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The n is not a typo. There is no field called WHERE, but there are WHERE1, WHERE2 etc.

const wherePascalCase = {
'FIRST': 'First',
'LAST': 'Last',
'FROM_START': 'FromStart',
'FROM_END': 'FromEnd',
};
type WhereOption = keyof typeof wherePascalCase;
// Get sublist.
const list =
generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]';
const where1 = block.getFieldValue('WHERE1');
const where2 = block.getFieldValue('WHERE2');
const where1 = block.getFieldValue('WHERE1') as WhereOption;
const where2 = block.getFieldValue('WHERE2') as WhereOption;
let code;
if (where1 === 'FIRST' && where2 === 'LAST') {
code = list + '.slice(0)';
Expand Down Expand Up @@ -317,12 +327,6 @@ export function lists_getSublist(block: Block, generator: JavascriptGenerator):
} else {
const at1 = generator.getAdjusted(block, 'AT1');
const at2 = generator.getAdjusted(block, 'AT2');
const wherePascalCase = {
'FIRST': 'First',
'LAST': 'Last',
'FROM_START': 'FromStart',
'FROM_END': 'FromEnd',
};
// The value for 'FROM_END' and'FROM_START' depends on `at` so
// we add it as a parameter.
const at1Param =
Expand Down
5 changes: 3 additions & 2 deletions generators/javascript/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ export function controls_if(block: Block, generator: JavascriptGenerator) {
export const controls_ifelse = controls_if;

export function logic_compare(block: Block, generator: JavascriptGenerator): [string, Order] {
// Comparison operator.
// Dictionary of OP comparison operators and their implementations.
const OPERATORS =
{'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='};
const operator = OPERATORS[block.getFieldValue('OP')];
type OperatorOption = keyof typeof OPERATORS;
const operator = OPERATORS[block.getFieldValue('OP') as OperatorOption];
const order = (operator === '==' || operator === '!=') ?
Order.EQUALITY :
Order.RELATIONAL;
Expand Down
19 changes: 10 additions & 9 deletions generators/javascript/loops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import * as stringUtils from '../../core/utils/string.js';
import type {Block} from '../../core/block.js';
import type {ControlFlowInLoopBlock} from '../../blocks/loops.js';
import type {JavascriptGenerator} from './javascript_generator.js';
import {NameType} from '../../core/names.js';
import {Order} from './javascript_generator.js';
Expand All @@ -33,11 +34,11 @@ export function controls_repeat_ext(block: Block, generator: JavascriptGenerator
branch = generator.addLoopTrap(branch, block);
let code = '';
const loopVar =
generator.nameDB_.getDistinctName('count', NameType.VARIABLE);
generator.nameDB_!.getDistinctName('count', NameType.VARIABLE);
let endVar = repeats;
if (!repeats.match(/^\w+$/) && !stringUtils.isNumber(repeats)) {
endVar =
generator.nameDB_.getDistinctName(
generator.nameDB_!.getDistinctName(
'repeat_end', NameType.VARIABLE);
code += 'var ' + endVar + ' = ' + repeats + ';\n';
}
Expand Down Expand Up @@ -96,23 +97,23 @@ export function controls_for(block: Block, generator: JavascriptGenerator) {
// Cache non-trivial values to variables to prevent repeated look-ups.
let startVar = argument0;
if (!argument0.match(/^\w+$/) && !stringUtils.isNumber(argument0)) {
startVar = generator.nameDB_.getDistinctName(
startVar = generator.nameDB_!.getDistinctName(
variable0 + '_start', NameType.VARIABLE);
code += 'var ' + startVar + ' = ' + argument0 + ';\n';
}
let endVar = argument1;
if (!argument1.match(/^\w+$/) && !stringUtils.isNumber(argument1)) {
endVar = generator.nameDB_.getDistinctName(
endVar = generator.nameDB_!.getDistinctName(
variable0 + '_end', NameType.VARIABLE);
code += 'var ' + endVar + ' = ' + argument1 + ';\n';
}
// Determine loop direction at start, in case one of the bounds
// changes during loop execution.
const incVar = generator.nameDB_.getDistinctName(
const incVar = generator.nameDB_!.getDistinctName(
variable0 + '_inc', NameType.VARIABLE);
code += 'var ' + incVar + ' = ';
if (stringUtils.isNumber(increment)) {
code += Math.abs(increment) + ';\n';
code += Math.abs(Number(increment)) + ';\n';
} else {
code += 'Math.abs(' + increment + ');\n';
}
Expand Down Expand Up @@ -140,11 +141,11 @@ export function controls_forEach(block: Block, generator: JavascriptGenerator) {
// Cache non-trivial values to variables to prevent repeated look-ups.
let listVar = argument0;
if (!argument0.match(/^\w+$/)) {
listVar = generator.nameDB_.getDistinctName(
listVar = generator.nameDB_!.getDistinctName(
variable0 + '_list', NameType.VARIABLE);
code += 'var ' + listVar + ' = ' + argument0 + ';\n';
}
const indexVar = generator.nameDB_.getDistinctName(
const indexVar = generator.nameDB_!.getDistinctName(
variable0 + '_index', NameType.VARIABLE);
branch = generator.INDENT + variable0 + ' = ' + listVar +
'[' + indexVar + '];\n' + branch;
Expand All @@ -167,7 +168,7 @@ export function controls_flow_statements(block: Block, generator: JavascriptGene
generator.STATEMENT_SUFFIX, block);
}
if (generator.STATEMENT_PREFIX) {
const loop = block.getSurroundLoop();
const loop = (block as ControlFlowInLoopBlock).getSurroundLoop();
if (loop && !loop.suppressPrefixSuffix) {
// Inject loop's statement prefix here since the regular one at the end
// of the loop will not get executed if 'continue' is triggered.
Expand Down
21 changes: 12 additions & 9 deletions generators/javascript/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,23 @@ import {Order} from './javascript_generator.js';

export function math_number(block: Block, generator: JavascriptGenerator): [string, Order] {
// Numeric value.
const code = Number(block.getFieldValue('NUM'));
const order = code >= 0 ? Order.ATOMIC :
const number = Number(block.getFieldValue('NUM'));
const order = number >= 0 ? Order.ATOMIC :
Order.UNARY_NEGATION;
return [code, order];
return [String(number), order];
};

export function math_arithmetic(block: Block, generator: JavascriptGenerator): [string, Order] {
// Basic arithmetic operators, and power.
const OPERATORS = {
const OPERATORS: Record<string, [string | null, Order]> = {
'ADD': [' + ', Order.ADDITION],
'MINUS': [' - ', Order.SUBTRACTION],
'MULTIPLY': [' * ', Order.MULTIPLICATION],
'DIVIDE': [' / ', Order.DIVISION],
'POWER': [null, Order.NONE], // Handle power separately.
};
const tuple = OPERATORS[block.getFieldValue('OP')];
type OperatorOption = keyof typeof OPERATORS;
const tuple = OPERATORS[block.getFieldValue('OP') as OperatorOption];
const operator = tuple[0];
const order = tuple[1];
const argument0 = generator.valueToCode(block, 'A', order) || '0';
Expand Down Expand Up @@ -134,21 +135,22 @@ export function math_single(block: Block, generator: JavascriptGenerator): [stri

export function math_constant(block: Block, generator: JavascriptGenerator): [string, Order] {
// Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY.
const CONSTANTS = {
const CONSTANTS: Record<string, [string, Order]> = {
'PI': ['Math.PI', Order.MEMBER],
'E': ['Math.E', Order.MEMBER],
'GOLDEN_RATIO': ['(1 + Math.sqrt(5)) / 2', Order.DIVISION],
'SQRT2': ['Math.SQRT2', Order.MEMBER],
'SQRT1_2': ['Math.SQRT1_2', Order.MEMBER],
'INFINITY': ['Infinity', Order.ATOMIC],
};
return CONSTANTS[block.getFieldValue('CONSTANT')];
type ConstantOption = keyof typeof CONSTANTS
return CONSTANTS[block.getFieldValue('CONSTANT') as ConstantOption];
};

export function math_number_property(block: Block, generator: JavascriptGenerator): [string, Order] {
// Check if a number is even, odd, prime, whole, positive, or negative
// or if it is divisible by certain number. Returns true or false.
const PROPERTIES = {
const PROPERTIES: Record<string, [string | null, Order, Order]> = {
'EVEN': [' % 2 === 0', Order.MODULUS, Order.EQUALITY],
'ODD': [' % 2 === 1', Order.MODULUS, Order.EQUALITY],
'WHOLE': [' % 1 === 0', Order.MODULUS,
Expand All @@ -160,7 +162,8 @@ export function math_number_property(block: Block, generator: JavascriptGenerato
'DIVISIBLE_BY': [null, Order.MODULUS, Order.EQUALITY],
'PRIME': [null, Order.NONE, Order.FUNCTION_CALL],
};
const dropdownProperty = block.getFieldValue('PROPERTY');
type PropertyOption = keyof typeof PROPERTIES;
const dropdownProperty = block.getFieldValue('PROPERTY') as PropertyOption;
const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty];
const numberToCheck =
generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) ||
Expand Down
9 changes: 6 additions & 3 deletions generators/javascript/procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// Former goog.module ID: Blockly.JavaScript.procedures

import type {Block} from '../../core/block.js';
import type {IfReturnBlock} from '../../blocks/procedures.js';
import type {JavascriptGenerator} from './javascript_generator.js';
import {Order} from './javascript_generator.js';

Expand Down Expand Up @@ -58,7 +59,9 @@ export function procedures_defreturn(block: Block, generator: JavascriptGenerato
loopTrap + branch + xfix2 + returnValue + '}';
code = generator.scrub_(block, code);
// Add % so as not to collide with helper functions in definitions list.
generator.definitions_['%' + funcName] = code;
// TODO(#7600): find better approach than casting to any to override
cpcallen marked this conversation as resolved.
Show resolved Hide resolved
// CodeGenerator declaring .definitions protected.
(generator as AnyDuringMigration).definitions_['%' + funcName] = code;
return null;
};

Expand All @@ -83,7 +86,7 @@ export function procedures_callnoreturn(block: Block, generator: JavascriptGener
// Call a procedure with no return value.
// Generated code is for a function call as a statement is the same as a
// function call as a value, with the addition of line ending.
const tuple = generator.forBlock['procedures_callreturn'](block, generator);
const tuple = generator.forBlock['procedures_callreturn'](block, generator) as [string, Order];
return tuple[0] + ';\n';
};

Expand All @@ -101,7 +104,7 @@ export function procedures_ifreturn(block: Block, generator: JavascriptGenerator
generator.STATEMENT_SUFFIX, block),
generator.INDENT);
}
if (block.hasReturnValue_) {
if ((block as IfReturnBlock).hasReturnValue_) {
const value =
generator.valueToCode(block, 'VALUE', Order.NONE) || 'null';
code += generator.INDENT + 'return ' + value + ';\n';
Expand Down
Loading