chore: promote no-explicit-any from warn to error (#12244)

* chore: promote no-explicit-any from warn to error and resolve violations

Upgrades the oxlint rule severity and removes all 40 existing
`no-explicit-any` warnings across the codebase. Most call sites gained
proper types (SharedEditor refs, JSONNode/JSONMark for ProseMirror JSON
walking, DocumentsStore, dd-trace `Span` parameter inference, prosemirror
Fragment public API in place of internal `(fragment as any).content`).
A few load-bearing `any` uses were preserved with scoped disable
comments where changing the type would cascade widely (Sequelize JSONB
columns on `Event`, the `withTracing` higher-order function generic,
`Extension.options` consumed by many subclasses, dd-trace's `req`
patching).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Tom Moor
2026-05-02 12:14:23 -04:00
committed by GitHub
parent f270611505
commit fca10221b9
24 changed files with 107 additions and 80 deletions
+1 -1
View File
@@ -31,7 +31,7 @@
"no-empty-pattern": "error", "no-empty-pattern": "error",
"no-empty-static-block": "error", "no-empty-static-block": "error",
"no-ex-assign": "error", "no-ex-assign": "error",
"no-explicit-any": "warn", "no-explicit-any": "error",
"no-extra-boolean-cast": "error", "no-extra-boolean-cast": "error",
"no-fallthrough": "error", "no-fallthrough": "error",
"no-func-assign": "error", "no-func-assign": "error",
+4 -2
View File
@@ -119,10 +119,12 @@ export default class ComponentView {
// Apply classes from inline decorations. // Apply classes from inline decorations.
this.decorations.forEach((decoration) => { this.decorations.forEach((decoration) => {
// For inline decorations, attrs contain the class property. // For inline decorations, attrs contain the class property.
const attrs = (decoration as any).type?.attrs; const attrs = (
decoration as Decoration & { type?: { attrs?: { class?: string } } }
).type?.attrs;
if (attrs?.class) { if (attrs?.class) {
const classes = attrs.class.split(" "); const classes = attrs.class.split(" ");
classes.forEach((className: string) => { classes.forEach((className) => {
if (className && this.dom) { if (className && this.dom) {
this.dom.classList.add(className); this.dom.classList.add(className);
} }
+3 -3
View File
@@ -345,7 +345,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
} }
}; };
const triggerFilePick = (accept: string, attrs?: Record<string, any>) => { const triggerFilePick = (accept: string, attrs?: Record<string, unknown>) => {
if (inputRef.current) { if (inputRef.current) {
if (accept) { if (accept) {
inputRef.current.accept = accept; inputRef.current.accept = accept;
@@ -887,7 +887,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
onPointerMove={handlePointerMove} onPointerMove={handlePointerMove}
onPointerDown={handlePointerDown} onPointerDown={handlePointerDown}
> >
{props.renderMenuItem(item as any, index, { {props.renderMenuItem(item as unknown as T, index, {
selected: index === selectedIndex, selected: index === selectedIndex,
disclosure: hasChildren, disclosure: hasChildren,
onClick: handleOnClick, onClick: handleOnClick,
@@ -1053,7 +1053,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
key={`sub-${childIndex}-${child.name}`} key={`sub-${childIndex}-${child.name}`}
onPointerMove={handleChildPointerMove} onPointerMove={handleChildPointerMove}
> >
{props.renderMenuItem(child as any, childIndex, { {props.renderMenuItem(child as unknown as T, childIndex, {
selected: childIndex === submenu.selectedIndex, selected: childIndex === submenu.selectedIndex,
onClick: handleChildClick, onClick: handleChildClick,
})} })}
+5 -6
View File
@@ -1,4 +1,3 @@
import some from "lodash/some";
import { action, observable } from "mobx"; import { action, observable } from "mobx";
import type { EditorState, Selection } from "prosemirror-state"; import type { EditorState, Selection } from "prosemirror-state";
import { NodeSelection, Plugin, TextSelection } from "prosemirror-state"; import { NodeSelection, Plugin, TextSelection } from "prosemirror-state";
@@ -82,12 +81,12 @@ export default class SelectionToolbarExtension extends Extension {
return false; return false;
} }
const slice = selection.content(); const fragment = selection.content().content;
const fragment = slice.content;
const nodes = (fragment as any).content;
if (some(nodes, (n) => n.content.size)) { for (let i = 0; i < fragment.childCount; i++) {
return selection; if (fragment.child(i).content.size) {
return selection;
}
} }
return false; return false;
+3 -3
View File
@@ -28,9 +28,9 @@ export default class UpArrowAtStart extends Extension {
const isAtDocStart = $pos.parentOffset === 0 && $pos.depth <= 1; const isAtDocStart = $pos.parentOffset === 0 && $pos.depth <= 1;
if (isAtDocStart) { if (isAtDocStart) {
// Call the onUpArrowAtStart callback if it exists const props = this.editor.props as {
// Cast to any to access the custom prop since it's not in the base Props type onUpArrowAtStart?: () => void;
const props = this.editor.props as any; };
if (props.onUpArrowAtStart) { if (props.onUpArrowAtStart) {
props.onUpArrowAtStart(); props.onUpArrowAtStart();
return true; return true;
+12 -7
View File
@@ -38,7 +38,11 @@ import { basicExtensions as extensions } from "@shared/editor/nodes";
import type Node from "@shared/editor/nodes/Node"; import type Node from "@shared/editor/nodes/Node";
import type ReactNode from "@shared/editor/nodes/ReactNode"; import type ReactNode from "@shared/editor/nodes/ReactNode";
import type { ComponentProps } from "@shared/editor/types"; import type { ComponentProps } from "@shared/editor/types";
import type { ProsemirrorData, UserPreferences } from "@shared/types"; import type {
ProsemirrorData,
ProsemirrorMark,
UserPreferences,
} from "@shared/types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper"; import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import EventEmitter from "@shared/utils/events"; import EventEmitter from "@shared/utils/events";
import type Document from "~/models/Document"; import type Document from "~/models/Document";
@@ -117,7 +121,8 @@ export type Props = {
/** Callback when user uses cancel key combo */ /** Callback when user uses cancel key combo */
onCancel?: () => void; onCancel?: () => void;
/** Callback when user changes editor content */ /** Callback when user changes editor content */
onChange?: (value: () => any) => void; // oxlint-disable-next-line @typescript-eslint/no-explicit-any
onChange?: (value: (asString?: boolean, trim?: boolean) => any) => void;
/** Callback when a comment mark is clicked */ /** Callback when a comment mark is clicked */
onClickCommentMark?: (commentId: string) => void; onClickCommentMark?: (commentId: string) => void;
/** /**
@@ -755,9 +760,9 @@ export class Editor extends React.PureComponent<
} }
if (isArray(node.attrs?.marks)) { if (isArray(node.attrs?.marks)) {
const existingMarks = node.attrs.marks; const existingMarks = node.attrs.marks as ProsemirrorMark[];
const updatedMarks = existingMarks.filter( const updatedMarks = existingMarks.filter(
(mark: any) => mark.attrs.id !== commentId (mark) => mark.attrs?.id !== commentId
); );
const attrs = { const attrs = {
...node.attrs, ...node.attrs,
@@ -800,9 +805,9 @@ export class Editor extends React.PureComponent<
} }
if (isArray(node.attrs?.marks)) { if (isArray(node.attrs?.marks)) {
const existingMarks = node.attrs.marks; const existingMarks = node.attrs.marks as ProsemirrorMark[];
const updatedMarks = existingMarks.map((mark: any) => const updatedMarks = existingMarks.map((mark) =>
mark.type === "comment" && mark.attrs.id === commentId mark.type === "comment" && mark.attrs?.id === commentId
? { ...mark, attrs: { ...mark.attrs, ...attrs } } ? { ...mark, attrs: { ...mark.attrs, ...attrs } }
: mark : mark
); );
+8 -1
View File
@@ -14,6 +14,7 @@ import styled from "styled-components";
import { s } from "@shared/styles"; import { s } from "@shared/styles";
import { StatusFilter } from "@shared/types"; import { StatusFilter } from "@shared/types";
import type Collection from "~/models/Collection"; import type Collection from "~/models/Collection";
import type DocumentsStore from "~/stores/DocumentsStore";
import CenteredContent from "~/components/CenteredContent"; import CenteredContent from "~/components/CenteredContent";
import { CollectionBreadcrumb } from "~/components/CollectionBreadcrumb"; import { CollectionBreadcrumb } from "~/components/CollectionBreadcrumb";
import Heading from "~/components/Heading"; import Heading from "~/components/Heading";
@@ -362,7 +363,13 @@ const Content = styled.div`
`; `;
const RecentDocuments = observer( const RecentDocuments = observer(
({ collection, documents }: { collection: Collection; documents: any }) => { ({
collection,
documents,
}: {
collection: Collection;
documents: DocumentsStore;
}) => {
useEffect(() => { useEffect(() => {
void collection.fetchDocuments(); void collection.fetchDocuments();
}, [collection]); }, [collection]);
+10 -8
View File
@@ -15,6 +15,7 @@ import type { RefHandle } from "~/components/ContentEditable";
import { useDocumentContext } from "~/components/DocumentContext"; import { useDocumentContext } from "~/components/DocumentContext";
import type { Props as EditorProps } from "~/components/Editor"; import type { Props as EditorProps } from "~/components/Editor";
import Editor from "~/components/Editor"; import Editor from "~/components/Editor";
import type { Editor as SharedEditor } from "~/editor";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import Time from "~/components/Time"; import Time from "~/components/Time";
import { withUIExtensions } from "~/editor/extensions"; import { withUIExtensions } from "~/editor/extensions";
@@ -59,7 +60,8 @@ type Props = Omit<EditorProps, "editorStyle"> & {
* The main document editor includes an editable title with metadata below it, * The main document editor includes an editable title with metadata below it,
* and support for commenting. * and support for commenting.
*/ */
function DocumentEditor(props: Props, ref: React.RefObject<any>) { function DocumentEditor(props: Props, ref: React.ForwardedRef<SharedEditor>) {
const editorRef = React.useRef<SharedEditor>(null);
const titleRef = React.useRef<RefHandle>(null); const titleRef = React.useRef<RefHandle>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const match = useRouteMatch(); const match = useRouteMatch();
@@ -87,10 +89,10 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
const iconColor = document.color ?? (first(colorPalette) as string); const iconColor = document.color ?? (first(colorPalette) as string);
const childRef = React.useRef<HTMLDivElement>(null); const childRef = React.useRef<HTMLDivElement>(null);
const focusAtStart = React.useCallback(() => { const focusAtStart = React.useCallback(() => {
if (ref.current) { if (editorRef.current) {
ref.current.focusAtStart(); editorRef.current.focusAtStart();
} }
}, [ref]); }, []);
React.useEffect(() => { React.useEffect(() => {
if (focusedComment && focusedComment.documentId === document.id) { if (focusedComment && focusedComment.documentId === document.id) {
@@ -113,15 +115,15 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
const handleGoToNextInput = React.useCallback( const handleGoToNextInput = React.useCallback(
(insertParagraph: boolean) => { (insertParagraph: boolean) => {
if (insertParagraph && ref.current) { if (insertParagraph && editorRef.current) {
const { view } = ref.current; const { view } = editorRef.current;
const { dispatch, state } = view; const { dispatch, state } = view;
dispatch(state.tr.insert(0, state.schema.nodes.paragraph.create())); dispatch(state.tr.insert(0, state.schema.nodes.paragraph.create()));
} }
focusAtStart(); focusAtStart();
}, },
[focusAtStart, ref] [focusAtStart]
); );
// Create a Comment model in local store when a comment mark is created, this // Create a Comment model in local store when a comment mark is created, this
@@ -231,7 +233,7 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
/> />
) : null} ) : null}
<EditorComponent <EditorComponent
ref={mergeRefs([ref, handleRefChanged])} ref={mergeRefs([ref, editorRef, handleRefChanged])}
lang={getLangFor(document.language)} lang={getLangFor(document.language)}
autoFocus={!!document.title && !props.defaultValue} autoFocus={!!document.title && !props.defaultValue}
placeholder={t("Type '/' to insert, or start writing…")} placeholder={t("Type '/' to insert, or start writing…")}
@@ -13,8 +13,8 @@ type Item = Revision | Event<Document>;
type Props = { type Props = {
items: Item[]; items: Item[];
document: Document; document: Document;
fetch: (options: Record<string, any> | undefined) => Promise<Item[]>; fetch: (options: Record<string, unknown> | undefined) => Promise<Item[]>;
options?: Record<string, any>; options?: Record<string, unknown>;
heading?: React.ReactNode; heading?: React.ReactNode;
empty?: JSX.Element; empty?: JSX.Element;
}; };
@@ -7,6 +7,7 @@ import {
useEffect, useEffect,
forwardRef, forwardRef,
useRef, useRef,
type ForwardedRef,
} from "react"; } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
@@ -18,6 +19,7 @@ import EDITOR_VERSION from "@shared/editor/version";
import { supportsPassiveListener } from "@shared/utils/browser"; import { supportsPassiveListener } from "@shared/utils/browser";
import type { Props as EditorProps } from "~/components/Editor"; import type { Props as EditorProps } from "~/components/Editor";
import Editor from "~/components/Editor"; import Editor from "~/components/Editor";
import type { Editor as SharedEditor } from "~/editor";
import MultiplayerExtension from "~/editor/extensions/Multiplayer"; import MultiplayerExtension from "~/editor/extensions/Multiplayer";
import env from "~/env"; import env from "~/env";
import useCurrentUser from "~/hooks/useCurrentUser"; import useCurrentUser from "~/hooks/useCurrentUser";
@@ -50,7 +52,10 @@ type MessageEvent = {
}; };
}; };
function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) { function MultiplayerEditor(
{ onSynced, ...props }: Props,
ref: ForwardedRef<SharedEditor>
) {
const documentId = props.id; const documentId = props.id;
const history = useHistory(); const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -352,4 +357,4 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
); );
} }
export default forwardRef<typeof MultiplayerEditor, Props>(MultiplayerEditor); export default forwardRef<SharedEditor, Props>(MultiplayerEditor);
+10 -24
View File
@@ -1,6 +1,7 @@
import data from "@emoji-mart/data"; import data from "@emoji-mart/data";
import type { EmojiMartData } from "@emoji-mart/data"; import type { EmojiMartData } from "@emoji-mart/data";
import { Schema } from "prosemirror-model"; import { Schema } from "prosemirror-model";
import type { Editor } from "~/editor";
import ExtensionManager from "@shared/editor/lib/ExtensionManager"; import ExtensionManager from "@shared/editor/lib/ExtensionManager";
import { populateEmojiData } from "@shared/editor/lib/emoji"; import { populateEmojiData } from "@shared/editor/lib/emoji";
import { import {
@@ -12,6 +13,12 @@ import Mention from "@shared/editor/nodes/Mention";
populateEmojiData(data as EmojiMartData); populateEmojiData(data as EmojiMartData);
// Server-side parsing/serializing only requires schema and a few static props,
// but the Extension API expects a full Editor. This stub satisfies bindEditor
// without instantiating the React component.
const stubEditor = (s: Schema): Editor =>
({ schema: s, props: { theme: { isDark: false } } }) as unknown as Editor;
const extensions = withComments(richExtensions); const extensions = withComments(richExtensions);
export const extensionManager = new ExtensionManager(extensions); export const extensionManager = new ExtensionManager(extensions);
@@ -21,14 +28,7 @@ export const schema = new Schema({
}); });
for (const extension of extensionManager.extensions) { for (const extension of extensionManager.extensions) {
extension.bindEditor({ extension.bindEditor(stubEditor(schema));
schema,
props: {
theme: {
isDark: false,
},
},
} as any);
} }
export const parser = extensionManager.parser({ export const parser = extensionManager.parser({
@@ -48,14 +48,7 @@ export const basicSchema = new Schema({
}); });
for (const extension of basicExtensionManager.extensions) { for (const extension of basicExtensionManager.extensions) {
extension.bindEditor({ extension.bindEditor(stubEditor(basicSchema));
schema: basicSchema,
props: {
theme: {
isDark: false,
},
},
} as any);
} }
export const basicParser = basicExtensionManager.parser({ export const basicParser = basicExtensionManager.parser({
@@ -72,14 +65,7 @@ export const commentSchema = new Schema({
}); });
for (const extension of commentExtensionManager.extensions) { for (const extension of commentExtensionManager.extensions) {
extension.bindEditor({ extension.bindEditor(stubEditor(commentSchema));
schema: commentSchema,
props: {
theme: {
isDark: false,
},
},
} as any);
} }
export const commentParser = commentExtensionManager.parser({ export const commentParser = commentExtensionManager.parser({
+1 -2
View File
@@ -194,12 +194,11 @@ class Logger {
// Errors have non-enumerable message/stack which are dropped by spreads // Errors have non-enumerable message/stack which are dropped by spreads
// and JSON serialization, so convert them to a plain object up-front. // and JSON serialization, so convert them to a plain object up-front.
if (input instanceof Error) { if (input instanceof Error) {
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
return { return {
name: input.name, name: input.name,
message: input.message, message: input.message,
stack: input.stack, stack: input.stack,
} as any as T; } as unknown as T;
} }
// Short circuit if we're not in production to enable easier debugging // Short circuit if we're not in production to enable easier debugging
+5 -1
View File
@@ -14,6 +14,7 @@ function isExplicitlyNonReportable(error: Error): error is ReportableError {
} }
type PrivateDatadogContext = { type PrivateDatadogContext = {
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
req: Record<string, any> & { req: Record<string, any> & {
_datadog?: { _datadog?: {
span?: Span; span?: Span;
@@ -41,7 +42,10 @@ const getCurrentSpan = (): Span | null => tracer.scope().active();
* @param tags An object with the tags to add to the span * @param tags An object with the tags to add to the span
* @param span An optional span object to add the tags to. If none provided,the current span will be used. * @param span An optional span object to add the tags to. If none provided,the current span will be used.
*/ */
export function addTags(tags: Record<string, any>, span?: Span | null): void { export function addTags(
tags: Parameters<Span["addTags"]>[0],
span?: Span | null
): void {
if (tracer) { if (tracer) {
const currentSpan = span || getCurrentSpan(); const currentSpan = span || getCurrentSpan();
+2
View File
@@ -60,6 +60,7 @@ class Event extends IdModel<
* Note that the `data` column will be visible to the client and API requests. * Note that the `data` column will be visible to the client and API requests.
*/ */
@Column(DataType.JSONB) @Column(DataType.JSONB)
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
data: Record<string, any> | null; data: Record<string, any> | null;
/** /**
@@ -67,6 +68,7 @@ class Event extends IdModel<
* used for arbitrary data associated with the event. * used for arbitrary data associated with the event.
*/ */
@Column(DataType.JSONB) @Column(DataType.JSONB)
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
changes: Record<string, any> | null; changes: Record<string, any> | null;
// hooks // hooks
+4 -4
View File
@@ -757,7 +757,7 @@ export class ProsemirrorHelper extends SharedProsemirrorHelper {
// Create a new document with the emoji removed from the text // Create a new document with the emoji removed from the text
const json = doc.toJSON(); const json = doc.toJSON();
function removeEmojiFromNode(node: any): any { function removeEmojiFromNode(node: ProsemirrorData): ProsemirrorData {
if (node.type === "text" && node.text && node.text.startsWith(emoji)) { if (node.type === "text" && node.text && node.text.startsWith(emoji)) {
return { return {
...node, ...node,
@@ -768,7 +768,7 @@ export class ProsemirrorHelper extends SharedProsemirrorHelper {
let found = false; let found = false;
return { return {
...node, ...node,
content: node.content.map((child: any) => { content: node.content.map((child) => {
if (found) { if (found) {
return child; return child;
} }
@@ -783,7 +783,7 @@ export class ProsemirrorHelper extends SharedProsemirrorHelper {
return node; return node;
} }
const modifiedJson = removeEmojiFromNode(json); const modifiedJson = removeEmojiFromNode(json as ProsemirrorData);
return { return {
emoji, emoji,
doc: Node.fromJSON(schema, modifiedJson), doc: Node.fromJSON(schema, modifiedJson),
@@ -798,7 +798,7 @@ export class ProsemirrorHelper extends SharedProsemirrorHelper {
* @returns A cleanup function to restore the global environment. * @returns A cleanup function to restore the global environment.
*/ */
public static patchGlobalEnv(domWindow: JSDOM["window"]) { public static patchGlobalEnv(domWindow: JSDOM["window"]) {
const g = global as any; const g = global as unknown as Record<string, unknown>;
const globalParams = { const globalParams = {
window: g.window, window: g.window,
+1 -1
View File
@@ -3,7 +3,7 @@ import { BaseTask } from "./base/BaseTask";
type Props = { type Props = {
templateName: string; templateName: string;
props: Record<string, any>; props: Record<string, unknown>;
}; };
export default class EmailTask extends BaseTask<Props> { export default class EmailTask extends BaseTask<Props> {
+1 -1
View File
@@ -56,7 +56,7 @@ export default function init(
if (ioHandleUpgrade) { if (ioHandleUpgrade) {
server.removeListener( server.removeListener(
"upgrade", "upgrade",
ioHandleUpgrade as (...args: any[]) => void ioHandleUpgrade as (...args: unknown[]) => void
); );
} }
+7 -5
View File
@@ -237,7 +237,8 @@ export function monkeyPatchSequelizeErrorsForJest(instance: Sequelize) {
return instance; return instance;
} }
const sequelizeVersion = (Sequelize as any).version; const sequelizeVersion = (Sequelize as unknown as { version: string })
.version;
const major = sequelizeVersion.split(".").map(Number)[0]; const major = sequelizeVersion.split(".").map(Number)[0];
if (major >= 7) { if (major >= 7) {
@@ -250,12 +251,13 @@ export function monkeyPatchSequelizeErrorsForJest(instance: Sequelize) {
} }
const origQueryFunc = instance.query.bind(instance); const origQueryFunc = instance.query.bind(instance);
instance.query = (async (...args: any[]) => { instance.query = (async (...args: Parameters<typeof origQueryFunc>) => {
try { try {
return await origQueryFunc(...(args as Parameters<typeof origQueryFunc>)); return await origQueryFunc(...args);
} catch (err: any) { } catch (err) {
// Ensure error appears in Jest output, not swallowed by Sequelize internals // Ensure error appears in Jest output, not swallowed by Sequelize internals
Logger.error(err.message, err.parent); const error = err as Error & { parent?: Error };
Logger.error(error.message, error.parent ?? error);
throw err; throw err;
} }
}) as typeof instance.query; }) as typeof instance.query;
+2
View File
@@ -86,6 +86,7 @@ export function error(err: unknown): CallToolResult {
* @param handler - the handler function to wrap. * @param handler - the handler function to wrap.
* @returns the wrapped handler with tracing enabled. * @returns the wrapped handler with tracing enabled.
*/ */
/* oxlint-disable @typescript-eslint/no-explicit-any */
export function withTracing<F extends (...args: any[]) => any>( export function withTracing<F extends (...args: any[]) => any>(
toolName: string, toolName: string,
handler: F handler: F
@@ -107,6 +108,7 @@ export function withTracing<F extends (...args: any[]) => any>(
return handler.apply(this, args); return handler.apply(this, args);
} as F); } as F);
} }
/* oxlint-enable @typescript-eslint/no-explicit-any */
/** /**
* Builds a map from document ID to its zero-based index among siblings, * Builds a map from document ID to its zero-based index among siblings,
+5 -2
View File
@@ -23,11 +23,14 @@ export async function getVersionInfo(currentVersion: string): Promise<{
// Continue fetching pages until the required versions are found or no more pages // Continue fetching pages until the required versions are found or no more pages
while (nextUrl) { while (nextUrl) {
const response = await fetch(nextUrl); const response = await fetch(nextUrl);
const data = await response.json(); const data = (await response.json()) as {
results: { name: string }[];
next?: string | null;
};
// Map and filter the versions to keep only full releases // Map and filter the versions to keep only full releases
const pageVersions = data.results const pageVersions = data.results
.map((result: any) => result.name) .map((result) => result.name)
.filter(isFullReleaseVersion); .filter(isFullReleaseVersion);
allVersions = allVersions.concat(pageVersions); allVersions = allVersions.concat(pageVersions);
+5 -1
View File
@@ -55,7 +55,11 @@ export function assertKeysIn(
Object.keys(obj).forEach((key) => assertIn(key, Object.values(type))); Object.keys(obj).forEach((key) => assertIn(key, Object.values(type)));
} }
export const assertSort = (value: string, model: any, message?: string) => { export const assertSort = (
value: string,
model: { rawAttributes: Record<string, unknown> },
message?: string
) => {
if (!Object.keys(model.rawAttributes).includes(value)) { if (!Object.keys(model.rawAttributes).includes(value)) {
throw ValidationError( throw ValidationError(
message ?? `${String(value)} is not a valid sort field` message ?? `${String(value)} is not a valid sort field`
+2
View File
@@ -13,9 +13,11 @@ export type WidgetProps = {
}; };
export default class Extension { export default class Extension {
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
options: any; options: any;
editor: Editor; editor: Editor;
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
constructor(options: Record<string, any> = {}) { constructor(options: Record<string, any> = {}) {
this.options = { this.options = {
...this.defaultOptions, ...this.defaultOptions,
+1
View File
@@ -1,3 +1,4 @@
const env = typeof window === "undefined" ? process.env : window.env; const env = typeof window === "undefined" ? process.env : window.env;
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
export default env as Record<string, any>; export default env as Record<string, any>;
+6 -4
View File
@@ -688,15 +688,17 @@ export type JSONValue =
export type JSONObject = { [x: string]: JSONValue }; export type JSONObject = { [x: string]: JSONValue };
export type ProsemirrorMark = {
type: string;
attrs?: JSONObject;
};
export type ProsemirrorData = { export type ProsemirrorData = {
type: string; type: string;
content?: ProsemirrorData[]; content?: ProsemirrorData[];
text?: string; text?: string;
attrs?: JSONObject; attrs?: JSONObject;
marks?: { marks?: ProsemirrorMark[];
type: string;
attrs?: JSONObject;
}[];
}; };
export type ProsemirrorDoc = { export type ProsemirrorDoc = {