Skip to content

Commit

Permalink
Merged PR 69: Requirement 187
Browse files Browse the repository at this point in the history
- added build-types task to tasks.json
- improved typing of ContractHandler
- Moved ContractHandler to static definition
- Implemented Unit test

Related work items: #187
  • Loading branch information
mlhaufe committed Sep 26, 2019
2 parents 81dbf37 + 4e67868 commit 14998cb
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 19 deletions.
9 changes: 9 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
"problemMatcher": [
"$tsc"
]
},
{
"label": "build-types",
"type": "npm",
"script": "build-types",
"problemMatcher": [
"$tsc"
],
"group": "build"
}
]
}
10 changes: 5 additions & 5 deletions src/ContractHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Assertion from './Assertion';
const contractHandler = Symbol('Contract handler');

/**
* The ContractHandler manages the registraction and evaluation of contracts associated with a class
* The ContractHandler manages the registration and evaluation of contracts associated with a class
*/
class ContractHandler {
protected _assert: typeof Assertion.prototype.assert;
Expand All @@ -35,7 +35,7 @@ class ContractHandler {
* @param feature
* @param target
*/
protected _decorated(feature: any, target: any) {
protected _decorated(feature: Function, target: object) {
this.assertInvariants(target);
let result = feature.apply(target, arguments);
this.assertInvariants(target);
Expand All @@ -62,7 +62,7 @@ class ContractHandler {
*
* @param self - The context class
*/
assertInvariants(self: any) {
assertInvariants(self: object) {
this._invariantRegistry.forEach((message, predicate) => {
this._assert(predicate(self), message);
});
Expand All @@ -74,7 +74,7 @@ class ContractHandler {
* @param target - The target object
* @param prop - The name or Symbol of the property to get
*/
get(target: any, prop: any) {
get(target: object, prop: keyof typeof target) {
let feature = target[prop];

// TODO: get could be a getter
Expand All @@ -90,7 +90,7 @@ class ContractHandler {
* @param prop - The name or Symbol of the property to set
* @param value - The new value of the property to set.
*/
set(target: any, prop: any, value: any) {
set(target: object, prop: keyof typeof target, value: (typeof target)[keyof typeof target]) {
this.assertInvariants(target);
target[prop] = value;
this.assertInvariants(target);
Expand Down
47 changes: 47 additions & 0 deletions src/InvariantDecorator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,4 +663,51 @@ describe('An invariant decorator must accept multiple assertions', () => {
return Stack;
}).not.toThrow();
});
});

/**
* Requirement 187
* https://dev.azure.com/thenewobjective/decorator-contracts/_workitems/edit/187
*/
describe('A subclass with its own invariants must enforce all ancestor invariants', () => {
test('Debug Mode', () => {
let {invariant} = new Contracts(true);

expect(() => {
@invariant(
self => self instanceof Base,
self => self != null
)
class Base {}

@invariant(self => self instanceof Sub)
class Sub extends Base {}

return new Sub();
}).not.toThrow();

expect(() => {
@invariant(self => self instanceof Array)
class Base {}

@invariant(self => self instanceof Sub)
class Sub extends Base {}

return new Sub();
}).toThrow(AssertionError);

expect(() => {
@invariant(
self => self instanceof Base,
'I thought I was a Base!'
)
class Base {}

@invariant(self => self instanceof Array)
// @ts-ignore : Ignore unused error
class Sub extends Base {}

return new Base();
}).not.toThrow();
});
});
25 changes: 11 additions & 14 deletions src/InvariantDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,38 +30,35 @@ export default class InvariantDecorator {
invariant<Self>(predicate: Predicate<Self> | [Predicate<Self>, Message][], message?: any, ...rest: Predicate<Self>[]): ClassDecorator {
let assert = this._assert,
debugMode = this.debugMode,
defaultMessage = 'Invariant violated',
rs = rest == undefined ? [] : rest;
defaultMessage = 'Invariant violated';

let invariants: [Predicate<Self>, Message][] =
Array.isArray(predicate) ? predicate :
typeof message == 'string' ? [[predicate, message]] :
message == undefined ? [[predicate, defaultMessage]] :
typeof message == 'function' ? [
predicate, message, ...rs
].map(pred => [pred, defaultMessage]) : [];
[predicate, message, ...rest].map(pred => [pred, defaultMessage]);

return function<T extends Constructor<any>>(Constructor: T) {
return function<T extends Constructor<any>>(Base: T) {
if(!debugMode) {
return Constructor;
return Base;
}

let hasHandler = Object.getOwnPropertySymbols(Constructor.prototype).includes(contractHandler);
let handler = hasHandler ?
Constructor.prototype[contractHandler] :
let hasHandler = Object.getOwnPropertySymbols(Base).includes(contractHandler);
let handler: ContractHandler = hasHandler ?
(Base as any)[contractHandler] :
new ContractHandler(assert);
invariants.forEach(([pred, message]) => {
handler.addInvariant(pred, message);
});
if(hasHandler) {
return Constructor;
return Base;
} else {
return class InvariantClass extends Constructor {
[contractHandler] = handler;
return class InvariantClass extends Base {
static [contractHandler]: ContractHandler = handler;

constructor(...args: any[]) {
super(...args);
this[contractHandler].assertInvariants(this);
InvariantClass[contractHandler].assertInvariants(this);

return new Proxy(this, handler);
}
Expand Down

0 comments on commit 14998cb

Please sign in to comment.