From 2f7200b06fe7ee639fa2864046c84964bd5ca90c Mon Sep 17 00:00:00 2001 From: jan-molak Date: Thu, 1 Mar 2018 17:24:11 +0000 Subject: [PATCH] feat(predicates): Check has been deprecated in favour of Ensure. New "deprecated" decorator can be u --- package.json | 4 + spec/{chec.spec.ts => ensure.ts} | 8 +- spec/expect.ts | 3 + spec/objects/deprecated.spec.ts | 151 ++++++++++++++++++ spec/predicates/and.spec.ts | 4 +- spec/predicates/hasLengthOf.spec.ts | 6 +- spec/predicates/isArray.spec.ts | 4 +- spec/predicates/isDefined.spec.ts | 4 +- spec/predicates/isEqualTo.spec.ts | 8 +- spec/predicates/isGreaterThan.spec.ts | 4 +- .../predicates/isGreaterThanOrEqualTo.spec.ts | 4 +- spec/predicates/isInRange.spec.ts | 4 +- spec/predicates/isInteger.spec.ts | 4 +- spec/predicates/isLessThan.spec.ts | 4 +- spec/predicates/isLessThanOrEqual.spec.ts | 4 +- spec/predicates/isOneOf.spec.ts | 4 +- spec/predicates/or.spec.ts | 6 +- src/TinyType.ts | 4 +- src/check.ts | 37 +---- src/ensure.ts | 35 ++++ src/index.ts | 2 + src/objects/deprecated.ts | 50 ++++++ src/objects/index.ts | 1 + src/predicates/and.ts | 2 +- src/predicates/hasLengthOf.ts | 2 +- src/predicates/isArray.ts | 2 +- src/predicates/isDefined.ts | 2 +- src/predicates/isEqualTo.ts | 2 +- src/predicates/isGreaterThan.ts | 2 +- src/predicates/isGreaterThanOrEqualTo.ts | 2 +- src/predicates/isInRange.ts | 2 +- src/predicates/isInteger.ts | 2 +- src/predicates/isLessThan.ts | 2 +- src/predicates/isLessThanOrEqualTo.ts | 2 +- src/predicates/isOneOf.ts | 2 +- src/predicates/or.ts | 2 +- src/types/constructors.ts | 3 + tsconfig.json | 2 + 38 files changed, 305 insertions(+), 81 deletions(-) rename spec/{chec.spec.ts => ensure.ts} (60%) create mode 100644 spec/objects/deprecated.spec.ts create mode 100644 src/ensure.ts create mode 100644 src/objects/deprecated.ts diff --git a/package.json b/package.json index 50178c7d..8c7aad93 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,8 @@ "@types/chai": "4.1.2", "@types/mocha": "2.2.48", "@types/node": "9.4.0", + "@types/sinon": "4.1.3", + "@types/sinon-chai": "2.7.29", "chai": "4.1.2", "cheerio": "1.0.0-rc.2", "commitizen": "2.9.6", @@ -58,6 +60,8 @@ "rimraf": "2.6.2", "semantic-release": "^12.4.1", "semantic-release-cli": "3.6.2", + "sinon": "4.4.2", + "sinon-chai": "2.14.0", "travis-deploy-once": "^4.3.3", "ts-node": "5.0.0", "tslint": "5.9.1", diff --git a/spec/chec.spec.ts b/spec/ensure.ts similarity index 60% rename from spec/chec.spec.ts rename to spec/ensure.ts index 9f24715c..90d262c6 100644 --- a/spec/chec.spec.ts +++ b/spec/ensure.ts @@ -1,15 +1,15 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, hasLengthOf, isArray, isDefined, isGreaterThan, isInteger, TinyType } from '../src'; +import { ensure, hasLengthOf, isArray, isDefined, isGreaterThan, isInteger, TinyType } from '../src'; import { expect } from './expect'; -/** @test {check} */ -describe('::check', () => { +/** @test {ensure} */ +describe('::ensure', () => { it(`advises the developer if they've forgotten to specify the checks`, () => { const value = 2; - expect(() => check('SomeProperty', value)) + expect(() => ensure('SomeProperty', value)) .to.throw(`Looks like you haven't specified any predicates to check the value against?`); }); }); diff --git a/spec/expect.ts b/spec/expect.ts index 36a17d1b..f446469a 100644 --- a/spec/expect.ts +++ b/spec/expect.ts @@ -1,3 +1,6 @@ import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; + +chai.use(sinonChai); export const expect = chai.expect; diff --git a/spec/objects/deprecated.spec.ts b/spec/objects/deprecated.spec.ts new file mode 100644 index 00000000..30b2c11c --- /dev/null +++ b/spec/objects/deprecated.spec.ts @@ -0,0 +1,151 @@ +import 'mocha'; +import * as sinon from 'sinon'; +import { deprecated } from '../../src/objects'; +import { expect } from '../expect'; + +/** @test {deprecated} */ +describe('deprecated', () => { + + describe('when used to annotate a class', () => { + it('logs a warning when the class is constructed', () => { + const consoleWarn = sinon.spy(); + + @deprecated(undefined, consoleWarn) + class Foo { + } + + const foo = new Foo(); + + expect(consoleWarn) + .to.have.been.calledWith('Foo has been deprecated.'); + }); + + it('can provide additional suggestion on what other class should be used instead', () => { + const consoleWarn = sinon.spy(); + + @deprecated('Please use Bar instead.', consoleWarn) + class Foo { + } + + const foo = new Foo(); + + expect(consoleWarn) + .to.have.been.calledWith('Foo has been deprecated. Please use Bar instead.'); + }); + + it('maintains the type and behaviour of the annotated class', () => { + const consoleWarn = sinon.spy(); + + @deprecated('Please use Client instead.', consoleWarn) + class Person { + constructor(public readonly name: string) { + } + } + + const p = new Person('Alice'); + + expect(consoleWarn) + .to.have.been.calledWith('Person has been deprecated. Please use Client instead.'); + + expect(p).to.be.instanceOf(Person); + }); + }); + + describe('when used to annotate a method', () => { + + it('logs a warning when the method is used', () => { + const consoleWarn = sinon.spy(); + + class Person { + @deprecated('', consoleWarn) + greet() { return null; } + welcome() { return null; } + } + + const p = new Person(); + p.greet(); + + expect(consoleWarn) + .to.have.been.calledWith('Person#greet has been deprecated.'); + }); + + it('can provide additional suggestion on what other method should be used instead', () => { + const consoleWarn = sinon.spy(); + + class Person { + + @deprecated('Please use Person#welcome instead.', consoleWarn) + greet() { return null; } + welcome() { return null; } + } + + const p = new Person(); + p.greet(); + + expect(consoleWarn) + .to.have.been.calledWith('Person#greet has been deprecated. Please use Person#welcome instead.'); + }); + + it('maintains the behaviour of the annotated method', () => { + const consoleWarn = sinon.spy(); + + class Person { + constructor(public readonly name: string) { + } + + @deprecated('Please use Person#welcome instead.', consoleWarn) + greet(greeting: string) { + return `${ greeting }, my name is ${this.name}`; + } + } + + const p = new Person('Alice'); + + expect(p.greet('Hi')).to.equal('Hi, my name is Alice'); + + expect(consoleWarn) + .to.have.been.calledWith('Person#greet has been deprecated. Please use Person#welcome instead.'); + }); + }); + + describe('when used to deprecate a function', () => { + it('logs a warning when the function is used', () => { + const consoleWarn = sinon.spy(); + + function foo() { + return null; + } + + const deprecatedFoo = deprecated('Please use bar instead.', consoleWarn)(foo); + + deprecatedFoo(); + + expect(consoleWarn) + .to.have.been.calledWith('foo has been deprecated. Please use bar instead.'); + + }); + + it('logs a warning when an arrow function is used', () => { + const consoleWarn = sinon.spy(); + + const foo = () => null; + + const deprecatedFoo = deprecated('Please use bar instead.', consoleWarn)(foo); + + deprecatedFoo(); + + expect(consoleWarn) + .to.have.been.calledWith('foo has been deprecated. Please use bar instead.'); + }); + }); + + describe('when used incorrectly', () => { + + it('complains', () => { + const consoleWarn = sinon.spy(); + + expect(() => deprecated('something that does not make sense')(42)) + .to.throw(`Only a class, method or function can be marked as deprecated. number given.`); + }); + }) +}); diff --git a/spec/predicates/and.spec.ts b/spec/predicates/and.spec.ts index d7b84daf..feeb2d4d 100644 --- a/spec/predicates/and.spec.ts +++ b/spec/predicates/and.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { and, check, isDefined, isGreaterThan, isInteger, isLessThan, or, TinyType } from '../../src'; +import { and, ensure, isDefined, isGreaterThan, isInteger, isLessThan, or, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -12,7 +12,7 @@ describe('predicates', () => { class InvestmentLengthInYears extends TinyType { constructor(public readonly value: number) { super(); - check('InvestmentLengthInYears', value, and( + ensure('InvestmentLengthInYears', value, and( isDefined(), isInteger(), isGreaterThan(0), diff --git a/spec/predicates/hasLengthOf.spec.ts b/spec/predicates/hasLengthOf.spec.ts index f942e9e7..ea49cf8d 100644 --- a/spec/predicates/hasLengthOf.spec.ts +++ b/spec/predicates/hasLengthOf.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, hasLengthOf, TinyType } from '../../src'; +import { ensure, hasLengthOf, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -15,7 +15,7 @@ describe('predicates', () => { constructor(public readonly value: string) { super(); - check('Password', value, hasLengthOf(8)); + ensure('Password', value, hasLengthOf(8)); } } @@ -46,7 +46,7 @@ describe('predicates', () => { constructor(public readonly values: string[]) { super(); - check('Collection', values, hasLengthOf(2)); + ensure('Collection', values, hasLengthOf(2)); } } diff --git a/spec/predicates/isArray.spec.ts b/spec/predicates/isArray.spec.ts index 07275d79..62bb0027 100644 --- a/spec/predicates/isArray.spec.ts +++ b/spec/predicates/isArray.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isArray, TinyType } from '../../src'; +import { ensure, isArray, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -13,7 +13,7 @@ describe('predicates', () => { constructor(public readonly values: string[]) { super(); - check('Collection', values, isArray()); + ensure('Collection', values, isArray()); } } diff --git a/spec/predicates/isDefined.spec.ts b/spec/predicates/isDefined.spec.ts index 88e426d2..ef5f5544 100644 --- a/spec/predicates/isDefined.spec.ts +++ b/spec/predicates/isDefined.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isDefined, TinyType } from '../../src'; +import { ensure, isDefined, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -12,7 +12,7 @@ describe('predicates', () => { constructor(public readonly value: string) { super(); - check('UserName', value, isDefined()); + ensure('UserName', value, isDefined()); } } diff --git a/spec/predicates/isEqualTo.spec.ts b/spec/predicates/isEqualTo.spec.ts index 38d19083..f3e0f242 100644 --- a/spec/predicates/isEqualTo.spec.ts +++ b/spec/predicates/isEqualTo.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isEqualTo, TinyTypeOf } from '../../src'; +import { ensure, isEqualTo, TinyTypeOf } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -19,7 +19,7 @@ describe('predicates', () => { class AccountsService { constructor(public readonly loggedInUser: AccountId) {} handle(command: Command) { - check('AccountId', command.value, isEqualTo(this.loggedInUser)); + ensure('AccountId', command.value, isEqualTo(this.loggedInUser)); } } @@ -58,7 +58,7 @@ describe('predicates', () => { [], ). it('ensures they are equal', (value: any) => { - expect(check('Val', value, isEqualTo(value))).to.not.throw; // tslint:disable-line:no-unused-expression + expect(ensure('Val', value, isEqualTo(value))).to.not.throw; // tslint:disable-line:no-unused-expression }); given( @@ -69,7 +69,7 @@ describe('predicates', () => { [], ). it('complains if they are not equal', (value: any) => { - expect(() => check('Value', value, isEqualTo('expected value'))) + expect(() => ensure('Value', value, isEqualTo('expected value'))) .to.throw('Value should be equal to expected value'); }); }); diff --git a/spec/predicates/isGreaterThan.spec.ts b/spec/predicates/isGreaterThan.spec.ts index 4c96c783..33190236 100644 --- a/spec/predicates/isGreaterThan.spec.ts +++ b/spec/predicates/isGreaterThan.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isGreaterThan, TinyType } from '../../src'; +import { ensure, isGreaterThan, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -12,7 +12,7 @@ describe('predicates', () => { constructor(public readonly value: number) { super(); - check('InvestmentLength', value, isGreaterThan(0)); + ensure('InvestmentLength', value, isGreaterThan(0)); } } diff --git a/spec/predicates/isGreaterThanOrEqualTo.spec.ts b/spec/predicates/isGreaterThanOrEqualTo.spec.ts index 34a5953f..15e91d33 100644 --- a/spec/predicates/isGreaterThanOrEqualTo.spec.ts +++ b/spec/predicates/isGreaterThanOrEqualTo.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isGreaterThanOrEqualTo, TinyType } from '../../src'; +import { ensure, isGreaterThanOrEqualTo, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -12,7 +12,7 @@ describe('predicates', () => { constructor(public readonly value: number) { super(); - check('InvestmentLength', value, isGreaterThanOrEqualTo(0)); + ensure('InvestmentLength', value, isGreaterThanOrEqualTo(0)); } } diff --git a/spec/predicates/isInRange.spec.ts b/spec/predicates/isInRange.spec.ts index 4c189dd3..a9959b74 100644 --- a/spec/predicates/isInRange.spec.ts +++ b/spec/predicates/isInRange.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isInRange, TinyType } from '../../src'; +import { ensure, isInRange, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -13,7 +13,7 @@ describe('predicates', () => { constructor(public readonly value: number) { super(); - check('InvestmentLength', value, isInRange(1, 5)); + ensure('InvestmentLength', value, isInRange(1, 5)); } } diff --git a/spec/predicates/isInteger.spec.ts b/spec/predicates/isInteger.spec.ts index 6e4a9d49..21bed6a0 100644 --- a/spec/predicates/isInteger.spec.ts +++ b/spec/predicates/isInteger.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isInteger, TinyType } from '../../src'; +import { ensure, isInteger, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -12,7 +12,7 @@ describe('predicates', () => { constructor(public readonly value: number) { super(); - check('AgeInYears', value, isInteger()); + ensure('AgeInYears', value, isInteger()); } } diff --git a/spec/predicates/isLessThan.spec.ts b/spec/predicates/isLessThan.spec.ts index 80d5ddab..a8179507 100644 --- a/spec/predicates/isLessThan.spec.ts +++ b/spec/predicates/isLessThan.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isLessThan, TinyType } from '../../src'; +import { ensure, isLessThan, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -12,7 +12,7 @@ describe('predicates', () => { constructor(public readonly value: number) { super(); - check('InvestmentLength', value, isLessThan(50)); + ensure('InvestmentLength', value, isLessThan(50)); } } diff --git a/spec/predicates/isLessThanOrEqual.spec.ts b/spec/predicates/isLessThanOrEqual.spec.ts index 550947f1..19a4c7fb 100644 --- a/spec/predicates/isLessThanOrEqual.spec.ts +++ b/spec/predicates/isLessThanOrEqual.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isLessThanOrEqualTo, TinyType } from '../../src'; +import { ensure, isLessThanOrEqualTo, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -12,7 +12,7 @@ describe('predicates', () => { constructor(public readonly value: number) { super(); - check('InvestmentLength', value, isLessThanOrEqualTo(50)); + ensure('InvestmentLength', value, isLessThanOrEqualTo(50)); } } diff --git a/spec/predicates/isOneOf.spec.ts b/spec/predicates/isOneOf.spec.ts index be8832aa..872e293c 100644 --- a/spec/predicates/isOneOf.spec.ts +++ b/spec/predicates/isOneOf.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isOneOf, TinyType } from '../../src'; +import { ensure, isOneOf, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -13,7 +13,7 @@ describe('predicates', () => { constructor(public readonly value: string) { super(); - check('StreetLight', value, isOneOf('red', 'yellow', 'green')); + ensure('StreetLight', value, isOneOf('red', 'yellow', 'green')); } } diff --git a/spec/predicates/or.spec.ts b/spec/predicates/or.spec.ts index ba78b0fd..bd2ab1a3 100644 --- a/spec/predicates/or.spec.ts +++ b/spec/predicates/or.spec.ts @@ -1,7 +1,7 @@ import 'mocha'; import { given } from 'mocha-testdata'; -import { check, isDefined, isEqualTo, isGreaterThan, isInteger, isLessThan, or, TinyType } from '../../src'; +import { ensure, isDefined, isEqualTo, isGreaterThan, isInteger, isLessThan, or, TinyType } from '../../src'; import { expect } from '../expect'; describe('predicates', () => { @@ -12,7 +12,7 @@ describe('predicates', () => { class Percentage extends TinyType { constructor(public readonly value: number) { super(); - check('Percentage', value, + ensure('Percentage', value, isDefined(), isInteger(), or(isEqualTo(0), isGreaterThan(0)), @@ -40,7 +40,7 @@ describe('predicates', () => { }); it('concatenates the error messages in a human-friendly way', () => { - expect(() => check('Project name', 'node.js', + expect(() => ensure('Project name', 'node.js', or(isEqualTo('Serenity/JS'), isEqualTo('TinyTypes'), isEqualTo('Build Monitor')), )).to.throw( 'Project name should either be equal to Serenity/JS, be equal to TinyTypes or be equal to Build Monitor', diff --git a/src/TinyType.ts b/src/TinyType.ts index c0aabb54..6a08a8dc 100644 --- a/src/TinyType.ts +++ b/src/TinyType.ts @@ -1,4 +1,4 @@ -import { check } from './check'; +import { ensure } from './ensure'; import { equal, significantFieldsOf } from './objects'; import { isDefined } from './predicates'; import { JSONObject, JSONValue, NonNullJSONPrimitive, Serialisable, Serialised } from './types'; @@ -23,7 +23,7 @@ export function TinyTypeOf(): { new(_: T): { value: T } & TinyType } { return class extends TinyType { constructor(public readonly value: T) { super(); - check(this.constructor.name, value, isDefined()); + ensure(this.constructor.name, value, isDefined()); } }; } diff --git a/src/check.ts b/src/check.ts index 84182280..78515fab 100644 --- a/src/check.ts +++ b/src/check.ts @@ -1,35 +1,8 @@ -import { and, Failure, isArray, isDefined, isGreaterThan, Predicate } from './predicates'; +import { ensure } from './ensure'; +import { deprecated } from './objects'; /** - * @desc The `check` function verifies if the value meets the specified {Predicate}s. - * - * @example Basic usage - * import { check, isDefined } from 'tiny-types' - * - * const username = 'jan-molak' - * check('Username', username, isDefined()); - * - * @example Ensuring validity of a domain object upon creation - * import { TinyType, check, isDefined, isInteger, isInRange } from 'tiny-types' - * - * class Age extends TinyType { - * constructor(public readonly value: number) { - * check('Age', value, isDefined(), isInteger(), isInRange(0, 125)); - * } - * } - * - * @param {string} name - the name of the value to check. - * This name will be included in the error message should the check fail - * @param {T} value - the argument to check - * @param {...Array>} predicates - a list of predicates to check the value against - * @returns {T} - if the original value passes all the predicates, it's returned from the function + * @desc This function has been deprecated. Please use {@link ensure} instead + * @deprecated */ -export function check(name: string, value: T, ...predicates: Array>): T { - const result = and(...predicates).check(value); - - if (result instanceof Failure) { - throw new Error(`${ name } should ${ result.description }`); - } - - return result.value; -} +export const check = deprecated('Please use `ensure` instead')(ensure); diff --git a/src/ensure.ts b/src/ensure.ts new file mode 100644 index 00000000..ddee3c6a --- /dev/null +++ b/src/ensure.ts @@ -0,0 +1,35 @@ +import { and, Failure, isArray, isDefined, isGreaterThan, Predicate } from './predicates'; + +/** + * @desc The `ensure` function verifies if the value meets the specified {Predicate}s. + * + * @example Basic usage + * import { check, isDefined } from 'tiny-types' + * + * const username = 'jan-molak' + * check('Username', username, isDefined()); + * + * @example Ensuring validity of a domain object upon creation + * import { TinyType, check, isDefined, isInteger, isInRange } from 'tiny-types' + * + * class Age extends TinyType { + * constructor(public readonly value: number) { + * check('Age', value, isDefined(), isInteger(), isInRange(0, 125)); + * } + * } + * + * @param {string} name - the name of the value to check. + * This name will be included in the error message should the check fail + * @param {T} value - the argument to check + * @param {...Array>} predicates - a list of predicates to check the value against + * @returns {T} - if the original value passes all the predicates, it's returned from the function + */ +export function ensure(name: string, value: T, ...predicates: Array>): T { + const result = and(...predicates).check(value); + + if (result instanceof Failure) { + throw new Error(`${ name } should ${ result.description }`); + } + + return result.value; +} diff --git a/src/index.ts b/src/index.ts index 97440f2a..ee09a86f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,6 @@ export * from './check'; +export * from './ensure'; +export * from './objects/deprecated'; export * from './predicates'; export * from './match'; export * from './pattern-matching/PatternMatcher'; diff --git a/src/objects/deprecated.ts b/src/objects/deprecated.ts new file mode 100644 index 00000000..a868f2a7 --- /dev/null +++ b/src/objects/deprecated.ts @@ -0,0 +1,50 @@ +import { Constructor } from '../types'; + +export type Logger = (_: string) => void; + +/** + * @desc A decorator to mark a class, method or function as deprecated and make it log a warning whenever it is used. + * Please see the tests for examples of usage. + * + * @param {string} message - describes the alternative implementation that should be used instead + * of the deprecated method/function/class + * @param {Logger} log - a function that handles the printing of the message, + * such as {@link console.warn} + */ +export function deprecated(message: string = '', log: Logger = console.warn): (target: any, propertyKey?: string, descriptor?: any) => any { + const hasPrototype = (target: { hasOwnProperty(_: string): boolean }): boolean => target.hasOwnProperty('prototype'); + + return (target: any, propertyKey?: string, descriptor?: any): any => { // tslint:disable-line:ban-types + if (target && propertyKey && descriptor) { + return deprecateMethod(message, target, propertyKey, descriptor, log); + } + else if (hasPrototype(target)) { + return deprecateClass(message, target, log); + } + else { + throw new Error(`Only a class, method or function can be marked as deprecated. ${typeof target} given.`); + } + }; +} + +function deprecateClass(message: string, target: Constructor, log: (...args: any[]) => void): Constructor { + return class extends target { + constructor(...args: any[]) { + log(`${target.name} has been deprecated. ${ message }`.trim()); + + super(...args); + } + }; +} + +function deprecateMethod(message: string, target: T, propertyKey: string, descriptor: any, log: (...args: any[]) => void) { + const originalMethod = descriptor.value; + + descriptor.value = function(...args: any[]) { + log(`${target.constructor.name}#${propertyKey} has been deprecated. ${ message }`.trim()); + + return originalMethod.apply(this, args); + }; + + return descriptor; +} diff --git a/src/objects/index.ts b/src/objects/index.ts index 5fb238f8..29f1e149 100644 --- a/src/objects/index.ts +++ b/src/objects/index.ts @@ -1,2 +1,3 @@ +export * from './deprecated'; export * from './equal'; export * from './significantFields'; diff --git a/src/predicates/and.ts b/src/predicates/and.ts index 7374d301..21a12418 100644 --- a/src/predicates/and.ts +++ b/src/predicates/and.ts @@ -4,7 +4,7 @@ import { isGreaterThan } from './isGreaterThan'; import { Failure, Predicate, Result, Success } from './Predicate'; /** - * @desc Checks if the `value` meets all the provided {@link Predicate}s. + * @desc Ensures that the `value` meets all the provided {@link Predicate}s. * * @example * import { and, check, isDefined, isGreaterThan, isInteger, TinyType } from 'tiny-types'; diff --git a/src/predicates/hasLengthOf.ts b/src/predicates/hasLengthOf.ts index 79b8485b..791190d2 100644 --- a/src/predicates/hasLengthOf.ts +++ b/src/predicates/hasLengthOf.ts @@ -3,7 +3,7 @@ import { Predicate } from './Predicate'; export interface HasLength { length: number; } /** - * @desc Checks if the `value` is of `expectedLength`. + * @desc Ensures that the `value` is of `expectedLength`. * Applies to {@link String}s, {@link Array}s and anything that has a `.length` property. * * @example Array diff --git a/src/predicates/isArray.ts b/src/predicates/isArray.ts index 4eddd510..43e44c4e 100644 --- a/src/predicates/isArray.ts +++ b/src/predicates/isArray.ts @@ -1,7 +1,7 @@ import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is an {@link Array}. + * @desc Ensures that the `value` is an {@link Array}. * * @example * import { check, isArray, TinyType, TinyTypeOf } from 'tiny-types'; diff --git a/src/predicates/isDefined.ts b/src/predicates/isDefined.ts index 7e10620c..7030fae3 100644 --- a/src/predicates/isDefined.ts +++ b/src/predicates/isDefined.ts @@ -1,7 +1,7 @@ import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is defined as anything other than {@link null} or {@link undefined}. + * @desc Ensures that the `value` is defined as anything other than {@link null} or {@link undefined}. * * @example * import { check, isDefined, TinyType } from 'tiny-types'; diff --git a/src/predicates/isEqualTo.ts b/src/predicates/isEqualTo.ts index c2e2362c..cc0a1c11 100644 --- a/src/predicates/isEqualTo.ts +++ b/src/predicates/isEqualTo.ts @@ -5,7 +5,7 @@ export function isEqualTo(expectedValue: TinyType): Predicate; export function isEqualTo(expectedValue: T): Predicate; /** - * @desc Checks if the `value` is equal to `expectedValue`. + * @desc Ensures that the `value` is equal to `expectedValue`. * This {@link Predicate} is typically used in combination with other {@link Predicate}s. * * @example Comparing Tiny Types diff --git a/src/predicates/isGreaterThan.ts b/src/predicates/isGreaterThan.ts index 30ab7b6d..3e91954e 100644 --- a/src/predicates/isGreaterThan.ts +++ b/src/predicates/isGreaterThan.ts @@ -1,7 +1,7 @@ import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is greater than the `lowerBound`. + * @desc Ensures that the `value` is greater than the `lowerBound`. * * @example * import { check, isGreaterThan, TinyType } from 'tiny-types'; diff --git a/src/predicates/isGreaterThanOrEqualTo.ts b/src/predicates/isGreaterThanOrEqualTo.ts index 15d2ffc3..4b661350 100644 --- a/src/predicates/isGreaterThanOrEqualTo.ts +++ b/src/predicates/isGreaterThanOrEqualTo.ts @@ -4,7 +4,7 @@ import { or } from './or'; import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is greater than or equal to the `lowerBound`. + * @desc Ensures that the `value` is greater than or equal to the `lowerBound`. * * @example * import { check, isGreaterThanOrEqualTo, TinyType } from 'tiny-types'; diff --git a/src/predicates/isInRange.ts b/src/predicates/isInRange.ts index 298fb863..3add43fa 100644 --- a/src/predicates/isInRange.ts +++ b/src/predicates/isInRange.ts @@ -4,7 +4,7 @@ import { isLessThanOrEqualTo } from './isLessThanOrEqualTo'; import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is greater than or equal to the `lowerBound` and less than or equal to the `upperBound` + * @desc Ensures that the `value` is greater than or equal to the `lowerBound` and less than or equal to the `upperBound` * * @example * import { check, isInRange, TinyType } from 'tiny-types'; diff --git a/src/predicates/isInteger.ts b/src/predicates/isInteger.ts index 228f348e..7ffd1f83 100644 --- a/src/predicates/isInteger.ts +++ b/src/predicates/isInteger.ts @@ -1,7 +1,7 @@ import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is an integer {@link Number}. + * @desc Ensures that the `value` is an integer {@link Number}. * * @example * import { and, isInteger, TinyType } from 'tiny-types'; diff --git a/src/predicates/isLessThan.ts b/src/predicates/isLessThan.ts index 46c80b43..8cca2e54 100644 --- a/src/predicates/isLessThan.ts +++ b/src/predicates/isLessThan.ts @@ -1,7 +1,7 @@ import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is less than the `upperBound`. + * @desc Ensures that the `value` is less than the `upperBound`. * * @example * import { check, isLessThan, TinyType } from 'tiny-types'; diff --git a/src/predicates/isLessThanOrEqualTo.ts b/src/predicates/isLessThanOrEqualTo.ts index 76436072..997ffbc1 100644 --- a/src/predicates/isLessThanOrEqualTo.ts +++ b/src/predicates/isLessThanOrEqualTo.ts @@ -4,7 +4,7 @@ import { or } from './or'; import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is less than or equal to the `upperBound`. + * @desc Ensures that the `value` is less than or equal to the `upperBound`. * * @example * import { check, isLessThanOrEqualTo, TinyType } from 'tiny-types'; diff --git a/src/predicates/isOneOf.ts b/src/predicates/isOneOf.ts index 821c1696..d24e25a8 100644 --- a/src/predicates/isOneOf.ts +++ b/src/predicates/isOneOf.ts @@ -3,7 +3,7 @@ import { or } from './or'; import { Predicate } from './Predicate'; /** - * @desc Checks if the `value` is equal to one of the `allowedValues` + * @desc Ensures that the `value` is equal to one of the `allowedValues` * * @example * import { check, isOneOf, TinyType } from 'tiny-types'; diff --git a/src/predicates/or.ts b/src/predicates/or.ts index 8c31625c..4459557d 100644 --- a/src/predicates/or.ts +++ b/src/predicates/or.ts @@ -4,7 +4,7 @@ import { isGreaterThan } from './isGreaterThan'; import { Failure, Predicate, Result, Success } from './Predicate'; /** - * @desc Checks if the `value` meets at least one of the provided {@link Predicate}s. + * @desc Ensures that the `value` meets at least one of the provided {@link Predicate}s. * * @example * import { check, isEqualTo, isGreaterThan, isLessThan, or } from 'tiny-type'l diff --git a/src/types/constructors.ts b/src/types/constructors.ts index bfd61e01..51f7e20f 100644 --- a/src/types/constructors.ts +++ b/src/types/constructors.ts @@ -1,2 +1,5 @@ +export interface Constructor { + new (...args: any[]): T; +} export type ConstructorOrAbstract = Function & { prototype: T }; // tslint:disable-line:ban-types export type ConstructorAbstractOrInstance = T | ConstructorOrAbstract; // tslint:disable-line:ban-types diff --git a/tsconfig.json b/tsconfig.json index 0cb2f7ba..cc3aeaa3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,8 @@ "declaration": true, "strict": true, "noImplicitAny": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, "outDir": "./lib" },