rubydex 0.1.0.beta11 → 0.1.0.beta13

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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +23 -23
  3. data/README.md +125 -125
  4. data/THIRD_PARTY_LICENSES.html +2018 -945
  5. data/exe/rdx +47 -47
  6. data/ext/rubydex/declaration.c +453 -388
  7. data/ext/rubydex/declaration.h +23 -23
  8. data/ext/rubydex/definition.c +284 -197
  9. data/ext/rubydex/definition.h +28 -28
  10. data/ext/rubydex/diagnostic.c +6 -6
  11. data/ext/rubydex/diagnostic.h +11 -11
  12. data/ext/rubydex/document.c +97 -98
  13. data/ext/rubydex/document.h +10 -10
  14. data/ext/rubydex/extconf.rb +146 -127
  15. data/ext/rubydex/graph.c +701 -512
  16. data/ext/rubydex/graph.h +10 -10
  17. data/ext/rubydex/handle.h +44 -44
  18. data/ext/rubydex/location.c +22 -22
  19. data/ext/rubydex/location.h +15 -15
  20. data/ext/rubydex/reference.c +123 -104
  21. data/ext/rubydex/reference.h +15 -16
  22. data/ext/rubydex/rubydex.c +22 -22
  23. data/ext/rubydex/utils.c +108 -86
  24. data/ext/rubydex/utils.h +34 -28
  25. data/lib/rubydex/comment.rb +17 -17
  26. data/lib/rubydex/declaration.rb +11 -0
  27. data/lib/rubydex/diagnostic.rb +21 -21
  28. data/lib/rubydex/failures.rb +15 -15
  29. data/lib/rubydex/graph.rb +98 -92
  30. data/lib/rubydex/keyword.rb +17 -0
  31. data/lib/rubydex/keyword_parameter.rb +13 -0
  32. data/lib/rubydex/location.rb +90 -90
  33. data/lib/rubydex/mixin.rb +22 -0
  34. data/lib/rubydex/version.rb +5 -5
  35. data/lib/rubydex.rb +24 -20
  36. data/rbi/rubydex.rbi +425 -310
  37. data/rust/Cargo.lock +1851 -1851
  38. data/rust/Cargo.toml +29 -29
  39. data/rust/about.toml +10 -10
  40. data/rust/{about.hbs → about_templates/about.hbs} +81 -78
  41. data/rust/about_templates/mingw_licenses.hbs +1071 -0
  42. data/rust/rubydex/Cargo.toml +42 -42
  43. data/rust/rubydex/src/compile_assertions.rs +13 -13
  44. data/rust/rubydex/src/diagnostic.rs +110 -109
  45. data/rust/rubydex/src/errors.rs +28 -28
  46. data/rust/rubydex/src/indexing/local_graph.rs +224 -224
  47. data/rust/rubydex/src/indexing/rbs_indexer.rs +1551 -1554
  48. data/rust/rubydex/src/indexing/ruby_indexer.rs +2329 -6753
  49. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +4962 -0
  50. data/rust/rubydex/src/indexing.rs +210 -210
  51. data/rust/rubydex/src/integrity.rs +279 -278
  52. data/rust/rubydex/src/job_queue.rs +199 -205
  53. data/rust/rubydex/src/lib.rs +17 -17
  54. data/rust/rubydex/src/listing.rs +371 -272
  55. data/rust/rubydex/src/main.rs +160 -160
  56. data/rust/rubydex/src/model/built_in.rs +83 -0
  57. data/rust/rubydex/src/model/comment.rs +24 -24
  58. data/rust/rubydex/src/model/declaration.rs +679 -588
  59. data/rust/rubydex/src/model/definitions.rs +1682 -1602
  60. data/rust/rubydex/src/model/document.rs +222 -252
  61. data/rust/rubydex/src/model/encoding.rs +22 -22
  62. data/rust/rubydex/src/model/graph.rs +3782 -3556
  63. data/rust/rubydex/src/model/id.rs +110 -110
  64. data/rust/rubydex/src/model/identity_maps.rs +58 -58
  65. data/rust/rubydex/src/model/ids.rs +60 -38
  66. data/rust/rubydex/src/model/keywords.rs +256 -256
  67. data/rust/rubydex/src/model/name.rs +298 -298
  68. data/rust/rubydex/src/model/references.rs +111 -111
  69. data/rust/rubydex/src/model/string_ref.rs +50 -50
  70. data/rust/rubydex/src/model/visibility.rs +41 -41
  71. data/rust/rubydex/src/model.rs +15 -14
  72. data/rust/rubydex/src/offset.rs +147 -147
  73. data/rust/rubydex/src/position.rs +6 -6
  74. data/rust/rubydex/src/query.rs +1841 -1700
  75. data/rust/rubydex/src/resolution.rs +1852 -5895
  76. data/rust/rubydex/src/resolution_tests.rs +4701 -0
  77. data/rust/rubydex/src/stats/memory.rs +71 -71
  78. data/rust/rubydex/src/stats/orphan_report.rs +264 -263
  79. data/rust/rubydex/src/stats/timer.rs +127 -127
  80. data/rust/rubydex/src/stats.rs +11 -11
  81. data/rust/rubydex/src/test_utils/context.rs +226 -226
  82. data/rust/rubydex/src/test_utils/graph_test.rs +730 -679
  83. data/rust/rubydex/src/test_utils/local_graph_test.rs +602 -602
  84. data/rust/rubydex/src/test_utils.rs +52 -52
  85. data/rust/rubydex/src/visualization/dot.rs +192 -176
  86. data/rust/rubydex/src/visualization.rs +6 -6
  87. data/rust/rubydex/tests/cli.rs +185 -167
  88. data/rust/rubydex-mcp/Cargo.toml +28 -28
  89. data/rust/rubydex-mcp/src/main.rs +48 -48
  90. data/rust/rubydex-mcp/src/server.rs +1145 -1145
  91. data/rust/rubydex-mcp/src/tools.rs +49 -49
  92. data/rust/rubydex-mcp/tests/mcp.rs +302 -302
  93. data/rust/rubydex-sys/Cargo.toml +20 -20
  94. data/rust/rubydex-sys/build.rs +14 -14
  95. data/rust/rubydex-sys/cbindgen.toml +12 -12
  96. data/rust/rubydex-sys/src/declaration_api.rs +485 -469
  97. data/rust/rubydex-sys/src/definition_api.rs +443 -352
  98. data/rust/rubydex-sys/src/diagnostic_api.rs +99 -99
  99. data/rust/rubydex-sys/src/document_api.rs +85 -54
  100. data/rust/rubydex-sys/src/graph_api.rs +1017 -700
  101. data/rust/rubydex-sys/src/lib.rs +79 -9
  102. data/rust/rubydex-sys/src/location_api.rs +79 -79
  103. data/rust/rubydex-sys/src/name_api.rs +187 -135
  104. data/rust/rubydex-sys/src/reference_api.rs +267 -195
  105. data/rust/rubydex-sys/src/utils.rs +70 -70
  106. data/rust/rustfmt.toml +2 -2
  107. metadata +16 -9
  108. data/lib/rubydex/librubydex_sys.so +0 -0
