solargraph-dr_talon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +17 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +149 -0
- data/Rakefile +13 -0
- data/lib/solargraph/dr_talon/attribute_node.rb +127 -0
- data/lib/solargraph/dr_talon/block_node.rb +105 -0
- data/lib/solargraph/dr_talon/convention.rb +84 -0
- data/lib/solargraph/dr_talon/host.rb +37 -0
- data/lib/solargraph/dr_talon/version.rb +7 -0
- data/lib/solargraph/dr_talon.rb +26 -0
- data/lib/solargraph-dr_talon.rb +5 -0
- data/sig/solargraph/dr_talon.rbs +36 -0
- metadata +73 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 59cb25b83626aa02916b35bc21ed7b4fcd4b79e0c524d722a65f46230cd75b19
|
|
4
|
+
data.tar.gz: 488e29552652a04a9b00e04a89ae975ecac5736ab8c79b20c5271f65d064083f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 21b155a0758aea291a38904e988631650489041d48cbc0dc06886d811c2bf9266af5e5b9c80cc568375b238fda66239731bf01d7c286c9447b517596f2bd48d8
|
|
7
|
+
data.tar.gz: a2fd60d16ce640bec21a5b77a4c2411f305a1b7e95567a5d65eff8b4fa42b231ba898fb3aca8965735d59bc0dcc6109e3b37cb710b9804ecf75885b3fe6c010c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-10-11
|
|
4
|
+
|
|
5
|
+
- Initial release: a Solargraph plugin for the [Talon](https://github.com/asimmons91/talon) game framework.
|
|
6
|
+
- `Talon::Concern` mixins: methods in a `class_methods do ... end` block are defined in the concern's `ClassMethods`
|
|
7
|
+
module, and including or prepending a concern (or a class whose superclass did) adds them, and those of the
|
|
8
|
+
concerns it includes, to the host's class methods. Inside `included do ... end`, `self` gets the concern's own
|
|
9
|
+
class methods and those of the concerns it includes.
|
|
10
|
+
- `prop` and `state` declarations define reader and writer methods, typed by a `@return` tag above the call or
|
|
11
|
+
inferred from a literal `default:`. Declarations in a concern's `included` block belong to the concern.
|
|
12
|
+
- Lifecycle hook blocks in a class body (`on_start`, `on_update`, `on_draw`, `on_destroy`, `on_enter`, `on_exit`,
|
|
13
|
+
any other `on_*`, `set_callback` and `define_callback`) are evaluated on the instance. Their block parameters
|
|
14
|
+
are typed by the hook method's `@yieldparam` tag, which Talon's built-in hooks document as `GTK::Args`.
|
|
15
|
+
|
|
16
|
+
[Unreleased]: https://github.com/asimmons91/solargraph-dr_talon/compare/v0.1.0...HEAD
|
|
17
|
+
[0.1.0]: https://github.com/asimmons91/solargraph-dr_talon/releases/tag/v0.1.0
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"solargraph-dr_talon" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["austin_simmons@fastmail.com"](mailto:"austin_simmons@fastmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Austin Simmons
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# solargraph-dr_talon
|
|
2
|
+
|
|
3
|
+
A [Solargraph](https://solargraph.org) plugin that gives LSP editors autocomplete, hover docs and go-to-definition
|
|
4
|
+
for games built with [Talon](https://github.com/asimmons91/talon), a scene/game object/component framework for
|
|
5
|
+
[DragonRuby](https://dragonruby.org).
|
|
6
|
+
|
|
7
|
+
Solargraph already indexes Talon's source when it's in your game. This plugin teaches it the parts of Talon that
|
|
8
|
+
are defined by metaprogramming:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
class Mover < Talon::Component
|
|
12
|
+
prop :speed, default: 2 # speed / speed= (Integer), from prop and state
|
|
13
|
+
state :x, default: 0 # prop, state, on_update, ... complete in the class body
|
|
14
|
+
|
|
15
|
+
on_update do |args| # hook blocks run on the instance: self is a Mover
|
|
16
|
+
self.x += speed # so props, state, go, scene and attr_dr helpers complete
|
|
17
|
+
args.outputs.sprites << { ... } # args is GTK::Args in Talon's built-in hooks
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module Visible
|
|
22
|
+
extend Talon::Concern
|
|
23
|
+
include Talon::Configurable
|
|
24
|
+
|
|
25
|
+
included do
|
|
26
|
+
prop :visible, default: true # Lamp.new.visible completes
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class_methods do
|
|
30
|
+
def hidden_by_default # Lamp.hidden_by_default completes
|
|
31
|
+
prop_defaults[:visible] = false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Lamp < Talon::Component
|
|
37
|
+
include Visible
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- A CRuby install. DragonRuby doesn't need one, but Solargraph does.
|
|
44
|
+
- Solargraph 0.61.x: `gem install solargraph -v '~> 0.61.0'`
|
|
45
|
+
- Talon's source in your Solargraph workspace, for example vendored in `mygame/lib/talon`. Vendor Talon from
|
|
46
|
+
commit 6625c07 or later so hook block parameters are typed as `GTK::Args`.
|
|
47
|
+
- [solargraph-dragonruby](https://github.com/asimmons91/solargraph-dragonruby), recommended, for the DragonRuby
|
|
48
|
+
API (`args`, `outputs`, `attr_dr`, ...). This plugin works without it, but `args`, `outputs` and the other
|
|
49
|
+
DragonRuby helpers won't complete.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
Install the gems into the same Ruby that runs `solargraph`:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
gem install solargraph-dragonruby solargraph-dr_talon
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then add both plugins to the `.solargraph.yml` in your game's root directory (`solargraph config` generates a
|
|
60
|
+
default one), and make sure `include` covers Talon's source:
|
|
61
|
+
|
|
62
|
+
```yml
|
|
63
|
+
include:
|
|
64
|
+
- "mygame/**/*.rb"
|
|
65
|
+
plugins:
|
|
66
|
+
- solargraph-dragonruby
|
|
67
|
+
- solargraph-dr_talon
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Restart your editor's language server. See solargraph-dragonruby's README for editor setup.
|
|
71
|
+
|
|
72
|
+
## What's covered
|
|
73
|
+
|
|
74
|
+
- **`Talon::Concern`**
|
|
75
|
+
- Methods in a `class_methods do ... end` block are defined in the concern's `ClassMethods` module, which is how
|
|
76
|
+
they exist at runtime.
|
|
77
|
+
- Including or prepending a concern adds its class methods to the host, along with those of every concern it
|
|
78
|
+
includes (its dependencies). Subclasses inherit them, so `prop`, `state`, `set_callback`, `prop_defaults`, ...
|
|
79
|
+
complete in any subclass of `Talon::Component`, `Talon::Scene`, `Talon::GameObject` or `Talon::Game`.
|
|
80
|
+
- Inside `included do ... end` (and `prepended do ... end`), `self` is the class that includes the concern, which
|
|
81
|
+
can't be known statically. So the concern's own class methods and those of the concerns it includes complete
|
|
82
|
+
there. To use `prop` in an `included` block, have the concern `include Talon::Configurable` (for `state`,
|
|
83
|
+
`Talon::Stateful`, and for `on_*` hooks, `Talon::Callbacks`). Talon then also includes them into the host.
|
|
84
|
+
- **`prop` and `state`** define a reader and a writer.
|
|
85
|
+
- The type comes from a `@return` tag in the comment above the call, or is inferred from a literal `default:`
|
|
86
|
+
(`2` is an `Integer`, `"a"` a `String`, `true` a `Boolean`, `Vector.new(...)` a `Vector`, ...).
|
|
87
|
+
- The comment becomes the hover docs, and go-to-definition jumps to the declaration.
|
|
88
|
+
- A declaration in a concern's `included` block defines the methods on the concern, so every host gets them.
|
|
89
|
+
- **Lifecycle hook blocks** in a class body are evaluated on the instance, as Talon runs them.
|
|
90
|
+
- This applies to `on_start`, `on_update`, `on_draw`, `on_destroy`, `on_enter` and `on_exit`, and to `set_callback`
|
|
91
|
+
and `define_callback`.
|
|
92
|
+
- It also covers any other `on_*` call, so custom hooks (`def self.on_hit(&b) = set_callback(:hit, &b)`) work
|
|
93
|
+
too.
|
|
94
|
+
- Block parameters are typed by the hook method's `@yieldparam` tag. Talon's built-in hooks document theirs as
|
|
95
|
+
`GTK::Args` (since Talon 6625c07; older copies leave `args` untyped). Document a custom hook the same way:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# @yieldparam enemy [Enemy]
|
|
99
|
+
def self.on_hit(*method_names, &block) = set_callback(:hit, *method_names, &block)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Limitations
|
|
103
|
+
|
|
104
|
+
- The plugin recognizes Talon's DSLs by name, so it doesn't check that a class actually descends from a Talon
|
|
105
|
+
class:
|
|
106
|
+
- `prop :name` and `state :name` called in any class body define accessors.
|
|
107
|
+
- A block passed to any bare `on_*` call in a class body is evaluated on the instance.
|
|
108
|
+
- A concern's own class side (`Visible.hidden_by_default`) also completes its `ClassMethods`. This is what makes
|
|
109
|
+
them complete inside `included do ... end`, but calling them on the concern itself fails at runtime.
|
|
110
|
+
- The Talon methods behind a hook block (such as `update` from `on_update`) are generated at runtime and don't
|
|
111
|
+
complete.
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
bin/setup # install dependencies
|
|
117
|
+
bundle exec rake # tests + standardrb
|
|
118
|
+
bundle exec rake fixtures:sync # re-vendor Talon's lib/ into test/fixtures from main
|
|
119
|
+
bundle exec rake fixtures:sync[<ref>] # ...or from a specific branch, tag or commit
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- `test/fixtures/talon` is a snapshot of Talon's `lib/` that the tests run against. `UPSTREAM` records its commit.
|
|
123
|
+
After a Talon change, sync it and re-run the tests.
|
|
124
|
+
- The Solargraph dependency is pinned to `~> 0.61.0`, because the node processor and convention internals this
|
|
125
|
+
plugin hooks into change between minor releases. Re-run the tests before widening it.
|
|
126
|
+
|
|
127
|
+
### Releasing
|
|
128
|
+
|
|
129
|
+
Add changes to the `## [Unreleased]` section of `CHANGELOG.md` as you go. To release:
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
bundle exec rake release:prepare[minor] # or major, patch, or an explicit version like 1.2.3
|
|
133
|
+
git push origin main v0.2.0 # the command it prints
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`release:prepare` runs the tests, bumps `version.rb` and `Gemfile.lock`, moves the Unreleased notes under a
|
|
137
|
+
dated heading, and commits and tags the release. The first release can use the current version:
|
|
138
|
+
`rake release:prepare[0.1.0]`. Pushing the tag runs `.github/workflows/release.yml`. It checks that the tag,
|
|
139
|
+
version and changelog agree, publishes the gem to RubyGems.org with
|
|
140
|
+
[Trusted Publishing](https://guides.rubygems.org/trusted-publishing/), and creates the GitHub release from that
|
|
141
|
+
version's changelog notes.
|
|
142
|
+
|
|
143
|
+
One-time setup: on RubyGems.org, add a trusted publisher for this repository with the workflow `release.yml` and
|
|
144
|
+
the environment `release`. The gem has never been pushed, so add it as a "pending" trusted publisher.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
149
|
+
The vendored Talon source in `test/fixtures/talon` is also MIT licensed, by Austin Simmons.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "minitest/test_task"
|
|
5
|
+
|
|
6
|
+
Minitest::TestTask.create do |t|
|
|
7
|
+
# Solargraph and its dependencies emit many -w warnings on Ruby 4.0.
|
|
8
|
+
t.warning = false
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require "standard/rake"
|
|
12
|
+
|
|
13
|
+
task default: %i[test standard]
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
module DrTalon
|
|
5
|
+
# Maps Talon's attribute DSLs to reader and writer pins:
|
|
6
|
+
#
|
|
7
|
+
# prop :speed, default: 2 # Talon::Configurable
|
|
8
|
+
# state :score, default: 0 # Talon::Stateful
|
|
9
|
+
#
|
|
10
|
+
# A `@return` tag in the comment above the call sets the type; otherwise
|
|
11
|
+
# it's inferred from a literal `default:`.
|
|
12
|
+
#
|
|
13
|
+
# This is a node processor rather than a Convention#local because
|
|
14
|
+
# Solargraph 0.61 doesn't notice when a file's convention pins change, so
|
|
15
|
+
# adding or removing a declaration wouldn't take effect until a restart.
|
|
16
|
+
class AttributeNode < Solargraph::Parser::NodeProcessor::Base
|
|
17
|
+
DSLS = {
|
|
18
|
+
prop: "Talon prop",
|
|
19
|
+
state: "Talon state value, stored in `args.state`"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
# Literal node types and the type of the value they produce.
|
|
23
|
+
LITERAL_TYPES = {
|
|
24
|
+
int: "Integer",
|
|
25
|
+
float: "Float",
|
|
26
|
+
rational: "Rational",
|
|
27
|
+
str: "String",
|
|
28
|
+
dstr: "String",
|
|
29
|
+
sym: "Symbol",
|
|
30
|
+
dsym: "Symbol",
|
|
31
|
+
# standard:disable Lint/BooleanSymbol -- these are AST node types
|
|
32
|
+
true: "Boolean",
|
|
33
|
+
false: "Boolean",
|
|
34
|
+
# standard:enable Lint/BooleanSymbol
|
|
35
|
+
array: "Array",
|
|
36
|
+
hash: "Hash",
|
|
37
|
+
irange: "Range",
|
|
38
|
+
erange: "Range"
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
# Runs after Solargraph's own SendNode, which has already processed the
|
|
42
|
+
# children, so this only adds the attribute pins.
|
|
43
|
+
#
|
|
44
|
+
# @return [Boolean] true, so later :send processors still run
|
|
45
|
+
def process
|
|
46
|
+
receiver, dsl, attribute, *options = node.children
|
|
47
|
+
return true unless receiver.nil? && DSLS.key?(dsl) && attribute&.type == :sym
|
|
48
|
+
|
|
49
|
+
host = Host.namespace(region)
|
|
50
|
+
return true unless host
|
|
51
|
+
|
|
52
|
+
name = attribute.children[0].to_s
|
|
53
|
+
comments = attribute_comments(dsl, options)
|
|
54
|
+
reader = method_pin(host, name, comments)
|
|
55
|
+
writer = method_pin(host, "#{name}=", comments)
|
|
56
|
+
writer.parameters.push Solargraph::Pin::Parameter.new(name: "value", decl: :arg, closure: writer, source: :dr_talon)
|
|
57
|
+
if reader.return_type.defined?
|
|
58
|
+
writer.docstring.add_tag YARD::Tags::Tag.new(:param, "", reader.return_type.items.map(&:rooted_tags), "value")
|
|
59
|
+
end
|
|
60
|
+
pins.push reader, writer
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# @param host [Solargraph::Pin::Namespace]
|
|
67
|
+
# @param name [String]
|
|
68
|
+
# @param comments [String]
|
|
69
|
+
# @return [Solargraph::Pin::Method]
|
|
70
|
+
def method_pin(host, name, comments)
|
|
71
|
+
# Talon defines these with attr_accessor and define_method inside its
|
|
72
|
+
# own methods, so they're public whatever the caller's visibility.
|
|
73
|
+
Solargraph::Pin::Method.new(
|
|
74
|
+
location: location,
|
|
75
|
+
closure: host,
|
|
76
|
+
name: name,
|
|
77
|
+
comments: comments,
|
|
78
|
+
scope: :instance,
|
|
79
|
+
visibility: :public,
|
|
80
|
+
attribute: true,
|
|
81
|
+
source: :dr_talon
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The comment above the call, or a description of the declaration, plus
|
|
86
|
+
# an inferred `@return` tag if the comment doesn't have one.
|
|
87
|
+
#
|
|
88
|
+
# @param dsl [Symbol]
|
|
89
|
+
# @param options [Array<Parser::AST::Node>]
|
|
90
|
+
# @return [String]
|
|
91
|
+
def attribute_comments(dsl, options)
|
|
92
|
+
comments = comments_for(node).to_s
|
|
93
|
+
comments = "#{DSLS[dsl]}: `#{region.code_for(node)}`" if comments.strip.empty?
|
|
94
|
+
return comments if comments.match?(/^\s*@return\b/)
|
|
95
|
+
|
|
96
|
+
type = inferred_type(options)
|
|
97
|
+
type ? "#{comments}\n@return [#{type}]" : comments
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @param options [Array<Parser::AST::Node>]
|
|
101
|
+
# @return [String, nil]
|
|
102
|
+
def inferred_type(options)
|
|
103
|
+
default = default_value(options)
|
|
104
|
+
return unless default
|
|
105
|
+
return LITERAL_TYPES[default.type] if LITERAL_TYPES.key?(default.type)
|
|
106
|
+
|
|
107
|
+
# Foo.new(...)
|
|
108
|
+
receiver, method = default.children
|
|
109
|
+
if default.type == :send && method == :new && receiver&.type == :const
|
|
110
|
+
region.code_for(receiver)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @param options [Array<Parser::AST::Node>]
|
|
115
|
+
# @return [Parser::AST::Node, nil]
|
|
116
|
+
def default_value(options)
|
|
117
|
+
hash = options.last
|
|
118
|
+
return unless hash&.type == :hash || hash&.type == :kwargs
|
|
119
|
+
|
|
120
|
+
pair = hash.children.find do |child|
|
|
121
|
+
child.type == :pair && child.children[0].type == :sym && child.children[0].children[0] == :default
|
|
122
|
+
end
|
|
123
|
+
pair&.children&.last
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
module DrTalon
|
|
5
|
+
# Maps the blocks that Talon evaluates somewhere other than where they're
|
|
6
|
+
# written, and defers every other block to Solargraph's BlockNode:
|
|
7
|
+
#
|
|
8
|
+
# * `class_methods do ... end` in a concern: its methods are defined in the
|
|
9
|
+
# concern's `ClassMethods` module (see Talon::Concern#class_methods).
|
|
10
|
+
# * `included do ... end` and `prepended do ... end` in a concern: `self` is
|
|
11
|
+
# the host, so they get the concern's class side (see Convention).
|
|
12
|
+
# * Lifecycle hook blocks in a class body (`on_update do ... end`, and any
|
|
13
|
+
# other bare `on_*`, `set_callback` or `define_callback` call): Talon
|
|
14
|
+
# evaluates them on an instance, so `self` is the instance. Block
|
|
15
|
+
# parameters are typed by the hook method's own `@yieldparam` tags.
|
|
16
|
+
class BlockNode < Solargraph::Parser::ParserGem::NodeProcessors::BlockNode
|
|
17
|
+
# Methods that register a block as an instance callback, besides `on_*`.
|
|
18
|
+
CALLBACK_METHODS = %i[set_callback define_callback].freeze
|
|
19
|
+
|
|
20
|
+
# @return [Boolean] false when this processor mapped the block, so
|
|
21
|
+
# later :block processors don't map it again
|
|
22
|
+
def process
|
|
23
|
+
call = node.children[0]
|
|
24
|
+
return super unless call.type == :send && call.children[0].nil?
|
|
25
|
+
|
|
26
|
+
name = call.children[1]
|
|
27
|
+
concern = concern_namespace
|
|
28
|
+
if name == :class_methods && call.children.length == 2 && concern
|
|
29
|
+
process_class_methods(concern)
|
|
30
|
+
elsif Host::CONCERN_BLOCKS.include?(name) && concern
|
|
31
|
+
process_bound_block("Class<#{concern.path}>", :class, comments_for(node))
|
|
32
|
+
elsif hook?(name) && (host = Host.namespace(region))
|
|
33
|
+
process_bound_block(host.path, :instance, comments_for(node))
|
|
34
|
+
else
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# @return [Solargraph::Pin::Namespace, nil] the module whose body this
|
|
42
|
+
# block is in
|
|
43
|
+
def concern_namespace
|
|
44
|
+
closure = region.closure
|
|
45
|
+
closure if region.scope != :class && closure.is_a?(Solargraph::Pin::Namespace) && closure.type == :module
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param name [Symbol]
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
def hook?(name)
|
|
51
|
+
name.start_with?("on_") || CALLBACK_METHODS.include?(name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param concern [Solargraph::Pin::Namespace]
|
|
55
|
+
# @return [false]
|
|
56
|
+
def process_class_methods(concern)
|
|
57
|
+
class_methods = Solargraph::Pin::Namespace.new(
|
|
58
|
+
type: :module,
|
|
59
|
+
location: location,
|
|
60
|
+
closure: concern,
|
|
61
|
+
name: "ClassMethods",
|
|
62
|
+
comments: comments_for(node),
|
|
63
|
+
visibility: :public,
|
|
64
|
+
gates: concern.gates.freeze,
|
|
65
|
+
source: :dr_talon
|
|
66
|
+
)
|
|
67
|
+
pins.push class_methods
|
|
68
|
+
body = node.children[2]
|
|
69
|
+
Solargraph::Parser::NodeProcessor.process(body, region.update(closure: class_methods, visibility: :public), pins, locals, ivars)
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param context [String] the type of `self` in the block
|
|
74
|
+
# @param scope [Symbol]
|
|
75
|
+
# @param comments [String]
|
|
76
|
+
# @return [false]
|
|
77
|
+
def process_bound_block(context, scope, comments)
|
|
78
|
+
block_pin = BoundBlock.new(
|
|
79
|
+
location: location,
|
|
80
|
+
closure: region.closure,
|
|
81
|
+
node: node,
|
|
82
|
+
context: Solargraph::ComplexType.try_parse(context),
|
|
83
|
+
receiver: node.children[0],
|
|
84
|
+
comments: comments,
|
|
85
|
+
scope: scope,
|
|
86
|
+
source: :dr_talon
|
|
87
|
+
)
|
|
88
|
+
pins.push block_pin
|
|
89
|
+
process_children region.update(closure: block_pin)
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# A block whose `self` the plugin knows from Talon's semantics. Solargraph
|
|
95
|
+
# rebinds a block from its method's `@yieldreceiver` tag, but Talon's tags
|
|
96
|
+
# are generic (Concern#included says `[Class]`), so this keeps the context
|
|
97
|
+
# it was mapped with.
|
|
98
|
+
class BoundBlock < Solargraph::Pin::Block
|
|
99
|
+
# @param _api_map [Solargraph::ApiMap]
|
|
100
|
+
# @return [void]
|
|
101
|
+
def rebind(_api_map)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
module DrTalon
|
|
5
|
+
# Adds class methods from Talon::Concern mixins. Including a concern
|
|
6
|
+
# extends the host with the concern's `ClassMethods`, and with those of
|
|
7
|
+
# every concern it includes (its dependencies).
|
|
8
|
+
#
|
|
9
|
+
# Inside a concern, `self` in an `included do ... end` block is the host,
|
|
10
|
+
# which can't be known statically, so the concern's own class side gets
|
|
11
|
+
# its `ClassMethods` and its dependencies'.
|
|
12
|
+
#
|
|
13
|
+
# Resolved when methods are looked up rather than when files are parsed,
|
|
14
|
+
# since whether an included module is a concern depends on other files.
|
|
15
|
+
class Convention < Solargraph::Convention::Base
|
|
16
|
+
CONCERN = "Talon::Concern"
|
|
17
|
+
|
|
18
|
+
# @param api_map [Solargraph::ApiMap]
|
|
19
|
+
# @param rooted_tag [String]
|
|
20
|
+
# @param scope [Symbol] :class or :instance
|
|
21
|
+
# @param visibility [Array<Symbol>]
|
|
22
|
+
# @param deep [Boolean]
|
|
23
|
+
# @param skip [Set<String>]
|
|
24
|
+
# @param _no_core [Boolean]
|
|
25
|
+
# @return [Solargraph::Environ]
|
|
26
|
+
def object(api_map, rooted_tag, scope, visibility, deep, skip, _no_core)
|
|
27
|
+
return EMPTY_ENVIRON unless scope == :class
|
|
28
|
+
|
|
29
|
+
rooted_type = Solargraph::ComplexType.parse(rooted_tag).force_rooted
|
|
30
|
+
fqns = rooted_type.namespace
|
|
31
|
+
namespace_pin = api_map.get_path_pins(fqns).find { |pin| pin.is_a?(Solargraph::Pin::Namespace) }
|
|
32
|
+
return EMPTY_ENVIRON unless namespace_pin
|
|
33
|
+
|
|
34
|
+
concerns = []
|
|
35
|
+
concerns << fqns if concern?(api_map, fqns)
|
|
36
|
+
collect_concerns(api_map, fqns, concerns)
|
|
37
|
+
return EMPTY_ENVIRON if concerns.empty?
|
|
38
|
+
|
|
39
|
+
pins = concerns.flat_map do |concern|
|
|
40
|
+
api_map.inner_get_methods_from_reference("#{concern}::ClassMethods", namespace_pin, rooted_type,
|
|
41
|
+
:instance, visibility, deep, skip, true)
|
|
42
|
+
end
|
|
43
|
+
Solargraph::Environ.new(pins: pins)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Appends the concerns that `fqns` includes or prepends, and theirs, in
|
|
49
|
+
# the order Talon includes them into a host.
|
|
50
|
+
#
|
|
51
|
+
# @param api_map [Solargraph::ApiMap]
|
|
52
|
+
# @param fqns [String]
|
|
53
|
+
# @param concerns [Array<String>]
|
|
54
|
+
# @return [void]
|
|
55
|
+
def collect_concerns(api_map, fqns, concerns)
|
|
56
|
+
mixins(api_map, fqns).each do |ref|
|
|
57
|
+
mixin = api_map.dereference(ref)
|
|
58
|
+
next if mixin.nil? || concerns.include?(mixin) || !concern?(api_map, mixin)
|
|
59
|
+
|
|
60
|
+
collect_concerns(api_map, mixin, concerns)
|
|
61
|
+
concerns << mixin
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param api_map [Solargraph::ApiMap]
|
|
66
|
+
# @param fqns [String]
|
|
67
|
+
# @return [Array<Solargraph::Pin::Reference>]
|
|
68
|
+
def mixins(api_map, fqns)
|
|
69
|
+
# ApiMap has no public prepend lookup.
|
|
70
|
+
api_map.get_includes(fqns) + api_map.send(:store).get_prepends(fqns)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param api_map [Solargraph::ApiMap]
|
|
74
|
+
# @param fqns [String]
|
|
75
|
+
# @return [Boolean]
|
|
76
|
+
def concern?(api_map, fqns)
|
|
77
|
+
api_map.get_extends(fqns).any? do |ref|
|
|
78
|
+
# Fall back to the written name when Talon isn't in the workspace.
|
|
79
|
+
(api_map.dereference(ref) || ref.name.delete_prefix("::")) == CONCERN
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
module DrTalon
|
|
5
|
+
# Finds the class or module that a Talon class-body DSL call applies to.
|
|
6
|
+
module Host
|
|
7
|
+
# Blocks that a Talon::Concern evaluates in the including class. A DSL
|
|
8
|
+
# call inside one declares on the concern, so its hosts inherit it.
|
|
9
|
+
CONCERN_BLOCKS = %i[included prepended].freeze
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# The namespace whose class body `region` is in, looking through a
|
|
14
|
+
# concern's `included`/`prepended` block. Nil inside methods, other
|
|
15
|
+
# blocks, `class << self`, and at the top level.
|
|
16
|
+
#
|
|
17
|
+
# @param region [Solargraph::Parser::Region]
|
|
18
|
+
# @return [Solargraph::Pin::Namespace, nil]
|
|
19
|
+
def namespace(region)
|
|
20
|
+
return if region.scope == :class
|
|
21
|
+
|
|
22
|
+
closure = region.closure
|
|
23
|
+
closure = closure.closure if concern_block?(closure)
|
|
24
|
+
closure if closure.is_a?(Solargraph::Pin::Namespace) && !closure.path.empty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param pin [Solargraph::Pin::Base]
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def concern_block?(pin)
|
|
30
|
+
return false unless pin.is_a?(Solargraph::Pin::Block) && pin.receiver&.type == :send
|
|
31
|
+
|
|
32
|
+
receiver, name = pin.receiver.children
|
|
33
|
+
receiver.nil? && CONCERN_BLOCKS.include?(name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "solargraph"
|
|
4
|
+
require_relative "dr_talon/version"
|
|
5
|
+
require_relative "dr_talon/host"
|
|
6
|
+
require_relative "dr_talon/block_node"
|
|
7
|
+
require_relative "dr_talon/attribute_node"
|
|
8
|
+
require_relative "dr_talon/convention"
|
|
9
|
+
|
|
10
|
+
module Solargraph
|
|
11
|
+
# Solargraph plugin for the Talon game framework for DragonRuby. Talon itself
|
|
12
|
+
# is indexed from the game's workspace; this plugin teaches Solargraph the
|
|
13
|
+
# metaprogramming that static parsing misses.
|
|
14
|
+
module DrTalon
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Solargraph::Convention.register Solargraph::DrTalon::Convention
|
|
19
|
+
|
|
20
|
+
# DrTalon::BlockNode handles `class_methods` and hook blocks itself and defers
|
|
21
|
+
# every other block to Solargraph's BlockNode, which it subclasses. It replaces
|
|
22
|
+
# that processor rather than registering beside it, because processors run in
|
|
23
|
+
# registration order and Solargraph's would otherwise map the block first.
|
|
24
|
+
Solargraph::Parser::NodeProcessor.deregister :block, Solargraph::Parser::ParserGem::NodeProcessors::BlockNode
|
|
25
|
+
Solargraph::Parser::NodeProcessor.register :block, Solargraph::DrTalon::BlockNode
|
|
26
|
+
Solargraph::Parser::NodeProcessor.register :send, Solargraph::DrTalon::AttributeNode
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Solargraph
|
|
2
|
+
module DrTalon
|
|
3
|
+
VERSION: String
|
|
4
|
+
|
|
5
|
+
module Host
|
|
6
|
+
CONCERN_BLOCKS: Array[Symbol]
|
|
7
|
+
|
|
8
|
+
def self.namespace: (Solargraph::Parser::Region region) -> Solargraph::Pin::Namespace?
|
|
9
|
+
|
|
10
|
+
def self.concern_block?: (Solargraph::Pin::Base pin) -> bool
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class BlockNode < Solargraph::Parser::ParserGem::NodeProcessors::BlockNode
|
|
14
|
+
CALLBACK_METHODS: Array[Symbol]
|
|
15
|
+
|
|
16
|
+
def process: () -> bool
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class BoundBlock < Solargraph::Pin::Block
|
|
20
|
+
def rebind: (Solargraph::ApiMap _api_map) -> void
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class AttributeNode < Solargraph::Parser::NodeProcessor::Base
|
|
24
|
+
DSLS: Hash[Symbol, String]
|
|
25
|
+
LITERAL_TYPES: Hash[Symbol, String]
|
|
26
|
+
|
|
27
|
+
def process: () -> bool
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Convention < Solargraph::Convention::Base
|
|
31
|
+
CONCERN: String
|
|
32
|
+
|
|
33
|
+
def object: (Solargraph::ApiMap api_map, String rooted_tag, Symbol scope, Array[Symbol] visibility, bool deep, Set[String] skip, bool _no_core) -> Solargraph::Environ
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: solargraph-dr_talon
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Austin Simmons
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: solargraph
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.61.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.61.0
|
|
26
|
+
description: 'Teaches Solargraph the metaprogramming in the Talon game framework for
|
|
27
|
+
DragonRuby: Talon::Concern mixins (class_methods, included), the prop and state
|
|
28
|
+
DSLs, and on_* lifecycle hook blocks, so LSP editors get autocomplete, hover and
|
|
29
|
+
go-to-definition in Talon games.'
|
|
30
|
+
email:
|
|
31
|
+
- austin_simmons@fastmail.com
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- CHANGELOG.md
|
|
37
|
+
- CODE_OF_CONDUCT.md
|
|
38
|
+
- LICENSE.txt
|
|
39
|
+
- README.md
|
|
40
|
+
- Rakefile
|
|
41
|
+
- lib/solargraph-dr_talon.rb
|
|
42
|
+
- lib/solargraph/dr_talon.rb
|
|
43
|
+
- lib/solargraph/dr_talon/attribute_node.rb
|
|
44
|
+
- lib/solargraph/dr_talon/block_node.rb
|
|
45
|
+
- lib/solargraph/dr_talon/convention.rb
|
|
46
|
+
- lib/solargraph/dr_talon/host.rb
|
|
47
|
+
- lib/solargraph/dr_talon/version.rb
|
|
48
|
+
- sig/solargraph/dr_talon.rbs
|
|
49
|
+
homepage: https://github.com/asimmons91/solargraph-dr_talon
|
|
50
|
+
licenses:
|
|
51
|
+
- MIT
|
|
52
|
+
metadata:
|
|
53
|
+
source_code_uri: https://github.com/asimmons91/solargraph-dr_talon
|
|
54
|
+
changelog_uri: https://github.com/asimmons91/solargraph-dr_talon/blob/main/CHANGELOG.md
|
|
55
|
+
rubygems_mfa_required: 'true'
|
|
56
|
+
rdoc_options: []
|
|
57
|
+
require_paths:
|
|
58
|
+
- lib
|
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: 3.2.0
|
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
requirements: []
|
|
70
|
+
rubygems_version: 4.0.20
|
|
71
|
+
specification_version: 4
|
|
72
|
+
summary: Solargraph plugin providing Talon game framework completion for DragonRuby
|
|
73
|
+
test_files: []
|