diff --git a/.eslintrc.js b/.eslintrc.js index ed331bd..7d9c224 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,11 +6,14 @@ module.exports = { sourceType: 'module', }, + plugins: ['@typescript-eslint'], + // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style extends: [ 'standard', '@vue/standard', '@vue/typescript', + 'plugin:@typescript-eslint/recommended', 'plugin:vue/recommended', ], @@ -19,17 +22,18 @@ module.exports = { }, rules: { - // allow paren-less arrow functions + '@typescript-eslint/ban-ts-ignore': 'off', // @TODO + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-explicit-any': 'off', // @TODO + '@typescript-eslint/no-unused-vars': ['error'], // @TODO 'arrow-parens': 0, 'comma-dangle': ['error', 'only-multiline'], 'indent': ['error', 4, { SwitchCase: 1 }], 'max-depth': ['error', 3], 'max-lines-per-function': ['error', 40], 'no-console': ['warn', {allow: ['warn', 'error']}], - // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': ['error'], 'vue/html-closing-bracket-spacing': ['error', { startTag: 'never', endTag: 'never', @@ -42,4 +46,12 @@ module.exports = { ignores: [], }], }, + + overrides: [{ + files: ['*/**/shims-*.d.ts'], + rules: { + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-unused-vars': 'off', + }, + }], } diff --git a/src/FileUpload.ts b/src/FileUpload.ts index 272dd15..d53bd7f 100644 --- a/src/FileUpload.ts +++ b/src/FileUpload.ts @@ -1,26 +1,25 @@ import nanoid from 'nanoid/non-secure' import { AxiosResponse, AxiosError } from '@/axios.types' -import { ObjectType } from '@/common.types' interface FileItem { - uuid: string - name: string - path: string | false - progress: number | false - error: any | false - complete: boolean - file: File - justFinished: boolean - removeFile(): void - previewData: string | false + uuid: string; + name: string; + path: string | false; + progress: number | false; + error: any | false; + complete: boolean; + file: File; + justFinished: boolean; + removeFile(): void; + previewData: string | false; } interface ProgressSetter { - (progress: number): void + (progress: number): void; } interface ErrorHandler { - (error: AxiosError): any + (error: AxiosError): any; } // noinspection JSUnusedGlobalSymbols @@ -32,11 +31,11 @@ class FileUpload { public input: DataTransfer public fileList: FileList public files: FileItem[] - public options: ObjectType - public context: ObjectType + public options: Record + public context: Record public results: any[] | boolean - constructor (input: DataTransfer, context: ObjectType = {}, options: ObjectType = {}) { + constructor (input: DataTransfer, context: Record = {}, options: Record = {}) { this.input = input this.fileList = input.files this.files = [] @@ -54,7 +53,7 @@ class FileUpload { * Given a pre-existing array of files, create a faux FileList. * @param {array} items expects an array of objects [{ url: '/uploads/file.pdf' }] */ - rehydrateFileList (items: any[]) { + rehydrateFileList (items: any[]): void { const fauxFileList = items.reduce((fileList, item) => { const key = this.options ? this.options.fileUrlKey : 'url' const url = item[key] @@ -75,7 +74,7 @@ class FileUpload { * Produce an array of files and alert the callback. * @param {FileList} fileList */ - addFileList (fileList: FileList) { + addFileList (fileList: FileList): void { for (let i = 0; i < fileList.length; i++) { const file: File = fileList[i] const uuid = nanoid() @@ -98,7 +97,7 @@ class FileUpload { /** * Check if the file has an. */ - hasUploader () { + hasUploader (): boolean { return !!this.context.uploader } @@ -108,7 +107,7 @@ class FileUpload { * * https://github.com/axios/axios/issues/737 */ - uploaderIsAxios () { + uploaderIsAxios (): boolean { return this.hasUploader && typeof this.context.uploader.request === 'function' && typeof this.context.uploader.get === 'function' && @@ -119,7 +118,7 @@ class FileUpload { /** * Get a new uploader function. */ - getUploader (...args: [File, ProgressSetter, ErrorHandler, ObjectType]) { + getUploader (...args: [File, ProgressSetter, ErrorHandler, Record]) { if (this.uploaderIsAxios()) { const data = new FormData() data.append(this.context.name || 'file', args[0]) @@ -184,7 +183,7 @@ class FileUpload { /** * Remove a file from the uploader (and the file list) */ - removeFile (uuid: string) { + removeFile (uuid: string): void { this.files = this.files.filter(file => file.uuid !== uuid) this.context.performValidation() if (window && this.fileList instanceof FileList) { @@ -223,7 +222,7 @@ class FileUpload { return this.files } - toString () { + toString (): string { const descriptor = this.files.length ? this.files.length + ' files' : 'empty' return this.results ? JSON.stringify(this.results, null, ' ') : `FileUpload(${descriptor})` } diff --git a/src/FormSubmission.ts b/src/FormSubmission.ts index 0a0ddc7..f565e4a 100644 --- a/src/FormSubmission.ts +++ b/src/FormSubmission.ts @@ -15,18 +15,15 @@ export default class FormSubmission { /** * Determine if the form has any validation errors. - * - * @return {Promise} resolves a boolean */ - hasValidationErrors () { + hasValidationErrors (): Promise { return (this.form as any).hasValidationErrors() } /** * Asynchronously generate the values payload of this form. - * @return {Promise} resolves to json */ - values () { + values (): Promise> { return new Promise((resolve, reject) => { const form = this.form as any const pending = [] @@ -36,9 +33,10 @@ export default class FormSubmission { if ( Object.prototype.hasOwnProperty.call(values, key) && typeof form.proxy[key] === 'object' && - form.proxy[key] instanceof FileUpload) { + form.proxy[key] instanceof FileUpload + ) { pending.push( - form.proxy[key].upload().then((data: Object) => Object.assign(values, { [key]: data })) + form.proxy[key].upload().then((data: Record) => Object.assign(values, { [key]: data })) ) } } diff --git a/src/Formulario.ts b/src/Formulario.ts index faf31d1..c7681be 100644 --- a/src/Formulario.ts +++ b/src/Formulario.ts @@ -12,27 +12,26 @@ import merge from '@/utils/merge' import FormularioForm from '@/FormularioForm.vue' import FormularioInput from '@/FormularioInput.vue' import FormularioGrouping from '@/FormularioGrouping.vue' -import { ObjectType } from '@/common.types' import { ValidationContext } from '@/validation/types' interface ErrorHandler { - (error: any, formName?: string): any + (error: any, formName?: string): any; } interface FormularioOptions { - components?: { [name: string]: VueConstructor } - plugins?: any[] - library?: any - rules?: any - mimes?: any - locale?: any - uploader?: any - uploadUrl?: any - fileUrlKey?: any - errorHandler?: ErrorHandler - uploadJustCompleteDuration?: any - validationMessages?: any - idPrefix?: string + components?: { [name: string]: VueConstructor }; + plugins?: any[]; + library?: any; + rules?: any; + mimes?: any; + locale?: any; + uploader?: any; + uploadUrl?: any; + fileUrlKey?: any; + errorHandler?: ErrorHandler; + uploadJustCompleteDuration?: any; + validationMessages?: any; + idPrefix?: string; } // noinspection JSUnusedGlobalSymbols @@ -122,7 +121,7 @@ export default class Formulario { /** * Get validation rules by merging any passed in with global rules. */ - rules (rules: Object = {}) { + rules (rules: Record = {}) { return { ...this.options.rules, ...rules } } @@ -206,7 +205,7 @@ export default class Formulario { /** * Set the form values. */ - setValues (formName: string, values?: ObjectType) { + setValues (formName: string, values?: Record) { if (values) { const form = this.registry.get(formName) as FormularioForm // @ts-ignore @@ -224,7 +223,7 @@ export default class Formulario { /** * Get the global upload url. */ - getUploadUrl () { + getUploadUrl (): string | boolean { return this.options.uploadUrl || false } @@ -239,7 +238,7 @@ export default class Formulario { /** * Create a new instance of an upload. */ - createUpload (data: DataTransfer, context: ObjectType) { + createUpload (data: DataTransfer, context: Record) { return new FileUpload(data, context, this.options) } } diff --git a/src/FormularioForm.vue b/src/FormularioForm.vue index 74c6bdb..db32e82 100644 --- a/src/FormularioForm.vue +++ b/src/FormularioForm.vue @@ -14,32 +14,31 @@ import { Watch, } from 'vue-property-decorator' import { arrayify, getNested, has, setNested, shallowEqualObjects } from '@/libs/utils' -import { ObjectType } from '@/common.types' import Registry from '@/libs/registry' import FormSubmission from '@/FormSubmission' import FormularioInput from '@/FormularioInput.vue' @Component export default class FormularioForm extends Vue { - @Provide() formularioFieldValidation (errorObject) { - return this.$emit('validation', errorObject) + @Provide() formularioFieldValidation (errorObject): void { + this.$emit('validation', errorObject) } @Provide() formularioRegister = this.register @Provide() formularioDeregister = this.deregister @Provide() formularioSetter = this.setFieldValue - @Provide() getFormValues = () => this.proxy + @Provide() getFormValues = (): Record => this.proxy @Provide() observeErrors = this.addErrorObserver - @Provide() path: string = '' + @Provide() path = '' - @Provide() removeErrorObserver (observer) { + @Provide() removeErrorObserver (observer): void { this.errorObservers = this.errorObservers.filter(obs => obs.callback !== observer) } @Model('input', { type: Object, default: () => ({}) - }) readonly formularioValue!: Object + }) readonly formularioValue!: Record @Prop({ type: [String, Boolean], @@ -49,31 +48,31 @@ export default class FormularioForm extends Vue { @Prop({ type: [Object, Boolean], default: false - }) readonly values!: Object | Boolean + }) readonly values!: Record | boolean @Prop({ type: [Object, Boolean], default: false - }) readonly errors!: Object | Boolean + }) readonly errors!: Record | boolean @Prop({ type: Array, default: () => ([]) }) readonly formErrors!: [] - public proxy: Object = {} + public proxy: Record = {} registry: Registry = new Registry(this) - childrenShouldShowErrors: boolean = false + childrenShouldShowErrors = false - formShouldShowErrors: boolean = false + formShouldShowErrors = false errorObservers: [] = [] namedErrors: [] = [] - namedFieldErrors: Object = {} + namedFieldErrors: Record = {} get mergedFormErrors () { return this.formErrors.concat(this.namedErrors) @@ -95,11 +94,11 @@ export default class FormularioForm extends Vue { return errors } - get hasFormErrorObservers () { + get hasFormErrorObservers (): boolean { return this.errorObservers.some(o => o.type === 'form') } - get hasInitialValue () { + get hasInitialValue (): boolean { return ( (this.formularioValue && typeof this.formularioValue === 'object') || (this.values && typeof this.values === 'object') || @@ -107,14 +106,14 @@ export default class FormularioForm extends Vue { ) } - get isVmodeled () { + get isVmodeled (): boolean { return !!(has(this.$options.propsData, 'formularioValue') && this._events && Array.isArray(this._events.input) && this._events.input.length) } - get initialValues () { + get initialValues (): Record { if ( has(this.$options.propsData, 'formularioValue') && typeof this.formularioValue === 'object' @@ -136,50 +135,50 @@ export default class FormularioForm extends Vue { } @Watch('formularioValue', { deep: true }) - onFormularioValueChanged (values) { + onFormularioValueChanged (values): void { if (this.isVmodeled && values && typeof values === 'object') { this.setValues(values) } } @Watch('mergedFormErrors') - onMergedFormErrorsChanged (errors) { + onMergedFormErrorsChanged (errors): void { this.errorObservers .filter(o => o.type === 'form') .forEach(o => o.callback(errors)) } @Watch('mergedFieldErrors', { immediate: true }) - onMergedFieldErrorsChanged (errors) { + onMergedFieldErrorsChanged (errors): void { this.errorObservers .filter(o => o.type === 'input') .forEach(o => o.callback(errors[o.field] || [])) } - created () { + created (): void { this.$formulario.register(this) this.applyInitialValues() } - destroyed () { + destroyed (): void { this.$formulario.deregister(this) } - public register (field: string, component: FormularioInput) { + public register (field: string, component: FormularioInput): void { this.registry.register(field, component) } - public deregister (field: string) { + public deregister (field: string): void { this.registry.remove(field) } - applyErrors ({ formErrors, inputErrors }) { + applyErrors ({ formErrors, inputErrors }): void { // given an object of errors, apply them to this form this.namedErrors = formErrors this.namedFieldErrors = inputErrors } - addErrorObserver (observer) { + addErrorObserver (observer): void { if (!this.errorObservers.find(obs => observer.callback === obs.callback)) { this.errorObservers.push(observer) if (observer.type === 'form') { @@ -190,13 +189,13 @@ export default class FormularioForm extends Vue { } } - registerErrorComponent (component) { + registerErrorComponent (component): void { if (!this.errorComponents.includes(component)) { this.errorComponents.push(component) } } - formSubmitted () { + formSubmitted (): Promise { // perform validation here this.showErrors() const submission = new FormSubmission(this) @@ -208,17 +207,16 @@ export default class FormularioForm extends Vue { this.$emit('submit', data) return data } - return undefined }) } - applyInitialValues () { + applyInitialValues (): void { if (this.hasInitialValue) { this.proxy = this.initialValues } } - setFieldValue (field, value) { + setFieldValue (field, value): void { if (value === undefined) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { [field]: value, ...proxy } = this.proxy @@ -229,21 +227,21 @@ export default class FormularioForm extends Vue { this.$emit('input', Object.assign({}, this.proxy)) } - hasValidationErrors () { + hasValidationErrors (): Promise { return Promise.all(this.registry.reduce((resolvers, cmp) => { resolvers.push(cmp.performValidation() && cmp.getValidationErrors()) return resolvers }, [])).then(errorObjects => errorObjects.some(item => item.hasErrors)) } - showErrors () { + showErrors (): void { this.childrenShouldShowErrors = true this.registry.forEach((input: FormularioInput) => { input.formShouldShowErrors = true }) } - hideErrors () { + hideErrors (): void { this.childrenShouldShowErrors = false this.registry.forEach((input: FormularioInput) => { input.formShouldShowErrors = false @@ -251,7 +249,7 @@ export default class FormularioForm extends Vue { }) } - setValues (values: ObjectType) { + setValues (values: Record): void { // Collect all keys, existing and incoming const keys = Array.from(new Set(Object.keys(values).concat(Object.keys(this.proxy)))) keys.forEach(field => { diff --git a/src/FormularioInput.vue b/src/FormularioInput.vue index 9913f0a..39f9060 100644 --- a/src/FormularioInput.vue +++ b/src/FormularioInput.vue @@ -26,7 +26,6 @@ import { } from 'vue-property-decorator' import { shallowEqualObjects, parseRules, snakeToCamel, has, arrayify, groupBails } from './libs/utils' import { ValidationError } from '@/validation/types' -import { ObjectType } from '@/common.types' const ERROR_BEHAVIOR = { BLUR: 'blur', @@ -37,10 +36,10 @@ const ERROR_BEHAVIOR = { @Component({ inheritAttrs: false }) export default class FormularioInput extends Vue { @Inject({ default: undefined }) formularioSetter!: Function|undefined - @Inject({ default: () => () => ({}) }) formularioFieldValidation!: Function + @Inject({ default: () => (): void => {} }) formularioFieldValidation!: Function @Inject({ default: undefined }) formularioRegister!: Function|undefined @Inject({ default: undefined }) formularioDeregister!: Function|undefined - @Inject({ default: () => () => ({}) }) getFormValues!: Function + @Inject({ default: () => (): Record => ({}) }) getFormValues!: Function @Inject({ default: undefined }) observeErrors!: Function|undefined @Inject({ default: undefined }) removeErrorObserver!: Function|undefined @Inject({ default: '' }) path!: string @@ -74,12 +73,12 @@ export default class FormularioInput extends Vue { @Prop({ type: Object, default: () => ({}), - }) validationRules!: ObjectType + }) validationRules!: Record @Prop({ type: Object, default: () => ({}), - }) validationMessages!: ObjectType + }) validationMessages!: Record @Prop({ type: [Array, String, Boolean], @@ -96,23 +95,23 @@ export default class FormularioInput extends Vue { @Prop({ default: false }) disableErrors!: boolean @Prop({ default: true }) preventWindowDrops!: boolean @Prop({ default: 'preview' }) imageBehavior!: string - @Prop({ default: false }) uploader!: Function|Object|boolean + @Prop({ default: false }) uploader!: Function|Record|boolean @Prop({ default: false }) uploadUrl!: string|boolean @Prop({ default: 'live' }) uploadBehavior!: string defaultId: string = this.$formulario.nextId(this) - localAttributes: ObjectType = {} + localAttributes: Record = {} localErrors: ValidationError[] = [] - proxy: ObjectType = this.getInitialValue() + proxy: Record = this.getInitialValue() behavioralErrorVisibility: boolean = this.errorBehavior === 'live' - formShouldShowErrors: boolean = false + formShouldShowErrors = false validationErrors: [] = [] pendingValidation: Promise = Promise.resolve() // These registries are used for injected messages registrants only (mostly internal). ruleRegistry: [] = [] - messageRegistry: ObjectType = {} + messageRegistry: Record = {} - get context () { + get context (): Record { return this.defineModel({ attributes: this.elementAttributes, blurHandler: this.blurHandler.bind(this), @@ -164,7 +163,7 @@ export default class FormularioInput extends Vue { /** * Reducer for attributes that will be applied to each core input element. */ - get elementAttributes () { + get elementAttributes (): Record { const attrs = Object.assign({}, this.localAttributes) // pass the ID prop through to the root element if (this.id) { @@ -188,14 +187,14 @@ export default class FormularioInput extends Vue { /** * Return the element’s name, or select a fallback. */ - get nameOrFallback () { + get nameOrFallback (): string { return this.path !== '' ? `${this.path}.${this.name}` : this.name } /** * Determine if an input has a user-defined name. */ - get hasGivenName () { + get hasGivenName (): boolean { return typeof this.name !== 'boolean' } @@ -210,21 +209,21 @@ export default class FormularioInput extends Vue { * Use the uploadURL on the input if it exists, otherwise use the uploadURL * that is defined as a plugin option. */ - get mergedUploadUrl () { + get mergedUploadUrl (): string | boolean { return this.uploadUrl || this.$formulario.getUploadUrl() } /** * Does this computed property have errors */ - get hasErrors () { + get hasErrors (): boolean { return this.allErrors.length > 0 } /** * Returns if form has actively visible errors (of any kind) */ - get hasVisibleErrors () { + get hasVisibleErrors (): boolean { return ((this.validationErrors && this.showValidationErrors) || !!this.explicitErrors.length) } @@ -275,12 +274,12 @@ export default class FormularioInput extends Vue { } @Watch('$attrs', { deep: true }) - onAttrsChanged (value) { + onAttrsChanged (value): void { this.updateLocalAttributes(value) } @Watch('proxy') - onProxyChanged (newValue, oldValue) { + onProxyChanged (newValue, oldValue): void { this.performValidation() if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) { this.context.model = newValue @@ -288,18 +287,18 @@ export default class FormularioInput extends Vue { } @Watch('formularioValue') - onFormularioValueChanged (newValue, oldValue) { + onFormularioValueChanged (newValue, oldValue): void { if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) { this.context.model = newValue } } @Watch('showValidationErrors', { immediate: true }) - onShowValidationErrorsChanged (val) { + onShowValidationErrorsChanged (val): void { this.$emit('error-visibility', val) } - created () { + created (): void { this.applyInitialValue() if (this.formularioRegister && typeof this.formularioRegister === 'function') { this.formularioRegister(this.nameOrFallback, this) @@ -312,7 +311,7 @@ export default class FormularioInput extends Vue { } // noinspection JSUnusedGlobalSymbols - beforeDestroy () { + beforeDestroy (): void { if (!this.disableErrors && typeof this.removeErrorObserver === 'function') { this.removeErrorObserver(this.setErrors) } @@ -346,7 +345,7 @@ export default class FormularioInput extends Vue { /** * Set the value from a model. */ - modelSetter (value) { + modelSetter (value): void { if (!shallowEqualObjects(value, this.proxy)) { this.proxy = value } @@ -366,16 +365,16 @@ export default class FormularioInput extends Vue { } } - getInitialValue () { - if (has(this.$options.propsData as ObjectType, 'value')) { + getInitialValue (): any { + if (has(this.$options.propsData as Record, 'value')) { return this.value - } else if (has(this.$options.propsData as ObjectType, 'formularioValue')) { + } else if (has(this.$options.propsData as Record, 'formularioValue')) { return this.formularioValue } return '' } - applyInitialValue () { + applyInitialValue (): void { // This should only be run immediately on created and ensures that the // proxy and the model are both the same before any additional registration. if (!shallowEqualObjects(this.context.model, this.proxy)) { @@ -383,7 +382,7 @@ export default class FormularioInput extends Vue { } } - updateLocalAttributes (value) { + updateLocalAttributes (value): void { if (!shallowEqualObjects(value, this.localAttributes)) { this.localAttributes = value } @@ -432,7 +431,7 @@ export default class FormularioInput extends Vue { }) } - didValidate (messages) { + didValidate (messages): void { const validationChanged = !shallowEqualObjects(messages, this.validationErrors) this.validationErrors = messages if (validationChanged) { @@ -461,7 +460,7 @@ export default class FormularioInput extends Vue { } } - getMessageFunc (ruleName) { + getMessageFunc (ruleName: string) { ruleName = snakeToCamel(ruleName) if (this.messages && typeof this.messages[ruleName] !== 'undefined') { switch (typeof this.messages[ruleName]) { @@ -497,11 +496,11 @@ export default class FormularioInput extends Vue { } } - setErrors (errors) { + setErrors (errors): void { this.localErrors = arrayify(errors) } - registerRule (rule, args, ruleName, message = null) { + registerRule (rule, args, ruleName, message = null): void { if (!this.ruleRegistry.some(r => r[2] === ruleName)) { // These are the raw rule format since they will be used directly. this.ruleRegistry.push([rule, args, ruleName]) @@ -511,7 +510,7 @@ export default class FormularioInput extends Vue { } } - removeRule (key) { + removeRule (key): void { const ruleIndex = this.ruleRegistry.findIndex(r => r[2] === key) if (ruleIndex >= 0) { this.ruleRegistry.splice(ruleIndex, 1) diff --git a/src/RuleValidationMessages.ts b/src/RuleValidationMessages.ts index 2bf7635..66ce704 100644 --- a/src/RuleValidationMessages.ts +++ b/src/RuleValidationMessages.ts @@ -46,14 +46,14 @@ const validationMessages = { /** * The value is not a letter. */ - alpha (vm: FormularioInput, context: Object): string { + alpha (vm: FormularioInput, context: Record): string { return vm.$t('validation.alpha', context) }, /** * Rule: checks if the value is alpha numeric */ - alphanumeric (vm: FormularioInput, context: Object): string { + alphanumeric (vm: FormularioInput, context: Record): string { return vm.$t('validation.alphanumeric', context) }, @@ -116,7 +116,7 @@ const validationMessages = { /** * Value is an allowed value. */ - in: function (vm: FormularioInput, context: ValidationContext) { + in: function (vm: FormularioInput, context: ValidationContext): string { if (typeof context.value === 'string' && context.value) { return vm.$t('validation.in.string', context) } @@ -127,14 +127,14 @@ const validationMessages = { /** * Value is not a match. */ - matches (vm: FormularioInput, context: ValidationContext) { + matches (vm: FormularioInput, context: ValidationContext): string { return vm.$t('validation.matches.default', context) }, /** * The maximum value allowed. */ - max (vm: FormularioInput, context: ValidationContext) { + max (vm: FormularioInput, context: ValidationContext): string { const maximum = context.args[0] as number if (Array.isArray(context.value)) { @@ -150,7 +150,7 @@ const validationMessages = { /** * The (field-level) error message for mime errors. */ - mime (vm: FormularioInput, context: ValidationContext) { + mime (vm: FormularioInput, context: ValidationContext): string { const types = context.args[0] if (types) { @@ -163,7 +163,7 @@ const validationMessages = { /** * The maximum value allowed. */ - min (vm: FormularioInput, context: ValidationContext) { + min (vm: FormularioInput, context: ValidationContext): string { const minimum = context.args[0] as number if (Array.isArray(context.value)) { @@ -179,35 +179,35 @@ const validationMessages = { /** * The field is not an allowed value */ - not (vm: FormularioInput, context: Object) { + not (vm: FormularioInput, context: Record): string { return vm.$t('validation.not.default', context) }, /** * The field is not a number */ - number (vm: FormularioInput, context: Object) { + number (vm: FormularioInput, context: Record): string { return vm.$t('validation.number.default', context) }, /** * Required field. */ - required (vm: FormularioInput, context: Object) { + required (vm: FormularioInput, context: Record): string { return vm.$t('validation.required.default', context) }, /** * Starts with specified value */ - startsWith (vm: FormularioInput, context: Object) { + startsWith (vm: FormularioInput, context: Record): string { return vm.$t('validation.startsWith.default', context) }, /** * Value is not a url. */ - url (vm: FormularioInput, context: Object) { + url (vm: FormularioInput, context: Record): string { return vm.$t('validation.url.default', context) } } diff --git a/src/common.types.ts b/src/common.types.ts deleted file mode 100644 index 098f699..0000000 --- a/src/common.types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type ArrayType = [any] -export type ObjectType = { [key: string]: any } diff --git a/src/libs/registry.ts b/src/libs/registry.ts index 70ecf86..e69c4a1 100644 --- a/src/libs/registry.ts +++ b/src/libs/registry.ts @@ -1,5 +1,4 @@ import { shallowEqualObjects, has, getNested } from './utils' -import { ObjectType } from '@/common.types' import FormularioForm from '@/FormularioForm.vue' import FormularioInput from '@/FormularioInput.vue' @@ -23,36 +22,33 @@ export default class Registry { /** * Add an item to the registry. */ - add (name: string, component: FormularioInput) { + add (name: string, component: FormularioInput): void { this.registry.set(name, component) - return this } /** * Remove an item from the registry. - * @param {string} name */ - remove (name: string) { + remove (name: string): void { this.registry.delete(name) // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unused-vars const { [name]: value, ...newProxy } = this.ctx.proxy // @ts-ignore this.ctx.proxy = newProxy - return this } /** * Check if the registry has the given key. */ - has (key: string) { + has (key: string): boolean { return this.registry.has(key) } /** * Check if the registry has elements, that equals or nested given key */ - hasNested (key: string) { + hasNested (key: string): boolean { for (const i of this.registry.keys()) { if (i === key || i.includes(key + '.')) { return true @@ -72,7 +68,7 @@ export default class Registry { /** * Get registry value for key or nested to given key */ - getNested (key: string) { + getNested (key: string): Map { const result = new Map() for (const i of this.registry.keys()) { @@ -87,7 +83,7 @@ export default class Registry { /** * Map over the registry (recursively). */ - map (mapper: Function) { + map (mapper: Function): Record { const value = {} this.registry.forEach((component, field) => Object.assign(value, { [field]: mapper(component, field) })) return value @@ -96,7 +92,7 @@ export default class Registry { /** * Map over the registry (recursively). */ - forEach (callback: Function) { + forEach (callback: Function): void { this.registry.forEach((component, field) => { callback(component, field) }) @@ -114,13 +110,13 @@ export default class Registry { * @param {string} field name of the field. * @param {FormularioForm} component the actual component instance. */ - register (field: string, component: FormularioInput) { + register (field: string, component: FormularioInput): void { if (this.registry.has(field)) { - return false + return } this.registry.set(field, component) - const hasVModelValue = has(component.$options.propsData as ObjectType, 'formularioValue') - const hasValue = has(component.$options.propsData as ObjectType, 'value') + const hasVModelValue = has(component.$options.propsData as Record, 'formularioValue') + const hasValue = has(component.$options.propsData as Record, 'value') if ( !hasVModelValue && // @ts-ignore diff --git a/src/libs/utils.ts b/src/libs/utils.ts index 19e10ce..9c4c1db 100644 --- a/src/libs/utils.ts +++ b/src/libs/utils.ts @@ -1,16 +1,12 @@ import FileUpload from '@/FileUpload' -import { - ArrayType, - ObjectType, -} from '@/common.types' /** * Function to map over an object. * @param {Object} original An object to map over * @param {Function} callback */ -export function map (original: ObjectType, callback: Function) { - const obj: ObjectType = {} +export function map (original: Record, callback: Function): Record { + const obj: Record = {} for (const key in original) { if (Object.prototype.hasOwnProperty.call(original, key)) { obj[key] = callback(key, original[key]) @@ -19,7 +15,7 @@ export function map (original: ObjectType, callback: Function) { return obj } -export function shallowEqualObjects (objA: any, objB: any) { +export function shallowEqualObjects (objA: Record, objB: Record): boolean { if (objA === objB) { return true } @@ -66,7 +62,7 @@ export function snakeToCamel (string: string | any) { * If given parameter is not string, object ot array, result will be an empty array. * @param {*} item */ -export function arrayify (item: any) { +export function arrayify (item: any): any[] { if (!item) { return [] } @@ -102,7 +98,7 @@ export function parseRules (validation: any, rules: any): any[] { * Given a string or function, parse it and return an array in the format * [fn, [...arguments]] */ -function parseRule (rule: any, rules: ObjectType) { +function parseRule (rule: any, rules: Record) { if (typeof rule === 'function') { return [rule, []] } @@ -243,12 +239,12 @@ export function isScalar (data: any) { * A simple (somewhat non-comprehensive) cloneDeep function, valid for our use * case of needing to unbind reactive watchers. */ -export function cloneDeep (value: any) { +export function cloneDeep (value: any): any { if (typeof value !== 'object') { return value } - const copy: ArrayType | ObjectType = Array.isArray(value) ? [] : {} + const copy: any | Record = Array.isArray(value) ? [] : {} for (const key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { @@ -267,7 +263,7 @@ export function cloneDeep (value: any) { * Given a locale string, parse the options. * @param {string} locale */ -export function parseLocale (locale: string) { +export function parseLocale (locale: string): string[] { const segments = locale.split('-') return segments.reduce((options: string[], segment: string) => { if (options.length) { @@ -280,21 +276,14 @@ export function parseLocale (locale: string) { /** * Shorthand for Object.prototype.hasOwnProperty.call (space saving) */ -export function has (ctx: ObjectType, prop: string): boolean { +export function has (ctx: Record, prop: string): boolean { return Object.prototype.hasOwnProperty.call(ctx, prop) } -/** - * Set a unique Symbol identifier on an object. - */ -export function setId (o: object, id: Symbol) { - return Object.defineProperty(o, '__id', Object.assign(Object.create(null), { value: id || Symbol('uuid') })) -} - -export function getNested (obj: ObjectType, field: string) { +export function getNested (obj: Record, field: string): any { const fieldParts = field.split('.') - let result: ObjectType = obj + let result: Record = obj for (const key in fieldParts) { const matches = fieldParts[key].match(/(.+)\[(\d+)\]$/) @@ -315,10 +304,10 @@ export function getNested (obj: ObjectType, field: string) { return result } -export function setNested (obj: ObjectType, field: string, value: any) { +export function setNested (obj: Record, field: string, value: any): void { const fieldParts = field.split('.') - let subProxy: ObjectType = obj + let subProxy: Record = obj for (let i = 0; i < fieldParts.length; i++) { const fieldPart = fieldParts[i] const matches = fieldPart.match(/(.+)\[(\d+)\]$/) @@ -348,6 +337,4 @@ export function setNested (obj: ObjectType, field: string, value: any) { } } } - - return obj } diff --git a/src/shims-ext.d.ts b/src/shims-ext.d.ts index 840bc38..8eeb681 100644 --- a/src/shims-ext.d.ts +++ b/src/shims-ext.d.ts @@ -1,20 +1,20 @@ import Formulario from '@/Formulario' declare module 'vue/types/vue' { - interface VueRoute { - path: string + interface Vue { + $formulario: Formulario; + $route: VueRoute; + $t: Function; + $tc: Function; } - interface Vue { - $formulario: Formulario, - $route: VueRoute, - $t: Function, - $tc: Function, + interface VueRoute { + path: string; } interface FormularioForm extends Vue { - name: string | boolean - proxy: Object - hasValidationErrors(): Promise + name: string | boolean; + proxy: Record; + hasValidationErrors(): Promise; } } diff --git a/src/shims-tsx.d.ts b/src/shims-tsx.d.ts index c656c68..08943fb 100644 --- a/src/shims-tsx.d.ts +++ b/src/shims-tsx.d.ts @@ -2,12 +2,10 @@ import Vue, { VNode } from 'vue' declare global { namespace JSX { - // tslint:disable no-empty-interface interface Element extends VNode {} - // tslint:disable no-empty-interface interface ElementClass extends Vue {} interface IntrinsicElements { - [elem: string]: any + [elem: string]: any; } } } diff --git a/src/utils/merge.ts b/src/utils/merge.ts index 27edc78..02d3c05 100644 --- a/src/utils/merge.ts +++ b/src/utils/merge.ts @@ -1,35 +1,38 @@ import isPlainObject from 'is-plain-object' -import { ObjectType } from '@/common.types.ts' import { has } from '@/libs/utils.ts' /** * Create a new object by copying properties of base and mergeWith. * Note: arrays don't overwrite - they push * - * @param {Object} base - * @param {Object} mergeWith + * @param {Object} a + * @param {Object} b * @param {boolean} concatArrays */ -export default function merge (base: ObjectType, mergeWith: ObjectType, concatArrays: boolean = true) { - const merged: ObjectType = {} +export default function merge ( + a: Record, + b: Record, + concatArrays = true +): Record { + const merged: Record = {} - for (const key in base) { - if (has(mergeWith, key)) { - if (isPlainObject(mergeWith[key]) && isPlainObject(base[key])) { - merged[key] = merge(base[key], mergeWith[key], concatArrays) - } else if (concatArrays && Array.isArray(base[key]) && Array.isArray(mergeWith[key])) { - merged[key] = base[key].concat(mergeWith[key]) + for (const key in a) { + if (has(b, key)) { + if (isPlainObject(b[key]) && isPlainObject(a[key])) { + merged[key] = merge(a[key], b[key], concatArrays) + } else if (concatArrays && Array.isArray(a[key]) && Array.isArray(b[key])) { + merged[key] = a[key].concat(b[key]) } else { - merged[key] = mergeWith[key] + merged[key] = b[key] } } else { - merged[key] = base[key] + merged[key] = a[key] } } - for (const prop in mergeWith) { + for (const prop in b) { if (!has(merged, prop)) { - merged[prop] = mergeWith[prop] + merged[prop] = b[prop] } } diff --git a/src/validation/rules.ts b/src/validation/rules.ts index ee0d552..915ce00 100644 --- a/src/validation/rules.ts +++ b/src/validation/rules.ts @@ -2,12 +2,11 @@ import isUrl from 'is-url' import FileUpload from '../FileUpload' import { shallowEqualObjects, regexForFormat, has } from '@/libs/utils' -import { ObjectType } from '@/common.types' import { ValidatableData } from '@/validation/types' interface ConfirmValidatableData extends ValidatableData { - getFormValues: () => ObjectType, - name: string, + getFormValues: () => Record; + name: string; } /** @@ -33,7 +32,7 @@ export default { /** * Rule: checks if the value is only alpha */ - alpha ({ value }: { value: string }, set: string = 'default'): Promise { + alpha ({ value }: { value: string }, set = 'default'): Promise { const sets = { default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/, latin: /^[a-zA-Z]+$/ @@ -69,7 +68,7 @@ export default { * Rule: checks if the value is between two other values */ between ({ value }: { value: string|number }, from: number|any = 0, to: number|any = 10, force?: string): Promise { - return Promise.resolve((() => { + return Promise.resolve(((): boolean => { if (from === null || to === null || isNaN(from) || isNaN(to)) { return false } @@ -92,7 +91,7 @@ export default { * for password confirmations. */ confirm ({ value, getFormValues, name }: ConfirmValidatableData, field?: string): Promise { - return Promise.resolve((() => { + return Promise.resolve(((): boolean => { const formValues = getFormValues() let confirmationFieldName = field if (!confirmationFieldName) { @@ -168,7 +167,7 @@ export default { mime ({ value }: { value: any }, ...types: string[]): Promise { if (value instanceof FileUpload) { const files = value.getFiles() - const isMimeCorrect = (file: File) => types.includes(file.type) + const isMimeCorrect = (file: File): boolean => types.includes(file.type) const allValid: boolean = files.reduce((valid: boolean, { file }) => valid && isMimeCorrect(file), true) return Promise.resolve(allValid) @@ -181,7 +180,7 @@ export default { * Check the minimum value of a particular. */ min ({ value }: { value: any }, minimum: number | any = 1, force?: string): Promise { - return Promise.resolve((() => { + return Promise.resolve(((): boolean => { if (Array.isArray(value)) { minimum = !isNaN(minimum) ? Number(minimum) : minimum return value.length >= minimum @@ -202,7 +201,7 @@ export default { * Check the maximum value of a particular. */ max ({ value }: { value: any }, maximum: string | number = 10, force?: string): Promise { - return Promise.resolve((() => { + return Promise.resolve(((): boolean => { if (Array.isArray(value)) { maximum = !isNaN(Number(maximum)) ? Number(maximum) : maximum return value.length <= maximum @@ -239,7 +238,7 @@ export default { * Rule: must be a value */ required ({ value }: { value: any }, isRequired: string|boolean = true): Promise { - return Promise.resolve((() => { + return Promise.resolve(((): boolean => { if (!isRequired || ['no', 'false'].includes(isRequired as string)) { return true } diff --git a/src/validation/types.ts b/src/validation/types.ts index 23dd711..e8dfa1b 100644 --- a/src/validation/types.ts +++ b/src/validation/types.ts @@ -1,17 +1,17 @@ interface ValidatableData { - value: any, + value: any; } interface ValidationContext { - args: any[] - name: string - value: any + args: any[]; + name: string; + value: any; } interface ValidationError { - rule?: string - context?: any - message: string + rule?: string; + context?: any; + message: string; } export { ValidatableData } diff --git a/test/unit/FormularioForm.test.js b/test/unit/FormularioForm.test.js index c5b01e0..7d6695c 100644 --- a/test/unit/FormularioForm.test.js +++ b/test/unit/FormularioForm.test.js @@ -265,9 +265,8 @@ describe('FormularioForm', () => { const wrapper = mount(FormularioForm, { propsData: { values: { name: 'Dave Barnett', candy: true } }, slots: { default: ` - - - + + ` } }) @@ -288,12 +287,8 @@ describe('FormularioForm', () => { const wrapper = mount({ template: `
- - + +
` })