@@ -1,256 +1,256 @@
1
- /// A Ruby keyword with its documentation.
2
- #[derive(Debug)]
3
- pub struct Keyword {
4
- /// The keyword text as it appears in source (e.g., "yield", "defined?", "__FILE__")
5
- name: &'static str,
6
- /// Documentation string for hover display
7
- documentation: &'static str,
8
- }
9
-
10
- impl Keyword {
11
- #[must_use]
12
- pub const fn name(&self) -> &'static str {
13
- self.name
14
- }
15
-
16
- #[must_use]
17
- pub const fn documentation(&self) -> &'static str {
18
- self.documentation
19
- }
20
- }
21
-
22
- /// Looks up a keyword by its exact name. Returns `None` if the name is not a keyword.
23
- ///
24
- /// Uses binary search on the sorted `KEYWORDS` array for O(log n) lookup.
25
- #[must_use]
26
- pub fn get(name: &str) -> Option<&'static Keyword> {
27
- KEYWORDS
28
- .binary_search_by_key(&name, |k| k.name)
29
- .ok()
30
- .map(|i| &KEYWORDS[i])
31
- }
32
-
33
- /// All Ruby keywords, sorted lexicographically by name for binary search.
34
- pub static KEYWORDS: &[Keyword] = &[
35
- Keyword {
36
- name: "BEGIN",
37
- documentation: "Registers a block of code to be executed before the program starts. Syntax: `BEGIN { ... }`.",
38
- },
39
- Keyword {
40
- name: "END",
41
- documentation: "Registers a block of code to be executed after the program finishes. Syntax: `END { ... }`.",
42
- },
43
- Keyword {
44
- name: "__ENCODING__",
45
- documentation: "Returns the `Encoding` object representing the encoding of the current source file.",
46
- },
47
- Keyword {
48
- name: "__FILE__",
49
- documentation: "Returns the path of the current source file as a `String`.",
50
- },
51
- Keyword {
52
- name: "__LINE__",
53
- documentation: "Returns the current line number in the source file as an `Integer`.",
54
- },
55
- Keyword {
56
- name: "alias",
57
- documentation: "Creates an alias between two methods or global variables. Syntax: `alias new_name old_name`.",
58
- },
59
- Keyword {
60
- name: "and",
61
- documentation: "Low-precedence logical AND operator. Unlike `&&`, it has lower precedence than assignment.",
62
- },
63
- Keyword {
64
- name: "begin",
65
- documentation: "Opens an exception handling block. Can be followed by `rescue`, `else`, `ensure`, and closed with `end`.",
66
- },
67
- Keyword {
68
- name: "break",
69
- documentation: "Exits from a loop or block, optionally returning a value. Syntax: `break` or `break value`.",
70
- },
71
- Keyword {
72
- name: "case",
73
- documentation: "Starts a case expression for pattern matching. Used with `when` clauses and closed with `end`.",
74
- },
75
- Keyword {
76
- name: "class",
77
- documentation: "Defines a new class or opens an existing one. Syntax: `class Name < Superclass; end`.",
78
- },
79
- Keyword {
80
- name: "def",
81
- documentation: "Defines a method. Syntax: `def method_name(params); end`.",
82
- },
83
- Keyword {
84
- name: "defined?",
85
- documentation: "Returns a string describing the type of an expression, or `nil` if it is not defined. The argument is not evaluated.",
86
- },
87
- Keyword {
88
- name: "do",
89
- documentation: "Starts a block of code, typically following an iterator method call. Paired with `end`.",
90
- },
91
- Keyword {
92
- name: "else",
93
- documentation: "Provides an alternative branch in `if`, `unless`, `case`, or `begin/rescue` expressions.",
94
- },
95
- Keyword {
96
- name: "elsif",
97
- documentation: "Provides an additional conditional branch within an `if` expression.",
98
- },
99
- Keyword {
100
- name: "end",
101
- documentation: "Closes a `class`, `module`, `def`, `if`, `unless`, `case`, `while`, `until`, `for`, `begin`, or `do` block.",
102
- },
103
- Keyword {
104
- name: "ensure",
105
- documentation: "Defines a block of code within `begin`/`def` that always runs, whether an exception was raised or not.",
106
- },
107
- Keyword {
108
- name: "false",
109
- documentation: "The singleton instance of `FalseClass`. One of two falsy values in Ruby, along with `nil`.",
110
- },
111
- Keyword {
112
- name: "for",
113
- documentation: "Iterates over a collection. Syntax: `for variable in collection; end`. Prefer `.each` in idiomatic Ruby.",
114
- },
115
- Keyword {
116
- name: "if",
117
- documentation: "Conditional branch. Can be used as a statement (`if cond; end`) or a modifier (`expr if cond`).",
118
- },
119
- Keyword {
120
- name: "in",
121
- documentation: "Used with `for` loops (`for x in collection`) and pattern matching (`case value; in pattern`).",
122
- },
123
- Keyword {
124
- name: "module",
125
- documentation: "Defines a new module or opens an existing one. Modules provide namespacing and mixins via `include`/`extend`.",
126
- },
127
- Keyword {
128
- name: "next",
129
- documentation: "Skips to the next iteration of a loop or block, optionally returning a value. Syntax: `next` or `next value`.",
130
- },
131
- Keyword {
132
- name: "nil",
133
- documentation: "The singleton instance of `NilClass`. Represents the absence of a value. Falsy in boolean context.",
134
- },
135
- Keyword {
136
- name: "not",
137
- documentation: "Low-precedence logical NOT operator. Unlike `!`, it has lower precedence than most operators.",
138
- },
139
- Keyword {
140
- name: "or",
141
- documentation: "Low-precedence logical OR operator. Unlike `||`, it has lower precedence than assignment.",
142
- },
143
- Keyword {
144
- name: "redo",
145
- documentation: "Restarts the current iteration of a loop or block without re-evaluating the condition.",
146
- },
147
- Keyword {
148
- name: "rescue",
149
- documentation: "Catches exceptions in a `begin`/`def` block. Can also be used inline: `expr rescue default`.",
150
- },
151
- Keyword {
152
- name: "retry",
153
- documentation: "Re-executes the `begin` block from the start. Only valid inside a `rescue` clause.",
154
- },
155
- Keyword {
156
- name: "return",
157
- documentation: "Exits from the current method, optionally returning a value. Syntax: `return` or `return value`.",
158
- },
159
- Keyword {
160
- name: "self",
161
- documentation: "References the current object. Inside a method, it is the receiver. Inside a class/module body, it is the class/module itself.",
162
- },
163
- Keyword {
164
- name: "super",
165
- documentation: "Calls the next occurrence of the method in the ancestor chain. Forwards all arguments if called without parentheses.",
166
- },
167
- Keyword {
168
- name: "then",
169
- documentation: "Optional separator after the condition in `if`, `unless`, `when`, or `in` clauses.",
170
- },
171
- Keyword {
172
- name: "true",
173
- documentation: "The singleton instance of `TrueClass`. Truthy in boolean context.",
174
- },
175
- Keyword {
176
- name: "undef",
177
- documentation: "Removes a method definition from the current class. Syntax: `undef method_name`.",
178
- },
179
- Keyword {
180
- name: "unless",
181
- documentation: "Inverted conditional. Executes the body when the condition is falsy. Can be used as statement or modifier.",
182
- },
183
- Keyword {
184
- name: "until",
185
- documentation: "Loops while the condition is falsy. Can be used as a statement or modifier.",
186
- },
187
- Keyword {
188
- name: "when",
189
- documentation: "Defines a branch in a `case` expression. Syntax: `when value; ...`.",
190
- },
191
- Keyword {
192
- name: "while",
193
- documentation: "Loops while the condition is truthy. Can be used as a statement or modifier.",
194
- },
195
- Keyword {
196
- name: "yield",
197
- documentation: "Calls the block passed to the current method, passing optional arguments. Returns the block's return value.",
198
- },
199
- ];
200
-
201
- #[cfg(test)]
202
- mod tests {
203
- use super::*;
204
-
205
- #[test]
206
- fn all_keywords_have_documentation() {
207
- for keyword in KEYWORDS {
208
- assert!(
209
- !keyword.documentation().is_empty(),
210
- "keyword '{}' has empty documentation",
211
- keyword.name()
212
- );
213
- }
214
- }
215
-
216
- #[test]
217
- fn get_returns_none_for_non_keywords() {
218
- assert!(get("puts").is_none());
219
- assert!(get("require").is_none());
220
- assert!(get("").is_none());
221
- assert!(get("Foo").is_none());
222
- }
223
-
224
- #[test]
225
- fn get_returns_keyword_with_correct_data() {
226
- let kw = get("yield").unwrap();
227
- assert_eq!(kw.name(), "yield");
228
- assert!(kw.documentation().contains("block"));
229
-
230
- let kw = get("defined?").unwrap();
231
- assert_eq!(kw.name(), "defined?");
232
-
233
- let kw = get("__FILE__").unwrap();
234
- assert_eq!(kw.name(), "__FILE__");
235
- }
236
-
237
- #[test]
238
- fn keyword_names_are_unique() {
239
- let mut seen = std::collections::HashSet::new();
240
- for keyword in KEYWORDS {
241
- assert!(seen.insert(keyword.name()), "duplicate keyword: '{}'", keyword.name());
242
- }
243
- }
244
-
245
- #[test]
246
- fn keywords_are_sorted() {
247
- for window in KEYWORDS.windows(2) {
248
- assert!(
249
- window[0].name() < window[1].name(),
250
- "KEYWORDS not sorted: '{}' should come before '{}'",
251
- window[1].name(),
252
- window[0].name()
253
- );
254
- }
255
- }
256
- }
1
+ /// A Ruby keyword with its documentation.
2
+ #[derive(Debug)]
3
+ pub struct Keyword {
4
+ /// The keyword text as it appears in source (e.g., "yield", "defined?", "__FILE__")
5
+ name: &'static str,
6
+ /// Documentation string for hover display
7
+ documentation: &'static str,
8
+ }
9
+
10
+ impl Keyword {
11
+ #[must_use]
12
+ pub const fn name(&self) -> &'static str {
13
+ self.name
14
+ }
15
+
16
+ #[must_use]
17
+ pub const fn documentation(&self) -> &'static str {
18
+ self.documentation
19
+ }
20
+ }
21
+
22
+ /// Looks up a keyword by its exact name. Returns `None` if the name is not a keyword.
23
+ ///
24
+ /// Uses binary search on the sorted `KEYWORDS` array for O(log n) lookup.
25
+ #[must_use]
26
+ pub fn get(name: &str) -> Option<&'static Keyword> {
27
+ KEYWORDS
28
+ .binary_search_by_key(&name, |k| k.name)
29
+ .ok()
30
+ .map(|i| &KEYWORDS[i])
31
+ }
32
+
33
+ /// All Ruby keywords, sorted lexicographically by name for binary search.
34
+ pub static KEYWORDS: &[Keyword] = &[
35
+ Keyword {
36
+ name: "BEGIN",
37
+ documentation: "Registers a block of code to be executed before the program starts. Syntax: `BEGIN { ... }`.",
38
+ },
39
+ Keyword {
40
+ name: "END",
41
+ documentation: "Registers a block of code to be executed after the program finishes. Syntax: `END { ... }`.",
42
+ },
43
+ Keyword {
44
+ name: "__ENCODING__",
45
+ documentation: "Returns the `Encoding` object representing the encoding of the current source file.",
46
+ },
47
+ Keyword {
48
+ name: "__FILE__",
49
+ documentation: "Returns the path of the current source file as a `String`.",
50
+ },
51
+ Keyword {
52
+ name: "__LINE__",
53
+ documentation: "Returns the current line number in the source file as an `Integer`.",
54
+ },
55
+ Keyword {
56
+ name: "alias",
57
+ documentation: "Creates an alias between two methods or global variables. Syntax: `alias new_name old_name`.",
58
+ },
59
+ Keyword {
60
+ name: "and",
61
+ documentation: "Low-precedence logical AND operator. Unlike `&&`, it has lower precedence than assignment.",
62
+ },
63
+ Keyword {
64
+ name: "begin",
65
+ documentation: "Opens an exception handling block. Can be followed by `rescue`, `else`, `ensure`, and closed with `end`.",
66
+ },
67
+ Keyword {
68
+ name: "break",
69
+ documentation: "Exits from a loop or block, optionally returning a value. Syntax: `break` or `break value`.",
70
+ },
71
+ Keyword {
72
+ name: "case",
73
+ documentation: "Starts a case expression for pattern matching. Used with `when` clauses and closed with `end`.",
74
+ },
75
+ Keyword {
76
+ name: "class",
77
+ documentation: "Defines a new class or opens an existing one. Syntax: `class Name < Superclass; end`.",
78
+ },
79
+ Keyword {
80
+ name: "def",
81
+ documentation: "Defines a method. Syntax: `def method_name(params); end`.",
82
+ },
83
+ Keyword {
84
+ name: "defined?",
85
+ documentation: "Returns a string describing the type of an expression, or `nil` if it is not defined. The argument is not evaluated.",
86
+ },
87
+ Keyword {
88
+ name: "do",
89
+ documentation: "Starts a block of code, typically following an iterator method call. Paired with `end`.",
90
+ },
91
+ Keyword {
92
+ name: "else",
93
+ documentation: "Provides an alternative branch in `if`, `unless`, `case`, or `begin/rescue` expressions.",
94
+ },
95
+ Keyword {
96
+ name: "elsif",
97
+ documentation: "Provides an additional conditional branch within an `if` expression.",
98
+ },
99
+ Keyword {
100
+ name: "end",
101
+ documentation: "Closes a `class`, `module`, `def`, `if`, `unless`, `case`, `while`, `until`, `for`, `begin`, or `do` block.",
102
+ },
103
+ Keyword {
104
+ name: "ensure",
105
+ documentation: "Defines a block of code within `begin`/`def` that always runs, whether an exception was raised or not.",
106
+ },
107
+ Keyword {
108
+ name: "false",
109
+ documentation: "The singleton instance of `FalseClass`. One of two falsy values in Ruby, along with `nil`.",
110
+ },
111
+ Keyword {
112
+ name: "for",
113
+ documentation: "Iterates over a collection. Syntax: `for variable in collection; end`. Prefer `.each` in idiomatic Ruby.",
114
+ },
115
+ Keyword {
116
+ name: "if",
117
+ documentation: "Conditional branch. Can be used as a statement (`if cond; end`) or a modifier (`expr if cond`).",
118
+ },
119
+ Keyword {
120
+ name: "in",
121
+ documentation: "Used with `for` loops (`for x in collection`) and pattern matching (`case value; in pattern`).",
122
+ },
123
+ Keyword {
124
+ name: "module",
125
+ documentation: "Defines a new module or opens an existing one. Modules provide namespacing and mixins via `include`/`extend`.",
126
+ },
127
+ Keyword {
128
+ name: "next",
129
+ documentation: "Skips to the next iteration of a loop or block, optionally returning a value. Syntax: `next` or `next value`.",
130
+ },
131
+ Keyword {
132
+ name: "nil",
133
+ documentation: "The singleton instance of `NilClass`. Represents the absence of a value. Falsy in boolean context.",
134
+ },
135
+ Keyword {
136
+ name: "not",
137
+ documentation: "Low-precedence logical NOT operator. Unlike `!`, it has lower precedence than most operators.",
138
+ },
139
+ Keyword {
140
+ name: "or",
141
+ documentation: "Low-precedence logical OR operator. Unlike `||`, it has lower precedence than assignment.",
142
+ },
143
+ Keyword {
144
+ name: "redo",
145
+ documentation: "Restarts the current iteration of a loop or block without re-evaluating the condition.",
146
+ },
147
+ Keyword {
148
+ name: "rescue",
149
+ documentation: "Catches exceptions in a `begin`/`def` block. Can also be used inline: `expr rescue default`.",
150
+ },
151
+ Keyword {
152
+ name: "retry",
153
+ documentation: "Re-executes the `begin` block from the start. Only valid inside a `rescue` clause.",
154
+ },
155
+ Keyword {
156
+ name: "return",
157
+ documentation: "Exits from the current method, optionally returning a value. Syntax: `return` or `return value`.",
158
+ },
159
+ Keyword {
160
+ name: "self",
161
+ documentation: "References the current object. Inside a method, it is the receiver. Inside a class/module body, it is the class/module itself.",
162
+ },
163
+ Keyword {
164
+ name: "super",
165
+ documentation: "Calls the next occurrence of the method in the ancestor chain. Forwards all arguments if called without parentheses.",
166
+ },
167
+ Keyword {
168
+ name: "then",
169
+ documentation: "Optional separator after the condition in `if`, `unless`, `when`, or `in` clauses.",
170
+ },
171
+ Keyword {
172
+ name: "true",
173
+ documentation: "The singleton instance of `TrueClass`. Truthy in boolean context.",
174
+ },
175
+ Keyword {
176
+ name: "undef",
177
+ documentation: "Removes a method definition from the current class. Syntax: `undef method_name`.",
178
+ },
179
+ Keyword {
180
+ name: "unless",
181
+ documentation: "Inverted conditional. Executes the body when the condition is falsy. Can be used as statement or modifier.",
182
+ },
183
+ Keyword {
184
+ name: "until",
185
+ documentation: "Loops while the condition is falsy. Can be used as a statement or modifier.",
186
+ },
187
+ Keyword {
188
+ name: "when",
189
+ documentation: "Defines a branch in a `case` expression. Syntax: `when value; ...`.",
190
+ },
191
+ Keyword {
192
+ name: "while",
193
+ documentation: "Loops while the condition is truthy. Can be used as a statement or modifier.",
194
+ },
195
+ Keyword {
196
+ name: "yield",
197
+ documentation: "Calls the block passed to the current method, passing optional arguments. Returns the block's return value.",
198
+ },
199
+ ];
200
+
201
+ #[cfg(test)]
202
+ mod tests {
203
+ use super::*;
204
+
205
+ #[test]
206
+ fn all_keywords_have_documentation() {
207
+ for keyword in KEYWORDS {
208
+ assert!(
209
+ !keyword.documentation().is_empty(),
210
+ "keyword '{}' has empty documentation",
211
+ keyword.name()
212
+ );
213
+ }
214
+ }
215
+
216
+ #[test]
217
+ fn get_returns_none_for_non_keywords() {
218
+ assert!(get("puts").is_none());
219
+ assert!(get("require").is_none());
220
+ assert!(get("").is_none());
221
+ assert!(get("Foo").is_none());
222
+ }
223
+
224
+ #[test]
225
+ fn get_returns_keyword_with_correct_data() {
226
+ let kw = get("yield").unwrap();
227
+ assert_eq!(kw.name(), "yield");
228
+ assert!(kw.documentation().contains("block"));
229
+
230
+ let kw = get("defined?").unwrap();
231
+ assert_eq!(kw.name(), "defined?");
232
+
233
+ let kw = get("__FILE__").unwrap();
234
+ assert_eq!(kw.name(), "__FILE__");
235
+ }
236
+
237
+ #[test]
238
+ fn keyword_names_are_unique() {
239
+ let mut seen = std::collections::HashSet::new();
240
+ for keyword in KEYWORDS {
241
+ assert!(seen.insert(keyword.name()), "duplicate keyword: '{}'", keyword.name());
242
+ }
243
+ }
244
+
245
+ #[test]
246
+ fn keywords_are_sorted() {
247
+ for window in KEYWORDS.windows(2) {
248
+ assert!(
249
+ window[0].name() < window[1].name(),
250
+ "KEYWORDS not sorted: '{}' should come before '{}'",
251
+ window[1].name(),
252
+ window[0].name()
253
+ );
254
+ }
255
+ }
256
+ }