oxc/napi/transform/src/context.rs
DonIsaac 9b51e045c7 refactor: overhaul napi transformer package (#4592)
## What This PR Does
- Support declaration emit with `transform()`
- Consolidate shared parsing and error reporting logic into a `TransformContext`
- Add JSDoc comments to options

I'm getting this package ready for consumption in [oxc-jest](https://github.com/oxc-project/oxc-jest).
2024-08-06 01:14:10 +00:00

173 lines
4.9 KiB
Rust

use oxc_allocator::Allocator;
use oxc_ast::{ast::Program, Trivias};
use oxc_codegen::Codegen;
use std::{
cell::{OnceCell, Ref, RefCell, RefMut},
path::Path,
sync::Arc,
};
use oxc_diagnostics::{Error, NamedSource, OxcDiagnostic};
use oxc_parser::{Parser, ParserReturn};
use oxc_span::SourceType;
use crate::TransformOptions;
#[must_use]
pub(crate) struct TransformContext<'a> {
pub allocator: &'a Allocator,
program: RefCell<Program<'a>>,
pub trivias: Trivias,
/// Will be initialized if provided to constructor or first accessed.
/// Prevents allocations for codegen tasks that don't require options.
options: OnceCell<oxc_transformer::TransformOptions>,
/// Generate source maps?
source_map: bool,
/// Generate `.d.ts` files?
///
/// Used by [`crate::transform`].
declarations: bool,
/// Path to the file being transformed.
filename: &'a str,
/// Source text of the file being transformed.
source_text: &'a str,
source_type: SourceType,
/// Errors that occurred during transformation.
errors: RefCell<Vec<OxcDiagnostic>>,
}
impl<'a> TransformContext<'a> {
pub fn new(
allocator: &'a Allocator,
filename: &'a str,
source_text: &'a str,
source_type: SourceType,
options: Option<TransformOptions>,
) -> Self {
let ParserReturn { errors, program, trivias, .. } =
Parser::new(allocator, source_text, source_type).parse();
// Options that are added by this napi crates and don't exist in
// oxc_transformer.
let source_map = options.as_ref().and_then(|o| o.sourcemap).unwrap_or_default();
let declarations = options
.as_ref()
.and_then(|o| o.typescript.as_ref())
.and_then(|t| t.declaration)
.unwrap_or_default();
// Insert options into the cell if provided. Otherwise they will be
// initialized to default when first accessed.
let options_cell = OnceCell::new();
if let Some(options) = options {
options_cell.set(options.into()).unwrap();
}
Self {
allocator,
program: RefCell::new(program),
trivias,
options: options_cell,
source_map,
declarations,
filename,
source_text,
source_type,
errors: RefCell::new(errors),
}
}
#[inline]
pub fn file_name(&self) -> &'a str {
self.filename
}
#[inline]
pub fn file_path(&self) -> &'a Path {
Path::new(self.filename)
}
#[inline]
pub fn source_text(&self) -> &'a str {
self.source_text
}
#[inline]
pub fn oxc_options(&self) -> oxc_transformer::TransformOptions {
self.options.get_or_init(Default::default).clone()
}
#[inline]
pub(crate) fn declarations(&self) -> bool {
self.declarations
}
#[inline]
pub fn source_type(&self) -> SourceType {
self.source_type
}
#[inline]
pub fn program(&self) -> Ref<'_, Program<'a>> {
self.program.borrow()
}
#[inline]
pub fn program_mut(&self) -> RefMut<'_, Program<'a>> {
self.program.borrow_mut()
}
pub fn codegen<const MINIFY: bool>(&self) -> Codegen<'a, MINIFY> {
let codegen = Codegen::<MINIFY>::new();
if self.source_map {
codegen.enable_source_map(self.file_name(), self.source_text())
} else {
codegen
}
}
pub fn add_diagnostics(&self, diagnostics: Vec<OxcDiagnostic>) {
if diagnostics.is_empty() {
return;
}
self.errors.borrow_mut().extend(diagnostics);
}
pub fn take_and_render_reports(&self) -> Vec<String> {
let diagnostics = std::mem::take(&mut *self.errors.borrow_mut());
// TODO: make pretty-printed errors configurable
self.wrap_diagnostics(diagnostics).map(|error| format!("{error:?}")).collect()
}
fn wrap_diagnostics<D: IntoIterator<Item = OxcDiagnostic>>(
&self,
diagnostics: D,
) -> impl Iterator<Item = Error> {
let source = {
let lang = match (self.source_type.is_javascript(), self.source_type.is_jsx()) {
(true, false) => "JavaScript",
(true, true) => "JSX",
(false, true) => "TypeScript React",
(false, false) => {
if self.source_type.is_typescript_definition() {
"TypeScript Declaration"
} else {
"TypeScript"
}
}
};
let ns = NamedSource::new(self.file_name(), self.source_text().to_string())
.with_language(lang);
Arc::new(ns)
};
diagnostics
.into_iter()
.map(move |diagnostic| Error::from(diagnostic).with_source_code(Arc::clone(&source)))
}
}