From b409937c82b7456c5f2c6bcff0674d925dc54f1b Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 26 Feb 2026 11:34:54 +0000 Subject: [PATCH] feat(diagnostic): report unresolved require modules Add an unresolved-require diagnostic for require("...") calls that resolve to a string module path but cannot be found in the module index. Extend the existing require visibility checker to emit unresolved-require when module lookup fails. --- .../resources/schema.json | 5 ++ .../checker/require_module_visibility.rs | 18 +++++-- .../src/diagnostic/lua_diagnostic_code.rs | 3 ++ .../src/diagnostic/test/mod.rs | 1 + .../test/unresolved_require_test.rs | 48 +++++++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 crates/emmylua_code_analysis/src/diagnostic/test/unresolved_require_test.rs diff --git a/crates/emmylua_code_analysis/resources/schema.json b/crates/emmylua_code_analysis/resources/schema.json index 630849084..66bea0e5d 100644 --- a/crates/emmylua_code_analysis/resources/schema.json +++ b/crates/emmylua_code_analysis/resources/schema.json @@ -388,6 +388,11 @@ "type": "string", "const": "cast-type-mismatch" }, + { + "description": "unresolved-require", + "type": "string", + "const": "unresolved-require" + }, { "description": "require-module-not-visible", "type": "string", diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/require_module_visibility.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/require_module_visibility.rs index 8f7b1977f..4899c02bc 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/require_module_visibility.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/require_module_visibility.rs @@ -7,7 +7,10 @@ use super::{Checker, DiagnosticContext}; pub struct RequireModuleVisibilityChecker; impl Checker for RequireModuleVisibilityChecker { - const CODES: &[DiagnosticCode] = &[DiagnosticCode::RequireModuleNotVisible]; + const CODES: &[DiagnosticCode] = &[ + DiagnosticCode::RequireModuleNotVisible, + DiagnosticCode::UnresolvedRequire, + ]; fn check(context: &mut DiagnosticContext, semantic_model: &SemanticModel) { let root = semantic_model.get_root().clone(); @@ -37,10 +40,19 @@ fn check_require_call_expr( }; // 查找模块信息 - let module_info = semantic_model + let Some(module_info) = semantic_model .get_db() .get_module_index() - .find_module(&module_path)?; + .find_module(&module_path) + else { + context.add_diagnostic( + DiagnosticCode::UnresolvedRequire, + arg_expr.get_range(), + t!("Cannot resolve module `%{module}`.", module = module_path).to_string(), + None, + ); + return Some(()); + }; // 检查可见性 if !check_export_visibility(semantic_model, module_info).unwrap_or(false) { diff --git a/crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs b/crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs index a1ee12139..2bf3b9ba7 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/lua_diagnostic_code.rs @@ -97,6 +97,8 @@ pub enum DiagnosticCode { GenericConstraintMismatch, /// cast-type-mismatch CastTypeMismatch, + /// unresolved-require + UnresolvedRequire, /// require-module-not-visible RequireModuleNotVisible, /// enum-value-mismatch @@ -142,6 +144,7 @@ pub fn get_default_severity(code: DiagnosticCode) -> DiagnosticSeverity { DiagnosticCode::AnnotationUsageError => DiagnosticSeverity::ERROR, DiagnosticCode::RedefinedLocal => DiagnosticSeverity::HINT, DiagnosticCode::DuplicateRequire => DiagnosticSeverity::HINT, + DiagnosticCode::UnresolvedRequire => DiagnosticSeverity::WARNING, DiagnosticCode::IterVariableReassign => DiagnosticSeverity::ERROR, DiagnosticCode::PreferredLocalAlias => DiagnosticSeverity::HINT, DiagnosticCode::CallNonCallable => DiagnosticSeverity::WARNING, diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/mod.rs b/crates/emmylua_code_analysis/src/diagnostic/test/mod.rs index 37e337047..cbf3e428b 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/mod.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/mod.rs @@ -31,4 +31,5 @@ mod undefined_global_test; mod unknown_doc_tag; mod unnecessary_assert_test; mod unnecessary_if_test; +mod unresolved_require_test; mod unused_test; diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/unresolved_require_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/unresolved_require_test.rs new file mode 100644 index 000000000..d63f452dd --- /dev/null +++ b/crates/emmylua_code_analysis/src/diagnostic/test/unresolved_require_test.rs @@ -0,0 +1,48 @@ +#[cfg(test)] +mod tests { + use crate::{DiagnosticCode, VirtualWorkspace}; + + #[test] + fn test_unresolved_require() { + let mut ws = VirtualWorkspace::new(); + assert!(!ws.check_code_for( + DiagnosticCode::UnresolvedRequire, + r#" + local a = require("missing.module") + "#, + )); + } + + #[test] + fn test_resolved_require() { + let mut ws = VirtualWorkspace::new(); + ws.def_file( + "test.lua", + r#" + local M = {} + return M + "#, + ); + + assert!(ws.check_code_for( + DiagnosticCode::UnresolvedRequire, + r#" + local a = require("test") + "#, + )); + } + + #[test] + fn test_non_literal_require() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::UnresolvedRequire, + r#" + local function module_name() + return "missing.module" + end + local a = require(module_name) + "#, + )); + } +}