chore: resolve no-explicit-any and no-base-to-string lint warnings (#12217)

This commit is contained in:
Tom Moor
2026-04-29 17:45:02 -04:00
committed by GitHub
parent 281b778b2d
commit 4c8a1c89b2
44 changed files with 162 additions and 110 deletions
+6 -2
View File
@@ -17,8 +17,12 @@ import Analytics from "~/utils/Analytics";
import history from "~/utils/history"; import history from "~/utils/history";
import type { Action as KbarAction } from "kbar"; import type { Action as KbarAction } from "kbar";
export function resolve<T>(value: any, context: ActionContext): T { export function resolve<T>(value: unknown, context: ActionContext): T {
return typeof value === "function" ? value(context) : value; return (
typeof value === "function"
? (value as (context: ActionContext) => T)(context)
: value
) as T;
} }
export const ActionSeparator: TActionSeparator = { export const ActionSeparator: TActionSeparator = {
+2 -2
View File
@@ -29,7 +29,7 @@ import { useDialogContext } from "~/components/DialogContext";
const IconPicker = createLazyComponent(() => import("~/components/IconPicker")); const IconPicker = createLazyComponent(() => import("~/components/IconPicker"));
export interface FormData { export type FormData = {
name: string; name: string;
icon: string; icon: string;
color: string | null; color: string | null;
@@ -37,7 +37,7 @@ export interface FormData {
permission: CollectionPermission | undefined; permission: CollectionPermission | undefined;
commenting?: boolean | null; commenting?: boolean | null;
templateManagement: CollectionPermission; templateManagement: CollectionPermission;
} };
const useIconColor = (collection?: Collection) => { const useIconColor = (collection?: Collection) => {
const { collections } = useStores(); const { collections } = useStores();
+10 -9
View File
@@ -87,22 +87,23 @@ const ContentEditable = React.forwardRef(function ContentEditable_(
})); }));
const wrappedEvent = const wrappedEvent =
( <E extends React.SyntheticEvent<HTMLSpanElement>>(
callback: callback: ((event: E) => void) | undefined
| React.FocusEventHandler<HTMLSpanElement>
| React.FormEventHandler<HTMLSpanElement>
| React.KeyboardEventHandler<HTMLSpanElement>
| undefined
) => ) =>
(event: any) => { (event: E) => {
if (readOnly) { if (readOnly) {
return; return;
} }
const text = event.currentTarget.textContent || ""; const text = event.currentTarget.textContent || "";
if (maxLength && isPrintableKeyEvent(event) && text.length >= maxLength) { if (
event?.preventDefault(); maxLength &&
event.nativeEvent instanceof KeyboardEvent &&
isPrintableKeyEvent(event.nativeEvent) &&
text.length >= maxLength
) {
event.preventDefault();
return; return;
} }
+1
View File
@@ -1,6 +1,7 @@
import * as React from "react"; import * as React from "react";
import lazyWithRetry from "~/utils/lazyWithRetry"; import lazyWithRetry from "~/utils/lazyWithRetry";
// oxlint-disable no-explicit-any -- ComponentType<any> is the standard React pattern for generic component constraints
export interface LazyComponent<T extends React.ComponentType<any>> { export interface LazyComponent<T extends React.ComponentType<any>> {
Component: React.LazyExoticComponent<T>; Component: React.LazyExoticComponent<T>;
preload: () => Promise<{ default: T }>; preload: () => Promise<{ default: T }>;
@@ -13,7 +13,7 @@ import Switch from "../Switch";
import EventBoundary from "@shared/components/EventBoundary"; import EventBoundary from "@shared/components/EventBoundary";
import { InputClientType } from "./InputClientType"; import { InputClientType } from "./InputClientType";
export interface FormData { export type FormData = {
name: string; name: string;
developerName: string; developerName: string;
developerUrl: string; developerUrl: string;
@@ -22,7 +22,7 @@ export interface FormData {
redirectUris: string[]; redirectUris: string[];
published: boolean; published: boolean;
clientType: "confidential" | "public"; clientType: "confidential" | "public";
} };
export const OAuthClientForm = observer(function OAuthClientForm_({ export const OAuthClientForm = observer(function OAuthClientForm_({
handleSubmit, handleSubmit,
+3 -1
View File
@@ -7,7 +7,9 @@ import PaginatedList from "~/components/PaginatedList";
type Props = { type Props = {
documents: Document[]; documents: Document[];
fetch: (options: any) => Promise<Document[] | undefined>; // oxlint-disable-next-line no-explicit-any
fetch: (options: Record<string, any>) => Promise<Document[] | undefined>;
// oxlint-disable-next-line no-explicit-any
options?: Record<string, any>; options?: Record<string, any>;
heading?: React.ReactNode; heading?: React.ReactNode;
empty?: JSX.Element; empty?: JSX.Element;
+3 -1
View File
@@ -35,10 +35,12 @@ interface Props<
* @param options Pagination and other query options * @param options Pagination and other query options
*/ */
fetch?: ( fetch?: (
// oxlint-disable-next-line no-explicit-any
options: Record<string, any> | undefined options: Record<string, any> | undefined
) => Promise<unknown[] | undefined> | undefined; ) => Promise<unknown[] | undefined> | undefined;
/** Additional options to pass to the fetch function */ /** Additional options to pass to the fetch function */
// oxlint-disable-next-line no-explicit-any
options?: Record<string, any>; options?: Record<string, any>;
/** Optional header content to display above the list */ /** Optional header content to display above the list */
@@ -78,7 +80,7 @@ interface Props<
* Function to render section headings (typically date-based) * Function to render section headings (typically date-based)
* @param name The heading text or element to render * @param name The heading text or element to render
*/ */
renderHeading?: (name: React.ReactElement<any> | string) => React.ReactNode; renderHeading?: (name: React.ReactElement | string) => React.ReactNode;
/** /**
* Function to determine if an item is a duplicate of the previous item. * Function to determine if an item is a duplicate of the previous item.
+1 -1
View File
@@ -64,7 +64,7 @@ type Props = {
}; };
const Tabs: React.FC = ({ children }: Props) => { const Tabs: React.FC = ({ children }: Props) => {
const ref = React.useRef<any>(); const ref = React.useRef<HTMLElement>(null);
const [shadowVisible, setShadow] = React.useState(false); const [shadowVisible, setShadow] = React.useState(false);
const { width } = useWindowSize(); const { width } = useWindowSize();
+3 -1
View File
@@ -4,6 +4,7 @@ import { Toaster, useSonner } from "sonner";
import styled, { useTheme } from "styled-components"; import styled, { useTheme } from "styled-components";
import { useWebHaptics } from "web-haptics/react"; import { useWebHaptics } from "web-haptics/react";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import type { ResolvedTheme } from "~/stores/UiStore";
function Toasts() { function Toasts() {
const { ui } = useStores(); const { ui } = useStores();
@@ -26,7 +27,8 @@ function Toasts() {
return ( return (
<StyledToaster <StyledToaster
theme={ui.resolvedTheme as any} // @ts-expect-error styled-components overrides sonner's theme prop with DefaultTheme
theme={ui.resolvedTheme as ResolvedTheme}
closeButton closeButton
toastOptions={{ toastOptions={{
duration: 5000, duration: 5000,
+1
View File
@@ -1,3 +1,4 @@
// oxlint-disable no-explicit-any -- window.env is a server-injected boundary with mixed value types
declare global { declare global {
interface Window { interface Window {
env: Record<string, any>; env: Record<string, any>;
+6 -6
View File
@@ -69,15 +69,15 @@ export const ActionContextProvider = observer(function ActionContextProvider_({
activeDocumentId: stores.ui.activeDocumentId ?? undefined, activeDocumentId: stores.ui.activeDocumentId ?? undefined,
getActiveModels: <T extends Model>( getActiveModels: <T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
): T[] => stores.ui.getActiveModels<T>(modelClass), ): T[] => stores.ui.getActiveModels<T>(modelClass),
getActiveModel: <T extends Model>( getActiveModel: <T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
): T | undefined => stores.ui.getActiveModels<T>(modelClass)[0], ): T | undefined => stores.ui.getActiveModels<T>(modelClass)[0],
getActivePolicies: <T extends Model>( getActivePolicies: <T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
): Policy[] => ): Policy[] =>
stores.ui stores.ui
.getActiveModels<T>(modelClass) .getActiveModels<T>(modelClass)
@@ -97,7 +97,7 @@ export const ActionContextProvider = observer(function ActionContextProvider_({
// Override model accessors when models are provided in value // Override model accessors when models are provided in value
const getActiveModels = const getActiveModels =
valueModels && valueModels.length > 0 valueModels && valueModels.length > 0
? <T extends Model>(modelClass: new (...args: any[]) => T): T[] => { ? <T extends Model>(modelClass: new (...args: never[]) => T): T[] => {
const matching = valueModels.filter( const matching = valueModels.filter(
(model): model is T => model instanceof modelClass (model): model is T => model instanceof modelClass
); );
@@ -108,11 +108,11 @@ export const ActionContextProvider = observer(function ActionContextProvider_({
: baseContext.getActiveModels; : baseContext.getActiveModels;
const getActiveModel = <T extends Model>( const getActiveModel = <T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
): T | undefined => getActiveModels(modelClass)[0]; ): T | undefined => getActiveModels(modelClass)[0];
const getActivePolicies = <T extends Model>( const getActivePolicies = <T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
): Policy[] => ): Policy[] =>
getActiveModels(modelClass) getActiveModels(modelClass)
.map((node) => stores.policies.get(node.id)) .map((node) => stores.policies.get(node.id))
+7 -4
View File
@@ -1,12 +1,15 @@
import { MobXProviderContext } from "mobx-react"; import { MobXProviderContext } from "mobx-react";
import { useContext } from "react"; import { useContext } from "react";
import type RootStore from "~/stores"; import type RootStore from "~/stores/RootStore";
/** /**
* Hook to access the MobX stores from the React context. * Hook to access the MobX stores from the React context.
* *
* @returns The root store containing all application stores * @returns The root store containing all application stores.
*/ */
export default function useStores() { export default function useStores(): RootStore {
return useContext(MobXProviderContext) as typeof RootStore; const { rootStore } = useContext(MobXProviderContext) as {
rootStore: RootStore;
};
return rootStore;
} }
+3 -1
View File
@@ -20,7 +20,9 @@ const defaultOptions: ThrottleSettings = {
* @param dependencies The dependencies to watch for changes * @param dependencies The dependencies to watch for changes
* @param options The throttle options * @param options The throttle options
*/ */
export default function useThrottledCallback<T extends (...args: any[]) => any>( export default function useThrottledCallback<
T extends (...args: never[]) => unknown,
>(
fn: T, fn: T,
wait = 250, wait = 250,
dependencies: React.DependencyList = [], dependencies: React.DependencyList = [],
+1 -1
View File
@@ -5,7 +5,7 @@ import { useRef, useEffect } from "react";
* *
* @param callback Function to be called on component unmount * @param callback Function to be called on component unmount
*/ */
const useUnmount = (callback: (...args: Array<any>) => any) => { const useUnmount = (callback: () => void) => {
const ref = useRef(callback); const ref = useRef(callback);
ref.current = callback; ref.current = callback;
+1 -1
View File
@@ -57,7 +57,7 @@ if (element) {
const App = () => ( const App = () => (
<StrictMode> <StrictMode>
<HelmetProvider> <HelmetProvider>
<Provider {...stores}> <Provider rootStore={stores}>
<Analytics> <Analytics>
<Router history={history}> <Router history={history}>
<Theme> <Theme>
+6 -5
View File
@@ -37,11 +37,12 @@ class AuthenticationProvider extends Model {
@AfterDelete @AfterDelete
static afterDelete(model: AuthenticationProvider) { static afterDelete(model: AuthenticationProvider) {
// Restore a placeholder record to allow re-connection // Restore a placeholder record to allow re-connection
return (model.store as AuthenticationProvidersStore).add({ return (model.store as AuthenticationProvidersStore).add(
...model, Object.assign({}, model, {
isEnabled: false, isEnabled: false,
isConnected: false, isConnected: false,
}); })
);
} }
} }
+2 -2
View File
@@ -38,7 +38,7 @@ type SaveOptions = JSONObject & {
export default class Document extends ArchivableModel implements Searchable { export default class Document extends ArchivableModel implements Searchable {
static modelName = "Document"; static modelName = "Document";
constructor(fields: Record<string, any>, store: DocumentsStore) { constructor(fields: Record<string, unknown>, store: DocumentsStore) {
super(fields, store); super(fields, store);
this.embedsDisabled = Storage.get(`embedsDisabled-${this.id}`) ?? false; this.embedsDisabled = Storage.get(`embedsDisabled-${this.id}`) ?? false;
@@ -570,7 +570,7 @@ export default class Document extends ArchivableModel implements Searchable {
); );
// if saving is successful set the new values on the model itself // if saving is successful set the new values on the model itself
set(this, { ...params, ...model }); set(this, Object.assign({}, params, model));
this.persistedAttributes = this.toAPI(); this.persistedAttributes = this.toAPI();
+7 -7
View File
@@ -28,7 +28,7 @@ export default abstract class Model {
store: Store<Model>; store: Store<Model>;
constructor(fields: Record<string, any>, store: Store<Model>) { constructor(fields: Record<string, unknown>, store: Store<Model>) {
this.store = store; this.store = store;
this.updateData(fields); this.updateData(fields);
this.isNew = !this.id; this.isNew = !this.id;
@@ -43,7 +43,7 @@ export default abstract class Model {
async loadRelations( async loadRelations(
this: Model, this: Model,
options: { withoutPolicies?: boolean } = {} options: { withoutPolicies?: boolean } = {}
): Promise<any> { ): Promise<unknown> {
// this is to ensure that multiple loads dont happen in parallel // this is to ensure that multiple loads dont happen in parallel
if (this.loadingRelations) { if (this.loadingRelations) {
return this.loadingRelations; return this.loadingRelations;
@@ -90,7 +90,7 @@ export default abstract class Model {
* @returns A promise that resolves with the updated model * @returns A promise that resolves with the updated model
*/ */
save = async ( save = async (
params?: Record<string, any>, params?: Record<string, unknown>,
options?: Record<string, string | boolean | number | undefined> options?: Record<string, string | boolean | number | undefined>
): Promise<Model> => { ): Promise<Model> => {
const isNew = this.isNew; const isNew = this.isNew;
@@ -120,7 +120,7 @@ export default abstract class Model {
); );
// if saving is successful set the new values on the model itself // if saving is successful set the new values on the model itself
this.updateData({ ...params, ...model }); this.updateData(Object.assign({}, params, model));
if (isNew) { if (isNew) {
LifecycleManager.executeHooks(this.constructor, "afterCreate", this); LifecycleManager.executeHooks(this.constructor, "afterCreate", this);
@@ -134,7 +134,7 @@ export default abstract class Model {
} }
}; };
updateData = action((data: Partial<Model>) => { updateData = action((data: Record<string, unknown>) => {
if (this.initialized) { if (this.initialized) {
LifecycleManager.executeHooks(this.constructor, "beforeChange", this); LifecycleManager.executeHooks(this.constructor, "beforeChange", this);
} }
@@ -197,7 +197,7 @@ export default abstract class Model {
* *
* @returns A plain object representation of the model * @returns A plain object representation of the model
*/ */
toAPI = (): Record<string, any> => { toAPI = (): Partial<Model> => {
const fields = getFieldsForModel(this); const fields = getFieldsForModel(this);
return pick(this, fields); return pick(this, fields);
}; };
@@ -247,7 +247,7 @@ export default abstract class Model {
protected persistedAttributes: Partial<Model> = {}; protected persistedAttributes: Partial<Model> = {};
/** A promise that resolves when all relations have been loaded. */ /** A promise that resolves when all relations have been loaded. */
private loadingRelations: Promise<any[]> | undefined; private loadingRelations: Promise<unknown[]> | undefined;
/** A boolean representing if the constructor has been called. */ /** A boolean representing if the constructor has been called. */
private initialized = false; private initialized = false;
+2 -2
View File
@@ -12,9 +12,9 @@ export const getFieldsForModel = <T extends Model>(target: T) =>
* @param target * @param target
* @param propertyKey * @param propertyKey
*/ */
const Field = <T>(target: any, propertyKey: keyof T) => { const Field = (target: Model, propertyKey: string | symbol) => {
const className = target.constructor.name; const className = target.constructor.name;
fields.set(className, [...(fields.get(className) || []), propertyKey]); fields.set(className, [...(fields.get(className) ?? []), propertyKey]);
}; };
export default Field; export default Field;
+27 -19
View File
@@ -1,24 +1,32 @@
export class LifecycleManager { type ModelClass = { readonly name: string };
private static hooks = new Map(); type Hook = (...args: unknown[]) => unknown;
public static getHooks(target: any, lifecycle: string) { export class LifecycleManager {
private static hooks = new Map<string, Map<string, string[]>>();
public static getHooks(target: ModelClass, lifecycle: string): string[] {
const key = `lifecycle:${lifecycle}`; const key = `lifecycle:${lifecycle}`;
const modelHooks = this.hooks.get(target.name); const modelHooks = this.hooks.get(target.name);
return modelHooks?.get(key) || []; return modelHooks?.get(key) ?? [];
} }
public static executeHooks(target: any, lifecycle: string, ...args: any[]) { public static executeHooks(
target: ModelClass,
lifecycle: string,
...args: unknown[]
): void {
const hooks = this.getHooks(target, lifecycle); const hooks = this.getHooks(target, lifecycle);
hooks.forEach((hook: keyof typeof target) => { hooks.forEach((hook) => {
target[hook](...args); const fn = (target as unknown as Record<string, Hook>)[hook];
fn(...args);
}); });
} }
public static registerHook( public static registerHook(
target: any, target: ModelClass,
propertyKey: string, propertyKey: string,
lifecycle: string lifecycle: string
) { ): void {
const key = `lifecycle:${lifecycle}`; const key = `lifecycle:${lifecycle}`;
let modelHooks = this.hooks.get(target.name); let modelHooks = this.hooks.get(target.name);
@@ -37,42 +45,42 @@ export class LifecycleManager {
} }
} }
export function BeforeCreate(target: any, propertyKey: string) { export function BeforeCreate(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "beforeCreate"); LifecycleManager.registerHook(target, propertyKey, "beforeCreate");
} }
export function AfterCreate(target: any, propertyKey: string) { export function AfterCreate(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "afterCreate"); LifecycleManager.registerHook(target, propertyKey, "afterCreate");
} }
export function BeforeUpdate(target: any, propertyKey: string) { export function BeforeUpdate(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "beforeUpdate"); LifecycleManager.registerHook(target, propertyKey, "beforeUpdate");
} }
export function AfterUpdate(target: any, propertyKey: string) { export function AfterUpdate(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "afterUpdate"); LifecycleManager.registerHook(target, propertyKey, "afterUpdate");
} }
export function BeforeChange(target: any, propertyKey: string) { export function BeforeChange(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "beforeChange"); LifecycleManager.registerHook(target, propertyKey, "beforeChange");
} }
export function AfterChange(target: any, propertyKey: string) { export function AfterChange(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "afterChange"); LifecycleManager.registerHook(target, propertyKey, "afterChange");
} }
export function BeforeRemove(target: any, propertyKey: string) { export function BeforeRemove(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "beforeRemove"); LifecycleManager.registerHook(target, propertyKey, "beforeRemove");
} }
export function AfterRemove(target: any, propertyKey: string) { export function AfterRemove(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "afterRemove"); LifecycleManager.registerHook(target, propertyKey, "afterRemove");
} }
export function BeforeDelete(target: any, propertyKey: string) { export function BeforeDelete(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "beforeDelete"); LifecycleManager.registerHook(target, propertyKey, "beforeDelete");
} }
export function AfterDelete(target: any, propertyKey: string) { export function AfterDelete(target: ModelClass, propertyKey: string) {
LifecycleManager.registerHook(target, propertyKey, "afterDelete"); LifecycleManager.registerHook(target, propertyKey, "afterDelete");
} }
+4 -4
View File
@@ -85,7 +85,7 @@ export default function Relation<T extends typeof Model>(
classResolver: () => T, classResolver: () => T,
options?: RelationOptions options?: RelationOptions
) { ) {
return function (target: any, propertyKey: string) { return function (target: Model, propertyKey: string) {
const idKey = options?.multiple const idKey = options?.multiple
? `${String(singular(propertyKey))}Ids` ? `${String(singular(propertyKey))}Ids`
: `${String(propertyKey)}Id`; : `${String(propertyKey)}Id`;
@@ -96,16 +96,16 @@ export default function Relation<T extends typeof Model>(
// TODO: requestAnimationFrame is a temporary solution to a bug in rolldown compiled code that // TODO: requestAnimationFrame is a temporary solution to a bug in rolldown compiled code that
// will place static methods _after_ decorators. Temporary fix is to delay the registration until // will place static methods _after_ decorators. Temporary fix is to delay the registration until
// the next frame. // the next frame.
const modelName = (target.constructor as typeof Model).modelName;
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (options) { if (options) {
const configForClass = const configForClass = relations.get(modelName) ?? new Map();
relations.get(target.constructor.modelName) || new Map();
configForClass.set(propertyKey, { configForClass.set(propertyKey, {
options, options,
relationClassResolver: classResolver, relationClassResolver: classResolver,
idKey, idKey,
}); });
relations.set(target.constructor.modelName, configForClass); relations.set(modelName, configForClass);
} }
}); });
+1 -1
View File
@@ -81,7 +81,7 @@ function Login({ children, onBack }: Props) {
const handleGoSubdomain = React.useCallback(async (event) => { const handleGoSubdomain = React.useCallback(async (event) => {
event.preventDefault(); event.preventDefault();
const data = Object.fromEntries(new FormData(event.target)); const data = Object.fromEntries(new FormData(event.target));
await navigateToSubdomain(data.subdomain.toString()); await navigateToSubdomain(data.subdomain as string);
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
@@ -104,7 +104,7 @@ function AuthenticationProvider(props: Props) {
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "hidden"; input.type = "hidden";
input.name = fieldName; input.name = fieldName;
input.value = String(value); input.value = String(value as string | number | boolean);
formRef.current?.appendChild(input); formRef.current?.appendChild(input);
} }
}); });
+1
View File
@@ -2,6 +2,7 @@ import Event from "~/models/Event";
import type RootStore from "./RootStore"; import type RootStore from "./RootStore";
import Store, { RPCAction } from "./base/Store"; import Store, { RPCAction } from "./base/Store";
// oxlint-disable no-explicit-any -- Event generic must be `any` because the store holds events for all model types
export default class EventsStore extends Store<Event<any>> { export default class EventsStore extends Store<Event<any>> {
actions = [RPCAction.List]; actions = [RPCAction.List];
+7 -3
View File
@@ -26,6 +26,8 @@ export enum SystemTheme {
Dark = "dark", Dark = "dark",
} }
export type ResolvedTheme = "light" | "dark" | "system";
type PersistedData = Pick< type PersistedData = Pick<
UiStore, UiStore,
| "languagePromptDismissed" | "languagePromptDismissed"
@@ -209,7 +211,9 @@ class UiStore {
* @param modelClass the model class to filter by. * @param modelClass the model class to filter by.
* @returns array of active models of the specified type. * @returns array of active models of the specified type.
*/ */
getActiveModels<T extends Model>(modelClass: new (...args: any[]) => T): T[] { getActiveModels<T extends Model>(
modelClass: new (...args: never[]) => T
): T[] {
return Array.from(this.activeModels.values()).filter( return Array.from(this.activeModels.values()).filter(
(model) => model.constructor === modelClass (model) => model.constructor === modelClass
) as T[]; ) as T[];
@@ -231,7 +235,7 @@ class UiStore {
* @param modelClass optional model class to filter by. * @param modelClass optional model class to filter by.
*/ */
@action @action
clearActiveModels(modelClass?: new (...args: any[]) => Model): void { clearActiveModels(modelClass?: new (...args: never[]) => Model): void {
if (modelClass) { if (modelClass) {
const modelsToRemove = this.getActiveModels(modelClass); const modelsToRemove = this.getActiveModels(modelClass);
modelsToRemove.forEach((model) => this.activeModels.delete(model.id)); modelsToRemove.forEach((model) => this.activeModels.delete(model.id));
@@ -247,7 +251,7 @@ class UiStore {
* @returns the most recently added model of the specified type. * @returns the most recently added model of the specified type.
*/ */
getPrimaryActiveModel<T extends Model>( getPrimaryActiveModel<T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
): T | undefined { ): T | undefined {
const models = this.getActiveModels<T>(modelClass); const models = this.getActiveModels<T>(modelClass);
return models[models.length - 1]; return models[models.length - 1];
+3
View File
@@ -44,6 +44,7 @@ export type PaginatedResponse<T> = T[] & {
}; };
}; };
// oxlint-disable-next-line no-explicit-any
export type FetchPageParams = PaginationParams & Record<string, any>; export type FetchPageParams = PaginationParams & Record<string, any>;
export default abstract class Store<T extends Model> { export default abstract class Store<T extends Model> {
@@ -59,6 +60,7 @@ export default abstract class Store<T extends Model> {
@observable @observable
isLoaded = false; isLoaded = false;
// oxlint-disable-next-line no-explicit-any
requests: Map<string, Promise<any>> = new Map(); requests: Map<string, Promise<any>> = new Map();
model: typeof Model; model: typeof Model;
@@ -430,6 +432,7 @@ export default abstract class Store<T extends Model> {
@action @action
fetchAll = async ( fetchAll = async (
// oxlint-disable-next-line no-explicit-any
params?: Record<string, any> params?: Record<string, any>
): Promise<PaginatedResponse<T>> => { ): Promise<PaginatedResponse<T>> => {
const limit = params?.limit ?? Pagination.defaultLimit; const limit = params?.limit ?? Pagination.defaultLimit;
+4 -4
View File
@@ -116,13 +116,13 @@ export type ActionContext = {
// New API - work directly with Model instances // New API - work directly with Model instances
getActiveModels: <T extends Model>( getActiveModels: <T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
) => T[]; ) => T[];
getActiveModel: <T extends Model>( getActiveModel: <T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
) => T | undefined; ) => T | undefined;
getActivePolicies: <T extends Model>( getActivePolicies: <T extends Model>(
modelClass: new (...args: any[]) => T modelClass: new (...args: never[]) => T
) => Policy[]; ) => Policy[];
isModelActive: (model: Model) => boolean; isModelActive: (model: Model) => boolean;
activeModels: ReadonlySet<Model>; activeModels: ReadonlySet<Model>;
@@ -160,7 +160,7 @@ export type Action = BaseAction & {
tooltip?: tooltip?:
| ((context: ActionContext) => React.ReactChild | undefined) | ((context: ActionContext) => React.ReactChild | undefined)
| React.ReactChild; | React.ReactChild;
perform: (context: ActionContext) => any; perform: (context: ActionContext) => unknown;
}; };
export type InternalLinkAction = BaseAction & { export type InternalLinkAction = BaseAction & {
+6 -2
View File
@@ -47,6 +47,7 @@ class ApiClient {
shareId?: string; shareId?: string;
/** Map of in-flight POST requests for deduplication, keyed by path + body. */ /** Map of in-flight POST requests for deduplication, keyed by path + body. */
// oxlint-disable-next-line no-explicit-any
private inflightRequests = new Map<string, Promise<any>>(); private inflightRequests = new Map<string, Promise<any>>();
constructor(options: Options = {}) { constructor(options: Options = {}) {
@@ -57,6 +58,7 @@ class ApiClient {
this.shareId = shareId; this.shareId = shareId;
}; };
// oxlint-disable-next-line no-explicit-any
fetch = async <T = any>( fetch = async <T = any>(
path: string, path: string,
method: string, method: string,
@@ -93,7 +95,7 @@ class ApiClient {
// toggling Content-Type to application/json // toggling Content-Type to application/json
if ( if (
typeof data === "object" && typeof data === "object" &&
(data || "").toString() === "[object Object]" Object.prototype.toString.call(data) === "[object Object]"
) { ) {
body = JSON.stringify(data); body = JSON.stringify(data);
} }
@@ -206,7 +208,7 @@ class ApiClient {
const error: { const error: {
message?: string; message?: string;
error?: string; error?: string;
data?: Record<string, any>; data?: Record<string, unknown>;
} = {}; } = {};
try { try {
@@ -277,12 +279,14 @@ class ApiClient {
throw err; throw err;
}; };
// oxlint-disable-next-line no-explicit-any
get = <T = any>( get = <T = any>(
path: string, path: string,
data: JSONObject | undefined, data: JSONObject | undefined,
options?: FetchOptions options?: FetchOptions
) => this.fetch<T>(path, "GET", data, options); ) => this.fetch<T>(path, "GET", data, options);
// oxlint-disable-next-line no-explicit-any
post = <T = any>( post = <T = any>(
path: string, path: string,
data?: JSONObject | FormData, data?: JSONObject | FormData,
+1
View File
@@ -13,6 +13,7 @@ type LogCategory =
| "plugins" | "plugins"
| "policies"; | "policies";
// oxlint-disable-next-line no-explicit-any
type Extra = Record<string, any>; type Extra = Record<string, any>;
class Logger { class Logger {
+2 -3
View File
@@ -41,9 +41,8 @@ export default function download(
} }
// go ahead and download dataURLs right away // go ahead and download dataURLs right away
if (String(x).match(/^data:[\w+-]+\/[\w+-]+[,;]/)) { if (typeof x === "string" && x.match(/^data:[\w+-]+\/[\w+-]+[,;]/)) {
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. return saver(x);
return saver(x); // everyone else can save dataURLs un-processed
} }
// end if dataURL passed? // end if dataURL passed?
+1
View File
@@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
// oxlint-disable no-explicit-any -- ComponentType<any> is the standard React pattern for generic component constraints
type ComponentPromise<T extends React.ComponentType<any>> = Promise<{ type ComponentPromise<T extends React.ComponentType<any>> = Promise<{
default: T; default: T;
}>; }>;
+2 -2
View File
@@ -60,8 +60,8 @@ class Logger {
winston.format.printf( winston.format.printf(
({ message, level, label, ...extra }) => ({ message, level, label, ...extra }) =>
`${level}: ${ `${level}: ${
label ? styleText("bold", `[${String(label)}] `) : "" label ? styleText("bold", `[${label as string}] `) : ""
}${String(message)} ${isEmpty(extra) ? "" : JSON.stringify(extra)}` }${message as string} ${isEmpty(extra) ? "" : JSON.stringify(extra)}`
) )
), ),
}) })
+3 -3
View File
@@ -371,13 +371,13 @@ export class ProsemirrorHelper extends SharedProsemirrorHelper {
function replaceAttachmentUrls(node: ProsemirrorData) { function replaceAttachmentUrls(node: ProsemirrorData) {
if (node.attrs?.src) { if (node.attrs?.src) {
node.attrs.src = getMapping(String(node.attrs.src)); node.attrs.src = getMapping(node.attrs.src as string);
} else if (node.attrs?.href) { } else if (node.attrs?.href) {
node.attrs.href = getMapping(String(node.attrs.href)); node.attrs.href = getMapping(node.attrs.href as string);
} else if (node.marks) { } else if (node.marks) {
node.marks.forEach((mark) => { node.marks.forEach((mark) => {
if (mark.attrs?.href) { if (mark.attrs?.href) {
mark.attrs.href = getMapping(String(mark.attrs.href)); mark.attrs.href = getMapping(mark.attrs.href as string);
} }
}); });
} }
+7 -2
View File
@@ -63,9 +63,14 @@ export function withAPIContext<T>(
* @param obj Object to convert to form-urlencoded string * @param obj Object to convert to form-urlencoded string
* @returns Form-urlencoded string representation of the object * @returns Form-urlencoded string representation of the object
*/ */
export function toFormData(obj: Record<string, any>): string { export function toFormData(
obj: Record<string, string | number | boolean | null | undefined>
): string {
return Object.entries(obj) return Object.entries(obj)
.filter(([_, value]) => value !== undefined) .filter(
(entry): entry is [string, string | number | boolean] =>
entry[1] !== undefined && entry[1] !== null
)
.map( .map(
([key, value]) => ([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}` `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
+2 -2
View File
@@ -356,7 +356,7 @@ export function documentTools(server: McpServer, scopes: string[]) {
}, },
{ {
type: "text" as const, type: "text" as const,
text: String(text ?? ""), text: typeof text === "string" ? text : "",
}, },
], ],
} satisfies CallToolResult; } satisfies CallToolResult;
@@ -606,7 +606,7 @@ export function documentTools(server: McpServer, scopes: string[]) {
}, },
{ {
type: "text" as const, type: "text" as const,
text: String(text ?? ""), text: typeof text === "string" ? text : "",
}, },
], ],
} satisfies CallToolResult; } satisfies CallToolResult;
+1 -1
View File
@@ -119,7 +119,7 @@ export function fetchTool(server: McpServer, scopes: string[]) {
}, },
{ {
type: "text" as const, type: "text" as const,
text: String(text ?? ""), text: typeof text === "string" ? text : "",
}, },
], ],
} satisfies CallToolResult; } satisfies CallToolResult;
+3 -3
View File
@@ -299,9 +299,9 @@ export class DocumentConverter {
// Replace the content-location with a data URI for each attachment. // Replace the content-location with a data URI for each attachment.
for (const attachment of parsed.attachments) { for (const attachment of parsed.attachments) {
const contentLocation = String( const contentLocation =
attachment.headers.get("content-location") ?? "" (attachment.headers.get("content-location") as string | undefined) ??
); "";
const id = contentLocation.split("/").pop(); const id = contentLocation.split("/").pop();
if (!id) { if (!id) {
-2
View File
@@ -13,6 +13,4 @@ export class MutexLock {
}), }),
}; };
} }
private static redlock: any;
} }
+7 -2
View File
@@ -195,10 +195,15 @@ export const MentionURL = (props: IssueUrlProps) => {
...attrs ...attrs
} = getAttributesFromNode(node); } = getAttributesFromNode(node);
const url = String(attrs.href); const url = typeof attrs.href === "string" ? attrs.href : undefined;
const unfurl = unfurls.get(url)?.data ?? unfurlAttr; const unfurl = url ? (unfurls.get(url)?.data ?? unfurlAttr) : undefined;
React.useEffect(() => { React.useEffect(() => {
if (!url) {
setLoaded(true);
return;
}
const fetchUnfurl = async () => { const fetchUnfurl = async () => {
try { try {
const unfurlModel = await unfurls.fetchUnfurl({ url }); const unfurlModel = await unfurls.fetchUnfurl({ url });
+3 -2
View File
@@ -142,7 +142,8 @@ export function setCellAttrs(node: Node): Attrs {
attrs["data-colwidth"] = node.attrs.colwidth.map(Number).join(","); attrs["data-colwidth"] = node.attrs.colwidth.map(Number).join(",");
} else { } else {
attrs.style = attrs.style =
(attrs.style ?? "") + `min-width: ${Number(node.attrs.colwidth[0])}px;`; ((attrs.style as string) ?? "") +
`min-width: ${Number(node.attrs.colwidth[0])}px;`;
} }
} }
if (Array.isArray(node.attrs.marks)) { if (Array.isArray(node.attrs.marks)) {
@@ -156,7 +157,7 @@ export function setCellAttrs(node: Node): Attrs {
const color = backgroundMark.attrs!.color as string; const color = backgroundMark.attrs!.color as string;
attrs["data-bgcolor"] = color; attrs["data-bgcolor"] = color;
attrs.style = attrs.style =
(attrs.style ?? "") + ((attrs.style as string) ?? "") +
`--cell-bg-color: ${color}; --cell-text-color: ${readableColor(color)};`; `--cell-bg-color: ${color}; --cell-text-color: ${readableColor(color)};`;
} }
} }
+1 -1
View File
@@ -2,5 +2,5 @@ import { MobXProviderContext } from "mobx-react";
import * as React from "react"; import * as React from "react";
export default function useStores() { export default function useStores() {
return React.useContext(MobXProviderContext); return React.useContext(MobXProviderContext).rootStore;
} }
+1 -1
View File
@@ -30,7 +30,7 @@ export const ellipsis = () => `
*/ */
export const s = export const s =
(key: keyof DefaultTheme) => (props: { theme: DefaultTheme }) => (key: keyof DefaultTheme) => (props: { theme: DefaultTheme }) =>
String(props.theme[key]); props.theme[key] as string;
/** /**
* Mixin to hide scrollbars. * Mixin to hide scrollbars.
+3 -3
View File
@@ -506,19 +506,19 @@ export class ProsemirrorHelper {
if ( if (
node.type === "image" && node.type === "image" &&
node.attrs?.src && node.attrs?.src &&
regex.test(String(node.attrs.src)) regex.test(node.attrs.src as string)
) { ) {
node.attrs.src = env.URL + node.attrs.src; node.attrs.src = env.URL + node.attrs.src;
} else if ( } else if (
node.type === "video" && node.type === "video" &&
node.attrs?.src && node.attrs?.src &&
regex.test(String(node.attrs.src)) regex.test(node.attrs.src as string)
) { ) {
node.attrs.src = env.URL + node.attrs.src; node.attrs.src = env.URL + node.attrs.src;
} else if ( } else if (
node.type === "attachment" && node.type === "attachment" &&
node.attrs?.href && node.attrs?.href &&
regex.test(String(node.attrs.href)) regex.test(node.attrs.href as string)
) { ) {
node.attrs.href = env.URL + node.attrs.href; node.attrs.href = env.URL + node.attrs.href;
} }
+4 -1
View File
@@ -40,7 +40,10 @@ export class CSVHelper {
return ""; return "";
} }
const stringValue = String(value); const stringValue =
typeof value === "object"
? JSON.stringify(value)
: String(value as string | number | boolean);
// If the value contains comma, quote, or newline, wrap it in quotes and escape internal quotes // If the value contains comma, quote, or newline, wrap it in quotes and escape internal quotes
if ( if (