Turn on the "noUncheckedIndexedAccess" flag in all TS config files (#3587)

This commit is contained in:
Utku Ufuk
2025-08-26 19:25:54 +03:00
committed by GitHub
parent d1fdc13441
commit 10ded437f0
43 changed files with 301 additions and 305 deletions
+1
View File
@@ -5,6 +5,7 @@
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": false,
"declaration": true,
"outDir": "dist",
+2 -14
View File
@@ -121,17 +121,6 @@ export function getCacheTag(
}
}
/**
* Get the cache tag for a given URL.
*/
export function getCacheTagForURL(url: string | URL) {
const parsedURL = url instanceof URL ? url : new URL(url);
return getCacheTag({
tag: 'url',
hostname: parsedURL.hostname,
});
}
/**
* Get the tags for a computed content source.
*/
@@ -182,7 +171,7 @@ export function getComputedContentSourceCacheTags(
break;
default:
// Do not throw for unknown dependency types
// as it might mean we are lacking behind the API version
// as it might mean we are lagging behind the API version
break;
}
});
@@ -198,9 +187,8 @@ export function getComputedContentSourceCacheTags(
}
// We invalidate the computed content when a new version of the integration is deployed.
if (source.type.startsWith('integration:')) {
const integration = source.type.split(':')[1];
const integration = source.type.split(':')[1]!;
tags.push(
getCacheTag({
tag: 'integration',
+1
View File
@@ -5,6 +5,7 @@
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": false,
"declaration": true,
"outDir": "dist",
+8 -8
View File
@@ -8,7 +8,7 @@ type RGBColor = [number, number, number];
type OKLABColor = { L: number; A: number; B: number };
type OKLCHColor = { L: number; C: number; H: number };
const D65 = [95.047, 100.0, 108.883]; // Reference white (D65)
const D65 = [95.047, 100.0, 108.883] as const; // Reference white (D65)
export enum ColorCategory {
backgrounds = 'backgrounds',
@@ -211,8 +211,8 @@ export function colorScale(
const result = [];
for (let index = 0; index < mapping.length; index++) {
const targetL =
foregroundColor.L * mapping[index] + backgroundColor.L * (1 - mapping[index]);
const step = mapping[index]!;
const targetL = foregroundColor.L * step + backgroundColor.L * (1 - step);
if (
index === 8 &&
@@ -295,7 +295,7 @@ export function rgbArrayToHex(rgb: RGBColor): string {
export function getColor(percentage: number, start: RGBColor, end: RGBColor) {
const rgb = end.map((channel, index) => {
return Math.round(channel + percentage * (start[index] - channel));
return Math.round(channel + percentage * (start[index]! - channel));
});
return rgbArrayToHex(rgb as RGBColor);
@@ -392,14 +392,14 @@ export function xyzToLab65(xyz: [number, number, number]): {
B: number;
} {
const [x, y, z] = xyz.map((v, i) => {
const scaled = v / D65[i];
const scaled = v / D65[i]!;
return scaled > 0.008856 ? Math.cbrt(scaled) : 7.787 * scaled + 16 / 116;
});
return {
L: 116 * y - 16,
A: 500 * (x - y),
B: 200 * (y - z),
L: 116 * y! - 16,
A: 500 * (x! - y!),
B: 200 * (y! - z!),
};
}
+1
View File
@@ -5,6 +5,7 @@
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": false,
"declaration": true,
"outDir": "dist",
+2 -2
View File
@@ -67,7 +67,7 @@ function getBestUnicodeRange(text: string, ranges: Record<string, string>): stri
const body = token.slice(2); // drop "U+"
const [startHex, endHex] = body.split('-');
const start = Number.parseInt(startHex, 16);
const start = Number.parseInt(startHex!, 16);
const end = endHex ? Number.parseInt(endHex, 16) : start;
if (Number.isNaN(start) || Number.isNaN(end) || end < start) return null;
@@ -92,7 +92,7 @@ function getBestUnicodeRange(text: string, ranges: Record<string, string>): stri
for (const [label, rangesArr] of Object.entries(parsed)) {
if (rangesArr.some(([lo, hi]) => cp >= lo && cp <= hi)) {
hits[label]++;
hits[label]!++;
}
}
}
+1
View File
@@ -4,6 +4,7 @@
"module": "ESNext",
"target": "es2022",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
@@ -11,7 +11,7 @@ const args = process.argv.slice(2);
const versionId = args[0];
// The preview URL is in the format https://<versionId>-gitbook-open-v2-server-preview.gitbook.workers.dev
const previewHostname = `${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`;
const previewHostname = `${versionId?.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`;
let updatedFile = file.replace(
/"PREVIEW_HOSTNAME": "TO_REPLACE"/,
@@ -39,7 +39,9 @@ export async function streamRenderAIMessage(
if (message.steps[stepIndex]) {
message.steps = [...message.steps];
// @ts-expect-error
message.steps[stepIndex] = { ...message.steps[stepIndex] };
// @ts-expect-error
callback(message.steps[stepIndex]);
} else {
message.steps = [
@@ -52,6 +54,7 @@ export async function streamRenderAIMessage(
},
},
];
// @ts-expect-error
callback(message.steps[stepIndex]);
}
};
@@ -111,14 +111,15 @@ function DefaultAction(props: AIActionsDropdownProps) {
(assistant) => assistant.ui === true && assistant.pageAction
);
if (assistants.length) {
return <OpenAIAssistant assistant={assistants[0]} type="button" />;
const assistant = assistants[0];
if (assistant) {
return <OpenAIAssistant assistant={assistant} type="button" />;
}
if (actions.markdown) {
return (
<CopyMarkdown
isDefaultAction={!assistants.length}
isDefaultAction={!assistant}
markdownPageUrl={markdownPageUrl}
type="button"
/>
@@ -119,7 +119,7 @@ export async function highlight(
currentIndex += 1; // for the \n
return {
highlighted: Boolean(lineBlock.data.highlighted),
highlighted: Boolean(lineBlock?.data.highlighted),
tokens: result,
};
});
@@ -7,13 +7,16 @@ import { Block, type BlockProps } from './Block';
import { Blocks } from './Blocks';
import { getBlockTextStyle } from './spacing';
export function Hint(props: BlockProps<DocumentBlockHint>) {
const { block, style, ancestorBlocks, ...contextProps } = props;
export function Hint({
block,
style,
ancestorBlocks,
...contextProps
}: BlockProps<DocumentBlockHint>) {
const hintStyle = HINT_STYLES[block.data.style] ?? HINT_STYLES.info;
const firstLine = getBlockTextStyle(block.nodes[0]);
const firstNode = block.nodes[0];
const hasHeading = ['heading-1', 'heading-2', 'heading-3'].includes(block.nodes[0].type);
const firstNode = block.nodes[0]!;
const firstLine = getBlockTextStyle(firstNode);
const hasHeading = ['heading-1', 'heading-2', 'heading-3'].includes(firstNode.type);
return (
<div
@@ -19,9 +19,9 @@ export function ListItem(props: BlockProps<DocumentBlockListItem>) {
const parent = ancestorBlocks[ancestorBlocks.length - 1];
assert(
(parent && parent.type === 'list-ordered') ||
parent.type === 'list-unordered' ||
parent.type === 'list-tasks',
parent?.type === 'list-ordered' ||
parent?.type === 'list-unordered' ||
parent?.type === 'list-tasks',
'Invalid parent list type'
);
@@ -112,11 +112,11 @@ function getListItemDepth(input: {
for (let i = ancestorBlocks.length - 1; i >= 0; i--) {
const block = ancestorBlocks[i];
if (block.type === type) {
if (block?.type === type) {
depth = depth + 1;
continue;
}
if (block.type === 'list-item') {
if (block?.type === 'list-item') {
continue;
}
break;
@@ -10,7 +10,7 @@ export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
const { block, style, ancestorBlocks, ...contextProps } = props;
const ancestor = ancestorBlocks[ancestorBlocks.length - 1];
assert(ancestor.type === 'stepper', 'Ancestor block must be a stepper');
assert(ancestor?.type === 'stepper', 'Ancestor block must be a stepper');
const index = ancestor.nodes.indexOf(block);
@@ -26,6 +26,7 @@ export function RecordRow(
autoSizedColumns,
fixedColumns,
});
// @ts-expect-error
const verticalAlignment = getColumnVerticalAlignment(block.data.definition[column]);
return (
@@ -25,7 +25,9 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
/* Only show the header when configured and not empty */
const withHeader =
!view.hideHeader &&
view.columns.some((columnId) => block.data.definition[columnId].title.trim().length > 0);
view.columns.some(
(columnId) => (block.data.definition[columnId]?.title.trim().length ?? 0) > 0
);
return (
<div className={tcls(style, styles.tableWrapper)}>
@@ -43,28 +45,31 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
)}
>
<div role="row" className={tcls('flex', 'w-full')}>
{view.columns.map((column) => (
<div
key={column}
role="columnheader"
className={tcls(
styles.columnHeader,
getColumnAlignment(block.data.definition[column])
)}
style={{
width: getColumnWidth({
column,
columnWidths,
autoSizedColumns,
fixedColumns,
}),
minWidth: columnWidths?.[column] || '100px',
}}
title={block.data.definition[column].title}
>
{block.data.definition[column].title}
</div>
))}
{view.columns.map((column) => {
const definition = block.data.definition[column]!;
return (
<div
key={column}
role="columnheader"
className={tcls(
styles.columnHeader,
getColumnAlignment(definition)
)}
style={{
width: getColumnWidth({
column,
columnWidths,
autoSizedColumns,
fixedColumns,
}),
minWidth: columnWidths?.[column] || '100px',
}}
title={definition.title}
>
{definition.title}
</div>
);
})}
</div>
</div>
)}
@@ -206,7 +206,7 @@ export function DynamicTabs(
'max-w-full',
'truncate',
active.id === tab.id
active?.id === tab.id
? [
'shrink-0',
'active-tab',
@@ -222,7 +222,7 @@ export function DynamicTabs(
<button
type="button"
role="tab"
aria-selected={active.id === tab.id}
aria-selected={active?.id === tab.id}
aria-controls={getTabPanelId(tab.id)}
id={getTabButtonId(tab.id)}
onClick={() => {
@@ -255,7 +255,7 @@ export function DynamicTabs(
role="tabpanel"
id={getTabPanelId(tab.id)}
aria-labelledby={getTabButtonId(tab.id)}
className={tcls('p-4', tab.id !== active.id ? 'hidden' : null)}
className={tcls('p-4', tab.id !== active?.id ? 'hidden' : null)}
>
{tabsBody[index]}
</div>
@@ -43,9 +43,9 @@ export function getBlockTextStyle(block: DocumentBlock): {
case 'list-ordered':
case 'list-tasks':
case 'list-unordered':
return getBlockTextStyle(block.nodes[0]);
return getBlockTextStyle(block.nodes[0]!);
case 'list-item':
return getBlockTextStyle(block.nodes[0]);
return getBlockTextStyle(block.nodes[0]!);
default:
return {
textSize: 'text-base',
@@ -21,7 +21,7 @@ export function isBlockOffscreen(
const allAncestors = [document, ...ancestorBlocks];
for (let index = allAncestors.length - 1; index >= 0; index--) {
const parent = allAncestors[index];
const parent = allAncestors[index]!;
const offset = getBlockOffset({ block: current, parent });
if (offset > screenHeight) {
@@ -19,14 +19,8 @@ const SECTION_INTERSECTING_THRESHOLD = 0.9;
*/
const ACTIVE_ITEM_OFFSET = 100;
export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
const { sections } = props;
const ids = React.useMemo(() => {
return sections.map((section) => {
return section.id;
});
}, [sections]);
export function ScrollSectionsList({ sections }: { sections: DocumentSection[] }) {
const ids = React.useMemo(() => sections.map(({ id }) => id), [sections]);
const enabled = useBodyLoaded();
@@ -140,7 +140,13 @@ export async function CustomizationRootLayout(props: {
customization.styling.primaryColor.light
)
};
--header-link: ${hexToRgb(customization.header.linkColor?.light ?? colorContrast(tintColor?.light ?? customization.styling.primaryColor.light))};
--header-link: ${hexToRgb(
// @ts-expect-error
customization.header.linkColor?.light ??
colorContrast(
tintColor?.light ?? customization.styling.primaryColor.light
)
)};
${generateColorVariable('info', infoColor.light)}
${generateColorVariable('warning', warningColor.light)}
@@ -154,7 +160,13 @@ export async function CustomizationRootLayout(props: {
${generateColorVariable('neutral', DEFAULT_TINT_COLOR, { darkMode: true })}
--header-background: ${hexToRgb(customization.header.backgroundColor?.dark ?? tintColor?.dark ?? customization.styling.primaryColor.dark)};
--header-link: ${hexToRgb(customization.header.linkColor?.dark ?? colorContrast(tintColor?.dark ?? customization.styling.primaryColor.dark))};
--header-link: ${hexToRgb(
// @ts-expect-error
customization.header.linkColor?.dark ??
colorContrast(
tintColor?.dark ?? customization.styling.primaryColor.dark
)
)};
${generateColorVariable('info', infoColor.dark, { darkMode: true })}
${generateColorVariable('warning', warningColor.dark, { darkMode: true })}
@@ -325,6 +337,7 @@ function generateColorVariable(
return Object.entries(shades)
.map(([key, value]) => {
const rgbValue = hexToRgb(value); // Check the original hex value
// @ts-expect-error
const contrastValue = withContrast ? hexToRgb(colorContrast(value)) : undefined; // Add contrast if needed
return `--${name}-${key}: ${rgbValue}; ${
contrastValue ? `--contrast-${name}-${key}: ${contrastValue};` : ''
@@ -55,7 +55,7 @@ export function SearchContainer(props: SearchContainerProps) {
initialRef.current = true;
// For simplicity we're only triggering the first assistant
assistants[0].open(state?.ask ?? undefined);
assistants[0]?.open(state?.ask ?? undefined);
}, [state?.ask, assistants.length, assistants[0]?.open]);
const onClose = React.useCallback(
@@ -296,7 +296,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
key={item.id}
question={item.question}
active={index === cursor}
assistant={assistants[0]}
assistant={assistants[0]!}
recommended
/>
);
@@ -71,8 +71,7 @@ export interface AskAnswerResult {
export async function searchAllSiteContent(query: string): Promise<OrderedComputedResult[]> {
return traceErrorOnly('Search.searchAllSiteContent', async () => {
const context = await getServerActionBaseContext();
return await searchSiteContent(context, {
return searchSiteContent(context, {
query,
scope: { mode: 'all' },
});
@@ -270,7 +269,7 @@ async function searchSiteContent(
return (
await Promise.all(
searchResults.map(async (spaceItem) => {
searchResults.map((spaceItem) => {
const found = findSiteSpaceBy(
structure,
(siteSpace) => siteSpace.space.id === spaceItem.id
@@ -25,7 +25,7 @@ export function useHasBeenInViewport(
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
if (entry?.isIntersecting) {
setVisible(true);
observer.disconnect();
}
@@ -30,8 +30,9 @@ export function useInViewportListener(
const observer = new IntersectionObserver(
([entry]) => {
isIntersectingRef.current = entry.isIntersecting;
listenerRef.current(entry.isIntersecting, () => {
const isIntersecting = entry?.isIntersecting ?? false;
isIntersectingRef.current = isIntersecting;
listenerRef.current(isIntersecting, () => {
observer.disconnect();
});
},
@@ -5,19 +5,22 @@ import React from 'react';
*/
export function useScrollActiveId(
ids: string[],
options: {
{
rootMargin,
threshold = 0.5,
enabled,
}: {
rootMargin?: string;
threshold?: number;
enabled: boolean;
} = { enabled: true }
) {
const { rootMargin, threshold = 0.5, enabled } = options;
const [activeId, setActiveId] = React.useState<string>(ids[0]);
const [activeId, setActiveId] = React.useState<string>(ids[0]!);
const sectionsIntersectingMap = React.useRef<Map<string, boolean>>(new Map());
React.useEffect(() => {
const defaultActiveId = ids[0];
// @ts-expect-error
setActiveId((activeId) => (ids.indexOf(activeId) !== -1 ? activeId : defaultActiveId));
if (!enabled) {
return;
@@ -41,8 +41,8 @@ export function ZoomImage(
// Since the image is removed from the DOM when the modal is opened,
// We only care when the size is defined.
if (imgEntry && imgEntry.contentRect.width !== 0) {
viewWidth = entries[0]?.contentRect.width;
setPlaceholderRect(entries[0].contentRect);
viewWidth = imgEntry.contentRect.width;
setPlaceholderRect(imgEntry.contentRect);
onChange();
}
})
+169 -190
View File
@@ -3,201 +3,180 @@ import type { CustomizationFontDefinition } from '@gitbook/api';
import stylelint from 'stylelint';
import { generateFontFacesCSS, getFontSourcesToPreload } from './custom';
const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
basic: {
id: 'open-sans',
custom: true,
fontFamily: 'Open Sans',
fontFaces: [
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/opensans-regular.woff2',
format: 'woff2',
},
],
},
{
weight: 700,
sources: [
{
url: 'https://example.com/fonts/opensans-bold.woff2',
format: 'woff2',
},
],
},
],
permissions: {
edit: false,
const TEST_FONTS_BASIC: CustomizationFontDefinition = {
id: 'open-sans',
custom: true,
fontFamily: 'Open Sans',
fontFaces: [
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/opensans-regular.woff2',
format: 'woff2',
},
],
},
{
weight: 700,
sources: [
{
url: 'https://example.com/fonts/opensans-bold.woff2',
format: 'woff2',
},
],
},
],
permissions: {
edit: false,
},
};
multiWeight: {
id: 'roboto',
custom: true,
fontFamily: 'Roboto',
fontFaces: [
{
weight: 300,
sources: [
{
url: 'https://example.com/fonts/roboto-light.woff2',
format: 'woff2',
},
],
},
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/roboto-regular.woff2',
format: 'woff2',
},
],
},
{
weight: 500,
sources: [
{
url: 'https://example.com/fonts/roboto-medium.woff2',
format: 'woff2',
},
],
},
{
weight: 700,
sources: [
{
url: 'https://example.com/fonts/roboto-bold.woff2',
format: 'woff2',
},
],
},
{
weight: 900,
sources: [
{
url: 'https://example.com/fonts/roboto-black.woff2',
format: 'woff2',
},
],
},
],
permissions: {
edit: false,
const TEST_FONTS_MULTI_WEIGHT: CustomizationFontDefinition = {
id: 'roboto',
custom: true,
fontFamily: 'Roboto',
fontFaces: [
{
weight: 300,
sources: [
{
url: 'https://example.com/fonts/roboto-light.woff2',
format: 'woff2',
},
],
},
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/roboto-regular.woff2',
format: 'woff2',
},
],
},
{
weight: 500,
sources: [
{
url: 'https://example.com/fonts/roboto-medium.woff2',
format: 'woff2',
},
],
},
{
weight: 700,
sources: [
{
url: 'https://example.com/fonts/roboto-bold.woff2',
format: 'woff2',
},
],
},
{
weight: 900,
sources: [
{
url: 'https://example.com/fonts/roboto-black.woff2',
format: 'woff2',
},
],
},
],
permissions: {
edit: false,
},
};
multiSource: {
id: 'lato',
custom: true,
fontFamily: 'Lato',
fontFaces: [
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/lato-regular.woff2',
format: 'woff2',
},
{ url: 'https://example.com/fonts/lato-regular.woff', format: 'woff' },
],
},
],
permissions: {
edit: false,
const TEST_FONTS_MULTI_SOURCE: CustomizationFontDefinition = {
id: 'lato',
custom: true,
fontFamily: 'Lato',
fontFaces: [
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/lato-regular.woff2',
format: 'woff2',
},
{ url: 'https://example.com/fonts/lato-regular.woff', format: 'woff' },
],
},
],
permissions: {
edit: false,
},
};
missingFormat: {
id: 'source-sans',
custom: true,
fontFamily: 'Source Sans Pro',
fontFaces: [
{
weight: 400,
sources: [
{ url: 'https://example.com/fonts/sourcesans-regular.woff2' },
{
url: 'https://example.com/fonts/sourcesans-regular.woff',
format: 'woff',
},
],
},
],
permissions: {
edit: false,
const TEST_FONTS_MISSING_FORMAT: CustomizationFontDefinition = {
id: 'source-sans',
custom: true,
fontFamily: 'Source Sans Pro',
fontFaces: [
{
weight: 400,
sources: [
{ url: 'https://example.com/fonts/sourcesans-regular.woff2' },
{
url: 'https://example.com/fonts/sourcesans-regular.woff',
format: 'woff',
},
],
},
],
permissions: {
edit: false,
},
};
empty: {
id: 'empty-font',
custom: true,
fontFamily: 'Empty Font',
fontFaces: [],
permissions: {
edit: false,
},
const TEST_FONTS_EMPTY: CustomizationFontDefinition = {
id: 'empty-font',
custom: true,
fontFamily: 'Empty Font',
fontFaces: [],
permissions: {
edit: false,
},
};
specialChars: {
id: 'special-font',
custom: true,
fontFamily: 'Special Font & Co.',
fontFaces: [
{
weight: 400,
sources: [{ url: 'https://example.com/fonts/special.woff2', format: 'woff2' }],
},
],
permissions: {
edit: false,
const TEST_FONTS_SPECIAL_CHARS: CustomizationFontDefinition = {
id: 'special-font',
custom: true,
fontFamily: 'Special Font & Co.',
fontFaces: [
{
weight: 400,
sources: [{ url: 'https://example.com/fonts/special.woff2', format: 'woff2' }],
},
],
permissions: {
edit: false,
},
};
complex: {
id: 'complex-font',
custom: true,
fontFamily: 'Complex Font',
fontFaces: [
{
weight: 400,
sources: [
{ url: 'https://example.com/fonts/regular.woff2' },
{ url: 'https://example.com/fonts/regular.woff' },
],
},
{
weight: 700,
sources: [
{ url: 'https://example.com/fonts/bold.woff2' },
{ url: 'https://example.com/fonts/bold.woff' },
],
},
],
permissions: {
edit: false,
const TEST_FONTS_COMPLEX: CustomizationFontDefinition = {
id: 'complex-font',
custom: true,
fontFamily: 'Complex Font',
fontFaces: [
{
weight: 400,
sources: [
{ url: 'https://example.com/fonts/regular.woff2' },
{ url: 'https://example.com/fonts/regular.woff' },
],
},
},
variousURLs: {
id: 'various-urls',
custom: true,
fontFamily: 'Various URLs Font',
fontFaces: [
{
weight: 400,
sources: [
{ url: 'https://example.com/fonts.woff2' },
{ url: 'https://example.com/fonts.woff' },
{ url: 'https://example.com/fonts.woff2' },
],
},
],
permissions: {
edit: false,
{
weight: 700,
sources: [
{ url: 'https://example.com/fonts/bold.woff2' },
{ url: 'https://example.com/fonts/bold.woff' },
],
},
],
permissions: {
edit: false,
},
};
@@ -229,7 +208,7 @@ async function isCSSValid(css: string): Promise<boolean> {
describe('generateFontFacesCSS', () => {
test('basic case with regular and bold weights', async () => {
const css = generateFontFacesCSS(TEST_FONTS.basic, 'content');
const css = generateFontFacesCSS(TEST_FONTS_BASIC, 'content');
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
@@ -244,7 +223,7 @@ describe('generateFontFacesCSS', () => {
});
test('mono type', async () => {
const css = generateFontFacesCSS(TEST_FONTS.basic, 'mono');
const css = generateFontFacesCSS(TEST_FONTS_BASIC, 'mono');
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
@@ -259,7 +238,7 @@ describe('generateFontFacesCSS', () => {
});
test('multiple font weights', async () => {
const css = generateFontFacesCSS(TEST_FONTS.multiWeight, 'content');
const css = generateFontFacesCSS(TEST_FONTS_MULTI_WEIGHT, 'content');
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
@@ -270,7 +249,7 @@ describe('generateFontFacesCSS', () => {
});
test('multiple sources for a single weight', async () => {
const css = generateFontFacesCSS(TEST_FONTS.multiSource, 'content');
const css = generateFontFacesCSS(TEST_FONTS_MULTI_SOURCE, 'content');
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
@@ -281,7 +260,7 @@ describe('generateFontFacesCSS', () => {
});
test('missing format property', async () => {
const css = generateFontFacesCSS(TEST_FONTS.missingFormat, 'content');
const css = generateFontFacesCSS(TEST_FONTS_MISSING_FORMAT, 'content');
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
@@ -292,13 +271,13 @@ describe('generateFontFacesCSS', () => {
});
test('empty font faces array', async () => {
const css = generateFontFacesCSS(TEST_FONTS.empty, 'content');
const css = generateFontFacesCSS(TEST_FONTS_EMPTY, 'content');
expect(css).toBe('');
});
test('font with special characters in name', async () => {
const css = generateFontFacesCSS(TEST_FONTS.specialChars, 'content');
const css = generateFontFacesCSS(TEST_FONTS_SPECIAL_CHARS, 'content');
// Validate CSS syntax
const isValid = await isCSSValid(css);
@@ -308,13 +287,13 @@ describe('generateFontFacesCSS', () => {
describe('getFontSourcesToPreload', () => {
const preloadTestCases = [
{ name: 'basic case', font: TEST_FONTS.basic, expectedCount: 2 },
{ name: 'multiple weights', font: TEST_FONTS.multiWeight, expectedCount: 2 },
{ name: 'multiple sources', font: TEST_FONTS.multiSource, expectedCount: 2 },
{ name: 'missing format', font: TEST_FONTS.missingFormat, expectedCount: 2 },
{ name: 'empty font faces', font: TEST_FONTS.empty, expectedCount: 0 },
{ name: 'special characters', font: TEST_FONTS.specialChars, expectedCount: 1 },
{ name: 'complex case', font: TEST_FONTS.complex, expectedCount: 4 },
{ name: 'basic case', font: TEST_FONTS_BASIC, expectedCount: 2 },
{ name: 'multiple weights', font: TEST_FONTS_MULTI_WEIGHT, expectedCount: 2 },
{ name: 'multiple sources', font: TEST_FONTS_MULTI_SOURCE, expectedCount: 2 },
{ name: 'missing format', font: TEST_FONTS_MISSING_FORMAT, expectedCount: 2 },
{ name: 'empty font faces', font: TEST_FONTS_EMPTY, expectedCount: 0 },
{ name: 'special characters', font: TEST_FONTS_SPECIAL_CHARS, expectedCount: 1 },
{ name: 'complex case', font: TEST_FONTS_COMPLEX, expectedCount: 4 },
];
preloadTestCases.forEach(({ name, font, expectedCount }) => {
+1
View File
@@ -28,6 +28,7 @@ export function t(
const [partToPush, partToReplace] = currentStringToReplace.split(`\${${i + 1}}`);
parts.push(<React.Fragment key={`string-${i}`}>{partToPush}</React.Fragment>);
parts.push(<React.Fragment key={`arg-${i}`}>{arg}</React.Fragment>);
// @ts-expect-error
currentStringToReplace = partToReplace;
}
});
+1 -1
View File
@@ -195,7 +195,7 @@ export async function fetchSiteContextByURLLookup(
baseContext: GitBookBaseContext,
data: SiteURLData
): Promise<GitBookSiteContext> {
return await fetchSiteContextByIds(baseContext, {
return fetchSiteContextByIds(baseContext, {
organization: data.organization,
site: data.site,
siteSection: data.siteSection,
+1 -1
View File
@@ -111,7 +111,7 @@ export function getURLLookupAlternatives(input: URL) {
// Mark the longuest entry to lookup as primary
alternatives.sort((a, b) => b.extraPath.length - a.extraPath.length);
if (alternatives.length > 0) {
alternatives[alternatives.length - 1].primary = true;
alternatives[alternatives.length - 1]!.primary = true;
}
return { urls: alternatives, basePath, changeRequest, revision };
+1 -1
View File
@@ -110,7 +110,7 @@ function isEmptyMarkdownPage(markdown: string): boolean {
if (
node.type === 'paragraph' &&
node.children.length === 1 &&
node.children[0].type === 'text' &&
node.children[0]!.type === 'text' &&
!node.children[0].value.trim()
) {
continue;
@@ -12,7 +12,7 @@ describe('#enrichFilesystem', () => {
rootURL: null,
});
const enriched = await enrichFilesystem(filesystem);
expect(enriched[0].specification.paths['/pet'].put['x-gitbook-description-html']).toBe(
expect(enriched[0]?.specification.paths['/pet'].put['x-gitbook-description-html']).toBe(
'<p>Social platform</p>'
);
});
+1 -1
View File
@@ -9,5 +9,5 @@ export function getPreviewRequestIdentifier(requestURL: URL): string {
// For preview requests, we extract the site ID from the pathname
// e.g. https://preview/site_id/...
const pathname = requestURL.pathname.slice(1).split('/');
return pathname[0];
return pathname[0]!;
}
+2 -2
View File
@@ -157,7 +157,7 @@ export function getVisitorUnsignedClaims(args: {
if (typeof parsed === 'object' && parsed !== null) {
Object.assign(claims, parsed);
}
} catch (_err) {
} catch {
console.warn(`Invalid JSON in unsigned claim cookie "${cookie.name}"`);
}
}
@@ -188,7 +188,7 @@ function setVisitorClaimByPath(
let current = claims;
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
const key = keys[index]!;
if (index === keys.length - 1) {
current[key] = value;
+1 -3
View File
@@ -337,8 +337,6 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
pathname,
].join('/');
console.log(`rewriting ${request.nextUrl.toString()} to ${route}`);
const rewrittenURL = new URL(`/${route}`, request.nextUrl.toString());
rewrittenURL.search = request.nextUrl.search; // Preserve the original search params
@@ -541,7 +539,7 @@ function encodePathInSiteContent(rawPathname: string): {
const embedPage = pathname.match(/^~gitbook\/embed\/page\/(\S+)$/);
if (embedPage) {
return {
pathname: `~gitbook/embed/page/${encodeURIComponent(embedPage[1])}`,
pathname: `~gitbook/embed/page/${encodeURIComponent(embedPage[1]!)}`,
};
}
+4 -4
View File
@@ -123,7 +123,7 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
body: colorContrast(
customization.header.backgroundColor?.[theme] || colors.background,
[baseColors.light, baseColors.dark]
),
)!,
};
gridAsset = colors.body === baseColors.light ? gridWhite : gridBlack;
break;
@@ -134,15 +134,15 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
gradient: colorContrast(customization.styling.primaryColor[theme], [
baseColors.light,
baseColors.dark,
]),
])!,
title: colorContrast(customization.styling.primaryColor[theme], [
baseColors.light,
baseColors.dark,
]),
])!,
body: colorContrast(customization.styling.primaryColor[theme], [
baseColors.light,
baseColors.dark,
]),
])!,
};
gridAsset = colors.body === baseColors.light ? gridWhite : gridBlack;
break;
+6 -7
View File
@@ -36,14 +36,13 @@ function generateVarShades(varName: string, filter: ColorCategory[] = []) {
*/
function generateShades(color: string) {
const rawShades = shadesOfColor(color);
const shadeMap = shades.reduce(
(acc, shade) => {
acc[shade] = `rgb(${hexToRgb(rawShades[`${shade}`])} / <alpha-value>)`;
return acc;
},
{} as Record<string, string>
);
const shadeMap = shades.reduce((acc: Record<string, string>, shade) => {
// @ts-expect-error
acc[shade] = `rgb(${hexToRgb(rawShades[`${shade}`])} / <alpha-value>)`;
return acc;
}, {});
// @ts-expect-error
shadeMap.DEFAULT = shadeMap[500];
return shadeMap;
+1
View File
@@ -5,6 +5,7 @@
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": true,
"incremental": true,
"module": "esnext",
+1
View File
@@ -4,6 +4,7 @@
"module": "ESNext",
"target": "es2022",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
+1
View File
@@ -5,6 +5,7 @@
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": false,
"declaration": true,
"outDir": "dist",
+1
View File
@@ -5,6 +5,7 @@
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": false,
"declaration": true,
"outDir": "dist",