Skip to content

Commit 22d069d

Browse files
authored
feat: Added support for wrapping private class methods
2 parents 01a3322 + e09e5af commit 22d069d

10 files changed

Lines changed: 183 additions & 11 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ type FunctionQuery =
129129
{ functionName: string; kind: FunctionKind; index?: number; isExportAlias?: boolean }
130130
| // Match arrow function or function expression
131131
{ expressionName: string; kind: FunctionKind; index?: number; isExportAlias?: boolean };
132+
| // Match private class methods
133+
{ className: string; privateMethodName: string; kind: FunctionKind; index?: number };
132134
```
133135

134136
#### **`ModuleMatcher`**

src/function_query.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub(crate) enum FunctionType {
1010
FunctionDeclaration,
1111
FunctionExpression,
1212
Method,
13+
PrivateMethod,
1314
}
1415

1516
/// The kind of function - Sync or returns a promise
@@ -58,6 +59,14 @@ pub enum FunctionQuery {
5859
#[cfg_attr(feature = "wasm", tsify(optional))]
5960
is_export_alias: bool,
6061
},
62+
PrivateMethod {
63+
class_name: String,
64+
private_method_name: String,
65+
kind: FunctionKind,
66+
#[cfg_attr(feature = "serde", serde(default))]
67+
#[cfg_attr(feature = "wasm", tsify(optional))]
68+
index: usize,
69+
},
6170
ClassConstructor {
6271
class_name: String,
6372
#[cfg_attr(feature = "serde", serde(default))]
@@ -146,6 +155,16 @@ impl FunctionQuery {
146155
}
147156
}
148157

158+
#[must_use]
159+
pub fn private_method(class_name: &str, private_method_name: &str, kind: FunctionKind) -> Self {
160+
FunctionQuery::PrivateMethod {
161+
class_name: class_name.to_string(),
162+
private_method_name: private_method_name.to_string(),
163+
kind,
164+
index: 0,
165+
}
166+
}
167+
149168
#[must_use]
150169
pub fn as_export_alias(mut self) -> Self {
151170
match &mut self {
@@ -161,7 +180,7 @@ impl FunctionQuery {
161180
| FunctionQuery::FunctionExpression {
162181
is_export_alias, ..
163182
} => *is_export_alias = true,
164-
FunctionQuery::ObjectMethod { .. } => {}
183+
FunctionQuery::ObjectMethod { .. } | FunctionQuery::PrivateMethod { .. } => {}
165184
}
166185
self
167186
}
@@ -172,7 +191,8 @@ impl FunctionQuery {
172191
FunctionQuery::ClassMethod { kind, .. }
173192
| FunctionQuery::ObjectMethod { kind, .. }
174193
| FunctionQuery::FunctionDeclaration { kind, .. }
175-
| FunctionQuery::FunctionExpression { kind, .. } => kind,
194+
| FunctionQuery::FunctionExpression { kind, .. }
195+
| FunctionQuery::PrivateMethod { kind, .. } => kind,
176196
}
177197
}
178198

@@ -181,6 +201,10 @@ impl FunctionQuery {
181201
FunctionQuery::ClassConstructor { .. } => "constructor",
182202
FunctionQuery::ClassMethod { method_name, .. }
183203
| FunctionQuery::ObjectMethod { method_name, .. } => method_name,
204+
FunctionQuery::PrivateMethod {
205+
private_method_name,
206+
..
207+
} => private_method_name,
184208
FunctionQuery::FunctionDeclaration { function_name, .. } => function_name,
185209
FunctionQuery::FunctionExpression {
186210
expression_name, ..
@@ -195,6 +219,7 @@ impl FunctionQuery {
195219
| FunctionQuery::ObjectMethod { .. } => FunctionType::Method,
196220
FunctionQuery::FunctionDeclaration { .. } => FunctionType::FunctionDeclaration,
197221
FunctionQuery::FunctionExpression { .. } => FunctionType::FunctionExpression,
222+
FunctionQuery::PrivateMethod { .. } => FunctionType::PrivateMethod,
198223
}
199224
}
200225

@@ -205,15 +230,17 @@ impl FunctionQuery {
205230
| FunctionQuery::ClassMethod { index, .. }
206231
| FunctionQuery::ObjectMethod { index, .. }
207232
| FunctionQuery::FunctionDeclaration { index, .. }
208-
| FunctionQuery::FunctionExpression { index, .. } => *index,
233+
| FunctionQuery::FunctionExpression { index, .. }
234+
| FunctionQuery::PrivateMethod { index, .. } => *index,
209235
}
210236
}
211237

212238
#[must_use]
213239
pub(crate) fn class_name(&self) -> Option<&str> {
214240
match self {
215241
FunctionQuery::ClassConstructor { class_name, .. }
216-
| FunctionQuery::ClassMethod { class_name, .. } => Some(class_name),
242+
| FunctionQuery::ClassMethod { class_name, .. }
243+
| FunctionQuery::PrivateMethod { class_name, .. } => Some(class_name),
217244
_ => None,
218245
}
219246
}
@@ -240,7 +267,7 @@ impl FunctionQuery {
240267
is_export_alias,
241268
..
242269
} => Some((expression_name, *is_export_alias)),
243-
FunctionQuery::ObjectMethod { .. } => None,
270+
FunctionQuery::ObjectMethod { .. } | FunctionQuery::PrivateMethod { .. } => None,
244271
}
245272
}
246273

@@ -282,4 +309,10 @@ impl FunctionQuery {
282309
matches!(self.typ(), FunctionType::Method) && name == self.name();
283310
self.maybe_increment_count(matches_except_count, count)
284311
}
312+
313+
pub fn matches_private_method(&self, count: &mut usize, name: &str) -> bool {
314+
let matches_except_count =
315+
matches!(self.typ(), FunctionType::PrivateMethod) && name == self.name();
316+
self.maybe_increment_count(matches_except_count, count)
317+
}
285318
}

src/instrumentation.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use swc_core::ecma::{
1010
ast::{
1111
ArrowExpr, AssignExpr, AssignTarget, BlockStmt, ClassDecl, ClassExpr, ClassMethod,
1212
Constructor, Expr, FnDecl, FnExpr, Ident, Lit, MemberProp, MethodProp, Module, ModuleItem,
13-
Param, Pat, PropName, Script, SimpleAssignTarget, Stmt, Str, VarDecl,
13+
Param, Pat, PrivateMethod, PropName, Script, SimpleAssignTarget, Stmt, Str, VarDecl,
1414
},
1515
atoms::Atom,
1616
};
@@ -335,6 +335,27 @@ impl Instrumentation {
335335
true
336336
}
337337

338+
pub fn visit_mut_private_method(&mut self, node: &mut PrivateMethod) -> bool {
339+
let name = node.key.name.clone();
340+
341+
// Only increment count when class matches
342+
if !self.is_correct_class {
343+
return true;
344+
}
345+
346+
if self
347+
.config
348+
.function_query
349+
.matches_private_method(&mut self.count, name.as_ref())
350+
&& node.function.body.is_some()
351+
{
352+
if let Some(body) = node.function.body.as_mut() {
353+
self.insert_tracing(body, &node.function.params, node.function.is_async);
354+
}
355+
}
356+
true
357+
}
358+
338359
pub fn visit_mut_constructor(&mut self, node: &mut Constructor) -> bool {
339360
if !self.is_correct_class || self.config.function_query.name() != "constructor" {
340361
return false;

src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ use swc_core::{
2929
ecma::{
3030
ast::{
3131
AssignExpr, ClassDecl, ClassExpr, ClassMethod, Constructor, EsVersion, ExportSpecifier,
32-
FnDecl, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, Script, Str,
33-
VarDecl,
32+
FnDecl, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, PrivateMethod,
33+
Script, Str, VarDecl,
3434
},
3535
visit::{VisitMut, VisitMutWith},
3636
},
@@ -352,5 +352,6 @@ impl VisitMut for InstrumentationVisitor {
352352
visit_with_all_fn!(visit_mut_class_decl, ClassDecl);
353353
visit_with_all_fn!(visit_mut_class_expr, ClassExpr);
354354
visit_with_all_fn!(visit_mut_class_method, ClassMethod);
355+
visit_with_all_fn!(visit_mut_private_method, PrivateMethod);
355356
visit_with_all_fn!(visit_mut_constructor, Constructor);
356357
}

tests/instrumentor_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ mod nested_functions;
2424
mod object_method_cjs;
2525
mod polyfill_cjs;
2626
mod polyfill_mjs;
27+
mod private_method_cjs;

tests/private_method_cjs/mod.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2025 Datadog, Inc.
4+
**/
5+
class TestClass {
6+
constructor() {
7+
this.prop = true
8+
}
9+
10+
async #testMe() {
11+
return 42
12+
}
13+
14+
async testMe() {
15+
return this.#testMe()
16+
}
17+
}
18+
19+
module.exports = TestClass

tests/private_method_cjs/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use crate::common::*;
2+
use orchestrion_js::*;
3+
4+
#[test]
5+
fn private_method_cjs() {
6+
transpile_and_test(
7+
file!(),
8+
false,
9+
Config::new_single(InstrumentationConfig::new(
10+
"TestClass:testMe",
11+
test_module_matcher(),
12+
FunctionQuery::private_method("TestClass", "testMe", FunctionKind::Async),
13+
)),
14+
);
15+
}

tests/private_method_cjs/test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2025 Datadog, Inc.
4+
**/
5+
const TestClass = require('./instrumented.js');
6+
const { assert, getContext } = require('../common/preamble.js');
7+
const context = getContext('orchestrion:undici:TestClass:testMe');
8+
(async () => {
9+
const test = new TestClass;
10+
const result = await test.testMe()
11+
assert.strictEqual(result, 42);
12+
assert.deepStrictEqual(context, {
13+
start: true,
14+
end: true,
15+
asyncStart: 42,
16+
asyncEnd: 42
17+
});
18+
})();

tests/wasm/__snapshots__/tests.test.mjs.snap

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
exports[`Orchestrion JS Transformer > should transform CommonJS module correctly 1`] = `
44
{
55
"code": "const { tracingChannel: tr_ch_apm_tracingChannel } = require("diagnostics_channel");
6+
const tr_ch_apm$up_privateSend = tr_ch_apm_tracingChannel("orchestrion:one:up:privateSend");
67
const tr_ch_apm$up_asyncGet = tr_ch_apm_tracingChannel("orchestrion:one:up:asyncGet");
78
const tr_ch_apm$up_asyncFetch = tr_ch_apm_tracingChannel("orchestrion:one:up:asyncFetch");
89
const tr_ch_apm$up_fetch = tr_ch_apm_tracingChannel("orchestrion:one:up:fetch");
@@ -75,6 +76,20 @@ module.exports = class Up {
7576
moduleVersion: "1.0.0"
7677
});
7778
}
79+
async #send() {
80+
const __apm$original_args = arguments;
81+
const __apm$traced = async ()=>{
82+
const __apm$wrapped = async ()=>{};
83+
return __apm$wrapped.apply(null, __apm$original_args);
84+
};
85+
if (!tr_ch_apm$up_privateSend.hasSubscribers) return __apm$traced();
86+
return tr_ch_apm$up_privateSend.tracePromise(__apm$traced, {
87+
arguments,
88+
self: this,
89+
moduleVersion: "1.0.0"
90+
});
91+
}
92+
async send() {}
7893
};
7994
",
8095
"map": undefined,
@@ -84,6 +99,7 @@ module.exports = class Up {
8499
exports[`Orchestrion JS Transformer > should transform ESM module correctly 1`] = `
85100
{
86101
"code": "import { tracingChannel as tr_ch_apm_tracingChannel } from "diagnostics_channel";
102+
const tr_ch_apm$up_privateSend = tr_ch_apm_tracingChannel("orchestrion:one:up:privateSend");
87103
const tr_ch_apm$up_asyncGet = tr_ch_apm_tracingChannel("orchestrion:one:up:asyncGet");
88104
const tr_ch_apm$up_asyncFetch = tr_ch_apm_tracingChannel("orchestrion:one:up:asyncFetch");
89105
const tr_ch_apm$up_fetch = tr_ch_apm_tracingChannel("orchestrion:one:up:fetch");
@@ -156,6 +172,20 @@ export class Up {
156172
moduleVersion: "1.0.0"
157173
});
158174
}
175+
async #send() {
176+
const __apm$original_args = arguments;
177+
const __apm$traced = async ()=>{
178+
const __apm$wrapped = async ()=>{};
179+
return __apm$wrapped.apply(null, __apm$original_args);
180+
};
181+
if (!tr_ch_apm$up_privateSend.hasSubscribers) return __apm$traced();
182+
return tr_ch_apm$up_privateSend.tracePromise(__apm$traced, {
183+
arguments,
184+
self: this,
185+
moduleVersion: "1.0.0"
186+
});
187+
}
188+
async send() {}
159189
}
160190
",
161191
"map": undefined,
@@ -165,6 +195,7 @@ export class Up {
165195
exports[`Orchestrion JS Transformer > should transform TypeScript with source map correctly 1`] = `
166196
{
167197
"code": "import { tracingChannel as tr_ch_apm_tracingChannel } from "diagnostics_channel";
198+
const tr_ch_apm$up_privateSend = tr_ch_apm_tracingChannel("orchestrion:one:up:privateSend");
168199
const tr_ch_apm$up_asyncGet = tr_ch_apm_tracingChannel("orchestrion:one:up:asyncGet");
169200
const tr_ch_apm$up_asyncFetch = tr_ch_apm_tracingChannel("orchestrion:one:up:asyncFetch");
170201
const tr_ch_apm$up_fetch = tr_ch_apm_tracingChannel("orchestrion:one:up:fetch");
@@ -237,8 +268,22 @@ export class Up {
237268
moduleVersion: "1.0.0"
238269
});
239270
}
271+
async #send() {
272+
const __apm$original_args = arguments;
273+
const __apm$traced = async ()=>{
274+
const __apm$wrapped = async ()=>{};
275+
return __apm$wrapped.apply(null, __apm$original_args);
276+
};
277+
if (!tr_ch_apm$up_privateSend.hasSubscribers) return __apm$traced();
278+
return tr_ch_apm$up_privateSend.tracePromise(__apm$traced, {
279+
arguments,
280+
self: this,
281+
moduleVersion: "1.0.0"
282+
});
283+
}
284+
async send() {}
240285
}
241286
",
242-
"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;IACD,KAAK,CAAC,UAAU,GAAA;;;;;;;;;;;;IAAU,CAAC;IAC3B,GAAG,GAAA;;;;;;;;;;;;IAAU,CAAC;CACjB"}",
287+
"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;YACzB,CAAC;;;;;;;;;;IACD,KAAK,CAAC,UAAU,GAAA;;;;;;;;;;;;IAAU,CAAC;IAC3B,GAAG,GAAA;;;;;;;;;;;;IAAU,CAAC;IACd,KAAK,EAAC,IAAK;;;;;;;;;;;;IAAU,CAAC;IACtB,KAAK,CAAC,IAAI,GAAA,CAAU,CAAC;CACxB"}",
243288
}
244289
`;

tests/wasm/tests.test.mjs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ describe('Orchestrion JS Transformer', () => {
3737
kind: "Async",
3838
},
3939
},
40+
{
41+
channelName: "up:privateSend",
42+
module: { name: "one", versionRange: ">=1", filePath: "index.js" },
43+
functionQuery: {
44+
className: "Up",
45+
privateMethodName: "send",
46+
kind: "Async",
47+
},
48+
},
4049
]);
4150

4251
const matchedTransforms = instrumentor.getTransformer(
@@ -61,6 +70,8 @@ describe('Orchestrion JS Transformer', () => {
6170
6271
async asyncFetch() {}
6372
get() {}
73+
async #send() {}
74+
async send() {}
6475
}`;
6576

