From 9fe28873ce7481d1d309120915c87736bc82d767 Mon Sep 17 00:00:00 2001
From: VladCuciureanu <dev@vladinski.md>
Date: Mon, 31 Oct 2022 19:49:10 +0200
Subject: [PATCH] Fix memleak when project path contains '.md'

---
 .changeset/clever-files-wash.md               |  5 +++
 packages/astro/src/core/util.ts               | 15 ++-------
 .../vite-plugin-astro-postprocess/index.ts    |  2 +-
 packages/astro/src/vite-plugin-jsx/index.ts   |  2 +-
 .../src/vite-plugin-markdown-legacy/index.ts  |  8 ++---
 .../astro/src/vite-plugin-markdown/index.ts   |  2 +-
 .../impostor-mdx-file/astro.config.mjs        |  7 +++++
 .../fixtures/impostor-mdx-file/package.json   | 11 +++++++
 .../src/components/Foo.mdx.jsx                |  6 ++++
 .../impostor-mdx-file/src/pages/index.astro   | 12 +++++++
 packages/astro/test/impostor-mdx-file.js      | 31 +++++++++++++++++++
 pnpm-lock.yaml                                | 12 +++++++
 12 files changed, 94 insertions(+), 19 deletions(-)
 create mode 100644 .changeset/clever-files-wash.md
 create mode 100644 packages/astro/test/fixtures/impostor-mdx-file/astro.config.mjs
 create mode 100644 packages/astro/test/fixtures/impostor-mdx-file/package.json
 create mode 100644 packages/astro/test/fixtures/impostor-mdx-file/src/components/Foo.mdx.jsx
 create mode 100644 packages/astro/test/fixtures/impostor-mdx-file/src/pages/index.astro
 create mode 100644 packages/astro/test/impostor-mdx-file.js

diff --git a/.changeset/clever-files-wash.md b/.changeset/clever-files-wash.md
new file mode 100644
index 000000000000..6a62c4864061
--- /dev/null
+++ b/.changeset/clever-files-wash.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixed memleak caused by project dir names containing '.md' or '.mdx'
diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts
index 14c641de08b1..01ed44237d06 100644
--- a/packages/astro/src/core/util.ts
+++ b/packages/astro/src/core/util.ts
@@ -19,19 +19,10 @@ export function isURL(value: unknown): value is URL {
 	return Object.prototype.toString.call(value) === '[object URL]';
 }
 /** Check if a file is a markdown file based on its extension */
