Skip to content

Commit f369f3e

Browse files
authored
Ignore braces when building Sandpack file map (#7996)
Previously, `createFileMap` split the MDX meta string on spaces and assumed the first token was the filename. Once we prefixed code fences with `{expectedErrors: ...}`, it would incorrectly parse the meta and crash. This PR updates createFileMap to skip tokens in the meta containing a start and end brace pair (using a stack to ensure we close on the correct brace) while tokenizing the meta string as expected. Test plan: pages reported in #7994 no longer crash on the next PR Closes #7994
1 parent 2a9ef2d commit f369f3e

File tree

1 file changed

+81
-4
lines changed

1 file changed

+81
-4
lines changed

src/components/MDX/Sandpack/createFileMap.ts

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,66 @@ export const AppJSPath = `/src/App.js`;
1616
export const StylesCSSPath = `/src/styles.css`;
1717
export const SUPPORTED_FILES = [AppJSPath, StylesCSSPath];
1818

19+
/**
20+
* Tokenize meta attributes while ignoring brace-wrapped metadata (e.g. {expectedErrors: …}).
21+
*/
22+
function splitMeta(meta: string): string[] {
23+
const tokens: string[] = [];
24+
let current = '';
25+
let depth = 0;
26+
const trimmed = meta.trim();
27+
28+
for (let ii = 0; ii < trimmed.length; ii++) {
29+
const char = trimmed[ii];
30+
31+
if (char === '{') {
32+
if (depth === 0 && current) {
33+
tokens.push(current);
34+
current = '';
35+
}
36+
depth += 1;
37+
continue;
38+
}
39+
40+
if (char === '}') {
41+
if (depth > 0) {
42+
depth -= 1;
43+
}
44+
if (depth === 0) {
45+
current = '';
46+
}
47+
if (depth < 0) {
48+
throw new Error(`Unexpected closing brace in meta: ${meta}`);
49+
}
50+
continue;
51+
}
52+
53+
if (depth > 0) {
54+
continue;
55+
}
56+
57+
if (/\s/.test(char)) {
58+
if (current) {
59+
tokens.push(current);
60+
current = '';
61+
}
62+
continue;
63+
}
64+
65+
current += char;
66+
}
67+
68+
if (current) {
69+
tokens.push(current);
70+
}
71+
72+
if (depth !== 0) {
73+
throw new Error(`Unclosed brace in meta: ${meta}`);
74+
}
75+
76+
return tokens;
77+
}
78+
1979
export const createFileMap = (codeSnippets: any) => {
2080
return codeSnippets.reduce(
2181
(result: Record<string, SandpackFile>, codeSnippet: React.ReactElement) => {
@@ -37,12 +97,17 @@ export const createFileMap = (codeSnippets: any) => {
3797
let fileActive = false; // if the file tab is shown by default
3898

3999
if (props.meta) {
40-
const [name, ...params] = props.meta.split(' ');
41-
filePath = '/' + name;
42-
if (params.includes('hidden')) {
100+
const tokens = splitMeta(props.meta);
101+
const name = tokens.find(
102+
(token) => token.includes('/') || token.includes('.')
103+
);
104+
if (name) {
105+
filePath = name.startsWith('/') ? name : `/${name}`;
106+
}
107+
if (tokens.includes('hidden')) {
43108
fileHidden = true;
44109
}
45-
if (params.includes('active')) {
110+
if (tokens.includes('active')) {
46111
fileActive = true;
47112
}
48113
} else {
@@ -57,6 +122,18 @@ export const createFileMap = (codeSnippets: any) => {
57122
}
58123
}
59124

125+
if (!filePath) {
126+
if (props.className === 'language-js') {
127+
filePath = AppJSPath;
128+
} else if (props.className === 'language-css') {
129+
filePath = StylesCSSPath;
130+
} else {
131+
throw new Error(
132+
`Code block is missing a filename: ${props.children}`
133+
);
134+
}
135+
}
136+
60137
if (result[filePath]) {
61138
throw new Error(
62139
`File ${filePath} was defined multiple times. Each file snippet should have a unique path name`

0 commit comments

Comments
 (0)