Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -69,6 +69,7 @@ pub fn language_frontend_fixture() -> LanguageFrontendFixture {
java_packages: vec!["java-spring".into()],
kotlin_packages: vec!["kotlin-spring".into()],
elixir_apps: vec!["phoenix-routes".into()],
dart_packages: vec![],
},
queue_enqueues: vec!["**/*".into()],
queue_workers: vec!["**/*".into()],
Expand All @@ -90,6 +91,7 @@ pub fn collect_language_frontend_facts(
&facts.java,
&facts.kotlin,
&facts.elixir,
&facts.dart,
];
LanguageFrontendSummary {
files: fixture.files.len(),
Expand Down Expand Up @@ -140,7 +142,7 @@ pub fn match_language_frontend_queue_globs(
}

fn fact_len(
maps: [&LangFactMap; 8],
maps: [&LangFactMap; 9],
field: impl Fn(&crate::codebase::lang_frontends::LangFileFacts) -> usize,
) -> usize {
maps.iter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub enum RelationshipArg {
Java,
Kotlin,
Elixir,
Dart,
Resource,
Trpc,
All,
Expand Down Expand Up @@ -82,6 +83,7 @@ impl RelationshipArg {
Self::Java => Some("java"),
Self::Kotlin => Some("kotlin"),
Self::Elixir => Some("elixir"),
Self::Dart => Some("dart"),
Self::Trpc => Some("trpc"),
_ => None,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ fn standard_relationship_edges() -> std::collections::HashSet<EdgeKind> {
EdgeKind::KotlinReference,
EdgeKind::ElixirImport,
EdgeKind::ElixirReference,
EdgeKind::DartImport,
EdgeKind::DartReference,
EdgeKind::Resource,
]
.into_iter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fn language_relationship_edges(relationship: &RelationshipArg) -> Option<&'stati
RelationshipArg::Java => &[EdgeKind::JavaImport, EdgeKind::JavaReference],
RelationshipArg::Kotlin => &[EdgeKind::KotlinImport, EdgeKind::KotlinReference],
RelationshipArg::Elixir => &[EdgeKind::ElixirImport, EdgeKind::ElixirReference],
RelationshipArg::Dart => &[EdgeKind::DartImport, EdgeKind::DartReference],
_ => return None,
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub(crate) fn test_globs(framework: &str) -> Vec<String> {
"java" => globs_to_strings(&["**/*Test.java", "**/*Tests.java", "**/*IT.java"]),
"kotlin" => globs_to_strings(&["**/*Test.kt", "**/*Tests.kt", "**/*IT.kt"]),
"elixir" => globs_to_strings(&["**/*_test.exs"]),
"dart" => globs_to_strings(&["**/*_test.dart"]),
_ => vec![],
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ fn allowed_requests_language_frontends(allowed: &HashSet<EdgeKind>) -> bool {
EdgeKind::KotlinReference,
EdgeKind::ElixirImport,
EdgeKind::ElixirReference,
EdgeKind::DartImport,
EdgeKind::DartReference,
]
.into_iter()
.any(|kind| allowed.contains(&kind))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
fn collect_dart_http_call_edges(
root: &Path,
all_files: &[PathBuf],
config_options: &GraphConfigOptions,
route_defs: &[(PathBuf, String)],
interner: &PathInterner,
) -> Vec<Edge> {
if config_options.dart_packages.is_empty() {
return Vec::new();
}
let roots =
crate::codebase::lang_frontends::configured_roots(root, &config_options.dart_packages);
let dart_files: Vec<PathBuf> = all_files
.iter()
.filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("dart"))
.filter(|path| roots.iter().any(|package_root| path.starts_with(package_root)))
.cloned()
.collect();
if dart_files.is_empty() {
return Vec::new();
}
let store = crate::codebase::ts_source::SourceStore::new(std::sync::Arc::new(
crate::codebase::ts_source::FileInventory::from_paths(&dart_files),
));
Comment on lines +22 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse the prepared source store for Dart HTTP

When a graph requests both Dart language edges and HTTP edges, collect_language_frontend_edges first reads every configured Dart file through the request's memoized SourceStore, but this collector then constructs a separate store and physically reads the same files again. This defeats the request-level single-read guarantee, doubles Dart source I/O on full graph and test-impact builds, and can make the HTTP edges observe different contents if a file changes during the request. Pass the prepared or visible-snapshot source store into this collector instead of creating a new one.

Useful? React with 👍 / 👎.

let prefixes = resolved_backend_prefixes(config_options);
dart_files
.par_iter()
.flat_map(|caller| {
let Ok(source) = store.read_path(caller) else {
return Vec::new();
};
let calls: Vec<_> = crate::codebase::lang_frontends::extract_http_paths(&source)
Comment thread
jonathanong marked this conversation as resolved.
.into_iter()
.filter(|path| prefixes.iter().any(|prefix| path.starts_with(prefix)))
.map(|path| crate::codebase::ts_http_calls::HttpCall { path, line: 0 })
.collect();
Comment thread
jonathanong marked this conversation as resolved.
http_edges_for_calls(caller, &calls, route_defs, interner)
})
.collect()
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ fn collect_language_frontend_edges(
&mut edges,
interner,
);
emit_lang_edges(
&facts.dart,
EdgeKind::DartImport,
EdgeKind::DartReference,
Comment thread
jonathanong marked this conversation as resolved.
&mut edges,
interner,
);
emit_queue_edges(root, &facts.python, options, &mut edges, interner);
emit_queue_edges(root, &facts.go, options, &mut edges, interner);
emit_queue_edges(root, &facts.ruby, options, &mut edges, interner);
Expand Down Expand Up @@ -127,6 +134,7 @@ fn lang_config_from_options(options: &GraphConfigOptions) -> LangFrontendConfig
java_packages: options.java_packages.clone(),
kotlin_packages: options.kotlin_packages.clone(),
elixir_apps: options.elixir_apps.clone(),
dart_packages: options.dart_packages.clone(),
}
}

Expand All @@ -139,5 +147,6 @@ fn config_is_empty(config: &LangFrontendConfig) -> bool {
&& config.java_packages.is_empty()
&& config.kotlin_packages.is_empty()
&& config.elixir_apps.is_empty()
&& config.dart_packages.is_empty()
}

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub(crate) fn collect_language_frontend_edges_for_bench(
java_packages: request.languages.java_packages.clone(),
kotlin_packages: request.languages.kotlin_packages.clone(),
elixir_apps: request.languages.elixir_apps.clone(),
dart_packages: request.languages.dart_packages.clone(),
queue_enqueues: request.queue_enqueues.to_vec(),
queue_workers: request.queue_workers.to_vec(),
queue_cluster: request.queue_cluster,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ fn emit_lang_edges(
| EdgeKind::JavaImport
| EdgeKind::KotlinImport
| EdgeKind::ElixirImport
| EdgeKind::DartImport
)
|| facts
.files
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
// ── HTTP call edges ───────────────────────────────────────────────────────────

/// Collect `HttpCall` edges: files that make literal HTTP calls to paths that
/// match a backend route definition.
///
/// Route definitions and backend prefixes must be configured by
/// `http-route-static-paths`, `http-call-static-paths`, or legacy
/// `route-consistency` options.
/// HTTP client calls are any `.<verb>(literal_path)` or `fetch(literal_path)`
/// where `literal_path` starts with a known backend prefix.
///
/// Runs defensively: non-literal call sites produce no edge. The
/// `http-call-static-paths` guardrail enforces literal discipline.
/// `route-consistency` options. HTTP client calls are any
/// `.<verb>(literal_path)` or `fetch(literal_path)` where `literal_path`
/// starts with a known backend prefix. Non-literal call sites produce no
/// edge; `http-call-static-paths` enforces literal discipline.
fn collect_http_call_edges(
root: &Path,
facts: Option<&dyn TsFactLookup>,
Expand All @@ -20,8 +16,6 @@ fn collect_http_call_edges(
config_options: Option<&GraphConfigOptions>,
interner: &PathInterner,
) -> Vec<Edge> {
use crate::codebase::ts_http_calls::extract_http_calls;

let Some(config_options) = config_options else {
return vec![];
};
Expand All @@ -30,7 +24,6 @@ fn collect_http_call_edges(
return vec![];
}

// Collect backend route definitions: (file, pattern)
let mut route_defs = match (
resolved_backend_pattern(config_options),
resolved_backend_register_object(config_options),
Expand Down Expand Up @@ -58,9 +51,9 @@ fn collect_http_call_edges(
return vec![];
}
let prefix_strs: Vec<&str> = backend_prefixes.iter().map(String::as_str).collect();

if let Some(facts) = facts {
return graph_files
use crate::codebase::ts_http_calls::extract_http_calls;
let mut edges: Vec<Edge> = if let Some(facts) = facts {
graph_files
.par_iter()
.filter_map(|caller| {
facts
Expand All @@ -70,19 +63,28 @@ fn collect_http_call_edges(
.flat_map_iter(|(caller, calls)| {
http_edges_for_calls(caller, calls, &route_defs, interner)
})
.collect();
}

// For each source file, find HTTP calls and match against route defs.
files
.par_iter()
.flat_map_iter(|(caller, source)| {
let calls = extract_http_calls(source, &prefix_strs);
http_edges_for_calls(caller, &calls, &route_defs, interner)
})
.collect()
.collect()
} else {
files
.par_iter()
.flat_map_iter(|(caller, source)| {
let calls = extract_http_calls(source, &prefix_strs);
http_edges_for_calls(caller, &calls, &route_defs, interner)
})
.collect()
};
edges.extend(collect_dart_http_call_edges(
root,
all_files,
config_options,
&route_defs,
interner,
));
edges
}

include!("edge_dart_http.rs");

fn collect_next_route_handler_defs(
root: &Path,
all_files: &[PathBuf],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct GraphConfigOptions {
java_packages: Vec<String>,
kotlin_packages: Vec<String>,
elixir_apps: Vec<String>,
dart_packages: Vec<String>,
queue_enqueues: Vec<String>,
queue_workers: Vec<String>,
queue_cluster: Option<String>,
Expand Down Expand Up @@ -123,6 +124,7 @@ fn graph_config_options_from_loaded_with_test_filter(
java_packages: v2_config.tests.java.packages.clone(),
kotlin_packages: v2_config.tests.kotlin.packages.clone(),
elixir_apps: v2_config.tests.elixir.apps.clone(),
dart_packages: v2_config.tests.dart.packages.clone(),
queue_enqueues: flatten_queue_globs(v2_config, prefixed_queue_globs_enqueues),
queue_workers: flatten_queue_globs(v2_config, prefixed_queue_globs_workers),
queue_cluster: v2_config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ fn graph_config_helpers_require_explicit_prefixes_and_valid_globs() {
java_packages: vec![],
kotlin_packages: vec![],
elixir_apps: vec![],
dart_packages: vec![],
queue_enqueues: vec![],
queue_workers: vec![],
queue_cluster: None,
Expand Down Expand Up @@ -115,6 +116,7 @@ fn graph_config_helpers_require_explicit_prefixes_and_valid_globs() {
java_packages: vec![],
kotlin_packages: vec![],
elixir_apps: vec![],
dart_packages: vec![],
queue_enqueues: vec![],
queue_workers: vec![],
queue_cluster: None,
Expand Down Expand Up @@ -170,6 +172,7 @@ fn graph_config_helpers_require_explicit_prefixes_and_valid_globs() {
java_packages: vec![],
kotlin_packages: vec![],
elixir_apps: vec![],
dart_packages: vec![],
queue_enqueues: vec![],
queue_workers: vec![],
queue_cluster: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ fn effective_fact_plan_skips_config_dependent_domains_without_required_config()
java_packages: vec![],
kotlin_packages: vec![],
elixir_apps: vec![],
dart_packages: vec![],
queue_enqueues: vec![],
queue_workers: vec![],
queue_cluster: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ fn empty_options() -> GraphConfigOptions {
java_packages: Vec::new(),
kotlin_packages: Vec::new(),
elixir_apps: Vec::new(),
dart_packages: Vec::new(),
queue_enqueues: Vec::new(),
queue_workers: Vec::new(),
queue_cluster: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ fn symbol_edge_helpers_cover_defensive_symbol_branches() {
java_packages: vec![],
kotlin_packages: vec![],
elixir_apps: vec![],
dart_packages: vec![],
queue_enqueues: vec![],
queue_workers: vec![],
queue_cluster: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,49 @@ fn rails_sidekiq_emits_queue_enqueue_and_worker_edges() {
.is_none_or(|path| !path.ends_with("dynamic.rb"))
}));
}

#[test]
fn dart_exact_imports_cross_configured_packages() {
let root = crate::codebase::ts_resolver::normalize_path(
&PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../test-cases/codebase-analysis/dart-cross-package/fixture"),
);
let options = GraphConfigOptions {
dart_packages: vec!["libs/shared".into(), "services/app".into()],
..GraphConfigOptions::default()
};
let edges = collect_language_frontend_edges_for_test(&root, &lang_files(&root), Some(&options));
assert!(edges.iter().any(|(from, to, kind)| {
*kind == EdgeKind::DartImport
&& from.as_file().is_some_and(|path| path.ends_with("app.dart"))
&& to.as_file().is_some_and(|path| path.ends_with("user.dart"))
}));
}

#[test]
fn dart_http_calls_match_configured_ts_backend_routes() {
let root = crate::codebase::ts_resolver::normalize_path(
&PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../test-cases/codebase-analysis/dart-flutter-http/fixture"),
);
let options = super::graph_config_options(&root).expect("dart fixture config");
let files = lang_files(&root);
let edges = super::collect_http_call_edges(
&root,
None,
&[],
&files,
&files,
Some(&options),
&crate::codebase::analysis_session::PathInterner::new(),
);
assert!(edges.iter().any(|(from, to, kind)| {
*kind == EdgeKind::HttpCall
&& from.as_file().is_some_and(|path| path.ends_with("api.dart"))
&& to.as_file().is_some_and(|path| path.ends_with("server.ts"))
}));
assert!(edges.iter().all(|(_, to, kind)| {
*kind != EdgeKind::HttpCall
|| to.as_file().is_none_or(|path| !path.ends_with("admin.ts"))
}));
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ fn graph_build_plan_from_allowed_covers_each_edge_family() {
EdgeKind::KotlinReference,
EdgeKind::ElixirImport,
EdgeKind::ElixirReference,
EdgeKind::DartImport,
EdgeKind::DartReference,
] {
let allowed: HashSet<_> = [kind].into();
assert!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ pub enum EdgeKind {
KotlinReference,
ElixirImport,
ElixirReference,
DartImport,
DartReference,
/// Workflow file → virtual job node.
WorkflowJob,
/// Virtual workflow job → virtual workflow step node.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ fn language_frontend_str(kind: &EdgeKind) -> Option<&'static str> {
EdgeKind::KotlinReference => "kotlin-ref",
EdgeKind::ElixirImport => "elixir-import",
EdgeKind::ElixirReference => "elixir-ref",
EdgeKind::DartImport => "dart-import",
EdgeKind::DartReference => "dart-ref",
_ => return None,
})
}
Expand Down
Loading
Loading