Compare commits

...

3 Commits

Author SHA1 Message Date
Matiss Janis Aboltins
bed293f5fb Update index.ts 2026-03-11 18:59:09 +00:00
Matiss Janis Aboltins
1216a2e3f7 refactor: update sorting functions to use two-argument comparator 2026-03-10 22:50:31 +00:00
Matiss Janis Aboltins
6445180974 lint: promote no-floating-promises and require-array-sort-compare to error and fix violations
- Set typescript/no-floating-promises and typescript/require-array-sort-compare to error in .oxlintrc.json
- Add explicit compare functions to all .sort() calls: migrations (localeCompare), crdt/merkle (localeCompare), FiltersMenu (tuple key), useScheduleEdit (two-arg comparator), exec.test (localeCompare), goal-template and category-template-context (numeric), main.test and transactions.test (localeCompare / amount+id)
- Fix invalid single-arg sort in useScheduleEdit to proper two-arg comparator

Made-with: Cursor
2026-03-10 22:25:49 +00:00
16 changed files with 74 additions and 31 deletions

View File

@@ -102,8 +102,8 @@
// we want to allow unions such as "{ name: DbAccount['name'] | DbPayee['name'] }"
"typescript/no-duplicate-type-constituents": "off",
"typescript/await-thenable": "error",
"typescript/no-floating-promises": "warn", // TODO: covert to error
"typescript/require-array-sort-compare": "warn", // TODO: covert to error
"typescript/no-floating-promises": "error",
"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

View File

@@ -91,7 +91,7 @@ export function diff(trie1: TrieNode, trie2: TrieNode): number | null {
while (true) {
const keyset = new Set([...getKeys(node1), ...getKeys(node2)]);
const keys = [...keyset.values()];
keys.sort();
keys.sort((a, b) => a.localeCompare(b));
let diffkey: null | '0' | '1' | '2' = null;
@@ -145,7 +145,7 @@ export function prune(trie: TrieNode, n = 2): TrieNode {
}
const keys = getKeys(trie);
keys.sort();
keys.sort((a, b) => a.localeCompare(b));
const next: TrieNode = { hash: trie.hash };

View File

@@ -55,7 +55,7 @@ export function BudgetSummaries() {
}
const to = -offsetX;
spring.start({ from: { x: from }, x: to });
void spring.start({ from: { x: from }, x: to });
}, [spring, firstMonth, monthWidth, allMonths]);
useLayoutEffect(() => {
@@ -63,7 +63,7 @@ export function BudgetSummaries() {
}, [firstMonth]);
useLayoutEffect(() => {
spring.start({ from: { x: -monthWidth }, to: { x: -monthWidth } });
void spring.start({ from: { x: -monthWidth }, to: { x: -monthWidth } });
}, [spring, monthWidth]);
const { SummaryComponent } = useBudgetComponents();

View File

@@ -518,7 +518,7 @@ export function FilterButton<T extends RuleConditionEntity>({
items={[
...translatedFilterFields
.filter(f => (exclude ? !exclude.includes(f[0]) : true))
.sort()
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([name, text]) => ({
name,
text: titleFirst(text),

View File

@@ -47,7 +47,7 @@ export function ActionableGridListItem<T extends object>({
if (active) {
dragStartedRef.current = true;
api.start({
void api.start({
x: Math.max(-actionsWidth, Math.min(0, currentX)),
onRest: () => {
dragStartedRef.current = false;
@@ -61,7 +61,7 @@ export function ActionableGridListItem<T extends object>({
currentX < -actionsWidth / 2 ||
(vx < -0.5 && currentX < -actionsWidth / 5);
api.start({
void api.start({
x: shouldReveal ? -actionsWidth : 0,
onRest: () => {
dragStartedRef.current = false;
@@ -140,7 +140,7 @@ export function ActionableGridListItem<T extends object>({
{typeof actions === 'function'
? actions({
close: () => {
api.start({
void api.start({
x: 0,
onRest: () => {
setIsRevealed(false);

View File

@@ -59,7 +59,7 @@ export function MobileNavTabs() {
// when cancel is true, it means that the user passed the upwards threshold
// so we change the spring config to create a nice wobbly effect
setNavbarState('open');
api.start({
void api.start({
y: OPEN_FULL_Y,
immediate: isTestEnv,
config: canceled ? config.wobbly : config.stiff,
@@ -71,7 +71,7 @@ export function MobileNavTabs() {
const openDefault = useCallback(
(velocity = 0) => {
setNavbarState('default');
api.start({
void api.start({
y: OPEN_DEFAULT_Y,
immediate: isTestEnv,
config: { ...config.stiff, velocity },
@@ -83,7 +83,7 @@ export function MobileNavTabs() {
const hide = useCallback(
(velocity = 0) => {
setNavbarState('hidden');
api.start({
void api.start({
y: HIDDEN_Y,
immediate: isTestEnv,
config: { ...config.stiff, velocity },
@@ -199,7 +199,7 @@ export function MobileNavTabs() {
} else {
// when the user keeps dragging, we just move the sheet according to
// the cursor position
api.start({ y: oy, immediate: true });
void api.start({ y: oy, immediate: true });
}
},
{

View File

@@ -1680,7 +1680,7 @@ function TransactionEditUnconnected({
...serializeTransaction(transaction, dateFormat),
payee: nearestPayee.id,
};
onUpdate(updated, 'payee');
void onUpdate(updated, 'payee');
}, [transactions, nearestPayee, onUpdate, dateFormat]);
if (accounts.length === 0) {

View File

@@ -164,9 +164,11 @@ function createScheduleEditReducer(useGetScheduledAmount: boolean = false) {
const transactions = action.transactions;
// Sort transactions if we have a transactionId to prioritize
if (action.transactionId && transactions) {
transactions.sort(a => {
return action.transactionId === a.id ? -1 : 1;
});
transactions.sort(
(a, b) =>
(action.transactionId === b.id ? 1 : 0) -
(action.transactionId === a.id ? 1 : 0),
);
}
return { ...state, transactions };
}

View File

@@ -420,8 +420,10 @@ async function createWindow() {
clientWin = win;
// Execute queued logs - displaying them in the client window
queuedClientWinLogs.map((log: string) =>
win.webContents.executeJavaScript(log),
void Promise.all(
queuedClientWinLogs.map((log: string) =>
win.webContents.executeJavaScript(log),
),
);
queuedClientWinLogs = [];

View File

@@ -271,7 +271,7 @@ describe('compileAndRunQuery', () => {
'SELECT id FROM transactions WHERE amount < -50',
);
const ids = rows.slice(0, 3).map(row => row.id);
ids.sort();
ids.sort((a, b) => String(a).localeCompare(String(b)));
const { data } = await compileAndRunAqlQuery(
q('transactions')
@@ -280,6 +280,10 @@ describe('compileAndRunQuery', () => {
.raw()
.serialize(),
);
expect(data.map(row => row.id).sort()).toEqual(ids);
expect(
data
.map(row => row.id)
.sort((a, b) => String(a).localeCompare(String(b))),
).toEqual(ids);
});
});

View File

@@ -124,7 +124,9 @@ export class CategoryTemplateContext {
// what is the full requested amount this month
async runAll(available: number) {
let toBudget: number = 0;
const prioritiesSorted = new Int32Array([...this.getPriorities()].sort());
const prioritiesSorted = new Int32Array(
[...this.getPriorities()].sort((a, b) => a - b),
);
for (let i = 0; i < prioritiesSorted.length; i++) {
const p = prioritiesSorted[i];
toBudget += await this.runTemplatesForPriority(p, available, available);

View File

@@ -258,7 +258,7 @@ async function processTemplate(
};
}
const priorities = new Int32Array([...prioritiesSet]).sort();
const priorities = new Int32Array([...prioritiesSet]).sort((a, b) => a - b);
// run each priority level
for (const priority of priorities) {
const availStart = availBudget;

View File

@@ -261,7 +261,9 @@ describe('Budget', () => {
let changed = await captureChangedCells(() =>
runHandler(handlers['transaction-add'], trans),
);
expect(changed.sort()).toMatchSnapshot();
expect(
changed.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)),
).toMatchSnapshot();
// Test updates
changed = await captureChangedCells(async () => {
await runHandler(handlers['transaction-update'], {
@@ -269,12 +271,16 @@ describe('Budget', () => {
amount: 7000,
});
});
expect(changed.sort()).toMatchSnapshot();
expect(
changed.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)),
).toMatchSnapshot();
// Test deletions
changed = await captureChangedCells(async () => {
await runHandler(handlers['transaction-delete'], { id: trans.id });
});
expect(changed.sort()).toMatchSnapshot();
expect(
changed.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)),
).toMatchSnapshot();
});
});

View File

@@ -51,7 +51,14 @@ describe('Transactions', () => {
deleted: [],
updated: [expect.objectContaining({ id: 't1', amount: 5000 })],
});
expect(data.map(t => ({ id: t.id, amount: t.amount })).sort()).toEqual([
expect(
data
.map(t => ({ id: t.id, amount: t.amount }))
.sort(
(a, b) =>
b.amount - a.amount || String(a.id).localeCompare(String(b.id)),
),
).toEqual([
{ id: expect.any(String), amount: 5000 },
{ id: 't1', amount: 5000 },
{ id: expect.any(String), amount: 3000 },
@@ -66,7 +73,14 @@ describe('Transactions', () => {
];
const { data, diff } = updateTransaction(transactions, updatedTransaction);
expect(diff).toEqual({ added: [], deleted: [], updated: [] });
expect(data.map(t => ({ id: t.id, amount: t.amount })).sort()).toEqual([
expect(
data
.map(t => ({ id: t.id, amount: t.amount }))
.sort(
(a, b) =>
b.amount - a.amount || String(a.id).localeCompare(String(b.id)),
),
).toEqual([
{ id: expect.any(String), amount: 5000 },
{ id: expect.any(String), amount: 3000 },
]);
@@ -85,7 +99,14 @@ describe('Transactions', () => {
deleted: [{ id: 't1' }],
updated: [],
});
expect(data.map(t => ({ id: t.id, amount: t.amount })).sort()).toEqual([
expect(
data
.map(t => ({ id: t.id, amount: t.amount }))
.sort(
(a, b) =>
b.amount - a.amount || String(a.id).localeCompare(String(b.id)),
),
).toEqual([
{ id: expect.any(String), amount: 5000 },
{ id: expect.any(String), amount: 3000 },
]);

View File

@@ -31,7 +31,7 @@ export async function run(direction: 'up' | 'down' = 'up'): Promise<void> {
.filter(
f => (f.endsWith('.js') || f.endsWith('.ts')) && !f.endsWith('.d.ts'),
)
.sort()) {
.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0))) {
migrationsModules[f] = await import(
pathToFileURL(path.join(migrationsDir, f)).href
);

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Lint: fix 'typescript/no-floating-promises' and 'typescript/require-array-sort-compare' issues