From 378325342341b4a22a006f20b984629e914b3eca Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Mon, 11 Dec 2023 19:55:22 +0000 Subject: [PATCH 01/11] :recycle: (TypeScript) fix strictFunctionTypes violations (pt 1) --- .../src/server/budget/types/handlers.d.ts | 44 ++++++++++++++----- packages/loot-core/src/server/mutators.ts | 5 +-- packages/loot-core/src/server/undo.ts | 17 +++---- packages/loot-core/src/shared/async.ts | 14 +++--- .../loot-core/src/types/api-handlers.d.ts | 4 +- packages/loot-core/src/types/handlers.d.ts | 2 + .../loot-core/src/types/server-handlers.d.ts | 2 +- tsconfig.json | 2 + 8 files changed, 54 insertions(+), 36 deletions(-) diff --git a/packages/loot-core/src/server/budget/types/handlers.d.ts b/packages/loot-core/src/server/budget/types/handlers.d.ts index bb3f6964080..66c2f72c731 100644 --- a/packages/loot-core/src/server/budget/types/handlers.d.ts +++ b/packages/loot-core/src/server/budget/types/handlers.d.ts @@ -5,13 +5,13 @@ export interface BudgetHandlers { category: string /* category id */; month: string; amount: number; - }) => Promise; + }) => Promise; - 'budget/copy-previous-month': (...args: unknown[]) => Promise; + 'budget/copy-previous-month': (arg: { month: string }) => Promise; - 'budget/set-zero': (...args: unknown[]) => Promise; + 'budget/set-zero': (arg: { month: string }) => Promise; - 'budget/set-3month-avg': (...args: unknown[]) => Promise; + 'budget/set-3month-avg': (arg: { month: string }) => Promise; 'budget/check-templates': () => Promise; @@ -27,17 +27,37 @@ export interface BudgetHandlers { month: string; }) => Promise; - 'budget/hold-for-next-month': (...args: unknown[]) => Promise; + 'budget/hold-for-next-month': (arg: { + month: string; + amount: number; + }) => Promise; - 'budget/reset-hold': (...args: unknown[]) => Promise; + 'budget/reset-hold': (arg: { month: string }) => Promise; - 'budget/cover-overspending': (...args: unknown[]) => Promise; + 'budget/cover-overspending': (arg: { + month: string; + to: string; + from: string; + }) => Promise; - 'budget/transfer-available': (...args: unknown[]) => Promise; + 'budget/transfer-available': (arg: { + month: string; + amount: number; + category: string; + }) => Promise; - 'budget/transfer-category': (...args: unknown[]) => Promise; + 'budget/transfer-category': (arg: { + month: string; + amount: number; + to: string; + from: string; + }) => Promise; - 'budget/set-carryover': (...args: unknown[]) => Promise; + 'budget/set-carryover': (arg: { + startMonth: string; + category: string; + flag: boolean; + }) => Promise; 'budget/apply-single-template': (arg: { month: string; @@ -48,10 +68,10 @@ export interface BudgetHandlers { month: string; N: number; category: string; //category id - }) => Promise; + }) => Promise; 'budget/copy-single-month': (arg: { month: string; category: string; //category id - }) => Promise; + }) => Promise; } diff --git a/packages/loot-core/src/server/mutators.ts b/packages/loot-core/src/server/mutators.ts index 9d85c086157..3988e5c58f8 100644 --- a/packages/loot-core/src/server/mutators.ts +++ b/packages/loot-core/src/server/mutators.ts @@ -1,5 +1,6 @@ import { captureException, captureBreadcrumb } from '../platform/exceptions'; import { sequential } from '../shared/async'; +import { type HandlerFunctions } from '../types/handlers'; const runningMethods = new Set(); @@ -9,9 +10,7 @@ let globalMutationsEnabled = false; let _latestHandlerNames = []; -export function mutator unknown>( - handler: T, -): T { +export function mutator(handler: T): T { mutatingMethods.set(handler, true); return handler; } diff --git a/packages/loot-core/src/server/undo.ts b/packages/loot-core/src/server/undo.ts index faf5fa9278d..549c6a8ca61 100644 --- a/packages/loot-core/src/server/undo.ts +++ b/packages/loot-core/src/server/undo.ts @@ -2,6 +2,7 @@ import { Timestamp } from '@actual-app/crdt'; import * as connection from '../platform/server/connection'; import { getIn } from '../shared/util'; +import { type HandlerFunctions } from '../types/handlers'; import { withMutatorContext, getMutatorContext } from './mutators'; import { Message, sendMessages } from './sync'; @@ -89,18 +90,10 @@ export function withUndo( ); } -// for some reason `void` is not inferred properly without this overload -export function undoable( - func: (...args: Args) => Promise, -): (...args: Args) => Promise; -export function undoable< - Args extends unknown[], - Return extends Promise, ->(func: (...args: Args) => Return): (...args: Args) => Return; -export function undoable(func: (...args: unknown[]) => Promise) { - return (...args: unknown[]) => { - return withUndo(() => { - return func(...args); +export function undoable(func: T) { + return (...args: Parameters) => { + return withUndo>>(() => { + return func.apply(null, ...args); }); }; } diff --git a/packages/loot-core/src/shared/async.ts b/packages/loot-core/src/shared/async.ts index 718204f3663..0e6a0c92200 100644 --- a/packages/loot-core/src/shared/async.ts +++ b/packages/loot-core/src/shared/async.ts @@ -1,4 +1,6 @@ -export function sequential unknown>( +import { type HandlerFunctions } from '../types/handlers'; + +export function sequential( fn: T, ): (...args: Parameters) => Promise>> { const sequenceState = { @@ -16,7 +18,7 @@ export function sequential unknown>( } function run(args, resolve, reject) { - sequenceState.running = fn(...args); + sequenceState.running = fn.apply(null, args); sequenceState.running.then( val => { @@ -43,13 +45,13 @@ export function sequential unknown>( }; } -export function once Promise>( +export function once( fn: T, ): (...args: Parameters) => Promise>> { let promise = null; - const onceFn = (...args: Parameters): Promise>> => { + return (...args) => { if (!promise) { - promise = fn(...args).finally(() => { + promise = fn.apply(null, args).finally(() => { promise = null; }); return promise; @@ -57,6 +59,4 @@ export function once Promise>( return promise; }; - - return onceFn; } diff --git a/packages/loot-core/src/types/api-handlers.d.ts b/packages/loot-core/src/types/api-handlers.d.ts index 30e7b5b66bc..71bc1530666 100644 --- a/packages/loot-core/src/types/api-handlers.d.ts +++ b/packages/loot-core/src/types/api-handlers.d.ts @@ -1,3 +1,5 @@ +import { type ServerHandlers } from './server-handlers'; + export interface ApiHandlers { 'api/batch-budget-start': () => Promise; @@ -5,7 +7,7 @@ export interface ApiHandlers { 'api/load-budget': ( ...args: Parameters - ) => Promise; + ) => Promise; 'api/download-budget': (arg: { syncId; password }) => Promise; diff --git a/packages/loot-core/src/types/handlers.d.ts b/packages/loot-core/src/types/handlers.d.ts index 2537c027962..7d131d44554 100644 --- a/packages/loot-core/src/types/handlers.d.ts +++ b/packages/loot-core/src/types/handlers.d.ts @@ -17,3 +17,5 @@ export interface Handlers RulesHandlers, SchedulesHandlers, ToolsHandlers {} + +export type HandlerFunctions = Handlers[keyof Handlers]; diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index 9553f836363..59d7f8d26ad 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -321,7 +321,7 @@ export interface ServerHandlers { error?: { message: string; reason: string; meta: unknown }; }>; - 'load-budget': (arg: { id }) => Promise<{ error }>; + 'load-budget': (arg: { id: string }) => Promise<{ error }>; 'create-demo-budget': () => Promise; diff --git a/tsconfig.json b/tsconfig.json index 1a4dce7bf8f..844bcaf11c4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,8 @@ "downlevelIteration": true, // TODO: enable once every file is ts // "strict": true, + // TODO: enable once the violations are fixed + // "strictFunctionTypes": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, "jsx": "preserve", From f039130435c4ee86173bda95e4bc73e1d46af816 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Mon, 11 Dec 2023 19:56:42 +0000 Subject: [PATCH 02/11] Release notes --- upcoming-release-notes/2065.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 upcoming-release-notes/2065.md diff --git a/upcoming-release-notes/2065.md b/upcoming-release-notes/2065.md new file mode 100644 index 00000000000..873d58604ec --- /dev/null +++ b/upcoming-release-notes/2065.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Fixing TypeScript issues when enabling `strictFunctionTypes`. From 56bef95a45b8b0603b25e249a8dbf3da521f8221 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Mon, 11 Dec 2023 20:01:44 +0000 Subject: [PATCH 03/11] oops --- packages/loot-core/src/server/undo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/loot-core/src/server/undo.ts b/packages/loot-core/src/server/undo.ts index 549c6a8ca61..ac12e9c388c 100644 --- a/packages/loot-core/src/server/undo.ts +++ b/packages/loot-core/src/server/undo.ts @@ -93,7 +93,7 @@ export function withUndo( export function undoable(func: T) { return (...args: Parameters) => { return withUndo>>(() => { - return func.apply(null, ...args); + return func.apply(null, args); }); }; } From 670788696888b1189d208600496e838a25614706 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Mon, 11 Dec 2023 20:26:36 +0000 Subject: [PATCH 04/11] :recycle: (TypeScript) fix strictFunctionTypes violations (pt 2) --- .../desktop-client/src/components/sidebar/Item.tsx | 4 +++- .../src/components/sidebar/SecondaryItem.tsx | 4 +++- packages/desktop-client/src/components/sort.tsx | 10 +++++++--- packages/desktop-client/src/components/table.tsx | 2 +- packages/loot-core/package.json | 2 +- .../loot-core/src/server/sync/sync.property.test.ts | 6 +++--- upcoming-release-notes/2066.md | 6 ++++++ yarn.lock | 10 +++++----- 8 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 upcoming-release-notes/2066.md diff --git a/packages/desktop-client/src/components/sidebar/Item.tsx b/packages/desktop-client/src/components/sidebar/Item.tsx index 32ec1c87ef6..043e44d4123 100644 --- a/packages/desktop-client/src/components/sidebar/Item.tsx +++ b/packages/desktop-client/src/components/sidebar/Item.tsx @@ -13,7 +13,9 @@ import ItemContent from './ItemContent'; type ItemProps = { title: string; - Icon: ComponentType>; + Icon: + | ComponentType> + | ComponentType>; to?: string; children?: ReactNode; style?: CSSProperties; diff --git a/packages/desktop-client/src/components/sidebar/SecondaryItem.tsx b/packages/desktop-client/src/components/sidebar/SecondaryItem.tsx index dbe355c35e7..0780acbfb4e 100644 --- a/packages/desktop-client/src/components/sidebar/SecondaryItem.tsx +++ b/packages/desktop-client/src/components/sidebar/SecondaryItem.tsx @@ -16,7 +16,9 @@ const fontWeight = 600; type SecondaryItemProps = { title: string; to?: string; - Icon?: ComponentType>; + Icon?: + | ComponentType> + | ComponentType>; style?: CSSProperties; onClick?: MouseEventHandler; bold?: boolean; diff --git a/packages/desktop-client/src/components/sort.tsx b/packages/desktop-client/src/components/sort.tsx index 10e7d963d11..a3ce6cb883c 100644 --- a/packages/desktop-client/src/components/sort.tsx +++ b/packages/desktop-client/src/components/sort.tsx @@ -72,7 +72,7 @@ export function useDraggable({ } export type OnDropCallback = ( - id: unknown, + id: string, dropPos: DropPosition, targetId: unknown, ) => Promise | void; @@ -86,7 +86,7 @@ type UseDroppableArgs = { onLongHover?: OnLongHoverCallback; }; -export function useDroppable({ +export function useDroppable({ types, id, onDrop, @@ -95,7 +95,11 @@ export function useDroppable({ const ref = useRef(null); const [dropPos, setDropPos] = useState(null); - const [{ isOver }, dropRef] = useDrop({ + const [{ isOver }, dropRef] = useDrop< + { item: T }, + unknown, + { isOver: boolean } + >({ accept: types, drop({ item }) { onDrop(item.id, dropPos, id); diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx index b85928335f1..92e2e833a47 100644 --- a/packages/desktop-client/src/components/table.tsx +++ b/packages/desktop-client/src/components/table.tsx @@ -660,7 +660,7 @@ export function SelectCell({ type SheetCellValueProps = { binding: Binding; type: string; - getValueStyle?: (value: unknown) => CSSProperties; + getValueStyle?: (value: string | number) => CSSProperties; formatExpr?: (value) => string; unformatExpr?: (value: string) => unknown; privacyFilter?: ConditionalPrivacyFilterProps['privacyFilter']; diff --git a/packages/loot-core/package.json b/packages/loot-core/package.json index 44c8460f753..d6c155b264f 100644 --- a/packages/loot-core/package.json +++ b/packages/loot-core/package.json @@ -46,7 +46,7 @@ "@swc/core": "^1.3.82", "@swc/helpers": "^0.5.1", "@swc/jest": "^0.2.29", - "@types/better-sqlite3": "^7.6.7", + "@types/better-sqlite3": "^7.6.8", "@types/jest": "^27.5.0", "@types/jlongster__sql.js": "npm:@types/sql.js@latest", "@types/pegjs": "^0.10.3", diff --git a/packages/loot-core/src/server/sync/sync.property.test.ts b/packages/loot-core/src/server/sync/sync.property.test.ts index 9532405d486..346eb3f16ce 100644 --- a/packages/loot-core/src/server/sync/sync.property.test.ts +++ b/packages/loot-core/src/server/sync/sync.property.test.ts @@ -93,7 +93,7 @@ const baseTime = 1565374471903; const clientId1 = '80dd7da215247293'; const clientId2 = '90xU1sd5124329ac'; -function makeGen({ +function makeGen>({ table, row, field, @@ -102,7 +102,7 @@ function makeGen({ table: string; row?: Arbitrary; field: string; - value: Arbitrary; + value: T; }) { return jsc.record({ dataset: jsc.constant(table), @@ -127,7 +127,7 @@ function makeGen({ }); } -const generators = []; +const generators: Array> = []; Object.keys(schema).forEach(table => { Object.keys(schema[table]).reduce((obj, field) => { if (table === 'spreadsheet_cells' && field === 'expr') { diff --git a/upcoming-release-notes/2066.md b/upcoming-release-notes/2066.md new file mode 100644 index 00000000000..6ff11102d47 --- /dev/null +++ b/upcoming-release-notes/2066.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Fixing TypeScript issues when enabling `strictFunctionTypes` (pt.2). diff --git a/yarn.lock b/yarn.lock index 2251b1091e1..19999972fb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4300,12 +4300,12 @@ __metadata: languageName: node linkType: hard -"@types/better-sqlite3@npm:^7.6.7": - version: 7.6.7 - resolution: "@types/better-sqlite3@npm:7.6.7" +"@types/better-sqlite3@npm:^7.6.8": + version: 7.6.8 + resolution: "@types/better-sqlite3@npm:7.6.8" dependencies: "@types/node": "npm:*" - checksum: 5021c1bae4a494408c1a77d84bc31dc15e373b8a1cf8880acba6517f63bc5c2dbf032c81938641346fb967600dbebc1475033c2458b5a5b93eb8f2c53bdbcbf8 + checksum: 404e9b7210564866b0f8878353cc6a16c6ffb313077cbb5aec6176ad2b0a30f64236f03f0a40d36d86bf4eab7658bdcd6d6a8a65dc377de7910fc9e9932885a4 languageName: node linkType: hard @@ -13385,7 +13385,7 @@ __metadata: "@swc/helpers": "npm:^0.5.1" "@swc/jest": "npm:^0.2.29" "@types/adm-zip": "npm:^0.5.0" - "@types/better-sqlite3": "npm:^7.6.7" + "@types/better-sqlite3": "npm:^7.6.8" "@types/jest": "npm:^27.5.0" "@types/jlongster__sql.js": "npm:@types/sql.js@latest" "@types/pegjs": "npm:^0.10.3" From eacca881017b3cdd97eac7f7dadebdf71055e25f Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 12 Dec 2023 12:38:31 +0000 Subject: [PATCH 05/11] :recycle: (TypeScript) fix strictFunctionTypes violations (pt 3) --- .../components/autocomplete/Autocomplete.tsx | 145 +++++++++++------- .../autocomplete/PayeeAutocomplete.tsx | 1 + .../autocomplete/SavedFilterAutocomplete.tsx | 14 +- .../src/client/data-hooks/filters.tsx | 3 +- .../loot-core/src/types/models/index.d.ts | 1 + .../src/types/models/transaction-filter.d.ts | 7 + 6 files changed, 107 insertions(+), 64 deletions(-) create mode 100644 packages/loot-core/src/types/models/transaction-filter.d.ts diff --git a/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx b/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx index 5c413b01d55..65b09844eb1 100644 --- a/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/Autocomplete.tsx @@ -10,7 +10,7 @@ import React, { type ChangeEvent, } from 'react'; -import Downshift from 'downshift'; +import Downshift, { type StateChangeTypes } from 'downshift'; import { css } from 'glamor'; import Remove from '../../icons/v2/Remove'; @@ -20,18 +20,31 @@ import Input from '../common/Input'; import View from '../common/View'; import { Tooltip } from '../tooltips'; -const inst: { lastChangeType? } = {}; +type Item = { + id?: string; + name: string; +}; + +const inst: { lastChangeType?: StateChangeTypes } = {}; -function findItem(strict, suggestions, value) { +function findItem( + strict: boolean, + suggestions: T[], + value: T | T['id'], +): T | null { if (strict) { const idx = suggestions.findIndex(item => item.id === value); return idx === -1 ? null : suggestions[idx]; } + if (typeof value === 'string') { + throw new Error('value can be string only if strict = false'); + } + return value; } -function getItemName(item) { +function getItemName(item: null | string | Item): string { if (item == null) { return ''; } else if (typeof item === 'string') { @@ -40,24 +53,36 @@ function getItemName(item) { return item.name || ''; } -function getItemId(item) { +function getItemId(item: Item | Item['id']) { if (typeof item === 'string') { return item; } return item ? item.id : null; } -export function defaultFilterSuggestion(suggestion, value) { +export function defaultFilterSuggestion( + suggestion: T, + value: string, +) { return getItemName(suggestion).toLowerCase().includes(value.toLowerCase()); } -function defaultFilterSuggestions(suggestions, value) { +function defaultFilterSuggestions( + suggestions: T[], + value: string, +) { return suggestions.filter(suggestion => defaultFilterSuggestion(suggestion, value), ); } -function fireUpdate(onUpdate, strict, suggestions, index, value) { +function fireUpdate( + onUpdate: ((selected: string | null, value: string) => void) | undefined, + strict: boolean, + suggestions: T[], + index: number, + value: string, +) { // If the index is null, look up the id in the suggestions. If the // value is empty it will select nothing (as expected). If it's not // empty but nothing is selected, it still resolves to an id. It @@ -82,11 +107,15 @@ function fireUpdate(onUpdate, strict, suggestions, index, value) { onUpdate?.(selected, value); } -function defaultRenderInput(props) { +function defaultRenderInput(props: ComponentProps) { return ; } -function defaultRenderItems(items, getItemProps, highlightedIndex) { +function defaultRenderItems( + items: T[], + getItemProps: (arg: { item: T }) => ComponentProps, + highlightedIndex: number, +) { return (
{items.map((item, index) => { @@ -134,15 +163,15 @@ function defaultRenderItems(items, getItemProps, highlightedIndex) { ); } -function defaultShouldSaveFromKey(e) { +function defaultShouldSaveFromKey(e: KeyboardEvent) { return e.code === 'Enter'; } -function defaultItemToString(item) { +function defaultItemToString(item?: T) { return item ? getItemName(item) : ''; } -type SingleAutocompleteProps = { +type SingleAutocompleteProps = { focused?: boolean; embedded?: boolean; containerProps?: HTMLProps; @@ -150,31 +179,31 @@ type SingleAutocompleteProps = { inputProps?: Omit, 'onChange'> & { onChange?: (value: string) => void; }; - suggestions?: unknown[]; + suggestions?: T[]; tooltipStyle?: CSSProperties; tooltipProps?: ComponentProps; renderInput?: (props: ComponentProps) => ReactNode; renderItems?: ( - items, - getItemProps: (arg: { item: unknown }) => ComponentProps, + items: T[], + getItemProps: (arg: { item: T }) => ComponentProps, idx: number, - value?: unknown, + value?: string, ) => ReactNode; - itemToString?: (item) => string; + itemToString?: (item: T) => string; shouldSaveFromKey?: (e: KeyboardEvent) => boolean; - filterSuggestions?: (suggestions, value: string) => unknown[]; + filterSuggestions?: (suggestions: T[], value: string) => T[]; openOnFocus?: boolean; - getHighlightedIndex?: (suggestions) => number | null; + getHighlightedIndex?: (suggestions: T[]) => number | null; highlightFirst?: boolean; - onUpdate?: (id: unknown, value: string) => void; + onUpdate?: (id: T['id'], value: string) => void; strict?: boolean; - onSelect: (id: unknown, value: string) => void; + onSelect: (id: T['id'], value: string) => void; tableBehavior?: boolean; closeOnBlur?: boolean; - value: unknown[] | string; + value: T | T['id']; isMulti?: boolean; }; -function SingleAutocomplete({ +function SingleAutocomplete({ focused, embedded = false, containerProps, @@ -198,7 +227,7 @@ function SingleAutocomplete({ closeOnBlur = true, value: initialValue, isMulti = false, -}: SingleAutocompleteProps) { +}: SingleAutocompleteProps) { const [selectedItem, setSelectedItem] = useState(() => findItem(strict, suggestions, initialValue), ); @@ -220,9 +249,9 @@ function SingleAutocomplete({ setSelectedItem(findItem(strict, suggestions, initialValue)); }, [initialValue, suggestions, strict]); - function resetState(newValue) { + function resetState(newValue?: string) { const val = newValue === undefined ? initialValue : newValue; - const selectedItem = findItem(strict, suggestions, val); + const selectedItem = findItem(strict, suggestions, val); setSelectedItem(selectedItem); setValue(selectedItem ? getItemName(selectedItem) : ''); @@ -527,7 +556,12 @@ function SingleAutocomplete({ ); } -function MultiItem({ name, onRemove }) { +type MultiItemProps = { + name: string; + onRemove: () => void; +}; + +function MultiItem({ name, onRemove }: MultiItemProps) { return ( & { - value: unknown[]; - onSelect: (ids: unknown[], id?: string) => void; +type MultiAutocompleteProps< + T extends Item, + Value = SingleAutocompleteProps['value'], +> = Omit, 'value' | 'onSelect'> & { + value: Value[]; + onSelect: (ids: Value[], id?: string) => void; }; -function MultiAutocomplete({ +function MultiAutocomplete({ value: selectedItems, onSelect, suggestions, strict, ...props -}: MultiAutocompleteProps) { +}: MultiAutocompleteProps) { const [focused, setFocused] = useState(false); - const lastSelectedItems = useRef(); + const lastSelectedItems = useRef(); useEffect(() => { lastSelectedItems.current = selectedItems; }); - function onRemoveItem(id) { + function onRemoveItem(id: (typeof selectedItems)[0]) { const items = selectedItems.filter(i => i !== id); onSelect(items); } - function onAddItem(id) { + function onAddItem(id: string) { if (id) { id = id.trim(); onSelect([...selectedItems, id], id); } } - function onKeyDown(e, prevOnKeyDown) { + function onKeyDown( + e: KeyboardEvent, + prevOnKeyDown?: ComponentProps['onKeyDown'], + ) { + // @ts-expect-error We're missing `target.value` on KeyboardEvent if (e.key === 'Backspace' && e.target.value === '') { onRemoveItem(selectedItems[selectedItems.length - 1]); } @@ -680,31 +718,24 @@ export function AutocompleteFooter({ ); } -type AutocompleteProps = - | ComponentProps - | ComponentProps; +type AutocompleteProps = + | ComponentProps> + | ComponentProps>; -function isMultiAutocomplete( - props: AutocompleteProps, +function isMultiAutocomplete( + _props: AutocompleteProps, multi?: boolean, -): props is ComponentProps { +): _props is ComponentProps> { return multi; } -function isSingleAutocomplete( - props: AutocompleteProps, - multi?: boolean, -): props is ComponentProps { - return !multi; -} - -export default function Autocomplete({ +export default function Autocomplete({ multi, ...props -}: AutocompleteProps & { multi?: boolean }) { +}: AutocompleteProps & { multi?: boolean }) { if (isMultiAutocomplete(props, multi)) { return ; - } else if (isSingleAutocomplete(props, multi)) { - return ; } + + return ; } diff --git a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx index 6ed812c7b02..1c908d0be68 100644 --- a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx @@ -308,6 +308,7 @@ export default function PayeeAutocomplete({ const isf = filtered.length > 100; filtered = filtered.slice(0, 100); + // @ts-expect-error TODO: solve this somehow filtered.filtered = isf; if (filtered.length >= 2 && filtered[0].id === 'new') { diff --git a/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx index a2800b85646..989e0b17123 100644 --- a/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx @@ -1,25 +1,26 @@ import React, { type ComponentProps } from 'react'; import { useFilters } from 'loot-core/src/client/data-hooks/filters'; +import { type TransactionFilterEntity } from 'loot-core/src/types/models'; import { theme } from '../../style'; import View from '../common/View'; import Autocomplete from './Autocomplete'; -type FilterListProps = { - items: { id: string; name: string }[]; - getItemProps: (arg: { item: unknown }) => ComponentProps; +type FilterListProps = { + items: T[]; + getItemProps: (arg: { item: T }) => ComponentProps; highlightedIndex: number; embedded?: boolean; }; -function FilterList({ +function FilterList({ items, getItemProps, highlightedIndex, embedded, -}: FilterListProps) { +}: FilterListProps) { return ( ; +} & ComponentProps>; export default function SavedFilterAutocomplete({ embedded, @@ -73,6 +74,7 @@ export default function SavedFilterAutocomplete({ suggestions={filters} renderItems={(items, getItemProps, highlightedIndex) => ( q('transaction_filters').select('*'), []) || [], ); diff --git a/packages/loot-core/src/types/models/index.d.ts b/packages/loot-core/src/types/models/index.d.ts index d411ff64eb8..cceded549bb 100644 --- a/packages/loot-core/src/types/models/index.d.ts +++ b/packages/loot-core/src/types/models/index.d.ts @@ -6,3 +6,4 @@ export type * from './payee'; export type * from './rule'; export type * from './schedule'; export type * from './transaction'; +export type * from './transaction-filter'; diff --git a/packages/loot-core/src/types/models/transaction-filter.d.ts b/packages/loot-core/src/types/models/transaction-filter.d.ts new file mode 100644 index 00000000000..e43d50e21e3 --- /dev/null +++ b/packages/loot-core/src/types/models/transaction-filter.d.ts @@ -0,0 +1,7 @@ +export interface TransactionFilterEntity { + id: string; + name: string; + conditions_op: string; + conditions: unknown; + tombstone: boolean; +} From f176b92a4acd4d21537c8ab449a2a3abcbd81ea0 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 12 Dec 2023 12:40:36 +0000 Subject: [PATCH 06/11] Release notes --- upcoming-release-notes/2070.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 upcoming-release-notes/2070.md diff --git a/upcoming-release-notes/2070.md b/upcoming-release-notes/2070.md new file mode 100644 index 00000000000..f04d8bd7ac7 --- /dev/null +++ b/upcoming-release-notes/2070.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Fixing TypeScript issues when enabling `strictFunctionTypes` (pt.3). From aa14d648e5a7e40388154bae87481e6bdcc90322 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 12 Dec 2023 13:14:59 +0000 Subject: [PATCH 07/11] :recycle: (TypeScript) fix strictFunctionTypes violations (pt 4) --- .../src/components/common/Menu.tsx | 10 +- .../src/components/payees/PayeeTable.tsx | 99 ++-- .../components/schedules/SchedulesTable.tsx | 2 +- .../desktop-client/src/components/table.tsx | 498 +++++++++--------- .../src/server/sync/sync.property.test.ts | 5 +- upcoming-release-notes/2071.md | 6 + 6 files changed, 310 insertions(+), 310 deletions(-) create mode 100644 upcoming-release-notes/2071.md diff --git a/packages/desktop-client/src/components/common/Menu.tsx b/packages/desktop-client/src/components/common/Menu.tsx index 1d0258cd77e..eb5efa66c66 100644 --- a/packages/desktop-client/src/components/common/Menu.tsx +++ b/packages/desktop-client/src/components/common/Menu.tsx @@ -33,19 +33,19 @@ type MenuItem = { key?: string; }; -type MenuProps = { +type MenuProps = { header?: ReactNode; footer?: ReactNode; - items: Array; - onMenuSelect: (itemName: MenuItem['name']) => void; + items: Array; + onMenuSelect: (itemName: T['name']) => void; }; -export default function Menu({ +export default function Menu({ header, footer, items: allItems, onMenuSelect, -}: MenuProps) { +}: MenuProps) { const elRef = useRef(null); const items = allItems.filter(x => x); const [hoveredIndex, setHoveredIndex] = useState(null); diff --git a/packages/desktop-client/src/components/payees/PayeeTable.tsx b/packages/desktop-client/src/components/payees/PayeeTable.tsx index b376752ae30..c7504d76cfe 100644 --- a/packages/desktop-client/src/components/payees/PayeeTable.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTable.tsx @@ -1,10 +1,8 @@ import { - forwardRef, useCallback, useLayoutEffect, useState, type ComponentProps, - type ComponentRef, } from 'react'; import { type PayeeEntity } from 'loot-core/src/types/models'; @@ -20,6 +18,7 @@ import PayeeTableRow from './PayeeTableRow'; type PayeeWithId = PayeeEntity & Required>; type PayeeTableProps = { + tableRef: ComponentProps>['tableRef']; payees: PayeeWithId[]; ruleCounts: Map; navigator: TableNavigator; @@ -28,56 +27,56 @@ type PayeeTableProps = { 'onUpdate' | 'onViewRules' | 'onCreateRule' >; -const PayeeTable = forwardRef< - ComponentRef>, - PayeeTableProps ->( - ( - { payees, ruleCounts, navigator, onUpdate, onViewRules, onCreateRule }, - ref, - ) => { - const [hovered, setHovered] = useState(null); - const selectedItems = useSelectedItems(); +const PayeeTable = ({ + tableRef, + payees, + ruleCounts, + navigator, + onUpdate, + onViewRules, + onCreateRule, +}: PayeeTableProps) => { + const [hovered, setHovered] = useState(null); + const selectedItems = useSelectedItems(); - useLayoutEffect(() => { - const firstSelected = [...selectedItems][0] as string; - if (typeof ref !== 'function') { - ref.current.scrollTo(firstSelected, 'center'); - } - navigator.onEdit(firstSelected, 'select'); - }, []); + useLayoutEffect(() => { + const firstSelected = [...selectedItems][0] as string; + if (typeof tableRef !== 'function') { + tableRef.current.scrollTo(firstSelected, 'center'); + } + navigator.onEdit(firstSelected, 'select'); + }, []); - const onHover = useCallback(id => { - setHovered(id); - }, []); + const onHover = useCallback((id: string) => { + setHovered(id); + }, []); - return ( - setHovered(null)}> - - ref={ref} - items={payees} - navigator={navigator} - renderItem={({ item, editing, focusedField, onEdit }) => { - return ( - - ); - }} - /> - - ); - }, -); + return ( + setHovered(null)}> + { + return ( + + ); + }} + /> + + ); +}; export default PayeeTable; diff --git a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx index 149cb6fd188..e7c2574936c 100644 --- a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx +++ b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx @@ -118,7 +118,7 @@ function OverflowMenu({ onClose={() => setOpen(false)} > { + onMenuSelect={name => { onAction(name, schedule.id); setOpen(false); }} diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx index 92e2e833a47..ddf274b1b50 100644 --- a/packages/desktop-client/src/components/table.tsx +++ b/packages/desktop-client/src/components/table.tsx @@ -11,7 +11,6 @@ import React, { type ReactNode, type KeyboardEvent, type UIEvent, - type ReactElement, type Ref, } from 'react'; import { useStore } from 'react-redux'; @@ -847,17 +846,18 @@ type TableHandleRef = { type TableWithNavigatorProps = TableProps & { fields; }; -export const TableWithNavigator = forwardRef< - TableHandleRef, - TableWithNavigatorProps ->(({ fields, ...props }, ref) => { +export const TableWithNavigator = ({ + fields, + ...props +}: TableWithNavigatorProps) => { const navigator = useTableNavigator(props.items, fields); return
; -}); +}; type TableItem = { id: number | string }; type TableProps = { + tableRef?: Ref>; items: T[]; count?: number; headers?: ReactNode | TableHeaderProps['headers']; @@ -886,282 +886,276 @@ type TableProps = { saveScrollWidth?: (parent, child) => void; }; -export const Table: ( - props: TableProps & { ref?: Ref> }, -) => ReactElement = forwardRef( - ( - { - items, - count, - headers, - contentHeader, - loading, - rowHeight = ROW_HEIGHT, - backgroundColor = theme.tableHeaderBackground, - renderItem, - renderEmpty, - getItemKey, - loadMore, - style, - navigator, - onScroll, - version = 'v1', - allowPopupsEscape, - isSelected, - saveScrollWidth, - ...props - }, - ref, - ) => { - if (!navigator) { - navigator = { - onEdit: () => {}, - editingId: null, - focusedField: null, - getNavigatorProps: props => props, - }; - } - - const { onEdit, editingId, focusedField, getNavigatorProps } = navigator; - const list = useRef(null); - const listContainer = useRef(null); - const scrollContainer = useRef(null); - const initialScrollTo = useRef(null); - const listInitialized = useRef(false); - - useImperativeHandle(ref, () => ({ - scrollTo: (id, alignment = 'smart') => { - const index = items.findIndex(item => item.id === id); - if (index !== -1) { - if (!list.current) { - // If the table hasn't been laid out yet, we need to wait for - // that to happen before we can scroll to something - initialScrollTo.current = index; - } else { - list.current.scrollToItem(index, alignment); - } - } - }, - - scrollToTop: () => { - list.current?.scrollTo(0); - }, +export const Table = ({ + tableRef, + items, + count, + headers, + contentHeader, + loading, + rowHeight = ROW_HEIGHT, + backgroundColor = theme.tableHeaderBackground, + renderItem, + renderEmpty, + getItemKey, + loadMore, + style, + navigator, + onScroll, + version = 'v1', + allowPopupsEscape, + isSelected, + saveScrollWidth, + ...props +}: TableProps) => { + if (!navigator) { + navigator = { + onEdit: () => {}, + editingId: null, + focusedField: null, + getNavigatorProps: props => props, + }; + } - getScrolledItem: () => { - if (scrollContainer.current) { - const offset = scrollContainer.current.scrollTop; - const index = list.current.getStartIndexForOffset(offset); - return items[index].id; + const { onEdit, editingId, focusedField, getNavigatorProps } = navigator; + const list = useRef(null); + const listContainer = useRef(null); + const scrollContainer = useRef(null); + const initialScrollTo = useRef(null); + const listInitialized = useRef(false); + + useImperativeHandle(tableRef, () => ({ + scrollTo: (id, alignment = 'smart') => { + const index = items.findIndex(item => item.id === id); + if (index !== -1) { + if (!list.current) { + // If the table hasn't been laid out yet, we need to wait for + // that to happen before we can scroll to something + initialScrollTo.current = index; + } else { + list.current.scrollToItem(index, alignment); } - return 0; - }, - - setRowAnimation: flag => { - list.current?.setRowAnimation(flag); - }, + } + }, - edit(id, field, shouldScroll) { - onEdit(id, field); + scrollToTop: () => { + list.current?.scrollTo(0); + }, - if (id && shouldScroll) { - // @ts-expect-error this should not be possible - ref.scrollTo(id); - } - }, + getScrolledItem: () => { + if (scrollContainer.current) { + const offset = scrollContainer.current.scrollTop; + const index = list.current.getStartIndexForOffset(offset); + return items[index].id; + } + return 0; + }, - anchor() { - list.current?.anchor(); - }, + setRowAnimation: flag => { + list.current?.setRowAnimation(flag); + }, - unanchor() { - list.current?.unanchor(); - }, + edit(id, field, shouldScroll) { + onEdit(id, field); - isAnchored() { - return list.current && list.current.isAnchored(); - }, - })); - - useLayoutEffect(() => { - // We wait for the list to mount because AutoSizer needs to run - // before it's mounted - if (!listInitialized.current && listContainer.current) { - // Animation is on by default - list.current?.setRowAnimation(true); - listInitialized.current = true; + if (id && shouldScroll) { + // @ts-expect-error this should not be possible + ref.scrollTo(id); } + }, - if (scrollContainer.current && saveScrollWidth) { - saveScrollWidth( - scrollContainer.current.offsetParent - ? scrollContainer.current.offsetParent.clientWidth - : 0, - scrollContainer.current ? scrollContainer.current.clientWidth : 0, - ); - } - }); + anchor() { + list.current?.anchor(); + }, - function renderRow({ index, style, key }) { - const item = items[index]; - const editing = editingId === item.id; - const selected = isSelected && isSelected(item.id); - - const row = renderItem({ - item, - editing, - focusedField: editing && focusedField, - onEdit, - index, - position: style.top, - }); - - // TODO: Need to also apply zIndex if item is selected - // * Port over ListAnimation to Table - // * Move highlighted functionality into here - return ( - - {row} - - ); + unanchor() { + list.current?.unanchor(); + }, + + isAnchored() { + return list.current && list.current.isAnchored(); + }, + })); + + useLayoutEffect(() => { + // We wait for the list to mount because AutoSizer needs to run + // before it's mounted + if (!listInitialized.current && listContainer.current) { + // Animation is on by default + list.current?.setRowAnimation(true); + listInitialized.current = true; } - function getScrollOffset(height, index) { - return ( - index * (rowHeight - 1) + - (rowHeight - 1) / 2 - - height / 2 + - (rowHeight - 1) * 2 + if (scrollContainer.current && saveScrollWidth) { + saveScrollWidth( + scrollContainer.current.offsetParent + ? scrollContainer.current.offsetParent.clientWidth + : 0, + scrollContainer.current ? scrollContainer.current.clientWidth : 0, ); } + }); - function onItemsRendered({ overscanStartIndex, overscanStopIndex }) { - if (loadMore && overscanStopIndex > items.length - 100) { - loadMore(); - } - } + function renderRow({ index, style, key }) { + const item = items[index]; + const editing = editingId === item.id; + const selected = isSelected && isSelected(item.id); - function getEmptyContent(empty) { - if (empty == null) { - return null; - } else if (typeof empty === 'function') { - return empty(); - } + const row = renderItem({ + item, + editing, + focusedField: editing && focusedField, + onEdit, + index, + position: style.top, + }); - return ( - - {empty} - - ); - } + // TODO: Need to also apply zIndex if item is selected + // * Port over ListAnimation to Table + // * Move highlighted functionality into here + return ( + + {row} + + ); + } - if (loading) { - return ( - - - - ); + function getScrollOffset(height, index) { + return ( + index * (rowHeight - 1) + + (rowHeight - 1) / 2 - + height / 2 + + (rowHeight - 1) * 2 + ); + } + + function onItemsRendered({ overscanStartIndex, overscanStopIndex }) { + if (loadMore && overscanStopIndex > items.length - 100) { + loadMore(); } + } - const isEmpty = (count || items.length) === 0; + function getEmptyContent(empty) { + if (empty == null) { + return null; + } else if (typeof empty === 'function') { + return empty(); + } return ( - {headers && ( - - )} - - {isEmpty ? ( - getEmptyContent(renderEmpty) - ) : ( - - {({ width, height }) => { - if (width === 0 || height === 0) { - return null; - } + {empty} + + ); + } - return ( - - - items[index].id) - } - indexForKey={key => - items.findIndex(item => item.id === key) - } - initialScrollOffset={ - initialScrollTo.current - ? getScrollOffset(height, initialScrollTo.current) - : 0 - } - overscanCount={5} - onItemsRendered={onItemsRendered} - onScroll={onScroll} - /> - - - ); - }} - - )} - + if (loading) { + return ( + + ); - }, -); + } + + const isEmpty = (count || items.length) === 0; + + return ( + + {headers && ( + + )} + + {isEmpty ? ( + getEmptyContent(renderEmpty) + ) : ( + + {({ width, height }) => { + if (width === 0 || height === 0) { + return null; + } + + return ( + + + items[index].id) + } + indexForKey={key => + items.findIndex(item => item.id === key) + } + initialScrollOffset={ + initialScrollTo.current + ? getScrollOffset(height, initialScrollTo.current) + : 0 + } + overscanCount={5} + onItemsRendered={onItemsRendered} + onScroll={onScroll} + /> + + + ); + }} + + )} + + + ); +}; export type TableNavigator = { onEdit: (id: T['id'], field?: string) => void; diff --git a/packages/loot-core/src/server/sync/sync.property.test.ts b/packages/loot-core/src/server/sync/sync.property.test.ts index 346eb3f16ce..7f92124d197 100644 --- a/packages/loot-core/src/server/sync/sync.property.test.ts +++ b/packages/loot-core/src/server/sync/sync.property.test.ts @@ -93,7 +93,8 @@ const baseTime = 1565374471903; const clientId1 = '80dd7da215247293'; const clientId2 = '90xU1sd5124329ac'; -function makeGen>({ +// @typescript-eslint/no-explicit-any +function makeGen>({ table, row, field, @@ -111,7 +112,7 @@ function makeGen>({ value, timestamp: jsc.integer(1000, 10000).smap( x => { - let clientId; + let clientId: string; switch (jsc.random(0, 1)) { case 0: clientId = clientId1; diff --git a/upcoming-release-notes/2071.md b/upcoming-release-notes/2071.md new file mode 100644 index 00000000000..e443cc99adc --- /dev/null +++ b/upcoming-release-notes/2071.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Fixing TypeScript issues when enabling `strictFunctionTypes` (pt.4). From 6b4de0f710bdb47b5615a636a7386f86cfb04a82 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 12 Dec 2023 13:22:10 +0000 Subject: [PATCH 08/11] Test and lint fixes --- .../src/components/transactions/TransactionsTable.js | 2 +- packages/loot-core/src/server/sync/sync.property.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.js b/packages/desktop-client/src/components/transactions/TransactionsTable.js index 26b9f2361dc..3a1a2f6391b 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.js +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.js @@ -1722,7 +1722,7 @@ function TransactionTableInner({ >
>({ table, row, From dcdf461998f473dd18bcba6b0771f4ae56f0f400 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Tue, 12 Dec 2023 15:32:33 +0000 Subject: [PATCH 09/11] :recycle: (TypeScript) fix strictFunctionTypes violations (pt 5) --- .../autocomplete/SavedFilterAutocomplete.tsx | 1 - packages/desktop-client/src/hooks/useActions.ts | 7 ++++++- .../platform/server/sqlite/index.electron.ts | 2 ++ packages/loot-core/src/server/aql/compiler.ts | 17 ++++++++++++++--- .../loot-core/src/server/aql/schema/index.ts | 4 +++- tsconfig.json | 3 +-- upcoming-release-notes/2072.md | 6 ++++++ 7 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 upcoming-release-notes/2072.md diff --git a/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx index 989e0b17123..684cdbb54f5 100644 --- a/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx @@ -74,7 +74,6 @@ export default function SavedFilterAutocomplete({ suggestions={filters} renderItems={(items, getItemProps, highlightedIndex) => ( unknown> = export type BoundActions = { [Key in keyof typeof actions]: ( ...args: Parameters<(typeof actions)[Key]> - ) => ActionReturnType<(typeof actions)[Key]>; + ) => // @ts-expect-error temporarily disabling this TS error; eventually the `useActions` hook should be removed + ActionReturnType<(typeof actions)[Key]>; }; // https://react-redux.js.org/api/hooks#recipe-useactions +/** + * @deprecated please use actions directly with `useDispatch` + * @see https://github.com/reduxjs/react-redux/issues/1252#issuecomment-488160930 + **/ export function useActions() { const dispatch = useDispatch(); return useMemo(() => { diff --git a/packages/loot-core/src/platform/server/sqlite/index.electron.ts b/packages/loot-core/src/platform/server/sqlite/index.electron.ts index 146e8ac192c..0374f34d8b5 100644 --- a/packages/loot-core/src/platform/server/sqlite/index.electron.ts +++ b/packages/loot-core/src/platform/server/sqlite/index.electron.ts @@ -98,9 +98,11 @@ export function openDatabase(pathOrBuffer: string | Buffer) { const db = new SQL(pathOrBuffer); // Define Unicode-aware LOWER and UPPER implementation. // This is necessary because better-sqlite3 uses SQLite build without ICU support. + // @ts-expect-error @types/better-sqlite3 does not support setting strict 3rd argument db.function('UNICODE_LOWER', { deterministic: true }, (arg: string | null) => arg?.toLowerCase(), ); + // @ts-expect-error @types/better-sqlite3 does not support setting strict 3rd argument db.function('UNICODE_UPPER', { deterministic: true }, (arg: string | null) => arg?.toUpperCase(), ); diff --git a/packages/loot-core/src/server/aql/compiler.ts b/packages/loot-core/src/server/aql/compiler.ts index ade3d1d0200..29b437ec38b 100644 --- a/packages/loot-core/src/server/aql/compiler.ts +++ b/packages/loot-core/src/server/aql/compiler.ts @@ -989,10 +989,21 @@ export function isAggregateQuery(queryState) { }); } -type SchemaConfig = { - tableViews?: Record | ((...args: unknown[]) => unknown); +export type SchemaConfig = { + tableViews?: + | Record + | ((name: string, config: { withDead; isJoin; tableOptions }) => unknown); tableFilters?: (name: string) => unknown[]; - customizeQuery?: (queryString: T) => T; + customizeQuery?: ( + queryString: T, + ) => T; + views?: Record< + string, + { + fields?: Record; + [key: `v_${string}`]: string | ((internalFields, publicFields) => string); + } + >; }; export function compileQuery( queryState, diff --git a/packages/loot-core/src/server/aql/schema/index.ts b/packages/loot-core/src/server/aql/schema/index.ts index 26af5205b07..1af6735bdc4 100644 --- a/packages/loot-core/src/server/aql/schema/index.ts +++ b/packages/loot-core/src/server/aql/schema/index.ts @@ -1,3 +1,5 @@ +import { SchemaConfig } from '../compiler'; + function f(type: string, opts?: Record) { return { type, ...opts }; } @@ -140,7 +142,7 @@ export const schema = { }, }; -export const schemaConfig = { +export const schemaConfig: SchemaConfig = { // Note: these views *must* represent the underlying table that we // are mapping here. The compiler makes optimizations with this // assumption diff --git a/tsconfig.json b/tsconfig.json index 844bcaf11c4..a426766d86a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,8 +15,7 @@ "downlevelIteration": true, // TODO: enable once every file is ts // "strict": true, - // TODO: enable once the violations are fixed - // "strictFunctionTypes": true, + "strictFunctionTypes": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, "jsx": "preserve", diff --git a/upcoming-release-notes/2072.md b/upcoming-release-notes/2072.md new file mode 100644 index 00000000000..edad616bb1f --- /dev/null +++ b/upcoming-release-notes/2072.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Fixing TypeScript issues when enabling `strictFunctionTypes` (pt.5). From 34ea930b4ff5b73be04a93fa65cbdaa0492971b7 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Fri, 22 Dec 2023 21:31:45 +0000 Subject: [PATCH 10/11] Fixes --- .../src/components/autocomplete/SavedFilterAutocomplete.tsx | 1 - packages/loot-core/src/server/sync/sync.property.test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx index 989e0b17123..684cdbb54f5 100644 --- a/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/SavedFilterAutocomplete.tsx @@ -74,7 +74,6 @@ export default function SavedFilterAutocomplete({ suggestions={filters} renderItems={(items, getItemProps, highlightedIndex) => ( >({ +function makeGen| Arbitrary>({ table, row, field, From d1b63192ab0bc34f7a06da2f3e3fb7830a202b52 Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Fri, 22 Dec 2023 21:32:32 +0000 Subject: [PATCH 11/11] Any --- packages/loot-core/src/server/sync/sync.property.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/loot-core/src/server/sync/sync.property.test.ts b/packages/loot-core/src/server/sync/sync.property.test.ts index e75b5ed9fd2..cbe5cefe01f 100644 --- a/packages/loot-core/src/server/sync/sync.property.test.ts +++ b/packages/loot-core/src/server/sync/sync.property.test.ts @@ -93,7 +93,8 @@ const baseTime = 1565374471903; const clientId1 = '80dd7da215247293'; const clientId2 = '90xU1sd5124329ac'; -function makeGen| Arbitrary>({ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function makeGen>({ table, row, field,