Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ jobs:
with:
node-version-file: 'package.json'
- name: Build
run: cargo build --all
run: cargo build --all-features
- name: Run tests
run: cargo test --all
run: cargo test --all-features
- name: Run clippy
run: cargo clippy --all -- -D warnings
run: cargo clippy --all-features -- -D warnings
- name: Run fmt
run: cargo fmt --all -- --check
- name: Build and Test wasm
Expand Down
1 change: 1 addition & 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 @@ -18,6 +18,7 @@ swc = "21"
swc_core = { version = "22", features = ["ecma_plugin_transform","ecma_quote"] }
swc_ecma_parser = "11"
swc_ecma_visit = { version = "8", features = ["path"] }
sourcemap = "9"

# serde feature
serde = { version = "1", features = ["derive"], optional = true }
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
"types": "./pkg/orchestrion_js.d.ts",
"scripts": {
"build": "wasm-pack build --target nodejs --release -- --features wasm",
"test": "node ./tests/wasm/tests.mjs"
"test": "vitest run",
"test:watch": "vitest"
},
"devDependencies": {
"source-map": "^0.7.6",
"typescript": "^5.8.3",
"vitest": "^3.2.4",
"wasm-pack": "^0.13.1"
},
"volta": {
"node": "22.15.0"
}
}
}
54 changes: 45 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ use crate::error::OrchestrionError;
#[cfg(feature = "wasm")]
pub mod wasm;

/// Output of a transformation operation
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "lowercase")
)]
#[cfg_attr(
feature = "wasm",
derive(tsify::Tsify),
tsify(into_wasm_abi, from_wasm_abi)
)]
#[derive(Debug, Clone)]
pub struct TransformOutput {
/// The transformed JavaScript code
pub code: String,
/// The sourcemap for the transformation (if generated)
pub map: Option<String>,
}

/// This struct is responsible for managing all instrumentations. It's created from a YAML string
/// via the [`FromStr`] trait. See tests for examples, but by-and-large this just means you can
/// call `.parse()` on a YAML string to get an `Instrumentor` instance, if it's valid.
Expand Down Expand Up @@ -141,18 +160,25 @@ impl InstrumentationVisitor {
}
}

