use lsp_types::{Range, Url};
use syn::__private::Span;
use syn::spanned::Spanned;
#[derive(Debug, Clone)]
pub struct RustDocument {
pub uri: Url,
pub content: String,
pub macros: Vec<SummerMacro>,
}
#[derive(Debug, Clone)]
pub enum SummerMacro {
DeriveService(ServiceMacro),
Component(ComponentMacro),
Inject(InjectMacro),
AutoConfig(AutoConfigMacro),
Route(RouteMacro),
Job(JobMacro),
}
#[derive(Debug, Clone)]
pub struct ServiceMacro {
pub struct_name: String,
pub fields: Vec<Field>,
pub range: Range,
}
#[derive(Debug, Clone)]
pub struct ComponentMacro {
pub function_name: String,
pub component_type: String,
pub dependencies: Vec<ComponentDependency>,
pub plugin_name: Option<String>,
pub is_async: bool,
pub range: Range,
}
#[derive(Debug, Clone)]
pub struct ComponentDependency {
pub dep_type: DependencyType,
pub type_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DependencyType {
Config,
Component,
}
#[derive(Debug, Clone)]
pub struct Field {
pub name: String,
pub type_name: String,
pub inject: Option<InjectMacro>,
}
#[derive(Debug, Clone)]
pub struct InjectMacro {
pub inject_type: InjectType,
pub component_name: Option<String>,
pub range: Range,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InjectType {
Component,
Config,
}
#[derive(Debug, Clone)]
pub struct AutoConfigMacro {
pub configurator_type: String,
pub range: Range,
}
#[derive(Debug, Clone)]
pub struct RouteMacro {
pub path: String,
pub methods: Vec<HttpMethod>,
pub middlewares: Vec<String>,
pub handler_name: String,
pub is_openapi: bool,
pub range: Range,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
Head,
Options,
Connect,
Trace,
}
impl HttpMethod {
pub fn parse_method(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"GET" => Some(HttpMethod::Get),
"POST" => Some(HttpMethod::Post),
"PUT" => Some(HttpMethod::Put),
"DELETE" => Some(HttpMethod::Delete),
"PATCH" => Some(HttpMethod::Patch),
"HEAD" => Some(HttpMethod::Head),
"OPTIONS" => Some(HttpMethod::Options),
"CONNECT" => Some(HttpMethod::Connect),
"TRACE" => Some(HttpMethod::Trace),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
HttpMethod::Get => "GET",
HttpMethod::Post => "POST",
HttpMethod::Put => "PUT",
HttpMethod::Delete => "DELETE",
HttpMethod::Patch => "PATCH",
HttpMethod::Head => "HEAD",
HttpMethod::Options => "OPTIONS",
HttpMethod::Connect => "CONNECT",
HttpMethod::Trace => "TRACE",
}
}
}
#[derive(Debug, Clone)]
pub enum JobMacro {
Cron {
expression: String,
range: Range,
},
FixDelay {
seconds: u64,
range: Range,
},
FixRate {
seconds: u64,
range: Range,
},
}
pub struct MacroAnalyzer;
impl MacroAnalyzer {
pub fn new() -> Self {
Self
}
pub fn hover_macro(&self, macro_info: &SummerMacro) -> String {
match macro_info {
SummerMacro::DeriveService(service) => self.hover_service_macro(service),
SummerMacro::Component(component) => self.hover_component_macro(component),
SummerMacro::Inject(inject) => self.hover_inject_macro(inject),
SummerMacro::AutoConfig(auto_config) => self.hover_auto_config_macro(auto_config),
SummerMacro::Route(route) => self.hover_route_macro(route),
SummerMacro::Job(job) => self.hover_job_macro(job),
}
}
fn hover_component_macro(&self, component: &ComponentMacro) -> String {
let mut hover = String::new();
hover.push_str("# Component 属性宏\n\n");
hover.push_str("自动将组件创建函数转换为 Plugin 实现,无需手动实现 Plugin trait。\n\n");
hover.push_str(&format!("**函数**: `{}`\n\n", component.function_name));
hover.push_str(&format!("**组件类型**: `{}`\n\n", component.component_type));
if let Some(name) = &component.plugin_name {
hover.push_str(&format!("**插件名称**: `\"{}\"`\n\n", name));
}
hover.push_str(&format!(
"**异步**: {}\n\n",
if component.is_async { "是" } else { "否" }
));
if !component.dependencies.is_empty() {
hover.push_str("**依赖**:\n\n");
for dep in &component.dependencies {
let dep_type_str = match dep.dep_type {
DependencyType::Config => "配置",
DependencyType::Component => "组件",
};
hover.push_str(&format!("- `{}` - {}\n", dep.type_name, dep_type_str));
}
hover.push('\n');
}
hover.push_str("**展开后的代码**:\n\n");
hover.push_str("```rust\n");
hover.push_str(&self.expand_component_macro(component));
hover.push_str("```\n");
hover
}
fn hover_service_macro(&self, service: &ServiceMacro) -> String {
let mut hover = String::new();
hover.push_str("# Service 派生宏\n\n");
hover.push_str("自动为结构体实现依赖注入功能,从应用上下文中获取组件和配置。\n\n");
hover.push_str(&format!("**结构体**: `{}`\n\n", service.struct_name));
if !service.fields.is_empty() {
hover.push_str("**注入字段**:\n\n");
for field in &service.fields {
hover.push_str(&format!("- `{}`: `{}`", field.name, field.type_name));
if let Some(inject) = &field.inject {
match inject.inject_type {
InjectType::Component => {
if let Some(name) = &inject.component_name {
hover.push_str(&format!(" - 注入组件 `\"{}\"`", name));
} else {
hover.push_str(" - 注入组件");
}
}
InjectType::Config => {
hover.push_str(" - 注入配置");
}
}
}
hover.push('\n');
}
hover.push('\n');
}
hover.push_str("**展开后的代码**:\n\n");
hover.push_str("```rust\n");
hover.push_str(&self.expand_service_macro(service));
hover.push_str("```\n");
hover
}
fn hover_inject_macro(&self, inject: &InjectMacro) -> String {
let mut hover = String::new();
hover.push_str("# Inject 属性宏\n\n");
hover.push_str("标记字段从应用上下文中自动注入依赖。\n\n");
match inject.inject_type {
InjectType::Component => {
hover.push_str("**注入类型**: 组件 (Component)\n\n");
hover.push_str("从应用上下文中获取已注册的组件实例。\n\n");
if let Some(name) = &inject.component_name {
hover.push_str(&format!("**组件名称**: `\"{}\"`\n\n", name));
hover.push_str("使用指定名称查找组件,适用于多实例场景(如多数据源)。\n\n");
hover.push_str("**注入代码**:\n\n");
hover.push_str("```rust\n");
hover.push_str(&format!("app.get_component::<T>(\"{}\")\n", name));
hover.push_str("```\n");
} else {
hover.push_str("使用类型自动查找组件。\n\n");
hover.push_str("**注入代码**:\n\n");
hover.push_str("```rust\n");
hover.push_str("app.get_component::<T>()\n");
hover.push_str("```\n");
}
}
InjectType::Config => {
hover.push_str("**注入类型**: 配置 (Config)\n\n");
hover.push_str("从配置文件中加载配置项。\n\n");
hover.push_str(
"配置项通过 `#[config_prefix]` 指定的前缀从 `config/app.toml` 中读取。\n\n",
);
hover.push_str("**注入代码**:\n\n");
hover.push_str("```rust\n");
hover.push_str("app.get_config::<T>()\n");
hover.push_str("```\n");
}
}
hover.push_str("\n**使用示例**:\n\n");
hover.push_str("```rust\n");
hover.push_str("#[derive(Clone, Service)]\n");
hover.push_str("struct MyService {\n");
match inject.inject_type {
InjectType::Component => {
if let Some(name) = &inject.component_name {
hover.push_str(&format!(" #[inject(component = \"{}\")]\n", name));
hover.push_str(" db: ConnectPool,\n");
} else {
hover.push_str(" #[inject(component)]\n");
hover.push_str(" db: ConnectPool,\n");
}
}
InjectType::Config => {
hover.push_str(" #[inject(config)]\n");
hover.push_str(" config: MyConfig,\n");
}
}
hover.push_str("}\n");
hover.push_str("```\n");
hover
}
fn hover_auto_config_macro(&self, auto_config: &AutoConfigMacro) -> String {
let mut hover = String::new();
hover.push_str("# AutoConfig 属性宏\n\n");
hover.push_str("自动注册配置器,在应用启动时配置路由、任务等。\n\n");
hover.push_str(&format!(
"**配置器类型**: `{}`\n\n",
auto_config.configurator_type
));
hover.push_str("**展开后的代码**:\n\n");
hover.push_str("```rust\n");
hover.push_str(&self.expand_auto_config_macro(auto_config));
hover.push_str("```\n");
hover
}
fn hover_route_macro(&self, route: &RouteMacro) -> String {
let mut hover = String::new();
hover.push_str("# 路由宏\n\n");
hover.push_str("注册 HTTP 路由处理器。\n\n");
hover.push_str(&format!("**路由路径**: `{}`\n\n", route.path));
hover.push_str(&format!(
"**HTTP 方法**: {}\n\n",
route
.methods
.iter()
.map(|m| format!("`{}`", m.as_str()))
.collect::<Vec<_>>()
.join(", ")
));
if !route.middlewares.is_empty() {
hover.push_str(&format!(
"**中间件**: {}\n\n",
route
.middlewares
.iter()
.map(|m| format!("`{}`", m))
.collect::<Vec<_>>()
.join(", ")
));
}
hover.push_str(&format!("**处理器函数**: `{}`\n\n", route.handler_name));
hover.push_str("**展开后的代码**:\n\n");
hover.push_str("```rust\n");
hover.push_str(&self.expand_route_macro(route));
hover.push_str("```\n");
hover
}
fn hover_job_macro(&self, job: &JobMacro) -> String {
let mut hover = String::new();
hover.push_str("# 任务调度宏\n\n");
match job {
JobMacro::Cron { expression, .. } => {
hover.push_str("定时任务,使用 Cron 表达式指定执行时间。\n\n");
hover.push_str(&format!("**Cron 表达式**: `{}`\n\n", expression));
hover.push_str("**格式**: `秒 分 时 日 月 星期`\n\n");
}
JobMacro::FixDelay { seconds, .. } => {
hover.push_str("固定延迟任务,任务完成后延迟指定秒数再次执行。\n\n");
hover.push_str(&format!("**延迟秒数**: `{}`\n\n", seconds));
}
JobMacro::FixRate { seconds, .. } => {
hover.push_str("固定频率任务,每隔指定秒数执行一次。\n\n");
hover.push_str(&format!("**频率秒数**: `{}`\n\n", seconds));
}
}
hover.push_str("**展开后的代码**:\n\n");
hover.push_str("```rust\n");
hover.push_str(&self.expand_job_macro(job));
hover.push_str("```\n");
hover
}
pub fn expand_macro(&self, macro_info: &SummerMacro) -> String {
match macro_info {
SummerMacro::DeriveService(service) => self.expand_service_macro(service),
SummerMacro::Component(component) => self.expand_component_macro(component),
SummerMacro::Inject(inject) => self.expand_inject_macro(inject),
SummerMacro::AutoConfig(auto_config) => self.expand_auto_config_macro(auto_config),
SummerMacro::Route(route) => self.expand_route_macro(route),
SummerMacro::Job(job) => self.expand_job_macro(job),
}
}
fn expand_component_macro(&self, component: &ComponentMacro) -> String {
let mut code = String::new();
code.push_str("// 原始定义\n");
code.push_str("#[component");
if let Some(name) = &component.plugin_name {
code.push_str(&format!("(name = \"{}\")", name));
}
code.push_str("]\n");
if component.is_async {
code.push_str("async ");
}
code.push_str(&format!("fn {}(", component.function_name));
for (i, dep) in component.dependencies.iter().enumerate() {
if i > 0 {
code.push_str(", ");
}
match dep.dep_type {
DependencyType::Config => {
code.push_str(&format!("Config(config): Config<{}>", dep.type_name));
}
DependencyType::Component => {
code.push_str(&format!("Component(comp): Component<{}>", dep.type_name));
}
}
}
code.push_str(&format!(") -> {} {{\n", component.component_type));
code.push_str(" // ...\n");
code.push_str("}\n\n");
code.push_str("// 展开后的代码\n");
let plugin_name = component
.plugin_name
.as_ref()
.map(|s| s.to_string())
.unwrap_or_else(|| format!("{}Plugin", component.component_type));
code.push_str(&format!("struct {};\n\n", plugin_name));
code.push_str("#[::summer::async_trait]\n");
code.push_str(&format!(
"impl ::summer::plugin::Plugin for {} {{\n",
plugin_name
));
code.push_str(" async fn build(&self, app: &mut ::summer::app::AppBuilder) {{\n");
for dep in &component.dependencies {
match dep.dep_type {
DependencyType::Config => {
code.push_str(&format!(
" let config = app.get_config::<{}>()?;\n",
dep.type_name
));
}
DependencyType::Component => {
code.push_str(&format!(
" let comp = app.get_component::<{}>()?;\n",
dep.type_name
));
}
}
}
code.push_str(&format!(
" let component = {}(",
component.function_name
));
for (i, dep) in component.dependencies.iter().enumerate() {
if i > 0 {
code.push_str(", ");
}
match dep.dep_type {
DependencyType::Config => code.push_str("Config(config)"),
DependencyType::Component => code.push_str("Component(comp)"),
}
}
if component.is_async {
code.push_str(").await?;\n");
} else {
code.push_str(")?;\n");
}
code.push_str(" app.add_component(component);\n");
code.push_str(" }\n");
code.push_str("}\n\n");
code.push_str(&format!(
"::summer::submit_component_plugin!({}); \n",
plugin_name
));
code
}
fn expand_service_macro(&self, service: &ServiceMacro) -> String {
let struct_name = &service.struct_name;
let mut code = String::new();
code.push_str("// 原始定义\n");
code.push_str("#[derive(Clone)]\n");
code.push_str(&format!("pub struct {} {{\n", struct_name));
for field in &service.fields {
if let Some(inject) = &field.inject {
let inject_type = match inject.inject_type {
InjectType::Component => "component",
InjectType::Config => "config",
};
if let Some(name) = &inject.component_name {
code.push_str(&format!(" #[inject({} = \"{}\")]\n", inject_type, name));
} else {
code.push_str(&format!(" #[inject({})]\n", inject_type));
}
}
code.push_str(&format!(" pub {}: {},\n", field.name, field.type_name));
}
code.push_str("}\n\n");
code.push_str("// 展开后的代码\n");
code.push_str(&format!("impl {} {{\n", struct_name));
code.push_str(" /// 从应用上下文构建服务实例\n");
code.push_str(" pub fn build(app: &AppBuilder) -> Result<Self> {\n");
for field in &service.fields {
if let Some(inject) = &field.inject {
match inject.inject_type {
InjectType::Component => {
if let Some(name) = &inject.component_name {
code.push_str(&format!(
" let {} = app.get_component::<{}>(\"{}\")?\n",
field.name, field.type_name, name
));
} else {
code.push_str(&format!(
" let {} = app.get_component::<{}>()?;\n",
field.name, field.type_name
));
}
}
InjectType::Config => {
code.push_str(&format!(
" let {} = app.get_config::<{}>()?;\n",
field.name, field.type_name
));
}
}
} else {
code.push_str(&format!(
" let {} = Default::default(); // 需要手动初始化\n",
field.name
));
}
}
code.push_str("\n Ok(Self {\n");
for field in &service.fields {
code.push_str(&format!(" {},\n", field.name));
}
code.push_str(" })\n");
code.push_str(" }\n");
code.push_str("}\n");
code
}
fn expand_inject_macro(&self, inject: &InjectMacro) -> String {
let mut code = String::new();
code.push_str("// Inject 属性展开\n");
code.push_str("// 这个字段将在运行时从应用上下文中注入\n");
match inject.inject_type {
InjectType::Component => {
if let Some(name) = &inject.component_name {
code.push_str("// 注入类型: 组件\n");
code.push_str(&format!("// 组件名称: \"{}\"\n", name));
code.push_str(&format!(
"// 注入代码: app.get_component::<T>(\"{}\")\n",
name
));
} else {
code.push_str("// 注入类型: 组件\n");
code.push_str("// 注入代码: app.get_component::<T>()\n");
}
}
InjectType::Config => {
code.push_str("// 注入类型: 配置\n");
code.push_str("// 注入代码: app.get_config::<T>()\n");
}
}
code
}
fn expand_auto_config_macro(&self, auto_config: &AutoConfigMacro) -> String {
let mut code = String::new();
code.push_str("// AutoConfig 宏展开\n");
code.push_str(&format!(
"// 配置器类型: {}\n",
auto_config.configurator_type
));
code.push_str("// 这个函数将在应用启动时自动注册配置\n");
code.push_str("// 展开后的代码:\n");
code.push_str("// \n");
code.push_str("// fn main() {\n");
code.push_str(&format!(
"// let configurator = {}::new();\n",
auto_config.configurator_type
));
code.push_str("// configurator.configure(&mut app);\n");
code.push_str("// // ... 原函数体\n");
code.push_str("// }\n");
code
}
fn expand_route_macro(&self, route: &RouteMacro) -> String {
let mut code = String::new();
code.push_str("// 路由宏展开\n");
code.push_str(&format!("// 路由路径: {}\n", route.path));
code.push_str(&format!(
"// HTTP 方法: {}\n",
route
.methods
.iter()
.map(|m| m.as_str())
.collect::<Vec<_>>()
.join(", ")
));
if !route.middlewares.is_empty() {
code.push_str(&format!("// 中间件: {}\n", route.middlewares.join(", ")));
}
code.push_str("// \n");
code.push_str("// 展开后的代码:\n");
code.push_str("// \n");
for method in &route.methods {
code.push_str(&format!(
"// router.route(\"{}\", {}, {});\n",
route.path,
method.as_str().to_lowercase(),
route.handler_name
));
}
if !route.middlewares.is_empty() {
code.push_str("// \n");
code.push_str("// 应用中间件:\n");
for middleware in &route.middlewares {
code.push_str(&format!("// .layer({})\n", middleware));
}
}
code
}
fn expand_job_macro(&self, job: &JobMacro) -> String {
let mut code = String::new();
code.push_str("// 任务调度宏展开\n");
match job {
JobMacro::Cron { expression, .. } => {
code.push_str("// 任务类型: Cron\n");
code.push_str(&format!("// Cron 表达式: {}\n", expression));
code.push_str("// \n");
code.push_str("// 展开后的代码:\n");
code.push_str("// \n");
code.push_str("// scheduler.add_job(\n");
code.push_str(&format!(
"// CronJob::new(\"{}\", || async {{\n",
expression
));
code.push_str("// // 任务函数体\n");
code.push_str("// }})\n");
code.push_str("// );\n");
}
JobMacro::FixDelay { seconds, .. } => {
code.push_str("// 任务类型: FixDelay\n");
code.push_str(&format!("// 延迟秒数: {}\n", seconds));
code.push_str("// 说明: 任务完成后延迟指定秒数再次执行\n");
code.push_str("// \n");
code.push_str("// 展开后的代码:\n");
code.push_str("// \n");
code.push_str("// scheduler.add_job(\n");
code.push_str(&format!(
"// FixDelayJob::new({}, || async {{\n",
seconds
));
code.push_str("// // 任务函数体\n");
code.push_str("// }})\n");
code.push_str("// );\n");
}
JobMacro::FixRate { seconds, .. } => {
code.push_str("// 任务类型: FixRate\n");
code.push_str(&format!("// 频率秒数: {}\n", seconds));
code.push_str("// 说明: 每隔指定秒数执行一次任务\n");
code.push_str("// \n");
code.push_str("// 展开后的代码:\n");
code.push_str("// \n");
code.push_str("// scheduler.add_job(\n");
code.push_str(&format!(
"// FixRateJob::new({}, || async {{\n",
seconds
));
code.push_str("// // 任务函数体\n");
code.push_str("// }})\n");
code.push_str("// );\n");
}
}
code
}
pub fn parse(&self, uri: Url, content: String) -> Result<RustDocument, syn::Error> {
let _syntax_tree = syn::parse_file(&content)?;
let doc = RustDocument {
uri,
content,
macros: Vec::new(),
};
Ok(doc)
}
pub fn extract_macros(&self, mut doc: RustDocument) -> Result<RustDocument, syn::Error> {
let syntax_tree = syn::parse_file(&doc.content)?;
let mut macros = Vec::new();
for item in &syntax_tree.items {
match item {
syn::Item::Struct(item_struct) => {
if let Some(service_macro) = self.extract_service_macro(item_struct) {
macros.push(SummerMacro::DeriveService(service_macro));
}
}
syn::Item::Fn(item_fn) => {
if let Some(component_macro) = self.extract_component_macro(item_fn) {
macros.push(SummerMacro::Component(component_macro));
}
if let Some(route_macro) = self.extract_route_macro(item_fn) {
macros.push(SummerMacro::Route(route_macro));
}
if let Some(auto_config_macro) = self.extract_auto_config_macro(item_fn) {
macros.push(SummerMacro::AutoConfig(auto_config_macro));
}
if let Some(job_macro) = self.extract_job_macro(item_fn) {
macros.push(SummerMacro::Job(job_macro));
}
}
_ => {}
}
}
doc.macros = macros;
Ok(doc)
}
fn extract_component_macro(&self, item_fn: &syn::ItemFn) -> Option<ComponentMacro> {
for attr in &item_fn.attrs {
if attr.path().is_ident("component") {
let plugin_name = if let Ok(meta_list) = attr.meta.require_list() {
self.extract_component_name(&meta_list.tokens.to_string())
} else {
None
};
let dependencies = self.extract_component_dependencies(&item_fn.sig.inputs);
let component_type = self.extract_return_type(&item_fn.sig.output);
let is_async = item_fn.sig.asyncness.is_some();
return Some(ComponentMacro {
function_name: item_fn.sig.ident.to_string(),
component_type,
dependencies,
plugin_name,
is_async,
range: self.span_to_range(&item_fn.sig.ident.span()),
});
}
}
None
}
fn extract_component_dependencies(
&self,
inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
) -> Vec<ComponentDependency> {
let mut dependencies = Vec::new();
for input in inputs {
if let syn::FnArg::Typed(pat_type) = input {
if let syn::Type::Path(type_path) = &*pat_type.ty {
if let Some(last_segment) = type_path.path.segments.last() {
let type_name = last_segment.ident.to_string();
if type_name == "Config" || type_name == "Component" {
if let syn::PathArguments::AngleBracketed(args) =
&last_segment.arguments
{
if let Some(syn::GenericArgument::Type(inner_ty)) =
args.args.first()
{
let inner_type_name = self.type_to_string(inner_ty);
let dep_type = if type_name == "Config" {
DependencyType::Config
} else {
DependencyType::Component
};
dependencies.push(ComponentDependency {
dep_type,
type_name: inner_type_name,
});
}
}
}
}
}
}
}
dependencies
}
fn extract_return_type(&self, output: &syn::ReturnType) -> String {
match output {
syn::ReturnType::Type(_, ty) => {
if let syn::Type::Path(type_path) = &**ty {
if let Some(last_segment) = type_path.path.segments.last() {
if last_segment.ident == "Result" {
if let syn::PathArguments::AngleBracketed(args) =
&last_segment.arguments
{
if let Some(syn::GenericArgument::Type(inner_ty)) =
args.args.first()
{
return self.type_to_string(inner_ty);
}
}
}
}
}
self.type_to_string(ty)
}
syn::ReturnType::Default => "()".to_string(),
}
}
fn extract_service_macro(&self, item_struct: &syn::ItemStruct) -> Option<ServiceMacro> {
for attr in &item_struct.attrs {
if attr.path().is_ident("derive") {
if let Ok(meta_list) = attr.meta.require_list() {
let has_service = meta_list.tokens.to_string().contains("Service");
if has_service {
let fields = self.extract_fields(&item_struct.fields);
return Some(ServiceMacro {
struct_name: item_struct.ident.to_string(),
fields,
range: self.span_to_range(&item_struct.ident.span()),
});
}
}
}
}
None
}
fn extract_fields(&self, fields: &syn::Fields) -> Vec<Field> {
let mut result = Vec::new();
if let syn::Fields::Named(fields_named) = fields {
for field in &fields_named.named {
if let Some(ident) = &field.ident {
let inject = self.extract_inject_macro(&field.attrs);
result.push(Field {
name: ident.to_string(),
type_name: self.type_to_string(&field.ty),
inject,
});
}
}
}
result
}
fn extract_inject_macro(&self, attrs: &[syn::Attribute]) -> Option<InjectMacro> {
for attr in attrs {
if attr.path().is_ident("inject") {
if let Ok(meta_list) = attr.meta.require_list() {
let tokens_str = meta_list.tokens.to_string();
let inject_type = if tokens_str.contains("component") {
InjectType::Component
} else if tokens_str.contains("config") {
InjectType::Config
} else {
continue;
};
let component_name = self.extract_component_name(&tokens_str);
return Some(InjectMacro {
inject_type,
component_name,
range: self.span_to_range(&attr.span()),
});
}
}
}
None
}
fn extract_component_name(&self, tokens_str: &str) -> Option<String> {
if let Some(eq_pos) = tokens_str.find('=') {
let after_eq = &tokens_str[eq_pos + 1..].trim();
let name = after_eq.trim_matches('"').trim();
if !name.is_empty() && name != "component" && name != "config" {
return Some(name.to_string());
}
}
None
}
fn extract_route_macro(&self, item_fn: &syn::ItemFn) -> Option<RouteMacro> {
for attr in &item_fn.attrs {
let method_path_and_openapi: Option<(Vec<HttpMethod>, String, bool)> =
if attr.path().is_ident("get") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Get], path, false))
} else if attr.path().is_ident("post") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Post], path, false))
} else if attr.path().is_ident("put") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Put], path, false))
} else if attr.path().is_ident("delete") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Delete], path, false))
} else if attr.path().is_ident("patch") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Patch], path, false))
} else if attr.path().is_ident("head") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Head], path, false))
} else if attr.path().is_ident("options") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Options], path, false))
} else if attr.path().is_ident("trace") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Trace], path, false))
} else if attr.path().is_ident("connect") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Connect], path, false))
} else if attr.path().is_ident("get_api") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Get], path, true))
} else if attr.path().is_ident("post_api") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Post], path, true))
} else if attr.path().is_ident("put_api") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Put], path, true))
} else if attr.path().is_ident("delete_api") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Delete], path, true))
} else if attr.path().is_ident("patch_api") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Patch], path, true))
} else if attr.path().is_ident("head_api") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Head], path, true))
} else if attr.path().is_ident("options_api") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Options], path, true))
} else if attr.path().is_ident("trace_api") {
self.extract_path_from_attr(attr)
.map(|path| (vec![HttpMethod::Trace], path, true))
} else if attr.path().is_ident("route") {
self.extract_route_attr(attr)
.map(|(methods, path)| (methods, path, false))
} else {
None
};
if let Some((methods, path, is_openapi)) = method_path_and_openapi {
let middlewares = self.extract_middlewares(&item_fn.attrs);
return Some(RouteMacro {
path,
methods,
middlewares,
handler_name: item_fn.sig.ident.to_string(),
is_openapi,
range: self.span_to_range(&item_fn.sig.ident.span()),
});
}
}
None
}
fn extract_path_from_attr(&self, attr: &syn::Attribute) -> Option<String> {
if let Ok(meta_list) = attr.meta.require_list() {
let tokens_str = meta_list.tokens.to_string();
let path = tokens_str.trim().trim_matches('"');
return Some(path.to_string());
}
None
}
fn extract_route_attr(&self, attr: &syn::Attribute) -> Option<(Vec<HttpMethod>, String)> {
if let Ok(meta_list) = attr.meta.require_list() {
let tokens_str = meta_list.tokens.to_string();
let path = if let Some(start) = tokens_str.find('"') {
if let Some(end) = tokens_str[start + 1..].find('"') {
tokens_str[start + 1..start + 1 + end].to_string()
} else {
return None;
}
} else {
return None;
};
let mut methods = Vec::new();
for part in tokens_str.split(',') {
if part.contains("method") {
if let Some(eq_pos) = part.find('=') {
let method_str = part[eq_pos + 1..].trim().trim_matches('"');
if let Some(method) = HttpMethod::parse_method(method_str) {
methods.push(method);
}
}
}
}
if !methods.is_empty() {
return Some((methods, path));
}
}
None
}
fn extract_middlewares(&self, attrs: &[syn::Attribute]) -> Vec<String> {
let mut middlewares = Vec::new();
for attr in attrs {
if attr.path().is_ident("middlewares") {
if let Ok(meta_list) = attr.meta.require_list() {
let tokens_str = meta_list.tokens.to_string();
for part in tokens_str.split(',') {
let middleware = part.trim().to_string();
if !middleware.is_empty() {
middlewares.push(middleware);
}
}
}
}
}
middlewares
}
fn extract_auto_config_macro(&self, item_fn: &syn::ItemFn) -> Option<AutoConfigMacro> {
for attr in &item_fn.attrs {
if attr.path().is_ident("auto_config") {
let configurator_type = if let Ok(meta_list) = attr.meta.require_list() {
meta_list.tokens.to_string()
} else {
String::new()
};
return Some(AutoConfigMacro {
configurator_type,
range: self.span_to_range(&attr.span()),
});
}
}
None
}
fn extract_job_macro(&self, item_fn: &syn::ItemFn) -> Option<JobMacro> {
for attr in &item_fn.attrs {
if attr.path().is_ident("cron") {
if let Some(expression) = self.extract_path_from_attr(attr) {
return Some(JobMacro::Cron {
expression,
range: self.span_to_range(&attr.span()),
});
}
} else if attr.path().is_ident("fix_delay") {
if let Ok(meta_list) = attr.meta.require_list() {
let tokens_str = meta_list.tokens.to_string();
if let Ok(seconds) = tokens_str.trim().parse::<u64>() {
return Some(JobMacro::FixDelay {
seconds,
range: self.span_to_range(&attr.span()),
});
}
}
} else if attr.path().is_ident("fix_rate") {
if let Ok(meta_list) = attr.meta.require_list() {
let tokens_str = meta_list.tokens.to_string();
if let Ok(seconds) = tokens_str.trim().parse::<u64>() {
return Some(JobMacro::FixRate {
seconds,
range: self.span_to_range(&attr.span()),
});
}
}
}
}
None
}
fn type_to_string(&self, ty: &syn::Type) -> String {
match ty {
syn::Type::Path(type_path) => {
if let Some(last_segment) = type_path.path.segments.last() {
let type_name = last_segment.ident.to_string();
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments {
if matches!(
type_name.as_str(),
"Arc" | "Option" | "LazyComponent" | "Box" | "Rc"
) {
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return self.type_to_string(inner_ty);
}
}
}
type_name
} else {
type_path
.path
.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<_>>()
.join("::")
}
}
_ => "Unknown".to_string(),
}
}
fn span_to_range(&self, span: &Span) -> Range {
let start = span.start();
let end = span.end();
Range {
start: lsp_types::Position {
line: start.line.saturating_sub(1) as u32, character: start.column as u32,
},
end: lsp_types::Position {
line: end.line.saturating_sub(1) as u32,
character: end.column as u32,
},
}
}
pub fn validate_macro(&self, macro_info: &SummerMacro) -> Vec<lsp_types::Diagnostic> {
match macro_info {
SummerMacro::DeriveService(service) => self.validate_service_macro(service),
SummerMacro::Component(component) => self.validate_component_macro(component),
SummerMacro::Inject(inject) => self.validate_inject_macro(inject),
SummerMacro::AutoConfig(auto_config) => self.validate_auto_config_macro(auto_config),
SummerMacro::Route(route) => self.validate_route_macro(route),
SummerMacro::Job(job) => self.validate_job_macro(job),
}
}
fn validate_component_macro(&self, component: &ComponentMacro) -> Vec<lsp_types::Diagnostic> {
let mut diagnostics = Vec::new();
if component.component_type.is_empty() || component.component_type == "()" {
diagnostics.push(lsp_types::Diagnostic {
range: component.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E016".to_string())),
source: Some("summer-lsp".to_string()),
message: "Component 函数必须返回一个具体的类型,不能是 ()".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
if let Some(name) = &component.plugin_name {
if name.is_empty() {
diagnostics.push(lsp_types::Diagnostic {
range: component.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E017".to_string())),
source: Some("summer-lsp".to_string()),
message: "插件名称不能为空字符串".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
}
let mut seen_types = std::collections::HashSet::new();
for dep in &component.dependencies {
let key = format!("{:?}:{}", dep.dep_type, dep.type_name);
if !seen_types.insert(key) {
diagnostics.push(lsp_types::Diagnostic {
range: component.range,
severity: Some(lsp_types::DiagnosticSeverity::WARNING),
code: Some(lsp_types::NumberOrString::String("W002".to_string())),
source: Some("summer-lsp".to_string()),
message: format!(
"重复的依赖: {} {}",
match dep.dep_type {
DependencyType::Config => "Config",
DependencyType::Component => "Component",
},
dep.type_name
),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
}
diagnostics
}
fn validate_service_macro(&self, service: &ServiceMacro) -> Vec<lsp_types::Diagnostic> {
let mut diagnostics = Vec::new();
for field in &service.fields {
if let Some(inject) = &field.inject {
let inject_diagnostics = self.validate_inject_macro(inject);
diagnostics.extend(inject_diagnostics);
if let Some(name) = &inject.component_name {
if name.is_empty() {
diagnostics.push(lsp_types::Diagnostic {
range: inject.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E001".to_string())),
source: Some("summer-lsp".to_string()),
message: format!("字段 '{}' 的组件名称不能为空字符串", field.name),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
}
}
}
diagnostics
}
fn validate_inject_macro(&self, inject: &InjectMacro) -> Vec<lsp_types::Diagnostic> {
let mut diagnostics = Vec::new();
if inject.inject_type == InjectType::Config && inject.component_name.is_some() {
diagnostics.push(lsp_types::Diagnostic {
range: inject.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E002".to_string())),
source: Some("summer-lsp".to_string()),
message: "配置注入 (config) 不应该指定组件名称".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
diagnostics
}
fn validate_auto_config_macro(
&self,
auto_config: &AutoConfigMacro,
) -> Vec<lsp_types::Diagnostic> {
let mut diagnostics = Vec::new();
if auto_config.configurator_type.is_empty() {
diagnostics.push(lsp_types::Diagnostic {
range: auto_config.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E003".to_string())),
source: Some("summer-lsp".to_string()),
message: "AutoConfig 宏必须指定配置器类型".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
diagnostics
}
fn validate_route_macro(&self, route: &RouteMacro) -> Vec<lsp_types::Diagnostic> {
let mut diagnostics = Vec::new();
if route.path.is_empty() {
diagnostics.push(lsp_types::Diagnostic {
range: route.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E004".to_string())),
source: Some("summer-lsp".to_string()),
message: "路由路径不能为空".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
} else {
if !route.path.starts_with('/') {
diagnostics.push(lsp_types::Diagnostic {
range: route.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E005".to_string())),
source: Some("summer-lsp".to_string()),
message: format!("路由路径必须以 '/' 开头,当前路径: '{}'", route.path),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
self.validate_path_parameters(&route.path, route.range, &mut diagnostics);
}
if route.methods.is_empty() {
diagnostics.push(lsp_types::Diagnostic {
range: route.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E006".to_string())),
source: Some("summer-lsp".to_string()),
message: "路由必须至少指定一个 HTTP 方法".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
if route.handler_name.is_empty() {
diagnostics.push(lsp_types::Diagnostic {
range: route.range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E007".to_string())),
source: Some("summer-lsp".to_string()),
message: "路由处理器函数名称不能为空".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
diagnostics
}
fn validate_path_parameters(
&self,
path: &str,
range: Range,
diagnostics: &mut Vec<lsp_types::Diagnostic>,
) {
let mut open_braces = 0;
let mut param_start = None;
for (i, ch) in path.char_indices() {
match ch {
'{' => {
if open_braces > 0 {
diagnostics.push(lsp_types::Diagnostic {
range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E008".to_string())),
source: Some("summer-lsp".to_string()),
message: format!("路径参数不能嵌套,位置: {}", i),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
open_braces += 1;
param_start = Some(i);
}
'}' => {
if open_braces == 0 {
diagnostics.push(lsp_types::Diagnostic {
range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E009".to_string())),
source: Some("summer-lsp".to_string()),
message: format!("路径参数缺少开括号 '{{', 位置: {}", i),
related_information: None,
tags: None,
code_description: None,
data: None,
});
} else {
open_braces -= 1;
if let Some(start) = param_start {
let param_name = &path[start + 1..i];
if param_name.is_empty() {
diagnostics.push(lsp_types::Diagnostic {
range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String(
"E010".to_string(),
)),
source: Some("summer-lsp".to_string()),
message: format!("路径参数名称不能为空,位置: {}", start),
related_information: None,
tags: None,
code_description: None,
data: None,
});
} else if !param_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
diagnostics.push(lsp_types::Diagnostic {
range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String(
"E011".to_string(),
)),
source: Some("summer-lsp".to_string()),
message: format!(
"路径参数名称只能包含字母、数字和下划线,当前参数: '{}'",
param_name
),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
}
param_start = None;
}
}
_ => {}
}
}
if open_braces > 0 {
diagnostics.push(lsp_types::Diagnostic {
range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E012".to_string())),
source: Some("summer-lsp".to_string()),
message: "路径参数缺少闭括号 '}'".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
}
fn validate_job_macro(&self, job: &JobMacro) -> Vec<lsp_types::Diagnostic> {
let mut diagnostics = Vec::new();
match job {
JobMacro::Cron { expression, range } => {
if expression.is_empty() {
diagnostics.push(lsp_types::Diagnostic {
range: *range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E013".to_string())),
source: Some("summer-lsp".to_string()),
message: "Cron 表达式不能为空".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
} else {
self.validate_cron_expression(expression, *range, &mut diagnostics);
}
}
JobMacro::FixDelay { seconds, range } => {
if *seconds == 0 {
diagnostics.push(lsp_types::Diagnostic {
range: *range,
severity: Some(lsp_types::DiagnosticSeverity::WARNING),
code: Some(lsp_types::NumberOrString::String("W001".to_string())),
source: Some("summer-lsp".to_string()),
message: "延迟秒数为 0 可能不是预期的行为".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
}
JobMacro::FixRate { seconds, range } => {
if *seconds == 0 {
diagnostics.push(lsp_types::Diagnostic {
range: *range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E014".to_string())),
source: Some("summer-lsp".to_string()),
message: "频率秒数不能为 0".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
}
}
diagnostics
}
fn validate_cron_expression(
&self,
expression: &str,
range: Range,
diagnostics: &mut Vec<lsp_types::Diagnostic>,
) {
let parts: Vec<&str> = expression.split_whitespace().collect();
if parts.len() != 6 {
diagnostics.push(lsp_types::Diagnostic {
range,
severity: Some(lsp_types::DiagnosticSeverity::ERROR),
code: Some(lsp_types::NumberOrString::String("E015".to_string())),
source: Some("summer-lsp".to_string()),
message: format!(
"Cron 表达式应该包含 6 个部分(秒 分 时 日 月 星期),当前有 {} 个部分",
parts.len()
),
related_information: None,
tags: None,
code_description: None,
data: None,
});
}
}
}
impl Default for MacroAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests;