mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
chore: resolve lint warnings (no-explicit-any, no-redundant-type-constituents, no-base-to-string) (#12209)
* chore: resolve no-redundant-type-constituents and test/mock no-explicit-any warnings Clears 36 lint warnings: all 5 no-redundant-type-constituents, 6 no-misused-spread (via narrowing getPartitionWhereClause's return type to WhereAttributeHash), and 25 no-explicit-any in test/mock files. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: resolve no-base-to-string warnings in tests Convert userProvisioner try/catch error assertions to Jest's .rejects.toThrow() idiom, and cast webhook test body to string. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: resolve no-explicit-any warnings in cancan and tracing Tighten types in the cancan policy framework and tracing decorators. Constructor / generic-function upper bounds keep `any` where TypeScript variance requires it, scoped to single-line oxlint-disable comments. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -59,8 +59,7 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
||||
const { presence, auth, ui } = useStores();
|
||||
const [editorVersionBehind, setEditorVersionBehind] = useState(false);
|
||||
const [showCursorNames, setShowCursorNames] = useState(false);
|
||||
const [remoteProvider, setRemoteProvider] =
|
||||
useState<HocuspocusProvider | null>(null);
|
||||
const [remoteProvider, setRemoteProvider] = useState<HocuspocusProvider>();
|
||||
const [hasLocalPersistence, setHasLocalPersistence] = useState(true);
|
||||
const [isLocalSynced, setLocalSynced] = useState(false);
|
||||
const [isRemoteSynced, setRemoteSynced] = useState(false);
|
||||
@@ -223,7 +222,7 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
|
||||
window.removeEventListener("scroll", syncScrollPosition);
|
||||
provider?.destroy();
|
||||
void localProvider?.destroy();
|
||||
setRemoteProvider(null);
|
||||
setRemoteProvider(undefined);
|
||||
ui.setMultiplayerStatus(undefined, undefined);
|
||||
};
|
||||
}, [
|
||||
|
||||
@@ -44,9 +44,7 @@ describe("DeliverWebhookTask", () => {
|
||||
"http://example.com",
|
||||
expect.anything()
|
||||
);
|
||||
const parsedBody = JSON.parse(
|
||||
fetchMock.mock.calls[0]![1]!.body!.toString()
|
||||
);
|
||||
const parsedBody = JSON.parse(fetchMock.mock.calls[0]![1]!.body as string);
|
||||
expect(parsedBody.webhookSubscriptionId).toBe(subscription.id);
|
||||
expect(parsedBody.event).toBe("users.signin");
|
||||
expect(parsedBody.payload.id).toBe(signedInUser.id);
|
||||
@@ -120,9 +118,7 @@ describe("DeliverWebhookTask", () => {
|
||||
"http://example.com",
|
||||
expect.anything()
|
||||
);
|
||||
const parsedBody = JSON.parse(
|
||||
fetchMock.mock.calls[0]![1]!.body!.toString()
|
||||
);
|
||||
const parsedBody = JSON.parse(fetchMock.mock.calls[0]![1]!.body as string);
|
||||
expect(parsedBody.webhookSubscriptionId).toBe(subscription.id);
|
||||
expect(parsedBody.event).toBe("users.delete");
|
||||
expect(parsedBody.payload.id).toBe(deletedUserId);
|
||||
|
||||
@@ -15,7 +15,7 @@ export default class Queue {
|
||||
return 0;
|
||||
}
|
||||
|
||||
add = function (data: any) {
|
||||
add = function (data: unknown) {
|
||||
const job = this.createJob(data);
|
||||
|
||||
if (!this.handler) {
|
||||
@@ -25,7 +25,7 @@ export default class Queue {
|
||||
this.handler(job, this.done);
|
||||
};
|
||||
|
||||
process = function (handler: any) {
|
||||
process = function (handler: (job: unknown, done: () => void) => void) {
|
||||
if (this.handler) {
|
||||
throw Error("Cannot define a handler more than once per Queue instance");
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export default class Queue {
|
||||
this.handler = handler;
|
||||
};
|
||||
|
||||
createJob = function (data: any) {
|
||||
createJob = function (data: unknown) {
|
||||
return {
|
||||
data,
|
||||
};
|
||||
|
||||
@@ -5,18 +5,18 @@ import type { Tracer } from "dd-trace";
|
||||
const emptyFn = function () {};
|
||||
|
||||
const callableHandlers = {
|
||||
get<T, P extends keyof T>(_target: T, _prop: P, _receiver: any): T[P] {
|
||||
get<T, P extends keyof T>(_target: T, _prop: P, _receiver: unknown): T[P] {
|
||||
const newMock = new Proxy(emptyFn, callableHandlers);
|
||||
return newMock as any as T[P];
|
||||
return newMock as unknown as T[P];
|
||||
},
|
||||
|
||||
apply<T extends (...args: any) => any, A extends Parameters<T>>(
|
||||
apply<T extends (...args: never[]) => unknown, A extends Parameters<T>>(
|
||||
_target: T,
|
||||
_thisArg: any,
|
||||
_thisArg: unknown,
|
||||
_args: A
|
||||
): ReturnType<T> {
|
||||
const newMock = new Proxy(emptyFn, callableHandlers);
|
||||
return newMock as any as ReturnType<T>;
|
||||
return newMock as unknown as ReturnType<T>;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ export const mockTracer = new Proxy({} as MockTracer, {
|
||||
}
|
||||
|
||||
if (key === "wrap") {
|
||||
return (_: any, f: any) => f;
|
||||
return (_: unknown, f: unknown) => f;
|
||||
}
|
||||
|
||||
return callableMock;
|
||||
|
||||
@@ -308,10 +308,9 @@ describe("userProvisioner", () => {
|
||||
|
||||
const authenticationProviders = await team.$get("authenticationProviders");
|
||||
const authenticationProvider = authenticationProviders[0];
|
||||
let error;
|
||||
|
||||
try {
|
||||
await userProvisioner(ctx, {
|
||||
await expect(
|
||||
userProvisioner(ctx, {
|
||||
name: "Uninvited User",
|
||||
email: "invite@ExamPle.com",
|
||||
teamId: team.id,
|
||||
@@ -321,14 +320,8 @@ describe("userProvisioner", () => {
|
||||
accessToken: "123",
|
||||
scopes: ["read"],
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error && error.toString()).toContain(
|
||||
"You need an invite to join this team"
|
||||
);
|
||||
})
|
||||
).rejects.toThrow("You need an invite to join this team");
|
||||
});
|
||||
|
||||
it("should create a user from allowed domain", async () => {
|
||||
@@ -389,19 +382,14 @@ describe("userProvisioner", () => {
|
||||
|
||||
it("should not create a user with emailMatchOnly when no allowed domains are set", async () => {
|
||||
const team = await buildTeam();
|
||||
let error;
|
||||
|
||||
try {
|
||||
await userProvisioner(ctx, {
|
||||
await expect(
|
||||
userProvisioner(ctx, {
|
||||
name: "Test Name",
|
||||
email: faker.internet.email(),
|
||||
teamId: team.id,
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error && error.toString()).toContain("UnauthorizedError");
|
||||
})
|
||||
).rejects.toThrow("No matching user for email or allowed domain");
|
||||
});
|
||||
|
||||
it("should reject an user when the domain is not allowed", async () => {
|
||||
@@ -415,10 +403,9 @@ describe("userProvisioner", () => {
|
||||
|
||||
const authenticationProviders = await team.$get("authenticationProviders");
|
||||
const authenticationProvider = authenticationProviders[0];
|
||||
let error;
|
||||
|
||||
try {
|
||||
await userProvisioner(ctx, {
|
||||
await expect(
|
||||
userProvisioner(ctx, {
|
||||
name: "Bad Domain User",
|
||||
email: faker.internet.email(),
|
||||
teamId: team.id,
|
||||
@@ -428,13 +415,7 @@ describe("userProvisioner", () => {
|
||||
accessToken: "123",
|
||||
scopes: ["read"],
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error && error.toString()).toContain(
|
||||
"The domain is not allowed for this workspace"
|
||||
);
|
||||
})
|
||||
).rejects.toThrow("The domain is not allowed for this workspace");
|
||||
});
|
||||
});
|
||||
|
||||
+17
-11
@@ -29,13 +29,14 @@ import * as Tracing from "./tracer";
|
||||
type DDTag = (typeof DDTags)[keyof typeof DDTags];
|
||||
|
||||
type Tags = {
|
||||
[tag in DDTag]?: any;
|
||||
[tag in DDTag]?: unknown;
|
||||
} & {
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
interface Constructor {
|
||||
new (...args: any[]): any;
|
||||
// oxlint-disable-next-line @typescript-eslint/no-explicit-any -- variance requires `any[]` to accept arbitrary constructors
|
||||
new (...args: any[]): unknown;
|
||||
}
|
||||
|
||||
interface TraceConfig {
|
||||
@@ -58,6 +59,7 @@ interface TraceConfig {
|
||||
export const traceFunction =
|
||||
(config: TraceConfig) =>
|
||||
<
|
||||
// oxlint-disable-next-line @typescript-eslint/no-explicit-any -- variance requires `any` to accept arbitrary functions
|
||||
F extends (...args: any[]) => any,
|
||||
P extends Parameters<F>,
|
||||
R extends ReturnType<F>,
|
||||
@@ -66,7 +68,7 @@ export const traceFunction =
|
||||
): F =>
|
||||
env.ENVIRONMENT === "test"
|
||||
? target
|
||||
: (function wrapperFn(this: any, ...args: P): R {
|
||||
: (function wrapperFn(this: unknown, ...args: P): R {
|
||||
const { className, methodName = target.name, tags } = config;
|
||||
const childOf = config.isRoot
|
||||
? undefined
|
||||
@@ -125,8 +127,8 @@ export const traceFunction =
|
||||
} as F);
|
||||
|
||||
const traceMethod = (config?: TraceConfig) =>
|
||||
function <R, A extends any[], F extends (...args: A) => R>(
|
||||
target: any,
|
||||
function <R, A extends unknown[], F extends (...args: A) => R>(
|
||||
target: { name?: string; constructor: { name: string } },
|
||||
_propertyKey: string,
|
||||
descriptor: PropertyDescriptor
|
||||
): TypedPropertyDescriptor<F> {
|
||||
@@ -191,20 +193,24 @@ const traceClass = (config?: TraceConfig) =>
|
||||
export function trace(config?: TraceConfig) {
|
||||
function traceDecorator(target: Constructor): void;
|
||||
function traceDecorator<T>(
|
||||
target: Record<string, any>,
|
||||
target: Record<string, unknown>,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: TypedPropertyDescriptor<T>
|
||||
): void;
|
||||
function traceDecorator(
|
||||
a: Constructor | Record<string, any>,
|
||||
b?: any,
|
||||
c?: any
|
||||
a: Constructor | Record<string, unknown>,
|
||||
b?: string | symbol,
|
||||
c?: PropertyDescriptor
|
||||
): void {
|
||||
if (typeof a === "function") {
|
||||
// Need to cast as there is no safe runtime way to check if a function is a constructor
|
||||
traceClass(config)(a as Constructor);
|
||||
} else {
|
||||
traceMethod(config)(a, b, c);
|
||||
traceMethod(config)(
|
||||
a as { name?: string; constructor: { name: string } },
|
||||
b as string,
|
||||
c!
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@ import isObject from "lodash/isPlainObject";
|
||||
import type { Model } from "sequelize-typescript";
|
||||
import { AuthorizationError } from "@server/errors";
|
||||
|
||||
type Constructor = new (...args: any) => any;
|
||||
// oxlint-disable-next-line @typescript-eslint/no-explicit-any -- variance requires `any` to accept arbitrary constructors
|
||||
type Constructor = new (...args: any[]) => unknown;
|
||||
|
||||
type Policy = Record<string, boolean | string[]>;
|
||||
|
||||
type Condition<T extends Constructor, P extends Constructor> = (
|
||||
performer: InstanceType<P>,
|
||||
target: InstanceType<T> | null,
|
||||
options?: any
|
||||
options?: unknown
|
||||
) => boolean | string;
|
||||
|
||||
type Ability = {
|
||||
@@ -132,7 +133,7 @@ export class CanCan {
|
||||
if (performer instanceof model) {
|
||||
for (const [action, abilities] of actionMap.entries()) {
|
||||
for (const ability of abilities) {
|
||||
if (target instanceof (ability.target as any)) {
|
||||
if (target instanceof (ability.target as Constructor)) {
|
||||
actionsToCheck.add(action);
|
||||
break;
|
||||
}
|
||||
@@ -211,7 +212,7 @@ export class CanCan {
|
||||
if (
|
||||
ability.target === "all" ||
|
||||
target === ability.target ||
|
||||
target instanceof (ability.target as any)
|
||||
target instanceof (ability.target as Constructor)
|
||||
) {
|
||||
matchingAbilities.push(ability);
|
||||
}
|
||||
@@ -225,7 +226,7 @@ export class CanCan {
|
||||
if (
|
||||
ability.target === "all" ||
|
||||
target === ability.target ||
|
||||
target instanceof (ability.target as any)
|
||||
target instanceof (ability.target as Constructor)
|
||||
) {
|
||||
matchingAbilities.push(ability);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Minute } from "@shared/utils/time";
|
||||
import type { PartitionInfo } from "./CronTask";
|
||||
import { CronTask, TaskInterval } from "./CronTask";
|
||||
|
||||
type RangeWhere = Record<string, { [Op.gte]: string; [Op.lte]: string }>;
|
||||
|
||||
// Create a concrete implementation of CronTask for testing
|
||||
class TestTask extends CronTask {
|
||||
public async perform() {
|
||||
@@ -18,8 +20,8 @@ class TestTask extends CronTask {
|
||||
public testPartitionWhereClause(
|
||||
idField: string,
|
||||
partition: PartitionInfo | undefined
|
||||
) {
|
||||
return this.getPartitionWhereClause(idField, partition);
|
||||
): RangeWhere {
|
||||
return this.getPartitionWhereClause(idField, partition) as RangeWhere;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +107,7 @@ describe("CronTask", () => {
|
||||
const where = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 0,
|
||||
partitionCount: 3,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
expect(where).toBeDefined();
|
||||
expect(where.id).toBeDefined();
|
||||
@@ -117,17 +119,17 @@ describe("CronTask", () => {
|
||||
const where0 = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 0,
|
||||
partitionCount: 3,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const where1 = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 1,
|
||||
partitionCount: 3,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const where2 = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 2,
|
||||
partitionCount: 3,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
// Partition 0: Should start from 00000000
|
||||
expect(where0.id[Op.gte]).toBe("00000000-0000-4000-8000-000000000000");
|
||||
@@ -146,12 +148,12 @@ describe("CronTask", () => {
|
||||
const where0 = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 0,
|
||||
partitionCount: 2,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const where1 = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 1,
|
||||
partitionCount: 2,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
// Partition 0: 0x00000000 to 0x7fffffff
|
||||
expect(where0.id[Op.gte]).toBe("00000000-0000-4000-8000-000000000000");
|
||||
@@ -170,7 +172,7 @@ describe("CronTask", () => {
|
||||
const where = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: i,
|
||||
partitionCount,
|
||||
}) as any;
|
||||
});
|
||||
ranges.push({
|
||||
start: where.id[Op.gte],
|
||||
end: where.id[Op.lte],
|
||||
@@ -199,7 +201,7 @@ describe("CronTask", () => {
|
||||
const where = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 0,
|
||||
partitionCount: 1,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
// Should cover entire UUID space
|
||||
expect(where.id[Op.gte]).toBe("00000000-0000-4000-8000-000000000000");
|
||||
@@ -233,12 +235,12 @@ describe("CronTask", () => {
|
||||
const where1 = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 0,
|
||||
partitionCount: 2,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const where2 = task.testPartitionWhereClause("documentId", {
|
||||
partitionIndex: 0,
|
||||
partitionCount: 2,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
expect(where1.id).toBeDefined();
|
||||
expect(where1.documentId).toBeUndefined();
|
||||
@@ -254,7 +256,7 @@ describe("CronTask", () => {
|
||||
const where = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: i,
|
||||
partitionCount,
|
||||
}) as any;
|
||||
});
|
||||
ranges.push({
|
||||
start: where.id[Op.gte],
|
||||
end: where.id[Op.lte],
|
||||
@@ -276,7 +278,7 @@ describe("CronTask", () => {
|
||||
const where = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: 1,
|
||||
partitionCount: 16, // 16 partitions = 0x10000000 per partition
|
||||
}) as any;
|
||||
});
|
||||
|
||||
// Partition 1 should be from 0x10000000 to 0x1fffffff
|
||||
expect(where.id[Op.gte]).toBe("10000000-0000-4000-8000-000000000000");
|
||||
@@ -304,7 +306,7 @@ describe("CronTask", () => {
|
||||
const where = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: i,
|
||||
partitionCount,
|
||||
}) as any;
|
||||
});
|
||||
|
||||
const startUuid = where.id[Op.gte];
|
||||
const endUuid = where.id[Op.lte];
|
||||
@@ -334,7 +336,7 @@ describe("CronTask", () => {
|
||||
const where = task.testPartitionWhereClause("id", {
|
||||
partitionIndex: i,
|
||||
partitionCount,
|
||||
}) as any;
|
||||
});
|
||||
ranges.push({
|
||||
start: where.id[Op.gte],
|
||||
end: where.id[Op.lte],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { WhereOptions } from "sequelize";
|
||||
import type { WhereAttributeHash } from "sequelize";
|
||||
import { Op } from "sequelize";
|
||||
import { Minute } from "@shared/utils/time";
|
||||
import { BaseTask } from "./BaseTask";
|
||||
@@ -178,7 +178,7 @@ export abstract class CronTask extends BaseTask<Props> {
|
||||
protected getPartitionWhereClause(
|
||||
idField: string,
|
||||
partitionInfo: PartitionInfo | undefined
|
||||
): WhereOptions {
|
||||
): WhereAttributeHash {
|
||||
if (!partitionInfo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -3,19 +3,19 @@ import type MarkdownIt from "markdown-it";
|
||||
|
||||
const CHECKBOX_REGEX = /\[(X|\s|_|-)\]\s(.*)?/i;
|
||||
|
||||
function matches(token: Token | undefined) {
|
||||
function matches(token: Token) {
|
||||
return token && token.content.match(CHECKBOX_REGEX);
|
||||
}
|
||||
|
||||
function isInline(token: Token | undefined): boolean {
|
||||
function isInline(token: Token): boolean {
|
||||
return !!token && token.type === "inline";
|
||||
}
|
||||
|
||||
function isParagraph(token: Token | undefined): boolean {
|
||||
function isParagraph(token: Token): boolean {
|
||||
return !!token && token.type === "paragraph_open";
|
||||
}
|
||||
|
||||
function isListItem(token: Token | undefined): boolean {
|
||||
function isListItem(token: Token): boolean {
|
||||
// Only match list_item_open, not checkbox_item_open - items that are already
|
||||
// checkbox_item_open have been processed (e.g., by the tables rule for
|
||||
// checkboxes in table cells) and should not be processed again.
|
||||
|
||||
Reference in New Issue
Block a user