6677
const output = matchedTransforms.transform(originalEsm, 'esm');
@@ -79,6 +90,8 @@ describe('Orchestrion JS Transformer', () => {
7990
8091
async asyncFetch() {}
8192
get() {}
93+
async #send() {}
94+
async send() {}
8295
}
8396
8497
`;
@@ -98,6 +111,8 @@ export class Up {
98111
}
99112
async asyncFetch(): void {}
100113
get(): void {}
114+
async #send(): void {}
115+
async send(): void {}
101116
}`;
102117

103118
const { outputText: outputJavaScript, sourceMapText: originalTypescriptSourceMap } = tsc.transpileModule(originalTypescript, {
@@ -125,7 +140,7 @@ export class Up {
125140
});
126141

127142
// This is the position of the fetch function in the original TypeScript
128-
expect(originalPosition.line).toEqual(7);
143+
expect(originalPosition.line).toEqual(6);
129144
expect(originalPosition.column).toEqual(4);
130145

131146
sourceMapConsumer.destroy();
@@ -142,10 +157,12 @@ export class Up {
142157
}
143158
async asyncFetch() {}
144159
get() {}
160+
async #send() {}
161+
async send() {}
145162
}`;
146163

147164
expect(() => {
148165
matchedTransforms.transform(noMatchSource, 'unknown');
149-
}).toThrow('Failed to find injection points for: ["constructor", "fetch", "asyncFetch", "get"]');
166+
}).toThrow('Failed to find injection points for: ["constructor", "fetch", "asyncFetch", "get", "send"]');
150167
});
151168
});

0 commit comments

Comments
 (0)