From 3cde6543a05592bbd3519ec0194717fe1fe8b32e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:22:54 +0000 Subject: [PATCH 1/2] Initial plan From abe9c02538cba53cfc340e419daeaa84a9a2ed0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:32:19 +0000 Subject: [PATCH 2/2] feat: add support for absolute paths with <root>/ prefix in remarkFileCodeBlock Co-authored-by: SoonIter <79413249+SoonIter@users.noreply.github.com> --- packages/core/src/node/mdx/options.ts | 2 +- .../mdx/remarkPlugins/fileCodeBlock.test.ts | 86 +++++++++++++++++++ .../node/mdx/remarkPlugins/fileCodeBlock.ts | 36 +++++++- 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/packages/core/src/node/mdx/options.ts b/packages/core/src/node/mdx/options.ts index 6b8a605a6..21a2e61c3 100644 --- a/packages/core/src/node/mdx/options.ts +++ b/packages/core/src/node/mdx/options.ts @@ -65,7 +65,7 @@ export async function createMDXOptions(options: { remarkGFM, remarkToc, remarkContainerSyntax, - [remarkFileCodeBlock, { filepath, addDependency }], + [remarkFileCodeBlock, { filepath, docDirectory, addDependency }], [ remarkLink, { diff --git a/packages/core/src/node/mdx/remarkPlugins/fileCodeBlock.test.ts b/packages/core/src/node/mdx/remarkPlugins/fileCodeBlock.test.ts index a5764ff86..17e5c16c6 100644 --- a/packages/core/src/node/mdx/remarkPlugins/fileCodeBlock.test.ts +++ b/packages/core/src/node/mdx/remarkPlugins/fileCodeBlock.test.ts @@ -29,4 +29,90 @@ describe('remarkFileCodeBlock', () => { }); expect(result).toMatchSnapshot(); }); + + it('should support absolute path with / prefix', async () => { + vol.fromJSON({ + '/usr/rspress-project/docs/src/components/Button.tsx': `export const Button = () => { + return ; +} +`, + }); + const result = await compile({ + source: ` +\`\`\`tsx file="/src/components/Button.tsx" +\`\`\` +`, + + docDirectory: '/usr/rspress-project/docs', + filepath: '/usr/rspress-project/docs/guide/components.mdx', + config: null, + pluginDriver: null, + routeService: null, + }); + // The result should contain the transformed code with the Button component + expect(result).toContain('Button'); + expect(result).toContain('button'); + expect(result).toContain('Click me'); + }); + + it('should support nested absolute paths with / prefix', async () => { + vol.fromJSON({ + '/usr/rspress-project/docs/examples/code/demo.js': `const demo = 'test'; +console.log(demo); +`, + }); + const result = await compile({ + source: ` +\`\`\`js file="/examples/code/demo.js" +\`\`\` +`, + + docDirectory: '/usr/rspress-project/docs', + filepath: '/usr/rspress-project/docs/api/reference.mdx', + config: null, + pluginDriver: null, + routeService: null, + }); + // The result should contain the transformed code with demo variable + expect(result).toContain('demo'); + expect(result).toContain('test'); + expect(result).toContain('console'); + }); + + it('should throw error when absolute path file does not exist', async () => { + vol.fromJSON({}); + await expect( + compile({ + source: ` +\`\`\`tsx file="/non-existent.tsx" +\`\`\` +`, + docDirectory: '/usr/rspress-project/docs', + filepath: '/usr/rspress-project/docs/index.mdx', + config: null, + pluginDriver: null, + routeService: null, + }), + ).rejects.toThrow(/does not exist/); + }); + + it('should throw error when content is not empty with absolute path', async () => { + vol.fromJSON({ + '/usr/rspress-project/docs/test.tsx': 'const test = 1;', + }); + await expect( + compile({ + source: ` +\`\`\`tsx file="/test.tsx" +some content +\`\`\` +`, + docDirectory: '/usr/rspress-project/docs', + filepath: '/usr/rspress-project/docs/index.mdx', + config: null, + pluginDriver: null, + routeService: null, + }), + ).rejects.toThrow(/should be empty/); + }); }); diff --git a/packages/core/src/node/mdx/remarkPlugins/fileCodeBlock.ts b/packages/core/src/node/mdx/remarkPlugins/fileCodeBlock.ts index 19315fd85..423832c8a 100644 --- a/packages/core/src/node/mdx/remarkPlugins/fileCodeBlock.ts +++ b/packages/core/src/node/mdx/remarkPlugins/fileCodeBlock.ts @@ -24,9 +24,15 @@ function parseFileFromMeta(meta: string | undefined): string { } export const remarkFileCodeBlock: Plugin< - [{ filepath: string; addDependency?: Rspack.LoaderContext['addDependency'] }], + [ + { + filepath: string; + docDirectory: string; + addDependency?: Rspack.LoaderContext['addDependency']; + }, + ], Root -> = ({ filepath, addDependency }) => { +> = ({ filepath, docDirectory, addDependency }) => { return async tree => { const promiseList: Promise[] = []; visit(tree, 'code', node => { @@ -39,8 +45,25 @@ export const remarkFileCodeBlock: Plugin< const originalMetaForErrorInfo = picocolors.cyan(`\`\`\`${lang} ${meta}`); - if (file.startsWith('./') || file.startsWith('../')) { - const resolvedFilePath = path.join(path.dirname(filepath), file); + // Support relative paths and absolute paths with / prefix + if ( + file.startsWith('./') || + file.startsWith('../') || + file.startsWith('/') + ) { + let resolvedFilePath: string; + + if (file.startsWith('/')) { + // Absolute path relative to docDirectory + resolvedFilePath = path.join( + docDirectory, + file.slice('/'.length), + ); + } else { + // Relative path to current file + resolvedFilePath = path.join(path.dirname(filepath), file); + } + // we allow blank lines or spaces, which may be necessary due to formatting tools and other reasons. if (value.trim() !== '') { logger.error(`${ERROR_PREFIX} ${originalMetaForErrorInfo} The content of file code block should be empty. @@ -82,6 +105,11 @@ Please use below: \`\`\`tsx file="./filename" \`\`\` + +or + +\`\`\`tsx file="/path/to/filename" +\`\`\` `); throw new Error( `${ERROR_PREFIX} ${originalMetaForErrorInfo} syntax error of file code block`,