Custom reporting for unknown package when checking a module directly

This commit is contained in:
Agus Zubiaga 2024-06-08 18:29:30 -03:00
parent 7faff12cbf
commit ec55caa77a
No known key found for this signature in database
4 changed files with 165 additions and 17 deletions

View file

@ -649,7 +649,7 @@ mod cli_run {
#[test]
#[cfg_attr(windows, ignore)]
fn roc_test_main_flag() {
fn test_module_imports_pkg_w_flag() {
test_roc_expect(
"crates/cli/tests/module_imports_pkg",
"Module.roc",
@ -662,6 +662,63 @@ mod cli_run {
)
}
#[test]
#[cfg_attr(windows, ignore)]
fn test_module_imports_pkg_no_flag() {
test_roc_expect(
"crates/cli/tests/module_imports_pkg",
"Module.roc",
&[],
indoc!(
r#"
UNKNOWN PACKAGE in tests/module_imports_pkg/Module.roc
This module is trying to import from `pkg`:
3 import pkg.Foo
^^^^^^^
A lowercase name indicates a package shorthand, but I don't know which
packages are available.
When checking a module directly, I look for a `main.roc` app or
package to resolve shorthands from.
You can create it, or specify an existing one with the --main flag."#
),
)
}
#[test]
#[cfg_attr(windows, ignore)]
fn test_module_imports_unknown_pkg() {
test_roc_expect(
"crates/cli/tests/module_imports_pkg",
"ImportsUnknownPkg.roc",
&["--main", "tests/module_imports_pkg/app.roc"],
indoc!(
r#"
UNKNOWN PACKAGE in tests/module_imports_pkg/ImportsUnknownPkg.roc
This module is trying to import from `cli`:
3 import cli.Foo
^^^^^^^
A lowercase name indicates a package shorthand, but I don't recognize
this one. Did you mean one of these?
pkg
Note: I'm using the following module to resolve package shorthands:
tests/module_imports_pkg/app.roc
You can specify a different one with the --main flag."#
),
)
}
#[test]
#[cfg_attr(windows, ignore)]
fn transitive_expects() {

View file

@ -0,0 +1,7 @@
module [valueFromPkg]
import cli.Foo
valueFromPkg = Foo.foo
expect valueFromPkg == "Foo"

View file

@ -196,6 +196,7 @@ fn start_phase<'a>(
arc_shorthands: Arc::clone(&state.arc_shorthands),
module_ids: Arc::clone(&state.arc_modules),
ident_ids_by_module: Arc::clone(&state.ident_ids_by_module),
root_type: state.root_type.clone(),
}
}
Phase::CanonicalizeAndConstrain => {
@ -687,6 +688,7 @@ struct State<'a> {
pub root_id: ModuleId,
pub root_subs: Option<Subs>,
pub root_path: PathBuf,
pub root_type: RootType,
pub cache_dir: PathBuf,
/// If the root is an app module, the shorthand specified in its header's `to` field
pub opt_platform_shorthand: Option<&'a str>,
@ -756,6 +758,7 @@ impl<'a> State<'a> {
fn new(
root_id: ModuleId,
root_path: PathBuf,
root_type: RootType,
opt_platform_shorthand: Option<&'a str>,
target: Target,
function_kind: FunctionKind,
@ -776,6 +779,7 @@ impl<'a> State<'a> {
root_id,
root_path,
root_subs: None,
root_type,
opt_platform_shorthand,
cache_dir,
target,
@ -870,6 +874,7 @@ enum BuildTask<'a> {
arc_shorthands: Arc<Mutex<MutMap<&'a str, ShorthandPath>>>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: SharedIdentIdsByModule,
root_type: RootType,
},
CanonicalizeAndConstrain {
parsed: ParsedModule<'a>,
@ -987,10 +992,22 @@ pub enum LoadingProblem<'a> {
#[derive(Debug)]
pub enum AvailableShorthands<'a> {
FromRoot(Vec<&'a str>),
FromMain(&'a Path, Vec<&'a str>),
FromMain(PathBuf, Vec<&'a str>),
UnknownMain,
}
impl<'a> AvailableShorthands<'a> {
fn new(root_type: RootType, shorthands: Vec<&'a str>) -> Self {
match root_type {
RootType::Main => AvailableShorthands::FromRoot(shorthands),
RootType::Module { main_path } => match main_path {
None => AvailableShorthands::UnknownMain,
Some(main_path) => AvailableShorthands::FromMain(main_path, shorthands),
},
}
}
}
#[derive(Debug)]
pub enum ChannelProblem {
FailedToEnqueueTask(Box<PanicReportInfo>),
@ -1115,10 +1132,17 @@ pub struct LoadStart<'a> {
root_id: ModuleId,
root_path: PathBuf,
root_msg: Msg<'a>,
root_type: RootType,
opt_platform_shorthand: Option<&'a str>,
src_dir: PathBuf,
}
#[derive(Debug, Clone)]
enum RootType {
Main,
Module { main_path: Option<PathBuf> },
}
impl<'a> LoadStart<'a> {
pub fn from_path(
arena: &'a Bump,
@ -1149,7 +1173,7 @@ impl<'a> LoadStart<'a> {
root_start_time,
);
let header_output = match res_loaded {
let (header_output, root_type) = match res_loaded {
Ok(mut header_output) => {
if let Msg::Header(ModuleHeader {
module_id: header_id,
@ -1168,7 +1192,9 @@ impl<'a> LoadStart<'a> {
let cache_dir = roc_cache_dir.as_persistent_path();
if let (Some(main_path), Some(cache_dir)) = (main_path, cache_dir) {
if let (Some(main_path), Some(cache_dir)) =
(main_path.clone(), cache_dir)
{
let mut messages = Vec::with_capacity(4);
messages.push(header_output.msg);
@ -1185,12 +1211,18 @@ impl<'a> LoadStart<'a> {
header_output.msg = Msg::Many(messages);
}
}
App { .. } | Package { .. } | Platform { .. } => {}
}
}
adjust_header_paths(header_output, &mut src_dir)
let header_output = adjust_header_paths(header_output, &mut src_dir);
(header_output, RootType::Module { main_path })
}
App { .. } | Package { .. } | Platform { .. } => {
(header_output, RootType::Main)
}
}
} else {
(header_output, RootType::Main)
}
}
Err(problem) => {
@ -1217,6 +1249,7 @@ impl<'a> LoadStart<'a> {
root_id: header_output.module_id,
root_path: filename,
root_msg: header_output.msg,
root_type,
opt_platform_shorthand: header_output.opt_platform_shorthand,
})
}
@ -1262,6 +1295,8 @@ impl<'a> LoadStart<'a> {
root_id,
root_path: filename,
root_msg,
// todo(agus): could be module
root_type: RootType::Main,
opt_platform_shorthand: opt_platform_id,
})
}
@ -1519,6 +1554,7 @@ pub fn load_single_threaded<'a>(
root_id,
root_path,
root_msg,
root_type,
src_dir,
opt_platform_shorthand,
..
@ -1534,6 +1570,7 @@ pub fn load_single_threaded<'a>(
let mut state = State::new(
root_id,
root_path,
root_type,
opt_platform_shorthand,
target,
function_kind,
@ -1918,6 +1955,7 @@ fn load_multi_threaded<'a>(
root_id,
root_path,
root_msg,
root_type,
src_dir,
opt_platform_shorthand,
..
@ -1948,6 +1986,7 @@ fn load_multi_threaded<'a>(
let mut state = State::new(
root_id,
root_path,
root_type,
opt_platform_shorthand,
target,
function_kind,
@ -5328,6 +5367,7 @@ fn parse<'a>(
arc_shorthands: Arc<Mutex<MutMap<&'a str, ShorthandPath>>>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: SharedIdentIdsByModule,
root_type: RootType,
) -> Result<Msg<'a>, LoadingProblem<'a>> {
let mut module_timing = header.module_timing;
let parse_start = Instant::now();
@ -5405,8 +5445,8 @@ fn parse<'a>(
for (shorthand, region) in used_shorthands {
if !shorthands.contains_key(shorthand) {
// todo(agus): Handle module root
let available = AvailableShorthands::FromRoot(shorthands.keys().cloned().collect());
let available =
AvailableShorthands::new(root_type, shorthands.keys().copied().collect());
return Err(LoadingProblem::UnknownPackageShorthand {
filename: header.module_path,
@ -6328,12 +6368,14 @@ fn run_task<'a>(
arc_shorthands,
module_ids,
ident_ids_by_module,
root_type,
} => parse(
arena,
header,
arc_shorthands,
module_ids,
ident_ids_by_module,
root_type,
),
CanonicalizeAndConstrain {
parsed,
@ -6684,14 +6726,13 @@ fn to_unknown_package_shorthand_report(
};
let alloc = RocDocAllocator::new(&src_lines, module_id, &interns);
let details = match available {
let help = match available {
AvailableShorthands::FromRoot(options) => {
let mut suggestions = suggest::sort(shorthand, options);
suggestions.truncate(4);
if suggestions.is_empty() {
alloc.reflow("A lowercase name indicates a package shorthand, but no packages have been specified.")
// todo(agus): "in this app/package"
} else {
alloc.stack([
alloc.reflow("A lowercase name indicates a package shorthand, but I don't recognize this one. Did you mean one of these?"),
@ -6700,10 +6741,46 @@ fn to_unknown_package_shorthand_report(
.indent(4),
])
}
// todo(agus): "You can add a package like this"
}
_ => todo!("agus"),
AvailableShorthands::FromMain(main_path, options) => {
let mut suggestions = suggest::sort(shorthand, options);
suggestions.truncate(4);
let suggestions = if suggestions.is_empty() {
alloc.reflow("A lowercase name indicates a package shorthand, but no packages have been specified.")
} else {
alloc.stack([
alloc.reflow("A lowercase name indicates a package shorthand, but I don't recognize this one. Did you mean one of these?"),
alloc
.vcat(suggestions.into_iter().map(|v| alloc.shorthand(v)))
.indent(4),
])
};
alloc.stack([
suggestions,
alloc.note("I'm using the following module to resolve package shorthands:"),
alloc.file_path(&main_path).indent(4),
alloc.concat([
alloc.reflow("You can specify a different one with the "),
alloc.keyword("--main"),
alloc.reflow(" flag.")
]),
])
}
AvailableShorthands::UnknownMain => {
alloc.stack([
alloc.reflow("A lowercase name indicates a package shorthand, but I don't know which packages are available."),
alloc.concat([
alloc.reflow("When checking a module directly, I look for a `main.roc` app or package to resolve shorthands from."),
]),
alloc.concat([
alloc.reflow("You can create it, or specify an existing one with the "),
alloc.keyword("--main"),
alloc.reflow(" flag."),
])
])
}
};
let doc = alloc.stack([
@ -6713,7 +6790,7 @@ fn to_unknown_package_shorthand_report(
alloc.reflow("`:"),
]),
alloc.region(lines.convert_region(region)),
details,
help,
]);
let report = Report {

View file

@ -857,6 +857,13 @@ impl<'a> RocDocAllocator<'a> {
}
self.text(result.chars().rev().collect::<String>())
}
pub fn file_path(&'a self, path: &Path) -> DocBuilder<'a, Self, Annotation> {
let cwd = std::env::current_dir().unwrap();
let relative_path = path.strip_prefix(cwd).unwrap_or(path).to_str().unwrap();
self.text(relative_path.to_string())
}
}
#[derive(Copy, Clone, Debug)]