Skip to main content

hazler_js_parser/
framework.rs

1//! Framework detection and framework-specific endpoint extraction
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5
6/// Detected framework information
7#[derive(Debug, Clone, PartialEq)]
8pub enum Framework {
9    React,
10    Angular,
11    Vue,
12    NextJs,
13    Nuxt,
14    Svelte,
15    Ember,
16    Backbone,
17    Unknown,
18}
19
20/// Helper function to compile regex patterns safely
21/// Panics only during initialization if patterns are invalid (fail-fast on startup)
22fn compile_regex(pattern: &str) -> Regex {
23    Regex::new(pattern).unwrap_or_else(|e| {
24        panic!("Invalid regex pattern '{}': {}", pattern, e);
25    })
26}
27
28/// Framework detection patterns
29pub static FRAMEWORK_PATTERNS: Lazy<Vec<(Framework, Vec<Regex>)>> = Lazy::new(|| {
30    vec![
31        // React patterns
32        (
33            Framework::React,
34            vec![
35                compile_regex(r"react\."),
36                compile_regex(r"React\."),
37                compile_regex(r"from\s+['\x22]react['\x22]"),
38                compile_regex(r"ReactDOM"),
39                compile_regex(r"useState|useEffect|useContext"),
40                compile_regex(r"__webpack_require__.*react"),
41            ],
42        ),
43        // Next.js patterns
44        (
45            Framework::NextJs,
46            vec![
47                compile_regex(r"next/"),
48                compile_regex(r"_next/static/"),
49                compile_regex(r"__NEXT_DATA__"),
50                compile_regex(r"next\.config"),
51            ],
52        ),
53        // Angular patterns
54        (
55            Framework::Angular,
56            vec![
57                compile_regex(r"@angular/"),
58                compile_regex(r"angular\."),
59                compile_regex(r"ng-"),
60                compile_regex(r"platformBrowserDynamic"),
61                compile_regex(r"NgModule"),
62            ],
63        ),
64        // Vue.js patterns
65        (
66            Framework::Vue,
67            vec![
68                compile_regex(r"vue\."),
69                compile_regex(r"Vue\."),
70                compile_regex(r"from\s+['\x22]vue['\x22]"),
71                compile_regex(r"createApp|Vue\.component"),
72                compile_regex(r"v-if|v-for|v-model"),
73            ],
74        ),
75        // Nuxt patterns
76        (
77            Framework::Nuxt,
78            vec![compile_regex(r"nuxt"), compile_regex(r"__NUXT__")],
79        ),
80        // Svelte patterns
81        (
82            Framework::Svelte,
83            vec![compile_regex(r"svelte"), compile_regex(r"SvelteComponent")],
84        ),
85        // Ember patterns
86        (
87            Framework::Ember,
88            vec![compile_regex(r"Ember\."), compile_regex(r"ember-")],
89        ),
90        // Backbone patterns
91        (
92            Framework::Backbone,
93            vec![compile_regex(r"Backbone\."), compile_regex(r"backbone")],
94        ),
95    ]
96});
97
98/// Framework-specific endpoint patterns
99pub static FRAMEWORK_ENDPOINT_PATTERNS: Lazy<Vec<(Framework, Vec<&'static str>)>> =
100    Lazy::new(|| {
101        vec![
102            // React Router patterns
103            (
104                Framework::React,
105                vec![
106                    r#"<Route\s+path=["']([^"']+)["']"#,
107                    r#"path:\s*["']([^"']+)["']"#,
108                    r#"useNavigate|useLocation|useParams"#,
109                    r#"BrowserRouter|HashRouter|MemoryRouter"#,
110                ],
111            ),
112            // Next.js patterns
113            (
114                Framework::NextJs,
115                vec![
116                    r#"pages/api/([^"'\s]+)"#,
117                    r#"/api/([^"'\s]+)"#,
118                    r#"getServerSideProps|getStaticProps"#,
119                ],
120            ),
121            // Angular patterns
122            (
123                Framework::Angular,
124                vec![
125                    r#"RouterModule\.forRoot"#,
126                    r#"path:\s*['"]([^'"]+)['"]"#,
127                    r#"\.navigate\(\s*\[['"]([^'"]+)['"]"#,
128                    r#"HttpClient\.(get|post|put|delete|patch)"#,
129                ],
130            ),
131            // Vue Router patterns
132            (
133                Framework::Vue,
134                vec![
135                    r#"createRouter|new\s+VueRouter"#,
136                    r#"path:\s*['"]([^'"]+)['"]"#,
137                    r#"\$router\.push|this\.router\.push"#,
138                ],
139            ),
140        ]
141    });
142
143/// Pre-compiled framework-specific endpoint regexes.
144///
145/// Compiled once at program startup so that `extract_framework_endpoints`
146/// does not recompile the same patterns on every call.
147pub static FRAMEWORK_ENDPOINT_REGEXES: Lazy<Vec<(Framework, Vec<Regex>)>> = Lazy::new(|| {
148    FRAMEWORK_ENDPOINT_PATTERNS
149        .iter()
150        .map(|(fw, patterns)| {
151            let compiled = patterns.iter().filter_map(|p| Regex::new(p).ok()).collect();
152            (fw.clone(), compiled)
153        })
154        .collect()
155});
156
157/// Detect framework from JavaScript content
158pub fn detect_framework(js_content: &str) -> Vec<Framework> {
159    let mut detected = Vec::new();
160
161    for (framework, patterns) in FRAMEWORK_PATTERNS.iter() {
162        for pattern in patterns {
163            if pattern.is_match(js_content) {
164                detected.push(framework.clone());
165                break;
166            }
167        }
168    }
169
170    if detected.is_empty() {
171        detected.push(Framework::Unknown);
172    }
173
174    detected
175}
176
177/// Get framework-specific endpoint patterns as pre-compiled [`Regex`] references.
178///
179/// Using pre-compiled regexes avoids re-compiling the same patterns on every
180/// call to `extract_framework_endpoints`, which is a significant performance
181/// improvement when processing large JavaScript files.
182pub fn get_compiled_framework_patterns(framework: &Framework) -> Vec<&'static Regex> {
183    FRAMEWORK_ENDPOINT_REGEXES
184        .iter()
185        .filter(|(fw, _)| fw == framework)
186        .flat_map(|(_, patterns)| patterns.iter())
187        .collect()
188}
189
190/// Get framework-specific endpoint patterns
191pub fn get_framework_patterns(framework: &Framework) -> Vec<String> {
192    FRAMEWORK_ENDPOINT_PATTERNS
193        .iter()
194        .filter(|(fw, _)| fw == framework)
195        .flat_map(|(_, patterns)| patterns.iter().map(|p| p.to_string()))
196        .collect()
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_detect_react() {
205        let code = r#"
206            import React from 'react';
207            import { useState, useEffect } from 'react';
208        "#;
209        let detected = detect_framework(code);
210        assert!(detected.contains(&Framework::React));
211    }
212
213    #[test]
214    fn test_detect_nextjs() {
215        let code = r#"
216            const data = __NEXT_DATA__;
217            import { GetServerSideProps } from 'next';
218        "#;
219        let detected = detect_framework(code);
220        assert!(detected.contains(&Framework::NextJs));
221    }
222
223    #[test]
224    fn test_detect_angular() {
225        let code = r#"
226            import { NgModule } from '@angular/core';
227            platformBrowserDynamic().bootstrapModule(AppModule);
228        "#;
229        let detected = detect_framework(code);
230        assert!(detected.contains(&Framework::Angular));
231    }
232
233    #[test]
234    fn test_detect_vue() {
235        let code = r#"
236            import Vue from 'vue';
237            const app = createApp({});
238        "#;
239        let detected = detect_framework(code);
240        assert!(detected.contains(&Framework::Vue));
241    }
242
243    #[test]
244    fn test_unknown_framework() {
245        let code = "console.log('hello world');";
246        let detected = detect_framework(code);
247        assert!(detected.contains(&Framework::Unknown));
248    }
249}