refactor: Use IVariableModel instead of VariableModel. (#8400)

* refactor: Use IVariableModel methods instead of directly accessing properties.

* refactor: replace references to VariableModel with IVariableModel.
This commit is contained in:
Aaron Dodson
2024-07-19 14:58:04 -07:00
committed by GitHub
parent 02e64bebbe
commit 294ef74d1b
22 changed files with 248 additions and 141 deletions

View File

@@ -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',

View File

@@ -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<IVariableState>[];
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<IVariableState>[] {
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<IVariableState>,
) {
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<IVariableState>[];
};
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<IVariableState>[];
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<IVariableState>[] {
return this.argumentVarModels_;
},
/**

View File

@@ -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());
}
},
};

View File

@@ -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<IVariableState>[] {
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<IVariableState>) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
for (let j = 0, field; (field = input.fieldRow[j]); j++) {
if (

View File

@@ -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<IVariableState>) {
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;
}
/**

View File

@@ -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<IVariableState>) {
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();
}
/**

View File

@@ -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<IVariableState>) {
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();
}
/**

View File

@@ -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<IVariableState>, 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;
}

View File

@@ -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<IVariableState> | 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<IVariableState> | 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<IVariableState>[] = [];
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'],

View File

@@ -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<IVariableState>;
}
/**

View File

@@ -95,7 +95,7 @@ export class Names {
}
const variable = this.variableMap.getVariableById(id);
if (variable) {
return variable.name;
return variable.getName();
}
return null;
}

View File

@@ -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<IVariableState>[],
) {
if (eventUtils.isEnabled()) {
const newVariables = Variables.getAddedVariables(

View File

@@ -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<VariableModel> {
export class VariableMap
implements IVariableMap<IVariableModel<IVariableState>>
{
/**
* 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<string, Map<string, VariableModel>>();
private variableMap = new Map<
string,
Map<string, IVariableModel<IVariableState>>
>();
/** @param workspace The workspace this map belongs to. */
constructor(public workspace: Workspace) {}
@@ -63,9 +68,12 @@ export class VariableMap implements IVariableMap<VariableModel> {
* @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<IVariableState>,
newName: string,
): IVariableModel<IVariableState> {
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<VariableModel> {
return variable;
}
changeVariableType(variable: VariableModel, newType: string): VariableModel {
changeVariableType(
variable: IVariableModel<IVariableState>,
newType: string,
): IVariableModel<IVariableState> {
this.variableMap.get(variable.getType())?.delete(variable.getId());
variable.setType(newType);
const newTypeVariables =
this.variableMap.get(newType) ?? new Map<string, VariableModel>();
this.variableMap.get(newType) ??
new Map<string, IVariableModel<IVariableState>>();
newTypeVariables.set(variable.getId(), variable);
if (!this.variableMap.has(newType)) {
this.variableMap.set(newType, newTypeVariables);
@@ -129,14 +141,14 @@ export class VariableMap implements IVariableMap<VariableModel> {
* @param blocks The list of all blocks in the workspace.
*/
private renameVariableAndUses_(
variable: VariableModel,
variable: IVariableModel<IVariableState>,
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<VariableModel> {
* @param blocks The list of all blocks in the workspace.
*/
private renameVariableWithConflict_(
variable: VariableModel,
variable: IVariableModel<IVariableState>,
newName: string,
conflictVar: VariableModel,
conflictVar: IVariableModel<IVariableState>,
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<VariableModel> {
name: string,
opt_type?: string,
opt_id?: string,
): VariableModel {
): IVariableModel<IVariableState> {
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<VariableModel> {
}
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<string, VariableModel>();
this.variableMap.get(type) ??
new Map<string, IVariableModel<IVariableState>>();
variables.set(variable.getId(), variable);
if (!this.variableMap.has(type)) {
this.variableMap.set(type, variables);
@@ -235,10 +256,13 @@ export class VariableMap implements IVariableMap<VariableModel> {
*
* @param variable The variable to add.
*/
addVariable(variable: VariableModel) {
addVariable(variable: IVariableModel<IVariableState>) {
const type = variable.getType();
if (!this.variableMap.has(type)) {
this.variableMap.set(type, new Map<string, VariableModel>());
this.variableMap.set(
type,
new Map<string, IVariableModel<IVariableState>>(),
);
}
this.variableMap.get(type)?.set(variable.getId(), variable);
}
@@ -249,13 +273,13 @@ export class VariableMap implements IVariableMap<VariableModel> {
*
* @param variable Variable to delete.
*/
deleteVariable(variable: VariableModel) {
const variables = this.variableMap.get(variable.type);
deleteVariable(variable: IVariableModel<IVariableState>) {
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<VariableModel> {
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<VariableModel> {
* @param uses An array of uses of the variable.
* @internal
*/
deleteVariableInternal(variable: VariableModel, uses: Block[]) {
deleteVariableInternal(
variable: IVariableModel<IVariableState>,
uses: Block[],
) {
const existingGroup = eventUtils.getGroup();
if (!existingGroup) {
eventUtils.setGroup(true);
@@ -336,7 +363,10 @@ export class VariableMap implements IVariableMap<VariableModel> {
* 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<IVariableState> | null {
const type = opt_type || '';
const variables = this.variableMap.get(type);
if (!variables) return null;
@@ -354,7 +384,7 @@ export class VariableMap implements IVariableMap<VariableModel> {
* @param id The ID to check for.
* @returns The variable with the given ID.
*/
getVariableById(id: string): VariableModel | null {
getVariableById(id: string): IVariableModel<IVariableState> | 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<VariableModel> {
* @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<IVariableState>[] {
type = type || '';
const variables = this.variableMap.get(type);
if (!variables) return [];
@@ -416,8 +446,8 @@ export class VariableMap implements IVariableMap<VariableModel> {
*
* @returns List of variable models.
*/
getAllVariables(): VariableModel[] {
let allVariables: VariableModel[] = [];
getAllVariables(): IVariableModel<IVariableState>[] {
let allVariables: IVariableModel<IVariableState>[] = [];
for (const variables of this.variableMap.values()) {
allVariables = allVariables.concat(...variables.values());
}

View File

@@ -26,7 +26,7 @@ import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js';
* @see {Blockly.FieldVariable}
*/
export class VariableModel implements IVariableModel<IVariableState> {
type: string;
private type: string;
private readonly id_: string;
/**
@@ -39,8 +39,8 @@ export class VariableModel implements IVariableModel<IVariableState> {
* @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<IVariableState> {
* @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'});
}
}

View File

@@ -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<IVariableState>[] {
const blocks = ws.getAllBlocks(false);
const variables = new Set<VariableModel>();
const variables = new Set<IVariableModel<IVariableState>>();
// 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<IVariableState>,
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<IVariableState> | 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<IVariableState> | 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<IVariableState>,
): Element {
/* Generates the following XML:
* <field name="VAR" id="goKTKmYJ8DhVHpruv" variabletype="int">foo</field>
@@ -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<IVariableState> {
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<IVariableState> | 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<IVariableState> {
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<IVariableState>[],
): IVariableModel<IVariableState>[] {
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<IVariableState>,
var2: IVariableModel<IVariableState>,
): number {
return var1
.getName()
.localeCompare(var2.getName(), undefined, {sensitivity: 'base'});
}
export const TEST_ONLY = {
generateUniqueNameInternal,
};

View File

@@ -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');

View File

@@ -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<IVariableState> {
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<IVariableState> | 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<IVariableState> | 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<IVariableState>[] {
return this.variableMap.getVariablesOfType(type ?? '');
}
@@ -478,7 +484,7 @@ export class Workspace implements IASTNodeLocation {
*
* @returns List of variable models.
*/
getAllVariables(): VariableModel[] {
getAllVariables(): IVariableModel<IVariableState>[] {
return this.variableMap.getAllVariables();
}

View File

@@ -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<IVariableState> {
const newVar = super.createVariable(name, opt_type, opt_id);
this.refreshToolboxSelection();
return newVar;

View File

@@ -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<IVariableState>[],
): 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);

View File

@@ -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));

View File

@@ -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));

View File

@@ -922,6 +922,12 @@ suite('XML', function () {
getId: function () {
return varId;
},
getName: function () {
return name;
},
getType: function () {
return type;
},
};
const generatedXml = Blockly.Xml.domToText(