rubydex 0.3.0 → 0.4.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -7
  3. data/THIRD_PARTY_LICENSES.html +238 -2
  4. data/exe/rdx +2 -149
  5. data/ext/rubydex/config.c +140 -0
  6. data/ext/rubydex/config.h +16 -0
  7. data/ext/rubydex/diagnostic.c +75 -1
  8. data/ext/rubydex/diagnostic.h +2 -0
  9. data/ext/rubydex/graph.c +13 -45
  10. data/ext/rubydex/graph.h +6 -0
  11. data/ext/rubydex/query.c +398 -16
  12. data/ext/rubydex/rubydex.c +2 -0
  13. data/ext/rubydex/utils.c +11 -4
  14. data/lib/ruby_lsp/rubydex/addon.rb +211 -0
  15. data/lib/rubydex/cli/command/console.rb +55 -0
  16. data/lib/rubydex/cli/command/lint/explain.rb +74 -0
  17. data/lib/rubydex/cli/command/lint.rb +202 -0
  18. data/lib/rubydex/cli/command/mcp.rb +30 -0
  19. data/lib/rubydex/cli/command/query.rb +70 -0
  20. data/lib/rubydex/cli/command/skill.rb +69 -0
  21. data/lib/rubydex/cli/command.rb +168 -0
  22. data/lib/rubydex/cli.rb +93 -0
  23. data/lib/rubydex/config.rb +59 -0
  24. data/lib/rubydex/diagnostic.rb +12 -3
  25. data/lib/rubydex/errors.rb +42 -1
  26. data/lib/rubydex/graph.rb +10 -3
  27. data/lib/rubydex/linter/custom_rule.rb +97 -0
  28. data/lib/rubydex/linter/helpers/path_helpers.rb +78 -0
  29. data/lib/rubydex/linter/helpers/source_access_helpers.rb +31 -0
  30. data/lib/rubydex/linter/rule_loader.rb +36 -0
  31. data/lib/rubydex/linter/rule_test_case.rb +343 -0
  32. data/lib/rubydex/linter/runner.rb +56 -0
  33. data/lib/rubydex/linter.rb +19 -0
  34. data/lib/rubydex/location.rb +3 -0
  35. data/lib/rubydex/mcp_server.rb +1 -2
  36. data/lib/rubydex/related_information.rb +17 -0
  37. data/lib/rubydex/rule.rb +33 -0
  38. data/lib/rubydex/severity.rb +70 -0
  39. data/lib/rubydex/skill.rb +88 -0
  40. data/lib/rubydex/skill_registry.rb +62 -0
  41. data/lib/rubydex/version.rb +1 -1
  42. data/lib/rubydex.rb +6 -0
  43. data/lib/rubydex_linter/rules/rule_structure.rb +125 -0
  44. data/rbi/rubydex.rbi +558 -17
  45. data/rust/Cargo.lock +2 -2
  46. data/rust/rubydex/Cargo.toml +1 -1
  47. data/rust/rubydex/benches/graph_memory.rs +3 -5
  48. data/rust/rubydex/src/config.rs +538 -157
  49. data/rust/rubydex/src/diagnostic.rs +66 -40
  50. data/rust/rubydex/src/errors.rs +0 -1
  51. data/rust/rubydex/src/indexing/local_graph.rs +6 -5
  52. data/rust/rubydex/src/indexing/rbs_indexer.rs +270 -6
  53. data/rust/rubydex/src/indexing/ruby_indexer.rs +10 -12
  54. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +134 -81
  55. data/rust/rubydex/src/lib.rs +1 -0
  56. data/rust/rubydex/src/listing.rs +26 -1
  57. data/rust/rubydex/src/main.rs +7 -4
  58. data/rust/rubydex/src/model/declaration.rs +302 -219
  59. data/rust/rubydex/src/model/graph.rs +27 -40
  60. data/rust/rubydex/src/model/name.rs +58 -17
  61. data/rust/rubydex/src/operation/ruby_builder.rs +30 -45
  62. data/rust/rubydex/src/path_helpers.rs +77 -0
  63. data/rust/rubydex/src/query/cypher/schema.rs +63 -0
  64. data/rust/rubydex/src/query/cypher/tests.rs +26 -1
  65. data/rust/rubydex/src/query/cypher.rs +8 -11
  66. data/rust/rubydex/src/query.rs +123 -43
  67. data/rust/rubydex/src/resolution.rs +139 -187
  68. data/rust/rubydex/src/resolution_tests.rs +247 -19
  69. data/rust/rubydex/src/test_utils/context.rs +2 -1
  70. data/rust/rubydex/src/test_utils/graph_test.rs +26 -12
  71. data/rust/rubydex/src/test_utils/local_graph_test.rs +19 -0
  72. data/rust/rubydex/tests/cli.rs +4 -4
  73. data/rust/rubydex-sys/src/config_api.rs +205 -0
  74. data/rust/rubydex-sys/src/cypher_api.rs +791 -0
  75. data/rust/rubydex-sys/src/diagnostic_api.rs +77 -8
  76. data/rust/rubydex-sys/src/graph_api.rs +13 -194
  77. data/rust/rubydex-sys/src/lib.rs +2 -0
  78. data/rust/rubydex-sys/src/name_api.rs +2 -6
  79. data/rust/rubydex-sys/src/utils.rs +37 -0
  80. data/skills/send-private-method/SKILL.md +133 -0
  81. metadata +31 -2
