mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 05:01:42 +03:00
feat(plugin/api): Determine plugin api (#2199)
This commit is contained in:
parent
2379fe1ce0
commit
7a31a3f530
1
.github/workflows/cargo.yml
vendored
1
.github/workflows/cargo.yml
vendored
@ -150,6 +150,7 @@ jobs:
|
||||
- swc_stylis
|
||||
- swc_visit
|
||||
- swc_visit_macros
|
||||
- swd
|
||||
- testing
|
||||
- testing_macros
|
||||
- wasm
|
||||
|
1039
Cargo.lock
generated
1039
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
members = [
|
||||
"css",
|
||||
"css/stylis",
|
||||
"dev/cli",
|
||||
"ecmascript",
|
||||
"ecmascript/babel/compat",
|
||||
"ecmascript/jsdoc",
|
||||
|
23
dev/cli/Cargo.toml
Normal file
23
dev/cli/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
description = "Command line for writting swc plugins in rust"
|
||||
documentation = "https://rustdoc.swc.rs/swc_plugin/"
|
||||
edition = "2018"
|
||||
license = "Apache-2.0/MIT"
|
||||
name = "swd"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.0.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.41"
|
||||
cargo = "0.56.0"
|
||||
cargo-edit = "0.8.0"
|
||||
cargo_metadata = "0.14.0"
|
||||
clap = "2.33.3"
|
||||
structopt = "0.3.21"
|
||||
tokio = {version = "1.10.1", features = ["rt", "rt-multi-thread", "time", "process", "macros", "sync"]}
|
||||
tracing = "0.1.26"
|
||||
tracing-subscriber = "0.2.20"
|
||||
url = "2"
|
15
dev/cli/README.md
Normal file
15
dev/cli/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# swc-dev
|
||||
|
||||
# Usage
|
||||
|
||||
```sh
|
||||
# Dependency
|
||||
cargo install cargo-edit
|
||||
cargo install swc-dev
|
||||
```
|
||||
|
||||
## Managing plugins
|
||||
|
||||
```
|
||||
swc-dev plugin --help
|
||||
```
|
8
dev/cli/scripts/invoke.sh
Executable file
8
dev/cli/scripts/invoke.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
cargo install --path . --debug
|
||||
export RUST_LOG=debug
|
||||
|
||||
(cd plugins/packages/jest && swc-dev $@)
|
32
dev/cli/src/main.rs
Normal file
32
dev/cli/src/main.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use anyhow::Error;
|
||||
use plugin::PluginCommand;
|
||||
use structopt::StructOpt;
|
||||
use tracing::Level;
|
||||
|
||||
mod plugin;
|
||||
mod util;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
|
||||
pub enum Cmd {
|
||||
Plugin(PluginCommand),
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
tracing_subscriber::fmt::Subscriber::builder()
|
||||
.with_target(false)
|
||||
.with_max_level(Level::DEBUG)
|
||||
.pretty()
|
||||
.init();
|
||||
|
||||
let cmd = Cmd::from_args();
|
||||
|
||||
match cmd {
|
||||
Cmd::Plugin(cmd) => {
|
||||
cmd.run().await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
50
dev/cli/src/plugin/build.rs
Normal file
50
dev/cli/src/plugin/build.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::util::cargo::cargo_target_dir;
|
||||
use anyhow::{bail, Context, Error};
|
||||
use std::env;
|
||||
use structopt::StructOpt;
|
||||
use tokio::process::Command;
|
||||
|
||||
/// Build your plugin using `cargo`.
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct BuildCommand {
|
||||
/// Build for production.
|
||||
#[structopt(long)]
|
||||
pub release: bool,
|
||||
}
|
||||
|
||||
impl BuildCommand {
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
run_cargo_build(self.release).await?;
|
||||
let path = env::current_dir().context("failed to get current directory")?;
|
||||
|
||||
let target_dir = cargo_target_dir(&path).await?;
|
||||
tracing::debug!("Cargo target directory: {}", target_dir.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_cargo_build(release: bool) -> Result<(), Error> {
|
||||
tracing::info!(
|
||||
"Running cargo build ({})",
|
||||
if release { "release" } else { "debug" },
|
||||
);
|
||||
|
||||
let mut c = Command::new("cargo");
|
||||
c.arg("build");
|
||||
|
||||
if release {
|
||||
c.arg("--release");
|
||||
}
|
||||
|
||||
let status = c
|
||||
.status()
|
||||
.await
|
||||
.context("`status()` for `cargo build` failed")?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("`cargo build` failed with status code {}", status);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
33
dev/cli/src/plugin/init.rs
Normal file
33
dev/cli/src/plugin/init.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use crate::util::cargo::add::run_cargo_add;
|
||||
use anyhow::{bail, Context, Error};
|
||||
use std::process::Stdio;
|
||||
use structopt::StructOpt;
|
||||
use tokio::process::Command;
|
||||
|
||||
/// Initializes a plugin project.
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct InitCommand {}
|
||||
|
||||
impl InitCommand {
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
let mut c = Command::new("cargo");
|
||||
c.arg("init").stderr(Stdio::inherit());
|
||||
|
||||
let status = c
|
||||
.arg("--lib")
|
||||
.status()
|
||||
.await
|
||||
.with_context(|| format!("failed to run `cargo init`"))?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("failed to initialize a cargo project")
|
||||
}
|
||||
|
||||
run_cargo_add("abi_stable").await?;
|
||||
run_cargo_add("swc_atoms").await?;
|
||||
run_cargo_add("swc_common").await?;
|
||||
run_cargo_add("swc_plugin").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
46
dev/cli/src/plugin/mod.rs
Normal file
46
dev/cli/src/plugin/mod.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use self::{
|
||||
build::BuildCommand, init::InitCommand, package::PackageCommand, publish::PublishCommand,
|
||||
upgrade_deps::UpgradeDepsCommand,
|
||||
};
|
||||
use anyhow::{Context, Error};
|
||||
use structopt::StructOpt;
|
||||
|
||||
pub mod build;
|
||||
pub mod init;
|
||||
pub mod package;
|
||||
pub mod publish;
|
||||
pub mod upgrade_deps;
|
||||
|
||||
/// Manages the plugin. Used for developing plugins.
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub enum PluginCommand {
|
||||
Init(InitCommand),
|
||||
Build(BuildCommand),
|
||||
Package(PackageCommand),
|
||||
Publish(PublishCommand),
|
||||
UpgradeDeps(UpgradeDepsCommand),
|
||||
}
|
||||
|
||||
impl PluginCommand {
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
match self {
|
||||
PluginCommand::Init(cmd) => {
|
||||
cmd.run()
|
||||
.await
|
||||
.context("failed to initialize a plugin project")?;
|
||||
}
|
||||
PluginCommand::Build(cmd) => {
|
||||
cmd.run()
|
||||
.await
|
||||
.context("failed to build a plugin project")?;
|
||||
}
|
||||
PluginCommand::Package(_) => todo!(),
|
||||
PluginCommand::Publish(_) => todo!(),
|
||||
PluginCommand::UpgradeDeps(cmd) => {
|
||||
cmd.run().await.context("failed to upgrade dependencies")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
8
dev/cli/src/plugin/package.rs
Normal file
8
dev/cli/src/plugin/package.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct PackageCommand {
|
||||
/// Build for production.
|
||||
#[structopt(long)]
|
||||
pub release: bool,
|
||||
}
|
4
dev/cli/src/plugin/publish.rs
Normal file
4
dev/cli/src/plugin/publish.rs
Normal file
@ -0,0 +1,4 @@
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct PublishCommand {}
|
23
dev/cli/src/plugin/upgrade_deps.rs
Normal file
23
dev/cli/src/plugin/upgrade_deps.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use crate::util::cargo::upgrade::upgrade_dep;
|
||||
use anyhow::Error;
|
||||
use structopt::StructOpt;
|
||||
use tracing::info;
|
||||
|
||||
/// Upgrade dependencies related to `swc`.
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct UpgradeDepsCommand {
|
||||
/// Run upgrade command for all crates in the current workspace.
|
||||
#[structopt(long)]
|
||||
pub workspace: bool,
|
||||
}
|
||||
|
||||
impl UpgradeDepsCommand {
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
for crate_name in &["swc_atoms", "swc_common", "swc_plugin"] {
|
||||
info!("Upgrading {}", crate_name);
|
||||
upgrade_dep(&crate_name, self.workspace).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
16
dev/cli/src/util/cargo/add.rs
Normal file
16
dev/cli/src/util/cargo/add.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use anyhow::{bail, Context, Error};
|
||||
use tokio::process::Command;
|
||||
|
||||
pub async fn run_cargo_add(crate_name: &str) -> Result<(), Error> {
|
||||
let status = Command::new("cargo")
|
||||
.arg("add")
|
||||
.arg(crate_name)
|
||||
.status()
|
||||
.await
|
||||
.with_context(|| format!("failed to run `cargo add {}`", crate_name))?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("failed to run `cargo add {}`", crate_name)
|
||||
}
|
||||
}
|
32
dev/cli/src/util/cargo/mod.rs
Normal file
32
dev/cli/src/util/cargo/mod.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use anyhow::{Context, Error};
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
pub mod add;
|
||||
pub mod upgrade;
|
||||
|
||||
pub async fn cargo_metadata(
|
||||
mut cmd: MetadataCommand,
|
||||
from: &Path,
|
||||
) -> Result<cargo_metadata::Metadata, Error> {
|
||||
let from = from.to_path_buf();
|
||||
spawn_blocking(move || {
|
||||
let result = cmd
|
||||
.current_dir(&from)
|
||||
.exec()
|
||||
.context("failed to execute `cargo metadata`")?;
|
||||
|
||||
Ok(result)
|
||||
})
|
||||
.await
|
||||
.context("failed to join the task for `cargo metadata`")?
|
||||
}
|
||||
|
||||
pub async fn cargo_target_dir(from: &Path) -> Result<PathBuf, Error> {
|
||||
let mut cmd = MetadataCommand::new();
|
||||
cmd.no_deps();
|
||||
let md = cargo_metadata(cmd, from).await?;
|
||||
|
||||
Ok(md.target_directory.as_std_path().to_path_buf())
|
||||
}
|
280
dev/cli/src/util/cargo/upgrade.rs
Normal file
280
dev/cli/src/util/cargo/upgrade.rs
Normal file
@ -0,0 +1,280 @@
|
||||
use crate::util::{cargo::cargo_metadata, CargoEditResultExt};
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
use cargo_edit::{find, get_latest_dependency, CrateName, Dependency, LocalManifest};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
/// A collection of manifests.
|
||||
struct Manifests(Vec<(LocalManifest, cargo_metadata::Package)>);
|
||||
|
||||
/// Helper function to check whether a `cargo_metadata::Dependency` is a version
|
||||
/// dependency.
|
||||
fn is_version_dep(dependency: &cargo_metadata::Dependency) -> bool {
|
||||
match dependency.source {
|
||||
// This is the criterion cargo uses (in `SourceId::from_url`) to decide whether a
|
||||
// dependency has the 'registry' kind.
|
||||
Some(ref s) => s.splitn(2, '+').next() == Some("registry"),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl Manifests {
|
||||
/// Get all manifests in the workspace.
|
||||
async fn get_all(manifest_path: &Option<PathBuf>) -> Result<Self, Error> {
|
||||
let cur_dir = env::current_dir().context("failed to get current directory")?;
|
||||
|
||||
let mut cmd = cargo_metadata::MetadataCommand::new();
|
||||
cmd.no_deps();
|
||||
if let Some(path) = manifest_path {
|
||||
cmd.manifest_path(path);
|
||||
}
|
||||
let result = cargo_metadata(cmd, &cur_dir).await?;
|
||||
|
||||
result
|
||||
.packages
|
||||
.into_iter()
|
||||
.map(|package| {
|
||||
Ok((
|
||||
LocalManifest::try_new(Path::new(&package.manifest_path))
|
||||
.map_err_op("create cargo_edit::LocalManifest")?,
|
||||
package,
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()
|
||||
.map(Manifests)
|
||||
}
|
||||
|
||||
/// Get the manifest specified by the manifest path. Try to make an educated
|
||||
/// guess if no path is provided.
|
||||
async fn get_local_one(manifest_path: &Option<PathBuf>) -> Result<Self, Error> {
|
||||
let cur_dir = env::current_dir().context("failed to get current directory")?;
|
||||
|
||||
let resolved_manifest_path: String = find(manifest_path)
|
||||
.map_err_op("invoke cargo_edit::find")?
|
||||
.to_string_lossy()
|
||||
.into();
|
||||
|
||||
let manifest = LocalManifest::find(manifest_path)
|
||||
.map_err_op("invoke cargo_edit::LocalManifeat::find")?;
|
||||
|
||||
let mut cmd = cargo_metadata::MetadataCommand::new();
|
||||
cmd.no_deps();
|
||||
if let Some(path) = manifest_path {
|
||||
cmd.manifest_path(path);
|
||||
}
|
||||
let result = cargo_metadata(cmd, &cur_dir).await?;
|
||||
|
||||
let packages = result.packages;
|
||||
let package = packages
|
||||
.iter()
|
||||
.find(|p| p.manifest_path == resolved_manifest_path)
|
||||
// If we have successfully got metadata, but our manifest path does not correspond to a
|
||||
// package, we must have been called against a virtual manifest.
|
||||
.with_context(|| {
|
||||
"Found virtual manifest, but this command requires running against an actual \
|
||||
package in this workspace. Try adding `--workspace`."
|
||||
})?;
|
||||
|
||||
Ok(Manifests(vec![(manifest, package.to_owned())]))
|
||||
}
|
||||
|
||||
/// Get the the combined set of dependencies to upgrade. If the user has
|
||||
/// specified per-dependency desired versions, extract those here.
|
||||
fn get_dependencies(
|
||||
&self,
|
||||
only_update: Vec<String>,
|
||||
exclude: Vec<String>,
|
||||
) -> Result<DesiredUpgrades, Error> {
|
||||
// Map the names of user-specified dependencies to the (optionally) requested
|
||||
// version.
|
||||
let selected_dependencies = only_update
|
||||
.into_iter()
|
||||
.map(|name| -> Result<_, Error> {
|
||||
if let Some(dependency) = CrateName::new(&name)
|
||||
.parse_as_version()
|
||||
.map_err_op("parse the version of dependency")?
|
||||
{
|
||||
Ok((
|
||||
dependency.name.clone(),
|
||||
dependency.version().map(String::from),
|
||||
))
|
||||
} else {
|
||||
Ok((name, None))
|
||||
}
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, _>>()?;
|
||||
|
||||
Ok(DesiredUpgrades(
|
||||
self.0
|
||||
.iter()
|
||||
.flat_map(|&(_, ref package)| package.dependencies.clone())
|
||||
.filter(is_version_dep)
|
||||
.filter(|dependency| !exclude.contains(&dependency.name))
|
||||
// Exclude renamed dependecies aswell
|
||||
.filter(|dependency| {
|
||||
dependency
|
||||
.rename
|
||||
.as_ref()
|
||||
.map_or(true, |rename| !exclude.contains(rename))
|
||||
})
|
||||
.filter_map(|dependency| {
|
||||
let is_prerelease = dependency.req.to_string().contains('-');
|
||||
if selected_dependencies.is_empty() {
|
||||
// User hasn't asked for any specific dependencies to be upgraded,
|
||||
// so upgrade all the dependencies.
|
||||
let mut dep = Dependency::new(&dependency.name);
|
||||
if let Some(rename) = dependency.rename {
|
||||
dep = dep.set_rename(&rename);
|
||||
}
|
||||
Some((
|
||||
dep,
|
||||
UpgradeMetadata {
|
||||
registry: dependency.registry,
|
||||
version: None,
|
||||
is_prerelease,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
// User has asked for specific dependencies. Check if this dependency
|
||||
// was specified, populating the registry from the lockfile metadata.
|
||||
match selected_dependencies.get(&dependency.name) {
|
||||
Some(version) => Some((
|
||||
Dependency::new(&dependency.name),
|
||||
UpgradeMetadata {
|
||||
registry: dependency.registry,
|
||||
version: version.clone(),
|
||||
is_prerelease,
|
||||
},
|
||||
)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Upgrade the manifests on disk following the previously-determined
|
||||
/// upgrade schema.
|
||||
fn upgrade(
|
||||
self,
|
||||
upgraded_deps: &ActualUpgrades,
|
||||
dry_run: bool,
|
||||
skip_compatible: bool,
|
||||
) -> Result<(), Error> {
|
||||
for (mut manifest, package) in self.0 {
|
||||
println!("{}:", package.name);
|
||||
|
||||
for (dep, version) in &upgraded_deps.0 {
|
||||
let mut new_dep = Dependency::new(&dep.name).set_version(version);
|
||||
if let Some(rename) = dep.rename() {
|
||||
new_dep = new_dep.set_rename(rename);
|
||||
}
|
||||
manifest
|
||||
.upgrade(&new_dep, dry_run, skip_compatible)
|
||||
.map_err_op("invoke cargo_edit::upgrade")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Some metadata about the dependency
|
||||
// we're trying to upgrade.
|
||||
struct UpgradeMetadata {
|
||||
registry: Option<String>,
|
||||
// `Some` if the user has specified an explicit
|
||||
// version to upgrade to.
|
||||
version: Option<String>,
|
||||
is_prerelease: bool,
|
||||
}
|
||||
|
||||
/// The set of dependencies to be upgraded, alongside the registries returned
|
||||
/// from cargo metadata, and the desired versions, if specified by the user.
|
||||
struct DesiredUpgrades(HashMap<Dependency, UpgradeMetadata>);
|
||||
|
||||
impl DesiredUpgrades {
|
||||
/// Transform the dependencies into their upgraded forms. If a version is
|
||||
/// specified, all dependencies will get that version.
|
||||
fn get_upgraded(
|
||||
self,
|
||||
allow_prerelease: bool,
|
||||
manifest_path: &Path,
|
||||
) -> Result<ActualUpgrades, Error> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.map(
|
||||
|(
|
||||
dep,
|
||||
UpgradeMetadata {
|
||||
registry,
|
||||
version,
|
||||
is_prerelease,
|
||||
},
|
||||
)| {
|
||||
let name = dep.name.clone();
|
||||
|
||||
if let Some(v) = version {
|
||||
Ok((dep, v))
|
||||
} else {
|
||||
let registry_url = match registry {
|
||||
Some(x) => Some(
|
||||
Url::parse(&x)
|
||||
.map_err(|err| anyhow!("invalid cargo config: {}", err))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let allow_prerelease = allow_prerelease || is_prerelease;
|
||||
get_latest_dependency(
|
||||
&dep.name,
|
||||
allow_prerelease,
|
||||
manifest_path,
|
||||
®istry_url,
|
||||
)
|
||||
.map(|new_dep| {
|
||||
(
|
||||
dep,
|
||||
new_dep
|
||||
.version()
|
||||
.expect("Invalid dependency type")
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
.map_err_op("invoke cargo_edit::get_latest_dependency")
|
||||
.with_context(|| format!("failed to get new version of {}", name))
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Result<_, _>>()
|
||||
.map(ActualUpgrades)
|
||||
}
|
||||
}
|
||||
|
||||
/// The complete specification of the upgrades that will be performed. Map of
|
||||
/// the dependency names to the new versions.
|
||||
struct ActualUpgrades(HashMap<Dependency, String>);
|
||||
|
||||
/// `cargo upgrade`, from `cargo-edit`.
|
||||
pub async fn upgrade_dep(crate_name: &str, workspace: bool) -> Result<(), Error> {
|
||||
let manifests = if workspace {
|
||||
Manifests::get_all(&None).await
|
||||
} else {
|
||||
Manifests::get_local_one(&None).await
|
||||
}
|
||||
.context("failed to fetch manifest for `cargo-edit`")?;
|
||||
|
||||
let existing_dependencies =
|
||||
manifests.get_dependencies(Default::default(), Default::default())?;
|
||||
|
||||
let upgraded_dependencies = existing_dependencies
|
||||
.get_upgraded(false, &find(&None).map_err_op("invoke cargo_edit::find")?)?;
|
||||
|
||||
manifests
|
||||
.upgrade(&upgraded_dependencies, false, false)
|
||||
.with_context(|| format!("failed to upgrade {}", crate_name))
|
||||
}
|
13
dev/cli/src/util/mod.rs
Normal file
13
dev/cli/src/util/mod.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use anyhow::{anyhow, Error};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub mod cargo;
|
||||
|
||||
pub(crate) trait CargoEditResultExt<T>: Into<cargo_edit::Result<T>> {
|
||||
fn map_err_op(self, op: impl Display) -> Result<T, Error> {
|
||||
self.into()
|
||||
.map_err(|err| anyhow!("failed to {}: {:?}", op, err))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CargoEditResultExt<T> for cargo_edit::Result<T> {}
|
@ -18,5 +18,4 @@ serde_json = "1.0.64"
|
||||
swc_atoms = {version = "0.2.7", path = "../atoms"}
|
||||
swc_common = {version = "0.13.0", path = "../common"}
|
||||
swc_ecma_ast = {version = "0.54.0", path = "../ecmascript/ast"}
|
||||
swc_ecma_utils = {version = "0.46.0", path = "../ecmascript/utils"}
|
||||
swc_ecma_visit = {version = "0.40.0", path = "../ecmascript/visit"}
|
||||
|
@ -6,12 +6,9 @@ use abi_stable::{
|
||||
std_types::{RResult, RStr, RString},
|
||||
StableAbi,
|
||||
};
|
||||
|
||||
pub mod ecmascript {
|
||||
pub extern crate swc_ecma_ast as ast;
|
||||
pub extern crate swc_ecma_utils as utils;
|
||||
pub extern crate swc_ecma_visit as visit;
|
||||
}
|
||||
use anyhow::Context;
|
||||
use serde::de::DeserializeOwned;
|
||||
use swc_ecma_ast::Program;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(StableAbi)]
|
||||
@ -30,3 +27,73 @@ impl RootModule for SwcPluginRef {
|
||||
const NAME: &'static str = "swc_plugin";
|
||||
const VERSION_STRINGS: VersionStrings = package_version_strings!();
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn invoke_js_plugin<C, F>(
|
||||
op: fn(C) -> F,
|
||||
config_json: RStr,
|
||||
ast_json: RString,
|
||||
) -> RResult<RString, RString>
|
||||
where
|
||||
C: DeserializeOwned,
|
||||
F: swc_ecma_visit::Fold,
|
||||
{
|
||||
use swc_ecma_visit::FoldWith;
|
||||
|
||||
let config = serde_json::from_str(config_json.as_str())
|
||||
.context("failed to deserialize config string as json");
|
||||
let config: C = match config {
|
||||
Ok(v) => v,
|
||||
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: Program = match ast {
|
||||
Ok(v) => v,
|
||||
Err(err) => return RResult::RErr(format!("{:?}", err).into()),
|
||||
};
|
||||
|
||||
let mut tr = op(config);
|
||||
|
||||
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
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
RResult::ROk(res.into())
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! define_js_plugin {
|
||||
($fn_name:ident) => {
|
||||
#[abi_stable::export_root_module]
|
||||
pub fn swc_library() -> $crate::SwcPluginRef {
|
||||
extern "C" fn swc_js_plugin(
|
||||
config_json: abi_stable::std_types::RStr,
|
||||
ast_json: abi_stable::std_types::RString,
|
||||
) -> abi_stable::std_types::RResult<
|
||||
abi_stable::std_types::RString,
|
||||
abi_stable::std_types::RString,
|
||||
> {
|
||||
$crate::invoke_js_plugin($fn_name, config_json, ast_json)
|
||||
}
|
||||
use abi_stable::prefix_type::PrefixTypeTrait;
|
||||
|
||||
$crate::SwcPlugin {
|
||||
process_js: Some(swc_js_plugin),
|
||||
}
|
||||
.leak_into_prefix()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
14
plugin/tests/js.rs
Normal file
14
plugin/tests/js.rs
Normal file
@ -0,0 +1,14 @@
|
||||
//! Ensure that worng macro definitions are catched by swc monorepo.
|
||||
|
||||
use swc_ecma_visit::Fold;
|
||||
use swc_plugin::define_js_plugin;
|
||||
|
||||
define_js_plugin!(drop_console);
|
||||
|
||||
fn drop_console(_: ()) -> impl Fold {
|
||||
DropConsole
|
||||
}
|
||||
|
||||
struct DropConsole;
|
||||
|
||||
impl Fold for DropConsole {}
|
4
scripts/cli.sh
Executable file
4
scripts/cli.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
cargo install --offline --debug --path cli
|
Loading…
Reference in New Issue
Block a user