Skip to content

Highlight private/protected/public as methods - #8

Merged
tarebyte merged 1 commit into
masterfrom
fix/highlight-visibility-methods
Aug 24, 2026
Merged

Highlight private/protected/public as methods#8
tarebyte merged 1 commit into
masterfrom
fix/highlight-visibility-methods

Conversation

@tarebyte

@tarebyte tarebyte commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Problem

queries/highlights.scm grouped private, protected and public into the same bracketed list as def, end, class and module, giving them @keyword — the identical capture real keywords get.

They are not keywords. They are Module instance methods:

Module.private_instance_methods(false).include?(:private)  # => true
Ripper.lex("private").first[1]                             # => :on_ident   (`def` is :on_kw)

Consequences of being ordinary methods, all of which the @keyword capture contradicted:

private :foo                  # takes arguments
private def bar; end          # takes a def
send(:private, :baz)          # reachable through send
private = 1                   # can be shadowed by a local

Change

Captures all three as @function.method.builtin, in two patterns:

  • A bare private parses as a plain identifier, so it needs an identifier pattern. That pattern carries #is-not? local, which is what keeps a shadowing local variable out — the query file already uses the same guard on the general @function.method identifier pattern directly above it.
  • The argument forms parse as a call, which the general (call method: (identifier) @function.method) pattern already claims. Later patterns win, so covering them takes a second pattern placed after it. !receiver keeps an unrelated method with a colliding name, such as acl.public, an ordinary call.

@function.method.builtin is the capture this query already uses for require, and the require pattern is placed after the general call pattern for exactly the same precedence reason. (defined? also uses it, though that one is Ruby syntax rather than a method call.)

Validation

332 corpus parses and 5 highlight files, zero failures. test/highlight/classes.rb goes from 14 to 19 assertions:

Source Capture
private (bare) function.method.builtin
private :with_symbol function.method.builtin
private def with_def; end function.method.builtin
private = 1 and a later bare private variable
acl.public function.method

The three previously existing bare-form assertions changed from keyword to function.method.builtin. Each new assertion was confirmed to fail without the corresponding pattern — dropping #is-not? local produces expected highlight 'variable', actual highlights: 'function.method.builtin'.

Relationship to tree-sitter#277

Same three identifiers, same file. That PR proposed @function.builtin; this uses @function.method.builtin to match the existing require capture, and additionally handles the argument forms and local shadowing, which tree-sitter#277 does not.

Note that on master a shadowing local was already mis-highlighted, as keyword. That part is a pre-existing bug this PR fixes rather than a regression it introduces.

`private`, `protected` and `public` are `Module` instance methods, not
keywords. They take arguments (`private :foo`), accept a `def`
expression (`private def foo; end`), can be reached through `send`, and
can be shadowed by a local variable. Highlighting them as `@keyword`
made them read like `def` or `end` and diverged from how RubyMine and
Ruby LSP render them.

Capture them as `@function.method.builtin`, matching how this query
already treats other well-known method calls such as `require` and
`defined?`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tarebyte
tarebyte merged commit 36c26ee into master Aug 24, 2026
4 checks passed
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