From cf5a2f9233ec142069a5826b581eebca64c6a041 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Mon, 11 Sep 2023 18:02:14 +0100 Subject: [PATCH 1/8] refactor(generators): Migrate javascript_generator.js to TypeScript --- ...t_generator.js => javascript_generator.ts} | 193 +++++++++--------- 1 file changed, 102 insertions(+), 91 deletions(-) rename generators/javascript/{javascript_generator.js => javascript_generator.ts} (65%) diff --git a/generators/javascript/javascript_generator.js b/generators/javascript/javascript_generator.ts similarity index 65% rename from generators/javascript/javascript_generator.js rename to generators/javascript/javascript_generator.ts index f85fce18ffa..750c7cdc7d7 100644 --- a/generators/javascript/javascript_generator.js +++ b/generators/javascript/javascript_generator.ts @@ -5,62 +5,61 @@ */ /** - * @fileoverview Helper functions for generating JavaScript for blocks. - * @suppress {checkTypes|globalThis} + * @file JavaScript code generator class, including helper methods for + * generating JavaScript for blocks. */ // Former goog.module ID: Blockly.JavaScript import * as Variables from '../../core/variables.js'; import * as stringUtils from '../../core/utils/string.js'; -// import type {Block} from '../../core/block.js'; +import type {Block} from '../../core/block.js'; import {CodeGenerator} from '../../core/generator.js'; import {Names, NameType} from '../../core/names.js'; -// import type {Workspace} from '../../core/workspace.js'; +import type {Workspace} from '../../core/workspace.js'; import {inputTypes} from '../../core/inputs/input_types.js'; /** * Order of operation ENUMs. * https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence - * @enum {number} */ -export const Order = { - ATOMIC: 0, // 0 "" ... - NEW: 1.1, // new - MEMBER: 1.2, // . [] - FUNCTION_CALL: 2, // () - INCREMENT: 3, // ++ - DECREMENT: 3, // -- - BITWISE_NOT: 4.1, // ~ - UNARY_PLUS: 4.2, // + - UNARY_NEGATION: 4.3, // - - LOGICAL_NOT: 4.4, // ! - TYPEOF: 4.5, // typeof - VOID: 4.6, // void - DELETE: 4.7, // delete - AWAIT: 4.8, // await - EXPONENTIATION: 5.0, // ** - MULTIPLICATION: 5.1, // * - DIVISION: 5.2, // / - MODULUS: 5.3, // % - SUBTRACTION: 6.1, // - - ADDITION: 6.2, // + - BITWISE_SHIFT: 7, // << >> >>> - RELATIONAL: 8, // < <= > >= - IN: 8, // in - INSTANCEOF: 8, // instanceof - EQUALITY: 9, // == != === !== - BITWISE_AND: 10, // & - BITWISE_XOR: 11, // ^ - BITWISE_OR: 12, // | - LOGICAL_AND: 13, // && - LOGICAL_OR: 14, // || - CONDITIONAL: 15, // ?: - ASSIGNMENT: 16, //: += -= **= *= /= %= <<= >>= ... - YIELD: 17, // yield - COMMA: 18, // , - NONE: 99, // (...) +export enum Order { + ATOMIC = 0, // 0 "" ... + NEW = 1.1, // new + MEMBER = 1.2, // . [] + FUNCTION_CALL = 2, // () + INCREMENT = 3, // ++ + DECREMENT = 3, // -- + BITWISE_NOT = 4.1, // ~ + UNARY_PLUS = 4.2, // + + UNARY_NEGATION = 4.3, // - + LOGICAL_NOT = 4.4, // ! + TYPEOF = 4.5, // typeof + VOID = 4.6, // void + DELETE = 4.7, // delete + AWAIT = 4.8, // await + EXPONENTIATION = 5.0, // ** + MULTIPLICATION = 5.1, // * + DIVISION = 5.2, // / + MODULUS = 5.3, // % + SUBTRACTION = 6.1, // - + ADDITION = 6.2, // + + BITWISE_SHIFT = 7, // << >> >>> + RELATIONAL = 8, // < <= > >= + IN = 8, // in + INSTANCEOF = 8, // instanceof + EQUALITY = 9, // == != === !== + BITWISE_AND = 10, // & + BITWISE_XOR = 11, // ^ + BITWISE_OR = 12, // | + LOGICAL_AND = 13, // && + LOGICAL_OR = 14, // || + CONDITIONAL = 15, // ?: + ASSIGNMENT = 16, // = += -= **= *= /= %= <<= >>= ... + YIELD = 17, // yield + COMMA = 18, // , + NONE = 99, // (...) }; /** @@ -69,9 +68,8 @@ export const Order = { export class JavascriptGenerator extends CodeGenerator { /** * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} */ - ORDER_OVERRIDES = [ + ORDER_OVERRIDES: number[][] = [ // (foo()).bar -> foo().bar // (foo())[0] -> foo()[0] [Order.FUNCTION_CALL, Order.MEMBER], @@ -98,8 +96,9 @@ export class JavascriptGenerator extends CodeGenerator { [Order.LOGICAL_OR, Order.LOGICAL_OR] ]; - constructor(name) { - super(name ?? 'JavaScript'); + /** @param name Name of language generator is for. */ + constructor(name = 'JavaScript') { + super(name); this.isInitialized = false; // Copy Order values onto instance for backwards compatibility @@ -110,7 +109,16 @@ export class JavascriptGenerator extends CodeGenerator { // replace data properties with get accessors that call // deprecate.warn().) for (const key in Order) { - this['ORDER_' + key] = Order[key]; + // Must assign Order[key] to a temporary to get the type guard to work; + // see https://github.com/microsoft/TypeScript/issues/10530. + const value = Order[key]; + // Skip reverse-lookup entries in the enum. Due to + // https://github.com/microsoft/TypeScript/issues/55713 this (as + // of TypeScript 5.5.2) actually narrows the type of value to + // never - but that still allows the following assignment to + // succeed. + if (typeof value === 'string') continue; + (this as unknown as Record)['ORDER_' + key] = value; } // List of illegal variable names. This is not intended to be a @@ -137,9 +145,10 @@ export class JavascriptGenerator extends CodeGenerator { /** * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. + * + * @param workspace Workspace to generate code from. */ - init(workspace) { + init(workspace: Workspace) { super.init(workspace); if (!this.nameDB_) { @@ -176,37 +185,40 @@ export class JavascriptGenerator extends CodeGenerator { /** * Prepend the generated code with the variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. + * + * @param code Generated code. + * @returns Completed code. */ - finish(code) { + finish(code: string): string { // Convert the definitions dictionary into a list. const definitions = Object.values(this.definitions_); // Call Blockly.CodeGenerator's finish. super.finish(code); this.isInitialized = false; - this.nameDB_.reset(); + this.nameDB_?.reset(); return definitions.join('\n\n') + '\n\n\n' + code; } /** * Naked values are top-level blocks with outputs that aren't plugged into * anything. A trailing semicolon is needed to make this legal. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. + * + * @param line Line of generated code. + * @returns Legal line of code. */ - scrubNakedValue(line) { + scrubNakedValue(line: string): string { return line + ';\n'; } /** * Encode a string as a properly escaped JavaScript string, complete with * quotes. - * @param {string} string Text to encode. - * @return {string} JavaScript string. + * + * @param string Text to encode. + * @returns JavaScript string. */ - quote_(string) { + quote_(string: string): string { // Can't use goog.string.quote since Google's style guide recommends // JS string literals use single quotes. string = string.replace(/\\/g, '\\\\') @@ -218,10 +230,10 @@ export class JavascriptGenerator extends CodeGenerator { /** * Encode a string as a properly escaped multiline JavaScript string, complete * with quotes. - * @param {string} string Text to encode. - * @return {string} JavaScript string. + * @param string Text to encode. + * @returns JavaScript string. */ - multiline_quote_(string) { + multiline_quote_(string: string): string { // Can't use goog.string.quote since Google's style guide recommends // JS string literals use single quotes. const lines = string.split(/\n/g).map(this.quote_); @@ -232,14 +244,14 @@ export class JavascriptGenerator extends CodeGenerator { * Common tasks for generating JavaScript from blocks. * Handles comments for the specified block and any connected value blocks. * Calls any statements following this block. - * @param {!Block} block The current block. - * @param {string} code The JavaScript code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this - * statement. - * @return {string} JavaScript code with comments and subsequent blocks added. + * + * @param block The current block. + * @param code The JavaScript code created for this block. + * @param thisOnly True to generate code for only this statement. + * @returns JavaScript code with comments and subsequent blocks added. * @protected */ - scrub_(block, code, opt_thisOnly) { + scrub_(block: Block, code: string, thisOnly = false): string { let commentCode = ''; // Only collect comments for blocks that aren't inline. if (!block.outputConnection || !block.outputConnection.targetConnection) { @@ -253,7 +265,7 @@ export class JavascriptGenerator extends CodeGenerator { // Don't collect comments for nested statements. for (let i = 0; i < block.inputList.length; i++) { if (block.inputList[i].type === inputTypes.VALUE) { - const childBlock = block.inputList[i].connection.targetBlock(); + const childBlock = block.inputList[i].connection?.targetBlock(); if (childBlock) { comment = this.allNestedComments(childBlock); if (comment) { @@ -265,22 +277,21 @@ export class JavascriptGenerator extends CodeGenerator { } const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + const nextCode = thisOnly ? '' : this.blockToCode(nextBlock); return commentCode + code + nextCode; } /** * Gets a property and adjusts the value while taking into account indexing. - * @param {!Block} block The block. - * @param {string} atId The property ID of the element to get. - * @param {number=} opt_delta Value to add. - * @param {boolean=} opt_negate Whether to negate the value. - * @param {number=} opt_order The highest order acting on this value. - * @return {string|number} + * + * @param block The block. + * @param atId The property ID of the element to get. + * @param delta Value to add. + * @param negate Whether to negate the value. + * @param order The highest order acting on this value. + * @returns The adjusted value. */ - getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { - let delta = opt_delta || 0; - let order = opt_order || this.ORDER_NONE; + getAdjusted(block: Block, atId: string, delta = 0, negate = false, order = Order.NONE): string|number { if (block.workspace.options.oneBasedIndex) { delta--; } @@ -289,23 +300,23 @@ export class JavascriptGenerator extends CodeGenerator { let innerOrder; let outerOrder = order; if (delta > 0) { - outerOrder = this.ORDER_ADDITION; - innerOrder = this.ORDER_ADDITION; + outerOrder = Order.ADDITION; + innerOrder = Order.ADDITION; } else if (delta < 0) { - outerOrder = this.ORDER_SUBTRACTION; - innerOrder = this.ORDER_SUBTRACTION; - } else if (opt_negate) { - outerOrder = this.ORDER_UNARY_NEGATION; - innerOrder = this.ORDER_UNARY_NEGATION; + outerOrder = Order.SUBTRACTION; + innerOrder = Order.SUBTRACTION; + } else if (negate) { + outerOrder = Order.UNARY_NEGATION; + innerOrder = Order.UNARY_NEGATION; } - let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; + let at = this.valueToCode(block, atId, innerOrder) || defaultAtIndex; if (stringUtils.isNumber(at)) { // If the index is a naked number, adjust it right now. - at = Number(at) + delta; - if (opt_negate) { - at = -at; + at = String(Number(at) + delta); + if (negate) { + at = String(-Number(at)); } } else { // If the index is dynamic, adjust it in code. @@ -314,14 +325,14 @@ export class JavascriptGenerator extends CodeGenerator { } else if (delta < 0) { at = at + ' - ' + -delta; } - if (opt_negate) { + if (negate) { if (delta) { at = '-(' + at + ')'; } else { at = '-' + at; } } - innerOrder = Math.floor(innerOrder); + innerOrder = innerOrder === undefined ? NaN : Math.floor(innerOrder); order = Math.floor(order); if (innerOrder && order >= innerOrder) { at = '(' + at + ')'; From e8dfca99c562c43119713db80d333fb7c7c377f1 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Thu, 14 Sep 2023 19:52:50 +0100 Subject: [PATCH 2/8] refactor(generators): Simplify getAdjusted Slightly simplify the implementation of getAdjusted, in part to make it more readable. Also improve its JSDoc comment. --- generators/javascript/javascript_generator.ts | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/generators/javascript/javascript_generator.ts b/generators/javascript/javascript_generator.ts index 750c7cdc7d7..370558424ce 100644 --- a/generators/javascript/javascript_generator.ts +++ b/generators/javascript/javascript_generator.ts @@ -282,7 +282,9 @@ export class JavascriptGenerator extends CodeGenerator { } /** - * Gets a property and adjusts the value while taking into account indexing. + * Generate code representing the specified value input, adjusted to take into + * account indexing (zero- or one-based) and optionally by a specified delta + * and/or by negation. * * @param block The block. * @param atId The property ID of the element to get. @@ -297,46 +299,42 @@ export class JavascriptGenerator extends CodeGenerator { } const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - let innerOrder; - let outerOrder = order; + let innerOrder = order; if (delta > 0) { - outerOrder = Order.ADDITION; innerOrder = Order.ADDITION; } else if (delta < 0) { - outerOrder = Order.SUBTRACTION; innerOrder = Order.SUBTRACTION; } else if (negate) { - outerOrder = Order.UNARY_NEGATION; innerOrder = Order.UNARY_NEGATION; } let at = this.valueToCode(block, atId, innerOrder) || defaultAtIndex; + // Easy case: no adjustments. + if (delta === 0 && !negate) { + return at; + } + // If the index is a naked number, adjust it right now. if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. at = String(Number(at) + delta); if (negate) { at = String(-Number(at)); } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = at + ' + ' + delta; - } else if (delta < 0) { - at = at + ' - ' + -delta; - } - if (negate) { - if (delta) { - at = '-(' + at + ')'; - } else { - at = '-' + at; - } - } - innerOrder = innerOrder === undefined ? NaN : Math.floor(innerOrder); - order = Math.floor(order); - if (innerOrder && order >= innerOrder) { - at = '(' + at + ')'; - } + return at; + } + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = `${at} + ${delta}`; + } else if (delta < 0) { + at = `${at} - ${-delta}`; + } + if (negate) { + at = delta ? `-(${at})` : `-${at}`; + } + innerOrder = Math.floor(innerOrder); + order = Math.floor(order); + if (order >= innerOrder) { + at = `(${at})`; } return at; } From d15e80252c283f0d4cbc0fb7ed3461c8d19325d8 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Wed, 18 Oct 2023 12:31:41 +0100 Subject: [PATCH 3/8] refactor(generators): Migrate generators/javascript/* to TypeScript First pass doing very mechanistic migration, not attempting to fix all the resulting type errors. --- .../javascript/{colour.js => colour.ts} | 10 ++-- generators/javascript/{lists.js => lists.ts} | 36 ++++++------- generators/javascript/{logic.js => logic.ts} | 16 +++--- generators/javascript/{loops.js => loops.ts} | 12 +++-- generators/javascript/{math.js => math.ts} | 26 +++++----- .../{procedures.js => procedures.ts} | 10 ++-- generators/javascript/{text.js => text.ts} | 50 ++++++++++--------- .../javascript/{variables.js => variables.ts} | 6 ++- ...iables_dynamic.js => variables_dynamic.ts} | 0 9 files changed, 91 insertions(+), 75 deletions(-) rename generators/javascript/{colour.js => colour.ts} (85%) rename generators/javascript/{lists.js => lists.ts} (88%) rename generators/javascript/{logic.js => logic.ts} (82%) rename generators/javascript/{loops.js => loops.ts} (92%) rename generators/javascript/{math.js => math.ts} (90%) rename generators/javascript/{procedures.js => procedures.ts} (87%) rename generators/javascript/{text.js => text.ts} (84%) rename generators/javascript/{variables.js => variables.ts} (69%) rename generators/javascript/{variables_dynamic.js => variables_dynamic.ts} (100%) diff --git a/generators/javascript/colour.js b/generators/javascript/colour.ts similarity index 85% rename from generators/javascript/colour.js rename to generators/javascript/colour.ts index 41d588c7594..e459f564725 100644 --- a/generators/javascript/colour.js +++ b/generators/javascript/colour.ts @@ -10,16 +10,18 @@ // Former goog.module ID: Blockly.JavaScript.colour +import type {Block} from '../../core/block.js'; +import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; -export function colour_picker(block, generator) { +export function colour_picker(block: Block, generator: JavascriptGenerator): [string, Order] { // Colour picker. const code = generator.quote_(block.getFieldValue('COLOUR')); return [code, Order.ATOMIC]; }; -export function colour_random(block, generator) { +export function colour_random(block: Block, generator: JavascriptGenerator): [string, Order] { // Generate a random colour. const functionName = generator.provideFunction_('colourRandom', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { @@ -31,7 +33,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { return [code, Order.FUNCTION_CALL]; }; -export function colour_rgb(block, generator) { +export function colour_rgb(block: Block, generator: JavascriptGenerator): [string, Order] { // Compose a colour from RGB components expressed as percentages. const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; const green = @@ -53,7 +55,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) { return [code, Order.FUNCTION_CALL]; }; -export function colour_blend(block, generator) { +export function colour_blend(block: Block, generator: JavascriptGenerator): [string, Order] { // Blend two colours together. const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; diff --git a/generators/javascript/lists.js b/generators/javascript/lists.ts similarity index 88% rename from generators/javascript/lists.js rename to generators/javascript/lists.ts index 9e42657161e..a5a2ad29115 100644 --- a/generators/javascript/lists.js +++ b/generators/javascript/lists.ts @@ -11,16 +11,18 @@ // Former goog.module ID: Blockly.JavaScript.lists +import type {Block} from '../../core/block.js'; +import type {JavascriptGenerator} from './javascript_generator.js'; import {NameType} from '../../core/names.js'; import {Order} from './javascript_generator.js'; -export function lists_create_empty(block, generator) { +export function lists_create_empty(block: Block, generator: JavascriptGenerator): [string, Order] { // Create an empty list. return ['[]', Order.ATOMIC]; }; -export function lists_create_with(block, generator) { +export function lists_create_with(block: Block, generator: JavascriptGenerator): [string, Order] { // 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++) { @@ -32,7 +34,7 @@ export function lists_create_with(block, generator) { return [code, Order.ATOMIC]; }; -export function lists_repeat(block, generator) { +export function lists_repeat(block: Block, generator: JavascriptGenerator): [string, Order] { // Create a list with one element repeated. const functionName = generator.provideFunction_('listsRepeat', ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(value, n) { @@ -51,21 +53,21 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(value, n) { return [code, Order.FUNCTION_CALL]; }; -export function lists_length(block, generator) { +export function lists_length(block: Block, generator: JavascriptGenerator): [string, Order] { // String or array length. const list = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; return [list + '.length', Order.MEMBER]; }; -export function lists_isEmpty(block, generator) { +export function lists_isEmpty(block: Block, generator: JavascriptGenerator): [string, Order] { // Is the string null or array empty? const list = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; return ['!' + list + '.length', Order.LOGICAL_NOT]; }; -export function lists_indexOf(block, generator) { +export function lists_indexOf(block: Block, generator: JavascriptGenerator): [string, Order] { // Find an item in the list. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; @@ -80,7 +82,7 @@ export function lists_indexOf(block, generator) { return [code, Order.FUNCTION_CALL]; }; -export function lists_getIndex(block, generator) { +export function lists_getIndex(block: Block, generator: JavascriptGenerator): [string, Order] | string { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; @@ -163,7 +165,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { throw Error('Unhandled combination (lists_getIndex).'); }; -export function lists_setIndex(block, generator) { +export function lists_setIndex(block: Block, generator: JavascriptGenerator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. let list = @@ -248,12 +250,12 @@ export function lists_setIndex(block, generator) { /** * Returns an expression calculating the index into a list. - * @param {string} listName Name of the list, used to calculate length. - * @param {string} where The method of indexing, selected by dropdown in Blockly - * @param {string=} opt_at The optional offset when indexing from start/end. - * @return {string|undefined} Index expression. + * @param listName Name of the list, used to calculate length. + * @param where The method of indexing, selected by dropdown in Blockly + * @param opt_at The optional offset when indexing from start/end. + * @returns Index expression. */ -const getSubstringIndex = function(listName, where, opt_at) { +const getSubstringIndex = function(listName: string, where: string, opt_at?: string): string | undefined { if (where === 'FIRST') { return '0'; } else if (where === 'FROM_END') { @@ -265,7 +267,7 @@ const getSubstringIndex = function(listName, where, opt_at) { } }; -export function lists_getSublist(block, generator) { +export function lists_getSublist(block: Block, generator: JavascriptGenerator): [string, Order] { // Get sublist. const list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; @@ -345,7 +347,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) return [code, Order.FUNCTION_CALL]; }; -export function lists_sort(block, generator) { +export function lists_sort(block: Block, generator: JavascriptGenerator): [string, Order] { // Block for sorting a list. const list = generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || @@ -374,7 +376,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(type, direction) { ]; }; -export function lists_split(block, generator) { +export function lists_split(block: Block, generator: JavascriptGenerator): [string, Order] { // Block for splitting text into a list, or joining a list into text. let input = generator.valueToCode(block, 'INPUT', Order.MEMBER); const delimiter = @@ -398,7 +400,7 @@ export function lists_split(block, generator) { return [code, Order.FUNCTION_CALL]; }; -export function lists_reverse(block, generator) { +export function lists_reverse(block: Block, generator: JavascriptGenerator): [string, Order] { // Block for reversing a list. const list = generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || diff --git a/generators/javascript/logic.js b/generators/javascript/logic.ts similarity index 82% rename from generators/javascript/logic.js rename to generators/javascript/logic.ts index 896c8954b9e..eed3f6f42dc 100644 --- a/generators/javascript/logic.js +++ b/generators/javascript/logic.ts @@ -10,10 +10,12 @@ // Former goog.module ID: Blockly.JavaScript.logic +import type {Block} from '../../core/block.js'; +import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; -export function controls_if(block, generator) { +export function controls_if(block: Block, generator: JavascriptGenerator) { // If/elseif/else condition. let n = 0; let code = ''; @@ -55,7 +57,7 @@ export function controls_if(block, generator) { export const controls_ifelse = controls_if; -export function logic_compare(block, generator) { +export function logic_compare(block: Block, generator: JavascriptGenerator): [string, Order] { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; @@ -69,7 +71,7 @@ export function logic_compare(block, generator) { return [code, order]; }; -export function logic_operation(block, generator) { +export function logic_operation(block: Block, generator: JavascriptGenerator): [string, Order] { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; const order = (operator === '&&') ? Order.LOGICAL_AND : @@ -94,7 +96,7 @@ export function logic_operation(block, generator) { return [code, order]; }; -export function logic_negate(block, generator) { +export function logic_negate(block: Block, generator: JavascriptGenerator): [string, Order] { // Negation. const order = Order.LOGICAL_NOT; const argument0 = @@ -103,18 +105,18 @@ export function logic_negate(block, generator) { return [code, order]; }; -export function logic_boolean(block, generator) { +export function logic_boolean(block: Block, generator: JavascriptGenerator): [string, Order] { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; return [code, Order.ATOMIC]; }; -export function logic_null(block, generator) { +export function logic_null(block: Block, generator: JavascriptGenerator): [string, Order] { // Null data type. return ['null', Order.ATOMIC]; }; -export function logic_ternary(block, generator) { +export function logic_ternary(block: Block, generator: JavascriptGenerator): [string, Order] { // Ternary operator. const value_if = generator.valueToCode(block, 'IF', Order.CONDITIONAL) || diff --git a/generators/javascript/loops.js b/generators/javascript/loops.ts similarity index 92% rename from generators/javascript/loops.js rename to generators/javascript/loops.ts index fbe532ce77b..eaa3e6d40d2 100644 --- a/generators/javascript/loops.js +++ b/generators/javascript/loops.ts @@ -11,11 +11,13 @@ // Former goog.module ID: Blockly.JavaScript.loops import * as stringUtils from '../../core/utils/string.js'; +import type {Block} from '../../core/block.js'; +import type {JavascriptGenerator} from './javascript_generator.js'; import {NameType} from '../../core/names.js'; import {Order} from './javascript_generator.js'; -export function controls_repeat_ext(block, generator) { +export function controls_repeat_ext(block: Block, generator: JavascriptGenerator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -46,7 +48,7 @@ export function controls_repeat_ext(block, generator) { export const controls_repeat = controls_repeat_ext; -export function controls_whileUntil(block, generator) { +export function controls_whileUntil(block: Block, generator: JavascriptGenerator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = @@ -62,7 +64,7 @@ export function controls_whileUntil(block, generator) { return 'while (' + argument0 + ') {\n' + branch + '}\n'; }; -export function controls_for(block, generator) { +export function controls_for(block: Block, generator: JavascriptGenerator) { // For loop. const variable0 = generator.getVariableName( @@ -125,7 +127,7 @@ export function controls_for(block, generator) { return code; }; -export function controls_forEach(block, generator) { +export function controls_forEach(block: Block, generator: JavascriptGenerator) { // For each loop. const variable0 = generator.getVariableName(block.getFieldValue('VAR')); @@ -150,7 +152,7 @@ export function controls_forEach(block, generator) { return code; }; -export function controls_flow_statements(block, generator) { +export function controls_flow_statements(block: Block, generator: JavascriptGenerator) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { diff --git a/generators/javascript/math.js b/generators/javascript/math.ts similarity index 90% rename from generators/javascript/math.js rename to generators/javascript/math.ts index 95fa32f5a67..59eb2f0cf78 100644 --- a/generators/javascript/math.js +++ b/generators/javascript/math.ts @@ -11,10 +11,12 @@ // Former goog.module ID: Blockly.JavaScript.math +import type {Block} from '../../core/block.js'; +import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; -export function math_number(block, generator) { +export function math_number(block: Block, generator: JavascriptGenerator): [string, Order] { // Numeric value. const code = Number(block.getFieldValue('NUM')); const order = code >= 0 ? Order.ATOMIC : @@ -22,7 +24,7 @@ export function math_number(block, generator) { return [code, order]; }; -export function math_arithmetic(block, generator) { +export function math_arithmetic(block: Block, generator: JavascriptGenerator): [string, Order] { // Basic arithmetic operators, and power. const OPERATORS = { 'ADD': [' + ', Order.ADDITION], @@ -46,7 +48,7 @@ export function math_arithmetic(block, generator) { return [code, order]; }; -export function math_single(block, generator) { +export function math_single(block: Block, generator: JavascriptGenerator): [string, Order] { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; @@ -130,7 +132,7 @@ export function math_single(block, generator) { return [code, Order.DIVISION]; }; -export function math_constant(block, generator) { +export function math_constant(block: Block, generator: JavascriptGenerator): [string, Order] { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { 'PI': ['Math.PI', Order.MEMBER], @@ -143,7 +145,7 @@ export function math_constant(block, generator) { return CONSTANTS[block.getFieldValue('CONSTANT')]; }; -export function math_number_property(block, generator) { +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 = { @@ -197,7 +199,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(n) { return [code, outputOrder]; }; -export function math_change(block, generator) { +export function math_change(block: Block, generator: JavascriptGenerator) { // Add to a variable in place. const argument0 = generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0'; @@ -211,7 +213,7 @@ export const math_round = math_single; // Trigonometry functions have a single operand. export const math_trig = math_single; -export function math_on_list(block, generator) { +export function math_on_list(block: Block, generator: JavascriptGenerator): [string, Order] { // Math functions for lists. const func = block.getFieldValue('OP'); let list; @@ -341,7 +343,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list) { return [code, Order.FUNCTION_CALL]; }; -export function math_modulo(block, generator) { +export function math_modulo(block: Block, generator: JavascriptGenerator): [string, Order] { // Remainder computation. const argument0 = generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0'; @@ -351,7 +353,7 @@ export function math_modulo(block, generator) { return [code, Order.MODULUS]; }; -export function math_constrain(block, generator) { +export function math_constrain(block: Block, generator: JavascriptGenerator): [string, Order] { // Constrain a number between two limits. const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; @@ -364,7 +366,7 @@ export function math_constrain(block, generator) { return [code, Order.FUNCTION_CALL]; }; -export function math_random_int(block, generator) { +export function math_random_int(block: Block, generator: JavascriptGenerator): [string, Order] { // Random integer between [X] and [Y]. const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; @@ -385,12 +387,12 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(a, b) { return [code, Order.FUNCTION_CALL]; }; -export function math_random_float(block, generator) { +export function math_random_float(block: Block, generator: JavascriptGenerator): [string, Order] { // Random fraction between 0 and 1. return ['Math.random()', Order.FUNCTION_CALL]; }; -export function math_atan2(block, generator) { +export function math_atan2(block: Block, generator: JavascriptGenerator): [string, Order] { // Arctangent of point (X, Y) in degrees from -180 to 180. const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; diff --git a/generators/javascript/procedures.js b/generators/javascript/procedures.ts similarity index 87% rename from generators/javascript/procedures.js rename to generators/javascript/procedures.ts index 7f975289c09..19539eaaea4 100644 --- a/generators/javascript/procedures.js +++ b/generators/javascript/procedures.ts @@ -10,10 +10,12 @@ // Former goog.module ID: Blockly.JavaScript.procedures +import type {Block} from '../../core/block.js'; +import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; -export function procedures_defreturn(block, generator) { +export function procedures_defreturn(block: Block, generator: JavascriptGenerator) { // Define a procedure with a return value. const funcName = generator.getProcedureName(block.getFieldValue('NAME')); let xfix1 = ''; @@ -64,7 +66,7 @@ export function procedures_defreturn(block, generator) { // a procedure with a return value. export const procedures_defnoreturn = procedures_defreturn; -export function procedures_callreturn(block, generator) { +export function procedures_callreturn(block: Block, generator: JavascriptGenerator): [string, Order] { // Call a procedure with a return value. const funcName = generator.getProcedureName(block.getFieldValue('NAME')); const args = []; @@ -77,7 +79,7 @@ export function procedures_callreturn(block, generator) { return [code, Order.FUNCTION_CALL]; }; -export function procedures_callnoreturn(block, generator) { +export function procedures_callnoreturn(block: Block, generator: JavascriptGenerator) { // 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. @@ -85,7 +87,7 @@ export function procedures_callnoreturn(block, generator) { return tuple[0] + ';\n'; }; -export function procedures_ifreturn(block, generator) { +export function procedures_ifreturn(block: Block, generator: JavascriptGenerator) { // Conditionally return value from a procedure. const condition = generator.valueToCode(block, 'CONDITION', Order.NONE) || diff --git a/generators/javascript/text.js b/generators/javascript/text.ts similarity index 84% rename from generators/javascript/text.js rename to generators/javascript/text.ts index 32125051f37..e71b5b5f794 100644 --- a/generators/javascript/text.js +++ b/generators/javascript/text.ts @@ -10,6 +10,8 @@ // Former goog.module ID: Blockly.JavaScript.texts +import type {Block} from '../../core/block.js'; +import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; @@ -21,11 +23,11 @@ const strRegExp = /^\s*'([^']|\\')*'\s*$/; /** * Enclose the provided value in 'String(...)' function. * Leave string literals alone. - * @param {string} value Code evaluating to a value. - * @return {Array} Array containing code evaluating to a string + * @param value Code evaluating to a value. + * @returns Array containing code evaluating to a string * and the order of the returned code.[string, number] */ -const forceString = function(value) { +const forceString = function(value: string): [string, Order] { if (strRegExp.test(value)) { return [value, Order.ATOMIC]; } @@ -34,12 +36,12 @@ const forceString = function(value) { /** * Returns an expression calculating the index into a string. - * @param {string} stringName Name of the string, used to calculate length. - * @param {string} where The method of indexing, selected by dropdown in Blockly - * @param {string=} opt_at The optional offset when indexing from start/end. - * @return {string|undefined} Index expression. + * @param stringName Name of the string, used to calculate length. + * @param where The method of indexing, selected by dropdown in Blockly + * @param opt_at The optional offset when indexing from start/end. + * @returns Index expression. */ -const getSubstringIndex = function(stringName, where, opt_at) { +const getSubstringIndex = function(stringName: string, where: string, opt_at?: string): string | undefined { if (where === 'FIRST') { return '0'; } else if (where === 'FROM_END') { @@ -51,13 +53,13 @@ const getSubstringIndex = function(stringName, where, opt_at) { } }; -export function text(block, generator) { +export function text(block: Block, generator: JavascriptGenerator): [string, Order] { // Text value. const code = generator.quote_(block.getFieldValue('TEXT')); return [code, Order.ATOMIC]; }; -export function text_multiline(block, generator) { +export function text_multiline(block: Block, generator: JavascriptGenerator): [string, Order] { // Text value. const code = generator.multiline_quote_(block.getFieldValue('TEXT')); @@ -66,7 +68,7 @@ export function text_multiline(block, generator) { return [code, order]; }; -export function text_join(block, generator) { +export function text_join(block: Block, generator: JavascriptGenerator): [string, Order] { // Create a string made up of any number of elements of any type. switch (block.itemCount_) { case 0: @@ -98,7 +100,7 @@ export function text_join(block, generator) { } }; -export function text_append(block, generator) { +export function text_append(block: Block, generator: JavascriptGenerator) { // Append to a variable in place. const varName = generator.getVariableName(block.getFieldValue('VAR')); const value = generator.valueToCode(block, 'TEXT', @@ -108,21 +110,21 @@ export function text_append(block, generator) { return code; }; -export function text_length(block, generator) { +export function text_length(block: Block, generator: JavascriptGenerator): [string, Order] { // String or array length. const text = generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; return [text + '.length', Order.MEMBER]; }; -export function text_isEmpty(block, generator) { +export function text_isEmpty(block: Block, generator: JavascriptGenerator): [string, Order] { // Is the string null or array empty? const text = generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; return ['!' + text + '.length', Order.LOGICAL_NOT]; }; -export function text_indexOf(block, generator) { +export function text_indexOf(block: Block, generator: JavascriptGenerator): [string, Order] { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; @@ -138,7 +140,7 @@ export function text_indexOf(block, generator) { return [code, Order.FUNCTION_CALL]; }; -export function text_charAt(block, generator) { +export function text_charAt(block: Block, generator: JavascriptGenerator): [string, Order] { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; @@ -181,7 +183,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) { throw Error('Unhandled option (text_charAt).'); }; -export function text_getSubstring(block, generator) { +export function text_getSubstring(block: Block, generator: JavascriptGenerator): [string, Order] { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); @@ -260,7 +262,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) return [code, Order.FUNCTION_CALL]; }; -export function text_changeCase(block, generator) { +export function text_changeCase(block: Block, generator: JavascriptGenerator): [string, Order] { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.toUpperCase()', @@ -289,7 +291,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str) { return [code, Order.FUNCTION_CALL]; }; -export function text_trim(block, generator) { +export function text_trim(block: Block, generator: JavascriptGenerator): [string, Order] { // Trim spaces. const OPERATORS = { 'LEFT': ".replace(/^[\\s\\xa0]+/, '')", @@ -302,14 +304,14 @@ export function text_trim(block, generator) { return [text + operator, Order.FUNCTION_CALL]; }; -export function text_print(block, generator) { +export function text_print(block: Block, generator: JavascriptGenerator) { // Print statement. const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'window.alert(' + msg + ');\n'; }; -export function text_prompt_ext(block, generator) { +export function text_prompt_ext(block: Block, generator: JavascriptGenerator): [string, Order] { // Prompt function. let msg; if (block.getField('TEXT')) { @@ -329,7 +331,7 @@ export function text_prompt_ext(block, generator) { export const text_prompt = text_prompt_ext; -export function text_count(block, generator) { +export function text_count(block: Block, generator: JavascriptGenerator): [string, Order] { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const sub = generator.valueToCode(block, 'SUB', @@ -347,7 +349,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) { return [code, Order.FUNCTION_CALL]; }; -export function text_replace(block, generator) { +export function text_replace(block: Block, generator: JavascriptGenerator): [string, Order] { const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const from = generator.valueToCode(block, 'FROM', @@ -366,7 +368,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) return [code, Order.FUNCTION_CALL]; }; -export function text_reverse(block, generator) { +export function text_reverse(block: Block, generator: JavascriptGenerator): [string, Order] { const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const code = text + ".split('').reverse().join('')"; diff --git a/generators/javascript/variables.js b/generators/javascript/variables.ts similarity index 69% rename from generators/javascript/variables.js rename to generators/javascript/variables.ts index fd0f6c19866..3d3a02f2bd7 100644 --- a/generators/javascript/variables.js +++ b/generators/javascript/variables.ts @@ -10,16 +10,18 @@ // Former goog.module ID: Blockly.JavaScript.variables +import type {Block} from '../../core/block.js'; +import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; -export function variables_get(block, generator) { +export function variables_get(block: Block, generator: JavascriptGenerator): [string, Order] { // Variable getter. const code = generator.getVariableName(block.getFieldValue('VAR')); return [code, Order.ATOMIC]; }; -export function variables_set(block, generator) { +export function variables_set(block: Block, generator: JavascriptGenerator) { // Variable setter. const argument0 = generator.valueToCode( block, 'VALUE', Order.ASSIGNMENT) || '0'; diff --git a/generators/javascript/variables_dynamic.js b/generators/javascript/variables_dynamic.ts similarity index 100% rename from generators/javascript/variables_dynamic.js rename to generators/javascript/variables_dynamic.ts From 3df32471ba10d29e681ee6c04eca28523d3ecef2 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Fri, 20 Oct 2023 16:54:52 +0100 Subject: [PATCH 4/8] fix(generators): Fix type errors in generator functions This consists almost entirely of adding casts, so the code output by tsc should be as similar as possible to the pre-migration .js source files. --- blocks/lists.ts | 8 +++++-- blocks/loops.ts | 8 +++++-- blocks/procedures.ts | 8 +++++-- blocks/text.ts | 8 +++++-- generators/javascript/lists.ts | 30 +++++++++++++----------- generators/javascript/logic.ts | 5 ++-- generators/javascript/loops.ts | 19 +++++++-------- generators/javascript/math.ts | 21 +++++++++-------- generators/javascript/procedures.ts | 9 +++++--- generators/javascript/text.ts | 36 ++++++++++++++++++----------- 10 files changed, 95 insertions(+), 57 deletions(-) diff --git a/blocks/lists.ts b/blocks/lists.ts index a85e393527e..d115a321cb9 100644 --- a/blocks/lists.ts +++ b/blocks/lists.ts @@ -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; } diff --git a/blocks/loops.ts b/blocks/loops.ts index e70fdae313b..02d9d34be72 100644 --- a/blocks/loops.ts +++ b/blocks/loops.ts @@ -325,8 +325,12 @@ export const loopTypes: Set = 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 + */ +export type ControlFlowInLoopBlock = Block & ControlFlowInLoopMixin; interface ControlFlowInLoopMixin extends ControlFlowInLoopMixinType {} type ControlFlowInLoopMixinType = typeof CONTROL_FLOW_IN_LOOP_CHECK_MIXIN; diff --git a/blocks/procedures.ts b/blocks/procedures.ts index 46109fa27b6..7150bda8a29 100644 --- a/blocks/procedures.ts +++ b/blocks/procedures.ts @@ -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; } diff --git a/blocks/text.ts b/blocks/text.ts index eba64d9abe3..cb95358af27 100644 --- a/blocks/text.ts +++ b/blocks/text.ts @@ -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; diff --git a/generators/javascript/lists.ts b/generators/javascript/lists.ts index a5a2ad29115..8a513ced6ff 100644 --- a/generators/javascript/lists.ts +++ b/generators/javascript/lists.ts @@ -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'; @@ -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'; @@ -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; @@ -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'; @@ -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. + 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)'; @@ -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 = diff --git a/generators/javascript/logic.ts b/generators/javascript/logic.ts index eed3f6f42dc..fe90b1da109 100644 --- a/generators/javascript/logic.ts +++ b/generators/javascript/logic.ts @@ -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; diff --git a/generators/javascript/loops.ts b/generators/javascript/loops.ts index eaa3e6d40d2..5eb5c39a748 100644 --- a/generators/javascript/loops.ts +++ b/generators/javascript/loops.ts @@ -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'; @@ -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'; } @@ -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'; } @@ -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; @@ -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. diff --git a/generators/javascript/math.ts b/generators/javascript/math.ts index 59eb2f0cf78..726fd623862 100644 --- a/generators/javascript/math.ts +++ b/generators/javascript/math.ts @@ -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 = { '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'; @@ -134,7 +135,7 @@ 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 = { 'PI': ['Math.PI', Order.MEMBER], 'E': ['Math.E', Order.MEMBER], 'GOLDEN_RATIO': ['(1 + Math.sqrt(5)) / 2', Order.DIVISION], @@ -142,13 +143,14 @@ export function math_constant(block: Block, generator: JavascriptGenerator): [st '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 = { 'EVEN': [' % 2 === 0', Order.MODULUS, Order.EQUALITY], 'ODD': [' % 2 === 1', Order.MODULUS, Order.EQUALITY], 'WHOLE': [' % 1 === 0', Order.MODULUS, @@ -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) || diff --git a/generators/javascript/procedures.ts b/generators/javascript/procedures.ts index 19539eaaea4..f4e656e371b 100644 --- a/generators/javascript/procedures.ts +++ b/generators/javascript/procedures.ts @@ -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'; @@ -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 + // CodeGenerator declaring .definitions protected. + (generator as AnyDuringMigration).definitions_['%' + funcName] = code; return null; }; @@ -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'; }; @@ -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'; diff --git a/generators/javascript/text.ts b/generators/javascript/text.ts index e71b5b5f794..a3bf9bef468 100644 --- a/generators/javascript/text.ts +++ b/generators/javascript/text.ts @@ -11,6 +11,7 @@ // Former goog.module ID: Blockly.JavaScript.texts import type {Block} from '../../core/block.js'; +import type {JoinMutatorBlock} from '../../blocks/text.js'; import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; @@ -69,29 +70,30 @@ export function text_multiline(block: Block, generator: JavascriptGenerator): [s }; export function text_join(block: Block, generator: JavascriptGenerator): [string, Order] { + const joinBlock = block as JoinMutatorBlock; // Create a string made up of any number of elements of any type. - switch (block.itemCount_) { + switch (joinBlock.itemCount_) { case 0: return ["''", Order.ATOMIC]; case 1: { - const element = generator.valueToCode(block, 'ADD0', + const element = generator.valueToCode(joinBlock, 'ADD0', Order.NONE) || "''"; const codeAndOrder = forceString(element); return codeAndOrder; } case 2: { - const element0 = generator.valueToCode(block, 'ADD0', + const element0 = generator.valueToCode(joinBlock, 'ADD0', Order.NONE) || "''"; - const element1 = generator.valueToCode(block, 'ADD1', + const element1 = generator.valueToCode(joinBlock, 'ADD1', Order.NONE) || "''"; const code = forceString(element0)[0] + ' + ' + forceString(element1)[0]; return [code, Order.ADDITION]; } default: { - const elements = new Array(block.itemCount_); - for (let i = 0; i < block.itemCount_; i++) { - elements[i] = generator.valueToCode(block, 'ADD' + i, + const elements = new Array(joinBlock.itemCount_); + for (let i = 0; i < joinBlock.itemCount_; i++) { + elements[i] = generator.valueToCode(joinBlock, 'ADD' + i, Order.NONE) || "''"; } const code = '[' + elements.join(',') + '].join(\'\')'; @@ -184,9 +186,17 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) { }; export function text_getSubstring(block: Block, generator: JavascriptGenerator): [string, Order] { + // Dictionary of WHEREn field choices and their CamelCase equivalents. */ + const wherePascalCase = { + 'FIRST': 'First', + 'LAST': 'Last', + 'FROM_START': 'FromStart', + 'FROM_END': 'FromEnd', + }; + type WhereOption = keyof typeof wherePascalCase; // Get substring. - const where1 = block.getFieldValue('WHERE1'); - const where2 = block.getFieldValue('WHERE2'); + const where1 = block.getFieldValue('WHERE1') as WhereOption; + const where2 = block.getFieldValue('WHERE2') as WhereOption; const requiresLengthCall = (where1 !== 'FROM_END' && where1 !== 'LAST' && where2 !== 'FROM_END' && where2 !== 'LAST'); const textOrder = requiresLengthCall ? Order.MEMBER : @@ -236,8 +246,6 @@ export function text_getSubstring(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 = @@ -269,7 +277,8 @@ export function text_changeCase(block: Block, generator: JavascriptGenerator): [ 'LOWERCASE': '.toLowerCase()', 'TITLECASE': null, }; - const operator = OPERATORS[block.getFieldValue('CASE')]; + type OperatorOption = keyof typeof OPERATORS; + const operator = OPERATORS[block.getFieldValue('CASE') as OperatorOption]; const textOrder = operator ? Order.MEMBER : Order.NONE; const text = generator.valueToCode(block, 'TEXT', textOrder) || "''"; @@ -298,7 +307,8 @@ export function text_trim(block: Block, generator: JavascriptGenerator): [string 'RIGHT': ".replace(/[\\s\\xa0]+$/, '')", 'BOTH': '.trim()', }; - const operator = OPERATORS[block.getFieldValue('MODE')]; + type OperatorOption = keyof typeof OPERATORS; + const operator = OPERATORS[block.getFieldValue('MODE') as OperatorOption]; const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; return [text + operator, Order.FUNCTION_CALL]; From a93dc78a089c050d34ad6682bf544077bd48a24b Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Fri, 20 Oct 2023 17:01:26 +0100 Subject: [PATCH 5/8] refactor(generators): Migrate generators/javascript.js to TypeScript The way the generator functions are added to javascriptGenerator.forBlock has been modified so that incorrect generator function signatures will cause tsc to generate a type error. --- generators/{javascript.js => javascript.ts} | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) rename generators/{javascript.js => javascript.ts} (80%) diff --git a/generators/javascript.js b/generators/javascript.ts similarity index 80% rename from generators/javascript.js rename to generators/javascript.ts index 7ff772c5bf2..e1d1a7481c9 100644 --- a/generators/javascript.js +++ b/generators/javascript.ts @@ -32,8 +32,10 @@ export * from './javascript/javascript_generator.js'; export const javascriptGenerator = new JavascriptGenerator(); // Install per-block-type generator functions: -Object.assign( - javascriptGenerator.forBlock, - colour, lists, logic, loops, math, procedures, - text, variables, variablesDynamic -); +const generators: typeof javascriptGenerator.forBlock = { + ...colour, ...lists, ...logic, ...loops, ...math, + ...procedures, ...text, ...variables, ...variablesDynamic +}; +for (const name in generators) { + javascriptGenerator.forBlock[name] = generators[name]; +} From 4be173dc9663d3f3b314cda5ed49282433869efd Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Fri, 20 Oct 2023 17:22:02 +0100 Subject: [PATCH 6/8] chore(generator): Format One block protected with // prettier-ignore to preserve careful comment formatting. Where there are repeated concatenations prettier has made a pretty mess of things, but the correct fix is probably to use template literals instead (rather than just locally disabling prettier). This has been added to the to-do list in #7600. --- .prettierignore | 5 +- generators/javascript.ts | 11 +- generators/javascript/colour.ts | 65 +++-- generators/javascript/javascript_generator.ts | 39 ++- generators/javascript/lists.ts | 266 ++++++++++------- generators/javascript/logic.ts | 116 ++++---- generators/javascript/loops.ts | 170 +++++++---- generators/javascript/math.ts | 249 +++++++++------- generators/javascript/procedures.ts | 81 +++--- generators/javascript/text.ts | 268 ++++++++++-------- generators/javascript/variables.ts | 14 +- generators/javascript/variables_dynamic.ts | 1 - 12 files changed, 775 insertions(+), 510 deletions(-) diff --git a/.prettierignore b/.prettierignore index 64dd749f1b0..9d52f19fe6a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,7 +18,6 @@ # Demos, scripts, misc /node_modules/* -/generators/* /demos/* /appengine/* /externs/* @@ -27,5 +26,5 @@ CHANGELOG.md PULL_REQUEST_TEMPLATE.md -# Don't bother formatting js blocks since we're getting rid of them -/blocks/*.js +# Don't bother formatting JavaScript files we're about to migrate: +/generators/**/*.js diff --git a/generators/javascript.ts b/generators/javascript.ts index e1d1a7481c9..079c2f793a4 100644 --- a/generators/javascript.ts +++ b/generators/javascript.ts @@ -33,8 +33,15 @@ export const javascriptGenerator = new JavascriptGenerator(); // Install per-block-type generator functions: const generators: typeof javascriptGenerator.forBlock = { - ...colour, ...lists, ...logic, ...loops, ...math, - ...procedures, ...text, ...variables, ...variablesDynamic + ...colour, + ...lists, + ...logic, + ...loops, + ...math, + ...procedures, + ...text, + ...variables, + ...variablesDynamic, }; for (const name in generators) { javascriptGenerator.forBlock[name] = generators[name]; diff --git a/generators/javascript/colour.ts b/generators/javascript/colour.ts index e459f564725..3e7089cd769 100644 --- a/generators/javascript/colour.ts +++ b/generators/javascript/colour.ts @@ -14,33 +14,44 @@ import type {Block} from '../../core/block.js'; import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; - -export function colour_picker(block: Block, generator: JavascriptGenerator): [string, Order] { +export function colour_picker( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Colour picker. const code = generator.quote_(block.getFieldValue('COLOUR')); return [code, Order.ATOMIC]; -}; +} -export function colour_random(block: Block, generator: JavascriptGenerator): [string, Order] { +export function colour_random( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Generate a random colour. - const functionName = generator.provideFunction_('colourRandom', ` + const functionName = generator.provideFunction_( + 'colourRandom', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { var num = Math.floor(Math.random() * Math.pow(2, 24)); return '#' + ('00000' + num.toString(16)).substr(-6); } -`); +`, + ); const code = functionName + '()'; return [code, Order.FUNCTION_CALL]; -}; +} -export function colour_rgb(block: Block, generator: JavascriptGenerator): [string, Order] { +export function colour_rgb( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Compose a colour from RGB components expressed as percentages. const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; - const green = - generator.valueToCode(block, 'GREEN', Order.NONE) || 0; - const blue = - generator.valueToCode(block, 'BLUE', Order.NONE) || 0; - const functionName = generator.provideFunction_('colourRgb', ` + const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; + const blue = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; + const functionName = generator.provideFunction_( + 'colourRgb', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) { r = Math.max(Math.min(Number(r), 100), 0) * 2.55; g = Math.max(Math.min(Number(g), 100), 0) * 2.55; @@ -50,20 +61,23 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) { b = ('0' + (Math.round(b) || 0).toString(16)).slice(-2); return '#' + r + g + b; } -`); +`, + ); const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function colour_blend(block: Block, generator: JavascriptGenerator): [string, Order] { +export function colour_blend( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Blend two colours together. - const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || - "'#000000'"; - const c2 = generator.valueToCode(block, 'COLOUR2', Order.NONE) || - "'#000000'"; - const ratio = - generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; - const functionName = generator.provideFunction_('colourBlend', ` + const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; + const c2 = generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; + const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; + const functionName = generator.provideFunction_( + 'colourBlend', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(c1, c2, ratio) { ratio = Math.max(Math.min(Number(ratio), 1), 0); var r1 = parseInt(c1.substring(1, 3), 16); @@ -80,7 +94,8 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(c1, c2, ratio) { b = ('0' + (b || 0).toString(16)).slice(-2); return '#' + r + g + b; } -`); +`, + ); const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; return [code, Order.FUNCTION_CALL]; -}; +} diff --git a/generators/javascript/javascript_generator.ts b/generators/javascript/javascript_generator.ts index 370558424ce..7d9d9d03e41 100644 --- a/generators/javascript/javascript_generator.ts +++ b/generators/javascript/javascript_generator.ts @@ -19,11 +19,11 @@ import {Names, NameType} from '../../core/names.js'; import type {Workspace} from '../../core/workspace.js'; import {inputTypes} from '../../core/inputs/input_types.js'; - /** * Order of operation ENUMs. * https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence */ +// prettier-ignore export enum Order { ATOMIC = 0, // 0 "" ... NEW = 1.1, // new @@ -60,7 +60,7 @@ export enum Order { YIELD = 17, // yield COMMA = 18, // , NONE = 99, // (...) -}; +} /** * JavaScript code generator class. @@ -93,7 +93,7 @@ export class JavascriptGenerator extends CodeGenerator { // a && (b && c) -> a && b && c [Order.LOGICAL_AND, Order.LOGICAL_AND], // a || (b || c) -> a || b || c - [Order.LOGICAL_OR, Order.LOGICAL_OR] + [Order.LOGICAL_OR, Order.LOGICAL_OR], ]; /** @param name Name of language generator is for. */ @@ -126,8 +126,8 @@ export class JavascriptGenerator extends CodeGenerator { // this list is trivial. This is intended to prevent users from // accidentally clobbering a built-in object or function. this.addReservedWords( - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords - 'break,case,catch,class,const,continue,debugger,default,delete,do,' + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords + 'break,case,catch,class,const,continue,debugger,default,delete,do,' + 'else,export,extends,finally,for,function,if,import,in,instanceof,' + 'new,return,super,switch,this,throw,try,typeof,var,void,' + 'while,with,yield,' + @@ -139,7 +139,7 @@ export class JavascriptGenerator extends CodeGenerator { 'arguments,' + // Everything in the current environment (835 items in Chrome, // 104 in Node). - Object.getOwnPropertyNames(globalThis).join(',') + Object.getOwnPropertyNames(globalThis).join(','), ); } @@ -166,14 +166,16 @@ export class JavascriptGenerator extends CodeGenerator { const devVarList = Variables.allDeveloperVariables(workspace); for (let i = 0; i < devVarList.length; i++) { defvars.push( - this.nameDB_.getName(devVarList[i], NameType.DEVELOPER_VARIABLE)); + this.nameDB_.getName(devVarList[i], NameType.DEVELOPER_VARIABLE), + ); } // Add user variables, but only ones that are being used. const variables = Variables.allUsedVarModels(workspace); for (let i = 0; i < variables.length; i++) { defvars.push( - this.nameDB_.getName(variables[i].getId(), NameType.VARIABLE)); + this.nameDB_.getName(variables[i].getId(), NameType.VARIABLE), + ); } // Declare all of the variables. @@ -221,10 +223,11 @@ export class JavascriptGenerator extends CodeGenerator { quote_(string: string): string { // Can't use goog.string.quote since Google's style guide recommends // JS string literals use single quotes. - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; + string = string + .replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/'/g, "\\'"); + return "'" + string + "'"; } /** @@ -237,7 +240,7 @@ export class JavascriptGenerator extends CodeGenerator { // Can't use goog.string.quote since Google's style guide recommends // JS string literals use single quotes. const lines = string.split(/\n/g).map(this.quote_); - return lines.join(' + \'\\n\' +\n'); + return lines.join(" + '\\n' +\n"); } /** @@ -276,7 +279,7 @@ export class JavascriptGenerator extends CodeGenerator { } } const nextBlock = - block.nextConnection && block.nextConnection.targetBlock(); + block.nextConnection && block.nextConnection.targetBlock(); const nextCode = thisOnly ? '' : this.blockToCode(nextBlock); return commentCode + code + nextCode; } @@ -293,7 +296,13 @@ export class JavascriptGenerator extends CodeGenerator { * @param order The highest order acting on this value. * @returns The adjusted value. */ - getAdjusted(block: Block, atId: string, delta = 0, negate = false, order = Order.NONE): string|number { + getAdjusted( + block: Block, + atId: string, + delta = 0, + negate = false, + order = Order.NONE, + ): string | number { if (block.workspace.options.oneBasedIndex) { delta--; } diff --git a/generators/javascript/lists.ts b/generators/javascript/lists.ts index 8a513ced6ff..6283f1d274b 100644 --- a/generators/javascript/lists.ts +++ b/generators/javascript/lists.ts @@ -17,28 +17,36 @@ import type {JavascriptGenerator} from './javascript_generator.js'; import {NameType} from '../../core/names.js'; import {Order} from './javascript_generator.js'; - -export function lists_create_empty(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_create_empty( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Create an empty list. return ['[]', Order.ATOMIC]; -}; +} -export function lists_create_with(block: Block, generator: JavascriptGenerator): [string, Order] { +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(createWithBlock.itemCount_); for (let i = 0; i < createWithBlock.itemCount_; i++) { - elements[i] = - generator.valueToCode(block, 'ADD' + i, Order.NONE) || - 'null'; + elements[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null'; } const code = '[' + elements.join(', ') + ']'; return [code, Order.ATOMIC]; -}; +} -export function lists_repeat(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_repeat( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Create a list with one element repeated. - const functionName = generator.provideFunction_('listsRepeat', ` + const functionName = generator.provideFunction_( + 'listsRepeat', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(value, n) { var array = []; for (var i = 0; i < n; i++) { @@ -46,56 +54,61 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(value, n) { } return array; } -`); - const element = - generator.valueToCode(block, 'ITEM', Order.NONE) || 'null'; - const repeatCount = - generator.valueToCode(block, 'NUM', Order.NONE) || '0'; +`, + ); + const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'null'; + const repeatCount = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; const code = functionName + '(' + element + ', ' + repeatCount + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function lists_length(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_length( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // String or array length. - const list = - generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; + const list = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; return [list + '.length', Order.MEMBER]; -}; +} -export function lists_isEmpty(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_isEmpty( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Is the string null or array empty? - const list = - generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; + const list = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; return ['!' + list + '.length', Order.LOGICAL_NOT]; -}; +} -export function lists_indexOf(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_indexOf( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Find an item in the list. const operator = - block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; - const item = - generator.valueToCode(block, 'FIND', Order.NONE) || "''"; - const list = - generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; + block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; + const item = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; + const list = generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; const code = list + '.' + operator + '(' + item + ')'; if (block.workspace.options.oneBasedIndex) { return [code + ' + 1', Order.ADDITION]; } return [code, Order.FUNCTION_CALL]; -}; +} -export function lists_getIndex(block: Block, generator: JavascriptGenerator): [string, Order] | string { +export function lists_getIndex( + block: Block, + generator: JavascriptGenerator, +): [string, Order] | string { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; - const listOrder = - (where === 'RANDOM') ? Order.NONE : Order.MEMBER; - const list = - generator.valueToCode(block, 'VALUE', listOrder) || '[]'; + const listOrder = where === 'RANDOM' ? Order.NONE : Order.MEMBER; + const list = generator.valueToCode(block, 'VALUE', listOrder) || '[]'; switch (where) { - case ('FIRST'): + case 'FIRST': if (mode === 'GET') { const code = list + '[0]'; return [code, Order.MEMBER]; @@ -106,7 +119,7 @@ export function lists_getIndex(block: Block, generator: JavascriptGenerator): [s return list + '.shift();\n'; } break; - case ('LAST'): + case 'LAST': if (mode === 'GET') { const code = list + '.slice(-1)[0]'; return [code, Order.MEMBER]; @@ -117,7 +130,7 @@ export function lists_getIndex(block: Block, generator: JavascriptGenerator): [s return list + '.pop();\n'; } break; - case ('FROM_START'): { + case 'FROM_START': { const at = generator.getAdjusted(block, 'AT'); if (mode === 'GET') { const code = list + '[' + at + ']'; @@ -130,7 +143,7 @@ export function lists_getIndex(block: Block, generator: JavascriptGenerator): [s } break; } - case ('FROM_END'): { + case 'FROM_END': { const at = generator.getAdjusted(block, 'AT', 1, true); if (mode === 'GET') { const code = list + '.slice(' + at + ')[0]'; @@ -143,9 +156,10 @@ export function lists_getIndex(block: Block, generator: JavascriptGenerator): [s } break; } - case ('RANDOM'): { - const functionName = - generator.provideFunction_('listsGetRandomItem', ` + case 'RANDOM': { + const functionName = generator.provideFunction_( + 'listsGetRandomItem', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { var x = Math.floor(Math.random() * list.length); if (remove) { @@ -154,7 +168,8 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { return list[x]; } } -`); +`, + ); const code = functionName + '(' + list + ', ' + (mode !== 'GET') + ')'; if (mode === 'GET' || mode === 'GET_REMOVE') { return [code, Order.FUNCTION_CALL]; @@ -165,40 +180,38 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { } } throw Error('Unhandled combination (lists_getIndex).'); -}; +} export function lists_setIndex(block: Block, generator: JavascriptGenerator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. - let list = - generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; + let list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; - const value = - generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || - 'null'; + const value = generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || 'null'; // Cache non-trivial values to variables to prevent repeated look-ups. // Closure, which accesses and modifies 'list'. function cacheList() { if (list.match(/^\w+$/)) { return ''; } - const listVar = - generator.nameDB_!.getDistinctName( - 'tmpList', NameType.VARIABLE)!; + const listVar = generator.nameDB_!.getDistinctName( + 'tmpList', + NameType.VARIABLE, + )!; const code = 'var ' + listVar + ' = ' + list + ';\n'; list = listVar; return code; } switch (where) { - case ('FIRST'): + case 'FIRST': if (mode === 'SET') { return list + '[0] = ' + value + ';\n'; } else if (mode === 'INSERT') { return list + '.unshift(' + value + ');\n'; } break; - case ('LAST'): + case 'LAST': if (mode === 'SET') { let code = cacheList(); code += list + '[' + list + '.length - 1] = ' + value + ';\n'; @@ -207,7 +220,7 @@ export function lists_setIndex(block: Block, generator: JavascriptGenerator) { return list + '.push(' + value + ');\n'; } break; - case ('FROM_START'): { + case 'FROM_START': { const at = generator.getAdjusted(block, 'AT'); if (mode === 'SET') { return list + '[' + at + '] = ' + value + ';\n'; @@ -216,27 +229,40 @@ export function lists_setIndex(block: Block, generator: JavascriptGenerator) { } break; } - case ('FROM_END'): { + case 'FROM_END': { const at = generator.getAdjusted( - block, 'AT', 1, false, Order.SUBTRACTION); + block, + 'AT', + 1, + false, + Order.SUBTRACTION, + ); let code = cacheList(); if (mode === 'SET') { code += list + '[' + list + '.length - ' + at + '] = ' + value + ';\n'; return code; } else if (mode === 'INSERT') { - code += list + '.splice(' + list + '.length - ' + at + ', 0, ' + value + - ');\n'; + code += + list + + '.splice(' + + list + + '.length - ' + + at + + ', 0, ' + + value + + ');\n'; return code; } break; } - case ('RANDOM'): { + case 'RANDOM': { let code = cacheList(); - const xVar = - generator.nameDB_!.getDistinctName( - 'tmpX', NameType.VARIABLE); - code += 'var ' + xVar + ' = Math.floor(Math.random() * ' + list + - '.length);\n'; + const xVar = generator.nameDB_!.getDistinctName( + 'tmpX', + NameType.VARIABLE, + ); + code += + 'var ' + xVar + ' = Math.floor(Math.random() * ' + list + '.length);\n'; if (mode === 'SET') { code += list + '[' + xVar + '] = ' + value + ';\n'; return code; @@ -248,7 +274,7 @@ export function lists_setIndex(block: Block, generator: JavascriptGenerator) { } } throw Error('Unhandled combination (lists_setIndex).'); -}; +} /** * Returns an expression calculating the index into a list. @@ -257,7 +283,11 @@ export function lists_setIndex(block: Block, generator: JavascriptGenerator) { * @param opt_at The optional offset when indexing from start/end. * @returns Index expression. */ -const getSubstringIndex = function(listName: string, where: string, opt_at?: string): string | undefined { +const getSubstringIndex = function ( + listName: string, + where: string, + opt_at?: string, +): string | undefined { if (where === 'FIRST') { return '0'; } else if (where === 'FROM_END') { @@ -269,7 +299,10 @@ const getSubstringIndex = function(listName: string, where: string, opt_at?: str } }; -export function lists_getSublist(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_getSublist( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Dictionary of WHEREn field choices and their CamelCase equivalents. const wherePascalCase = { 'FIRST': 'First', @@ -279,16 +312,16 @@ export function lists_getSublist(block: Block, generator: JavascriptGenerator): }; type WhereOption = keyof typeof wherePascalCase; // Get sublist. - const list = - generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; + const list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; 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)'; } else if ( - list.match(/^\w+$/) || - (where1 !== 'FROM_END' && where2 === 'FROM_START')) { + list.match(/^\w+$/) || + (where1 !== 'FROM_END' && where2 === 'FROM_START') + ) { // If the list is a variable or doesn't require a call for length, don't // generate a helper function. let at1; @@ -297,8 +330,7 @@ export function lists_getSublist(block: Block, generator: JavascriptGenerator): at1 = generator.getAdjusted(block, 'AT1'); break; case 'FROM_END': - at1 = generator.getAdjusted( - block, 'AT1', 1, false, Order.SUBTRACTION); + at1 = generator.getAdjusted(block, 'AT1', 1, false, Order.SUBTRACTION); at1 = list + '.length - ' + at1; break; case 'FIRST': @@ -313,8 +345,7 @@ export function lists_getSublist(block: Block, generator: JavascriptGenerator): at2 = generator.getAdjusted(block, 'AT2', 1); break; case 'FROM_END': - at2 = generator.getAdjusted( - block, 'AT2', 0, false, Order.SUBTRACTION); + at2 = generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION); at2 = list + '.length - ' + at2; break; case 'LAST': @@ -330,36 +361,46 @@ export function lists_getSublist(block: Block, generator: JavascriptGenerator): // The value for 'FROM_END' and'FROM_START' depends on `at` so // we add it as a parameter. const at1Param = - (where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : ''; + where1 === 'FROM_END' || where1 === 'FROM_START' ? ', at1' : ''; const at2Param = - (where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : ''; + where2 === 'FROM_END' || where2 === 'FROM_START' ? ', at2' : ''; const functionName = generator.provideFunction_( - 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) { + 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], + ` +function ${ + generator.FUNCTION_NAME_PLACEHOLDER_ + }(sequence${at1Param}${at2Param}) { var start = ${getSubstringIndex('sequence', where1, 'at1')}; var end = ${getSubstringIndex('sequence', where2, 'at2')} + 1; return sequence.slice(start, end); } -`); - code = functionName + '(' + list + - // The value for 'FROM_END' and 'FROM_START' depends on `at` so we - // pass it. - ((where1 === 'FROM_END' || where1 === 'FROM_START') ? ', ' + at1 : '') + - ((where2 === 'FROM_END' || where2 === 'FROM_START') ? ', ' + at2 : '') + - ')'; +`, + ); + code = + functionName + + '(' + + list + + // The value for 'FROM_END' and 'FROM_START' depends on `at` so we + // pass it. + (where1 === 'FROM_END' || where1 === 'FROM_START' ? ', ' + at1 : '') + + (where2 === 'FROM_END' || where2 === 'FROM_START' ? ', ' + at2 : '') + + ')'; } return [code, Order.FUNCTION_CALL]; -}; +} -export function lists_sort(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_sort( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Block for sorting a list. const list = - generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || - '[]'; + generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || '[]'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const type = block.getFieldValue('TYPE'); - const getCompareFunctionName = - generator.provideFunction_('listsGetSortCompare', ` + const getCompareFunctionName = generator.provideFunction_( + 'listsGetSortCompare', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(type, direction) { var compareFuncs = { 'NUMERIC': function(a, b) { @@ -372,19 +413,28 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(type, direction) { var compare = compareFuncs[type]; return function(a, b) { return compare(a, b) * direction; }; } - `); + `, + ); return [ - list + '.slice().sort(' + getCompareFunctionName + '("' + type + '", ' + - direction + '))', - Order.FUNCTION_CALL + list + + '.slice().sort(' + + getCompareFunctionName + + '("' + + type + + '", ' + + direction + + '))', + Order.FUNCTION_CALL, ]; -}; +} -export function lists_split(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_split( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Block for splitting text into a list, or joining a list into text. let input = generator.valueToCode(block, 'INPUT', Order.MEMBER); - const delimiter = - generator.valueToCode(block, 'DELIM', Order.NONE) || "''"; + const delimiter = generator.valueToCode(block, 'DELIM', Order.NONE) || "''"; const mode = block.getFieldValue('MODE'); let functionName; if (mode === 'SPLIT') { @@ -402,13 +452,15 @@ export function lists_split(block: Block, generator: JavascriptGenerator): [stri } const code = input + '.' + functionName + '(' + delimiter + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function lists_reverse(block: Block, generator: JavascriptGenerator): [string, Order] { +export function lists_reverse( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Block for reversing a list. const list = - generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || - '[]'; + generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || '[]'; const code = list + '.slice().reverse()'; return [code, Order.FUNCTION_CALL]; -}; +} diff --git a/generators/javascript/logic.ts b/generators/javascript/logic.ts index fe90b1da109..36604ea7d12 100644 --- a/generators/javascript/logic.ts +++ b/generators/javascript/logic.ts @@ -14,69 +14,81 @@ import type {Block} from '../../core/block.js'; import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; - export function controls_if(block: Block, generator: JavascriptGenerator) { // If/elseif/else condition. let n = 0; let code = ''; if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - code += generator.injectId( - generator.STATEMENT_PREFIX, block); + code += generator.injectId(generator.STATEMENT_PREFIX, block); } do { const conditionCode = - generator.valueToCode(block, 'IF' + n, Order.NONE) || - 'false'; + generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false'; let branchCode = generator.statementToCode(block, 'DO' + n); if (generator.STATEMENT_SUFFIX) { - branchCode = generator.prefixLines( - generator.injectId( - generator.STATEMENT_SUFFIX, block), - generator.INDENT) + - branchCode; + branchCode = + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT, + ) + branchCode; } - code += (n > 0 ? ' else ' : '') + 'if (' + conditionCode + ') {\n' + - branchCode + '}'; + code += + (n > 0 ? ' else ' : '') + + 'if (' + + conditionCode + + ') {\n' + + branchCode + + '}'; n++; } while (block.getInput('IF' + n)); if (block.getInput('ELSE') || generator.STATEMENT_SUFFIX) { let branchCode = generator.statementToCode(block, 'ELSE'); if (generator.STATEMENT_SUFFIX) { - branchCode = generator.prefixLines( - generator.injectId( - generator.STATEMENT_SUFFIX, block), - generator.INDENT) + - branchCode; + branchCode = + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT, + ) + branchCode; } code += ' else {\n' + branchCode + '}'; } return code + '\n'; -}; +} export const controls_ifelse = controls_if; -export function logic_compare(block: Block, generator: JavascriptGenerator): [string, Order] { +export function logic_compare( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Dictionary of OP comparison operators and their implementations. - const OPERATORS = - {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; + const OPERATORS = { + 'EQ': '==', + 'NEQ': '!=', + 'LT': '<', + 'LTE': '<=', + 'GT': '>', + 'GTE': '>=', + }; type OperatorOption = keyof typeof OPERATORS; const operator = OPERATORS[block.getFieldValue('OP') as OperatorOption]; - const order = (operator === '==' || operator === '!=') ? - Order.EQUALITY : - Order.RELATIONAL; + const order = + operator === '==' || operator === '!=' ? Order.EQUALITY : Order.RELATIONAL; const argument0 = generator.valueToCode(block, 'A', order) || '0'; const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + ' ' + operator + ' ' + argument1; return [code, order]; -}; +} -export function logic_operation(block: Block, generator: JavascriptGenerator): [string, Order] { +export function logic_operation( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Operations 'and', 'or'. - const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; - const order = (operator === '&&') ? Order.LOGICAL_AND : - Order.LOGICAL_OR; + const operator = block.getFieldValue('OP') === 'AND' ? '&&' : '||'; + const order = operator === '&&' ? Order.LOGICAL_AND : Order.LOGICAL_OR; let argument0 = generator.valueToCode(block, 'A', order); let argument1 = generator.valueToCode(block, 'B', order); if (!argument0 && !argument1) { @@ -85,7 +97,7 @@ export function logic_operation(block: Block, generator: JavascriptGenerator): [ argument1 = 'false'; } else { // Single missing arguments have no effect on the return value. - const defaultArgument = (operator === '&&') ? 'true' : 'false'; + const defaultArgument = operator === '&&' ? 'true' : 'false'; if (!argument0) { argument0 = defaultArgument; } @@ -95,39 +107,47 @@ export function logic_operation(block: Block, generator: JavascriptGenerator): [ } const code = argument0 + ' ' + operator + ' ' + argument1; return [code, order]; -}; +} -export function logic_negate(block: Block, generator: JavascriptGenerator): [string, Order] { +export function logic_negate( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Negation. const order = Order.LOGICAL_NOT; - const argument0 = - generator.valueToCode(block, 'BOOL', order) || 'true'; + const argument0 = generator.valueToCode(block, 'BOOL', order) || 'true'; const code = '!' + argument0; return [code, order]; -}; +} -export function logic_boolean(block: Block, generator: JavascriptGenerator): [string, Order] { +export function logic_boolean( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Boolean values true and false. - const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; + const code = block.getFieldValue('BOOL') === 'TRUE' ? 'true' : 'false'; return [code, Order.ATOMIC]; -}; +} -export function logic_null(block: Block, generator: JavascriptGenerator): [string, Order] { +export function logic_null( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Null data type. return ['null', Order.ATOMIC]; -}; +} -export function logic_ternary(block: Block, generator: JavascriptGenerator): [string, Order] { +export function logic_ternary( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Ternary operator. const value_if = - generator.valueToCode(block, 'IF', Order.CONDITIONAL) || - 'false'; + generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false'; const value_then = - generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || - 'null'; + generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'null'; const value_else = - generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || - 'null'; + generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'null'; const code = value_if + ' ? ' + value_then + ' : ' + value_else; return [code, Order.CONDITIONAL]; -}; +} diff --git a/generators/javascript/loops.ts b/generators/javascript/loops.ts index 5eb5c39a748..a5dd8635731 100644 --- a/generators/javascript/loops.ts +++ b/generators/javascript/loops.ts @@ -17,8 +17,10 @@ import type {JavascriptGenerator} from './javascript_generator.js'; import {NameType} from '../../core/names.js'; import {Order} from './javascript_generator.js'; - -export function controls_repeat_ext(block: Block, generator: JavascriptGenerator) { +export function controls_repeat_ext( + block: Block, + generator: JavascriptGenerator, +) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -26,65 +28,88 @@ export function controls_repeat_ext(block: Block, generator: JavascriptGenerator repeats = String(Number(block.getFieldValue('TIMES'))); } else { // External number. - repeats = - generator.valueToCode(block, 'TIMES', Order.ASSIGNMENT) || - '0'; + repeats = generator.valueToCode(block, 'TIMES', Order.ASSIGNMENT) || '0'; } let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block); let code = ''; - const loopVar = - generator.nameDB_!.getDistinctName('count', NameType.VARIABLE); + const loopVar = generator.nameDB_!.getDistinctName( + 'count', + NameType.VARIABLE, + ); let endVar = repeats; if (!repeats.match(/^\w+$/) && !stringUtils.isNumber(repeats)) { - endVar = - generator.nameDB_!.getDistinctName( - 'repeat_end', NameType.VARIABLE); + endVar = generator.nameDB_!.getDistinctName( + 'repeat_end', + NameType.VARIABLE, + ); code += 'var ' + endVar + ' = ' + repeats + ';\n'; } - code += 'for (var ' + loopVar + ' = 0; ' + loopVar + ' < ' + endVar + '; ' + - loopVar + '++) {\n' + branch + '}\n'; + code += + 'for (var ' + + loopVar + + ' = 0; ' + + loopVar + + ' < ' + + endVar + + '; ' + + loopVar + + '++) {\n' + + branch + + '}\n'; return code; -}; +} export const controls_repeat = controls_repeat_ext; -export function controls_whileUntil(block: Block, generator: JavascriptGenerator) { +export function controls_whileUntil( + block: Block, + generator: JavascriptGenerator, +) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = - generator.valueToCode( - block, 'BOOL', - until ? Order.LOGICAL_NOT : Order.NONE) || - 'false'; + generator.valueToCode( + block, + 'BOOL', + until ? Order.LOGICAL_NOT : Order.NONE, + ) || 'false'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block); if (until) { argument0 = '!' + argument0; } return 'while (' + argument0 + ') {\n' + branch + '}\n'; -}; +} export function controls_for(block: Block, generator: JavascriptGenerator) { // For loop. - const variable0 = - generator.getVariableName( - block.getFieldValue('VAR')); + const variable0 = generator.getVariableName(block.getFieldValue('VAR')); const argument0 = - generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0'; - const argument1 = - generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0'; - const increment = - generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1'; + generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0'; + const argument1 = generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0'; + const increment = generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block); let code; - if (stringUtils.isNumber(argument0) && stringUtils.isNumber(argument1) && - stringUtils.isNumber(increment)) { + if ( + stringUtils.isNumber(argument0) && + stringUtils.isNumber(argument1) && + stringUtils.isNumber(increment) + ) { // All arguments are simple numbers. const up = Number(argument0) <= Number(argument1); - code = 'for (' + variable0 + ' = ' + argument0 + '; ' + variable0 + - (up ? ' <= ' : ' >= ') + argument1 + '; ' + variable0; + code = + 'for (' + + variable0 + + ' = ' + + argument0 + + '; ' + + variable0 + + (up ? ' <= ' : ' >= ') + + argument1 + + '; ' + + variable0; const step = Math.abs(Number(increment)); if (step === 1) { code += up ? '++' : '--'; @@ -98,19 +123,25 @@ export function controls_for(block: Block, generator: JavascriptGenerator) { let startVar = argument0; if (!argument0.match(/^\w+$/) && !stringUtils.isNumber(argument0)) { startVar = generator.nameDB_!.getDistinctName( - variable0 + '_start', NameType.VARIABLE); + variable0 + '_start', + NameType.VARIABLE, + ); code += 'var ' + startVar + ' = ' + argument0 + ';\n'; } let endVar = argument1; if (!argument1.match(/^\w+$/) && !stringUtils.isNumber(argument1)) { endVar = generator.nameDB_!.getDistinctName( - variable0 + '_end', NameType.VARIABLE); + 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( - variable0 + '_inc', NameType.VARIABLE); + variable0 + '_inc', + NameType.VARIABLE, + ); code += 'var ' + incVar + ' = '; if (stringUtils.isNumber(increment)) { code += Math.abs(Number(increment)) + ';\n'; @@ -120,21 +151,37 @@ export function controls_for(block: Block, generator: JavascriptGenerator) { code += 'if (' + startVar + ' > ' + endVar + ') {\n'; code += generator.INDENT + incVar + ' = -' + incVar + ';\n'; code += '}\n'; - code += 'for (' + variable0 + ' = ' + startVar + '; ' + incVar + - ' >= 0 ? ' + variable0 + ' <= ' + endVar + ' : ' + variable0 + - ' >= ' + endVar + '; ' + variable0 + ' += ' + incVar + ') {\n' + - branch + '}\n'; + code += + 'for (' + + variable0 + + ' = ' + + startVar + + '; ' + + incVar + + ' >= 0 ? ' + + variable0 + + ' <= ' + + endVar + + ' : ' + + variable0 + + ' >= ' + + endVar + + '; ' + + variable0 + + ' += ' + + incVar + + ') {\n' + + branch + + '}\n'; } return code; -}; +} export function controls_forEach(block: Block, generator: JavascriptGenerator) { // For each loop. - const variable0 = - generator.getVariableName(block.getFieldValue('VAR')); + const variable0 = generator.getVariableName(block.getFieldValue('VAR')); const argument0 = - generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || - '[]'; + generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || '[]'; let branch = generator.statementToCode(block, 'DO'); branch = generator.addLoopTrap(branch, block); let code = ''; @@ -142,30 +189,42 @@ export function controls_forEach(block: Block, generator: JavascriptGenerator) { let listVar = argument0; if (!argument0.match(/^\w+$/)) { listVar = generator.nameDB_!.getDistinctName( - variable0 + '_list', NameType.VARIABLE); + variable0 + '_list', + NameType.VARIABLE, + ); code += 'var ' + listVar + ' = ' + argument0 + ';\n'; } const indexVar = generator.nameDB_!.getDistinctName( - variable0 + '_index', NameType.VARIABLE); - branch = generator.INDENT + variable0 + ' = ' + listVar + - '[' + indexVar + '];\n' + branch; + variable0 + '_index', + NameType.VARIABLE, + ); + branch = + generator.INDENT + + variable0 + + ' = ' + + listVar + + '[' + + indexVar + + '];\n' + + branch; code += 'for (var ' + indexVar + ' in ' + listVar + ') {\n' + branch + '}\n'; return code; -}; +} -export function controls_flow_statements(block: Block, generator: JavascriptGenerator) { +export function controls_flow_statements( + block: Block, + generator: JavascriptGenerator, +) { // Flow statements: continue, break. let xfix = ''; if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - xfix += generator.injectId( - generator.STATEMENT_PREFIX, block); + xfix += generator.injectId(generator.STATEMENT_PREFIX, block); } if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the break/continue is triggered. - xfix += generator.injectId( - generator.STATEMENT_SUFFIX, block); + xfix += generator.injectId(generator.STATEMENT_SUFFIX, block); } if (generator.STATEMENT_PREFIX) { const loop = (block as ControlFlowInLoopBlock).getSurroundLoop(); @@ -173,8 +232,7 @@ export function controls_flow_statements(block: Block, generator: JavascriptGene // Inject loop's statement prefix here since the regular one at the end // of the loop will not get executed if 'continue' is triggered. // In the case of 'break', a prefix is needed due to the loop's suffix. - xfix += generator.injectId( - generator.STATEMENT_PREFIX, loop); + xfix += generator.injectId(generator.STATEMENT_PREFIX, loop); } } switch (block.getFieldValue('FLOW')) { @@ -184,4 +242,4 @@ export function controls_flow_statements(block: Block, generator: JavascriptGene return xfix + 'continue;\n'; } throw Error('Unknown flow statement.'); -}; +} diff --git a/generators/javascript/math.ts b/generators/javascript/math.ts index 726fd623862..c05e817cf3e 100644 --- a/generators/javascript/math.ts +++ b/generators/javascript/math.ts @@ -15,23 +15,27 @@ import type {Block} from '../../core/block.js'; import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; - -export function math_number(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_number( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Numeric value. const number = Number(block.getFieldValue('NUM')); - const order = number >= 0 ? Order.ATOMIC : - Order.UNARY_NEGATION; + const order = number >= 0 ? Order.ATOMIC : Order.UNARY_NEGATION; return [String(number), order]; -}; +} -export function math_arithmetic(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_arithmetic( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Basic arithmetic operators, and power. const OPERATORS: Record = { 'ADD': [' + ', Order.ADDITION], 'MINUS': [' - ', Order.SUBTRACTION], 'MULTIPLY': [' * ', Order.MULTIPLICATION], 'DIVIDE': [' / ', Order.DIVISION], - 'POWER': [null, Order.NONE], // Handle power separately. + 'POWER': [null, Order.NONE], // Handle power separately. }; type OperatorOption = keyof typeof OPERATORS; const tuple = OPERATORS[block.getFieldValue('OP') as OperatorOption]; @@ -47,17 +51,19 @@ export function math_arithmetic(block: Block, generator: JavascriptGenerator): [ } code = argument0 + operator + argument1; return [code, order]; -}; +} -export function math_single(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_single( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; let arg; if (operator === 'NEG') { // Negation is a special case given its different operator precedence. - arg = generator.valueToCode(block, 'NUM', - Order.UNARY_NEGATION) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.UNARY_NEGATION) || '0'; if (arg[0] === '-') { // --3 is not legal in JS. arg = ' ' + arg; @@ -66,11 +72,9 @@ export function math_single(block: Block, generator: JavascriptGenerator): [stri return [code, Order.UNARY_NEGATION]; } if (operator === 'SIN' || operator === 'COS' || operator === 'TAN') { - arg = generator.valueToCode(block, 'NUM', - Order.DIVISION) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.DIVISION) || '0'; } else { - arg = generator.valueToCode(block, 'NUM', - Order.NONE) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; } // First, handle cases which generate values that don't need parentheses // wrapping the code. @@ -131,9 +135,12 @@ export function math_single(block: Block, generator: JavascriptGenerator): [stri throw Error('Unknown math operator: ' + operator); } return [code, Order.DIVISION]; -}; +} -export function math_constant(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_constant( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS: Record = { 'PI': ['Math.PI', Order.MEMBER], @@ -143,22 +150,22 @@ export function math_constant(block: Block, generator: JavascriptGenerator): [st 'SQRT1_2': ['Math.SQRT1_2', Order.MEMBER], 'INFINITY': ['Infinity', Order.ATOMIC], }; - type ConstantOption = keyof typeof CONSTANTS + type ConstantOption = keyof typeof CONSTANTS; return CONSTANTS[block.getFieldValue('CONSTANT') as ConstantOption]; -}; +} -export function math_number_property(block: Block, generator: JavascriptGenerator): [string, Order] { +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: Record = { 'EVEN': [' % 2 === 0', Order.MODULUS, Order.EQUALITY], 'ODD': [' % 2 === 1', Order.MODULUS, Order.EQUALITY], - 'WHOLE': [' % 1 === 0', Order.MODULUS, - Order.EQUALITY], - 'POSITIVE': [' > 0', Order.RELATIONAL, - Order.RELATIONAL], - 'NEGATIVE': [' < 0', Order.RELATIONAL, - Order.RELATIONAL], + 'WHOLE': [' % 1 === 0', Order.MODULUS, Order.EQUALITY], + 'POSITIVE': [' > 0', Order.RELATIONAL, Order.RELATIONAL], + 'NEGATIVE': [' < 0', Order.RELATIONAL, Order.RELATIONAL], 'DIVISIBLE_BY': [null, Order.MODULUS, Order.EQUALITY], 'PRIME': [null, Order.NONE, Order.FUNCTION_CALL], }; @@ -166,12 +173,13 @@ export function math_number_property(block: Block, generator: JavascriptGenerato const dropdownProperty = block.getFieldValue('PROPERTY') as PropertyOption; const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; const numberToCheck = - generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || - '0'; + generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || '0'; let code; if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - const functionName = generator.provideFunction_('mathIsPrime', ` + const functionName = generator.provideFunction_( + 'mathIsPrime', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(n) { // https://en.wikipedia.org/wiki/Primality_test#Naive_methods if (n == 2 || n == 3) { @@ -190,68 +198,81 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(n) { } return true; } -`); +`, + ); code = functionName + '(' + numberToCheck + ')'; } else if (dropdownProperty === 'DIVISIBLE_BY') { - const divisor = generator.valueToCode(block, 'DIVISOR', - Order.MODULUS) || '0'; + const divisor = + generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0'; code = numberToCheck + ' % ' + divisor + ' === 0'; } else { code = numberToCheck + suffix; } return [code, outputOrder]; -}; +} export function math_change(block: Block, generator: JavascriptGenerator) { // Add to a variable in place. - const argument0 = generator.valueToCode(block, 'DELTA', - Order.ADDITION) || '0'; + const argument0 = + generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0'; const varName = generator.getVariableName(block.getFieldValue('VAR')); - return varName + ' = (typeof ' + varName + ' === \'number\' ? ' + varName + - ' : 0) + ' + argument0 + ';\n'; -}; + return ( + varName + + ' = (typeof ' + + varName + + " === 'number' ? " + + varName + + ' : 0) + ' + + argument0 + + ';\n' + ); +} // Rounding functions have a single operand. export const math_round = math_single; // Trigonometry functions have a single operand. export const math_trig = math_single; -export function math_on_list(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_on_list( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Math functions for lists. const func = block.getFieldValue('OP'); let list; let code; switch (func) { case 'SUM': - list = generator.valueToCode(block, 'LIST', - Order.MEMBER) || '[]'; + list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; code = list + '.reduce(function(x, y) {return x + y;}, 0)'; break; case 'MIN': - list = generator.valueToCode(block, 'LIST', - Order.NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = 'Math.min.apply(null, ' + list + ')'; break; case 'MAX': - list = generator.valueToCode(block, 'LIST', - Order.NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = 'Math.max.apply(null, ' + list + ')'; break; case 'AVERAGE': { // mathMean([null,null,1,3]) === 2.0. - const functionName = generator.provideFunction_('mathMean', ` + const functionName = generator.provideFunction_( + 'mathMean', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList) { return myList.reduce(function(x, y) {return x + y;}, 0) / myList.length; } -`); - list = generator.valueToCode(block, 'LIST', - Order.NONE) || '[]'; +`, + ); + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'MEDIAN': { // mathMedian([null,null,1,3]) === 2.0. - const functionName = generator.provideFunction_('mathMedian', ` + const functionName = generator.provideFunction_( + 'mathMedian', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList) { var localList = myList.filter(function (x) {return typeof x === 'number';}); if (!localList.length) return null; @@ -262,9 +283,9 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList) { return localList[(localList.length - 1) / 2]; } } -`); - list = generator.valueToCode(block, 'LIST', - Order.NONE) || '[]'; +`, + ); + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } @@ -272,7 +293,9 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList) { // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]. - const functionName = generator.provideFunction_('mathModes', ` + const functionName = generator.provideFunction_( + 'mathModes', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(values) { var modes = []; var counts = []; @@ -301,15 +324,16 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(values) { } return modes; } -`); - list = generator.valueToCode(block, 'LIST', - Order.NONE) || '[]'; +`, + ); + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'STD_DEV': { - const functionName = - generator.provideFunction_('mathStandardDeviation', ` + const functionName = generator.provideFunction_( + 'mathStandardDeviation', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(numbers) { var n = numbers.length; if (!n) return null; @@ -321,22 +345,23 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(numbers) { variance = variance / n; return Math.sqrt(variance); } -`); - list = generator.valueToCode(block, 'LIST', - Order.NONE) || '[]'; +`, + ); + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'RANDOM': { - const functionName = - generator.provideFunction_('mathRandomList', ` + const functionName = generator.provideFunction_( + 'mathRandomList', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list) { var x = Math.floor(Math.random() * list.length); return list[x]; } -`); - list = generator.valueToCode(block, 'LIST', - Order.NONE) || '[]'; +`, + ); + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } @@ -344,38 +369,51 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list) { throw Error('Unknown operator: ' + func); } return [code, Order.FUNCTION_CALL]; -}; +} -export function math_modulo(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_modulo( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Remainder computation. - const argument0 = generator.valueToCode(block, 'DIVIDEND', - Order.MODULUS) || '0'; - const argument1 = generator.valueToCode(block, 'DIVISOR', - Order.MODULUS) || '0'; + const argument0 = + generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0'; + const argument1 = + generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0'; const code = argument0 + ' % ' + argument1; return [code, Order.MODULUS]; -}; +} -export function math_constrain(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_constrain( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Constrain a number between two limits. - const argument0 = generator.valueToCode(block, 'VALUE', - Order.NONE) || '0'; - const argument1 = generator.valueToCode(block, 'LOW', - Order.NONE) || '0'; - const argument2 = generator.valueToCode(block, 'HIGH', - Order.NONE) || 'Infinity'; - const code = 'Math.min(Math.max(' + argument0 + ', ' + argument1 + '), ' + - argument2 + ')'; + const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'LOW', Order.NONE) || '0'; + const argument2 = + generator.valueToCode(block, 'HIGH', Order.NONE) || 'Infinity'; + const code = + 'Math.min(Math.max(' + + argument0 + + ', ' + + argument1 + + '), ' + + argument2 + + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function math_random_int(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_random_int( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Random integer between [X] and [Y]. - const argument0 = generator.valueToCode(block, 'FROM', - Order.NONE) || '0'; - const argument1 = generator.valueToCode(block, 'TO', - Order.NONE) || '0'; - const functionName = generator.provideFunction_('mathRandomInt', ` + const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; + const functionName = generator.provideFunction_( + 'mathRandomInt', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(a, b) { if (a > b) { // Swap a and b to ensure a is smaller. @@ -385,22 +423,29 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(a, b) { } return Math.floor(Math.random() * (b - a + 1) + a); } -`); +`, + ); const code = functionName + '(' + argument0 + ', ' + argument1 + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function math_random_float(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_random_float( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Random fraction between 0 and 1. return ['Math.random()', Order.FUNCTION_CALL]; -}; +} -export function math_atan2(block: Block, generator: JavascriptGenerator): [string, Order] { +export function math_atan2( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Arctangent of point (X, Y) in degrees from -180 to 180. - const argument0 = generator.valueToCode(block, 'X', - Order.NONE) || '0'; - const argument1 = generator.valueToCode(block, 'Y', - Order.NONE) || '0'; - return ['Math.atan2(' + argument1 + ', ' + argument0 + ') / Math.PI * 180', - Order.DIVISION]; -}; + const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; + return [ + 'Math.atan2(' + argument1 + ', ' + argument0 + ') / Math.PI * 180', + Order.DIVISION, + ]; +} diff --git a/generators/javascript/procedures.ts b/generators/javascript/procedures.ts index f4e656e371b..f0b5c94f09a 100644 --- a/generators/javascript/procedures.ts +++ b/generators/javascript/procedures.ts @@ -15,18 +15,18 @@ import type {IfReturnBlock} from '../../blocks/procedures.js'; import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; - -export function procedures_defreturn(block: Block, generator: JavascriptGenerator) { +export function procedures_defreturn( + block: Block, + generator: JavascriptGenerator, +) { // Define a procedure with a return value. const funcName = generator.getProcedureName(block.getFieldValue('NAME')); let xfix1 = ''; if (generator.STATEMENT_PREFIX) { - xfix1 += generator.injectId( - generator.STATEMENT_PREFIX, block); + xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block); } if (generator.STATEMENT_SUFFIX) { - xfix1 += generator.injectId( - generator.STATEMENT_SUFFIX, block); + xfix1 += generator.injectId(generator.STATEMENT_SUFFIX, block); } if (xfix1) { xfix1 = generator.prefixLines(xfix1, generator.INDENT); @@ -34,13 +34,12 @@ export function procedures_defreturn(block: Block, generator: JavascriptGenerato let loopTrap = ''; if (generator.INFINITE_LOOP_TRAP) { loopTrap = generator.prefixLines( - generator.injectId( - generator.INFINITE_LOOP_TRAP, block), - generator.INDENT); + generator.injectId(generator.INFINITE_LOOP_TRAP, block), + generator.INDENT, + ); } const branch = generator.statementToCode(block, 'STACK'); - let returnValue = - generator.valueToCode(block, 'RETURN', Order.NONE) || ''; + let returnValue = generator.valueToCode(block, 'RETURN', Order.NONE) || ''; let xfix2 = ''; if (branch && returnValue) { // After executing the function body, revisit this block for the return. @@ -52,65 +51,83 @@ export function procedures_defreturn(block: Block, generator: JavascriptGenerato const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = - generator.getVariableName(variables[i]); + args[i] = generator.getVariableName(variables[i]); } - let code = 'function ' + funcName + '(' + args.join(', ') + ') {\n' + xfix1 + - loopTrap + branch + xfix2 + returnValue + '}'; + let code = + 'function ' + + funcName + + '(' + + args.join(', ') + + ') {\n' + + xfix1 + + loopTrap + + branch + + xfix2 + + returnValue + + '}'; code = generator.scrub_(block, code); // Add % so as not to collide with helper functions in definitions list. // TODO(#7600): find better approach than casting to any to override // CodeGenerator declaring .definitions protected. (generator as AnyDuringMigration).definitions_['%' + funcName] = code; return null; -}; +} // Defining a procedure without a return value uses the same generator as // a procedure with a return value. export const procedures_defnoreturn = procedures_defreturn; -export function procedures_callreturn(block: Block, generator: JavascriptGenerator): [string, Order] { +export function procedures_callreturn( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Call a procedure with a return value. const funcName = generator.getProcedureName(block.getFieldValue('NAME')); const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = generator.valueToCode(block, 'ARG' + i, Order.NONE) || - 'null'; + args[i] = generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'null'; } const code = funcName + '(' + args.join(', ') + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function procedures_callnoreturn(block: Block, generator: JavascriptGenerator) { +export function procedures_callnoreturn( + block: Block, + generator: JavascriptGenerator, +) { // 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) as [string, Order]; + const tuple = generator.forBlock['procedures_callreturn']( + block, + generator, + ) as [string, Order]; return tuple[0] + ';\n'; -}; +} -export function procedures_ifreturn(block: Block, generator: JavascriptGenerator) { +export function procedures_ifreturn( + block: Block, + generator: JavascriptGenerator, +) { // Conditionally return value from a procedure. const condition = - generator.valueToCode(block, 'CONDITION', Order.NONE) || - 'false'; + generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; let code = 'if (' + condition + ') {\n'; if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the return is triggered. code += generator.prefixLines( - generator.injectId( - generator.STATEMENT_SUFFIX, block), - generator.INDENT); + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT, + ); } if ((block as IfReturnBlock).hasReturnValue_) { - const value = - generator.valueToCode(block, 'VALUE', Order.NONE) || 'null'; + const value = generator.valueToCode(block, 'VALUE', Order.NONE) || 'null'; code += generator.INDENT + 'return ' + value + ';\n'; } else { code += generator.INDENT + 'return;\n'; } code += '}\n'; return code; -}; +} diff --git a/generators/javascript/text.ts b/generators/javascript/text.ts index a3bf9bef468..79008cba9a0 100644 --- a/generators/javascript/text.ts +++ b/generators/javascript/text.ts @@ -15,7 +15,6 @@ import type {JoinMutatorBlock} from '../../blocks/text.js'; import type {JavascriptGenerator} from './javascript_generator.js'; import {Order} from './javascript_generator.js'; - /** * Regular expression to detect a single-quoted string literal. */ @@ -28,7 +27,7 @@ const strRegExp = /^\s*'([^']|\\')*'\s*$/; * @returns Array containing code evaluating to a string * and the order of the returned code.[string, number] */ -const forceString = function(value: string): [string, Order] { +const forceString = function (value: string): [string, Order] { if (strRegExp.test(value)) { return [value, Order.ATOMIC]; } @@ -42,7 +41,11 @@ const forceString = function(value: string): [string, Order] { * @param opt_at The optional offset when indexing from start/end. * @returns Index expression. */ -const getSubstringIndex = function(stringName: string, where: string, opt_at?: string): string | undefined { +const getSubstringIndex = function ( + stringName: string, + where: string, + opt_at?: string, +): string | undefined { if (where === 'FIRST') { return '0'; } else if (where === 'FROM_END') { @@ -54,102 +57,112 @@ const getSubstringIndex = function(stringName: string, where: string, opt_at?: s } }; -export function text(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Text value. const code = generator.quote_(block.getFieldValue('TEXT')); return [code, Order.ATOMIC]; -}; +} -export function text_multiline(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_multiline( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Text value. - const code = - generator.multiline_quote_(block.getFieldValue('TEXT')); - const order = code.indexOf('+') !== -1 ? Order.ADDITION : - Order.ATOMIC; + const code = generator.multiline_quote_(block.getFieldValue('TEXT')); + const order = code.indexOf('+') !== -1 ? Order.ADDITION : Order.ATOMIC; return [code, order]; -}; +} -export function text_join(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_join( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { const joinBlock = block as JoinMutatorBlock; // Create a string made up of any number of elements of any type. switch (joinBlock.itemCount_) { case 0: return ["''", Order.ATOMIC]; case 1: { - const element = generator.valueToCode(joinBlock, 'ADD0', - Order.NONE) || "''"; + const element = + generator.valueToCode(joinBlock, 'ADD0', Order.NONE) || "''"; const codeAndOrder = forceString(element); return codeAndOrder; } case 2: { - const element0 = generator.valueToCode(joinBlock, 'ADD0', - Order.NONE) || "''"; - const element1 = generator.valueToCode(joinBlock, 'ADD1', - Order.NONE) || "''"; - const code = forceString(element0)[0] + - ' + ' + forceString(element1)[0]; + const element0 = + generator.valueToCode(joinBlock, 'ADD0', Order.NONE) || "''"; + const element1 = + generator.valueToCode(joinBlock, 'ADD1', Order.NONE) || "''"; + const code = forceString(element0)[0] + ' + ' + forceString(element1)[0]; return [code, Order.ADDITION]; } default: { const elements = new Array(joinBlock.itemCount_); for (let i = 0; i < joinBlock.itemCount_; i++) { - elements[i] = generator.valueToCode(joinBlock, 'ADD' + i, - Order.NONE) || "''"; + elements[i] = + generator.valueToCode(joinBlock, 'ADD' + i, Order.NONE) || "''"; } - const code = '[' + elements.join(',') + '].join(\'\')'; + const code = '[' + elements.join(',') + "].join('')"; return [code, Order.FUNCTION_CALL]; } } -}; +} export function text_append(block: Block, generator: JavascriptGenerator) { // Append to a variable in place. const varName = generator.getVariableName(block.getFieldValue('VAR')); - const value = generator.valueToCode(block, 'TEXT', - Order.NONE) || "''"; - const code = varName + ' += ' + - forceString(value)[0] + ';\n'; + const value = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const code = varName + ' += ' + forceString(value)[0] + ';\n'; return code; -}; +} -export function text_length(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_length( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // String or array length. - const text = generator.valueToCode(block, 'VALUE', - Order.MEMBER) || "''"; + const text = generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; return [text + '.length', Order.MEMBER]; -}; +} -export function text_isEmpty(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_isEmpty( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Is the string null or array empty? - const text = generator.valueToCode(block, 'VALUE', - Order.MEMBER) || "''"; + const text = generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; return ['!' + text + '.length', Order.LOGICAL_NOT]; -}; +} -export function text_indexOf(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_indexOf( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Search the text for a substring. - const operator = block.getFieldValue('END') === 'FIRST' ? - 'indexOf' : 'lastIndexOf'; - const substring = generator.valueToCode(block, 'FIND', - Order.NONE) || "''"; - const text = generator.valueToCode(block, 'VALUE', - Order.MEMBER) || "''"; + const operator = + block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; + const substring = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; + const text = generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; const code = text + '.' + operator + '(' + substring + ')'; // Adjust index if using one-based indices. if (block.workspace.options.oneBasedIndex) { return [code + ' + 1', Order.ADDITION]; } return [code, Order.FUNCTION_CALL]; -}; +} -export function text_charAt(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_charAt( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; - const textOrder = (where === 'RANDOM') ? Order.NONE : - Order.MEMBER; - const text = - generator.valueToCode(block, 'VALUE', textOrder) || "''"; + const textOrder = where === 'RANDOM' ? Order.NONE : Order.MEMBER; + const text = generator.valueToCode(block, 'VALUE', textOrder) || "''"; switch (where) { case 'FIRST': { const code = text + '.charAt(0)'; @@ -171,21 +184,26 @@ export function text_charAt(block: Block, generator: JavascriptGenerator): [stri return [code, Order.FUNCTION_CALL]; } case 'RANDOM': { - const functionName = - generator.provideFunction_('textRandomLetter', ` + const functionName = generator.provideFunction_( + 'textRandomLetter', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) { var x = Math.floor(Math.random() * text.length); return text[x]; } -`); +`, + ); const code = functionName + '(' + text + ')'; return [code, Order.FUNCTION_CALL]; } } throw Error('Unhandled option (text_charAt).'); -}; +} -export function text_getSubstring(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_getSubstring( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Dictionary of WHEREn field choices and their CamelCase equivalents. */ const wherePascalCase = { 'FIRST': 'First', @@ -197,12 +215,13 @@ export function text_getSubstring(block: Block, generator: JavascriptGenerator): // Get substring. const where1 = block.getFieldValue('WHERE1') as WhereOption; const where2 = block.getFieldValue('WHERE2') as WhereOption; - const requiresLengthCall = (where1 !== 'FROM_END' && where1 !== 'LAST' && - where2 !== 'FROM_END' && where2 !== 'LAST'); - const textOrder = requiresLengthCall ? Order.MEMBER : - Order.NONE; - const text = - generator.valueToCode(block, 'STRING', textOrder) || "''"; + const requiresLengthCall = + where1 !== 'FROM_END' && + where1 !== 'LAST' && + where2 !== 'FROM_END' && + where2 !== 'LAST'; + const textOrder = requiresLengthCall ? Order.MEMBER : Order.NONE; + const text = generator.valueToCode(block, 'STRING', textOrder) || "''"; let code; if (where1 === 'FIRST' && where2 === 'LAST') { code = text; @@ -216,8 +235,7 @@ export function text_getSubstring(block: Block, generator: JavascriptGenerator): at1 = generator.getAdjusted(block, 'AT1'); break; case 'FROM_END': - at1 = generator.getAdjusted(block, 'AT1', 1, false, - Order.SUBTRACTION); + at1 = generator.getAdjusted(block, 'AT1', 1, false, Order.SUBTRACTION); at1 = text + '.length - ' + at1; break; case 'FIRST': @@ -232,8 +250,7 @@ export function text_getSubstring(block: Block, generator: JavascriptGenerator): at2 = generator.getAdjusted(block, 'AT2', 1); break; case 'FROM_END': - at2 = generator.getAdjusted(block, 'AT2', 0, false, - Order.SUBTRACTION); + at2 = generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION); at2 = text + '.length - ' + at2; break; case 'LAST': @@ -249,28 +266,38 @@ export function text_getSubstring(block: Block, generator: JavascriptGenerator): // The value for 'FROM_END' and'FROM_START' depends on `at` so // we add it as a parameter. const at1Param = - (where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : ''; + where1 === 'FROM_END' || where1 === 'FROM_START' ? ', at1' : ''; const at2Param = - (where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : ''; + where2 === 'FROM_END' || where2 === 'FROM_START' ? ', at2' : ''; const functionName = generator.provideFunction_( - 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) { + 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], + ` +function ${ + generator.FUNCTION_NAME_PLACEHOLDER_ + }(sequence${at1Param}${at2Param}) { var start = ${getSubstringIndex('sequence', where1, 'at1')}; var end = ${getSubstringIndex('sequence', where2, 'at2')} + 1; return sequence.slice(start, end); } -`); - code = functionName + '(' + text + - // The value for 'FROM_END' and 'FROM_START' depends on `at` so we - // pass it. - ((where1 === 'FROM_END' || where1 === 'FROM_START') ? ', ' + at1 : '') + - ((where2 === 'FROM_END' || where2 === 'FROM_START') ? ', ' + at2 : '') + - ')'; +`, + ); + code = + functionName + + '(' + + text + + // The value for 'FROM_END' and 'FROM_START' depends on `at` so we + // pass it. + (where1 === 'FROM_END' || where1 === 'FROM_START' ? ', ' + at1 : '') + + (where2 === 'FROM_END' || where2 === 'FROM_START' ? ', ' + at2 : '') + + ')'; } return [code, Order.FUNCTION_CALL]; -}; +} -export function text_changeCase(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_changeCase( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.toUpperCase()', @@ -280,27 +307,31 @@ export function text_changeCase(block: Block, generator: JavascriptGenerator): [ type OperatorOption = keyof typeof OPERATORS; const operator = OPERATORS[block.getFieldValue('CASE') as OperatorOption]; const textOrder = operator ? Order.MEMBER : Order.NONE; - const text = - generator.valueToCode(block, 'TEXT', textOrder) || "''"; + const text = generator.valueToCode(block, 'TEXT', textOrder) || "''"; let code; if (operator) { // Upper and lower case are functions built into generator. code = text + operator; } else { // Title case is not a native JavaScript function. Define one. - const functionName = - generator.provideFunction_('textToTitleCase', ` + const functionName = generator.provideFunction_( + 'textToTitleCase', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str) { return str.replace(/\\S+/g, function(txt) {return txt[0].toUpperCase() + txt.substring(1).toLowerCase();}); } -`); +`, + ); code = functionName + '(' + text + ')'; } return [code, Order.FUNCTION_CALL]; -}; +} -export function text_trim(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_trim( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Trim spaces. const OPERATORS = { 'LEFT': ".replace(/^[\\s\\xa0]+/, '')", @@ -309,19 +340,20 @@ export function text_trim(block: Block, generator: JavascriptGenerator): [string }; type OperatorOption = keyof typeof OPERATORS; const operator = OPERATORS[block.getFieldValue('MODE') as OperatorOption]; - const text = generator.valueToCode(block, 'TEXT', - Order.MEMBER) || "''"; + const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; return [text + operator, Order.FUNCTION_CALL]; -}; +} export function text_print(block: Block, generator: JavascriptGenerator) { // Print statement. - const msg = generator.valueToCode(block, 'TEXT', - Order.NONE) || "''"; + const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'window.alert(' + msg + ');\n'; -}; +} -export function text_prompt_ext(block: Block, generator: JavascriptGenerator): [string, Order] { +export function text_prompt_ext( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { // Prompt function. let msg; if (block.getField('TEXT')) { @@ -337,16 +369,19 @@ export function text_prompt_ext(block: Block, generator: JavascriptGenerator): [ code = 'Number(' + code + ')'; } return [code, Order.FUNCTION_CALL]; -}; +} export const text_prompt = text_prompt_ext; -export function text_count(block: Block, generator: JavascriptGenerator): [string, Order] { - const text = generator.valueToCode(block, 'TEXT', - Order.NONE) || "''"; - const sub = generator.valueToCode(block, 'SUB', - Order.NONE) || "''"; - const functionName = generator.provideFunction_('textCount', ` +export function text_count( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; + const functionName = generator.provideFunction_( + 'textCount', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) { if (needle.length === 0) { return haystack.length + 1; @@ -354,33 +389,40 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) { return haystack.split(needle).length - 1; } } -`); +`, + ); const code = functionName + '(' + text + ', ' + sub + ')'; return [code, Order.FUNCTION_CALL]; -}; +} -export function text_replace(block: Block, generator: JavascriptGenerator): [string, Order] { - const text = generator.valueToCode(block, 'TEXT', - Order.NONE) || "''"; - const from = generator.valueToCode(block, 'FROM', - Order.NONE) || "''"; +export function text_replace( + block: Block, + generator: JavascriptGenerator, +): [string, Order] { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; // The regex escaping code below is taken from the implementation of // goog.string.regExpEscape. - const functionName = generator.provideFunction_('textReplace', ` + const functionName = generator.provideFunction_( + 'textReplace', + ` function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) { needle = needle.replace(/([-()\\[\\]{}+?*.$\\^|,:# Date: Wed, 25 Oct 2023 22:28:21 +0100 Subject: [PATCH 7/8] fix(generators): Fixes for PR #7602 --- generators/javascript/javascript_generator.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/generators/javascript/javascript_generator.ts b/generators/javascript/javascript_generator.ts index 7d9d9d03e41..7310851bfa2 100644 --- a/generators/javascript/javascript_generator.ts +++ b/generators/javascript/javascript_generator.ts @@ -96,7 +96,7 @@ export class JavascriptGenerator extends CodeGenerator { [Order.LOGICAL_OR, Order.LOGICAL_OR], ]; - /** @param name Name of language generator is for. */ + /** @param name Name of the language the generator is for. */ constructor(name = 'JavaScript') { super(name); this.isInitialized = false; @@ -125,8 +125,9 @@ export class JavascriptGenerator extends CodeGenerator { // security feature. Blockly is 100% client-side, so bypassing // this list is trivial. This is intended to prevent users from // accidentally clobbering a built-in object or function. + // + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords this.addReservedWords( - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords 'break,case,catch,class,const,continue,debugger,default,delete,do,' + 'else,export,extends,finally,for,function,if,import,in,instanceof,' + 'new,return,super,switch,this,throw,try,typeof,var,void,' + @@ -198,7 +199,7 @@ export class JavascriptGenerator extends CodeGenerator { super.finish(code); this.isInitialized = false; - this.nameDB_?.reset(); + this.nameDB_!.reset(); return definitions.join('\n\n') + '\n\n\n' + code; } @@ -268,7 +269,7 @@ export class JavascriptGenerator extends CodeGenerator { // Don't collect comments for nested statements. for (let i = 0; i < block.inputList.length; i++) { if (block.inputList[i].type === inputTypes.VALUE) { - const childBlock = block.inputList[i].connection?.targetBlock(); + const childBlock = block.inputList[i].connection!.targetBlock(); if (childBlock) { comment = this.allNestedComments(childBlock); if (comment) { @@ -290,7 +291,7 @@ export class JavascriptGenerator extends CodeGenerator { * and/or by negation. * * @param block The block. - * @param atId The property ID of the element to get. + * @param atId The ID of the input block to get (and adjust) the value of. * @param delta Value to add. * @param negate Whether to negate the value. * @param order The highest order acting on this value. @@ -308,16 +309,16 @@ export class JavascriptGenerator extends CodeGenerator { } const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - let innerOrder = order; + let inputOrder = order; if (delta > 0) { - innerOrder = Order.ADDITION; + orderForInput = Order.ADDITION; } else if (delta < 0) { - innerOrder = Order.SUBTRACTION; + orderForInput = Order.SUBTRACTION; } else if (negate) { - innerOrder = Order.UNARY_NEGATION; + orderForInput = Order.UNARY_NEGATION; } - let at = this.valueToCode(block, atId, innerOrder) || defaultAtIndex; + let at = this.valueToCode(block, atId, orderForInput) || defaultAtIndex; // Easy case: no adjustments. if (delta === 0 && !negate) { @@ -340,9 +341,7 @@ export class JavascriptGenerator extends CodeGenerator { if (negate) { at = delta ? `-(${at})` : `-${at}`; } - innerOrder = Math.floor(innerOrder); - order = Math.floor(order); - if (order >= innerOrder) { + if (Math.floor(order) >= Math.floor(orderForInput)) { at = `(${at})`; } return at; From b1bf0ea1ea9f8c199658b8f3555b885413371b34 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Mon, 30 Oct 2023 07:59:15 +0000 Subject: [PATCH 8/8] fix(generators): Fix syntax error --- generators/javascript/javascript_generator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/javascript/javascript_generator.ts b/generators/javascript/javascript_generator.ts index 7310851bfa2..547b7daf7d1 100644 --- a/generators/javascript/javascript_generator.ts +++ b/generators/javascript/javascript_generator.ts @@ -309,7 +309,7 @@ export class JavascriptGenerator extends CodeGenerator { } const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - let inputOrder = order; + let orderForInput = order; if (delta > 0) { orderForInput = Order.ADDITION; } else if (delta < 0) {