From 693727bbba552c53ee7a470115e126dce89a90b4 Mon Sep 17 00:00:00 2001 From: Long Ho Date: Mon, 18 May 2026 10:30:08 -0400 Subject: [PATCH] feat: ignore unresolved import patterns --- codescythe.schema.json | 19 +++++++++++--- crates/codescythe/analyze.rs | 50 +++++++++++++++++++++++++++++++----- crates/codescythe/config.rs | 10 +++++++- crates/codescythe/lib.rs | 2 +- 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/codescythe.schema.json b/codescythe.schema.json index c8cc0e2..2505bbd 100644 --- a/codescythe.schema.json +++ b/codescythe.schema.json @@ -30,9 +30,22 @@ }, "unresolvedImports": { "description": "Controls how unresolved imports are handled.", - "type": "string", - "enum": ["report", "ignore", "error"], - "default": "report" + "type": "object", + "additionalProperties": false, + "properties": { + "mode": { + "type": "string", + "enum": ["report", "ignore", "error"], + "default": "report" + }, + "ignore": { + "description": "Unresolved import specifier glob patterns to ignore.", + "$ref": "#/$defs/stringOrStringArray" + } + }, + "default": { + "mode": "report" + } }, "includeEntryExports": { "type": "boolean", diff --git a/crates/codescythe/analyze.rs b/crates/codescythe/analyze.rs index 8398e78..2e75fa2 100644 --- a/crates/codescythe/analyze.rs +++ b/crates/codescythe/analyze.rs @@ -102,7 +102,7 @@ pub fn analyze_path( .map(|(index, file)| (file.path.clone(), index)) .collect::>(); let module_resolver = ModuleResolver::new(&cwd, &files, config); - let unresolved_policy = UnresolvedImportPolicy::new(config); + let unresolved_policy = UnresolvedImportPolicy::new(config)?; let mut entry_indexes = HashSet::::new(); let mut used_files = UsedFiles::new(); @@ -622,13 +622,15 @@ fn is_relative_alias_path(value: &str) -> bool { struct UnresolvedImportPolicy { mode: UnresolvedImportsMode, + ignore: GlobSet, } impl UnresolvedImportPolicy { - fn new(config: &CodescytheConfig) -> Self { - Self { - mode: config.unresolved_imports, - } + fn new(config: &CodescytheConfig) -> Result { + Ok(Self { + mode: config.unresolved_imports.mode, + ignore: build_glob_set(&config.unresolved_imports.ignore)?, + }) } fn record( @@ -637,6 +639,10 @@ impl UnresolvedImportPolicy { importer: &str, specifier: &str, ) -> Result<()> { + if self.ignore.is_match(specifier) { + return Ok(()); + } + match self.mode { UnresolvedImportsMode::Report => { unresolved @@ -1484,6 +1490,38 @@ mod tests { assert!(message.contains("./missing")); } + #[test] + fn ignored_unresolved_patterns_do_not_count_as_issues() { + let tempdir = tempfile::tempdir().unwrap(); + let cwd = tempdir.path(); + + write_file( + cwd, + "codescythe.json", + r##"{ + "entry": "src/main.ts", + "project": "src/**/*.ts", + "unresolvedImports": { + "ignore": ["#virtual_generated/**"] + } + }"##, + ); + write_file( + cwd, + "src/main.ts", + "import '#virtual_generated/api/foo';\nimport './missing';\n", + ); + + let config = crate::load_config(cwd, None).unwrap(); + let analysis = analyze_path(cwd, &config, AnalysisOptions::default()).unwrap(); + + assert_eq!( + analysis.issues.unresolved["src/main.ts"], + vec!["./missing".to_string()] + ); + assert_eq!(analysis.counters.unresolved, 1); + } + #[test] fn reports_missing_local_imports() { let tempdir = tempfile::tempdir().unwrap(); @@ -1522,7 +1560,7 @@ console.log(missingExternal, missingExternalSubpath); let tempdir = tempfile::tempdir().unwrap(); let cwd = tempdir.path(); let mode_config = mode - .map(|mode| format!(r#", "unresolvedImports": "{mode}""#)) + .map(|mode| format!(r#", "unresolvedImports": {{ "mode": "{mode}" }}"#)) .unwrap_or_default(); write_file( diff --git a/crates/codescythe/config.rs b/crates/codescythe/config.rs index b9da1cf..3a1725f 100644 --- a/crates/codescythe/config.rs +++ b/crates/codescythe/config.rs @@ -17,11 +17,19 @@ pub struct CodescytheConfig { pub ignore: Vec, #[serde(deserialize_with = "deserialize_aliases")] pub aliases: BTreeMap>, - pub unresolved_imports: UnresolvedImportsMode, + pub unresolved_imports: UnresolvedImportsConfig, pub include_entry_exports: bool, pub ignore_exports_used_in_file: bool, } +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase", default)] +pub struct UnresolvedImportsConfig { + pub mode: UnresolvedImportsMode, + #[serde(deserialize_with = "deserialize_patterns")] + pub ignore: Vec, +} + #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum UnresolvedImportsMode { diff --git a/crates/codescythe/lib.rs b/crates/codescythe/lib.rs index 4a5cdb0..c8202d2 100644 --- a/crates/codescythe/lib.rs +++ b/crates/codescythe/lib.rs @@ -5,7 +5,7 @@ mod fix; pub use analyze::{ Analysis, AnalysisOptions, Counters, FileIssue, Issues, SymbolIssue, analyze_path, }; -pub use config::{CodescytheConfig, UnresolvedImportsMode, load_config}; +pub use config::{CodescytheConfig, UnresolvedImportsConfig, UnresolvedImportsMode, load_config}; pub use fix::{FixResult, apply_fixes}; use std::path::Path;