diff --git a/lib/CONST.d.ts b/lib/CONST.d.ts
index 4351ca7c..77974b46 100644
--- a/lib/CONST.d.ts
+++ b/lib/CONST.d.ts
@@ -254,11 +254,11 @@ export declare const CONST: {
/**
* Regex matching a text containing general phone number
*/
- readonly GENERAL_PHONE_PART: RegExp,
+ readonly GENERAL_PHONE_PART: RegExp;
/**
- * Regex matching a text containing an E.164 format phone number
- */
- readonly PHONE_PART: "\\+[1-9]\\d{1,14}";
+ * Regex matching a text containing an E.164 format phone number
+ */
+ readonly PHONE_PART: '\\+[1-9]\\d{1,14}';
/**
* Regular expression to check that a basic name is valid
*/
diff --git a/lib/CONST.jsx b/lib/CONST.jsx
index 49cebfb6..b68261b4 100644
--- a/lib/CONST.jsx
+++ b/lib/CONST.jsx
@@ -291,15 +291,15 @@ export const CONST = {
EMAIL_PART: EMAIL_BASE_REGEX,
/**
- * Regex matching a text containing general phone number
- *
- * @type RegExp
- */
+ * Regex matching a text containing general phone number
+ *
+ * @type RegExp
+ */
GENERAL_PHONE_PART: /^(\+\d{1,2}\s?)?(\(\d{3}\)|\d{3})[\s.-]?\d{3}[\s.-]?\d{4}$/,
/**
- * Regex matching a text containing an E.164 format phone number
- */
+ * Regex matching a text containing an E.164 format phone number
+ */
PHONE_PART: '\\+[1-9]\\d{1,14}',
/**
@@ -368,7 +368,8 @@ export const CONST = {
*
* @type RegExp
*/
- EMOJI_RULE: /[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/gu,
+ EMOJI_RULE:
+ /[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/gu,
},
REPORT: {
diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js
index 998acf48..79d62e8b 100644
--- a/lib/ExpensiMark.js
+++ b/lib/ExpensiMark.js
@@ -21,7 +21,7 @@ export default class ExpensiMark {
{
name: 'emoji',
regex: Constants.CONST.REG_EXP.EMOJI_RULE,
- replacement: match => `
${isStartingWithSpace ? ' ' : ''}${replacedText}`; @@ -426,8 +429,8 @@ export default class ExpensiMark { .trim() .split('\n'); - resultString = _.map(resultString, (m) => `> ${m}`).join('\n'); - + const prependGreaterSign = (m) => `> ${m}`; + resultString = _.map(resultString, prependGreaterSign).join('\n'); // We want to keep
tag here and let method replaceBlockElementWithNewLine to handle the line break later return `${resultString}`; }, @@ -462,7 +465,7 @@ export default class ExpensiMark { } return `!(${g2})`; - } + }, }, { name: 'reportMentions', @@ -559,7 +562,7 @@ export default class ExpensiMark { name: 'stripTag', regex: /(<([^>]+)>)/gi, replacement: '', - } + }, ]; /** @@ -570,9 +573,16 @@ export default class ExpensiMark { /** * The list of rules that have to be applied when shouldKeepWhitespace flag is true. - * @type {Object[]} + * @param {Object} rule - The rule to check. + * @returns {boolean} Returns true if the rule should be applied, otherwise false. + */ + this.filterRules = (rule) => !_.includes(this.whitespaceRulesToDisable, rule.name); + + /** + * Filters rules to determine which should keep whitespace. + * @returns {Object[]} The filtered rules. */ - this.shouldKeepWhitespaceRules = _.filter(this.rules, (rule) => !_.includes(this.whitespaceRulesToDisable, rule.name)); + this.shouldKeepWhitespaceRules = _.filter(this.rules, this.filterRules); /** * maxQuoteDepth is the maximum depth of nested quotes that we want to support. @@ -589,14 +599,16 @@ export default class ExpensiMark { getHtmlRuleset(filterRules, disabledRules, shouldKeepRawInput) { let rules = this.rules; - if(shouldKeepRawInput) { + const hasRuleName = (rule) => _.contains(filterRules, rule.name); + const hasDisabledRuleName = (rule) => !_.contains(disabledRules, rule.name); + if (shouldKeepRawInput) { rules = this.shouldKeepWhitespaceRules; } if (!_.isEmpty(filterRules)) { - rules = _.filter(this.rules, (rule) => _.contains(filterRules, rule.name)); + rules = _.filter(this.rules, hasRuleName); } if (!_.isEmpty(disabledRules)) { - rules = _.filter(rules, (rule) => !_.contains(disabledRules, rule.name)); + rules = _.filter(rules, hasDisabledRuleName); } return rules; } @@ -619,24 +631,25 @@ export default class ExpensiMark { let replacedText = shouldEscapeText ? _.escape(text) : text; const rules = this.getHtmlRuleset(filterRules, disabledRules, shouldKeepRawInput); - try { - rules.forEach((rule) => { - // Pre-process text before applying regex - if (rule.pre) { - replacedText = rule.pre(replacedText); - } - const replacementFunction = shouldKeepRawInput && rule.rawInputReplacement ? rule.rawInputReplacement : rule.replacement; - if (rule.process) { - replacedText = rule.process(replacedText, replacementFunction, shouldKeepRawInput); - } else { - replacedText = replacedText.replace(rule.regex, replacementFunction); - } + const processRule = (rule) => { + // Pre-process text before applying regex + if (rule.pre) { + replacedText = rule.pre(replacedText); + } + const replacementFunction = shouldKeepRawInput && rule.rawInputReplacement ? rule.rawInputReplacement : rule.replacement; + if (rule.process) { + replacedText = rule.process(replacedText, replacementFunction, shouldKeepRawInput); + } else { + replacedText = replacedText.replace(rule.regex, replacementFunction); + } - // Post-process text after applying regex - if (rule.post) { - replacedText = rule.post(replacedText); - } - }); + // Post-process text after applying regex + if (rule.post) { + replacedText = rule.post(replacedText); + } + }; + try { + rules.forEach(processRule); } catch (e) { // eslint-disable-next-line no-console console.warn('Error replacing text with html in ExpensiMark.replace', {error: e}); @@ -811,7 +824,8 @@ export default class ExpensiMark { let splitText = htmlString.split( /|<\/div>| |\n<\/comment>|<\/comment>| |<\/h1>|
|<\/h2>|
|<\/h3>|
|<\/h4>|
|<\/h5>|
|<\/h6>|
|<\/p>|
|<\/li>| |<\/blockquote>/, ); - splitText = _.map(splitText, (text) => Str.stripHTML(text)); + const stripHTML = (text) => Str.stripHTML(text); + splitText = _.map(splitText, stripHTML); let joinedText = ''; // Delete whitespace at the end @@ -822,7 +836,7 @@ export default class ExpensiMark { splitText.pop(); } - splitText.forEach((text, index) => { + const processText = (text, index) => { if (text.trim().length === 0 && !text.match(/\n/)) { return; } @@ -833,7 +847,9 @@ export default class ExpensiMark { } else { joinedText += `${text}\n`; } - }); + }; + + splitText.forEach(processText); return joinedText; } @@ -856,7 +872,7 @@ export default class ExpensiMark { generatedMarkdown = parseBodyTag[2]; } - this.htmlToMarkdownRules.forEach((rule) => { + const processRule = (rule) => { // Pre-processes input HTML before applying regex if (rule.pre) { generatedMarkdown = rule.pre(generatedMarkdown); @@ -865,7 +881,9 @@ export default class ExpensiMark { // if replacement is a function, we want to pass optional extras to it const replacementFunction = typeof rule.replacement === 'function' ? (...args) => rule.replacement(...args, extras) : rule.replacement; generatedMarkdown = generatedMarkdown.replace(rule.regex, replacementFunction); - }); + }; + + this.htmlToMarkdownRules.forEach(processRule); return Str.htmlDecode(this.replaceBlockElementWithNewLine(generatedMarkdown)); } @@ -879,13 +897,13 @@ export default class ExpensiMark { */ htmlToText(htmlString, extras) { let replacedText = htmlString; - - this.htmlToTextRules.forEach((rule) => { - + const processRule = (rule) => { // if replacement is a function, we want to pass optional extras to it const replacementFunction = typeof rule.replacement === 'function' ? (...args) => rule.replacement(...args, extras) : rule.replacement; replacedText = replacedText.replace(rule.regex, replacementFunction); - }); + }; + + this.htmlToTextRules.forEach(processRule); // Unescaping because the text is escaped in 'replace' function // We use 'htmlDecode' instead of 'unescape' to replace entities like ' ' @@ -968,13 +986,14 @@ export default class ExpensiMark { formatTextForQuote(regex, textToCheck, replacement) { if (textToCheck.match(regex)) { // Remove '>' and trim the spaces between nested quotes - let textToFormat = _.map(textToCheck.split('\n'), (row) => { + const formatRow = (row) => { const quoteContent = row[4] === ' ' ? row.substr(5) : row.substr(4); if (quoteContent.trimStart().startsWith('>')) { return quoteContent.trimStart(); } return quoteContent; - }).join('\n'); + }; + let textToFormat = _.map(textToCheck.split('\n'), formatRow).join('\n'); // Remove leading and trailing line breaks textToFormat = textToFormat.replace(/^\n+|\n+$/g, ''); @@ -1029,13 +1048,13 @@ export default class ExpensiMark { extractLinksInMarkdownComment(comment) { try { const htmlString = this.replace(comment, {filterRules: ['link']}); - // We use same anchor tag template as link and autolink rules to extract link const regex = new RegExp(``, 'gi'); const matches = [...htmlString.matchAll(regex)]; // Element 1 from match is the regex group if it exists which contains the link URLs - const links = _.map(matches, (match) => Str.sanitizeURL(match[1])); + const sanitizeMatch = (match) => Str.sanitizeURL(match[1]); + const links = _.map(matches, sanitizeMatch); return links; } catch (e) { // eslint-disable-next-line no-console diff --git a/lib/components/StepProgressBar.js b/lib/components/StepProgressBar.js index 34373814..d682ef4a 100644 --- a/lib/components/StepProgressBar.js +++ b/lib/components/StepProgressBar.js @@ -23,37 +23,36 @@ const propTypes = { * @return {React.Component} */ function StepProgressBar({steps, currentStep}) { - const currentStepIndex = Math.max( - 0, - _.findIndex(steps, (step) => step.id === currentStep), - ); + const isCurrentStep = (step) => step.id === currentStep; + const currentStepIndex = Math.max(0, _.findIndex(steps, isCurrentStep)); + + const renderStep = (step, i) => { + let status = currentStepIndex === i ? UIConstants.UI.ACTIVE : ''; + if (currentStepIndex > i) { + status = 'complete'; + } + + return ( +++ ); + }; + return (+ +++ {step.title} ++-); } diff --git a/lib/components/form/element/combobox.js b/lib/components/form/element/combobox.js index 43cc6efe..6843cd51 100644 --- a/lib/components/form/element/combobox.js +++ b/lib/components/form/element/combobox.js @@ -264,6 +264,14 @@ class Combobox extends React.Component { // Get the scroll position of the currently selected value this.scrollPosition = this.dropDown.scrollTop; + const stateUpdateCallback = () => { + this.resetClickAwayHandler(); + + // Fire our onChange callback + this.initialValue = selectedValue; + this.props.onChange(selectedValue); + }; + this.setState( { options: this.getTruncatedOptions(selectedValue), @@ -274,13 +282,7 @@ class Combobox extends React.Component { isDropdownOpen: false, hasError: get(currentlySelectedOption, 'hasError', false), }, - () => { - this.resetClickAwayHandler(); - - // Fire our onChange callback - this.initialValue = selectedValue; - this.props.onChange(selectedValue); - }, + stateUpdateCallback, ); } @@ -323,6 +325,34 @@ class Combobox extends React.Component { let currentValue; let currentText; + const updateStateDownKey = (state) => ({ + focusedIndex: newFocusedIndex, + options: state.options, + isDropdownOpen: true, + }); + + const updateStateUpKey = (state) => ({ + focusedIndex: newFocusedIndex, + options: state.options, + isDropdownOpen: true, + }); + + const updateStateEnterKey = (state) => ({ + options: this.getTruncatedOptions(currentValue), + selectedIndex: state.focusedIndex, + currentValue, + currentText, + isDropdownOpen: false, + }); + + const resetStateEnterKey = () => { + this.resetClickAwayHandler(); + + // Fire our onChange callback + this.props.onChange(currentValue); + this.initialValue = currentValue; + }; + // Handle the arrow keys switch (e.which) { case 40: @@ -336,14 +366,8 @@ class Combobox extends React.Component { } this.switchFocusedIndex(oldFocusedIndex, newFocusedIndex); - this.setState( - (state) => ({ - focusedIndex: newFocusedIndex, - options: state.options, - isDropdownOpen: true, - }), - this.resetClickAwayHandler, - ); + + this.setState(updateStateDownKey, this.resetClickAwayHandler); this.stopEvent(e); break; @@ -358,14 +382,7 @@ class Combobox extends React.Component { } this.switchFocusedIndex(oldFocusedIndex, newFocusedIndex); - this.setState( - (state) => ({ - focusedIndex: newFocusedIndex, - options: state.options, - isDropdownOpen: true, - }), - this.resetClickAwayHandler, - ); + this.setState(updateStateUpKey, this.resetClickAwayHandler); this.stopEvent(e); break; @@ -390,22 +407,7 @@ class Combobox extends React.Component { currentText = currentValue; } - this.setState( - (state) => ({ - options: this.getTruncatedOptions(currentValue), - selectedIndex: state.focusedIndex, - currentValue, - currentText, - isDropdownOpen: false, - }), - () => { - this.resetClickAwayHandler(); - - // Fire our onChange callback - this.props.onChange(currentValue); - this.initialValue = currentValue; - }, - ); + this.setState(updateStateEnterKey, resetStateEnterKey); this.stopEvent(e); break; @@ -443,8 +445,10 @@ class Combobox extends React.Component { const value = this.props.value || this.props.defaultValue || ''; const currentValue = this.initialValue || value; + const matchingOptionWithoutSMSDomain = (o) => (Str.isString(o) ? Str.removeSMSDomain(o.value) : o.value) === currentValue && !o.isFake; + // We use removeSMSDomain here in case currentValue is a phone number - let defaultSelectedOption = _(this.options).find((o) => (Str.isString(o) ? Str.removeSMSDomain(o.value) : o.value) === currentValue && !o.isFake); + let defaultSelectedOption = _(this.options).find(matchingOptionWithoutSMSDomain); // If no default was found and initialText was present then we can use initialText values if (!defaultSelectedOption && this.initialText) { @@ -475,32 +479,30 @@ class Combobox extends React.Component { const alreadySelected = newAlreadySelectedOptions || this.props.alreadySelectedOptions; // Get the divider index if we have one - const dividerIndex = _.findIndex(this.options, (option) => option.divider); - + const findDivider = (option) => option.divider; + const dividerIndex = _.findIndex(this.options, findDivider); // Split into two arrays everything before and after the divider (if the divider does not exist then we'll return a single array) const splitOptions = dividerIndex ? [this.options.slice(0, dividerIndex + 1), this.options.slice(dividerIndex + 1)] : [this.options]; + const formatOption = (option) => ({ + focused: false, + isSelected: option.selected && (_.isEqual(option.value, currentValue) || Boolean(_.findWhere(alreadySelected, {value: option.value}))), + ...option, + }); + + const sortByOption = (o) => { + // Unselectable text-only entries (isFake: true) go to the bottom and selected entries go to the top only if alwaysShowSelectedOnTop was passed + if (o.showLast) { + return 2; + } + + return o.isSelected && this.props.alwaysShowSelectedOnTop ? 0 : 1; + }; + // Take each array and format it, sort it, and move selected items to top (if applicable) - const truncatedOptions = _.chain(splitOptions) - .map((array) => - _.chain(array) - .map((option) => ({ - focused: false, - isSelected: option.selected && (_.isEqual(option.value, currentValue) || Boolean(_.findWhere(alreadySelected, {value: option.value}))), - ...option, - })) - .sortBy((o) => { - // Unselectable text-only entries (isFake: true) go to the bottom and selected entries go to the top only if alwaysShowSelectedOnTop was passed - if (o.showLast) { - return 2; - } - return o.isSelected && this.props.alwaysShowSelectedOnTop ? 0 : 1; - }) - .first(this.props.maxItemsToShow) - .value(), - ) - .flatten() - .value(); + const formatOptions = (array) => _.chain(array).map(formatOption).sortBy(sortByOption).first(this.props.maxItemsToShow).value(); + + const truncatedOptions = _.chain(splitOptions).map(formatOptions).flatten().value(); if (!truncatedOptions.length) { truncatedOptions.push({ @@ -543,20 +545,25 @@ class Combobox extends React.Component { const optionMatchingVal = _.findWhere(this.options, {value: val}); const currentText = get(optionMatchingVal, 'text', ''); - this.initialValue = val; - this.setState((state) => ({ + const deselectOption = (initialOption) => { + const option = initialOption; + const isSelected = _.isEqual(option.value, val); + option.isSelected = isSelected || Boolean(_.findWhere(this.props.alreadySelectedOptions, {value: option.value})); + + return option; + }; + + const deselectOptions = (options) => _(options).map(deselectOption); + + const setValueState = (state) => ({ currentValue: val, currentText, - // Deselect all other options but the one matching our value - options: _(state.options).map((o) => { - const option = o; - const isSelected = _.isEqual(option.value, val); - option.isSelected = isSelected || Boolean(_.findWhere(this.props.alreadySelectedOptions, {value: option.value})); - - return option; - }), - })); + options: deselectOptions(state.options), + }); + + this.initialValue = val; + this.setState(setValueState); } /** @@ -601,14 +608,16 @@ class Combobox extends React.Component { return; } - this.setState((state) => { + const setValueState = (state) => { const newOptions = [...state.options]; newOptions[state.selectedIndex].isSelected = false; return { options: newOptions, }; - }); + }; + + this.setState(setValueState); } /** @@ -618,7 +627,7 @@ class Combobox extends React.Component { * @param {number} newFocusedIndex */ switchFocusedIndex(oldFocusedIndex, newFocusedIndex) { - this.setState((state) => { + const setFocusedState = (state) => { const newOptions = [...state.options]; newOptions[oldFocusedIndex].focused = false; newOptions[newFocusedIndex].focused = true; @@ -626,7 +635,8 @@ class Combobox extends React.Component { return { options: newOptions, }; - }); + }; + this.setState(setFocusedState); } /** @@ -641,20 +651,27 @@ class Combobox extends React.Component { this.options = newOptions; } const state = this.getStartState(noDefaultValue, this.options, newAlreadySelectedOptions); - this.setState(state, () => this.props.onDropdownStateChange(Boolean(state.isDropdownOpen))); + const handleDropdownStateChange = () => { + this.props.onDropdownStateChange(Boolean(state.isDropdownOpen)); + }; + this.setState(state, handleDropdownStateChange); } /** * When the dropdown is closed, we reset the focused property of all of our options */ resetFocusedElements() { - this.setState((state) => ({ - options: _(state.options).map((o) => { - const option = o; - option.focused = false; - return option; - }), - })); + const resetFocusedProperty = (o) => { + const option = o; + option.focused = false; + return option; + }; + + const setValueState = (state) => ({ + options: _(state.options).map(resetFocusedProperty), + }); + + this.setState(setValueState); } /** @@ -696,16 +713,16 @@ class Combobox extends React.Component { if (this.state.isDropdownOpen) { return; } - this.setState( - { - isDropdownOpen: true, - }, - () => { - this.props.onDropdownStateChange(true); - this.resetClickAwayHandler(); - $(this.value).focus().select(); - }, - ); + const setValueState = () => ({ + isDropdownOpen: true, + }); + + const resetState = () => { + this.props.onDropdownStateChange(true); + this.resetClickAwayHandler(); + $(this.value).focus().select(); + }; + this.setState(setValueState, resetState); } /** @@ -725,15 +742,16 @@ class Combobox extends React.Component { return; } - this.setState( - { - isDropdownOpen: false, - }, - () => { - this.props.onDropdownStateChange(false); - this.resetClickAwayHandler(); - }, - ); + const setValueState = () => ({ + isDropdownOpen: false, + }); + + const resetState = () => { + this.props.onDropdownStateChange(false); + this.resetClickAwayHandler(); + }; + + this.setState(setValueState, resetState); // The value a user selects is set in state prior to this function running so we want to always treat this as if // it were just a blur event and reset the input to an empty value and then let onChange handle showing the proper value @@ -835,11 +853,13 @@ class Combobox extends React.Component { } matches = Array.from(matches); - const options = _(matches).map((option) => ({ + const formatOption = (option) => ({ focused: false, isSelected: _.isEqual(option.value ? option.value.toUpperCase : '', value.toUpperCase()) || Boolean(_.findWhere(this.props.alreadySelectedOptions, {value: option.value})), ...option, - })); + }); + + const options = _(matches).map(formatOption); // Focus the first option if there is one and show a message dependent on what options are present if (options.length) { @@ -863,8 +883,7 @@ class Combobox extends React.Component { showLast: true, }); } - - this.setState((state) => { + const setValueState = (state) => { let shouldShowDropdown = state.isDropdownOpen; // If we don't have an empty value, show the dropdown. @@ -879,7 +898,9 @@ class Combobox extends React.Component { focusedIndex: 0, options, }; - }, this.resetClickAwayHandler); + }; + + this.setState(setValueState, this.resetClickAwayHandler); } render() { diff --git a/lib/components/form/element/dropdown.js b/lib/components/form/element/dropdown.js index bc040a71..df78770e 100644 --- a/lib/components/form/element/dropdown.js +++ b/lib/components/form/element/dropdown.js @@ -49,34 +49,45 @@ const defaultProps = { }; class DropDown extends React.Component { - /** - * Handle what happens when an option is clicked on in the dropdown - * @param {Object} option - */ + constructor(props) { + super(props); + this.handleClick = this.handleClick.bind(this); + this.renderOption = this.renderOption.bind(this); + } + handleClick(option) { - // Don't do anything if the option can't be selected if (option.isSelectable === false || option.disabled) { return; } - this.props.onChange(option.value); } - render() { + renderOption(option) { return ( -- {_.map(steps, (step, i) => { - let status = currentStepIndex === i ? UIConstants.UI.ACTIVE : ''; - if (currentStepIndex > i) { - status = 'complete'; - } - - return ( -+-- ); - })} -- --- {step.title} --{_.map(steps, renderStep)}- {_(this.props.options).map((option) => ( -
+this.handleClick(option)} - /> - ))} - this.handleClick(option)} + > + {option.children} + ); } + + render() { + const {options, extraClasses} = this.props; + return{_.map(options, this.renderOption)}
; + } } DropDown.propTypes = propTypes; diff --git a/lib/jquery.expensifyIframify.js b/lib/jquery.expensifyIframify.js index bcab91f9..d44c0b1e 100644 --- a/lib/jquery.expensifyIframify.js +++ b/lib/jquery.expensifyIframify.js @@ -347,10 +347,11 @@ export default { } else if (!name) { eventHandlers = {}; } else { - _.each(eventHandlers, (obj) => { + const removeEventHandler = (obj) => { const object = obj; delete object[name]; - }); + }; + _.each(eventHandlers, removeEventHandler); } }, diff --git a/lib/mixins/validationClasses.js b/lib/mixins/validationClasses.js index ff647770..37970788 100644 --- a/lib/mixins/validationClasses.js +++ b/lib/mixins/validationClasses.js @@ -14,10 +14,20 @@ export default { this.setState(this.getInitialState()); }, + /** + * Update the error state of this element + * + * @param {object} state - The current state of the component. + * @returns {object} The updated state with modified classes. + */ + handleErrorStateUpdate: (state) => ({ + classes: cn(state.classes, CONST.UI.ERROR), + }), + /** * Display the error state of this element */ showError() { - this.setState((state) => ({classes: cn(state.classes, CONST.UI.ERROR)})); + this.setState(this.handleErrorStateUpdate); }, }; diff --git a/lib/str.js b/lib/str.js index a360a51c..f9978b4e 100644 --- a/lib/str.js +++ b/lib/str.js @@ -625,7 +625,8 @@ const Str = { * @returns {String} Uppercase worded string */ ucwords(str) { - return String(str).replace(/^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, ($1) => $1.toUpperCase()); + const capitalize = ($1) => $1.toUpperCase(); + return String(str).replace(/^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, capitalize); }, /** @@ -1006,12 +1007,14 @@ const Str = { */ matchAll(str, regex) { const matches = []; - str.replace(regex, (...args) => { + const collectMatches = (...args) => { const match = Array.prototype.slice.call(args, 0, -2); match.input = args[args.length - 1]; match.index = args[args.length - 2]; matches.push(match); - }); + }; + + str.replace(regex, collectMatches); return matches; }, @@ -1144,4 +1147,4 @@ const Str = { }, }; -export default Str; \ No newline at end of file +export default Str; diff --git a/package-lock.json b/package-lock.json index 7c950aa4..d4ae774a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "babel-jest": "^29.0.0", "babelify": "10.0.0", "eslint": "^7.15.0", - "eslint-config-expensify": "^2.0.44", + "eslint-config-expensify": "^2.0.45", "eslint-config-prettier": "^8.10.0", "eslint-plugin-jest": "^24.7.0", "grunt": "1.6.1", @@ -4741,9 +4741,9 @@ } }, "node_modules/eslint-config-expensify": { - "version": "2.0.44", - "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.44.tgz", - "integrity": "sha512-fwa7lcQk7llYgqcWA1TX4kcSigYqSVkKGk+anODwYlYSbVbXwzzkQsncsaiWVTM7+eJdk46GmWPeiMAWOGWPvw==", + "version": "2.0.45", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.45.tgz", + "integrity": "sha512-WNnsXx88NBAV+hH5hRU/TGJjaLYw8VtOBHTvtOyeH3Gslj7eNT7cGCvJNZSDBOmT9dTyjlxqZtW2LSf7hWsEuw==", "dev": true, "dependencies": { "@lwc/eslint-plugin-lwc": "^1.7.2", diff --git a/package.json b/package.json index c3c94e01..b06492f1 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "babel-jest": "^29.0.0", "babelify": "10.0.0", "eslint": "^7.15.0", - "eslint-config-expensify": "^2.0.44", + "eslint-config-expensify": "^2.0.45", "eslint-config-prettier": "^8.10.0", "eslint-plugin-jest": "^24.7.0", "grunt": "1.6.1",