Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 7 additions & 5 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ fn find_affected_internal(
"Source file not in analyzer.files, using root fallback: {:?}",
file_path
);
let owning_packages = project_index.get_package_names_by_path(file_path);
let owning_packages = project_index.get_owning_packages_by_path(file_path);
for pkg in &owning_packages {
debug!(
"File {:?} belongs to package '{}' (via root fallback)",
Expand Down Expand Up @@ -218,8 +218,10 @@ fn find_affected_internal(
)
.collect();

// Add all packages that own this file (multiple projects can share the same sourceRoot)
let owning_packages = project_index.get_package_names_by_path(file_path);
// Add all packages that own this file (multiple projects can share the same sourceRoot).
// Uses the unfiltered lookup — a directly changed file always belongs to its project
// regardless of tsconfig excludes (spec files, stories, config files all count).
let owning_packages = project_index.get_owning_packages_by_path(file_path);
for pkg in &owning_packages {
debug!("File {:?} belongs to package '{}'", file_path, pkg);
affected_packages.insert(pkg.clone());
Expand Down Expand Up @@ -316,8 +318,8 @@ fn find_affected_internal(
for asset_file in &asset_files {
let asset_path = &asset_file.file_path;

// Mark all owning projects as affected (multiple projects can share the same sourceRoot)
let owning_packages = project_index.get_package_names_by_path(asset_path);
// Mark all owning projects as affected — uses unfiltered lookup (direct change).
let owning_packages = project_index.get_owning_packages_by_path(asset_path);
for pkg in &owning_packages {
debug!("Asset {:?} belongs to package '{}'", asset_path, pkg);
affected_packages.insert(pkg.clone());
Expand Down
89 changes: 89 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,45 @@ impl ProjectIndex {
}
result
}

/// Like `get_package_names_by_path` but skips tsconfig exclude filtering.
///
/// Use for **direct changes**: a file that was modified in the diff always
/// belongs to its project regardless of whether tsconfig compiles it (spec
/// files, stories, config files all count). The filtered variant should
/// only be used for reference traversal where cascade through excluded
/// files is undesirable.
pub fn get_owning_packages_by_path(&self, file_path: &Path) -> Vec<String> {
let mut result = Vec::new();
Comment thread
nirsky marked this conversation as resolved.
Outdated
if self.root_entries.is_empty() {
for (root, names) in &self.entries {
if file_path.starts_with(root) {
result.extend(names.iter().cloned());
}
}
return result;
}

let mut seen_via_source_root: FxHashSet<&str> = FxHashSet::default();
for (root, names) in &self.entries {
if file_path.starts_with(root) {
for name in names {
seen_via_source_root.insert(name.as_str());
result.push(name.clone());
}
}
}
for (root, names) in &self.root_entries {
if file_path.starts_with(root) {
for name in names {
if !seen_via_source_root.contains(name.as_str()) {
result.push(name.clone());
}
}
}
}
result
}
}

/// Convert line number to byte offset in source text
Comment thread
nirsky marked this conversation as resolved.
Expand Down Expand Up @@ -370,6 +409,56 @@ mod tests {
);
}

#[test]
fn test_get_owning_packages_skips_tsconfig_excludes() {
let tmp = tempfile::TempDir::new().unwrap();
let cwd = tmp.path();

let lib_dir = cwd.join("libs/ui-widgets");
std::fs::create_dir_all(&lib_dir).unwrap();
std::fs::write(
lib_dir.join("tsconfig.lib.json"),
r#"{ "exclude": ["**/*.spec.ts", "**/*.stories.tsx"] }"#,
)
.unwrap();

let projects = vec![Project {
name: "ui-widgets".to_string(),
root: "libs/ui-widgets".into(),
source_root: "libs/ui-widgets/src".into(),
ts_config: Some(lib_dir.join("tsconfig.lib.json")),
implicit_dependencies: vec![],
targets: vec![],
}];

let index = ProjectIndex::new(&projects, cwd);

// get_package_names_by_path excludes spec/stories (for reference traversal)
assert!(
index
.get_package_names_by_path(Path::new("libs/ui-widgets/src/utils.spec.ts"))
.is_empty(),
"filtered method should exclude spec files"
);

// get_owning_packages_by_path includes them (for direct changes)
assert_eq!(
index.get_owning_packages_by_path(Path::new("libs/ui-widgets/src/utils.spec.ts")),
vec!["ui-widgets"],
"direct-change method should include spec files"
);
assert_eq!(
index.get_owning_packages_by_path(Path::new("libs/ui-widgets/src/Grid.stories.tsx")),
vec!["ui-widgets"],
"direct-change method should include stories files"
);
assert_eq!(
index.get_owning_packages_by_path(Path::new("libs/ui-widgets/src/index.ts")),
vec!["ui-widgets"],
"normal files work with both methods"
);
}

#[test]
fn test_project_index_root_fallback() {
let tmp = tempfile::TempDir::new().unwrap();
Expand Down
Loading