Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
mweststrate committed Jun 10, 2016
1 parent aca99fe commit 68dbee8
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 244 deletions.
85 changes: 21 additions & 64 deletions src/api/computeddecorator.ts
@@ -1,36 +1,15 @@
import {ValueMode, getValueModeFromValue, asStructure} from "../types/modifiers";
import {IObservableValue} from "./observable";
import {asObservableObject, setObservableObjectInstanceProperty, defineObservableProperty} from "../types/observableobject";
import {invariant, assertPropertyConfigurable} from "../utils/utils";
import {asObservableObject, defineObservableProperty} from "../types/observableobject";
import {invariant} from "../utils/utils";
import {createClassPropertyDecorator} from "../utils/decorators";
import {ComputedValue} from "../core/computedvalue";
import {getDebugName} from "../types/type-utils";

export interface IComputedValueOptions {
asStructure: boolean;
}

/**
* Decorator for class properties: @computed get value() { return expr; }.
* For legacy purposes also invokable as ES5 observable created: `computed(() => expr)`;
*/
export function computed<T>(func: () => T, scope?: any): IObservableValue<T>;
export function computed(opts: IComputedValueOptions): (target: Object, key: string, baseDescriptor?: PropertyDescriptor) => void;
export function computed(target: Object, key: string | symbol, baseDescriptor?: PropertyDescriptor): void;
export function computed(targetOrExpr: any, keyOrScope?: any, baseDescriptor?: PropertyDescriptor, options?: IComputedValueOptions) {
if (arguments.length < 3 && typeof targetOrExpr === "function")
return computedExpr(targetOrExpr, keyOrScope);
invariant(!baseDescriptor || !baseDescriptor.set, `@observable properties cannot have a setter: ${keyOrScope}`);
return computedDecoratorImpl.apply(null, arguments);
// return computedDecorator.apply(null, arguments);
}

function computedExpr<T>(expr: () => T, scope?: any) {
const [mode, value] = getValueModeFromValue(expr, ValueMode.Recursive);
return new ComputedValue(value, scope, mode === ValueMode.Structure, value.name);
}

