使用 SWC Rust 插件注入注释

问题描述 投票:0回答:1

我正在尝试向现有的 javascript 代码注入注释。

在 Babel 中,有

addComment
助手,可以在 AST 节点上调用。

这是一个用 Babel 编写的简单(但相当愚蠢)的转换:

console.log`Hello, ${name}!`;
->
/*#__PURE__*/console.log("Hello, ", name, "!");

const babel = require("@babel/core");

function run(code) {
  const result = babel.transform(code, {
    plugins: [
      function transform({ types: t }) {
        return {
          visitor: {
            TaggedTemplateExpression(path) {
              const { tag, quasi } = path.node;

              if (
                t.isMemberExpression(tag) &&
                t.isIdentifier(tag.object, { name: "console" }) &&
                t.isIdentifier(tag.property, { name: "log" })
              ) {
                let args = [];

                quasi.quasis.forEach((element, index) => {
                  args.push(t.stringLiteral(element.value.raw));
                  if (index < quasi.expressions.length) {
                    args.push(quasi.expressions[index]);
                  }
                });

                path.replaceWith(
                  t.callExpression(
                    t.addComment(
                      t.memberExpression(
                        t.identifier("console"),
                        t.identifier("log")
                      ),
                      "leading",
                      "#__PURE__"
                    ),
                    args
                  )
                );
              }
            },
          },
        };
      },
    ],
  });
  return result.code;
}

const code = "console.log`Hello, ${name}!`;";
console.log(run(code));
// -> /*#__PURE__*/console.log("Hello, ", name, "!");

然而,在 Rust 中,事情有点复杂,因为只有一个变量可以拥有数据,并且除了 SWC 实现了一些性能技巧之外,最多还有一个对其的可变引用。

因此,在 SWC 中,您必须使用 PluginCommentsProxy,这在当前 SWC 版本 0.279.0 中通过以下流程图进行了解释:

 Below diagram shows one reference example how guest does trampoline between
 host's memory space.
┌───────────────────────────────────────┐    ┌─────────────────────────────────────────────┐
│Host (SWC/core)                        │    │Plugin (wasm)                                │
│  ┌────────────────────────────────┐   │    │                                             │
│  │COMMENTS.with()                 │   │    │  ┌──────────────────────────────────────┐   │
│  │                                │   │    │  │PluginCommentsProxy                   │   │
│  │                                │   │    │  │                                      │   │
│  │ ┌────────────────────────────┐ │   │    │  │ ┌────────────────────────────────┐   │   │
│  │ │get_leading_comments_proxy()│◀┼───┼────┼──┼─┤get_leading()                   │   │   │
│  │ │                            │ │   │    │  │ │                                │   │   │
│  │ │                            │ │   │    │  │ │ ┌──────────────────────────┐   │   │   │
│  │ │                            │─┼───┼──┬─┼──┼─┼─▶AllocatedBytesPtr(p,len)  │   │   │   │
│  │ └────────────────────────────┘ │   │  │ │  │ │ │                          │   │   │   │
│  │                                │   │  │ │  │ │ └─────────────┬────────────┘   │   │   │
│  │                                │   │  │ │  │ │               │                │   │   │
│  │                                │   │  │ │  │ │ ┌─────────────▼────────────┐   │   │   │
│  └────────────────────────────────┘   │  │ │  │ │ │Vec<Comments>             │   │   │   │
│                                       │  └─┼──┼─┼─▶                          │   │   │   │
│                                       │    │  │ │ └──────────────────────────┘   │   │   │
│                                       │    │  │ └────────────────────────────────┘   │   │
│                                       │    │  └──────────────────────────────────────┘   │
└───────────────────────────────────────┘    └─────────────────────────────────────────────┘

 1. Plugin calls `PluginCommentsProxy::get_leading()`. PluginCommentsProxy is
 a struct constructed in plugin's memory space.
 2. `get_leading()` internally calls `__get_leading_comments_proxy`, which is
 imported fn `get_leading_comments_proxy` exists in the host.
 3. Host access necessary values in its memory space (COMMENTS)
 4. Host copies value to be returned into plugin's memory space. Memory
 allocation for the value should be manually performed.
 5. Host completes imported fn, `PluginCommentsProxy::get_leading()` now can
 read, deserialize memory host wrote.
 - In case of `get_leading`, returned value is non-deterministic vec
 (`Vec<Comments>`) guest cannot preallocate with specific length. Instead,
 guest passes a fixed size struct (AllocatedBytesPtr), once host allocates
 actual vec into guest it'll write pointer to the vec into the struct.
comments.add_leading(
  node.span.lo,
  Comment {
    kind: swc_core::common::comments::CommentKind::Block,
    span: DUMMY_SP,
    text: "#__PURE__".to_string(),
  },
)

不幸的是我无法用

swc_core::ecma::transforms::testing
正确测试它。

#[cfg(test)]

mod tests {
  use super::*;
  use std::path::PathBuf;
  use swc_core::ecma::transforms::testing::{test_fixture};
  use swc_ecma_transforms_testing::{FixtureTestConfig};

  #[testing::fixture("tests/fixture/**/input.tsx")]
  fn fixture(input: PathBuf) {
    test_fixture(
      Default::default(),
      &|tester| as_folder(TransformVisitor::new(&tester.comments)),
      &input,
      &input.with_file_name("output.tsx"),
      FixtureTestConfig::default(),
    );
  }
}

不幸的是,这不起作用,因为

tester.comments
 Rc<SingleThreadedComments>
类型。

我看到了使用

<C>
的示例,例如 MillionJs 变压器

fn transform_block<C>(context: &ProgramStateContext, node: &mut CallExpr, comments: C)
where
    C: Comments,
{

理想情况下,测试应该反映代码在生产中的使用方式。添加仅用于测试的泛型类型参数会使代码更难以阅读,并且对我来说感觉不对。

有更好的方法吗?

javascript rust babeljs swc
1个回答
0
投票

SWC 作者在此。

您可以使您的转换器变得通用

C: Comments
,就像官方的pure_annotations pass一样。然后像其他仿制药一样存储
C

struct PureAnnotations<C>
where
    C: Comments,
{
    imports: AHashMap<Id, (JsWord, JsWord)>,
    comments: Option<C>,
}

之后,您应该使 impl 部分变得通用

C

impl<C> VisitMut for PureAnnotations<C>
where
    C: Comments,
{
    noop_visit_mut_type!();

}

您可以添加适当的访问者方法来实现您的目标。

或者,您可以接受

&dyn Comments
Option<&dyn Comments>
。官方
fixer
pass
使用此模式来减少二进制大小。在这种情况下,您应该在构造函数返回类型中的
'_
impl
之间添加
Fold

pub fn fixer(comments: Option<&dyn Comments>) -> impl '_ + Fold + VisitMut {
    as_folder(Fixer {
        comments,
        ctx: Default::default(),
        span_map: Default::default(),
        in_for_stmt_head: Default::default(),
        in_opt_chain: Default::default(),
        remove_only: false,
    })
}
© www.soinside.com 2019 - 2024. All rights reserved.