Refactor and move variable reference fixup function into target.

This commit is contained in:
Karishma Chadha 2018-06-19 15:31:08 -04:00
parent f2aacbc79c
commit 9646e3d11e
4 changed files with 84 additions and 44 deletions

View file

@ -7,7 +7,6 @@ const {Map} = require('immutable');
const BlocksExecuteCache = require('./blocks-execute-cache');
const log = require('../util/log');
const Variable = require('./variable');
const StringUtil = require('../util/string-util');
/**
* @fileoverview
@ -673,26 +672,15 @@ class Blocks {
}
/**
* Fixes up variable references in this blocks container avoiding conflicts with
* pre-existing variables in the same scope.
* This is used when uploading a new sprite (the given target)
* into an existing project, where the new sprite may contain references
* to variable names that already exist as global variables in the project
* (and thus are in scope for variable references in the given sprite).
*
* If the given target has a block that references an existing global variable and that
* variable *does not* exist in the target itself (e.g. it was a global variable in the
* project the sprite was originally exported from), fix the variable references in this sprite
* to reference the id of the pre-existing global variable.
* If the given target has a block that references an existing global variable and that
* variable does exist in the target itself (e.g. it's a local variable in the sprite being uploaded),
* then the variable is renamed to distinguish itself from the pre-existing variable.
* All blocks that reference the local variable will be updated to use the new name.
* @param {Target} target The new target being uploaded, with potential variable conflicts
* @param {Runtime} runtime The runtime context with any pre-existing variables
* Returns a map of all references to variables or lists from blocks
* in this block container.
* @return {object} A map of variable ID to a list of all variable references
* for that ID. A variable reference contains the field referencing that variable
* and also the type of the variable being referenced.
*/
fixUpVariableReferences (target, runtime) {
getAllVariableAndListReferences () {
const blocks = this._blocks;
const allReferences = Object.create(null);
for (const blockId in blocks) {
let varOrListField = null;
let varType = null;
@ -705,33 +693,20 @@ class Blocks {
}
if (varOrListField) {
const currVarId = varOrListField.id;
const currVarName = varOrListField.value;
if (target.lookupVariableById(currVarId)) {
// Found a variable with the id in either the target or the stage,
// figure out which one.
if (target.variables.hasOwnProperty(currVarId)) {
// If the target has the variable, then check whether the stage
// has one with the same name and type. If it does, then rename
// this target specific variable so that there is a distinction.
const stage = runtime.getTargetForStage();
if (stage && stage.variables &&
stage.lookupVariableByNameAndType(currVarName, varType)) {
// TODO what should the new name be?
const newName = StringUtil.unusedName(
`${target.getName()}_${currVarName}`,
target.getAllVariableNamesInScopeByType(varType));
target.renameVariable(currVarId, newName);
this.updateBlocksAfterVarRename(currVarId, newName);
}
}
if (allReferences[currVarId]) {
allReferences[currVarId].push({
referencingField: varOrListField,
type: varType
});
} else {
const existingVar = target.lookupVariableByNameAndType(currVarName, varType);
if (existingVar) {
varOrListField.id = existingVar.id;
}
allReferences[currVarId] = [{
referencingField: varOrListField,
type: varType
}];
}
}
}
return allReferences;
}
/**

View file

@ -6,6 +6,7 @@ const Comment = require('../engine/comment');
const uid = require('../util/uid');
const {Map} = require('immutable');
const log = require('../util/log');
const StringUtil = require('../util/string-util');
/**
* @fileoverview
@ -331,6 +332,70 @@ class Target extends EventEmitter {
}
}
/**
* Fixes up variable references in this target avoiding conflicts with
* pre-existing variables in the same scope.
* This is used when uploading this target as a new sprite into an existing
* project, where the new sprite may contain references
* to variable names that already exist as global variables in the project
* (and thus are in scope for variable references in the given sprite).
*
* If the given target has a block that references an existing global variable and that
* variable *does not* exist in the target itself (e.g. it was a global variable in the
* project the sprite was originally exported from), fix the variable references in this sprite
* to reference the id of the pre-existing global variable.
* If the given target has a block that references an existing global variable and that
* variable does exist in the target itself (e.g. it's a local variable in the sprite being uploaded),
* then the variable is renamed to distinguish itself from the pre-existing variable.
* All blocks that reference the local variable will be updated to use the new name.
// * @param {Target} target The new target being uploaded, with potential variable conflicts
// * @param {Runtime} runtime The runtime context with any pre-existing variables
*/
fixUpVariableReferences () {
if (!this.runtime) return; // There's no runtime context to conflict with
if (this.isStage) return; // Stage can't have variable conflicts with itself (and also can't be uploaded)
const allReferences = this.blocks.getAllVariableAndListReferences();
const conflictIdsToReplace = Object.create(null);
for (const varId in allReferences) {
// We don't care about which var ref we get, they should all have the same var info
const varRef = allReferences[varId][0];
const varName = varRef.referencingField.value;
const varType = varRef.type;
if (this.lookupVariableById(varId)) {
// Found a variable with the id in either the target or the stage,
// figure out which one.
if (this.variables.hasOwnProperty(varId)) {
// If the target has the variable, then check whether the stage
// has one with the same name and type. If it does, then rename
// this target specific variable so that there is a distinction.
const stage = this.runtime.getTargetForStage();
if (stage && stage.variables &&
stage.lookupVariableByNameAndType(varName, varType)) {
// TODO what should the new name be?
const newName = StringUtil.unusedName(
`${this.getName()}_${varName}`,
this.getAllVariableNamesInScopeByType(varType));
this.renameVariable(varId, newName);
this.blocks.updateBlocksAfterVarRename(varId, newName);
}
}
} else {
const existingVar = this.lookupVariableByNameAndType(varName, varType);
if (existingVar && !conflictIdsToReplace[varId]) {
conflictIdsToReplace[varId] = existingVar.id;
}
}
}
for (const conflictId in conflictIdsToReplace) {
const existingId = conflictIdsToReplace[conflictId];
allReferences[conflictId].map(varRef => {
varRef.referencingField.id = existingId;
return varRef;
});
}
}
/**
* Post/edit sprite info.
* @param {object} data An object with sprite info data to set.