From 8d81373ef8e85ef4cf1387ecb6c44890afad1185 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Thu, 13 Feb 2014 05:21:26 -0500 Subject: [PATCH] Began named trait implementation Does not yet support staging object like classes --- lib/Trait.js | 52 ++++++++++++++++++++++-- test/Trait/NamedTest.js | 87 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 test/Trait/NamedTest.js diff --git a/lib/Trait.js b/lib/Trait.js index 1179079..0e5074b 100644 --- a/lib/Trait.js +++ b/lib/Trait.js @@ -30,12 +30,54 @@ function Trait() case 1: return Trait.extend.apply( this, arguments ); break; + + case 2: + return createNamedTrait.apply( this, arguments ); + break; + + default: + throw Error( "Missing trait name or definition" ); } }; +/** + * Create a named trait + * + * @param {string} name trait name + * @param {Object} def trait definition + * + * @return {Function} named trait + */ +function createNamedTrait( name, dfn ) +{ + if ( arguments.length > 2 ) + { + throw Error( + "Expecting at most two arguments for definition of named " + + "Trait " + name + "'; " + arguments.length + " given" + ); + } + + if ( typeof name !== 'string' ) + { + throw Error( + "First argument of named class definition must be a string" + ); + } + + dfn.__name = name; + + return Trait.extend( dfn ); +} + + Trait.extend = function( dfn ) { + // store any provided name, since we'll be clobbering it (the definition + // object will be used to define the hidden abstract class) + var name = dfn.__name || '(Trait)'; + // we need at least one abstract member in order to declare a class as // abstract (in this case, our trait class), so let's create a dummy one // just in case DFN does not contain any abstract members itself @@ -52,9 +94,13 @@ Trait.extend = function( dfn ) // and here we can see that traits are quite literally abstract classes var tclass = AbstractClass( dfn ); - TraitType.__trait = true; - TraitType.__acls = tclass; - TraitType.__ccls = null; + TraitType.__trait = true; + TraitType.__acls = tclass; + TraitType.__ccls = null; + TraitType.toString = function() + { + return ''+name; + }; // traits are not permitted to define constructors if ( tclass.___$$methods$$['public'].__construct !== undefined ) diff --git a/test/Trait/NamedTest.js b/test/Trait/NamedTest.js new file mode 100644 index 0000000..d4a5a48 --- /dev/null +++ b/test/Trait/NamedTest.js @@ -0,0 +1,87 @@ +/** + * Tests named trait definitions + * + * 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 . + */ + +require( 'common' ).testCase( +{ + caseSetUp: function() + { + this.Sut = this.require( 'Trait' ); + this.Class = this.require( 'class' ); + }, + + + /** + * If a trait is not given a name, then converting it to a string should + * indicate that it is anonymous. Further, to disambiguate from + * anonymous classes, we should further indicate that it is a trait. + * + * This test is fragile in the sense that it tests for an explicit + * string: this is intended, since some developers may rely on this + * string (even though they really should use Trait.isTrait), and so it + * should be explicitly documented. + */ + 'Anonymous trait is properly indicated when converted to string': + function() + { + var given = this.Sut( {} ).toString(); + this.assertEqual( given, '(Trait)' ); + }, + + + /** + * Analagous to named classes: we should provide the name when + * converting to a string to aid in debugging. + */ + 'Named trait contains name when converted to string': function() + { + var name = 'FooTrait', + T = this.Sut( name, {} ); + + this.assertOk( T.toString().match( name ) ); + }, + + + /** + * We assume that, if two or more arguments are provided, that the + * definition is named. + */ + 'Named trait definition cannot contain zero or more than two arguments': + function() + { + var Sut = this.Sut; + this.assertThrows( function() { Sut(); } ); + this.assertThrows( function() { Sut( 1, 2, 3 ); } ); + }, + + + /** + * Operating on the same assumption as the above test. + */ + 'First argument in named trait definition must be a string': + function() + { + var Sut = this.Sut; + this.assertThrows( function() + { + Sut( {}, {} ); + } ); + }, +} );