Skip to content

Commit adcaf40

Browse files
authored
chore(fsharp): align npm grammar with cargo at v0.3.0 (#1165)
* chore(fsharp): align npm grammar with cargo at v0.3.0 The WASM engine pulled tree-sitter-fsharp 0.1.0 from npm while the native engine used 0.3.0 from crates.io. The two versions diverged in how they parse type signatures in .fsi files: 0.1.0 emits `function_type` nodes for `a -> b` types, while 0.3.0 wraps every signature in `curried_spec` with `arguments_spec` children for function shapes. The F# extractor was forced to detect both shapes simultaneously, which is fragile — future grammar churn could silently desync further. * package.json now installs tree-sitter-fsharp from the ionide v0.3.0 GitHub tarball (npm has no 0.3.0 release; ionide is the upstream the cargo crate also tracks). Lockfile pins via SRI hash. * Both extractors now check only `curried_spec` → `arguments_spec`, removing the dead `function_type` branch from each. docs check acknowledged: README's F# row already covers .fs/.fsx/.fsi and the user-facing language count is unchanged; the grammar version is an internal implementation detail. Closes #1161 * docs(fsharp): explain tree-sitter-fsharp tarball pin (#1165)
1 parent 4b94d11 commit adcaf40

4 files changed

Lines changed: 32 additions & 55 deletions

File tree

crates/codegraph-core/src/extractors/fsharp.rs

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -415,31 +415,15 @@ fn extract_value_name(decl_left: &Node, source: &[u8]) -> Option<String> {
415415
}
416416

417417
fn has_function_type(node: &Node) -> bool {
418-
// The two grammar versions use different node shapes for type signatures:
419-
//
420-
// • WASM (tree-sitter-fsharp npm 0.1.0): `function_type` is the explicit
421-
// function-type kind, only present for `a -> b` types.
422-
// • Native (tree-sitter-fsharp 0.3.0): every type signature is wrapped
423-
// in `curried_spec`. For a function it contains `arguments_spec`
424-
// children; for a plain value (e.g. `val pi : float`) it wraps a
425-
// single `simple_type`.
426-
//
427-
// Treat both engines consistently by classifying as a function whenever
428-
// a function_type node appears OR a curried_spec contains `arguments_spec`.
429-
for i in 0..node.child_count() {
430-
let Some(child) = node.child(i) else { continue };
431-
match child.kind() {
432-
"function_type" => return true,
433-
"curried_spec" => {
434-
for j in 0..child.child_count() {
435-
if let Some(g) = child.child(j) {
436-
if g.kind() == "arguments_spec" {
437-
return true;
438-
}
439-
}
440-
}
418+
// The grammar wraps every type signature in `curried_spec`. A function type
419+
// (e.g. `val add : int -> int -> int`) contains one or more `arguments_spec`
420+
// children; a plain value (e.g. `val pi : float`) wraps a single `simple_type`.
421+
let Some(curried) = find_child(node, "curried_spec") else { return false };
422+
for i in 0..curried.child_count() {
423+
if let Some(child) = curried.child(i) {
424+
if child.kind() == "arguments_spec" {
425+
return true;
441426
}
442-
_ => {}
443427
}
444428
}
445429
false

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
"tree-sitter-dart": "^1.0.0",
163163
"tree-sitter-elixir": "^0.3.5",
164164
"tree-sitter-erlang": "github:WhatsApp/tree-sitter-erlang#semver:*",
165-
"tree-sitter-fsharp": "^0.1.0",
165+
"tree-sitter-fsharp": "https://github.com/ionide/tree-sitter-fsharp/archive/refs/tags/0.3.0.tar.gz",
166166
"tree-sitter-gleam": "github:gleam-lang/tree-sitter-gleam",
167167
"tree-sitter-go": "^0.25.0",
168168
"tree-sitter-groovy": "^0.1.2",

src/extractors/fsharp.ts

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ import { findChild, nodeEndLine } from './helpers.js';
1010
/**
1111
* Extract symbols from F# files.
1212
*
13+
* Grammar source: `tree-sitter-fsharp` v0.3.0 installed via a pinned GitHub
14+
* tarball in `package.json` because the ionide/tree-sitter-fsharp project has
15+
* no v0.3.0 release published to the npm registry. The cargo crate the native
16+
* engine uses is also v0.3.0; both engines must stay aligned. Upgrading
17+
* requires a manual edit of the tarball URL in `package.json` and
18+
* `package-lock.json` — `npm update` will not bump this entry.
19+
*
1320
* tree-sitter-fsharp grammar notes:
1421
* - named_module: top-level module declaration
1522
* - function_declaration_left: LHS of `let name params = ...`
@@ -310,31 +317,17 @@ function handleValueDefinition(
310317
findChild(pattern, 'identifier');
311318
if (!ident) return;
312319

313-
// The two grammar versions use different shapes for type signatures:
314-
// • WASM (npm tree-sitter-fsharp 0.1.0): `function_type` is the explicit
315-
// function-type kind, only present for `a -> b` types.
316-
// • Native (cargo tree-sitter-fsharp 0.3.0): every type signature is
317-
// wrapped in `curried_spec`. For a function it contains `arguments_spec`
318-
// children; for a plain value (e.g. `val pi : float`) it wraps a single
319-
// `simple_type`.
320-
// Treat both engines consistently by classifying as a function whenever
321-
// function_type appears OR a curried_spec contains an arguments_spec child.
320+
// The grammar wraps every type signature in `curried_spec`. A function type
321+
// (e.g. `val add : int -> int -> int`) contains one or more `arguments_spec`
322+
// children; a plain value (e.g. `val pi : float`) wraps a single `simple_type`.
323+
const curriedSpec = findChild(node, 'curried_spec');
322324
let hasFunctionType = false;
323-
for (let i = 0; i < node.childCount; i++) {
324-
const c = node.child(i);
325-
if (!c) continue;
326-
if (c.type === 'function_type') {
327-
hasFunctionType = true;
328-
break;
329-
}
330-
if (c.type === 'curried_spec') {
331-
for (let j = 0; j < c.childCount; j++) {
332-
if (c.child(j)?.type === 'arguments_spec') {
333-
hasFunctionType = true;
334-
break;
335-
}
325+
if (curriedSpec) {
326+
for (let i = 0; i < curriedSpec.childCount; i++) {
327+
if (curriedSpec.child(i)?.type === 'arguments_spec') {
328+
hasFunctionType = true;
329+
break;
336330
}
337-
if (hasFunctionType) break;
338331
}
339332
}
340333

0 commit comments

Comments
 (0)