Skip to content

Commit ab2d969

Browse files
chore: apply strict typescript checking (#6680)
Applying Typescript checks to the code, including strict null checks that raise errors on unhandled `null` or `undefined` types. Also adding the typescript check to the build step, to raise error on TS errors.
1 parent cf1d320 commit ab2d969

21 files changed

+136
-61
lines changed

astro.config.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import { rehypeOptimizeStatic } from './plugins/rehype-optimize-static';
1414
import { rehypeTasklistEnhancer } from './plugins/rehype-tasklist-enhancer';
1515
import react from '@astrojs/react';
1616
import { remarkGraphvizPlugin } from './plugins/remark-graphviz';
17-
import dotenv from 'dotenv';
17+
import dotenv, { DotenvPopulateInput } from 'dotenv';
1818
import vue from '@astrojs/vue';
1919
import sitemap from '@astrojs/sitemap';
2020
import { remarkAlgolia } from './plugins/remark-algolia';
2121

2222
dotenv.config({
2323
path: `.env.${process.env.NODE_ENV}`,
2424
});
25-
dotenv.populate(process.env, {
25+
dotenv.populate(process.env as DotenvPopulateInput, {
2626
PUBLIC_ALGOLIA_APP_ID: '81GKA2I1R0',
2727
PUBLIC_ALGOLIA_INDEX_NAME: 'docs-pages-dev',
2828
PUBLIC_ALGOLIA_SEARCH_KEY: '6fce1b6d8ccf82a6601a169c0167c0e3',

integrations/astro-asides.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,23 @@ function remarkAsides(): unified.Plugin<[], mdast.Root> {
4848
let title: string | undefined;
4949
remove(node, (child) => {
5050
if ((child.data as { directiveLabel?: string })?.directiveLabel) {
51-
if ('children' in child && 'value' in child.children[0]) {
52-
title = child.children[0].value;
51+
if ('children' in child && 'value' in (child.children as any)[0]) {
52+
title = (child.children as any)[0].value;
5353
}
5454
return true;
5555
}
5656
});
5757

58-
// Replace this node with the aside component it represents.
59-
parent.children[index] = makeComponentNode(
60-
AsideTagname,
61-
{ attributes: { type, title } },
62-
...node.children as mdast.BlockContent[]
63-
);
58+
if (index !== undefined ) {
59+
// Replace this node with the aside component it represents.
60+
parent.children[index] = makeComponentNode(
61+
AsideTagname,
62+
{ attributes: { type, title } },
63+
...node.children as mdast.BlockContent[]
64+
);
65+
} else {
66+
throw new Error('Invalid index for parent node');
67+
}
6468
});
6569
};
6670

integrations/astro-youtube-embed.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,23 @@ function remarkYoutubeEmbed(): unified.Plugin<[], mdast.Root> {
2828

2929
remove(node, (child) => {
3030
if ((child.data as { directiveLabel?: string })?.directiveLabel) {
31-
if ('children' in child && 'value' in child.children[0]) {
32-
title = child.children[0].value;
31+
if ('children' in child && 'value' in (child.children as any)[0]) {
32+
title = (child.children as any)[0].value;
3333
}
3434
return true;
3535
}
3636
});
3737

38-
// Replace this node with the aside component it represents.
39-
parent.children[index] = makeComponentNode(
40-
YoutubeTagname,
41-
{ attributes: { title, video: node.attributes.v } },
42-
...node.children as mdast.BlockContent[]
43-
);
38+
if (index !== undefined) {
39+
// Replace this node with the aside component it represents.
40+
parent.children[index] = makeComponentNode(
41+
YoutubeTagname,
42+
{ attributes: { title, video: node.attributes?.v } },
43+
...node.children as mdast.BlockContent[]
44+
);
45+
} else {
46+
throw new Error('Youtube directive has no parent');
47+
}
4448
});
4549
};
4650

package-lock.json

+30
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"scripts": {
66
"dev": "astro dev",
77
"start": "astro dev",
8-
"build": "astro build",
8+
"build": "tsc && astro build",
99
"cf-build": "./cf-build.sh",
1010
"test": "vitest",
1111
"preview": "astro preview",
@@ -15,6 +15,7 @@
1515
"@actions/core": "^1.11.1",
1616
"@astrojs/mdx": "^4.2.3",
1717
"@astrojs/sitemap": "^3.3.0",
18+
"@astrojs/ts-plugin": "^1.10.4",
1819
"@babel/core": "^7.26.10",
1920
"@types/hast": "^3.0.4",
2021
"@types/html-escaper": "^3.0.4",
@@ -148,4 +149,4 @@
148149
"@rollup/rollup-linux-arm64-gnu": "^4.39.0",
149150
"@rollup/rollup-linux-x64-gnu": "^4.39.0"
150151
}
151-
}
152+
}

src/@types/astro.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module '*.astro' {
2+
const Component: any;
3+
export default Component;
4+
}

src/components/HeadSEO.astro

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface Props {
1111
const { content, canonicalURL } = Astro.props;
1212
const ogImageUrl = getOgImageUrl(canonicalURL.pathname);
1313
const imageSrc = ogImageUrl;
14-
const canonicalImageSrc = new URL(imageSrc, Astro.site);
14+
const canonicalImageSrc = imageSrc ? new URL(imageSrc, Astro.site) : undefined;
1515
const imageAlt = imageSrc === ogImageUrl ? '' : "Mergify's logo with text Docs on blue background";
1616
const siteDescription =
1717
'Learn how to use Mergify, the powerful pull request automation tool that helps teams merge code faster and more safely. Automate your entire pull request workflow, from code review to deployment, and save time and frustration.';

src/components/LeftSidebar/NavGroup.astro

+8-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ const { navGroup, currentPageMatch } = Astro.props as Props;
5454
const uuid = this.dataset.uuid;
5555
const storedNavState = window.localStorage.getItem('navState');
5656
let isOpen = false;
57-
if (storedNavState !== null) {
57+
if (!uuid) {
58+
console.warn('No uuid found for nav-group');
59+
return;
60+
}
61+
if (storedNavState !== null && uuid) {
5862
const navState = JSON.parse(storedNavState);
5963
isOpen = navState[uuid];
6064
}
@@ -65,12 +69,12 @@ const { navGroup, currentPageMatch } = Astro.props as Props;
6569
);
6670

6771
if (isOpen) {
68-
navGroupDetails.setAttribute('open', '');
72+
navGroupDetails?.setAttribute('open', '');
6973
} else {
70-
navGroupDetails.removeAttribute('open');
74+
navGroupDetails?.removeAttribute('open');
7175
}
7276

73-
navGroupTitle.addEventListener('click', () => {
77+
navGroupTitle?.addEventListener('click', () => {
7478
const storedNavState = window.localStorage.getItem('navState');
7579
let navState = { uuid: true };
7680
if (storedNavState !== null) {

src/components/LeftSidebar/NavLink.astro

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export interface Props {
99
const { currentPageMatch, navItem } = Astro.props as Props;
1010
const isHomepage = navItem.path === '/';
1111
const isHomepageMatch = isHomepage && currentPageMatch === '';
12-
const isPageMatch = !isHomepage && currentPageMatch.endsWith(removeLeadingSlash(navItem.path));
12+
const isPageMatch =
13+
!isHomepage && currentPageMatch.endsWith(removeLeadingSlash(navItem.path ?? ''));
1314
---
1415

1516
<li class="nav-link">

src/components/MergeQueueCalculator/MergeQueueCalculator.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import './MergeQueueCalculator.scss';
88
import Stat from './Stat';
99

1010
function MergeQueueCalculator() {
11-
const [ciTime, setCiTime] = useState(30);
12-
const [prPerHour, setPrPerHour] = useState(10);
13-
const [ciUsagePct, setCiUsagePct] = useState(100);
14-
const [successRatio, setSuccessRatio] = useState(98);
15-
const [throughput, setThroughput] = useState(null);
16-
const [speculativeChecks, setSpeculativeChecks] = useState(null);
17-
const [batchSize, setBatchSize] = useState(null);
18-
const [latency, setLatency] = useState(null);
11+
const [ciTime, setCiTime] = useState<number>(30);
12+
const [prPerHour, setPrPerHour] = useState<number>(10);
13+
const [ciUsagePct, setCiUsagePct] = useState<number>(100);
14+
const [successRatio, setSuccessRatio] = useState<number>(98);
15+
const [throughput, setThroughput] = useState<number | null>(null);
16+
const [speculativeChecks, setSpeculativeChecks] = useState<number | null>(null);
17+
const [batchSize, setBatchSize] = useState<number | null>(null);
18+
const [latency, setLatency] = useState<number | null>(null);
1919

2020
const calculate = () => {
2121
const calculatedBatchSize = Math.ceil(100 / ciUsagePct);

src/components/MergeQueueCalculator/NumberInput.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ interface Props {
88

99
export default function NumberInput({ label, onChange, value, min, max }: Props) {
1010
const handleIncrement = () => {
11-
onChange(value + 1 > max ? value : value + 1);
11+
onChange(value + 1 > (max ?? Infinity) ? value : value + 1);
1212
};
1313

1414
const handleDecrement = () => {
15-
onChange(value - 1 < min ? value : value - 1);
15+
onChange(value - 1 < (min ?? -Infinity) ? value : value - 1);
1616
};
1717

1818
return (

src/components/MergeQueueCalculator/Stat.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { IconType } from 'react-icons';
22

33
interface Props {
44
label: string;
5-
stat: number;
5+
stat: number | null;
66
helperText: string;
77
icon: IconType;
88
}
@@ -15,7 +15,7 @@ export default function Stat({ helperText, label, stat, ...props }: Props) {
1515
{label}
1616
</label>
1717
<div style={{ display: 'flex', alignItems: 'flex-end', margin: '8px 0', gap: 12 }}>
18-
<h3 style={{ margin: 0 }}>{stat}</h3>
18+
<h3 style={{ margin: 0 }}>{stat ?? 0}</h3>
1919
<span style={{ color: 'var(--theme-text-light)' }}>{helperText}</span>
2020
</div>
2121
</div>

src/components/RightSidebar/TableOfContents.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ const TableOfContents = ({ toc = [], labels, isMobile }: Props) => {
9898
if (!isMobile) return;
9999
setOpen(false);
100100
setCurrentHeading({
101-
slug: e.currentTarget.getAttribute('href').replace('#', ''),
101+
slug: e.currentTarget.getAttribute('href')?.replace('#', '') ?? '',
102102
text: e.currentTarget.textContent || '',
103103
});
104104
};

src/components/Search/PageDetails.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ export default function Preview({
8989
)
9090
.map((table, index) =>
9191
renderMdxTable({
92-
data: table?.data?.value,
92+
data: table?.data?.value ?? '',
9393
node: tables[index].node,
94-
content: table?.content?.value,
94+
content: table?.content?.value ?? '',
9595
})
9696
)}
9797
{_highlightResult?.headings && (

src/components/Search/Results.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ export default function Results({ results }: ResultsProps) {
105105
return newFocused;
106106
});
107107
} else if (e.key === 'Enter') {
108-
const slug = focusedPage.objectID.substring(0, focusedPage.objectID.indexOf('.mdx'));
108+
const slug = focusedPage?.objectID.substring(0, focusedPage.objectID.indexOf('.mdx'));
109109

110-
if (focusedPage) window.location.replace(slug);
110+
if (focusedPage) window.location.replace(slug ?? '');
111111
}
112112
};
113113

src/components/Search/SearchBar.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@ export default function SearchBar() {
7474
}
7575
};
7676

77-
button.addEventListener('click', openModal);
77+
button?.addEventListener('click', openModal);
7878
window.addEventListener('keypress', openModalKeypress);
7979

8080
return () => {
81-
button.removeEventListener('click', openModal);
81+
button?.removeEventListener('click', openModal);
8282
window.removeEventListener('keypress', openModalKeypress);
8383
};
8484
}, []);

src/components/Tables/ConfigOptions.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { ReactElement } from 'react';
22
import jsonpointer from 'jsonpointer';
33
import configSchema from '../../../public/mergify-configuration-schema.json';
44
import { renderMarkdown } from './utils';
@@ -69,6 +69,9 @@ function getTypeLink(ref: string): string | undefined {
6969
const refPath = splitRefPath(ref);
7070
const refId = refPath.at(-1);
7171

72+
if (!refId) {
73+
return undefined;
74+
}
7275
return valueTypeLinks[refId];
7376
}
7477

@@ -101,11 +104,11 @@ function getTitle(schema: object, ref: OptionDefinitionRef): string {
101104
return item?.title || item?.name || '';
102105
}
103106

104-
export function getValueType(schema: object, definition: any): React.ReactElement {
105-
let valueType = null;
107+
export function getValueType(schema: object, definition: any): React.ReactElement | null {
108+
let valueType: ReactElement | null = null;
106109
if (definition.type === 'array') {
107-
let typeLink: string;
108-
let typeDescription: string | React.ReactElement;
110+
let typeLink: string | undefined;
111+
let typeDescription: string | React.ReactElement | null;
109112

110113
if ('$ref' in definition.items) {
111114
typeLink = getTypeLink(definition.items.$ref);

src/components/Tables/PullRequestAttributes.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getValueType } from './ConfigOptions';
44
import { renderMarkdown } from './utils';
55

66
interface Props {
7-
staticAttributes: keyof typeof configSchema.$defs.PullRequestAttributes.properties;
7+
staticAttributes: typeof configSchema.$defs.PullRequestAttributes.properties;
88
}
99

1010
export default function PullRequestAttributes({ staticAttributes }: Props) {

src/pages/index.astro

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import MainLayout from '../layouts/MainLayout.astro';
33
import { Content, getHeadings, frontmatter } from '../content/docs/index.mdx';
44
---
55

6-
<MainLayout content={frontmatter} headings={getHeadings()}>
6+
<MainLayout
7+
content={{
8+
title: frontmatter.title,
9+
description: frontmatter.description,
10+
}}
11+
headings={getHeadings()}
12+
>
713
<Content />
814
</MainLayout>

src/util/mdxast.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ interface TableType {
1616
content: string | null;
1717
}
1818

19-
export function stripHtmlFromkeys(jsonObject: { [s: string]: unknown }): { [s: string]: unknown } {
19+
type JSONObject = Record<string, unknown>;
20+
21+
export function stripHtmlFromkeys(jsonObject: JSONObject | null): JSONObject {
22+
console.log('stripHtmlFromkeys', jsonObject);
23+
2024
let stripped = {};
2125
if (typeof jsonObject === 'string') {
2226
return jsonObject;
@@ -25,7 +29,7 @@ export function stripHtmlFromkeys(jsonObject: { [s: string]: unknown }): { [s: s
2529
if (Array.isArray(jsonObject)) {
2630
stripped = jsonObject.map(stripHtmlFromkeys);
2731
} else {
28-
Object.entries(jsonObject).forEach(([key, value]) => {
32+
Object.entries(jsonObject ?? {}).forEach(([key, value]) => {
2933
const strippedKey = key.replace(/<\/?em>/g, '');
3034

3135
if (Array.isArray(value)) {
@@ -65,7 +69,7 @@ function extractHighlightsFromText(text: string | null): string[] {
6569
const highlights: string[] = [];
6670

6771
const regex = /<em>(.*?)<\/em>/g;
68-
let match: RegExpExecArray;
72+
let match: RegExpExecArray | null;
6973

7074
while ((match = regex.exec(text)) !== null) {
7175
highlights.push(match[1]);
@@ -130,7 +134,7 @@ export function renderMdxTable(table: TableType) {
130134

131135
case 'PullRequestAttributesTable': {
132136
const attributes =
133-
onlyHighlightedOptions as keyof typeof configSchema.$defs.PullRequestAttributes.properties;
137+
onlyHighlightedOptions as typeof configSchema.$defs.PullRequestAttributes.properties;
134138
return <PullRequestAttributesTable staticAttributes={attributes} />;
135139
}
136140

0 commit comments

Comments
 (0)