From 34211f27d1c3ee0d5402bc6d92409588e3ae5913 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 29 Apr 2024 11:23:34 +0200 Subject: [PATCH 001/168] feat: limit accepted syntax for code fence --- lib/ExpensiMark.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index ae306472..0f203ef4 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -32,7 +32,7 @@ export default class ExpensiMark { name: 'codeFence', // ` is a backtick symbol we are matching on three of them before then after a new line character - regex: /(```(?:\r\n|\n)?)((?:\s*?(?!(?:\r\n|\n)?```(?!`))[\S])+\s*?)((?=(?:\r\n|\n)?)```)/g, + regex: /(```(?:\r\n|\n))((?:\s*?(?!(?:\r\n|\n)?```(?!`))[\S])+\s*?)((?:\r\n|\n)```)/g, // We're using a function here to perform an additional replace on the content // inside the backticks because Android is not able to use
 tags and does
@@ -45,7 +45,7 @@ export default class ExpensiMark {
                     return `
${group}
`; }, rawInputReplacement: (match, __, textWithinFences) => { - const withinFences = match.replace(/(?:```)([\s\S]*?)(?:```)/g, '$1'); + const withinFences = match.replace(/(?:```)([\s\S]*?)(?:```)/g, '$1').replace(/|<\/emoji>/g, ''); const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, ' '); return `
${group}
`; }, From a4928cb0ee7a7f61ccf5f085eb252cad10f347c2 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 6 May 2024 20:25:51 +0200 Subject: [PATCH 002/168] fix: failing tests --- __tests__/ExpensiMark-HTML-test.js | 156 ++++++++++++----------------- lib/ExpensiMark.js | 2 +- 2 files changed, 64 insertions(+), 94 deletions(-) diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index eb72e625..82b624b5 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -23,8 +23,8 @@ test('Test multi-line bold markdown replacement', () => { test('Test bold within code blocks is skipped', () => { - const testString = 'bold\n```*not a bold*```\nThis is *bold*'; - const replacedString = 'bold
*not a bold*
This is bold'; + const testString = 'bold\n```\n*not a bold*\n```\nThis is *bold*'; + const replacedString = 'bold
*not a bold*
This is bold'; expect(parser.replace(testString)).toBe(replacedString); }); @@ -202,14 +202,14 @@ test('Test markdown replacement for emails wrapped in bold/strikethrough/italic // Check emails within other markdown test('Test emails within other markdown', () => { const testString = '> test@example.com\n' - + '```test@example.com```\n' + + '```\ntest@example.com\n```\n' + '`test@example.com`\n' + '_test@example.com_ ' + '_test@example.com__ ' + '__test@example.com__ ' + '__test@example.com_'; const result = '
test@example.com
' - + '
test@example.com
' + + '
test@example.com
' + 'test@example.com
' + 'test@example.com ' + 'test@example.com_ ' @@ -412,30 +412,12 @@ test('Test period replacements', () => { test('Test code fencing', () => { let codeFenceExampleMarkdown = '```\nconst javaScript = \'javaScript\'\n```'; expect(parser.replace(codeFenceExampleMarkdown)).toBe('
const javaScript = 'javaScript'
'); - - codeFenceExampleMarkdown = '```const javaScript = \'javaScript\'\n```'; - expect(parser.replace(codeFenceExampleMarkdown)).toBe('
const javaScript = 'javaScript'
'); - - codeFenceExampleMarkdown = '```\nconst javaScript = \'javaScript\'```'; - expect(parser.replace(codeFenceExampleMarkdown)).toBe('
const javaScript = 'javaScript'
'); - - codeFenceExampleMarkdown = '```const javaScript = \'javaScript\'```'; - expect(parser.replace(codeFenceExampleMarkdown)).toBe('
const javaScript = 'javaScript'
'); }); test('Test code fencing with spaces and new lines', () => { let codeFenceExample = '```\nconst javaScript = \'javaScript\'\n const php = \'php\'\n```'; expect(parser.replace(codeFenceExample)).toBe('
const javaScript = 'javaScript'
const php = 'php'
'); - codeFenceExample = '```const javaScript = \'javaScript\'\n const php = \'php\'\n```'; - expect(parser.replace(codeFenceExample)).toBe('
const javaScript = 'javaScript'
const php = 'php'
'); - - codeFenceExample = '```\nconst javaScript = \'javaScript\'\n const php = \'php\'```'; - expect(parser.replace(codeFenceExample)).toBe('
const javaScript = 'javaScript'
const php = 'php'
'); - - codeFenceExample = '```const javaScript = \'javaScript\'\n const php = \'php\'```'; - expect(parser.replace(codeFenceExample)).toBe('
const javaScript = 'javaScript'
const php = 'php'
'); - codeFenceExample = '```\n\n# test\n\n```'; expect(parser.replace(codeFenceExample)).toBe('

# test

'); @@ -490,28 +472,19 @@ test('Test inline code blocks with two backticks', () => { test('Test code fencing with ExpensiMark syntax inside', () => { let codeFenceExample = '```\nThis is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)\n```'; expect(parser.replace(codeFenceExample)).toBe('
This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)
'); - - codeFenceExample = '```This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)\n```'; - expect(parser.replace(codeFenceExample)).toBe('
This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)
'); - - codeFenceExample = '```\nThis is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)```'; - expect(parser.replace(codeFenceExample)).toBe('
This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)
'); - - codeFenceExample = '```This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)```'; - expect(parser.replace(codeFenceExample)).toBe('
This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)
'); }); test('Test code fencing with ExpensiMark syntax outside', () => { - let codeFenceExample = '# Test1 ```code``` Test2'; - expect(parser.replace(codeFenceExample)).toBe('

Test1

code
Test2'); + let codeFenceExample = '# Test1 ```\ncode\n``` Test2'; + expect(parser.replace(codeFenceExample)).toBe('

Test1

code
Test2'); - codeFenceExample = '*Test1 ```code``` Test2*'; - expect(parser.replace(codeFenceExample)).toBe('*Test1
code
Test2*'); + codeFenceExample = '*Test1 ```\ncode\n``` Test2*'; + expect(parser.replace(codeFenceExample)).toBe('*Test1
code
Test2*'); expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('*Test1
code
Test2*'); - codeFenceExample = '_Test1 ```code``` Test2_'; - expect(parser.replace(codeFenceExample)).toBe('_Test1
code
Test2_'); - expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('_Test1
code
Test2_'); + codeFenceExample = '_Test1 ```\ncode\n``` Test2_'; + expect(parser.replace(codeFenceExample)).toBe('_Test1
code
Test2_'); + expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('_Test1
code
Test2_'); codeFenceExample = '~Test1 ```code``` Test2~'; expect(parser.replace(codeFenceExample)).toBe('~Test1
code
Test2~'); @@ -523,23 +496,20 @@ test('Test code fencing with ExpensiMark syntax outside', () => { }); test('Test code fencing with additional backticks inside', () => { - let nestedBackticks = '````test````'; - expect(parser.replace(nestedBackticks)).toBe('
`test`
'); - - nestedBackticks = '````\ntest\n````'; - expect(parser.replace(nestedBackticks)).toBe('
`
test
`
'); + let nestedBackticks = '```\n`test`\n```'; + expect(parser.replace(nestedBackticks)).toBe('
`test`
'); - nestedBackticks = '````````'; - expect(parser.replace(nestedBackticks)).toBe('
``
'); + nestedBackticks = '```\n`\ntest\n`\n```'; + expect(parser.replace(nestedBackticks)).toBe('
`
test
`
'); - nestedBackticks = '````\n````'; - expect(parser.replace(nestedBackticks)).toBe('
`
`
'); + nestedBackticks = '```\n``\n```'; + expect(parser.replace(nestedBackticks)).toBe('
``
'); - nestedBackticks = '```````````'; - expect(parser.replace(nestedBackticks)).toBe('
`````
'); + nestedBackticks = '```\n`\n`\n```'; + expect(parser.replace(nestedBackticks)).toBe('
`
`
'); - nestedBackticks = '````This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)````'; - expect(parser.replace(nestedBackticks)).toBe('
`This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)`
'); + nestedBackticks = '```\n`This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)`\n```'; + expect(parser.replace(nestedBackticks)).toBe('
`This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)`
'); }); test('Test combination replacements', () => { @@ -1016,41 +986,41 @@ test('Test autolink replacement to avoid parsing nested links', () => { }); test('Test quotes markdown replacement with text matching inside and outside codefence without spaces', () => { - const testString = 'The next line should be quoted\n>Hello,I’mtext\n```\nThe next line should not be quoted\n>Hello,I’mtext\nsince its inside a codefence```'; + const testString = 'The next line should be quoted\n>Hello,I’mtext\n```\nThe next line should not be quoted\n>Hello,I’mtext\nsince its inside a codefence\n```'; - const resultString = 'The next line should be quoted
Hello,I’mtext
The next line should not be quoted
>Hello,I’mtext
since its inside a codefence
'; + const resultString = 'The next line should be quoted
Hello,I’mtext
The next line should not be quoted
>Hello,I’mtext
since its inside a codefence
'; expect(parser.replace(testString)).toBe(resultString); }); test('Test quotes markdown replacement with text matching inside and outside codefence at the same line', () => { - const testString = 'The next line should be quoted\n>Hello,I’mtext\nThe next line should not be quoted\n```>Hello,I’mtext```\nsince its inside a codefence'; + const testString = 'The next line should be quoted\n>Hello,I’mtext\nThe next line should not be quoted\n```\n>Hello,I’mtext\n```\nsince its inside a codefence'; - const resultString = 'The next line should be quoted
Hello,I’mtext
The next line should not be quoted
>Hello,I’mtext
since its inside a codefence'; + const resultString = 'The next line should be quoted
Hello,I’mtext
The next line should not be quoted
>Hello,I’mtext
since its inside a codefence'; expect(parser.replace(testString)).toBe(resultString); }); test('Test quotes markdown replacement with text matching inside and outside codefence at the end of the text', () => { - const testString = 'The next line should be quoted\n>Hello,I’mtext\nThe next line should not be quoted\n```>Hello,I’mtext```'; + const testString = 'The next line should be quoted\n>Hello,I’mtext\nThe next line should not be quoted\n```\n>Hello,I’mtext\n```'; - const resultString = 'The next line should be quoted
Hello,I’mtext
The next line should not be quoted
>Hello,I’mtext
'; + const resultString = 'The next line should be quoted
Hello,I’mtext
The next line should not be quoted
>Hello,I’mtext
'; expect(parser.replace(testString)).toBe(resultString); }); test('Test quotes markdown replacement with text matching inside and outside codefence with quotes at the end of the text', () => { - const testString = 'The next line should be quoted\n```>Hello,I’mtext```\nThe next line should not be quoted\n>Hello,I’mtext'; + const testString = 'The next line should be quoted\n```\n>Hello,I’mtext\n```\nThe next line should not be quoted\n>Hello,I’mtext'; - const resultString = 'The next line should be quoted
>Hello,I’mtext
The next line should not be quoted
Hello,I’mtext
'; + const resultString = 'The next line should be quoted
>Hello,I’mtext
The next line should not be quoted
Hello,I’mtext
'; expect(parser.replace(testString)).toBe(resultString); }); test('Test quotes markdown replacement and removing
from
 and 

', () => { - const testString = 'The next line should be quoted\n```>Hello,I’mtext```\nThe next line should not be quoted'; + const testString = 'The next line should be quoted\n```\n>Hello,I’mtext\n```\nThe next line should not be quoted'; - const resultString = 'The next line should be quoted
>Hello,I’mtext
The next line should not be quoted'; + const resultString = 'The next line should be quoted
>Hello,I’mtext
The next line should not be quoted'; expect(parser.replace(testString)).toBe(resultString); }); @@ -1255,8 +1225,8 @@ test('Test for user mention with invalid username', () => { }); test('Test for user mention with codefence style', () => { - const testString = '```@username@expensify.com```'; - const resultString = '
@username@expensify.com
'; + const testString = '```\n@username@expensify.com\n```'; + const resultString = '
@username@expensify.com
'; expect(parser.replace(testString)).toBe(resultString); }); @@ -1267,8 +1237,8 @@ test('Test for user mention with inlineCodeBlock style', () => { }); test('Test for user mention with text with codefence style', () => { - const testString = '```hi @username@expensify.com```'; - const resultString = '
hi @username@expensify.com
'; + const testString = '```\nhi @username@expensify.com\n```'; + const resultString = '
hi @username@expensify.com
'; expect(parser.replace(testString)).toBe(resultString); }); @@ -1297,8 +1267,8 @@ test('Test for user mention with user email includes underscores', () => { }); test('Test for @here mention with codefence style', () => { - const testString = '```@here```'; - const resultString = '
@here
'; + const testString = '```\n@here\n```'; + const resultString = '
@here
'; expect(parser.replace(testString)).toBe(resultString); }); @@ -1560,11 +1530,11 @@ test('Test here mention with @here@here', () => { }); test('Test link with code fence inside the alias text part', () => { - const testString = '[```code```](google.com) ' - + '[test ```code``` test](google.com)'; + const testString = '[```\ncode\n```](google.com) ' + + '[test ```\ncode\n``` test](google.com)'; - const resultString = '[
code
](google.com) ' - + '[test
code
test](google.com)'; + const resultString = '[
code
](google.com) ' + + '[test
code
test](google.com)'; expect(parser.replace(testString)).toBe(resultString); }); @@ -1606,20 +1576,20 @@ test('Linebreak between end of text and start of code block should be remained', resultString: '|
code
', }, { - testString: 'text1```code```text2', - resultString: 'text1
code
text2', + testString: 'text1```\ncode\n```text2', + resultString: 'text1
code
text2', }, { - testString: 'text1 ``` code ``` text2', - resultString: 'text1
 code 
text2', + testString: 'text1 ```\n code \n``` text2', + resultString: 'text1
 code 
text2', }, { - testString: 'text1\n```code```\ntext2', - resultString: 'text1
code
text2', + testString: 'text1\n```\ncode\n```\ntext2', + resultString: 'text1
code
text2', }, { - testString: 'text1\n``` code ```\ntext2', - resultString: 'text1
 code 
text2', + testString: 'text1\n```\n code \n```\ntext2', + resultString: 'text1
 code 
text2', }, { testString: 'text1\n```\n\ncode\n```\ntext2', @@ -1645,24 +1615,24 @@ test('Linebreak between end of text and start of code block should be remained', test('Test autoEmail with markdown of
, , ,  and  tag', () => {
     const testString = '`code`test@gmail.com '
-        + '```code block```test@gmail.com '
+        + '```\ncode block\n```test@gmail.com '
         + '[Google](https://google.com)test@gmail.com '
         + '_test@gmail.com_ '
         + '_test\n\ntest@gmail.com_ '
         + '`test@expensify.com` '
-        + '```test@expensify.com``` '
+        + '```\ntest@expensify.com\n``` '
         + '@test@expensify.com '
         + '_@username@expensify.com_ '
         + '[https://staging.new.expensify.com/details/test@expensify.com](https://staging.new.expensify.com/details/test@expensify.com) '
         + '[test italic style wrap email _test@gmail.com_ inside a link](https://google.com) ';
 
     const resultString = 'codetest@gmail.com '
-        + '
code block
test@gmail.com ' + + '
code block
test@gmail.com ' + 'Googletest@gmail.com ' + 'test@gmail.com ' + 'test

