1
0
Fork 0
Commit Graph

525 Commits (master)

Author SHA1 Message Date
Mike Gerwitz 016bb1362b
Copyright year update for all files modified since 0.2.8
* lib/MemberBuilderValidator.js: Updated copyright year.
* lib/class.js: Updated copyright year.
* test/Class/ConstructorTest.js: Updated copyright year.
* test/Class/ExtendTest.js: Updated copyright year.
* test/Class/GeneralTest.js: Updated copyright year.
* test/MemberBuilderValidator/MethodTest.js: Updated copyright year.
* test/Trait/LinearizationTest.js: Updated copyright year.
* test/Trait/VirtualTest.js: Updated copyright year.
2017-11-04 15:08:02 -04:00
Mike Gerwitz 1d9c06a169
Properly handle error subtype #constructor (#__constructor alias)
Using `#constructor' in place of `#__construct' caused an error attempting
to "redefine" `#__constructor' (once the alias was applied); the system was
making improper assumptions and not accounting for aliases.

In fact, everthing about how this was being done was bad, since adding any
keywords would have thrown it off as well!

* lib/ClassBuilder.js (build): Move ector detection past prop
  parsing.  Check parsed properties instead of raw.
* test/ClassBuilder/ErrorExtendTest.js: Iterate ctor override test on new
    `ctors'.
  (caseSetUp) [ctors]: Add property.
2017-11-04 14:51:29 -04:00
Mike Gerwitz 60920f18a6
Fail intelligently if provided class in place of definition object
This is intended mainly to handle cases where the user forgets the second
argument when extending a class:

  Class.extend( Base )  // missing second argument (definition object)

* lib/ClassBuilder.js
  (exports.isClass): Move from lib/class.js.
  (exports.prototype.build): Throw error if definition object is a class.
* lib/class.js (module.exports.isClass): Reference `ClassBuilder.isClass'.
* test/Class/ExtendTest.js: Add respective test case.
2017-11-04 01:07:06 -04:00
Mike Gerwitz 55f47a3c5a
Correct Trait#__inst in ES3 environments
The previous commit's test case failed in ES3 environments.  I'd amend that
commit, but Savannah refuses force pushes, and I can't work around that on
`master'.

* lib/Trait.js (tctor): Assign `__inst' to the public visibility object
  rather than `vis'.
2017-11-04 00:34:00 -04:00
Mike Gerwitz bb3956f1b4
Fix trait __inst
`this.__inst' within trait methods will now correctly resolve to the public
visibility object of the class we're mixed into, rather than
`undefined'.  This behavior is consistent with the rest of the system.

* lib/ClassBuilder.js (initInstance): Add `inst' to private metadata.  This
  is the public visibility object.
* lib/Trait.js (tctor): Initialize concrete trait `__inst' to aforementioned
  `inst' metadata value.
* test/Trait/LinearizationTest.js: Add respective test.
2017-11-02 00:15:31 -04:00
Mike Gerwitz 6c6e41c415
Add Class.assert{InstanceOf,isA}
* lib/class.js (assertInstanceOf, assertIsA): New methods.
* test/Class/GeneralTest.js: Add respective tests.
* doc/classes.texi (Type Checks and Polymorphism): Add reference for
    methods.  Update and format text.  Add indexes for "polymorphism",
    "type checking", and "duck typing".
2017-10-27 23:46:16 -04:00
Mike Gerwitz 90fd1a8d08
`override' implies `virtual'
This behavior is consistent with other OO languages like C++ and C# that do
not have virtual methods by default.

This solution isn't ideal, but I don't have time for a larger refactoring
right now.  I sat on this change for a good few weeks before committing it
unchanged.

* lib/MemberBuilderValidator.js (validateMethod): Allow override of
  supertype overrides.

