sdivi_patterns/queries/data_access.rs
1//! Node kinds classified as data-access patterns.
2//!
3//! These node kinds correspond to the `data_access` category in the
4//! [`PatternCatalog`](crate::catalog::PatternCatalog).
5
6use std::sync::LazyLock;
7
8use regex::Regex;
9
10/// Tree-sitter node kinds for data-access patterns.
11///
12/// - `call_expression`: function/method calls that access data stores or
13/// external resources (TypeScript/JavaScript/Go: `fetch`, `query`, `read`,
14/// `write`, `db.*`, `sql.*`, etc.). All `call_expression` nodes are
15/// classified here; callee-name narrowing is the consumer's responsibility.
16/// - `call`: Python function calls accessing data (`cursor.*`, `session.*`,
17/// `open`). Same broad-classification rule as above.
18pub const NODE_KINDS: &[&str] = &["call_expression", "call"];
19
20// TypeScript / JavaScript / Go:
21// ^(fetch|axios)\b — top-level fetch/axios calls
22// \b(query|read|write|get|post|put|delete|patch)\( — method calls by name
23// \b(db|sql)\. — db.* and sql.* receiver calls
24// \.(query|read|write|fetch)\( — chained method calls
25// ^ anchors the first alternative only; \b guards the rest from false prefixes.
26static TS_JS_GO_RE: LazyLock<Regex> = LazyLock::new(|| {
27 Regex::new(
28 r"^(fetch|axios)\b|\b(query|read|write|get|post|put|delete|patch)\(|\b(db|sql)\.|\.(query|read|write|fetch)\(",
29 )
30 .expect("data_access TS/JS/Go regex is valid")
31});
32
33// Python:
34// ^ anchors each alternative to the start of the text.
35// open( — built-in file open
36// requests. / httpx. / cursor. / session. / conn. — common data-access libs
37static PYTHON_RE: LazyLock<Regex> = LazyLock::new(|| {
38 Regex::new(r"^(open\(|requests\.|httpx\.|cursor\.|session\.|conn\.)")
39 .expect("data_access Python regex is valid")
40});
41
42/// Return `true` when `text` looks like a data-access callee for `language`.
43///
44/// Rust and Java always return `false` in v0 — data-access detection for those
45/// languages is library-shaped (`sqlx::query!`, `reqwest::get`) and deferred to
46/// a future regex pass. TypeScript, JavaScript, and Go share one regex table;
47/// Python has its own.
48///
49/// # Examples
50///
51/// ```rust
52/// use sdivi_patterns::queries::data_access::matches_callee;
53///
54/// assert!(matches_callee("fetch(\"/api/users\")", "typescript"));
55/// assert!(matches_callee("cursor.execute(sql)", "python"));
56/// assert!(!matches_callee("Math.max(a, b)", "typescript"));
57/// assert!(!matches_callee("len(x)", "python"));
58/// ```
59pub fn matches_callee(text: &str, language: &str) -> bool {
60 match language {
61 "typescript" | "javascript" | "go" => TS_JS_GO_RE.is_match(text),
62 "python" => PYTHON_RE.is_match(text),
63 _ => false,
64 }
65}