diff --git a/.oxlintrc.json b/.oxlintrc.json index f1c3d9b482..3451a56e92 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -106,7 +106,7 @@ "typescript/require-array-sort-compare": "error", "typescript/unbound-method": "error", "typescript/no-for-in-array": "warn", // TODO: covert to error - "typescript/restrict-template-expressions": "warn", // TODO: covert to error + "typescript/restrict-template-expressions": "error", // Import rules "import/consistent-type-specifier-style": "error", diff --git a/packages/desktop-client/e2e/page-models/configuration-page.ts b/packages/desktop-client/e2e/page-models/configuration-page.ts index f3c08fcf85..01801e5620 100644 --- a/packages/desktop-client/e2e/page-models/configuration-page.ts +++ b/packages/desktop-client/e2e/page-models/configuration-page.ts @@ -61,7 +61,7 @@ export class ConfigurationPage { break; default: - throw new Error(`Unrecognized import type: ${type}`); + throw new Error(`Unrecognized import type: ${String(type)}`); } const fileChooser = await fileChooserPromise; diff --git a/packages/desktop-client/e2e/page-models/custom-report-page.ts b/packages/desktop-client/e2e/page-models/custom-report-page.ts index 885e9bd947..ec5a211a48 100644 --- a/packages/desktop-client/e2e/page-models/custom-report-page.ts +++ b/packages/desktop-client/e2e/page-models/custom-report-page.ts @@ -39,7 +39,7 @@ export class CustomReportPage { .click(); break; default: - throw new Error(`Unrecognized mode: ${mode}`); + throw new Error(`Unrecognized mode: ${String(mode)}`); } } } diff --git a/packages/desktop-client/src/budget/mutations.ts b/packages/desktop-client/src/budget/mutations.ts index 3c7b87d3f0..a7f2ce64eb 100644 --- a/packages/desktop-client/src/budget/mutations.ts +++ b/packages/desktop-client/src/budget/mutations.ts @@ -779,7 +779,7 @@ export function useBudgetActions() { }); return null; default: - throw new Error(`Unknown budget action type: ${type}`); + throw new Error(`Unknown budget action type: ${String(type)}`); } }, onSuccess: notification => { diff --git a/packages/desktop-client/src/components/HelpMenu.tsx b/packages/desktop-client/src/components/HelpMenu.tsx index b14d00a2df..5f5d357f5c 100644 --- a/packages/desktop-client/src/components/HelpMenu.tsx +++ b/packages/desktop-client/src/components/HelpMenu.tsx @@ -95,7 +95,7 @@ export const HelpMenu = () => { dispatch(pushModal({ modal: { name: 'goal-templates' } })); break; default: - throw new Error(`Unrecognized menu option: ${item}`); + throw new Error(`Unrecognized menu option: ${String(item)}`); } }; diff --git a/packages/desktop-client/src/components/budget/envelope/IncomeMenu.tsx b/packages/desktop-client/src/components/budget/envelope/IncomeMenu.tsx index ac1377043e..3db2f41139 100644 --- a/packages/desktop-client/src/components/budget/envelope/IncomeMenu.tsx +++ b/packages/desktop-client/src/components/budget/envelope/IncomeMenu.tsx @@ -45,7 +45,7 @@ export function IncomeMenu({ onClose(); break; default: - throw new Error(`Unrecognized menu option: ${name}`); + throw new Error(`Unrecognized menu option: ${String(name)}`); } }} items={[ diff --git a/packages/desktop-client/src/components/budget/goals/reducer.ts b/packages/desktop-client/src/components/budget/goals/reducer.ts index 455c7b3ba1..5ce50eff50 100644 --- a/packages/desktop-client/src/components/budget/goals/reducer.ts +++ b/packages/desktop-client/src/components/budget/goals/reducer.ts @@ -68,7 +68,9 @@ export const getInitialState = (template: Template | null): ReducerState => { case 'error': throw new Error('An error occurred while parsing the template'); default: - throw new Error(`Unknown template type: ${type satisfies undefined}`); + throw new Error( + `Unknown template type: ${String(type satisfies undefined)}`, + ); } }; @@ -168,7 +170,9 @@ const changeType = ( }; default: // Make sure we're not missing any cases - throw new Error(`Unknown display type: ${visualType satisfies never}`); + throw new Error( + `Unknown display type: ${String(visualType satisfies never)}`, + ); } }; @@ -251,6 +255,6 @@ export const templateReducer = ( return mapTemplateTypesForUpdate(state, action.payload); default: // Make sure we're not missing any cases - throw new Error(`Unknown display type: ${type satisfies never}`); + throw new Error(`Unknown display type: ${String(type satisfies never)}`); } }; diff --git a/packages/desktop-client/src/components/budget/tracking/BalanceMenu.tsx b/packages/desktop-client/src/components/budget/tracking/BalanceMenu.tsx index 9903296ef7..01d6da269a 100644 --- a/packages/desktop-client/src/components/budget/tracking/BalanceMenu.tsx +++ b/packages/desktop-client/src/components/budget/tracking/BalanceMenu.tsx @@ -34,7 +34,7 @@ export function BalanceMenu({ onCarryover?.(!carryover); break; default: - throw new Error(`Unrecognized menu option: ${name}`); + throw new Error(`Unrecognized menu option: ${String(name)}`); } }} items={[ diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionList.tsx b/packages/desktop-client/src/components/mobile/transactions/TransactionList.tsx index e1f8d32c05..328c1d9919 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionList.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionList.tsx @@ -506,7 +506,7 @@ function SelectedTransactionsFloatingActionBar({ : integerToCurrency(Number(value)); break; case 'notes': - displayValue = `${mode} with ${value}`; + displayValue = `${mode} with ${String(value)}`; break; default: displayValue = value; @@ -514,15 +514,15 @@ function SelectedTransactionsFloatingActionBar({ } showUndoNotification({ - message: `Successfully updated ${name} of ${ids.length} transaction${ids.length > 1 ? 's' : ''} to [${displayValue}](#${displayValue}).`, + message: `Successfully updated ${name} of ${ids.length} transaction${ids.length > 1 ? 's' : ''} to [${String(displayValue)}](#${String(displayValue)}).`, messageActions: { [String(displayValue)]: () => { switch (name) { case 'account': - void navigate(`/accounts/${value}`); + void navigate(`/accounts/${String(value)}`); break; case 'category': - void navigate(`/categories/${value}`); + void navigate(`/categories/${String(value)}`); break; case 'payee': void navigate(`/payees`); diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index 4540160085..4715edf278 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -295,7 +295,7 @@ function AdditionalAccountMenu({ onToggleReconciled?.(); break; default: - throw new Error(`Unrecognized menu option: ${name}`); + throw new Error(`Unrecognized menu option: ${String(name)}`); } }} /> diff --git a/packages/desktop-client/src/components/modals/EnvelopeIncomeBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/EnvelopeIncomeBalanceMenuModal.tsx index e64ba9c233..7a6ed478d8 100644 --- a/packages/desktop-client/src/components/modals/EnvelopeIncomeBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/EnvelopeIncomeBalanceMenuModal.tsx @@ -117,7 +117,7 @@ export function EnvelopeIncomeBalanceMenuModal({ onShowActivity?.(); break; default: - throw new Error(`Unrecognized menu option: ${name}`); + throw new Error(`Unrecognized menu option: ${String(name)}`); } }} items={[ diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/SelectField.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/SelectField.tsx index e7f3c253d4..97a1deec45 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/SelectField.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/SelectField.tsx @@ -26,7 +26,7 @@ export function SelectField({ option, hasHeaderRow ? option - : `Column ${parseInt(option) + 1} (${firstTransaction[option]})`, + : `Column ${parseInt(option) + 1} (${String(firstTransaction[option])})`, ] as const, ); diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx index 36c9d97833..5d90b6e2e8 100644 --- a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx +++ b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.tsx @@ -128,7 +128,7 @@ export function SelectLinkedAccountsModal({ externalAccounts: toSort as SyncServerGoCardlessAccount[], }; default: - throw new Error(`Unrecognized sync source: ${syncSource}`); + throw new Error(`Unrecognized sync source: ${String(syncSource)}`); } }, [externalAccounts, syncSource, requisitionId]); diff --git a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx index 28c3efe680..19549eb3b6 100644 --- a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx @@ -209,7 +209,7 @@ export const PayeeTableRow = memo( onCreateRule(id); break; default: - throw new Error(`Unrecognized menu option: ${name}`); + throw new Error(`Unrecognized menu option: ${String(name)}`); } setMenuOpen(false); }} diff --git a/packages/desktop-client/src/components/reports/Overview.tsx b/packages/desktop-client/src/components/reports/Overview.tsx index c907049fa1..b0ed72554e 100644 --- a/packages/desktop-client/src/components/reports/Overview.tsx +++ b/packages/desktop-client/src/components/reports/Overview.tsx @@ -671,7 +671,7 @@ export function Overview({ dashboard }: OverviewProps) { break; default: throw new Error( - `Unrecognized menu option: ${item}`, + `Unrecognized menu option: ${String(item)}`, ); } }} diff --git a/packages/desktop-client/src/components/rules/RuleRow.tsx b/packages/desktop-client/src/components/rules/RuleRow.tsx index 1cb76dd5a8..6a8e89d9da 100644 --- a/packages/desktop-client/src/components/rules/RuleRow.tsx +++ b/packages/desktop-client/src/components/rules/RuleRow.tsx @@ -99,7 +99,7 @@ export const RuleRow = memo( onEditRule(rule); break; default: - throw new Error(`Unrecognized menu option: ${name}`); + throw new Error(`Unrecognized menu option: ${String(name)}`); } setMenuOpen(false); }} diff --git a/packages/desktop-client/src/components/rules/Value.tsx b/packages/desktop-client/src/components/rules/Value.tsx index 26f0f053ab..67cfe39c9e 100644 --- a/packages/desktop-client/src/components/rules/Value.tsx +++ b/packages/desktop-client/src/components/rules/Value.tsx @@ -137,7 +137,7 @@ export function Value({ return '…'; default: - throw new Error(`Unknown field ${field}`); + throw new Error(`Unknown field ${String(field)}`); } } } diff --git a/packages/desktop-client/src/components/schedules/index.tsx b/packages/desktop-client/src/components/schedules/index.tsx index 16331e6795..d8f0b7f07a 100644 --- a/packages/desktop-client/src/components/schedules/index.tsx +++ b/packages/desktop-client/src/components/schedules/index.tsx @@ -72,7 +72,7 @@ export function Schedules() { await send('schedule/delete', { id }); break; default: - throw new Error(`Unknown action: ${name}`); + throw new Error(`Unknown action: ${String(name)}`); } }, [], diff --git a/packages/desktop-client/src/components/sidebar/Account.tsx b/packages/desktop-client/src/components/sidebar/Account.tsx index 85d8b18466..263d5984bc 100644 --- a/packages/desktop-client/src/components/sidebar/Account.tsx +++ b/packages/desktop-client/src/components/sidebar/Account.tsx @@ -272,7 +272,9 @@ export function Account>({ break; } default: { - throw new Error(`Unrecognized menu option: ${type}`); + throw new Error( + `Unrecognized menu option: ${String(type)}`, + ); } } setMenuOpen(false); diff --git a/packages/desktop-client/src/components/tags/TagRow.tsx b/packages/desktop-client/src/components/tags/TagRow.tsx index ef1b0181c4..eced72c0a0 100644 --- a/packages/desktop-client/src/components/tags/TagRow.tsx +++ b/packages/desktop-client/src/components/tags/TagRow.tsx @@ -114,7 +114,7 @@ export const TagRow = memo( deleteTag({ id: tag.id }); break; default: - throw new Error(`Unrecognized menu option: ${name}`); + throw new Error(`Unrecognized menu option: ${String(name)}`); } setMenuOpen(false); }} diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.test.tsx b/packages/desktop-client/src/components/transactions/TransactionsTable.test.tsx index 66d08d2549..72d575261d 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.test.tsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.test.tsx @@ -400,7 +400,7 @@ expect.extend({ } else { return { message: () => - `Expected ${validPayeeListWithFavorite} to have favorite stars`, + `Expected ${String(validPayeeListWithFavorite)} to have favorite stars`, pass: true, }; } diff --git a/packages/desktop-client/src/hooks/useTransactionBatchActions.ts b/packages/desktop-client/src/hooks/useTransactionBatchActions.ts index d13f9295ef..e24a839657 100644 --- a/packages/desktop-client/src/hooks/useTransactionBatchActions.ts +++ b/packages/desktop-client/src/hooks/useTransactionBatchActions.ts @@ -120,10 +120,10 @@ export function useTransactionBatchActions() { if (name === 'notes') { if (mode === 'prepend') { valueToSet = - trans.notes === null ? value : `${value}${trans.notes}`; + trans.notes === null ? value : `${String(value)}${trans.notes}`; } else if (mode === 'append') { valueToSet = - trans.notes === null ? value : `${trans.notes}${value}`; + trans.notes === null ? value : `${trans.notes}${String(value)}`; } else if (mode === 'replace') { valueToSet = value; } else if ( diff --git a/packages/desktop-electron/index.ts b/packages/desktop-electron/index.ts index 1095192838..bad47d9e6c 100644 --- a/packages/desktop-electron/index.ts +++ b/packages/desktop-electron/index.ts @@ -323,7 +323,10 @@ async function startSyncServer() { return await Promise.race([syncServerPromise, syncServerTimeout]); // Either the server has started or the timeout is reached } catch (error) { - logMessage('error', `Sync-Server: Error starting sync server: ${error}`); + logMessage( + 'error', + `Sync-Server: Error starting sync server: ${String(error)}`, + ); } } @@ -661,7 +664,7 @@ ipcMain.handle( } catch (error) { logMessage( 'error', - `There was an error moving your directory: ${error}`, + `There was an error moving your directory: ${String(error)}`, ); throw error; } @@ -687,7 +690,7 @@ ipcMain.handle( // This call needs to succeed to allow the user to continue using the app with the files in the new location. logMessage( 'error', - `There was an error removing the old directory: ${error}`, + `There was an error removing the old directory: ${String(error)}`, ); } }, diff --git a/packages/desktop-electron/server.ts b/packages/desktop-electron/server.ts index 8f7fc61719..81ec6e4c9f 100644 --- a/packages/desktop-electron/server.ts +++ b/packages/desktop-electron/server.ts @@ -35,7 +35,7 @@ const lazyLoadBackend = async (isDev: boolean) => { } catch (error) { console.error('Failed to init the server bundle after all retries:', error); throw new Error( - `Failed to init the server bundle after all retries: ${error}`, + `Failed to init the server bundle after all retries: ${String(error)}`, ); } }; diff --git a/packages/loot-core/src/mocks/util.ts b/packages/loot-core/src/mocks/util.ts index 1e27273f47..eef6066a15 100644 --- a/packages/loot-core/src/mocks/util.ts +++ b/packages/loot-core/src/mocks/util.ts @@ -76,7 +76,7 @@ export function patchFetchForSqlJS(baseURL: string) { }, }); } - return Promise.reject(new Error(`fetch not mocked for ${url}`)); + return Promise.reject(new Error(`fetch not mocked for ${String(url)}`)); }, ); } diff --git a/packages/loot-core/src/server/api.ts b/packages/loot-core/src/server/api.ts index 8b6ce30690..fbb94dcbab 100644 --- a/packages/loot-core/src/server/api.ts +++ b/packages/loot-core/src/server/api.ts @@ -944,7 +944,7 @@ handlers['api/schedule-update'] = withMutation(async function ({ break; default: throw APIError( - `Invalid amount operator: ${value}. Expected: is, isapprox, or isbetween`, + `Invalid amount operator: ${String(value)}. Expected: is, isapprox, or isbetween`, ); } sched._conditions[amountIndex].op = convertedOp; diff --git a/packages/loot-core/src/server/aql/compiler.ts b/packages/loot-core/src/server/aql/compiler.ts index 778fc0b0c9..22f262adcd 100644 --- a/packages/loot-core/src/server/aql/compiler.ts +++ b/packages/loot-core/src/server/aql/compiler.ts @@ -715,7 +715,11 @@ const compileOp = saveStack('op', (state, fieldRef, opData) => { // Dedupe the ids const ids = [...new Set(right)]; - return `${left} IN (` + ids.map(id => `'${id}'`).join(',') + ')'; + return ( + `${String(left)} IN (` + + ids.map(id => `'${String(id)}'`).join(',') + + ')' + ); } case '$like': { const [left, right] = valArray(state, [lhs, rhs], ['string', 'string']); diff --git a/packages/loot-core/src/server/budget/category-template-context.ts b/packages/loot-core/src/server/budget/category-template-context.ts index 6000b23ba9..59ba98c9a8 100644 --- a/packages/loot-core/src/server/budget/category-template-context.ts +++ b/packages/loot-core/src/server/budget/category-template-context.ts @@ -611,7 +611,7 @@ export class CategoryTemplateContext { monthUtils.addMonths(date, numPeriods * 12); break; default: - throw new Error(`Unrecognized periodic period: ${period}`); + throw new Error(`Unrecognized periodic period: ${String(period)}`); } //shift the starting date until its in our month or in the future diff --git a/packages/loot-core/src/server/dashboard/app.ts b/packages/loot-core/src/server/dashboard/app.ts index b8d7b4fb3b..4b8901dded 100644 --- a/packages/loot-core/src/server/dashboard/app.ts +++ b/packages/loot-core/src/server/dashboard/app.ts @@ -89,7 +89,7 @@ const exportModel = { if (!isWidgetType(widget.type)) { throw new ValidationError( - `Invalid widget.${idx}.type value ${widget.type}.`, + `Invalid widget.${idx}.type value ${String(widget.type)}.`, ); } diff --git a/packages/loot-core/src/server/util/custom-sync-mapping.ts b/packages/loot-core/src/server/util/custom-sync-mapping.ts index ddf9311b71..071b02c3a9 100644 --- a/packages/loot-core/src/server/util/custom-sync-mapping.ts +++ b/packages/loot-core/src/server/util/custom-sync-mapping.ts @@ -24,7 +24,7 @@ export const mappingsFromString = (str: string): Mappings => { ); } catch (e) { const message = e instanceof Error ? e.message : e; - throw new Error(`Failed to parse mapping: ${message}`); + throw new Error(`Failed to parse mapping: ${String(message)}`); } }; diff --git a/packages/loot-core/src/shared/errors.ts b/packages/loot-core/src/shared/errors.ts index c5e484d13e..47dd43d865 100644 --- a/packages/loot-core/src/shared/errors.ts +++ b/packages/loot-core/src/shared/errors.ts @@ -79,7 +79,7 @@ export function getDownloadError({ default: const info = meta && typeof meta === 'object' && 'fileId' in meta && meta.fileId - ? `, fileId: ${meta.fileId}` + ? `, fileId: ${String(meta.fileId)}` : ''; return t( 'Something went wrong trying to download that file, sorry! Visit https://actualbudget.org/contact/ for support. reason: {{reason}}{{info}}', diff --git a/packages/sync-server/bin/actual-server.js b/packages/sync-server/bin/actual-server.js index be20770de1..7797119c09 100755 --- a/packages/sync-server/bin/actual-server.js +++ b/packages/sync-server/bin/actual-server.js @@ -91,12 +91,12 @@ if (values.config) { if (!configExists) { console.log( - `Please specify a valid config path. The path ${values.config} does not exist.`, + `Please specify a valid config path. The path ${String(values.config)} does not exist.`, ); process.exit(); } else { - console.log(`Loading config from ${values.config}`); + console.log(`Loading config from ${String(values.config)}`); const configJson = JSON.parse(readFileSync(values.config, 'utf-8')); process.env.ACTUAL_CONFIG_PATH = values.config; setupDataDir(configJson.dataDir); diff --git a/packages/sync-server/src/util/middlewares.ts b/packages/sync-server/src/util/middlewares.ts index 4435b9e95c..347a0f4be5 100644 --- a/packages/sync-server/src/util/middlewares.ts +++ b/packages/sync-server/src/util/middlewares.ts @@ -55,7 +55,7 @@ const requestLoggerMiddleware = expressWinston.logger({ const { timestamp, level, meta } = args; const { res, req } = meta as { res: Response; req: Request }; - return `${timestamp} ${level}: ${req.method} ${res.statusCode} ${req.url}`; + return `${String(timestamp)} ${String(level)}: ${req.method} ${res.statusCode} ${req.url}`; }), ), }); diff --git a/upcoming-release-notes/7181.md b/upcoming-release-notes/7181.md new file mode 100644 index 0000000000..a1c6aeb6f4 --- /dev/null +++ b/upcoming-release-notes/7181.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [MatissJanis] +--- + +Enable strict linting for template expressions, ensuring better error handling and message clarity.