Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,28 @@ fn collect_language_frontend_edges(
{
return Vec::new();
}
let facts = collect_all_lang_facts(root, all_files, &config);
let mut edges = Vec::new();
emit_lang_edges(&facts.python, EdgeKind::PythonImport, EdgeKind::PythonReference, &mut edges);
emit_lang_edges(&facts.go, EdgeKind::GoImport, EdgeKind::GoReference, &mut edges);
emit_lang_edges(&facts.rust, EdgeKind::RustUse, EdgeKind::RustUse, &mut edges);
emit_mod_edges(&facts.rust, EdgeKind::RustMod, &mut edges);
emit_package_edges(&facts.rust, EdgeKind::RustPackage, &mut edges);
emit_lang_edges(&facts.ruby, EdgeKind::RubyRequire, EdgeKind::RubyReference, &mut edges);
emit_lang_edges(&facts.php, EdgeKind::PhpUse, EdgeKind::PhpUse, &mut edges);
emit_package_edges(&facts.php, EdgeKind::PhpPackage, &mut edges);
emit_queue_edges(root, &facts.python, options, &mut edges);
emit_queue_edges(root, &facts.go, options, &mut edges);
emit_queue_edges(root, &facts.ruby, options, &mut edges);
emit_queue_edges(root, &facts.php, options, &mut edges);
emit_route_edges(root, &facts.python, options, &mut edges);
emit_route_edges(root, &facts.ruby, options, &mut edges);
emit_route_edges(root, &facts.php, options, &mut edges);
emit_kafka_edges(root, all_files, options, &mut edges);
if !config_is_empty(&config) {
let facts = collect_all_lang_facts(root, all_files, &config);
emit_lang_edges(&facts.python, EdgeKind::PythonImport, EdgeKind::PythonReference, &mut edges);
emit_lang_edges(&facts.go, EdgeKind::GoImport, EdgeKind::GoReference, &mut edges);
emit_lang_edges(&facts.rust, EdgeKind::RustUse, EdgeKind::RustUse, &mut edges);
emit_mod_edges(&facts.rust, EdgeKind::RustMod, &mut edges);
emit_package_edges(&facts.rust, EdgeKind::RustPackage, &mut edges);
emit_lang_edges(&facts.ruby, EdgeKind::RubyRequire, EdgeKind::RubyReference, &mut edges);
emit_lang_edges(&facts.php, EdgeKind::PhpUse, EdgeKind::PhpUse, &mut edges);
emit_package_edges(&facts.php, EdgeKind::PhpPackage, &mut edges);
emit_queue_edges(root, &facts.python, options, &mut edges);
emit_queue_edges(root, &facts.go, options, &mut edges);
emit_queue_edges(root, &facts.ruby, options, &mut edges);
emit_queue_edges(root, &facts.php, options, &mut edges);
emit_route_edges(root, &facts.python, options, &mut edges);
emit_route_edges(root, &facts.ruby, options, &mut edges);
emit_route_edges(root, &facts.php, options, &mut edges);
}
if !options.queue_enqueues.is_empty() || !options.queue_workers.is_empty() {
emit_kafka_edges(root, all_files, options, &mut edges);
}
edges
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ pub(crate) fn count_queue_glob_matches(
queue_workers: worker_globs.to_vec(),
..GraphConfigOptions::default()
};
let enqueue = compile_queue_globs(enqueue_globs);
let worker = compile_queue_globs(worker_globs);
files
.iter()
.filter(|path| {
let enqueue = matching_queue_cluster(root, path, enqueue_globs, &options).is_some();
let worker = matching_queue_cluster(root, path, worker_globs, &options).is_some();
let enqueue = matching_queue_cluster(root, path, &enqueue, &options).is_some();
let worker = matching_queue_cluster(root, path, &worker, &options).is_some();
enqueue || worker
})
.count()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
struct CompiledQueueGlobs {
matchers: Vec<(globset::GlobMatcher, String)>,
}

fn compile_queue_globs(globs: &[String]) -> CompiledQueueGlobs {
CompiledQueueGlobs {
matchers: globs
.iter()
.filter_map(|glob| {
Comment thread
jonathanong marked this conversation as resolved.
globset::Glob::new(glob)
.ok()
.map(|compiled| (compiled.compile_matcher(), glob.clone()))
})
.collect(),
}
}

fn emit_queue_edges(
root: &Path,
facts: &LangFactMap,
options: &GraphConfigOptions,
edges: &mut Vec<Edge>,
) {
let worker_globs = compile_queue_globs(&options.queue_workers);
let enqueue_globs = compile_queue_globs(&options.queue_enqueues);
Comment thread
jonathanong marked this conversation as resolved.
let mut workers: std::collections::HashMap<String, std::collections::BTreeSet<PathBuf>> =
std::collections::HashMap::new();
for file in facts.files.values() {
let Some(cluster) = matching_queue_cluster(root, &file.path, &options.queue_workers, options)
else {
let Some(cluster) = matching_queue_cluster(root, &file.path, &worker_globs, options) else {
continue;
};
for job in &file.queue_workers {
Expand All @@ -19,8 +37,7 @@ fn emit_queue_edges(
}
}
for file in facts.files.values() {
let Some(cluster) =
matching_queue_cluster(root, &file.path, &options.queue_enqueues, options)
let Some(cluster) = matching_queue_cluster(root, &file.path, &enqueue_globs, options)
else {
continue;
};
Expand All @@ -44,22 +61,20 @@ fn emit_queue_edges(
fn matching_queue_cluster(
root: &Path,
path: &Path,
globs: &[String],
compiled: &CompiledQueueGlobs,
options: &GraphConfigOptions,
) -> Option<Option<String>> {
if globs.is_empty() {
if compiled.matchers.is_empty() {
return None;
}
let rel = path.strip_prefix(root).unwrap_or(path);
let rel_s = rel.to_string_lossy();
globs.iter().find_map(|glob| {
if !matches_any(&rel_s, std::slice::from_ref(glob)) {
return None;
}
Some(match options.queue_glob_clusters.get(glob) {
Some(cluster) => cluster.clone(),
None => options.queue_cluster.clone(),
})
compiled.matchers.iter().find_map(|(matcher, glob)| {
matcher
.is_match(rel)
.then(|| match options.queue_glob_clusters.get(glob) {
Some(cluster) => cluster.clone(),
None => options.queue_cluster.clone(),
})
})
}

Expand All @@ -69,11 +84,13 @@ fn emit_kafka_edges(
options: &GraphConfigOptions,
edges: &mut Vec<Edge>,
) {
let enqueue_globs = compile_queue_globs(&options.queue_enqueues);
let worker_globs = compile_queue_globs(&options.queue_workers);
let mut produces = Vec::new();
let mut consumes = Vec::new();
for path in all_files {
let enqueue = matching_queue_cluster(root, path, &options.queue_enqueues, options);
let worker = matching_queue_cluster(root, path, &options.queue_workers, options);
let enqueue = matching_queue_cluster(root, path, &enqueue_globs, options);
let worker = matching_queue_cluster(root, path, &worker_globs, options);
if enqueue.is_none() && worker.is_none() {
continue;
}
Expand Down Expand Up @@ -101,11 +118,7 @@ fn emit_kafka_edges(
for topic in topics {
let identity = topic_identity(cluster.as_deref(), &topic);
let node = NodeId::queue_job(&path, identity.clone());
edges.push((
NodeId::file(&path),
node.clone(),
EdgeKind::QueueEnqueue,
));
edges.push((NodeId::file(&path), node.clone(), EdgeKind::QueueEnqueue));
if let Some(targets) = workers.get(&identity) {
for worker in targets {
edges.push((node.clone(), NodeId::file(worker), EdgeKind::QueueWorker));
Expand All @@ -114,11 +127,3 @@ fn emit_kafka_edges(
}
}
}

fn matches_any(rel: &str, globs: &[String]) -> bool {
globs.iter().any(|glob| {
globset::Glob::new(glob)
.ok()
.is_some_and(|g| g.compile_matcher().is_match(rel))
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,53 @@ fn language_frontend_edges_keep_go_imports_across_modules() {
&& from
.as_file()
.is_some_and(|path| path.ends_with("enqueue.go"))
&& to.as_file().is_some_and(|path| path.ends_with("nested/mail.go"))
&& to
.as_file()
.is_some_and(|path| path.ends_with("nested/mail.go"))
}));
}

fn matches_any_naive(rel: &Path, globs: &[String]) -> bool {
globs.iter().any(|glob| {
globset::Glob::new(glob)
.ok()
.is_some_and(|compiled| compiled.compile_matcher().is_match(rel))
})
}

#[test]
fn compiled_queue_globs_agree_with_per_file_glob_new() {
let kafka = lang_fixture("kafka-topics");
let files = lang_files(&kafka);
let globs = vec!["**/*".into(), "[".into(), "producer.ts".into()];
Comment thread
jonathanong marked this conversation as resolved.
let compiled = compile_queue_globs(&globs);
for path in &files {
let rel = path.strip_prefix(&kafka).unwrap_or(path);
let naive = matches_any_naive(rel, &globs);
let compiled_hit = compiled
.matchers
.iter()
.any(|(matcher, _)| matcher.is_match(rel));
assert_eq!(
naive,
compiled_hit,
"compiled glob match must agree with Glob::new per file for {}",
path.display()
);
}
}

#[test]
fn empty_language_config_still_emits_kafka_queue_edges() {
let kafka = lang_fixture("kafka-topics");
let options = GraphConfigOptions {
queue_enqueues: vec!["**/*".into()],
queue_workers: vec!["**/*".into()],
queue_cluster: Some("orders".into()),
..GraphConfigOptions::default()
};
let edges = collect_language_frontend_edges(&kafka, &lang_files(&kafka), Some(&options));
assert!(edges
.iter()
.any(|(_, _, kind)| *kind == EdgeKind::QueueEnqueue));
}
42 changes: 32 additions & 10 deletions crates/no-mistakes/src/codebase/lang_frontends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub(crate) struct LangFrontendConfig {
pub php_framework: Option<String>,
}

#[derive(Default)]
pub(crate) struct CollectedLangFacts {
pub python: LangFactMap,
pub go: LangFactMap,
Expand All @@ -51,16 +52,37 @@ pub(crate) fn collect_all_lang_facts(
all_files: &[PathBuf],
config: &LangFrontendConfig,
) -> CollectedLangFacts {
let ((python, go), (rust, (ruby, php))) = rayon::join(
|| {
rayon::join(
|| collect_python_facts(root, all_files, &config.python_packages),
|| collect_go_facts(root, all_files, &config.go_modules),
)
},
|| {
rayon::join(
|| collect_rust_facts(root, all_files, &config.rust_packages),
|| {
rayon::join(
|| collect_ruby_facts(root, all_files, &config.rails_apps),
|| {
collect_php_facts(
root,
all_files,
&config.php_apps,
config.php_framework.as_deref(),
)
},
)
},
)
},
);
CollectedLangFacts {
python: collect_python_facts(root, all_files, &config.python_packages),
go: collect_go_facts(root, all_files, &config.go_modules),
rust: collect_rust_facts(root, all_files, &config.rust_packages),
ruby: collect_ruby_facts(root, all_files, &config.rails_apps),
php: collect_php_facts(
root,
all_files,
&config.php_apps,
config.php_framework.as_deref(),
),
python,
go,
rust,
ruby,
php,
}
}
Loading