use crate::ir::{FrameCategory, FrameKind};
pub struct FrameClassifier {
app_base_path: Option<String>,
}
impl FrameClassifier {
pub fn new(app_base_path: Option<String>) -> Self {
Self { app_base_path }
}
pub fn classify_kind(&self, name: &str, url: &str) -> FrameKind {
if name.starts_with('(') && name.ends_with(')') {
return match name {
"(garbage collector)" => FrameKind::GC,
"(idle)" => FrameKind::Idle,
"(program)" => FrameKind::Program,
_ => FrameKind::Native,
};
}
if name.contains("Builtin:") || name == "(native)" {
return FrameKind::Builtin;
}
if url.contains("eval at") || name.contains("eval") {
return FrameKind::Eval;
}
if url.starts_with("wasm://") || name.starts_with("wasm-") {
return FrameKind::Wasm;
}
if name.starts_with("RegExp:") {
return FrameKind::RegExp;
}
if url.is_empty() && !name.is_empty() {
return FrameKind::Native;
}
FrameKind::Function
}
pub fn classify_category(&self, url: &str, name: &str) -> FrameCategory {
if url.starts_with("node:") || url.contains("internal/") {
return FrameCategory::NodeInternal;
}
if name.starts_with('(') && name.ends_with(')') {
match name {
"(garbage collector)" | "(idle)" | "(program)" | "(root)" => {
return FrameCategory::V8Internal;
}
_ => {}
}
}
if url.is_empty() || name.contains("Builtin:") || name == "(native)" {
return FrameCategory::Native;
}
if url.contains("node_modules") {
return FrameCategory::Deps;
}
if let Some(base) = &self.app_base_path
&& !url.is_empty()
&& !url.starts_with(base)
&& !url.starts_with("file://")
&& (url.contains("/dist/") || url.contains("/build/"))
{
return FrameCategory::App; }
FrameCategory::App
}
pub fn classify(&self, url: &str, name: &str) -> (FrameKind, FrameCategory) {
(
self.classify_kind(name, url),
self.classify_category(url, name),
)
}
}
impl Default for FrameClassifier {
fn default() -> Self {
Self::new(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gc_classification() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_kind("(garbage collector)", ""),
FrameKind::GC
);
assert_eq!(
classifier.classify_category("", "(garbage collector)"),
FrameCategory::V8Internal
);
}
#[test]
fn test_idle_classification() {
let classifier = FrameClassifier::default();
assert_eq!(classifier.classify_kind("(idle)", ""), FrameKind::Idle);
assert_eq!(
classifier.classify_category("", "(idle)"),
FrameCategory::V8Internal
);
}
#[test]
fn test_program_classification() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_kind("(program)", ""),
FrameKind::Program
);
assert_eq!(
classifier.classify_category("", "(program)"),
FrameCategory::V8Internal
);
}
#[test]
fn test_root_classification() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_category("", "(root)"),
FrameCategory::V8Internal
);
}
#[test]
fn test_builtin_classification() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_kind("Builtin:ArrayPush", ""),
FrameKind::Builtin
);
assert_eq!(classifier.classify_kind("(native)", ""), FrameKind::Native);
}
#[test]
fn test_eval_classification() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_kind("anonymous", "eval at <anonymous>"),
FrameKind::Eval
);
assert_eq!(
classifier.classify_kind("eval", "/src/main.js"),
FrameKind::Eval
);
}
#[test]
fn test_wasm_classification() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_kind("funcName", "wasm://wasm/123456"),
FrameKind::Wasm
);
assert_eq!(
classifier.classify_kind("wasm-function[42]", ""),
FrameKind::Wasm
);
}
#[test]
fn test_regexp_classification() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_kind("RegExp: /\\d+/", ""),
FrameKind::RegExp
);
}
#[test]
fn test_native_no_url() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_kind("nativeFunction", ""),
FrameKind::Native
);
}
#[test]
fn test_regular_function() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_kind("myFunction", "/src/main.js"),
FrameKind::Function
);
}
#[test]
fn test_node_internal() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_category("node:fs", "readFile"),
FrameCategory::NodeInternal
);
assert_eq!(
classifier.classify_category("node:internal/modules/cjs/loader", "load"),
FrameCategory::NodeInternal
);
assert_eq!(
classifier.classify_category("/internal/bootstrap.js", "startup"),
FrameCategory::NodeInternal
);
}
#[test]
fn test_deps_classification() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_category("/project/node_modules/lodash/index.js", "map"),
FrameCategory::Deps
);
assert_eq!(
classifier.classify_category("/node_modules/@babel/core/lib/index.js", "transform"),
FrameCategory::Deps
);
assert_eq!(
classifier.classify_category(
"file:///Users/test/project/node_modules/vitest/dist/index.js",
"run"
),
FrameCategory::Deps
);
}
#[test]
fn test_app_code() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_category("/project/src/main.ts", "processData"),
FrameCategory::App
);
assert_eq!(
classifier.classify_category("file:///Users/test/project/src/utils.js", "helper"),
FrameCategory::App
);
}
#[test]
fn test_native_category() {
let classifier = FrameClassifier::default();
assert_eq!(
classifier.classify_category("", "someFunction"),
FrameCategory::Native
);
assert_eq!(
classifier.classify_category("", "Builtin:ArrayPush"),
FrameCategory::Native
);
}
#[test]
fn test_classify_combined() {
let classifier = FrameClassifier::default();
let (kind, category) = classifier.classify("", "(garbage collector)");
assert_eq!(kind, FrameKind::GC);
assert_eq!(category, FrameCategory::V8Internal);
let (kind, category) = classifier.classify("/src/main.js", "processData");
assert_eq!(kind, FrameKind::Function);
assert_eq!(category, FrameCategory::App);
let (kind, category) = classifier.classify("node:fs", "readFile");
assert_eq!(kind, FrameKind::Function);
assert_eq!(category, FrameCategory::NodeInternal);
}
#[test]
fn test_custom_app_base_path() {
let classifier = FrameClassifier::new(Some("/home/user/myproject".to_string()));
assert_eq!(
classifier.classify_category("/home/user/myproject/src/main.ts", "func"),
FrameCategory::App
);
assert_eq!(
classifier
.classify_category("/home/user/myproject/node_modules/lodash/index.js", "map"),
FrameCategory::Deps
);
}
}