1use once_cell::sync::Lazy;
4use regex::Regex;
5
6#[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
20fn compile_regex(pattern: &str) -> Regex {
23 Regex::new(pattern).unwrap_or_else(|e| {
24 panic!("Invalid regex pattern '{}': {}", pattern, e);
25 })
26}
27
28pub static FRAMEWORK_PATTERNS: Lazy<Vec<(Framework, Vec<Regex>)>> = Lazy::new(|| {
30 vec![
31 (
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 (
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 (
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 (
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 (
77 Framework::Nuxt,
78 vec![compile_regex(r"nuxt"), compile_regex(r"__NUXT__")],
79 ),
80 (
82 Framework::Svelte,
83 vec![compile_regex(r"svelte"), compile_regex(r"SvelteComponent")],
84 ),
85 (
87 Framework::Ember,
88 vec![compile_regex(r"Ember\."), compile_regex(r"ember-")],
89 ),
90 (
92 Framework::Backbone,
93 vec![compile_regex(r"Backbone\."), compile_regex(r"backbone")],
94 ),
95 ]
96});
97
98pub static FRAMEWORK_ENDPOINT_PATTERNS: Lazy<Vec<(Framework, Vec<&'static str>)>> =
100 Lazy::new(|| {
101 vec["']"#,
107 r#"path:\s*["']([^"']+)["']"#,
108 r#"useNavigate|useLocation|useParams"#,
109 r#"BrowserRouter|HashRouter|MemoryRouter"#,
110 ],
111 ),
112 (
114 Framework::NextJs,
115 vec![
116 r#"pages/api/([^"'\s]+)"#,
117 r#"/api/([^"'\s]+)"#,
118 r#"getServerSideProps|getStaticProps"#,
119 ],
120 ),
121 (
123 Framework::Angular,
124 vec['"]"#,
127 r#"\.navigate\(\s*\[['"]([^'"]+)['"]"#,
128 r#"HttpClient\.(get|post|put|delete|patch)"#,
129 ],
130 ),
131 (
133 Framework::Vue,
134 vec['"]"#,
137 r#"\$router\.push|this\.router\.push"#,
138 ],
139 ),
140 ]
141 });
142
143pub 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
157pub 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
177pub 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
190pub 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}