use once_cell::sync::Lazy;
use regex::Regex;
#[derive(Debug, Clone, PartialEq)]
pub enum Framework {
React,
Angular,
Vue,
NextJs,
Nuxt,
Svelte,
Ember,
Backbone,
Unknown,
}
fn compile_regex(pattern: &str) -> Regex {
Regex::new(pattern).unwrap_or_else(|e| {
panic!("Invalid regex pattern '{}': {}", pattern, e);
})
}
pub static FRAMEWORK_PATTERNS: Lazy<Vec<(Framework, Vec<Regex>)>> = Lazy::new(|| {
vec![
(
Framework::React,
vec![
compile_regex(r"react\."),
compile_regex(r"React\."),
compile_regex(r"from\s+['\x22]react['\x22]"),
compile_regex(r"ReactDOM"),
compile_regex(r"useState|useEffect|useContext"),
compile_regex(r"__webpack_require__.*react"),
],
),
(
Framework::NextJs,
vec![
compile_regex(r"next/"),
compile_regex(r"_next/static/"),
compile_regex(r"__NEXT_DATA__"),
compile_regex(r"next\.config"),
],
),
(
Framework::Angular,
vec![
compile_regex(r"@angular/"),
compile_regex(r"angular\."),
compile_regex(r"ng-"),
compile_regex(r"platformBrowserDynamic"),
compile_regex(r"NgModule"),
],
),
(
Framework::Vue,
vec![
compile_regex(r"vue\."),
compile_regex(r"Vue\."),
compile_regex(r"from\s+['\x22]vue['\x22]"),
compile_regex(r"createApp|Vue\.component"),
compile_regex(r"v-if|v-for|v-model"),
],
),
(
Framework::Nuxt,
vec![compile_regex(r"nuxt"), compile_regex(r"__NUXT__")],
),
(
Framework::Svelte,
vec![compile_regex(r"svelte"), compile_regex(r"SvelteComponent")],
),
(
Framework::Ember,
vec![compile_regex(r"Ember\."), compile_regex(r"ember-")],
),
(
Framework::Backbone,
vec![compile_regex(r"Backbone\."), compile_regex(r"backbone")],
),
]
});
pub static FRAMEWORK_ENDPOINT_PATTERNS: Lazy<Vec<(Framework, Vec<&'static str>)>> =
Lazy::new(|| {
vec["']"#,
r#"path:\s*["']([^"']+)["']"#,
r#"useNavigate|useLocation|useParams"#,
r#"BrowserRouter|HashRouter|MemoryRouter"#,
],
),
(
Framework::NextJs,
vec![
r#"pages/api/([^"'\s]+)"#,
r#"/api/([^"'\s]+)"#,
r#"getServerSideProps|getStaticProps"#,
],
),
(
Framework::Angular,
vec['"]"#,
r#"\.navigate\(\s*\[['"]([^'"]+)['"]"#,
r#"HttpClient\.(get|post|put|delete|patch)"#,
],
),
(
Framework::Vue,
vec['"]"#,
r#"\$router\.push|this\.router\.push"#,
],
),
]
});
pub static FRAMEWORK_ENDPOINT_REGEXES: Lazy<Vec<(Framework, Vec<Regex>)>> = Lazy::new(|| {
FRAMEWORK_ENDPOINT_PATTERNS
.iter()
.map(|(fw, patterns)| {
let compiled = patterns.iter().filter_map(|p| Regex::new(p).ok()).collect();
(fw.clone(), compiled)
})
.collect()
});
pub fn detect_framework(js_content: &str) -> Vec<Framework> {
let mut detected = Vec::new();
for (framework, patterns) in FRAMEWORK_PATTERNS.iter() {
for pattern in patterns {
if pattern.is_match(js_content) {
detected.push(framework.clone());
break;
}
}
}
if detected.is_empty() {
detected.push(Framework::Unknown);
}
detected
}
pub fn get_compiled_framework_patterns(framework: &Framework) -> Vec<&'static Regex> {
FRAMEWORK_ENDPOINT_REGEXES
.iter()
.filter(|(fw, _)| fw == framework)
.flat_map(|(_, patterns)| patterns.iter())
.collect()
}
pub fn get_framework_patterns(framework: &Framework) -> Vec<String> {
FRAMEWORK_ENDPOINT_PATTERNS
.iter()
.filter(|(fw, _)| fw == framework)
.flat_map(|(_, patterns)| patterns.iter().map(|p| p.to_string()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_react() {
let code = r#"
import React from 'react';
import { useState, useEffect } from 'react';
"#;
let detected = detect_framework(code);
assert!(detected.contains(&Framework::React));
}
#[test]
fn test_detect_nextjs() {
let code = r#"
const data = __NEXT_DATA__;
import { GetServerSideProps } from 'next';
"#;
let detected = detect_framework(code);
assert!(detected.contains(&Framework::NextJs));
}
#[test]
fn test_detect_angular() {
let code = r#"
import { NgModule } from '@angular/core';
platformBrowserDynamic().bootstrapModule(AppModule);
"#;
let detected = detect_framework(code);
assert!(detected.contains(&Framework::Angular));
}
#[test]
fn test_detect_vue() {
let code = r#"
import Vue from 'vue';
const app = createApp({});
"#;
let detected = detect_framework(code);
assert!(detected.contains(&Framework::Vue));
}
#[test]
fn test_unknown_framework() {
let code = "console.log('hello world');";
let detected = detect_framework(code);
assert!(detected.contains(&Framework::Unknown));
}
}