Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove underscore from expensify-common and reduce usage of lodash and jQuery #718

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0c5bc1a
Remmove undersore from ExpensiMark
Skalakid May 23, 2024
a564439
Move escape & unescape to utils file
Skalakid May 23, 2024
269d6af
remove jQuery
kosmydel May 23, 2024
d14d3bf
draft
kosmydel May 23, 2024
4ac66ff
Remove underscore from str
Skalakid May 23, 2024
27ea958
migrate Num.jsx
kosmydel May 23, 2024
d82255e
migrate Func.jsx
kosmydel May 23, 2024
2495481
PubSub draft
kosmydel May 23, 2024
c076d18
remove lodash from StepProgressBar
kosmydel May 23, 2024
1cf633d
Log.jsx
kosmydel May 23, 2024
055befe
Remove underscore from APIDeferred
Skalakid May 23, 2024
5051bc1
Remove underscore from API
Skalakid May 24, 2024
8e73a85
Remove underscore from ExpenseRule
Skalakid May 24, 2024
0d9254e
Remove underscore from fastMerge
Skalakid May 24, 2024
0e9d1fe
Remove jQuery from Func
Skalakid May 24, 2024
443eab1
Remove underscore from Logger
Skalakid May 24, 2024
27dce3c
Remove underscore from Network
Skalakid May 24, 2024
252633e
Remove underscore from PubSub
Skalakid May 24, 2024
55f4559
Remove underscore from ReportHistoryStore
Skalakid May 24, 2024
958f201
Fix ExpensiMark tests
Skalakid May 24, 2024
e6d4a8d
Remove underscore from PubSub.jsx
Skalakid May 24, 2024
1cc66f9
Reuse isFunction function from utils
Skalakid May 24, 2024
88e2ef1
Remove underscore from components
Skalakid May 24, 2024
f1b5308
Remove underscore from Templates
Skalakid May 24, 2024
4d97142
Change fix and once functions to lodash ones
Skalakid May 24, 2024
f209bdc
Update utils and eslintrc
Skalakid May 24, 2024
cbb6035
Add first review changes
Skalakid Jun 10, 2024
ca82e4f
Add second review changes
Skalakid Jun 10, 2024
691fc79
Turn off prefer-underscore-method for ts files
Skalakid Jun 11, 2024
3b30443
Remove underscore forom package.json
Skalakid Jun 11, 2024
25d0f81
Fix ESlint and TS errors
Skalakid Jun 12, 2024
ba69e50
Fix unnecessary changes after rebase
Skalakid Jun 12, 2024
798b8cf
Add review changes
Skalakid Jun 13, 2024
5ded3ae
Merge branch 'main' into remove-underscore-and-reduce-usage-of-lodash…
Skalakid Jun 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ module.exports = {
'rulesdir/no-multiple-api-calls': 'off',
'rulesdir/prefer-import-module-contents': 'off',
'no-constructor-return': 'off',
'max-classes-per-file': 'off',
'arrow-body-style': 'off',
'es/no-nullish-coalescing-operators': 'off',
'rulesdir/prefer-underscore-method': 'off',
'es/no-optional-chaining': 'off',
'import/extensions': [
'error',
'ignorePackages',
Expand Down
26 changes: 15 additions & 11 deletions __tests__/ExpensiMark-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable max-len */
import ExpensiMark from '../lib/ExpensiMark';
import _ from 'underscore';
import * as Utils from '../lib/utils';

const parser = new ExpensiMark();

Expand All @@ -17,24 +17,28 @@ test('Test text is unescaped', () => {
});

test('Test with regex Maximum regex stack depth reached error', () => {
const testString = '<h1>heading</h1> asjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfisjksksjsjssskssjskskssksksksksskdkddkddkdksskskdkdkdksskskskdkdkdkdkekeekdkddenejeodxkdndekkdjddkeemdjxkdenendkdjddekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdi.cdjd';
const parser = new ExpensiMark();
const testString =
'<h1>heading</h1> asjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfisjksksjsjssskssjskskssksksksksskdkddkddkdksskskdkdkdksskskskdkdkdkdkekeekdkddenejeodxkdndekkdjddkeemdjxkdenendkdjddekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdi.cdjd';
const expensiMarkParser = new ExpensiMark();
// Mock method modifyTextForUrlLinks to let it throw an error to test try/catch of method ExpensiMark.replace
const modifyTextForUrlLinksMock = jest.fn((a, b, c) => {throw new Error('Maximum regex stack depth reached')});
parser.modifyTextForUrlLinks = modifyTextForUrlLinksMock;
expect(parser.replace(testString)).toBe(_.escape(testString));
const modifyTextForUrlLinksMock = jest.fn(() => {
throw new Error('Maximum regex stack depth reached');
});
expensiMarkParser.modifyTextForUrlLinks = modifyTextForUrlLinksMock;
expect(expensiMarkParser.replace(testString)).toBe(Utils.escape(testString));
expect(modifyTextForUrlLinksMock).toHaveBeenCalledTimes(1);

// Mock method extractLinksInMarkdownComment to let it return undefined to test try/catch of method ExpensiMark.extractLinksInMarkdownComment
const extractLinksInMarkdownCommentMock = jest.fn((a) => undefined);
parser.extractLinksInMarkdownComment = extractLinksInMarkdownCommentMock;
expect(parser.extractLinksInMarkdownComment(testString)).toBe(undefined);
expect(parser.getRemovedMarkdownLinks(testString, 'google.com')).toStrictEqual([]);
const extractLinksInMarkdownCommentMock = jest.fn(() => undefined);
expensiMarkParser.extractLinksInMarkdownComment = extractLinksInMarkdownCommentMock;
expect(expensiMarkParser.extractLinksInMarkdownComment(testString)).toBe(undefined);
expect(expensiMarkParser.getRemovedMarkdownLinks(testString, 'google.com')).toStrictEqual([]);
expect(extractLinksInMarkdownCommentMock).toHaveBeenCalledTimes(3);
});

test('Test extract link with ending parentheses', () => {
const comment = '[Staging Detail](https://staging.new.expensify.com/details) [Staging Detail](https://staging.new.expensify.com/details)) [Staging Detail](https://staging.new.expensify.com/details)))';
const comment =
'[Staging Detail](https://staging.new.expensify.com/details) [Staging Detail](https://staging.new.expensify.com/details)) [Staging Detail](https://staging.new.expensify.com/details)))';
const links = ['https://staging.new.expensify.com/details', 'https://staging.new.expensify.com/details', 'https://staging.new.expensify.com/details'];
expect(parser.extractLinksInMarkdownComment(comment)).toStrictEqual(links);
});
Expand Down
42 changes: 22 additions & 20 deletions lib/API.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
* WIP, This is in the process of migration from web-e. Please add methods to this as is needed.|
* ----------------------------------------------------------------------------------------------
*/
import _ from 'underscore';

// Use this deferred lib so we don't have a dependency on jQuery (so we can use this module in mobile)
import {Deferred} from 'simply-deferred';
import {has} from 'lodash';
import ExpensifyAPIDeferred from './APIDeferred';
import {isWindowAvailable} from './utils';
import * as Utils from './utils';

/**
* @param {Network} network
Expand Down Expand Up @@ -40,15 +40,15 @@ export default function API(network, args) {
* Returns a promise that is rejected if a change is detected
* Otherwise, it is resolved successfully
*
* @returns {Object} $.Deferred
* @returns {Object} Deferred
*/
function isRunningLatestVersionOfCode() {
const promise = new Deferred();

network
.get('/revision.txt')
.done((codeRevision) => {
if (isWindowAvailable() && codeRevision.trim() === window.CODE_REVISION) {
if (Utils.isWindowAvailable() && codeRevision.trim() === window.CODE_REVISION) {
console.debug('Code revision is up to date');
promise.resolve();
} else {
Expand All @@ -75,7 +75,7 @@ export default function API(network, args) {
* @param {String} apiDeferred
*/
function attachJSONCodeCallbacks(apiDeferred) {
_(defaultHandlers).each((callbacks, code) => {
Object.entries(defaultHandlers).forEach(([code, callbacks]) => {
// The key, `code`, is returned as a string, so we must cast it to an Integer
const jsonCode = parseInt(code, 10);
callbacks.forEach((callback) => {
Expand Down Expand Up @@ -105,7 +105,7 @@ export default function API(network, args) {
let newParameters = {...parameters, command};

// If there was an enhanceParameters() method supplied in our args, then we will call that here
if (args && _.isFunction(args.enhanceParameters)) {
if (args && Utils.isFunction(args.enhanceParameters)) {
newParameters = args.enhanceParameters(newParameters);
}

Expand Down Expand Up @@ -153,17 +153,19 @@ export default function API(network, args) {
function requireParameters(parameterNames, parameters, commandName) {
// eslint-disable-next-line rulesdir/prefer-early-return
parameterNames.forEach((parameterName) => {
if (!_(parameters).has(parameterName) || parameters[parameterName] === null || parameters[parameterName] === undefined) {
const parametersCopy = _.clone(parameters);
if (_(parametersCopy).has('authToken')) {
parametersCopy.authToken = '<redacted>';
}
if (_(parametersCopy).has('password')) {
parametersCopy.password = '<redacted>';
}
const keys = _(parametersCopy).keys().join(', ') || 'none';
throw new Error(`Parameter ${parameterName} is required for "${commandName}". Supplied parameters: ${keys}`);
if (has(parameters, parameterName) && parameters[parameterName] !== null && parameters[parameterName] !== undefined) {
return;
}

const parametersCopy = {...parameters};
if (has(parametersCopy, 'authToken')) {
parametersCopy.authToken = '<redacted>';
}
if (has(parametersCopy, 'password')) {
parametersCopy.password = '<redacted>';
}
const keys = Object.keys(parametersCopy).join(', ') || 'none';
throw new Error(`Parameter ${parameterName} is required for "${commandName}". Supplied parameters: ${keys}`);
});
}

Expand All @@ -173,7 +175,7 @@ export default function API(network, args) {
* @param {Function} callback
*/
registerDefaultHandler(jsonCodes, callback) {
if (!_(callback).isFunction()) {
if (!Utils.isFunction(callback)) {
return;
}

Expand Down Expand Up @@ -230,7 +232,7 @@ export default function API(network, args) {

return (parameters, keepalive = false) => {
// Optional validate function for required logic before making the call. e.g. validating params in the front-end etc.
if (_.isFunction(data.validate)) {
if (Utils.isFunction(data.validate)) {
data.validate(parameters);
}

Expand Down Expand Up @@ -265,7 +267,7 @@ export default function API(network, args) {
requireParameters(['email'], parameters, commandName);

// Tell the API not to set cookies for this request
const newParameters = _.extend({api_setCookie: false}, parameters);
const newParameters = {...parameters, api_setCookie: false};

return performPOSTRequest(commandName, newParameters);
},
Expand Down Expand Up @@ -426,7 +428,7 @@ export default function API(network, args) {
const commandName = 'ResetPassword';
requireParameters(['email'], parameters, commandName);

const newParameters = _.extend({api_setCookie: false}, parameters);
const newParameters = {...parameters, api_setCookie: false};
return performPOSTRequest(commandName, newParameters);
},

Expand Down
48 changes: 24 additions & 24 deletions lib/APIDeferred.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* WIP, This is in the process of migration from web-e. Please add methods to this as is needed.|
* ----------------------------------------------------------------------------------------------
*/

import _ from 'underscore';
import {invoke, bulkInvoke} from './Func';
import {once} from 'lodash';
import * as Utils from './utils';
import * as Func from './Func';

/**
* @param {jquery.Deferred} promise
Expand Down Expand Up @@ -50,15 +50,15 @@ export default function APIDeferred(promise, extractedProperty) {
function handleError(jsonCode, response) {
// Look for handlers for this error code
const handlers = errorHandlers[jsonCode];
if (!_(handlers).isEmpty()) {
bulkInvoke(handlers, [jsonCode, response]);
if (handlers.length > 0) {
Func.bulkInvoke(handlers, [jsonCode, response]);
} else {
// No explicit handlers, call the unhandled callbacks
bulkInvoke(unhandledCallbacks, [jsonCode, response]);
Func.bulkInvoke(unhandledCallbacks, [jsonCode, response]);
}

// Always run the "fail" callbacks in case of error
bulkInvoke(failCallbacks, [jsonCode, response]);
Func.bulkInvoke(failCallbacks, [jsonCode, response]);
}

/**
Expand All @@ -73,8 +73,8 @@ export default function APIDeferred(promise, extractedProperty) {

// Figure out if we need to extract a property from the response, and if it is there.
const jsonCode = extractJSONCode(response);
const propertyRequested = !_.isNull(extractedPropertyName);
const requestedPropertyPresent = propertyRequested && response && !_.isUndefined(response[extractedPropertyName]);
const propertyRequested = !Number.isNull(extractedPropertyName);
const requestedPropertyPresent = propertyRequested && response && response[extractedPropertyName] !== undefined;
const propertyRequestedButMissing = propertyRequested && !requestedPropertyPresent;

// Save the response for any callbacks that might run in the future
Expand All @@ -86,8 +86,8 @@ export default function APIDeferred(promise, extractedProperty) {
returnedData = propertyRequested && requestedPropertyPresent ? response[extractedPropertyName] : response;

// And then run the success callbacks
bulkInvoke(doneCallbacks, [returnedData]);
} else if (!_(jsonCode).isNull() && jsonCode !== 200) {
Func.bulkInvoke(doneCallbacks, [returnedData]);
} else if (jsonCode !== null && jsonCode !== 200) {
// Exception thrown, handle it
handleError(jsonCode, response);
} else {
Expand All @@ -102,7 +102,7 @@ export default function APIDeferred(promise, extractedProperty) {
}

// Always run the "always" callbacks
bulkInvoke(alwaysCallbacks, [response]);
Func.bulkInvoke(alwaysCallbacks, [response]);
}

/**
Expand Down Expand Up @@ -133,8 +133,8 @@ export default function APIDeferred(promise, extractedProperty) {
* @returns {APIDeferred} itself, for chaining
*/
done(callback) {
if (_(callback).isFunction()) {
doneCallbacks.push(_(callback).once());
if (Utils.isFunction(callback)) {
doneCallbacks.push(once(callback));
ensureFutureCallbacksFire();
}
return this;
Expand All @@ -148,8 +148,8 @@ export default function APIDeferred(promise, extractedProperty) {
* @returns {APIDeferred} itself, for chaining
*/
always(callback) {
if (_(callback).isFunction()) {
alwaysCallbacks.push(_(callback).once());
if (Utils.isFunction(callback)) {
alwaysCallbacks.push(once(callback));
ensureFutureCallbacksFire();
}
return this;
Expand All @@ -165,7 +165,7 @@ export default function APIDeferred(promise, extractedProperty) {
* @returns {APIDeferred} itself, for chaining
*/
handle(jsonCodes, callback) {
if (_(callback).isFunction()) {
if (Utils.isFunction(callback)) {
jsonCodes.forEach((code) => {
if (code === 200) {
return;
Expand All @@ -174,7 +174,7 @@ export default function APIDeferred(promise, extractedProperty) {
if (!errorHandlers[code]) {
errorHandlers[code] = [];
}
errorHandlers[code].push(_(callback).once());
errorHandlers[code].push(once(callback));
});

ensureFutureCallbacksFire();
Expand All @@ -191,8 +191,8 @@ export default function APIDeferred(promise, extractedProperty) {
* @returns {APIDeferred} itself, for chaining
*/
unhandled(callback) {
if (_(callback).isFunction()) {
unhandledCallbacks.push(_(callback).once());
if (Utils.isFunction(callback)) {
unhandledCallbacks.push(once(callback));
ensureFutureCallbacksFire();
}
return this;
Expand All @@ -207,8 +207,8 @@ export default function APIDeferred(promise, extractedProperty) {
* @returns {APIDeferred} itself, for chaining
*/
fail(callback) {
if (_(callback).isFunction()) {
failCallbacks.push(_(callback).once());
if (Utils.isFunction(callback)) {
failCallbacks.push(once(callback));
ensureFutureCallbacksFire();
}
return this;
Expand All @@ -225,11 +225,11 @@ export default function APIDeferred(promise, extractedProperty) {
return promise.then((response) => {
const responseCode = extractJSONCode(response);

if (responseCode !== 200 || !_(callback).isFunction()) {
if (responseCode !== 200 || !Utils.isFunction(callback)) {
return;
}

invoke(callback, [response]);
Func.invoke(callback, [response]);

return this;
});
Expand Down
6 changes: 3 additions & 3 deletions lib/BrowserDetect.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isNavigatorAvailable, isWindowAvailable} from './utils';
import * as Utils from './utils';

const BROWSERS = {
EDGE: 'Edge',
Expand All @@ -15,7 +15,7 @@ const MOBILE_PLATFORMS = {
};

function searchString() {
if (!isWindowAvailable() || !isNavigatorAvailable()) {
if (!Utils.isWindowAvailable() || !Utils.isNavigatorAvailable()) {
return '';
}

Expand Down Expand Up @@ -78,7 +78,7 @@ function searchString() {
}

function getMobileDevice() {
if (!isNavigatorAvailable() || !navigator.userAgent) {
if (!Utils.isNavigatorAvailable() || !navigator.userAgent) {
return '';
}

Expand Down
8 changes: 3 additions & 5 deletions lib/ExpenseRule.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import _ from 'underscore';

export default class ExpenseRule {
/**
* Creates a new instance of this class.
*
* @param {Array} ruleArray
*/
constructor(ruleArray) {
_.each(ruleArray, (value, key) => {
ruleArray.forEach((value, key) => {
this[key] = value;
});
}
Expand All @@ -21,7 +19,7 @@ export default class ExpenseRule {
* @return {Object}
*/
getApplyWhenByField(field) {
return _.find(this.applyWhen, (conditions) => conditions.field === field) || {};
return this.applyWhen.find((conditions) => conditions.field === field) || {};
}

/**
Expand All @@ -41,7 +39,7 @@ export default class ExpenseRule {
*/
isMatch(expense) {
let isMatch = true;
_.each(this.applyWhen, (conditions) => {
this.applyWhen.forEach((conditions) => {
switch (conditions.field) {
case 'category':
if (!this.checkCondition(conditions.condition, conditions.value, expense.getCategory())) {
Expand Down
Loading
Loading