From 25b073ba0cfb78c4e24cf639f92b9ef5b25aa46c Mon Sep 17 00:00:00 2001 From: Ron Spickenagel Date: Sun, 9 Jun 2019 17:58:55 -0400 Subject: [PATCH] Updated for full TypeScript Support + Added TypeScript support (Primary changes in private/util.js->Decorate and lazy-initialize.js->handleDescriptor) + Added full types for all decorators + Added documentation and overloads to index.d.ts for intellisense + Fixed circular dependency issue (caused issue with tsc) (new file: private/meta.js) + Updated tests to work with both ts-node and babel + Updated outdated dependencies By: Ron Spickenagel - http://www.github.com/ronspickenagel --- README.md | 32 ++++- index.d.ts | 194 ++++++++++++++++++++++++---- package.json | 20 +-- src/debounce.js | 3 +- src/lazy-initialize.js | 51 ++++++-- src/private/meta.js | 34 +++++ src/private/utils.js | 83 ++++++------ src/profile.js | 3 +- src/throttle.js | 3 +- test/exports.spec.js | 6 +- test/test.spec.js | 4 +- test/unit/applyDecorators.spec.js | 6 +- test/unit/autobind.spec.js | 16 ++- test/unit/debounce.spec.js | 8 +- test/unit/decorate.spec.js | 5 +- test/unit/deprecate.spec.js | 4 +- test/unit/enumerable.spec.js | 2 +- test/unit/extendDescriptor.js | 4 +- test/unit/lazy-initialize.spec.js | 49 +++++-- test/unit/memoize.spec.js | 5 +- test/unit/mixin.spec.js | 7 +- test/unit/nonconfigurable.spec.js | 3 +- test/unit/nonenumerable.spec.js | 2 +- test/unit/override.spec.js | 8 +- test/unit/profile.spec.js | 5 +- test/unit/readonly.spec.js | 3 +- test/unit/suppress-warnings.spec.js | 2 +- test/unit/throttle.spec.js | 2 +- test/unit/time.spec.js | 3 +- tsconfig.json | 6 +- 30 files changed, 438 insertions(+), 135 deletions(-) create mode 100755 src/private/meta.js diff --git a/README.md b/README.md index 758469c..1b07a32 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,18 @@ npm install core-decorators --save This can be consumed by any transpiler that supports stage-0 of the decorators spec, like [babel.js](https://babeljs.io/) version 5. *Babel 6 [does not yet support decorators natively](https://phabricator.babeljs.io/T2645), but you can include [babel-plugin-transform-decorators-legacy](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy) or use the [`applyDecorators()` helper](#applydecorators-helper).* -core-decorators does not officially support TypeScript. There are known incompatibilities with the way it transpiles the output. PRs certainly welcome to fix that! - ##### Bower/globals A globals version is available [here in the artifact repo](https://github.com/jayphelps/core-decorators-artifacts), or via `$ bower install core-decorators`. It defines a global variable `CoreDecorators`, which can then be used as you might expect: `@CoreDecorators.autobind()`, etc. I *highly* recommend against using that globals build as it's quite strange you're using decorators (a proposed future feature of JavaScript) while not using ES2015 modules, a spec ratified feature used by nearly every modern framework. Also--[bower is on its deathbed](https://github.com/bower/bower/pull/1748) and IMO for very good reasons. +### TypeScript Support + +Full TypeScript support has been added. Although there are differences in the way compilers like Babel and TypeScript compile output, this library has been updated to work _almost_ uniformly across both (see caveat in [@lazyInitialize](#lazyinitalize)). It should be noted, though, that because of the experimental nature of the features we are working with, compiler functionality may change in future versions. + +Everything seems to work as of TypeScript 3.5.1 & ts-node 8.2.0. If you are concerned, try cloning the repo and running tests with your desired compiler version. + ## Need lodash utilities as decorators? core-decorators aims to provide decorators that are fundamental to JavaScript itself--mostly things you could do with normal `Object.defineProperty` but not as easily when using ES2015 classes. Things like debouncing, throttling, and other more opinionated decorators are being phased out in favor of [lodash-decorators](https://www.npmjs.com/package/lodash-decorators) which wraps applicable lodash utilities as decorators. We don't want to duplicate the effort of lodash, which has years and years of robust testing and bugfixes. @@ -357,7 +361,7 @@ count === 1; // true ``` -### @lazyInitialize +### @lazyInitialize Prevents a property initializer from running until the decorated property is actually looked up. Useful to prevent excess allocations that might otherwise not be used, but be careful not to over-optimize things. @@ -385,6 +389,28 @@ editor.hugeBuffer; // createHugeBuffer() is not called again ``` +#### TypeScript Caveat +Babel's compiler supports 'initalizer' in property descriptor, which allows for the above syntax to work. If using TypeScript / non-babel compiler, you can accomplish the same by passing an initializer function to the decorator as a parameter. + +The first argument is the function, all further arguments will be passed to that initializer function. + +```typescript +import { lazyInitialize } from 'core-decorators'; + +const createHugeBuffer = () => new Array(1000000); + +const addNumbers = (...numbers) => numbers.reduce((p,c)=>p+c); + +class Editor { + @lazyInitialize(createHugeBuffer) + hugeBuffer:ArrayBuffer; + + @lazyInitialize(addNumbers,2,5,8) // = 15 + myCount:number; +} +``` + + ### @mixin (alias: @mixins) :no_entry_sign: DEPRECATED Mixes in all property descriptors from the provided Plain Old JavaScript Objects (aka POJOs) as arguments. Mixins are applied in the order they are passed, but do **not** override descriptors already on the class, including those inherited traditionally. diff --git a/index.d.ts b/index.d.ts index 46019f6..528308f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,69 +1,219 @@ -// Type definitions for core-decorators.js 0.19 +// Type definitions for core-decorators.js 0.21.0 // Project: https://github.com/jayphelps/core-decorators.js -// Definitions by: Qubo -// dtweedle -// TypeScript Version: 2.4.1 +// Definitions by: Qubo +// dtweedle +// Ron Spickenagel +// TypeScript Version: 3.5.1 -export interface PropertyOrMethodDecorator extends MethodDecorator, PropertyDecorator { - (target: object, propertyKey: string | symbol): void; +export interface PropertyOrMethodDecorator { + (target: Object, propertyKey: PropertyKey, descriptor?: PropertyDescriptor): void } -export interface Deprecate extends MethodDecorator { - (message?: string, option?: DeprecateOption): MethodDecorator; +export interface Type extends Function { + new (...args: any[]): T; } -export interface DeprecateOption { - url: string; -} /** * Forces invocations of this function to always have this refer to the class instance, * even if the function is passed around or would otherwise lose its this context. e.g. var fn = context.method; */ -export const autobind: Function; +export function autobind (target: TFunction): TFunction | void; +/** + * Forces invocations of this function to always have this refer to the class instance, + * even if the function is passed around or would otherwise lose its this context. e.g. var fn = context.method; + */ +export function autobind (target: Object, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void; + /** * Marks a property or method as not being writable. */ export const readonly: PropertyOrMethodDecorator; + + /** * Checks that the marked method indeed overrides a function with the same signature somewhere on the prototype chain. */ export const override: MethodDecorator; + + /** - * Calls console.warn() with a deprecation message. Provide a custom message to override the default one. You can also provide an options hash with a url, for further reading. + * Calls console.warn() with a deprecation message. You can also provide an options hash with a url, for further reading. + * @param msg - Custom message to override the default one. + * @param options.url - URL for further detail */ -export const deprecate: Deprecate; +export function deprecate (msg?: string, options?: { url?: string }): PropertyOrMethodDecorator /** - * Calls console.warn() with a deprecation message. Provide a custom message to override the default one. You can also provide an options hash with a url, for further reading. + * Calls console.warn() with a deprecation message. You can also provide an options hash with a url, for further reading. + * @param msg - Custom message to override the default one. + * @param options.url - URL for further detail */ -export const deprecated: Deprecate; +export function deprecate (target: Object, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void; +/** + * Calls console.warn() with a deprecation message. You can also provide an options hash with a url, for further reading. + * @param msg - Custom message to override the default one. + * @param options.url - URL for further detail + */ +export function deprecated (msg?: string, options?: { url?: string }): PropertyOrMethodDecorator +/** + * Calls console.warn() with a deprecation message. You can also provide an options hash with a url, for further reading. + * @param msg - Custom message to override the default one. + * @param options.url - URL for further detail + */ +export function deprecated (target: Object, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void; + + /** * Suppresses any JavaScript console.warn() call while the decorated function is called. (i.e. on the stack) */ export const suppressWarnings: MethodDecorator; + + /** * Marks a property or method as not being enumerable. */ export const nonenumerable: PropertyOrMethodDecorator; + + /** * Marks a property or method as not being writable. */ export const nonconfigurable: PropertyOrMethodDecorator; + + /** - * Initial implementation included, likely slow. WIP. + * (Deprecated) Initial implementation included, likely slow. WIP. */ export const memoize: MethodDecorator; + + /** * Immediately applies the provided function and arguments to the method, allowing you to wrap methods with arbitrary helpers like those provided by lodash. - * The first argument is the function to apply, all further arguments will be passed to that decorating function. + * @param decorator - Decorator to apply + * @param args - Argument to pass to decorator + */ +export function decorate (decorator: PropertyOrMethodDecorator, ...args:any[]): PropertyOrMethodDecorator; + + +/** + * Prevents a property initializer from running until the decorated property is actually looked up. + * _Note: If you're not using Babel, you must pass initilizer function as a decorator parameter or it will not work._ + * + * Usage: + * Babel - @lazyInitialize myParam = myInitializer() + * TypeScript - @lazyInitialize(myInitializer) myParam:myType + * @param initializer - Value initializer method + * @param args - Arguments to pass to initializer + * @see lazyInitialize Caveats */ -export function decorate(func: Function, ...args: any[]): MethodDecorator; +export function lazyInitialize(initializer?: Function, args?: any[]); /** * Prevents a property initializer from running until the decorated property is actually looked up. - * Useful to prevent excess allocations that might otherwise not be used, but be careful not to over-optimize things. + * _Note: If you're not using Babel, you must pass initilizer function as a decorator parameter or it will not work._ + * + * Usage: + * Babel - @lazyInitialize myParam = myInitializer() + * TypeScript - @lazyInitialize(myInitializer) myParam:myType + * @param initializer - Value initializer method + * @param args - Arguments to pass to initializer + * @see lazyInitialize Caveats */ -export const lazyInitialize: PropertyDecorator; +export function lazyInitialize (target: Object, propertyKey: PropertyKey): void; + + /** * Uses console.time and console.timeEnd to provide function timings with a unique label whose default prefix is ClassName.method. Supply a first argument to override the prefix: + * @param label - Override the preifx + * @param Console - Custom console object + */ +export function time (label?: string, console?: Console): MethodDecorator; +/** + * Uses console.time and console.timeEnd to provide function timings with a unique label whose default prefix is ClassName.method. Supply a first argument to override the prefix: + * @param label - Override the preifx + * @param Console - Custom console object + */ +export function time (target: Object, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void; + + +/** + * Marks a property or method as being enumerable + */ +export const enumerable: PropertyOrMethodDecorator; + + +/** + * (Deprecated) Creates a new debounced function which will be invoked after wait milliseconds since the time it was invoked. Default timeout is 300 ms. + * @param wait - Wait time in ms + * @param triggerOnLeadingq - Trigger function on the leading instead of the trailing edge of the wait interval + */ +export function debounce (wait: number, triggerOnLeading?: boolean): MethodDecorator; +/** + * (Deprecated) Creates a new debounced function which will be invoked after wait milliseconds since the time it was invoked. Default timeout is 300 ms. + * @param wait - Wait time in ms + * @param triggerOnLeadingq - Trigger function on the leading instead of the trailing edge of the wait interval + */ +export function debounce (target: Object, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void + + +/** + * (Deprecated) Creates a new throttled function which will be invoked in every milliseconds. Default timeout is 300 ms. + * @param wait [default=300] - Interval in ms + * @param options.leading [default=true] - Trigger function on the leading. + * @param options.trailing [default=true] - Trigger function on the trailing edge of the wait interval. + */ +export function throttle (wait?: number, options?: {leading?: boolean, trailing?: boolean} ): MethodDecorator; +/** + * (Deprecated) Creates a new throttled function which will be invoked in every milliseconds. Default timeout is 300 ms. + * @param wait - Interval in ms + * @param options.leading - Trigger function on the leading. + * @param options.trailing - Trigger function on the trailing edge of the wait interval. + */ +export function throttle (target: Object, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void + + +/** + * (Deprecated) Mixes in all property descriptors from the provided Plain Old JavaScript Objects (aka POJOs) as arguments. + * Mixins are applied in the order they are passed, but do not override descriptors already on the class, including those inherited traditionally. + */ +export function mixin (...mixins: object[]): ClassDecorator; +/** + * (Deprecated) Mixes in all property descriptors from the provided Plain Old JavaScript Objects (aka POJOs) as arguments. + * Mixins are applied in the order they are passed, but do not override descriptors already on the class, including those inherited traditionally. + */ +export function mixins (...mixins: object[]): ClassDecorator; + + +/** + * Extends the new property descriptor with the descriptor from the super/parent class prototype. + */ +export const extendDescriptor: PropertyOrMethodDecorator; + + +/** + * Uses console.profile and console.profileEnd to provide function profiling with a unique label whose default prefix is ClassName.method + * @param prefix - Override the prefix + * @param runOnce - Because profiling is expensive, set to true to prevent running every time the function is called. + */ +export function profile (prefix?: string | null, runOnce?: boolean): MethodDecorator; +/** + * Uses console.profile and console.profileEnd to provide function profiling with a unique label whose default prefix is ClassName.method + * @param prefix - Override the prefix + * @param msBetweenProfiles - Delay in milliseconds between profiles. Profiling is always ran on the leading edge. + */ +export function profile (prefix?: string | null, msBetweenProfiles?: number): MethodDecorator; +/** + * Uses console.profile and console.profileEnd to provide function profiling with a unique label whose default prefix is ClassName.method + * @param prefix - Override the prefix + * @param shouldProfileCallback - A function that returns a boolean to determine if profiling should occur. The function will have this context of the instance and the arguments to the method will be passed to the function as well. Arrow functions will not receive the instance context. + */ +export function profile (prefix?: string | null, shouldProfileCallback?: (...args:any[]) => boolean): MethodDecorator; +/** + * Uses console.profile and console.profileEnd to provide function profiling with a unique label whose default prefix is ClassName.method + */ +export function profile (target: Object, propertyKey: PropertyKey, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void; + + +/** + * The applyDecorators() helper can be used when you don't have language support for decorators like in Babel 6 or even with vanilla ES5 code without a transpiler. */ -export function time(label: string, console?: Console): MethodDecorator; +export const applyDecorators: (Class: Type | Function, props: Record) => any; diff --git a/package.json b/package.json index a80d0f7..d50edfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "core-decorators", - "version": "0.20.0", + "version": "0.21.0", "description": "Library of JavaScript stage-0 decorators (aka ES2016/ES7 decorators but that's not accurate!) inspired by languages that come with built-ins like @​override, @​deprecate, @​autobind, @​mixin and more! Works great with React/Angular/more!", "main": "lib/core-decorators.js", "module": "es/core-decorators.js", @@ -21,7 +21,9 @@ "prebuild-tsc": "rimraf built", "build-tsc": "tsc --outDir lib", "clean": "rimraf lib es built", - "test": "mocha --compilers js:babel-core/register --require babel-polyfill \"test/**/*.spec.js\"" + "test:babel": "cross-env TEST_MODE=babel mocha --require babel-core/register --require babel-polyfill \"test/**/*.spec.js\"", + "test:ts": "cross-env TEST_MODE=typescript mocha --require ts-node/register \"test/**/*.spec.js\"", + "test": "npm run test:babel && npm run test:ts" }, "repository": { "type": "git", @@ -55,6 +57,7 @@ "@types/chai": "^3.5.2", "@types/mocha": "^2.2.41", "@types/node": "^6.0.73", + "@types/sinon": "^7.0.12", "babel-cli": "^6.24.0", "babel-core": "^6.24.0", "babel-plugin-transform-class-properties": "^6.23.0", @@ -66,15 +69,16 @@ "babel-preset-es2015": "^6.24.0", "camelcase": "^4.1.0", "chai": "^3.5.0", - "cross-env": "^5.0.0", + "cross-env": "^5.2.0", "glob": "^7.1.1", "global-wrap": "^2.0.0", "interop-require": "1.0.0", - "lodash": "4.17.5", - "mocha": "^3.2.0", + "lodash": "^4.17.11", + "mocha": "^6.1.4", "rimraf": "^2.6.1", - "sinon": "2.1.0", - "sinon-chai": "^2.9.0", - "typescript": "^2.3.3" + "sinon": "7.3.2", + "sinon-chai": "^3.3.0", + "ts-node": "^8.2.0", + "typescript": "^3.5.1" } } diff --git a/src/debounce.js b/src/debounce.js index 30db25f..b228794 100644 --- a/src/debounce.js +++ b/src/debounce.js @@ -1,4 +1,5 @@ -import { decorate, metaFor, internalDeprecation } from './private/utils'; +import { decorate, internalDeprecation } from './private/utils'; +import {metaFor} from "./private/meta"; const DEFAULT_TIMEOUT = 300; diff --git a/src/lazy-initialize.js b/src/lazy-initialize.js index 2f14dd4..6d890c1 100644 --- a/src/lazy-initialize.js +++ b/src/lazy-initialize.js @@ -1,20 +1,29 @@ import { decorate, createDefaultSetter } from './private/utils'; const { defineProperty } = Object; -function handleDescriptor(target, key, descriptor) { - const { configurable, enumerable, initializer, value } = descriptor; - return { +function handleDescriptor(target, key, descriptor, [fnInitializerOrNoWarn, ...arg]) { + let { configurable, enumerable, initializer, value } = descriptor; + + const noInitializerSupport = (initializer === undefined); + + // Process Decorator Argument + let fnInitializer, noWarn; + if ((typeof fnInitializerOrNoWarn === 'object') && (!!fnInitializerOrNoWarn.noWarn)) + noWarn = true; + else + fnInitializer = fnInitializerOrNoWarn; + + initializer = fnInitializer ? fnInitializer : initializer; + + const desc = { configurable, enumerable, get() { - // This happens if someone accesses the - // property directly on the prototype - if (this === target) { - return; - } + // This happens if someone accesses the property directly on the prototype + if (this === target) { return; } - const ret = initializer ? initializer.call(this) : value; + const ret = initializer ? initializer.call(this, ...arg) : value; defineProperty(this, key, { configurable, @@ -28,8 +37,32 @@ function handleDescriptor(target, key, descriptor) { set: createDefaultSetter(key) }; + + // Compensate for the following typescript issue: (as of typescript v3.5.1) + // - TypeScript compiler does not properly handle property 'initalizer', therefore it renders myProp = myInitializer() + // as this.myProp = myInitializer() in the class constructor + // + // + Workaround: pass initializer as decorator parameter - ie. @lazyInitialize(myInitializer) myVar:myType + if (noInitializerSupport) { + if (!fnInitializer && !noWarn) { + console.warn('Looks like your compiler does not support initializers!\n To ensure lazyInitialize functions properly - Instead of @lazyInitialze myProp = myInitializer() use: @lazyInitialize(myInitializer) myProp;'); + } + + // Allow compiler to set initial value + desc.set = function(val) { + Object.defineProperty(target, key, {...desc, set: createDefaultSetter(key) }); + value = val; + return val; + }; + + // If no value assigned, object needs to be initially created (supports fnInitializer) + if (fnInitializer && !value) Object.defineProperty(target, key, {...desc, configurable: true, enumerable: true}); + } + + return desc; } export default function lazyInitialize(...args) { return decorate(handleDescriptor, args); } + diff --git a/src/private/meta.js b/src/private/meta.js new file mode 100755 index 0000000..586660f --- /dev/null +++ b/src/private/meta.js @@ -0,0 +1,34 @@ +import lazyInitialize from "../lazy-initialize"; +const { defineProperty } = Object; + +class Meta { + @lazyInitialize({noWarn: true}) + debounceTimeoutIds = {}; + + @lazyInitialize({noWarn: true}) + throttleTimeoutIds = {}; + + @lazyInitialize({noWarn: true}) + throttlePreviousTimestamps = {}; + + @lazyInitialize({noWarn: true}) + throttleTrailingArgs = null; + + @lazyInitialize({noWarn: true}) + profileLastRan = null; +} + +const META_KEY = (typeof Symbol === 'function') + ? Symbol('__core_decorators__') + : '__core_decorators__'; + +export function metaFor(obj) { + if (obj.hasOwnProperty(META_KEY) === false) { + defineProperty(obj, META_KEY, { + // Defaults: NOT enumerable, configurable, or writable + value: new Meta() + }); + } + + return obj[META_KEY]; +} diff --git a/src/private/utils.js b/src/private/utils.js index bd9aca2..a241c85 100644 --- a/src/private/utils.js +++ b/src/private/utils.js @@ -1,7 +1,10 @@ -import lazyInitialize from '../lazy-initialize'; +const { getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols } = Object; -const { defineProperty, getOwnPropertyDescriptor, - getOwnPropertyNames, getOwnPropertySymbols } = Object; +const DEFAULT_DESCRIPTOR = { + writable: true, + enumerable: true, + configurable: true +}; export function isDescriptor(desc) { if (!desc || !desc.hasOwnProperty) { @@ -19,46 +22,48 @@ export function isDescriptor(desc) { return false; } -export function decorate(handleDescriptor, entryArgs) { - if (isDescriptor(entryArgs[entryArgs.length - 1])) { - return handleDescriptor(...entryArgs, []); - } else { - return function () { - return handleDescriptor(...Array.prototype.slice.call(arguments), entryArgs); - }; - } -} - -class Meta { - @lazyInitialize - debounceTimeoutIds = {}; - - @lazyInitialize - throttleTimeoutIds = {}; - - @lazyInitialize - throttlePreviousTimestamps = {}; - - @lazyInitialize - throttleTrailingArgs = null; +/** Check if decorator call is a factory */ +export const isDecoratorFactory = (arg) => (arg.length !== 3) || (typeof arg[0] !== 'object') || ((typeof arg[1] !== 'string') && (typeof arg[1] !== 'symbol')); - @lazyInitialize - profileLastRan = null; -} - -const META_KEY = (typeof Symbol === 'function') - ? Symbol('__core_decorators__') - : '__core_decorators__'; +export function decorate(handleDescriptor, entryArgs) { + function handle (handleDescriptor, target, key, descriptor, params = []) { + // Attempt to get new descriptor information from provided handleDescriptor callback + const provided_desc = handleDescriptor(target, key, (isDescriptor(descriptor) ? descriptor : DEFAULT_DESCRIPTOR), params); + descriptor = isDescriptor(provided_desc) ? provided_desc : DEFAULT_DESCRIPTOR; + + // Compensate for the following typescript issues/differences: (as of typescript v3.5.1) + // - For non-method Class properties, some descriptor properties (like enumerable) are overwritten at instance creation + // - Also, setting initial writable = false prevents constructor and ESnext class fields from assigning initial value. + if (!(target.hasOwnProperty(key))) { + // Copy provided descriptor + let final_descriptor = Object.assign({}, descriptor); + // Compensate for typescript compiler read only issue + delete descriptor['writable']; + + // Setup initial descriptor + descriptor = { + get: function () { + return undefined; + }, + set: function (val) { + // Set final property + Object.defineProperty(this, key, {value: val, ...final_descriptor}) + }, + ...descriptor + }; + } -export function metaFor(obj) { - if (obj.hasOwnProperty(META_KEY) === false) { - defineProperty(obj, META_KEY, { - // Defaults: NOT enumerable, configurable, or writable - value: new Meta() - }); + return descriptor; } - return obj[META_KEY]; + // Wrap if factory + if (isDecoratorFactory(entryArgs)) { + return function () { + return handle(handleDescriptor, ...Array.prototype.slice.call(arguments), entryArgs); + } + } else { + return handle(handleDescriptor, ...entryArgs, []); + } } export const getOwnKeys = getOwnPropertySymbols diff --git a/src/profile.js b/src/profile.js index a2952b3..2dee806 100644 --- a/src/profile.js +++ b/src/profile.js @@ -1,4 +1,5 @@ -import { decorate, metaFor, warn, bind } from './private/utils'; +import { decorate, warn, bind } from './private/utils'; +import {metaFor} from "./private/meta"; const oc = console; diff --git a/src/throttle.js b/src/throttle.js index 1dc93d8..fcb7953 100644 --- a/src/throttle.js +++ b/src/throttle.js @@ -1,4 +1,5 @@ -import { decorate, metaFor, internalDeprecation } from './private/utils'; +import { decorate, internalDeprecation } from './private/utils'; +import {metaFor} from "./private/meta"; const DEFAULT_TIMEOUT = 300; diff --git a/test/exports.spec.js b/test/exports.spec.js index 240171e..02c3354 100644 --- a/test/exports.spec.js +++ b/test/exports.spec.js @@ -1,9 +1,9 @@ import * as chai from 'chai' import * as path from 'path'; import * as glob from 'glob'; -import toCamelCase from 'camelcase'; -import interopRequire from 'interop-require'; -import * as decorators from '../'; +const toCamelCase = require('camelcase'); +const interopRequire = require('interop-require'); +import * as decorators from '..'; const should = chai.should(); diff --git a/test/test.spec.js b/test/test.spec.js index ba6923e..3aed5fd 100644 --- a/test/test.spec.js +++ b/test/test.spec.js @@ -1,6 +1,6 @@ import * as chai from 'chai'; -import sinon from 'sinon'; -import sinonChai from 'sinon-chai'; +const sinon = require('sinon'); +const sinonChai = require('sinon-chai'); chai.should(); chai.use(sinonChai); diff --git a/test/unit/applyDecorators.spec.js b/test/unit/applyDecorators.spec.js index fe2f9e4..6c1d2d5 100644 --- a/test/unit/applyDecorators.spec.js +++ b/test/unit/applyDecorators.spec.js @@ -1,7 +1,4 @@ -import applyDecorators from '../../lib/applyDecorators'; -import autobind from '../../lib/autobind'; -import readonly from '../../lib/readonly'; -import enumerable from '../../lib/enumerable'; +import { applyDecorators, autobind, readonly, enumerable } from '../..'; describe('applyDecorators() helper', function () { class Foo { @@ -27,6 +24,7 @@ describe('applyDecorators() helper', function () { foo.second().should.equal(foo); (function () { + // @ts-ignore foo.second = 'I will error'; }).should.throw('Cannot assign to read only property \'second\' of object \'#\''); diff --git a/test/unit/autobind.spec.js b/test/unit/autobind.spec.js index 392c400..85a0a4d 100644 --- a/test/unit/autobind.spec.js +++ b/test/unit/autobind.spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import autobind from '../../lib/autobind'; +import { autobind } from '../..'; const root = (typeof window !== 'undefined') ? window : global; @@ -10,7 +10,7 @@ describe('@autobind', function () { let barCount; beforeEach(function () { - Foo = class Foo { + class CFoo { @autobind getFoo() { return this; @@ -25,10 +25,11 @@ describe('@autobind', function () { return this; } } + Foo = CFoo; barCount = 0; - Bar = class Bar extends Foo { + class CBar extends Foo { @autobind() getFoo() { const foo = super.getFoo(); @@ -49,13 +50,15 @@ describe('@autobind', function () { return this; } } + Bar = CBar; - Car = class Car extends Foo { + class CCar extends Foo { @autobind() getCarFromFoo() { return super.onlyOnFoo(); } } + Car = CCar }); afterEach(function () { @@ -151,7 +154,9 @@ describe('@autobind', function () { }); it('throws when it needs WeakMap but it is not available', function () { + // @ts-ignore const WeakMap = root.WeakMap; + // @ts-ignore delete root.WeakMap; const bar = new Bar(); @@ -163,6 +168,7 @@ describe('@autobind', function () { barCount.should.equal(0); + // @ts-ignore root.WeakMap = WeakMap; }); @@ -202,6 +208,7 @@ describe('@autobind', function () { @autobind class Car extends Vehicle { + name; constructor() { super(); this.name = 'amazing'; @@ -248,6 +255,7 @@ describe('@autobind', function () { it('correctly binds with multiple class prototype levels', function () { @autobind class A { + test; method() { return this.test || 'WRONG ONE'; } diff --git a/test/unit/debounce.spec.js b/test/unit/debounce.spec.js index 099f911..67c4675 100644 --- a/test/unit/debounce.spec.js +++ b/test/unit/debounce.spec.js @@ -1,5 +1,11 @@ import { useFakeTimers } from 'sinon'; -import debounce from '../../lib/debounce'; +import { debounce } from '../..'; + +import * as chai from 'chai'; +const sinonChai = require('sinon-chai'); + +chai.should(); +chai.use(sinonChai); class Editor { counter = 0; diff --git a/test/unit/decorate.spec.js b/test/unit/decorate.spec.js index dfa9f54..40b6527 100644 --- a/test/unit/decorate.spec.js +++ b/test/unit/decorate.spec.js @@ -1,4 +1,4 @@ -import decorate from '../../lib/decorate'; +import { decorate } from '../..'; import { memoize } from 'lodash'; describe('@decorate', function () { @@ -14,7 +14,7 @@ describe('@decorate', function () { callCount = 0; - Foo = class Foo { + class CFoo { @decorate(append, '!') suchWow(something) { return something + 'bro'; @@ -39,6 +39,7 @@ describe('@decorate', function () { return this; } } + Foo = CFoo; }); it('correctly applies user provided function to method', function () { diff --git a/test/unit/deprecate.spec.js b/test/unit/deprecate.spec.js index 515917f..c73e2d7 100644 --- a/test/unit/deprecate.spec.js +++ b/test/unit/deprecate.spec.js @@ -1,5 +1,5 @@ -import sinon from 'sinon'; -import deprecate from '../../lib/deprecate'; +import * as sinon from 'sinon'; +import { deprecate } from '../..'; import * as utils from '../../lib/private/utils'; class Foo { diff --git a/test/unit/enumerable.spec.js b/test/unit/enumerable.spec.js index c6136a3..e83960a 100644 --- a/test/unit/enumerable.spec.js +++ b/test/unit/enumerable.spec.js @@ -1,4 +1,4 @@ -import enumerable from '../../lib/enumerable'; +import { enumerable } from '../..' describe('@enumerable', function () { class Foo { diff --git a/test/unit/extendDescriptor.js b/test/unit/extendDescriptor.js index 16e3042..bfd54b3 100644 --- a/test/unit/extendDescriptor.js +++ b/test/unit/extendDescriptor.js @@ -1,6 +1,4 @@ -import extendDescriptor from '../../lib/extendDescriptor'; -import enumerable from '../../lib/enumerable'; -import nonenumerable from '../../lib/nonenumerable'; +import { extendDescriptor, enumerable, nonenumerable } from '../..' describe('@extendDescriptor', function () { class Base { diff --git a/test/unit/lazy-initialize.spec.js b/test/unit/lazy-initialize.spec.js index ad4a00d..ae1ba34 100644 --- a/test/unit/lazy-initialize.spec.js +++ b/test/unit/lazy-initialize.spec.js @@ -1,29 +1,58 @@ -import { expect } from 'chai'; -import { spy } from 'sinon'; -import lazyInitialize from '../../lib/lazy-initialize'; +import { lazyInitialize } from '../..' -describe('@lazyInitialize', function () { - let initializer; +const chai = require("chai"); +const sinon = require("sinon"); +const sinonChai = require("sinon-chai"); +const { expect } = chai; +const { spy } = sinon; + +chai.should(); +chai.use(sinonChai); - class Foo { - @lazyInitialize - bar = initializer(); - } + +describe('@lazyInitialize', function () { + let initializer, initializer2, Foo; beforeEach(function () { - initializer = spy(() => 'test'); + initializer = spy(() => 'test' ); + initializer2 = spy(() => 'test' ); + + if (process.env.TEST_MODE === 'babel') { + class CFoo { + @lazyInitialize + bar = initializer(); + + @lazyInitialize(initializer2,1,2,3) + bar2 = {}; + } + Foo = CFoo; + } else { + class TSFoo { + @lazyInitialize(initializer) + bar; + + @lazyInitialize(initializer2,1,2,3) + bar2; + } + Foo = TSFoo; + } }); afterEach(function () { initializer = null; + initializer2 = null; }); it('does not initialize property until it the getter is called', function () { const foo = new Foo(); initializer.should.not.have.been.called; + initializer2.should.not.have.been.called; foo.bar.should.equal('test'); foo.bar.should.equal('test'); + foo.bar2.should.equal('test'); + foo.bar2.should.equal('test'); initializer.should.have.been.called.once; + expect(initializer2.calledOnceWithExactly(1,2,3)).to.be.true; }); it('allows normal reassignment', function () { diff --git a/test/unit/memoize.spec.js b/test/unit/memoize.spec.js index d34a3c7..ec3fdeb 100644 --- a/test/unit/memoize.spec.js +++ b/test/unit/memoize.spec.js @@ -1,17 +1,18 @@ import { stub } from 'sinon'; -import memoize from '../../lib/memoize'; +import { memoize } from '../..'; describe('@memoize', function () { var Foo, work, run; beforeEach(function () { work = null; - Foo = class Foo { + class CFoo { @memoize bar(...args){ return work(...args); } }; + Foo = CFoo; run = function (id, shouldCall, shouldReturn, args) { var inst = new Foo(); diff --git a/test/unit/mixin.spec.js b/test/unit/mixin.spec.js index 3f40e5f..e58199c 100644 --- a/test/unit/mixin.spec.js +++ b/test/unit/mixin.spec.js @@ -1,4 +1,4 @@ -import mixin from '../../lib/mixin'; +import { mixin } from '../..'; const BarMixin = { stuff1: 'stuff1', @@ -48,7 +48,9 @@ describe('@mixin', function () { it('correctly adds a single mixin\'s descriptors', function () { const foo = new (applyMixins(BarMixin)); + // @ts-ignore foo.stuff1.should.equal('stuff1'); + // @ts-ignore foo.getStuff2().should.equal('stuff2'); foo.getStuff3().should.equal('stuff3'); foo.getStuff4().should.equal('stuff4'); @@ -57,10 +59,13 @@ describe('@mixin', function () { it('correctly adds multiple mixins descriptors', function () { const foo = new (applyMixins(BarMixin, OverrideMixin)); + // @ts-ignore foo.stuff1.should.equal('stuff1'); + // @ts-ignore foo.getStuff2().should.equal('stuff2'); foo.getStuff3().should.equal('stuff3'); foo.getStuff4().should.equal('stuff4'); + // @ts-ignore foo.stuff5.should.equal('stuff5'); }); diff --git a/test/unit/nonconfigurable.spec.js b/test/unit/nonconfigurable.spec.js index db4df91..b8c3c5f 100644 --- a/test/unit/nonconfigurable.spec.js +++ b/test/unit/nonconfigurable.spec.js @@ -1,5 +1,4 @@ -import nonconfigurable from '../../lib/nonconfigurable'; -import readonly from '../../lib/readonly'; +import { readonly, nonconfigurable } from '../..'; describe('@nonconfigurable', function () { class Foo { diff --git a/test/unit/nonenumerable.spec.js b/test/unit/nonenumerable.spec.js index af3b750..5dfb035 100644 --- a/test/unit/nonenumerable.spec.js +++ b/test/unit/nonenumerable.spec.js @@ -1,4 +1,4 @@ -import nonenumerable from '../../lib/nonenumerable'; +import { nonenumerable } from '../..'; describe('@nonenumerable', function () { class Foo { diff --git a/test/unit/override.spec.js b/test/unit/override.spec.js index aaae527..13ab725 100644 --- a/test/unit/override.spec.js +++ b/test/unit/override.spec.js @@ -1,4 +1,4 @@ -import override from '../../lib/override'; +import { override } from '../..'; class Parent { speak(first, second) {} @@ -11,7 +11,7 @@ describe('@override', function () { @override speak() {} } - }).should.throw('Child#speak() does not properly override Parent#speak(first, second)'); + }).should.throw(/Child#speak\(\)( { })? does not properly override Parent#speak\(first, second\)/); }); it('throws error when no is matching name is found', function () { @@ -20,7 +20,7 @@ describe('@override', function () { @override wow() {} } - }).should.throw('No descriptor matching Child#wow() was found on the prototype chain.'); + }).should.throw(/No descriptor matching Child#wow\(\)( { })? was found on the prototype chain./); }); it('throws error when no is matching name is found but suggests a closely named method if exists', function () { @@ -29,7 +29,7 @@ describe('@override', function () { @override speaks() {} } - }).should.throw('No descriptor matching Child#speaks() was found on the prototype chain.\n\n Did you mean "speak"?'); + }).should.throw(/No descriptor matching Child#speaks()\(\)( { })? was found on the prototype chain\.\n\n {2}Did you mean "speak"\?/); }); it('does not throw an error when signatures match', function () { diff --git a/test/unit/profile.spec.js b/test/unit/profile.spec.js index 8c461fe..93451fb 100644 --- a/test/unit/profile.spec.js +++ b/test/unit/profile.spec.js @@ -1,6 +1,6 @@ import { spy, useFakeTimers } from 'sinon'; -import applyDecorators from '../../lib/applyDecorators'; -import profile, { defaultConsole } from '../../lib/profile'; +import { applyDecorators, profile } from '../..'; +import { defaultConsole } from '../../lib/profile'; const CONSOLE_PROFILE = defaultConsole.profile; const CONSOLE_PROFILEEND = defaultConsole.profileEnd; @@ -196,6 +196,7 @@ describe('@profile', function() { profileSpy.calledOnce.should.equal(false); cbSpy.calledOnce.should.equal(true); + // @ts-ignore foo.isAwesome = true; foo.profileFunctioned(cbSpy); diff --git a/test/unit/readonly.spec.js b/test/unit/readonly.spec.js index 36e5c18..ae92778 100644 --- a/test/unit/readonly.spec.js +++ b/test/unit/readonly.spec.js @@ -1,4 +1,4 @@ -import readonly from '../../lib/readonly'; +import { readonly } from '../..'; describe('@readonly', function () { class Foo { @@ -18,6 +18,7 @@ describe('@readonly', function () { const foo = new Foo(); (function () { + // @ts-ignore foo.first = 'I will error'; }).should.throw('Cannot assign to read only property \'first\' of object \'#\''); diff --git a/test/unit/suppress-warnings.spec.js b/test/unit/suppress-warnings.spec.js index aebf36a..e05edbc 100644 --- a/test/unit/suppress-warnings.spec.js +++ b/test/unit/suppress-warnings.spec.js @@ -1,5 +1,5 @@ import { spy } from 'sinon'; -import suppressWarnings from '../../lib/suppress-warnings'; +import { suppressWarnings } from '../..'; const CONSOLE_WARN = console.warn; diff --git a/test/unit/throttle.spec.js b/test/unit/throttle.spec.js index bb4e8cc..cc618ff 100644 --- a/test/unit/throttle.spec.js +++ b/test/unit/throttle.spec.js @@ -1,5 +1,5 @@ import { useFakeTimers } from 'sinon'; -import throttle from '../../lib/throttle'; +import { throttle } from '../..'; const defaultValue = {}; diff --git a/test/unit/time.spec.js b/test/unit/time.spec.js index c8a55dd..0c4b9c0 100644 --- a/test/unit/time.spec.js +++ b/test/unit/time.spec.js @@ -1,5 +1,6 @@ import { spy } from 'sinon'; -import time, { defaultConsole } from '../../lib/time'; +import { time } from '../..'; +import { defaultConsole } from '../../lib/time'; const CONSOLE_TIME = defaultConsole.time; const CONSOLE_TIMEEND = defaultConsole.timeEnd; diff --git a/tsconfig.json b/tsconfig.json index f03ecf5..5511e5c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,11 @@ { "include": ["src"], - "exclude": ["scripts", "lib/**", "es", "node_modules", "built" ], + "exclude": ["scripts", "lib/**", "es", "node_modules", "built", "test" ], "compilerOptions": { "outDir": "built", - "target": "es5", + "target": "es2015", "module":"commonjs", - "sourceMap": true, + "inlineSourceMap": true, "allowJs": true, "checkJs": false, "allowUnreachableCode": true,