Skip to main content

sdivi_patterns/queries/
logging.rs

1//! Node kinds that conceptually belong to the `logging` category.
2//!
3//! **This module is intentionally not wired into [`category_for_node_kind`].**
4//! The node kinds listed below overlap with [`data_access`](super::data_access)
5//! (`call_expression`, `call`) and [`resource_management`](super::resource_management)
6//! (`macro_invocation`) at the AST level — only the callee name distinguishes
7//! a logging invocation from a data-access or resource-management one. Native
8//! classification by node kind alone would either steal hits from those
9//! existing categories or duplicate-classify every call/macro.
10//!
11//! Foreign extractors (e.g. the Meridian consumer app) MUST apply callee-text
12//! filtering on their side before emitting `PatternInstanceInput` values
13//! with `category = "logging"`. As of M32, [`matches_callee`] provides the
14//! canonical regex tables — embedders should prefer it over hand-rolled filters.
15//!
16//! [`category_for_node_kind`]: super::category_for_node_kind
17
18use std::sync::LazyLock;
19
20use regex::Regex;
21
22/// Conceptual tree-sitter node kinds for logging patterns. Reference only —
23/// not consulted by [`category_for_node_kind`](super::category_for_node_kind).
24///
25/// Listed for documentation parity with sibling category modules and so
26/// that embedders can grep `sdivi-patterns/src/queries/logging.rs` to see
27/// the node-kind shapes the canonical contract has in mind.
28///
29/// - `call_expression`: TS/JS/Go logger / console / Print* calls
30/// - `call`: Python `logging.*` and `print`
31/// - `macro_invocation`: Rust `tracing::*!`, `log::*!`, `println!`/`eprintln!`
32pub const NODE_KINDS: &[&str] = &["call_expression", "call", "macro_invocation"];
33
34// TypeScript / JavaScript:
35//   ^(console|logger|log)\.  — console.log, logger.info, log.debug, etc.
36// ^ anchors to the start of the callee text.
37static TS_JS_RE: LazyLock<Regex> =
38    LazyLock::new(|| Regex::new(r"^(console|logger|log)\.").expect("logging TS/JS regex is valid"));
39
40// Python:
41//   ^(logging\.|print\b)  — logging.info(...) or print(...); \b guards "printer" etc.
42static PYTHON_RE: LazyLock<Regex> =
43    LazyLock::new(|| Regex::new(r"^(logging\.|print\b)").expect("logging Python regex is valid"));
44
45// Go:
46//   ^fmt\.(Print|Println|Printf|Errorf|Fprint|Sprint)  — fmt package output calls.
47// ^ anchor + exact package prefix prevents matching "myfmt.Println".
48static GO_RE: LazyLock<Regex> = LazyLock::new(|| {
49    Regex::new(r"^fmt\.(Print|Println|Printf|Errorf|Fprint|Sprint)")
50        .expect("logging Go regex is valid")
51});
52
53// Rust:
54//   ^(tracing|log)::  — tracing::info!("...") or log::debug!(...)
55//   ^(println|eprintln|print|eprint|dbg)!  — standard macro forms
56// Two anchored alternatives combined with |; both start with ^.
57static RUST_RE: LazyLock<Regex> = LazyLock::new(|| {
58    Regex::new(r"^(tracing|log)::|^(println|eprintln|print|eprint|dbg)!")
59        .expect("logging Rust regex is valid")
60});
61
62// Java:
63//   ^(System\.(out|err)\.|logger\.|Log\.|LOG\.)  — common Java logging patterns.
64// ^ anchor prevents matching "mySystem.out.println".
65static JAVA_RE: LazyLock<Regex> = LazyLock::new(|| {
66    Regex::new(r"^(System\.(out|err)\.|logger\.|Log\.|LOG\.)").expect("logging Java regex is valid")
67});
68
69/// Return `true` when `text` looks like a logging callee for `language`.
70///
71/// This function promotes `logging` from catalog-only (M30 design) to natively
72/// classifiable when called via [`classify_hint`](super::classify_hint). The
73/// older [`category_for_node_kind`](super::category_for_node_kind) continues to
74/// never return `Some("logging")` — that sentinel is unchanged.
75///
76/// # Examples
77///
78/// ```rust
79/// use sdivi_patterns::queries::logging::matches_callee;
80///
81/// assert!(matches_callee("console.log(\"x\")", "typescript"));
82/// assert!(matches_callee("tracing::info!(\"hi\")", "rust"));
83/// assert!(matches_callee("fmt.Println(\"x\")", "go"));
84/// assert!(!matches_callee("Math.max(a, b)", "typescript"));
85/// assert!(!matches_callee("vec![1, 2]", "rust"));
86/// ```
87pub fn matches_callee(text: &str, language: &str) -> bool {
88    match language {
89        "typescript" | "javascript" => TS_JS_RE.is_match(text),
90        "python" => PYTHON_RE.is_match(text),
91        "go" => GO_RE.is_match(text),
92        "rust" => RUST_RE.is_match(text),
93        "java" => JAVA_RE.is_match(text),
94        _ => false,
95    }
96}