feat(plugin): Proxy swc_common apis (#2646)

swc_common:
 - Add `Runtime` trait for plugin.
 - Implement `serde` for diagnostics.
 - Proxy diagnostics using `Runtime`.
 - Proxy `HygieneData::with` with `Runtime`.
 - Add implementation of `Runtime` with cargo feature `plugin-rt`.
 - Make `Runtime` implement `StableAbi`.

swc_plugin:
 - Move api code to `swc_plugin_api`.
 - Depend on `swc_common/plugin-mode`.
 - Configure `Runtime` before invoking custom transforms.
 - Use `bincode` for serde.

swc_plugin_runner:
 - Depend on `swc_common/plugin-rt`.
 - Pass `Runtime` implementation to the plugins.
 - Use `bincode` for serde.
This commit is contained in:
Donny/강동윤 2021-11-04 19:24:49 +09:00 committed by GitHub
parent 0b76d29ae4
commit 380722976a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 440 additions and 71 deletions

View File

@ -116,6 +116,7 @@ jobs:
- swc_css
- swc_css_ast
- swc_css_codegen
- swc_css_codegen_macros
- swc_css_parser
- swc_css_utils
- swc_css_visit
@ -147,6 +148,7 @@ jobs:
- swc_node_base
- swc_node_bundler
- swc_plugin
- swc_plugin_api
- swc_plugin_runner
- swc_plugin_testing
- swc_stylis

37
Cargo.lock generated
View File

@ -220,6 +220,15 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -2490,12 +2499,15 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.14.4"
version = "0.14.5"
dependencies = [
"abi_stable",
"ahash",
"anyhow",
"arbitrary",
"ast_node",
"atty",
"bincode",
"cfg-if 0.1.10",
"either",
"from_variant",
@ -3123,7 +3135,22 @@ dependencies = [
[[package]]
name = "swc_plugin"
version = "0.9.0"
version = "0.10.0"
dependencies = [
"abi_stable",
"anyhow",
"serde",
"serde_json",
"swc_atoms 0.2.9",
"swc_common",
"swc_ecma_ast",
"swc_ecma_visit",
"swc_plugin_api",
]
[[package]]
name = "swc_plugin_api"
version = "0.1.0"
dependencies = [
"abi_stable",
"anyhow",
@ -3137,7 +3164,7 @@ dependencies = [
[[package]]
name = "swc_plugin_runner"
version = "0.13.1"
version = "0.14.0"
dependencies = [
"abi_stable",
"anyhow",
@ -3149,13 +3176,13 @@ dependencies = [
"swc_ecma_ast",
"swc_ecma_codegen",
"swc_ecma_parser",
"swc_plugin",
"swc_plugin_api",
"testing",
]
[[package]]
name = "swc_plugin_testing"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"anyhow",
"swc_atoms 0.2.9",

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_common"
repository = "https://github.com/swc-project/swc.git"
version = "0.14.4"
version = "0.14.5"
[lib]
crate-type = ["lib", "dylib"]
@ -15,13 +15,20 @@ crate-type = ["lib", "dylib"]
concurrent = ["parking_lot"]
debug = []
default = []
diagnostic-serde = []
plugin-base = ["abi_stable", "anyhow", "bincode", "diagnostic-serde"]
plugin-mode = ["plugin-base"]
plugin-rt = ["plugin-base"]
tty-emitter = ["atty", "termcolor"]
[dependencies]
abi_stable = {version = "0.10.3", optional = true}
ahash = "0.7.4"
anyhow = {version = "1.0.45", optional = true}
arbitrary = {version = "1", optional = true, features = ["derive"]}
ast_node = {version = "0.7.3", path = "../macros/ast_node"}
atty = {version = "0.2", optional = true}
bincode = {version = "1.3.3", optional = true}
cfg-if = "0.1.2"
either = "1.5"
from_variant = {version = "0.1.3", path = "../macros/from_variant"}

View File

@ -14,6 +14,10 @@ use std::fmt;
#[must_use]
#[derive(Clone, Debug, PartialEq, Hash)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Diagnostic {
pub level: Level,
pub message: Vec<(String, Style)>,
@ -24,6 +28,10 @@ pub struct Diagnostic {
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum DiagnosticId {
Error(String),
Lint(String),
@ -31,6 +39,10 @@ pub enum DiagnosticId {
/// For example a note attached to an error.
#[derive(Clone, Debug, PartialEq, Hash)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct SubDiagnostic {
pub level: Level,
pub message: Vec<(String, Style)>,

View File

@ -27,7 +27,7 @@ use tracing::debug;
#[derive(Clone)]
pub struct DiagnosticBuilder<'a> {
pub handler: &'a Handler,
diagnostic: Box<Diagnostic>,
pub(crate) diagnostic: Box<Diagnostic>,
allow_suggestions: bool,
}

View File

@ -40,6 +40,10 @@ mod snippet;
mod styled_buffer;
#[derive(Copy, Clone, Debug, PartialEq, Hash)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum Applicability {
MachineApplicable,
HasPlaceholders,
@ -48,6 +52,10 @@ pub enum Applicability {
}
#[derive(Clone, Debug, PartialEq, Hash)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct CodeSuggestion {
/// Each substitute can have multiple variants due to multiple
/// applicable suggestions
@ -91,11 +99,19 @@ pub struct CodeSuggestion {
#[derive(Clone, Debug, PartialEq, Hash)]
/// See the docs on `CodeSuggestion::substitutions`
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Substitution {
pub parts: Vec<SubstitutionPart>,
}
#[derive(Clone, Debug, PartialEq, Hash)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct SubstitutionPart {
pub span: Span,
pub snippet: String,
@ -764,6 +780,10 @@ impl Handler {
}
#[derive(Copy, PartialEq, Clone, Hash, Debug)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum Level {
Bug,
Fatal,

View File

@ -179,6 +179,10 @@ pub struct StyledString {
}
#[derive(Copy, Clone, Debug, PartialEq, Hash)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum Style {
MainHeaderMsg,
HeaderMsg,

View File

@ -11,6 +11,21 @@
//! ## `sourcemap`
//!
//! Adds methods to generate web sourcemap.
//!
//! ## `plugin-base`
//!
//! Base mode for plugins, which can be enabled by `plugin-mode` or `plugin-rt`.
//!
//! This mode creates a trait which can be used to override `swc_common` itself.
//!
//! ## `plugin-rt`
//!
//! Creates an implementation for the plugin trait. This implements simply
//! invokes thread-locals declared in `swc_common`.
//!
//! ## `plugin-mode`
//!
//! Allows replacing operations related to thread-local variables with a trait.
#![deny(unused)]
pub use self::{
@ -46,6 +61,8 @@ pub mod input;
pub mod iter;
pub mod macros;
pub mod pass;
#[cfg(feature = "plugin-base")]
pub mod plugin;
mod pos;
mod rustc_data_structures;
pub mod serializer;
@ -53,3 +70,6 @@ pub mod source_map;
pub mod sync;
mod syntax_pos;
pub mod util;
#[cfg(all(not(debug_assertions), feature = "plugin-rt", feature = "plugin-mode"))]
compile_error!("You can't enable `plugin-rt` and `plugin-mode` at the same time");

180
common/src/plugin.rs Normal file
View File

@ -0,0 +1,180 @@
//! Plugin support.
//!
//! We need to replace operations related to thread-local variables in
//! `swc_common`.
#![allow(unused)]
use crate::{syntax_pos::Mark, SyntaxContext};
use abi_stable::{
sabi_trait,
std_types::{RBox, RVec},
StableAbi,
};
use anyhow::{Context, Error};
use serde::{de::DeserializeOwned, Serialize};
use std::any::type_name;
#[repr(transparent)]
#[derive(StableAbi)]
pub struct Runtime {
inner: RuntimeImpl_TO<'static, RBox<()>>,
}
#[cfg(feature = "plugin-mode")]
scoped_tls::scoped_thread_local!(
/// If this variable is configured, many methods of
/// `swc_common` will be proxied to this variable.
pub(crate) static RT: RuntimeImpl_TO<'static, RBox<()>>
);
/// **INTERNAL API**
///
///
/// Don't use this. This is for internal use only.
/// This can be changed without breaking semver version bump.
#[sabi_trait]
pub trait RuntimeImpl {
/// Emit a structured diagnostic.
///
/// - `db`: Serialized version of Diagnostic which is serialized using
/// bincode.
fn emit_diagnostic(&self, db: RVec<u8>);
fn fresh_mark(&self, parent: Mark) -> Mark;
fn parent_mark(&self, mar: Mark) -> Mark;
fn is_mark_builtin(&self, mark: Mark) -> bool;
fn set_mark_is_builtin(&self, mark: Mark, is_builtin: bool);
fn is_mark_descendant_of(&self, mark: Mark, ancestor: Mark) -> bool;
fn least_ancestor_of_marks(&self, a: Mark, b: Mark) -> Mark;
fn apply_mark_to_syntax_context_internal(
&self,
ctxt: SyntaxContext,
mark: Mark,
) -> SyntaxContext;
fn remove_mark_of_syntax_context(&self, ctxt: &mut SyntaxContext) -> Mark;
fn outer_mark_of_syntax_context(&self, ctxt: SyntaxContext) -> Mark;
}
#[cfg(feature = "plugin-mode")]
struct PluginEmitter;
#[cfg(feature = "plugin-mode")]
impl crate::errors::Emitter for PluginEmitter {
fn emit(&mut self, db: &crate::errors::DiagnosticBuilder<'_>) {
let bytes: RVec<_> = serialize_for_plugin(&db.diagnostic).unwrap().into();
RT.with(|rt| rt.emit_diagnostic(bytes))
}
}
#[cfg(feature = "plugin-mode")]
pub fn with_runtime<F, Ret>(rt: &Runtime, op: F) -> Ret
where
F: FnOnce() -> Ret,
{
use crate::errors::{Handler, HANDLER};
let handler = Handler::with_emitter(true, false, Box::new(PluginEmitter));
RT.set(&rt.inner, || {
// We proxy error reporting to the core runtime.
HANDLER.set(&handler, || op())
})
}
pub fn serialize_for_plugin<T>(t: &T) -> Result<Vec<u8>, Error>
where
T: Serialize,
{
bincode::serialize(&t)
.with_context(|| format!("failed to serialize `{}` using bincode", type_name::<T>()))
}
pub fn deserialize_for_plugin<T>(bytes: &[u8]) -> Result<T, Error>
where
T: DeserializeOwned,
{
bincode::deserialize(bytes)
.with_context(|| format!("failed to deserialize `{}` using bincode", type_name::<T>()))
}
#[cfg(feature = "plugin-rt")]
struct PluginRt {
name: String,
}
#[cfg(feature = "plugin-rt")]
impl RuntimeImpl for PluginRt {
fn emit_diagnostic(&self, db: RVec<u8>) {
use crate::errors::{Diagnostic, DiagnosticBuilder, HANDLER};
let diagnostic: Diagnostic =
deserialize_for_plugin(db.as_slice()).expect("plugin send invalid diagnostic");
HANDLER.with(|handler| {
DiagnosticBuilder::new_diagnostic(&handler, diagnostic)
.note(&format!(
"this message is generated by plugin `{}`",
&self.name
))
.emit();
});
}
fn fresh_mark(&self, parent: Mark) -> Mark {
Mark::fresh(parent)
}
fn parent_mark(&self, mark: Mark) -> Mark {
mark.parent()
}
fn is_mark_builtin(&self, mark: Mark) -> bool {
mark.is_builtin()
}
fn set_mark_is_builtin(&self, mark: Mark, is_builtin: bool) {
mark.set_is_builtin(is_builtin)
}
fn is_mark_descendant_of(&self, mark: Mark, ancestor: Mark) -> bool {
mark.is_descendant_of(ancestor)
}
fn least_ancestor_of_marks(&self, a: Mark, b: Mark) -> Mark {
Mark::least_ancestor(a, b)
}
fn apply_mark_to_syntax_context_internal(
&self,
ctxt: SyntaxContext,
mark: Mark,
) -> SyntaxContext {
ctxt.apply_mark(mark)
}
fn remove_mark_of_syntax_context(&self, ctxt: &mut SyntaxContext) -> Mark {
ctxt.remove_mark()
}
fn outer_mark_of_syntax_context(&self, ctxt: SyntaxContext) -> Mark {
ctxt.outer()
}
}
#[cfg(feature = "plugin-rt")]
pub fn get_runtime_for_plugin(plugin_name: String) -> Runtime {
use abi_stable::erased_types::TD_Opaque;
let rt = PluginRt { name: plugin_name };
let rt: RuntimeImpl_TO<'_, RBox<_>> = RuntimeImpl_TO::from_value(rt, TD_Opaque);
Runtime { inner: rt }
}

View File

@ -176,6 +176,10 @@ impl FileName {
/// - they can have a *label*. In this case, the label is written next to the
/// mark in the snippet when we render.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[cfg_attr(
feature = "diagnostic-serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct MultiSpan {
primary_spans: Vec<Span>,
span_labels: Vec<(Span, String)>,

View File

@ -27,6 +27,8 @@ use std::{
/// marks).
#[derive(Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
#[cfg_attr(feature = "abi_stable", repr(transparent))]
#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
pub struct SyntaxContext(u32);
#[cfg(feature = "arbitrary")]
@ -48,6 +50,8 @@ struct SyntaxContextData {
/// A mark is a unique id associated with a macro expansion.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "abi_stable", repr(transparent))]
#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
pub struct Mark(u32);
#[derive(Clone, Debug)]
@ -58,6 +62,11 @@ struct MarkData {
impl Mark {
pub fn fresh(parent: Mark) -> Self {
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT.with(|rt| rt.fresh_mark(parent));
}
HygieneData::with(|data| {
data.marks.push(MarkData {
parent,
@ -86,22 +95,44 @@ impl Mark {
#[inline]
pub fn parent(self) -> Mark {
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT.with(|rt| rt.parent_mark(self));
}
HygieneData::with(|data| data.marks[self.0 as usize].parent)
}
#[inline]
pub fn is_builtin(self) -> bool {
assert_ne!(self, Mark::root());
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT.with(|rt| rt.is_mark_builtin(self));
}
HygieneData::with(|data| data.marks[self.0 as usize].is_builtin)
}
#[inline]
pub fn set_is_builtin(self, is_builtin: bool) {
assert_ne!(self, Mark::root());
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT.with(|rt| rt.set_mark_is_builtin(self, is_builtin));
}
HygieneData::with(|data| data.marks[self.0 as usize].is_builtin = is_builtin)
}
pub fn is_descendant_of(mut self, ancestor: Mark) -> bool {
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT.with(|rt| rt.is_mark_descendant_of(self, ancestor));
}
HygieneData::with(|data| {
while self != ancestor {
if self == Mark::root() {
@ -121,7 +152,13 @@ impl Mark {
/// assert!(a.is_descendant_of(la))
/// assert!(b.is_descendant_of(la))
/// ```
#[allow(unused_mut)]
pub fn least_ancestor(mut a: Mark, mut b: Mark) -> Mark {
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT.with(|rt| rt.least_ancestor_of_marks(a, b));
}
HygieneData::with(|data| {
// Compute the path from a to the root
let mut a_path = HashSet::<Mark>::default();
@ -208,6 +245,12 @@ impl SyntaxContext {
}
fn apply_mark_internal(self, mark: Mark) -> SyntaxContext {
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT
.with(|rt| rt.apply_mark_to_syntax_context_internal(self, mark));
}
HygieneData::with(|data| {
let syntax_contexts = &mut data.syntax_contexts;
let mut opaque = syntax_contexts[self.0 as usize].opaque;
@ -258,6 +301,11 @@ impl SyntaxContext {
/// the SyntaxContext for the invocation of f that created g1.
/// Returns the mark that was removed.
pub fn remove_mark(&mut self) -> Mark {
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT.with(|rt| rt.remove_mark_of_syntax_context(self));
}
HygieneData::with(|data| {
let outer_mark = data.syntax_contexts[self.0 as usize].outer_mark;
*self = data.syntax_contexts[self.0 as usize].prev_ctxt;
@ -375,6 +423,11 @@ impl SyntaxContext {
#[inline]
pub fn outer(self) -> Mark {
#[cfg(feature = "plugin-mode")]
if crate::plugin::RT.is_set() {
return crate::plugin::RT.with(|rt| rt.outer_mark_of_syntax_context(self));
}
HygieneData::with(|data| data.syntax_contexts[self.0 as usize].outer_mark)
}
}

View File

@ -93,6 +93,7 @@
"prec",
"PREC",
"proto",
"proxied",
"punct",
"putc",
"qself",

View File

@ -78,6 +78,7 @@ allow = [
"BSD-3-Clause",
"CC0-1.0",
"ISC",
"Zlib",
]
# List of explictly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_plugin"
repository = "https://github.com/swc-project/swc.git"
version = "0.9.0"
version = "0.10.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,6 +16,7 @@ anyhow = "1.0.41"
serde = "1.0.126"
serde_json = "1.0.64"
swc_atoms = {version = "0.2.7", path = "../atoms"}
swc_common = {version = "0.14.0", path = "../common"}
swc_common = {version = "0.14.0", path = "../common", features = ["plugin-mode"]}
swc_ecma_ast = {version = "0.56.0", path = "../ecmascript/ast"}
swc_ecma_visit = {version = "0.42.0", path = "../ecmascript/visit"}
swc_plugin_api = {version = "0.1.0", path = "./api"}

21
plugin/api/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "API for the swc plugins"
documentation = "https://rustdoc.swc.rs/swc_plugin/"
edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_plugin_api"
repository = "https://github.com/swc-project/swc.git"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
abi_stable = "0.10.2"
anyhow = "1.0.41"
serde = "1.0.126"
serde_json = "1.0.64"
swc_atoms = {version = "0.2.7", path = "../../atoms"}
swc_common = {version = "0.14.5", path = "../../common", features = ["plugin-base"]}
swc_ecma_ast = {version = "0.56.0", path = "../../ecmascript/ast"}
swc_ecma_visit = {version = "0.42.0", path = "../../ecmascript/visit"}

31
plugin/api/src/lib.rs Normal file
View File

@ -0,0 +1,31 @@
use abi_stable::{
library::RootModule,
package_version_strings,
sabi_types::VersionStrings,
std_types::{RResult, RStr, RString, RVec},
StableAbi,
};
/// Don't use this directly.
#[repr(C)]
#[derive(StableAbi)]
#[sabi(kind(Prefix(prefix_ref = "SwcPluginRef")))]
#[sabi(missing_field(panic))]
pub struct SwcPlugin {
#[sabi(last_prefix_field)]
pub process_js: Option<
extern "C" fn(
rt: swc_common::plugin::Runtime,
config_json: RStr,
ast: RVec<u8>,
) -> RResult<RVec<u8>, RString>,
>,
}
impl RootModule for SwcPluginRef {
abi_stable::declare_root_module_statics! {SwcPluginRef}
const BASE_NAME: &'static str = "swc_plugin";
const NAME: &'static str = "swc_plugin";
const VERSION_STRINGS: VersionStrings = package_version_strings!();
}

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_plugin_runner"
repository = "https://github.com/swc-project/swc.git"
version = "0.13.1"
version = "0.14.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
@ -16,10 +16,10 @@ libloading = "0.7.0"
serde = {version = "1.0.126", features = ["derive"]}
serde_json = "1.0.64"
swc_atoms = "0.2.7"
swc_common = {version = "0.14.0", path = "../../common"}
swc_common = {version = "0.14.0", path = "../../common", features = ["plugin-rt"]}
swc_ecma_ast = {version = "0.56.0", path = "../../ecmascript/ast"}
swc_ecma_parser = {version = "0.76.3", path = "../../ecmascript/parser"}
swc_plugin = {version = "0.9.0", path = "../"}
swc_plugin_api = {version = "0.1.0", path = "../api"}
[dev-dependencies]
swc_ecma_codegen = {version = "0.78.1", path = "../../ecmascript/codegen"}

View File

@ -1,34 +1,38 @@
use abi_stable::{
library::RootModule,
std_types::{RResult, RStr, RString},
std_types::{RResult, RStr},
};
use anyhow::{anyhow, Context, Error};
use std::path::Path;
use swc_common::plugin::{deserialize_for_plugin, serialize_for_plugin};
use swc_ecma_ast::Program;
use swc_plugin::SwcPluginRef;
use swc_plugin_api::SwcPluginRef;
pub fn apply_js_plugin(program: &Program, path: &Path) -> Result<Program, Error> {
pub fn apply_js_plugin(
plugin_name: &str,
path: &Path,
config_json: &str,
program: &Program,
) -> Result<Program, Error> {
(|| -> Result<_, Error> {
let plugin_rt = swc_common::plugin::get_runtime_for_plugin(plugin_name.to_string());
let plugin = SwcPluginRef::load_from_file(path).context("failed to load plugin")?;
let config_json = "{}";
let ast_json =
serde_json::to_string(&program).context("failed to serialize program as json")?;
let ast_serde = serialize_for_plugin(&program).context("failed to serialize ast")?;
let plugin_fn = plugin
.process_js()
.ok_or_else(|| anyhow!("the plugin does not support transforming js"))?;
let new_ast = plugin_fn(RStr::from(config_json), RString::from(ast_json));
let new_ast = plugin_fn(plugin_rt, RStr::from(config_json), ast_serde.into());
let new = match new_ast {
RResult::ROk(v) => v,
RResult::RErr(err) => return Err(anyhow!("plugin returned an error\n{}", err)),
};
let new = new.into_string();
let new = serde_json::from_str(&new)
.with_context(|| format!("plugin generated invalid ast: `{}`", new))?;
let new: Program = deserialize_for_plugin(new.as_slice())
.with_context(|| format!("plugin generated invalid ast`"))?;
Ok(new)
})()

View File

@ -1,39 +1,18 @@
/// Reexported for convenience.
use abi_stable::{
library::RootModule,
package_version_strings,
sabi_types::VersionStrings,
std_types::{RResult, RStr, RString},
StableAbi,
};
use abi_stable::std_types::{RResult, RStr, RString, RVec};
use anyhow::Context;
use serde::de::DeserializeOwned;
use swc_common::plugin::{deserialize_for_plugin, serialize_for_plugin};
use swc_ecma_ast::Program;
#[repr(C)]
#[derive(StableAbi)]
#[sabi(kind(Prefix(prefix_ref = "SwcPluginRef")))]
#[sabi(missing_field(panic))]
pub struct SwcPlugin {
#[sabi(last_prefix_field)]
pub process_js:
Option<extern "C" fn(config_str: RStr, ast_json: RString) -> RResult<RString, RString>>,
}
impl RootModule for SwcPluginRef {
abi_stable::declare_root_module_statics! {SwcPluginRef}
const BASE_NAME: &'static str = "swc_plugin";
const NAME: &'static str = "swc_plugin";
const VERSION_STRINGS: VersionStrings = package_version_strings!();
}
pub use swc_plugin_api::*;
#[doc(hidden)]
pub fn invoke_js_plugin<C, F>(
rt: swc_common::plugin::Runtime,
op: fn(C) -> F,
config_json: RStr,
ast_json: RString,
) -> RResult<RString, RString>
ast: RVec<u8>,
) -> RResult<RVec<u8>, RString>
where
C: DeserializeOwned,
F: swc_ecma_visit::Fold,
@ -47,31 +26,32 @@ where
Err(err) => return RResult::RErr(format!("{:?}", err).into()),
};
let ast =
serde_json::from_str(ast_json.as_str()).context("failed to deserialize ast string as json");
let ast = deserialize_for_plugin(ast.as_slice());
let ast: Program = match ast {
Ok(v) => v,
Err(err) => return RResult::RErr(format!("{:?}", err).into()),
};
let mut tr = op(config);
swc_common::plugin::with_runtime(&rt, || {
let mut tr = op(config);
let ast = ast.fold_with(&mut tr);
let ast = ast.fold_with(&mut tr);
let res = match serde_json::to_string(&ast) {
Ok(v) => v,
Err(err) => {
return RResult::RErr(
format!(
"failed to serialize swc_ecma_ast::Program as json: {:?}",
err
let res = match serialize_for_plugin(&ast) {
Ok(v) => v,
Err(err) => {
return RResult::RErr(
format!(
"failed to serialize swc_ecma_ast::Program as json: {:?}",
err
)
.into(),
)
.into(),
)
}
};
}
};
RResult::ROk(res.into())
RResult::ROk(res.into())
})
}
#[macro_export]
@ -80,13 +60,14 @@ macro_rules! define_js_plugin {
#[abi_stable::export_root_module]
pub fn swc_library() -> $crate::SwcPluginRef {
extern "C" fn swc_js_plugin(
rt: swc_common::plugin::Runtime,
config_json: abi_stable::std_types::RStr,
ast_json: abi_stable::std_types::RString,
ast: abi_stable::std_types::RVec<u8>,
) -> abi_stable::std_types::RResult<
abi_stable::std_types::RString,
abi_stable::std_types::RVec<u8>,
abi_stable::std_types::RString,
> {
$crate::invoke_js_plugin($fn_name, config_json, ast_json)
$crate::invoke_js_plugin(rt, $fn_name, config_json, ast)
}
use abi_stable::prefix_type::PrefixTypeTrait;

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_plugin_testing"
repository = "https://github.com/swc-project/swc.git"
version = "0.15.1"
version = "0.16.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -18,4 +18,4 @@ swc_ecma_ast = {version = "0.56.0", path = "../../ecmascript/ast"}
swc_ecma_codegen = {version = "0.78.1", path = "../../ecmascript/codegen"}
swc_ecma_utils = {version = "0.50.0", path = "../../ecmascript/utils"}
swc_ecma_visit = {version = "0.42.0", path = "../../ecmascript/visit"}
swc_plugin = {version = "0.9.0", path = "../"}
swc_plugin = {version = "0.10.0", path = "../"}