diff --git a/core/block.js b/core/block.js index 4d5612de2..56710b7a3 100644 --- a/core/block.js +++ b/core/block.js @@ -456,7 +456,8 @@ Blockly.Block.prototype.unplugFromRow_ = function(opt_healStack) { // Disconnect the child block. childConnection.disconnect(); // Connect child to the parent if possible, otherwise bump away. - if (childConnection.checkType(parentConnection)) { + if (this.workspace.connectionTypeChecker.canConnect( + childConnection, parentConnection, false, false)) { parentConnection.connect(childConnection); } else { childConnection.onFailedConnect(parentConnection); @@ -508,7 +509,9 @@ Blockly.Block.prototype.unplugFromStack_ = function(opt_healStack) { // Disconnect the next statement. var nextTarget = this.nextConnection.targetConnection; nextTarget.disconnect(); - if (previousTarget && previousTarget.checkType(nextTarget)) { + if (previousTarget && + this.workspace.connectionTypeChecker.canConnect( + previousTarget, nextTarget, false, false)) { // Attach the next statement to the previous statement. previousTarget.connect(nextTarget); } diff --git a/core/connection.js b/core/connection.js index a3597de03..9098f63a5 100644 --- a/core/connection.js +++ b/core/connection.js @@ -147,10 +147,10 @@ Blockly.Connection.prototype.connect_ = function(childConnection) { if (nextBlock && !nextBlock.isShadow()) { newBlock = nextBlock; } else { - if (orphanBlock.workspace.connectionTypeChecker.checkType( - orphanBlock.previousConnection, newBlock.nextConnection)) { - //orphanBlock.previousConnection.checkType( - //newBlock.nextConnection)) { + var typeChecker = orphanBlock.workspace.connectionTypeChecker; + if (typeChecker.canConnect( + orphanBlock.previousConnection, newBlock.nextConnection, + false, false)) { newBlock.nextConnection.connect(orphanBlock.previousConnection); orphanBlock = null; } @@ -249,6 +249,7 @@ Blockly.Connection.prototype.isConnected = function() { * @param {Blockly.Connection} target Connection to check compatibility with. * @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal, * an error code otherwise. + * @deprecated July 2020 */ Blockly.Connection.prototype.canConnectWithReason = function(target) { // TODO: deprecation warning with date, plus tests. @@ -261,6 +262,7 @@ Blockly.Connection.prototype.canConnectWithReason = function(target) { * @param {Blockly.Connection} target The connection to check compatibility * with. * @package + * @deprecated July 2020 */ Blockly.Connection.prototype.checkConnection = function(target) { // TODO: Add deprecation warning notices *and* add tests to make sure these @@ -269,50 +271,21 @@ Blockly.Connection.prototype.checkConnection = function(target) { checker.canConnect(this, target, false, true); }; +/** + * Get the workspace's connection type checker object. + * @return {!Blockly.ConnectionTypeChecker} The connection type checker for the + * source block's workspace. + * @package + */ Blockly.Connection.prototype.getConnectionTypeChecker = function() { return this.sourceBlock_.workspace.connectionTypeChecker; }; -// /** -// * Check if the two connections can be dragged to connect to each other. -// * This is used by the connection database when searching for the closest -// * connection. -// * @param {!Blockly.Connection} candidate A nearby connection to check, which -// * must be a previous connection. -// * @return {boolean} True if the connection is allowed, false otherwise. -// * @private -// */ -// Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) { -// if (this.targetConnection) { -// // This connection is already occupied. -// // A next connection will never disconnect itself mid-drag. -// return false; -// } - -// // Don't let blocks try to connect to themselves or ones they nest. -// if (Blockly.draggingConnections.indexOf(candidate) != -1) { -// return false; -// } - -// if (!candidate.targetConnection) { -// return true; -// } - -// var targetBlock = candidate.targetBlock(); -// // If it is connected to a real block, game over. -// if (!targetBlock.isInsertionMarker()) { -// return false; -// } -// // If it's connected to an insertion marker but that insertion marker -// // is the first block in a stack, it's still fine. If that insertion -// // marker is in the middle of a stack, it won't work. -// return !targetBlock.getPreviousBlock(); -// }; - /** * Check if the two connections can be dragged to connect to each other. * @param {!Blockly.Connection} candidate A nearby connection to check. * @return {boolean} True if the connection is allowed, false otherwise. + * @deprecated July 2020 */ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) { return this.getConnectionTypeChecker().canConnect(this, candidate, true, false); @@ -339,22 +312,22 @@ Blockly.Connection.prototype.connect = function(otherConnection) { } var checker = this.getConnectionTypeChecker(); - checker.canConnect(this, otherConnection, false, true); - - var eventGroup = Blockly.Events.getGroup(); - if (!eventGroup) { - Blockly.Events.setGroup(true); - } - // Determine which block is superior (higher in the source stack). - if (this.isSuperior()) { - // Superior block. - this.connect_(otherConnection); - } else { - // Inferior block. - otherConnection.connect_(this); - } - if (!eventGroup) { - Blockly.Events.setGroup(false); + if (checker.canConnect(this, otherConnection, false, true)) { + var eventGroup = Blockly.Events.getGroup(); + if (!eventGroup) { + Blockly.Events.setGroup(true); + } + // Determine which block is superior (higher in the source stack). + if (this.isSuperior()) { + // Superior block. + this.connect_(otherConnection); + } else { + // Inferior block. + otherConnection.connect_(this); + } + if (!eventGroup) { + Blockly.Events.setGroup(false); + } } }; @@ -383,10 +356,12 @@ Blockly.Connection.connectReciprocally_ = function(first, second) { */ Blockly.Connection.singleConnection_ = function(block, orphanBlock) { var connection = null; + var output = orphanBlock.outputConnection; for (var i = 0; i < block.inputList.length; i++) { var thisConnection = block.inputList[i].connection; + var typeChecker = output.getConnectionTypeChecker(); if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE && - orphanBlock.outputConnection.checkType(thisConnection)) { + typeChecker.canConnect(output, thisConnection, false, false)) { if (connection) { return null; // More than one connection. } @@ -516,7 +491,7 @@ Blockly.Connection.prototype.targetBlock = function() { * @return {boolean} True if the connections share a type. */ Blockly.Connection.prototype.checkType = function(otherConnection) { - return this.getConnectionTypeChecker().checkType(this, otherConnection); + return this.getConnectionTypeChecker().canConnect(this, otherConnection, false, false); }; /** @@ -541,7 +516,8 @@ Blockly.Connection.prototype.checkType_ = function(otherConnection) { Blockly.Connection.prototype.onCheckChanged_ = function() { // The new value type may not be compatible with the existing connection. if (this.isConnected() && (!this.targetConnection || - !this.checkType(this.targetConnection))) { + !this.getConnectionTypeChecker().canConnect( + this, this.targetConnection, false, false))) { var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; child.unplug(); } diff --git a/core/connection_checks.js b/core/connection_checks.js index 7e51f62cb..3d703b85a 100644 --- a/core/connection_checks.js +++ b/core/connection_checks.js @@ -11,6 +11,94 @@ goog.requireType('Blockly.Connection'); Blockly.ConnectionTypeChecker = function() { }; +/** + * Check whether the current connection can connect with the target + * connection. + * @param {Blockly.Connection} one Connection to check compatibility with. + * @param {Blockly.Connection} two Connection to check compatibility with. + * @param {boolean} isDragging True if the connection is being made by dragging + * a block. + * @param {boolean} shouldThrow Whether to throw an error when a connection is + * invalid. + * @return {boolean} Whether the connection is legal. + */ +Blockly.ConnectionTypeChecker.prototype.canConnect = function(one, two, + isDragging, shouldThrow) { + if (this.passesSafetyChecks(one, two, shouldThrow)) { + if (this.passesTypeChecks(one, two, shouldThrow)) { + if (!isDragging || this.passesDragChecks(one, two, shouldThrow)) { + return true; + } + } + } + return false; +}; + +/** + * Check that connecting the given connections is safe, meaning that it would + * not break any of Blockly's basic assumptions--no self connections, etc. + * @param {!Blockly.Connection} one The first of the connections to check. + * @param {!Blockly.Connection} two The second of the connections to check. + * @param {boolean} shouldThrow Whether to throw an error if the connection is + * unsafe. + * @return {boolean} Whether the connection is safe. + * @package + */ +Blockly.ConnectionTypeChecker.prototype.passesSafetyChecks = function(one, two, shouldThrow) { + var safety = this.doSafetyChecks_(one, two); + if (safety == Blockly.Connection.CAN_CONNECT) { + return true; + } + if (shouldThrow) { + throw Error(this.getErrorMessage_(safety, one, two)); + } + return false; +}; + + +Blockly.ConnectionTypeChecker.prototype.passesTypeChecks = function(one, two, + shouldThrow) { + if (this.doTypeChecks_(one, two)) { + return true; + } + if (shouldThrow) { + throw Error(this.getErrorMessage_( + Blockly.Connection.REASON_CHECKS_FAILED, one, two)); + } + return false; +}; + +Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two, + shouldThrow) { + if (this.doDragChecks_(one, two)) { + return true; + } + if (shouldThrow) { + throw Error(this.getErrorMessage_( + Blockly.Connection.REASON_DRAG_CHECKS_FAILED, one, two)); + } + return false; +}; + +/** + * Checks whether the current connection can connect with the target + * connection. + * @param {Blockly.Connection} one Connection to check compatibility with. + * @param {Blockly.Connection} two Connection to check compatibility with. + * @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal, + * an error code otherwise. + */ +Blockly.ConnectionTypeChecker.prototype.canConnectWithReason = function(one, two) { + var safety = this.doSafetyChecks_(one, two); + if (safety != Blockly.Connection.CAN_CONNECT) { + return safety; + } + if (!this.doTypeChecks_(one, two)) { + return Blockly.Connection.REASON_CHECKS_FAILED; + } + return Blockly.Connection.CAN_CONNECT; +}; + /** * Helper method that translates a connection error code into a string. * @param {number} errorCode The error code. @@ -18,9 +106,9 @@ Blockly.ConnectionTypeChecker = function() { * @param {!Blockly.Connection} two The second of the two connections being * checked. * @return {string} A developer-readable error string. - * @package + * @private */ -Blockly.ConnectionTypeChecker.prototype.getErrorMessage = function(errorCode, +Blockly.ConnectionTypeChecker.prototype.getErrorMessage_ = function(errorCode, one, two) { switch (errorCode) { case Blockly.Connection.REASON_SELF_CONNECTION: @@ -39,32 +127,21 @@ Blockly.ConnectionTypeChecker.prototype.getErrorMessage = function(errorCode, case Blockly.Connection.REASON_SHADOW_PARENT: return 'Connecting non-shadow to shadow block.'; case Blockly.Connection.REASON_DRAG_CHECKS_FAILED: - return 'Drag checks failed.' + return 'Drag checks failed.'; default: return 'Unknown connection failure: this should never happen!'; } }; /** - * Checks whether the current connection can connect with the target - * connection. - * @param {Blockly.Connection} one Connection to check compatibility with. - * @param {Blockly.Connection} two Connection to check compatibility with. - * @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal, - * an error code otherwise. + * Check that connecting the given connections is safe, meaning that it would + * not break any of Blockly's basic assumptions--no self connections, etc. + * @param {!Blockly.Connection} one The first of the connections to check. + * @param {!Blockly.Connection} two The second of the connections to check. + * @return {boolean} True if making this connection is safe. + * @private */ -Blockly.ConnectionTypeChecker.prototype.canConnectWithReason = function(one, two) { - var validity = this.doValidityChecks(one, two); - if (validity != Blockly.Connection.CAN_CONNECT) { - return validity; - } - if (!this.checkType(one, two)) { - return Blockly.Connection.REASON_CHECKS_FAILED; - } - return Blockly.Connection.CAN_CONNECT; -}; - -Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) { +Blockly.ConnectionTypeChecker.prototype.doSafetyChecks_ = function(one, two) { if (!one || !two) { return Blockly.Connection.REASON_TARGET_NULL; } @@ -75,8 +152,6 @@ Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) { var blockB = one.getSourceBlock(); var blockA = two.getSourceBlock(); } - // TODO (fenichel): The null checks seem like they're only for making tests - // work better. if (blockA == blockB) { return Blockly.Connection.REASON_SELF_CONNECTION; } else if (two.type != Blockly.OPPOSITE_TYPE[one.type]) { @@ -90,53 +165,15 @@ Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) { }; /** - * Checks whether the current connection can connect with the target - * connection. - * @param {Blockly.Connection} one Connection to check compatibility with. - * @param {Blockly.Connection} two Connection to check compatibility with. - * @return {boolean} Whether the connection is legal. - */ -Blockly.ConnectionTypeChecker.prototype.canConnect = function(one, two, - isDragging, shouldThrow) { - var validity = this.doValidityChecks(one, two); - if (validity != Blockly.Connection.CAN_CONNECT) { - if (shouldThrow) { - throw Error(this.getErrorMessage(validity, one, two)); - } - return false; - } - - var passesTypeChecks = this.checkType(one, two); - if (!passesTypeChecks) { - - if (shouldThrow) { - throw Error(this.getErrorMessage( - Blockly.Connection.REASON_CHECKS_FAILED, one, two)); - } - return false; - } - - if (isDragging) { - var passesDragChecks = this.passesDragChecks(one, two); - if (!passesDragChecks) { - if (shouldThrow) { - throw Error(this.getErrorMessage( - Blockly.Connection.REASON_DRAG_CHECKS_FAILED, one, two)); - } - return false; - } - } - return true; -}; - -/** - * Is this connection compatible with another connection with respect to the - * value type system. E.g. square_root("Hello") is not compatible. + * Check whether this connection is compatible with another connection with + * respect to the value type system. E.g. square_root("Hello") is not + * compatible. * @param {!Blockly.Connection} one Connection to compare. * @param {!Blockly.Connection} two Connection to compare against. * @return {boolean} True if the connections share a type. + * @protected */ -Blockly.ConnectionTypeChecker.prototype.checkType = function(one, two) { +Blockly.ConnectionTypeChecker.prototype.doTypeChecks_ = function(one, two) { var checkArrayOne = one.getCheck(); var checkArrayTwo = two.getCheck(); @@ -154,7 +191,14 @@ Blockly.ConnectionTypeChecker.prototype.checkType = function(one, two) { return false; }; -Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) { +/** + * Check whether this connection can be made by dragging. + * @param {!Blockly.Connection} one Connection to compare. + * @param {!Blockly.Connection} two Connection to compare against. + * @return {boolean} True if the connections share a type. + * @protected + */ +Blockly.ConnectionTypeChecker.prototype.doDragChecks_ = function(one, two) { // Don't consider insertion markers. if (two.sourceBlock_.isInsertionMarker()) { return false; @@ -198,7 +242,8 @@ Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) { break; } default: - throw Error('Unknown connection type in passesDragChecks'); + // Unexpected connection type. + return false; } // Don't let blocks try to connect to themselves or ones they nest. @@ -210,15 +255,13 @@ Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) { }; /** - * Check if the two connections can be dragged to connect to each other. - * This is used by the connection database when searching for the closest - * connection. + * Helper function for drag checking * @param {!Blockly.Connection} one The connection to check, which must be a * statement input or next connection. * @param {!Blockly.Connection} two A nearby connection to check, which * must be a previous connection. * @return {boolean} True if the connection is allowed, false otherwise. - * @private + * @protected */ Blockly.ConnectionTypeChecker.prototype.canConnectToPrevious_ = function(one, two) { if (one.targetConnection) { diff --git a/core/rendered_connection.js b/core/rendered_connection.js index a2bd877e2..ea8e2868e 100644 --- a/core/rendered_connection.js +++ b/core/rendered_connection.js @@ -422,6 +422,7 @@ Blockly.RenderedConnection.prototype.startTrackingAll = function() { * @param {number=} maxRadius The maximum radius allowed for connections, in * workspace units. * @return {boolean} True if the connection is allowed, false otherwise. + * @deprecated July 2020 */ Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate, maxRadius) { @@ -551,7 +552,8 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) { Blockly.RenderedConnection.prototype.onCheckChanged_ = function() { // The new value type may not be compatible with the existing connection. if (this.isConnected() && (!this.targetConnection || - !this.checkType(this.targetConnection))) { + !this.getConnectionTypeChecker().canConnect( + this, this.targetConnection, false, false))) { var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; child.unplug(); // Bump away. diff --git a/core/renderers/common/renderer.js b/core/renderers/common/renderer.js index 05885d18c..f0b158c0e 100644 --- a/core/renderers/common/renderer.js +++ b/core/renderers/common/renderer.js @@ -254,7 +254,8 @@ Blockly.blockRendering.Renderer.prototype.orphanCanConnectAtEnd = if (!lastConnection) { return false; } - return orphanConnection.checkType(lastConnection); + return orphanConnection.getConnectionTypeChecker().canConnect( + lastConnection, orphanConnection, false, false); }; /** diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js index a4ac148d7..dd8627943 100644 --- a/tests/mocha/connection_test.js +++ b/tests/mocha/connection_test.js @@ -229,13 +229,12 @@ suite('Connection type checker', function() { this.con2 = new Blockly.Connection({}, Blockly.NEXT_STATEMENT); }); function assertCheckTypes(checker, one, two) { - chai.assert.isTrue(checker.checkType(one, two)); + chai.assert.isTrue(checker.passesTypeChecks(one, two)); // Order should not matter. - chai.assert.isTrue(checker.checkType(one, two)); + chai.assert.isTrue(checker.passesTypeChecks(one, two)); } test('No Types', function() { assertCheckTypes(this.checker, this.con1, this.con2); - chai.assert.isTrue(this.checker.checkType(this.con1, this.con2)); }); test('Same Type', function() { this.con1.setCheck('type1'); @@ -259,7 +258,7 @@ suite('Connection type checker', function() { test('No Compatible Types', function() { this.con1.setCheck('type1'); this.con2.setCheck('type2'); - chai.assert.isFalse(this.checker.checkType(this.con1, this.con2)); + chai.assert.isFalse(this.checker.passesTypeChecks(this.con1, this.con2)); }); }); });