From 7f3e7fba352c3824ac9e1757444f42280073a516 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 23 Apr 2014 01:50:35 -0400 Subject: [PATCH] Overriding vanilla prototype methods no longer errors This is something that I've been aware of for quite some time, but never got around to fixing; ease.js had stalled until it was revitalized by becoming a GNU project. --- lib/MemberBuilder.js | 14 +++++- test/Class/InteropTest.js | 102 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 test/Class/InteropTest.js diff --git a/lib/MemberBuilder.js b/lib/MemberBuilder.js index c1c67fc..777fcf6 100644 --- a/lib/MemberBuilder.js +++ b/lib/MemberBuilder.js @@ -120,11 +120,16 @@ exports.buildMethod = function( members, meta, name, value, keywords, instCallback, cid, base, state ) { + // these defaults will be used whenever a keyword set is unavailable, + // which should only ever be the case if we're inheriting from a + // prototype rather than an ease.js class/etc + var kdefaults = this._methodKeywordDefaults; + // TODO: We can improve performance by not scanning each one individually // every time this method is called var prev_data = scanMembers( members, name, base ), prev = ( prev_data ) ? prev_data.member : null, - prev_keywords = ( prev && prev.___$$keywords$$ ), + prev_keywords = ( prev && ( prev.___$$keywords$$ || kdefaults ) ), dest = getMemberVisibility( members, keywords, name ); ; @@ -194,6 +199,13 @@ exports.buildMethod = function( }; +/** + * Default keywords to apply to methods inherited from a prototype. + * @type {Object} + */ +exports._methodKeywordDefaults = { 'virtual': true }; + + /** * Creates an abstract override super method proxy to NAME * diff --git a/test/Class/InteropTest.js b/test/Class/InteropTest.js new file mode 100644 index 0000000..b66f348 --- /dev/null +++ b/test/Class/InteropTest.js @@ -0,0 +1,102 @@ +/** + * Tests class interoperability with vanilla ECMAScript + * + * Copyright (C) 2014 Mike Gerwitz + * + * This file is part of GNU ease.js. + * + * ease.js is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Note that these tests all use the `new' keyword for instantiating + * classes, even though it is not required with ease.js; this is both for + * historical reasons (when `new' was required during early development) and + * because we are not testing (and do want to depend upon) that feature. + */ + +require( 'common' ).testCase( +{ + caseSetUp: function() + { + this.Class = this.require( 'class' ); + }, + + + /** + * While this may seem at odds with ease.js' philosophy (because ease.js + * methods are *not* virtual by default), we do not have much choice in + * the matter: JavaScript is very lax and does not offer a way to + * declare something as virtual or otherwise. Given that, we have to + * choose between implicit virtual methods, or never allowing the user + * to override methods inherited from a prototype. The latter is not a + * wise choice, since there would be no way to change that behavior. + * + * Of course, if such a distinction were important, a wrapper class + * could be created that simply extends the prototype, marks methods + * virtual as appropriate, and retain only that reference for use from + * that point forward. + */ + 'Methods inherited from a prototype are implicitly virtual': function() + { + var expected = {}; + + var P = function() + { + this.foo = function() + { + return null; + }; + } + + var Class = this.Class, + inst; + + // if an error is thrown here, then we're probably not virtual + this.assertDoesNotThrow( function() + { + inst = Class.extend( P, + { + 'override foo': function() + { + return expected; + } + } )(); + } ); + + // the sky is falling if the above worked but this didn't + this.assertStrictEqual( inst.foo(), expected ); + }, + + + /** + * Complement to the above test. + */ + 'Prototype method overrides must provide override keyword': function() + { + var P = function() + { + this.foo = function() {}; + }; + + var Class = this.Class; + this.assertThrows( function() + { + Class.extend( P, + { + // missing override keyword + foo: function() {}, + } ); + } ); + }, +} ); +