/// Transform the given JavaScript code.
/// Transform the given JavaScript code with sourcemap support.
/// # Errors
/// Returns an error if the transformation fails.
pub fn transform(
&mut self,
contents: &str,
is_module: IsModule,
) -> Result<String, Box<dyn Error>> {
sourcemap: Option<&str>,
) -> Result<TransformOutput, Box<dyn Error>> {
let compiler = Compiler::new(Arc::new(swc_core::common::SourceMap::new(
FilePathMapping::empty(),
)));

// Parse input sourcemap if provided
let sourcemap = sourcemap
.and_then(|input_map| sourcemap::SourceMap::from_slice(input_map.as_bytes()).ok());

let filename = sourcemap.as_ref().and_then(|map| map.get_file());

#[allow(clippy::redundant_closure_for_method_calls)]
let result = try_with_handler(
compiler.cm.clone(),
Expand All @@ -161,11 +187,15 @@ impl InstrumentationVisitor {
skip_filename: false,
},
|handler| {
let source_file = compiler.cm.new_source_file(
Arc::new(FileName::Real(PathBuf::from("index.mjs"))),
contents.to_string(),
let source_filename = filename.map_or_else(
|| Arc::new(FileName::Real(PathBuf::from("index.js"))),
|f| Arc::new(FileName::Real(PathBuf::from(f))),
);

let source_file = compiler
.cm
.new_source_file(source_filename, contents.to_string());

let program = compiler
.parse_js(
source_file.clone(),
Expand All @@ -184,18 +214,24 @@ impl InstrumentationVisitor {
program.visit_mut_with(self);
program
})?;

let enable_sourcemap = sourcemap.is_some();
let result = compiler.print(
&program,
PrintArgs {
source_file_name: None,
source_map: SourceMapsConfig::Bool(false),
source_file_name: filename,
source_map: SourceMapsConfig::Bool(enable_sourcemap),
comments: None,
emit_source_map_columns: false,
emit_source_map_columns: true,
orig: sourcemap.as_ref(),
..Default::default()
},
)?;

Ok(result.code)
Ok(TransformOutput {
code: result.code,
map: result.map,
})
},
)
.map_err(|e| e.to_pretty_error())?;
Expand Down
16 changes: 13 additions & 3 deletions src/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Config, InstrumentationConfig, InstrumentationVisitor, Instrumentor};
use crate::{Config, InstrumentationConfig, InstrumentationVisitor, Instrumentor, TransformOutput};
use std::path::PathBuf;
use swc::config::IsModule;
use wasm_bindgen::prelude::*;
Expand Down Expand Up @@ -58,15 +58,25 @@ pub struct Transformer(InstrumentationVisitor);

#[wasm_bindgen]
impl Transformer {
/// Transform the given JavaScript code with optional sourcemap support.
/// # Errors
/// Returns an error if the transformation fails to find injection points.
#[wasm_bindgen]
pub fn transform(&mut self, contents: &str, is_module: ModuleType) -> Result<String, JsError> {
#[allow(clippy::needless_pass_by_value)]
pub fn transform(
&mut self,
code: String,
module_type: ModuleType,
sourcemap: Option<String>,
) -> Result<TransformOutput, JsError> {
self.0
.transform(contents, is_module.into())
.transform(&code, module_type.into(), sourcemap.as_deref())
.map_err(|e| JsError::new(&e.to_string()))
}
}

#[wasm_bindgen]
#[must_use]
pub fn create(
configs: Vec<InstrumentationConfig>,
dc_module: Option<String>,
Expand Down
4 changes: 2 additions & 2 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ pub fn transpile_and_test(test_file: &str, mjs: bool, config: Config) {
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();

if let Ok(result) = instrumentations.transform(&contents, IsModule::Bool(mjs)) {
if let Ok(result) = instrumentations.transform(&contents, IsModule::Bool(mjs), None) {
let instrumented_file = test_dir.join(format!("instrumented.{}", extension));
let mut file = std::fs::File::create(&instrumented_file).unwrap();
file.write_all(result.as_bytes()).unwrap();
file.write_all(result.code.as_bytes()).unwrap();
}

let test_file = format!("test.{}", extension);
Expand Down
154 changes: 154 additions & 0 deletions tests/wasm/__snapshots__/tests.test.mjs.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Orchestrion JS Transformer > should transform CommonJS module correctly 1`] = `
{
"code": "const { tracingChannel: tr_ch_apm_tracingChannel } = require("diagnostics_channel");
const tr_ch_apm$up_fetch = tr_ch_apm_tracingChannel("orchestrion:one:up:fetch");
const tr_ch_apm$up_constructor = tr_ch_apm_tracingChannel("orchestrion:one:up:constructor");
module.exports = class Up {
constructor(){
const tr_ch_apm_ctx$up_constructor = {
arguments
};
try {
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm$up_constructor.start.publish(tr_ch_apm_ctx$up_constructor);
}
console.log('constructor');
} catch (tr_ch_err) {
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm_ctx$up_constructor.error = tr_ch_err;
try {
tr_ch_apm_ctx$up_constructor.self = this;
} catch (refErr) {}
tr_ch_apm$up_constructor.error.publish(tr_ch_apm_ctx$up_constructor);
}
throw tr_ch_err;
} finally{
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm_ctx$up_constructor.self = this;
tr_ch_apm$up_constructor.end.publish(tr_ch_apm_ctx$up_constructor);
}
}
}
fetch() {
const __apm$original_args = arguments;
const __apm$traced = ()=>{
const __apm$wrapped = ()=>{
console.log('fetch');
};
return __apm$wrapped.apply(null, __apm$original_args);
};
if (!tr_ch_apm$up_fetch.hasSubscribers) return __apm$traced();
return tr_ch_apm$up_fetch.traceSync(__apm$traced, {
arguments,
self: this
});
}
};
",
"map": undefined,
}
`;

exports[`Orchestrion JS Transformer > should transform ESM module correctly 1`] = `
{
"code": "import { tracingChannel as tr_ch_apm_tracingChannel } from "diagnostics_channel";
const tr_ch_apm$up_fetch = tr_ch_apm_tracingChannel("orchestrion:one:up:fetch");
const tr_ch_apm$up_constructor = tr_ch_apm_tracingChannel("orchestrion:one:up:constructor");
export class Up {
constructor(){
const tr_ch_apm_ctx$up_constructor = {
arguments
};
try {
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm$up_constructor.start.publish(tr_ch_apm_ctx$up_constructor);
}
console.log('constructor');
} catch (tr_ch_err) {
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm_ctx$up_constructor.error = tr_ch_err;
try {
tr_ch_apm_ctx$up_constructor.self = this;
} catch (refErr) {}
tr_ch_apm$up_constructor.error.publish(tr_ch_apm_ctx$up_constructor);
}
throw tr_ch_err;
} finally{
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm_ctx$up_constructor.self = this;
tr_ch_apm$up_constructor.end.publish(tr_ch_apm_ctx$up_constructor);
}
}
}
fetch() {
const __apm$original_args = arguments;
const __apm$traced = ()=>{
const __apm$wrapped = ()=>{
console.log('fetch');
};
return __apm$wrapped.apply(null, __apm$original_args);
};
if (!tr_ch_apm$up_fetch.hasSubscribers) return __apm$traced();
return tr_ch_apm$up_fetch.traceSync(__apm$traced, {
arguments,
self: this
});
}
}
",
"map": undefined,
}
`;

exports[`Orchestrion JS Transformer > should transform TypeScript with source map correctly 1`] = `
{
"code": "import { tracingChannel as tr_ch_apm_tracingChannel } from "diagnostics_channel";
const tr_ch_apm$up_fetch = tr_ch_apm_tracingChannel("orchestrion:one:up:fetch");
const tr_ch_apm$up_constructor = tr_ch_apm_tracingChannel("orchestrion:one:up:constructor");
export class Up {
constructor(){
const tr_ch_apm_ctx$up_constructor = {
arguments
};
try {
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm$up_constructor.start.publish(tr_ch_apm_ctx$up_constructor);
}
console.log('constructor');
} catch (tr_ch_err) {
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm_ctx$up_constructor.error = tr_ch_err;
try {
tr_ch_apm_ctx$up_constructor.self = this;
} catch (refErr) {}
tr_ch_apm$up_constructor.error.publish(tr_ch_apm_ctx$up_constructor);
}
throw tr_ch_err;
} finally{
if (tr_ch_apm$up_constructor.hasSubscribers) {
tr_ch_apm_ctx$up_constructor.self = this;
tr_ch_apm$up_constructor.end.publish(tr_ch_apm_ctx$up_constructor);
}
}
}
fetch(url) {
const __apm$original_args = arguments;
const __apm$traced = ()=>{
const __apm$wrapped = (url)=>{
console.log('fetch');
};
return __apm$wrapped.apply(null, __apm$original_args);
};
if (!tr_ch_apm$up_fetch.hasSubscribers) return __apm$traced();
return tr_ch_apm$up_fetch.traceSync(__apm$traced, {
arguments,
self: this
});
}
}
",
"map": "{"version":3,"file":"module.js","sources":["module.ts"],"sourceRoot":"","names":[],"mappings":";;;AAEA,MAAM,CAAA,MAAO,EAAE;IACX,aAAA;;;;;;;;YACI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;;;;;;;;;;;;;;;;IAC/B,CAAC;IACD,KAAK,IAAS,EAAA;;;mCAAR;gBACF,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;;;;;;;;;IACzB,CAAC;CACJ"}",
}
`;
44 changes: 0 additions & 44 deletions tests/wasm/testdata/expected-cjs.js

This file was deleted.

Loading
Loading