solargraph-dr_talon 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59cb25b83626aa02916b35bc21ed7b4fcd4b79e0c524d722a65f46230cd75b19
4
- data.tar.gz: 488e29552652a04a9b00e04a89ae975ecac5736ab8c79b20c5271f65d064083f
3
+ metadata.gz: '0748faad9cf90c06804e66167093ac65dd6f81523d95ead02fd35a0faa462dcd'
4
+ data.tar.gz: 50ace6eb5d6384c7bd15bc12cda516f3c8b6f24f76298a0fc2cd75588d767a6a
5
5
  SHA512:
6
- metadata.gz: 21b155a0758aea291a38904e988631650489041d48cbc0dc06886d811c2bf9266af5e5b9c80cc568375b238fda66239731bf01d7c286c9447b517596f2bd48d8
7
- data.tar.gz: a2fd60d16ce640bec21a5b77a4c2411f305a1b7e95567a5d65eff8b4fa42b231ba898fb3aca8965735d59bc0dcc6109e3b37cb710b9804ecf75885b3fe6c010c
6
+ metadata.gz: a8e4fb38812af974a23e7283879ac11de5c73124c48454b5b6167246eb42252f165017195ed78e57babab5dcdf39aed72548b81247eaad8629ccf54b9148dc57
7
+ data.tar.gz: 29e32ead48c70e1552f09368b8a02180078fec57b346724146780b7957950c951e702b96c8aee5607d0c890dae08d44412da73b418d77167564bb704ceb12570
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-10-11
4
+
5
+ - `prop` and `state` declarations in an `each` loop over a list of symbols (a literal array, or a constant
6
+ assigned one in the same file) define a reader and writer per symbol. `Talon::StateSprite` declares its sprite
7
+ attributes this way, so `x`, `y`, `path`, `angle`, ... now complete on `Talon::GameObject`.
8
+ - Test fixtures synced to Talon c4b670c.
9
+
3
10
  ## [0.1.0] - 2026-10-11
4
11
 
5
12
  - Initial release: a Solargraph plugin for the [Talon](https://github.com/asimmons91/talon) game framework.
@@ -13,5 +20,6 @@
13
20
  any other `on_*`, `set_callback` and `define_callback`) are evaluated on the instance. Their block parameters
14
21
  are typed by the hook method's `@yieldparam` tag, which Talon's built-in hooks document as `GTK::Args`.
15
22
 
16
- [Unreleased]: https://github.com/asimmons91/solargraph-dr_talon/compare/v0.1.0...HEAD
23
+ [Unreleased]: https://github.com/asimmons91/solargraph-dr_talon/compare/v0.2.0...HEAD
24
+ [0.2.0]: https://github.com/asimmons91/solargraph-dr_talon/compare/v0.1.0...v0.2.0
17
25
  [0.1.0]: https://github.com/asimmons91/solargraph-dr_talon/releases/tag/v0.1.0
data/README.md CHANGED
@@ -86,6 +86,16 @@ Restart your editor's language server. See solargraph-dragonruby's README for ed
86
86
  (`2` is an `Integer`, `"a"` a `String`, `true` a `Boolean`, `Vector.new(...)` a `Vector`, ...).
87
87
  - The comment becomes the hover docs, and go-to-definition jumps to the declaration.
88
88
  - A declaration in a concern's `included` block defines the methods on the concern, so every host gets them.
89
+ - Declarations in a loop over a list of symbols define one reader and writer per symbol. The list can be a
90
+ literal array or a constant assigned one earlier in the same file:
91
+
92
+ ```ruby
93
+ AXES = %i[dx dy].freeze
94
+ AXES.each { |axis| state axis, default: 0 } # dx, dx=, dy, dy=
95
+ ```
96
+
97
+ This is how `Talon::StateSprite` declares the sprite attributes of every `Talon::GameObject` (`x`, `y`, `w`,
98
+ `h`, `path`, `angle`, ...), so they complete on game objects (from Talon c4b670c).
89
99
  - **Lifecycle hook blocks** in a class body are evaluated on the instance, as Talon runs them.
90
100
  - This applies to `on_start`, `on_update`, `on_draw`, `on_destroy`, `on_enter` and `on_exit`, and to `set_callback`
91
101
  and `define_callback`.
@@ -105,6 +115,9 @@ Restart your editor's language server. See solargraph-dragonruby's README for ed
105
115
  class:
106
116
  - `prop :name` and `state :name` called in any class body define accessors.
107
117
  - A block passed to any bare `on_*` call in a class body is evaluated on the instance.
118
+ - Only `each` loops are unrolled, with a single block parameter passed straight to `prop` or `state`. A list
119
+ that comes from a method call, another file, or contains anything but symbols isn't known statically, so its
120
+ attributes don't complete.
108
121
  - A concern's own class side (`Visible.hidden_by_default`) also completes its `ClassMethods`. This is what makes
109
122
  them complete inside `included do ... end`, but calling them on the concern itself fails at runtime.
110
123
  - The Talon methods behind a hook block (such as `update` from `on_update`) are generated at runtime and don't
@@ -10,6 +10,15 @@ module Solargraph
10
10
  # A `@return` tag in the comment above the call sets the type; otherwise
11
11
  # it's inferred from a literal `default:`.
12
12
  #
13
+ # It also unrolls declarations made in a loop over a list of symbols, as
14
+ # Talon::StateSprite does for its sprite attributes:
15
+ #
16
+ # ATTRIBUTES = [:x, :y, :w, :h].freeze
17
+ # ATTRIBUTES.each { |name| state name }
18
+ #
19
+ # The list must be a literal array of symbols, or a constant assigned one
20
+ # earlier in the same file.
21
+ #
13
22
  # This is a node processor rather than a Convention#local because
