diff --git a/blocks/loops.ts b/blocks/loops.ts index c7cb710d7..5835ab8be 100644 --- a/blocks/loops.ts +++ b/blocks/loops.ts @@ -269,7 +269,7 @@ const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = { } const varField = this.getField('VAR') as FieldVariable; const variable = varField.getVariable()!; - const varName = variable.name; + const varName = variable.getName(); if (!this.isCollapsed() && varName !== null) { const getVarBlockState = { type: 'variables_get', diff --git a/blocks/procedures.ts b/blocks/procedures.ts index 1214eb55e..583ca9d20 100644 --- a/blocks/procedures.ts +++ b/blocks/procedures.ts @@ -32,7 +32,10 @@ import {FieldTextInput} from '../core/field_textinput.js'; import {Msg} from '../core/msg.js'; import {MutatorIcon as Mutator} from '../core/icons/mutator_icon.js'; import {Names} from '../core/names.js'; -import type {VariableModel} from '../core/variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from '../core/interfaces/i_variable_model.js'; import type {Workspace} from '../core/workspace.js'; import type {WorkspaceSvg} from '../core/workspace_svg.js'; import {config} from '../core/config.js'; @@ -48,7 +51,7 @@ export const blocks: {[key: string]: BlockDefinition} = {}; type ProcedureBlock = Block & ProcedureMixin; interface ProcedureMixin extends ProcedureMixinType { arguments_: string[]; - argumentVarModels_: VariableModel[]; + argumentVarModels_: IVariableModel[]; callType_: string; paramIds_: string[]; hasStatements_: boolean; @@ -128,7 +131,7 @@ const PROCEDURE_DEF_COMMON = { for (let i = 0; i < this.argumentVarModels_.length; i++) { const parameter = xmlUtils.createElement('arg'); const argModel = this.argumentVarModels_[i]; - parameter.setAttribute('name', argModel.name); + parameter.setAttribute('name', argModel.getName()); parameter.setAttribute('varid', argModel.getId()); if (opt_paramIds && this.paramIds_) { parameter.setAttribute('paramId', this.paramIds_[i]); @@ -196,7 +199,7 @@ const PROCEDURE_DEF_COMMON = { state['params'].push({ // We don't need to serialize the name, but just in case we decide // to separate params from variables. - 'name': this.argumentVarModels_[i].name, + 'name': this.argumentVarModels_[i].getName(), 'id': this.argumentVarModels_[i].getId(), }); } @@ -224,7 +227,7 @@ const PROCEDURE_DEF_COMMON = { param['name'], '', ); - this.arguments_.push(variable.name); + this.arguments_.push(variable.getName()); this.argumentVarModels_.push(variable); } } @@ -352,7 +355,9 @@ const PROCEDURE_DEF_COMMON = { * * @returns List of variable models. */ - getVarModels: function (this: ProcedureBlock): VariableModel[] { + getVarModels: function ( + this: ProcedureBlock, + ): IVariableModel[] { return this.argumentVarModels_; }, /** @@ -370,23 +375,23 @@ const PROCEDURE_DEF_COMMON = { newId: string, ) { const oldVariable = this.workspace.getVariableById(oldId)!; - if (oldVariable.type !== '') { + if (oldVariable.getType() !== '') { // Procedure arguments always have the empty type. return; } - const oldName = oldVariable.name; + const oldName = oldVariable.getName(); const newVar = this.workspace.getVariableById(newId)!; let change = false; for (let i = 0; i < this.argumentVarModels_.length; i++) { if (this.argumentVarModels_[i].getId() === oldId) { - this.arguments_[i] = newVar.name; + this.arguments_[i] = newVar.getName(); this.argumentVarModels_[i] = newVar; change = true; } } if (change) { - this.displayRenamedVar_(oldName, newVar.name); + this.displayRenamedVar_(oldName, newVar.getName()); Procedures.mutateCallers(this); } }, @@ -398,9 +403,9 @@ const PROCEDURE_DEF_COMMON = { */ updateVarName: function ( this: ProcedureBlock & BlockSvg, - variable: VariableModel, + variable: IVariableModel, ) { - const newName = variable.name; + const newName = variable.getName(); let change = false; let oldName; for (let i = 0; i < this.argumentVarModels_.length; i++) { @@ -473,12 +478,16 @@ const PROCEDURE_DEF_COMMON = { const getVarBlockState = { type: 'variables_get', fields: { - VAR: {name: argVar.name, id: argVar.getId(), type: argVar.type}, + VAR: { + name: argVar.getName(), + id: argVar.getId(), + type: argVar.getType(), + }, }, }; options.push({ enabled: true, - text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', argVar.name), + text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', argVar.getName()), callback: ContextMenu.callbackFactory(this, getVarBlockState), }); } @@ -623,7 +632,7 @@ type ArgumentMixinType = typeof PROCEDURES_MUTATORARGUMENT; // TODO(#6920): This is kludgy. type FieldTextInputForArgument = FieldTextInput & { oldShowEditorFn_(_e?: Event, quietInput?: boolean): void; - createdVariables_: VariableModel[]; + createdVariables_: IVariableModel[]; }; const PROCEDURES_MUTATORARGUMENT = { @@ -708,7 +717,7 @@ const PROCEDURES_MUTATORARGUMENT = { } let model = outerWs.getVariable(varName, ''); - if (model && model.name !== varName) { + if (model && model.getName() !== varName) { // Rename the variable (case change) outerWs.renameVariableById(model.getId(), varName); } @@ -739,7 +748,7 @@ const PROCEDURES_MUTATORARGUMENT = { } for (let i = 0; i < this.createdVariables_.length; i++) { const model = this.createdVariables_[i]; - if (model.name !== newText) { + if (model.getName() !== newText) { outerWs.deleteVariableById(model.getId()); } } @@ -750,7 +759,7 @@ blocks['procedures_mutatorarg'] = PROCEDURES_MUTATORARGUMENT; /** Type of a block using the PROCEDURE_CALL_COMMON mixin. */ type CallBlock = Block & CallMixin; interface CallMixin extends CallMixinType { - argumentVarModels_: VariableModel[]; + argumentVarModels_: IVariableModel[]; arguments_: string[]; defType_: string; quarkIds_: string[] | null; @@ -1029,7 +1038,7 @@ const PROCEDURE_CALL_COMMON = { * * @returns List of variable models. */ - getVarModels: function (this: CallBlock): VariableModel[] { + getVarModels: function (this: CallBlock): IVariableModel[] { return this.argumentVarModels_; }, /** diff --git a/blocks/variables_dynamic.ts b/blocks/variables_dynamic.ts index e74cae423..ff94d8c96 100644 --- a/blocks/variables_dynamic.ts +++ b/blocks/variables_dynamic.ts @@ -144,9 +144,9 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { const id = this.getFieldValue('VAR'); const variableModel = Variables.getVariable(this.workspace, id)!; if (this.type === 'variables_get_dynamic') { - this.outputConnection!.setCheck(variableModel.type); + this.outputConnection!.setCheck(variableModel.getType()); } else { - this.getInput('VALUE')!.connection!.setCheck(variableModel.type); + this.getInput('VALUE')!.connection!.setCheck(variableModel.getType()); } }, }; diff --git a/core/block.ts b/core/block.ts index 52191d63c..c08b75a69 100644 --- a/core/block.ts +++ b/core/block.ts @@ -45,7 +45,10 @@ import * as idGenerator from './utils/idgenerator.js'; import * as parsing from './utils/parsing.js'; import * as registry from './registry.js'; import {Size} from './utils/size.js'; -import type {VariableModel} from './variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from './interfaces/i_variable_model.js'; import type {Workspace} from './workspace.js'; import {DummyInput} from './inputs/dummy_input.js'; import {EndRowInput} from './inputs/end_row_input.js'; @@ -1133,7 +1136,7 @@ export class Block implements IASTNodeLocation { * @returns List of variable models. * @internal */ - getVarModels(): VariableModel[] { + getVarModels(): IVariableModel[] { const vars = []; for (let i = 0, input; (input = this.inputList[i]); i++) { for (let j = 0, field; (field = input.fieldRow[j]); j++) { @@ -1159,7 +1162,7 @@ export class Block implements IASTNodeLocation { * @param variable The variable being renamed. * @internal */ - updateVarName(variable: VariableModel) { + updateVarName(variable: IVariableModel) { for (let i = 0, input; (input = this.inputList[i]); i++) { for (let j = 0, field; (field = input.fieldRow[j]); j++) { if ( diff --git a/core/events/events_var_base.ts b/core/events/events_var_base.ts index 74537f144..1ec59ac6c 100644 --- a/core/events/events_var_base.ts +++ b/core/events/events_var_base.ts @@ -11,7 +11,10 @@ */ // Former goog.module ID: Blockly.Events.VarBase -import type {VariableModel} from '../variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from '../interfaces/i_variable_model.js'; import { Abstract as AbstractEvent, @@ -31,13 +34,13 @@ export class VarBase extends AbstractEvent { * @param opt_variable The variable this event corresponds to. Undefined for * a blank event. */ - constructor(opt_variable?: VariableModel) { + constructor(opt_variable?: IVariableModel) { super(); this.isBlank = typeof opt_variable === 'undefined'; if (!opt_variable) return; this.varId = opt_variable.getId(); - this.workspaceId = opt_variable.workspace.id; + this.workspaceId = opt_variable.getWorkspace().id; } /** diff --git a/core/events/events_var_create.ts b/core/events/events_var_create.ts index a719cad98..0685b8ad8 100644 --- a/core/events/events_var_create.ts +++ b/core/events/events_var_create.ts @@ -12,7 +12,10 @@ // Former goog.module ID: Blockly.Events.VarCreate import * as registry from '../registry.js'; -import type {VariableModel} from '../variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from '../interfaces/i_variable_model.js'; import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; @@ -33,14 +36,14 @@ export class VarCreate extends VarBase { /** * @param opt_variable The created variable. Undefined for a blank event. */ - constructor(opt_variable?: VariableModel) { + constructor(opt_variable?: IVariableModel) { super(opt_variable); if (!opt_variable) { return; // Blank event to be populated by fromJson. } - this.varType = opt_variable.type; - this.varName = opt_variable.name; + this.varType = opt_variable.getType(); + this.varName = opt_variable.getName(); } /** diff --git a/core/events/events_var_delete.ts b/core/events/events_var_delete.ts index fc19461d4..d469db806 100644 --- a/core/events/events_var_delete.ts +++ b/core/events/events_var_delete.ts @@ -7,7 +7,10 @@ // Former goog.module ID: Blockly.Events.VarDelete import * as registry from '../registry.js'; -import type {VariableModel} from '../variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from '../interfaces/i_variable_model.js'; import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; @@ -28,14 +31,14 @@ export class VarDelete extends VarBase { /** * @param opt_variable The deleted variable. Undefined for a blank event. */ - constructor(opt_variable?: VariableModel) { + constructor(opt_variable?: IVariableModel) { super(opt_variable); if (!opt_variable) { return; // Blank event to be populated by fromJson. } - this.varType = opt_variable.type; - this.varName = opt_variable.name; + this.varType = opt_variable.getType(); + this.varName = opt_variable.getName(); } /** diff --git a/core/events/events_var_rename.ts b/core/events/events_var_rename.ts index 3bb1e90eb..0de56c544 100644 --- a/core/events/events_var_rename.ts +++ b/core/events/events_var_rename.ts @@ -7,7 +7,10 @@ // Former goog.module ID: Blockly.Events.VarRename import * as registry from '../registry.js'; -import type {VariableModel} from '../variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from '../interfaces/i_variable_model.js'; import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; @@ -31,13 +34,13 @@ export class VarRename extends VarBase { * @param opt_variable The renamed variable. Undefined for a blank event. * @param newName The new name the variable will be changed to. */ - constructor(opt_variable?: VariableModel, newName?: string) { + constructor(opt_variable?: IVariableModel, newName?: string) { super(opt_variable); if (!opt_variable) { return; // Blank event to be populated by fromJson. } - this.oldName = opt_variable.name; + this.oldName = opt_variable.getName(); this.newName = typeof newName === 'undefined' ? '' : newName; } diff --git a/core/field_variable.ts b/core/field_variable.ts index d0a929bf0..a40a21ccc 100644 --- a/core/field_variable.ts +++ b/core/field_variable.ts @@ -30,7 +30,7 @@ import type {MenuItem} from './menuitem.js'; import {Msg} from './msg.js'; import * as parsing from './utils/parsing.js'; import {Size} from './utils/size.js'; -import {VariableModel} from './variable_model.js'; +import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js'; import * as Variables from './variables.js'; import * as Xml from './xml.js'; @@ -52,7 +52,7 @@ export class FieldVariable extends FieldDropdown { protected override size_: Size; /** The variable model associated with this field. */ - private variable: VariableModel | null = null; + private variable: IVariableModel | null = null; /** * Serializable fields are saved by the serializer, non-serializable fields @@ -196,12 +196,12 @@ export class FieldVariable extends FieldDropdown { ); // This should never happen :) - if (variableType !== null && variableType !== variable.type) { + if (variableType !== null && variableType !== variable.getType()) { throw Error( "Serialized variable type with id '" + variable.getId() + "' had type " + - variable.type + + variable.getType() + ', and ' + 'does not match variable field that references it: ' + Xml.domToText(fieldElement) + @@ -224,9 +224,9 @@ export class FieldVariable extends FieldDropdown { this.initModel(); fieldElement.id = this.variable!.getId(); - fieldElement.textContent = this.variable!.name; - if (this.variable!.type) { - fieldElement.setAttribute('variabletype', this.variable!.type); + fieldElement.textContent = this.variable!.getName(); + if (this.variable!.getType()) { + fieldElement.setAttribute('variabletype', this.variable!.getType()); } return fieldElement; } @@ -249,8 +249,8 @@ export class FieldVariable extends FieldDropdown { this.initModel(); const state = {'id': this.variable!.getId()}; if (doFullSerialization) { - (state as AnyDuringMigration)['name'] = this.variable!.name; - (state as AnyDuringMigration)['type'] = this.variable!.type; + (state as AnyDuringMigration)['name'] = this.variable!.getName(); + (state as AnyDuringMigration)['type'] = this.variable!.getType(); } return state; } @@ -307,7 +307,7 @@ export class FieldVariable extends FieldDropdown { * is selected. */ override getText(): string { - return this.variable ? this.variable.name : ''; + return this.variable ? this.variable.getName() : ''; } /** @@ -318,7 +318,7 @@ export class FieldVariable extends FieldDropdown { * @returns The selected variable, or null if none was selected. * @internal */ - getVariable(): VariableModel | null { + getVariable(): IVariableModel | null { return this.variable; } @@ -365,7 +365,7 @@ export class FieldVariable extends FieldDropdown { return null; } // Type Checks. - const type = variable.type; + const type = variable.getType(); if (!this.typeIsAllowed(type)) { console.warn("Variable type doesn't match this field! Type was " + type); return null; @@ -499,16 +499,13 @@ export class FieldVariable extends FieldDropdown { const id = menuItem.getValue(); // Handle special cases. if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) { - if (id === internalConstants.RENAME_VARIABLE_ID) { + if (id === internalConstants.RENAME_VARIABLE_ID && this.variable) { // Rename variable. - Variables.renameVariable( - this.sourceBlock_.workspace, - this.variable as VariableModel, - ); + Variables.renameVariable(this.sourceBlock_.workspace, this.variable); return; - } else if (id === internalConstants.DELETE_VARIABLE_ID) { + } else if (id === internalConstants.DELETE_VARIABLE_ID && this.variable) { // Delete variable. - this.sourceBlock_.workspace.deleteVariableById(this.variable!.getId()); + this.sourceBlock_.workspace.deleteVariableById(this.variable.getId()); return; } } @@ -560,7 +557,7 @@ export class FieldVariable extends FieldDropdown { ); } const name = this.getText(); - let variableModelList: VariableModel[] = []; + let variableModelList: IVariableModel[] = []; if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) { const variableTypes = this.getVariableTypes(); // Get a copy of the list, so that adding rename and new variable options @@ -572,12 +569,15 @@ export class FieldVariable extends FieldDropdown { variableModelList = variableModelList.concat(variables); } } - variableModelList.sort(VariableModel.compareByName); + variableModelList.sort(Variables.compareByName); const options: [string, string][] = []; for (let i = 0; i < variableModelList.length; i++) { // Set the UUID as the internal representation of the variable. - options[i] = [variableModelList[i].name, variableModelList[i].getId()]; + options[i] = [ + variableModelList[i].getName(), + variableModelList[i].getId(), + ]; } options.push([ Msg['RENAME_VARIABLE'], diff --git a/core/interfaces/i_variable_backed_parameter_model.ts b/core/interfaces/i_variable_backed_parameter_model.ts index b2042bfb2..4fda2df46 100644 --- a/core/interfaces/i_variable_backed_parameter_model.ts +++ b/core/interfaces/i_variable_backed_parameter_model.ts @@ -4,13 +4,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {VariableModel} from '../variable_model.js'; +import type {IVariableModel, IVariableState} from './i_variable_model.js'; import {IParameterModel} from './i_parameter_model.js'; /** Interface for a parameter model that holds a variable model. */ export interface IVariableBackedParameterModel extends IParameterModel { /** Returns the variable model held by this type. */ - getVariableModel(): VariableModel; + getVariableModel(): IVariableModel; } /** diff --git a/core/names.ts b/core/names.ts index 4f4c72faa..9d724bff9 100644 --- a/core/names.ts +++ b/core/names.ts @@ -95,7 +95,7 @@ export class Names { } const variable = this.variableMap.getVariableById(id); if (variable) { - return variable.name; + return variable.getName(); } return null; } diff --git a/core/serialization/blocks.ts b/core/serialization/blocks.ts index dbb58cffb..355e53cac 100644 --- a/core/serialization/blocks.ts +++ b/core/serialization/blocks.ts @@ -30,7 +30,10 @@ import { import * as priorities from './priorities.js'; import * as serializationRegistry from './registry.js'; import * as Variables from '../variables.js'; -import {VariableModel} from '../variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from '../interfaces/i_variable_model.js'; // TODO(#5160): Remove this once lint is fixed. /* eslint-disable no-use-before-define */ @@ -503,7 +506,7 @@ function appendPrivate( */ function checkNewVariables( workspace: Workspace, - originalVariables: VariableModel[], + originalVariables: IVariableModel[], ) { if (eventUtils.isEnabled()) { const newVariables = Variables.getAddedVariables( diff --git a/core/variable_map.ts b/core/variable_map.ts index b8e2e4e0a..1483e0c37 100644 --- a/core/variable_map.ts +++ b/core/variable_map.ts @@ -23,7 +23,7 @@ import * as registry from './registry.js'; import {Msg} from './msg.js'; import {Names} from './names.js'; import * as idGenerator from './utils/idgenerator.js'; -import {VariableModel} from './variable_model.js'; +import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js'; import type {Workspace} from './workspace.js'; import type {IVariableMap} from './interfaces/i_variable_map.js'; @@ -32,13 +32,18 @@ import type {IVariableMap} from './interfaces/i_variable_map.js'; * variable types as keys and lists of variables as values. The list of * variables are the type indicated by the key. */ -export class VariableMap implements IVariableMap { +export class VariableMap + implements IVariableMap> +{ /** * A map from variable type to map of IDs to variables. The maps contain * all of the named variables in the workspace, including variables that are * not currently in use. */ - private variableMap = new Map>(); + private variableMap = new Map< + string, + Map> + >(); /** @param workspace The workspace this map belongs to. */ constructor(public workspace: Workspace) {} @@ -63,9 +68,12 @@ export class VariableMap implements IVariableMap { * @param newName New variable name. * @returns The newly renamed variable. */ - renameVariable(variable: VariableModel, newName: string): VariableModel { - if (variable.name === newName) return variable; - const type = variable.type; + renameVariable( + variable: IVariableModel, + newName: string, + ): IVariableModel { + if (variable.getName() === newName) return variable; + const type = variable.getType(); const conflictVar = this.getVariable(newName, type); const blocks = this.workspace.getAllBlocks(false); const existingGroup = eventUtils.getGroup(); @@ -91,11 +99,15 @@ export class VariableMap implements IVariableMap { return variable; } - changeVariableType(variable: VariableModel, newType: string): VariableModel { + changeVariableType( + variable: IVariableModel, + newType: string, + ): IVariableModel { this.variableMap.get(variable.getType())?.delete(variable.getId()); variable.setType(newType); const newTypeVariables = - this.variableMap.get(newType) ?? new Map(); + this.variableMap.get(newType) ?? + new Map>(); newTypeVariables.set(variable.getId(), variable); if (!this.variableMap.has(newType)) { this.variableMap.set(newType, newTypeVariables); @@ -129,14 +141,14 @@ export class VariableMap implements IVariableMap { * @param blocks The list of all blocks in the workspace. */ private renameVariableAndUses_( - variable: VariableModel, + variable: IVariableModel, newName: string, blocks: Block[], ) { eventUtils.fire( new (eventUtils.get(eventUtils.VAR_RENAME))(variable, newName), ); - variable.name = newName; + variable.setName(newName); for (let i = 0; i < blocks.length; i++) { blocks[i].updateVarName(variable); } @@ -154,13 +166,13 @@ export class VariableMap implements IVariableMap { * @param blocks The list of all blocks in the workspace. */ private renameVariableWithConflict_( - variable: VariableModel, + variable: IVariableModel, newName: string, - conflictVar: VariableModel, + conflictVar: IVariableModel, blocks: Block[], ) { - const type = variable.type; - const oldCase = conflictVar.name; + const type = variable.getType(); + const oldCase = conflictVar.getName(); if (newName !== oldCase) { // Simple rename to change the case and update references. @@ -194,7 +206,7 @@ export class VariableMap implements IVariableMap { name: string, opt_type?: string, opt_id?: string, - ): VariableModel { + ): IVariableModel { let variable = this.getVariable(name, opt_type); if (variable) { if (opt_id && variable.getId() !== opt_id) { @@ -217,10 +229,19 @@ export class VariableMap implements IVariableMap { } const id = opt_id || idGenerator.genUid(); const type = opt_type || ''; + const VariableModel = registry.getObject( + registry.Type.VARIABLE_MODEL, + registry.DEFAULT, + true, + ); + if (!VariableModel) { + throw new Error('No variable model is registered.'); + } variable = new VariableModel(this.workspace, name, type, id); const variables = - this.variableMap.get(type) ?? new Map(); + this.variableMap.get(type) ?? + new Map>(); variables.set(variable.getId(), variable); if (!this.variableMap.has(type)) { this.variableMap.set(type, variables); @@ -235,10 +256,13 @@ export class VariableMap implements IVariableMap { * * @param variable The variable to add. */ - addVariable(variable: VariableModel) { + addVariable(variable: IVariableModel) { const type = variable.getType(); if (!this.variableMap.has(type)) { - this.variableMap.set(type, new Map()); + this.variableMap.set( + type, + new Map>(), + ); } this.variableMap.get(type)?.set(variable.getId(), variable); } @@ -249,13 +273,13 @@ export class VariableMap implements IVariableMap { * * @param variable Variable to delete. */ - deleteVariable(variable: VariableModel) { - const variables = this.variableMap.get(variable.type); + deleteVariable(variable: IVariableModel) { + const variables = this.variableMap.get(variable.getType()); if (!variables || !variables.has(variable.getId())) return; variables.delete(variable.getId()); eventUtils.fire(new (eventUtils.get(eventUtils.VAR_DELETE))(variable)); if (variables.size === 0) { - this.variableMap.delete(variable.type); + this.variableMap.delete(variable.getType()); } } @@ -269,7 +293,7 @@ export class VariableMap implements IVariableMap { const variable = this.getVariableById(id); if (variable) { // Check whether this variable is a function parameter before deleting. - const variableName = variable.name; + const variableName = variable.getName(); const uses = this.getVariableUsesById(id); for (let i = 0, block; (block = uses[i]); i++) { if ( @@ -312,7 +336,10 @@ export class VariableMap implements IVariableMap { * @param uses An array of uses of the variable. * @internal */ - deleteVariableInternal(variable: VariableModel, uses: Block[]) { + deleteVariableInternal( + variable: IVariableModel, + uses: Block[], + ) { const existingGroup = eventUtils.getGroup(); if (!existingGroup) { eventUtils.setGroup(true); @@ -336,7 +363,10 @@ export class VariableMap implements IVariableMap { * the empty string, which is a specific type. * @returns The variable with the given name, or null if it was not found. */ - getVariable(name: string, opt_type?: string): VariableModel | null { + getVariable( + name: string, + opt_type?: string, + ): IVariableModel | null { const type = opt_type || ''; const variables = this.variableMap.get(type); if (!variables) return null; @@ -354,7 +384,7 @@ export class VariableMap implements IVariableMap { * @param id The ID to check for. * @returns The variable with the given ID. */ - getVariableById(id: string): VariableModel | null { + getVariableById(id: string): IVariableModel | null { for (const variables of this.variableMap.values()) { if (variables.has(id)) { return variables.get(id) ?? null; @@ -371,7 +401,7 @@ export class VariableMap implements IVariableMap { * @returns The sought after variables of the passed in type. An empty array * if none are found. */ - getVariablesOfType(type: string | null): VariableModel[] { + getVariablesOfType(type: string | null): IVariableModel[] { type = type || ''; const variables = this.variableMap.get(type); if (!variables) return []; @@ -416,8 +446,8 @@ export class VariableMap implements IVariableMap { * * @returns List of variable models. */ - getAllVariables(): VariableModel[] { - let allVariables: VariableModel[] = []; + getAllVariables(): IVariableModel[] { + let allVariables: IVariableModel[] = []; for (const variables of this.variableMap.values()) { allVariables = allVariables.concat(...variables.values()); } diff --git a/core/variable_model.ts b/core/variable_model.ts index 017b02c60..15ad5abf9 100644 --- a/core/variable_model.ts +++ b/core/variable_model.ts @@ -26,7 +26,7 @@ import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js'; * @see {Blockly.FieldVariable} */ export class VariableModel implements IVariableModel { - type: string; + private type: string; private readonly id_: string; /** @@ -39,8 +39,8 @@ export class VariableModel implements IVariableModel { * @param opt_id The unique ID of the variable. This will default to a UUID. */ constructor( - public workspace: Workspace, - public name: string, + private workspace: Workspace, + private name: string, opt_type?: string, opt_id?: string, ) { @@ -130,7 +130,9 @@ export class VariableModel implements IVariableModel { * @internal */ static compareByName(var1: VariableModel, var2: VariableModel): number { - return var1.name.localeCompare(var2.name, undefined, {sensitivity: 'base'}); + return var1 + .getName() + .localeCompare(var2.getName(), undefined, {sensitivity: 'base'}); } } diff --git a/core/variables.ts b/core/variables.ts index da9d28bff..9809feca2 100644 --- a/core/variables.ts +++ b/core/variables.ts @@ -12,7 +12,7 @@ import {isVariableBackedParameterModel} from './interfaces/i_variable_backed_par import {Msg} from './msg.js'; import {isLegacyProcedureDefBlock} from './interfaces/i_legacy_procedure_blocks.js'; import * as utilsXml from './utils/xml.js'; -import {VariableModel} from './variable_model.js'; +import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; @@ -34,9 +34,11 @@ export const CATEGORY_NAME = 'VARIABLE'; * @param ws The workspace to search for variables. * @returns Array of variable models. */ -export function allUsedVarModels(ws: Workspace): VariableModel[] { +export function allUsedVarModels( + ws: Workspace, +): IVariableModel[] { const blocks = ws.getAllBlocks(false); - const variables = new Set(); + const variables = new Set>(); // Iterate through every block and add each variable to the set. for (let i = 0; i < blocks.length; i++) { const blockVariables = blocks[i].getVarModels(); @@ -142,7 +144,7 @@ export function flyoutCategoryBlocks(workspace: Workspace): Element[] { } if (Blocks['variables_get']) { - variableModelList.sort(VariableModel.compareByName); + variableModelList.sort(compareByName); for (let i = 0, variable; (variable = variableModelList[i]); i++) { const block = utilsXml.createElement('block'); block.setAttribute('type', 'variables_get'); @@ -266,11 +268,13 @@ export function createVariableButtonHandler( } let msg; - if (existing.type === type) { - msg = Msg['VARIABLE_ALREADY_EXISTS'].replace('%1', existing.name); + if (existing.getType() === type) { + msg = Msg['VARIABLE_ALREADY_EXISTS'].replace('%1', existing.getName()); } else { msg = Msg['VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE']; - msg = msg.replace('%1', existing.name).replace('%2', existing.type); + msg = msg + .replace('%1', existing.getName()) + .replace('%2', existing.getType()); } dialog.alert(msg, function () { promptAndCheckWithAlert(text); @@ -293,14 +297,14 @@ export function createVariableButtonHandler( */ export function renameVariable( workspace: Workspace, - variable: VariableModel, + variable: IVariableModel, opt_callback?: (p1?: string | null) => void, ) { // This function needs to be named so it can be called recursively. function promptAndCheckWithAlert(defaultName: string) { const promptText = Msg['RENAME_VARIABLE_TITLE'].replace( '%1', - variable.name, + variable.getName(), ); promptName(promptText, defaultName, function (newName) { if (!newName) { @@ -309,9 +313,13 @@ export function renameVariable( return; } - const existing = nameUsedWithOtherType(newName, variable.type, workspace); + const existing = nameUsedWithOtherType( + newName, + variable.getType(), + workspace, + ); const procedure = nameUsedWithConflictingParam( - variable.name, + variable.getName(), newName, workspace, ); @@ -325,8 +333,8 @@ export function renameVariable( let msg = ''; if (existing) { msg = Msg['VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE'] - .replace('%1', existing.name) - .replace('%2', existing.type); + .replace('%1', existing.getName()) + .replace('%2', existing.getType()); } else if (procedure) { msg = Msg['VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER'] .replace('%1', newName) @@ -380,12 +388,15 @@ function nameUsedWithOtherType( name: string, type: string, workspace: Workspace, -): VariableModel | null { +): IVariableModel | null { const allVariables = workspace.getVariableMap().getAllVariables(); name = name.toLowerCase(); for (let i = 0, variable; (variable = allVariables[i]); i++) { - if (variable.name.toLowerCase() === name && variable.type !== type) { + if ( + variable.getName().toLowerCase() === name && + variable.getType() !== type + ) { return variable; } } @@ -402,12 +413,12 @@ function nameUsedWithOtherType( export function nameUsedWithAnyType( name: string, workspace: Workspace, -): VariableModel | null { +): IVariableModel | null { const allVariables = workspace.getVariableMap().getAllVariables(); name = name.toLowerCase(); for (let i = 0, variable; (variable = allVariables[i]); i++) { - if (variable.name.toLowerCase() === name) { + if (variable.getName().toLowerCase() === name) { return variable; } } @@ -453,7 +464,7 @@ function checkForConflictingParamWithProcedureModels( const params = procedure .getParameters() .filter(isVariableBackedParameterModel) - .map((param) => param.getVariableModel().name); + .map((param) => param.getVariableModel().getName()); if (!params) continue; const procHasOld = params.some((param) => param.toLowerCase() === oldName); const procHasNew = params.some((param) => param.toLowerCase() === newName); @@ -493,7 +504,7 @@ function checkForConflictingParamWithLegacyProcedures( * @returns The generated DOM. */ export function generateVariableFieldDom( - variableModel: VariableModel, + variableModel: IVariableModel, ): Element { /* Generates the following XML: * foo @@ -501,8 +512,8 @@ export function generateVariableFieldDom( const field = utilsXml.createElement('field'); field.setAttribute('name', 'VAR'); field.setAttribute('id', variableModel.getId()); - field.setAttribute('variabletype', variableModel.type); - const name = utilsXml.createTextNode(variableModel.name); + field.setAttribute('variabletype', variableModel.getType()); + const name = utilsXml.createTextNode(variableModel.getName()); field.appendChild(name); return field; } @@ -524,7 +535,7 @@ export function getOrCreateVariablePackage( id: string | null, opt_name?: string, opt_type?: string, -): VariableModel { +): IVariableModel { let variable = getVariable(workspace, id, opt_name, opt_type); if (!variable) { variable = createVariable(workspace, id, opt_name, opt_type); @@ -552,7 +563,7 @@ export function getVariable( id: string | null, opt_name?: string, opt_type?: string, -): VariableModel | null { +): IVariableModel | null { const potentialVariableMap = workspace.getPotentialVariableMap(); let variable = null; // Try to just get the variable, by ID if possible. @@ -597,7 +608,7 @@ function createVariable( id: string | null, opt_name?: string, opt_type?: string, -): VariableModel { +): IVariableModel { const potentialVariableMap = workspace.getPotentialVariableMap(); // Variables without names get uniquely named for this workspace. if (!opt_name) { @@ -637,8 +648,8 @@ function createVariable( */ export function getAddedVariables( workspace: Workspace, - originalVariables: VariableModel[], -): VariableModel[] { + originalVariables: IVariableModel[], +): IVariableModel[] { const allCurrentVariables = workspace.getAllVariables(); const addedVariables = []; if (originalVariables.length !== allCurrentVariables.length) { @@ -654,6 +665,24 @@ export function getAddedVariables( return addedVariables; } +/** + * A custom compare function for the VariableModel objects. + * + * @param var1 First variable to compare. + * @param var2 Second variable to compare. + * @returns -1 if name of var1 is less than name of var2, 0 if equal, and 1 if + * greater. + * @internal + */ +export function compareByName( + var1: IVariableModel, + var2: IVariableModel, +): number { + return var1 + .getName() + .localeCompare(var2.getName(), undefined, {sensitivity: 'base'}); +} + export const TEST_ONLY = { generateUniqueNameInternal, }; diff --git a/core/variables_dynamic.ts b/core/variables_dynamic.ts index 6c4457549..5f1e2492c 100644 --- a/core/variables_dynamic.ts +++ b/core/variables_dynamic.ts @@ -9,7 +9,6 @@ import {Blocks} from './blocks.js'; import {Msg} from './msg.js'; import * as xml from './utils/xml.js'; -import {VariableModel} from './variable_model.js'; import * as Variables from './variables.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; @@ -129,7 +128,7 @@ export function flyoutCategoryBlocks(workspace: Workspace): Element[] { xmlList.push(block); } if (Blocks['variables_get_dynamic']) { - variableModelList.sort(VariableModel.compareByName); + variableModelList.sort(Variables.compareByName); for (let i = 0, variable; (variable = variableModelList[i]); i++) { const block = xml.createElement('block'); block.setAttribute('type', 'variables_get_dynamic'); diff --git a/core/workspace.ts b/core/workspace.ts index 71a2e4af9..063144995 100644 --- a/core/workspace.ts +++ b/core/workspace.ts @@ -29,7 +29,10 @@ import * as idGenerator from './utils/idgenerator.js'; import * as math from './utils/math.js'; import type * as toolbox from './utils/toolbox.js'; import {VariableMap} from './variable_map.js'; -import type {VariableModel} from './variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from './interfaces/i_variable_model.js'; import {WorkspaceComment} from './comments/workspace_comment.js'; import {IProcedureMap} from './interfaces/i_procedure_map.js'; import {ObservableProcedureMap} from './observable_procedure_map.js'; @@ -399,7 +402,7 @@ export class Workspace implements IASTNodeLocation { name: string, opt_type?: string | null, opt_id?: string | null, - ): VariableModel { + ): IVariableModel { return this.variableMap.createVariable( name, opt_type ?? undefined, @@ -436,7 +439,10 @@ export class Workspace implements IASTNodeLocation { * the empty string, which is a specific type. * @returns The variable with the given name. */ - getVariable(name: string, opt_type?: string): VariableModel | null { + getVariable( + name: string, + opt_type?: string, + ): IVariableModel | null { // TODO (#1559): Possibly delete this function after resolving #1559. return this.variableMap.getVariable(name, opt_type); } @@ -447,7 +453,7 @@ export class Workspace implements IASTNodeLocation { * @param id The ID to check for. * @returns The variable with the given ID. */ - getVariableById(id: string): VariableModel | null { + getVariableById(id: string): IVariableModel | null { return this.variableMap.getVariableById(id); } @@ -459,7 +465,7 @@ export class Workspace implements IASTNodeLocation { * @returns The sought after variables of the passed in type. An empty array * if none are found. */ - getVariablesOfType(type: string | null): VariableModel[] { + getVariablesOfType(type: string | null): IVariableModel[] { return this.variableMap.getVariablesOfType(type ?? ''); } @@ -478,7 +484,7 @@ export class Workspace implements IASTNodeLocation { * * @returns List of variable models. */ - getAllVariables(): VariableModel[] { + getAllVariables(): IVariableModel[] { return this.variableMap.getAllVariables(); } diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index aad748105..55a7540bd 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -62,7 +62,10 @@ import {Svg} from './utils/svg.js'; import * as svgMath from './utils/svg_math.js'; import * as toolbox from './utils/toolbox.js'; import * as userAgent from './utils/useragent.js'; -import type {VariableModel} from './variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from './interfaces/i_variable_model.js'; import * as Variables from './variables.js'; import * as VariablesDynamic from './variables_dynamic.js'; import * as WidgetDiv from './widgetdiv.js'; @@ -1354,7 +1357,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { name: string, opt_type?: string | null, opt_id?: string | null, - ): VariableModel { + ): IVariableModel { const newVar = super.createVariable(name, opt_type, opt_id); this.refreshToolboxSelection(); return newVar; diff --git a/core/xml.ts b/core/xml.ts index b8ecf6433..48d43c6f0 100644 --- a/core/xml.ts +++ b/core/xml.ts @@ -17,7 +17,10 @@ import {inputTypes} from './inputs/input_types.js'; import * as dom from './utils/dom.js'; import {Size} from './utils/size.js'; import * as utilsXml from './utils/xml.js'; -import type {VariableModel} from './variable_model.js'; +import type { + IVariableModel, + IVariableState, +} from './interfaces/i_variable_model.js'; import * as Variables from './variables.js'; import type {Workspace} from './workspace.js'; import {WorkspaceSvg} from './workspace_svg.js'; @@ -86,14 +89,16 @@ export function saveWorkspaceComment( * @param variableList List of all variable models. * @returns Tree of XML elements. */ -export function variablesToDom(variableList: VariableModel[]): Element { +export function variablesToDom( + variableList: IVariableModel[], +): Element { const variables = utilsXml.createElement('variables'); for (let i = 0; i < variableList.length; i++) { const variable = variableList[i]; const element = utilsXml.createElement('variable'); - element.appendChild(utilsXml.createTextNode(variable.name)); - if (variable.type) { - element.setAttribute('type', variable.type); + element.appendChild(utilsXml.createTextNode(variable.getName())); + if (variable.getType()) { + element.setAttribute('type', variable.getType()); } element.id = variable.getId(); variables.appendChild(element); diff --git a/generators/php/procedures.ts b/generators/php/procedures.ts index acf84aea6..9e3edd31f 100644 --- a/generators/php/procedures.ts +++ b/generators/php/procedures.ts @@ -25,7 +25,7 @@ export function procedures_defreturn(block: Block, generator: PhpGenerator) { const workspace = block.workspace; const usedVariables = Variables.allUsedVarModels(workspace) || []; for (const variable of usedVariables) { - const varName = variable.name; + const varName = variable.getName(); // getVars returns parameter names, not ids, for procedure blocks if (!block.getVars().includes(varName)) { globals.push(generator.getVariableName(varName)); diff --git a/generators/python/procedures.ts b/generators/python/procedures.ts index 51d2ee9a3..39c50698b 100644 --- a/generators/python/procedures.ts +++ b/generators/python/procedures.ts @@ -25,7 +25,7 @@ export function procedures_defreturn(block: Block, generator: PythonGenerator) { const workspace = block.workspace; const usedVariables = Variables.allUsedVarModels(workspace) || []; for (const variable of usedVariables) { - const varName = variable.name; + const varName = variable.getName(); // getVars returns parameter names, not ids, for procedure blocks if (!block.getVars().includes(varName)) { globals.push(generator.getVariableName(varName)); diff --git a/tests/mocha/xml_test.js b/tests/mocha/xml_test.js index c3ca2d416..d30716edb 100644 --- a/tests/mocha/xml_test.js +++ b/tests/mocha/xml_test.js @@ -922,6 +922,12 @@ suite('XML', function () { getId: function () { return varId; }, + getName: function () { + return name; + }, + getType: function () { + return type; + }, }; const generatedXml = Blockly.Xml.domToText(