Skip to main content

next_napi_bindings/
code_frame.rs

1use napi::bindgen_prelude::*;
2use napi_derive::napi;
3use next_code_frame::{CodeFrameLocation, CodeFrameOptions, Language, Location, render_code_frame};
4
5/// Default max width when the caller doesn't provide one (e.g., no terminal).
6const DEFAULT_MAX_WIDTH: u32 = 100;
7
8#[napi(object)]
9pub struct NapiLocation {
10    pub line: u32,
11    pub column: Option<u32>,
12}
13
14impl From<NapiLocation> for Location {
15    fn from(loc: NapiLocation) -> Self {
16        Location {
17            line: loc.line as usize,
18            column: loc.column.map(|c| c as usize),
19        }
20    }
21}
22
23#[napi(object)]
24pub struct NapiCodeFrameLocation {
25    pub start: NapiLocation,
26    pub end: Option<NapiLocation>,
27}
28
29impl From<NapiCodeFrameLocation> for CodeFrameLocation {
30    fn from(loc: NapiCodeFrameLocation) -> Self {
31        CodeFrameLocation {
32            start: loc.start.into(),
33            end: loc.end.map(Into::into),
34        }
35    }
36}
37
38#[napi(object)]
39#[derive(Default)]
40pub struct NapiCodeFrameOptions {
41    /// Number of lines to show above the error (default: 2)
42    pub lines_above: Option<u32>,
43    /// Number of lines to show below the error (default: 3)
44    pub lines_below: Option<u32>,
45    /// Maximum width of the output in columns (default: 100)
46    pub max_width: Option<u32>,
47    /// Whether to use ANSI colors (default: false)
48    pub color: Option<bool>,
49    /// Whether to highlight code syntax (default: follows color)
50    ///
51    /// This might be useful if syntax highlighting is very expensive or known to be useless for
52    /// this file.  The current syntax rules are optimized for javascript but should work well with
53    /// other C-like languages.
54    pub highlight_code: Option<bool>,
55    /// Optional message to display with the code frame
56    pub message: Option<String>,
57    /// Language hint for keyword highlighting: "javascript" (default) or "css"
58    pub language: Option<String>,
59}
60
61fn parse_language(s: &Option<String>) -> Language {
62    match s.as_deref() {
63        Some("css") => Language::Css,
64        _ => Language::JavaScript,
65    }
66}
67
68impl From<NapiCodeFrameOptions> for CodeFrameOptions {
69    fn from(opts: NapiCodeFrameOptions) -> Self {
70        CodeFrameOptions {
71            lines_above: opts.lines_above.unwrap_or(2) as usize,
72            lines_below: opts.lines_below.unwrap_or(3) as usize,
73            max_width: opts.max_width.unwrap_or(DEFAULT_MAX_WIDTH) as usize,
74            color: opts.color.unwrap_or(false),
75            highlight_code: opts.highlight_code.unwrap_or(opts.color.unwrap_or(false)),
76            message: opts.message,
77            language: parse_language(&opts.language),
78        }
79    }
80}
81
82/// Renders a code frame showing the location of an error in source code
83///
84/// This is a Rust implementation that replaces Babel's code-frame for better:
85/// - Performance on large files
86/// - Handling of long lines
87/// - Memory efficiency
88///
89/// # Arguments
90/// * `source` - The source code to render
91/// * `location` - The location to highlight (line and column numbers are 1-indexed)
92/// * `options` - Optional configuration
93///
94/// # Returns
95/// The formatted code frame string, or `undefined` if the location is out of
96/// range (e.g., empty source or line number past end of file).
97#[napi]
98pub fn code_frame_columns(
99    source: String,
100    location: NapiCodeFrameLocation,
101    options: Option<NapiCodeFrameOptions>,
102) -> Result<Option<String>> {
103    let code_frame_location: CodeFrameLocation = location.into();
104    let code_frame_options: CodeFrameOptions = options.unwrap_or_default().into();
105
106    render_code_frame(&source, &code_frame_location, &code_frame_options)
107        .map_err(|e| Error::from_reason(format!("Failed to render code frame: {e:?}")))
108}