261020.1 · 1d7e0126f3e601103d93586a6f872144f2789dad
chore: self-maintain Node.js features
reading declarations
Changes to deps/swc/bindings/binding_nodejs_support_wasm/src/. All files
deps/swc/bindings/binding_nodejs_support_wasm/src/error_reporter.rs
@@ -1,881 +0,0 @@-use std::fmt::{self};--use miette::{- Diagnostic, GraphicalTheme, LabeledSpan, ReportHandler, SourceCode, SourceSpan, ThemeCharacters,-};-use owo_colors::{OwoColorize, Style};-use unicode_width::UnicodeWidthChar;--/**-A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a-quasi-graphical way, using terminal colors, unicode drawing characters, and-other such things.--This is the default reporter bundled with `miette`.--This printer can be customized by using [`new_themed()`](GraphicalReportHandler::new_themed) and handing it a-[`GraphicalTheme`] of your own creation (or using one of its own defaults!)--See [`set_hook()`](crate::set_hook) for more details on customizing your global-printer.-*/-#[derive(Debug, Clone)]-pub struct SwcReportHandler {- pub(crate) theme: GraphicalTheme,- pub(crate) context_lines: usize,- pub(crate) tab_width: usize,-}--impl SwcReportHandler {- /// Create a new `GraphicalReportHandler` with the default- /// [`GraphicalTheme`]. This will use both unicode characters and colors.- pub fn new() -> Self {- Self {- theme: GraphicalTheme::default(),- context_lines: 1,- tab_width: 4,- }- }-- /// Set a theme for this handler.- pub fn with_theme(mut self, theme: GraphicalTheme) -> Self {- self.theme = theme;- self- }-}--impl Default for SwcReportHandler {- fn default() -> Self {- Self::new()- }-}--impl SwcReportHandler {- /// Render a [`Diagnostic`]. This function is mostly internal and meant to- /// be called by the toplevel [`ReportHandler`] handler, but is made public- /// to make it easier (possible) to test in isolation from global state.- pub fn render_report(- &self,- f: &mut impl fmt::Write,- diagnostic: &dyn Diagnostic,- ) -> fmt::Result {- self.render_report_inner(f, diagnostic, diagnostic.source_code())- }-- fn render_report_inner(- &self,- f: &mut impl fmt::Write,- diagnostic: &dyn Diagnostic,- parent_src: Option<&dyn SourceCode>,- ) -> fmt::Result {- let src = diagnostic.source_code().or(parent_src);- self.render_snippets(f, diagnostic, src)?;-- Ok(())- }-- fn render_snippets(- &self,- f: &mut impl fmt::Write,- diagnostic: &dyn Diagnostic,- opt_source: Option<&dyn SourceCode>,- ) -> fmt::Result {- let source = match opt_source {- Some(source) => source,- None => return Ok(()),- };- let labels = match diagnostic.labels() {- Some(labels) => labels,- None => return Ok(()),- };-- let mut labels = labels.collect::<Vec<_>>();- labels.sort_unstable_by_key(|l| l.inner().offset());-- let mut contexts = Vec::with_capacity(labels.len());- for right in labels.iter().cloned() {- let right_conts =- match source.read_span(right.inner(), self.context_lines, self.context_lines) {- Ok(cont) => cont,- Err(err) => {- writeln!(- f,- " [{} `{}` (offset: {}, length: {}): {:?}]",- "Failed to read contents for label".style(self.theme.styles.error),- right- .label()- .unwrap_or("<none>")- .style(self.theme.styles.link),- right.offset().style(self.theme.styles.link),- right.len().style(self.theme.styles.link),- err.style(self.theme.styles.warning)- )?;- return Ok(());- }- };-- if contexts.is_empty() {- contexts.push((right, right_conts));- continue;- }-- let (left, left_conts) = contexts.last().unwrap();- if left_conts.line() + left_conts.line_count() >= right_conts.line() {- // The snippets will overlap, so we create one Big Chunky Boi- let left_end = left.offset() + left.len();- let right_end = right.offset() + right.len();- let new_end = std::cmp::max(left_end, right_end);-- let new_span = LabeledSpan::new(- left.label().map(String::from),- left.offset(),- new_end - left.offset(),- );- // Check that the two contexts can be combined- if let Ok(new_conts) =- source.read_span(new_span.inner(), self.context_lines, self.context_lines)- {- contexts.pop();- // We'll throw the contents away later- contexts.push((new_span, new_conts));- continue;- }- }-- contexts.push((right, right_conts));- }- for (ctx, _) in contexts {- self.render_context(f, source, &ctx, &labels[..])?;- }-- Ok(())- }-- fn render_context(- &self,- f: &mut impl fmt::Write,- source: &dyn SourceCode,- context: &LabeledSpan,- labels: &[LabeledSpan],- ) -> fmt::Result {- let lines = self.get_lines(source, context.inner())?;-- // sorting is your friend- let labels = labels- .iter()- .zip(self.theme.styles.highlights.iter().cloned().cycle())- .map(|(label, st)| FancySpan::new(label.label().map(String::from), *label.inner(), st))- .collect::<Vec<_>>();-- // The max number of gutter-lines that will be active at any given- // point. We need this to figure out indentation, so we do one loop- // over the lines to see what the damage is gonna be.- let mut max_gutter = 0usize;- for line in &lines {- let mut num_highlights = 0;- for hl in &labels {- if !line.span_line_only(hl) && line.span_applies_gutter(hl) {- num_highlights += 1;- }- }- max_gutter = std::cmp::max(max_gutter, num_highlights);- }-- // Now it's time for the fun part--actually rendering everything!- for line in &lines {- // Then, we need to print the gutter, along with any fly-bys We- // have separate gutters depending on whether we're on the actual- // line, or on one of the "highlight lines" below it.- self.render_line_gutter(f, max_gutter, line, &labels)?;-- // And _now_ we can print out the line text itself!- self.render_line_text(f, &line.text)?;-- // Next, we write all the highlights that apply to this particular line.- let (single_line, multi_line): (Vec<_>, Vec<_>) = labels- .iter()- .filter(|hl| line.span_applies(hl))- .partition(|hl| line.span_line_only(hl));- if !single_line.is_empty() {- // gutter _again_- self.render_highlight_gutter(- f,- max_gutter,- line,- &labels,- LabelRenderMode::SingleLine,- )?;- self.render_single_line_highlights(f, line, max_gutter, &single_line, &labels)?;- }- for hl in multi_line {- if hl.label().is_some() && line.span_ends(hl) && !line.span_starts(hl) {- self.render_multi_line_end(f, &labels, max_gutter, line, hl)?;- }- }- }-- Ok(())- }-- fn render_multi_line_end(- &self,- f: &mut impl fmt::Write,- labels: &[FancySpan],- max_gutter: usize,- line: &Line,- label: &FancySpan,- ) -> fmt::Result {- if let Some(label_parts) = label.label_parts() {- // if it has a label, how long is it?- let (first, rest) = label_parts.split_first().expect(- "cannot crash because rest would have been None, see docs on the `label` field of \- FancySpan",- );-- if rest.is_empty() {- // gutter _again_- self.render_highlight_gutter(- f,- max_gutter,- line,- labels,- LabelRenderMode::SingleLine,- )?;-- self.render_multi_line_end_single(- f,- first,- label.style,- LabelRenderMode::SingleLine,- )?;- } else {- // gutter _again_- self.render_highlight_gutter(- f,- max_gutter,- line,- labels,- LabelRenderMode::MultiLineFirst,- )?;-- self.render_multi_line_end_single(- f,- first,- label.style,- LabelRenderMode::MultiLineFirst,- )?;- for label_line in rest {- // gutter _again_- self.render_highlight_gutter(- f,- max_gutter,- line,- labels,- LabelRenderMode::MultiLineRest,- )?;- self.render_multi_line_end_single(- f,- label_line,- label.style,- LabelRenderMode::MultiLineRest,- )?;- }- }- } else {- // gutter _again_- self.render_highlight_gutter(f, max_gutter, line, labels, LabelRenderMode::SingleLine)?;- // has no label- writeln!(f, "{}", self.theme.characters.hbar.style(label.style))?;- }-- Ok(())- }-- fn render_line_gutter(- &self,- f: &mut impl fmt::Write,- max_gutter: usize,- line: &Line,- highlights: &[FancySpan],- ) -> fmt::Result {- if max_gutter == 0 {- return Ok(());- }- let chars = &self.theme.characters;- let mut gutter = String::new();- let applicable = highlights.iter().filter(|hl| line.span_applies_gutter(hl));- let mut arrow = false;- for (i, hl) in applicable.enumerate() {- if line.span_starts(hl) {- gutter.push_str(&chars.ltop.style(hl.style).to_string());- gutter.push_str(- &chars- .hbar- .to_string()- .repeat(max_gutter.saturating_sub(i))- .style(hl.style)- .to_string(),- );- gutter.push_str(&chars.rarrow.style(hl.style).to_string());- arrow = true;- break;- } else if line.span_ends(hl) {- if hl.label().is_some() {- gutter.push_str(&chars.lcross.style(hl.style).to_string());- } else {- gutter.push_str(&chars.lbot.style(hl.style).to_string());- }- gutter.push_str(- &chars- .hbar- .to_string()- .repeat(max_gutter.saturating_sub(i))- .style(hl.style)- .to_string(),- );- gutter.push_str(&chars.rarrow.style(hl.style).to_string());- arrow = true;- break;- } else if line.span_flyby(hl) {- gutter.push_str(&chars.vbar.style(hl.style).to_string());- } else {- gutter.push(' ');- }- }- write!(- f,- "{}{}",- gutter,- " ".repeat(- if arrow { 1 } else { 3 } + max_gutter.saturating_sub(gutter.chars().count())- )- )?;- Ok(())- }-- fn render_highlight_gutter(- &self,- f: &mut impl fmt::Write,- max_gutter: usize,- line: &Line,- highlights: &[FancySpan],- render_mode: LabelRenderMode,- ) -> fmt::Result {- if max_gutter == 0 {- return Ok(());- }-- // keeps track of how many columns wide the gutter is- // important for ansi since simply measuring the size of the final string- // gives the wrong result when the string contains ansi codes.- let mut gutter_cols = 0;-- let chars = &self.theme.characters;- let mut gutter = String::new();- let applicable = highlights.iter().filter(|hl| line.span_applies_gutter(hl));- for (i, hl) in applicable.enumerate() {- if !line.span_line_only(hl) && line.span_ends(hl) {- if render_mode == LabelRenderMode::MultiLineRest {- // this is to make multiline labels work. We want to make the right amount- // of horizontal space for them, but not actually draw the lines- let horizontal_space = max_gutter.saturating_sub(i) + 2;- for _ in 0..horizontal_space {- gutter.push(' ');- }- // account for one more horizontal space, since in multiline mode- // we also add in the vertical line before the label like this:- // 2 │ ╭─▶ text- // 3 │ ├─▶ here- // · ╰──┤ these two lines- // · │ are the problem- // ^this- gutter_cols += horizontal_space + 1;- } else {- let num_repeat = max_gutter.saturating_sub(i) + 2;-- gutter.push_str(&chars.lbot.style(hl.style).to_string());-- gutter.push_str(- &chars- .hbar- .to_string()- .repeat(- num_repeat- // if we are rendering a multiline label, then leave a bit of space for the- // rcross character- - if render_mode == LabelRenderMode::MultiLineFirst {- 1- } else {- 0- },- )- .style(hl.style)- .to_string(),- );-- // we count 1 for the lbot char, and then a few more, the same number- // as we just repeated for. For each repeat we only add 1, even though- // due to ansi escape codes the number of bytes in the string could grow- // a lot each time.- gutter_cols += num_repeat + 1;- }- break;- } else {- gutter.push_str(&chars.vbar.style(hl.style).to_string());-- // we may push many bytes for the ansi escape codes style adds,- // but we still only add a single character-width to the string in a terminal- gutter_cols += 1;- }- }-- // now calculate how many spaces to add based on how many columns we just- // created. it's the max width of the gutter, minus how many- // character-widths we just generated capped at 0 (though this should- // never go below in reality), and then we add 3 to account for- // arrowheads when a gutter line ends- let num_spaces = (max_gutter + 3).saturating_sub(gutter_cols);- // we then write the gutter and as many spaces as we need- write!(f, "{}{:width$}", gutter, "", width = num_spaces)?;- Ok(())- }-- /// Returns an iterator over the visual width of each character in a line.- fn line_visual_char_width<'a>(&self, text: &'a str) -> impl Iterator<Item = usize> + 'a {- let mut column = 0;- let mut escaped = false;- let tab_width = self.tab_width;- text.chars().map(move |c| {- let width = match (escaped, c) {- // Round up to the next multiple of tab_width- (false, '\t') => tab_width - column % tab_width,- // start of ANSI escape- (false, '\x1b') => {- escaped = true;- 0- }- // use Unicode width for all other characters- (false, c) => c.width().unwrap_or(0),- // end of ANSI escape- (true, 'm') => {- escaped = false;- 0- }- // characters are zero width within escape sequence- (true, _) => 0,- };- column += width;- width- })- }-- /// Returns the visual column position of a byte offset on a specific line.- ///- /// If the offset occurs in the middle of a character, the returned column- /// corresponds to that character's first column in `start` is true, or its- /// last column if `start` is false.- fn visual_offset(&self, line: &Line, offset: usize, start: bool) -> usize {- let line_range = line.offset..=(line.offset + line.length);- assert!(line_range.contains(&offset));-- let mut text_index = offset - line.offset;- while text_index <= line.text.len() && !line.text.is_char_boundary(text_index) {- if start {- text_index -= 1;- } else {- text_index += 1;- }- }- let text = &line.text[..text_index.min(line.text.len())];- let text_width = self.line_visual_char_width(text).sum();- if text_index > line.text.len() {- // Spans extending past the end of the line are always rendered as- // one column past the end of the visible line.- //- // This doesn't necessarily correspond to a specific byte-offset,- // since a span extending past the end of the line could contain:- // - an actual \n character (1 byte)- // - a CRLF (2 bytes)- // - EOF (0 bytes)- text_width + 1- } else {- text_width- }- }-- /// Renders a line to the output formatter, replacing tabs with spaces.- fn render_line_text(&self, f: &mut impl fmt::Write, text: &str) -> fmt::Result {- for (c, width) in text.chars().zip(self.line_visual_char_width(text)) {- if c == '\t' {- for _ in 0..width {- f.write_char(' ')?;- }- } else {- f.write_char(c)?;- }- }- f.write_char('\n')?;- Ok(())- }-- fn render_single_line_highlights(- &self,- f: &mut impl fmt::Write,- line: &Line,- max_gutter: usize,- single_liners: &[&FancySpan],- all_highlights: &[FancySpan],- ) -> fmt::Result {- let mut underlines = String::new();- let mut highest = 0;-- let chars = &self.theme.characters;- let vbar_offsets: Vec<_> = single_liners- .iter()- .map(|hl| {- let byte_start = hl.offset();- let byte_end = hl.offset() + hl.len();- let start = self.visual_offset(line, byte_start, true).max(highest);- let end = if hl.len() == 0 {- start + 1- } else {- self.visual_offset(line, byte_end, false).max(start + 1)- };-- let vbar_offset = (start + end) / 2;- let num_left = vbar_offset - start;- let num_right = end - vbar_offset - 1;- underlines.push_str(- &format!(- "{:width$}{}{}{}",- "",- chars.underline.to_string().repeat(num_left),- if hl.len() == 0 {- chars.uarrow- } else if hl.label().is_some() {- chars.underbar- } else {- chars.underline- },- chars.underline.to_string().repeat(num_right),- width = start.saturating_sub(highest),- )- .style(hl.style)- .to_string(),- );- highest = std::cmp::max(highest, end);-- (hl, vbar_offset)- })- .collect();- writeln!(f, "{underlines}")?;-- for hl in single_liners.iter().rev() {- if let Some(label) = hl.label_parts() {- if label.len() == 1 {- self.write_label_text(- f,- line,- max_gutter,- all_highlights,- chars,- &vbar_offsets,- hl,- &label[0],- LabelRenderMode::SingleLine,- )?;- } else {- let mut first = true;- for label_line in &label {- self.write_label_text(- f,- line,- max_gutter,- all_highlights,- chars,- &vbar_offsets,- hl,- label_line,- if first {- LabelRenderMode::MultiLineFirst- } else {- LabelRenderMode::MultiLineRest- },- )?;- first = false;- }- }- }- }- Ok(())- }-- // I know it's not good practice, but making this a function makes a lot of- // sense and making a struct for this does not...- #[allow(clippy::too_many_arguments)]- fn write_label_text(- &self,- f: &mut impl fmt::Write,- line: &Line,- max_gutter: usize,- all_highlights: &[FancySpan],- chars: &ThemeCharacters,- vbar_offsets: &[(&&FancySpan, usize)],- hl: &&FancySpan,- label: &str,- render_mode: LabelRenderMode,- ) -> fmt::Result {- self.render_highlight_gutter(- f,- max_gutter,- line,- all_highlights,- LabelRenderMode::SingleLine,- )?;- let mut curr_offset = 1usize;- for (offset_hl, vbar_offset) in vbar_offsets {- while curr_offset < *vbar_offset + 1 {- write!(f, " ")?;- curr_offset += 1;- }- if *offset_hl != hl {- write!(f, "{}", chars.vbar.to_string().style(offset_hl.style))?;- curr_offset += 1;- } else {- let lines = match render_mode {- LabelRenderMode::SingleLine => format!(- "{}{} {}",- chars.lbot,- chars.hbar.to_string().repeat(2),- label,- ),- LabelRenderMode::MultiLineFirst => {- format!("{}{}{} {}", chars.lbot, chars.hbar, chars.rcross, label,)- }- LabelRenderMode::MultiLineRest => {- format!(" {} {}", chars.vbar, label,)- }- };- writeln!(f, "{}", lines.style(hl.style))?;- break;- }- }- Ok(())- }-- fn render_multi_line_end_single(- &self,- f: &mut impl fmt::Write,- label: &str,- style: Style,- render_mode: LabelRenderMode,- ) -> fmt::Result {- match render_mode {- LabelRenderMode::SingleLine => {- writeln!(f, "{} {}", self.theme.characters.hbar.style(style), label)?;- }- LabelRenderMode::MultiLineFirst => {- writeln!(f, "{} {}", self.theme.characters.rcross.style(style), label)?;- }- LabelRenderMode::MultiLineRest => {- writeln!(f, "{} {}", self.theme.characters.vbar.style(style), label)?;- }- }-- Ok(())- }-- fn get_lines<'a>(- &'a self,- source: &'a dyn SourceCode,- context_span: &'a SourceSpan,- ) -> Result<Vec<Line>, fmt::Error> {- let context_data = source- .read_span(context_span, self.context_lines, self.context_lines)- .map_err(|_| fmt::Error)?;- let context = String::from_utf8_lossy(context_data.data());- let mut column = context_data.column();- let mut offset = context_data.span().offset();- let mut line_offset = offset;- let mut line_str = String::with_capacity(context.len());- let mut lines = Vec::with_capacity(1);- let mut iter = context.chars().peekable();- while let Some(ch) = iter.next() {- offset += ch.len_utf8();- match ch {- '\r' if iter.next_if_eq(&'\n').is_some() => {- offset += 1;- column = 0;- }- '\r' => {- line_str.push(ch);- column += 1;- }- '\n' => {- column = 0;- }- _ => {- line_str.push(ch);- column += 1;- }- }-- if column == 0 || iter.peek().is_none() {- lines.push(Line {- offset: line_offset,- length: offset - line_offset,- text: line_str.clone(),- });- line_str.clear();- line_offset = offset;- }- }- Ok(lines)- }-}--impl ReportHandler for SwcReportHandler {- fn debug(&self, diagnostic: &dyn Diagnostic, f: &mut fmt::Formatter<'_>) -> fmt::Result {- if f.alternate() {- return fmt::Debug::fmt(diagnostic, f);- }-- self.render_report(f, diagnostic)- }-}--/*-Support types-*/--#[derive(PartialEq, Debug)]-enum LabelRenderMode {- /// we're rendering a single line label (or not rendering in any special- /// way)- SingleLine,- /// we're rendering a multiline label- MultiLineFirst,- /// we're rendering the rest of a multiline label- MultiLineRest,-}--#[derive(Debug)]-struct Line {- offset: usize,- length: usize,- text: String,-}--impl Line {- fn span_line_only(&self, span: &FancySpan) -> bool {- span.offset() >= self.offset && span.offset() + span.len() <= self.offset + self.length- }-- /// Returns whether `span` should be visible on this line, either in the- /// gutter or under the text on this line- fn span_applies(&self, span: &FancySpan) -> bool {- let spanlen = if span.len() == 0 { 1 } else { span.len() };- // Span starts in this line-- (span.offset() >= self.offset && span.offset() < self.offset + self.length)- // Span passes through this line- || (span.offset() < self.offset && span.offset() + spanlen > self.offset + self.length) //todo- // Span ends on this line- || (span.offset() + spanlen > self.offset && span.offset() + spanlen <= self.offset + self.length)- }-- /// Returns whether `span` should be visible on this line in the gutter (so- /// this excludes spans that are only visible on this line and do not- /// span multiple lines)- fn span_applies_gutter(&self, span: &FancySpan) -> bool {- let spanlen = if span.len() == 0 { 1 } else { span.len() };- // Span starts in this line- self.span_applies(span)- && !(- // as long as it doesn't start *and* end on this line- (span.offset() >= self.offset && span.offset() < self.offset + self.length)- && (span.offset() + spanlen > self.offset- && span.offset() + spanlen <= self.offset + self.length)- )- }-- // A 'flyby' is a multi-line span that technically covers this line, but- // does not begin or end within the line itself. This method is used to- // calculate gutters.- fn span_flyby(&self, span: &FancySpan) -> bool {- // The span itself starts before this line's starting offset (so, in a- // prev line).- span.offset() < self.offset- // ...and it stops after this line's end.- && span.offset() + span.len() > self.offset + self.length- }-- // Does this line contain the *beginning* of this multiline span?- // This assumes self.span_applies() is true already.- fn span_starts(&self, span: &FancySpan) -> bool {- span.offset() >= self.offset- }-- // Does this line contain the *end* of this multiline span?- // This assumes self.span_applies() is true already.- fn span_ends(&self, span: &FancySpan) -> bool {- span.offset() + span.len() >= self.offset- && span.offset() + span.len() <= self.offset + self.length- }-}--#[derive(Debug, Clone)]-struct FancySpan {- /// this is deliberately an option of a vec because I wanted to be very- /// explicit that there can also be *no* label. If there is a label, it- /// can have multiple lines which is what the vec is for.- label: Option<Vec<String>>,- span: SourceSpan,- style: Style,-}--impl PartialEq for FancySpan {- fn eq(&self, other: &Self) -> bool {- self.label == other.label && self.span == other.span- }-}--fn split_label(v: String) -> Vec<String> {- v.split('\n').map(|i| i.to_string()).collect()-}--impl FancySpan {- fn new(label: Option<String>, span: SourceSpan, style: Style) -> Self {- FancySpan {- label: label.map(split_label),- span,- style,- }- }-- fn style(&self) -> Style {- self.style- }-- fn label(&self) -> Option<String> {- self.label- .as_ref()- .map(|l| l.join("\n").style(self.style()).to_string())- }-- fn label_parts(&self) -> Option<Vec<String>> {- self.label.as_ref().map(|l| {- l.iter()- .map(|i| i.style(self.style()).to_string())- .collect()- })- }-- fn offset(&self) -> usize {- self.span.offset()- }-- fn len(&self) -> usize {- self.span.len()- }-}
deps/swc/bindings/binding_nodejs_support_wasm/src/lib.rs
@@ -1,345 +0,0 @@-use std::{- iter::once,- mem::take,- sync::{Arc, Mutex},-};--use anyhow::Error;-use error_reporter::SwcReportHandler;-use js_sys::Uint8Array;-use miette::{GraphicalTheme, LabeledSpan, ThemeCharacters, ThemeStyles};-use serde::Serialize;-use swc_common::{- errors::{DiagnosticBuilder, DiagnosticId, Emitter, Handler, HANDLER},- sync::Lrc,- SourceMap, Span, GLOBALS,-};-use swc_error_reporters::{convert_span, to_pretty_source_code};-use swc_ts_fast_strip::{Options, TransformOutput};-use wasm_bindgen::prelude::*;-use wasm_bindgen_futures::{future_to_promise, js_sys::Promise};--mod error_reporter;-mod nodejs;--/// Custom interface definitions for `@swc/nodejs-support-wasm`.-#[wasm_bindgen(typescript_custom_section)]-const INTERFACE_DEFINITIONS: &'static str = r#"-export declare function transform(src: string | Uint8Array, opts?: Options): Promise<TransformOutput>;-export declare function transformSync(src: string | Uint8Array, opts?: Options): TransformOutput;-export declare function transformModuleSyntax(src: string | Uint8Array): ModuleSyntaxTransformOutput;-export declare function getFirstExpression(src: string | Uint8Array, startColumn: number): string;-export declare function isValidSyntax(src: string | Uint8Array): boolean;-export declare function isRecoverableError(src: string | Uint8Array): boolean;-export declare function tokenize(src: string | Uint8Array): Token[];-export declare function findTopLevelAwaits(src: string | Uint8Array): TopLevelAwaitLocation[];-export type { Options, TransformOutput };--export type TokenKind = "keyword" | "identifier" | "number" | "bigint" | "string" | "template" | "regexp" | "comment" | "punctuator";--export interface Token {- kind: TokenKind;- /** Zero-based UTF-16 offset, inclusive. */- start: number;- /** Zero-based UTF-16 offset, exclusive. */- end: number;-}--export interface TopLevelAwaitLocation {- /** One-based line number. */- line: number;- /** Zero-based UTF-16 column. */- column: number;-}--export interface ModuleSyntaxTransformOutput {- code: string;- hadModuleSyntax: boolean;-}-"#;--#[wasm_bindgen(skip_typescript)]-pub fn transform(input: JsValue, options: JsValue) -> Promise {- future_to_promise(async move { transform_sync(input, options) })-}--#[wasm_bindgen(js_name = "transformSync", skip_typescript)]-pub fn transform_sync(input: JsValue, options: JsValue) -> Result<JsValue, JsValue> {- let options: Options = if options.is_falsy() {- Default::default()- } else {- serde_wasm_bindgen::from_value(options)?- };-- let input = coerce_input(input)?;-- let result = GLOBALS.set(&Default::default(), || operate(input, options));-- match result {- Ok(v) => Ok(serde_wasm_bindgen::to_value(&v)?),- Err(errors) => Err(serde_wasm_bindgen::to_value(&errors[0])?),- }-}--#[wasm_bindgen(js_name = "transformModuleSyntax", skip_typescript)]-pub fn transform_module_syntax(input: JsValue) -> Result<JsValue, JsValue> {- let input = coerce_input(input)?;- let output = nodejs::transform_module_syntax(input);-- Ok(serde_wasm_bindgen::to_value(&output)?)-}--#[wasm_bindgen(js_name = "getFirstExpression", skip_typescript)]-pub fn get_first_expression(input: JsValue, start_column: u32) -> Result<String, JsValue> {- let input = coerce_input(input)?;-- Ok(nodejs::get_first_expression(input, start_column))-}--#[wasm_bindgen(js_name = "isValidSyntax", skip_typescript)]-pub fn is_valid_syntax(input: JsValue) -> Result<bool, JsValue> {- let input = coerce_input(input)?;-- Ok(nodejs::is_valid_syntax(input))-}--#[wasm_bindgen(js_name = "isRecoverableError", skip_typescript)]-pub fn is_recoverable_error(input: JsValue) -> Result<bool, JsValue> {- let input = coerce_input(input)?;-- Ok(nodejs::is_recoverable_error(input))-}--/// Returns compact tokens for syntax highlighting, including a partial prefix-/// when the input is incomplete or invalid.-#[wasm_bindgen(skip_typescript)]-pub fn tokenize(input: JsValue) -> Result<JsValue, JsValue> {- let input = coerce_input(input)?;- Ok(serde_wasm_bindgen::to_value(&nodejs::tokenize(&input))?)-}--/// Returns module-scope await locations in source order using Acorn-/// coordinates.-#[wasm_bindgen(js_name = "findTopLevelAwaits", skip_typescript)]-pub fn find_top_level_awaits(input: JsValue) -> Result<JsValue, JsValue> {- let input = coerce_input(input)?;- Ok(serde_wasm_bindgen::to_value(- &nodejs::find_top_level_awaits(&input),- )?)-}--fn coerce_input(input: JsValue) -> Result<String, JsValue> {- match input.as_string() {- Some(input) => Ok(input),- None => {- if input.is_instance_of::<Uint8Array>() {- let input = input.unchecked_into::<Uint8Array>();-- String::from_utf8(input.to_vec())- .map_err(|_| JsValue::from_str("Input Uint8Array is not valid utf-8"))- } else {- Err(JsValue::from_str("Input is not a string or Uint8Array"))- }- }- }-}--fn operate(input: String, options: Options) -> Result<TransformOutput, Vec<JsonDiagnostic>> {- let cm = Lrc::new(SourceMap::default());-- try_with_json_handler(cm.clone(), |handler| {- swc_ts_fast_strip::operate(&cm, handler, input, options).map_err(anyhow::Error::new)- })-}--#[derive(Clone)]-struct JsonErrorWriter {- errors: Arc<Mutex<Vec<JsonDiagnostic>>>,- /// Successful transforms never need the graphical diagnostic reporter.- reporter: Option<SwcReportHandler>,- cm: Lrc<SourceMap>,-}--fn try_with_json_handler<F, Ret>(cm: Lrc<SourceMap>, op: F) -> Result<Ret, Vec<JsonDiagnostic>>-where- F: FnOnce(&Handler) -> Result<Ret, Error>,-{- let wr = JsonErrorWriter {- errors: Default::default(),- reporter: None,- cm,- };- let emitter: Box<dyn Emitter> = Box::new(wr.clone());-- let handler = Handler::with_emitter(true, false, emitter);-- let ret = HANDLER.set(&handler, || op(&handler));-- if handler.has_errors() {- let mut lock = wr.errors.lock().unwrap();- let error = take(&mut *lock);-- Err(error)- } else {- Ok(ret.expect("it should not fail without emitting errors to handler"))- }-}--impl Emitter for JsonErrorWriter {- fn emit(&mut self, db: &mut DiagnosticBuilder) {- let d = &**db;-- let snippet = d.span.primary_span().and_then(|span| {- let mut snippet = String::new();- let reporter = self.reporter.get_or_insert_with(json_reporter);- match reporter.render_report(- &mut snippet,- &Snippet {- source_code: &to_pretty_source_code(&self.cm, true),- span,- },- ) {- Ok(()) => Some(snippet),- Err(_) => None,- }- });-- let children = d- .children- .iter()- .map(|d| todo!("json subdiagnostic: {d:?}"))- .collect::<Vec<_>>();-- let error_code = match &d.code {- Some(DiagnosticId::Error(s)) => Some(&**s),- Some(DiagnosticId::Lint(s)) => Some(&**s),- None => None,- };-- let start = d- .span- .primary_span()- .and_then(|span| self.cm.try_lookup_char_pos(span.lo()).ok());-- let end = d- .span- .primary_span()- .and_then(|span| self.cm.try_lookup_char_pos(span.hi()).ok());-- let filename = start.as_ref().map(|loc| loc.file.name.to_string());-- let error = JsonDiagnostic {- code: error_code.map(|s| s.to_string()),- message: d.message[0].0.to_string(),- snippet,- filename,- start_line: start.as_ref().map(|loc| loc.line),- start_column: start.as_ref().map(|loc| loc.col_display),- end_line: end.as_ref().map(|loc| loc.line),- end_column: end.as_ref().map(|loc| loc.col_display),- children,- };-- self.errors.lock().unwrap().push(error);- }-- fn take_diagnostics(&mut self) -> Vec<String> {- Default::default()- }-}--fn json_reporter() -> SwcReportHandler {- SwcReportHandler::default().with_theme(GraphicalTheme {- characters: ThemeCharacters {- hbar: ' ',- vbar: ' ',- xbar: ' ',- vbar_break: ' ',- ltop: ' ',- rtop: ' ',- mtop: ' ',- lbot: ' ',- rbot: ' ',- mbot: ' ',- error: "".into(),- warning: "".into(),- advice: "".into(),- ..ThemeCharacters::ascii()- },- styles: ThemeStyles::none(),- })-}--#[derive(Serialize)]-#[serde(rename_all = "camelCase")]-struct JsonDiagnostic {- /// Error code- #[serde(skip_serializing_if = "Option::is_none")]- code: Option<String>,- message: String,-- #[serde(skip_serializing_if = "Option::is_none")]- snippet: Option<String>,-- #[serde(skip_serializing_if = "Option::is_none")]- filename: Option<String>,-- #[serde(skip_serializing_if = "Option::is_none")]- start_line: Option<usize>,- #[serde(skip_serializing_if = "Option::is_none")]- start_column: Option<usize>,-- #[serde(skip_serializing_if = "Option::is_none")]- end_line: Option<usize>,- #[serde(skip_serializing_if = "Option::is_none")]- end_column: Option<usize>,-- #[serde(skip_serializing_if = "Vec::is_empty")]- children: Vec<JsonSubdiagnostic>,-}--#[derive(Serialize)]-#[serde(rename_all = "camelCase")]-struct JsonSubdiagnostic {- message: String,- #[serde(skip_serializing_if = "Option::is_none")]- snippet: Option<String>,- filename: String,- line: usize,-}--struct Snippet<'a> {- source_code: &'a dyn miette::SourceCode,- span: Span,-}--impl miette::Diagnostic for Snippet<'_> {- fn source_code(&self) -> Option<&dyn miette::SourceCode> {- if self.span.lo().is_dummy() || self.span.hi().is_dummy() {- return None;- }-- Some(self.source_code)- }-- fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {- Some(Box::new(once(LabeledSpan::new_with_span(- None,- convert_span(self.span),- ))))- }-}--impl std::error::Error for Snippet<'_> {}--impl std::fmt::Display for Snippet<'_> {- fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {- Ok(())- }-}--impl std::fmt::Debug for Snippet<'_> {- fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {- Ok(())- }-}
deps/swc/bindings/binding_nodejs_support_wasm/src/nodejs.rs
@@ -1,1369 +0,0 @@-//! Node.js-specific helpers for `@swc/nodejs-support-wasm`.-//!-//! These functions intentionally expose only the compact operations Node needs-//! from Rust and avoid serializing SWC ASTs into JavaScript.--use serde::Serialize;-use swc_common::{comments::SingleThreadedComments, sync::Lrc, FileName, SourceMap, Span, Spanned};-use swc_ecma_ast::{- CallExpr, Callee, Decl, DefaultDecl, EsVersion, ExportSpecifier, Expr, ImportDecl,- ImportSpecifier, MetaPropExpr, MetaPropKind, Module, ModuleDecl, ModuleExportName, ModuleItem,- NamedExport, ObjectLit,-};-use swc_ecma_parser::{- error::{Error as ParseError, SyntaxError},- lexer::Lexer,- unstable::{Token, TokenAndSpan},- EsSyntax, Parser, StringInput, Syntax, TsSyntax,-};-use swc_ecma_visit::{Visit, VisitWith};--mod awaits;-mod position;-mod tokenize;--#[cfg(test)]-mod fixtures;--pub use self::{awaits::find_top_level_awaits, tokenize::tokenize};--const DYNAMIC_IMPORT_NAME: &str = "__nodeREPLDynamicImport";--#[derive(Debug, Serialize)]-#[serde(rename_all = "camelCase")]-pub struct ModuleSyntaxTransformOutput {- pub code: String,- pub had_module_syntax: bool,-}--/// Rewrites Node REPL module syntax into script syntax.-///-/// The transform follows Node's edit-list approach: static imports become-/// awaited dynamic imports, dynamic `import(...)` is routed through Node's REPL-/// helper, and exports are unwrapped or dropped depending on whether they have-/// runtime declarations.-pub fn transform_module_syntax(code: String) -> ModuleSyntaxTransformOutput {- if !code.contains("import") && !code.contains("export") {- return ModuleSyntaxTransformOutput {- code,- had_module_syntax: false,- };- }-- let Some(module) = parse_module(&code) else {- return ModuleSyntaxTransformOutput {- code,- had_module_syntax: false,- };- };-- let (edits, hoisted) = {- let mut transform = ModuleSyntaxTransform::new(&code);-- for item in &module.body {- transform.collect_module_item_edit(item);- }-- let replaced_ranges = transform- .edits- .iter()- .map(|edit| (edit.start, edit.end))- .collect::<Vec<_>>();- module.visit_with(&mut ModuleFeatureCollector {- code: &code,- edits: &mut transform.edits,- replaced_ranges: &replaced_ranges,- });-- if transform.edits.is_empty() && transform.hoisted.is_empty() {- return ModuleSyntaxTransformOutput {- code,- had_module_syntax: false,- };- }-- (transform.edits, transform.hoisted)- };-- ModuleSyntaxTransformOutput {- code: prepend_hoisted_imports(apply_edits(code, edits), hoisted),- had_module_syntax: true,- }-}--/// Extracts the first source expression associated with a V8 error column.-///-/// `start_column` is interpreted as a JavaScript UTF-16 code-unit column to-/// match Node/V8 source locations. The implementation uses only lexer tokens-/// because the source line can be incomplete JavaScript.-pub fn get_first_expression(code: String, start_column: u32) -> String {- let start_byte = utf16_column_to_byte_pos(&code, start_column);- let mut last_token = None;- let mut potential_expression_start_token = None;- let mut first_member_access_name_token = None;- let mut pending_optional_chain_name_token = None;- let mut terminating_byte = None;- let mut paren_level = 0u32;- let mut member_bracket_depth = 0u32;-- for token in lex_tokens(&code) {- if token.token == Token::Eof {- break;- }-- let Some((token_start, token_end)) = span_range(token.span) else {- continue;- };- if token_start < start_byte {- if token.token == Token::Semi {- potential_expression_start_token = None;- first_member_access_name_token = None;- pending_optional_chain_name_token = None;- member_bracket_depth = 0;- last_token = None;- continue;- }-- if member_bracket_depth > 0 {- match token.token {- Token::LBracket => member_bracket_depth += 1,- Token::RBracket => {- member_bracket_depth = member_bracket_depth.saturating_sub(1);- }- _ => {}- }- last_token = Some(token);- continue;- }-- if is_member_identifier_token(&code, &token)- && first_member_access_name_token.is_none()- && potential_expression_start_token.is_none()- {- potential_expression_start_token = Some(token);- }-- if token.token == Token::QuestionMark- && last_token- .as_ref()- .is_some_and(|last: &TokenAndSpan| is_member_identifier_token(&code, last))- {- pending_optional_chain_name_token = last_token;- last_token = Some(token);- continue;- }-- if token.token == Token::Dot- && last_token- .as_ref()- .is_some_and(|last: &TokenAndSpan| last.token == Token::QuestionMark)- {- if let Some(name_token) = pending_optional_chain_name_token.take() {- first_member_access_name_token.get_or_insert(name_token);- last_token = Some(token);- continue;- }- } else if pending_optional_chain_name_token.take().is_some() {- first_member_access_name_token = None;- }-- if token.token == Token::LBracket- && last_token- .as_ref()- .is_some_and(|last: &TokenAndSpan| is_member_identifier_token(&code, last))- {- first_member_access_name_token- .get_or_insert(potential_expression_start_token.unwrap_or(last_token.unwrap()));- member_bracket_depth = 1;- last_token = Some(token);- continue;- }-- if is_member_access_token(token.token) {- if last_token- .as_ref()- .is_some_and(|last: &TokenAndSpan| is_member_identifier_token(&code, last))- {- first_member_access_name_token.get_or_insert(- potential_expression_start_token.unwrap_or(last_token.unwrap()),- );- last_token = Some(token);- continue;- }- } else if token.token != Token::LParen && !is_member_name_token(&code, &token) {- potential_expression_start_token = None;- first_member_access_name_token = None;- }-- if token.token == Token::LParen && first_member_access_name_token.is_some() {- paren_level += 1;- last_token = Some(token);- continue;- }-- if token.token == Token::LParen- && last_token- .as_ref()- .is_some_and(|last: &TokenAndSpan| is_member_identifier_token(&code, last))- {- first_member_access_name_token- .get_or_insert(potential_expression_start_token.unwrap_or(last_token.unwrap()));- paren_level += 1;- last_token = Some(token);- continue;- }-- last_token = Some(token);- continue;- }-- if token.token == Token::LParen- && last_token- .as_ref()- .is_some_and(|last: &TokenAndSpan| is_member_identifier_token(&code, last))- {- first_member_access_name_token- .get_or_insert(potential_expression_start_token.unwrap_or(last_token.unwrap()));- }-- match token.token {- Token::LParen => {- paren_level += 1;- }- Token::RParen => {- paren_level = paren_level.saturating_sub(1);- if paren_level == 0 {- terminating_byte = Some(token_end);- break;- }- }- Token::Semi => {- terminating_byte = Some(token_start);- break;- }- _ => {}- }- }-- let start = first_member_access_name_token- .and_then(|token| span_range(token.span).map(|(start, _)| start))- .unwrap_or(start_byte);- let end = terminating_byte.unwrap_or(code.len());-- if start >= code.len()- || start > end- || !code.is_char_boundary(start)- || !code.is_char_boundary(end)- {- return String::new();- }-- code[start..end].to_string()-}--/// Returns true when the source parses as a program or expression.-pub fn is_valid_syntax(code: String) -> bool {- parses_as_program(&code) || parses_as_program(&format!("_={code}"))-}--/// Returns true for parser errors that should keep a Node REPL input open.-pub fn is_recoverable_error(code: String) -> bool {- match parse_program_result(&code) {- ParseOutcome::Valid => false,- ParseOutcome::Invalid(errors) => {- if errors- .iter()- .any(|error| is_recoverable_parse_error(&code, error))- {- return true;- }-- code.trim_start().starts_with('{') && is_recoverable_error(format!("({code}"))- }- }-}--struct ModuleSyntaxTransform<'a> {- code: &'a str,- edits: Vec<Edit>,- hoisted: Vec<String>,-}--impl<'a> ModuleSyntaxTransform<'a> {- fn new(code: &'a str) -> Self {- Self {- code,- edits: Vec::new(),- hoisted: Vec::new(),- }- }-- fn collect_module_item_edit(&mut self, item: &ModuleItem) {- let ModuleItem::ModuleDecl(decl) = item else {- return;- };-- match decl {- ModuleDecl::Import(import) => {- self.delete_span(import.span);- if !import.type_only {- if let Some(script) = self.import_decl_to_script(import) {- self.hoisted.push(script);- }- }- }- ModuleDecl::ExportDecl(export) => {- if is_type_only_decl(&export.decl) {- self.delete_span(export.span);- } else if let (Some((start, _)), Some((decl_start, _))) =- (span_range(export.span), span_range(export.decl.span()))- {- push_range_edit(&mut self.edits, self.code, start, decl_start, String::new());- }- }- ModuleDecl::ExportNamed(export) => {- self.delete_span(export.span);- if let Some(src) = &export.src {- if let Some(required_exports) = named_export_source_imports(export) {- self.hoist_validated_import(- &src.value.to_string_lossy(),- export.with.as_deref(),- &required_exports,- );- }- }- }- ModuleDecl::ExportDefaultDecl(export) => {- self.unwrap_default_declaration(export.span, export.decl.span(), &export.decl);- }- ModuleDecl::ExportDefaultExpr(export) => {- self.unwrap_default_expression(- export.span,- export.expr.span(),- matches!(&*export.expr, Expr::Object(..)),- );- }- ModuleDecl::ExportAll(export) => {- self.delete_span(export.span);- if !export.type_only {- self.hoist_import(&export.src.value.to_string_lossy(), export.with.as_deref());- }- }- ModuleDecl::TsImportEquals(import) => {- if import.is_type_only {- self.delete_span(import.span);- }- }- ModuleDecl::TsNamespaceExport(export) => {- self.delete_span(export.span);- }- ModuleDecl::TsExportAssignment(..) => {}- }- }-- fn delete_span(&mut self, span: Span) {- let replacement = if needs_statement_separator(self.code, span) {- ";"- } else {- ""- };- self.replace_span(span, replacement.to_string());- }-- fn replace_span(&mut self, span: Span, text: String) {- push_span_edit(&mut self.edits, self.code, span, text);- }-- fn hoist_import(&mut self, specifier: &str, with: Option<&ObjectLit>) {- self.hoisted.push(format!(- "await {};",- await_import(self.code, specifier, with, &[])- ));- }-- fn hoist_validated_import(- &mut self,- specifier: &str,- with: Option<&ObjectLit>,- required_exports: &[String],- ) {- self.hoisted.push(format!(- "await {};",- await_import(self.code, specifier, with, required_exports)- ));- }-- fn import_decl_to_script(&self, node: &ImportDecl) -> Option<String> {- let specifier = node.src.value.to_string_lossy();- let with = node.with.as_deref();-- if node.specifiers.is_empty() {- return Some(format!(- "await {};",- await_import(self.code, &specifier, with, &[])- ));- }-- let mut namespace_name = None;- let mut default_name = None;- let mut named = Vec::new();- let mut required_exports = Vec::new();-- for specifier in &node.specifiers {- match specifier {- ImportSpecifier::Namespace(specifier) => {- namespace_name = Some(specifier.local.sym.to_string());- }- ImportSpecifier::Default(specifier) => {- default_name = Some(specifier.local.sym.to_string());- push_required_export(&mut required_exports, "default".to_string());- }- ImportSpecifier::Named(specifier) => {- if specifier.is_type_only {- continue;- }-- let local = specifier.local.sym.to_string();- let imported = specifier- .imported- .as_ref()- .map(module_export_name)- .unwrap_or_else(|| local.clone());- push_required_export(&mut required_exports, imported.clone());- named.push((local, imported));- }- }- }-- if namespace_name.is_none() && default_name.is_none() && named.is_empty() {- return None;- }-- let import = await_import(self.code, &specifier, with, &required_exports);- if let Some(namespace_name) = namespace_name {- let mut out = format!("const {namespace_name} = await {import};");- if let Some(default_name) = default_name {- out.push_str(&format!(- " const {default_name} = {namespace_name}.default;"- ));- }- return Some(out);- }-- // Declare the user's bindings so subsequent REPL inputs can access them.- // Like Node's REPL transform, these capture the initial export values;- // namespace imports still refer to the live module namespace object.- let mut bindings = Vec::with_capacity(named.len() + usize::from(default_name.is_some()));- if let Some(default_name) = default_name {- bindings.push(format!("default: {default_name}"));- }- for (local, imported) in named {- // Quote arbitrary export names while keeping ordinary names readable.- let key = if is_identifier_name(&imported) {- imported- } else {- json_string(&imported)- };- bindings.push(format!("{key}: {local}"));- }- Some(format!(- "const {{ {} }} = await {import};",- bindings.join(", ")- ))- }-- fn unwrap_default_declaration(- &mut self,- export_span: Span,- decl_span: Span,- decl: &DefaultDecl,- ) {- if matches!(decl, DefaultDecl::TsInterfaceDecl(..)) {- self.delete_span(export_span);- return;- }-- let (Some((export_start, _)), Some((decl_start, decl_end))) =- (span_range(export_span), span_range(decl_span))- else {- return;- };-- push_range_edit(- &mut self.edits,- self.code,- export_start,- decl_start,- String::new(),- );-- if default_declaration_needs_parentheses(decl) {- push_range_edit(- &mut self.edits,- self.code,- decl_start,- decl_start,- "(".to_string(),- );- push_range_edit(- &mut self.edits,- self.code,- decl_end,- decl_end,- ");".to_string(),- );- }- }-- fn unwrap_default_expression(- &mut self,- export_span: Span,- expr_span: Span,- needs_parentheses: bool,- ) {- let (Some((export_start, export_end)), Some((expr_start, expr_end))) =- (span_range(export_span), span_range(expr_span))- else {- return;- };-- push_range_edit(- &mut self.edits,- self.code,- export_start,- expr_start,- String::new(),- );-- if needs_parentheses {- push_range_edit(- &mut self.edits,- self.code,- expr_start,- expr_start,- "(".to_string(),- );- }-- let has_separator = self- .code- .get(expr_end..export_end)- .is_some_and(|tail| tail.contains(';'));- if needs_parentheses || !has_separator {- let mut suffix = String::new();- if needs_parentheses {- suffix.push(')');- }- if !has_separator {- suffix.push(';');- }- push_range_edit(&mut self.edits, self.code, expr_end, expr_end, suffix);- }- }-}--fn await_import(- code: &str,- specifier: &str,- with: Option<&ObjectLit>,- required_exports: &[String],-) -> String {- let mut out = format!("{DYNAMIC_IMPORT_NAME}({}", json_string(specifier));- if let Some(options) = import_options_to_dynamic_options(code, with) {- out.push_str(", ");- out.push_str(&options);- }- out.push(')');- if !required_exports.is_empty() {- out.push_str(".then((m) => { ");- for export_name in required_exports {- out.push_str("if (!(");- out.push_str(&json_string(export_name));- out.push_str(" in m)) throw new SyntaxError(");- out.push_str(&json_string(&format!(- "The requested module '{specifier}' does not provide an export named \- '{export_name}'"- )));- out.push_str("); ");- }- out.push_str("return m; })");- }- out-}--fn import_options_to_dynamic_options(code: &str, with: Option<&ObjectLit>) -> Option<String> {- let attributes = slice_span(code, with?.span)?;- Some(format!("{{ with: {attributes} }}"))-}--fn default_declaration_needs_parentheses(decl: &DefaultDecl) -> bool {- matches!(decl, DefaultDecl::Class(class) if class.ident.is_none())- || matches!(decl, DefaultDecl::Fn(function) if function.ident.is_none())-}--fn is_type_only_decl(decl: &Decl) -> bool {- matches!(decl, Decl::TsInterface(..) | Decl::TsTypeAlias(..))-}--fn named_export_source_imports(export: &NamedExport) -> Option<Vec<String>> {- if export.type_only {- return None;- }-- let mut loads_source = export.specifiers.is_empty();- let mut required_exports = Vec::new();-- for specifier in &export.specifiers {- match specifier {- ExportSpecifier::Named(named) => {- if named.is_type_only {- continue;- }-- loads_source = true;- push_required_export(&mut required_exports, module_export_name(&named.orig));- }- ExportSpecifier::Default(..) => {- loads_source = true;- push_required_export(&mut required_exports, "default".to_string());- }- ExportSpecifier::Namespace(..) => {- loads_source = true;- }- }- }-- loads_source.then_some(required_exports)-}--fn push_required_export(required_exports: &mut Vec<String>, export_name: String) {- if !required_exports- .iter()- .any(|required| required == &export_name)- {- required_exports.push(export_name);- }-}--fn module_export_name(name: &ModuleExportName) -> String {- match name {- ModuleExportName::Ident(ident) => ident.sym.to_string(),- ModuleExportName::Str(string) => string.value.to_string_lossy().into_owned(),- }-}--#[derive(Clone, Debug, Eq, PartialEq)]-struct Edit {- start: usize,- end: usize,- text: String,-}--fn push_span_edit(edits: &mut Vec<Edit>, code: &str, span: Span, text: String) {- if let Some((start, end)) = span_range(span) {- push_range_edit(edits, code, start, end, text);- }-}--fn push_range_edit(edits: &mut Vec<Edit>, code: &str, start: usize, end: usize, text: String) {- if start <= end && end <= code.len() {- edits.push(Edit { start, end, text });- }-}--fn apply_edits(mut code: String, mut edits: Vec<Edit>) -> String {- edits.sort_by(|a, b| b.start.cmp(&a.start).then_with(|| b.end.cmp(&a.end)));-- for edit in edits {- if edit.start <= edit.end- && edit.end <= code.len()- && code.is_char_boundary(edit.start)- && code.is_char_boundary(edit.end)- {- code.replace_range(edit.start..edit.end, &edit.text);- }- }-- code-}--fn needs_statement_separator(code: &str, span: Span) -> bool {- let Some((start, end)) = span_range(span) else {- return false;- };-- code[..start].chars().any(|ch| !ch.is_whitespace())- && code[end..].chars().any(|ch| !ch.is_whitespace())-}--fn prepend_hoisted_imports(code: String, hoisted: Vec<String>) -> String {- if hoisted.is_empty() {- return code;- }-- let imports = hoisted.join("\n");- if code.starts_with("#!") {- if let Some(line_end) = code.find('\n') {- let insert_at = line_end + 1;- let (hashbang, rest) = code.split_at(insert_at);- if rest.is_empty() || rest.starts_with('\n') {- format!("{hashbang}{imports}{rest}")- } else {- format!("{hashbang}{imports}\n{rest}")- }- } else {- format!("{code}\n{imports}")- }- } else if code.is_empty() {- imports- } else if code.starts_with('\n') || code.starts_with("\r\n") {- format!("{imports}{code}")- } else {- format!("{imports}\n{code}")- }-}--struct ModuleFeatureCollector<'a, 'b> {- code: &'a str,- edits: &'b mut Vec<Edit>,- replaced_ranges: &'b [(usize, usize)],-}--impl Visit for ModuleFeatureCollector<'_, '_> {- fn visit_call_expr(&mut self, node: &CallExpr) {- if matches!(node.callee, Callee::Import(..)) {- if let Some((start, end)) = span_range(node.span) {- if !range_is_replaced(self.replaced_ranges, start, end) {- let original = &self.code[start..end];- if original.starts_with("import") {- self.edits.push(Edit {- start,- end: start + "import".len(),- text: DYNAMIC_IMPORT_NAME.to_string(),- });- }- }- }- }-- node.visit_children_with(self);- }-- fn visit_meta_prop_expr(&mut self, node: &MetaPropExpr) {- if node.kind == MetaPropKind::ImportMeta {- if let Some((start, end)) = span_range(node.span) {- if !range_is_replaced(self.replaced_ranges, start, end) {- self.edits.push(Edit {- start,- end,- text: import_meta_error_expression(),- });- }- }- }- }-}--fn range_is_replaced(replaced_ranges: &[(usize, usize)], start: usize, end: usize) -> bool {- replaced_ranges- .iter()- .any(|(replace_start, replace_end)| start >= *replace_start && end <= *replace_end)-}--fn import_meta_error_expression() -> String {- "(() => { throw new SyntaxError(\"Cannot use import.meta outside a module\"); })()".to_string()-}--fn is_identifier_name(value: &str) -> bool {- let mut chars = value.chars();- let Some(first) = chars.next() else {- return false;- };-- if !(first == '_' || first == '$' || first.is_ascii_alphabetic()) {- return false;- }-- chars.all(|ch| ch == '_' || ch == '$' || ch.is_ascii_alphanumeric())-}--enum ParseOutcome {- Valid,- Invalid(Vec<ParseError>),-}--fn parse_program_result(code: &str) -> ParseOutcome {- let es = parse_program_result_with_syntax(code, Syntax::Es(es_syntax()));- if matches!(es, ParseOutcome::Valid) {- return es;- }-- parse_program_result_with_syntax(code, Syntax::Typescript(ts_syntax()))-}--fn parse_program_result_with_syntax(code: &str, syntax: Syntax) -> ParseOutcome {- let cm = Lrc::new(SourceMap::default());- let fm = cm.new_source_file(FileName::Anon.into(), code.to_string());- let comments = SingleThreadedComments::default();- let lexer = Lexer::new(- syntax,- EsVersion::latest(),- StringInput::from(&*fm),- Some(&comments),- );- let mut parser = Parser::new_from(lexer);-- match parser.parse_program() {- Ok(..) => {- let errors = parser.take_errors();- if errors.is_empty() {- ParseOutcome::Valid- } else {- ParseOutcome::Invalid(errors)- }- }- Err(error) => {- let mut errors = vec![error];- errors.extend(parser.take_errors());- ParseOutcome::Invalid(errors)- }- }-}--fn parse_module(code: &str) -> Option<Module> {- parse_module_with_syntax(code, Syntax::Es(es_syntax()))- .or_else(|| parse_module_with_syntax(code, Syntax::Typescript(ts_syntax())))-}--fn parse_module_with_syntax(code: &str, syntax: Syntax) -> Option<Module> {- let cm = Lrc::new(SourceMap::default());- let fm = cm.new_source_file(FileName::Anon.into(), code.to_string());- let comments = SingleThreadedComments::default();- let lexer = Lexer::new(- syntax,- EsVersion::latest(),- StringInput::from(&*fm),- Some(&comments),- );- let mut parser = Parser::new_from(lexer);- let module = parser.parse_module().ok()?;-- if parser.take_errors().is_empty() {- Some(module)- } else {- None- }-}--fn parses_as_program(code: &str) -> bool {- matches!(parse_program_result(code), ParseOutcome::Valid)-}--fn lex_tokens(code: &str) -> Vec<TokenAndSpan> {- let cm = Lrc::new(SourceMap::default());- let fm = cm.new_source_file(FileName::Anon.into(), code.to_string());- let comments = SingleThreadedComments::default();- Lexer::new(- Syntax::Es(es_syntax()),- EsVersion::latest(),- StringInput::from(&*fm),- Some(&comments),- )- .collect()-}--fn es_syntax() -> EsSyntax {- EsSyntax {- auto_accessors: true,- explicit_resource_management: true,- import_attributes: true,- ..Default::default()- }-}--fn ts_syntax() -> TsSyntax {- TsSyntax {- decorators: true,- ..Default::default()- }-}--fn is_recoverable_parse_error(code: &str, error: &ParseError) -> bool {- match error.kind() {- SyntaxError::Eof | SyntaxError::UnterminatedTpl | SyntaxError::UnterminatedBlockComment => {- true- }- SyntaxError::UnterminatedStrLit => has_trailing_line_continuation(code),- SyntaxError::Expected(_, got) | SyntaxError::Unexpected { got, .. } => got == "<eof>",- _ => false,- }-}--fn has_trailing_line_continuation(code: &str) -> bool {- code.ends_with("\\\n")- || code.ends_with("\\\r")- || code.ends_with("\\\r\n")- || code.ends_with("\\\u{2028}")- || code.ends_with("\\\u{2029}")-}--fn is_member_access_token(token: Token) -> bool {- matches!(- token,- Token::Dot | Token::OptionalChain | Token::LBracket | Token::RBracket- )-}--fn is_member_name_token(code: &str, token: &TokenAndSpan) -> bool {- matches!(token.token, Token::Ident | Token::Str | Token::Num)- || token.token.is_known_ident()- || token_text_is_identifier(code, token)-}--fn is_member_identifier_token(code: &str, token: &TokenAndSpan) -> bool {- token.token == Token::Ident- || token.token.is_known_ident()- || token_text_is_identifier(code, token)-}--fn token_text_is_identifier(code: &str, token: &TokenAndSpan) -> bool {- slice_span(code, token.span).is_some_and(is_identifier_name)-}--fn slice_span(code: &str, span: Span) -> Option<&str> {- let (start, end) = span_range(span)?;- code.get(start..end)-}--fn span_range(span: Span) -> Option<(usize, usize)> {- if span.lo.is_dummy() || span.hi.is_dummy() {- return None;- }-- let start = span.lo.0.checked_sub(1)? as usize;- let end = span.hi.0.checked_sub(1)? as usize;- Some((start, end))-}--fn utf16_column_to_byte_pos(code: &str, column: u32) -> usize {- let mut utf16_pos = 0u32;-- for (byte_pos, ch) in code.char_indices() {- if utf16_pos >= column {- return byte_pos;- }-- utf16_pos += ch.len_utf16() as u32;- if utf16_pos > column {- return byte_pos;- }- }-- code.len()-}--fn json_string(value: &str) -> String {- serde_json::to_string(value).expect("serializing a string should not fail")-}--#[cfg(test)]-mod tests {- use super::*;-- fn validated_import(specifier: &str, required_exports: &[&str]) -> String {- validated_import_with_options(specifier, None, required_exports)- }-- fn validated_import_with_options(- specifier: &str,- options: Option<&str>,- required_exports: &[&str],- ) -> String {- let mut out = format!("{DYNAMIC_IMPORT_NAME}({}", json_string(specifier));- if let Some(options) = options {- out.push_str(", ");- out.push_str(options);- }- out.push(')');-- if !required_exports.is_empty() {- out.push_str(".then((m) => { ");- for export_name in required_exports {- out.push_str("if (!(");- out.push_str(&json_string(export_name));- out.push_str(" in m)) throw new SyntaxError(");- out.push_str(&json_string(&format!(- "The requested module '{specifier}' does not provide an export named \- '{export_name}'"- )));- out.push_str("); ");- }- out.push_str("return m; })");- }-- out- }-- #[test]- fn transform_import_forms() {- assert_eq!(- transform_module_syntax("import \"node:fs\";".into()).code,- "await __nodeREPLDynamicImport(\"node:fs\");"- );- assert_eq!(- transform_module_syntax("import fs from \"node:fs\";".into()).code,- format!(- "const {{ default: fs }} = await {};",- validated_import("node:fs", &["default"])- )- );- assert_eq!(- transform_module_syntax("import { readFile as rf } from \"node:fs\";".into()).code,- format!(- "const {{ readFile: rf }} = await {};",- validated_import("node:fs", &["readFile"])- )- );- assert_eq!(- transform_module_syntax("import { readFile as rf } from \"node:fs\";\nrf();".into())- .code,- format!(- "const {{ readFile: rf }} = await {};\nrf();",- validated_import("node:fs", &["readFile"])- )- );- assert_eq!(- transform_module_syntax("import def, * as ns from \"mod\";".into()).code,- format!(- "const ns = await {}; const def = ns.default;",- validated_import("mod", &["default"])- )- );- assert_eq!(- transform_module_syntax("import def, * as ns from \"mod\";\ndef; ns;".into()).code,- format!(- "const ns = await {}; const def = ns.default;\ndef; ns;",- validated_import("mod", &["default"])- )- );- assert_eq!(- transform_module_syntax("import { \"a-b\" as c } from \"mod\";".into()).code,- format!(- "const {{ \"a-b\": c }} = await {};",- validated_import("mod", &["a-b"])- )- );- assert_eq!(- transform_module_syntax("import { \"a-b\" as c } from \"mod\";\nc;".into()).code,- format!(- "const {{ \"a-b\": c }} = await {};\nc;",- validated_import("mod", &["a-b"])- )- );- assert_eq!(- transform_module_syntax(- "import data from \"./data.json\" with { type: \"json\" };".into()- )- .code,- format!(- "const {{ default: data }} = await {};",- validated_import_with_options(- "./data.json",- Some("{ with: { type: \"json\" } }"),- &["default"]- )- )- );- assert_eq!(- transform_module_syntax("console.log(typeof fs);\nimport fs from \"node:fs\";".into())- .code,- format!(- "const {{ default: fs }} = await {};\nconsole.log(typeof fs);\n",- validated_import("node:fs", &["default"])- )- );- }-- #[test]- fn transform_export_forms() {- assert_eq!(- transform_module_syntax("export const x = 1;".into()).code,- "const x = 1;"- );- assert_eq!(- transform_module_syntax("export default value;".into()).code,- "value;"- );- assert_eq!(- transform_module_syntax("export default foo; doSomething();".into()).code,- "foo; doSomething();"- );- assert_eq!(- transform_module_syntax("export default { ...config };".into()).code,- "({ ...config });"- );- assert_eq!(- transform_module_syntax("export default function () {}".into()).code,- "(function () {});"- );- assert_eq!(- transform_module_syntax(- "export default function f() { return import(\"node:fs\"); }".into()- )- .code,- "function f() { return __nodeREPLDynamicImport(\"node:fs\"); }"- );- assert_eq!(- transform_module_syntax(- "export default function () { return import(\"node:fs\"); }".into()- )- .code,- "(function () { return __nodeREPLDynamicImport(\"node:fs\"); });"- );- assert_eq!(- transform_module_syntax("export default class {}".into()).code,- "(class {});"- );- assert_eq!(transform_module_syntax("export { x };".into()).code, "");- assert_eq!(- transform_module_syntax("export * from \"mod\";".into()).code,- "await __nodeREPLDynamicImport(\"mod\");"- );- assert_eq!(- transform_module_syntax("console.log(1);\nexport { value } from \"mod\";".into()).code,- format!(- "await {};\nconsole.log(1);\n",- validated_import("mod", &["value"])- )- );- }-- #[test]- fn transform_preserves_missing_export_failures() {- let missing_default = transform_module_syntax("import missing from \"mod\";".into());- assert!(missing_default.code.contains("\"default\" in m"));- assert!(missing_default.code.contains("export named 'default'"));-- let missing_named = transform_module_syntax("import { missing, ok } from \"mod\";".into());- assert!(missing_named.code.contains("\"missing\" in m"));- assert!(missing_named.code.contains("\"ok\" in m"));-- let re_export = transform_module_syntax("export { missing } from \"mod\";".into());- assert!(re_export.code.contains("\"missing\" in m"));- }-- #[test]- fn transform_preserves_import_binding_semantics() {- assert_eq!(- transform_module_syntax("import { spec } from \"m\";\nimport(spec);".into()).code,- format!(- "const {{ spec: spec }} = await {};\n__nodeREPLDynamicImport(spec);",- validated_import("m", &["spec"])- )- );-- assert_eq!(- transform_module_syntax(- "import { x } from \"m\";\nfunction f(x) { return x; }\nx;".into()- )- .code,- format!(- "const {{ x: x }} = await {};\nfunction f(x) {{ return x; }}\nx;",- validated_import("m", &["x"])- )- );-- assert_eq!(- transform_module_syntax("import { x } from \"m\";\n{ const x = 1; x; }\nx;".into())- .code,- format!(- "const {{ x: x }} = await {};\n{{ const x = 1; x; }}\nx;",- validated_import("m", &["x"])- )- );-- assert_eq!(- transform_module_syntax("import { x } from \"m\";\ntry {} catch (x) { x; }\nx;".into())- .code,- format!(- "const {{ x: x }} = await {};\ntry {{}} catch (x) {{ x; }}\nx;",- validated_import("m", &["x"])- )- );-- assert_eq!(- transform_module_syntax(- "import { x } from \"m\";\nfunction f(){ if (ok) { var x = 1; } return x; }\nx;"- .into()- )- .code,- format!(- "const {{ x: x }} = await {};\nfunction f(){{ if (ok) {{ var x = 1; }} return x; \- }}\nx;",- validated_import("m", &["x"])- )- );-- assert_eq!(- transform_module_syntax("import { readFile } from \"node:fs\";\nreadFile = 1;".into())- .code,- format!(- "const {{ readFile: readFile }} = await {};\nreadFile = 1;",- validated_import("node:fs", &["readFile"])- )- );-- assert_eq!(- transform_module_syntax(- "import { readFile } from \"node:fs\";\n({ readFile } = obj);".into()- )- .code,- format!(- "const {{ readFile: readFile }} = await {};\n({{ readFile }} = obj);",- validated_import("node:fs", &["readFile"])- )- );-- assert_eq!(- transform_module_syntax("import { readFile } from \"node:fs\";\nreadFile++;".into())- .code,- format!(- "const {{ readFile: readFile }} = await {};\nreadFile++;",- validated_import("node:fs", &["readFile"])- )- );- }-- #[test]- fn transform_dynamic_import_and_noops() {- assert_eq!(- transform_module_syntax("const mod = import(\"node:fs\");".into()).code,- "const mod = __nodeREPLDynamicImport(\"node:fs\");"- );-- let no_module_syntax = transform_module_syntax("const importName = \"import\";".into());- assert!(!no_module_syntax.had_module_syntax);- assert_eq!(no_module_syntax.code, "const importName = \"import\";");-- let incomplete = transform_module_syntax("import {".into());- assert!(!incomplete.had_module_syntax);- assert_eq!(incomplete.code, "import {");-- let import_meta = transform_module_syntax("console.log(import.meta.url);".into());- assert!(import_meta.had_module_syntax);- assert!(import_meta- .code- .contains("Cannot use import.meta outside a module"));- }-- #[test]- fn transform_preserves_statement_boundaries() {- let hashbang = transform_module_syntax(- "#!/usr/bin/env node\nimport fs from \"node:fs\";\nfs.readFile;".into(),- )- .code;- assert!(hashbang.starts_with("#!/usr/bin/env node\nconst { default: fs } = await "));- assert!(hashbang.contains("\nfs.readFile;"));-- assert_eq!(- transform_module_syntax("foo()\nimport \"x\";\n[1].forEach(bar)".into()).code,- "await __nodeREPLDynamicImport(\"x\");\nfoo()\n;\n[1].forEach(bar)"- );- }-- #[test]- fn transform_typescript_module_syntax() {- assert_eq!(- transform_module_syntax("import fs from \"node:fs\";\nconst x: number = 1;".into())- .code,- format!(- "const {{ default: fs }} = await {};\nconst x: number = 1;",- validated_import("node:fs", &["default"])- )- );-- let type_only = transform_module_syntax(- "import type { Foo } from \"types\";\nexport type { Foo };\nconst x: number = 1;"- .into(),- );- assert!(type_only.had_module_syntax);- assert!(!type_only.code.contains("__nodeREPLDynamicImport"));- assert!(!type_only.code.contains("import type"));- assert!(!type_only.code.contains("export type"));-- let re_export_type =- transform_module_syntax("export { type Foo } from \"types\";\nconst x = 1;".into());- assert!(!re_export_type.code.contains("__nodeREPLDynamicImport"));- assert_eq!(re_export_type.code, "\nconst x = 1;");-- let import_equals =- transform_module_syntax("import type Foo = require(\"foo\");\nconst x = 1;".into());- assert!(import_equals.had_module_syntax);- assert_eq!(import_equals.code, "\nconst x = 1;");-- let namespace_export =- transform_module_syntax("export as namespace Foo;\nconst x = 1;".into());- assert!(namespace_export.had_module_syntax);- assert_eq!(namespace_export.code, "\nconst x = 1;");- }-- #[test]- fn first_expression_from_error_column() {- assert_eq!(- get_first_expression("assert(value)".into(), 6),- "assert(value)"- );- assert_eq!(get_first_expression("ok(value)".into(), 3), "ok(value)");- assert_eq!(- get_first_expression("assert.ok(value)".into(), 9),- "assert.ok(value)"- );- assert_eq!(- get_first_expression("assert['ok'](value)".into(), 12),- "assert['ok'](value)"- );- assert_eq!(- get_first_expression("assert[method + suffix](value)".into(), 23),- "assert[method + suffix](value)"- );- assert_eq!(- get_first_expression("assert.deepEqual(foo(), 1)".into(), 17),- "assert.deepEqual(foo(), 1)"- );- assert_eq!(- get_first_expression("assert?.ok(value)".into(), 10),- "assert?.ok(value)"- );- assert_eq!(- get_first_expression("a(); assert.ok(value); b()".into(), 13),- "assert.ok(value)"- );- assert_eq!(- get_first_expression("const 🍣 = 1; assert.ok(🍣)".into(), 23),- "assert.ok(🍣)"- );- }-- #[test]- fn valid_syntax_checks() {- assert!(is_valid_syntax("await foo".into()));- assert!(is_valid_syntax("{ value: 1 }".into()));- assert!(is_valid_syntax("foo + bar".into()));- assert!(is_valid_syntax("const x: number = 1".into()));- assert!(!is_valid_syntax("function foo(".into()));- }-- #[test]- fn recoverable_syntax_checks() {- assert!(is_recoverable_error("function foo() {".into()));- assert!(is_recoverable_error("{".into()));- assert!(is_recoverable_error("`template".into()));- assert!(is_recoverable_error("/* comment".into()));- assert!(is_recoverable_error("\"continued\\\n".into()));- assert!(!is_recoverable_error("const x: number = 1".into()));- assert!(!is_recoverable_error("{ value: 1 }".into()));- assert!(!is_recoverable_error("{}".into()));- assert!(!is_recoverable_error("2e".into()));- assert!(!is_recoverable_error("\"unterminated".into()));- }-}