rubydex 0.2.9 → 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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +110 -6
  3. data/THIRD_PARTY_LICENSES.html +271 -2
  4. data/exe/rdx +2 -73
  5. data/ext/rubydex/config.c +140 -0
  6. data/ext/rubydex/config.h +16 -0
  7. data/ext/rubydex/declaration.c +1 -1
  8. data/ext/rubydex/definition.c +32 -4
  9. data/ext/rubydex/diagnostic.c +75 -1
  10. data/ext/rubydex/diagnostic.h +2 -0
  11. data/ext/rubydex/graph.c +27 -48
  12. data/ext/rubydex/graph.h +6 -0
  13. data/ext/rubydex/query.c +487 -0
  14. data/ext/rubydex/query.h +8 -0
  15. data/ext/rubydex/reference.c +60 -0
  16. data/ext/rubydex/rubydex.c +4 -0
  17. data/ext/rubydex/utils.c +23 -4
  18. data/ext/rubydex/utils.h +5 -0
  19. data/lib/ruby_lsp/rubydex/addon.rb +211 -0
  20. data/lib/rubydex/cli/command/console.rb +55 -0
  21. data/lib/rubydex/cli/command/lint/explain.rb +74 -0
  22. data/lib/rubydex/cli/command/lint.rb +202 -0
  23. data/lib/rubydex/cli/command/mcp.rb +30 -0
  24. data/lib/rubydex/cli/command/query.rb +70 -0
  25. data/lib/rubydex/cli/command/skill.rb +69 -0
  26. data/lib/rubydex/cli/command.rb +168 -0
  27. data/lib/rubydex/cli.rb +93 -0
  28. data/lib/rubydex/config.rb +59 -0
  29. data/lib/rubydex/diagnostic.rb +12 -3
  30. data/lib/rubydex/errors.rb +42 -1
  31. data/lib/rubydex/graph.rb +10 -3
  32. data/lib/rubydex/linter/custom_rule.rb +97 -0
  33. data/lib/rubydex/linter/helpers/path_helpers.rb +78 -0
  34. data/lib/rubydex/linter/helpers/source_access_helpers.rb +31 -0
  35. data/lib/rubydex/linter/rule_loader.rb +36 -0
  36. data/lib/rubydex/linter/rule_test_case.rb +343 -0
  37. data/lib/rubydex/linter/runner.rb +56 -0
  38. data/lib/rubydex/linter.rb +19 -0
  39. data/lib/rubydex/location.rb +3 -0
  40. data/lib/rubydex/mcp_server.rb +1 -2
  41. data/lib/rubydex/related_information.rb +17 -0
  42. data/lib/rubydex/rule.rb +33 -0
  43. data/lib/rubydex/severity.rb +70 -0
  44. data/lib/rubydex/skill.rb +88 -0
  45. data/lib/rubydex/skill_registry.rb +62 -0
  46. data/lib/rubydex/version.rb +1 -1
  47. data/lib/rubydex.rb +6 -0
  48. data/lib/rubydex_linter/rules/rule_structure.rb +125 -0
  49. data/rbi/rubydex.rbi +578 -15
  50. data/rust/Cargo.lock +7 -0
  51. data/rust/rubydex/Cargo.toml +1 -0
  52. data/rust/rubydex/benches/graph_memory.rs +20 -4
  53. data/rust/rubydex/src/compile_assertions.rs +15 -0
  54. data/rust/rubydex/src/config.rs +538 -157
  55. data/rust/rubydex/src/diagnostic.rs +66 -40
  56. data/rust/rubydex/src/errors.rs +0 -1
  57. data/rust/rubydex/src/indexing/local_graph.rs +6 -5
  58. data/rust/rubydex/src/indexing/rbs_indexer.rs +284 -8
  59. data/rust/rubydex/src/indexing/ruby_indexer.rs +59 -70
  60. data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +195 -86
  61. data/rust/rubydex/src/lib.rs +1 -0
  62. data/rust/rubydex/src/listing.rs +26 -1
  63. data/rust/rubydex/src/main.rs +9 -128
  64. data/rust/rubydex/src/model/declaration.rs +301 -229
  65. data/rust/rubydex/src/model/definitions.rs +27 -26
  66. data/rust/rubydex/src/model/document.rs +43 -7
  67. data/rust/rubydex/src/model/graph.rs +67 -68
  68. data/rust/rubydex/src/model/id.rs +55 -0
  69. data/rust/rubydex/src/model/ids.rs +21 -9
  70. data/rust/rubydex/src/model/name.rs +88 -19
  71. data/rust/rubydex/src/model/references.rs +16 -13
  72. data/rust/rubydex/src/operation/ruby_builder.rs +78 -104
  73. data/rust/rubydex/src/path_helpers.rs +77 -0
  74. data/rust/rubydex/src/query/cypher/schema.rs +853 -0
  75. data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
  76. data/rust/rubydex/src/query/cypher/tests.rs +253 -0
  77. data/rust/rubydex/src/query/cypher.rs +54 -0
  78. data/rust/rubydex/src/query.rs +125 -43
  79. data/rust/rubydex/src/resolution.rs +368 -395
  80. data/rust/rubydex/src/resolution_tests.rs +504 -78
  81. data/rust/rubydex/src/test_utils/context.rs +2 -1
  82. data/rust/rubydex/src/test_utils/graph_test.rs +26 -12
  83. data/rust/rubydex/src/test_utils/local_graph_test.rs +19 -0
  84. data/rust/rubydex/tests/cli.rs +4 -4
  85. data/rust/rubydex-sys/src/config_api.rs +205 -0
  86. data/rust/rubydex-sys/src/cypher_api.rs +791 -0
  87. data/rust/rubydex-sys/src/declaration_api.rs +6 -3
  88. data/rust/rubydex-sys/src/definition_api.rs +27 -7
  89. data/rust/rubydex-sys/src/diagnostic_api.rs +77 -8
  90. data/rust/rubydex-sys/src/graph_api.rs +31 -68
  91. data/rust/rubydex-sys/src/lib.rs +2 -0
  92. data/rust/rubydex-sys/src/name_api.rs +2 -6
  93. data/rust/rubydex-sys/src/reference_api.rs +58 -12
  94. data/rust/rubydex-sys/src/utils.rs +37 -0
  95. data/skills/send-private-method/SKILL.md +133 -0
  96. metadata +37 -2