const computedDecoratorImpl = createClassPropertyDecorator(
const computedDecorator = createClassPropertyDecorator(
(target, name, _, decoratorArgs, originalDescriptor) => {
const baseValue = originalDescriptor.get;
invariant(typeof baseValue === "function", "@computed can only be used on getter functions, like: '@computed get myProps() { return ...; }'");
Expand All @@ -45,52 +24,30 @@ const computedDecoratorImpl = createClassPropertyDecorator(
function (name) {
return this.$mobx.values[name].get();
},
function (name) {
invariant(false, `It is not allowed to assign new values to @computed properties: ${getDebugName(this)}.${name}`);
},
throwingComputedValueSetter,
false,
true,
true
);

/**
* Decorator for class properties: @computed get value() { return expr; }.
* For legacy purposes also invokable as ES5 observable created: `computed(() => expr)`;
*/
export function computed<T>(func: () => T, scope?: any): IObservableValue<T>;
export function computed(opts: IComputedValueOptions): (target: Object, key: string, baseDescriptor?: PropertyDescriptor) => void;
export function computed(target: Object, key: string | symbol, baseDescriptor?: PropertyDescriptor): void;
export function computed(targetOrExpr: any, keyOrScope?: any, baseDescriptor?: PropertyDescriptor, options?: IComputedValueOptions) {
if (arguments.length < 3 && typeof targetOrExpr === "function")
return computedExpr(targetOrExpr, keyOrScope);
invariant(!baseDescriptor || !baseDescriptor.set, `@observable properties cannot have a setter: ${keyOrScope}`);
return computedDecorator.apply(null, arguments);
}

function computedDecorator(target: any, key?: any, baseDescriptor?: PropertyDescriptor, options?: IComputedValueOptions): any {
// invoked as decorator factory with options
if (arguments.length === 1) {
const options = target;
return (target, key, baseDescriptor) => computedDecorator.call(null, target, key, baseDescriptor, options);
}
invariant(baseDescriptor && baseDescriptor.hasOwnProperty("get"), "@computed can only be used on getter functions, like: '@computed get myProps() { return ...; }'");
assertPropertyConfigurable(target, key);

const descriptor: PropertyDescriptor = {};
const getter = baseDescriptor.get;

invariant(typeof target === "object", `The @observable decorator can only be used on objects`, key);
invariant(typeof getter === "function", `@observable expects a getter function if used on a property.`, key);
invariant(!baseDescriptor.set, `@observable properties cannot have a setter.`, key);
invariant(getter.length === 0, `@observable getter functions should not take arguments.`, key);

descriptor.configurable = true;
descriptor.enumerable = false;
descriptor.get = function() {
setObservableObjectInstanceProperty(
asObservableObject(this, undefined, ValueMode.Recursive),
key,
options && options.asStructure === true ? asStructure(getter) : getter
);
return this[key];
};

// by default, assignments to properties without setter are ignored. Let's fail fast instead.
descriptor.set = throwingComputedValueSetter;
if (!baseDescriptor) {
Object.defineProperty(target, key, descriptor); // For typescript
} else {
return descriptor;
}
function computedExpr<T>(expr: () => T, scope?: any) {
const [mode, value] = getValueModeFromValue(expr, ValueMode.Recursive);
return new ComputedValue(value, scope, mode === ValueMode.Structure, value.name);
}

export function throwingComputedValueSetter() {
throw new Error(`[ComputedValue] It is not allowed to assign new values to computed properties.`);
}
}
43 changes: 1 addition & 42 deletions src/api/observabledecorator.ts
@@ -1,11 +1,8 @@
import {ValueMode, asReference} from "../types/modifiers";
import {allowStateChanges} from "../core/action";
import {computed} from "../api/computeddecorator";
import {asObservableObject, defineObservableProperty, setPropertyValue} from "../types/observableobject";
import {invariant, assertPropertyConfigurable, deprecated} from "../utils/utils";
import {checkIfStateModificationsAreAllowed} from "../core/derivation";
import {invariant, assertPropertyConfigurable} from "../utils/utils";
import {createClassPropertyDecorator} from "../utils/decorators";
import {ObservableValue} from "../types/observablevalue";

const decoratorImpl = createClassPropertyDecorator(
(target, name, baseValue) => {
Expand Down Expand Up @@ -42,42 +39,4 @@ export function observableDecorator(target: Object, key: string, baseDescriptor:
assertPropertyConfigurable(target, key);
invariant(!baseDescriptor || !baseDescriptor.get, "@observable can not be used on getters, use @computed instead");
return decoratorImpl.apply(null, arguments);

// - In typescript, observable annotations are invoked on the prototype, not on actual instances,
// so upon invocation, determine the 'this' instance, and define a property on the
// instance as well (that hides the propotype property)
// - In babel, the initial value is passed as the closure baseDiscriptor.initializer'
/*
if (baseDescriptor && baseDescriptor.hasOwnProperty("get")) {
deprecated("Using @observable on computed values is deprecated. Use @computed instead.");
return computed.apply(null, arguments);
}
const descriptor: PropertyDescriptor = {};
invariant(typeof target === "object", `The @observable decorator can only be used on objects`, key);
descriptor.configurable = true;
descriptor.enumerable = true;
descriptor.get = function() {
let baseValue = undefined;
if (baseDescriptor && (<any>baseDescriptor).initializer) { // For babel
baseValue = (<any>baseDescriptor).initializer();
if (typeof baseValue === "function")
baseValue = asReference(baseValue);
}
// the getter might create a reactive property lazily, so this might even happen during a view.
allowStateChanges(true, () => {
setObservableObjectProperty(asObservableObject(this, undefined, ValueMode.Recursive), key, baseValue);
});
return this[key];
};
descriptor.set = function(value) {
setObservableObjectProperty(asObservableObject(this, undefined, ValueMode.Recursive), key, typeof value === "function" ? asReference(value) : value);
};
if (!baseDescriptor) {
Object.defineProperty(target, key, descriptor); // For typescript
} else {
return descriptor;
}
*/
}

61 changes: 4 additions & 57 deletions src/core/action.ts
Expand Up @@ -18,7 +18,10 @@ const actionFieldDecorator = createClassPropertyDecorator(
});
} else {
// bound instance methods
addBoundAction(target, key, wrappedAction);
Object.defineProperty(target, key, {
configurable: true, enumerable: false, writable: false,
value: wrappedAction
});
}
},
function (key) {
Expand All @@ -44,62 +47,6 @@ export function action(arg1, arg2?, arg3?, arg4?): any {
return actionFieldDecorator.apply(null, arguments);
}

function actionDecorator(name: string, target: any, key: string, descriptor: PropertyDescriptor) {
if (descriptor === undefined) {
// typescript: @action f = () => { }
typescriptActionValueDecorator(name, target, key);
return;
}
if (descriptor.value === undefined && typeof (descriptor as any).initializer === "function") {
// babel: @action f = () => { }
return babelActionValueDecorator(name, target, key, descriptor);
}
const base = descriptor.value;
descriptor.value = actionImplementation(name, base);
}

/**
* Decorators the following pattern @action method = () => {} by desugaring it to method = action(() => {})
*/
function babelActionValueDecorator(name: string, target, prop, descriptor): PropertyDescriptor {
return {
configurable: true,
enumerable: false,
get: function() {
const v = descriptor.initializer.call(this);
invariant(typeof v === "function", `Babel @action decorator expects the field '${prop} to be initialized with a function`);
const implementation = action(name, v);
addBoundAction(this, prop, implementation);
return implementation;
},
set: function() {
invariant(false, `Babel @action decorator: field '${prop}' not initialized`);
}
};
}

function typescriptActionValueDecorator(name: string, target, prop) {
Object.defineProperty(target, prop, {
configurable: true,
enumerable: false,
get: function() {
invariant(false, `TypeScript @action decorator: field '${prop}' not initialized`);
},
set: function(v) {
invariant(typeof v === "function", `TypeScript @action decorator expects the field '${prop} to be initialized with a function`);
addBoundAction(this, prop, action(name, v));
}
});
}

function addBoundAction(target, prop, implementation) {
Object.defineProperty(target, prop, {
enumerable: false,
writable: false,
value: implementation
});
}

export function actionImplementation(actionName: string, fn: Function): Function {
invariant(typeof fn === "function", "`action` can only be invoked on functions");
invariant(typeof actionName === "string" && actionName.length > 0, `actions should have valid names, got: '${actionName}'`);
Expand Down
84 changes: 3 additions & 81 deletions src/utils/decorators.ts
Expand Up @@ -26,12 +26,7 @@ export function createClassPropertyDecorator(
/**
* Can this decorator invoked with arguments? e.g. @decorator(args)
*/
allowCustomArguments: boolean,
/**
* Usually the initial value will be based on either the .value or .initializer or initial assignment to the property.
* If this flag is set, the initial value will be the original defined get value. This is used by @computed.
*/
useGetterAsInitialValue: boolean = false
allowCustomArguments: boolean
): any {
function classPropertyDecorator(target: any, key: string, descriptor, customArgs?: IArguments) {
invariant(allowCustomArguments || quacksLikeADecorator(arguments), "This function is a decorator, but it wasn't invoked like a decorator");
Expand Down Expand Up @@ -63,12 +58,11 @@ export function createClassPropertyDecorator(
}

const {value, initializer} = descriptor;
const getter = descriptor.get;
target.__mobxLazyInitializers.push(instance => {
onInitialize(
instance,
key,
useGetterAsInitialValue ? getter : (initializer ? initializer.call(instance) : value),
(initializer ? initializer.call(instance) : value),
customArgs,
descriptor
);
Expand Down Expand Up @@ -115,16 +109,6 @@ function typescriptInitializeProperty(instance, key, v, onInitialize, customArgs
onInitialize(instance, key, v, customArgs, baseDescriptor);
}

function isPropInitialized(instance, prop) {
if (instance["__initialized" + prop] !== true) {
Object.defineProperty(instance, "__initialized" + prop, {
configurable: false, enumerable: false, writable: false, value: true
});
return false;
}
return true;
}

export function runLazyInitializers(instance) {
if (instance.__mobxDidRunLazyInitializers === true)
return;
Expand All @@ -139,68 +123,6 @@ export function runLazyInitializers(instance) {
}
}

// values on prototype: @action
// fields on object: @observable
// getters on object: @computed
// enumerable (on protot and self!)
// custom args
export function decoratorFactory(
allowCustomArguments: boolean,
allowValuesOnPrototype: boolean,
getDescriptor: (target, propName, initialValue: any, customArgs: IArguments) => PropertyDescriptor
): (target, prop: string, descriptor?) => PropertyDescriptor {
function theDecorator(target: Object, key: string, baseDescriptor, customArgs?: IArguments): any {
invariant(allowCustomArguments || quacksLikeADecorator(arguments), "This function is a decorator, but it wasn't invoked like a decorator");
const intermediateDescriptor = {
configurable: true,
enumerable: false,
get: illegalState,
set: illegalState
};
if (baseDescriptor === undefined) {
/* typescript */
// TODO: or return?
Object.defineProperty(baseDescriptor, key, intermediateDescriptor);
} else {
/* babel */
if (typeof baseDescriptor.initializer === "function") {
/* initializers are used for instance properties*/
(intermediateDescriptor as any).initializer = function () {
Object.defineProperty(this, key, getDescriptor(this, key, baseDescriptor.initializer(), customArgs));
};
return intermediateDescriptor;
} else if (baseDescriptor.hasOwnProperty("value")) {
/* values are used for values that can be defined on the prototype, e.g. methos */
invariant(allowValuesOnPrototype, "This decorator can only be used on field assignments");
/* prototype props cannot be reconfigured, so just set a new value */
const _temp = getDescriptor(target, key, baseDescriptor.value, customArgs);
baseDescriptor.value = _temp.value;
// TODO: mweh? just return _temp?
return baseDescriptor;
} else {
invariant(false, "Illegal state while applying decorator");
}
}
}

if (allowCustomArguments) {
/** If custom arguments are allowed, we should return a function that returns a decorator */
return function() {
/** Direct invocation: @decorator bla */
if (quacksLikeADecorator(arguments))
return theDecorator.apply(null, arguments);
/** Indirect invocation: @decorator(args) bla */
const outerArgs = arguments;
return (target, key, descriptor) => theDecorator(target, key, descriptor, outerArgs);
};
}
return theDecorator;
}

function illegalState() {
invariant(false, "Illegal state while trying to apply decorator");
}

function quacksLikeADecorator(args: IArguments): boolean {
return (args.length === 2 || args.length === 3) && typeof args[1] === "string";
}
}

0 comments on commit 68dbee8

Please sign in to comment.