From cd18d7c29f3835e3bec90ff05af0658275641577 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Sep 2023 11:08:29 +0200 Subject: [PATCH 1/5] ref: move ValidationUtils lib to TS --- ...{ValidationUtils.js => ValidationUtils.ts} | 206 +++++------------- 1 file changed, 49 insertions(+), 157 deletions(-) rename src/libs/{ValidationUtils.js => ValidationUtils.ts} (67%) diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.ts similarity index 67% rename from src/libs/ValidationUtils.js rename to src/libs/ValidationUtils.ts index 7aded82fb0a9..d9c751d0bb75 100644 --- a/src/libs/ValidationUtils.js +++ b/src/libs/ValidationUtils.ts @@ -1,19 +1,16 @@ import {subYears, addYears, startOfDay, endOfMonth, parse, isAfter, isBefore, isValid, isWithinInterval, isSameDay, format} from 'date-fns'; -import _ from 'underscore'; import {URL_REGEX_WITH_REQUIRED_PROTOCOL} from 'expensify-common/lib/Url'; import {parsePhoneNumber} from 'awesome-phonenumber'; import CONST from '../CONST'; import * as CardUtils from './CardUtils'; import * as LoginUtils from './LoginUtils'; +import {Report} from '../types/onyx'; /** * Implements the Luhn Algorithm, a checksum formula used to validate credit card * numbers. - * - * @param {String} val - * @returns {Boolean} */ -function validateCardNumber(val) { +function validateCardNumber(val: string): boolean { let sum = 0; for (let i = 0; i < val.length; i++) { let intVal = parseInt(val.substr(i, 1), 10); @@ -30,11 +27,8 @@ function validateCardNumber(val) { /** * Validating that this is a valid address (PO boxes are not allowed) - * - * @param {String} value - * @returns {Boolean} */ -function isValidAddress(value) { +function isValidAddress(value: string): boolean { if (!CONST.REGEX.ANY_VALUE.test(value)) { return false; } @@ -44,11 +38,8 @@ function isValidAddress(value) { /** * Validate date fields - * - * @param {String|Date} date - * @returns {Boolean} true if valid */ -function isValidDate(date) { +function isValidDate(date: string | Date): boolean { if (!date) { return false; } @@ -61,11 +52,8 @@ function isValidDate(date) { /** * Validate that date entered isn't a future date. - * - * @param {String|Date} date - * @returns {Boolean} true if valid */ -function isValidPastDate(date) { +function isValidPastDate(date: string | Date): boolean { if (!date) { return false; } @@ -78,15 +66,13 @@ function isValidPastDate(date) { /** * Used to validate a value that is "required". - * - * @param {*} value - * @returns {Boolean} */ -function isRequiredFulfilled(value) { - if (_.isString(value)) { - return !_.isEmpty(value.trim()); +// TODO: Ask Fabio about this +function isRequiredFulfilled(value: string | Date | Array | Object): boolean { + if (typeof value === 'string') { + return value.trim().length > 0; } - if (_.isDate(value)) { + if (Object.prototype.toString.call(value) === '[object Date]') { return isValidDate(value); } if (_.isArray(value) || _.isObject(value)) { @@ -97,14 +83,10 @@ function isRequiredFulfilled(value) { /** * Used to add requiredField error to the fields passed. - * - * @param {Object} values - * @param {Array} requiredFields - * @returns {Object} */ -function getFieldRequiredErrors(values, requiredFields) { - const errors = {}; - _.each(requiredFields, (fieldKey) => { +function getFieldRequiredErrors(values: Record, requiredFields: string[]) { + const errors: Record = {}; + requiredFields.forEach((fieldKey) => { if (isRequiredFulfilled(values[fieldKey])) { return; } @@ -119,11 +101,8 @@ function getFieldRequiredErrors(values, requiredFields) { * 2. MM/YYYY * 3. MMYY * 4. MMYYYY - * - * @param {String} string - * @returns {Boolean} */ -function isValidExpirationDate(string) { +function isValidExpirationDate(string: string): boolean { if (!CONST.REGEX.CARD_EXPIRATION_DATE.test(string)) { return false; } @@ -136,21 +115,15 @@ function isValidExpirationDate(string) { /** * Validates that this is a valid security code * in the XXX or XXXX format. - * - * @param {String} string - * @returns {Boolean} */ -function isValidSecurityCode(string) { +function isValidSecurityCode(string: string): boolean { return CONST.REGEX.CARD_SECURITY_CODE.test(string); } /** * Validates a debit card number (15 or 16 digits). - * - * @param {String} string - * @returns {Boolean} */ -function isValidDebitCard(string) { +function isValidDebitCard(string: string): boolean { if (!CONST.REGEX.CARD_NUMBER.test(string)) { return false; } @@ -158,45 +131,26 @@ function isValidDebitCard(string) { return validateCardNumber(string); } -/** - * @param {String} code - * @returns {Boolean} - */ -function isValidIndustryCode(code) { +function isValidIndustryCode(code: string): boolean { return CONST.REGEX.INDUSTRY_CODE.test(code); } -/** - * @param {String} zipCode - * @returns {Boolean} - */ -function isValidZipCode(zipCode) { +function isValidZipCode(zipCode: string): boolean { return CONST.REGEX.ZIP_CODE.test(zipCode); } -/** - * @param {String} ssnLast4 - * @returns {Boolean} - */ -function isValidSSNLastFour(ssnLast4) { +function isValidSSNLastFour(ssnLast4: string): boolean { return CONST.REGEX.SSN_LAST_FOUR.test(ssnLast4); } -/** - * @param {String} ssnFull9 - * @returns {Boolean} - */ -function isValidSSNFullNine(ssnFull9) { +function isValidSSNFullNine(ssnFull9: string): boolean { return CONST.REGEX.SSN_FULL_NINE.test(ssnFull9); } /** * Validate that a date meets the minimum age requirement. - * - * @param {String} date - * @returns {Boolean} */ -function meetsMinimumAgeRequirement(date) { +function meetsMinimumAgeRequirement(date: string): boolean { const testDate = new Date(date); const minDate = subYears(new Date(), CONST.DATE_BIRTH.MIN_AGE_FOR_PAYMENT); return isValid(testDate) && (isSameDay(testDate, minDate) || isBefore(testDate, minDate)); @@ -204,11 +158,8 @@ function meetsMinimumAgeRequirement(date) { /** * Validate that a date meets the maximum age requirement. - * - * @param {String} date - * @returns {Boolean} */ -function meetsMaximumAgeRequirement(date) { +function meetsMaximumAgeRequirement(date: string): boolean { const testDate = new Date(date); const maxDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); return isValid(testDate) && (isSameDay(testDate, maxDate) || isAfter(testDate, maxDate)); @@ -216,13 +167,8 @@ function meetsMaximumAgeRequirement(date) { /** * Validate that given date is in a specified range of years before now. - * - * @param {String} date - * @param {Number} minimumAge - * @param {Number} maximumAge - * @returns {String|Array} */ -function getAgeRequirementError(date, minimumAge, maximumAge) { +function getAgeRequirementError(date: string, minimumAge: number, maximumAge: number): string | Array> { const currentDate = startOfDay(new Date()); const testDate = parse(date, CONST.DATE.FNS_FORMAT_STRING, currentDate); @@ -247,24 +193,17 @@ function getAgeRequirementError(date, minimumAge, maximumAge) { /** * Similar to backend, checks whether a website has a valid URL or not. * http/https/ftp URL scheme required. - * - * @param {String} url - * @returns {Boolean} */ -function isValidWebsite(url) { +function isValidWebsite(url: string): boolean { return new RegExp(`^${URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i').test(url); } -/** - * @param {Object} identity - * @returns {Object} - */ -function validateIdentity(identity) { +function validateIdentity(identity: Record): Record { const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob']; - const errors = {}; + const errors: Record = {}; // Check that all required fields are filled - _.each(requiredFields, (fieldName) => { + requiredFields.forEach((fieldName) => { if (isRequiredFulfilled(identity[fieldName])) { return; } @@ -293,12 +232,7 @@ function validateIdentity(identity) { return errors; } -/** - * @param {String} phoneNumber - * @param {Boolean} [isCountryCodeOptional] - * @returns {Boolean} - */ -function isValidUSPhone(phoneNumber = '', isCountryCodeOptional) { +function isValidUSPhone(phoneNumber = '', isCountryCodeOptional?: boolean) { const phone = phoneNumber || ''; const regionCode = isCountryCodeOptional ? CONST.COUNTRY.US : null; @@ -306,41 +240,29 @@ function isValidUSPhone(phoneNumber = '', isCountryCodeOptional) { return parsedPhoneNumber.possible && parsedPhoneNumber.regionCode === CONST.COUNTRY.US; } -/** - * @param {string} validateCode - * @returns {Boolean} - */ -function isValidValidateCode(validateCode) { - return validateCode.match(CONST.VALIDATE_CODE_REGEX_STRING); +function isValidValidateCode(validateCode: string): boolean { + return Boolean(validateCode.match(CONST.VALIDATE_CODE_REGEX_STRING)); } -/** - * @param {String} code - * @returns {Boolean} - */ -function isValidTwoFactorCode(code) { +function isValidTwoFactorCode(code: string): boolean { return Boolean(code.match(CONST.REGEX.CODE_2FA)); } /** * Checks whether a value is a numeric string including `(`, `)`, `-` and optional leading `+` - * @param {String} input - * @returns {Boolean} */ -function isNumericWithSpecialChars(input) { +function isNumericWithSpecialChars(input: string): boolean { return /^\+?[\d\\+]*$/.test(LoginUtils.getPhoneNumberWithoutSpecialChars(input)); } /** * Checks the given number is a valid US Routing Number * using ABA routingNumber checksum algorithm: http://www.brainjar.com/js/validation/ - * @param {String} number - * @returns {Boolean} */ -function isValidRoutingNumber(number) { +function isValidRoutingNumber(routingNumber: string): boolean { let n = 0; - for (let i = 0; i < number.length; i += 3) { - n += parseInt(number.charAt(i), 10) * 3 + parseInt(number.charAt(i + 1), 10) * 7 + parseInt(number.charAt(i + 2), 10); + for (let i = 0; i < routingNumber.length; i += 3) { + n += parseInt(routingNumber.charAt(i), 10) * 3 + parseInt(routingNumber.charAt(i + 1), 10) * 7 + parseInt(routingNumber.charAt(i + 2), 10); } // If the resulting sum is an even multiple of ten (but not zero), @@ -353,57 +275,39 @@ function isValidRoutingNumber(number) { /** * Checks that the provided name doesn't contain any commas or semicolons - * - * @param {String} name - * @returns {Boolean} */ -function isValidDisplayName(name) { +function isValidDisplayName(name: string): boolean { return !name.includes(',') && !name.includes(';'); } /** * Checks that the provided legal name doesn't contain special characters - * - * @param {String} name - * @returns {Boolean} */ -function isValidLegalName(name) { +function isValidLegalName(name: string): boolean { return CONST.REGEX.ALPHABETIC_AND_LATIN_CHARS.test(name); } /** * Checks if the provided string includes any of the provided reserved words - * - * @param {String} value - * @param {String[]} reservedWords - * @returns {Boolean} */ -function doesContainReservedWord(value, reservedWords) { +function doesContainReservedWord(value: string, reservedWords: string[]): boolean { const valueToCheck = value.trim().toLowerCase(); - return _.some(reservedWords, (reservedWord) => valueToCheck.includes(reservedWord.toLowerCase())); + return reservedWords.some((reservedWord) => valueToCheck.includes(reservedWord.toLowerCase())); } /** * Checks if is one of the certain names which are reserved for default rooms * and should not be used for policy rooms. - * - * @param {String} roomName - * @returns {Boolean} */ -function isReservedRoomName(roomName) { - return _.contains(CONST.REPORT.RESERVED_ROOM_NAMES, roomName); +function isReservedRoomName(roomName: string): boolean { + return CONST.REPORT.RESERVED_ROOM_NAMES.includes(roomName); } /** * Checks if the room name already exists. - * - * @param {String} roomName - * @param {Object} reports - * @param {String} policyID - * @returns {Boolean} */ -function isExistingRoomName(roomName, reports, policyID) { - return _.some(reports, (report) => report && report.policyID === policyID && report.reportName === roomName); +function isExistingRoomName(roomName: string, reports: Report[], policyID: string): boolean { + return reports.some((report) => report && report.policyID === policyID && report.reportName === roomName); } /** @@ -411,31 +315,22 @@ function isExistingRoomName(roomName, reports, policyID) { * - It starts with a hash '#' * - After the first character, it contains only lowercase letters, numbers, and dashes * - It's between 1 and MAX_ROOM_NAME_LENGTH characters long - * - * @param {String} roomName - * @returns {Boolean} */ -function isValidRoomName(roomName) { +function isValidRoomName(roomName: string): boolean { return CONST.REGEX.ROOM_NAME.test(roomName); } /** * Checks if tax ID consists of 9 digits - * - * @param {String} taxID - * @returns {Boolean} */ -function isValidTaxID(taxID) { - return taxID && CONST.REGEX.TAX_ID.test(taxID); +function isValidTaxID(taxID: string): boolean { + return CONST.REGEX.TAX_ID.test(taxID); } /** * Checks if a string value is a number. - * - * @param {String} value - * @returns {Boolean} */ -function isNumeric(value) { +function isNumeric(value: string): boolean { if (typeof value !== 'string') { return false; } @@ -444,12 +339,9 @@ function isNumeric(value) { /** * Checks that the provided accountID is a number and bigger than 0. - * - * @param {Number} accountID - * @returns {Boolean} */ -function isValidAccountRoute(accountID) { - return CONST.REGEX.NUMBER.test(accountID) && accountID > 0; +function isValidAccountRoute(accountID: number): boolean { + return CONST.REGEX.NUMBER.test(String(accountID)) && accountID > 0; } export { From d38fdd5bf7e1c48fb16db281a54ae458f3965f6b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Sep 2023 16:26:31 +0200 Subject: [PATCH 2/5] fix: fixed problem --- src/libs/ValidationUtils.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index d9c751d0bb75..f38899c1e245 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -1,6 +1,9 @@ import {subYears, addYears, startOfDay, endOfMonth, parse, isAfter, isBefore, isValid, isWithinInterval, isSameDay, format} from 'date-fns'; import {URL_REGEX_WITH_REQUIRED_PROTOCOL} from 'expensify-common/lib/Url'; import {parsePhoneNumber} from 'awesome-phonenumber'; +import isDate from 'lodash/isDate'; +import isEmpty from 'lodash/isEmpty'; +import isObject from 'lodash/isObject'; import CONST from '../CONST'; import * as CardUtils from './CardUtils'; import * as LoginUtils from './LoginUtils'; @@ -67,16 +70,16 @@ function isValidPastDate(date: string | Date): boolean { /** * Used to validate a value that is "required". */ -// TODO: Ask Fabio about this -function isRequiredFulfilled(value: string | Date | Array | Object): boolean { +function isRequiredFulfilled(value: string | Date | unknown[] | Record): boolean { if (typeof value === 'string') { return value.trim().length > 0; } - if (Object.prototype.toString.call(value) === '[object Date]') { + + if (isDate(value)) { return isValidDate(value); } - if (_.isArray(value) || _.isObject(value)) { - return !_.isEmpty(value); + if (Array.isArray(value) || isObject(value)) { + return !isEmpty(value); } return Boolean(value); } @@ -234,7 +237,7 @@ function validateIdentity(identity: Record): Record Date: Wed, 20 Sep 2023 16:31:17 +0200 Subject: [PATCH 3/5] fix: last typescript error --- src/libs/ValidationUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index f38899c1e245..59adbc7435b5 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -303,7 +303,7 @@ function doesContainReservedWord(value: string, reservedWords: string[]): boolea * and should not be used for policy rooms. */ function isReservedRoomName(roomName: string): boolean { - return CONST.REPORT.RESERVED_ROOM_NAMES.includes(roomName); + return (CONST.REPORT.RESERVED_ROOM_NAMES as readonly string[]).includes(roomName); } /** From 38e537d110b7c0162ee70e88015a35cd9d60f782 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 21 Sep 2023 14:37:36 +0200 Subject: [PATCH 4/5] fix: resolve comments --- src/libs/ValidationUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index d01deed5f05a..962cb43fd43a 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -13,10 +13,10 @@ import {Report} from '../types/onyx'; * Implements the Luhn Algorithm, a checksum formula used to validate credit card * numbers. */ -function validateCardNumber(val: string): boolean { +function validateCardNumber(value: string): boolean { let sum = 0; - for (let i = 0; i < val.length; i++) { - let intVal = parseInt(val.substr(i, 1), 10); + for (let i = 0; i < value.length; i++) { + let intVal = parseInt(value.substr(i, 1), 10); if (i % 2 === 0) { intVal *= 2; if (intVal > 9) { @@ -235,7 +235,7 @@ function validateIdentity(identity: Record): Record Date: Thu, 21 Sep 2023 16:37:27 +0200 Subject: [PATCH 5/5] fix: resolve comments --- src/libs/ValidationUtils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index 962cb43fd43a..80b15690ac46 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -8,6 +8,7 @@ import CONST from '../CONST'; import * as CardUtils from './CardUtils'; import * as LoginUtils from './LoginUtils'; import {Report} from '../types/onyx'; +import * as OnyxCommon from '../types/onyx/OnyxCommon'; /** * Implements the Luhn Algorithm, a checksum formula used to validate credit card @@ -87,8 +88,8 @@ function isRequiredFulfilled(value: string | Date | unknown[] | Record, requiredFields: string[]) { - const errors: Record = {}; +function getFieldRequiredErrors(values: OnyxCommon.Errors, requiredFields: string[]) { + const errors: OnyxCommon.Errors = {}; requiredFields.forEach((fieldKey) => { if (isRequiredFulfilled(values[fieldKey])) { return;