feat: promise safety with eslint

This commit is contained in:
Mythie
2023-08-29 13:01:19 +10:00
parent 98c980a5c4
commit 7811035384
35 changed files with 103 additions and 57 deletions

7
.eslintignore Normal file
View File

@ -0,0 +1,7 @@
# Config files
*.config.js
*.config.cjs
# Statically hosted javascript files
apps/*/public/*.js
apps/*/public/*.cjs

View File

@ -5,7 +5,7 @@ import { allDocuments } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
export const generateStaticParams = async () =>
export const generateStaticParams = () =>
allDocuments.map((post) => ({ post: post._raw.flattenedPath }));
export const generateMetadata = ({ params }: { params: { content: string } }) => {

View File

@ -7,7 +7,7 @@ import { ChevronLeft } from 'lucide-react';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
export const generateStaticParams = async () =>
export const generateStaticParams = () =>
allBlogPosts.map((post) => ({ post: post._raw.flattenedPath }));
export const generateMetadata = ({ params }: { params: { post: string } }) => {

View File

@ -43,7 +43,7 @@ export default async function OpenPage() {
accept: 'application/vnd.github.v3+json',
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => ZGithubStatsResponse.parse(res));
const { total_count: mergedPullRequests } = await fetch(
@ -54,7 +54,7 @@ export default async function OpenPage() {
},
},
)
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => ZMergedPullRequestsResponse.parse(res));
const STARGAZERS_DATA = await fetch('https://stargrazer-live.onrender.com/api/stats', {
@ -62,7 +62,7 @@ export default async function OpenPage() {
accept: 'application/json',
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => ZStargazersLiveResponse.parse(res));
return (

View File

@ -24,7 +24,7 @@ export default async function IndexPage() {
accept: 'application/vnd.github.v3+json',
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => (typeof res.stargazers_count === 'number' ? res.stargazers_count : undefined))
.catch(() => undefined);

View File

@ -63,7 +63,9 @@ export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialog
const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => {
try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000));
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
const [redirectUrl] = await Promise.all([
claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }),

View File

@ -13,7 +13,7 @@ export const PasswordReveal = ({ password }: PasswordRevealProps) => {
const [, copy] = useCopyToClipboard();
const onCopyClick = () => {
copy(password).then(() => {
void copy(password).then(() => {
toast({
title: 'Copied to clipboard',
description: 'Your password has been copied to your clipboard.',

View File

@ -124,7 +124,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
setValue('signatureDataUrl', draftSignatureDataUrl);
setValue('signatureText', '');
trigger('signatureDataUrl');
void trigger('signatureDataUrl');
setShowSigningDialog(false);
};
@ -135,7 +135,9 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
signatureText,
}: TWidgetFormSchema) => {
try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000));
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
// eslint-disable-next-line turbo/no-undeclared-env-vars
const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID;

View File

@ -149,7 +149,7 @@ export const NameField = ({ field, recipient }: NameFieldProps) => {
disabled={!localFullName}
onClick={() => {
setShowFullNameModal(false);
onSign('local');
void onSign('local');
}}
>
Sign

View File

@ -182,7 +182,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
disabled={!localSignature}
onClick={() => {
setShowSignatureModal(false);
onSign('local');
void onSign('local');
}}
>
Sign

View File

@ -118,7 +118,7 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
<DropdownMenuItem
onSelect={() =>
signOut({
void signOut({
callbackUrl: '/',
})
}

View File

@ -63,7 +63,9 @@ export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialog
const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => {
try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000));
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
const [redirectUrl] = await Promise.all([
claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }),

View File

@ -13,7 +13,7 @@ export const PasswordReveal = ({ password }: PasswordRevealProps) => {
const [, copy] = useCopyToClipboard();
const onCopyClick = () => {
copy(password).then(() => {
void copy(password).then(() => {
toast({
title: 'Copied to clipboard',
description: 'Your password has been copied to your clipboard.',

View File

@ -124,7 +124,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
setValue('signatureDataUrl', draftSignatureDataUrl);
setValue('signatureText', '');
trigger('signatureDataUrl');
void trigger('signatureDataUrl');
setShowSigningDialog(false);
};
@ -135,7 +135,9 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
signatureText,
}: TWidgetFormSchema) => {
try {
const delay = new Promise<void>((resolve) => setTimeout(resolve, 1000));
const delay = new Promise<void>((resolve) => {
setTimeout(resolve, 1000);
});
// eslint-disable-next-line turbo/no-undeclared-env-vars
const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID;

View File

@ -79,10 +79,7 @@ export const SignInForm = ({ className }: SignInFormProps) => {
return (
<form
className={cn('flex w-full flex-col gap-y-4', className)}
onSubmit={(e) => {
e.preventDefault();
handleSubmit(onFormSubmit)();
}}
onSubmit={handleSubmit(onFormSubmit)}
>
<div>
<Label htmlFor="email" className="text-slate-500">

View File

@ -32,7 +32,7 @@ export const getFlag = async (
revalidate: 60,
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => ZFeatureFlagValueSchema.parse(res))
.catch(() => false);
@ -64,7 +64,7 @@ export const getAllFlags = async (
revalidate: 60,
},
})
.then((res) => res.json())
.then(async (res) => res.json())
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
.catch(() => LOCAL_FEATURE_FLAGS);
};

View File

@ -11,6 +11,6 @@ export default function PostHogServerClient() {
return new PostHog(postHogConfig.key, {
host: postHogConfig.host,
fetch: (...args) => fetch(...args),
fetch: async (...args) => fetch(...args),
});
}

View File

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
export default async function middleware(req: NextRequest) {
export default function middleware(req: NextRequest) {
if (req.nextUrl.pathname === '/') {
const redirectUrl = new URL('/documents', req.url);

View File

@ -4,7 +4,7 @@ import { appRouter } from '@documenso/trpc/server/router';
export default trpcNext.createNextApiHandler({
router: appRouter,
createContext: ({ req, res }) => createTrpcContext({ req, res }),
createContext: async ({ req, res }) => createTrpcContext({ req, res }),
});
// export default async function handler(_req: NextApiRequest, res: NextApiResponse) {

View File

@ -67,7 +67,7 @@ export function FeatureFlagProvider({
const interval = setInterval(() => {
if (document.hasFocus()) {
getAllFlags().then((newFlags) => setFlags(newFlags));
void getAllFlags().then((newFlags) => setFlags(newFlags));
}
}, FEATURE_FLAG_POLL_INTERVAL);
@ -84,7 +84,7 @@ export function FeatureFlagProvider({
return;
}
const onFocus = () => getAllFlags().then((newFlags) => setFlags(newFlags));
const onFocus = () => void getAllFlags().then((newFlags) => setFlags(newFlags));
window.addEventListener('focus', onFocus);

View File

@ -110,9 +110,10 @@ export class MailChannelsTransport implements Transport<SentMessageInfo> {
});
}
res.json().then((data) => {
return callback(new Error(`MailChannels error: ${data.message}`), null);
});
res
.json()
.then((data) => callback(new Error(`MailChannels error: ${data.message}`), null))
.catch((err) => callback(err, null));
})
.catch((err) => {
return callback(err, null);

View File

@ -20,6 +20,8 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['../../apps/*/tsconfig.json', '../../packages/*/tsconfig.json'],
ecmaVersion: 2022,
ecmaFeatures: {
jsx: true,
@ -33,6 +35,18 @@ module.exports = {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Safety with promises so we aren't running with scissors
'no-promise-executor-return': 'error',
'prefer-promise-reject-errors': 'error',
'require-atomic-updates': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': [
'error',
{ checksVoidReturn: { attributes: false } },
],
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/require-await': 'error',
// We never want to use `as` but are required to on occasion to handle
// shortcomings in third-party and generated types.
//

View File

@ -0,0 +1,9 @@
{
"extends": "@documenso/tsconfig/base.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true,
},
"include": ["**/*.cjs", "**/*.js"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@ -89,7 +89,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
};
},
async session({ token, session }) {
session({ token, session }) {
if (token && token.email) {
return {
...session,

View File

@ -1,9 +1,6 @@
import { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from 'next';
import { headers } from 'next/headers';
import { NextRequest } from 'next/server';
import { getServerSession as getNextAuthServerSession } from 'next-auth';
import { getToken } from 'next-auth/jwt';
import { prisma } from '@documenso/prisma';
@ -30,18 +27,6 @@ export const getServerSession = async ({ req, res }: GetServerSessionOptions) =>
return user;
};
export const getServerComponentToken = async () => {
const requestHeaders = Object.fromEntries(headers().entries());
const req = new NextRequest('http://example.com', {
headers: requestHeaders,
});
const token = await getToken({
req,
});
};
export const getServerComponentSession = async () => {
const session = await getNextAuthServerSession(NEXT_AUTH_OPTIONS);

View File

@ -87,6 +87,6 @@ export const completeDocumentWithToken = async ({
if (documents.count > 0) {
console.log('sealing document');
sealDocument({ documentId: document.id });
await sealDocument({ documentId: document.id });
}
};

View File

@ -0,0 +1,9 @@
{
"extends": "@documenso/tsconfig/base.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true,
},
"include": ["**/*.cjs", "**/*.js"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@ -10,7 +10,7 @@ const t = initTRPC.context<TrpcContext>().create({
/**
* Middlewares
*/
export const authenticatedMiddleware = t.middleware(({ ctx, next }) => {
export const authenticatedMiddleware = t.middleware(async ({ ctx, next }) => {
if (!ctx.session) {
throw new TRPCError({
code: 'UNAUTHORIZED',
@ -18,7 +18,7 @@ export const authenticatedMiddleware = t.middleware(({ ctx, next }) => {
});
}
return next({
return await next({
ctx: {
...ctx,

View File

@ -0,0 +1,8 @@
{
"extends": "./base.json",
"compilerOptions": {
"noEmit": true,
},
"include": ["**/*.ts", "**/*.tsx", "**/*.d.ts", "**/*.json"],
"exclude": ["dist", "build", "node_modules"]
}

View File

@ -86,7 +86,7 @@ export const DocumentDropzone = ({ className, onDrop, ...props }: DocumentDropzo
multiple: false,
onDrop: ([acceptedFile]) => {
if (acceptedFile && onDrop) {
onDrop(acceptedFile);
void onDrop(acceptedFile);
}
},
});

View File

@ -88,6 +88,8 @@ export const AddFieldsFormPartial = ({
},
});
const onFormSubmit = handleSubmit(onSubmit);
const {
append,
remove,
@ -500,7 +502,7 @@ export const AddFieldsFormPartial = ({
loading={isSubmitting}
disabled={isSubmitting}
onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()}
onGoNextClick={() => void onFormSubmit()}
/>
</DocumentFlowFormContainerFooter>
</>

View File

@ -68,6 +68,8 @@ export const AddSignersFormPartial = ({
},
});
const onFormSubmit = handleSubmit(onSubmit);
const {
append: appendSigner,
fields: signers,
@ -214,7 +216,7 @@ export const AddSignersFormPartial = ({
loading={isSubmitting}
disabled={isSubmitting}
onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()}
onGoNextClick={() => void onFormSubmit()}
/>
</DocumentFlowFormContainerFooter>
</>

View File

@ -47,6 +47,8 @@ export const AddSubjectFormPartial = ({
},
});
const onFormSubmit = handleSubmit(onSubmit);
return (
<>
<DocumentFlowFormContainerContent>
@ -130,7 +132,7 @@ export const AddSubjectFormPartial = ({
disabled={isSubmitting}
goNextLabel={document.status === DocumentStatus.DRAFT ? 'Send' : 'Update'}
onGoBackClick={documentFlow.onBackStep}
onGoNextClick={() => handleSubmit(onSubmit)()}
onGoNextClick={() => void onFormSubmit()}
/>
</DocumentFlowFormContainerFooter>
</>

View File

@ -66,7 +66,7 @@ export const PDFViewer = ({ className, document, onPageClick, ...props }: PDFVie
const pageY = event.clientY - top;
if (onPageClick) {
onPageClick({
void onPageClick({
pageNumber,
numPages,
originalEvent: event,

View File

@ -302,8 +302,8 @@ export class Canvas {
/**
* Retrieves the signature as an image blob.
*/
public toBlob(type?: string, quality?: number): Promise<Blob> {
return new Promise((resolve, reject) => {
public async toBlob(type?: string, quality?: number): Promise<Blob> {
const promise = new Promise<Blob>((resolve, reject) => {
this.$canvas.toBlob(
(blob) => {
if (!blob) {
@ -317,5 +317,7 @@ export class Canvas {
quality,
);
});
return await promise;
}
}