Skip to content

Fix DocumentedPublicApis crash on non-Node left siblings - #102

Open
armandrius wants to merge 2 commits into
rubyatscale:mainfrom
armandrius:fix-documented-public-apis-non-node-siblings
Open

Fix DocumentedPublicApis crash on non-Node left siblings#102
armandrius wants to merge 2 commits into
rubyatscale:mainfrom
armandrius:fix-documented-public-apis-non-node-siblings

Conversation

@armandrius

Copy link
Copy Markdown

Fixes #101

Summary

Two shapes of method definition made Packs/DocumentedPublicApis raise NoMethodError rather than report an offense. Both come from one assumption in node_is_sorbet_signature?, that a left sibling which is not nil is an AST node with source.

A method that is the leading statement of an argument-less block has the block's own empty args node as its sibling. That node has no source range, so Node#source returns nil and nil.include? raises. Struct.new(:a) do, Data.define(:a) do and class_eval do all hit it.

A method passed to a modifier other than private_class_method has a Symbol as its sibling, and Symbol has no #source.

The first commit guards against both. The second widens the existing private_class_method branch to any Symbol sibling, because otherwise the first commit converts the private def crash into a false positive: documentation_comment? looks up comments attached to the node it is handed, and for private def foo the comment attaches to the enclosing send, so a documented method gets flagged. That is the same reason #56 special-cased private_class_method in the first place.

The second commit is independently droppable if you would rather take only the crash fix.

Motivation

Found on a pack whose public API is a set of Data.define(...) do ... end value objects with instance methods. The cop cannot evaluate any file using that idiom.

Behaviour

Widening the branch to any Symbol is a superset of the old condition, so every input that took the old branch still takes it. The only newly routed inputs are the ones that used to raise. All 11 pre-existing examples keep byte-identical offense ranges.

One genuinely new behaviour worth flagging: a method defined on the right-hand side of an assignment now consults the comment above the assignment instead of raising, because a casgn also leaves a Symbol sibling.

Tests

Nine new examples. Five cover methods inside blocks: argument-less and undocumented, argument-less and documented, a sig with documentation above it, a sig with no documentation, and a block that declares parameters as a regression guard. Four mirror the existing private_class_method matrix using private def.

Notes

manual/ is unchanged. The class docstring was not touched, and tasks/cop_documentation.rake renders only the docstring and its examples rather than method-body comments, so CI=true VERIFYING_DOCUMENTATION=true bundle exec rake generate_cops_documentation passes with no diff.

Verified locally: 90 examples and 0 failures at 100 percent line and branch coverage, srb tc clean, rubocop clean.

main has moved on from the released v0.0.45, so this needs a release before affected consumers can pick it up.

cc @alexevanczuk (wrote the private_class_method handling in #56) and @dduugg

An argument-less block's empty args node has no source range, and a
modifier other than private_class_method leaves a Symbol as the sibling.
Both raised NoMethodError instead of reporting an offense.
A documentation comment above `private def foo` attaches to the enclosing
send, not to the definition, so the cop flagged documented methods. Route
every Symbol sibling through the branch that already handled
private_class_method.
@armandrius
armandrius requested a review from a team as a code owner July 28, 2026 16:44
@github-project-automation github-project-automation Bot moved this to Triage in Modularity Jul 28, 2026
@armandrius

Copy link
Copy Markdown
Author

@conwayje

Thanks for the review.

Both workflow runs on this PR are still sitting at action_required, so no CI has actually executed yet. Could someone hit "Approve and run workflows" before merging, so the checks produce a real signal?

I ran the equivalent gates locally on every Ruby version the shared-config workflow uses:

Gate 3.3.11 3.4.7 4.0.3
COVERAGE=true bundle exec rspec 90 examples, 0 failures, 100 percent line and branch same same
bundle exec rubocop not run on 3.3 by CI no offenses no offenses
bundle exec srb tc not run on 3.3 by CI no errors no errors

documentation_syntax_check and generate_cops_documentation also pass on 3.3, which is the version that job pins, and manual/ comes out with no diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

Packs/DocumentedPublicApis raises NoMethodError on a def inside an argument-less block

2 participants