Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions crates/mq-check/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub(crate) fn syntax_diagnostics(hir: &Hir, source_id: SourceId) -> Vec<CheckDia
let mut diagnostics: Vec<CheckDiagnostic> = hir
.errors()
.iter()
.filter(|error| hir_error_symbol(error).source.source_id == Some(source_id))
.filter(|error| error.symbol().source.source_id == Some(source_id))
.map(|error| CheckDiagnostic {
severity: Severity::Error,
code: hir_error_code(error),
Expand Down Expand Up @@ -88,19 +88,12 @@ fn hir_error_code(error: &HirError) -> &'static str {
HirError::UnresolvedSymbol { .. } => "hir::unresolved_symbol",
HirError::ModuleNotFound { .. } => "hir::module_not_found",
HirError::YieldOutsideFunction { .. } => "hir::yield_outside_function",
}
}

pub(crate) fn hir_error_symbol(error: &HirError) -> &Symbol {
match error {
HirError::UnresolvedSymbol { symbol, .. }
| HirError::ModuleNotFound { symbol, .. }
| HirError::YieldOutsideFunction { symbol } => symbol,
_ => "hir::unknown",
}
}

pub(crate) fn hir_error_range(error: &HirError) -> mq_lang::Range {
hir_error_symbol(error).source.text_range.unwrap_or_default()
error.symbol().source.text_range.unwrap_or_default()
}

/// Returns a stable rule code for a [`HirWarning`] variant.
Expand Down Expand Up @@ -149,6 +142,7 @@ fn type_error_code(error: &TypeError) -> &'static str {
TypeError::NullablePropagation { .. } => "typechecker::nullable_propagation",
TypeError::UnreachableCode { .. } => "typechecker::unreachable_code",
TypeError::NonExhaustiveMatch { .. } => "typechecker::non_exhaustive_patterns",
_ => "typechecker::unknown",
}
}