@@ -0,0 +1,140 @@
1
+ #include "config.h"
2
+ #include "diagnostic.h"
3
+ #include "rustbindings.h"
4
+ #include "utils.h"
5
+
6
+ /*
7
+ * RDoc parser workaround for https://github.com/ruby/rdoc/issues/1744:
8
+ * mRubydex = rb_define_module("Rubydex")
9
+ */
10
+
11
+ static VALUE mRubydex;
12
+ // Defined here so that the configuration can build them, but implemented in Ruby (`lib/rubydex/config.rb`), like the
13
+ // other value objects handed back to Ruby.
14
+ static VALUE cLinterConfig;
15
+ static VALUE cRuleConfig;
16
+ static ID id_linter;
17
+
18
+ // Free function for Rubydex::Config: releases the parsed configuration allocated by Rust.
19
+ static void config_free(void *ptr) {
20
+ if (ptr) {
21
+ rdx_config_free(ptr);
22
+ }
23
+ }
24
+
25
+ // Body function for rb_ensure while building a Rubydex::LinterConfig.
26
+ static VALUE config_linter_build(VALUE opaque_rule_array) {
27
+ CLinterRuleArray *rule_array = (CLinterRuleArray *)(uintptr_t)opaque_rule_array;
28
+ VALUE rules = rb_hash_new_capa((long)rule_array->len);
29
+
30
+ for (size_t i = 0; i < rule_array->len; i++) {
31
+ CLinterRule rule = rule_array->items[i];
32
+ VALUE rule_name = rb_str_freeze(rb_utf8_str_new(rule.name, (long)rule.name_length));
33
+ VALUE exclude_patterns = rb_ary_new_capa((long)rule.exclude_patterns_length);
34
+ for (size_t j = 0; j < rule.exclude_patterns_length; j++) {
35
+ CConfigString pattern = rule.exclude_patterns[j];
36
+ rb_ary_push(exclude_patterns, rb_str_freeze(rb_utf8_str_new(pattern.data, (long)pattern.length)));
37
+ }
38
+ rb_obj_freeze(exclude_patterns);
39
+
40
+ VALUE severity = rule.severity == NULL
41
+ ? Qnil
42
+ : rdxi_build_diagnostic_severity_value(mRubydex, *rule.severity);
43
+ VALUE argv[] = {rule_name, rule.enabled ? Qtrue : Qfalse, exclude_patterns, severity};
44
+
45
+ rb_hash_aset(rules, rule_name, rb_class_new_instance(4, argv, cRuleConfig));
46
+ }
47
+
48
+ return rb_class_new_instance(1, &rules, cLinterConfig);
49
+ }
50
+
51
+ // Ensure function for rb_ensure while building a Rubydex::LinterConfig.
52
+ static VALUE config_linter_ensure(VALUE opaque_rule_array) {
53
+ CLinterRuleArray *rule_array = (CLinterRuleArray *)(uintptr_t)opaque_rule_array;
54
+ rdx_config_linter_rules_free(*rule_array);
55
+
56
+ return Qnil;
57
+ }
58
+
59
+ static VALUE config_linter(VALUE config_obj) {
60
+ CLinterRuleArray rule_array = rdx_config_linter_rules(rdxi_config_from_object(config_obj));
61
+ VALUE opaque_rule_array = (VALUE)(uintptr_t)&rule_array;
62
+
63
+ return rb_ensure(config_linter_build, opaque_rule_array, config_linter_ensure, opaque_rule_array);
64
+ }
65
+
66
+ const rb_data_type_t config_type = {
67
+ .wrap_struct_name = "Rubydex::Config",
68
+ .function = {
69
+ .dmark = NULL,
70
+ .dfree = config_free,
71
+ .dsize = NULL,
72
+ .dcompact = NULL,
73
+ },
74
+ .parent = NULL,
75
+ .data = NULL,
76
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
77
+ };
78
+
79
+ /*
80
+ * call-seq:
81
+ * Rubydex::Config.load(workspace_path) -> Rubydex::Config
82
+ *
83
+ * Loads the configuration of the workspace rooted at +workspace_path+, which is where its `rubydex.toml` is expected to
84
+ * be. A workspace without a configuration file gets an empty configuration. Raises Rubydex::ConfigError if the file
85
+ * exists, but cannot be read or is malformed.
86
+ */
87
+ static VALUE rdxr_config_load(VALUE klass, VALUE workspace_path) {
88
+ Check_Type(workspace_path, T_STRING);
89
+
90
+ struct CConfigResult result = rdx_config_load(StringValueCStr(workspace_path));
91
+ if (result.error != NULL) {
92
+ VALUE message = rb_utf8_str_new_cstr(result.error);
93
+ free_c_string(result.error);
94
+
95
+ VALUE config_error = rb_const_get(mRubydex, rb_intern("ConfigError"));
96
+ rb_exc_raise(rb_exc_new_str(config_error, message));
97
+ }
98
+
99
+ VALUE config = TypedData_Wrap_Struct(klass, &config_type, result.config);
100
+ rb_ivar_set(config, id_linter, config_linter(config));
101
+
102
+ return config;
103
+ }
104
+
105
+ /*
106
+ * call-seq:
107
+ * workspace_path -> String
108
+ *
109
+ * Returns the root directory of the workspace this configuration was loaded for.
110
+ */
111
+ static VALUE rdxr_config_workspace_path(VALUE self) {
112
+ const char *result = rdx_config_workspace_path(rdxi_config_from_object(self));
113
+ // A configuration always has a workspace, so there is no absent case to hand back to Ruby. NULL only means the
114
+ // conversion itself failed, which is why this raises instead of returning nil.
115
+ if (result == NULL) {
116
+ rb_raise(rb_eRuntimeError, "Converting workspace path to Ruby string failed");
117
+ }
118
+
119
+ return rdxi_owned_c_string_to_ruby(result);
120
+ }
121
+
122
+ void rdxi_initialize_config(VALUE moduleRubydex) {
123
+ mRubydex = moduleRubydex;
124
+
125
+ cLinterConfig = rb_define_class_under(mRubydex, "LinterConfig", rb_cObject);
126
+ cRuleConfig = rb_define_class_under(mRubydex, "RuleConfig", rb_cObject);
127
+ id_linter = rb_intern("@linter");
128
+
129
+ VALUE cConfig = rb_define_class_under(mRubydex, "Config", rb_cObject);
130
+ rb_undef_alloc_func(cConfig);
131
+
132
+ // A configuration can only be obtained through `load`; `new` would create an object with no Rust data behind it.
133
+ rb_undef_method(rb_singleton_class(cConfig), "new");
134
+
135
+ rb_define_singleton_method(cConfig, "load", rdxr_config_load, 1);
136
+ rb_define_method(cConfig, "workspace_path", rdxr_config_workspace_path, 0);
137
+
138
+ /* Returns the linter settings read from the `[linter]` section. Its rules are empty when the section is absent. */
139
+ rb_define_attr(cConfig, "linter", true, false);
140
+ }
@@ -0,0 +1,16 @@
1
+ #ifndef RUBYDEX_CONFIG_H
2
+ #define RUBYDEX_CONFIG_H
3
+
4
+ #include "ruby.h"
5
+
6
+ extern const rb_data_type_t config_type;
7
+
8
+ static inline void *rdxi_config_from_object(VALUE config_obj) {
9
+ void *config;
10
+ TypedData_Get_Struct(config_obj, void *, &config_type, config);
11
+ return config;
12
+ }
13
+
14
+ void rdxi_initialize_config(VALUE mRubydex);
15
+
16
+ #endif // RUBYDEX_CONFIG_H
@@ -236,7 +236,7 @@ static VALUE rdxr_declaration_owner(VALUE self) {
236
236
  const CDeclaration *decl = rdx_declaration_owner(graph, data->id);
237
237
 
238
238
  if (decl == NULL) {
239
- rb_raise(rb_eRuntimeError, "owner can never be nil for any declarations");
239
+ rb_raise(rb_eRuntimeError, "Declaration must exist for a valid id");
240
240
  }
241
241
 
242
242
  VALUE decl_class = rdxi_declaration_class_for_kind(decl->kind);
@@ -18,6 +18,7 @@ static VALUE mRubydex;
18
18
  static VALUE cInclude;
19
19
  static VALUE cPrepend;
20
20
  static VALUE cExtend;
21
+ static VALUE cDocument;
21
22
  VALUE cComment;
22
23
  VALUE cDefinition;
23
24
  VALUE cClassDefinition;
@@ -88,6 +89,9 @@ static VALUE rdxr_definition_location(VALUE self) {
88
89
  void *graph = rdxi_graph_from_handle(self, &data);
89
90
 
90
91
  Location *loc = rdx_definition_location(graph, data->id);
92
+ if (loc == NULL) {
93
+ rb_raise(rb_eRuntimeError, "Definition must exist for a valid id");
94
+ }
91
95
  VALUE location = rdxi_build_location_value(loc);
92
96
  rdx_location_free(loc);
93
97
 
@@ -105,10 +109,11 @@ static VALUE rdxr_definition_comments(VALUE self) {
105
109
  void *graph = rdxi_graph_from_handle(self, &data);
106
110
 
107
111
  CommentArray *arr = rdx_definition_comments(graph, data->id);
108
- if (arr == NULL || arr->len == 0) {
109
- if (arr != NULL) {
110
- rdx_definition_comments_free(arr);
111
- }
112
+ if (arr == NULL) {
113
+ rb_raise(rb_eRuntimeError, "Definition must exist for a valid id");
114
+ }
115
+ if (arr->len == 0) {
116
+ rdx_definition_comments_free(arr);
112
117
  return rb_ary_new();
113
118
  }
114
119
 
@@ -261,6 +266,27 @@ static VALUE rdxr_definition_lexical_nesting(VALUE self) {
261
266
  return nesting;
262
267
  }
263
268
 
269
+ /*
270
+ * call-seq:
271
+ * document -> Rubydex::Document
272
+ *
273
+ * Returns the document this definition belongs to.
274
+ */
275
+ static VALUE rdxr_definition_document(VALUE self) {
276
+ HandleData *data;
277
+ void *graph = rdxi_graph_from_handle(self, &data);
278
+
279
+ const uint64_t *uri_id = rdx_definition_document(graph, data->id);
280
+ if (uri_id == NULL) {
281
+ rb_raise(rb_eRuntimeError, "Definition not found");
282
+ }
283
+
284
+ VALUE argv[] = {data->graph_obj, ULL2NUM(*uri_id)};
285
+ free_u64(uri_id);
286
+
287
+ return rb_class_new_instance(2, argv, cDocument);
288
+ }
289
+
264
290
  static VALUE rdxi_build_constant_reference(VALUE graph_obj, const CConstantReference *cref) {
265
291
  VALUE ref_class = (cref->declaration_id == 0)
266
292
  ? cUnresolvedConstantReference
@@ -397,6 +423,7 @@ void rdxi_initialize_definition(VALUE mod) {
397
423
  cInclude = rb_const_get(mRubydex, rb_intern("Include"));
398
424
  cPrepend = rb_const_get(mRubydex, rb_intern("Prepend"));
399
425
  cExtend = rb_const_get(mRubydex, rb_intern("Extend"));
426
+ cDocument = rb_const_get(mRubydex, rb_intern("Document"));
400
427
 
401
428
  cComment = rb_define_class_under(mRubydex, "Comment", rb_cObject);
402
429
 
@@ -412,6 +439,7 @@ void rdxi_initialize_definition(VALUE mod) {
412
439
  rb_define_method(cDefinition, "declaration", rdxr_definition_declaration, 0);
413
440
  rb_define_method(cDefinition, "lexical_owner", rdxr_definition_lexical_owner, 0);
414
441
  rb_define_method(cDefinition, "lexical_nesting", rdxr_definition_lexical_nesting, 0);
442
+ rb_define_method(cDefinition, "document", rdxr_definition_document, 0);
415
443
 
416
444
  cClassDefinition = rb_define_class_under(mRubydex, "ClassDefinition", cDefinition);
417
445
  rb_define_method(cClassDefinition, "superclass", rdxr_class_definition_superclass, 0);
@@ -6,6 +6,80 @@
6
6
  * mRubydex = rb_define_module("Rubydex")
7
7
  */
8
8
 
9
+ static VALUE mRubydex;
10
+ static VALUE mRules;
9
11
  VALUE cDiagnostic;
12
+ static VALUE cRule;
13
+ static ID id_default_severity;
10
14
 
11
- void rdxi_initialize_diagnostic(VALUE mRubydex) { cDiagnostic = rb_define_class_under(mRubydex, "Diagnostic", rb_cObject); }
15
+ VALUE rdxi_build_diagnostic_severity_value(VALUE mRubydex, DiagnosticSeverity severity) {
16
+ VALUE mSeverity = rb_const_get(mRubydex, rb_intern("Severity"));
17
+
18
+ switch (severity) {
19
+ case DiagnosticSeverity_Error:
20
+ return rb_const_get(mSeverity, rb_intern("Error"));
21
+ case DiagnosticSeverity_Warning:
22
+ return rb_const_get(mSeverity, rb_intern("Warning"));
23
+ case DiagnosticSeverity_Information:
24
+ return rb_const_get(mSeverity, rb_intern("Information"));
25
+ case DiagnosticSeverity_Hint:
26
+ return rb_const_get(mSeverity, rb_intern("Hint"));
27
+ default:
28
+ rb_raise(rb_eRuntimeError, "Unknown DiagnosticSeverity: %d", severity);
29
+ }
30
+
31
+ return Qnil;
32
+ }
33
+
34
+ /*
35
+ * call-seq:
36
+ * default_severity -> Rubydex::Severity::Base
37
+ *
38
+ * Returns the Rubydex::Severity subclass the diagnostics of this rule get unless the configuration overrides it.
39
+ */
40
+ static VALUE rdxr_generated_rule_default_severity(VALUE self) {
41
+ VALUE severity = rb_attr_get(self, id_default_severity);
42
+
43
+ // Class-level instance variables are not inherited, so a class inheriting from a generated rule reaches this without
44
+ // a severity of its own.
45
+ if (NIL_P(severity)) {
46
+ rb_raise(rb_eRuntimeError, "Rule %s has no default severity", rb_class2name(self));
47
+ }
48
+
49
+ return severity;
50
+ }
51
+
52
+ // Generates a rule class for every rule the graph can report. Severities are resolved here rather than when they are
53
+ // read, which is why Rubydex::Severity is loaded before this extension (see `lib/rubydex.rb`).
54
+ static void define_generated_rules(void) {
55
+ CRuleArray rule_array = rdx_rules();
56
+ VALUE rules = rb_ary_new_capa((long)rule_array.len);
57
+
58
+ for (size_t i = 0; i < rule_array.len; i++) {
59
+ CRule built_in_rule = rule_array.items[i];
60
+ VALUE name = rb_utf8_str_new(built_in_rule.name, (long)built_in_rule.name_length);
61
+ VALUE rule = rb_define_class_under(mRules, StringValueCStr(name), cRule);
62
+ VALUE severity = rdxi_build_diagnostic_severity_value(mRubydex, built_in_rule.default_severity);
63
+
64
+ rb_ivar_set(rule, id_default_severity, severity);
65
+ rb_define_singleton_method(rule, "default_severity", rdxr_generated_rule_default_severity, 0);
66
+ rb_ary_push(rules, rule);
67
+ }
68
+
69
+ rdx_rules_free(rule_array);
70
+ rb_define_const(mRules, "ALL", rb_obj_freeze(rules));
71
+ }
72
+
73
+ VALUE rdxi_rule_class_from_name(const char *name, size_t length) {
74
+ return rb_const_get_at(mRules, rb_intern2(name, (long)length));
75
+ }
76
+
77
+ void rdxi_initialize_diagnostic(VALUE moduleRubydex) {
78
+ mRubydex = moduleRubydex;
79
+ id_default_severity = rb_intern("@default_severity");
80
+
81
+ cDiagnostic = rb_define_class_under(mRubydex, "Diagnostic", rb_cObject);
82
+ cRule = rb_define_class_under(mRubydex, "Rule", rb_cObject);
83
+ mRules = rb_define_module_under(mRubydex, "Rules");
84
+ define_generated_rules();
85
+ }
@@ -7,5 +7,7 @@
7
7
  extern VALUE cDiagnostic;
8
8
 
9
9
  void rdxi_initialize_diagnostic(VALUE mRubydex);
10
+ VALUE rdxi_build_diagnostic_severity_value(VALUE mRubydex, DiagnosticSeverity severity);
11
+ VALUE rdxi_rule_class_from_name(const char *name, size_t length);
10
12
 
11
13
  #endif // RUBYDEX_DIAGNOSTIC_H
data/ext/rubydex/graph.c CHANGED
@@ -1,4 +1,5 @@
1
1
  #include "graph.h"
2
+ #include "config.h"
2
3
  #include "declaration.h"
3
4
  #include "diagnostic.h"
4
5
  #include "document.h"
@@ -63,16 +64,25 @@ const rb_data_type_t graph_type = {
63
64
  },
64
65
  .parent = NULL,
65
66
  .data = NULL,
66
- .flags = RUBY_TYPED_FREE_IMMEDIATELY,
67
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
67
68
  };
68
69
 
69
- // Custom allocator for the Graph class. Calls into Rust to create a new `Arc<Mutex<Graph>>` that gets stored internally
70
- // as a void pointer
70
+ // Custom allocator for the Graph class. Calls into Rust to create a new Box<RwLock<Graph>> that gets stored
71
+ // internally as an opaque void pointer. The RwLock provides thread-safe concurrent access across Ractors.
71
72
  static VALUE rdxr_graph_alloc(VALUE klass) {
72
73
  void *graph = rdx_graph_new();
73
74
  return TypedData_Wrap_Struct(klass, &graph_type, graph);
74
75
  }
75
76
 
77
+ // Graph is intentionally non-copyable. A dup/clone of the TypedData wrapper would alias the
78
+ // same underlying Rust allocation (double-free on GC) or, if deep-copied, balloon memory use.
79
+ // Block both paths: `dup` dispatches to `initialize_copy`, `clone` to `initialize_clone`.
80
+ static VALUE rdxr_graph_initialize_copy(VALUE self, VALUE other) {
81
+ (void)self;
82
+ (void)other;
83
+ rb_raise(rb_eRuntimeError, "Rubydex::Graph cannot be duplicated or cloned");
84
+ }
85
+
76
86
  /*
77
87
  * call-seq:
78
88
  * index_all(file_paths) -> Array[String]
@@ -483,6 +493,7 @@ static VALUE rdxr_graph_set_encoding(VALUE self, VALUE encoding) {
483
493
  static VALUE rdxr_graph_resolve_constant(VALUE self, VALUE const_name, VALUE nesting) {
484
494
  Check_Type(const_name, T_STRING);
485
495
  rdxi_check_array_of_strings(nesting);
496
+ const char *const_name_string = StringValueCStr(const_name);
486
497
 
487
498
  // Convert the given file paths into a char** array, so that we can pass to Rust
488
499
  size_t length = RARRAY_LEN(nesting);
@@ -492,7 +503,7 @@ static VALUE rdxr_graph_resolve_constant(VALUE self, VALUE const_name, VALUE nes
492
503
  TypedData_Get_Struct(self, void *, &graph_type, graph);
493
504
 
494
505
  const CDeclaration *decl =
495
- rdx_graph_resolve_constant(graph, StringValueCStr(const_name), (const char **)converted_file_paths, length);
506
+ rdx_graph_resolve_constant(graph, const_name_string, (const char **)converted_file_paths, length);
496
507
 
497
508
  rdxi_free_str_array(converted_file_paths, length);
498
509
 
@@ -622,7 +633,7 @@ static VALUE rdxr_graph_diagnostics(VALUE self) {
622
633
  for (size_t i = 0; i < array->len; i++) {
623
634
  DiagnosticEntry entry = array->items[i];
624
635
  VALUE message = entry.message == NULL ? Qnil : rb_utf8_str_new_cstr(entry.message);
625
- VALUE rule = rb_str_intern(rb_str_new2(entry.rule));
636
+ VALUE rule = rdxi_rule_class_from_name(entry.rule.name, entry.rule.name_length);
626
637
  VALUE location = rdxi_build_location_value(entry.location);
627
638
 
628
639
  VALUE kwargs = rb_hash_new();
@@ -778,6 +789,7 @@ static VALUE rdxr_graph_complete_method_argument(int argc, VALUE *argv, VALUE se
778
789
 
779
790
  Check_Type(name, T_STRING);
780
791
  rdxi_check_array_of_strings(nesting);
792
+ const char *name_string = StringValueCStr(name);
781
793
 
782
794
  const char *self_receiver = extract_self_receiver(opts);
783
795
 
@@ -788,7 +800,7 @@ static VALUE rdxr_graph_complete_method_argument(int argc, VALUE *argv, VALUE se
788
800
  char **converted_nesting = rdxi_str_array_to_char(nesting, nesting_count);
789
801
 
790
802
  struct CompletionResult result = rdx_graph_complete_method_argument(
791
- graph, StringValueCStr(name), (const char *const *)converted_nesting, nesting_count, self_receiver);
803
+ graph, name_string, (const char *const *)converted_nesting, nesting_count, self_receiver);
792
804
 
793
805
  rdxi_free_str_array(converted_nesting, nesting_count);
794
806
  return completion_result_to_ruby_array(result, self);
@@ -863,52 +875,18 @@ static VALUE rdxr_graph_workspace_path(VALUE self) {
863
875
 
864
876
  /*
865
877
  * call-seq:
866
- * workspace_path=(path) -> void
878
+ * load_config(config) -> void
867
879
  *
868
- * Sets the root directory of the workspace being indexed.
880
+ * Applies a parsed Rubydex::Config to the graph.
869
881
  */
870
- static VALUE rdxr_graph_set_workspace_path(VALUE self, VALUE path) {
871
- Check_Type(path, T_STRING);
872
-
873
- void *graph;
874
- TypedData_Get_Struct(self, void*, &graph_type, graph);
875
-
876
- rdx_graph_set_workspace_path(graph, StringValueCStr(path));
877
- return path;
878
- }
879
-
880
- /*
881
- * call-seq:
882
- * load_config(config_path = nil) -> void
883
- *
884
- * Loads a configuration file for the graph. If `config_path` is nil, loads the default configuration file at
885
- * `workspace_path/rubydex.toml` if it exists. Will raise on malformed files or if an explicit path is given but the
886
- * file does not exist.
887
- */
888
- static VALUE rdxr_graph_load_config(int argc, VALUE *argv, VALUE self) {
889
- VALUE config_path;
890
- rb_scan_args(argc, argv, "01", &config_path);
891
-
882
+ static VALUE rdxr_graph_load_config(VALUE self, VALUE config_obj) {
892
883
  void *graph;
893
884
  TypedData_Get_Struct(self, void *, &graph_type, graph);
894
885
 
895
- const char *config_path_cstr = NULL;
896
-
897
- if (!NIL_P(config_path)) {
898
- Check_Type(config_path, T_STRING);
899
- config_path_cstr = StringValueCStr(config_path);
900
- }
901
-
902
- const char *error = rdx_graph_load_config(graph, config_path_cstr);
903
- if (error == NULL) {
904
- return Qnil;
905
- }
886
+ void *config = rdxi_config_from_object(config_obj);
887
+ rdx_graph_load_config(graph, config);
906
888
 
907
- VALUE message = rb_utf8_str_new_cstr(error);
908
- free_c_string(error);
909
-
910
- VALUE config_error = rb_const_get(mRubydex, rb_intern("ConfigError"));
911
- rb_exc_raise(rb_exc_new_str(config_error, message));
889
+ return Qnil;
912
890
  }
913
891
 
914
892
  /*
@@ -943,6 +921,8 @@ void rdxi_initialize_graph(VALUE moduleRubydex) {
943
921
  id_self_receiver = rb_intern("self_receiver");
944
922
 
945
923
  rb_define_alloc_func(cGraph, rdxr_graph_alloc);
924
+ rb_define_method(cGraph, "initialize_copy", rdxr_graph_initialize_copy, 1);
925
+ rb_define_method(cGraph, "initialize_clone", rdxr_graph_initialize_copy, 1);
946
926
  rb_define_method(cGraph, "index_all", rdxr_graph_index_all, 1);
947
927
  rb_define_method(cGraph, "index_source", rdxr_graph_index_source, 3);
948
928
  rb_define_method(cGraph, "document", rdxr_graph_document, 1);
@@ -968,7 +948,6 @@ void rdxi_initialize_graph(VALUE moduleRubydex) {
968
948
  rb_define_method(cGraph, "exclude_patterns", rdxr_graph_exclude_patterns, 1);
969
949
  rb_define_method(cGraph, "excluded_patterns", rdxr_graph_excluded_patterns, 0);
970
950
  rb_define_method(cGraph, "workspace_path", rdxr_graph_workspace_path, 0);
971
- rb_define_method(cGraph, "workspace_path=", rdxr_graph_set_workspace_path, 1);
972
- rb_define_method(cGraph, "load_config", rdxr_graph_load_config, -1);
951
+ rb_define_method(cGraph, "load_config", rdxr_graph_load_config, 1);
973
952
  rb_define_method(cGraph, "keyword", rdxr_graph_keyword, 1);
974
953
  }
data/ext/rubydex/graph.h CHANGED
@@ -5,6 +5,12 @@
5
5
 
6
6
  extern const rb_data_type_t graph_type;
7
7
 
8
+ static inline void *rdxi_graph_from_object(VALUE graph_obj) {
9
+ void *graph;
10
+ TypedData_Get_Struct(graph_obj, void *, &graph_type, graph);
11
+ return graph;
12
+ }
13
+
8
14
  void rdxi_initialize_graph(VALUE mRubydex);
9
15
 
10
16
  #endif // RUBYDEX_GRAPH_H