code_ranker_plugin_javascript/
lib.rs1use anyhow::Result;
10use code_ranker_ecmascript_core::{
11 analyze_ecmascript, annotate_ecmascript_metrics, ecmascript_is_test_path, ecmascript_level,
12};
13use code_ranker_plugin_api::{
14 graph::Graph,
15 level::Level,
16 plugin::{LanguagePlugin, PluginInput, detect_with_marker},
17};
18use std::path::Path;
19
20pub struct JavascriptPlugin;
22
23const JS_EXTS: &[&str] = &["js", "jsx", "mjs", "cjs"];
24
25impl LanguagePlugin for JavascriptPlugin {
26 fn name(&self) -> &str {
27 "javascript"
28 }
29
30 fn detect(&self, workspace: &Path, _input: &PluginInput) -> bool {
31 detect_with_marker(workspace, "package.json")
32 }
33
34 fn levels(&self) -> Vec<Level> {
35 vec![ecmascript_level("files")]
36 }
37
38 fn analyze(&self, workspace: &Path, _level: &str, input: &PluginInput) -> Result<Graph> {
39 analyze_ecmascript(
40 workspace,
41 JS_EXTS,
42 |ext| match ext {
43 "js" | "jsx" | "mjs" => Some(tree_sitter_javascript::LANGUAGE.into()),
44 _ => None,
45 },
46 &["js", "jsx", "mjs", "cjs"],
47 input.ignore_tests,
48 )
49 }
50
51 fn metrics(&self, graph: &mut Graph) -> usize {
52 annotate_ecmascript_metrics(graph, |ext| match ext {
53 "js" | "jsx" | "mjs" | "cjs" => Some((tree_sitter_javascript::LANGUAGE.into(), false)),
54 _ => None,
55 })
56 }
57
58 fn is_test_path(&self, rel_path: &str) -> bool {
59 ecmascript_is_test_path(rel_path)
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use code_ranker_test_support::{edge_count_from, has_node, write_file};
67 use std::fs;
68 use tempfile::TempDir;
69
70 #[test]
71 fn plugin_name_is_javascript() {
72 assert_eq!(JavascriptPlugin.name(), "javascript");
73 }
74
75 #[test]
76 fn detect_requires_package_json() {
77 let tmp = TempDir::new().unwrap();
78 let input = PluginInput::default();
79 assert!(!JavascriptPlugin.detect(tmp.path(), &input));
80 fs::write(tmp.path().join("package.json"), "{}").unwrap();
81 assert!(JavascriptPlugin.detect(tmp.path(), &input));
82 }
83
84 #[test]
85 fn levels_returns_single_files_level() {
86 let levels = JavascriptPlugin.levels();
87 assert_eq!(levels.len(), 1);
88 assert_eq!(levels[0].name, "files");
89 assert!(levels[0].edge_kinds.contains_key("uses"));
90 }
91
92 #[test]
93 fn analyze_builds_js_graph_with_imports_and_externals() {
94 let tmp = TempDir::new().unwrap();
95 let root = tmp.path();
96 write_file(
97 root,
98 "src/a.js",
99 "import { greet } from \"./b\";\n\
100 import React from \"react\";\n\
101 export function helper() { return greet(); }\n",
102 );
103 write_file(root, "src/b.js", "export function greet() { return 1; }\n");
104
105 let graph = JavascriptPlugin
106 .analyze(root, "files", &PluginInput::default())
107 .expect("analyze should succeed");
108
109 let a_id = root.join("src/a.js").to_string_lossy().into_owned();
110 let b_id = root.join("src/b.js").to_string_lossy().into_owned();
111 assert!(has_node(&graph, &a_id), "a.js file node present");
112 assert!(
113 graph
114 .edges
115 .iter()
116 .any(|e| e.source == a_id && e.target == b_id && e.kind == "uses"),
117 "import edge a.js → b.js"
118 );
119 assert!(has_node(&graph, "ext:react"), "external node for react");
120 assert_eq!(edge_count_from(&graph, &a_id, "uses"), 2, "./b + react");
121 }
122
123 #[test]
124 fn metrics_annotates_file_nodes() {
125 let tmp = TempDir::new().unwrap();
126 let root = tmp.path();
127 write_file(
128 root,
129 "src/a.js",
130 "export function f(x) { if (x > 0) { return 1; } return 2; }\n",
131 );
132 let mut graph = JavascriptPlugin
133 .analyze(root, "files", &PluginInput::default())
134 .expect("analyze should succeed");
135 let annotated = JavascriptPlugin.metrics(&mut graph);
136 assert_eq!(annotated, 1, "the single .js file node is annotated");
137
138 let a_id = root.join("src/a.js").to_string_lossy().into_owned();
139 let node = graph.nodes.iter().find(|n| n.id == a_id).unwrap();
140 assert!(
142 node.attrs.contains_key("cyclomatic"),
143 "cyclomatic written onto the file node"
144 );
145 }
146
147 #[test]
148 fn cjs_is_not_detected_as_test() {
149 assert!(JavascriptPlugin.is_test_path("src/a.test.js"));
152 assert!(!JavascriptPlugin.is_test_path("src/a.js"));
153 }
154}