Skip to content

Commit

Permalink
feat(napi/transform,napi/parser): return structured error object (#7724)
Browse files Browse the repository at this point in the history
closes #7261
  • Loading branch information
Boshen committed Dec 8, 2024
1 parent 02f9903 commit 85eec3c
Show file tree
Hide file tree
Showing 19 changed files with 191 additions and 73 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ oxc_estree = { version = "0.39.0", path = "crates/oxc_estree" }
oxc_isolated_declarations = { version = "0.39.0", path = "crates/oxc_isolated_declarations" }
oxc_mangler = { version = "0.39.0", path = "crates/oxc_mangler" }
oxc_minifier = { version = "0.39.0", path = "crates/oxc_minifier" }
oxc_napi = { version = "0.39.0", path = "crates/oxc_napi" }
oxc_parser = { version = "0.39.0", path = "crates/oxc_parser" }
oxc_regular_expression = { version = "0.39.0", path = "crates/oxc_regular_expression" }
oxc_semantic = { version = "0.39.0", path = "crates/oxc_semantic" }
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_diagnostics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Feature gating napi in other crates will lead to symbol not found when compiling,
use this crate for common napi interfaces.
28 changes: 28 additions & 0 deletions crates/oxc_napi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "oxc_napi"
version = "0.39.0"
authors.workspace = true
categories.workspace = true
edition.workspace = true
homepage.workspace = true
include = ["/src"]
keywords.workspace = true
license.workspace = true
publish = true
repository.workspace = true
rust-version.workspace = true
description.workspace = true

[lints]
workspace = true

[lib]
doctest = false

[dependencies]
napi = { workspace = true }
napi-derive = { workspace = true }
oxc_diagnostics = { workspace = true }

[package.metadata.cargo-shear]
ignored = ["napi"]
68 changes: 68 additions & 0 deletions crates/oxc_napi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use napi_derive::napi;

use oxc_diagnostics::{LabeledSpan, OxcDiagnostic};

#[napi(object)]
pub struct Error {
pub severity: Severity,
pub message: String,
pub labels: Vec<ErrorLabel>,
pub help_message: Option<String>,
}

impl Error {
pub fn new(message: String) -> Self {
Self { severity: Severity::Error, message, labels: vec![], help_message: None }
}
}

impl From<OxcDiagnostic> for Error {
fn from(diagnostic: OxcDiagnostic) -> Self {
let labels = diagnostic
.labels
.as_ref()
.map(|labels| labels.iter().map(ErrorLabel::from).collect::<Vec<_>>())
.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<String>,
pub start: u32,
pub end: u32,
}

impl From<&LabeledSpan> for ErrorLabel {
#[allow(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<oxc_diagnostics::Severity> 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,
}
}
}
1 change: 1 addition & 0 deletions napi/parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ doctest = false

[dependencies]
oxc = { workspace = true, features = ["serialize"] }
oxc_napi = { workspace = true }

rustc-hash = { workspace = true }
serde_json = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions napi/parser/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,4 @@ module.exports.ImportNameKind = nativeBinding.ImportNameKind
module.exports.parseAsync = nativeBinding.parseAsync
module.exports.parseSync = nativeBinding.parseSync
module.exports.parseWithoutReturn = nativeBinding.parseWithoutReturn
module.exports.Severity = nativeBinding.Severity
21 changes: 20 additions & 1 deletion napi/parser/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ export interface EcmaScriptModule {
importMetas: Array<Span>
}

export interface Error {
severity: Severity
message: string
labels: Array<ErrorLabel>
helpMessage?: string
}

export interface ErrorLabel {
message?: string
start: number
end: number
}