* test/*: Stripped `virtual' keyword where appropriate.

* doc/classes.texi (Inheritance): Update to state that `override' implies
  `virtual'.
2017-06-30 02:01:40 -04:00
Mike Gerwitz b841b9cc5e
Fix trait extending of supertype with constructor
Supertypes that extend constructors may now be extended by traits without
completely blowing up.  Good feature.

* lib/Trait.js (__tconstruct): Add function.
  (createVirtProxy): Use it.

* test/Trait/ClassExtendTest.js: Add test.
2017-01-02 23:34:29 -05:00
Mike Gerwitz 92c57c8ffe
Constructor virtual by default
* lib/ClassBuilder.js (_keywordParser): Make __construct virtual.
* test/Class/ConstructorTest.js: Add test.

* doc/classes.texi (Constructors): Update documentation.
2017-01-02 23:30:33 -05:00
Mike Gerwitz e93f3a70f9 Support for named trait class extending
We can call this a bugfix...it's more of a neglected feature that's
otherwise completely inconsistent with the rest of the system. :)

* lib/Trait.js (createNamedTrait): Support base.
  (_createStaging) [extend]: Support base.

* test/Trait/NamedTest.js: Add test.
2016-12-29 03:49:47 -05:00
Mike Gerwitz 017b85d75b
Copyright year updates for recent trait changes
* lib/Trait.js: Add Copyright year 2016.
* test/Trait/ContextTest.js: Add Copyright year 2016.
2016-12-29 03:11:49 -05:00
Mike Gerwitz 04e98e682e
Correct trait->class calling context on class supertype
See test cases for more information.  This was a pretty unfortunate and
nasty bug that I discovered while working on a project that uses easejs; it
wasn't something that was found previously because this support was only
added relatively recently, and this problem does not exist if an interface
is used.

* lib/Trait.js (bindSuperCtx): Add function.
  (tctor): Use it.

* test/Trait/ScopeTest.js: Add calling context tests.
2016-12-29 02:39:16 -05:00
Mike Gerwitz 748ceaf0bf
Fix silly mix{o=e}r typo in Trait
* lib/Trait.js (_validateMixin): Change docblock and error message to
    resolve typo.
2016-12-28 03:07:29 -05:00
Mike Gerwitz a7e1d2ad70
Error constructor integration into ClassBuilder
This introduces the transparent subtyping.

* doc/classes.texi (Error Subtyping): Section added

* lib/ClassBuilder.js (ClassBuilder): Accepts ErrorCtor instance
  (build): Transparent Error subtyping

* lib/class.js: Provide ErrorCtor instance to ClassBuilder

* test/ClassBuilder/ErrorExtendTest.js: Add test case for transparent error
  subtyping
2016-07-15 00:21:06 -04:00
Mike Gerwitz d99ab2e5fb
Error constructor `after' support
* lib/ctor/ErrorCtor.js (createCtor): Add `after' parameter to be
  invoked by `__$$ector$$__' at end of function.

* test/ctor/ErrorCtorTest.js: Add respective tests.
2016-07-15 00:18:42 -04:00
Mike Gerwitz c69a42945c
Add error constructor generator
This produces the constructor used for Error subtypes.

* lib/ctor/ErrorCtor.js: Added
* test/ctor/ErrorCtorTest.js: Added
2016-07-15 00:18:37 -04:00
Mike Gerwitz c0a1a9bdcc
[copyright] Copyright update for recent changes 2015 2015-10-26 22:49:28 -04:00
Mike Gerwitz 3bd52122c5
Remove misplaced function annotations for defineSecureProp
Seemed like a good idea at the time, but now Closure Compiler complains.
2015-10-26 22:46:08 -04:00
Mike Gerwitz 8707e537ab
Fix protected mixin override for ES3 fallbacks
It's fun when you're about to make a release and find that ES3 fallback
tests are failing.  That's also what happens when you decide not to run
the combined tests until the last minute, because they usually don't fail.
2015-10-26 22:34:48 -04:00
Mike Gerwitz 903a1a135c
Prevent mixin failure on null/undefined supertype properties 2015-10-26 22:17:28 -04:00
Mike Gerwitz 47d51fd5da
Provide useful error on attempt to mix in non-trait
Before this change, mixin attempts would fail at the time of mixin when
easejs attempts to invoke the `__mixin` method on the object.  This is both
cryptic and void of any useful information on the stack.
2015-10-25 22:15:34 -04:00
Mike Gerwitz d9b86c1544
Support for trait class supertype overrides
Traits can now override methods of their class supertypes.  Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface.  This had two notable downsides:

  1. A trait that was only compatible with details of `C` could only work
     with `C#M` if it implemented an interface `I` that declared `I#M`.
     This required the author of `C` to create interfaces where they would
     otherwise not be necessary.

  2. As a corollary of #1---since methods of interfaces must be public, it
     was not possible for `T` to override any protected method of `C`; this
     meant that `C` would have to declare such methods public, which may
     break encapsulation or expose unnecessary concerns to the outside
     world.

Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior.  Stackable traits work
as you would expect:

```javascript
var C = Class(
{
    'virtual foo': function()
    {
        return 'C';
    },
} );

var T1 = Trait.extend( C,
{
    'virtual abstract override foo': function()
    {
        return 'T1' + this.__super();
    },
} );

var T2 = Trait.extend( C,
{
    'virtual abstract override foo': function()
    {
        return 'T2' + this.__super();
    },
} );

C.use( T1 )
    .use( T1 )
    .use( T2 )
    .use( T2 )
    .foo();
// result: "T2T2T1T1C"
```

If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:

```javascript
var C = Class(
{
    'virtual foo': function()
    {
        return 'C';
    },
} );

var T1 = Trait.extend( C,
{
    'virtual abstract override foo': function()
    {
        return 'T1' + this.__super();
    },
} );

var T2 = Trait.extend( C,
{
    // static override
    'virtual override foo': function()
    {
        return 'T2' + this.__super();
    },
} );

C.use( T1 )
    .use( T1 )
    .use( T2 )
    .use( T2 )
    .foo();
// result: "T2C"
```

This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.

Happy hacking.
2015-10-24 23:53:23 -04:00
Mike Gerwitz ba2605f836 Alias `constructor` member to `__construct`
This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor.  This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.

To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:

```
// ease.js using ES6
let Person = Class(
{
    _name: '',

    // note that __construct still works as well
    constructor( name ) {
      this._name = ''+name;
    },

    sayHi() {
      return "Hi, I'm " + this.getName();
    },

    // keywords still work as expected
    'protected getName'() {
      return this._name;
    }
} );

// ES6 using `class` keyword
class Person
{
    // note that ES6 will _not_ make this private
    _name: '',

    constructor( name ) {
      this._name = ''+name;
    },

    sayHi() {
      return "Hi, I'm " + this.getName();
    }

    // keywords unsupported (you'd have to use Symbols)
    getName() {
      return this._name;
    }
}

// ES3/5 ease.js
var Person = Class(
{
    _name: '',

    __construct: function( name ) {
      this._name = ''+name;
    },

    sayHi: function() {
      return "Hi, I'm " + this._name;
    },

    'protected getName': function() {
      return this._name;
    }
} );
```

As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
2015-09-16 00:02:00 -04:00
Mike Gerwitz 579527d168
Class is not a needed dependency for Interface 2015-05-28 01:23:52 -04:00
Mike Gerwitz db3ade378a
[copyright] Copyright update 2015-05-28 01:01:51 -04:00
Mike Gerwitz 867127ed2f Trait class extension support
"Extending" a class C simply creates a contract stating that the trait may
only be mixed into something of type C (so, C or its subtypes).
2015-05-27 23:23:47 -04:00
Mike Gerwitz 96c5a702ce Abstract mixin initial implementation 2015-05-27 23:23:47 -04:00
Mike Gerwitz 5b6a0c0bb5 "lazy" metadata support 2015-05-27 23:23:47 -04:00
Mike Gerwitz e866f53d4c Remove global _extending state
The previous implementation could not survive recursive applications, which
I did know about.  But that was before the system was brutally abused by
traits.
2015-05-27 23:23:47 -04:00
Mike Gerwitz 69e9828beb Object class masquerading 2015-05-27 23:23:47 -04:00
Mike Gerwitz a5c89565fe `undefined` is no longer applied to Trait.__mixin
This is a bugfix; the bug was introduced in v0.2.3.

Notably, this caused problems in IE<=8 for non-argument traits. The problem
is that `fund.apply( x, undefined )` is not valid in ES3---we must apply an
empty array (or argument list) instead.
2014-08-07 22:53:10 -04:00
Mike Gerwitz 324ff5ddca Global no longer uses root as alt object prototype
This is a bugfix; the bug was introduced in v0.2.3.

Initially, the implementation created a new object with the root object as
its prototype, taking advantage of ECMAScript's native
overrides/fallthroughs. Unfortunately, IE<=8 had a buggy implementation,
effectively treating the prototype as an empty object. So, rather than
alt.Array === root.Array, alt.Array === undefined.

The fix is simply to reference discrete objects.
2014-08-07 22:33:04 -04:00
Mike Gerwitz d0ec7aca9b method.super references now ES3-compatible
This is a bugfix; the bug was introduced in v0.2.3.

In ECMAScript 5, reserved keywords can be used to reference the field of an
object in dot notation (e.g. method.super), but in ES3 this is prohibited;
in these cases, method['super'] must be used. To maintain ES3 compatiblity,
GNU ease.js will use the latter within its code.

Of course, if you are developing software that need not support ES3, then
you can use the dot notation yourself in your own code.

This does not sway my decision to use `super`---ES3 will soon (hopefully)
become extinct, and would be already were it not for the terrible influence
of Microsloth's outdated browsers.
2014-08-07 22:24:25 -04:00
Mike Gerwitz b9ba6388d2 Interface.isInstanceOf will account for interop compatibility
This is a bug fix.

If the provided object's constructor is an ease.js type, then the
conventional rules will apply (as mentioned in the test docblock and in the
manual); however, if it's just a vanilla ECMAScript object, then the interop
compatibility checks will be used instead.

The manual already states that this is the case; unfortunately, it
lies---this was apparently overlooked, and is a bug.
2014-08-06 23:07:44 -04:00
Mike Gerwitz e934338b41 Subtype ctor guarantees with parent __mixin or __construct
A solution for this problem took a disproportionally large amount of time,
attempting many different approaches, and arriving still at a kluge; this is
indicative of a larger issue---we've long since breached the comfort of the
original design, and drastic refactoring is needed.

I have ideas for this, and have already started in another branch, but I
cannot but this implementation off any longer while waiting for it.

Sorry for anyone waiting on the next release: this is what held it up, in
combination with my attention being directed elsewhere during that time (see
the sparse commit timestamps). Including this ordering guarantee is very
important for a stable, well-designed [trait] system.
2014-07-27 01:54:30 -04:00
Mike Gerwitz 90a32a104f __construct and __mixin ordering guarantees
See test cases for rationale.

I'm not satisfied with this implementation, but the current state of ease.js
makes this kind of thing awkward. Will revisit at some point.
2014-07-27 01:54:30 -04:00
Mike Gerwitz f3cb815baa Sibling traits will each have __mixin called distinctly 2014-07-27 01:54:30 -04:00
Mike Gerwitz 2204ff6d28 Parameterized traits may now be mixed in without configuration
Rationale is available in the test case.
2014-07-27 01:54:30 -04:00
Mike Gerwitz 3fc0f90e01 Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:

  1. Expose setters for credentials
  2. Trait closure
  3. Extend the trait (not yet supported)

The first option seems like a simple solution:

```javascript
  Transport.use( HttpPlainAuth )()
    .setUser( 'username', 'password' )
    .send( ... );
```

But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.

More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!

The second option is to store configuration data outside of the Trait acting
as a closure:

```javascript
  var _user, _pass;
  function setCredentials( user, pass ) { _user = user; _pass = pass; }
  Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```

There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.

The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.

To recover from memory leaks, you could instead create a trait generator:

```javascript
  function createHttpPlainAuth( user, pass )
  {
      return Trait( { /* ... */ } );
  }
