Enable more oxlint rules (#34189)
* Fix type imports * Fix jsdoc * Fixup types * Fix stray awaits on non-thenables * Fixup imports * Fix splats * Fix this-context on callbacks * Memoise react contexts * Prefer find/flatMap * Make oxlint happier about our React keys * Avoid unsafe default function params * Fixup jsdoc * Fixup contexts * Switch from eslint to oxlint * Some oxlint-related tweaks * Iterate * Partial revert to defer some changes and shrink diff * Iterate * Add eslint-plugin-element-call and enable the copyright rule * Set strictStorePkgContentCheck * Iterate * Enable forwardRef oxlint rule * Enable no-unused-vars oxlint rule * Enable no-implied-eval oxlint rule * Enable no-duplicate-type-constituents oxlint rule * Enable explicit-length-check oxlint rule * Enable prefer-number-properties oxlint rule * Enable no-callback-in-promise oxlint rule * Enable no-require-imports oxlint rule * Remove disablement of most unicorn oxlint rules * Enable no-conditional-tests oxlint rule * Enable promise-valid-params oxlint rule * Enable require-unicode-regexp oxlint rule * Remove max-len comments as we use oxfmt for formatting * Enable majority of oxlint `suspicious` rules * Iterate * Fix oxlint type-aware lint running without dependencies built
This commit is contained in:
+1
@@ -27,6 +27,7 @@ import styles from "./GroupedVirtualizedList.module.css";
|
||||
* scrolled out of its render window, a tall section's header eventually unmounts and stops sticking;
|
||||
* the pinned overlay (rendered outside the list) backstops that gap.
|
||||
*/
|
||||
// oxlint-disable-next-line no-restricted-properties
|
||||
const StickyRowItem: Components["Item"] = React.forwardRef(function StickyRowItem(
|
||||
// `item` and `context` are Virtuoso-injected props, not DOM attributes — pull them out so they
|
||||
// aren't spread onto the div (`context` would otherwise render as `context="[object Object]"`).
|
||||
|
||||
@@ -52,7 +52,7 @@ function renderItemElement(
|
||||
const isFocused = context.tabIndexKey === itemKey;
|
||||
return (
|
||||
<div className="mx_item" data-testid={`row-${index}`} tabIndex={isFocused ? 0 : -1} role="gridcell">
|
||||
{item === SEPARATOR_ITEM ? "---" : (item as TestItem).name}
|
||||
{item === SEPARATOR_ITEM ? "---" : item.name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ function renderClickableItemElement(
|
||||
}}
|
||||
onFocus={(e) => onFocus(item, e)}
|
||||
>
|
||||
{item === SEPARATOR_ITEM ? "---" : (item as TestItem).name}
|
||||
{item === SEPARATOR_ITEM ? "---" : item.name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -448,7 +448,7 @@ describe.each<ListTestVariant>([flatVariant, groupedVariant])("$name", (variant)
|
||||
|
||||
mockIsItemFocusable.mockImplementation((item: TestItemWithSeparator) => {
|
||||
if (item === SEPARATOR_ITEM) return false;
|
||||
return (item as TestItem).isFocusable !== false;
|
||||
return item.isFocusable !== false;
|
||||
});
|
||||
|
||||
renderListWithHeight({ items: mixedItems });
|
||||
@@ -476,7 +476,7 @@ describe.each<ListTestVariant>([flatVariant, groupedVariant])("$name", (variant)
|
||||
|
||||
mockIsItemFocusable.mockImplementation((item: TestItemWithSeparator) => {
|
||||
if (item === SEPARATOR_ITEM) return false;
|
||||
return (item as TestItem).isFocusable !== false;
|
||||
return item.isFocusable !== false;
|
||||
});
|
||||
|
||||
renderListWithHeight({ items: mixedItems });
|
||||
@@ -709,7 +709,7 @@ describe.each<ListTestVariant>([flatVariant, groupedVariant])("$name", (variant)
|
||||
style={{ height: `${ITEM_HEIGHT}px`, display: "block", width: "100%" }}
|
||||
onFocus={(e) => onFocus(item, e)}
|
||||
>
|
||||
{item === SEPARATOR_ITEM ? "---" : (item as TestItem).name}
|
||||
{item === SEPARATOR_ITEM ? "---" : item.name}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -31,9 +31,9 @@ export class I18nApi implements II18nApi {
|
||||
const langs: Record<string, Record<string, string>> = {};
|
||||
|
||||
for (const key in translations) {
|
||||
for (const lang in translations[key as keyof Translations]) {
|
||||
for (const lang in translations[key]) {
|
||||
langs[lang] = langs[lang] || {};
|
||||
langs[lang][key] = translations[key as keyof Translations]![lang];
|
||||
langs[lang][key] = translations[key][lang];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -325,6 +325,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri
|
||||
|
||||
let replaced: SubstitutionValue;
|
||||
// If substitution is a function, call it
|
||||
// oxlint-disable-next-line unicorn/no-instanceof-builtins
|
||||
if (mapping[regexpString] instanceof Function) {
|
||||
replaced = ((mapping as Tags)[regexpString] as (...subs: string[]) => string)(...capturedGroups);
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import React, { type HTMLAttributes } from "react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { act, fireEvent, render } from "@test-utils";
|
||||
import { act, fireEvent, render, type RenderResult } from "@test-utils";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
@@ -38,7 +38,7 @@ const createButtonElement = (text: string): HTMLButtonElement => {
|
||||
const renderToolbar = (
|
||||
ui: React.ReactNode,
|
||||
props: Partial<React.ComponentProps<typeof RovingTabIndexProvider>> = {},
|
||||
): ReturnType<typeof render> => {
|
||||
): RenderResult => {
|
||||
return render(
|
||||
<RovingTabIndexProvider {...props}>
|
||||
{({ onKeyDownHandler }) => (
|
||||
@@ -583,7 +583,7 @@ describe("RovingTabIndex", () => {
|
||||
|
||||
act(() => container.querySelectorAll("button")[0].focus());
|
||||
const input = getByRole("textbox", { name: "Search input" });
|
||||
act(() => (input as HTMLElement).focus());
|
||||
act(() => input.focus());
|
||||
|
||||
fireEvent.keyDown(input, { key: "Tab" });
|
||||
expectTabIndexes(container.querySelectorAll("button"), [-1, 0]);
|
||||
|
||||
@@ -30,8 +30,11 @@ import React, {
|
||||
* @param el - The element being evaluated for native input behaviour.
|
||||
* @returns `true` when the element should keep its own arrow-key handling.
|
||||
*/
|
||||
export function checkInputableElement(el: HTMLElement): boolean {
|
||||
return el.matches('input:not([type="radio"]):not([type="checkbox"]), textarea, select, [contenteditable=true]');
|
||||
export function checkInputableElement(el: EventTarget): boolean {
|
||||
return (
|
||||
el instanceof Element &&
|
||||
el.matches('input:not([type="radio"]):not([type="checkbox"]), textarea, select, [contenteditable=true]')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,7 +501,7 @@ export const RovingTabIndexProvider: React.FC<RovingTabIndexProviderProps> = ({
|
||||
const action = getAction(ev);
|
||||
// Don't interfere with input default keydown behaviour
|
||||
// but allow people to move focus from it with Tab.
|
||||
const isInputTarget = !handleInputFields && checkInputableElement(ev.target as HTMLElement);
|
||||
const isInputTarget = !handleInputFields && checkInputableElement(ev.target);
|
||||
const { handled, focusNode } = isInputTarget
|
||||
? getInputNavigationResult(action, context.state.nodes, context.state.activeNode, ev.shiftKey)
|
||||
: getStandardNavigationResult(
|
||||
|
||||
@@ -121,7 +121,7 @@ describe("linkify-matrix", () => {
|
||||
});
|
||||
});
|
||||
it("properly parses " + char + "_foonetic_xkcd:matrix.org", () => {
|
||||
const test = "" + char + "_foonetic_xkcd:matrix.org";
|
||||
const test = char + "_foonetic_xkcd:matrix.org";
|
||||
expect(isLinkable(test)).toEqual(true);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -166,7 +166,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("accept " + char + "foo:bar.com", () => {
|
||||
const test = "" + char + "foo:bar.com";
|
||||
const test = char + "foo:bar.com";
|
||||
expect(isLinkable(test)).toEqual(true);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -181,7 +181,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("accept " + char + "foo:com (mostly for (TLD|DOMAIN)+ mixing)", () => {
|
||||
const test = "" + char + "foo:com";
|
||||
const test = char + "foo:com";
|
||||
expect(isLinkable(test)).toEqual(true);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -196,7 +196,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("accept repeated TLDs (e.g .org.uk)", () => {
|
||||
const test = "" + char + "foo:bar.org.uk";
|
||||
const test = char + "foo:bar.org.uk";
|
||||
expect(isLinkable(test)).toEqual(true);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -211,7 +211,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("accept hyphens in name " + char + "foo-bar:server.com", () => {
|
||||
const test = "" + char + "foo-bar:server.com";
|
||||
const test = char + "foo-bar:server.com";
|
||||
expect(isLinkable(test)).toEqual(true);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -226,7 +226,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("ignores trailing `:`", () => {
|
||||
const test = "" + char + "foo:bar.com:";
|
||||
const test = char + "foo:bar.com:";
|
||||
expect(isLinkable(test)).toEqual(false);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -241,7 +241,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("accept :NUM (port specifier)", () => {
|
||||
const test = "" + char + "foo:bar.com:2225";
|
||||
const test = char + "foo:bar.com:2225";
|
||||
expect(isLinkable(test)).toEqual(true);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -256,7 +256,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("ignores duplicate :NUM (double port specifier)", () => {
|
||||
const test = "" + char + "foo:bar.com:2225:1234";
|
||||
const test = char + "foo:bar.com:2225:1234";
|
||||
expect(isLinkable(test)).toEqual(false);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -271,7 +271,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("ignores all the trailing :", () => {
|
||||
const test = "" + char + "foo:bar.com::::";
|
||||
const test = char + "foo:bar.com::::";
|
||||
expect(isLinkable(test)).toEqual(false);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -286,7 +286,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("properly parses room alias with dots in name", () => {
|
||||
const test = "" + char + "foo.asdf:bar.com::::";
|
||||
const test = char + "foo.asdf:bar.com::::";
|
||||
expect(isLinkable(test)).toEqual(false);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -301,7 +301,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("does not parse room alias with too many separators", () => {
|
||||
const test = "" + char + "foo:::bar.com";
|
||||
const test = char + "foo:::bar.com";
|
||||
expect(isLinkable(test)).toEqual(false);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
@@ -316,7 +316,7 @@ describe("linkify-matrix", () => {
|
||||
]);
|
||||
});
|
||||
it("properly parses room alias with hyphen in domain part", () => {
|
||||
const test = "" + char + "foo:bar.com-baz.com";
|
||||
const test = char + "foo:bar.com-baz.com";
|
||||
expect(isLinkable(test)).toEqual(true);
|
||||
const found = findLinksInString(test);
|
||||
expect(found).toEqual([
|
||||
|
||||
@@ -14,7 +14,7 @@ import { type ViewModel } from "./ViewModel";
|
||||
* @param vm The view model to use
|
||||
* @returns The current snapshot
|
||||
*/
|
||||
export function useViewModel<T>(vm: ViewModel<T, unknown>): T {
|
||||
export function useViewModel<T>(vm: ViewModel<T>): T {
|
||||
// We need to pass the same getSnapshot function as getServerSnapshot as this
|
||||
// is used when making the HTML chat export.
|
||||
return useSyncExternalStore(vm.subscribe, vm.getSnapshot, vm.getSnapshot);
|
||||
|
||||
+3
-6
@@ -190,14 +190,14 @@ export const RoomListItemView = memo(function RoomListItemView({
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
internalRef.current?.focus({ preventScroll: true } as FocusOptions);
|
||||
internalRef.current?.focus({ preventScroll: true });
|
||||
}
|
||||
}, [isFocused]);
|
||||
|
||||
const onItemFocus = (e: React.FocusEvent<HTMLButtonElement>): void => {
|
||||
onFocus(item.id, e);
|
||||
// Only when focus enters the row from outside via the keyboard.
|
||||
if (!e.currentTarget.contains(e.relatedTarget as Node | null) && e.currentTarget.matches(":focus-visible")) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && e.currentTarget.matches(":focus-visible")) {
|
||||
setKeyboardActive(true);
|
||||
}
|
||||
};
|
||||
@@ -207,10 +207,7 @@ export const RoomListItemView = memo(function RoomListItemView({
|
||||
// (focus is then in the portaled popover, outside the row). The latter means that when the
|
||||
// menu closes with Escape, the trigger is still revealed, so the popover's own focus
|
||||
// restoration lands on it instead of dropping to <body>. Clear once focus leaves for good.
|
||||
if (
|
||||
!e.currentTarget.contains(e.relatedTarget as Node | null) &&
|
||||
!e.currentTarget.querySelector('[data-state="open"]')
|
||||
) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && !e.currentTarget.querySelector('[data-state="open"]')) {
|
||||
setKeyboardActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
+2
-5
@@ -174,7 +174,7 @@ export const RoomListSectionHeaderView = memo(function RoomListSectionHeaderView
|
||||
|
||||
const onHeaderFocus = (e: React.FocusEvent<HTMLButtonElement>): void => {
|
||||
onFocus(id, e);
|
||||
if (!e.currentTarget.contains(e.relatedTarget as Node | null) && e.currentTarget.matches(":focus-visible")) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && e.currentTarget.matches(":focus-visible")) {
|
||||
setKeyboardActive(true);
|
||||
}
|
||||
};
|
||||
@@ -183,10 +183,7 @@ export const RoomListSectionHeaderView = memo(function RoomListSectionHeaderView
|
||||
// Keep it revealed while focus is on the menu button, and while the menu is open (focus is
|
||||
// then in the portaled popover, outside the header). That way closing with Escape restores
|
||||
// focus to the still-revealed trigger instead of dropping to <body>. Clear once focus leaves.
|
||||
if (
|
||||
!e.currentTarget.contains(e.relatedTarget as Node | null) &&
|
||||
!e.currentTarget.querySelector('[data-state="open"]')
|
||||
) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && !e.currentTarget.querySelector('[data-state="open"]')) {
|
||||
setKeyboardActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface WidgetPipViewActions {
|
||||
* The view model will handle navigating back to the associated room.
|
||||
* @param ev The mouse event that triggered the back click.
|
||||
*/
|
||||
onBackClick: (ev: React.MouseEvent<Element, MouseEvent>) => void;
|
||||
onBackClick: (ev: React.MouseEvent) => void;
|
||||
/**
|
||||
* The view model exposes the `<PersistentApp />` component via this action.
|
||||
* `PersistentApp` is not available in shared components.
|
||||
@@ -34,7 +34,7 @@ export interface WidgetPipViewActions {
|
||||
* Action that needs to be called when the pip view starts to get dragged.
|
||||
* @param ev The mouse event that triggered the drag start.
|
||||
*/
|
||||
onStartMoving: (ev: React.MouseEvent<Element, MouseEvent>) => void;
|
||||
onStartMoving: (ev: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
export interface WidgetPipViewSnapshot {
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ export function RoomAvatarEventView({
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classes} ref={ref as Ref<HTMLSpanElement>} {...eventPresentationAttributes}>
|
||||
<span className={classes} ref={ref} {...eventPresentationAttributes}>
|
||||
{_t(
|
||||
"timeline|m.room.avatar|changed_img",
|
||||
{ senderDisplayName: snapshot.senderDisplayName },
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
import React, { type JSX, type Ref, type HTMLAttributes } from "react";
|
||||
import { IconButton, Text } from "@vector-im/compound-web";
|
||||
import { CloseIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
@@ -16,13 +16,13 @@ import styles from "./StatusPillView.module.css";
|
||||
* Displays a user's status message in a pill format with a button that can be used
|
||||
* to clear the status.
|
||||
*/
|
||||
export const StatusPillView = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
export const StatusPillView: React.FC<
|
||||
{
|
||||
status: UserStatus;
|
||||
clearStatus: () => void;
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
>(function StatusPillView({ status, clearStatus, ...props }, ref): JSX.Element {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
} & HTMLAttributes<HTMLDivElement>
|
||||
> = ({ status, clearStatus, ref, ...props }): JSX.Element => {
|
||||
return (
|
||||
<div ref={ref} {...props} className={styles.statusPill}>
|
||||
<Text as="span" className={styles.menuStatusEmoji}>
|
||||
@@ -41,4 +41,4 @@ export const StatusPillView = React.forwardRef<
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
import React, { type JSX, type FC } from "react";
|
||||
import { Text } from "@vector-im/compound-web";
|
||||
|
||||
import { type UserStatus } from "..";
|
||||
@@ -14,12 +14,12 @@ import styles from "./StatusTextView.module.css";
|
||||
/**
|
||||
* Displays a user's status message and emoji in simple text format
|
||||
*/
|
||||
export const StatusTextView = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
export const StatusTextView: FC<
|
||||
{
|
||||
status: UserStatus;
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
>(function StatusTextView({ status, ...props }, ref): JSX.Element {
|
||||
> = function StatusTextView({ status, ref, ...props }): JSX.Element {
|
||||
return (
|
||||
<div ref={ref} {...props} className={styles.statusText}>
|
||||
<Text as="span" className={styles.menuStatusEmoji}>
|
||||
@@ -30,4 +30,4 @@ export const StatusTextView = React.forwardRef<
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -61,7 +61,7 @@ const customRender = (ui: ReactElement, options: SharedRenderOptions = {}): Retu
|
||||
return render(ui, {
|
||||
...renderOptions,
|
||||
wrapper: wrapWithTooltipProvider(wrapper, presentation) as RenderOptions["wrapper"],
|
||||
}) as ReturnType<typeof render>;
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
|
||||
Reference in New Issue
Block a user