From ffb8907db8d0f11609c1fe14b2a450d3e639a871 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Mon, 31 Jan 2022 21:41:29 +0000 Subject: [PATCH] feature(blocks): Export block definitions (#5908) * refactor: Provide a BlockDefinition type * refactor: Split defineBlocksWithJsonArray Split defineBlocksWithJsonArray into: - createBlockDefinitionsFromJsonArray, which creates BlockDefinitions from a (possibly JSON-originated) POJsO array, having no side-effects except possibly issuing warnings to the console. - defineBlocks, which add any dictionary of BlockDefinitions to the Blocks dictionary. * feat(blocks): Export block definitions per-module - Define all blocks in a local blocks dictionary, often using createBlockDefinitionFromJsonArray, without registering them. - Separately, use defineBlocks to register the exported BlockDefinitions at the end of each Blockly.blocks.* module. - In Blockly.blocks.all, create a blocks export that combines all of the blocks exports from the individual blocks modules. * chore: have format script run clang-format on blocks/ too --- blocks/all.js | 11 ++++++ blocks/colour.js | 14 ++++++- blocks/lists.js | 33 +++++++++++------ blocks/logic.js | 14 ++++++- blocks/loops.js | 14 ++++++- blocks/math.js | 13 ++++++- blocks/procedures.js | 28 ++++++++++---- blocks/text.js | 33 +++++++++++------ blocks/variables.js | 15 +++++++- blocks/variables_dynamic.js | 14 ++++++- core/block.js | 2 +- core/blocks.js | 11 +++++- core/common.js | 63 +++++++++++++++++++++++--------- scripts/gulpfiles/build_tasks.js | 2 +- scripts/gulpfiles/chunks.json | 2 +- tests/deps.js | 6 +-- 16 files changed, 206 insertions(+), 69 deletions(-) diff --git a/blocks/all.js b/blocks/all.js index b145d4107d7..b233bfcd892 100644 --- a/blocks/all.js +++ b/blocks/all.js @@ -22,6 +22,8 @@ const procedures = goog.require('Blockly.blocks.procedures'); const texts = goog.require('Blockly.blocks.texts'); const variables = goog.require('Blockly.blocks.variables'); const variablesDynamic = goog.require('Blockly.blocks.variablesDynamic'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); exports.colour = colour; @@ -34,3 +36,12 @@ exports.texts = texts; exports.variables = variables; exports.variablesDynamic = variablesDynamic; +/** + * A dictionary of the block definitions provided by all the + * Blockly.blocks.* modules. + * @type {!Object} + */ +const blocks = Object.assign( + {}, colour.blocks, lists.blocks, logic.blocks, loops.blocks, math.blocks, + procedures.blocks, variables.blocks, variablesDynamic.blocks); +exports.blocks = blocks; diff --git a/blocks/colour.js b/blocks/colour.js index c2a8b6b4b3f..abf7347e184 100644 --- a/blocks/colour.js +++ b/blocks/colour.js @@ -11,12 +11,18 @@ goog.module('Blockly.blocks.colour'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldColour'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for colour picker. { 'type': 'colour_picker', @@ -107,3 +113,7 @@ defineBlocksWithJsonArray([ 'tooltip': '%{BKY_COLOUR_BLEND_TOOLTIP}', }, ]); +exports.blocks = blocks; + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/lists.js b/blocks/lists.js index 80400536d75..dbb6b2aa40a 100644 --- a/blocks/lists.js +++ b/blocks/lists.js @@ -16,19 +16,24 @@ const xmlUtils = goog.require('Blockly.utils.xml'); const {Align} = goog.require('Blockly.Input'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); -const {Blocks} = goog.require('Blockly.blocks'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); const {FieldDropdown} = goog.require('Blockly.FieldDropdown'); const {Msg} = goog.require('Blockly.Msg'); const {Mutator} = goog.require('Blockly.Mutator'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldDropdown'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for creating an empty list // The 'list_create_with' block is preferred as it is more flexible. // @@ -112,8 +117,9 @@ defineBlocksWithJsonArray([ 'helpUrl': '%{BKY_LISTS_LENGTH_HELPURL}', }, ]); +exports.blocks = blocks; -Blocks['lists_create_with'] = { +blocks['lists_create_with'] = { /** * Block for creating a list with any number of elements of any type. * @this {Block} @@ -255,7 +261,7 @@ Blocks['lists_create_with'] = { }, }; -Blocks['lists_create_with_container'] = { +blocks['lists_create_with_container'] = { /** * Mutator block for list container. * @this {Block} @@ -270,7 +276,7 @@ Blocks['lists_create_with_container'] = { }, }; -Blocks['lists_create_with_item'] = { +blocks['lists_create_with_item'] = { /** * Mutator block for adding items. * @this {Block} @@ -285,7 +291,7 @@ Blocks['lists_create_with_item'] = { }, }; -Blocks['lists_indexOf'] = { +blocks['lists_indexOf'] = { /** * Block for finding an item in the list. * @this {Block} @@ -312,7 +318,7 @@ Blocks['lists_indexOf'] = { }, }; -Blocks['lists_getIndex'] = { +blocks['lists_getIndex'] = { /** * Block for getting element at index. * @this {Block} @@ -516,7 +522,7 @@ Blocks['lists_getIndex'] = { }, }; -Blocks['lists_setIndex'] = { +blocks['lists_setIndex'] = { /** * Block for setting the element at index. * @this {Block} @@ -668,7 +674,7 @@ Blocks['lists_setIndex'] = { }, }; -Blocks['lists_getSublist'] = { +blocks['lists_getSublist'] = { /** * Block for getting sublist. * @this {Block} @@ -786,7 +792,7 @@ Blocks['lists_getSublist'] = { }, }; -Blocks['lists_sort'] = { +blocks['lists_sort'] = { /** * Block for sorting a list. * @this {Block} @@ -826,7 +832,7 @@ Blocks['lists_sort'] = { }, }; -Blocks['lists_split'] = { +blocks['lists_split'] = { /** * Block for splitting text into a list, or joining a list into text. * @this {Block} @@ -913,3 +919,6 @@ Blocks['lists_split'] = { // dropdown values. // XML hooks are kept for backwards compatibility. }; + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/logic.js b/blocks/logic.js index 8a79d7f9474..f7428ca190e 100644 --- a/blocks/logic.js +++ b/blocks/logic.js @@ -19,20 +19,26 @@ const Extensions = goog.require('Blockly.Extensions'); const xmlUtils = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {Msg} = goog.require('Blockly.Msg'); const {Mutator} = goog.require('Blockly.Mutator'); /* eslint-disable-next-line no-unused-vars */ const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldDropdown'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for boolean data type: true and false. { 'type': 'logic_boolean', @@ -258,6 +264,7 @@ defineBlocksWithJsonArray([ 'tooltip': '%{BKY_CONTROLS_IF_ELSE_TOOLTIP}', }, ]); +exports.blocks = blocks; /** * Tooltip text, keyed by block OP value. Used by logic_compare and @@ -645,3 +652,6 @@ const LOGIC_TERNARY_ONCHANGE_MIXIN = { }; Extensions.registerMixin('logic_ternary', LOGIC_TERNARY_ONCHANGE_MIXIN); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/loops.js b/blocks/loops.js index 21e66ece3ad..7ac0228e2f8 100644 --- a/blocks/loops.js +++ b/blocks/loops.js @@ -18,11 +18,13 @@ const ContextMenu = goog.require('Blockly.ContextMenu'); const Events = goog.require('Blockly.Events'); const Extensions = goog.require('Blockly.Extensions'); const Variables = goog.require('Blockly.Variables'); -const common = goog.require('Blockly.common'); const xmlUtils = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {Msg} = goog.require('Blockly.Msg'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldDropdown'); /** @suppress {extraRequire} */ @@ -35,7 +37,11 @@ goog.require('Blockly.FieldVariable'); goog.require('Blockly.Warning'); -common.defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for repeat n times (external number). { 'type': 'controls_repeat_ext', @@ -205,6 +211,7 @@ common.defineBlocksWithJsonArray([ ], }, ]); +exports.blocks = blocks; /** * Tooltips for the 'controls_whileUntil' block, keyed by MODE value. @@ -358,3 +365,6 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { Extensions.registerMixin( 'controls_flow_in_loop_check', CONTROL_FLOW_IN_LOOP_CHECK_MIXIN); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/math.js b/blocks/math.js index 003ff5bdc94..a5143ba998b 100644 --- a/blocks/math.js +++ b/blocks/math.js @@ -20,7 +20,8 @@ const xmlUtils = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); /* eslint-disable-next-line no-unused-vars */ -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {BlockDefinition} = goog.requireType('Blockly.blocks'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); /** @suppress {extraRequire} */ @@ -29,7 +30,11 @@ goog.require('Blockly.FieldNumber'); goog.require('Blockly.FieldVariable'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for numeric value. { 'type': 'math_number', @@ -384,6 +389,7 @@ defineBlocksWithJsonArray([ 'helpUrl': '%{BKY_MATH_ATAN2_HELPURL}', }, ]); +exports.blocks = blocks; /** * Mapping of math block OP value to tooltip message for blocks @@ -581,3 +587,6 @@ const LIST_MODES_MUTATOR_EXTENSION = function() { Extensions.registerMutator( 'math_modes_of_list_mutator', LIST_MODES_MUTATOR_MIXIN, LIST_MODES_MUTATOR_EXTENSION); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/procedures.js b/blocks/procedures.js index c41eb92dbfd..49964f64f80 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.js @@ -24,7 +24,8 @@ const xmlUtils = goog.require('Blockly.utils.xml'); const {Align} = goog.require('Blockly.Input'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); -const {Blocks} = goog.require('Blockly.blocks'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); /* eslint-disable-next-line no-unused-vars */ const {FieldCheckbox} = goog.require('Blockly.FieldCheckbox'); const {FieldLabel} = goog.require('Blockly.FieldLabel'); @@ -36,12 +37,20 @@ const {Names} = goog.require('Blockly.Names'); const {VariableModel} = goog.requireType('Blockly.VariableModel'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); +const {defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.Comment'); /** @suppress {extraRequire} */ goog.require('Blockly.Warning'); +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = {}; +exports.blocks = blocks; + /** * Common properties for the procedure_defnoreturn and * procedure_defreturn blocks. @@ -437,7 +446,7 @@ const PROCEDURE_DEF_COMMON = { callType_: 'procedures_callnoreturn', }; -Blocks['procedures_defnoreturn'] = { +blocks['procedures_defnoreturn'] = { ...PROCEDURE_DEF_COMMON, /** * Block for defining a procedure with no return value. @@ -479,7 +488,7 @@ Blocks['procedures_defnoreturn'] = { }, }; -Blocks['procedures_defreturn'] = { +blocks['procedures_defreturn'] = { ...PROCEDURE_DEF_COMMON, /** * Block for defining a procedure with a return value. @@ -524,7 +533,7 @@ Blocks['procedures_defreturn'] = { }, }; -Blocks['procedures_mutatorcontainer'] = { +blocks['procedures_mutatorcontainer'] = { /** * Mutator block for procedure container. * @this {Block} @@ -542,7 +551,7 @@ Blocks['procedures_mutatorcontainer'] = { }, }; -Blocks['procedures_mutatorarg'] = { +blocks['procedures_mutatorarg'] = { /** * Mutator block for procedure argument. * @this {Block} @@ -1033,7 +1042,7 @@ const PROCEDURE_CALL_COMMON = { }, }; -Blocks['procedures_callnoreturn'] = { +blocks['procedures_callnoreturn'] = { ...PROCEDURE_CALL_COMMON, /** * Block for calling a procedure with no return value. @@ -1056,7 +1065,7 @@ Blocks['procedures_callnoreturn'] = { defType_: 'procedures_defnoreturn', }; -Blocks['procedures_callreturn'] = { +blocks['procedures_callreturn'] = { ...PROCEDURE_CALL_COMMON, /** * Block for calling a procedure with a return value. @@ -1078,7 +1087,7 @@ Blocks['procedures_callreturn'] = { defType_: 'procedures_defreturn', }; -Blocks['procedures_ifreturn'] = { +blocks['procedures_ifreturn'] = { /** * Block for conditionally returning a value from a procedure. * @this {Block} @@ -1179,3 +1188,6 @@ Blocks['procedures_ifreturn'] = { */ FUNCTION_TYPES: ['procedures_defnoreturn', 'procedures_defreturn'], }; + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/text.js b/blocks/text.js index 9072c7fa1c3..decefb53e9a 100644 --- a/blocks/text.js +++ b/blocks/text.js @@ -19,7 +19,8 @@ const xmlUtils = goog.require('Blockly.utils.xml'); const {Align} = goog.require('Blockly.Input'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); -const {Blocks} = goog.require('Blockly.blocks'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {ConnectionType} = goog.require('Blockly.ConnectionType'); const {FieldDropdown} = goog.require('Blockly.FieldDropdown'); const {FieldImage} = goog.require('Blockly.FieldImage'); @@ -27,14 +28,18 @@ const {FieldTextInput} = goog.require('Blockly.FieldTextInput'); const {Mutator} = goog.require('Blockly.Mutator'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldMultilineInput'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldVariable'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for text value { 'type': 'text', @@ -238,8 +243,9 @@ defineBlocksWithJsonArray([ 'mutator': 'text_charAt_mutator', }, ]); +exports.blocks = blocks; -Blocks['text_getSubstring'] = { +blocks['text_getSubstring'] = { /** * Block for getting substring. * @this {Block} @@ -363,7 +369,7 @@ Blocks['text_getSubstring'] = { }, }; -Blocks['text_changeCase'] = { +blocks['text_changeCase'] = { /** * Block for changing capitalization. * @this {Block} @@ -383,7 +389,7 @@ Blocks['text_changeCase'] = { }, }; -Blocks['text_trim'] = { +blocks['text_trim'] = { /** * Block for trimming spaces. * @this {Block} @@ -403,7 +409,7 @@ Blocks['text_trim'] = { }, }; -Blocks['text_print'] = { +blocks['text_print'] = { /** * Block for print statement. * @this {Block} @@ -463,7 +469,7 @@ const TEXT_PROMPT_COMMON = { }, }; -Blocks['text_prompt_ext'] = { +blocks['text_prompt_ext'] = { ...TEXT_PROMPT_COMMON, /** * Block for prompt function (external message). @@ -496,7 +502,7 @@ Blocks['text_prompt_ext'] = { // XML hooks are kept for backwards compatibility. }; -Blocks['text_prompt'] = { +blocks['text_prompt'] = { ...TEXT_PROMPT_COMMON, /** * Block for prompt function (internal message). @@ -531,7 +537,7 @@ Blocks['text_prompt'] = { }, }; -Blocks['text_count'] = { +blocks['text_count'] = { /** * Block for counting how many times one string appears within another string. * @this {Block} @@ -560,7 +566,7 @@ Blocks['text_count'] = { }, }; -Blocks['text_replace'] = { +blocks['text_replace'] = { /** * Block for replacing one string with another in the text. * @this {Block} @@ -594,7 +600,7 @@ Blocks['text_replace'] = { }, }; -Blocks['text_reverse'] = { +blocks['text_reverse'] = { /** * Block for reversing a string. * @this {Block} @@ -981,3 +987,6 @@ Extensions.registerMutator( Extensions.registerMutator( 'text_charAt_mutator', TEXT_CHARAT_MUTATOR_MIXIN, TEXT_CHARAT_EXTENSION); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/variables.js b/blocks/variables.js index c9e4720fafd..8875f287a4c 100644 --- a/blocks/variables.js +++ b/blocks/variables.js @@ -18,15 +18,21 @@ const Variables = goog.require('Blockly.Variables'); const xmlUtils = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {Msg} = goog.require('Blockly.Msg'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldVariable'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for variable getter. { 'type': 'variables_get', @@ -67,6 +73,8 @@ defineBlocksWithJsonArray([ 'extensions': ['contextMenu_variableSetterGetter'], }, ]); +exports.blocks = blocks; + /** * Mixin to add context menu items to create getter/setter blocks for this @@ -161,3 +169,6 @@ const deleteOptionCallbackFactory = function(block) { Extensions.registerMixin( 'contextMenu_variableSetterGetter', CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/blocks/variables_dynamic.js b/blocks/variables_dynamic.js index 26a91af1ebc..3360d978e20 100644 --- a/blocks/variables_dynamic.js +++ b/blocks/variables_dynamic.js @@ -20,15 +20,21 @@ const Variables = goog.require('Blockly.Variables'); const xml = goog.require('Blockly.utils.xml'); /* eslint-disable-next-line no-unused-vars */ const {Block} = goog.requireType('Blockly.Block'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition} = goog.requireType('Blockly.blocks'); const {Msg} = goog.require('Blockly.Msg'); -const {defineBlocksWithJsonArray} = goog.require('Blockly.common'); +const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldLabel'); /** @suppress {extraRequire} */ goog.require('Blockly.FieldVariable'); -defineBlocksWithJsonArray([ +/** + * A dictionary of the block definitions provided by this module. + * @type {!Object} + */ +const blocks = createBlockDefinitionsFromJsonArray([ // Block for variable getter. { 'type': 'variables_get_dynamic', @@ -67,6 +73,7 @@ defineBlocksWithJsonArray([ 'extensions': ['contextMenu_variableDynamicSetterGetter'], }, ]); +exports.blocks = blocks; /** * Mixin to add context menu items to create getter/setter blocks for this @@ -178,3 +185,6 @@ const deleteOptionCallbackFactory = function(block) { Extensions.registerMixin( 'contextMenu_variableDynamicSetterGetter', CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); + +// Register provided blocks. +defineBlocks(blocks); diff --git a/core/block.js b/core/block.js index 082ca4296d0..2f816215428 100644 --- a/core/block.js +++ b/core/block.js @@ -316,7 +316,7 @@ const Block = function(workspace, prototypeName, opt_id) { this.type = prototypeName; const prototype = Blocks[prototypeName]; if (!prototype || typeof prototype !== 'object') { - throw TypeError('Unknown block type: ' + prototypeName); + throw TypeError('Invalid block definition for type: ' + prototypeName); } object.mixin(this, prototype); } diff --git a/core/blocks.js b/core/blocks.js index 0af6f613541..c29eaa96f13 100644 --- a/core/blocks.js +++ b/core/blocks.js @@ -16,11 +16,18 @@ goog.module('Blockly.blocks'); +/** + * A block definition. For now this very lose, but it can potentially + * be refined e.g. by replacing this typedef with a class definition. + * @typedef {!Object} + */ +let BlockDefinition; +exports.BlockDefinition = BlockDefinition; + /** * A mapping of block type names to block prototype objects. - * @type {!Object} + * @type {!Object} * @alias Blockly.blocks.Blocks */ const Blocks = Object.create(null); - exports.Blocks = Blocks; diff --git a/core/common.js b/core/common.js index 59052b55d8b..29a4af214e1 100644 --- a/core/common.js +++ b/core/common.js @@ -17,7 +17,8 @@ */ goog.module('Blockly.common'); -const {Blocks} = goog.require('Blockly.blocks'); +/* eslint-disable-next-line no-unused-vars */ +const {BlockDefinition, Blocks} = goog.require('Blockly.blocks'); /* eslint-disable-next-line no-unused-vars */ const {Connection} = goog.requireType('Blockly.Connection'); /* eslint-disable-next-line no-unused-vars */ @@ -210,27 +211,55 @@ const jsonInitFactory = function(jsonDef) { * @alias Blockly.common.defineBlocksWithJsonArray */ const defineBlocksWithJsonArray = function(jsonArray) { + defineBlocks(createBlockDefinitionsFromJsonArray(jsonArray)); +}; +exports.defineBlocksWithJsonArray = defineBlocksWithJsonArray; + +/** + * Define blocks from an array of JSON block definitions, as might be generated + * by the Blockly Developer Tools. + * @param {!Array} jsonArray An array of JSON block definitions. + * @return {!Object} A map of the block + * definitions created. + * @alias Blockly.common.defineBlocksWithJsonArray + */ +const createBlockDefinitionsFromJsonArray = function(jsonArray) { + const /** @type {!Object} */ blocks = {}; for (let i = 0; i < jsonArray.length; i++) { const elem = jsonArray[i]; if (!elem) { + console.warn(`Block definition #${i} in JSON array is ${elem}. Skipping`); + continue; + } + const type = elem.type; + if (!type) { console.warn( - 'Block definition #' + i + ' in JSON array is ' + elem + '. ' + + `Block definition #${i} in JSON array is missing a type attribute. ` + 'Skipping.'); - } else { - const typename = elem.type; - if (!typename) { - console.warn( - 'Block definition #' + i + - ' in JSON array is missing a type attribute. Skipping.'); - } else { - if (Blocks[typename]) { - console.warn( - 'Block definition #' + i + ' in JSON array' + - ' overwrites prior definition of "' + typename + '".'); - } - Blocks[typename] = {init: jsonInitFactory(elem)}; - } + continue; } + blocks[type] = {init: jsonInitFactory(elem)}; } + return blocks; }; -exports.defineBlocksWithJsonArray = defineBlocksWithJsonArray; +exports.createBlockDefinitionsFromJsonArray = + createBlockDefinitionsFromJsonArray; + +/** + * Add the specified block definitions to the block definitions + * dictionary (Blockly.Blocks). + * @param {!Object} blocks A map of block + * type names to block definitions. + * @alias Blockly.common.defineBlocks + */ +const defineBlocks = function(blocks) { + // Iterate over own enumerable properties. + for (const type of Object.keys(blocks)) { + const definition = blocks[type]; + if (type in Blocks) { + console.warn(`Block definiton "${type}" overwrites previous definition.`); + } + Blocks[type] = definition; + } +}; +exports.defineBlocks = defineBlocks; diff --git a/scripts/gulpfiles/build_tasks.js b/scripts/gulpfiles/build_tasks.js index aef2bf95b6e..8b647b3564a 100644 --- a/scripts/gulpfiles/build_tasks.js +++ b/scripts/gulpfiles/build_tasks.js @@ -626,7 +626,7 @@ function cleanBuildDir(done) { * Runs clang format on all files in the core directory. */ function format() { - return gulp.src(['core/**/*.js'], {base: '.'}) + return gulp.src(['core/**/*.js', 'blocks/**/*.js'], {base: '.'}) .pipe(clangFormatter.format('file', clangFormat)) .pipe(gulp.dest('.')); }; diff --git a/scripts/gulpfiles/chunks.json b/scripts/gulpfiles/chunks.json index c3df5c6120a..4f9b9449267 100644 --- a/scripts/gulpfiles/chunks.json +++ b/scripts/gulpfiles/chunks.json @@ -72,7 +72,6 @@ "./core/menuitem.js", "./core/menu.js", "./core/contextmenu.js", - "./core/blocks.js", "./core/utils/global.js", "./core/utils/useragent.js", "./core/utils/svg.js", @@ -264,6 +263,7 @@ "./core/xml.js", "./core/connection.js", "./core/common.js", + "./core/blocks.js", "./closure/goog/base_minimal.js", "./core/blockly.js", "./blocks/variables_dynamic.js", diff --git a/tests/deps.js b/tests/deps.js index 601c79e77ef..6c66f8850a2 100644 --- a/tests/deps.js +++ b/tests/deps.js @@ -1,11 +1,11 @@ goog.addDependency('../../blocks/all.js', ['Blockly.blocks.all'], ['Blockly.blocks.colour', 'Blockly.blocks.lists', 'Blockly.blocks.logic', 'Blockly.blocks.loops', 'Blockly.blocks.math', 'Blockly.blocks.procedures', 'Blockly.blocks.texts', 'Blockly.blocks.variables', 'Blockly.blocks.variablesDynamic'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../blocks/colour.js', ['Blockly.blocks.colour'], ['Blockly.FieldColour', 'Blockly.common'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../blocks/lists.js', ['Blockly.blocks.lists'], ['Blockly.ConnectionType', 'Blockly.FieldDropdown', 'Blockly.FieldDropdown', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.blocks', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../blocks/lists.js', ['Blockly.blocks.lists'], ['Blockly.ConnectionType', 'Blockly.FieldDropdown', 'Blockly.FieldDropdown', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../blocks/logic.js', ['Blockly.blocks.logic'], ['Blockly.Events', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../blocks/loops.js', ['Blockly.blocks.loops'], ['Blockly.ContextMenu', 'Blockly.Events', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.Warning', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../blocks/math.js', ['Blockly.blocks.math'], ['Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldLabel', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../blocks/procedures.js', ['Blockly.blocks.procedures'], ['Blockly.Comment', 'Blockly.ContextMenu', 'Blockly.Events', 'Blockly.FieldCheckbox', 'Blockly.FieldLabel', 'Blockly.FieldTextInput', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.Names', 'Blockly.Procedures', 'Blockly.Variables', 'Blockly.Warning', 'Blockly.Xml', 'Blockly.blocks', 'Blockly.internalConstants', 'Blockly.utils.xml'], {'lang': 'es9', 'module': 'goog'}); -goog.addDependency('../../blocks/text.js', ['Blockly.blocks.texts'], ['Blockly.ConnectionType', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldMultilineInput', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.blocks', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es9', 'module': 'goog'}); +goog.addDependency('../../blocks/procedures.js', ['Blockly.blocks.procedures'], ['Blockly.Comment', 'Blockly.ContextMenu', 'Blockly.Events', 'Blockly.FieldCheckbox', 'Blockly.FieldLabel', 'Blockly.FieldTextInput', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.Names', 'Blockly.Procedures', 'Blockly.Variables', 'Blockly.Warning', 'Blockly.Xml', 'Blockly.common', 'Blockly.internalConstants', 'Blockly.utils.xml'], {'lang': 'es9', 'module': 'goog'}); +goog.addDependency('../../blocks/text.js', ['Blockly.blocks.texts'], ['Blockly.ConnectionType', 'Blockly.Extensions', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldMultilineInput', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.Input', 'Blockly.Msg', 'Blockly.Mutator', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es9', 'module': 'goog'}); goog.addDependency('../../blocks/variables.js', ['Blockly.blocks.variables'], ['Blockly.ContextMenu', 'Blockly.Extensions', 'Blockly.FieldLabel', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../blocks/variables_dynamic.js', ['Blockly.blocks.variablesDynamic'], ['Blockly.ContextMenu', 'Blockly.Extensions', 'Blockly.FieldLabel', 'Blockly.FieldVariable', 'Blockly.Msg', 'Blockly.Variables', 'Blockly.common', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/block.js', ['Blockly.Block'], ['Blockly.ASTNode', 'Blockly.Connection', 'Blockly.ConnectionType', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Events.utils', 'Blockly.Extensions', 'Blockly.IASTNodeLocation', 'Blockly.IDeletable', 'Blockly.Input', 'Blockly.Tooltip', 'Blockly.blocks', 'Blockly.common', 'Blockly.constants', 'Blockly.fieldRegistry', 'Blockly.inputTypes', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.array', 'Blockly.utils.idGenerator', 'Blockly.utils.object', 'Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'});