lint: fix most eslint/no-empty-function violations (#6457)

* lint: fix most eslint/no-empty-function violations

* Remove unnecessary closing brace in .oxlintrc.json
This commit is contained in:
Matiss Janis Aboltins
2025-12-20 21:38:05 +00:00
committed by GitHub
parent 3d7f0827ad
commit 146aeb1f5a
38 changed files with 148 additions and 81 deletions

View File

@@ -161,7 +161,7 @@
"eslint/no-dupe-keys": "warn",
"eslint/no-duplicate-case": "warn",
"eslint/no-empty-character-class": "warn",
// "eslint/no-empty-function": "warn", // TODO: enable this
"eslint/no-empty-function": "warn",
"eslint/no-empty-pattern": "warn",
"eslint/no-eval": "warn",
"eslint/no-ex-assign": "warn",
@@ -393,6 +393,25 @@
"rules": {
"import/no-default-export": "off"
}
},
// TODO: enable these
{
"files": [
"packages/desktop-client/src/components/admin/UserAccess/UserAccess.tsx",
"packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx",
"packages/desktop-client/src/components/budget/BudgetCategories.tsx",
"packages/desktop-client/src/components/budget/envelope/BalanceMovementMenu.tsx",
"packages/desktop-client/src/components/ManageRules.tsx",
"packages/desktop-client/src/components/mobile/budget/ExpenseGroupList.tsx",
"packages/desktop-client/src/components/modals/EditFieldModal.tsx",
"packages/desktop-client/src/components/reports/reports/Calendar.tsx",
"packages/desktop-client/src/components/schedules/ScheduleLink.tsx",
"packages/desktop-client/src/components/ServerContext.tsx",
"packages/desktop-client/src/components/table.tsx"
],
"rules": {
"eslint/no-empty-function": "off"
}
}
]
}

View File

