Skip to content

False positive require-module-not-visible warning when multiple files share the same module name #1034

@EfveZombie

Description

@EfveZombie

Description

After commit ba53bc1 ("refactor(visibility): improve type visibility"), emmylua_check produces false positive warnings like:

warning: module 'global' visibility is not `public` [require-module-not-visible]

This happens when a project has a file (e.g. global.lua) that shares the same module name as an internal EmmyLua file (e.g. the built-in global.lua in std/library workspace).

Root Cause Analysis

The issue has two layers:

1. exact_find_module does not prioritize by workspace

In LuaModuleIndex::exact_find_module, when multiple files map to the same module path, it simply returns file_ids.first():

let node = self.module_nodes.get(&parent_node_id)?;
let file_id = node.file_ids.first()?; // No priority — just the first registered file
self.file_module_map.get(file_id)

The ModuleNode::file_ids is a Vec<FileId> populated via push() in registration order. There is no sorting or priority based on workspace type (main vs. library vs. std). So whichever file gets registered first wins.

2. New visibility model is stricter for cross-workspace Internal modules

The old check_export_visibility treated modules without @export as visible by default (unless require_export_global was enabled). The new check_module_visibility + is_requireable_from applies stricter rules:

ModuleVisibility::Internal => {
    (!self.workspace_id.is_library() && !workspace_id.is_library())
        || self.workspace_id == workspace_id
}

When find_module resolves to the wrong file (e.g. a library/std global.lua marked Internal), and the requiring file is in the main workspace, this check fails → false require-module-not-visible warning.

Suggested Fix

As an LLM that analyzed the codebase, I would suggest adding workspace-aware priority to exact_find_module. When ModuleNode.file_ids contains multiple entries, prefer the main workspace file over library/std files:

fn exact_find_module(&self, module_parts: &Vec<&str>) -> Option<&ModuleInfo> {
    let mut parent_node_id = self.module_root_id;
    for part in module_parts {
        let parent_node = self.module_nodes.get(&parent_node_id)?;
        let child_id = match parent_node.children.get(*part) {
            Some(id) => *id,
            None => return None,
        };
        parent_node_id = child_id;
    }

    let node = self.module_nodes.get(&parent_node_id)?;
    
    // When only one file, fast path
    if node.file_ids.len() == 1 {
        let file_id = node.file_ids.first()?;
        return self.file_module_map.get(file_id);
    }
    
    // When multiple files share the same module path,
    // prioritize: main > library > std
    node.file_ids
        .iter()
        .filter_map(|fid| self.file_module_map.get(fid))
        .min_by_key(|info| match info.workspace_id {
            id if id.is_main() => 0,
            id if id.is_library() => 1,
            _ => 2,
        })
}

Alternatively, the require_module_visibility checker could iterate over all candidate files for a module path and only report the warning if none of them are visible.

cc @xuhuanzy

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions