Skip to content

Commit 2b2c259

Browse files
authored
5728 switch to eslint flat config (#5756)
* WIP: migrate to flat config * fix: remove duplicates * fix: parserOptions.project is not defined * fix: remove eslint ignore * feat: updated eslint config * fix: test rules * fix: vscode setting to use eslint flat config * feat: eslint flat config * fix: settings.json * feat: updated test rules * fix: ui tests * fix: pr reviews
1 parent 7428a99 commit 2b2c259

File tree

117 files changed

+1098
-788
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+1098
-788
lines changed

.eslintignore

-5
This file was deleted.

.eslintrc.js renamed to eslint.config.mjs

+98-35
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
1-
module.exports = {
2-
parser: '@typescript-eslint/parser',
3-
env: {
4-
browser: true,
5-
node: true,
6-
commonjs: true,
7-
es6: true,
1+
// Importing necessary ESLint plugins
2+
import typescriptEslint from 'typescript-eslint';
3+
import noOnlyTestsPlugin from 'eslint-plugin-no-only-tests';
4+
import headerPlugin from 'eslint-plugin-header';
5+
import jsdocPlugin from 'eslint-plugin-jsdoc';
6+
import preferArrowPlugin from 'eslint-plugin-prefer-arrow';
7+
import importPlugin from 'eslint-plugin-import';
8+
import noNullPlugin from 'eslint-plugin-no-null';
9+
import localRulesPlugin from 'eslint-plugin-local-rules';
10+
import globals from 'globals';
11+
import eslintConfigPrettier from 'eslint-config-prettier';
12+
import pluginJs from '@eslint/js';
13+
14+
const commonConfig = {
15+
plugins: {
16+
'@typescript-eslint': typescriptEslint.plugin,
17+
'no-only-tests': noOnlyTestsPlugin,
18+
header: headerPlugin,
19+
jsdoc: jsdocPlugin,
20+
'prefer-arrow': preferArrowPlugin,
21+
import: importPlugin,
22+
'no-null': noNullPlugin,
23+
'local-rules': localRulesPlugin,
824
},
9-
globals: {
10-
$: false,
11-
chrome: false,
12-
OpenPGP: false,
13-
},
14-
extends: ['eslint:recommended', 'plugin:@typescript-eslint/strict', 'plugin:@typescript-eslint/stylistic', 'prettier'],
15-
ignorePatterns: ['.eslintrc.js'],
16-
parserOptions: {
17-
project: 'tsconfig.json',
25+
languageOptions: {
26+
parser: typescriptEslint.parser,
1827
sourceType: 'module',
28+
ecmaVersion: 'latest',
29+
globals: {
30+
...globals.browser,
31+
...globals.node,
32+
...globals.commonjs,
33+
...globals.es6,
34+
$: false,
35+
chrome: false,
36+
OpenPGP: false,
37+
},
1938
},
20-
plugins: [
21-
'no-only-tests',
22-
'header',
23-
'eslint-plugin-jsdoc',
24-
'eslint-plugin-prefer-arrow',
25-
'eslint-plugin-import',
26-
'eslint-plugin-no-null',
27-
'eslint-plugin-local-rules',
28-
'@typescript-eslint',
29-
],
30-
root: true,
3139
rules: {
3240
'@typescript-eslint/consistent-indexed-object-style': 'off',
3341
'@typescript-eslint/consistent-type-assertions': 'error',
@@ -84,6 +92,14 @@ module.exports = {
8492
'@typescript-eslint/no-parameter-properties': 'off',
8593
'@typescript-eslint/no-shadow': 'off',
8694
'@typescript-eslint/no-unsafe-return': 'error',
95+
'@typescript-eslint/prefer-nullish-coalescing': 'off',
96+
'@typescript-eslint/no-unnecessary-condition': 'off',
97+
'@typescript-eslint/restrict-template-expressions': 'off',
98+
'@typescript-eslint/restrict-plus-operands': 'off',
99+
'@typescript-eslint/require-await': 'off',
100+
'@typescript-eslint/no-confusing-void-expression': 'off',
101+
'@typescript-eslint/no-misused-promises': 'off',
102+
'@typescript-eslint/no-redundant-type-constituents': 'off',
87103
'@typescript-eslint/no-unused-vars': ['error'],
88104
'@typescript-eslint/no-unused-expressions': 'error',
89105
'@typescript-eslint/no-use-before-define': 'off',
@@ -156,13 +172,60 @@ module.exports = {
156172
],
157173
'local-rules/standard-loops': 'error',
158174
},
159-
overrides: [
160-
{
161-
files: './test/**/*.ts',
162-
rules: {
163-
'@typescript-eslint/no-unused-expressions': 'off',
164-
'@typescript-eslint/no-non-null-assertion': 'off',
175+
};
176+
177+
export default [
178+
{
179+
ignores: ['extension/types/**', 'extension/js/common/core/types/**', 'test/source/core/types/**', 'build/**', 'extension/lib/**', 'eslint.config.js'],
180+
},
181+
pluginJs.configs.recommended,
182+
...typescriptEslint.configs.strictTypeChecked,
183+
...typescriptEslint.configs.stylisticTypeChecked,
184+
eslintConfigPrettier,
185+
{
186+
...commonConfig,
187+
files: ['extension/**/*.ts'],
188+
languageOptions: {
189+
...commonConfig.languageOptions,
190+
parserOptions: {
191+
project: './tsconfig.json',
165192
},
166193
},
167-
],
168-
};
194+
},
195+
{
196+
...commonConfig,
197+
files: ['tooling/**/*.ts'],
198+
languageOptions: {
199+
...commonConfig.languageOptions,
200+
parserOptions: {
201+
project: './conf/tsconfig.tooling.json',
202+
},
203+
},
204+
},
205+
{
206+
...commonConfig,
207+
files: ['test/**/*.ts'],
208+
languageOptions: {
209+
...commonConfig.languageOptions,
210+
parserOptions: {
211+
project: './conf/tsconfig.test.eslint.json',
212+
},
213+
},
214+
rules: {
215+
...commonConfig.rules,
216+
'@typescript-eslint/no-unused-expressions': 'off',
217+
'@typescript-eslint/no-non-null-assertion': 'off',
218+
'@typescript-eslint/no-unsafe-assignment': 'off',
219+
'@typescript-eslint/no-unsafe-call': 'off',
220+
'@typescript-eslint/no-unsafe-member-access': 'off',
221+
},
222+
},
223+
...typescriptEslint.config({
224+
files: ['extension/js/content_scripts/webmail/**/*.ts'],
225+
languageOptions: {
226+
parserOptions: {
227+
project: './conf/tsconfig.content_scripts.json',
228+
},
229+
},
230+
}),
231+
];

extension/chrome/dev/ci_unit_test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const libs: unknown[] = [
5252
/* eslint-disable @typescript-eslint/no-explicit-any */
5353
// add them to global scope so ci can use them
5454
for (const lib of libs) {
55+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
5556
(window as any)[(lib as any).name] = lib;
5657
}
5758
/* eslint-enable @typescript-eslint/no-explicit-any */

extension/chrome/dev/export.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Catch.try(async () => {
5050
const { messages, resultSizeEstimate, nextPageToken } = await gmail.msgList('is:inbox OR is:sent', false, nextCyclePageToken);
5151
print(`msgList: ${(messages || []).length} msgs, resultSizeEstimate:${resultSizeEstimate}, nextPageToken: ${nextPageToken}`);
5252
msgMetas.push(...(messages || []));
53-
if (!messages || !messages.length || !nextPageToken) {
53+
if (!messages?.length || !nextPageToken) {
5454
break;
5555
}
5656
nextCyclePageToken = nextPageToken;

extension/chrome/elements/add_pubkey.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ View.run(
5353
this.fetchKeyUi.handleOnPaste($('.pubkey'));
5454
$('.action_settings').on(
5555
'click',
56-
this.setHandler(async () => await Browser.openSettingsPage('index.htm', this.acctEmail, '/chrome/settings/modules/contacts.htm'))
56+
this.setHandler(async () => {
57+
await Browser.openSettingsPage('index.htm', this.acctEmail, '/chrome/settings/modules/contacts.htm');
58+
})
5759
);
5860
};
5961

@@ -81,7 +83,9 @@ View.run(
8183
);
8284
$('.action_close').on(
8385
'click',
84-
this.setHandler(() => this.closeDialog())
86+
this.setHandler(() => {
87+
this.closeDialog();
88+
})
8589
);
8690
};
8791

extension/chrome/elements/attachment.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export class AttachmentDownloadView extends View {
217217

218218
private getUrlFileSize = async (url: string): Promise<number | undefined> => {
219219
console.info('trying to figure out figetUrlFileSizee size');
220-
if (url.indexOf('docs.googleusercontent.getUrlFileSizeom/docs/securesc') !== -1) {
220+
if (url.includes('docs.googleusercontent.getUrlFileSizeom/docs/securesc')) {
221221
try {
222222
const googleDriveFileId = url.split('/').pop()?.split('?').shift(); // try and catch any errors below if structure is not as expected
223223
url = googleDriveFileId ? `https://drive.google.com/uc?export=download&id=${googleDriveFileId}` : url; // attempt to get length headers from Google Drive file if available
@@ -374,7 +374,7 @@ export class AttachmentDownloadView extends View {
374374
private recoverMissingAttachmentIdIfNeeded = async () => {
375375
if (!this.attachment.url && !this.attachment.id && this.attachment.msgId) {
376376
const result = await this.gmail.msgGet(this.attachment.msgId, 'full');
377-
if (result && result.payload && result.payload.parts) {
377+
if (result?.payload?.parts) {
378378
for (const attMeta of result.payload.parts) {
379379
if (attMeta.filename === name && attMeta.body && attMeta.body.size === this.size && attMeta.body.attachmentId) {
380380
this.attachment.id = attMeta.body.attachmentId;

extension/chrome/elements/attachment_preview.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ View.run(
7272
$('#attachment-preview-download').appendTo('.attachment-preview-unavailable');
7373
}
7474
$('body').on('click', e => {
75-
if (e.target === document.body || $('body').children().toArray().indexOf(e.target) !== -1) {
75+
if (e.target === document.body || $('body').children().toArray().includes(e.target)) {
7676
BrowserMsg.send.closeDialog(this);
7777
}
7878
});
@@ -113,11 +113,12 @@ View.run(
113113
if ((result as DecryptSuccess).content) {
114114
return result.content;
115115
} else if ((result as DecryptError).error.type === DecryptErrTypes.needPassphrase) {
116-
return BrowserMsg.send.passphraseDialog(this.parentTabId, {
116+
BrowserMsg.send.passphraseDialog(this.parentTabId, {
117117
type: 'attachment',
118118
longids: (result as DecryptError).longids.needPassphrase,
119119
initiatorFrameId: this.initiatorFrameId,
120120
});
121+
return;
121122
}
122123
throw new DecryptionError(result as DecryptError);
123124
};

extension/chrome/elements/backup.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ View.run(
6161
if (!this.storedPrvWithMatchingLongid) {
6262
$('#action_import_key').on(
6363
'click',
64-
this.setHandler(async () => await Browser.openSettingsPage('index.htm', this.acctEmail, '/chrome/settings/modules/add_key.htm'))
64+
this.setHandler(async () => {
65+
await Browser.openSettingsPage('index.htm', this.acctEmail, '/chrome/settings/modules/add_key.htm');
66+
})
6567
);
6668
}
6769
$('.action_test_pass').on(
@@ -80,7 +82,7 @@ View.run(
8082
};
8183

8284
private testPassphraseHandler = async () => {
83-
if ((await KeyUtil.checkPassPhrase(this.armoredPrvBackup, String($('#pass_phrase').val()))) === true) {
85+
if (await KeyUtil.checkPassPhrase(this.armoredPrvBackup, String($('#pass_phrase').val()))) {
8486
await Ui.modal.info('Success - your pass phrase matches this backup!');
8587
} else {
8688
await Ui.modal.warning(

extension/chrome/elements/compose-modules/compose-attachments-module.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class ComposeAttachmentsModule extends ViewModule<ComposeView> {
2525
},
2626
});
2727
this.view.S.cached('body').on('click', '#attachment_list li', async e => {
28+
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
2829
const fileId = $(e.currentTarget).attr('qq-file-id') as string;
2930
const attachment = await this.attachment.collectAttachment(fileId);
3031
Browser.saveToDownloads(attachment);

extension/chrome/elements/compose-modules/compose-draft-module.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
5050
'click',
5151
this.view.setHandler(() => this.deleteDraftClickHandler(), this.view.errModule.handle('delete draft'))
5252
);
53-
this.view.recipientsModule.onRecipientAdded(async () => await this.draftSave(true));
53+
this.view.recipientsModule.onRecipientAdded(async () => {
54+
await this.draftSave(true);
55+
});
5456
};
5557

5658
/**
@@ -162,11 +164,11 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
162164
if (ApiErr.isAuthErr(e)) {
163165
BrowserMsg.send.notificationShowAuthPopupNeeded(this.view.parentTabId, { acctEmail: this.view.acctEmail });
164166
this.view.S.cached('send_btn_note').text('Not saved (reconnect)');
165-
} else if (e instanceof Error && e.message.indexOf('Could not find valid key packet for encryption in key') !== -1) {
167+
} else if (e instanceof Error && e.message.includes('Could not find valid key packet for encryption in key')) {
166168
this.view.S.cached('send_btn_note').text('Not saved (bad key)');
167169
} else if (
168170
this.view.draftId &&
169-
(ApiErr.isNotFound(e) || (e instanceof AjaxErr && e.status === 400 && e.responseText.indexOf('Message not a draft') !== -1))
171+
(ApiErr.isNotFound(e) || (e instanceof AjaxErr && e.status === 400 && e.responseText.includes('Message not a draft')))
170172
) {
171173
// not found - updating draft that was since deleted
172174
// not a draft - updating draft that was since sent as a message (in another window), and is not a draft anymore
@@ -313,7 +315,8 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
313315
private decryptAndRenderDraft = async (encrypted: MimeProccesedMsg): Promise<void> => {
314316
const rawBlock = encrypted.blocks.find(b => ['encryptedMsg', 'signedMsg', 'pkcs7'].includes(b.type));
315317
if (!rawBlock) {
316-
return await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!rawBlock');
318+
await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!rawBlock');
319+
return;
317320
}
318321
const decrypted = await MsgUtil.decryptMessage({
319322
kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.view.acctEmail),
@@ -326,14 +329,16 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
326329
await this.renderPPDialogAndWaitWhenPPEntered(decrypted.longids.needPassphrase);
327330
await this.decryptAndRenderDraft(encrypted);
328331
}
329-
return await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!decrypted.success');
332+
await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!decrypted.success');
333+
return;
330334
}
331335
this.wasMsgLoadedFromDraft = true;
332336
this.view.S.cached('prompt').css({ display: 'none' });
333337
const { blocks, isRichText } = await MsgBlockParser.fmtDecryptedAsSanitizedHtmlBlocks(decrypted.content, 'IMG-KEEP');
334338
const sanitizedContent = blocks.find(b => b.type === 'decryptedHtml')?.content;
335339
if (!sanitizedContent) {
336-
return await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!sanitizedContent');
340+
await this.abortAndRenderReplyMsgComposeTableIfIsReplyBox('!sanitizedContent');
341+
return;
337342
}
338343
if (isRichText) {
339344
this.view.sendBtnModule.popover.toggleItemTick($('.action-toggle-richtext-sending-option'), 'richtext', true);
@@ -393,7 +398,9 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
393398
.find('.action_close')
394399
.on(
395400
'click',
396-
this.view.setHandler(() => this.view.renderModule.closeMsg())
401+
this.view.setHandler(() => {
402+
this.view.renderModule.closeMsg();
403+
})
397404
);
398405
const setActiveWindow = this.view.setHandler(async () => {
399406
BrowserMsg.send.setActiveWindow(this.view.parentTabId, { frameId: this.view.frameId });

extension/chrome/elements/compose-modules/compose-err-module.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class ComposerUserError extends Error {}
2121
class ComposerNotReadyError extends ComposerUserError {}
2222
export class ComposerResetBtnTrigger extends Error {}
2323

24-
export const PUBKEY_LOOKUP_RESULT_FAIL = 'fail' as const;
24+
export const PUBKEY_LOOKUP_RESULT_FAIL = 'fail';
2525

2626
export class ComposeErrModule extends ViewModule<ComposeView> {
2727
private debugId = Str.sloppyRandom();

extension/chrome/elements/compose-modules/compose-input-module.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ export class ComposeInputModule extends ViewModule<ComposeView> {
107107

108108
private initSquire = (addLinks: boolean, removeExistingLinks = false) => {
109109
const squireHtml = this.squire?.getHTML();
110-
const el = this.view.S.cached('input_text').get(0) as HTMLElement;
110+
const el = this.view.S.cached('input_text').get(0);
111+
if (!el) {
112+
throw new Error('Input element not found');
113+
}
111114
this.squire?.destroy();
112115
this.squire = new window.Squire(el, { addLinks });
113116
this.initShortcuts();
@@ -129,7 +132,7 @@ export class ComposeInputModule extends ViewModule<ComposeView> {
129132
div.appendChild(e.detail.fragment);
130133
const html = div.innerHTML;
131134
const sanitized = this.isRichText() ? Xss.htmlSanitizeKeepBasicTags(html, 'IMG-KEEP') : Xss.htmlSanitizeAndStripAllTags(html, '<br>', false);
132-
if (this.willInputLimitBeExceeded(sanitized, this.squire.getRoot(), () => this.squire.getSelectedText().length as number)) {
135+
if (this.willInputLimitBeExceeded(sanitized, this.squire.getRoot(), () => this.squire.getSelectedText().length)) {
133136
e.preventDefault();
134137
await Ui.modal.warning(Lang.compose.inputLimitExceededOnPaste);
135138
return;
@@ -173,7 +176,7 @@ export class ComposeInputModule extends ViewModule<ComposeView> {
173176
this.squire.addEventListener('pasteImage', (ev: Event & { detail: { clipboardData: DataTransfer } }) => {
174177
if (!this.isRichText()) return;
175178
const items = Array.from(ev.detail.clipboardData?.items ?? []);
176-
const imageItem = items.find(item => /image/.test(item.type));
179+
const imageItem = items.find(item => item.type.includes('image'));
177180

178181
const imageFile = imageItem?.getAsFile();
179182
if (imageItem && imageFile) {
@@ -208,7 +211,7 @@ export class ComposeInputModule extends ViewModule<ComposeView> {
208211

209212
private initShortcuts = () => {
210213
try {
211-
const isMac = /Mac OS X/.test(navigator.userAgent);
214+
const isMac = navigator.userAgent.includes('Mac OS X');
212215
const ctrlKey = isMac ? 'Meta-' : 'Ctrl-';
213216
const mapKeyToFormat = (tag: string) => {
214217
return (self: Squire, event: Event) => {

extension/chrome/elements/compose-modules/compose-my-pubkey-module.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class ComposeMyPubkeyModule extends ViewModule<ComposeView> {
4949
// if we have cashed this fingerprint, setAttachPreference(false) rightaway and return
5050
const cached = this.wkdFingerprints[senderEmail];
5151
for (const parsedPrv of parsedStoredPrvs.filter(prv => prv.key.usableForEncryption || prv.key.usableForSigning)) {
52-
if (cached && cached.includes(parsedPrv.key.id)) {
52+
if (cached?.includes(parsedPrv.key.id)) {
5353
this.setAttachPreference(false); // at least one of our valid keys is on WKD: no need to attach
5454
return;
5555
}

0 commit comments

Comments
 (0)