Expand Down
22 changes: 22 additions & 0 deletions crates/mq-check/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ impl<'a> IntoIterator for &'a TypeEnv {
/// Type checking errors
#[derive(Debug, Error, Clone, Diagnostic)]
#[allow(unused_assignments)]
#[non_exhaustive]
pub enum TypeError {
#[error("Type mismatch: expected {expected}, found {found}")]
#[diagnostic(code(typechecker::type_mismatch))]
Expand Down Expand Up @@ -263,6 +264,7 @@ pub(crate) fn walk_ancestors(

/// Options for configuring the type checker behavior
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct TypeCheckerOptions {
/// When true, arrays must contain elements of a single type.
/// Heterogeneous arrays like `[1, "hello"]` will produce a type error.
Expand All @@ -274,6 +276,26 @@ pub struct TypeCheckerOptions {
pub input_type: Option<types::Type>,
}

impl TypeCheckerOptions {
/// Sets `strict_array`.
pub fn with_strict_array(mut self, strict_array: bool) -> Self {
self.strict_array = strict_array;
self
}

/// Sets `no_exhaustive_patterns`.
pub fn with_no_exhaustive_patterns(mut self, no_exhaustive_patterns: bool) -> Self {
self.no_exhaustive_patterns = no_exhaustive_patterns;
self
}

/// Sets `input_type`.
pub fn with_input_type(mut self, input_type: Option<types::Type>) -> Self {
self.input_type = input_type;
self
}
}

/// Wall-clock time of each phase of [`TypeChecker::check_profiled`], in execution order.
///
/// A phase that runs more than once appears once per run.
Expand Down
18 changes: 8 additions & 10 deletions crates/mq-check/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ struct Cli {

impl Cli {
fn type_checker_options(&self) -> TypeCheckerOptions {
TypeCheckerOptions {
strict_array: self.strict_array,
no_exhaustive_patterns: self.no_exhaustive_patterns,
input_type: self.input_type.clone(),
}
TypeCheckerOptions::default()
.with_strict_array(self.strict_array)
.with_no_exhaustive_patterns(self.no_exhaustive_patterns)
.with_input_type(self.input_type.clone())
}
}

Expand Down Expand Up @@ -252,7 +251,7 @@ fn check_syntax(w: &mut impl Write, hir: &mq_hir::Hir, source_id: mq_hir::Source
let errors: Vec<(String, mq_lang::Range)> = hir
.errors()
.iter()
.filter(|error| format::hir_error_symbol(error).source.source_id == Some(source_id))
.filter(|error| error.symbol().source.source_id == Some(source_id))
.map(|error| (error.to_string(), format::hir_error_range(error)))
.collect();
let warnings: Vec<(String, mq_lang::Range)> = hir
Expand Down Expand Up @@ -425,6 +424,7 @@ fn error_title(error: &TypeError) -> String {
TypeError::NonExhaustiveMatch { missing, .. } => {
format!("non-exhaustive patterns: missing case for {missing}")
}
_ => error.to_string(),
}
}

Expand Down Expand Up @@ -560,10 +560,8 @@ mod tests {

#[test]
fn test_input_type_is_applied_to_the_checked_source() {
let options = TypeCheckerOptions {
input_type: Some(mq_check::type_expr::parse_type("code").unwrap()),
..Default::default()
};
let options =
TypeCheckerOptions::default().with_input_type(Some(mq_check::type_expr::parse_type("code").unwrap()));
let diagnostics = collect_check_diagnostics(".depth", None, false, &options);
assert!(diagnostics.iter().any(|d| d.code == "typechecker::undefined_attribute"));
let diagnostics = collect_check_diagnostics(".depth", None, false, &TypeCheckerOptions::default());
Expand Down
3 changes: 2 additions & 1 deletion crates/mq-check/src/narrowing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ pub(crate) fn selector_kinds(selector: &mq_lang::Selector) -> Option<KindSet> {
Selector::MdxTextExpression => one(NodeKind::MdxTextExpression),
Selector::MdxJsEsm => one(NodeKind::MdxJsEsm),
Selector::MdxJsxFlowElement => one(NodeKind::MdxJsxFlowElement),
Selector::Recursive | Selector::Attr(_) | Selector::Property(_) => return None,
// `Selector` is `#[non_exhaustive]`: unknown selectors narrow nothing.
_ => return None,
})
}

Expand Down
8 changes: 4 additions & 4 deletions crates/mq-check/tests/node_types_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ fn test_attribute_of_a_constructed_node_is_checked() {
fn errors_with_input(code: &str, input_type: &str) -> Vec<mq_check::TypeError> {
let mut hir = Hir::default();
hir.add_code(None, code);
TypeChecker::with_options(mq_check::TypeCheckerOptions {
input_type: Some(mq_check::type_expr::parse_type(input_type).unwrap()),
..Default::default()
})
TypeChecker::with_options(
mq_check::TypeCheckerOptions::default()
.with_input_type(Some(mq_check::type_expr::parse_type(input_type).unwrap())),
)
.check(&hir)
}

Expand Down
10 changes: 2 additions & 8 deletions crates/mq-check/tests/type_errors_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,20 +419,14 @@ fn test_narrowing_selector_various_structural() {
/// Helper function to run type checker with strict array mode enabled
fn check_types_strict_array(code: &str) -> Vec<TypeError> {
let hir = create_hir(code);
let mut checker = TypeChecker::with_options(TypeCheckerOptions {
strict_array: true,
..Default::default()
});
let mut checker = TypeChecker::with_options(TypeCheckerOptions::default().with_strict_array(true));
checker.check(&hir)
}

/// Helper function to run type checker with tuple mode enabled
fn check_types_tuple(code: &str) -> Vec<TypeError> {
let hir = create_hir(code);
let mut checker = TypeChecker::with_options(TypeCheckerOptions {
strict_array: false,
..Default::default()
});
let mut checker = TypeChecker::with_options(TypeCheckerOptions::default().with_strict_array(false));
checker.check(&hir)
}

Expand Down
55 changes: 55 additions & 0 deletions crates/mq-crawler/src/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use url::Url;
///
/// Retries on network errors, `429`, and `5xx`; other client errors (e.g. `404`) are not retried.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct RetryConfig {
/// Retry attempts after the initial request fails.
pub max_retries: u32,
Expand All @@ -26,6 +27,32 @@ pub struct RetryConfig {
pub backoff_multiplier: f64,
}

impl RetryConfig {
/// Sets `max_retries`.
pub fn with_max_retries(mut self, max_retries: u32) -> Self {
self.max_retries = max_retries;
self
}

/// Sets `initial_backoff`.
pub fn with_initial_backoff(mut self, initial_backoff: Duration) -> Self {
self.initial_backoff = initial_backoff;
self
}

/// Sets `max_backoff`.
pub fn with_max_backoff(mut self, max_backoff: Duration) -> Self {
self.max_backoff = max_backoff;
self
}

/// Sets `backoff_multiplier`.
pub fn with_backoff_multiplier(mut self, backoff_multiplier: f64) -> Self {
self.backoff_multiplier = backoff_multiplier;
self
}
}

impl Default for RetryConfig {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -54,6 +81,7 @@ impl RetryConfig {

/// Default `Authorization` header credentials for a `reqwest`-based [`HttpClient`].
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AuthConfig {
/// `Authorization: Basic <base64(user:pass)>`.
Basic { username: String, password: Option<String> },
Expand Down Expand Up @@ -81,6 +109,7 @@ impl FetchError {
/// Multiple strategies can be combined; they are executed in order:
/// network-idle / selector wait first, then the fixed delay.
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct ChromiumWaitConfig {
/// Optional fixed delay applied after all other strategies complete.
pub fixed_delay: Duration,
Expand All @@ -95,6 +124,32 @@ pub struct ChromiumWaitConfig {
pub strategy_timeout: Duration,
}

impl ChromiumWaitConfig {
/// Sets `fixed_delay`.
pub fn with_fixed_delay(mut self, fixed_delay: Duration) -> Self {
self.fixed_delay = fixed_delay;
self
}

/// Sets `wait_for_selector`.
pub fn with_wait_for_selector(mut self, wait_for_selector: Option<String>) -> Self {
self.wait_for_selector = wait_for_selector;
self
}

/// Sets `network_idle`.
pub fn with_network_idle(mut self, network_idle: bool) -> Self {
self.network_idle = network_idle;
self
}

/// Sets `strategy_timeout`.
pub fn with_strategy_timeout(mut self, strategy_timeout: Duration) -> Self {
self.strategy_timeout = strategy_timeout;
self
}
}

/// Enum for different HTTP client implementations
#[derive(Debug, Clone)]
pub enum HttpClient {
Expand Down
32 changes: 14 additions & 18 deletions crates/mq-crawler/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,11 @@ async fn main() {
v
});

let retry_config = mq_crawler::http_client::RetryConfig {
max_retries: args.max_retries,
initial_backoff: std::time::Duration::from_secs_f64(args.retry_initial_backoff.max(0.0)),
max_backoff: std::time::Duration::from_secs_f64(args.retry_max_backoff.max(0.0)),
backoff_multiplier: args.retry_backoff_multiplier,
};
let retry_config = mq_crawler::http_client::RetryConfig::default()
.with_max_retries(args.max_retries)
.with_initial_backoff(std::time::Duration::from_secs_f64(args.retry_initial_backoff.max(0.0)))
.with_max_backoff(std::time::Duration::from_secs_f64(args.retry_max_backoff.max(0.0)))
.with_backoff_multiplier(args.retry_backoff_multiplier);

let header_map = match build_header_map(&args.headers, &args.cookie) {
Ok(header_map) => header_map,
Expand Down Expand Up @@ -309,12 +308,11 @@ async fn main() {
std::time::Duration::from_secs_f64(headless_wait_secs)
};

let wait_config = mq_crawler::http_client::ChromiumWaitConfig {
fixed_delay,
wait_for_selector: args.headless_wait_for_selector.clone(),
network_idle: args.headless_network_idle,
strategy_timeout,
};
let wait_config = mq_crawler::http_client::ChromiumWaitConfig::default()
.with_fixed_delay(fixed_delay)
.with_wait_for_selector(args.headless_wait_for_selector.clone())
.with_network_idle(args.headless_network_idle)
.with_strategy_timeout(strategy_timeout);

mq_crawler::http_client::HttpClient::new_chromium(args.chrome_path, wait_config, retry_config.clone())
.await
Expand Down Expand Up @@ -381,12 +379,10 @@ async fn main() {
args.output,
args.concurrency,
format,
mq_markdown::ConversionOptions {
extract_scripts_as_code_blocks: args.conversion.extract_scripts_as_code_blocks,
generate_front_matter: args.conversion.generate_front_matter,
use_title_as_h1: args.conversion.use_title_as_h1,
base_url: None,
},
mq_markdown::ConversionOptions::default()
.with_extract_scripts_as_code_blocks(args.conversion.extract_scripts_as_code_blocks)
.with_generate_front_matter(args.conversion.generate_front_matter)
.with_use_title_as_h1(args.conversion.use_title_as_h1),
args.depth,
effective_allowed,
sitemap_seed_urls,
Expand Down
1 change: 1 addition & 0 deletions crates/mq-dap/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use dap::prelude::Command;
use thiserror::Error;

#[derive(Error, Debug)]
#[non_exhaustive]
pub enum MqAdapterError {
#[error("Unhandled command: {0:?}")]
UnhandledCommand(Box<Command>),
Expand Down
11 changes: 5 additions & 6 deletions crates/mq-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,11 @@ impl From<MqConversionOptions> for ConversionOptions {
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
};
ConversionOptions {
extract_scripts_as_code_blocks: options.extract_scripts_as_code_blocks,
generate_front_matter: options.generate_front_matter,
use_title_as_h1: options.use_title_as_h1,
base_url,
}
ConversionOptions::default()
.with_extract_scripts_as_code_blocks(options.extract_scripts_as_code_blocks)
.with_generate_front_matter(options.generate_front_matter)
.with_use_title_as_h1(options.use_title_as_h1)
.with_base_url(base_url)
}
}

Expand Down
11 changes: 4 additions & 7 deletions crates/mq-formatter/benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,10 @@ def y(): test;
| let a = 2
| import "a.mq"
def b(): test;"#;
let config = FormatterConfig {
indent_width: 2,
sort_imports: true,
sort_functions: true,
sort_fields: true,
max_width: None,
};
let config = FormatterConfig::default()
.with_sort_imports(true)
.with_sort_functions(true)
.with_sort_fields(true);
let mut formatter = Formatter::new(Some(config));
formatter.format(code).unwrap();
}
Expand Down
Loading
Loading