@@ -86,9 +86,13 @@ global.Actual = {
});
},
startSyncServer: () => {},
startSyncServer: () => {
// Only for electron app
},
stopSyncServer: () => {},
stopSyncServer: () => {
// Only for electron app
},
isSyncServerRunning: () => false,
@@ -96,7 +100,9 @@ global.Actual = {
return '';
},
restartElectronServer: () => {},
restartElectronServer: () => {
// Only for electron app
},
openFileDialog: async ({ filters = [] }) => {
const FILE_ACCEPT_OVERRIDES = {
@@ -192,17 +198,23 @@ global.Actual = {
openInFileManager: () => {
// File manager not available in browser
},
onEventFromMain: () => {},
onEventFromMain: () => {
// Only for electron app
},
isUpdateReadyForDownload: () => isUpdateReadyForDownload,
waitForUpdateReadyForDownload: () => isUpdateReadyForDownloadPromise,
applyAppUpdate: async () => {
updateSW();
// Wait for the app to reload
await new Promise(() => {});
await new Promise(() => {
// Do nothing
});
},
ipcConnect: () => {},
ipcConnect: () => {
// Only for electron app
},
getServerSocket: async () => {
return worker;
},
@@ -211,5 +223,7 @@ global.Actual = {
window.__actionsForMenu.saveGlobalPrefs({ prefs: { theme } });
},
moveBudgetDirectory: () => {},
moveBudgetDirectory: () => {
// Only for electron app
},
};

View File

@@ -71,7 +71,7 @@ describe('ReconcilingMessage math & UI', () => {
<ReconcilingMessage
balanceQuery={makeBalanceQuery()}
targetBalance={10000}
onDone={() => {}}
onDone={vi.fn()}
onCreateTransaction={onCreateTransaction}
/>
</TestProvider>,
@@ -99,7 +99,7 @@ describe('ReconcilingMessage math & UI', () => {
<ReconcilingMessage
balanceQuery={makeBalanceQuery()}
targetBalance={10000}
onDone={() => {}}
onDone={vi.fn()}
onCreateTransaction={onCreateTransaction}
/>
</TestProvider>,

View File

@@ -13,7 +13,7 @@ type IncomeMenuProps = {
month: string;
onBudgetAction: (month: string, action: string, arg?: unknown) => void;
onShowActivity: (id: CategoryEntity['id'], month: string) => void;
onClose?: () => void;
onClose: () => void;
};
export function IncomeMenu({
@@ -21,7 +21,7 @@ export function IncomeMenu({
month,
onBudgetAction,
onShowActivity,
onClose = () => {},
onClose,
}: IncomeMenuProps) {
const { t } = useTranslation();
const carryover = useEnvelopeSheetValue(

View File

@@ -234,9 +234,6 @@ export const PayeeTableRow = memo(
<CustomCell
width={20}
exposed={!payee.transfer_acct}
onBlur={() => {}}
onUpdate={() => {}}
onClick={() => {}}
style={{
display: 'flex',
justifyContent: 'center',

View File

@@ -41,22 +41,26 @@ type HeaderProps = {
end: TimeFrame['end'],
mode: TimeFrame['mode'],
) => void;
conditionsOp: 'and' | 'or';
onUpdateFilter: ComponentProps<typeof AppliedFilters>['onUpdate'];
onDeleteFilter: ComponentProps<typeof AppliedFilters>['onDelete'];
onConditionsOpChange: ComponentProps<
typeof AppliedFilters
>['onConditionsOpChange'];
children?: ReactNode;
inlineContent?: ReactNode;
} & (
| {
filters: RuleConditionEntity[];
onApply: (conditions: RuleConditionEntity) => void;
onUpdateFilter: ComponentProps<typeof AppliedFilters>['onUpdate'];
onDeleteFilter: ComponentProps<typeof AppliedFilters>['onDelete'];
conditionsOp: 'and' | 'or';
onConditionsOpChange: ComponentProps<
typeof AppliedFilters
>['onConditionsOpChange'];
}
| {
filters?: never;
onApply?: never;
onUpdateFilter?: never;
onDeleteFilter?: never;
conditionsOp?: never;
onConditionsOpChange?: never;
}
);

View File

@@ -433,10 +433,6 @@ function CrossoverInner({ widget }: CrossoverInnerProps) {
earliestTransaction={earliestTransaction}
latestTransaction={latestTransaction}
onChangeDates={onChangeDates}
conditionsOp="and"
onUpdateFilter={() => {}}
onDeleteFilter={() => {}}
onConditionsOpChange={() => {}}
>
{widget && (
<Button variant="primary" onPress={onSaveWidget}>

View File

@@ -346,10 +346,6 @@ function SummaryInner({ widget }: SummaryInnerProps) {
firstDayOfWeekIdx={firstDayOfWeekIdx}
mode={mode}
onChangeDates={onChangeDates}
onUpdateFilter={dividendFilters.onUpdate}
onDeleteFilter={dividendFilters.onDelete}
conditionsOp={dividendFilters.conditionsOp}
onConditionsOpChange={dividendFilters.onConditionsOpChange}
show1Month={true}
>
{widget && (

View File

@@ -45,9 +45,7 @@ export function AuthSettings() {
pushModal({
modal: {
name: 'enable-openid',
options: {
onSave: async () => {},
},
options: {},
},
}),
)
@@ -73,9 +71,7 @@ export function AuthSettings() {
pushModal({
modal: {
name: 'enable-password-auth',
options: {
onSave: async () => {},
},
options: {},
},
}),
)

View File

@@ -208,7 +208,7 @@ function LiveTransactionTable(props: LiveTransactionTableProps) {
<TransactionTable
{...props}
transactions={transactions}
loadMoreTransactions={() => {}}
loadMoreTransactions={vi.fn()}
// @ts-ignore TODO:
commonPayees={[]}
payees={payees}

View File

@@ -8,6 +8,7 @@ import './i18n';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { type NavigateFunction } from 'react-router';
import { bindActionCreators } from '@reduxjs/toolkit';
@@ -98,6 +99,7 @@ root.render(
declare global {
// oxlint-disable-next-line typescript/consistent-type-definitions
interface Window {
__navigate?: NavigateFunction;
__actionsForMenu: typeof boundActions & {
undo: typeof undo;
redo: typeof redo;

View File

@@ -534,13 +534,13 @@ export type Modal =
| {
name: 'enable-openid';
options: {
onSave: () => void;
onSave?: () => void;
};
}
| {
name: 'enable-password-auth';
options: {
onSave: () => void;
onSave?: () => void;
};
}
| {

View File

@@ -38,7 +38,7 @@ export class LiveQuery<TResponse = unknown> {
private _listeners: Array<Listener<TResponse>>;
private _supportedSyncTypes: Set<'applied' | 'success'>;
private _query: Query;
private _onError: (error: Error) => void;
private _onError?: (error: Error) => void;
get query() {
return this._query;
@@ -77,7 +77,7 @@ export class LiveQuery<TResponse = unknown> {
this._data = null;
this._dependencies = null;
this._listeners = [];
this._onError = onError || (() => {});
this._onError = onError;
// TODO: error types?
this._supportedSyncTypes = options.onlySync
@@ -104,7 +104,7 @@ export class LiveQuery<TResponse = unknown> {
};
protected onError = (error: Error) => {
this._onError(error);
this._onError?.(error);
};
protected onUpdate = (tables: string[]) => {

View File

@@ -467,7 +467,7 @@ describe('pagedQuery', () => {
await tracer.expect('server-query', [{ result: { $count: '*' } }]);
await tracer.expect('server-query', ['id']);
await tracer.expect('data', () => {});
await tracer.expect('data', vi.fn());
paged.fetchNext();
paged.fetchNext();
@@ -475,7 +475,7 @@ describe('pagedQuery', () => {
paged.fetchNext();
await tracer.expect('server-query', ['id']);
await tracer.expect('data', () => {});
await tracer.expect('data', vi.fn());
// Wait a bit and make sure nothing comes through
const p = Promise.race([tracer.expect('server-query'), wait(200)]);

View File

@@ -42,7 +42,7 @@ type PagedQueryOptions = LiveQueryOptions & {
// Paging
export class PagedQuery<TResponse = unknown> extends LiveQuery<TResponse> {
private _hasReachedEnd: boolean;
private _onPageData: (data: Data<TResponse>) => void;
private _onPageData?: (data: Data<TResponse>) => void;
private _pageCount: number;
private _fetchDataPromise: Promise<void> | null;
private _totalCount: number;
@@ -67,7 +67,7 @@ export class PagedQuery<TResponse = unknown> extends LiveQuery<TResponse> {
this._pageCount = options.pageCount || 500;
this._fetchDataPromise = null;
this._hasReachedEnd = false;
this._onPageData = onPageData || (() => {});
this._onPageData = onPageData;
}
private fetchCount = () => {
@@ -171,7 +171,7 @@ export class PagedQuery<TResponse = unknown> extends LiveQuery<TResponse> {
};
private onPageData = (data: Data<TResponse>) => {
this._onPageData(data);
this._onPageData?.(data);
};
// The public version of this function is created below and

View File

@@ -75,7 +75,10 @@ contextBridge.exposeInMainWorld('Actual', {
// No auto-updates in the desktop app
isUpdateReadyForDownload: () => false,
waitForUpdateReadyForDownload: () => new Promise<void>(() => {}),
waitForUpdateReadyForDownload: () =>
new Promise<void>(() => {
// This is used in browser environment; do nothing in electron
}),
getServerSocket: async () => {
return null;

View File

@@ -199,6 +199,12 @@ export const unlisten: T.Unlisten = function (name) {
listeners.set(name, []);
};
export const initServer: T.InitServer = async function () {};
export const serverPush: T.ServerPush = async function () {};
export const clearServer: T.ClearServer = async function () {};
export const initServer: T.InitServer = async function () {
// initServer is used in tests to mock the server
};
export const serverPush: T.ServerPush = async function () {
// serverPush is used in tests to mock the server
};
export const clearServer: T.ClearServer = async function () {
// clearServer is used in tests to mock the server
};

View File

@@ -145,5 +145,9 @@ export const clearServer: T.ClearServer = async function () {
return new Promise(closeSocket);
}
};
export const initServer: T.InitServer = async function () {};
export const serverPush: T.ServerPush = async function () {};
export const initServer: T.InitServer = async function () {
// initServer is used in tests to mock the server
};
export const serverPush: T.ServerPush = async function () {
// serverPush is used in tests to mock the server
};

View File

@@ -1,3 +1,7 @@
export const captureException = function () {};
export const captureException = function () {
// Do not capture exceptions in tests
};
export const captureBreadcrumb = function () {};
export const captureBreadcrumb = function () {
// Do not capture breadcrumbs in tests
};

View File

@@ -2,4 +2,6 @@ export const captureException = function (exc: Error) {
console.error('[Exception]', exc);
};
export const captureBreadcrumb = function (_crumb: unknown) {};
export const captureBreadcrumb = function (crumb: unknown) {
console.info('[Breadcrumb]', crumb);
};

View File

@@ -4,7 +4,9 @@ import type * as T from '../index-types';
const store: GlobalPrefsJson = {};
export const init: T.Init = function () {};
export const init: T.Init = function () {
// No need to initialise in tests
};
export const getItem: T.GetItem = async function (key) {
return store[key];

View File

@@ -4,7 +4,9 @@ import { getDatabase } from '../indexeddb';
import type * as T from './index-types';
export const init: T.Init = function () {};
export const init: T.Init = function () {
// No need to initialise in the browser
};
export const getItem: T.GetItem = async function (key) {
const db = await getDatabase();

View File

@@ -2,7 +2,9 @@ import type * as T from '../index-types';
let events = [];
export const init: T.Init = function () {};
export const init: T.Init = function () {
// No need to initialise in tests
};
export const send: T.Send = function (type, args) {
events.push([type, args]);

View File

@@ -1,6 +1,8 @@
import type * as T from './index-types';
export const init: T.Init = function () {};
export const init: T.Init = function () {
// Nothing
};
export const send: T.Send = function () {
// Nothing

View File

@@ -85,4 +85,6 @@ export const send: T.Send = function (name, args) {
process.parentPort.postMessage({ type: 'push', name, args });
};
export const resetEvents: T.ResetEvents = function () {};
export const resetEvents: T.ResetEvents = function () {
// resetEvents is used in tests to mock the server
};

View File

@@ -136,4 +136,6 @@ export const getNumClients = function () {
return 1;
};
export const resetEvents: T.ResetEvents = function () {};
export const resetEvents: T.ResetEvents = function () {
// resetEvents is used in tests to mock the server
};

View File

@@ -16,7 +16,9 @@ function verifyParamTypes(sql, arr) {
});
}
export async function init() {}
export async function init() {
// No need to initialise on electron
}
export function prepare(db, sql) {
return db.prepare(sql);

View File

@@ -329,7 +329,9 @@ handlers['api/finish-import'] = async function () {
await handlers['get-budget-bounds']();
await sheet.waitOnSpreadsheet();
await cloudStorage.upload().catch(() => {});
await cloudStorage.upload().catch(() => {
// Ignore errors
});
connection.send('finish-import');
IMPORT_MODE = false;

View File

@@ -290,7 +290,9 @@ async function deleteBudget({
// If it's a cloud file, you can delete it from the server by
// passing its cloud id
if (cloudFileId) {
await cloudStorage.removeFile(cloudFileId).catch(() => {});
await cloudStorage.removeFile(cloudFileId).catch(() => {
// Ignore errors
});
}
// If a local file exists, you can delete it by passing its local id

View File

@@ -357,7 +357,9 @@ export async function possiblyUpload() {
}
// Don't block on uploading
upload().catch(() => {});
upload().catch(() => {
// Ignore errors
});
}
export async function removeFile(fileId) {

View File

@@ -43,5 +43,7 @@ export async function importActual(_filepath: string, buffer: Buffer) {
await handlers['load-budget']({ id });
await handlers['get-budget-bounds']();
await waitOnSpreadsheet();
await cloudStorage.upload().catch(() => {});
await cloudStorage.upload().catch(() => {
// Ignore errors
});
}

View File

@@ -268,8 +268,9 @@ export class Spreadsheet {
if (!this.running && this.computeQueue.length === 0) {
func([]);
// The remove function does nothing
return () => {};
return () => {
// The remove function does nothing
};
}
const remove = this.addEventListener('change', (...args) => {

View File

@@ -235,7 +235,7 @@ async function run(msgs) {
});
await global.emptyDatabase()();
await sheet.loadSpreadsheet(db, () => {});
await sheet.loadSpreadsheet(db, vi.fn());
// The test: split up the messages into chunks and in parallel send
// them all through `sendMessages`. Then add some messages to the

View File

@@ -15,7 +15,7 @@ beforeEach(global.emptyDatabase());
// it hard to test.
const old = console.warn;
beforeAll(() => {
console.warn = () => {};
console.warn = vi.fn();
});
afterAll(() => {
console.warn = old;

View File

@@ -60,7 +60,9 @@ describe('async', () => {
test('sequential fn should still flush queue when error is thrown', async () => {
const test = async fn => {
fn(1);
fn(2, { throwError: true }).catch(() => {});
fn(2, { throwError: true }).catch(() => {
// Ignore errors
});
await fn(3);
};

View File

@@ -1,6 +1,4 @@
// @ts-strict-ignore
import { type NavigateFunction } from 'react-router';
export {};
type FileDialogOptions = {
@@ -47,11 +45,6 @@ type Actual = {
};
declare global {
// oxlint-disable-next-line typescript/consistent-type-definitions
interface Window {
__navigate?: NavigateFunction;
}
var Actual: Actual;
var IS_TESTING: boolean;

View File

@@ -173,9 +173,9 @@ describe('app-cors-proxy', () => {
clearAllowlistCache();
vi.spyOn(console, 'log').mockImplementation(() => {});
vi.spyOn(console, 'warn').mockImplementation(() => {});
vi.spyOn(console, 'error').mockImplementation(() => {});
vi.spyOn(console, 'log').mockImplementation(vi.fn());
vi.spyOn(console, 'warn').mockImplementation(vi.fn());
vi.spyOn(console, 'error').mockImplementation(vi.fn());
});
describe('CORS preflight', () => {

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
lint: fix most eslint/no-empty-function violations