From e2035119657a0cb0326b9d7c6abfab5578c61b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacy=20=C5=81=C4=85tka?= Date: Mon, 1 Jun 2026 21:26:49 +0200 Subject: [PATCH] fix(argent): ship tree-sitter so react-profiler-component-source finds components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit react-profiler-component-source returned found:false for every component in a standard TS/TSX project. The bundled tool-server require()s "tree-sitter" and "tree-sitter-typescript" (native addons) at runtime, but the published @swmansion/argent declared neither as a dependency, so the require threw, was swallowed by loadTreeSitter()'s try/catch, and the AST index was always empty. The grammar/match logic was already correct — the pinned pair parses export-default-function, plain-function, and arrow components. Declare both as dependencies and mark them external in the tool-server esbuild bundle (esbuild can't inline .node addons). Also stop misreporting " not found in " when the parser failed to load — surface a clear "tree-sitter parser could not be loaded" message (and the indexed-file count on a genuine miss), which would have made this packaging gap obvious. The bug is invisible in the dev monorepo (which has tree-sitter present) and only affects installs of the published package. --- packages/argent/package.json | 4 +- packages/argent/scripts/bundle-tools.cjs | 4 ++ .../react/react-profiler-component-source.ts | 12 +++++- .../test/react-profiler/ast-index.test.ts | 38 +++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/tool-server/test/react-profiler/ast-index.test.ts diff --git a/packages/argent/package.json b/packages/argent/package.json index c794d88f..280917a8 100644 --- a/packages/argent/package.json +++ b/packages/argent/package.json @@ -41,7 +41,9 @@ "scripts/postinstall.cjs" ], "dependencies": { - "@modelcontextprotocol/sdk": "^1.20.0" + "@modelcontextprotocol/sdk": "^1.20.0", + "tree-sitter": "^0.21.1", + "tree-sitter-typescript": "^0.23.2" }, "devDependencies": { "@argent/cli": "file:../argent-cli", diff --git a/packages/argent/scripts/bundle-tools.cjs b/packages/argent/scripts/bundle-tools.cjs index 8aa7609f..a847e2c0 100644 --- a/packages/argent/scripts/bundle-tools.cjs +++ b/packages/argent/scripts/bundle-tools.cjs @@ -119,6 +119,10 @@ esbuild.buildSync({ outfile: OUT_FILE, alias: ALIASES, mainFields: MAIN_FIELDS, + // tree-sitter / tree-sitter-typescript are native addons (.node) that esbuild + // cannot inline; keep them external so the bundle `require()`s them at runtime + // from the published package's own dependencies (see package.json). + external: ["tree-sitter", "tree-sitter-typescript"], }); console.log(`✓ Bundled tools server → ${path.relative(process.cwd(), OUT_FILE)}`); diff --git a/packages/tool-server/src/tools/profiler/react/react-profiler-component-source.ts b/packages/tool-server/src/tools/profiler/react/react-profiler-component-source.ts index aa2c6416..844995cd 100644 --- a/packages/tool-server/src/tools/profiler/react/react-profiler-component-source.ts +++ b/packages/tool-server/src/tools/profiler/react/react-profiler-component-source.ts @@ -23,10 +23,20 @@ Returns found: false if the component is not found in user-owned code (e.g. live const entry = astIndex.index.get(params.component_name); if (!entry) { + if (!astIndex.treeSitterAvailable) { + return { + found: false, + component: params.component_name, + message: + `Component source lookup is unavailable: the tree-sitter parser could not be loaded, ` + + `so no source files were indexed. Ensure @swmansion/argent's "tree-sitter" and ` + + `"tree-sitter-typescript" dependencies are installed.`, + }; + } return { found: false, component: params.component_name, - message: `Component "${params.component_name}" not found in ${params.project_root}`, + message: `Component "${params.component_name}" not found in ${params.project_root} (searched ${astIndex.indexedFiles} files).`, }; } diff --git a/packages/tool-server/test/react-profiler/ast-index.test.ts b/packages/tool-server/test/react-profiler/ast-index.test.ts new file mode 100644 index 00000000..10f26029 --- /dev/null +++ b/packages/tool-server/test/react-profiler/ast-index.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect } from "vitest"; +import { mkdtempSync, mkdirSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { buildAstIndexWithDiagnostics } from "../../src/utils/react-profiler/pipeline/06-resolve/ast-index"; + +/** + * react-profiler-component-source returned found:false for every component in a + * standard TS/TSX Expo project because @swmansion/argent never declared (or + * shipped) tree-sitter / tree-sitter-typescript, so the parser require() threw, + * was swallowed, and the index was always empty. The grammar/match logic itself + * is correct — this test guards it for the idiomatic declaration forms and, via + * treeSitterAvailable, fails loudly if the parser dependency goes missing again. + */ +describe("buildAstIndexWithDiagnostics", () => { + it("indexes export-default-function, plain-function, and arrow TSX components", async () => { + const dir = mkdtempSync(join(tmpdir(), "ast-index-")); + mkdirSync(join(dir, "components"), { recursive: true }); + writeFileSync( + join(dir, "components", "foo.tsx"), + [ + `import React from "react";`, + `type P = { x: number };`, + `export default function Foo({ x }: P) { return {x}; }`, + `function Bar({ y }: { y: number }) { return {y}; }`, + `const Baz = ({ z }: { z: number }) => {z};`, + ``, + ].join("\n") + ); + + const res = await buildAstIndexWithDiagnostics(dir); + + expect(res.treeSitterAvailable).toBe(true); + expect(res.index.get("Foo")?.line).toBe(3); + expect(res.index.get("Bar")?.line).toBe(4); + expect(res.index.get("Baz")?.line).toBe(5); + }); +});