Skip to content

Attach doc comments across Sorbet sig blocks in tags query - #12

Open
tarebyte wants to merge 1 commit into
masterfrom
fix/tags-doc-comments-through-sig
Open

Attach doc comments across Sorbet sig blocks in tags query#12
tarebyte wants to merge 1 commit into
masterfrom
fix/tags-doc-comments-through-sig

Conversation

@tarebyte

@tarebyte tarebyte commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Problem

queries/tags.scm attaches a method's documentation with #select-adjacent!, which keeps a comment only while it sits on the row immediately above the anchor node. A Sorbet signature between the comment and the definition breaks that adjacency:

# Adds two numbers together.
sig { params(a: Integer, b: Integer).returns(Integer) }
def add(a, b); end

tree-sitter tags tags add but reports no docs for it.

This affects the comment-then-sig-then-def order only. Writing the signature above the comment already works on master, so this is not every documented method in a Sorbet codebase — it is the conventional ordering.

Change

Adds one pattern that allows a single sig call between the comment and the definition and anchors adjacency on the sig itself. It covers sig { }, sig do … end, sig(:final) { } and T::Sig::WithoutRuntime.sig { }, for both method and singleton_method.

The pattern is placed before the general method pattern because tree-sitter-tags deduplicates by name range and keeps the match from the earliest pattern.

The sig identifier has to be captured to be matched with #eq?, and tree-sitter-tags rejects capture names outside its known set — @_sig and similar are errors — so it is captured as @reference.call. The existing generic call pattern already emits a sig reference, and deduplication means no second one is added.

No grammar change: a signature is an ordinary Ruby block and already parses as a call.

Known limits

Both are asserted in the Rust test so that changing either is deliberate.

  • Consecutive overload signatures are not bridged. tree-sitter-tags anchors #select-adjacent! on the last node captured for the named capture, so with two signatures the anchor is the one nearest the definition and the comment is two rows away. Capturing only the first signature is not expressible, because the extra signatures would need a capture name and every valid name has other side effects.
  • private def is unaffected by this PR and still loses its comment. The definition is an argument to private, not a sibling of the comment. This is independent of Sorbet — a documented private def loses its comment on master too, with or without a signature.

Validation

  • New test/tags/sorbet.rb, picked up by tree-sitter test as tags: sorbet.rb (4 assertions). That harness compares only tag names and spans, so it is a guard against the new pattern changing what gets tagged; it cannot observe docs and does not fail on master.
  • New Rust test in bindings/rust/lib.rs drives tree-sitter-tags directly and asserts the doc strings. It is the test that actually demonstrates the fix: against master's query it fails with left: Some(None), right: Some(Some("Converts to a string.")).
  • 332 corpus parses, 5 highlight files, 1 tags file — zero failures.
  • Tags dumped for all 4,437 examples/ruby_spec files before and after: 274,521 tags on both sides, differing in exactly one line.

That one line is cvar_c | method becoming cvar_c | call, both at (0, 0). It comes from the (setter (identifier) @ignore) pattern. Tag::ignored hardcodes syntax_type_id: 0, and adding @reference.call earlier in the file moves call into index zero. The underlying tag is an ignored sentinel that tree-sitter-tags leaks into its output rather than a real tag, but it is consumer-visible — tree-sitter tags prints method on master and call on this branch. No non-ignored tag changed.

The corpus comparison is a regression check only. examples/ruby_spec contains no line-leading Sorbet sig, so it does not exercise the new pattern.

tree-sitter-tags is added as a dev-dependency; cargo tree -e normal does not include it. Cargo.lock also picks up a transitive memchr bump from 2.7.4 to 2.8.3.

Relationship to tree-sitter#175

This does not resolve that issue and is not a partial fix for it. tree-sitter#175 asks for dedicated AST nodes for Sorbet signatures; the maintainers answered that signatures are ordinary Ruby and belong in post-processing or a derived grammar. This PR changes only the tags query and adds no nodes. The doc-comment bug is a separate defect that happens to be triggered by the same syntax; it is not mentioned in tree-sitter#175.

Merge note

This and #5 both add tests to the same module in bindings/rust/lib.rs and will conflict; whichever lands second needs the other's test block re-applied.

A Sorbet `sig` block sits between a method's doc comment and its
definition, so the comment is not adjacent to the definition and
`#select-adjacent!` discarded it. Every documented method in a
`sig`-annotated codebase lost its doc string in code navigation output.

Add a method pattern that matches an intervening `sig` call and anchors
doc adjacency on that call. It precedes the general pattern because
tree-sitter-tags keeps the match from the earliest pattern when several
match the same node, so no duplicate tags are produced.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac21705f-befb-4606-9c83-657e3e9ea0dc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant