1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
use crate::cop;
use crate::source;
use crate::types;
use std::collections::HashMap;
use std::sync::Mutex;
static COP_NAME: &str = "Bundler/DuplicatedGem";
static MSG: &str =
"Gem `%gem_name%` requirements already given on line %line_number% of the Gemfile.";
lazy_static! {
static ref GEMS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());
}
pub fn init() {
cop::register(COP_NAME);
cop::register_node_handler("send", COP_NAME, on_send);
}
pub fn on_send(node: &types::Node, file: &source::File) {
let mut gems = GEMS.lock().unwrap();
if let types::Node::Send(node) = node {
if node.method_name == "gem" {
if node.args.len() >= 1 {
if let types::Node::Str(gem) = &node.args[0] {
let gem_name = gem.value.to_string().unwrap();
if let Some(line) = gems.get(&gem_name) {
let line = line + 1;
file.add_offense(
COP_NAME,
gem.expression_l,
MSG.replace("%gem_name%", &gem_name)
.replace("%line_number%", &line.to_string()),
);
} else {
let (line, _) = file
.parser_result
.input
.line_col_for_pos(gem.expression_l.begin)
.unwrap();
gems.insert(gem_name, line); }
}
}
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_detects_offenses() {
crate::expect_offense!(
"
gem 'rails'
gem 'rails'
"
);
}
}