export interface ExportExportName {
kind: ExportExportNameKind
name?: string
Expand Down Expand Up @@ -106,7 +119,7 @@ export interface ParseResult {
program: import("@oxc-project/types").Program
module: EcmaScriptModule
comments: Array<Comment>
errors: Array<string>
errors: Array<Error>
}

export interface ParserOptions {
Expand Down Expand Up @@ -135,6 +148,12 @@ export declare function parseSync(filename: string, sourceText: string, options?
*/
export declare function parseWithoutReturn(filename: string, sourceText: string, options?: ParserOptions | undefined | null): void

export declare const enum Severity {
Error = 'Error',
Warning = 'Warning',
Advice = 'Advice'
}

export interface Span {
start: number
end: number
Expand Down
15 changes: 2 additions & 13 deletions napi/parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@
mod convert;
mod types;

use std::sync::Arc;

use napi::{bindgen_prelude::AsyncTask, Task};
use napi_derive::napi;

use oxc::{
allocator::Allocator,
ast::CommentKind,
diagnostics::{Error, NamedSource},
parser::{ParseOptions, Parser, ParserReturn},
span::SourceType,
};
use oxc_napi::Error;

pub use crate::types::{Comment, EcmaScriptModule, ParseResult, ParserOptions};

Expand Down Expand Up @@ -70,16 +68,7 @@ fn parse_with_return(filename: &str, source_text: &str, options: &ParserOptions)
let ret = parse(&allocator, source_type, source_text, options);
let program = serde_json::to_string(&ret.program).unwrap();

let errors = if ret.errors.is_empty() {
vec![]
} else {
let source = Arc::new(NamedSource::new(filename, source_text.to_string()));
ret.errors
.into_iter()
.map(|diagnostic| Error::from(diagnostic).with_source_code(Arc::clone(&source)))
.map(|error| format!("{error:?}"))
.collect()
};
let errors = ret.errors.into_iter().map(Error::from).collect::<Vec<_>>();

let comments = ret
.program
Expand Down
4 changes: 3 additions & 1 deletion napi/parser/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use napi_derive::napi;

use oxc_napi::Error;

#[napi(object)]
#[derive(Default)]
pub struct ParserOptions {
Expand All @@ -26,7 +28,7 @@ pub struct ParseResult {
pub program: String,
pub module: EcmaScriptModule,
pub comments: Vec<Comment>,
pub errors: Vec<String>,
pub errors: Vec<Error>,
}

#[napi(object)]
Expand Down
20 changes: 20 additions & 0 deletions napi/parser/test/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,23 @@ describe('parse', () => {
expect(ret).toEqual(ret2);
});
});

describe('error', () => {
const code = 'asdf asdf';

it('returns structured error', () => {
const ret = parseSync('test.js', code);
expect(ret.errors.length).toBe(1);
expect(ret.errors[0]).toStrictEqual({
'helpMessage': 'Try insert a semicolon here',
'labels': [
{
'end': 4,
'start': 4,
},
],
'message': 'Expected a semicolon or an implicit semicolon after a statement, but found none',
'severity': 'Error',
});
});
});
1 change: 1 addition & 0 deletions napi/transform/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ doctest = false

[dependencies]
oxc = { workspace = true, features = ["full"] }
oxc_napi = { workspace = true }
oxc_sourcemap = { workspace = true, features = ["napi", "rayon"] }

rustc-hash = { workspace = true }
Expand Down
23 changes: 21 additions & 2 deletions napi/transform/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ export interface CompilerAssumptions {
setPublicClassFields?: boolean
}

export interface Error {
severity: Severity
message: string
labels: Array<ErrorLabel>
helpMessage?: string
}

export interface ErrorLabel {
message?: string
start: number
end: number
}

export interface Es2015Options {
/** Transform arrow functions into function expressions. */
arrowFunction?: ArrowFunctionsOptions
Expand All @@ -44,7 +57,7 @@ export interface IsolatedDeclarationsOptions {
export interface IsolatedDeclarationsResult {
code: string
map?: SourceMap
errors: Array<string>
errors: Array<Error>
}

/**
Expand Down Expand Up @@ -158,6 +171,12 @@ export interface ReactRefreshOptions {
emitFullSignatures?: boolean
}

export declare const enum Severity {
Error = 'Error',
Warning = 'Warning',
Advice = 'Advice'
}

export interface SourceMap {
file?: string
mappings: string
Expand Down Expand Up @@ -271,7 +290,7 @@ export interface TransformResult {
* transformed code may still be available even if there are errors in this
* list.
*/
errors: Array<string>
errors: Array<Error>
}

export interface TypeScriptOptions {
Expand Down
1 change: 1 addition & 0 deletions napi/transform/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,5 @@ if (!nativeBinding) {
}

module.exports.isolatedDeclaration = nativeBinding.isolatedDeclaration
module.exports.Severity = nativeBinding.Severity
module.exports.transform = nativeBinding.transform
41 changes: 0 additions & 41 deletions napi/transform/src/errors.rs

This file was deleted.

Loading

0 comments on commit 85eec3c

Please sign in to comment.