-export function isMarkdownFile(
-	fileId: string,
-	option: { criteria: 'endsWith' | 'includes'; suffix?: string }
-): boolean {
-	const _suffix = option.suffix ?? '';
-	if (option.criteria === 'endsWith') {
-		for (let markdownFileExtension of SUPPORTED_MARKDOWN_FILE_EXTENSIONS) {
-			if (fileId.endsWith(`${markdownFileExtension}${_suffix}`)) return true;
-		}
-		return false;
-	}
+export function isMarkdownFile(fileId: string, option?: { suffix?: string }): boolean {
+	const _suffix = option?.suffix ?? '';
 	for (let markdownFileExtension of SUPPORTED_MARKDOWN_FILE_EXTENSIONS) {
-		if (fileId.includes(`${markdownFileExtension}${_suffix}`)) return true;
+		if (fileId.endsWith(`${markdownFileExtension}${_suffix}`)) return true;
 	}
 	return false;
 }
diff --git a/packages/astro/src/vite-plugin-astro-postprocess/index.ts b/packages/astro/src/vite-plugin-astro-postprocess/index.ts
index 514d6834de87..c140ad72e395 100644
--- a/packages/astro/src/vite-plugin-astro-postprocess/index.ts
+++ b/packages/astro/src/vite-plugin-astro-postprocess/index.ts
@@ -18,7 +18,7 @@ export default function astro(_opts: AstroPluginOptions): Plugin {
 		name: 'astro:postprocess',
 		async transform(code, id) {
 			// Currently only supported in ".astro" and ".md" (or any alternative markdown file extension like `.markdown`) files
-			if (!id.endsWith('.astro') && !isMarkdownFile(id, { criteria: 'endsWith' })) {
+			if (!id.endsWith('.astro') && !isMarkdownFile(id)) {
 				return null;
 			}
 
diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts
index bd4f0cde5f68..e00f2d11d8fe 100644
--- a/packages/astro/src/vite-plugin-jsx/index.ts
+++ b/packages/astro/src/vite-plugin-jsx/index.ts
@@ -193,7 +193,7 @@ export default function jsx({ settings, logging }: AstroPluginJSXOptions): Plugi
 
 			const { mode } = viteConfig;
 			// Shortcut: only use Astro renderer for MD and MDX files
-			if (id.includes('.mdx') || isMarkdownFile(id, { criteria: 'includes' })) {
+			if (id.endsWith('.mdx')) {
 				const { code: jsxCode } = await esbuild.transform(code, {
 					loader: getEsbuildLoader(path.extname(id)) as esbuild.Loader,
 					jsx: 'preserve',
diff --git a/packages/astro/src/vite-plugin-markdown-legacy/index.ts b/packages/astro/src/vite-plugin-markdown-legacy/index.ts
index 16b6e3e10455..891e8bbea565 100644
--- a/packages/astro/src/vite-plugin-markdown-legacy/index.ts
+++ b/packages/astro/src/vite-plugin-markdown-legacy/index.ts
@@ -90,7 +90,7 @@ export default function markdown({ settings }: AstroPluginOptions): Plugin {
 			// Resolve any .md (or alternative extensions of markdown files like .markdown) files with the `?content` cache buster. This should only come from
 			// an already-resolved JS module wrapper. Needed to prevent infinite loops in Vite.
 			// Unclear if this is expected or if cache busting is just working around a Vite bug.
-			if (isMarkdownFile(id, { criteria: 'endsWith', suffix: MARKDOWN_CONTENT_FLAG })) {
+			if (isMarkdownFile(id, { suffix: MARKDOWN_CONTENT_FLAG })) {
 				const resolvedId = await this.resolve(id, importer, { skipSelf: true, ...options });
 				return resolvedId?.id.replace(MARKDOWN_CONTENT_FLAG, '');
 			}
@@ -98,7 +98,7 @@ export default function markdown({ settings }: AstroPluginOptions): Plugin {
 			// that defers the markdown -> HTML rendering until it is needed. This is especially useful
 			// when fetching and then filtering many markdown files, like with import.meta.glob() or Astro.glob().
 			// Otherwise, resolve directly to the actual component.
-			if (isMarkdownFile(id, { criteria: 'endsWith' }) && !isRootImport(importer)) {
+			if (isMarkdownFile(id) && !isRootImport(importer)) {
 				const resolvedId = await this.resolve(id, importer, { skipSelf: true, ...options });
 				if (resolvedId) {
 					return resolvedId.id + MARKDOWN_IMPORT_FLAG;
@@ -111,7 +111,7 @@ export default function markdown({ settings }: AstroPluginOptions): Plugin {
 			// A markdown file has been imported via ESM!
 			// Return the file's JS representation, including all Markdown
 			// frontmatter and a deferred `import() of the compiled markdown content.
-			if (isMarkdownFile(id, { criteria: 'endsWith', suffix: MARKDOWN_IMPORT_FLAG })) {
+			if (isMarkdownFile(id, { suffix: MARKDOWN_IMPORT_FLAG })) {
 				const { fileId, fileUrl } = getFileInfo(id, config);
 
 				const source = await fs.promises.readFile(fileId, 'utf8');
@@ -151,7 +151,7 @@ export default function markdown({ settings }: AstroPluginOptions): Plugin {
 			// A markdown file is being rendered! This markdown file was either imported
 			// directly as a page in Vite, or it was a deferred render from a JS module.
 			// This returns the compiled markdown -> astro component that renders to HTML.
-			if (isMarkdownFile(id, { criteria: 'endsWith' })) {
+			if (isMarkdownFile(id)) {
 				const filename = normalizeFilename(id);
 				const source = await fs.promises.readFile(filename, 'utf8');
 				const renderOpts = config.markdown;
diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts
index 6a4e62b9fc92..c403d21ff73a 100644
--- a/packages/astro/src/vite-plugin-markdown/index.ts
+++ b/packages/astro/src/vite-plugin-markdown/index.ts
@@ -59,7 +59,7 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu
 		// passing to the transform hook. This lets us get the truly raw value
 		// to escape "import.meta.env" ourselves.
 		async load(id) {
-			if (isMarkdownFile(id, { criteria: 'endsWith' })) {
+			if (isMarkdownFile(id)) {
 				const { fileId, fileUrl } = getFileInfo(id, settings.config);
 				const rawFile = await fs.promises.readFile(fileId, 'utf-8');
 				const raw = safeMatter(rawFile, id);
diff --git a/packages/astro/test/fixtures/impostor-mdx-file/astro.config.mjs b/packages/astro/test/fixtures/impostor-mdx-file/astro.config.mjs
new file mode 100644
index 000000000000..f03a45258926
--- /dev/null
+++ b/packages/astro/test/fixtures/impostor-mdx-file/astro.config.mjs
@@ -0,0 +1,7 @@
+import { defineConfig } from 'astro/config';
+import react from '@astrojs/react';
+
+// https://astro.build/config
+export default defineConfig({
+	integrations: [react()],
+});
\ No newline at end of file
diff --git a/packages/astro/test/fixtures/impostor-mdx-file/package.json b/packages/astro/test/fixtures/impostor-mdx-file/package.json
new file mode 100644
index 000000000000..af59d3cd6ccc
--- /dev/null
+++ b/packages/astro/test/fixtures/impostor-mdx-file/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "@test/impostor-md-file",
+  "version": "0.0.0",
+  "private": true,
+  "dependencies": {
+    "@astrojs/react": "workspace:*",
+    "astro": "workspace:*",
+    "react": "^18.1.0",
+    "react-dom": "^18.1.0"
+  }
+}
diff --git a/packages/astro/test/fixtures/impostor-mdx-file/src/components/Foo.mdx.jsx b/packages/astro/test/fixtures/impostor-mdx-file/src/components/Foo.mdx.jsx
new file mode 100644
index 000000000000..8f935086d2e3
--- /dev/null
+++ b/packages/astro/test/fixtures/impostor-mdx-file/src/components/Foo.mdx.jsx
@@ -0,0 +1,6 @@
+import {useState} from "react";
+
+export default function Foo() {
+  const [bar, setBar] = useState("Baz");
+  return <div className="main-component">{bar}</div>
+}
\ No newline at end of file
diff --git a/packages/astro/test/fixtures/impostor-mdx-file/src/pages/index.astro b/packages/astro/test/fixtures/impostor-mdx-file/src/pages/index.astro
new file mode 100644
index 000000000000..87f3911c48c4
--- /dev/null
+++ b/packages/astro/test/fixtures/impostor-mdx-file/src/pages/index.astro
@@ -0,0 +1,12 @@
+---
+import Foo from '../components/Foo.mdx.jsx';
+---
+
+<html>
+  <head>
+    <!-- Head Stuff -->
+  </head>
+  <body>
+    <Foo client:load />
+  </body>
+</html>
diff --git a/packages/astro/test/impostor-mdx-file.js b/packages/astro/test/impostor-mdx-file.js
new file mode 100644
index 000000000000..6f714737335d
--- /dev/null
+++ b/packages/astro/test/impostor-mdx-file.js
@@ -0,0 +1,31 @@
+import { expect } from 'chai';
+import { isWindows, loadFixture } from './test-utils.js';
+
+let fixture;
+
+describe('Impostor MDX File', () => {
+	before(async () => {
+		fixture = await loadFixture({
+			root: './fixtures/impostor-mdx-file/',
+		});
+	});
+	if (isWindows) return;
+
+	describe('dev', () => {
+		/** @type {import('./test-utils').Fixture} */
+		let devServer;
+
+		before(async () => {
+			devServer = await fixture.startDevServer();
+		});
+
+		after(async () => {
+			await devServer.stop();
+		});
+
+		it('does not crash when loading react component with .md or .mdx in name', async () => {
+			const result = await fixture.fetch('/').then((response) => response.text());
+			expect(result).to.contain('Baz');
+		});
+	});
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f2191a9a43ac..b207dc63624b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1810,6 +1810,18 @@ importers:
     dependencies:
       astro: link:../../..
 
+  packages/astro/test/fixtures/impostor-mdx-file:
+    specifiers:
+      '@astrojs/react': workspace:*
+      astro: workspace:*
+      react: ^18.1.0
+      react-dom: ^18.1.0
+    dependencies:
+      '@astrojs/react': link:../../../../integrations/react
+      astro: link:../../..
+      react: 18.2.0
+      react-dom: 18.2.0_react@18.2.0
+
   packages/astro/test/fixtures/integration-add-page-extension:
     specifiers:
       astro: workspace:*