From b4de3ead0814145223b33ace530037eed2752264 Mon Sep 17 00:00:00 2001 From: Trevor Parscal Date: Mon, 6 Aug 2012 13:38:00 -0700 Subject: [PATCH] Throw ve.Error instead of string literals Throwing strings is bad because it doesn't include a lot of important information that an error object does, such as a stack trace or where the error was actually thrown from. ve.Error inherits directly from Error. In the future we may create more specific subclasses and/or do custom stuff. Some interesting reading on the subject: * http://www.devthought.com/2011/12/22/a-string-is-not-an-error/ Change-Id: Ib7c568a1dcb98abac44c6c146e84dde5315b2826 --- VisualEditor.php | 1 + demos/ve/index.php | 1 + modules/ve/ce/nodes/ve.ce.TextNode.js | 2 +- modules/ve/ce/ve.ce.BranchNode.js | 4 +- modules/ve/ce/ve.ce.NodeFactory.js | 2 +- modules/ve/dm/ve.dm.Converter.js | 4 +- modules/ve/dm/ve.dm.Document.js | 24 ++++++------ modules/ve/dm/ve.dm.DocumentSynchronizer.js | 2 +- modules/ve/dm/ve.dm.Node.js | 2 +- modules/ve/dm/ve.dm.NodeFactory.js | 14 +++---- modules/ve/dm/ve.dm.Transaction.js | 18 ++++----- modules/ve/dm/ve.dm.TransactionProcessor.js | 16 ++++---- modules/ve/init/ve.init.Platform.js | 8 ++-- modules/ve/test/ce/ve.ce.NodeFactory.test.js | 2 +- modules/ve/test/dm/ve.dm.Document.test.js | 2 +- modules/ve/test/dm/ve.dm.Node.test.js | 2 +- modules/ve/test/dm/ve.dm.NodeFactory.test.js | 8 ++-- modules/ve/test/dm/ve.dm.Transaction.test.js | 10 ++--- .../dm/ve.dm.TransactionProcessor.test.js | 12 +++--- modules/ve/test/index.html | 1 + modules/ve/test/ve.BranchNode.test.js | 2 +- modules/ve/test/ve.Factory.test.js | 4 +- modules/ve/ui/tools/ve.ui.ButtonTool.js | 2 +- modules/ve/ui/tools/ve.ui.DropdownTool.js | 2 +- modules/ve/ui/ve.ui.Context.js | 6 +-- modules/ve/ui/ve.ui.Menu.js | 2 +- modules/ve/ui/ve.ui.Tool.js | 2 +- modules/ve/ve.BranchNode.js | 4 +- modules/ve/ve.Document.js | 6 +-- modules/ve/ve.Error.js | 38 +++++++++++++++++++ modules/ve/ve.EventEmitter.js | 8 ++-- modules/ve/ve.Factory.js | 4 +- modules/ve/ve.Node.js | 10 ++--- modules/ve/ve.Range.js | 2 +- 34 files changed, 134 insertions(+), 93 deletions(-) create mode 100644 modules/ve/ve.Error.js diff --git a/VisualEditor.php b/VisualEditor.php index 6d2e4b1033..b0a110000f 100644 --- a/VisualEditor.php +++ b/VisualEditor.php @@ -101,6 +101,7 @@ $wgResourceModules += array( 'scripts' => array( // ve 've/ve.js', + 've/ve.Error.js', 've/ve.EventEmitter.js', 've/init/ve.init.js', 've/init/ve.init.Platform.js', diff --git a/demos/ve/index.php b/demos/ve/index.php index 6ad5656f2f..8252068694 100644 --- a/demos/ve/index.php +++ b/demos/ve/index.php @@ -57,6 +57,7 @@ $html = '
' . file_get_contents( $page ) . '
'; + diff --git a/modules/ve/ce/nodes/ve.ce.TextNode.js b/modules/ve/ce/nodes/ve.ce.TextNode.js index f5155f4811..3fe6ce0d9f 100644 --- a/modules/ve/ce/nodes/ve.ce.TextNode.js +++ b/modules/ve/ce/nodes/ve.ce.TextNode.js @@ -157,7 +157,7 @@ ve.ce.TextNode.annotationRenderers = { */ ve.ce.TextNode.prototype.onUpdate = function ( force ) { if ( !force && !this.root.getSurface ) { - throw 'Can not update a text node that is not attached to a document'; + throw new ve.Error( 'Can not update a text node that is not attached to a document' ); } if ( force === true || this.root.getSurface().render === true ) { var $new = $( '' ).html( this.getHtml() ).contents(); diff --git a/modules/ve/ce/ve.ce.BranchNode.js b/modules/ve/ce/ve.ce.BranchNode.js index f170a23efb..107e50d850 100644 --- a/modules/ve/ce/ve.ce.BranchNode.js +++ b/modules/ve/ce/ve.ce.BranchNode.js @@ -66,11 +66,11 @@ ve.ce.BranchNode.getDomWrapperType = function ( model, key ) { var types, value = model.getAttribute( key ); if ( value === undefined ) { - throw 'Undefined attribute: ' + key; + throw new ve.Error( 'Undefined attribute: ' + key ); } types = ve.ce.nodeFactory.lookup( model.getType() ).domWrapperElementTypes; if ( types[value] === undefined ) { - throw 'Invalid attribute value: ' + value; + throw new ve.Error( 'Invalid attribute value: ' + value ); } return types[value]; }; diff --git a/modules/ve/ce/ve.ce.NodeFactory.js b/modules/ve/ce/ve.ce.NodeFactory.js index 661e9b59fe..1a7a1a8e06 100644 --- a/modules/ve/ce/ve.ce.NodeFactory.js +++ b/modules/ve/ce/ve.ce.NodeFactory.js @@ -30,7 +30,7 @@ ve.ce.NodeFactory.prototype.canNodeBeSplit = function ( type ) { if ( type in this.registry ) { return this.registry[type].rules.canBeSplit; } - throw 'Unknown node type: ' + type; + throw new ve.Error( 'Unknown node type: ' + type ); }; /* Inheritance */ diff --git a/modules/ve/dm/ve.dm.Converter.js b/modules/ve/dm/ve.dm.Converter.js index e687a0e7d1..25cf8f36d9 100644 --- a/modules/ve/dm/ve.dm.Converter.js +++ b/modules/ve/dm/ve.dm.Converter.js @@ -75,7 +75,7 @@ ve.dm.Converter.getDataContentFromText = function ( text, annotations ) { */ ve.dm.Converter.prototype.onNodeRegister = function ( dataElementType, constructor ) { if ( constructor.converters === undefined ) { - throw 'Missing conversion data in node implementation of ' + dataElementType; + throw new ve.Error( 'Missing conversion data in node implementation of ' + dataElementType ); } else if ( constructor.converters !== null ) { var i, domElementTypes = constructor.converters.domElementTypes, @@ -100,7 +100,7 @@ ve.dm.Converter.prototype.onNodeRegister = function ( dataElementType, construct */ ve.dm.Converter.prototype.onAnnotationRegister = function ( dataElementType, constructor ) { if ( constructor.converters === undefined ) { - throw 'Missing conversion data in annotation implementation of ' + dataElementType; + throw new ve.Error( 'Missing conversion data in annotation implementation of ' + dataElementType ); } else if ( constructor.converters !== null ) { var i, domElementTypes = constructor.converters.domElementTypes, diff --git a/modules/ve/dm/ve.dm.Document.js b/modules/ve/dm/ve.dm.Document.js index 77193add7d..c408d50355 100644 --- a/modules/ve/dm/ve.dm.Document.js +++ b/modules/ve/dm/ve.dm.Document.js @@ -93,7 +93,7 @@ ve.dm.Document = function ( data, parentDocument ) { parentStack = stack[stack.length - 2]; if ( !parentStack ) { // This can only happen if we got unbalanced data - throw 'Unbalanced input passed to document'; + throw new ve.Error( 'Unbalanced input passed to document' ); } if ( children.length === 0 && @@ -574,7 +574,7 @@ ve.dm.Document.prototype.getAnnotatedRangeFromSelection = function ( range, anno */ ve.dm.Document.prototype.offsetContainsMatchingAnnotations = function ( offset, pattern ) { if ( !( pattern instanceof RegExp ) ) { - throw 'Invalid Pattern. Pattern not instance of RegExp'; + throw new ve.Error( 'Invalid Pattern. Pattern not instance of RegExp' ); } var hash, annotations = ve.isArray( this.data[offset] ) ? @@ -599,7 +599,7 @@ ve.dm.Document.prototype.offsetContainsMatchingAnnotations = function ( offset, */ ve.dm.Document.prototype.getMatchingAnnotationsFromOffset = function ( offset, pattern ) { if ( !( pattern instanceof RegExp ) ) { - throw 'Invalid Pattern. Pattern not instance of RegExp'; + throw new ve.Error( 'Invalid Pattern. Pattern not instance of RegExp' ); } var hash, matches = {}, @@ -626,7 +626,7 @@ ve.dm.Document.prototype.getMatchingAnnotationsFromOffset = function ( offset, p */ ve.dm.Document.getMatchingAnnotations = function ( annotations, pattern ) { if ( !( pattern instanceof RegExp ) ) { - throw 'Invalid Pattern. Pattern not instance of RegExp'; + throw new ve.Error( 'Invalid Pattern. Pattern not instance of RegExp' ); } var hash, matches = {}; @@ -1045,16 +1045,16 @@ ve.dm.Document.prototype.fixupInsertion = function ( data, offset ) { closingStack.push( parentNode ); parentNode = parentNode.getParent(); if ( !parentNode ) { - throw 'Inserted data is trying to close the root node ' + - '(at index ' + index + ')'; + throw new ve.Error( 'Inserted data is trying to close the root node ' + + '(at index ' + index + ')' ); } parentType = expectedType; // Validate // FIXME this breaks certain input, should fix it up, not scream and die if ( element.type !== '/' + expectedType ) { - throw 'Type mismatch, expected /' + expectedType + - ' but got ' + element.type + ' (at index ' + index + ')'; + throw new ve.Error( 'Type mismatch, expected /' + expectedType + + ' but got ' + element.type + ' (at index ' + index + ')' ); } } } @@ -1112,8 +1112,8 @@ ve.dm.Document.prototype.fixupInsertion = function ( data, offset ) { if ( !parentsOK ) { // We can't have this as the parent if ( allowedParents.length === 0 ) { - throw 'Cannot insert ' + childType + ' because it ' + - ' cannot have a parent (at index ' + i + ')'; + throw new ve.Error( 'Cannot insert ' + childType + ' because it ' + + ' cannot have a parent (at index ' + i + ')' ); } // Open an allowed node around this node childType = allowedParents[0]; @@ -1151,9 +1151,9 @@ ve.dm.Document.prototype.fixupInsertion = function ( data, offset ) { reopenElements.push( parentNode.getClonedElement() ); parentNode = parentNode.getParent(); if ( !parentNode ) { - throw 'Cannot insert ' + childType + ' even ' + + throw new ve.Error( 'Cannot insert ' + childType + ' even ' + ' after closing all containing nodes ' + - '(at index ' + i + ')'; + '(at index ' + i + ')' ); } parentType = parentNode.getType(); } diff --git a/modules/ve/dm/ve.dm.DocumentSynchronizer.js b/modules/ve/dm/ve.dm.DocumentSynchronizer.js index 6213bfc5c5..e8cc2417d1 100644 --- a/modules/ve/dm/ve.dm.DocumentSynchronizer.js +++ b/modules/ve/dm/ve.dm.DocumentSynchronizer.js @@ -268,7 +268,7 @@ ve.dm.DocumentSynchronizer.prototype.synchronize = function () { if ( action.type in ve.dm.DocumentSynchronizer.synchronizers ) { ve.dm.DocumentSynchronizer.synchronizers[action.type].call( this, action ); } else { - throw 'Invalid action type ' + action.type; + throw new ve.Error( 'Invalid action type ' + action.type ); } } // Emit events in the event queue diff --git a/modules/ve/dm/ve.dm.Node.js b/modules/ve/dm/ve.dm.Node.js index 8d73099ab2..3e6796e197 100644 --- a/modules/ve/dm/ve.dm.Node.js +++ b/modules/ve/dm/ve.dm.Node.js @@ -154,7 +154,7 @@ ve.dm.Node.prototype.getOuterRange = function () { */ ve.dm.Node.prototype.setLength = function ( length ) { if ( length < 0 ) { - throw 'Length cannot be negative'; + throw new ve.Error( 'Length cannot be negative' ); } // Compute length adjustment from old length var diff = length - this.length; diff --git a/modules/ve/dm/ve.dm.NodeFactory.js b/modules/ve/dm/ve.dm.NodeFactory.js index 8cff862075..e74046546c 100644 --- a/modules/ve/dm/ve.dm.NodeFactory.js +++ b/modules/ve/dm/ve.dm.NodeFactory.js @@ -31,7 +31,7 @@ ve.dm.NodeFactory.prototype.getChildNodeTypes = function ( type ) { if ( type in this.registry ) { return this.registry[type].rules.childNodeTypes; } - throw 'Unknown node type: ' + type; + throw new ve.Error( 'Unknown node type: ' + type ); }; /** @@ -46,7 +46,7 @@ ve.dm.NodeFactory.prototype.getParentNodeTypes = function ( type ) { if ( type in this.registry ) { return this.registry[type].rules.parentNodeTypes; } - throw 'Unknown node type: ' + type; + throw new ve.Error( 'Unknown node type: ' + type ); }; /** @@ -64,7 +64,7 @@ ve.dm.NodeFactory.prototype.canNodeHaveChildren = function ( type ) { var types = this.registry[type].rules.childNodeTypes; return types === null || ( ve.isArray( types ) && types.length > 0 ); } - throw 'Unknown node type: ' + type; + throw new ve.Error( 'Unknown node type: ' + type ); }; /** @@ -81,7 +81,7 @@ ve.dm.NodeFactory.prototype.canNodeHaveGrandchildren = function ( type ) { !this.registry[type].rules.canContainContent && !this.registry[type].rules.isContent; } - throw 'Unknown node type: ' + type; + throw new ve.Error( 'Unknown node type: ' + type ); }; /** @@ -96,7 +96,7 @@ ve.dm.NodeFactory.prototype.isNodeWrapped = function ( type ) { if ( type in this.registry ) { return this.registry[type].rules.isWrapped; } - throw 'Unknown node type: ' + type; + throw new ve.Error( 'Unknown node type: ' + type ); }; /** @@ -111,7 +111,7 @@ ve.dm.NodeFactory.prototype.canNodeContainContent = function ( type ) { if ( type in this.registry ) { return this.registry[type].rules.canContainContent; } - throw 'Unknown node type: ' + type; + throw new ve.Error( 'Unknown node type: ' + type ); }; /** @@ -126,7 +126,7 @@ ve.dm.NodeFactory.prototype.isNodeContent = function ( type ) { if ( type in this.registry ) { return this.registry[type].rules.isContent; } - throw 'Unknown node type: ' + type; + throw new ve.Error( 'Unknown node type: ' + type ); }; /* Inheritance */ diff --git a/modules/ve/dm/ve.dm.Transaction.js b/modules/ve/dm/ve.dm.Transaction.js index b6f47d0922..78c6fac762 100644 --- a/modules/ve/dm/ve.dm.Transaction.js +++ b/modules/ve/dm/ve.dm.Transaction.js @@ -82,7 +82,7 @@ ve.dm.Transaction.newFromRemoval = function ( doc, range ) { selection = doc.selectNodes( range, 'covered' ); if ( selection.length === 0 ) { // Empty selection? Something is wrong! - throw 'Invalid range, cannot remove from ' + range.start + ' to ' + range.end; + throw new ve.Error( 'Invalid range, cannot remove from ' + range.start + ' to ' + range.end ); } first = selection[0]; last = selection[selection.length - 1]; @@ -169,11 +169,11 @@ ve.dm.Transaction.newFromAttributeChange = function ( doc, offset, key, value ) data = doc.getData(); // Verify element exists at offset if ( data[offset].type === undefined ) { - throw 'Can not set attributes to non-element data'; + throw new ve.Error( 'Can not set attributes to non-element data' ); } // Verify element is not a closing if ( data[offset].type.charAt( 0 ) === '/' ) { - throw 'Can not set attributes on closing element'; + throw new ve.Error( 'Can not set attributes on closing element' ); } // Retain up to element tx.pushRetain( offset ); @@ -362,7 +362,7 @@ ve.dm.Transaction.newFromWrap = function ( doc, range, unwrapOuter, wrapOuter, u // the range, so compensate for that tx.pushRetain( range.start - unwrapOuter.length ); } else if ( range.start < unwrapOuter.length ) { - throw 'unwrapOuter is longer than the data preceding the range'; + throw new ve.Error( 'unwrapOuter is longer than the data preceding the range' ); } // Replace the opening elements for the outer unwrap&wrap @@ -371,8 +371,8 @@ ve.dm.Transaction.newFromWrap = function ( doc, range, unwrapOuter, wrapOuter, u unwrapOuterData = doc.data.slice( range.start - unwrapOuter.length, range.start ); for ( i = 0; i < unwrapOuterData.length; i++ ) { if ( unwrapOuterData[i].type !== unwrapOuter[i].type ) { - throw 'Element in unwrapOuter does not match: expected ' + - unwrapOuter[i].type + ' but found ' + unwrapOuterData[i].type; + throw new ve.Error( 'Element in unwrapOuter does not match: expected ' + + unwrapOuter[i].type + ' but found ' + unwrapOuterData[i].type ); } } // Instead of putting in unwrapOuter as given, put it in the @@ -397,9 +397,9 @@ ve.dm.Transaction.newFromWrap = function ( doc, range, unwrapOuter, wrapOuter, u unwrapEachData = doc.data.slice( i, i + unwrapEach.length ); for ( j = 0; j < unwrapEachData.length; j++ ) { if ( unwrapEachData[j].type !== unwrapEach[j].type ) { - throw 'Element in unwrapEach does not match: expected ' + + throw new ve.Error( 'Element in unwrapEach does not match: expected ' + unwrapEach[j].type + ' but found ' + - unwrapEachData[j].type; + unwrapEachData[j].type ); } } // Instead of putting in unwrapEach as given, put it in the @@ -524,7 +524,7 @@ ve.dm.Transaction.prototype.translateRange = function ( range ) { */ ve.dm.Transaction.prototype.pushRetain = function ( length ) { if ( length < 0 ) { - throw 'Invalid retain length, can not retain backwards:' + length; + throw new ve.Error( 'Invalid retain length, can not retain backwards:' + length ); } if ( length ) { var end = this.operations.length - 1; diff --git a/modules/ve/dm/ve.dm.TransactionProcessor.js b/modules/ve/dm/ve.dm.TransactionProcessor.js index 194ab30544..ca296b5eb9 100644 --- a/modules/ve/dm/ve.dm.TransactionProcessor.js +++ b/modules/ve/dm/ve.dm.TransactionProcessor.js @@ -115,7 +115,7 @@ ve.dm.TransactionProcessor.processors.annotate = function ( op ) { } else if ( op.method === 'clear' ) { target = this.reversed ? this.set : this.clear; } else { - throw 'Invalid annotation method ' + op.method; + throw new ve.Error( 'Invalid annotation method ' + op.method ); } hash = $.toJSON( op.annotation ); @@ -150,7 +150,7 @@ ve.dm.TransactionProcessor.processors.attribute = function ( op ) { to = this.reversed ? op.from : op.to, from = this.reversed ? op.to : op.from; if ( element.type === undefined ) { - throw 'Invalid element error, can not set attributes on non-element data'; + throw new ve.Error( 'Invalid element error, can not set attributes on non-element data' ); } if ( to === undefined ) { // Clear @@ -346,7 +346,7 @@ ve.dm.TransactionProcessor.processors.replace = function ( op ) { // Get the next operation operation = this.nextOperation(); if ( !operation ) { - throw 'Unbalanced set of replace operations found'; + throw new ve.Error( 'Unbalanced set of replace operations found' ); } } // From all the affected ranges we have gathered, compute a range that covers all @@ -384,7 +384,7 @@ ve.dm.TransactionProcessor.prototype.executeOperation = function ( op ) { if ( op.type in ve.dm.TransactionProcessor.processors ) { ve.dm.TransactionProcessor.processors[op.type].call( this, op ); } else { - throw 'Invalid operation error. Operation type is not supported: ' + op.type; + throw new ve.Error( 'Invalid operation error. Operation type is not supported: ' + op.type ); } }; @@ -426,9 +426,9 @@ ve.dm.TransactionProcessor.prototype.applyAnnotations = function ( to ) { element = item.type !== undefined; if ( element ) { if ( item.type.charAt( 0 ) === '/' ) { - throw 'Invalid transaction, cannot annotate a branch closing element'; + throw new ve.Error( 'Invalid transaction, cannot annotate a branch closing element' ); } else if ( ve.dm.nodeFactory.canNodeHaveChildren( item.type ) ) { - throw 'Invalid transaction, cannot annotate a branch opening element'; + throw new ve.Error( 'Invalid transaction, cannot annotate a branch opening element' ); } } annotated = element ? 'annotations' in item : ve.isArray( item ); @@ -436,13 +436,13 @@ ve.dm.TransactionProcessor.prototype.applyAnnotations = function ( to ) { // Set and clear annotations for ( hash in this.set ) { if ( hash in annotations ) { - throw 'Invalid transaction, annotation to be set is already set'; + throw new ve.Error( 'Invalid transaction, annotation to be set is already set' ); } annotations[hash] = this.set[hash]; } for ( hash in this.clear ) { if ( !( hash in annotations ) ) { - throw 'Invalid transaction, annotation to be cleared is not set'; + throw new ve.Error( 'Invalid transaction, annotation to be cleared is not set' ); } delete annotations[hash]; } diff --git a/modules/ve/init/ve.init.Platform.js b/modules/ve/init/ve.init.Platform.js index 74317c9beb..68274df279 100644 --- a/modules/ve/init/ve.init.Platform.js +++ b/modules/ve/init/ve.init.Platform.js @@ -28,7 +28,7 @@ ve.init.Platform = function () { * @returns {RegExp} Regular expression object */ ve.init.Platform.prototype.getExternalLinkUrlProtocolsRegExp = function () { - throw 've.init.Platform.getExternalLinkUrlProtocolsRegExp must be overridden in subclass'; + throw new ve.Error( 've.init.Platform.getExternalLinkUrlProtocolsRegExp must be overridden in subclass' ); }; /** @@ -39,7 +39,7 @@ ve.init.Platform.prototype.getExternalLinkUrlProtocolsRegExp = function () { * @returns {String} Remote modules URL */ ve.init.Platform.prototype.getModulesUrl = function () { - throw 've.init.Platform.getModulesUrl must be overridden in subclass'; + throw new ve.Error( 've.init.Platform.getModulesUrl must be overridden in subclass' ); }; /** @@ -50,7 +50,7 @@ ve.init.Platform.prototype.getModulesUrl = function () { * @param {Object} messages Map of message-key/message-string pairs */ ve.init.Platform.prototype.addMessages = function ( messages ) { - throw 've.init.Platform.addMessages must be overridden in subclass'; + throw new ve.Error( 've.init.Platform.addMessages must be overridden in subclass' ); }; /** @@ -63,7 +63,7 @@ ve.init.Platform.prototype.addMessages = function ( messages ) { * @returns {String} Localized message */ ve.init.Platform.prototype.getMessage = function ( key ) { - throw 've.init.Platform.getMessage must be overridden in subclass'; + throw new ve.Error( 've.init.Platform.getMessage must be overridden in subclass' ); }; /* Inheritance */ diff --git a/modules/ve/test/ce/ve.ce.NodeFactory.test.js b/modules/ve/test/ce/ve.ce.NodeFactory.test.js index 4ed063a007..ef88b73636 100644 --- a/modules/ve/test/ce/ve.ce.NodeFactory.test.js +++ b/modules/ve/test/ce/ve.ce.NodeFactory.test.js @@ -26,7 +26,7 @@ QUnit.test( 'canNodeBeSplit', 2, function ( assert ) { assert.throws( function () { factory.canNodeBeSplit( 'node-factory-node-stub' ); }, - /^Unknown node type: node-factory-node-stub$/, + ve.Error, 'throws an exception when getting split rules for a node of an unregistered type' ); factory.register( 'node-factory-node-stub', ve.ce.NodeFactoryNodeStub ); diff --git a/modules/ve/test/dm/ve.dm.Document.test.js b/modules/ve/test/dm/ve.dm.Document.test.js index 31e37c0ffd..7d56ba113c 100644 --- a/modules/ve/test/dm/ve.dm.Document.test.js +++ b/modules/ve/test/dm/ve.dm.Document.test.js @@ -19,7 +19,7 @@ QUnit.test( 'constructor', 4, function ( assert ) { { 'type': 'paragraph' } ] ); }, - /^Unbalanced input passed to document$/, + ve.Error, 'unbalanced input causes exception' ); diff --git a/modules/ve/test/dm/ve.dm.Node.test.js b/modules/ve/test/dm/ve.dm.Node.test.js index b8667943c4..c3ead770cd 100644 --- a/modules/ve/test/dm/ve.dm.Node.test.js +++ b/modules/ve/test/dm/ve.dm.Node.test.js @@ -62,7 +62,7 @@ QUnit.test( 'setLength', 2, function ( assert ) { // Length can not be negative node.setLength( -1 ); }, - /^Length cannot be negative$/, + ve.Error, 'throws exception if length is negative' ); } ); diff --git a/modules/ve/test/dm/ve.dm.NodeFactory.test.js b/modules/ve/test/dm/ve.dm.NodeFactory.test.js index 7445892f23..ea5659c677 100644 --- a/modules/ve/test/dm/ve.dm.NodeFactory.test.js +++ b/modules/ve/test/dm/ve.dm.NodeFactory.test.js @@ -33,7 +33,7 @@ QUnit.test( 'getChildNodeTypes', 2, function ( assert ) { assert.throws( function () { factory.getChildNodeTypes( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); }, - /^Unknown node type: node-factory-node-stub$/, + ve.Error, 'throws an exception when getting allowed child nodes of a node of an unregistered type' ); factory.register( 'node-factory-node-stub', ve.dm.NodeFactoryNodeStub ); @@ -49,7 +49,7 @@ QUnit.test( 'getParentNodeTypes', 2, function ( assert ) { assert.throws( function () { factory.getParentNodeTypes( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); }, - /^Unknown node type: node-factory-node-stub$/, + ve.Error, 'throws an exception when getting allowed parent nodes of a node of an unregistered type' ); factory.register( 'node-factory-node-stub', ve.dm.NodeFactoryNodeStub ); @@ -65,7 +65,7 @@ QUnit.test( 'canNodeHaveChildren', 2, function ( assert ) { assert.throws( function () { factory.canNodeHaveChildren( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); }, - /^Unknown node type: node-factory-node-stub$/, + ve.Error, 'throws an exception when checking if a node of an unregistered type can have children' ); factory.register( 'node-factory-node-stub', ve.dm.NodeFactoryNodeStub ); @@ -81,7 +81,7 @@ QUnit.test( 'canNodeHaveGrandchildren', 2, function ( assert ) { assert.throws( function () { factory.canNodeHaveGrandchildren( 'node-factory-node-stub', 23, { 'bar': 'baz' } ); }, - /^Unknown node type: node-factory-node-stub$/, + ve.Error, 'throws an exception when checking if a node of an unregistered type can have grandchildren' ); factory.register( 'node-factory-node-stub', ve.dm.NodeFactoryNodeStub ); diff --git a/modules/ve/test/dm/ve.dm.Transaction.test.js b/modules/ve/test/dm/ve.dm.Transaction.test.js index f82efff34a..42ae699394 100644 --- a/modules/ve/test/dm/ve.dm.Transaction.test.js +++ b/modules/ve/test/dm/ve.dm.Transaction.test.js @@ -471,11 +471,11 @@ QUnit.test( 'newFromAttributeChange', function ( assert ) { }, 'non-element': { 'args': [doc, 1, 'level', 2], - 'exception': /^Can not set attributes to non-element data$/ + 'exception': ve.Error }, 'closing element': { 'args': [doc, 4, 'level', 2], - 'exception': /^Can not set attributes on closing element$/ + 'exception': ve.Error } }; runConstructorTests( assert, ve.dm.Transaction.newFromAttributeChange, cases ); @@ -704,15 +704,15 @@ QUnit.test( 'newFromWrap', function ( assert ) { }, 'checks integrity of unwrapOuter parameter': { 'args': [doc, new ve.Range( 13, 32 ), [ { 'type': 'table' } ], [], [], []], - 'exception': /^Element in unwrapOuter does not match: expected table but found list$/ + 'exception': ve.Error }, 'checks integrity of unwrapEach parameter': { 'args': [doc, new ve.Range( 13, 32 ), [ { 'type': 'list' } ], [], [ { 'type': 'paragraph' } ], []], - 'exception': /^Element in unwrapEach does not match: expected paragraph but found listItem$/ + 'exception': ve.Error }, 'checks that unwrapOuter fits before the range': { 'args': [doc, new ve.Range( 1, 4 ), [ { 'type': 'listItem' }, { 'type': 'paragraph' } ], [], [], []], - 'exception': /^unwrapOuter is longer than the data preceding the range$/ + 'exception': ve.Error } }; runConstructorTests( diff --git a/modules/ve/test/dm/ve.dm.TransactionProcessor.test.js b/modules/ve/test/dm/ve.dm.TransactionProcessor.test.js index 33d87d4b2a..17fd3a33af 100644 --- a/modules/ve/test/dm/ve.dm.TransactionProcessor.test.js +++ b/modules/ve/test/dm/ve.dm.TransactionProcessor.test.js @@ -62,7 +62,7 @@ QUnit.test( 'commit/rollback', function ( assert ) { ['pushRetain', 1], ['pushStopAnnotating', 'invalid-method', { 'type': 'textStyle/bold' }] ], - 'exception': /^Invalid annotation method/ + 'exception': ve.Error }, 'annotating branch opening element throws an exception': { 'calls': [ @@ -70,7 +70,7 @@ QUnit.test( 'commit/rollback', function ( assert ) { ['pushRetain', 1], ['pushStopAnnotating', 'set', { 'type': 'textStyle/bold' }] ], - 'exception': /^Invalid transaction, cannot annotate a branch opening element$/ + 'exception': ve.Error }, 'annotating branch closing element throws an exception': { 'calls': [ @@ -79,7 +79,7 @@ QUnit.test( 'commit/rollback', function ( assert ) { ['pushRetain', 1], ['pushStopAnnotating', 'set', { 'type': 'textStyle/bold' }] ], - 'exception': /^Invalid transaction, cannot annotate a branch closing element$/ + 'exception': ve.Error }, 'setting duplicate annotations throws an exception': { 'calls': [ @@ -88,7 +88,7 @@ QUnit.test( 'commit/rollback', function ( assert ) { ['pushRetain', 1], ['pushStopAnnotating', 'set', { 'type': 'textStyle/bold' }] ], - 'exception': /^Invalid transaction, annotation to be set is already set$/ + 'exception': ve.Error }, 'removing non-existent annotations throws an exception': { 'calls': [ @@ -97,7 +97,7 @@ QUnit.test( 'commit/rollback', function ( assert ) { ['pushRetain', 1], ['pushStopAnnotating', 'clear', { 'type': 'textStyle/bold' }] ], - 'exception': /^Invalid transaction, annotation to be cleared is not set$/ + 'exception': ve.Error }, 'changing, removing and adding attributes': { 'calls': [ @@ -120,7 +120,7 @@ QUnit.test( 'commit/rollback', function ( assert ) { ['pushRetain', 1], ['pushReplaceElementAttribute', 'foo', 23, 42] ], - 'exception': /^Invalid element error, can not set attributes on non-element data$/ + 'exception': ve.Error }, 'inserting text': { 'calls': [ diff --git a/modules/ve/test/index.html b/modules/ve/test/index.html index fbe39f494c..6b0e87e48e 100644 --- a/modules/ve/test/index.html +++ b/modules/ve/test/index.html @@ -15,6 +15,7 @@ + diff --git a/modules/ve/test/ve.BranchNode.test.js b/modules/ve/test/ve.BranchNode.test.js index 7dfb890923..90940e202b 100644 --- a/modules/ve/test/ve.BranchNode.test.js +++ b/modules/ve/test/ve.BranchNode.test.js @@ -152,7 +152,7 @@ QUnit.test( 'traverseLeafNodes', 1, function ( assert ) { { 'node': children[1], 'from': children[2], - 'exception': /^from parameter passed to traverseLeafNodes\(\) must be a descendant$/, + 'exception': ve.Error, 'desc': 'Passing a sibling for from results in an exception' } ]; diff --git a/modules/ve/test/ve.Factory.test.js b/modules/ve/test/ve.Factory.test.js index 9423e1df29..c3561a2340 100644 --- a/modules/ve/test/ve.Factory.test.js +++ b/modules/ve/test/ve.Factory.test.js @@ -22,7 +22,7 @@ QUnit.test( 'register', 1, function ( assert ) { function () { factory.register( 'factory-object-stub', 'not-a-function' ); }, - /^Constructor must be a function, cannot be a string$/, + ve.Error, 'throws an exception when trying to register a non-function value as a constructor' ); } ); @@ -33,7 +33,7 @@ QUnit.test( 'create', 2, function ( assert ) { function () { factory.create( 'factory-object-stub', 23, { 'bar': 'baz' } ); }, - /^Unknown object type: factory-object-stub$/, + ve.Error, 'throws an exception when trying to create a object of an unregistered type' ); factory.register( 'factory-object-stub', ve.FactoryObjectStub ); diff --git a/modules/ve/ui/tools/ve.ui.ButtonTool.js b/modules/ve/ui/tools/ve.ui.ButtonTool.js index f74a2ada63..1718ed7d04 100644 --- a/modules/ve/ui/tools/ve.ui.ButtonTool.js +++ b/modules/ve/ui/tools/ve.ui.ButtonTool.js @@ -44,7 +44,7 @@ ve.ui.ButtonTool = function ( toolbar, name, title ) { /* Methods */ ve.ui.ButtonTool.prototype.onClick = function () { - throw 'ButtonTool.onClick not implemented in this subclass:' + this.constructor; + throw new ve.Error( 'ButtonTool.onClick not implemented in this subclass:' + this.constructor ); }; ve.ui.ButtonTool.prototype.updateEnabled = function () { diff --git a/modules/ve/ui/tools/ve.ui.DropdownTool.js b/modules/ve/ui/tools/ve.ui.DropdownTool.js index 9ee8290c0b..9f71a5858f 100644 --- a/modules/ve/ui/tools/ve.ui.DropdownTool.js +++ b/modules/ve/ui/tools/ve.ui.DropdownTool.js @@ -63,7 +63,7 @@ ve.ui.DropdownTool = function ( toolbar, name, title, items ) { /* Methods */ ve.ui.DropdownTool.prototype.onSelect = function ( item ) { - throw 'DropdownTool.onSelect not implemented in this subclass:' + this.constructor; + throw new ve.Error( 'DropdownTool.onSelect not implemented in this subclass:' + this.constructor ); }; /* Inheritance */ diff --git a/modules/ve/ui/ve.ui.Context.js b/modules/ve/ui/ve.ui.Context.js index f47492cbc6..956aa33890 100644 --- a/modules/ve/ui/ve.ui.Context.js +++ b/modules/ve/ui/ve.ui.Context.js @@ -226,7 +226,7 @@ ve.ui.Context.prototype.clear = function () { ve.ui.Context.prototype.openInspector = function ( name ) { if ( !( name in this.inspectors ) ) { - throw 'Missing inspector error. Can not open nonexistent inspector: ' + name; + throw new ve.Error( 'Missing inspector error. Can not open nonexistent inspector: ' + name ); } this.inspectors[name].open(); this.resizeInspectorFrame( this.inspectors[name] ); @@ -251,7 +251,7 @@ ve.ui.Context.prototype.getInspector = function ( name ) { ve.ui.Context.prototype.addInspector = function ( name, inspector ) { if ( name in this.inspectors ) { - throw 'Duplicate inspector error. Previous registration with the same name: ' + name; + throw new ve.Error( 'Duplicate inspector error. Previous registration with the same name: ' + name ); } inspector.$.hide(); this.inspectors[name] = inspector; @@ -276,7 +276,7 @@ ve.ui.Context.prototype.resizeInspectorFrame = function ( inspector ) { ve.ui.Context.prototype.removeInspector = function ( name ) { if ( name in this.inspectors ) { - throw 'Missing inspector error. Can not remove nonexistent inspector: ' + name; + throw new ve.Error( 'Missing inspector error. Can not remove nonexistent inspector: ' + name ); } this.inspectors[name].detach(); delete this.inspectors[name]; diff --git a/modules/ve/ui/ve.ui.Menu.js b/modules/ve/ui/ve.ui.Menu.js index 04f2cd6ad0..b6f4939f39 100644 --- a/modules/ve/ui/ve.ui.Menu.js +++ b/modules/ve/ui/ve.ui.Menu.js @@ -68,7 +68,7 @@ ve.ui.Menu.prototype.addItem = function ( item, before ) { // Items that don't have custom DOM elements will be auto-created if ( !item.$ ) { if ( !item.name ) { - throw 'Invalid menu item error. Items must have a name property.'; + throw new ve.Error( 'Invalid menu item error. Items must have a name property.' ); } if ( item.label ) { item.$ = $( '
' ) diff --git a/modules/ve/ui/ve.ui.Tool.js b/modules/ve/ui/ve.ui.Tool.js index 8a161bb9aa..7cde081d92 100644 --- a/modules/ve/ui/ve.ui.Tool.js +++ b/modules/ve/ui/ve.ui.Tool.js @@ -27,7 +27,7 @@ ve.ui.Tool.tools = {}; /* Methods */ ve.ui.Tool.prototype.updateState = function () { - throw 'Tool.updateState not implemented in this subclass:' + this.constructor; + throw new ve.Error( 'Tool.updateState not implemented in this subclass:' + this.constructor ); }; ve.ui.Tool.prototype.clearState = function () { diff --git a/modules/ve/ve.BranchNode.js b/modules/ve/ve.BranchNode.js index 5369bd21da..bc037848cf 100644 --- a/modules/ve/ve.BranchNode.js +++ b/modules/ve/ve.BranchNode.js @@ -202,13 +202,13 @@ ve.BranchNode.prototype.traverseLeafNodes = function ( callback, from, reverse ) if ( !p ) { // n is a root node and we haven't reached this // That means from isn't a descendant of this - throw 'from parameter passed to traverseLeafNodes() must be a descendant'; + throw new ve.Error( 'from parameter passed to traverseLeafNodes() must be a descendant' ); } // Find the index of n in p i = p.indexOf( n ); if ( i === -1 ) { // This isn't supposed to be possible - throw 'Tree corruption detected: node isn\'t in its parent\'s children array'; + throw new ve.Error( 'Tree corruption detected: node isn\'t in its parent\'s children array' ); } indexStack.push( i ); // Move up diff --git a/modules/ve/ve.Document.js b/modules/ve/ve.Document.js index 01beab14c8..73e52aec83 100644 --- a/modules/ve/ve.Document.js +++ b/modules/ve/ve.Document.js @@ -85,14 +85,14 @@ ve.Document.prototype.selectNodes = function ( range, mode ) { mode = mode || 'leaves'; if ( mode !== 'leaves' && mode !== 'covered' && mode !== 'siblings' ) { - throw 'Invalid mode: ' + mode; + throw new ve.Error( 'Invalid mode: ' + mode ); } if ( start < 0 || start > doc.getLength() ) { - throw 'Invalid start offset: ' + start; + throw new ve.Error( 'Invalid start offset: ' + start ); } if ( end < 0 || end > doc.getLength() ) { - throw 'Invalid end offset: ' + end; + throw new ve.Error( 'Invalid end offset: ' + end ); } if ( !doc.children || doc.children.length === 0 ) { diff --git a/modules/ve/ve.Error.js b/modules/ve/ve.Error.js new file mode 100644 index 0000000000..5595ae6303 --- /dev/null +++ b/modules/ve/ve.Error.js @@ -0,0 +1,38 @@ +/** + * VisualEditor Error class. + * + * @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * Error. + * + * @class + * @constructor + * @param {String} message Human-readable description of the error + * @param {String} fileName The name of the file containing the code that caused the exception + * @param {Number} lineNumber The line number of the code that caused the exception + */ +ve.Error = function () { + // Inheritance + Error.apply( this, arguments ); +}; + +/* Methods */ + +/** + * Gets a human-readable description of the error, including the error type. + * + * @method + * @returns {String} Error type and description + */ +ve.Error.prototype.toString = function () { + return this.name + ': ' + this.message; +}; + +/* Inheritance */ + +ve.Error.prototype = new Error(); +ve.Error.prototype.constructor = ve.Error; +ve.Error.prototype.name = 've.Error'; diff --git a/modules/ve/ve.EventEmitter.js b/modules/ve/ve.EventEmitter.js index e04b22c2b6..4b4f242bd9 100644 --- a/modules/ve/ve.EventEmitter.js +++ b/modules/ve/ve.EventEmitter.js @@ -29,7 +29,7 @@ ve.EventEmitter = function () { */ ve.EventEmitter.prototype.emit = function ( type ) { if ( type === 'error' && !( 'error' in this.events ) ) { - throw 'Missing error handler error.'; + throw new ve.Error( 'Missing error handler error.' ); } if ( !( type in this.events ) ) { return false; @@ -55,7 +55,7 @@ ve.EventEmitter.prototype.emit = function ( type ) { */ ve.EventEmitter.prototype.addListener = function ( type, listener ) { if ( typeof listener !== 'function' ) { - throw 'Invalid listener error. Function expected.'; + throw new ve.Error( 'Invalid listener error. Function expected.' ); } this.emit( 'newListener', type, listener ); if ( type in this.events ) { @@ -94,7 +94,7 @@ ve.EventEmitter.prototype.addListenerMethod = function ( target, event, method ) if ( typeof target[method] === 'function' ) { target[method].apply( target, Array.prototype.slice.call( arguments, 0 ) ); } else { - throw 'Listener method error. Target has no such method: ' + method; + throw new ve.Error( 'Listener method error. Target has no such method: ' + method ); } } ); }; @@ -148,7 +148,7 @@ ve.EventEmitter.prototype.once = function ( type, listener ) { */ ve.EventEmitter.prototype.removeListener = function ( type, listener ) { if ( typeof listener !== 'function' ) { - throw 'Invalid listener error. Function expected.'; + throw new ve.Error( 'Invalid listener error. Function expected.' ); } if ( !( type in this.events ) || !this.events[type].length ) { return this; diff --git a/modules/ve/ve.Factory.js b/modules/ve/ve.Factory.js index 4207a7d197..ca93565483 100644 --- a/modules/ve/ve.Factory.js +++ b/modules/ve/ve.Factory.js @@ -36,7 +36,7 @@ ve.Factory = function () { */ ve.Factory.prototype.register = function ( type, constructor ) { if ( typeof constructor !== 'function' ) { - throw 'Constructor must be a function, cannot be a ' + typeof constructor; + throw new ve.Error( 'Constructor must be a function, cannot be a ' + typeof constructor ); } this.registry[type] = constructor; this.emit( 'register', type, constructor ); @@ -63,7 +63,7 @@ ve.Factory.prototype.create = function ( type, a, b ) { if ( type in this.registry ) { return new this.registry[type]( a, b ); } - throw 'Unknown object type: ' + type; + throw new ve.Error( 'Unknown object type: ' + type ); }; /** diff --git a/modules/ve/ve.Node.js b/modules/ve/ve.Node.js index 81490ea4fe..4ba6b00e12 100644 --- a/modules/ve/ve.Node.js +++ b/modules/ve/ve.Node.js @@ -43,7 +43,7 @@ ve.Node = function ( type ) { * @throws {Error} if not overridden */ ve.Node.prototype.canHaveChildren = function () { - throw 've.Node.canHaveChildren must be overridden in subclass'; + throw new ve.Error( 've.Node.canHaveChildren must be overridden in subclass' ); }; /** @@ -55,7 +55,7 @@ ve.Node.prototype.canHaveChildren = function () { * @throws {Error} if not overridden */ ve.Node.prototype.canHaveGrandchildren = function () { - throw 've.Node.canHaveGrandchildren must be overridden in subclass'; + throw new ve.Error( 've.Node.canHaveGrandchildren must be overridden in subclass' ); }; /** @@ -67,7 +67,7 @@ ve.Node.prototype.canHaveGrandchildren = function () { * @throws {Error} if not overridden */ ve.Node.prototype.isWrapped = function () { - throw 've.Node.isWrapped must be overridden in subclass'; + throw new ve.Error( 've.Node.isWrapped must be overridden in subclass' ); }; /** @@ -79,7 +79,7 @@ ve.Node.prototype.isWrapped = function () { * @throws {Error} if not overridden */ ve.Node.prototype.getLength = function () { - throw 've.Node.getLength must be overridden in subclass'; + throw new ve.Error( 've.Node.getLength must be overridden in subclass' ); }; /** @@ -91,7 +91,7 @@ ve.Node.prototype.getLength = function () { * @throws {Error} if not overridden */ ve.Node.prototype.getOuterLength = function () { - throw 've.Node.getOuterLength must be overridden in subclass'; + throw new ve.Error( 've.Node.getOuterLength must be overridden in subclass' ); }; /* Methods */ diff --git a/modules/ve/ve.Range.js b/modules/ve/ve.Range.js index 218528add3..3e659c3a5b 100644 --- a/modules/ve/ve.Range.js +++ b/modules/ve/ve.Range.js @@ -45,7 +45,7 @@ ve.Range.newFromTranslatedRange = function ( range, distance ) { ve.Range.newCoveringRange = function ( ranges ) { var minStart, maxEnd, i; if ( !ranges || ranges.length === 0 ) { - throw 'newCoveringRange() requires at least one range'; + throw new ve.Error( 'newCoveringRange() requires at least one range' ); } minStart = ranges[0].start; maxEnd = ranges[0].end;