diff --git a/crates/oxc_cli/Cargo.toml b/crates/oxc_cli/Cargo.toml index bf7b923489978..0518d6c961768 100644 --- a/crates/oxc_cli/Cargo.toml +++ b/crates/oxc_cli/Cargo.toml @@ -28,6 +28,11 @@ name = "oxlint" path = "src/lint/main.rs" test = false +[[bin]] +name = "oxparse" +path = "src/parse/main.rs" +test = false + [[bin]] name = "oxformat" path = "src/format/main.rs" diff --git a/crates/oxc_cli/src/lib.rs b/crates/oxc_cli/src/lib.rs index b034ee77f5769..c149535f6590f 100644 --- a/crates/oxc_cli/src/lib.rs +++ b/crates/oxc_cli/src/lib.rs @@ -1,6 +1,7 @@ mod command; mod format; mod lint; +mod parse; mod result; mod runner; mod walk; @@ -9,6 +10,7 @@ pub use crate::{ command::*, format::FormatRunner, lint::LintRunner, + parse::ParseRunner, result::{CliRunResult, LintResult}, runner::Runner, }; diff --git a/crates/oxc_cli/src/parse/main.rs b/crates/oxc_cli/src/parse/main.rs new file mode 100644 index 0000000000000..64d8433ec7d49 --- /dev/null +++ b/crates/oxc_cli/src/parse/main.rs @@ -0,0 +1,26 @@ +#![cfg(not(miri))] // Miri does not support custom allocators + +#[cfg(not(debug_assertions))] +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; + +#[cfg(not(debug_assertions))] +#[cfg(target_os = "windows")] +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +use oxc_cli::{CliRunResult, ParseRunner, Runner}; + +fn main() -> CliRunResult { + init_miette(); + + let command = oxc_cli::format_command().fallback_to_usage().run(); + command.handle_threads(); + ParseRunner::new(command.format_options).run() +} + +// Initialize the data which relies on `is_atty` system calls so they don't block subsequent threads. +fn init_miette() { + miette::set_hook(Box::new(|_| Box::new(miette::MietteHandlerOpts::new().build()))).unwrap(); +} diff --git a/crates/oxc_cli/src/parse/mod.rs b/crates/oxc_cli/src/parse/mod.rs new file mode 100644 index 0000000000000..16d4921717df6 --- /dev/null +++ b/crates/oxc_cli/src/parse/mod.rs @@ -0,0 +1,78 @@ +use std::{env, path::Path}; + +use ignore::gitignore::Gitignore; +use oxc_allocator::Allocator; +use oxc_parser::Parser; +use oxc_span::{SourceType, VALID_EXTENSIONS}; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; + +use crate::{ + command::FormatOptions, + result::{CliRunResult, ParseResult}, + walk::{Extensions, Walk}, + Runner, +}; + +pub struct ParseRunner { + options: FormatOptions, +} + +impl Runner for ParseRunner { + type Options = FormatOptions; + + fn new(options: Self::Options) -> Self { + Self { options } + } + + fn run(self) -> CliRunResult { + let FormatOptions { paths, ignore_options, .. } = self.options; + + let mut paths = paths; + + // The ignore crate whitelists explicit paths, but priority + // should be given to the ignore file. Many users lint + // automatically and pass a list of changed files explicitly. + // To accommodate this, unless `--no-ignore` is passed, + // pre-filter the paths. + if !paths.is_empty() && !ignore_options.no_ignore { + let (ignore, _err) = Gitignore::new(&ignore_options.ignore_path); + paths.retain(|p| if p.is_dir() { true } else { !ignore.matched(p, false).is_ignore() }); + } + + if paths.is_empty() { + if let Ok(cwd) = env::current_dir() { + paths.push(cwd); + } else { + return CliRunResult::InvalidOptions { + message: "Failed to get current working directory.".to_string(), + }; + } + } + + let extensions = VALID_EXTENSIONS.to_vec(); + + let now = std::time::Instant::now(); + + let paths = + Walk::new(&paths, &ignore_options).with_extensions(Extensions(extensions)).paths(); + + let total_duration = paths.par_iter().map(|path| Self::parse(path)).sum(); + + CliRunResult::ParseResult(ParseResult { + parse_duration: total_duration, + duration: now.elapsed(), + number_of_files: paths.len(), + }) + } +} + +impl ParseRunner { + fn parse(path: &Path) -> std::time::Duration { + let source_text = std::fs::read_to_string(path).unwrap(); + let allocator = Allocator::default(); + let source_type = SourceType::from_path(path).unwrap(); + let now = std::time::Instant::now(); + let _ = Parser::new(&allocator, &source_text, source_type).preserve_parens(false).parse(); + now.elapsed() + } +} diff --git a/crates/oxc_cli/src/result.rs b/crates/oxc_cli/src/result.rs index 1b23d1657949a..1d78ba3489454 100644 --- a/crates/oxc_cli/src/result.rs +++ b/crates/oxc_cli/src/result.rs @@ -11,6 +11,7 @@ pub enum CliRunResult { PathNotFound { paths: Vec }, LintResult(LintResult), FormatResult(FormatResult), + ParseResult(ParseResult), TypeCheckResult { duration: Duration, number_of_diagnostics: usize }, } @@ -32,6 +33,13 @@ pub struct FormatResult { pub number_of_files: usize, } +#[derive(Debug)] +pub struct ParseResult { + pub parse_duration: Duration, + pub duration: Duration, + pub number_of_files: usize, +} + impl Termination for CliRunResult { fn report(self) -> ExitCode { match self { @@ -95,6 +103,16 @@ impl Termination for CliRunResult { ); ExitCode::from(0) } + Self::ParseResult(ParseResult { parse_duration, duration, number_of_files }) => { + let threads = rayon::current_num_threads(); + let time = Self::get_execution_time(&duration); + let parse_time = Self::get_execution_time(&parse_duration); + let s = if number_of_files == 1 { "" } else { "s" }; + println!( + "Finished in {time} ({parse_time}) on {number_of_files} file{s} using {threads} threads." + ); + ExitCode::from(0) + } Self::TypeCheckResult { duration, number_of_diagnostics } => { let time = Self::get_execution_time(&duration); println!("Finished in {time}."); diff --git a/justfile b/justfile index 5cff77f92b266..77edfe9c38758 100755 --- a/justfile +++ b/justfile @@ -1,5 +1,7 @@ #!/usr/bin/env -S just --justfile +set windows-shell := ["pwsh.exe", "-c"] + _default: @just --list -u @@ -140,3 +142,31 @@ upgrade: clone-submodule dir url sha: git clone --depth=1 {{url}} {{dir}} || true cd {{dir}} && git fetch origin {{sha}} && git reset --hard {{sha}} + + +ecosystem_dir := "C:/source/ecosystem" +oxlint_bin := "C:/source/rust/oxc/target/release/oxparse.exe" +threads := "12" + +pgo_data_dir := "C:/source/rust/oxc/pgo-data" +llvm_profdata_bin := "~/.rustup/toolchains/1.78.0-x86_64-pc-windows-msvc/lib/rustlib/x86_64-pc-windows-msvc/bin/llvm-profdata.exe" + +build-pgo: + just build-pgo-init + just oxlint_bin=C:/source/rust/oxc/target/x86_64-pc-windows-msvc/release/oxparse.exe ecosystem + {{llvm_profdata_bin}} merge -o {{pgo_data_dir}}/merged.profdata {{pgo_data_dir}} + just build-pgo-final + +build-pgo-init $RUSTFLAGS="-Cprofile-generate=C:/source/rust/oxc/pgo-data": + cargo build --release -p oxc_cli --bin oxparse --features allocator --target x86_64-pc-windows-msvc + +build-pgo-final $RUSTFLAGS="-Cprofile-use=C:/source/rust/oxc/pgo-data/merged.profdata -Cllvm-args=-pgo-warn-missing-function": + cargo build --release -p oxc_cli --bin oxparse --features allocator --target x86_64-pc-windows-msvc + +ecosystem: + cd "{{ecosystem_dir}}/DefinitelyTyped" && {{oxlint_bin}} --threads={{threads}} + cd "{{ecosystem_dir}}/affine" && {{oxlint_bin}} --threads={{threads}} + cd "{{ecosystem_dir}}/napi-rs" && {{oxlint_bin}} --threads={{threads}} --ignore-path=.oxlintignore + cd "{{ecosystem_dir}}/preact" && {{oxlint_bin}} --threads={{threads}} oxlint src test debug compat hooks test-utils + cd "{{ecosystem_dir}}/rolldown" && {{oxlint_bin}} --threads={{threads}} --ignore-path=.oxlintignore + cd "{{ecosystem_dir}}/vscode" && {{oxlint_bin}} --threads={{threads}}