Skip to content

Commit

Permalink
feat: Add context logger to PropTypes.run helper, rename value to def…
Browse files Browse the repository at this point in the history
…aultValue and rename validate to customValidator
  • Loading branch information
znck committed Feb 16, 2019
1 parent 30552a7 commit 381c253
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 24 deletions.
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,10 @@
"dependencies": {
"vue": "2.*"
},
"sideEffects": false
"sideEffects": false,
"prettier": {
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}
}
141 changes: 123 additions & 18 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
ensureOne,
TYPES,
typeValues,
flat
flat,
} from './helpers'

/** @type {import('../types/index')} */
Expand All @@ -28,7 +28,13 @@ export default class PropTypes {
}

value(value) {
this.default = value && typeof value === 'object' ? () => value : value
console.warn(`'PropType.[type].value' is deprecated. Use 'PropType.[type].defaultValue' instead.`)

return this.defaultValue(value)
}

defaultValue(value) {
this.default = value

return this
}
Expand All @@ -40,6 +46,12 @@ export default class PropTypes {
}

validate(cb) {
console.warn(`'PropType.[type].validate' is deprecated. Use 'PropType.[type].customValidator' instead.`)

return this.customValidator(cb)
}

customValidator(cb) {
if (!(typeof cb === 'function')) return this

const validator = this.validator
Expand Down Expand Up @@ -73,50 +85,112 @@ export default class PropTypes {
}

static get string() {
return this.create(String)
const prop = this.create(String)

prop.__meta__ = {
custom: 'any',
}

return prop
}

static get number() {
return this.create(Number)
const prop = this.create(Number)

prop.__meta__ = {
custom: 'number',
}

return prop
}

static get bool() {
return this.create(Boolean)
const prop = this.create(Boolean)

prop.__meta__ = {
custom: 'bool',
}

return prop
}

static get array() {
return this.create(Array)
const prop = this.create(Array)

prop.__meta__ = {
custom: 'array',
}

return prop
}

static get object() {
return this.create(Object)
const prop = this.create(Object)

prop.__meta__ = {
custom: 'object',
}

return prop
}

static get func() {
return this.create(Function)
const prop = this.create(Function)

prop.__meta__ = {
custom: 'func',
}

return prop
}

static get symbol() {
return this.create(Symbol)
const prop = this.create(Symbol)

prop.__meta__ = {
custom: 'any',
}

return prop
}

static get any() {
return this.create()
const prop = this.create()

prop.__meta__ = {
custom: 'any',
}

return prop
}

static instanceOf(type) {
return this.create(type)
const prop = this.create(type)

prop.__meta__ = {
custom: 'instanceOf',
type
}

return prop
}

static oneOf(...values) {
values = flat(values)

ensureOne(values)

const types = Array.from(new Set(values.map(value => TYPES[typeof value] || Object)))
const types = Array.from(
new Set(values.map(value => TYPES[typeof value] || Object))
)
const prop = this.create(types)
const setOfValues = new Set(values)

prop.__meta__ = {
custom: 'oneOf',
values
}

prop.validator = value => {
return setOfValues.has(value)
}
Expand All @@ -131,6 +205,11 @@ export default class PropTypes {

const prop = this.create(flat(types.map(type => ensureArray(type.type))))

prop.__meta__ = {
custom: 'oneOfType',
types,
}

prop.validator = value =>
types.some(validator => runValidation(validator, value))

Expand All @@ -150,6 +229,12 @@ export default class PropTypes {
const prop = this.create(type)
const types = flat(expected).map(normalizeType)

prop.__meta__ = {
custom: 'collection',
type,
item: types,
}

prop.validator = value =>
(type === Array ? value : Object.values(value)).every(item =>
types.some(type => runValidation(type, item))
Expand All @@ -162,14 +247,14 @@ export default class PropTypes {
const prop = this.create(Object)
const shapeType = {}

Object.entries(shape).forEach(({0: key, 1: value}) => {
Object.entries(shape).forEach(({ 0: key, 1: value }) => {
shapeType[key] = normalizeType(value)
})

prop.validator = value => {
if (!(value && typeof value === 'object')) return prop.required !== true

return Object.entries(shapeType).every(({0: key, 1: type}) =>
return Object.entries(shapeType).every(({ 0: key, 1: type }) =>
runValidation(type, value[key])
)
}
Expand All @@ -178,16 +263,36 @@ export default class PropTypes {
}

static validate(fn) {
console.warn(`'PropType.validate' is deprecated. Use 'PropType.run' instead.`)

return this.run(null, fn)
}

static run(context, fn) {
if (arguments.length === 1) {
fn = context
context = null
}

const logger = createLogger(context)

try {
if (fn() === false) {
console.error('There are some failing validation.')
}
return fn(logger)
} catch (e) {
console.error(e)
logger.error(e.message)
}
}
}

import * as logger from './logger'
function createLogger(context) {
return {
error: message => logger.error(message, context),
tip: message => logger.tip(message, context),
warn: message => logger.warn(message, context),
}
}

function normalizeType(type) {
if (type instanceof PropTypes) return type

Expand Down
104 changes: 104 additions & 0 deletions src/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
let config = {}
try {
config = require('vue')
} catch (e) {}

const hasConsole = typeof console !== 'undefined'
const classifyRE = /(?:^|[-_])(\w)/g
const classify = str =>
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')

export const warn = (msg, vm) => {
const trace = vm ? generateComponentTrace(vm) : ''

if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace)
} else if (hasConsole && !config.silent) {
console.error(`[Vue warn]: ${msg}${trace}`)
}
}
export const error = (msg, vm) => {
const trace = vm ? generateComponentTrace(vm) : ''

if (config.errorHandler) {
config.errorHandler.call(null, msg, vm, trace)
} else if (hasConsole && !config.silent) {
console.error(`[Vue error]: ${msg}${trace}`)
}
}

export const tip = (msg, vm) => {
if (hasConsole && !config.silent) {
console.warn(`[Vue tip]: ${msg}` + (vm ? generateComponentTrace(vm) : ''))
}
}

export const formatComponentName = (vm, includeFile) => {
if (vm.$root === vm) {
return '<Root>'
}
const options =
typeof vm === 'function' && vm.cid != null
? vm.options
: vm._isVue
? vm.$options || vm.constructor.options
: vm
let name = options.name || options._componentTag
const file = options.__file
if (!name && file) {
const match = file.match(/([^/\\]+)\.vue$/)
name = match && match[1]
}

return (
(name ? `<${classify(name)}>` : `<Anonymous>`) +
(file && includeFile !== false ? ` at ${file}` : '')
)
}

const repeat = (str, n) => {
let res = ''
while (n) {
if (n % 2 === 1) res += str
if (n > 1) str += str
n >>= 1
}
return res
}

export const generateComponentTrace = vm => {
if (vm._isVue && vm.$parent) {
const tree = []
let currentRecursiveSequence = 0
while (vm) {
if (tree.length > 0) {
const last = tree[tree.length - 1]
if (last.constructor === vm.constructor) {
currentRecursiveSequence++
vm = vm.$parent
continue
} else if (currentRecursiveSequence > 0) {
tree[tree.length - 1] = [last, currentRecursiveSequence]
currentRecursiveSequence = 0
}
}
tree.push(vm)
vm = vm.$parent
}
return (
'\n\nfound in\n\n' +
tree
.map(
(vm, i) =>
`${i === 0 ? '---> ' : repeat(' ', 5 + i * 2)}${
Array.isArray(vm)
? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)`
: formatComponentName(vm)
}`
)
.join('\n')
)
} else {
return `\n\n(found in ${formatComponentName(vm)})`
}
}
14 changes: 12 additions & 2 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Project: @znck/prop-types
// Definitions by: Rahul Kadyan <https://znck.me>

import { PropOptions } from 'vue'
import Vue, { PropOptions } from 'vue'
import { Prop } from 'vue/types/options'

export as namespace PropTypes
Expand Down Expand Up @@ -82,7 +82,15 @@ export interface PropTypes {
objectOf<T>(type: PropValidator<T>): PropValidator<{ [key: string]: T }>
objectOf<T>(type: ValidatorFn<T>): PropValidator<{ [key: string]: T }>

validate(fn: () => void): void
validate(fn: (logger: ContextLogger) => void): void
run<R = any>(fn: (logger: ContextLogger) => R): R
run<R = any>(context: Vue, fn: (logger: ContextLogger) => R): R
}

export interface ContextLogger {
error(message: string): void
tip(message: string): void
warn(message: string): void
}

export interface PropTypesChain<
Expand All @@ -107,7 +115,9 @@ export interface PropTypesChain<
> {
isRequired: PropOptions<T>
value(value: U): PropValidator<T>
defaultValue(value: U): PropValidator<T>
validate: (value: ValidatorFn<T>) => PropValidator<T>
customValidator: (value: ValidatorFn<T>) => PropValidator<T>
description: (value: string) => PropValidator<T> & { description: string }
}

Expand Down
Loading

0 comments on commit 381c253

Please sign in to comment.