rubydex 0.4.0 → 0.4.2
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 +4 -4
- data/README.md +9 -14
- data/THIRD_PARTY_LICENSES.html +13 -24
- data/ext/rubydex/definition.c +34 -0
- data/ext/rubydex/definition.h +3 -0
- data/ext/rubydex/diagnostic.c +2 -49
- data/ext/rubydex/extconf.rb +4 -2
- data/ext/rubydex/graph.c +59 -11
- data/lib/rubydex/cli/command/dead_code.rb +59 -0
- data/lib/rubydex/cli/command/lint/explain.rb +8 -6
- data/lib/rubydex/cli/command/lint.rb +10 -4
- data/lib/rubydex/linter/rule_loader.rb +1 -1
- data/lib/rubydex/mcp_server/tools/codebase_stats_tool.rb +1 -1
- data/lib/rubydex/mcp_server/tools/find_constant_references_tool.rb +4 -4
- data/lib/rubydex/mcp_server/tools/find_dead_code_candidates_tool.rb +33 -0
- data/lib/rubydex/mcp_server/tools/get_declaration_tool.rb +2 -2
- data/lib/rubydex/mcp_server/tools/get_descendants_tool.rb +4 -4
- data/lib/rubydex/mcp_server/tools/get_file_declarations_tool.rb +2 -2
- data/lib/rubydex/mcp_server/tools/search_declarations_tool.rb +6 -6
- data/lib/rubydex/mcp_server.rb +24 -12
- data/lib/rubydex/rules/dynamic_ancestor.rb +29 -0
- data/lib/rubydex/rules/dynamic_constant_reference.rb +25 -0
- data/lib/rubydex/rules/dynamic_singleton_definition.rb +30 -0
- data/lib/rubydex/rules/invalid_constant_visibility.rb +28 -0
- data/lib/rubydex/rules/invalid_method_visibility.rb +28 -0
- data/lib/rubydex/rules/parse_error.rb +25 -0
- data/lib/rubydex/rules/parse_warning.rb +28 -0
- data/lib/rubydex/rules/top_level_mixin_self.rb +27 -0
- data/lib/rubydex/rules/undefined_constant_visibility_target.rb +27 -0
- data/lib/rubydex/rules/undefined_method_visibility_target.rb +27 -0
- data/lib/rubydex/skill.rb +1 -0
- data/lib/rubydex/version.rb +1 -1
- data/lib/rubydex.rb +2 -0
- data/lib/rubydex_linter/rules/rule_structure.rb +19 -4
- data/rbi/rubydex.rbi +32 -130
- data/rust/Cargo.lock +17 -11
- data/rust/Cargo.toml +3 -0
- data/rust/rubydex/Cargo.toml +6 -2
- data/rust/rubydex/src/bin/generate_ruby_rules.rs +94 -0
- data/rust/rubydex/src/compile_assertions.rs +2 -0
- data/rust/rubydex/src/config.rs +1 -1
- data/rust/rubydex/src/diagnostic.rs +142 -51
- data/rust/rubydex/src/indexing/rbs_indexer.rs +4 -4
- data/rust/rubydex/src/indexing/ruby_indexer.rs +26 -18
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +42 -2
- data/rust/rubydex/src/indexing.rs +6 -6
- data/rust/rubydex/src/listing.rs +12 -12
- data/rust/rubydex/src/model/built_in.rs +1 -1
- data/rust/rubydex/src/model/comment.rs +4 -4
- data/rust/rubydex/src/model/declaration.rs +25 -17
- data/rust/rubydex/src/model/document.rs +11 -11
- data/rust/rubydex/src/model/graph.rs +3 -3
- data/rust/rubydex/src/offset.rs +1 -13
- data/rust/rubydex/src/operation/ruby_builder.rs +29 -19
- data/rust/rubydex/src/query.rs +193 -2
- data/rust/rubydex/src/test_utils/graph_test.rs +2 -2
- data/rust/rubydex/src/test_utils/local_graph_test.rs +13 -6
- data/rust/rubydex-sys/Cargo.toml +2 -2
- data/rust/rubydex-sys/src/definition_api.rs +69 -2
- data/rust/rubydex-sys/src/diagnostic_api.rs +0 -35
- data/rust/rubydex-sys/src/graph_api.rs +193 -45
- data/rust/rubydex-sys/src/name_api.rs +219 -48
- metadata +191 -180
data/rust/Cargo.lock
CHANGED
|
@@ -99,7 +99,7 @@ dependencies = [
|
|
|
99
99
|
"quote",
|
|
100
100
|
"regex",
|
|
101
101
|
"rustc-hash",
|
|
102
|
-
"shlex",
|
|
102
|
+
"shlex 1.3.0",
|
|
103
103
|
"syn",
|
|
104
104
|
]
|
|
105
105
|
|
|
@@ -151,7 +151,7 @@ version = "1.2.30"
|
|
|
151
151
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
152
152
|
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
|
|
153
153
|
dependencies = [
|
|
154
|
-
"shlex",
|
|
154
|
+
"shlex 1.3.0",
|
|
155
155
|
]
|
|
156
156
|
|
|
157
157
|
[[package]]
|
|
@@ -718,9 +718,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|
|
718
718
|
|
|
719
719
|
[[package]]
|
|
720
720
|
name = "ruby-prism"
|
|
721
|
-
version = "
|
|
721
|
+
version = "2.0.0-rc.2"
|
|
722
722
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
723
|
-
checksum = "
|
|
723
|
+
checksum = "6d34b0f30ca0b7233a454827204ee7dc1a70d8faa3a26eae95f09ada5386757c"
|
|
724
724
|
dependencies = [
|
|
725
725
|
"ruby-prism-sys",
|
|
726
726
|
"serde",
|
|
@@ -729,12 +729,12 @@ dependencies = [
|
|
|
729
729
|
|
|
730
730
|
[[package]]
|
|
731
731
|
name = "ruby-prism-sys"
|
|
732
|
-
version = "
|
|
732
|
+
version = "2.0.0-rc.2"
|
|
733
733
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
734
|
-
checksum = "
|
|
734
|
+
checksum = "f0e59b94e20c878bb618a355abd00724ad1eb4514341936c6668bea987437a29"
|
|
735
735
|
dependencies = [
|
|
736
|
-
"bindgen",
|
|
737
736
|
"cc",
|
|
737
|
+
"shlex 2.0.1",
|
|
738
738
|
]
|
|
739
739
|
|
|
740
740
|
[[package]]
|
|
@@ -760,7 +760,7 @@ dependencies = [
|
|
|
760
760
|
|
|
761
761
|
[[package]]
|
|
762
762
|
name = "rubydex"
|
|
763
|
-
version = "0.2
|
|
763
|
+
version = "0.4.2"
|
|
764
764
|
dependencies = [
|
|
765
765
|
"assert_cmd",
|
|
766
766
|
"bitflags",
|
|
@@ -789,7 +789,7 @@ dependencies = [
|
|
|
789
789
|
|
|
790
790
|
[[package]]
|
|
791
791
|
name = "rubydex-sys"
|
|
792
|
-
version = "0.2
|
|
792
|
+
version = "0.4.2"
|
|
793
793
|
dependencies = [
|
|
794
794
|
"cbindgen",
|
|
795
795
|
"libc",
|
|
@@ -902,6 +902,12 @@ version = "1.3.0"
|
|
|
902
902
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
903
903
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
904
904
|
|
|
905
|
+
[[package]]
|
|
906
|
+
name = "shlex"
|
|
907
|
+
version = "2.0.1"
|
|
908
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
909
|
+
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
910
|
+
|
|
905
911
|
[[package]]
|
|
906
912
|
name = "smallvec"
|
|
907
913
|
version = "1.15.1"
|
|
@@ -1326,9 +1332,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
|
|
1326
1332
|
|
|
1327
1333
|
[[package]]
|
|
1328
1334
|
name = "xxhash-rust"
|
|
1329
|
-
version = "0.8.
|
|
1335
|
+
version = "0.8.16"
|
|
1330
1336
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1331
|
-
checksum = "
|
|
1337
|
+
checksum = "4d93c89cdc2d3a63c3ec48ffe926931bdc069eafa8e4402fe6d8f790c9d1e576"
|
|
1332
1338
|
|
|
1333
1339
|
[[package]]
|
|
1334
1340
|
name = "yoke"
|
data/rust/Cargo.toml
CHANGED
data/rust/rubydex/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rubydex"
|
|
3
|
-
version =
|
|
3
|
+
version.workspace = true
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.89.0"
|
|
6
6
|
license = "MIT"
|
|
@@ -15,6 +15,10 @@ categories = ["development-tools"]
|
|
|
15
15
|
name = "rubydex_cli"
|
|
16
16
|
path = "src/main.rs"
|
|
17
17
|
|
|
18
|
+
[[bin]]
|
|
19
|
+
name = "generate_ruby_rules"
|
|
20
|
+
path = "src/bin/generate_ruby_rules.rs"
|
|
21
|
+
|
|
18
22
|
[lib]
|
|
19
23
|
crate-type = ["rlib"]
|
|
20
24
|
|
|
@@ -28,7 +32,7 @@ test_utils = ["dep:tempfile"]
|
|
|
28
32
|
|
|
29
33
|
[dependencies]
|
|
30
34
|
cypher-parser = "0.8.1"
|
|
31
|
-
ruby-prism = "
|
|
35
|
+
ruby-prism = "2.0.0-rc.2"
|
|
32
36
|
ruby-rbs = "0.3"
|
|
33
37
|
url = "2.5.4"
|
|
34
38
|
xxhash-rust = { version = "0.8.15", features = ["xxh3"] }
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
use rubydex::diagnostic::{Rule, Severity};
|
|
2
|
+
use std::collections::HashSet;
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
use std::{env, fs};
|
|
5
|
+
|
|
6
|
+
// Takes the built-in diagnostic rules and generates Ruby classes for them in `lib/rubydex/rules`.
|
|
7
|
+
fn main() {
|
|
8
|
+
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("..").join("..");
|
|
9
|
+
let directory = root.join("lib").join("rubydex").join("rules");
|
|
10
|
+
let mut generated_files = HashSet::new();
|
|
11
|
+
|
|
12
|
+
for rule in Rule::all().iter().copied() {
|
|
13
|
+
write_rule(&directory, rule, &mut generated_files);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Remove stale rules
|
|
17
|
+
for entry in fs::read_dir(&directory).unwrap() {
|
|
18
|
+
let entry = entry.unwrap();
|
|
19
|
+
let path = entry.path();
|
|
20
|
+
|
|
21
|
+
if path.is_file() {
|
|
22
|
+
let file_name = path.file_name().unwrap().to_string_lossy();
|
|
23
|
+
|
|
24
|
+
if !generated_files.contains(&file_name.to_string()) {
|
|
25
|
+
fs::remove_file(path).unwrap();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn write_rule(dir: &Path, rule: Rule, generated_files: &mut HashSet<String>) {
|
|
32
|
+
let documentation = rule
|
|
33
|
+
.documentation()
|
|
34
|
+
.iter()
|
|
35
|
+
.map(|line| format!(" #{line}").trim_end().to_owned())
|
|
36
|
+
.collect::<Vec<String>>()
|
|
37
|
+
.join("\n");
|
|
38
|
+
|
|
39
|
+
let contents = format!(
|
|
40
|
+
"# typed: strict
|
|
41
|
+
# frozen_string_literal: true
|
|
42
|
+
|
|
43
|
+
# This file is generated by `generate_ruby_rules.rs`. Do not edit this manually.
|
|
44
|
+
|
|
45
|
+
module Rubydex
|
|
46
|
+
module Rules
|
|
47
|
+
{documentation}
|
|
48
|
+
class {name} < Rule
|
|
49
|
+
class << self
|
|
50
|
+
# @override
|
|
51
|
+
#: -> singleton(Severity::Base)
|
|
52
|
+
def default_severity
|
|
53
|
+
{severity}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
",
|
|
60
|
+
name = rule.name(),
|
|
61
|
+
severity = to_ruby_severity(rule.default_severity()),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
let file_name = format!("{}.rb", snake_case(rule.name()));
|
|
65
|
+
fs::write(dir.join(&file_name), contents).unwrap();
|
|
66
|
+
generated_files.insert(file_name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn to_ruby_severity(severity: Severity) -> &'static str {
|
|
70
|
+
match severity {
|
|
71
|
+
Severity::Error => "Severity::Error",
|
|
72
|
+
Severity::Warning => "Severity::Warning",
|
|
73
|
+
Severity::Information => "Severity::Information",
|
|
74
|
+
Severity::Hint => "Severity::Hint",
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fn snake_case(name: &str) -> String {
|
|
79
|
+
let mut result = String::with_capacity(name.len() + 4);
|
|
80
|
+
|
|
81
|
+
for (index, character) in name.char_indices() {
|
|
82
|
+
if character.is_uppercase() {
|
|
83
|
+
if index != 0 {
|
|
84
|
+
result.push('_');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
result.extend(character.to_lowercase());
|
|
88
|
+
} else {
|
|
89
|
+
result.push(character);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
result
|
|
94
|
+
}
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
macro_rules! assert_mem_size {
|
|
4
4
|
($struct:ident, $size:expr) => {
|
|
5
5
|
// Check struct name on this assert! error
|
|
6
|
+
#[cfg(target_pointer_width = "64")]
|
|
6
7
|
const _: () = assert!(
|
|
7
8
|
std::mem::size_of::<$struct>() == $size,
|
|
8
9
|
concat!("Incorrect size for `", stringify!($struct), "`")
|
|
9
10
|
);
|
|
10
11
|
// Check actual size on this array error
|
|
12
|
+
#[cfg(target_pointer_width = "64")]
|
|
11
13
|
const _: [(); $size] = [(); std::mem::size_of::<$struct>()];
|
|
12
14
|
};
|
|
13
15
|
}
|
data/rust/rubydex/src/config.rs
CHANGED
|
@@ -433,7 +433,7 @@ mod tests {
|
|
|
433
433
|
|
|
434
434
|
let other = rules.iter().find(|rule| rule.name() == "Other").unwrap();
|
|
435
435
|
assert!(!other.enabled());
|
|
436
|
-
|
|
436
|
+
assert_eq!(other.exclude_patterns(), []);
|
|
437
437
|
assert_eq!(other.severity(), None);
|
|
438
438
|
}
|
|
439
439
|
|
|
@@ -64,10 +64,10 @@ pub enum Severity {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
macro_rules! rules {
|
|
67
|
-
($($(#[$
|
|
67
|
+
($($(#[doc = $documentation:literal])+ $rule:ident => $severity:ident),+ $(,)?) => {
|
|
68
68
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
69
69
|
pub enum Rule {
|
|
70
|
-
$($(#[$
|
|
70
|
+
$($(#[doc = $documentation])+ $rule,)+
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
impl Rule {
|
|
@@ -75,60 +75,151 @@ macro_rules! rules {
|
|
|
75
75
|
pub fn all() -> &'static [Self] {
|
|
76
76
|
&[$(Self::$rule,)+]
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
#[must_use]
|
|
80
|
+
pub fn name(&self) -> &'static str {
|
|
81
|
+
match self {
|
|
82
|
+
$(Self::$rule => stringify!($rule),)+
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[must_use]
|
|
87
|
+
pub fn default_severity(&self) -> Severity {
|
|
88
|
+
match self {
|
|
89
|
+
$(Self::$rule => Severity::$severity,)+
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[must_use]
|
|
94
|
+
pub fn documentation(&self) -> &'static [&'static str] {
|
|
95
|
+
match self {
|
|
96
|
+
$(Self::$rule => &[$($documentation,)+],)+
|
|
97
|
+
}
|
|
98
|
+
}
|
|
78
99
|
}
|
|
79
100
|
};
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
rules! {
|
|
83
|
-
// Parsing
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
104
|
+
// ******** Parsing ******** //
|
|
105
|
+
|
|
106
|
+
/// A parse error represents invalid Ruby syntax and a program that will fail to execute. For example, a missing
|
|
107
|
+
/// `end`, an unterminated string, a missing parenthesis.
|
|
108
|
+
///
|
|
109
|
+
/// ```ruby
|
|
110
|
+
/// class Foo
|
|
111
|
+
/// ^^^^^ Syntax error. Missing end token
|
|
112
|
+
/// ```
|
|
113
|
+
ParseError => Error,
|
|
114
|
+
|
|
115
|
+
/// Parse warnings represent code that has valid syntax, but may not do what the developer expects. For example,
|
|
116
|
+
/// local variables that are completely unused or usage in a void context (creating a line of code that does
|
|
117
|
+
/// nothing).
|
|
118
|
+
///
|
|
119
|
+
/// ```ruby
|
|
120
|
+
/// CONST = 1
|
|
121
|
+
/// CONST
|
|
122
|
+
/// ^^^^^ Constant used in void context (the expression does nothing).
|
|
123
|
+
/// puts CONST + 2
|
|
124
|
+
/// ```
|
|
125
|
+
ParseWarning => Warning,
|
|
126
|
+
|
|
127
|
+
// ******** Indexing ******** //
|
|
128
|
+
|
|
129
|
+
/// Dynamic constant references cannot be reasoned about statically because they depend on runtime values. The
|
|
130
|
+
/// program may still be valid, but the quality of the analysis degrades.
|
|
131
|
+
///
|
|
132
|
+
/// ```ruby
|
|
133
|
+
/// var::Foo
|
|
134
|
+
/// ^^^^^^^^ Dynamic constant reference. This might be correct, but it cannot be understood by the analysis.
|
|
135
|
+
/// ```
|
|
136
|
+
DynamicConstantReference => Information,
|
|
137
|
+
|
|
138
|
+
/// Dynamic singleton targets cannot be reasoned about statically because they depend on runtime values. The program
|
|
139
|
+
/// may still be valid, but the quality of the analysis degrades.
|
|
140
|
+
///
|
|
141
|
+
/// ```ruby
|
|
142
|
+
/// class << var
|
|
143
|
+
/// ^^^ Dynamic singleton target. This might be correct, but it cannot be understood by the analysis.
|
|
144
|
+
/// end
|
|
145
|
+
///
|
|
146
|
+
/// def var.bar
|
|
147
|
+
/// ^^^ Dynamic singleton target.
|
|
148
|
+
/// end
|
|
149
|
+
/// ```
|
|
150
|
+
DynamicSingletonDefinition => Information,
|
|
151
|
+
|
|
152
|
+
/// Dynamic ancestor references cannot be reasoned about statically because they depend on runtime values. The
|
|
153
|
+
/// program may still be valid, but the quality of the analysis degrades. In the case of ancestors, since they
|
|
154
|
+
/// influence constant resolution, the degradation may be more impactful than other dynamic references.
|
|
155
|
+
///
|
|
156
|
+
/// ```ruby
|
|
157
|
+
/// class Foo < var
|
|
158
|
+
/// ^^^ Dynamic ancestor reference. This might be correct, but it cannot be understood by the analysis.
|
|
159
|
+
/// include SomeClass.method_call
|
|
160
|
+
/// ^^^^^^^^^^^^^^^^^^^^^ Dynamic ancestor reference.
|
|
161
|
+
/// end
|
|
162
|
+
/// ```
|
|
163
|
+
DynamicAncestor => Information,
|
|
164
|
+
|
|
165
|
+
/// The top level of a Ruby program is the special <main> object. It is not possible to use `include self` or
|
|
166
|
+
/// `extend self` on that object.
|
|
167
|
+
///
|
|
168
|
+
/// ```ruby
|
|
169
|
+
/// include self
|
|
170
|
+
/// ^^^^ Cannot include self at the top level
|
|
171
|
+
/// extend self
|
|
172
|
+
/// ^^^^ Cannot extend self at the top level
|
|
173
|
+
/// ```
|
|
174
|
+
TopLevelMixinSelf => Information,
|
|
175
|
+
|
|
176
|
+
/// Constant visibility operations that depend on dynamic values cannot be reasoned about statically because they
|
|
177
|
+
/// depend on runtime values. The program may still be valid, but the quality of the analysis degrades.
|
|
178
|
+
///
|
|
179
|
+
/// ```ruby
|
|
180
|
+
/// var.private_constant :Foo
|
|
181
|
+
/// ^^^ Invalid constant visibility. This might be correct, but it cannot be understood by the analysis.
|
|
182
|
+
///
|
|
183
|
+
/// private_constant(var)
|
|
184
|
+
/// ^^^ Invalid constant visibility.
|
|
185
|
+
/// ```
|
|
186
|
+
InvalidConstantVisibility => Warning,
|
|
187
|
+
|
|
188
|
+
/// Method visibility operations that depend on dynamic values cannot be reasoned about statically because they
|
|
189
|
+
/// depend on runtime values. The program may still be valid, but the quality of the analysis degrades.
|
|
190
|
+
///
|
|
191
|
+
/// ```ruby
|
|
192
|
+
/// var.private :foo
|
|
193
|
+
/// ^^^ Invalid method visibility. This might be correct, but it cannot be understood by the analysis.
|
|
194
|
+
///
|
|
195
|
+
/// private(var)
|
|
196
|
+
/// ^^^ Invalid method visibility.
|
|
197
|
+
/// ```
|
|
198
|
+
InvalidMethodVisibility => Warning,
|
|
199
|
+
|
|
200
|
+
// ******** Resolution ******** //
|
|
201
|
+
|
|
202
|
+
/// Undefined method visibility target means the analysis couldn't find the definition for the method the code is
|
|
203
|
+
/// attempting to change visibility of. It could be defined through meta-programming or it indeed does not exist.
|
|
204
|
+
///
|
|
205
|
+
/// ```ruby
|
|
206
|
+
/// class Foo
|
|
207
|
+
/// private :bar
|
|
208
|
+
/// ^^^ Undefined method visibility target. The method `bar` is not defined.
|
|
209
|
+
/// end
|
|
210
|
+
/// ```
|
|
211
|
+
UndefinedMethodVisibilityTarget => Warning,
|
|
212
|
+
|
|
213
|
+
/// Undefined constant visibility target means the analysis couldn't find the definition for the constant the code is
|
|
214
|
+
/// attempting to change visibility of. It could be defined through meta-programming or it indeed does not exist.
|
|
215
|
+
///
|
|
216
|
+
/// ```ruby
|
|
217
|
+
/// class Foo
|
|
218
|
+
/// private_constant :Bar
|
|
219
|
+
/// ^^^ Undefined constant visibility target. The constant `Bar` is not defined.
|
|
220
|
+
/// end
|
|
221
|
+
/// ```
|
|
222
|
+
UndefinedConstantVisibilityTarget => Warning,
|
|
132
223
|
}
|
|
133
224
|
|
|
134
225
|
impl std::fmt::Display for Rule {
|
|
@@ -33,8 +33,8 @@ pub struct RBSIndexer<'a> {
|
|
|
33
33
|
|
|
34
34
|
impl<'a> RBSIndexer<'a> {
|
|
35
35
|
#[must_use]
|
|
36
|
-
pub fn new(uri:
|
|
37
|
-
let uri_id = UriId::from(
|
|
36
|
+
pub fn new(uri: Box<str>, source: &'a str) -> Self {
|
|
37
|
+
let uri_id = UriId::from(&*uri);
|
|
38
38
|
let local_graph = LocalGraph::new(uri_id, Document::new(uri, source));
|
|
39
39
|
|
|
40
40
|
Self {
|
|
@@ -197,7 +197,7 @@ impl<'a> RBSIndexer<'a> {
|
|
|
197
197
|
// Skip past indentation to the comment text
|
|
198
198
|
current_offset += line_indent as u32;
|
|
199
199
|
|
|
200
|
-
let line_text = line[line_indent..].
|
|
200
|
+
let line_text: Box<str> = line[line_indent..].into();
|
|
201
201
|
let line_bytes = line_text.len() as u32;
|
|
202
202
|
let offset = Offset::new(current_offset, current_offset + line_bytes);
|
|
203
203
|
comments.push(Comment::new(offset, line_text));
|
|
@@ -1454,7 +1454,7 @@ mod tests {
|
|
|
1454
1454
|
fn split_multiline_comments_crlf() {
|
|
1455
1455
|
// Build the indexer directly to bypass normalize_indentation, which strips \r
|
|
1456
1456
|
let source = "# First line\r\n# Second line\r\nclass Foo\r\nend\r\n";
|
|
1457
|
-
let mut indexer = RBSIndexer::new("file:///foo.rbs".
|
|
1457
|
+
let mut indexer = RBSIndexer::new("file:///foo.rbs".into(), source);
|
|
1458
1458
|
indexer.index();
|
|
1459
1459
|
let context = LocalGraphTest::from_local_graph("file:///foo.rbs", source, indexer.local_graph());
|
|
1460
1460
|
|
|
@@ -93,8 +93,8 @@ pub struct RubyIndexer<'a> {
|
|
|
93
93
|
|
|
94
94
|
impl<'a> RubyIndexer<'a> {
|
|
95
95
|
#[must_use]
|
|
96
|
-
pub fn new(uri:
|
|
97
|
-
let uri_id = UriId::from(
|
|
96
|
+
pub fn new(uri: Box<str>, source: &'a str) -> Self {
|
|
97
|
+
let uri_id = UriId::from(&*uri);
|
|
98
98
|
let local_graph = LocalGraph::new(uri_id, Document::new(uri, source));
|
|
99
99
|
|
|
100
100
|
Self {
|
|
@@ -120,7 +120,7 @@ impl<'a> RubyIndexer<'a> {
|
|
|
120
120
|
self.local_graph.add_diagnostic(
|
|
121
121
|
Rule::ParseError,
|
|
122
122
|
Offset::from_prism_location(&error.location()),
|
|
123
|
-
error.message().to_string(),
|
|
123
|
+
String::from_utf8_lossy(error.message()).to_string(),
|
|
124
124
|
);
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -128,7 +128,7 @@ impl<'a> RubyIndexer<'a> {
|
|
|
128
128
|
self.local_graph.add_diagnostic(
|
|
129
129
|
Rule::ParseWarning,
|
|
130
130
|
Offset::from_prism_location(&warning.location()),
|
|
131
|
-
warning.message().to_string(),
|
|
131
|
+
String::from_utf8_lossy(warning.message()).to_string(),
|
|
132
132
|
);
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -313,7 +313,9 @@ impl<'a> RubyIndexer<'a> {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
if let Some(block) = parameters_list.block()
|
|
316
|
+
if let Some(block) = parameters_list.block()
|
|
317
|
+
&& let Some(block) = block.as_block_parameter_node()
|
|
318
|
+
{
|
|
317
319
|
let location = block.name_loc().unwrap_or_else(|| block.location());
|
|
318
320
|
let str_id = self.local_graph.intern_string(Self::location_to_string(&location));
|
|
319
321
|
|
|
@@ -1389,8 +1391,8 @@ impl<'a> RubyIndexer<'a> {
|
|
|
1389
1391
|
match arg {
|
|
1390
1392
|
ruby_prism::Node::SymbolNode { .. } => {
|
|
1391
1393
|
let symbol = arg.as_symbol_node().unwrap();
|
|
1392
|
-
let
|
|
1393
|
-
Some((Self::location_to_string(&
|
|
1394
|
+
let content_loc = symbol.content_loc();
|
|
1395
|
+
Some((Self::location_to_string(&content_loc), content_loc))
|
|
1394
1396
|
}
|
|
1395
1397
|
ruby_prism::Node::StringNode { .. } => {
|
|
1396
1398
|
let string = arg.as_string_node().unwrap();
|
|
@@ -1522,7 +1524,7 @@ impl CommentGroup {
|
|
|
1522
1524
|
// Accepts the next line if it is continuous
|
|
1523
1525
|
fn accepts(&self, next: &ruby_prism::Comment, source: &str) -> bool {
|
|
1524
1526
|
let current_end_offset = self.end_offset;
|
|
1525
|
-
let next_line_start_offset = next.location().
|
|
1527
|
+
let next_line_start_offset = next.location().start() as usize;
|
|
1526
1528
|
|
|
1527
1529
|
let between = &source.as_bytes()[current_end_offset..next_line_start_offset];
|
|
1528
1530
|
if !between.iter().all(|&b| b.is_ascii_whitespace()) {
|
|
@@ -1538,7 +1540,7 @@ impl CommentGroup {
|
|
|
1538
1540
|
// 1. still move the group end offset to the end of the magic comment
|
|
1539
1541
|
// 2. not add the comment to the comments array
|
|
1540
1542
|
fn add_comment(&mut self, comment: &ruby_prism::Comment) {
|
|
1541
|
-
self.end_offset = comment.location().
|
|
1543
|
+
self.end_offset = comment.location().end() as usize;
|
|
1542
1544
|
let text = String::from_utf8_lossy(comment.location().as_slice()).to_string();
|
|
1543
1545
|
|
|
1544
1546
|
if text.lines().any(|line| line.starts_with("# @deprecated")) {
|
|
@@ -1547,7 +1549,7 @@ impl CommentGroup {
|
|
|
1547
1549
|
|
|
1548
1550
|
self.comments.push(Comment::new(
|
|
1549
1551
|
Offset::from_prism_location(&comment.location()),
|
|
1550
|
-
text.trim().
|
|
1552
|
+
text.trim().into(),
|
|
1551
1553
|
));
|
|
1552
1554
|
}
|
|
1553
1555
|
|
|
@@ -1984,12 +1986,18 @@ impl Visit<'_> for RubyIndexer<'_> {
|
|
|
1984
1986
|
});
|
|
1985
1987
|
};
|
|
1986
1988
|
|
|
1987
|
-
let message_loc = node.
|
|
1989
|
+
let message_loc = if node.name().as_slice().is_empty() {
|
|
1990
|
+
// Incomplete calls like `Foo.` have no message. We still index them using the call operator location, so
|
|
1991
|
+
// that the receiver's singleton class gets created, which completion depends on
|
|
1992
|
+
node.call_operator_loc()
|
|
1993
|
+
} else {
|
|
1994
|
+
node.message_loc()
|
|
1995
|
+
};
|
|
1988
1996
|
|
|
1989
|
-
|
|
1997
|
+
let Some(message_loc) = message_loc else {
|
|
1990
1998
|
// No message, we can't index this node
|
|
1991
1999
|
return;
|
|
1992
|
-
}
|
|
2000
|
+
};
|
|
1993
2001
|
|
|
1994
2002
|
let message = String::from_utf8_lossy(node.name().as_slice()).to_string();
|
|
1995
2003
|
|
|
@@ -2217,11 +2225,11 @@ impl Visit<'_> for RubyIndexer<'_> {
|
|
|
2217
2225
|
self.visit(&receiver);
|
|
2218
2226
|
}
|
|
2219
2227
|
|
|
2220
|
-
self.index_method_reference(message.clone(), &
|
|
2228
|
+
self.index_method_reference(message.clone(), &message_loc, method_receiver);
|
|
2221
2229
|
|
|
2222
2230
|
match message.as_str() {
|
|
2223
2231
|
">" | "<" | ">=" | "<=" => {
|
|
2224
|
-
self.index_method_reference("<=>".to_string(), &
|
|
2232
|
+
self.index_method_reference("<=>".to_string(), &message_loc, method_receiver);
|
|
2225
2233
|
}
|
|
2226
2234
|
_ => {}
|
|
2227
2235
|
}
|
|
@@ -2382,7 +2390,7 @@ impl Visit<'_> for RubyIndexer<'_> {
|
|
|
2382
2390
|
match expression {
|
|
2383
2391
|
ruby_prism::Node::SymbolNode { .. } => {
|
|
2384
2392
|
let symbol = expression.as_symbol_node().unwrap();
|
|
2385
|
-
let name = Self::location_to_string(&symbol.
|
|
2393
|
+
let name = Self::location_to_string(&symbol.content_loc());
|
|
2386
2394
|
self.index_method_reference(name, &node.location(), None);
|
|
2387
2395
|
}
|
|
2388
2396
|
_ => {
|
|
@@ -2394,13 +2402,13 @@ impl Visit<'_> for RubyIndexer<'_> {
|
|
|
2394
2402
|
|
|
2395
2403
|
fn visit_alias_method_node(&mut self, node: &ruby_prism::AliasMethodNode<'_>) {
|
|
2396
2404
|
let mut new_name = if let Some(symbol_node) = node.new_name().as_symbol_node() {
|
|
2397
|
-
Self::location_to_string(&symbol_node.
|
|
2405
|
+
Self::location_to_string(&symbol_node.content_loc())
|
|
2398
2406
|
} else {
|
|
2399
2407
|
Self::location_to_string(&node.new_name().location())
|
|
2400
2408
|
};
|
|
2401
2409
|
|
|
2402
2410
|
let mut old_name = if let Some(symbol_node) = node.old_name().as_symbol_node() {
|
|
2403
|
-
Self::location_to_string(&symbol_node.
|
|
2411
|
+
Self::location_to_string(&symbol_node.content_loc())
|
|
2404
2412
|
} else {
|
|
2405
2413
|
Self::location_to_string(&node.old_name().location())
|
|
2406
2414
|
};
|