diff --git a/crates/codegraph-core/src/extractors/fsharp.rs b/crates/codegraph-core/src/extractors/fsharp.rs index ec7c41a72..c2387ea78 100644 --- a/crates/codegraph-core/src/extractors/fsharp.rs +++ b/crates/codegraph-core/src/extractors/fsharp.rs @@ -355,31 +355,15 @@ fn extract_value_name(decl_left: &Node, source: &[u8]) -> Option { } fn has_function_type(node: &Node) -> bool { - // The two grammar versions use different node shapes for type signatures: - // - // • WASM (tree-sitter-fsharp npm 0.1.0): `function_type` is the explicit - // function-type kind, only present for `a -> b` types. - // • Native (tree-sitter-fsharp 0.3.0): every type signature is wrapped - // in `curried_spec`. For a function it contains `arguments_spec` - // children; for a plain value (e.g. `val pi : float`) it wraps a - // single `simple_type`. - // - // Treat both engines consistently by classifying as a function whenever - // a function_type node appears OR a curried_spec contains `arguments_spec`. - for i in 0..node.child_count() { - let Some(child) = node.child(i) else { continue }; - match child.kind() { - "function_type" => return true, - "curried_spec" => { - for j in 0..child.child_count() { - if let Some(g) = child.child(j) { - if g.kind() == "arguments_spec" { - return true; - } - } - } + // The grammar wraps every type signature in `curried_spec`. A function type + // (e.g. `val add : int -> int -> int`) contains one or more `arguments_spec` + // children; a plain value (e.g. `val pi : float`) wraps a single `simple_type`. + let Some(curried) = find_child(node, "curried_spec") else { return false }; + for i in 0..curried.child_count() { + if let Some(child) = curried.child(i) { + if child.kind() == "arguments_spec" { + return true; } - _ => {} } } false diff --git a/package-lock.json b/package-lock.json index 4651f2f97..fc3b4a684 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "tree-sitter-dart": "^1.0.0", "tree-sitter-elixir": "^0.3.5", "tree-sitter-erlang": "github:WhatsApp/tree-sitter-erlang#semver:*", - "tree-sitter-fsharp": "^0.1.0", + "tree-sitter-fsharp": "https://github.com/ionide/tree-sitter-fsharp/archive/refs/tags/0.3.0.tar.gz", "tree-sitter-gleam": "github:gleam-lang/tree-sitter-gleam", "tree-sitter-go": "^0.25.0", "tree-sitter-groovy": "^0.1.2", @@ -7436,18 +7436,18 @@ } }, "node_modules/tree-sitter-fsharp": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tree-sitter-fsharp/-/tree-sitter-fsharp-0.1.0.tgz", - "integrity": "sha512-TCK+Jkg3qvEe4o6JxqUlE+vUc9lWhHhD7Toglu5Y04/PKa9DgACzqU5Jp9BYZnyhgGLEe+30kVOyFTY/iC/n1Q==", + "version": "0.3.0", + "resolved": "https://github.com/ionide/tree-sitter-fsharp/archive/refs/tags/0.3.0.tar.gz", + "integrity": "sha512-fXN3uk3m9PaOK5MrylouFYvLsu5LbY64oyB0iTlk17fQ290XfX/arrhfGYIhiscaC6fVsoxv2MS0pOJPFzDTxw==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "node-addon-api": "^8.1.0", - "node-gyp-build": "^4.8.2" + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4" }, "peerDependencies": { - "tree-sitter": "^0.21.0" + "tree-sitter": "^0.22.4" }, "peerDependenciesMeta": { "tree_sitter": { diff --git a/package.json b/package.json index 1df8cb376..8f95159f5 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "tree-sitter-dart": "^1.0.0", "tree-sitter-elixir": "^0.3.5", "tree-sitter-erlang": "github:WhatsApp/tree-sitter-erlang#semver:*", - "tree-sitter-fsharp": "^0.1.0", + "tree-sitter-fsharp": "https://github.com/ionide/tree-sitter-fsharp/archive/refs/tags/0.3.0.tar.gz", "tree-sitter-gleam": "github:gleam-lang/tree-sitter-gleam", "tree-sitter-go": "^0.25.0", "tree-sitter-groovy": "^0.1.2", diff --git a/src/extractors/fsharp.ts b/src/extractors/fsharp.ts index e2a077109..8672471f6 100644 --- a/src/extractors/fsharp.ts +++ b/src/extractors/fsharp.ts @@ -10,6 +10,13 @@ import { findChild, nodeEndLine } from './helpers.js'; /** * Extract symbols from F# files. * + * Grammar source: `tree-sitter-fsharp` v0.3.0 installed via a pinned GitHub + * tarball in `package.json` because the ionide/tree-sitter-fsharp project has + * no v0.3.0 release published to the npm registry. The cargo crate the native + * engine uses is also v0.3.0; both engines must stay aligned. Upgrading + * requires a manual edit of the tarball URL in `package.json` and + * `package-lock.json` — `npm update` will not bump this entry. + * * tree-sitter-fsharp grammar notes: * - named_module: top-level module declaration * - function_declaration_left: LHS of `let name params = ...` @@ -280,31 +287,17 @@ function handleValueDefinition( findChild(pattern, 'identifier'); if (!ident) return; - // The two grammar versions use different shapes for type signatures: - // • WASM (npm tree-sitter-fsharp 0.1.0): `function_type` is the explicit - // function-type kind, only present for `a -> b` types. - // • Native (cargo tree-sitter-fsharp 0.3.0): every type signature is - // wrapped in `curried_spec`. For a function it contains `arguments_spec` - // children; for a plain value (e.g. `val pi : float`) it wraps a single - // `simple_type`. - // Treat both engines consistently by classifying as a function whenever - // function_type appears OR a curried_spec contains an arguments_spec child. + // The grammar wraps every type signature in `curried_spec`. A function type + // (e.g. `val add : int -> int -> int`) contains one or more `arguments_spec` + // children; a plain value (e.g. `val pi : float`) wraps a single `simple_type`. + const curriedSpec = findChild(node, 'curried_spec'); let hasFunctionType = false; - for (let i = 0; i < node.childCount; i++) { - const c = node.child(i); - if (!c) continue; - if (c.type === 'function_type') { - hasFunctionType = true; - break; - } - if (c.type === 'curried_spec') { - for (let j = 0; j < c.childCount; j++) { - if (c.child(j)?.type === 'arguments_spec') { - hasFunctionType = true; - break; - } + if (curriedSpec) { + for (let i = 0; i < curriedSpec.childCount; i++) { + if (curriedSpec.child(i)?.type === 'arguments_spec') { + hasFunctionType = true; + break; } - if (hasFunctionType) break; } }