Skip to content

Commit ef7ca99

Browse files
branchseerclaude
andcommitted
refactor: store original filter strings for unmatched_selectors
`unmatched_selectors` was `Vec<usize>` (indices into the internal filter list), but those indices don't map back to CLI args after whitespace splitting and synthetic filter injection. Change to `Vec<Str>` by storing the original `--filter` token as `source: Option<Str>` in each `PackageFilter`. Synthetic filters (implicit cwd, `-w`) get `None` so they are never reported as unmatched. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0d1679a commit ef7ca99

File tree

3 files changed

+95
-16
lines changed

3 files changed

+95
-16
lines changed

crates/vite_task_graph/src/query/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,11 @@ pub struct TaskQueryResult {
5353
/// decide whether to show task-not-found UI.
5454
pub execution_graph: TaskExecutionGraph,
5555

56-
/// Indices into the original `PackageQuery::Filters` slice for selectors that
57-
/// matched no packages. The caller maps each index back to the original
58-
/// `--filter` string for typo warnings.
56+
/// Original `--filter` strings for inclusion selectors that matched no packages.
5957
///
58+
/// Omits synthetic filters (implicit cwd, `-w`) since the user didn't type them.
6059
/// Always empty when `PackageQuery::All` was used.
61-
pub unmatched_selectors: Vec<usize>,
60+
pub unmatched_selectors: Vec<Str>,
6261
}
6362

6463
impl IndexedTaskGraph {

crates/vite_workspace/src/package_filter.rs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ pub(crate) struct PackageFilter {
155155
/// Optional graph expansion from the initial match.
156156
/// `None` = exact match only (no traversal).
157157
pub(crate) traversal: Option<GraphTraversal>,
158+
159+
/// Original `--filter` token that produced this filter.
160+
/// `None` for synthetic filters (implicit cwd, package name, `-w`).
161+
pub(crate) source: Option<Str>,
158162
}
159163

160164
// ────────────────────────────────────────────────────────────────────────────
@@ -280,6 +284,7 @@ impl PackageQueryArgs {
280284
exclude: false,
281285
selector: PackageSelector::WorkspaceRoot,
282286
traversal: None,
287+
source: None,
283288
});
284289
}
285290
return Ok(PackageQuery::filters(parsed));
@@ -304,6 +309,7 @@ impl PackageQueryArgs {
304309
exclude: false,
305310
selector: PackageSelector::WorkspaceRoot,
306311
traversal,
312+
source: None,
307313
})));
308314
}
309315

@@ -320,7 +326,12 @@ impl PackageQueryArgs {
320326
} else {
321327
None
322328
};
323-
Ok(PackageQuery::filters(Vec1::new(PackageFilter { exclude: false, selector, traversal })))
329+
Ok(PackageQuery::filters(Vec1::new(PackageFilter {
330+
exclude: false,
331+
selector,
332+
traversal,
333+
source: None,
334+
})))
324335
}
325336
}
326337

@@ -391,7 +402,7 @@ pub(crate) fn parse_filter(
391402
// Ref: https://github.com/pnpm/pnpm/issues/1651
392403
let traversal = if supports_traversal { traversal } else { None };
393404

394-
Ok(PackageFilter { exclude, selector, traversal })
405+
Ok(PackageFilter { exclude, selector, traversal, source: Some(Str::from(input)) })
395406
}
396407

