Skip to content

Commit

Permalink
save
Browse files Browse the repository at this point in the history
  • Loading branch information
h-a-n-a committed Jan 11, 2024
1 parent 1387f71 commit 5a4f8b5
Show file tree
Hide file tree
Showing 39 changed files with 337 additions and 139 deletions.
1 change: 1 addition & 0 deletions crates/rspack_core/src/context_module_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ impl ContextModuleFactory {
let resolve_args = ResolveArgs {
context: data.context.clone(),
importer: None,
issuer: data.issuer.as_deref(),
specifier,
dependency_type: dependency.dependency_type(),
dependency_category: dependency.category(),
Expand Down
15 changes: 8 additions & 7 deletions crates/rspack_core/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,34 +141,35 @@ impl From<Box<dyn Diagnostic + Send + Sync>> for WithHelp {

impl miette::Diagnostic for WithHelp {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
(&*self.0).code()
(*self.0).code()
}

fn severity(&self) -> Option<miette::Severity> {
(&*self.0).severity()
(*self.0).severity()
}

fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
// Use overwritten help message instead.
self.1.as_ref().map(Box::new).map(|h| h as Box<dyn Display>)
}

fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
(&*self.0).url()
(*self.0).url()
}

fn source_code(&self) -> Option<&dyn miette::SourceCode> {
(&*self.0).source_code()
(*self.0).source_code()
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
(&*self.0).labels()
(*self.0).labels()
}

fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
(&*self.0).related()
(*self.0).related()
}

fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
(&*self.0).diagnostic_source()
(*self.0).diagnostic_source()
}
}
53 changes: 1 addition & 52 deletions crates/rspack_core/src/normal_module_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ impl NormalModuleFactory {

let resolve_args = ResolveArgs {
importer,
issuer: data.issuer.as_deref(),
context: if context_scheme != Scheme::None {
self.options.context.clone()
} else {
Expand Down Expand Up @@ -337,58 +338,6 @@ impl NormalModuleFactory {
Err(err) => {
data.add_file_dependencies(file_dependencies);
data.add_missing_dependencies(missing_dependencies);
// let mut file_dependencies = Default::default();
// let mut missing_dependencies = Default::default();
// let mut from_cache_result = from_cache;
// if !data
// .resolve_options
// .as_ref()
// .and_then(|x| x.fully_specified)
// .unwrap_or(false)
// {
// let new_args = ResolveArgs {
// importer,
// context: if context_scheme != Scheme::None {
// self.options.context.clone()
// } else {
// data.context.clone()
// },
// specifier: request_without_match_resource,
// dependency_type: dependency.dependency_type(),
// dependency_category: dependency.category(),
// resolve_options: data.resolve_options.take(),
// span: dependency.span(),
// resolve_to_context: false,
// optional,
// missing_dependencies: &mut missing_dependencies,
// file_dependencies: &mut file_dependencies,
// };
// let (resource_data, from_cache) = match self
// .cache
// .resolve_module_occasion
// .use_cache(new_args, |args| resolve(args, plugin_driver))
// .await
// {
// Ok(result) => result,
// Err(err) => (Err(err), false),
// };
// from_cache_result = from_cache;
// if let Ok(ResolveResult::Resource(resource)) = resource_data {
// // TODO: Here windows resolver will return normalized path.
// // eg. D:\a\rspack\rspack\packages\rspack\tests\fixtures\errors\resolve-fail-esm\answer.js
// if let Some(_extension) = resource.path.extension() {
// // let resource = format!(
// // "{request_without_match_resource}.{}",
// // extension.to_string_lossy()
// // );
// // diagnostics[0].add_notes(vec![format!("Did you mean '{resource}'?
// // BREAKING CHANGE: The request '{request_without_match_resource}' failed to resolve only because it was resolved as fully specified
// // (probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '\"type\": \"module\"').
// // The extension in the request is mandatory for it to be fully specified.
// // Add the extension to the request.")]);
// }
// }
// }
return Err(err);
}
}
Expand Down
18 changes: 18 additions & 0 deletions crates/rspack_core/src/options/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ impl From<TsconfigReferences> for oxc_resolver::TsconfigReferences {
}
}

macro_rules! impl_resolve_by_dependency {
($ident:ident) => {
pub fn $ident(&self, cat: Option<&DependencyCategory>) -> Option<bool> {
cat
.and_then(|cat| {
self
.by_dependency
.as_ref()
.and_then(|by_dep| by_dep.get(cat).and_then(|d| d.$ident))
})
.or(self.$ident)
}
};
}

impl Resolve {
pub fn merge_by_dependency(mut self, dependency_type: DependencyCategory) -> Self {
let Some(mut by_dependency) = self.by_dependency.as_mut().map(std::mem::take) else {
Expand All @@ -130,6 +145,9 @@ impl Resolve {
pub fn merge(self, value: Self) -> Self {
clever_merge::merge_resolve(self, value)
}

impl_resolve_by_dependency!(fully_specified);
impl_resolve_by_dependency!(prefer_relative);
}

type DependencyCategoryStr = Cow<'static, str>;
Expand Down
1 change: 1 addition & 0 deletions crates/rspack_core/src/plugin/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub struct NormalModuleAfterResolveArgs<'a> {
#[derive(Debug)]
pub struct ResolveArgs<'a> {
pub importer: Option<&'a ModuleIdentifier>,
pub issuer: Option<&'a str>,
pub context: Context,
pub specifier: &'a str,
pub dependency_type: &'a DependencyType,
Expand Down
127 changes: 122 additions & 5 deletions crates/rspack_core/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,26 @@ mod resolver_impl;

use std::{fmt, path::PathBuf};

use rspack_error::Error;
use once_cell::sync::Lazy;
use regex::Regex;
use rspack_error::{DiagnosticExt, Error};
use rspack_loader_runner::DescriptionData;
use sugar_path::{AsPath, SugarPath};

pub use self::factory::{ResolveOptionsWithDependencyType, ResolverFactory};
pub use self::resolver_impl::{ResolveInnerOptions, Resolver};
use crate::diagnostics::WithHelp;
use crate::{ResolveArgs, SharedPluginDriver};

static RELATIVE_PATH_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\.\.?\/").expect("should init regex"));

static PARENT_PATH_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\.\.[\/]").expect("should init regex"));

static CURRENT_DIR_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^(\.[\/])").expect("should init regex"));

/// A successful path resolution or an ignored path.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ResolveResult {
Expand Down Expand Up @@ -56,13 +68,112 @@ impl Resource {
}
}

pub fn resolve_for_error_hints(
args: ResolveArgs<'_>,
plugin_driver: &SharedPluginDriver,
) -> Option<String> {
let dep = ResolveOptionsWithDependencyType {
resolve_options: args.resolve_options.clone(),
resolve_to_context: args.resolve_to_context,
dependency_category: *args.dependency_category,
};

let base_dir = args.context.clone();
let base_dir = base_dir.as_ref();

let fully_specified = dep
.resolve_options
.as_ref()
.and_then(|o| o.fully_specified(Some(args.dependency_category)))
.unwrap_or_default();

let prefer_relative = dep
.resolve_options
.as_ref()
.and_then(|o| o.prefer_relative(Some(args.dependency_category)))
.unwrap_or_default();

// Try to resolve without fully specified
if fully_specified {
let mut dep = dep.clone();
dep.resolve_options = dep.resolve_options.map(|mut options| {
options.fully_specified = Some(false);
options
});
let resolver = plugin_driver.resolver_factory.get(dep);
match resolver.resolve(base_dir, args.specifier) {
Ok(ResolveResult::Resource(resource)) => {
let relative_path = resource.path.relative(args.context.as_path());
let suggestion = if let Some((_, [prefix])) = CURRENT_DIR_REGEX
.captures_iter(args.specifier)
.next()
.map(|c| c.extract())
{
// If the specifier is a relative path pointing to the current directory,
// we can suggest the path relative to the current directory.
format!("{}{}", prefix, relative_path.to_string_lossy())
} else if PARENT_PATH_REGEX.is_match(args.specifier) {
// If the specifier is a relative path to which the parent directory is,
// then we return the relative path directly.
relative_path.to_string_lossy().to_string()
} else {
// If the specifier is a package name like or some arbitrary alias,
// then we return the full path.
resource.path.to_string_lossy().to_string()
};
return Some(format!("Did you mean '{}'?
The request '{}' failed to resolve only because it was resolved as fully specified,
probably because the origin '{}' is strict EcmaScript Module,
e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '\"type\": \"module\"'.
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.", suggestion, args.specifier, args.issuer.unwrap_or_default()));
}
Err(_) => return None,
_ => {}
}
}

// Try to resolve with relative path if request is not relative
if !RELATIVE_PATH_REGEX.is_match(args.specifier) && !prefer_relative {
let dep = dep.clone();
let module_directories = dep
.resolve_options
.as_deref()
.or(Some(&plugin_driver.options.resolve))
.and_then(|o| o.modules.as_ref().map(|m| m.join(", ")))
.expect("module directories should exist");
let resolver = plugin_driver.resolver_factory.get(dep);
let request = format!("./{}", args.specifier);
match resolver.resolve(base_dir, &request) {
Ok(ResolveResult::Resource(_)) => {
return Some(format!(
"Did you mean './{}'?
Requests that should resolve in the current directory need to start with './'.
Requests that start with a name are treated as module requests and resolve within module directories ({module_directories}).
If changing the source code is not an option, there is also a resolve options called 'preferRelative'
which tries to resolve these kind of requests in the current directory too.",
args.specifier
));
}
Err(_) => return None,
_ => {}
}
}

None
}

/// Main entry point for module resolution.
pub async fn resolve(
mut args: ResolveArgs<'_>,
args: ResolveArgs<'_>,
plugin_driver: &SharedPluginDriver,
) -> Result<ResolveResult, Error> {
let dep = ResolveOptionsWithDependencyType {
resolve_options: args.resolve_options.take(),
resolve_options: args.resolve_options.clone(),
resolve_to_context: args.resolve_to_context,
dependency_category: *args.dependency_category,
};
Expand All @@ -72,7 +183,7 @@ pub async fn resolve(

let mut context = Default::default();
let resolver = plugin_driver.resolver_factory.get(dep);
let result = resolver
let mut result = resolver
.resolve_with_context(base_dir, args.specifier, &mut context)
.map_err(|error| error.into_resolve_error(&args));

Expand All @@ -81,5 +192,11 @@ pub async fn resolve(
.missing_dependencies
.extend(context.missing_dependencies);

result.map_err(|err| Error::new_boxed(err))
if result.is_err()
&& let Some(hint) = resolve_for_error_hints(args, plugin_driver)
{
result = result.map_err(|err| WithHelp::from(err).with_help(hint).boxed())
};

result.map_err(Error::new_boxed)
}
11 changes: 7 additions & 4 deletions crates/rspack_core/src/resolver/resolver_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{

use rspack_error::{
miette::{diagnostic, Diagnostic},
DiagnosticError, DiagnosticExt, Severity, TraceableError,
DiagnosticExt, Severity, TraceableError,
};
use rspack_loader_runner::DescriptionData;
use rustc_hash::FxHashSet as HashSet;
Expand Down Expand Up @@ -277,12 +277,15 @@ fn to_oxc_resolver_options(
}
}

fn map_oxc_resolver_error(error: oxc_resolver::ResolveError, args: &ResolveArgs<'_>) -> Error {
fn map_oxc_resolver_error(
error: oxc_resolver::ResolveError,
args: &ResolveArgs<'_>,
) -> Box<dyn Diagnostic + Send + Sync> {
match error {
oxc_resolver::ResolveError::IOError(error) => DiagnosticError::from(error.boxed()).into(),
oxc_resolver::ResolveError::IOError(error) => diagnostic!("{}", error).boxed(),
oxc_resolver::ResolveError::Recursion => map_resolver_error(true, args),
oxc_resolver::ResolveError::NotFound(_) => map_resolver_error(false, args),
_ => error!("{}", error),
_ => diagnostic!("{}", error).boxed(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/basic/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { answer } from "./answerrr";
import { answer } from "./answer";
function render() {
document.getElementById(
"root"
Expand Down
4 changes: 1 addition & 3 deletions packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,9 +607,7 @@ const SIMPLE_PRINTERS: Record<
// "error.details": (details, { formatError }) => formatError(details),
// "error.stack": stack => stack,
// "error.moduleTrace": moduleTrace => undefined,
// "error.separator!": () => "\n",
// Error was already formatted on the native.
// error: error => error,
"error.separator!": () => "\n",

"loggingEntry(error).loggingEntry.message": (message, { red }) =>
mapLines(message, x => `<e> ${red(x)}`),
Expand Down
4 changes: 2 additions & 2 deletions packages/rspack/tests/Errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ it("should emit warnings for resolve failure in esm", async () => {
Object {
"errors": Array [
Object {
"formatted": " Γ— Resolve error: Can't resolve './answer' in '<cwd>/tests/fixtures/errors/resolve-fail-esm'\\n ╭────\\n 1 β”‚ import { answer } from './answer';\\n Β· ──────────\\n ╰────\\n",
"message": " Γ— Resolve error: Can't resolve './answer' in '<cwd>/tests/fixtures/errors/resolve-fail-esm'\\n ╭────\\n 1 β”‚ import { answer } from './answer';\\n Β· ──────────\\n ╰────\\n",
"formatted": " Γ— Resolve error: Can't resolve './answer' in '<cwd>/tests/fixtures/errors/resolve-fail-esm'\\n ╭────\\n 1 β”‚ import { answer } from './answer';\\n Β· ──────────\\n ╰────\\n help: Did you mean './answer.js'?\\n \\n The request './answer' failed to resolve only because it was resolved as fully specified,\\n probably because the origin '<cwd>/tests/fixtures/errors/resolve-fail-esm/index.js' is strict EcmaScript Module,\\n e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '\\"type\\": \\"module\\"'.\\n \\n The extension in the request is mandatory for it to be fully specified.\\n Add the extension to the request.\\n",
"message": " Γ— Resolve error: Can't resolve './answer' in '<cwd>/tests/fixtures/errors/resolve-fail-esm'\\n ╭────\\n 1 β”‚ import { answer } from './answer';\\n Β· ──────────\\n ╰────\\n help: Did you mean './answer.js'?\\n \\n The request './answer' failed to resolve only because it was resolved as fully specified,\\n probably because the origin '<cwd>/tests/fixtures/errors/resolve-fail-esm/index.js' is strict EcmaScript Module,\\n e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '\\"type\\": \\"module\\"'.\\n \\n The extension in the request is mandatory for it to be fully specified.\\n Add the extension to the request.\\n",
"moduleId": "./resolve-fail-esm/index.js",
"moduleIdentifier": "javascript/esm|<cwd>/tests/fixtures/errors/resolve-fail-esm/index.js",
"moduleName": "./resolve-fail-esm/index.js",
Expand Down
3 changes: 2 additions & 1 deletion packages/rspack/tests/Stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ describe("Stats", () => {
./fixtures/c.js
./fixtures/abc.js
ERROR in ./fixtures/b.js ModuleParseError
ERROR in ./fixtures/b.js
ModuleParseError
Γ— Module parse failed:
╰─▢ Γ— JavaScript parsing error: Return statement is not allowed here
Expand Down
Loading

0 comments on commit 5a4f8b5

Please sign in to comment.