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 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
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module DrTalon
5
+ VERSION = "0.1.0"
6
+ end
7
+ 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Entry point for `.solargraph.yml` `plugins: [solargraph-dr_talon]`,
4
+ # which Solargraph loads with `require "solargraph-dr_talon"`.
5
+ require_relative "solargraph/dr_talon"
@@ -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: []