397408
/// Parse the core selector string (after stripping `!` and `...` markers).
@@ -1113,4 +1124,72 @@ mod tests {
11131124
Err(PackageQueryError::PackageNameWithWorkspaceRoot { .. })
11141125
));
11151126
}
1127+
1128+
// ── source field ───────────────────────────────────────────────────────
1129+
1130+
#[test]
1131+
fn parse_filter_sets_source() {
1132+
let cwd = abs("/workspace");
1133+
let f = parse_filter("@test/app...", cwd).unwrap();
1134+
assert_eq!(f.source.as_deref(), Some("@test/app..."));
1135+
}
1136+
1137+
#[test]
1138+
fn filter_source_preserved_after_whitespace_split() {
1139+
let cwd: Arc<AbsolutePath> = Arc::from(abs("/workspace"));
1140+
let args = PackageQueryArgs {
1141+
recursive: false,
1142+
transitive: false,
1143+
workspace_root: false,
1144+
filter: vec![Str::from("a b")],
1145+
};
1146+
let query = args.into_package_query(None, &cwd).unwrap();
1147+
match &query.0 {
1148+
crate::package_graph::PackageQueryKind::Filters(filters) => {
1149+
assert_eq!(filters.len(), 2);
1150+
assert_eq!(filters[0].source.as_deref(), Some("a"));
1151+
assert_eq!(filters[1].source.as_deref(), Some("b"));
1152+
}
1153+
other => panic!("expected Filters, got {other:?}"),
1154+
}
1155+
}
1156+
1157+
#[test]
1158+
fn synthetic_workspace_root_filter_has_no_source() {
1159+
let cwd: Arc<AbsolutePath> = Arc::from(abs("/workspace"));
1160+
let args = PackageQueryArgs {
1161+
recursive: false,
1162+
transitive: false,
1163+
workspace_root: true,
1164+
filter: vec![Str::from("foo")],
1165+
};
1166+
let query = args.into_package_query(None, &cwd).unwrap();
1167+
match &query.0 {
1168+
crate::package_graph::PackageQueryKind::Filters(filters) => {
1169+
assert_eq!(filters.len(), 2);
1170+
assert_eq!(filters[0].source.as_deref(), Some("foo"));
1171+
assert!(filters[1].source.is_none());
1172+
}
1173+
other => panic!("expected Filters, got {other:?}"),
1174+
}
1175+
}
1176+
1177+
#[test]
1178+
fn implicit_cwd_filter_has_no_source() {
1179+
let cwd: Arc<AbsolutePath> = Arc::from(abs("/workspace/packages/app"));
1180+
let args = PackageQueryArgs {
1181+
recursive: false,
1182+
transitive: false,
1183+
workspace_root: false,
1184+
filter: Vec::new(),
1185+
};
1186+
let query = args.into_package_query(None, &cwd).unwrap();
1187+
match &query.0 {
1188+
crate::package_graph::PackageQueryKind::Filters(filters) => {
1189+
assert_eq!(filters.len(), 1);
1190+
assert!(filters[0].source.is_none());
1191+
}
1192+
other => panic!("expected Filters, got {other:?}"),
1193+
}
1194+
}
11161195
}

crates/vite_workspace/src/package_graph.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@ pub struct FilterResolution {
8686
/// stage (construction time), keeping all downstream code edge-type-agnostic.
8787
pub package_subgraph: DiGraphMap<PackageNodeIndex, ()>,
8888

89-
/// Indices into the input `filters` slice for selectors that matched no packages.
89+
/// Original `--filter` strings for inclusion selectors that matched no packages.
9090
///
91-
/// The caller maps each index back to the original `--filter` string to emit
92-
/// typo warnings. Empty when `PackageQuery::All` is used.
93-
pub unmatched_selectors: Vec<usize>,
91+
/// Omits synthetic filters (implicit cwd, `-w`) since the user didn't type them.
92+
/// Empty when `PackageQuery::All` is used.
93+
pub unmatched_selectors: Vec<Str>,
9494
}
9595

9696
// ────────────────────────────────────────────────────────────────────────────
@@ -213,8 +213,7 @@ impl IndexedPackageGraph {
213213
fn resolve_filters(&self, filters: &[PackageFilter]) -> FilterResolution {
214214
let mut unmatched_selectors = Vec::new();
215215

216-
let (inclusions, exclusions): (Vec<_>, Vec<_>) =
217-
filters.iter().enumerate().partition(|(_, f)| !f.exclude);
216+
let (inclusions, exclusions): (Vec<_>, Vec<_>) = filters.iter().partition(|f| !f.exclude);
218217

219218
// Start from all packages when there are no inclusions (exclude-only mode).
220219
let mut selected: FxHashSet<PackageNodeIndex> = if inclusions.is_empty() {
@@ -224,17 +223,19 @@ impl IndexedPackageGraph {
224223
};
225224

226225
// Apply inclusions: union each filter's resolved set into `selected`.
227-
for (filter_idx, filter) in &inclusions {
226+
for filter in &inclusions {
228227
let matched = self.resolve_selector_entries(&filter.selector);
229-
if matched.is_empty() {
230-
unmatched_selectors.push(*filter_idx);
228+
if matched.is_empty()
229+
&& let Some(source) = &filter.source
230+
{
231+
unmatched_selectors.push(source.clone());
231232
}
232233
let expanded = self.expand_traversal(matched, filter.traversal.as_ref());
233234
selected.extend(expanded);
234235
}
235236

236237
// Apply exclusions: subtract each filter's resolved set from `selected`.
237-
for (_, filter) in &exclusions {
238+
for filter in &exclusions {
238239
let matched = self.resolve_selector_entries(&filter.selector);
239240
let to_remove = self.expand_traversal(matched, filter.traversal.as_ref());
240241
for pkg in to_remove {

0 commit comments

Comments
 (0)