> For the complete documentation index, see [llms.txt](https://light-services.kodkod.me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://light-services.kodkod.me/deep-dive/ruby-lsp.md).

# Ruby LSP Integration

Operandi provides a Ruby LSP add-on that enhances your editor experience by informing the language server about methods generated by the `arg` and `output` DSL keywords.

## Features

When you use the `arg` or `output` keywords, Operandi dynamically generates methods at runtime:

```ruby
class MyService < ApplicationService
  arg :user, type: User
  output :result, type: Hash
end
```

This generates the following methods:

* `user` - getter method (returns `User`)
* `user?` - predicate method (returns boolean)
* `user=` - setter method (private, accepts `User`)
* `result` - getter method (returns `Hash`)
* `result?` - predicate method (returns boolean)
* `result=` - setter method (private, accepts `Hash`)

The Ruby LSP add-on teaches the language server about these generated methods, enabling:

* **Go to Definition** - Navigate to the `arg`/`output` declaration
* **Completion** - Autocomplete generated method names
* **Hover** - See information about generated methods, including return types
* **Signature Help** - Get parameter hints for setter methods
* **Workspace Symbol** - Find generated methods in symbol search

## Setup

The add-on is automatically discovered by Ruby LSP when Operandi is in your project's dependencies. No additional configuration is required.

### Requirements

* Ruby LSP `~> 0.26` or later
* Operandi gem installed in your project

### Verification

To verify the add-on is loaded, check the Ruby LSP output in your editor. You should see "Ruby LSP Operandi" listed among the active add-ons.

## How It Works

The add-on uses Ruby LSP's **indexing enhancement** system to register generated methods during code indexing. When the indexer encounters an `arg` or `output` call with a symbol argument, it automatically registers the three generated methods (getter, predicate, setter) in the index.

This is a static analysis approach - the add-on analyzes your source code without executing it. This means:

* Methods are recognized immediately as you type
* No running application is required
* Works with any editor that supports Ruby LSP

## Type Inference

The add-on extracts type information from the `type:` option and includes it as YARD-style documentation comments. This enables hover information to display return types for generated methods.

### Simple Ruby Types

```ruby
arg :user, type: User      # → User
arg :items, type: Array    # → Array
arg :name, type: String    # → String
```

### Namespaced Types

```ruby
arg :payment, type: Stripe::Charge      # → Stripe::Charge
arg :config, type: MyApp::Configuration # → MyApp::Configuration
```

### Sorbet Runtime Types

Sorbet runtime types are resolved to their underlying Ruby types:

| Sorbet Type               | Ruby Type                 |
| ------------------------- | ------------------------- |
| `T::Boolean`              | `TrueClass \| FalseClass` |
| `T.nilable(String)`       | `String \| NilClass`      |
| `T::Array[String]`        | `Array`                   |
| `T::Hash[Symbol, String]` | `Hash`                    |
| `T.any(String, Integer)`  | `String \| Integer`       |

### Custom Type Mappings

You can add custom type mappings through the Operandi configuration:

```ruby
# config/initializers/operandi.rb
Operandi.configure do |config|
  config.ruby_lsp_type_mappings = {
    "Types::UUID" => "String",
    "Types::Money" => "BigDecimal",
    "Types::JSON" => "Hash",
    "CustomTypes::Email" => "String",
    "MyApp::Types::PhoneNumber" => "String",
  }
end
```

Custom mappings allow you to:

* Add mappings for your own custom types
* Override default mappings if needed
* Support domain-specific type modules

## Limitations

* Only `arg` and `output` declarations with a symbol as the first argument are recognized
* The add-on cannot detect dynamically computed argument names (e.g., `arg some_variable`)
* Inherited arguments/outputs from parent classes are not automatically discovered
* Parameterized types like `T::Array[String]` resolve to the container type (`Array`), not the full generic type

## What's Next?

Learn more about other integrations:

* [RuboCop Integration](/deep-dive/rubocop.md) - Static analysis cops for services
* [Testing](/deep-dive/testing.md) - Testing your services with RSpec matchers