```

This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.

The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.

Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:

```javascript
  Transport.use( HttpPlainAuth( user, pass ) )()
    .send( ... );
```

Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.

It further allows for a convenient means of providing useful, reusable
exports:

```javascript
  module.exports = {
      ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
      ServerBarAuth: HttpPlainAuth( userbar, passbar ),

      ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
      // ...
  };

  var module = require( 'foo' );

  // dynamic auth
  Transport.use( foo.ServerFooAuth )().send( ... );

  // or predefined classes
  module.ServerFooTransport().send( ... );
```

Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.

So how do you create parameterized traits? You need only define a `__mixin`
method:

  Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );

The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.

A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-07-27 01:54:30 -04:00
Mike Gerwitz d400a4cbb1 FallbackSymbol now returns instance of self
Existing functionality is maintained using toString. This is necessary to
support type checking, and to be more consistent with the native Symbol
implementation.

Technically `new FallbackSymbol` is supposed to throw a TypeError, just as
`new Symbol` would, but doing so complicates the constructor unncessarily,
so I am not going to bother with that here.
2014-07-09 23:50:36 -04:00
Mike Gerwitz 07c0a974af FIXME added for possibly leaking private symbol during tctor call
This is a security-related issue: we want to ensure that the user cannot,
without debugging tools, retrieve certain internal details that may be used
to compromise an implementation.
2014-07-09 00:14:25 -04:00
Mike Gerwitz 031489a07b Private symbol key is now non-enumerable
This will, at least in ES5 environments, prevent its general discovery and
[accidental] use. Of course, any decent debugger will still be able to see
it, so unless ES6 is used, this is not truely hidden.
2014-07-09 00:14:25 -04:00
Mike Gerwitz 072fef2dc0 createMeta no longer copying to new_class.prototype
I do not wholly recall why this was done initially (nor do I care to
research it, since it's not necessary now), but from the looks of it, it was
likely a kluge to handle a poor implementation of some feature.

This will help clean up a little, since it's rude to pollute a prototype
unnecessarily.
2014-07-09 00:14:25 -04:00
Mike Gerwitz 17d11c1832 Re-encapsulated ClassBuilder private symbol
Temporary method removed and symbol now provided directly to the trait ctor;
this is needed to retrieve the protected visibility object; there is
currently no API for doing so, and there may never be (providing an API for
such a thing seems questionable).

Of course, if we eventually want to decouple the trait implementation from
ClassBuilder (which would be a good idea), we'll have to figure out
something.
2014-07-09 00:14:25 -04:00
Mike Gerwitz 13b0bd2fb3 Visibility object is now encapsulated in symbol key
Note that this patch temporarily breaks encapsulation via
ClassBuilder.___$$privsym$$ to expose necessary internal details to Trait;
this will be resolved.
2014-07-09 00:14:25 -04:00
Mike Gerwitz a35a9c6ed3 Class ___$$meta$$ moved into private symbol field 2014-07-09 00:14:25 -04:00
Mike Gerwitz e0866db313 Corrected MemberBuilderValidator warning handler in class.js 2014-07-09 00:14:25 -04:00
Mike Gerwitz 5212515d99 ClassBuilder#build now only making one getMeta call 2014-07-09 00:14:24 -04:00
Mike Gerwitz c49a6d70de util/Symbol added 2014-07-09 00:14:24 -04:00
Mike Gerwitz 2f615fbe68 FallbackSymbol added
This is the closest we will get to implementing a concept similar to symbols
in pre-ES6. The intent is that, in an ES5 environment, the caller should
ensure that the object receiving this key will mark it as non-enumerable.
Otherwise, we're out of luck.

The symbol string is pseduo-randomly generated with an attempt to reduce the
likelihood of field collisions and malicious Math.{floor,random} overwrites
(so long as they are clean at the time of loading the module).
2014-07-09 00:14:24 -04:00