diff --git a/Cargo.lock b/Cargo.lock index 6961abd8ac774..4a4ea68088b82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,7 +1943,10 @@ dependencies = [ "napi", "napi-build", "napi-derive", + "oxc_ast", + "oxc_ast_visit", "oxc_diagnostics", + "oxc_syntax", ] [[package]] diff --git a/crates/oxc_napi/Cargo.toml b/crates/oxc_napi/Cargo.toml index ac0bf0a166423..fd59d63ca4153 100644 --- a/crates/oxc_napi/Cargo.toml +++ b/crates/oxc_napi/Cargo.toml @@ -21,12 +21,13 @@ doctest = false crate-type = ["lib", "cdylib"] [dependencies] +oxc_ast = { workspace = true } +oxc_ast_visit = { workspace = true, features = ["serialize"] } +oxc_diagnostics = { workspace = true } +oxc_syntax = { workspace = true } + napi = { workspace = true } napi-derive = { workspace = true } -oxc_diagnostics = { workspace = true } [build-dependencies] napi-build = { workspace = true } - -[package.metadata.cargo-shear] -ignored = ["napi"] diff --git a/crates/oxc_napi/src/comment.rs b/crates/oxc_napi/src/comment.rs new file mode 100644 index 0000000000000..0ecdbfa271ef4 --- /dev/null +++ b/crates/oxc_napi/src/comment.rs @@ -0,0 +1,11 @@ +use napi_derive::napi; + +#[derive(Debug, Clone)] +#[napi(object)] +pub struct Comment { + #[napi(ts_type = "'Line' | 'Block'")] + pub r#type: String, + pub value: String, + pub start: u32, + pub end: u32, +} diff --git a/crates/oxc_napi/src/error.rs b/crates/oxc_napi/src/error.rs new file mode 100644 index 0000000000000..0f1e691852df9 --- /dev/null +++ b/crates/oxc_napi/src/error.rs @@ -0,0 +1,68 @@ +use napi_derive::napi; + +use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; + +#[napi(object)] +pub struct OxcError { + pub severity: Severity, + pub message: String, + pub labels: Vec, + pub help_message: Option, +} + +impl OxcError { + pub fn new(message: String) -> Self { + Self { severity: Severity::Error, message, labels: vec![], help_message: None } + } +} + +impl From for OxcError { + fn from(diagnostic: OxcDiagnostic) -> Self { + let labels = diagnostic + .labels + .as_ref() + .map(|labels| labels.iter().map(ErrorLabel::from).collect::>()) + .unwrap_or_default(); + Self { + severity: Severity::from(diagnostic.severity), + message: diagnostic.message.to_string(), + labels, + help_message: diagnostic.help.as_ref().map(ToString::to_string), + } + } +} + +#[napi(object)] +pub struct ErrorLabel { + pub message: Option, + pub start: u32, + pub end: u32, +} + +impl From<&LabeledSpan> for ErrorLabel { + #[expect(clippy::cast_possible_truncation)] + fn from(label: &LabeledSpan) -> Self { + Self { + message: label.label().map(ToString::to_string), + start: label.offset() as u32, + end: (label.offset() + label.len()) as u32, + } + } +} + +#[napi(string_enum)] +pub enum Severity { + Error, + Warning, + Advice, +} + +impl From for Severity { + fn from(value: oxc_diagnostics::Severity) -> Self { + match value { + oxc_diagnostics::Severity::Error => Self::Error, + oxc_diagnostics::Severity::Warning => Self::Warning, + oxc_diagnostics::Severity::Advice => Self::Advice, + } + } +} diff --git a/crates/oxc_napi/src/lib.rs b/crates/oxc_napi/src/lib.rs index 0f1e691852df9..47cb8e5618db4 100644 --- a/crates/oxc_napi/src/lib.rs +++ b/crates/oxc_napi/src/lib.rs @@ -1,68 +1,58 @@ -use napi_derive::napi; - -use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; - -#[napi(object)] -pub struct OxcError { - pub severity: Severity, - pub message: String, - pub labels: Vec, - pub help_message: Option, -} - -impl OxcError { - pub fn new(message: String) -> Self { - Self { severity: Severity::Error, message, labels: vec![], help_message: None } - } -} - -impl From for OxcError { - fn from(diagnostic: OxcDiagnostic) -> Self { - let labels = diagnostic - .labels - .as_ref() - .map(|labels| labels.iter().map(ErrorLabel::from).collect::>()) - .unwrap_or_default(); - Self { - severity: Severity::from(diagnostic.severity), - message: diagnostic.message.to_string(), - labels, - help_message: diagnostic.help.as_ref().map(ToString::to_string), - } - } -} - -#[napi(object)] -pub struct ErrorLabel { - pub message: Option, - pub start: u32, - pub end: u32, -} - -impl From<&LabeledSpan> for ErrorLabel { - #[expect(clippy::cast_possible_truncation)] - fn from(label: &LabeledSpan) -> Self { - Self { - message: label.label().map(ToString::to_string), - start: label.offset() as u32, - end: (label.offset() + label.len()) as u32, +mod comment; +mod error; + +pub use comment::*; +pub use error::*; + +use oxc_ast::{CommentKind, ast::Program}; +use oxc_ast_visit::utf8_to_utf16::Utf8ToUtf16; +use oxc_syntax::module_record::ModuleRecord; + +/// Convert spans to UTF-16 +pub fn convert_utf8_to_utf16( + source_text: &str, + program: &mut Program, + module_record: &mut ModuleRecord, + errors: &mut Vec, +) -> Vec { + let span_converter = Utf8ToUtf16::new(source_text); + span_converter.convert_program(program); + + // Convert comments + let mut offset_converter = span_converter.converter(); + let comments = program + .comments + .iter() + .map(|comment| { + let value = comment.content_span().source_text(source_text).to_string(); + let mut span = comment.span; + if let Some(converter) = offset_converter.as_mut() { + converter.convert_span(&mut span); + } + Comment { + r#type: match comment.kind { + CommentKind::Line => String::from("Line"), + CommentKind::Block => String::from("Block"), + }, + value, + start: span.start, + end: span.end, + } + }) + .collect::>(); + + // Convert spans in module record to UTF-16 + span_converter.convert_module_record(module_record); + + // Convert spans in errors to UTF-16 + if let Some(mut converter) = span_converter.converter() { + for error in errors { + for label in &mut error.labels { + converter.convert_offset(&mut label.start); + converter.convert_offset(&mut label.end); + } } } -} -#[napi(string_enum)] -pub enum Severity { - Error, - Warning, - Advice, -} - -impl From for Severity { - fn from(value: oxc_diagnostics::Severity) -> Self { - match value { - oxc_diagnostics::Severity::Error => Self::Error, - oxc_diagnostics::Severity::Warning => Self::Warning, - oxc_diagnostics::Severity::Advice => Self::Advice, - } - } + comments } diff --git a/napi/parser/src/lib.rs b/napi/parser/src/lib.rs index eadb16ed80a26..66cf361834bcd 100644 --- a/napi/parser/src/lib.rs +++ b/napi/parser/src/lib.rs @@ -12,20 +12,18 @@ use napi_derive::napi; use oxc::{ allocator::Allocator, - ast::CommentKind, - ast_visit::utf8_to_utf16::Utf8ToUtf16, parser::{ParseOptions, Parser, ParserReturn}, semantic::SemanticBuilder, span::SourceType, }; -use oxc_napi::OxcError; +use oxc_napi::{OxcError, convert_utf8_to_utf16}; mod convert; mod raw_transfer; mod raw_transfer_types; mod types; pub use raw_transfer::{get_buffer_offset, parse_sync_raw, raw_transfer_supported}; -pub use types::{Comment, EcmaScriptModule, ParseResult, ParserOptions}; +pub use types::{EcmaScriptModule, ParseResult, ParserOptions}; mod generated { // Note: We intentionally don't import `generated/derive_estree.rs`. It's not needed. @@ -100,46 +98,8 @@ fn parse_with_return(filename: &str, source_text: String, options: &ParserOption errors.extend(semantic_ret.errors.into_iter().map(OxcError::from)); } - // Convert spans to UTF-16 - let span_converter = Utf8ToUtf16::new(&source_text); - span_converter.convert_program(&mut program); - - // Convert comments - let mut offset_converter = span_converter.converter(); - let comments = program - .comments - .iter() - .map(|comment| { - let value = comment.content_span().source_text(&source_text).to_string(); - let mut span = comment.span; - if let Some(converter) = offset_converter.as_mut() { - converter.convert_span(&mut span); - } - - Comment { - r#type: match comment.kind { - CommentKind::Line => String::from("Line"), - CommentKind::Block => String::from("Block"), - }, - value, - start: span.start, - end: span.end, - } - }) - .collect::>(); - - // Convert spans in module record to UTF-16 - span_converter.convert_module_record(&mut module_record); - - // Convert spans in errors to UTF-16 - if let Some(mut converter) = span_converter.converter() { - for error in &mut errors { - for label in &mut error.labels { - converter.convert_offset(&mut label.start); - converter.convert_offset(&mut label.end); - } - } - } + let comments = + convert_utf8_to_utf16(&source_text, &mut program, &mut module_record, &mut errors); let program = match ast_type { AstType::JavaScript => program.to_estree_js_json(), diff --git a/napi/parser/src/types.rs b/napi/parser/src/types.rs index f4d8a83be898b..1392a9c51b3ae 100644 --- a/napi/parser/src/types.rs +++ b/napi/parser/src/types.rs @@ -2,7 +2,7 @@ use std::mem; use napi_derive::napi; -use oxc_napi::OxcError; +use oxc_napi::{Comment, OxcError}; #[napi(object)] #[derive(Default)] @@ -70,15 +70,6 @@ impl ParseResult { } } -#[napi(object)] -pub struct Comment { - #[napi(ts_type = "'Line' | 'Block'")] - pub r#type: String, - pub value: String, - pub start: u32, - pub end: u32, -} - #[napi(object)] #[derive(Default)] pub struct EcmaScriptModule { diff --git a/napi/playground/index.d.ts b/napi/playground/index.d.ts index dd5ac848e2e8d..ec9fee3d118ff 100644 --- a/napi/playground/index.d.ts +++ b/napi/playground/index.d.ts @@ -12,8 +12,7 @@ export declare class Oxc { prettierFormattedText: string prettierIrText: string constructor() - getDiagnostics2(): Array - getDiagnostics(): Array + getDiagnostics(): Array getComments(): Array /** * # Errors @@ -23,17 +22,12 @@ export declare class Oxc { } export interface Comment { - type: CommentType + type: 'Line' | 'Block' value: string start: number end: number } -export declare const enum CommentType { - Line = 0, - Block = 1 -} - export interface ErrorLabel { message?: string start: number @@ -60,13 +54,6 @@ export interface OxcControlFlowOptions { verbose?: boolean } -export interface OxcDiagnostic { - start: number - end: number - severity: string - message: string -} - export interface OxcError { severity: Severity message: string diff --git a/napi/playground/index.js b/napi/playground/index.js index 473ac1db6002e..ab442e4079e96 100644 --- a/napi/playground/index.js +++ b/napi/playground/index.js @@ -371,5 +371,4 @@ if (!nativeBinding) { } module.exports.Oxc = nativeBinding.Oxc -module.exports.CommentType = nativeBinding.CommentType module.exports.Severity = nativeBinding.Severity diff --git a/napi/playground/playground.wasi-browser.js b/napi/playground/playground.wasi-browser.js index 81cb30c0e32e3..124366bb78813 100644 --- a/napi/playground/playground.wasi-browser.js +++ b/napi/playground/playground.wasi-browser.js @@ -54,5 +54,4 @@ const { }, }) export const Oxc = __napiModule.exports.Oxc -export const CommentType = __napiModule.exports.CommentType export const Severity = __napiModule.exports.Severity diff --git a/napi/playground/playground.wasi.cjs b/napi/playground/playground.wasi.cjs index bc6b008bc21f8..7712d05f642e9 100644 --- a/napi/playground/playground.wasi.cjs +++ b/napi/playground/playground.wasi.cjs @@ -86,5 +86,4 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule }) module.exports.Oxc = __napiModule.exports.Oxc -module.exports.CommentType = __napiModule.exports.CommentType module.exports.Severity = __napiModule.exports.Severity diff --git a/napi/playground/src/lib.rs b/napi/playground/src/lib.rs index a8d0c020da754..2014fd8b63fcf 100644 --- a/napi/playground/src/lib.rs +++ b/napi/playground/src/lib.rs @@ -1,16 +1,19 @@ use std::{ - cell::{Cell, RefCell}, + cell::Cell, path::{Path, PathBuf}, rc::Rc, sync::Arc, }; use napi_derive::napi; +use serde::Serialize; + use oxc::{ allocator::Allocator, - ast::{Comment as OxcComment, CommentKind, ast::Program}, - ast_visit::{Visit, utf8_to_utf16::Utf8ToUtf16}, + ast::ast::Program, + ast_visit::Visit, codegen::{CodeGenerator, CodegenOptions}, + diagnostics::OxcDiagnostic, isolated_declarations::{IsolatedDeclarations, IsolatedDeclarationsOptions}, minifier::{CompressOptions, MangleOptions, Minifier, MinifierOptions}, parser::{ParseOptions, Parser, ParserReturn}, @@ -24,9 +27,8 @@ use oxc::{ }; use oxc_index::Idx; use oxc_linter::{ConfigStoreBuilder, LintOptions, Linter, ModuleRecord}; -use oxc_napi::OxcError; +use oxc_napi::{Comment, OxcError, convert_utf8_to_utf16}; use oxc_prettier::{Prettier, PrettierOptions}; -use serde::Serialize; use crate::options::{OxcOptions, OxcRunOptions}; @@ -46,32 +48,7 @@ pub struct Oxc { pub prettier_formatted_text: String, pub prettier_ir_text: String, comments: Vec, - diagnostics: RefCell>, -} - -#[derive(Clone)] -#[napi(object)] -pub struct Comment { - pub r#type: CommentType, - pub value: String, - pub start: u32, - pub end: u32, -} - -#[derive(Clone)] -#[napi] -pub enum CommentType { - Line, - Block, -} - -#[derive(Default, Clone)] -#[napi(object)] -pub struct OxcDiagnostic { - pub start: u32, - pub end: u32, - pub severity: String, - pub message: String, + diagnostics: Vec, } #[napi] @@ -82,35 +59,8 @@ impl Oxc { } #[napi] - pub fn get_diagnostics2(&self) -> Vec { - self.diagnostics.borrow().clone().into_iter().map(OxcError::from).collect() - } - - #[napi] - pub fn get_diagnostics(&self) -> Vec { - self.diagnostics - .borrow() - .iter() - .flat_map(|error| match &error.labels { - Some(labels) => labels - .iter() - .map(|label| OxcDiagnostic { - #[expect(clippy::cast_possible_truncation)] - start: label.offset() as u32, - #[expect(clippy::cast_possible_truncation)] - end: (label.offset() + label.len()) as u32, - severity: format!("{:?}", error.severity), - message: format!("{error}"), - }) - .collect::>(), - None => vec![OxcDiagnostic { - start: 0, - end: 0, - severity: format!("{:?}", error.severity), - message: format!("{error}"), - }], - }) - .collect::>() + pub fn get_diagnostics(&self) -> Vec { + self.diagnostics.iter().cloned().map(OxcError::from).collect() } #[napi] @@ -121,10 +71,9 @@ impl Oxc { /// # Errors /// Serde serialization error #[napi] - #[allow(clippy::allow_attributes)] - #[allow(clippy::needless_pass_by_value)] + #[allow(clippy::allow_attributes, clippy::needless_pass_by_value)] pub fn run(&mut self, source_text: String, options: OxcOptions) -> napi::Result<()> { - self.diagnostics = RefCell::default(); + self.diagnostics = vec![]; let OxcOptions { run: run_options, @@ -168,7 +117,7 @@ impl Oxc { .allow_v8_intrinsics .unwrap_or(default_parser_options.allow_v8_intrinsics), }; - let ParserReturn { mut program, errors, module_record, .. } = + let ParserReturn { mut program, errors, mut module_record, .. } = Parser::new(&allocator, &source_text, source_type) .with_options(oxc_parser_options) .parse(); @@ -189,13 +138,11 @@ impl Oxc { )) }); if run_options.syntax.unwrap_or_default() { - self.save_diagnostics( - errors.into_iter().chain(semantic_ret.errors).collect::>(), - ); + self.diagnostics.extend(errors.into_iter().chain(semantic_ret.errors)); } - let module_record = Arc::new(ModuleRecord::new(&path, &module_record, &semantic)); - self.run_linter(&run_options, &path, &program, &module_record); + let linter_module_record = Arc::new(ModuleRecord::new(&path, &module_record, &semantic)); + self.run_linter(&run_options, &path, &program, &linter_module_record); self.run_prettier(&run_options, &source_text, source_type); @@ -229,7 +176,7 @@ impl Oxc { self.codegen_sourcemap_text = codegen_result.map.map(|map| map.to_json_string()); } else { - self.save_diagnostics(ret.errors.into_iter().collect::>()); + self.diagnostics.extend(ret.errors); self.codegen_text = String::new(); self.codegen_sourcemap_text = None; } @@ -242,9 +189,7 @@ impl Oxc { .and_then(|target| { TransformOptions::from_target(target) .map_err(|err| { - self.save_diagnostics(vec![oxc::diagnostics::OxcDiagnostic::error( - err, - )]); + self.diagnostics.push(OxcDiagnostic::error(err)); }) .ok() }) @@ -252,7 +197,7 @@ impl Oxc { let result = Transformer::new(&allocator, &path, &options) .build_with_scoping(scoping, &mut program); if !result.errors.is_empty() { - self.save_diagnostics(result.errors.into_iter().collect::>()); + self.diagnostics.extend(result.errors); } } @@ -291,20 +236,24 @@ impl Oxc { self.codegen_text = codegen_result.code; self.codegen_sourcemap_text = codegen_result.map.map(|map| map.to_json_string()); self.ir = format!("{:#?}", program.body); - self.convert_ast(&mut program); + let mut errors = vec![]; + let comments = + convert_utf8_to_utf16(&source_text, &mut program, &mut module_record, &mut errors); + self.ast_json = program.to_pretty_estree_ts_json(); + self.comments = comments; Ok(()) } fn run_linter( - &self, + &mut self, run_options: &OxcRunOptions, path: &Path, program: &Program, module_record: &Arc, ) { // Only lint if there are no syntax errors - if run_options.lint.unwrap_or_default() && self.diagnostics.borrow().is_empty() { + if run_options.lint.unwrap_or_default() && self.diagnostics.is_empty() { let semantic_ret = SemanticBuilder::new().with_cfg(true).build(program); let semantic = Rc::new(semantic_ret.semantic); let lint_config = @@ -314,8 +263,7 @@ impl Oxc { Rc::clone(&semantic), Arc::clone(module_record), ); - let diagnostics = linter_ret.into_iter().map(|e| e.error).collect(); - self.save_diagnostics(diagnostics); + self.diagnostics.extend(linter_ret.into_iter().map(|e| e.error)); } } @@ -442,44 +390,4 @@ impl Oxc { serde_json::to_string_pretty(&data).map_err(|e| napi::Error::from_reason(e.to_string())) } - - fn save_diagnostics(&self, diagnostics: Vec) { - self.diagnostics.borrow_mut().extend(diagnostics); - } - - fn convert_ast(&mut self, program: &mut Program) { - let span_converter = Utf8ToUtf16::new(program.source_text); - span_converter.convert_program(program); - self.ast_json = program.to_pretty_estree_ts_json(); - - self.comments = Self::map_comments(program.source_text, &program.comments, &span_converter); - } - - fn map_comments( - source_text: &str, - comments: &[OxcComment], - span_converter: &Utf8ToUtf16, - ) -> Vec { - let mut offset_converter = span_converter.converter(); - - comments - .iter() - .map(|comment| { - let value = comment.content_span().source_text(source_text).to_string(); - let mut span = comment.span; - if let Some(converter) = &mut offset_converter { - converter.convert_span(&mut span); - } - Comment { - r#type: match comment.kind { - CommentKind::Line => CommentType::Line, - CommentKind::Block => CommentType::Block, - }, - value, - start: span.start, - end: span.end, - } - }) - .collect() - } } diff --git a/napi/transform/index.d.ts b/napi/transform/index.d.ts index a3585c253b8cc..739f204baf272 100644 --- a/napi/transform/index.d.ts +++ b/napi/transform/index.d.ts @@ -12,6 +12,13 @@ export interface ArrowFunctionsOptions { spec?: boolean } +export interface Comment { + type: 'Line' | 'Block' + value: string + start: number + end: number +} + export interface CompilerAssumptions { ignoreFunctionLength?: boolean noDocumentAll?: boolean