test@gmail.com
' + 'test@expensify.com ' - + '
test@expensify.com
' + + '
test@expensify.com
' + '@test@expensify.com ' + '@username@expensify.com ' + 'https://staging.new.expensify.com/details/test@expensify.com ' @@ -1817,20 +1787,20 @@ describe('when should keep raw input flag is enabled', () => { }); test('Test code fence within inline code', () => { - let testString = 'Hello world `(```test```)` Hello world'; - expect(parser.replace(testString)).toBe('Hello world `(
test
)` Hello world'); + let testString = 'Hello world `(```\ntest\n```)` Hello world'; + expect(parser.replace(testString)).toBe('Hello world `(
test
)` Hello world'); - testString = 'Hello world `(```test\ntest```)` Hello world'; - expect(parser.replace(testString)).toBe('Hello world `(
test
test
)` Hello world'); + testString = 'Hello world `(```\ntest\ntest\n```)` Hello world'; + expect(parser.replace(testString)).toBe('Hello world `(
test
test
)` Hello world'); - testString = 'Hello world ```(`test`)``` Hello world'; - expect(parser.replace(testString)).toBe('Hello world
(`test`)
Hello world'); + testString = 'Hello world ```\n(`test`)\n``` Hello world'; + expect(parser.replace(testString)).toBe('Hello world
(`test`)
Hello world'); - testString = 'Hello world `test`space```block``` Hello world'; - expect(parser.replace(testString)).toBe('Hello world testspace
block
Hello world'); + testString = 'Hello world `test`space```\nblock\n``` Hello world'; + expect(parser.replace(testString)).toBe('Hello world testspace
block
Hello world'); - testString = 'Hello world ```block```space`test` Hello world'; - expect(parser.replace(testString)).toBe('Hello world
block
spacetest Hello world'); + testString = 'Hello world ```\nblock\n```space`test` Hello world'; + expect(parser.replace(testString)).toBe('Hello world
block
spacetest Hello world'); }); test('Test italic/bold/strikethrough markdown to keep consistency', () => { diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 0f203ef4..6db0d6e1 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -32,7 +32,7 @@ export default class ExpensiMark { name: 'codeFence', // ` is a backtick symbol we are matching on three of them before then after a new line character - regex: /(```(?:\r\n|\n))((?:\s*?(?!(?:\r\n|\n)?```(?!`))[\S])+\s*?)((?:\r\n|\n)```)/g, + regex: /(```(?:\r\n|\n))((?:\s*?(?!(?:\r\n|\n)?```(?!`))[\S])+\s*?(?:\r\n|\n))(```)/g, // We're using a function here to perform an additional replace on the content // inside the backticks because Android is not able to use
 tags and does

From e250526be471bfa243f31fca91a2611ab3486cb1 Mon Sep 17 00:00:00 2001
From: Robert Kozik 
Date: Mon, 6 May 2024 21:37:26 +0200
Subject: [PATCH 003/168] change inline code regex to match triple tick in one
 line

---
 __tests__/ExpensiMark-HTML-test.js | 15 +++++++++++++++
 lib/ExpensiMark.js                 | 12 ++----------
 2 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js
index 82b624b5..7f7fc915 100644
--- a/__tests__/ExpensiMark-HTML-test.js
+++ b/__tests__/ExpensiMark-HTML-test.js
@@ -439,6 +439,21 @@ test('Test inline code blocks', () => {
     expect(parser.replace(inlineCodeStartString)).toBe('My favorite language is JavaScript. How about you?');
 });
 
+test('Test inline code blocks with double backticks', () => {
+    const inlineCodeStartString = 'My favorite language is ``JavaScript``. How about you?';
+    expect(parser.replace(inlineCodeStartString)).toBe('My favorite language is `JavaScript`. How about you?');
+});
+
+test('Test inline code blocks with triple backticks', () => {
+    const inlineCodeStartString = 'My favorite language is ```JavaScript```. How about you?';
+    expect(parser.replace(inlineCodeStartString)).toBe('My favorite language is ``JavaScript``. How about you?');
+});
+
+test('Test multiple inline codes in one line', () => {
+    const inlineCodeStartString = 'My favorite language is `JavaScript`. How about you? I also like `Python`.';
+    expect(parser.replace(inlineCodeStartString)).toBe('My favorite language is JavaScript. How about you? I also like Python.');
+});
+
 test('Test inline code with one backtick as content', () => {
     const inlineCodeStartString = '```';
     expect(parser.replace(inlineCodeStartString)).toBe('```');
diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js
index 6db0d6e1..34a29994 100644
--- a/lib/ExpensiMark.js
+++ b/lib/ExpensiMark.js
@@ -61,16 +61,8 @@ export default class ExpensiMark {
                 // Use the url escaped version of a backtick (`) symbol. Mobile platforms do not support lookbehinds,
                 // so capture the first and third group and place them in the replacement.
                 // but we should not replace backtick symbols if they include 
 tags between them.
-                regex: /(\B|_|)`(?:(?!(?:(?!`).)*?
))(.*?\S.*?)`(\B|_|)(?!`|[^<]*<\/pre>)/g,
-                replacement: (match, g1, g2, g3) => {
-                    const regex = /^[`]+$/i;
-
-                    // if content of the inline code block is only backtick symbols, we should not replace them with  tag
-                    if (regex.test(g2)) {
-                        return match;
-                    }
-                    return `${g1}${g2}${g3}`;
-                },
+                regex: /(\B|_|)`(.*?(?![`])\S.*?)`(\B|_|)(?!`|[^<]*<\/pre>)/gm,
+                replacement: '$1$2$3',
             },
 
             /**

From 5857bd2251755cabe19926bd93b76da37f3f155b Mon Sep 17 00:00:00 2001
From: Bartosz Grajdek 
Date: Tue, 7 May 2024 12:56:49 +0200
Subject: [PATCH 004/168] fix: tests related to shouldKeepRawInput & code fence

---
 __tests__/ExpensiMark-HTML-test.js | 20 ++++++++++----------
 lib/ExpensiMark.js                 |  4 ++--
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js
index 7f7fc915..ebbdc5b1 100644
--- a/__tests__/ExpensiMark-HTML-test.js
+++ b/__tests__/ExpensiMark-HTML-test.js
@@ -410,7 +410,7 @@ test('Test period replacements', () => {
 });
 
 test('Test code fencing', () => {
-    let codeFenceExampleMarkdown = '```\nconst javaScript = \'javaScript\'\n```';
+    const codeFenceExampleMarkdown = '```\nconst javaScript = \'javaScript\'\n```';
     expect(parser.replace(codeFenceExampleMarkdown)).toBe('
const javaScript = 'javaScript'
'); }); @@ -485,7 +485,7 @@ test('Test inline code blocks with two backticks', () => { }); test('Test code fencing with ExpensiMark syntax inside', () => { - let codeFenceExample = '```\nThis is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)\n```'; + const codeFenceExample = '```\nThis is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)\n```'; expect(parser.replace(codeFenceExample)).toBe('
This is how you can write ~strikethrough~, *bold*, _italics_, and [links](https://www.expensify.com)
'); }); @@ -495,19 +495,19 @@ test('Test code fencing with ExpensiMark syntax outside', () => { codeFenceExample = '*Test1 ```\ncode\n``` Test2*'; expect(parser.replace(codeFenceExample)).toBe('*Test1
code
Test2*'); - expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('*Test1
code
Test2*'); + expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('*Test1
code\n
Test2*'); codeFenceExample = '_Test1 ```\ncode\n``` Test2_'; expect(parser.replace(codeFenceExample)).toBe('_Test1
code
Test2_'); - expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('_Test1
code
Test2_'); + expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('_Test1
code\n
Test2_'); - codeFenceExample = '~Test1 ```code``` Test2~'; - expect(parser.replace(codeFenceExample)).toBe('~Test1
code
Test2~'); - expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('~Test1
code
Test2~'); + codeFenceExample = '~Test1 ```\ncode\n``` Test2~'; + expect(parser.replace(codeFenceExample)).toBe('~Test1
code
Test2~'); + expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('~Test1
code\n
Test2~'); - codeFenceExample = '[```code```](google.com)'; - expect(parser.replace(codeFenceExample)).toBe('[
code
](google.com)'); - expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('[
code
](google.com)'); + codeFenceExample = '[```\ncode\n```](google.com)'; + expect(parser.replace(codeFenceExample)).toBe('[
code
](google.com)'); + expect(parser.replace(codeFenceExample, {shouldKeepRawInput: true})).toBe('[
code\n
](google.com)'); }); test('Test code fencing with additional backticks inside', () => { diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 34a29994..025e0740 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -32,7 +32,7 @@ export default class ExpensiMark { name: 'codeFence', // ` is a backtick symbol we are matching on three of them before then after a new line character - regex: /(```(?:\r\n|\n))((?:\s*?(?!(?:\r\n|\n)?```(?!`))[\S])+\s*?(?:\r\n|\n))(```)/g, + regex: /(```(?:\r\n|\n))((?:\s*?(?!(?:\r\n|\n)?```(?!`))[\S])+\s*?(?:\r\n|\n))(```)/g, // We're using a function here to perform an additional replace on the content // inside the backticks because Android is not able to use
 tags and does
@@ -45,7 +45,7 @@ export default class ExpensiMark {
                     return `
${group}
`; }, rawInputReplacement: (match, __, textWithinFences) => { - const withinFences = match.replace(/(?:```)([\s\S]*?)(?:```)/g, '$1').replace(/|<\/emoji>/g, ''); + const withinFences = match.replace(/(?:```)([\s\S]*?)(?:```)/g, '$1'); const group = textWithinFences.replace(/(?:(?![\n\r])\s)/g, ' '); return `
${group}
`; }, From 0a1e26e46ff89b2596ab8cf7f6462659ac34334e Mon Sep 17 00:00:00 2001 From: dragnoir Date: Thu, 9 May 2024 11:28:57 +0100 Subject: [PATCH 005/168] Fix: multi level blockquote HTML to Markdown --- lib/ExpensiMark.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 4b8a7eb2..45d7a9df 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -423,14 +423,28 @@ export default class ExpensiMark { regex: /<(blockquote|q)(?:"[^"]*"|'[^']*'|[^'">])*>([\s\S]*?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi, replacement: (match, g1, g2) => { // We remove the line break before heading inside quote to avoid adding extra line - let resultString = g2 + let resultString = match .replace(/\n?(

# )/g, '$1') .replace(/(

|<\/h1>)+/g, '\n') .trim() .split('\n'); - const prependGreaterSign = (m) => `> ${m}`; - resultString = _.map(resultString, prependGreaterSign).join('\n'); + resultString = _.map(resultString, (m) => { + // Recursive function to replace nested
with ">" + function replaceBlockquotes(text) { + while (/
/i.test(text)) { + // Count how many
tags + let depth = (text.match(/
/gi) || []).length; + // Replace all blockquote tags and add ">" per depth level + text = text.replace(/
/gi, ''); + text = text.replace(/<\/blockquote>/gi, ''); + return `${'>'.repeat(depth)} ${text}`; + } + return text; + } + return replaceBlockquotes(m); + }).join('\n'); + // We want to keep
tag here and let method replaceBlockElementWithNewLine to handle the line break later return `
${resultString}
`; }, From 1c716c7cd06f5f327f5af720003ee6784a05c43d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 15 May 2024 16:24:34 +0700 Subject: [PATCH 006/168] feat: add is valid phone number --- lib/ExpensiMark.js | 10 +++++++++- lib/str.d.ts | 5 +++++ lib/str.js | 12 ++++++++++++ package-lock.json | 9 +++++++++ package.json | 1 + 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 21f7081b..e20cf21e 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -178,7 +178,7 @@ export default class ExpensiMark { { name: 'reportMentions', - regex: /(?$1', }, @@ -202,6 +202,14 @@ export default class ExpensiMark { return match; } const phoneRegex = new RegExp(`^@${Constants.CONST.REG_EXP.PHONE_PART}$`); + const mention = g2.slice(1); + const metionWithoutSMSDomain = Str.removeSMSDomain(mention); + if ( + !Str.isValidMention(match) || + (new RegExp(`^${Constants.CONST.REG_EXP.PHONE_PART}$`).test(metionWithoutSMSDomain) && !Str.isValidPhoneNumber(metionWithoutSMSDomain)) + ) { + return match; + } return `${g1}${g2}${phoneRegex.test(g2) ? `@${Constants.CONST.SMS.DOMAIN}` : ''}`; }, rawInputReplacement: (match, g1, g2) => { diff --git a/lib/str.d.ts b/lib/str.d.ts index d703d8bf..f4f30487 100644 --- a/lib/str.d.ts +++ b/lib/str.d.ts @@ -485,6 +485,11 @@ declare const Str: { * Check for whether a phone number is valid according to E.164 standard. * @param phone */ + isValidPhoneNumber(phone: string): boolean; + /** + * Check for whether a phone number is valid. + * @param phone + */ isValidE164Phone(phone: string): boolean; /** * Check for whether a phone number is valid in different formats/standards. For example: diff --git a/lib/str.js b/lib/str.js index f9978b4e..d9ce6f6b 100644 --- a/lib/str.js +++ b/lib/str.js @@ -1,5 +1,6 @@ /* eslint-disable no-control-regex */ import _ from 'underscore'; +import {parsePhoneNumber} from 'awesome-phonenumber'; import * as HtmlEntities from 'html-entities'; import * as Constants from './CONST'; import * as UrlPatterns from './Url'; @@ -930,6 +931,17 @@ const Str = { return Constants.CONST.SMS.E164_REGEX.test(phone); }, + /** + * Check for whether a phone number is valid. + * @param {String} phone + * + * @return {bool} + */ + + isValidPhoneNumber(phone) { + return parsePhoneNumber(this.removeSMSDomain(phone)).possible; + }, + /** * Check for whether a phone number is valid according to E.164 standard. * @param {String} phone diff --git a/package-lock.json b/package-lock.json index 615318d5..5003d033 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "awesome-phonenumber": "^5.4.0", "classnames": "2.5.0", "clipboard": "2.0.11", "html-entities": "^2.5.2", @@ -3563,6 +3564,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/awesome-phonenumber": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/awesome-phonenumber/-/awesome-phonenumber-5.11.0.tgz", + "integrity": "sha512-25GfikMIo6CBQIqvjoewo4uiu5Ai7WqEC8gxesH3LDwCY43oEdkLaT15a+8adC7uWIJCGh+YQiBY5bjmDpoQcg==", + "engines": { + "node": ">=14" + } + }, "node_modules/axe-core": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", diff --git a/package.json b/package.json index 051a1584..440ee4d2 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "url": "git+ssh://git@github.com/Expensify/JS-Libs.git" }, "dependencies": { + "awesome-phonenumber": "^5.4.0", "classnames": "2.5.0", "clipboard": "2.0.11", "html-entities": "^2.5.2", From 647ffef04e7bce2c931f33671a0c1fbc70c4c7a5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 15 May 2024 16:26:34 +0700 Subject: [PATCH 007/168] fix chore --- lib/ExpensiMark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index e20cf21e..25db3e5c 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -178,7 +178,7 @@ export default class ExpensiMark { { name: 'reportMentions', - regex: /(?$1', }, From 322d7b7687fb49ee9ca427305670d9a1e4687e9b Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 15 May 2024 16:31:13 +0700 Subject: [PATCH 008/168] fix comment position --- lib/str.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/str.d.ts b/lib/str.d.ts index f4f30487..e4eb8acb 100644 --- a/lib/str.d.ts +++ b/lib/str.d.ts @@ -482,12 +482,12 @@ declare const Str: { */ isValidPhone(phone: string): boolean; /** - * Check for whether a phone number is valid according to E.164 standard. + * Check for whether a phone number is valid. * @param phone */ isValidPhoneNumber(phone: string): boolean; /** - * Check for whether a phone number is valid. + * Check for whether a phone number is valid according to E.164 standard. * @param phone */ isValidE164Phone(phone: string): boolean; From 4e9cdb02aa46b886388489c4d5f1da1561f7d1dd Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 15 May 2024 17:51:58 +0700 Subject: [PATCH 009/168] remove useless code --- lib/str.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/str.js b/lib/str.js index d9ce6f6b..65007fe2 100644 --- a/lib/str.js +++ b/lib/str.js @@ -939,7 +939,7 @@ const Str = { */ isValidPhoneNumber(phone) { - return parsePhoneNumber(this.removeSMSDomain(phone)).possible; + return parsePhoneNumber(phone).possible; }, /** From fa064832b1880d4d89b1334ccc5eded71c62b91f Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 15 May 2024 18:10:37 +0700 Subject: [PATCH 010/168] rename variable --- lib/ExpensiMark.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 25db3e5c..58234dba 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -198,18 +198,13 @@ export default class ExpensiMark { 'gim', ), replacement: (match, g1, g2) => { - if (!Str.isValidMention(match)) { - return match; - } - const phoneRegex = new RegExp(`^@${Constants.CONST.REG_EXP.PHONE_PART}$`); + const phoneNumberRegex = new RegExp(`^${Constants.CONST.REG_EXP.PHONE_PART}$`); const mention = g2.slice(1); const metionWithoutSMSDomain = Str.removeSMSDomain(mention); - if ( - !Str.isValidMention(match) || - (new RegExp(`^${Constants.CONST.REG_EXP.PHONE_PART}$`).test(metionWithoutSMSDomain) && !Str.isValidPhoneNumber(metionWithoutSMSDomain)) - ) { + if (!Str.isValidMention(match) || (phoneNumberRegex.test(metionWithoutSMSDomain) && !Str.isValidPhoneNumber(metionWithoutSMSDomain))) { return match; } + const phoneRegex = new RegExp(`^@${Constants.CONST.REG_EXP.PHONE_PART}$`); return `${g1}${g2}${phoneRegex.test(g2) ? `@${Constants.CONST.SMS.DOMAIN}` : ''}`; }, rawInputReplacement: (match, g1, g2) => { From 91d752fe313b6c0db544cb46871bedeeb749996b Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 11:47:12 +0200 Subject: [PATCH 011/168] Install deps, change configuration, add scripts to package.json --- package-lock.json | 1130 +++++++++++++++++++++++++++++++++++++-------- package.json | 23 +- 2 files changed, 945 insertions(+), 208 deletions(-) diff --git a/package-lock.json b/package-lock.json index 615318d5..81074c4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "expensify-common", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "expensify-common", - "version": "1.0.0", + "version": "2.0.0", "license": "MIT", "dependencies": { "classnames": "2.5.0", @@ -26,19 +26,25 @@ "devDependencies": { "@babel/preset-env": "^7.24.4", "@lwc/eslint-plugin-lwc": "^1.7.2", + "@types/jest": "^29.5.12", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.9.0", + "@typescript-eslint/parser": "^7.9.0", "babel-jest": "^29.0.0", "babelify": "10.0.0", - "eslint": "^7.15.0", + "eslint": "^8.57.0", "eslint-config-expensify": "^2.0.48", "eslint-config-prettier": "^8.10.0", "eslint-plugin-jest": "^24.7.0", + "eslint-plugin-prettier": "^5.1.3", "grunt": "1.6.1", "grunt-chokidar": "1.0.2", "grunt-eslint": "25.0.0", "jest": "^29.0.0", "jest-environment-jsdom": "^29.7.0", "jit-grunt": "^0.10.0", - "prettier": "^3.2.5" + "prettier": "^3.2.5", + "typescript": "^5.4.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1951,18 +1957,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.0.0.tgz", @@ -2808,18 +2802,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@lwc/eslint-plugin-lwc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -2865,6 +2847,18 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2972,6 +2966,16 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", @@ -3004,6 +3008,31 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz", + "integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -3031,6 +3060,107 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz", + "integrity": "sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/type-utils": "7.9.0", + "@typescript-eslint/utils": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz", + "integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz", + "integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz", + "integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/@typescript-eslint/experimental-utils": { "version": "4.16.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.16.1.tgz", @@ -3055,6 +3185,145 @@ "eslint": "*" } }, + "node_modules/@typescript-eslint/parser": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.9.0.tgz", + "integrity": "sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/typescript-estree": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz", + "integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz", + "integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz", + "integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz", + "integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "4.16.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz", @@ -3072,6 +3341,127 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.9.0.tgz", + "integrity": "sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.9.0", + "@typescript-eslint/utils": "7.9.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz", + "integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz", + "integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz", + "integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/types": { "version": "4.16.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz", @@ -3112,6 +3502,139 @@ } } }, + "node_modules/@typescript-eslint/utils": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.9.0.tgz", + "integrity": "sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/typescript-estree": "7.9.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz", + "integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz", + "integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz", + "integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz", + "integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.9.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "4.16.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz", @@ -3138,6 +3661,12 @@ "node": ">=10" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -3527,15 +4056,6 @@ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", @@ -4336,6 +4856,12 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -4890,54 +5416,55 @@ } }, "node_modules/eslint": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.15.0.tgz", - "integrity": "sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", - "ajv": "^6.10.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.2.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", - "file-entry-cache": "^6.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5302,18 +5829,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint-config-expensify/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-config-prettier": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", @@ -5533,6 +6048,36 @@ "node": "*" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react": { "version": "7.34.1", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz", @@ -5683,24 +6228,67 @@ } }, "node_modules/eslint/node_modules/@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/eslint/node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/eslint/node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/eslint/node_modules/ansi-styles": { @@ -5718,6 +6306,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5752,13 +6346,70 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/eslint/node_modules/file-entry-cache": { @@ -5773,6 +6424,22 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -5787,13 +6454,25 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/eslint/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" }, "engines": { "node": ">=8" @@ -5811,6 +6490,84 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -6015,21 +6772,26 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-json-stable-stringify": { @@ -6447,16 +7209,16 @@ } }, "node_modules/globby": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", - "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -6467,9 +7229,9 @@ } }, "node_modules/globby/node_modules/ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -10612,6 +11374,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -11246,29 +12020,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11444,27 +12195,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -11525,50 +12255,28 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", "dev": true, "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, - "node_modules/table/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "node_modules/synckit/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -11671,6 +12379,18 @@ "node": ">=12" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -11738,12 +12458,15 @@ } }, "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/typed-array-buffer": { @@ -11820,11 +12543,10 @@ } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 051a1584..b2dfc076 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,22 @@ { "name": "expensify-common", - "version": "1.0.0", + "version": "2.0.0", "author": "Expensify, Inc.", "description": "Expensify libraries and components shared across different repos", "homepage": "https://expensify.com", - "main": "index.js", "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*", + "API.md", + "README.md", + "LICENSE.md" + ], "scripts": { "grunt": "grunt", + "typecheck": "tsc --noEmit", + "build": "tsc -p tsconfig.build.json && cp ./lib/*.d.ts ./dist", "test": "jest", "lint": "eslint lib/", "prettier": "prettier --write lib/", @@ -36,19 +45,25 @@ "devDependencies": { "@babel/preset-env": "^7.24.4", "@lwc/eslint-plugin-lwc": "^1.7.2", + "@types/jest": "^29.5.12", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.9.0", + "@typescript-eslint/parser": "^7.9.0", "babel-jest": "^29.0.0", "babelify": "10.0.0", - "eslint": "^7.15.0", + "eslint": "^8.57.0", "eslint-config-expensify": "^2.0.48", "eslint-config-prettier": "^8.10.0", "eslint-plugin-jest": "^24.7.0", + "eslint-plugin-prettier": "^5.1.3", "grunt": "1.6.1", "grunt-chokidar": "1.0.2", "grunt-eslint": "25.0.0", "jest": "^29.0.0", "jest-environment-jsdom": "^29.7.0", "jit-grunt": "^0.10.0", - "prettier": "^3.2.5" + "prettier": "^3.2.5", + "typescript": "^5.4.5" }, "browserify": { "transform": [ From 3d910ce5e6eab38da16173e2ba5102d5b88d915d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 11:48:42 +0200 Subject: [PATCH 012/168] Ignore dist --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 608a5535..e1699c98 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ npm-debug.log package.json-e .DS_Store *.swp +dist From ddfc91eda4aa42d7561af24f1d91ac3045cb7f07 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 11:48:58 +0200 Subject: [PATCH 013/168] Adjust prettier and eslint ignore files --- .eslintignore | 3 +++ .prettierignore | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 .prettierignore diff --git a/.eslintignore b/.eslintignore index cd4efd8e..1f6dfa0a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,4 @@ *.d.ts +dist +node_modules +*.config.js diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..b67fe5c9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +dist +package.json +package-lock.json +*.html \ No newline at end of file From b981ab407d1fd2afb029c407a57e963842f97fde Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 11:49:09 +0200 Subject: [PATCH 014/168] Prepare eslint config --- .eslintrc.js | 63 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 02a609dc..c1de71a4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,11 +1,58 @@ module.exports = { extends: ['expensify', 'prettier'], - rules: { - // Allow JSX to be written in any file ignoring the extension type - 'react/jsx-filename-extension': 'off' - }, - plugins: ['jest'], - env: { - "jest/globals": true - } + parser: '@typescript-eslint/parser', + overrides: [ + { + files: ['*.js', '*.jsx'], + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, + }, + rules: { + // Allow JSX to be written in any file ignoring the extension type + 'react/jsx-filename-extension': 'off', + 'rulesdir/no-api-in-views': 'off', + 'rulesdir/no-multiple-api-calls': 'off', + 'no-constructor-return': 'off', + 'import/extensions': [ + 'error', + 'ignorePackages', + { + js: 'never', + jsx: 'never', + ts: 'never', + tsx: 'never', + }, + ], + }, + }, + { + files: ['*.ts', '*.tsx'], + extends: ['expensify', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/stylistic', 'plugin:import/typescript', 'prettier', 'plugin:prettier/recommended'], + plugins: ['react', 'import', '@typescript-eslint'], + parserOptions: { + project: './tsconfig.json', + }, + rules: { + 'prefer-regex-literals': 'off', + 'rulesdir/prefer-underscore-method': 'off', + 'react/jsx-props-no-spreading': 'off', + 'react/require-default-props': 'off', + 'react/jsx-filename-extension': ['error', {extensions: ['.tsx', '.jsx']}], + 'import/no-unresolved': 'error', + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_'}], + '@typescript-eslint/consistent-type-imports': ['error', {prefer: 'type-imports'}], + '@typescript-eslint/consistent-type-exports': ['error', {fixMixedExportsWithInlineTypeSpecifier: false}], + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/array-type': ['error', {default: 'array-simple'}], + '@typescript-eslint/consistent-type-definitions': 'off', + }, + }, + ], }; From 479c9cf03377af0cfe5917da5b9c5d0b8ef97f0a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 11:49:25 +0200 Subject: [PATCH 015/168] Prepare tsconfigs --- tsconfig.build.json | 5 +++++ tsconfig.json | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..eb941d63 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "include": ["./lib"] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..9873ff27 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "es2015", + "module": "commonjs", + "types": ["react", "jest", "node"], + "lib": ["esnext"], + "allowJs": true, + "checkJs": false, + "jsx": "react", + "isolatedModules": true, + "strict": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "./dist" + }, + "exclude": ["**/node_modules/**/*", "**/dist/**/*"] +} \ No newline at end of file From 243308a978df4fa9ebc0841447e228958a869c3c Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 11:50:25 +0200 Subject: [PATCH 016/168] Add prettier and typecheks workflows --- .github/workflows/lint.yml | 9 +++++++++ .github/workflows/typecheck.yml | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 .github/workflows/typecheck.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 71bab56f..a24f8b13 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,3 +27,12 @@ jobs: - run: npm run lint env: CI: true + + - name: Verify there's no Prettier diff + run: | + npm run prettier -- --loglevel silent + if ! git diff --name-only --exit-code; then + # shellcheck disable=SC2016 + echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' + exit 1 + fi diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 00000000..09d4a38f --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,24 @@ +name: TypeScript Checks + +on: + pull_request: + types: [opened, synchronize] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + cache: npm + cache-dependency-path: package-lock.json + + - run: npm ci + + - name: Type check with TypeScript + run: npm run typecheck + env: + CI: true From e85751d183e288b1a06a0ead1de7205e61ec6cbc Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 11:50:41 +0200 Subject: [PATCH 017/168] Migrate CONST --- lib/CONST.d.ts | 852 ------------------------------------ lib/{CONST.jsx => CONST.ts} | 19 +- 2 files changed, 11 insertions(+), 860 deletions(-) delete mode 100644 lib/CONST.d.ts rename lib/{CONST.jsx => CONST.ts} (98%) diff --git a/lib/CONST.d.ts b/lib/CONST.d.ts deleted file mode 100644 index 77974b46..00000000 --- a/lib/CONST.d.ts +++ /dev/null @@ -1,852 +0,0 @@ -/** - * URL of our CloudFront Instance - */ -export declare const g_cloudFront: 'https://d2k5nsl2zxldvw.cloudfront.net'; -/** - * URL of our image CDN - */ -export declare const g_cloudFrontImg: 'https://d2k5nsl2zxldvw.cloudfront.net/images/'; -export declare const CONST: { - readonly CORPAY_DIRECT_REIMBURSEMENT_CURRENCIES: readonly ['USD', 'GBP', 'EUR', 'AUD', 'CAD']; - /** - * Default max ACH limit. It can be overwritten by a private NVP - */ - readonly ACH_DEFAULT_MAX_AMOUNT_LIMIT: 2000000; - /** - * IRS remimbursement rate for mileage - * WARNING ! UPDATE THE PHP CONSTANT VERSION WHEN UPDATING THIS ONE - */ - readonly MILEAGE_IRS_RATE: 0.545 | 0.58; - readonly COUNTRY: { - readonly US: 'US'; - readonly AU: 'AU'; - readonly UK: 'UK'; - readonly NZ: 'NZ'; - }; - readonly CURRENCIES: { - readonly US: 'USD'; - readonly AU: 'AUD'; - readonly UK: 'GBP'; - readonly NZ: 'NZD'; - }; - readonly STATES: { - readonly AK: { - readonly stateISO: 'AK'; - readonly stateName: 'Alaska'; - }; - readonly AL: { - readonly stateISO: 'AL'; - readonly stateName: 'Alabama'; - }; - readonly AR: { - readonly stateISO: 'AR'; - readonly stateName: 'Arkansas'; - }; - readonly AZ: { - readonly stateISO: 'AZ'; - readonly stateName: 'Arizona'; - }; - readonly CA: { - readonly stateISO: 'CA'; - readonly stateName: 'California'; - }; - readonly CO: { - readonly stateISO: 'CO'; - readonly stateName: 'Colorado'; - }; - readonly CT: { - readonly stateISO: 'CT'; - readonly stateName: 'Connecticut'; - }; - readonly DE: { - readonly stateISO: 'DE'; - readonly stateName: 'Delaware'; - }; - readonly FL: { - readonly stateISO: 'FL'; - readonly stateName: 'Florida'; - }; - readonly GA: { - readonly stateISO: 'GA'; - readonly stateName: 'Georgia'; - }; - readonly HI: { - readonly stateISO: 'HI'; - readonly stateName: 'Hawaii'; - }; - readonly IA: { - readonly stateISO: 'IA'; - readonly stateName: 'Iowa'; - }; - readonly ID: { - readonly stateISO: 'ID'; - readonly stateName: 'Idaho'; - }; - readonly IL: { - readonly stateISO: 'IL'; - readonly stateName: 'Illinois'; - }; - readonly IN: { - readonly stateISO: 'IN'; - readonly stateName: 'Indiana'; - }; - readonly KS: { - readonly stateISO: 'KS'; - readonly stateName: 'Kansas'; - }; - readonly KY: { - readonly stateISO: 'KY'; - readonly stateName: 'Kentucky'; - }; - readonly LA: { - readonly stateISO: 'LA'; - readonly stateName: 'Louisiana'; - }; - readonly MA: { - readonly stateISO: 'MA'; - readonly stateName: 'Massachusetts'; - }; - readonly MD: { - readonly stateISO: 'MD'; - readonly stateName: 'Maryland'; - }; - readonly ME: { - readonly stateISO: 'ME'; - readonly stateName: 'Maine'; - }; - readonly MI: { - readonly stateISO: 'MI'; - readonly stateName: 'Michigan'; - }; - readonly MN: { - readonly stateISO: 'MN'; - readonly stateName: 'Minnesota'; - }; - readonly MO: { - readonly stateISO: 'MO'; - readonly stateName: 'Missouri'; - }; - readonly MS: { - readonly stateISO: 'MS'; - readonly stateName: 'Mississippi'; - }; - readonly MT: { - readonly stateISO: 'MT'; - readonly stateName: 'Montana'; - }; - readonly NC: { - readonly stateISO: 'NC'; - readonly stateName: 'North Carolina'; - }; - readonly ND: { - readonly stateISO: 'ND'; - readonly stateName: 'North Dakota'; - }; - readonly NE: { - readonly stateISO: 'NE'; - readonly stateName: 'Nebraska'; - }; - readonly NH: { - readonly stateISO: 'NH'; - readonly stateName: 'New Hampshire'; - }; - readonly NJ: { - readonly stateISO: 'NJ'; - readonly stateName: 'New Jersey'; - }; - readonly NM: { - readonly stateISO: 'NM'; - readonly stateName: 'New Mexico'; - }; - readonly NV: { - readonly stateISO: 'NV'; - readonly stateName: 'Nevada'; - }; - readonly NY: { - readonly stateISO: 'NY'; - readonly stateName: 'New York'; - }; - readonly OH: { - readonly stateISO: 'OH'; - readonly stateName: 'Ohio'; - }; - readonly OK: { - readonly stateISO: 'OK'; - readonly stateName: 'Oklahoma'; - }; - readonly OR: { - readonly stateISO: 'OR'; - readonly stateName: 'Oregon'; - }; - readonly PA: { - readonly stateISO: 'PA'; - readonly stateName: 'Pennsylvania'; - }; - readonly PR: { - readonly stateISO: 'PR'; - readonly stateName: 'Puerto Rico'; - }; - readonly RI: { - readonly stateISO: 'RI'; - readonly stateName: 'Rhode Island'; - }; - readonly SC: { - readonly stateISO: 'SC'; - readonly stateName: 'South Carolina'; - }; - readonly SD: { - readonly stateISO: 'SD'; - readonly stateName: 'South Dakota'; - }; - readonly TN: { - readonly stateISO: 'TN'; - readonly stateName: 'Tennessee'; - }; - readonly TX: { - readonly stateISO: 'TX'; - readonly stateName: 'Texas'; - }; - readonly UT: { - readonly stateISO: 'UT'; - readonly stateName: 'Utah'; - }; - readonly VA: { - readonly stateISO: 'VA'; - readonly stateName: 'Virginia'; - }; - readonly VT: { - readonly stateISO: 'VT'; - readonly stateName: 'Vermont'; - }; - readonly WA: { - readonly stateISO: 'WA'; - readonly stateName: 'Washington'; - }; - readonly WI: { - readonly stateISO: 'WI'; - readonly stateName: 'Wisconsin'; - }; - readonly WV: { - readonly stateISO: 'WV'; - readonly stateName: 'West Virginia'; - }; - readonly WY: { - readonly stateISO: 'WY'; - readonly stateName: 'Wyoming'; - }; - readonly DC: { - readonly stateISO: 'DC'; - readonly stateName: 'District Of Columbia'; - }; - }; - /** - * Store all the regular expression we are using for matching stuff - */ - readonly REG_EXP: { - /** - * Regular expression to check that a domain is valid - */ - readonly DOMAIN: RegExp; - /** - * Regex matching an text containing an email - */ - readonly EMAIL_PART: "([\\w\\-\\+\\'#]+(?:\\.[\\w\\-\\'\\+]+)*@(?:[\\w\\-]+\\.)+[a-z]{2,})"; - /** - * Regex matching a text containing general phone number - */ - readonly GENERAL_PHONE_PART: RegExp; - /** - * 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 - */ - readonly FREE_NAME: RegExp; - /** - * Regular expression to check that a card is masked - */ - readonly MASKED_CARD: RegExp; - /** - * Regular expression to check that an email is valid - */ - readonly EMAIL: RegExp; - /** - * Regular expression to extract an email from a text - */ - readonly EXTRACT_EMAIL: RegExp; - /** - * Regular expression to search for valid email addresses in a string - */ - readonly EMAIL_SEARCH: RegExp; - /** - * Regular expression to detect if something is a hyperlink - * - * Adapted from: https://gist.github.com/dperini/729294 - */ - readonly HYPERLINK: RegExp; - /** - * Regex to match valid emails during markdown transformations - */ - readonly MARKDOWN_EMAIL: "([a-zA-Z0-9.!#$%&'+/=?^`{|}-][a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]*@[a-zA-Z0-9-]+?(\\.[a-zA-Z]+)+)"; - /** - * Regex matching an text containing an Emoji - */ - readonly EMOJIS: RegExp; - /** - * Regex matching an text containing an Emoji that can be a single emoji or made up by some different emojis - * - * @type RegExp - */ - readonly EMOJI_RULE: RegExp; - }; - readonly REPORT: { - /** - * Limit when we decided to turn off print to pdf and use only the native feature - */ - readonly LIMIT_PRINT_PDF: 250; - readonly ACH_LIMIT: 2000000; - readonly ACH_DEFAULT_DAYS: 4; - /** - * This is the string that a user can enter in a formula to refer to the report title field - */ - readonly TITLE_FORMULA: '{report:title}'; - /** - * The max time a comment can be made after another to be considered the same comment, in seconds - */ - readonly MAX_AGE_SAME_COMMENT: 300; - readonly SMARTREPORT_AGENT_EMAIL: 'smartreports@expensify.com'; - }; - /** - * Root URLs - */ - readonly URL: { - readonly FORUM_ROOT: 'https://community.expensify.com/'; - readonly RECEIPTS: { - readonly DEVELOPMENT: 'https://www.expensify.com.dev/receipts/'; - readonly STAGING: 'https://staging.expensify.com/receipts/'; - readonly PRODUCTION: 'https://www.expensify.com/receipts/'; - }; - readonly CLOUDFRONT: 'https://d2k5nsl2zxldvw.cloudfront.net'; - readonly CLOUDFRONT_IMG: 'https://d2k5nsl2zxldvw.cloudfront.net/images/'; - readonly CLOUDFRONT_FILES: 'https://d2k5nsl2zxldvw.cloudfront.net/files/'; - readonly EXPENSIFY_SYNC_MANAGER: 'quickbooksdesktop/Expensify_QuickBooksDesktop_Setup_2300802.exe'; - readonly USEDOT_ROOT: 'https://use.expensify.com/'; - readonly ITUNES_SUBSCRIPTION: 'https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/manageSubscriptions'; - }; - readonly DATE: { - readonly FORMAT_STRING: 'yyyy-MM-dd'; - readonly FORMAT_STRING_PRETTY: 'MMM d, yyyy'; - /** - * Expensify date format string for moment js - * usage: moment().format( CONST.DATE.MOMENT_FORMAT_STRING ) - */ - readonly MOMENT_FORMAT_STRING: 'YYYY-MM-DD'; - /** - * This is a typical format of the date plus the time - */ - readonly MOMENT_DATE_TIME: 'YYYY-MM-DD HH:mm'; - /** - * Pretty format used for report history items - * - * @example Jun 19, 2019 12:38 PM - */ - readonly MOMENT_DATE_TIME_PRETTY: 'MMM DD YYYY h:mma'; - /** - * Date-time format, including timezone information, eg "2015-10-14T19:44:35+07:00" - */ - readonly MOMENT_DATE_TIME_TIMEZONE: 'YYYY-MM-DDTHH:mm:ssZ'; - /** - * Moment formatting option for a date of this format "Jul 2, 2014" - */ - readonly MOMENT_US_DATE: 'MMM D, YYYY'; - /** - * Moment formatting option for a date of this format "July 2, 2014" - * ie, full month name - */ - readonly MOMENT_US_DATE_LONG: 'MMMM D, YYYY'; - /** - * Moment formatting option for full month name and year as in "July 2015" - */ - readonly MOMENT_US_MONTH_YEAR_LONG: 'MMMM YYYY'; - /** - * Difference between the local time and UTC time in ms - */ - readonly TIMEZONE_OFFSET_MS: number; - readonly SHORT_MONTH_SHORT_DAY: 'MMM d'; - readonly LONG_YEAR_MONTH_DAY_24_TIME: 'yyyy-MM-dd HH:mm:ss'; - readonly SHORT_MONTH_DAY_LOCAL_TIME: 'MMM D [at] LT'; - readonly SHORT_MONTH_DAY_YEAR_LOCAL_TIME: 'MMM D, YYYY [at] LT'; - }; - /** - * Message used by the Func.die() exception - */ - readonly FUNC_DIE_MESSAGE: 'Aborting JavaScript execution'; - /** - * Default for how long the email delivery failure NVP should be valid (in seconds) - * Currently 14 days (14 * 24 * 60 * 60) - * - * WARNING ! UPDATE THE PHP CONSTANT VERSION WHEN UPDATING THIS ONE - */ - readonly EMAIL_DELIVERY_FAILURE_VALIDITY: 1209600; - /** - * Bill Processing-related constants - */ - readonly BILL_PROCESSING_PARTNER_NAME: 'expensify.cash'; - readonly BILL_PROCESSING_EMAIL_DOMAIN: 'expensify.cash'; - /** - * Bank Import Logic Constants - */ - readonly BANK_IMPORT: { - readonly BANK_STATUS_BROKEN: 2; - }; - /** - * Bank Account Logic Constants - */ - readonly BANK_ACCOUNT: { - readonly VERIFICATION_MAX_ATTEMPTS: 7; - }; - /** - * Emails that the user shouldn't be interacting with from the front-end interface - * Trying to add these emails as a delegate, onto a policy, or as an approver is considered invalid - * Any changes here should be reflected in the PHP constant in web-expensify, - * which is located in _constant.php and also named EXPENSIFY_EMAILS. - * And should also be reflected in the constant in expensify/app, - * which is located in src/CONST.js and also named EXPENSIFY_EMAILS. - */ - readonly EXPENSIFY_EMAILS: readonly [ - 'concierge@expensify.com', - 'help@expensify.com', - 'receipts@expensify.com', - 'chronos@expensify.com', - 'qa@expensify.com', - 'contributors@expensify.com', - 'firstresponders@expensify.com', - 'qa+travisreceipts@expensify.com', - 'bills@expensify.com', - 'studentambassadors@expensify.com', - 'accounting@expensify.com', - 'payroll@expensify.com', - 'svfg@expensify.com', - 'integrationtestingcreds@expensify.com', - 'admin@expensify.com', - 'notifications@expensify.com', - ]; - /** - * Emails that the user shouldn't submit reports to nor share reports with - * Any changes here should be reflected in the PHP constant, - * which is located in _constant.php and also named INVALID_APPROVER_AND_SHAREE_EMAILS - */ - readonly INVALID_APPROVER_AND_SHAREE_EMAILS: readonly [ - 'concierge@expensify.com', - 'help@expensify.com', - 'receipts@expensify.com', - 'chronos@expensify.com', - 'qa@expensify.com', - 'contributors@expensify.com', - 'firstresponders@expensify.com', - 'qa+travisreceipts@expensify.com', - 'bills@expensify.com', - 'admin@expensify.com', - 'notifications@expensify.com', - ]; - /** - * Smart scan-related constants - */ - readonly SMART_SCAN: { - readonly COST: 20; - readonly FREE_NUMBER: 25; - }; - readonly SMS: { - readonly DOMAIN: 'expensify.sms'; - readonly E164_REGEX: RegExp; - }; - readonly PASSWORD_COMPLEXITY_REGEX_STRING: '^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{8,}$'; - readonly INTEGRATIONS: { - /** - * Constants that specify how to map (import) Integrations data to Expensify - * Parallel to IntegrationEntityMappingTypeEnum in the IS - */ - readonly DATA_MAPPING: { - readonly NONE: 'NONE'; - readonly TAG: 'TAG'; - readonly REPORT_FIELD: 'REPORT_FIELD'; - readonly DEFAULT: 'DEFAULT'; - }; - readonly EXPORT_DATE: { - readonly LAST_EXPENSE: 'LAST_EXPENSE'; - readonly REPORT_EXPORTED: 'REPORT_EXPORTED'; - readonly REPORT_SUBMITTED: 'REPORT_SUBMITTED'; - }; - readonly XERO_HQ_CONNECTION_NAME: 'xerohq'; - readonly EXPENSIFY_SYNC_MANAGER_VERSION: '23.0.802.0'; - }; - readonly INTEGRATION_TYPES: { - readonly ACCOUNTING: 'accounting'; - readonly HR: 'hr'; - }; - readonly DIRECT_INTEGRATIONS: { - readonly zenefits: { - readonly value: 'zenefits'; - readonly text: 'Zenefits'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/zenefit.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/zenefit_gray.svg'; - readonly alert_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/zenefit_alert.svg'; - readonly types: readonly ['hr']; - readonly isCorporateOnly: false; - }; - readonly gusto: { - readonly value: 'gusto'; - readonly text: 'Gusto'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/gusto.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/gusto_gray.svg'; - readonly alert_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/gusto_alert.svg'; - readonly types: readonly ['hr']; - readonly isCorporateOnly: false; - }; - readonly quickbooksOnline: { - readonly value: 'quickbooksOnline'; - readonly text: 'QuickBooks Online'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/quickbooks.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/quickbooks_gray.svg'; - readonly alert_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/quickbooks_alert.svg'; - readonly types: readonly ['hr', 'accounting']; - readonly isCorporateOnly: false; - }; - readonly xero: { - readonly value: 'xero'; - readonly text: 'Xero'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/xero.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/xero_gray.svg'; - readonly alert_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/xero_alert.svg'; - readonly types: readonly ['accounting']; - readonly isCorporateOnly: false; - }; - readonly netsuite: { - readonly value: 'netsuite'; - readonly text: 'NetSuite'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/netsuite.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/netsuite_gray.svg'; - readonly alert_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/netsuite_alert.svg'; - readonly types: readonly ['hr', 'accounting']; - readonly isCorporateOnly: true; - }; - readonly quickbooksDesktop: { - readonly value: 'qbd'; - readonly text: 'QuickBooks Desktop'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/quickbooks.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/quickbooks_gray.svg'; - readonly alert_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/quickbooks_alert.svg'; - readonly types: readonly ['accounting']; - readonly isCorporateOnly: false; - }; - readonly intacct: { - readonly value: 'intacct'; - readonly text: 'Sage Intacct'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/sage.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/sage_gray.svg'; - readonly alert_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/sage_alert.svg'; - readonly types: readonly ['hr', 'accounting']; - readonly isCorporateOnly: true; - }; - readonly financialforce: { - readonly value: 'financialforce'; - readonly text: 'Certinia'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/certinia.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/certinia_gray.svg'; - readonly alert_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/certinia_alert.svg'; - readonly types: readonly ['accounting']; - readonly isCorporateOnly: true; - }; - }; - readonly INDIRECT_INTEGRATIONS: { - readonly microsoft_dynamics: { - readonly value: 'microsoft_dynamics'; - readonly text: 'Microsoft Dynamics'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/microsoft_dynamics.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/microsoft_dynamics_gray.svg'; - readonly types: readonly ['accounting']; - readonly isCorporateOnly: true; - }; - readonly oracle: { - readonly value: 'oracle'; - readonly text: 'Oracle'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/oracle.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/oracle_gray.svg'; - readonly types: readonly ['hr', 'accounting']; - readonly isCorporateOnly: true; - }; - readonly sage: { - readonly value: 'sage'; - readonly text: 'Sage'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/sage.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/sage_gray.svg'; - readonly types: readonly ['accounting']; - readonly isCorporateOnly: true; - }; - readonly sap: { - readonly value: 'sap'; - readonly text: 'SAP'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/sap.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/sap_gray.svg'; - readonly types: readonly ['accounting']; - readonly isCorporateOnly: true; - }; - readonly myob: { - readonly value: 'myob'; - readonly text: 'MYOB'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/myob.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/myob_gray.svg'; - readonly types: readonly ['accounting']; - readonly isCorporateOnly: true; - }; - readonly workday: { - readonly value: 'workday'; - readonly text: 'Workday'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/workday.svg'; - readonly gray_image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/workday_gray.svg'; - readonly types: readonly ['hr']; - readonly isCorporateOnly: true; - }; - readonly adp: { - readonly value: 'adp'; - readonly text: 'ADP'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/export-icons/adp.svg'; - readonly types: readonly ['hr']; - readonly isCorporateOnly: true; - }; - readonly generic_indirect_connection: { - readonly value: 'generic_indirect_connection'; - readonly text: 'Other'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/accounting-other--blue.svg'; - readonly types: readonly ['hr', 'accounting']; - }; - }; - readonly DEFAULT_IS_TEMPLATES: { - readonly default: { - readonly value: 'default_template'; - readonly text: 'Basic Export'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/accounting-other--blue.svg'; - }; - readonly tag: { - readonly value: 'tag_template'; - readonly text: 'Tag Export'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/accounting-other--blue.svg'; - }; - readonly category: { - readonly value: 'category_template'; - readonly text: 'Category Export'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/accounting-other--blue.svg'; - }; - readonly detailed: { - readonly value: 'detailed_export'; - readonly text: 'All Data - Expense Level Export'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/accounting-other--blue.svg'; - }; - readonly report: { - readonly value: 'report_level_export'; - readonly text: 'All Data - Report Level Export'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/accounting-other--blue.svg'; - }; - readonly tax: { - readonly value: 'multiple_tax_export'; - readonly text: 'Canadian Multiple Tax Export'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/accounting-other--blue.svg'; - }; - readonly perdiem: { - readonly value: 'per_diem_export'; - readonly text: 'Per Diem Export'; - readonly image: 'https://d2k5nsl2zxldvw.cloudfront.net/images/icons/accounting-other--blue.svg'; - }; - }; - readonly NVP: { - readonly DISMISSED_VIOLATIONS: 'dismissedViolations'; - }; - readonly FILESIZE: { - readonly BYTES_IN_MEGABYTE: 1000000; - readonly MAX: 10000000; - }; - readonly PARTNER_NAMES: { - readonly IPHONE: 'iphone'; - readonly ANDROID: 'android'; - readonly CHAT: 'chat-expensify-com'; - }; - readonly LOGIN_TYPES: { - readonly WEB: 'login'; - readonly MOBILE: 'device'; - }; - readonly EXPENSIFY_CARD: { - readonly FEED_NAME: 'Expensify Card'; - readonly FRAUD_STATES: { - readonly NONE: 0; - readonly DOMAIN_CARDS_REIMBURSEMENTS_INVESTIGATION: 1; - readonly DOMAIN_CARDS_RAPID_INCREASE_INVESTIGATION: 2; - readonly DOMAIN_CARDS_RAPID_INCREASE_CLEARED: 3; - readonly DOMAIN_CARDS_RAPID_INCREASE_CONFIRMED: 4; - readonly INDIVIDUAL_CARD_RAPID_INCREASE_INVESTIGATION: 5; - readonly INDIVIDUAL_CARD_RAPID_INCREASE_CLEARED: 6; - readonly INDIVIDUAL_CARD_RAPID_INCREASE_CONFIRMED: 7; - readonly SUSPICIOUS_PAN_ENTRY: 8; - readonly SUSPICIOUS_PAN_ENTRY_CLEARED: 9; - readonly SUSPICIOUS_PAN_ENTRY_CONFIRMED: 10; - }; - }; - readonly TRAVEL_BOOKING: { - readonly OPTIONS: { - readonly shortFlightFare: { - readonly economy: 'Economy'; - readonly premiumEconomy: 'Premium Economy'; - readonly business: 'Business'; - readonly first: 'First'; - }; - readonly longFlightFare: { - readonly economy: 'Economy'; - readonly premiumEconomy: 'Premium Economy'; - readonly business: 'Business'; - readonly first: 'First'; - }; - readonly hotelStar: { - readonly oneStar: '1'; - readonly twoStars: '2'; - readonly threeStars: '3'; - readonly fourStars: '4'; - readonly fiveStars: '5'; - }; - }; - readonly DEFAULT_OPTIONS: { - readonly shortFlightFare: 'economy'; - readonly longFlightFare: 'economy'; - readonly hotelStar: 'fourStars'; - }; - }; - readonly EXPENSIFY_DOMAINS: readonly ['expensify.com', 'expensifail.com', 'expensicorp.com']; - readonly SUBSCRIPTION_CHANGE_REASONS: { - readonly TOO_LIMITED: { - readonly id: 'tooLimited'; - readonly label: 'Functionality needs improvement'; - readonly prompt: 'What software are you migrating to and what led to this decision?'; - }; - readonly TOO_EXPENSIVE: { - readonly id: 'tooExpensive'; - readonly label: 'Too expensive'; - readonly prompt: 'What software are you migrating to and what led to this decision?'; - }; - readonly INADEQUATE_SUPPORT: { - readonly id: 'inadequateSupport'; - readonly label: 'Inadequate customer support'; - readonly prompt: 'What software are you migrating to and what led to this decision?'; - }; - readonly BUSINESS_CLOSING: { - readonly id: 'businessClosing'; - readonly label: 'Company closing, downsizing, or acquired'; - readonly prompt: 'What software are you migrating to and what led to this decision?'; - }; - }; -}; -/** - * UI Constants - */ -export declare const UI: { - readonly ICON: { - readonly DELETE: 'trashcan'; - readonly CAR: 'car'; - readonly CASH: 'cash'; - readonly MANAGED_CARD: 'corporate-card'; - readonly CARD: 'credit-card'; - readonly CLOCK: 'time'; - readonly PER_DIEM: 'per-diem'; - readonly PENDING_CARD: 'card-transaction-pending'; - readonly CSV_UPLOAD: 'csv-upload'; - readonly PENDING_CREDIT_CARD: 'credit-card-pending'; - }; - readonly spinnerDIV: '
'; - readonly spinnerSmallDIV: '
'; - readonly spinnerLargeDIV: '
'; - readonly spinnerClass: 'view_spinner'; - readonly SPINNER: 'spinner'; - readonly imageURLPrefix: 'https://d2k5nsl2zxldvw.cloudfront.net/images/'; - readonly ACTIVE: 'active'; - readonly ERROR: 'error'; - readonly HIDDEN: 'hidden'; - readonly INVISIBLE: 'invisible'; - readonly DEPRECIATED: 'depreciated'; - readonly DISABLED: 'disabled'; - readonly REQUIRED: 'required'; - readonly SELECT_DEFAULT: '###'; - readonly SELECTED: 'selected'; - readonly QR_CODE: 'js_qrCode'; - readonly DIALOG_Z_INDEX: 4000; -}; -export declare const PUBLIC_DOMAINS: readonly [ - 'accountant.com', - 'afis.ch', - 'aol.com', - 'artlover.com', - 'asia.com', - 'att.net', - 'bellsouth.net', - 'bills.expensify.com', - 'btinternet.com', - 'cheerful.com', - 'chromeexpensify.com', - 'comcast.net', - 'consultant.com', - 'contractor.com', - 'cox.net', - 'cpa.com', - 'cryptohistoryprice.com', - 'dr.com', - 'email.com', - 'engineer.com', - 'europe.com', - 'evernote.user', - 'execs.com', - 'expensify.cash', - 'expensify.sms', - 'gmail.com', - 'gmail.con', - 'googlemail.com', - 'hey.com', - 'hotmail.co.uk', - 'hotmail.com', - 'hotmail.fr', - 'hotmail.it', - 'icloud.com', - 'iname.com', - 'jeeviess.com', - 'live.com', - 'mac.com', - 'mail.com', - 'mail.ru', - 'mailfence.com', - 'me.com', - 'msn.com', - 'musician.org', - 'myself.com', - 'outlook.com', - 'pm.me', - 'post.com', - 'privaterelay.appleid.com', - 'proton.me', - 'protonmail.ch', - 'protonmail.com', - 'qq.com', - 'rigl.ch', - 'sasktel.net', - 'sbcglobal.net', - 'spacehotline.com', - 'tafmail.com', - 'techie.com', - 'usa.com', - 'verizon.net', - 'vomoto.com', - 'wolfandcranebar.tech', - 'workmail.com', - 'writeme.com', - 'yahoo.ca', - 'yahoo.co.in', - 'yahoo.co.uk', - 'yahoo.com', - 'yahoo.com.br', - 'ymail.com', -]; diff --git a/lib/CONST.jsx b/lib/CONST.ts similarity index 98% rename from lib/CONST.jsx rename to lib/CONST.ts index b68261b4..764469dd 100644 --- a/lib/CONST.jsx +++ b/lib/CONST.ts @@ -8,14 +8,14 @@ const MOMENT_FORMAT_STRING = 'YYYY-MM-DD'; /** * URL of our CloudFront Instance */ -export const g_cloudFront = 'https://d2k5nsl2zxldvw.cloudfront.net'; +const g_cloudFront = 'https://d2k5nsl2zxldvw.cloudfront.net'; /** * URL of our image CDN */ -export const g_cloudFrontImg = `${g_cloudFront}/images/`; +const g_cloudFrontImg = `${g_cloudFront}/images/`; -export const CONST = { +const CONST = { CORPAY_DIRECT_REIMBURSEMENT_CURRENCIES: ['USD', 'GBP', 'EUR', 'AUD', 'CAD'], /** @@ -361,6 +361,7 @@ export const CONST = { * * @type RegExp */ + // eslint-disable-next-line no-misleading-character-class EMOJIS: /[\p{Extended_Pictographic}\u200d\u{1f1e6}-\u{1f1ff}\u{1f3fb}-\u{1f3ff}\u{e0020}-\u{e007f}\u20E3\uFE0F]|[#*0-9]\uFE0F?\u20E3/gu, /** @@ -883,12 +884,12 @@ export const CONST = { prompt: 'What software are you migrating to and what led to this decision?', }, }, -}; +} as const; /** * UI Constants */ -export const UI = { +const UI = { ICON: { DELETE: 'trashcan', CAR: 'car', @@ -922,10 +923,10 @@ export const UI = { // Base z-index for dialogs $zindex-dialog in _vars.scss should take it's value from here! DIALOG_Z_INDEX: 4000, -}; +} as const; // List of most frequently used public domains -export const PUBLIC_DOMAINS = [ +const PUBLIC_DOMAINS = [ 'accountant.com', 'afis.ch', 'aol.com', @@ -997,4 +998,6 @@ export const PUBLIC_DOMAINS = [ 'yahoo.com', 'yahoo.com.br', 'ymail.com', -]; +] as const; + +export {g_cloudFront, g_cloudFrontImg, CONST, UI, PUBLIC_DOMAINS}; From 3914542418ff48d4a83dc9f1e06b656f02dd0c3e Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 11:54:30 +0200 Subject: [PATCH 018/168] Add endlines --- .prettierignore | 2 +- tsconfig.build.json | 2 +- tsconfig.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.prettierignore b/.prettierignore index b67fe5c9..d54e0bdf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,4 @@ dist package.json package-lock.json -*.html \ No newline at end of file +*.html diff --git a/tsconfig.build.json b/tsconfig.build.json index eb941d63..d04f033a 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -2,4 +2,4 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "./tsconfig.json", "include": ["./lib"] -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 9873ff27..92431b67 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,4 +18,4 @@ "outDir": "./dist" }, "exclude": ["**/node_modules/**/*", "**/dist/**/*"] -} \ No newline at end of file +} From 0c0c65c1d35b486ab5a639d555dd84ae288f7ef2 Mon Sep 17 00:00:00 2001 From: nkdengineer <161821005+nkdengineer@users.noreply.github.com> Date: Fri, 17 May 2024 17:07:50 +0700 Subject: [PATCH 019/168] Update lib/str.d.ts Co-authored-by: Benjamin Limpich --- lib/str.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/str.d.ts b/lib/str.d.ts index e4eb8acb..f48bc35a 100644 --- a/lib/str.d.ts +++ b/lib/str.d.ts @@ -478,7 +478,7 @@ declare const Str: { /** * Check for whether a phone number is valid. * @param phone - * @deprecated use isValidE164Phone to validate E.164 phone numbers or isValidPhoneFormat to validate phone numbers in general + * @deprecated use isValidE164Phone to validate E.164 phone numbers, isValidPhoneFormat to validate phone number format, or isValidPhoneNumber to validate phone numbers in general */ isValidPhone(phone: string): boolean; /** From 1369f15b372feb507ef01101518db950fd4dfe0e Mon Sep 17 00:00:00 2001 From: nkdengineer <161821005+nkdengineer@users.noreply.github.com> Date: Fri, 17 May 2024 17:08:30 +0700 Subject: [PATCH 020/168] Update lib/str.js Co-authored-by: Benjamin Limpich --- lib/str.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/str.js b/lib/str.js index 65007fe2..12571587 100644 --- a/lib/str.js +++ b/lib/str.js @@ -937,7 +937,6 @@ const Str = { * * @return {bool} */ - isValidPhoneNumber(phone) { return parsePhoneNumber(phone).possible; }, From 9192abef6e2d48db713d2e344ea2d93f8f737e04 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 17 May 2024 17:09:55 +0700 Subject: [PATCH 021/168] fix: typo --- lib/ExpensiMark.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 58234dba..23ca7eb7 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -200,8 +200,8 @@ export default class ExpensiMark { replacement: (match, g1, g2) => { const phoneNumberRegex = new RegExp(`^${Constants.CONST.REG_EXP.PHONE_PART}$`); const mention = g2.slice(1); - const metionWithoutSMSDomain = Str.removeSMSDomain(mention); - if (!Str.isValidMention(match) || (phoneNumberRegex.test(metionWithoutSMSDomain) && !Str.isValidPhoneNumber(metionWithoutSMSDomain))) { + const mentionWithoutSMSDomain = Str.removeSMSDomain(mention); + if (!Str.isValidMention(match) || (phoneNumberRegex.test(mentionWithoutSMSDomain) && !Str.isValidPhoneNumber(mentionWithoutSMSDomain))) { return match; } const phoneRegex = new RegExp(`^@${Constants.CONST.REG_EXP.PHONE_PART}$`); From 13345403d0d7a1f706c6a713bd066d3698143d2a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 13:44:22 +0200 Subject: [PATCH 022/168] Fix basic eslint errors --- .eslintrc.js | 1 + lib/API.jsx | 4 ++-- lib/ExpenseRule.jsx | 6 +----- lib/Log.jsx | 1 + lib/ReportHistoryStore.jsx | 3 --- lib/components/form/element/onOffSwitch.jsx | 1 + 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c1de71a4..134c038d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { 'react/jsx-filename-extension': 'off', 'rulesdir/no-api-in-views': 'off', 'rulesdir/no-multiple-api-calls': 'off', + 'rulesdir/prefer-import-module-contents': 'off', 'no-constructor-return': 'off', 'import/extensions': [ 'error', diff --git a/lib/API.jsx b/lib/API.jsx index 6a6d0482..6393f28d 100644 --- a/lib/API.jsx +++ b/lib/API.jsx @@ -823,7 +823,7 @@ export default function API(network, args) { * * @returns {APIDeferred} */ - createAdminIssuedVirtualCard: function (parameters) { + createAdminIssuedVirtualCard(parameters) { const commandName = 'Card_CreateAdminIssuedVirtualCard'; requireParameters(['cardTitle', 'assigneeEmail', 'cardLimit', 'cardLimitType', 'domainName'], parameters, commandName); return performPOSTRequest(commandName, parameters); @@ -842,7 +842,7 @@ export default function API(network, args) { * * @returns {APIDeferred} */ - editAdminIssuedVirtualCard: function (parameters) { + editAdminIssuedVirtualCard(parameters) { const commandName = 'Card_EditAdminIssuedVirtualCard'; requireParameters(['domainName', 'cardID', 'cardTitle', 'assigneeEmail', 'cardLimit', 'cardLimitType'], parameters, commandName); return performPOSTRequest(commandName, parameters); diff --git a/lib/ExpenseRule.jsx b/lib/ExpenseRule.jsx index 5d614932..03c66be3 100644 --- a/lib/ExpenseRule.jsx +++ b/lib/ExpenseRule.jsx @@ -21,11 +21,7 @@ export default class ExpenseRule { * @return {Object} */ getApplyWhenByField(field) { - return ( - _.find(this.applyWhen, (conditions) => { - return conditions.field === field; - }) || {} - ); + return _.find(this.applyWhen, (conditions) => conditions.field === field) || {}; } /** diff --git a/lib/Log.jsx b/lib/Log.jsx index ba0ca1b1..b114979b 100644 --- a/lib/Log.jsx +++ b/lib/Log.jsx @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import _ from 'underscore'; import API from './API'; import Network from './Network'; diff --git a/lib/ReportHistoryStore.jsx b/lib/ReportHistoryStore.jsx index 6cabca88..c8acae97 100644 --- a/lib/ReportHistoryStore.jsx +++ b/lib/ReportHistoryStore.jsx @@ -263,9 +263,6 @@ export default class ReportHistoryStore { delete this.cache[reportID]; } - // We'll poll the API for the un-cached history - const cachedHistory = this.cache[reportID] || []; - this.API.Report_GetHistory({ reportID, }) diff --git a/lib/components/form/element/onOffSwitch.jsx b/lib/components/form/element/onOffSwitch.jsx index b3a4da29..efba64de 100644 --- a/lib/components/form/element/onOffSwitch.jsx +++ b/lib/components/form/element/onOffSwitch.jsx @@ -176,6 +176,7 @@ class OnOffSwitch extends Component { descriptionElm = (
); From d31f093ce46bbf4209fb78e913629b626e6e2d34 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 17 May 2024 13:49:33 +0200 Subject: [PATCH 023/168] Fix rest of the eslint issues --- lib/API.jsx | 1 + lib/Cookie.jsx | 2 +- lib/CredentialsWrapper.jsx | 4 ++- lib/Logger.jsx | 2 ++ lib/Network.jsx | 1 + lib/Templates.jsx | 27 ++++++++++----------- lib/components/form/element/onOffSwitch.jsx | 1 + 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/lib/API.jsx b/lib/API.jsx index 6393f28d..41cdafa2 100644 --- a/lib/API.jsx +++ b/lib/API.jsx @@ -150,6 +150,7 @@ export default function API(network, args) { * @param {String} commandName The name of the API command */ 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); diff --git a/lib/Cookie.jsx b/lib/Cookie.jsx index 8dad1345..d2ae6f3c 100644 --- a/lib/Cookie.jsx +++ b/lib/Cookie.jsx @@ -58,7 +58,7 @@ function set(name, value, expiredays) { // Get expiry date, set const exdate = new Date(); exdate.setDate(exdate.getDate() + expiredays); - document.cookie = `${name}=${encodeURIComponent(value)}` + `${expiredays === null ? '' : `;expires=${exdate.toUTCString()}`}`; + document.cookie = `${name}=${encodeURIComponent(value)}${expiredays === null ? '' : `;expires=${exdate.toUTCString()}`}`; } /** diff --git a/lib/CredentialsWrapper.jsx b/lib/CredentialsWrapper.jsx index b6283c07..90fe7759 100644 --- a/lib/CredentialsWrapper.jsx +++ b/lib/CredentialsWrapper.jsx @@ -1,6 +1,6 @@ import localForage from 'localforage'; -export const LOGIN_PARTNER_DETAILS = { +const LOGIN_PARTNER_DETAILS = { CREDENTIALS_KEY: 'DEVICE_SESSION_CREDENTIALS', EXPENSIFY_PARTNER_PREFIX: 'expensify.', PARTNER_NAME: 'chat-expensify-com', @@ -33,6 +33,7 @@ const CredentialWrapper = { */ setCredentials(credentials) { if (!credentials.partnerUserID || !credentials.partnerUserSecret) { + // eslint-disable-next-line prefer-promise-reject-errors return Promise.reject('Invalid credential pair'); } @@ -48,3 +49,4 @@ const CredentialWrapper = { }; export default CredentialWrapper; +export {LOGIN_PARTNER_DETAILS}; diff --git a/lib/Logger.jsx b/lib/Logger.jsx index d1e8180b..29735844 100644 --- a/lib/Logger.jsx +++ b/lib/Logger.jsx @@ -30,6 +30,7 @@ export default class Logger { // We don't care about log setting web cookies so let's define it as false const linesToLog = _.map(this.logLines, (l) => { + // eslint-disable-next-line no-param-reassign delete l.onlyFlushWithOthers; return l; }); @@ -38,6 +39,7 @@ export default class Logger { if (!promise) { return; } + // eslint-disable-next-line rulesdir/prefer-early-return promise.then((response) => { if (response.requestID) { this.info('Previous log requestID', false, {requestID: response.requestID}, true); diff --git a/lib/Network.jsx b/lib/Network.jsx index ee388da0..96897136 100644 --- a/lib/Network.jsx +++ b/lib/Network.jsx @@ -64,6 +64,7 @@ export default function Network(endpoint) { if (isNewURLFormat) { // Remove command from parameters and use it in the URL const command = parameters.command; + // eslint-disable-next-line no-param-reassign delete parameters.command; newURL = `${endpoint}${command}`; } diff --git a/lib/Templates.jsx b/lib/Templates.jsx index 5f2c3878..e5e21fe8 100644 --- a/lib/Templates.jsx +++ b/lib/Templates.jsx @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import _ from 'underscore'; import $ from 'jquery'; @@ -67,6 +68,7 @@ export default (function () { get(data = {}) { // Add the "template" object to the parameter to allow nested templates const dataToCompile = {...data}; + // eslint-disable-next-line no-undef dataToCompile.nestedTemplate = Templates.get; if (!this.compiled) { this.compiled = _.template($(`#${this.id}`).html()); @@ -119,22 +121,18 @@ export default (function () { * @return {String} */ get(templatePath, data = {}) { - try { - const template = getTemplate(templatePath); - if (_.isUndefined(template)) { - throw Error(`Template '${templatePath}' is not defined`); - } - - // Check for the absense of get which means someone is likely using - // the templating engine wrong and trying to access a template namespace - if (!{}.propertyIsEnumerable.call(template, 'get')) { - throw Error(`'${templatePath}' is not a valid template path`); - } + const template = getTemplate(templatePath); + if (_.isUndefined(template)) { + throw Error(`Template '${templatePath}' is not defined`); + } - return template.get(data); - } catch (err) { - throw err; + // Check for the absense of get which means someone is likely using + // the templating engine wrong and trying to access a template namespace + if (!{}.propertyIsEnumerable.call(template, 'get')) { + throw Error(`'${templatePath}' is not a valid template path`); } + + return template.get(data); }, /** @@ -151,6 +149,7 @@ export default (function () { */ init() { // Read the DOM to find all the templates, and make them available to the code + // eslint-disable-next-line rulesdir/prefer-underscore-method $('.js_template').each((__, $el) => { const namespaceElements = $el.id.split('_'); const id = namespaceElements.pop(); diff --git a/lib/components/form/element/onOffSwitch.jsx b/lib/components/form/element/onOffSwitch.jsx index efba64de..72780595 100644 --- a/lib/components/form/element/onOffSwitch.jsx +++ b/lib/components/form/element/onOffSwitch.jsx @@ -19,6 +19,7 @@ const propTypes = { labelOnRight: PropTypes.bool, // Classes of the label + // eslint-disable-next-line react/forbid-prop-types labelClasses: PropTypes.any, // True if the switch is on From eb6f98b4b46aa0d2b989ca62523721fa2f5a25f8 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 08:23:41 +0200 Subject: [PATCH 024/168] Run prettier --- lib/ExpensiMark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 21f7081b..194b6b05 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -178,7 +178,7 @@ export default class ExpensiMark { { name: 'reportMentions', - regex: /(?$1', }, From a5b8bb778d87e52c93f8c4be56fa4e7b50724a13 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 08:29:37 +0200 Subject: [PATCH 025/168] Add babel to tests to enable Typescript --- babel.config.js | 11 +---- package-lock.json | 108 +++++++++++++++++++++++++++++++--------------- package.json | 1 + 3 files changed, 75 insertions(+), 45 deletions(-) diff --git a/babel.config.js b/babel.config.js index d7c97f55..f792e8e1 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,12 +1,3 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - }, - ], - ], + presets: [['@babel/preset-env', {targets: {node: 'current'}}], '@babel/preset-typescript'], }; diff --git a/package-lock.json b/package-lock.json index 81074c4e..eb88e12b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.24.4", + "@babel/preset-typescript": "^7.24.1", "@lwc/eslint-plugin-lwc": "^1.7.2", "@types/jest": "^29.5.12", "@types/react-dom": "^18.3.0", @@ -249,19 +250,19 @@ "dev": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", - "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", + "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.24.5", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-split-export-declaration": "^7.24.5", "semver": "^6.3.1" }, "engines": { @@ -357,12 +358,12 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", + "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -412,9 +413,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -479,30 +480,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -777,12 +778,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -894,12 +895,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -1632,6 +1633,24 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz", + "integrity": "sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.5", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-typescript": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", @@ -1813,6 +1832,25 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", + "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-syntax-jsx": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-typescript": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", @@ -1867,13 +1905,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { diff --git a/package.json b/package.json index b2dfc076..c7875b7b 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.24.4", + "@babel/preset-typescript": "^7.24.1", "@lwc/eslint-plugin-lwc": "^1.7.2", "@types/jest": "^29.5.12", "@types/react-dom": "^18.3.0", From 1df5b90804ec21e83e6765d85812b46425b6efe6 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 10:06:28 +0200 Subject: [PATCH 026/168] Migrate Logger to TS --- .eslintrc.js | 3 ++ lib/{Logger.jsx => Logger.ts} | 68 +++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 27 deletions(-) rename lib/{Logger.jsx => Logger.ts} (56%) diff --git a/.eslintrc.js b/.eslintrc.js index 134c038d..fa6b2448 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -42,6 +42,9 @@ module.exports = { 'rulesdir/prefer-underscore-method': 'off', 'react/jsx-props-no-spreading': 'off', 'react/require-default-props': 'off', + 'valid-jsdoc': 'off', + 'es/no-optional-chaining': 'off', + 'es/no-nullish-coalescing': 'off', 'react/jsx-filename-extension': ['error', {extensions: ['.tsx', '.jsx']}], 'import/no-unresolved': 'error', 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], diff --git a/lib/Logger.jsx b/lib/Logger.ts similarity index 56% rename from lib/Logger.jsx rename to lib/Logger.ts index 29735844..cfd002ae 100644 --- a/lib/Logger.jsx +++ b/lib/Logger.ts @@ -1,8 +1,22 @@ -import _ from 'underscore'; +type Parameters = string | Record | Array>; +type ServerLoggingCallbackOptions = {api_setCookie: boolean; logPacket: string}; +type ServerLoggingCallback = (logger: Logger, options: ServerLoggingCallbackOptions) => Promise<{requestID: string}> | undefined; +type ClientLoggingCallBack = (message: string) => void; +type LogLine = {message: string; parameters: Parameters; onlyFlushWithOthers?: boolean; timestamp: Date}; +type LoggerOptions = {serverLoggingCallback: ServerLoggingCallback; isDebug: boolean; clientLoggingCallback: ClientLoggingCallBack}; const MAX_LOG_LINES_BEFORE_FLUSH = 50; + export default class Logger { - constructor({serverLoggingCallback, isDebug, clientLoggingCallback}) { + logLines: LogLine[]; + + serverLoggingCallback: ServerLoggingCallback; + + clientLoggingCallback: ClientLoggingCallBack; + + isDebug: boolean; + + constructor({serverLoggingCallback, isDebug, clientLoggingCallback}: LoggerOptions) { // An array of log lines that limits itself to a certain number of entries (deleting the oldest) this.logLines = []; this.serverLoggingCallback = serverLoggingCallback; @@ -10,26 +24,27 @@ export default class Logger { this.isDebug = isDebug; // Public Methods + // eslint-disable-next-line no-constructor-return return { info: this.info.bind(this), alert: this.alert.bind(this), warn: this.warn.bind(this), hmmm: this.hmmm.bind(this), client: this.client.bind(this), - }; + } as this; } /** * Ask the server to write the log message */ - logToServer() { + logToServer(): void { // We do not want to call the server with an empty list or if all the lines has onlyFlushWithOthers=true - if (!this.logLines.length || _.all(this.logLines, (l) => l.onlyFlushWithOthers)) { + if (!this.logLines.length || this.logLines?.every((l) => l.onlyFlushWithOthers)) { return; } // We don't care about log setting web cookies so let's define it as false - const linesToLog = _.map(this.logLines, (l) => { + const linesToLog = this.logLines?.map((l) => { // eslint-disable-next-line no-param-reassign delete l.onlyFlushWithOthers; return l; @@ -49,12 +64,11 @@ export default class Logger { /** * Add a message to the list - * @param {String} message - * @param {Object|String} parameters The parameters associated with the message - * @param {Boolean} forceFlushToServer Should we force flushing all logs to server? - * @param {Boolean} onlyFlushWithOthers A request will never be sent to the server if all loglines have this set to true + * @param parameters The parameters associated with the message + * @param forceFlushToServer Should we force flushing all logs to server? + * @param onlyFlushWithOthers A request will never be sent to the server if all loglines have this set to true */ - add(message, parameters, forceFlushToServer, onlyFlushWithOthers = false) { + add(message: string, parameters: Parameters, forceFlushToServer: boolean, onlyFlushWithOthers = false) { const length = this.logLines.push({ message, parameters, @@ -76,12 +90,12 @@ export default class Logger { * Caches an informational message locally, to be sent to the server if * needed later. * - * @param {String} message The message to log. - * @param {Boolean} sendNow if true, the message will be sent right away. - * @param {Object|String} parameters The parameters to send along with the message - * @param {Boolean} onlyFlushWithOthers A request will never be sent to the server if all loglines have this set to true + * @param message The message to log. + * @param sendNow if true, the message will be sent right away. + * @param parameters The parameters to send along with the message + * @param onlyFlushWithOthers A request will never be sent to the server if all loglines have this set to true */ - info(message, sendNow = false, parameters = '', onlyFlushWithOthers = false) { + info(message: string, sendNow = false, parameters: Parameters = '', onlyFlushWithOthers = false) { const msg = `[info] ${message}`; this.add(msg, parameters, sendNow, onlyFlushWithOthers); } @@ -89,16 +103,16 @@ export default class Logger { /** * Logs an alert. * - * @param {String} message The message to alert. - * @param {Object|String} parameters The parameters to send along with the message - * @param {Boolean} includeStackTrace Must be disabled for testing + * @param message The message to alert. + * @param parameters The parameters to send along with the message + * @param includeStackTrace Must be disabled for testing */ - alert(message, parameters = {}, includeStackTrace = true) { + alert(message: string, parameters: Parameters = {}, includeStackTrace = true) { const msg = `[alrt] ${message}`; const params = parameters; if (includeStackTrace) { - params.stack = JSON.stringify(new Error().stack); + (params as Record).stack = JSON.stringify(new Error().stack); } this.add(msg, params, true); @@ -110,7 +124,7 @@ export default class Logger { * @param {String} message The message to warn. * @param {Object|String} parameters The parameters to send along with the message */ - warn(message, parameters = '') { + warn(message: string, parameters: Parameters = '') { const msg = `[warn] ${message}`; this.add(msg, parameters, true); } @@ -118,10 +132,10 @@ export default class Logger { /** * Logs a hmmm. * - * @param {String} message The message to hmmm. - * @param {Object|String} parameters The parameters to send along with the message + * @param message The message to hmmm. + * @param parameters The parameters to send along with the message */ - hmmm(message, parameters = '') { + hmmm(message: string, parameters: Parameters = '') { const msg = `[hmmm] ${message}`; this.add(msg, parameters, false); } @@ -129,9 +143,9 @@ export default class Logger { /** * Logs a message in the browser console. * - * @param {String} message The message to log. + * @param message The message to log. */ - client(message) { + client(message: string) { if (!this.clientLoggingCallback) { return; } From 78b719e981f703ca6c9faa3ab7be3f8e6ac4e29c Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 10:06:36 +0200 Subject: [PATCH 027/168] Migrate Device to TS --- lib/Device.d.ts | 7 ------- lib/{Device.jsx => Device.ts} | 9 ++++++++- package-lock.json | 7 +++++++ package.json | 1 + 4 files changed, 16 insertions(+), 8 deletions(-) delete mode 100644 lib/Device.d.ts rename lib/{Device.jsx => Device.ts} (65%) diff --git a/lib/Device.d.ts b/lib/Device.d.ts deleted file mode 100644 index 6cb8abd3..00000000 --- a/lib/Device.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare function getOSAndName(): { - os: string | undefined; - osVersion: string | undefined; - deviceName: string | undefined; - deviceVersion: string | undefined; -}; -export {getOSAndName}; diff --git a/lib/Device.jsx b/lib/Device.ts similarity index 65% rename from lib/Device.jsx rename to lib/Device.ts index 43419dba..1100cb45 100644 --- a/lib/Device.jsx +++ b/lib/Device.ts @@ -1,6 +1,13 @@ import {UAParser} from 'ua-parser-js'; -function getOSAndName() { +type DeviceInfo = { + os: string | undefined; + osVersion: string | undefined; + deviceName: string | undefined; + deviceVersion: string | undefined; +}; + +function getOSAndName(): DeviceInfo { const parser = new UAParser(); const result = parser.getResult(); return { diff --git a/package-lock.json b/package-lock.json index eb88e12b..669cab93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@lwc/eslint-plugin-lwc": "^1.7.2", "@types/jest": "^29.5.12", "@types/react-dom": "^18.3.0", + "@types/ua-parser-js": "^0.7.39", "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", "babel-jest": "^29.0.0", @@ -3083,6 +3084,12 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/ua-parser-js": { + "version": "0.7.39", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", + "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", diff --git a/package.json b/package.json index c7875b7b..b0a00ddc 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@lwc/eslint-plugin-lwc": "^1.7.2", "@types/jest": "^29.5.12", "@types/react-dom": "^18.3.0", + "@types/ua-parser-js": "^0.7.39", "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", "babel-jest": "^29.0.0", From d12063c2c8209a9af260baa71eb807b28fa4c4bc Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 10:06:57 +0200 Subject: [PATCH 028/168] Remove Logger.d.ts --- lib/Logger.d.ts | 63 ------------------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 lib/Logger.d.ts diff --git a/lib/Logger.d.ts b/lib/Logger.d.ts deleted file mode 100644 index f2b6995e..00000000 --- a/lib/Logger.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -declare type Parameters = string | Record | Array>; -declare type ServerLoggingCallbackOptions = {api_setCookie: boolean; logPacket: string}; -declare type ServerLoggingCallback = (logger: Logger, options: ServerLoggingCallbackOptions) => Promise<{requestID: string}> | undefined; -declare type ClientLoggingCallBack = (message: string) => void; -declare type LogLine = {message: string; parameters: Parameters; onlyFlushWithOthers: boolean; timestamp: Date}; -export default class Logger { - logLines: LogLine[]; - serverLoggingCallback: ServerLoggingCallback; - clientLoggingCallback: ClientLoggingCallBack; - isDebug: boolean; - constructor({serverLoggingCallback, isDebug, clientLoggingCallback}: {serverLoggingCallback: ServerLoggingCallback; isDebug: boolean; clientLoggingCallback: ClientLoggingCallBack}); - /** - * Ask the server to write the log message - */ - logToServer(): void; - /** - * Add a message to the list - * @param message - * @param parameters The parameters associated with the message - * @param forceFlushToServer Should we force flushing all logs to server? - * @param onlyFlushWithOthers A request will never be sent to the server if all loglines have this set to true - */ - add(message: string, parameters: Parameters, forceFlushToServer: boolean, onlyFlushWithOthers?: boolean): void; - /** - * Caches an informational message locally, to be sent to the server if - * needed later. - * - * @param message The message to log. - * @param sendNow if true, the message will be sent right away. - * @param parameters The parameters to send along with the message - * @param onlyFlushWithOthers A request will never be sent to the server if all loglines have this set to true - */ - info(message: string, sendNow?: boolean, parameters?: Parameters, onlyFlushWithOthers?: boolean): void; - /** - * Logs an alert. - * - * @param message The message to alert. - * @param parameters The parameters to send along with the message - * @param includeStackTrace Must be disabled for testing - */ - alert(message: string, parameters?: Parameters, includeStackTrace?: boolean): void; - /** - * Logs a warn. - * - * @param message The message to warn. - * @param parameters The parameters to send along with the message - */ - warn(message: string, parameters?: Parameters): void; - /** - * Logs a hmmm. - * - * @param message The message to hmmm. - * @param parameters The parameters to send along with the message - */ - hmmm(message: string, parameters?: Parameters): void; - /** - * Logs a message in the browser console. - * - * @param message The message to log. - */ - client(message: string): void; -} -export {}; From 55933f5b6ee38ebcb01c2c1585f17bc4c761dafa Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 10:16:14 +0200 Subject: [PATCH 029/168] Migrate fastMerge to TS --- lib/fastMerge.d.ts | 10 ----- lib/{fastMerge.js => fastMerge.ts} | 62 +++++++++++++++--------------- 2 files changed, 31 insertions(+), 41 deletions(-) delete mode 100644 lib/fastMerge.d.ts rename lib/{fastMerge.js => fastMerge.ts} (51%) diff --git a/lib/fastMerge.d.ts b/lib/fastMerge.d.ts deleted file mode 100644 index 4355619f..00000000 --- a/lib/fastMerge.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Merges two objects and removes null values if "shouldRemoveNullObjectValues" is set to true - * - * We generally want to remove null values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk. - * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values. - * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations. - */ -declare function fastMerge(target: T, source: T, shouldRemoveNullObjectValues: boolean): T; - -export default fastMerge; diff --git a/lib/fastMerge.js b/lib/fastMerge.ts similarity index 51% rename from lib/fastMerge.js rename to lib/fastMerge.ts index 57352cff..6b786654 100644 --- a/lib/fastMerge.js +++ b/lib/fastMerge.ts @@ -1,66 +1,71 @@ -import _ from 'underscore'; +/* eslint-disable @typescript-eslint/prefer-for-of */ // Mostly copied from https://medium.com/@lubaka.a/how-to-remove-lodash-performance-improvement-b306669ad0e1 /** - * @param {mixed} val - * @returns {boolean} + * Checks whether the given value can be merged. It has to be an object, but not an array, RegExp or Date. */ -function isMergeableObject(val) { - const nonNullObject = val != null ? typeof val === 'object' : false; - return nonNullObject && Object.prototype.toString.call(val) !== '[object RegExp]' && Object.prototype.toString.call(val) !== '[object Date]' && !_.isArray(val); +function isMergeableObject(value: unknown): value is Record { + const nonNullObject = value != null ? typeof value === 'object' : false; + return nonNullObject && Object.prototype.toString.call(value) !== '[object RegExp]' && Object.prototype.toString.call(value) !== '[object Date]' && !Array.isArray(value); } /** - * @param {Object} target - * @param {Object} source - * @param {Boolean} shouldRemoveNullObjectValues - * @returns {Object} + * Merges the source object into the target object. + * @param target - The target object. + * @param source - The source object. + * @param shouldRemoveNestedNulls - If true, null object values will be removed. + * @returns - The merged object. */ -function mergeObject(target, source, shouldRemoveNullObjectValues = true) { - const destination = {}; +function mergeObject>(target: TObject | null, source: TObject, shouldRemoveNullObjectValues = true): TObject { + const destination: Record = {}; + if (isMergeableObject(target)) { // lodash adds a small overhead so we don't use it here - const targetKeys = _.keys(target); + const targetKeys = Object.keys(target); for (let i = 0; i < targetKeys.length; ++i) { const key = targetKeys[i]; + const sourceValue = source?.[key]; + const targetValue = target?.[key]; // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object - const isSourceOrTargetNull = target[key] === null || source[key] === null; + const isSourceOrTargetNull = targetValue === null || sourceValue === null; const shouldOmitSourceKey = shouldRemoveNullObjectValues && isSourceOrTargetNull; if (!shouldOmitSourceKey) { - destination[key] = target[key]; + destination[key] = targetValue; } } } // lodash adds a small overhead so we don't use it here - const sourceKeys = _.keys(source); + const sourceKeys = Object.keys(source); for (let i = 0; i < sourceKeys.length; ++i) { const key = sourceKeys[i]; + const sourceValue = source?.[key]; + const targetValue = target?.[key]; // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object - const shouldOmitSourceKey = shouldRemoveNullObjectValues && source[key] === null; + const shouldOmitSourceKey = shouldRemoveNullObjectValues && sourceValue === null; // If we pass undefined as the updated value for a key, we want to generally ignore it - const isSourceKeyUndefined = source[key] === undefined; + const isSourceKeyUndefined = sourceValue === undefined; if (!isSourceKeyUndefined && !shouldOmitSourceKey) { - const isSourceKeyMergable = isMergeableObject(source[key]); + const isSourceKeyMergable = isMergeableObject(sourceValue); - if (isSourceKeyMergable && target[key]) { + if (isSourceKeyMergable && targetValue) { if (!shouldRemoveNullObjectValues || isSourceKeyMergable) { // eslint-disable-next-line no-use-before-define - destination[key] = fastMerge(target[key], source[key], shouldRemoveNullObjectValues); + destination[key] = fastMerge(targetValue as TObject, sourceValue, shouldRemoveNullObjectValues); } - } else if (!shouldRemoveNullObjectValues || source[key] !== null) { - destination[key] = source[key]; + } else if (!shouldRemoveNullObjectValues || sourceValue !== null) { + destination[key] = sourceValue; } } } - return destination; + return destination as TObject; } /** @@ -69,17 +74,12 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) { * We generally want to remove null values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk. * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values. * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations. - * - * @param {Object|Array} target - * @param {Object|Array} source - * @param {Boolean} shouldRemoveNullObjectValues - * @returns {Object|Array} */ -function fastMerge(target, source, shouldRemoveNullObjectValues = true) { +function fastMerge>(target: TObject | null, source: TObject | null, shouldRemoveNullObjectValues = true): TObject | null { // We have to ignore arrays and nullish values here, // otherwise "mergeObject" will throw an error, // because it expects an object as "source" - if (_.isArray(source) || source === null || source === undefined) { + if (Array.isArray(source) || source === null || source === undefined) { return source; } return mergeObject(target, source, shouldRemoveNullObjectValues); From 35b468333aa4592170d2a011236ead1b2fa3dbcc Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 12:32:14 +0200 Subject: [PATCH 030/168] Fix fastMerge types --- lib/fastMerge.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/fastMerge.ts b/lib/fastMerge.ts index 6b786654..a7ce03bd 100644 --- a/lib/fastMerge.ts +++ b/lib/fastMerge.ts @@ -17,7 +17,7 @@ function isMergeableObject(value: unknown): value is Record { * @param shouldRemoveNestedNulls - If true, null object values will be removed. * @returns - The merged object. */ -function mergeObject>(target: TObject | null, source: TObject, shouldRemoveNullObjectValues = true): TObject { +function mergeObject>(target: TObject, source: TObject, shouldRemoveNullObjectValues = true): TObject { const destination: Record = {}; if (isMergeableObject(target)) { @@ -75,14 +75,14 @@ function mergeObject>(target: TObject | * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values. * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations. */ -function fastMerge>(target: TObject | null, source: TObject | null, shouldRemoveNullObjectValues = true): TObject | null { +function fastMerge(target: TObject, source: TObject, shouldRemoveNullObjectValues = true): TObject { // We have to ignore arrays and nullish values here, // otherwise "mergeObject" will throw an error, // because it expects an object as "source" if (Array.isArray(source) || source === null || source === undefined) { return source; } - return mergeObject(target, source, shouldRemoveNullObjectValues); + return mergeObject(target as Record, source as Record, shouldRemoveNullObjectValues) as TObject; } export default fastMerge; From 2c983a1fa7567e0451406added17c14da974a5cb Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 12:32:39 +0200 Subject: [PATCH 031/168] Create missing index.ts file --- .eslintrc.js | 10 ++++++++++ lib/index.ts | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 lib/index.ts diff --git a/.eslintrc.js b/.eslintrc.js index fa6b2448..b996b4b7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -56,6 +56,16 @@ module.exports = { '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/array-type': ['error', {default: 'array-simple'}], '@typescript-eslint/consistent-type-definitions': 'off', + 'import/extensions': [ + 'error', + 'ignorePackages', + { + js: 'never', + jsx: 'never', + ts: 'never', + tsx: 'never', + }, + ], }, }, ], diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 00000000..72d34ee4 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,21 @@ +// eslint-disable-next-line rulesdir/no-api-in-views +export {default as API} from './API'; +export {default as APIDeferred} from './APIDeferred'; +export {default as BrowserDetect} from './BrowserDetect'; +export {g_cloudFront, g_cloudFrontImg, CONST, UI, PUBLIC_DOMAINS} from './CONST'; + +export {default as Cookie} from './Cookie'; +export {default as CredentialsWrapper, LOGIN_PARTNER_DETAILS} from './CredentialsWrapper'; +export * as Device from './Device'; +export {default as ExpensiMark} from './ExpensiMark'; +export {default as Logger} from './Logger'; +export {default as Network} from './Network'; +export {default as Num} from './Num'; +export {default as PageEvent} from './PageEvent'; +export {default as PubSub} from './PubSub'; +export {default as ReportHistoryStore} from './ReportHistoryStore'; +export {default as Templates} from './Templates'; +export {default as Url} from './Url'; +export {default as fastMerge} from './fastMerge'; +export {default as Str} from './str'; +export {default as TLD_REGEX} from './tlds'; From 0023d733a3e5bed7fa6c18aca7ef9af9c2367ac8 Mon Sep 17 00:00:00 2001 From: dragnoir Date: Mon, 20 May 2024 17:20:15 +0100 Subject: [PATCH 032/168] fix lint --- lib/ExpensiMark.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 45d7a9df..2d0e0a28 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -177,7 +177,7 @@ export default class ExpensiMark { { name: 'reportMentions', - regex: /(?$1', }, @@ -421,7 +421,7 @@ export default class ExpensiMark { { name: 'quote', regex: /<(blockquote|q)(?:"[^"]*"|'[^']*'|[^'">])*>([\s\S]*?)<\/\1>(?![^<]*(<\/pre>|<\/code>))/gi, - replacement: (match, g1, g2) => { + replacement(match) { // We remove the line break before heading inside quote to avoid adding extra line let resultString = match .replace(/\n?(

# )/g, '$1') @@ -429,21 +429,22 @@ export default class ExpensiMark { .trim() .split('\n'); - resultString = _.map(resultString, (m) => { + function processString(m) { // Recursive function to replace nested
with ">" function replaceBlockquotes(text) { - while (/
/i.test(text)) { - // Count how many
tags - let depth = (text.match(/
/gi) || []).length; - // Replace all blockquote tags and add ">" per depth level - text = text.replace(/
/gi, ''); - text = text.replace(/<\/blockquote>/gi, ''); - return `${'>'.repeat(depth)} ${text}`; - } - return text; - } + let modifiedText = text; + let depth; + do { + depth = (modifiedText.match(/
/gi) || []).length; + modifiedText = modifiedText.replace(/
/gi, ''); + modifiedText = modifiedText.replace(/<\/blockquote>/gi, ''); + } while (/
/i.test(modifiedText)); + return `${'>'.repeat(depth)} ${modifiedText}`; + } return replaceBlockquotes(m); - }).join('\n'); + } + + resultString = _.map(resultString, processString).join('\n'); // We want to keep
tag here and let method replaceBlockElementWithNewLine to handle the line break later return `
${resultString}
`; From cfc6b0a74a6d061c785ce52671f507563d6118df Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 21 May 2024 15:03:32 +0200 Subject: [PATCH 033/168] Adjust after review --- lib/Logger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Logger.ts b/lib/Logger.ts index cfd002ae..fc1af734 100644 --- a/lib/Logger.ts +++ b/lib/Logger.ts @@ -111,8 +111,8 @@ export default class Logger { const msg = `[alrt] ${message}`; const params = parameters; - if (includeStackTrace) { - (params as Record).stack = JSON.stringify(new Error().stack); + if (includeStackTrace && typeof params === 'object' && !Array.isArray(params)) { + params.stack = JSON.stringify(new Error().stack); } this.add(msg, params, true); From 4bbd3542f75deab25ab2779f4288da5e53b1026b Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 21 May 2024 15:28:49 +0200 Subject: [PATCH 034/168] Add Error to Logger parameters type --- lib/Logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Logger.ts b/lib/Logger.ts index fc1af734..a7ea2e28 100644 --- a/lib/Logger.ts +++ b/lib/Logger.ts @@ -1,4 +1,4 @@ -type Parameters = string | Record | Array>; +type Parameters = string | Record | Array> | Error; type ServerLoggingCallbackOptions = {api_setCookie: boolean; logPacket: string}; type ServerLoggingCallback = (logger: Logger, options: ServerLoggingCallbackOptions) => Promise<{requestID: string}> | undefined; type ClientLoggingCallBack = (message: string) => void; From 232f4adf5165cb8882ec26c360fcee2928af1453 Mon Sep 17 00:00:00 2001 From: Francois Laithier Date: Tue, 21 May 2024 17:19:18 -0700 Subject: [PATCH 035/168] Add `notifications@` as an invalida account to share reports with It was ommitted from https://github.com/Expensify/expensify-common/commit/25d49ac1271152ff8f42dd862f53987aa70d91de --- lib/CONST.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/CONST.jsx b/lib/CONST.jsx index b68261b4..7991d1e2 100644 --- a/lib/CONST.jsx +++ b/lib/CONST.jsx @@ -560,6 +560,7 @@ export const CONST = { 'qa+travisreceipts@expensify.com', 'bills@expensify.com', 'admin@expensify.com', + 'notifications@expensify.com', ], /** From b11b7cecb2a954789f9fc760ea952c7eafb40441 Mon Sep 17 00:00:00 2001 From: dominictb Date: Wed, 22 May 2024 16:00:33 +0700 Subject: [PATCH 036/168] fix: add rules for video conversion Signed-off-by: dominictb --- __tests__/ExpensiMark-HTML-test.js | 32 +++++++++++++++++ __tests__/ExpensiMark-Markdown-test.js | 25 +++++++++++++ lib/CONST.d.ts | 1 + lib/CONST.jsx | 2 ++ lib/ExpensiMark.d.ts | 8 +++++ lib/ExpensiMark.js | 50 +++++++++++++++++++++----- lib/fileHelpers.js | 12 +++++++ 7 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 lib/fileHelpers.js diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index d31b5dee..6f07c243 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -1951,6 +1951,38 @@ describe('multi-level blockquote', () => { }); }); +describe('Video markdown conversion to html tag', () => { + test('Single video with source', () => { + const testString = '![test](https://example.com/video.mp4)'; + const resultString = ''; + expect(parser.replace(testString)).toBe(resultString); + }); + + test('Text containing videos', () => { + const testString = 'A video of a banana: ![banana](https://example.com/banana.mp4) a video without name: !(https://example.com/developer.mp4)'; + const resultString = 'A video of a banana: a video without name: '; + expect(parser.replace(testString)).toBe(resultString); + }); + + test('Video with raw data attributes', () => { + const testString = '![test](https://example.com/video.mp4)'; + const resultString = ''; + expect(parser.replace(testString, {shouldKeepRawInput: true})).toBe(resultString); + }) + + test('Single video with extra cached attribues', () => { + const testString = '![test](https://example.com/video.mp4)'; + const resultString = ''; + expect(parser.replace(testString, { + extras: { + videoAttributeCache: { + 'https://example.com/video.mp4': 'data-expensify-height="100" data-expensify-width="100"' + } + } + })).toBe(resultString); + }) +}) + describe('Image markdown conversion to html tag', () => { test('Single image with alt text', () => { const testString = '![test](https://example.com/image.png)'; diff --git a/__tests__/ExpensiMark-Markdown-test.js b/__tests__/ExpensiMark-Markdown-test.js index 0f8e8831..1197c0be 100644 --- a/__tests__/ExpensiMark-Markdown-test.js +++ b/__tests__/ExpensiMark-Markdown-test.js @@ -832,3 +832,28 @@ describe('Image tag conversion to markdown', () => { expect(parser.htmlToMarkdown(testString)).toBe(resultString); }); }); + +describe('Video tag conversion to markdown', () => { + test('Video with name', () => { + const testString = ''; + const resultString = '![video](https://example.com/video.mp4)'; + expect(parser.htmlToMarkdown(testString)).toBe(resultString); + }) + + test('Video without name', () => { + const testString = ''; + const resultString = '!(https://example.com/video.mp4)'; + expect(parser.htmlToMarkdown(testString)).toBe(resultString); + }) + + test('While convert video, cache some extra attributes from the video tag', () => { + const cacheVideoAttributes = jest.fn(); + const testString = ''; + const resultString = '![video](https://example.com/video.mp4)'; + const extras = { + cacheVideoAttributes, + }; + expect(parser.htmlToMarkdown(testString, extras)).toBe(resultString); + expect(cacheVideoAttributes).toHaveBeenCalledWith("https://example.com/video.mp4", ' data-expensify-width="100" data-expensify-height="500" data-expensify-thumbnail-url="https://image.com/img.jpg"') + }) +}) diff --git a/lib/CONST.d.ts b/lib/CONST.d.ts index 77974b46..4500c9d4 100644 --- a/lib/CONST.d.ts +++ b/lib/CONST.d.ts @@ -742,6 +742,7 @@ export declare const CONST: { readonly prompt: 'What software are you migrating to and what led to this decision?'; }; }; + readonly VIDEO_EXTENSIONS: readonly string[] }; /** * UI Constants diff --git a/lib/CONST.jsx b/lib/CONST.jsx index 7991d1e2..0848c692 100644 --- a/lib/CONST.jsx +++ b/lib/CONST.jsx @@ -884,6 +884,8 @@ export const CONST = { prompt: 'What software are you migrating to and what led to this decision?', }, }, + + VIDEO_EXTENSIONS: ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv', '.webm'] }; /** diff --git a/lib/ExpensiMark.d.ts b/lib/ExpensiMark.d.ts index f431df7e..2d732d8d 100644 --- a/lib/ExpensiMark.d.ts +++ b/lib/ExpensiMark.d.ts @@ -37,7 +37,13 @@ declare type Rule = { declare type ExtrasObject = { reportIdToName?: Record; accountIDToName?: Record; + cacheVideoAttributes?: (vidSource: string, attrs: string) => void; }; + +declare type ExtraParamsForReplaceFunc = { + videoAttributeCache?: Record; +} + export default class ExpensiMark { rules: Rule[]; htmlToMarkdownRules: Rule[]; @@ -61,11 +67,13 @@ export default class ExpensiMark { filterRules, shouldEscapeText, shouldKeepRawInput, + extras, }?: { filterRules?: Name[]; disabledRules?: Name[]; shouldEscapeText?: boolean; shouldKeepRawInput?: boolean; + extras?: ExtraParamsForReplaceFunc; }, ): string; /** diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 21f7081b..e43edafb 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -3,6 +3,7 @@ import Str from './str'; import * as Constants from './CONST'; import * as UrlPatterns from './Url'; import Log from './Log'; +import * as fileHelpers from './fileHelpers'; const MARKDOWN_LINK_REGEX = new RegExp(`\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)]\\(${UrlPatterns.MARKDOWN_URL_REGEX}\\)(?![^<]*(<\\/pre>|<\\/code>))`, 'gi'); const MARKDOWN_IMAGE_REGEX = new RegExp(`\\!(?:\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)])?\\(${UrlPatterns.MARKDOWN_URL_REGEX}\\)(?![^<]*(<\\/pre>|<\\/code>))`, 'gi'); @@ -115,18 +116,32 @@ export default class ExpensiMark { }, /** - * Converts markdown style images to img tags e.g. ![Expensify](https://www.expensify.com/attachment.png) + * Converts markdown style images and videos to image/video tags e.g. ![Expensify](https://www.expensify.com/attachment.png) * We need to convert before linking rules since they will not try to create a link from an existing img * tag. * Additional sanitization is done to the alt attribute to prevent parsing it further to html by later * rules. */ { - name: 'image', + name: 'imageOrVideo', regex: MARKDOWN_IMAGE_REGEX, - replacement: (match, g1, g2) => `${this.escapeAttributeContent(g1)}`, - rawInputReplacement: (match, g1, g2) => - `${this.escapeAttributeContent(g1)}`, + replacement: (match, g1, g2, ...args) => { + const extras = args[args.length - 1]; + if (fileHelpers.isVideoFile(g2)) { + const extraAttrs = extras && extras.videoAttributeCache && extras.videoAttributeCache[g2]; + return `` + } + return `${this.escapeAttributeContent(g1)}` + }, + // will need to update this similarly + rawInputReplacement: (match, g1, g2, ...args) => { + const extras = args[args.length - 1]; + if (fileHelpers.isVideoFile(g2)) { + const extraAttrs = extras && extras.videoAttributeCache && extras.videoAttributeCache[g2]; + return `` + } + return `${this.escapeAttributeContent(g1)}` + } }, /** @@ -468,6 +483,23 @@ export default class ExpensiMark { return `!(${g2})`; }, }, + + { + name: 'video', + regex: /<]*data-expensify-source\s*=\s*(['"])(\S*?)\1(.*?)>([^><]*)<\/video>*(?![^<][\s\S]*?(<\/pre>|<\/code>))/gi, + replacement: (match, g1, g2, g3, g4, ...args) => { + const extras = args[args.length - 1]; + if(g3 && extras && extras.cacheVideoAttributes && typeof extras.cacheVideoAttributes === 'function') { + extras.cacheVideoAttributes(g2, g3); // cacheVideoAttributes(source, attributes) + } + if (g4) { + return `![${g4}](${g2})`; + } + + return `!(${g2})`; + }, + }, + { name: 'reportMentions', regex: //gi, @@ -631,7 +663,7 @@ export default class ExpensiMark { * * @returns {String} */ - replace(text, {filterRules = [], shouldEscapeText = true, shouldKeepRawInput = false, disabledRules = []} = {}) { + replace(text, {filterRules = [], shouldEscapeText = true, shouldKeepRawInput = false, disabledRules = [], extras} = {}) { // This ensures that any html the user puts into the comment field shows as raw html let replacedText = shouldEscapeText ? _.escape(text) : text; const rules = this.getHtmlRuleset(filterRules, disabledRules, shouldKeepRawInput); @@ -642,10 +674,12 @@ export default class ExpensiMark { replacedText = rule.pre(replacedText); } const replacementFunction = shouldKeepRawInput && rule.rawInputReplacement ? rule.rawInputReplacement : rule.replacement; + const replacementFnWithExtraParams = typeof replacementFunction === 'function' ? (...args) => replacementFunction(...args, extras) : replacementFunction; + if (rule.process) { - replacedText = rule.process(replacedText, replacementFunction, shouldKeepRawInput); + replacedText = rule.process(replacedText, replacementFnWithExtraParams, shouldKeepRawInput); } else { - replacedText = replacedText.replace(rule.regex, replacementFunction); + replacedText = replacedText.replace(rule.regex, replacementFnWithExtraParams); } // Post-process text after applying regex diff --git a/lib/fileHelpers.js b/lib/fileHelpers.js new file mode 100644 index 00000000..d162d749 --- /dev/null +++ b/lib/fileHelpers.js @@ -0,0 +1,12 @@ +import _ from 'lodash' +import * as Constants from "./CONST" + +const isVideoFile = (fileName) => { + const lowerCaseFileName = fileName.toLowerCase() + return _.some(Constants.CONST.VIDEO_EXTENSIONS, (ext => lowerCaseFileName.endsWith(ext))) +} + +export { + // eslint-disable-next-line import/prefer-default-export + isVideoFile +} \ No newline at end of file From 1260aa495815a1705bf0e6eacfdd13178ac03d06 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 22 May 2024 11:21:37 +0200 Subject: [PATCH 037/168] Migrate str to Typescript --- lib/str.ts | 1065 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1065 insertions(+) create mode 100644 lib/str.ts diff --git a/lib/str.ts b/lib/str.ts new file mode 100644 index 00000000..e324e34e --- /dev/null +++ b/lib/str.ts @@ -0,0 +1,1065 @@ +/* eslint-disable no-control-regex */ +import _ from 'underscore'; +import * as HtmlEntities from 'html-entities'; +import * as Constants from './CONST'; +import * as UrlPatterns from './Url'; + +const REMOVE_SMS_DOMAIN_PATTERN = /@expensify\.sms/gi; + +/** + * Checks if parameter is a string or function + * if it is a function then we will call it with + * any additional arguments. + */ +function result(parameter: string): string; +function result(parameter: (...args: A) => R, ...args: A): R; +function result(parameter: string | ((...args: A) => R), ...args: A): string | R { + if (typeof parameter === 'function') { + return parameter(...args); + } + + return parameter; +} + +const Str = { + /** + * Return true if the string is ending with the provided suffix + * + * @param str String ot search in + * @param suffix What to look for + */ + endsWith(str: string, suffix: string): boolean { + if (!str || !suffix) { + return false; + } + return str.substr(-suffix.length) === suffix; + }, + + /** + * Converts a USD string into th number of cents it represents. + * + * @param amountStr A string representing a USD value. + * @param allowFraction Flag indicating if fractions of cents should be + * allowed in the output. + * + * @return number The cent value of the @p amountStr. + */ + fromUSDToNumber(amountStr: string, allowFraction: boolean): number { + let amount: string | number = String(amountStr).replace(/[^\d.\-()]+/g, ''); + if (amount.match(/\(.*\)/)) { + const modifiedAmount = amount.replace(/[()]/g, ''); + amount = `-${modifiedAmount}`; + } + amount = Number(amount) * 100; + + amount = Math.round(amount * 1e3) / 1e3; + return allowFraction ? amount : Math.round(amount); + }, + + /** + * Truncates the middle section of a string based on the max allowed length + */ + truncateInMiddle(fullStr: string, maxLength: number): string { + if (fullStr.length <= maxLength) { + return fullStr; + } + + const separator = '...'; + const halfLengthToShow = (maxLength - separator.length) / 2; + const beginning = fullStr.substr(0, Math.ceil(halfLengthToShow)); + const end = fullStr.substr(fullStr.length - Math.floor(halfLengthToShow)); + + return beginning + separator + end; + }, + + /** + * Convert new line to
+ */ + nl2br(str: string): string { + return str.replace(/\n/g, '
'); + }, + + /** + * Decodes the given HTML encoded string. + * + * @param s The string to decode. + * @return string The decoded string. + */ + htmlDecode(s: string): string { + // Use jQuery if it exists or else use html-entities + if (typeof jQuery !== 'undefined') { + return jQuery('