@@ -0,0 +1,205 @@
1
+ use crate::diagnostic_api::DiagnosticSeverity;
2
+ use crate::utils;
3
+ use libc::{c_char, c_void};
4
+ use rubydex::config::{Config, Rule};
5
+ use rubydex::errors::Errors;
6
+ use std::ffi::CString;
7
+ use std::path::Path;
8
+ use std::ptr;
9
+
10
+ /// An opaque pointer to a loaded configuration
11
+ pub type ConfigPointer = *mut c_void;
12
+
13
+ /// The result of loading a configuration file, carrying either a parsed configuration or an error message.
14
+ #[repr(C)]
15
+ pub struct CConfigResult {
16
+ /// Non-null on success: a heap-allocated parsed configuration. Free with `rdx_config_free`.
17
+ pub config: ConfigPointer,
18
+ /// Non-null on error; null on success. Caller must free with `free_c_string`.
19
+ pub error: *const c_char,
20
+ }
21
+
22
+ impl CConfigResult {
23
+ fn success(config: Config) -> Self {
24
+ Self {
25
+ config: Box::into_raw(Box::new(config)).cast::<c_void>(),
26
+ error: ptr::null(),
27
+ }
28
+ }
29
+
30
+ fn error(message: &str) -> Self {
31
+ // Parse errors quote raw file content, which may contain NUL bytes that a C string cannot carry.
32
+ let message = message.replace('\0', "\u{FFFD}");
33
+
34
+ Self {
35
+ config: ptr::null_mut(),
36
+ error: utils::cstring_raw(&message),
37
+ }
38
+ }
39
+ }
40
+
41
+ /// Loads the configuration of the workspace rooted at `workspace_path`, which is where its `rubydex.toml` is expected
42
+ /// to be. A workspace without a configuration file produces an empty configuration.
43
+ ///
44
+ /// # Safety
45
+ ///
46
+ /// - `workspace_path` must be a valid, null-terminated string.
47
+ #[unsafe(no_mangle)]
48
+ pub unsafe extern "C" fn rdx_config_load(workspace_path: *const c_char) -> CConfigResult {
49
+ if workspace_path.is_null() {
50
+ return CConfigResult::error("workspace path is required");
51
+ }
52
+
53
+ let Ok(workspace_path) = (unsafe { utils::convert_char_ptr_to_string(workspace_path) }) else {
54
+ return CConfigResult::error("workspace path is not valid UTF-8");
55
+ };
56
+
57
+ match Config::load(Path::new(&workspace_path)) {
58
+ Ok(config) => CConfigResult::success(config),
59
+ Err(Errors::ConfigError(message) | Errors::FileError(message)) => CConfigResult::error(&message),
60
+ }
61
+ }
62
+
63
+ /// Returns the root directory of the workspace the configuration was loaded for, as a C string. Caller must free with
64
+ /// `free_c_string`.
65
+ ///
66
+ /// # Safety
67
+ ///
68
+ /// - `config` must be a valid `ConfigPointer` previously returned by `rdx_config_load`.
69
+ #[unsafe(no_mangle)]
70
+ pub unsafe extern "C" fn rdx_config_workspace_path(config: ConfigPointer) -> *const c_char {
71
+ let config = unsafe { &*config.cast::<Config>() };
72
+
73
+ CString::new(utils::interop_path(config.workspace_path()))
74
+ .map_or(ptr::null(), |c_string| c_string.into_raw().cast_const())
75
+ }
76
+
77
+ /// Frees a configuration through its pointer. Does nothing when given NULL.
78
+ ///
79
+ /// # Safety
80
+ ///
81
+ /// - `config` must either be NULL or a valid `ConfigPointer` previously returned by `rdx_config_load` and must not
82
+ /// be used afterwards. Any string pointer borrowed from it becomes invalid.
83
+ #[unsafe(no_mangle)]
84
+ pub unsafe extern "C" fn rdx_config_free(config: ConfigPointer) {
85
+ if config.is_null() {
86
+ return;
87
+ }
88
+
89
+ unsafe {
90
+ let _ = Box::from_raw(config.cast::<Config>());
91
+ }
92
+ }
93
+
94
+ /// Borrowed string bytes exposed while building Ruby configuration values.
95
+ #[repr(C)]
96
+ #[derive(Debug)]
97
+ pub struct CConfigString {
98
+ pub data: *const c_char,
99
+ pub length: usize,
100
+ }
101
+
102
+ /// C-compatible struct representing a single configured linter rule.
103
+ #[repr(C)]
104
+ #[derive(Debug)]
105
+ pub struct CLinterRule {
106
+ pub name: *const c_char,
107
+ pub name_length: usize,
108
+ pub enabled: bool,
109
+ pub exclude_patterns: *const CConfigString,
110
+ pub exclude_patterns_length: usize,
111
+ pub severity: *const DiagnosticSeverity,
112
+ }
113
+
114
+ impl From<&Rule> for CLinterRule {
115
+ fn from(rule: &Rule) -> Self {
116
+ let exclude_patterns = rule
117
+ .exclude_patterns()
118
+ .iter()
119
+ .map(|pattern| CConfigString {
120
+ data: pattern.as_ptr().cast::<c_char>(),
121
+ length: pattern.len(),
122
+ })
123
+ .collect::<Box<[CConfigString]>>();
124
+ let exclude_patterns_length = exclude_patterns.len();
125
+ let exclude_patterns = if exclude_patterns.is_empty() {
126
+ ptr::null()
127
+ } else {
128
+ Box::into_raw(exclude_patterns).cast::<CConfigString>().cast_const()
129
+ };
130
+ let severity = rule.severity().map_or(ptr::null(), |severity| {
131
+ Box::into_raw(Box::new(DiagnosticSeverity::from(*severity))).cast_const()
132
+ });
133
+
134
+ Self {
135
+ name: rule.name().as_ptr().cast::<c_char>(),
136
+ name_length: rule.name().len(),
137
+ enabled: rule.enabled(),
138
+ exclude_patterns,
139
+ exclude_patterns_length,
140
+ severity,
141
+ }
142
+ }
143
+ }
144
+
145
+ /// C-compatible array of configured linter rules.
146
+ #[repr(C)]
147
+ pub struct CLinterRuleArray {
148
+ pub items: *mut CLinterRule,
149
+ pub len: usize,
150
+ }
151
+
152
+ /// Returns the configured linter rules as an array. Caller must free it with `rdx_config_linter_rules_free`, while the
153
+ /// configuration is still alive, since the rule names are borrowed from it.
154
+ ///
155
+ /// # Safety
156
+ ///
157
+ /// - `config` must be a valid `ConfigPointer` previously returned by `rdx_config_load`.
158
+ #[unsafe(no_mangle)]
159
+ pub unsafe extern "C" fn rdx_config_linter_rules(config: ConfigPointer) -> CLinterRuleArray {
160
+ let config = unsafe { &*config.cast::<Config>() };
161
+ let rules = config.linter().rules();
162
+
163
+ if rules.is_empty() {
164
+ return CLinterRuleArray {
165
+ items: ptr::null_mut(),
166
+ len: 0,
167
+ };
168
+ }
169
+
170
+ let items = rules.iter().map(CLinterRule::from).collect::<Box<[CLinterRule]>>();
171
+
172
+ CLinterRuleArray {
173
+ len: items.len(),
174
+ items: Box::into_raw(items).cast::<CLinterRule>(),
175
+ }
176
+ }
177
+
178
+ /// Frees an array previously returned by `rdx_config_linter_rules`. The rule names it borrowed are left alone, since
179
+ /// they belong to the configuration.
180
+ ///
181
+ /// # Safety
182
+ ///
183
+ /// - `rules` must have been returned by `rdx_config_linter_rules` and must not be used afterwards.
184
+ #[unsafe(no_mangle)]
185
+ pub unsafe extern "C" fn rdx_config_linter_rules_free(rules: CLinterRuleArray) {
186
+ if rules.items.is_null() {
187
+ return;
188
+ }
189
+
190
+ unsafe {
191
+ let rules = Box::from_raw(ptr::slice_from_raw_parts_mut(rules.items, rules.len));
192
+
193
+ for rule in &*rules {
194
+ if !rule.exclude_patterns.is_null() {
195
+ let exclude_patterns =
196
+ ptr::slice_from_raw_parts_mut(rule.exclude_patterns.cast_mut(), rule.exclude_patterns_length);
197
+ let _ = Box::from_raw(exclude_patterns);
198
+ }
199
+
200
+ if !rule.severity.is_null() {
201
+ let _ = Box::from_raw(rule.severity.cast_mut());
202
+ }
203
+ }
204
+ }
205
+ }