From 5d24a9db223cae60cf0e9e07820ff00002631087 Mon Sep 17 00:00:00 2001 From: Sam Verschueren Date: Thu, 5 Oct 2017 08:07:27 +0200 Subject: [PATCH] Rewrite in TypeScript (#1) --- .gitignore | 3 ++ .travis.yml | 2 + example.js | 2 +- index.js | 66 ------------------------------ package.json | 16 ++++++-- source/index.ts | 3 ++ source/lib/argument-error.ts | 8 ++++ source/lib/predicates/predicate.ts | 20 +++++++++ source/lib/predicates/string.ts | 36 ++++++++++++++++ source/ow.ts | 24 +++++++++++ source/test/string.ts | 18 ++++++++ test.js | 6 --- tsconfig.json | 20 +++++++++ 13 files changed, 147 insertions(+), 77 deletions(-) delete mode 100644 index.js create mode 100644 source/index.ts create mode 100644 source/lib/argument-error.ts create mode 100644 source/lib/predicates/predicate.ts create mode 100644 source/lib/predicates/string.ts create mode 100644 source/ow.ts create mode 100644 source/test/string.ts delete mode 100644 test.js create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 239ecff..2bfb508 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules yarn.lock +.nyc_output +coverage +dist diff --git a/.travis.yml b/.travis.yml index 7d69d74..a997c30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,5 @@ node_js: - '8' - '6' - '4' +after_script: + - './node_modules/.bin/nyc report --reporter=text-lcov > coverage.lcov && ./node_modules/.bin/codecov' diff --git a/example.js b/example.js index 0cce90e..27a6036 100644 --- a/example.js +++ b/example.js @@ -1,5 +1,5 @@ 'use strict'; -const ow = require('.'); +const ow = require('./dist'); const fn = (input, options) => { ow(input, ow.string.minLength(10)); diff --git a/index.js b/index.js deleted file mode 100644 index 21a60d1..0000000 --- a/index.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; -const is = require('@sindresorhus/is'); - -class ArgumentError extends Error { - constructor(message, context) { - super(message); - // TODO: Node does not preserve the error name in output when using the below, why? - //Error.captureStackTrace(this, context); - this.name = 'ArgumentError'; - } -} - -const ow = (value, predicate) => { - for (const validator of predicate.context.validators) { - const result = validator(value); - if (result) { - // TODO: Modify the stack output to show the original `ow()` call instead of this `throw` statement - throw new ArgumentError(result, ow); - } - } -}; - -const newInstance = (fn, context) => { - const instance = fn(context); - instance.context = context; - return instance; -}; - -const stringPredicates = context => ({ - minLength: number => { - context.validators.push(value => { - if (value.length < number) { - return `Expected string length to be minimum ${number}`; - } - }); - return newInstance(stringPredicates, context); - }, - get alphanumeric() { - context.validators.push(value => { - if (/[a-z\d]/gi.test(value)) { - return `Expected string to contain only alphanumeric characters but received \`${value}\``; - } - }); - return newInstance(stringPredicates, context); - } -}); - -Object.defineProperty(ow, 'string', { - enumerable: true, - get() { - const instance = newInstance(stringPredicates, { - validators: [] - }); - - instance.context.validators.push(value => { - // TODO: Create a generic function for all types that can be used here - if (!is.string(value)) { - return `Expected argument to be of type \`string\` but received type \`${is(value)}\``; - } - }); - - return instance; - } -}); - -module.exports = ow; diff --git a/package.json b/package.json index b77fc42..3ebe362 100644 --- a/package.json +++ b/package.json @@ -9,15 +9,19 @@ "email": "sindresorhus@gmail.com", "url": "sindresorhus.com" }, + "main": "dist/index.js", + "typings": "dist/index.d.ts", "engines": { "node": ">=4" }, "scripts": { - "test": "xo && ava" + "prerelease": "npm run build", + "pretest": "npm run build -- --sourceMap", + "test": "nyc ava dist/test", + "build": "del dist && tsc -p tsconfig.json --declaration" }, "files": [ - "index.js", - "example.js" + "dist" ], "keywords": [ "type", @@ -43,7 +47,11 @@ "@sindresorhus/is": "^0.2.0" }, "devDependencies": { + "@types/node": "^8.0.31", "ava": "*", - "xo": "*" + "codecov": "^2.3.0", + "del-cli": "^1.1.0", + "nyc": "^11.2.1", + "typescript": "^2.5.3" } } diff --git a/source/index.ts b/source/index.ts new file mode 100644 index 0000000..19ddf51 --- /dev/null +++ b/source/index.ts @@ -0,0 +1,3 @@ +import { ow } from './ow'; + +export = ow; diff --git a/source/lib/argument-error.ts b/source/lib/argument-error.ts new file mode 100644 index 0000000..50b11f6 --- /dev/null +++ b/source/lib/argument-error.ts @@ -0,0 +1,8 @@ +export class ArgumentError extends Error { + constructor(message, context) { + super(message); + // TODO: Node does not preserve the error name in output when using the below, why? + //Error.captureStackTrace(this, context); + this.name = 'ArgumentError'; + } +} diff --git a/source/lib/predicates/predicate.ts b/source/lib/predicates/predicate.ts new file mode 100644 index 0000000..63a8d69 --- /dev/null +++ b/source/lib/predicates/predicate.ts @@ -0,0 +1,20 @@ +import * as is from '@sindresorhus/is'; + +export type Validator = (value: any) => string | undefined; + +export interface Context { + validators: Validator[]; +} + +export class Predicate { + constructor( + type: string, + public context: Context = { validators: [] } + ) { + this.context.validators.push(value => { + if (!is[type](value)) { + return `Expected argument to be of type \`${type}\` but received type \`${is(value)}\``; + } + }); + } +} diff --git a/source/lib/predicates/string.ts b/source/lib/predicates/string.ts new file mode 100644 index 0000000..dd9bfff --- /dev/null +++ b/source/lib/predicates/string.ts @@ -0,0 +1,36 @@ +import { Predicate, Context } from './predicate'; + +export class StringPredicate extends Predicate { + + constructor(context?: Context) { + super('string', context); + } + + /** + * Test a string to have a minimum length. + * + * @param number The minimum length of the string. + */ + minLength(number: number) { + this.context.validators.push(value => { + if (value.length < number) { + return `Expected string length to be minimum ${number}`; + } + }); + + return this; + } + + /** + * Test a string to be alphanumeric. + */ + get alphanumeric() { + this.context.validators.push(value => { + if (!/^[a-z\d]+$/i.test(value)) { + return `Expected string to contain only alphanumeric characters but received \`${value}\``; + } + }); + + return this; + } +} diff --git a/source/ow.ts b/source/ow.ts new file mode 100644 index 0000000..7fd5e72 --- /dev/null +++ b/source/ow.ts @@ -0,0 +1,24 @@ +import * as is from '@sindresorhus/is'; +import { ArgumentError } from './lib/argument-error'; +import { Predicate } from './lib/predicates/predicate'; +import { StringPredicate } from './lib/predicates/string'; + +export interface Ow { + (value: any, predicate: Predicate): void; + /** + * Test the value to be a string. + */ + string?: StringPredicate; +} + +export const ow: Ow = (value: any, predicate: Predicate) => { + for (const validator of predicate.context.validators) { + const result = validator(value); + if (result) { + // TODO: Modify the stack output to show the original `ow()` call instead of this `throw` statement + throw new ArgumentError(result, ow); + } + } +}; + +ow.string = new StringPredicate(); diff --git a/source/test/string.ts b/source/test/string.ts new file mode 100644 index 0000000..6eae313 --- /dev/null +++ b/source/test/string.ts @@ -0,0 +1,18 @@ +import test from 'ava'; +import * as m from '..'; + +test('string', t => { + t.notThrows(() => m('foo', m.string)); + t.throws(() => m(12, m.string), 'Expected argument to be of type `string` but received type `number`'); +}); + +test('string.minLength', t => { + t.notThrows(() => m('foo', m.string.minLength(2))); + t.notThrows(() => m('foo', m.string.minLength(3))); + t.throws(() => m('foo', m.string.minLength(4)), 'Expected string length to be minimum 4'); +}); + +test('string.alphanumeric', t => { + t.notThrows(() => m('Foo123', m.string.alphanumeric)); + t.throws(() => m('Foo123!', m.string.alphanumeric), 'Expected string to contain only alphanumeric characters but received `Foo123!`'); +}); diff --git a/test.js b/test.js deleted file mode 100644 index 10abbf7..0000000 --- a/test.js +++ /dev/null @@ -1,6 +0,0 @@ -import test from 'ava'; -import m from '.'; - -test('', t => { - -}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b5dec0d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "declaration": false, + "removeComments": false, + "pretty": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "noImplicitAny": false, + "noImplicitUseStrict": false, + "noFallthroughCasesInSwitch": true, + "allowSyntheticDefaultImports": true, + "outDir": "dist" + }, + "exclude": [ + "node_modules", + "dist" + ] +}