14
23
  # Solargraph 0.61 doesn't notice when a file's convention pins change, so
15
24
  # adding or removing a declaration wouldn't take effect until a restart.
@@ -44,13 +53,30 @@ module Solargraph
44
53
  # @return [Boolean] true, so later :send processors still run
45
54
  def process
46
55
  receiver, dsl, attribute, *options = node.children
47
- return true unless receiver.nil? && DSLS.key?(dsl) && attribute&.type == :sym
56
+ return true unless receiver.nil? && DSLS.key?(dsl)
57
+
58
+ if attribute&.type == :sym
59
+ host = Host.namespace(region)
60
+ push_accessors(host, attribute.children[0].to_s, dsl, options, region.code_for(node)) if host
61
+ elsif (names = iterated_names(attribute))
62
+ # The loop's block is the closure; the host is around it.
63
+ host = Host.namespace(region.update(closure: region.closure.closure))
64
+ names.each { |name| push_accessors(host, name.to_s, dsl, options, "#{dsl} :#{name}") } if host
65
+ end
66
+ true
67
+ end
48
68
 
49
- host = Host.namespace(region)
50
- return true unless host
69
+ private
51
70
 
52
- name = attribute.children[0].to_s
53
- comments = attribute_comments(dsl, options)
71
+ # @param host [Solargraph::Pin::Namespace]
72
+ # @param name [String]
73
+ # @param dsl [Symbol]
74
+ # @param options [Array<Parser::AST::Node>]
75
+ # @param declaration [String] the declaration's code, for undocumented
76
+ # attributes
77
+ # @return [void]
78
+ def push_accessors(host, name, dsl, options, declaration)
79
+ comments = attribute_comments(dsl, options, declaration)
54
80
  reader = method_pin(host, name, comments)
55
81
  writer = method_pin(host, "#{name}=", comments)
56
82
  writer.parameters.push Solargraph::Pin::Parameter.new(name: "value", decl: :arg, closure: writer, source: :dr_talon)
@@ -58,10 +84,53 @@ module Solargraph
58
84
  writer.docstring.add_tag YARD::Tags::Tag.new(:param, "", reader.return_type.items.map(&:rooted_tags), "value")
59
85
  end
60
86
  pins.push reader, writer
61
- true
62
87
  end
63
88
 
64
- private
89
+ # The names a declaration like `state name` declares when it's directly
90
+ # inside `LIST.each { |name| ... }`.
91
+ #
92
+ # @param attribute [Parser::AST::Node, nil]
93
+ # @return [Array<Symbol>, nil]
94
+ def iterated_names(attribute)
95
+ return unless attribute&.type == :lvar
96
+
97
+ block = region.closure
98
+ return unless block.is_a?(Solargraph::Pin::Block) && block.node
99
+
100
+ call, args = block.node.children
101
+ return unless call.type == :send && call.children[1] == :each && call.children.length == 2
102
+
103
+ params = args.children
104
+ return unless params.length == 1 && params[0].type == :arg && params[0].children[0] == attribute.children[0]
105
+
106
+ symbol_list(call.children[0])
107
+ end
108
+
109
+ # @param list [Parser::AST::Node, nil] a literal array of symbols, or a
110
+ # constant assigned one earlier in this file, optionally frozen
111
+ # @return [Array<Symbol>, nil]
112
+ def symbol_list(list)
113
+ list = unfreeze(list)
114
+ list = unfreeze(constant_value(list)) if list&.type == :const
115
+ return unless list&.type == :array && list.children.all? { |item| item.type == :sym }
116
+
117
+ list.children.map { |item| item.children[0] }
118
+ end
119
+
120
+ # @param node [Parser::AST::Node, nil]
121
+ # @return [Parser::AST::Node, nil] the receiver of `.freeze`, or `node`
122
+ def unfreeze(node)
123
+ frozen = node&.type == :send && node.children[1] == :freeze && node.children.length == 2
124
+ frozen ? node.children[0] : node
125
+ end
126
+
127
+ # @param const [Parser::AST::Node]
128
+ # @return [Parser::AST::Node, nil] the value most recently assigned to
129
+ # a constant of that name in this file
130
+ def constant_value(const)
131
+ name = const.children[1].to_s
132
+ pins.reverse_each.find { |pin| pin.is_a?(Solargraph::Pin::Constant) && pin.name == name }&.assignment
133
+ end
65
134
 
66
135
  # @param host [Solargraph::Pin::Namespace]
67
136
  # @param name [String]
@@ -87,10 +156,11 @@ module Solargraph
87
156
  #
88
157
  # @param dsl [Symbol]
89
158
  # @param options [Array<Parser::AST::Node>]
159
+ # @param declaration [String]
90
160
  # @return [String]
91
- def attribute_comments(dsl, options)
161
+ def attribute_comments(dsl, options, declaration)
92
162
  comments = comments_for(node).to_s
93
- comments = "#{DSLS[dsl]}: `#{region.code_for(node)}`" if comments.strip.empty?
163
+ comments = "#{DSLS[dsl]}: `#{declaration}`" if comments.strip.empty?
94
164
  return comments if comments.match?(/^\s*@return\b/)
95
165
 
96
166
  type = inferred_type(options)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Solargraph
4
4
  module DrTalon
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph-dr_talon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Austin Simmons