Skip to content

Commit bde10f6

Browse files
brijeshb42Janpot
andauthored
[code-infra] Migrate error code extraction to code-infra (#2670)
Co-authored-by: Janpot <2109932+Janpot@users.noreply.github.com>
1 parent b70db66 commit bde10f6

File tree

16 files changed

+222
-7
lines changed

16 files changed

+222
-7
lines changed

.markdownlint-cli2.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ export default {
77
config: {
88
...baseline.config,
99
MD038: false, // false positives in MDX
10+
MD041: false, // false positives in MDX
1011
},
1112
};

babel.config.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
import getBaseConfig from '@mui/internal-code-infra/babel-config';
2+
import * as path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
5+
const dirname = path.dirname(fileURLToPath(import.meta.url));
6+
7+
const errorCodesPath = path.join(dirname, 'docs/src/error-codes.json');
28

39
export default function getBabelConfig(api) {
410
const baseConfig = getBaseConfig(api);
511

12+
const plugins = [
13+
[
14+
'@mui/internal-babel-plugin-minify-errors',
15+
{
16+
missingError: 'annotate',
17+
runtimeModule: '#formatErrorMessage',
18+
detection: 'opt-out',
19+
errorCodesPath,
20+
},
21+
],
22+
];
23+
624
return {
725
...baseConfig,
26+
plugins: [...baseConfig.plugins, ...plugins],
827
overrides: [
928
{
1029
exclude: /\.test\.(js|ts|tsx)$/,

docs/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,20 @@ Package managers other than pnpm (like npm or Yarn) are not supported and will n
1616

1717
[You can follow this guide](https://github.com/mui/base-ui/blob/HEAD/CONTRIBUTING.md)
1818
on how to get started contributing to Base UI.
19+
20+
## Error code extraction
21+
22+
Errors in production are minified. They are extracted out of the source code by running the command
23+
24+
```bash
25+
pnpm extract-error-codes
26+
```
27+
28+
This updates the `./src/error-codes.json` file with the newly extracted errors.
29+
30+
Important: If you just altered the text of an error, you are allowed to update the existing error code with the new text in `./src/error-codes.json`, but only under the following conditions:
31+
32+
1. There hasn't been an update to the semantic meaning of the error message. Error codes need to outlive Base UI versions, so the same code must mean the same thing across versions.
33+
2. There hasn't been a change in parameters, no added and no removed.
34+
35+
In both of those cases, always create a new error code lline in `./src/error-codes.json`.

docs/public/static/error-codes.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/scripts/reportBrokenLinks.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-console */
22
import path from 'node:path';
3-
import { fileURLToPath } from 'node:url';
3+
import { fileURLToPath, pathToFileURL } from 'node:url';
44
import { readFile, writeFile } from 'node:fs/promises';
55
import { globby } from 'globby';
66
import * as jsxRuntime from 'react/jsx-runtime';
@@ -108,6 +108,7 @@ async function getLinksAndAnchors(
108108

109109
const { tableOfContents } = await evaluate(mdxSource, {
110110
...jsxRuntime,
111+
baseUrl: pathToFileURL(filePath),
111112
remarkPlugins: [remarkGfm],
112113
rehypePlugins: [
113114
rehypeReference,
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client';
2+
import * as React from 'react';
3+
import { useSearchParams } from 'next/navigation';
4+
5+
export interface ErrorDisplayProps {
6+
msg: string;
7+
}
8+
9+
function ErrorCodeContent() {
10+
return useSearchParams().get('code') ?? '';
11+
}
12+
13+
/**
14+
* Client component that interpolates arguments in an error message. Must be
15+
* a client component because it reads the search params.
16+
*/
17+
export default function ErrorCode() {
18+
return (
19+
<React.Suspense fallback="…">
20+
<ErrorCodeContent />
21+
</React.Suspense>
22+
);
23+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use client';
2+
import * as React from 'react';
3+
import { useSearchParams } from 'next/navigation';
4+
import codes from 'docs/src/error-codes.json';
5+
6+
function ErrorMessageWithArgs() {
7+
const searchParams = useSearchParams();
8+
9+
return React.useMemo(() => {
10+
const code = searchParams.get('code');
11+
const msg =
12+
(code ? (codes as Partial<Record<string, string>>)[code ?? ''] : null) ??
13+
`Unknown error code: ${code}`;
14+
const args = searchParams.getAll('args[]');
15+
let index = 0;
16+
return msg.replace(/%s/g, () => {
17+
const replacement = args[index];
18+
index += 1;
19+
return replacement === undefined ? '[missing argument]' : replacement;
20+
});
21+
}, [searchParams]);
22+
}
23+
24+
/**
25+
* Client component that interpolates arguments in an error message. Must be
26+
* a client component because it reads the search params.
27+
*/
28+
export default function ErrorDisplay() {
29+
return (
30+
<code>
31+
<React.Suspense fallback="…">
32+
<ErrorMessageWithArgs />
33+
</React.Suspense>
34+
</code>
35+
);
36+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import ErrorCode from './ErrorCode';
2+
import ErrorDisplay from './ErrorDisplay';
3+
4+
# Production error #<ErrorCode />
5+
6+
<Subtitle>Explanation for minified production error message.</Subtitle>
7+
<Meta
8+
name="description"
9+
content="In the production build, error messages are minified to keep your application lightweight."
10+
/>
11+
12+
A minified Base UI error occurred in the production build of React.
13+
14+
The full error message:
15+
16+
<ErrorDisplay />

docs/src/error-codes.json

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"1": "Unsupported number of selectors",
3+
"2": "Unsupported number of arguments",
4+
"3": "Base UI: CheckboxGroupContext is missing. CheckboxGroup parts must be placed within <CheckboxGroup>.",
5+
"4": "Base UI: DirectionContext is missing.",
6+
"5": "Base UI: MenubarContext is missing. Menubar parts must be placed within <Menubar>.",
7+
"6": "Base UI: useToast must be used within <Toast.Provider>.",
8+
"7": "Base UI: ToggleGroupContext is missing. ToggleGroup parts must be placed within <ToggleGroup>.",
9+
"8": "Base UI: Render element or function are not defined.",
10+
"9": "Base UI: AccordionItemContext is missing. Accordion parts must be placed within <Accordion.Item>.",
11+
"10": "Base UI: AccordionRootContext is missing. Accordion parts must be placed within <Accordion.Root>.",
12+
"11": "Base UI: <AlertDialog.Portal> is missing.",
13+
"12": "Base UI: AlertDialogRootContext is missing. AlertDialog parts must be placed within <AlertDialog.Root>.",
14+
"13": "Base UI: AvatarRootContext is missing. Avatar parts must be placed within <Avatar.Root>.",
15+
"14": "Base UI: CheckboxRootContext is missing. Checkbox parts must be placed within <Checkbox.Root>.",
16+
"15": "Base UI: CollapsibleRootContext is missing. Collapsible parts must be placed within <Collapsible.Root>.",
17+
"16": "Base UI: CompositeRootContext is missing. Composite parts must be placed within <Composite.Root>.",
18+
"17": "useComboboxChipContext must be used within a ComboboxChip",
19+
"18": "Base UI: ComboboxGroupContext is missing. ComboboxGroup parts must be placed within <Combobox.Group>.",
20+
"19": "Base UI: ComboboxItemContext is missing. ComboboxItem parts must be placed within <Combobox.Item>.",
21+
"20": "Base UI: <Combobox.Portal> is missing.",
22+
"21": "Base UI: <Combobox.Popup> and <Combobox.Arrow> must be used within the <Combobox.Positioner> component",
23+
"22": "Base UI: ComboboxRootContext is missing. Combobox parts must be placed within <Combobox.Root>.",
24+
"23": "Base UI: ComboboxFloatingContext is missing. Combobox parts must be placed within <Combobox.Root>.",
25+
"24": "Base UI: ComboboxItemsContext is missing. Combobox parts must be placed within <Combobox.Root>.",
26+
"25": "Base UI: ContextMenuRootContext is missing. ContextMenu parts must be placed within <ContextMenu.Root>.",
27+
"26": "Base UI: <Dialog.Portal> is missing.",
28+
"27": "Base UI: DialogRootContext is missing. Dialog parts must be placed within <Dialog.Root>.",
29+
"28": "Base UI: FieldRootContext is missing. Field parts must be placed within <Field.Root>.",
30+
"29": "[Floating UI]: Invalid grid - item width at index %s is greater than grid columns",
31+
"30": "Base UI: MenuCheckboxItemContext is missing. MenuCheckboxItem parts must be placed within <Menu.CheckboxItem>.",
32+
"31": "Base UI: MenuGroupRootContext is missing. Menu group parts must be used within <Menu.Group>.",
33+
"32": "Base UI: <Menu.Portal> is missing.",
34+
"33": "Base UI: MenuPositionerContext is missing. MenuPositioner parts must be placed within <Menu.Positioner>.",
35+
"34": "Base UI: MenuRadioGroupContext is missing. MenuRadioGroup parts must be placed within <Menu.RadioGroup>.",
36+
"35": "Base UI: MenuRadioItemContext is missing. MenuRadioItem parts must be placed within <Menu.RadioItem>.",
37+
"36": "Base UI: MenuRootContext is missing. Menu parts must be placed within <Menu.Root>.",
38+
"37": "Base UI: <Menu.SubmenuTrigger> must be placed in <Menu.SubmenuRoot>.",
39+
"38": "Base UI: MeterRootContext is missing. Meter parts must be placed within <Meter.Root>.",
40+
"39": "Base UI: NavigationMenuItem parts must be used within a <NavigationMenu.Item>.",
41+
"40": "Base UI: <NavigationMenu.Portal> is missing.",
42+
"41": "Base UI: NavigationMenuRootContext is missing. Navigation Menu parts must be placed within <NavigationMenu.Root>.",
43+
"42": "Base UI: NavigationMenuPositionerContext is missing. NavigationMenuPositioner parts must be placed within <NavigationMenu.Positioner>.",
44+
"43": "Base UI: NumberFieldRootContext is missing. NumberField parts must be placed within <NumberField.Root>.",
45+
"44": "Base UI: NumberFieldScrubAreaContext is missing. NumberFieldScrubArea parts must be placed within <NumberField.ScrubArea>.",
46+
"45": "Base UI: <Popover.Portal> is missing.",
47+
"46": "Base UI: PopoverPositionerContext is missing. PopoverPositioner parts must be placed within <Popover.Positioner>.",
48+
"47": "Base UI: PopoverRootContext is missing. Popover parts must be placed within <Popover.Root>.",
49+
"48": "Base UI: <PreviewCard.Portal> is missing.",
50+
"49": "Base UI: <PreviewCard.Popup> and <PreviewCard.Arrow> must be used within the <PreviewCard.Positioner> component",
51+
"50": "Base UI: PreviewCardRootContext is missing. PreviewCard parts must be placed within <PreviewCard.Root>.",
52+
"51": "Base UI: ProgressRootContext is missing. Progress parts must be placed within <Progress.Root>.",
53+
"52": "Base UI: RadioRootContext is missing. Radio parts must be placed within <Radio.Root>.",
54+
"53": "Base UI: ScrollAreaRootContext is missing. ScrollArea parts must be placed within <ScrollArea.Root>.",
55+
"54": "Base UI: ScrollAreaScrollbarContext is missing. ScrollAreaScrollbar parts must be placed within <ScrollArea.Scrollbar>.",
56+
"55": "Base UI: ScrollAreaViewportContext missing. ScrollAreaViewport parts must be placed within <ScrollArea.Viewport>.",
57+
"56": "Base UI: SelectGroupContext is missing. SelectGroup parts must be placed within <Select.Group>.",
58+
"57": "Base UI: SelectItemContext is missing. SelectItem parts must be placed within <Select.Item>.",
59+
"58": "Base UI: <Select.Portal> is missing.",
60+
"59": "Base UI: SelectPositionerContext is missing. SelectPositioner parts must be placed within <Select.Positioner>.",
61+
"60": "Base UI: SelectRootContext is missing. Select parts must be placed within <Select.Root>.",
62+
"61": "Base UI: SelectFloatingContext is missing. Select parts must be placed within <Select.Root>.",
63+
"62": "Base UI: SliderRootContext is missing. Slider parts must be placed within <Slider.Root>.",
64+
"63": "Base UI: SwitchRootContext is missing. Switch parts must be placed within <Switch.Root>.",
65+
"64": "Base UI: TabsRootContext is missing. Tabs parts must be placed within <Tabs.Root>.",
66+
"65": "Base UI: TabsListContext is missing. TabsList parts must be placed within <Tabs.List>.",
67+
"66": "Base UI: ToastRootContext is missing. Toast parts must be used within <Toast.Root>.",
68+
"67": "Base UI: ToastViewportContext is missing. Toast parts must be placed within <Toast.Viewport>.",
69+
"68": "Base UI: ToolbarGroupContext is missing. ToolbarGroup parts must be placed within <Toolbar.Group>.",
70+
"69": "Base UI: ToolbarRootContext is missing. Toolbar parts must be placed within <Toolbar.Root>.",
71+
"70": "Base UI: <Tooltip.Portal> is missing.",
72+
"71": "Base UI: TooltipPositionerContext is missing. TooltipPositioner parts must be placed within <Tooltip.Positioner>.",
73+
"72": "Base UI: TooltipRootContext is missing. Tooltip parts must be placed within <Tooltip.Root>.",
74+
"73": "Base UI: useToastManager must be used within <Toast.Provider>.",
75+
"74": "Base UI: PopoverTrigger must be either used within a PopoverRoot component or have the `handle` prop set.",
76+
"75": "Base UI: PopoverTrigger must have an `id` prop specified.",
77+
"76": "Base UI: selectors are required to call useState."
78+
}

docs/types.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22

33
declare module 'gtag.js';
44
declare module '@mui/monorepo/docs/nextConfigDocsInfra.js';
5+
6+
declare module '*.mdx' {
7+
const MDXComponent: (props) => JSX.Element;
8+
export default MDXComponent;
9+
}

0 commit comments

Comments
 (0)