1use lsp_types::{Range, Url};
4use syn::__private::Span;
5use syn::spanned::Spanned;
6
7#[derive(Debug, Clone)]
9pub struct RustDocument {
10 pub uri: Url,
12 pub content: String,
14 pub macros: Vec<SpringMacro>,
16}
17
18#[derive(Debug, Clone)]
20pub enum SpringMacro {
21 DeriveService(ServiceMacro),
23 Inject(InjectMacro),
25 AutoConfig(AutoConfigMacro),
27 Route(RouteMacro),
29 Job(JobMacro),
31}
32
33#[derive(Debug, Clone)]
35pub struct ServiceMacro {
36 pub struct_name: String,
38 pub fields: Vec<Field>,
40 pub range: Range,
42}
43
44#[derive(Debug, Clone)]
46pub struct Field {
47 pub name: String,
49 pub type_name: String,
51 pub inject: Option<InjectMacro>,
53}
54
55#[derive(Debug, Clone)]
57pub struct InjectMacro {
58 pub inject_type: InjectType,
60 pub component_name: Option<String>,
62 pub range: Range,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum InjectType {
69 Component,
71 Config,
73}
74
75#[derive(Debug, Clone)]
77pub struct AutoConfigMacro {
78 pub configurator_type: String,
80 pub range: Range,
82}
83
84#[derive(Debug, Clone)]
86pub struct RouteMacro {
87 pub path: String,
89 pub methods: Vec<HttpMethod>,
91 pub middlewares: Vec<String>,
93 pub handler_name: String,
95 pub range: Range,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Hash)]
101pub enum HttpMethod {
102 Get,
103 Post,
104 Put,
105 Delete,
106 Patch,
107 Head,
108 Options,
109 Connect,
110 Trace,
111}
112
113impl HttpMethod {
114 pub fn parse_method(s: &str) -> Option<Self> {
116 match s.to_uppercase().as_str() {
117 "GET" => Some(HttpMethod::Get),
118 "POST" => Some(HttpMethod::Post),
119 "PUT" => Some(HttpMethod::Put),
120 "DELETE" => Some(HttpMethod::Delete),
121 "PATCH" => Some(HttpMethod::Patch),
122 "HEAD" => Some(HttpMethod::Head),
123 "OPTIONS" => Some(HttpMethod::Options),
124 "CONNECT" => Some(HttpMethod::Connect),
125 "TRACE" => Some(HttpMethod::Trace),
126 _ => None,
127 }
128 }
129
130 pub fn as_str(&self) -> &'static str {
132 match self {
133 HttpMethod::Get => "GET",
134 HttpMethod::Post => "POST",
135 HttpMethod::Put => "PUT",
136 HttpMethod::Delete => "DELETE",
137 HttpMethod::Patch => "PATCH",
138 HttpMethod::Head => "HEAD",
139 HttpMethod::Options => "OPTIONS",
140 HttpMethod::Connect => "CONNECT",
141 HttpMethod::Trace => "TRACE",
142 }
143 }
144}
145
146#[derive(Debug, Clone)]
148pub enum JobMacro {
149 Cron {
151 expression: String,
153 range: Range,
155 },
156 FixDelay {
158 seconds: u64,
160 range: Range,
162 },
163 FixRate {
165 seconds: u64,
167 range: Range,
169 },
170}
171
172pub struct MacroAnalyzer;
174
175impl MacroAnalyzer {
176 pub fn new() -> Self {
178 Self
179 }
180
181 pub fn hover_macro(&self, macro_info: &SpringMacro) -> String {
193 match macro_info {
194 SpringMacro::DeriveService(service) => self.hover_service_macro(service),
195 SpringMacro::Inject(inject) => self.hover_inject_macro(inject),
196 SpringMacro::AutoConfig(auto_config) => self.hover_auto_config_macro(auto_config),
197 SpringMacro::Route(route) => self.hover_route_macro(route),
198 SpringMacro::Job(job) => self.hover_job_macro(job),
199 }
200 }
201
202 fn hover_service_macro(&self, service: &ServiceMacro) -> String {
206 let mut hover = String::new();
207
208 hover.push_str("# Service 派生宏\n\n");
210
211 hover.push_str("自动为结构体实现依赖注入功能,从应用上下文中获取组件和配置。\n\n");
213
214 hover.push_str(&format!("**结构体**: `{}`\n\n", service.struct_name));
216
217 if !service.fields.is_empty() {
219 hover.push_str("**注入字段**:\n\n");
220 for field in &service.fields {
221 hover.push_str(&format!("- `{}`: `{}`", field.name, field.type_name));
222 if let Some(inject) = &field.inject {
223 match inject.inject_type {
224 InjectType::Component => {
225 if let Some(name) = &inject.component_name {
226 hover.push_str(&format!(" - 注入组件 `\"{}\"`", name));
227 } else {
228 hover.push_str(" - 注入组件");
229 }
230 }
231 InjectType::Config => {
232 hover.push_str(" - 注入配置");
233 }
234 }
235 }
236 hover.push('\n');
237 }
238 hover.push('\n');
239 }
240
241 hover.push_str("**展开后的代码**:\n\n");
243 hover.push_str("```rust\n");
244 hover.push_str(&self.expand_service_macro(service));
245 hover.push_str("```\n");
246
247 hover
248 }
249
250 fn hover_inject_macro(&self, inject: &InjectMacro) -> String {
254 let mut hover = String::new();
255
256 hover.push_str("# Inject 属性宏\n\n");
258
259 hover.push_str("标记字段从应用上下文中自动注入依赖。\n\n");
261
262 match inject.inject_type {
264 InjectType::Component => {
265 hover.push_str("**注入类型**: 组件 (Component)\n\n");
266 hover.push_str("从应用上下文中获取已注册的组件实例。\n\n");
267
268 if let Some(name) = &inject.component_name {
269 hover.push_str(&format!("**组件名称**: `\"{}\"`\n\n", name));
270 hover.push_str("使用指定名称查找组件,适用于多实例场景(如多数据源)。\n\n");
271 hover.push_str("**注入代码**:\n\n");
272 hover.push_str("```rust\n");
273 hover.push_str(&format!("app.get_component::<T>(\"{}\")\n", name));
274 hover.push_str("```\n");
275 } else {
276 hover.push_str("使用类型自动查找组件。\n\n");
277 hover.push_str("**注入代码**:\n\n");
278 hover.push_str("```rust\n");
279 hover.push_str("app.get_component::<T>()\n");
280 hover.push_str("```\n");
281 }
282 }
283 InjectType::Config => {
284 hover.push_str("**注入类型**: 配置 (Config)\n\n");
285 hover.push_str("从配置文件中加载配置项。\n\n");
286 hover.push_str(
287 "配置项通过 `#[config_prefix]` 指定的前缀从 `config/app.toml` 中读取。\n\n",
288 );
289 hover.push_str("**注入代码**:\n\n");
290 hover.push_str("```rust\n");
291 hover.push_str("app.get_config::<T>()\n");
292 hover.push_str("```\n");
293 }
294 }
295
296 hover.push_str("\n**使用示例**:\n\n");
298 hover.push_str("```rust\n");
299 hover.push_str("#[derive(Clone, Service)]\n");
300 hover.push_str("struct MyService {\n");
301
302 match inject.inject_type {
303 InjectType::Component => {
304 if let Some(name) = &inject.component_name {
305 hover.push_str(&format!(" #[inject(component = \"{}\")]\n", name));
306 hover.push_str(" db: ConnectPool,\n");
307 } else {
308 hover.push_str(" #[inject(component)]\n");
309 hover.push_str(" db: ConnectPool,\n");
310 }
311 }
312 InjectType::Config => {
313 hover.push_str(" #[inject(config)]\n");
314 hover.push_str(" config: MyConfig,\n");
315 }
316 }
317
318 hover.push_str("}\n");
319 hover.push_str("```\n");
320
321 hover
322 }
323
324 fn hover_auto_config_macro(&self, auto_config: &AutoConfigMacro) -> String {
326 let mut hover = String::new();
327
328 hover.push_str("# AutoConfig 属性宏\n\n");
329 hover.push_str("自动注册配置器,在应用启动时配置路由、任务等。\n\n");
330 hover.push_str(&format!(
331 "**配置器类型**: `{}`\n\n",
332 auto_config.configurator_type
333 ));
334 hover.push_str("**展开后的代码**:\n\n");
335 hover.push_str("```rust\n");
336 hover.push_str(&self.expand_auto_config_macro(auto_config));
337 hover.push_str("```\n");
338
339 hover
340 }
341
342 fn hover_route_macro(&self, route: &RouteMacro) -> String {
344 let mut hover = String::new();
345
346 hover.push_str("# 路由宏\n\n");
347 hover.push_str("注册 HTTP 路由处理器。\n\n");
348 hover.push_str(&format!("**路由路径**: `{}`\n\n", route.path));
349 hover.push_str(&format!(
350 "**HTTP 方法**: {}\n\n",
351 route
352 .methods
353 .iter()
354 .map(|m| format!("`{}`", m.as_str()))
355 .collect::<Vec<_>>()
356 .join(", ")
357 ));
358
359 if !route.middlewares.is_empty() {
360 hover.push_str(&format!(
361 "**中间件**: {}\n\n",
362 route
363 .middlewares
364 .iter()
365 .map(|m| format!("`{}`", m))
366 .collect::<Vec<_>>()
367 .join(", ")
368 ));
369 }
370
371 hover.push_str(&format!("**处理器函数**: `{}`\n\n", route.handler_name));
372 hover.push_str("**展开后的代码**:\n\n");
373 hover.push_str("```rust\n");
374 hover.push_str(&self.expand_route_macro(route));
375 hover.push_str("```\n");
376
377 hover
378 }
379
380 fn hover_job_macro(&self, job: &JobMacro) -> String {
382 let mut hover = String::new();
383
384 hover.push_str("# 任务调度宏\n\n");
385
386 match job {
387 JobMacro::Cron { expression, .. } => {
388 hover.push_str("定时任务,使用 Cron 表达式指定执行时间。\n\n");
389 hover.push_str(&format!("**Cron 表达式**: `{}`\n\n", expression));
390 hover.push_str("**格式**: `秒 分 时 日 月 星期`\n\n");
391 }
392 JobMacro::FixDelay { seconds, .. } => {
393 hover.push_str("固定延迟任务,任务完成后延迟指定秒数再次执行。\n\n");
394 hover.push_str(&format!("**延迟秒数**: `{}`\n\n", seconds));
395 }
396 JobMacro::FixRate { seconds, .. } => {
397 hover.push_str("固定频率任务,每隔指定秒数执行一次。\n\n");
398 hover.push_str(&format!("**频率秒数**: `{}`\n\n", seconds));
399 }
400 }
401
402 hover.push_str("**展开后的代码**:\n\n");
403 hover.push_str("```rust\n");
404 hover.push_str(&self.expand_job_macro(job));
405 hover.push_str("```\n");
406
407 hover
408 }
409
410 pub fn expand_macro(&self, macro_info: &SpringMacro) -> String {
422 match macro_info {
423 SpringMacro::DeriveService(service) => self.expand_service_macro(service),
424 SpringMacro::Inject(inject) => self.expand_inject_macro(inject),
425 SpringMacro::AutoConfig(auto_config) => self.expand_auto_config_macro(auto_config),
426 SpringMacro::Route(route) => self.expand_route_macro(route),
427 SpringMacro::Job(job) => self.expand_job_macro(job),
428 }
429 }
430
431 fn expand_service_macro(&self, service: &ServiceMacro) -> String {
435 let struct_name = &service.struct_name;
436 let mut code = String::new();
437
438 code.push_str("// 原始定义\n");
440 code.push_str("#[derive(Clone)]\n");
441 code.push_str(&format!("pub struct {} {{\n", struct_name));
442 for field in &service.fields {
443 if let Some(inject) = &field.inject {
444 let inject_type = match inject.inject_type {
445 InjectType::Component => "component",
446 InjectType::Config => "config",
447 };
448 if let Some(name) = &inject.component_name {
449 code.push_str(&format!(" #[inject({} = \"{}\")]\n", inject_type, name));
450 } else {
451 code.push_str(&format!(" #[inject({})]\n", inject_type));
452 }
453 }
454 code.push_str(&format!(" pub {}: {},\n", field.name, field.type_name));
455 }
456 code.push_str("}\n\n");
457
458 code.push_str("// 展开后的代码\n");
460 code.push_str(&format!("impl {} {{\n", struct_name));
461 code.push_str(" /// 从应用上下文构建服务实例\n");
462 code.push_str(" pub fn build(app: &AppBuilder) -> Result<Self> {\n");
463
464 for field in &service.fields {
466 if let Some(inject) = &field.inject {
467 match inject.inject_type {
468 InjectType::Component => {
469 if let Some(name) = &inject.component_name {
470 code.push_str(&format!(
471 " let {} = app.get_component::<{}>(\"{}\")?\n",
472 field.name, field.type_name, name
473 ));
474 } else {
475 code.push_str(&format!(
476 " let {} = app.get_component::<{}>()?;\n",
477 field.name, field.type_name
478 ));
479 }
480 }
481 InjectType::Config => {
482 code.push_str(&format!(
483 " let {} = app.get_config::<{}>()?;\n",
484 field.name, field.type_name
485 ));
486 }
487 }
488 } else {
489 code.push_str(&format!(
491 " let {} = Default::default(); // 需要手动初始化\n",
492 field.name
493 ));
494 }
495 }
496
497 code.push_str("\n Ok(Self {\n");
498 for field in &service.fields {
499 code.push_str(&format!(" {},\n", field.name));
500 }
501 code.push_str(" })\n");
502 code.push_str(" }\n");
503 code.push_str("}\n");
504
505 code
506 }
507
508 fn expand_inject_macro(&self, inject: &InjectMacro) -> String {
512 let mut code = String::new();
513
514 code.push_str("// Inject 属性展开\n");
515 code.push_str("// 这个字段将在运行时从应用上下文中注入\n");
516
517 match inject.inject_type {
518 InjectType::Component => {
519 if let Some(name) = &inject.component_name {
520 code.push_str("// 注入类型: 组件\n");
521 code.push_str(&format!("// 组件名称: \"{}\"\n", name));
522 code.push_str(&format!(
523 "// 注入代码: app.get_component::<T>(\"{}\")\n",
524 name
525 ));
526 } else {
527 code.push_str("// 注入类型: 组件\n");
528 code.push_str("// 注入代码: app.get_component::<T>()\n");
529 }
530 }
531 InjectType::Config => {
532 code.push_str("// 注入类型: 配置\n");
533 code.push_str("// 注入代码: app.get_config::<T>()\n");
534 }
535 }
536
537 code
538 }
539
540 fn expand_auto_config_macro(&self, auto_config: &AutoConfigMacro) -> String {
544 let mut code = String::new();
545
546 code.push_str("// AutoConfig 宏展开\n");
547 code.push_str(&format!(
548 "// 配置器类型: {}\n",
549 auto_config.configurator_type
550 ));
551 code.push_str("// 这个函数将在应用启动时自动注册配置\n");
552 code.push_str("// 展开后的代码:\n");
553 code.push_str("// \n");
554 code.push_str("// fn main() {\n");
555 code.push_str(&format!(
556 "// let configurator = {}::new();\n",
557 auto_config.configurator_type
558 ));
559 code.push_str("// configurator.configure(&mut app);\n");
560 code.push_str("// // ... 原函数体\n");
561 code.push_str("// }\n");
562
563 code
564 }
565
566 fn expand_route_macro(&self, route: &RouteMacro) -> String {
570 let mut code = String::new();
571
572 code.push_str("// 路由宏展开\n");
573 code.push_str(&format!("// 路由路径: {}\n", route.path));
574 code.push_str(&format!(
575 "// HTTP 方法: {}\n",
576 route
577 .methods
578 .iter()
579 .map(|m| m.as_str())
580 .collect::<Vec<_>>()
581 .join(", ")
582 ));
583
584 if !route.middlewares.is_empty() {
585 code.push_str(&format!("// 中间件: {}\n", route.middlewares.join(", ")));
586 }
587
588 code.push_str("// \n");
589 code.push_str("// 展开后的代码:\n");
590 code.push_str("// \n");
591
592 for method in &route.methods {
593 code.push_str(&format!(
594 "// router.route(\"{}\", {}, {});\n",
595 route.path,
596 method.as_str().to_lowercase(),
597 route.handler_name
598 ));
599 }
600
601 if !route.middlewares.is_empty() {
602 code.push_str("// \n");
603 code.push_str("// 应用中间件:\n");
604 for middleware in &route.middlewares {
605 code.push_str(&format!("// .layer({})\n", middleware));
606 }
607 }
608
609 code
610 }
611
612 fn expand_job_macro(&self, job: &JobMacro) -> String {
616 let mut code = String::new();
617
618 code.push_str("// 任务调度宏展开\n");
619
620 match job {
621 JobMacro::Cron { expression, .. } => {
622 code.push_str("// 任务类型: Cron\n");
623 code.push_str(&format!("// Cron 表达式: {}\n", expression));
624 code.push_str("// \n");
625 code.push_str("// 展开后的代码:\n");
626 code.push_str("// \n");
627 code.push_str("// scheduler.add_job(\n");
628 code.push_str(&format!(
629 "// CronJob::new(\"{}\", || async {{\n",
630 expression
631 ));
632 code.push_str("// // 任务函数体\n");
633 code.push_str("// }})\n");
634 code.push_str("// );\n");
635 }
636 JobMacro::FixDelay { seconds, .. } => {
637 code.push_str("// 任务类型: FixDelay\n");
638 code.push_str(&format!("// 延迟秒数: {}\n", seconds));
639 code.push_str("// 说明: 任务完成后延迟指定秒数再次执行\n");
640 code.push_str("// \n");
641 code.push_str("// 展开后的代码:\n");
642 code.push_str("// \n");
643 code.push_str("// scheduler.add_job(\n");
644 code.push_str(&format!(
645 "// FixDelayJob::new({}, || async {{\n",
646 seconds
647 ));
648 code.push_str("// // 任务函数体\n");
649 code.push_str("// }})\n");
650 code.push_str("// );\n");
651 }
652 JobMacro::FixRate { seconds, .. } => {
653 code.push_str("// 任务类型: FixRate\n");
654 code.push_str(&format!("// 频率秒数: {}\n", seconds));
655 code.push_str("// 说明: 每隔指定秒数执行一次任务\n");
656 code.push_str("// \n");
657 code.push_str("// 展开后的代码:\n");
658 code.push_str("// \n");
659 code.push_str("// scheduler.add_job(\n");
660 code.push_str(&format!(
661 "// FixRateJob::new({}, || async {{\n",
662 seconds
663 ));
664 code.push_str("// // 任务函数体\n");
665 code.push_str("// }})\n");
666 code.push_str("// );\n");
667 }
668 }
669
670 code
671 }
672
673 pub fn parse(&self, uri: Url, content: String) -> Result<RustDocument, syn::Error> {
686 let _syntax_tree = syn::parse_file(&content)?;
688
689 let doc = RustDocument {
692 uri,
693 content,
694 macros: Vec::new(),
695 };
696
697 Ok(doc)
698 }
699
700 pub fn extract_macros(&self, mut doc: RustDocument) -> Result<RustDocument, syn::Error> {
712 let syntax_tree = syn::parse_file(&doc.content)?;
714
715 let mut macros = Vec::new();
716
717 for item in &syntax_tree.items {
719 match item {
720 syn::Item::Struct(item_struct) => {
722 if let Some(service_macro) = self.extract_service_macro(item_struct) {
724 macros.push(SpringMacro::DeriveService(service_macro));
725 }
726 }
727 syn::Item::Fn(item_fn) => {
729 if let Some(route_macro) = self.extract_route_macro(item_fn) {
731 macros.push(SpringMacro::Route(route_macro));
732 }
733
734 if let Some(auto_config_macro) = self.extract_auto_config_macro(item_fn) {
736 macros.push(SpringMacro::AutoConfig(auto_config_macro));
737 }
738
739 if let Some(job_macro) = self.extract_job_macro(item_fn) {
741 macros.push(SpringMacro::Job(job_macro));
742 }
743 }
744 _ => {}
745 }
746 }
747
748 doc.macros = macros;
749 Ok(doc)
750 }
751
752 fn extract_service_macro(&self, item_struct: &syn::ItemStruct) -> Option<ServiceMacro> {
754 for attr in &item_struct.attrs {
756 if attr.path().is_ident("derive") {
757 if let Ok(meta_list) = attr.meta.require_list() {
759 let has_service = meta_list.tokens.to_string().contains("Service");
761
762 if has_service {
763 let fields = self.extract_fields(&item_struct.fields);
765
766 return Some(ServiceMacro {
767 struct_name: item_struct.ident.to_string(),
768 fields,
769 range: self.span_to_range(&item_struct.ident.span()),
770 });
771 }
772 }
773 }
774 }
775 None
776 }
777
778 fn extract_fields(&self, fields: &syn::Fields) -> Vec<Field> {
780 let mut result = Vec::new();
781
782 if let syn::Fields::Named(fields_named) = fields {
783 for field in &fields_named.named {
784 if let Some(ident) = &field.ident {
785 let inject = self.extract_inject_macro(&field.attrs);
786
787 result.push(Field {
788 name: ident.to_string(),
789 type_name: self.type_to_string(&field.ty),
790 inject,
791 });
792 }
793 }
794 }
795
796 result
797 }
798
799 fn extract_inject_macro(&self, attrs: &[syn::Attribute]) -> Option<InjectMacro> {
801 for attr in attrs {
802 if attr.path().is_ident("inject") {
803 if let Ok(meta_list) = attr.meta.require_list() {
805 let tokens_str = meta_list.tokens.to_string();
806
807 let inject_type = if tokens_str.contains("component") {
809 InjectType::Component
810 } else if tokens_str.contains("config") {
811 InjectType::Config
812 } else {
813 continue;
814 };
815
816 let component_name = self.extract_component_name(&tokens_str);
818
819 return Some(InjectMacro {
820 inject_type,
821 component_name,
822 range: self.span_to_range(&attr.span()),
823 });
824 }
825 }
826 }
827 None
828 }
829
830 fn extract_component_name(&self, tokens_str: &str) -> Option<String> {
832 if let Some(eq_pos) = tokens_str.find('=') {
834 let after_eq = &tokens_str[eq_pos + 1..].trim();
835 let name = after_eq.trim_matches('"').trim();
837 if !name.is_empty() && name != "component" && name != "config" {
838 return Some(name.to_string());
839 }
840 }
841 None
842 }
843
844 fn extract_route_macro(&self, item_fn: &syn::ItemFn) -> Option<RouteMacro> {
846 for attr in &item_fn.attrs {
847 let method_and_path: Option<(Vec<HttpMethod>, String)> = if attr.path().is_ident("get")
849 {
850 self.extract_path_from_attr(attr)
851 .map(|path| (vec![HttpMethod::Get], path))
852 } else if attr.path().is_ident("post") {
853 self.extract_path_from_attr(attr)
854 .map(|path| (vec![HttpMethod::Post], path))
855 } else if attr.path().is_ident("put") {
856 self.extract_path_from_attr(attr)
857 .map(|path| (vec![HttpMethod::Put], path))
858 } else if attr.path().is_ident("delete") {
859 self.extract_path_from_attr(attr)
860 .map(|path| (vec![HttpMethod::Delete], path))
861 } else if attr.path().is_ident("patch") {
862 self.extract_path_from_attr(attr)
863 .map(|path| (vec![HttpMethod::Patch], path))
864 } else if attr.path().is_ident("head") {
865 self.extract_path_from_attr(attr)
866 .map(|path| (vec![HttpMethod::Head], path))
867 } else if attr.path().is_ident("options") {
868 self.extract_path_from_attr(attr)
869 .map(|path| (vec![HttpMethod::Options], path))
870 } else if attr.path().is_ident("route") {
871 self.extract_route_attr(attr)
873 } else {
874 None
875 };
876
877 if let Some((methods, path)) = method_and_path {
878 let middlewares = self.extract_middlewares(&item_fn.attrs);
880
881 return Some(RouteMacro {
882 path,
883 methods,
884 middlewares,
885 handler_name: item_fn.sig.ident.to_string(),
886 range: self.span_to_range(&item_fn.sig.ident.span()),
887 });
888 }
889 }
890 None
891 }
892
893 fn extract_path_from_attr(&self, attr: &syn::Attribute) -> Option<String> {
895 if let Ok(meta_list) = attr.meta.require_list() {
897 let tokens_str = meta_list.tokens.to_string();
898 let path = tokens_str.trim().trim_matches('"');
900 return Some(path.to_string());
901 }
902 None
903 }
904
905 fn extract_route_attr(&self, attr: &syn::Attribute) -> Option<(Vec<HttpMethod>, String)> {
907 if let Ok(meta_list) = attr.meta.require_list() {
908 let tokens_str = meta_list.tokens.to_string();
909
910 let path = if let Some(start) = tokens_str.find('"') {
912 if let Some(end) = tokens_str[start + 1..].find('"') {
913 tokens_str[start + 1..start + 1 + end].to_string()
914 } else {
915 return None;
916 }
917 } else {
918 return None;
919 };
920
921 let mut methods = Vec::new();
923 for part in tokens_str.split(',') {
924 if part.contains("method") {
925 if let Some(eq_pos) = part.find('=') {
926 let method_str = part[eq_pos + 1..].trim().trim_matches('"');
927 if let Some(method) = HttpMethod::parse_method(method_str) {
928 methods.push(method);
929 }
930 }
931 }
932 }
933
934 if !methods.is_empty() {
935 return Some((methods, path));
936 }
937 }
938 None
939 }
940
941 fn extract_middlewares(&self, attrs: &[syn::Attribute]) -> Vec<String> {
943 let mut middlewares = Vec::new();
944
945 for attr in attrs {
946 if attr.path().is_ident("middlewares") {
947 if let Ok(meta_list) = attr.meta.require_list() {
948 let tokens_str = meta_list.tokens.to_string();
949 for part in tokens_str.split(',') {
951 let middleware = part.trim().to_string();
952 if !middleware.is_empty() {
953 middlewares.push(middleware);
954 }
955 }
956 }
957 }
958 }
959
960 middlewares
961 }
962
963 fn extract_auto_config_macro(&self, item_fn: &syn::ItemFn) -> Option<AutoConfigMacro> {
965 for attr in &item_fn.attrs {
966 if attr.path().is_ident("auto_config") {
967 let configurator_type = if let Ok(meta_list) = attr.meta.require_list() {
969 meta_list.tokens.to_string()
970 } else {
971 String::new()
972 };
973
974 return Some(AutoConfigMacro {
975 configurator_type,
976 range: self.span_to_range(&attr.span()),
977 });
978 }
979 }
980 None
981 }
982
983 fn extract_job_macro(&self, item_fn: &syn::ItemFn) -> Option<JobMacro> {
985 for attr in &item_fn.attrs {
986 if attr.path().is_ident("cron") {
987 if let Some(expression) = self.extract_path_from_attr(attr) {
989 return Some(JobMacro::Cron {
990 expression,
991 range: self.span_to_range(&attr.span()),
992 });
993 }
994 } else if attr.path().is_ident("fix_delay") {
995 if let Ok(meta_list) = attr.meta.require_list() {
997 let tokens_str = meta_list.tokens.to_string();
998 if let Ok(seconds) = tokens_str.trim().parse::<u64>() {
999 return Some(JobMacro::FixDelay {
1000 seconds,
1001 range: self.span_to_range(&attr.span()),
1002 });
1003 }
1004 }
1005 } else if attr.path().is_ident("fix_rate") {
1006 if let Ok(meta_list) = attr.meta.require_list() {
1008 let tokens_str = meta_list.tokens.to_string();
1009 if let Ok(seconds) = tokens_str.trim().parse::<u64>() {
1010 return Some(JobMacro::FixRate {
1011 seconds,
1012 range: self.span_to_range(&attr.span()),
1013 });
1014 }
1015 }
1016 }
1017 }
1018 None
1019 }
1020
1021 fn type_to_string(&self, ty: &syn::Type) -> String {
1023 match ty {
1024 syn::Type::Path(type_path) => type_path
1025 .path
1026 .segments
1027 .iter()
1028 .map(|seg| seg.ident.to_string())
1029 .collect::<Vec<_>>()
1030 .join("::"),
1031 _ => "Unknown".to_string(),
1032 }
1033 }
1034
1035 fn span_to_range(&self, _span: &Span) -> Range {
1040 Range {
1041 start: lsp_types::Position {
1042 line: 0,
1043 character: 0,
1044 },
1045 end: lsp_types::Position {
1046 line: 0,
1047 character: 0,
1048 },
1049 }
1050 }
1051
1052 pub fn validate_macro(&self, macro_info: &SpringMacro) -> Vec<lsp_types::Diagnostic> {
1064 match macro_info {
1065 SpringMacro::DeriveService(service) => self.validate_service_macro(service),
1066 SpringMacro::Inject(inject) => self.validate_inject_macro(inject),
1067 SpringMacro::AutoConfig(auto_config) => self.validate_auto_config_macro(auto_config),
1068 SpringMacro::Route(route) => self.validate_route_macro(route),
1069 SpringMacro::Job(job) => self.validate_job_macro(job),
1070 }
1071 }
1072
1073 fn validate_service_macro(&self, service: &ServiceMacro) -> Vec<lsp_types::Diagnostic> {
1077 let mut diagnostics = Vec::new();
1078
1079 for field in &service.fields {
1081 if let Some(inject) = &field.inject {
1082 let inject_diagnostics = self.validate_inject_macro(inject);
1084 diagnostics.extend(inject_diagnostics);
1085
1086 if let Some(name) = &inject.component_name {
1088 if name.is_empty() {
1089 diagnostics.push(lsp_types::Diagnostic {
1090 range: inject.range,
1091 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1092 code: Some(lsp_types::NumberOrString::String("E001".to_string())),
1093 source: Some("spring-lsp".to_string()),
1094 message: format!("字段 '{}' 的组件名称不能为空字符串", field.name),
1095 related_information: None,
1096 tags: None,
1097 code_description: None,
1098 data: None,
1099 });
1100 }
1101 }
1102 }
1103 }
1104
1105 diagnostics
1106 }
1107
1108 fn validate_inject_macro(&self, inject: &InjectMacro) -> Vec<lsp_types::Diagnostic> {
1112 let mut diagnostics = Vec::new();
1113
1114 if inject.inject_type == InjectType::Config && inject.component_name.is_some() {
1116 diagnostics.push(lsp_types::Diagnostic {
1117 range: inject.range,
1118 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1119 code: Some(lsp_types::NumberOrString::String("E002".to_string())),
1120 source: Some("spring-lsp".to_string()),
1121 message: "配置注入 (config) 不应该指定组件名称".to_string(),
1122 related_information: None,
1123 tags: None,
1124 code_description: None,
1125 data: None,
1126 });
1127 }
1128
1129 diagnostics
1130 }
1131
1132 fn validate_auto_config_macro(
1136 &self,
1137 auto_config: &AutoConfigMacro,
1138 ) -> Vec<lsp_types::Diagnostic> {
1139 let mut diagnostics = Vec::new();
1140
1141 if auto_config.configurator_type.is_empty() {
1143 diagnostics.push(lsp_types::Diagnostic {
1144 range: auto_config.range,
1145 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1146 code: Some(lsp_types::NumberOrString::String("E003".to_string())),
1147 source: Some("spring-lsp".to_string()),
1148 message: "AutoConfig 宏必须指定配置器类型".to_string(),
1149 related_information: None,
1150 tags: None,
1151 code_description: None,
1152 data: None,
1153 });
1154 }
1155
1156 diagnostics
1157 }
1158
1159 fn validate_route_macro(&self, route: &RouteMacro) -> Vec<lsp_types::Diagnostic> {
1163 let mut diagnostics = Vec::new();
1164
1165 if route.path.is_empty() {
1167 diagnostics.push(lsp_types::Diagnostic {
1168 range: route.range,
1169 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1170 code: Some(lsp_types::NumberOrString::String("E004".to_string())),
1171 source: Some("spring-lsp".to_string()),
1172 message: "路由路径不能为空".to_string(),
1173 related_information: None,
1174 tags: None,
1175 code_description: None,
1176 data: None,
1177 });
1178 } else {
1179 if !route.path.starts_with('/') {
1181 diagnostics.push(lsp_types::Diagnostic {
1182 range: route.range,
1183 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1184 code: Some(lsp_types::NumberOrString::String("E005".to_string())),
1185 source: Some("spring-lsp".to_string()),
1186 message: format!("路由路径必须以 '/' 开头,当前路径: '{}'", route.path),
1187 related_information: None,
1188 tags: None,
1189 code_description: None,
1190 data: None,
1191 });
1192 }
1193
1194 self.validate_path_parameters(&route.path, route.range, &mut diagnostics);
1196 }
1197
1198 if route.methods.is_empty() {
1200 diagnostics.push(lsp_types::Diagnostic {
1201 range: route.range,
1202 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1203 code: Some(lsp_types::NumberOrString::String("E006".to_string())),
1204 source: Some("spring-lsp".to_string()),
1205 message: "路由必须至少指定一个 HTTP 方法".to_string(),
1206 related_information: None,
1207 tags: None,
1208 code_description: None,
1209 data: None,
1210 });
1211 }
1212
1213 if route.handler_name.is_empty() {
1215 diagnostics.push(lsp_types::Diagnostic {
1216 range: route.range,
1217 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1218 code: Some(lsp_types::NumberOrString::String("E007".to_string())),
1219 source: Some("spring-lsp".to_string()),
1220 message: "路由处理器函数名称不能为空".to_string(),
1221 related_information: None,
1222 tags: None,
1223 code_description: None,
1224 data: None,
1225 });
1226 }
1227
1228 diagnostics
1229 }
1230
1231 fn validate_path_parameters(
1235 &self,
1236 path: &str,
1237 range: Range,
1238 diagnostics: &mut Vec<lsp_types::Diagnostic>,
1239 ) {
1240 let mut open_braces = 0;
1241 let mut param_start = None;
1242
1243 for (i, ch) in path.char_indices() {
1244 match ch {
1245 '{' => {
1246 if open_braces > 0 {
1247 diagnostics.push(lsp_types::Diagnostic {
1249 range,
1250 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1251 code: Some(lsp_types::NumberOrString::String("E008".to_string())),
1252 source: Some("spring-lsp".to_string()),
1253 message: format!("路径参数不能嵌套,位置: {}", i),
1254 related_information: None,
1255 tags: None,
1256 code_description: None,
1257 data: None,
1258 });
1259 }
1260 open_braces += 1;
1261 param_start = Some(i);
1262 }
1263 '}' => {
1264 if open_braces == 0 {
1265 diagnostics.push(lsp_types::Diagnostic {
1267 range,
1268 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1269 code: Some(lsp_types::NumberOrString::String("E009".to_string())),
1270 source: Some("spring-lsp".to_string()),
1271 message: format!("路径参数缺少开括号 '{{', 位置: {}", i),
1272 related_information: None,
1273 tags: None,
1274 code_description: None,
1275 data: None,
1276 });
1277 } else {
1278 open_braces -= 1;
1279
1280 if let Some(start) = param_start {
1282 let param_name = &path[start + 1..i];
1283 if param_name.is_empty() {
1284 diagnostics.push(lsp_types::Diagnostic {
1285 range,
1286 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1287 code: Some(lsp_types::NumberOrString::String(
1288 "E010".to_string(),
1289 )),
1290 source: Some("spring-lsp".to_string()),
1291 message: format!("路径参数名称不能为空,位置: {}", start),
1292 related_information: None,
1293 tags: None,
1294 code_description: None,
1295 data: None,
1296 });
1297 } else if !param_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
1298 diagnostics.push(lsp_types::Diagnostic {
1300 range,
1301 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1302 code: Some(lsp_types::NumberOrString::String(
1303 "E011".to_string(),
1304 )),
1305 source: Some("spring-lsp".to_string()),
1306 message: format!(
1307 "路径参数名称只能包含字母、数字和下划线,当前参数: '{}'",
1308 param_name
1309 ),
1310 related_information: None,
1311 tags: None,
1312 code_description: None,
1313 data: None,
1314 });
1315 }
1316 }
1317 param_start = None;
1318 }
1319 }
1320 _ => {}
1321 }
1322 }
1323
1324 if open_braces > 0 {
1326 diagnostics.push(lsp_types::Diagnostic {
1327 range,
1328 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1329 code: Some(lsp_types::NumberOrString::String("E012".to_string())),
1330 source: Some("spring-lsp".to_string()),
1331 message: "路径参数缺少闭括号 '}'".to_string(),
1332 related_information: None,
1333 tags: None,
1334 code_description: None,
1335 data: None,
1336 });
1337 }
1338 }
1339
1340 fn validate_job_macro(&self, job: &JobMacro) -> Vec<lsp_types::Diagnostic> {
1344 let mut diagnostics = Vec::new();
1345
1346 match job {
1347 JobMacro::Cron { expression, range } => {
1348 if expression.is_empty() {
1350 diagnostics.push(lsp_types::Diagnostic {
1351 range: *range,
1352 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1353 code: Some(lsp_types::NumberOrString::String("E013".to_string())),
1354 source: Some("spring-lsp".to_string()),
1355 message: "Cron 表达式不能为空".to_string(),
1356 related_information: None,
1357 tags: None,
1358 code_description: None,
1359 data: None,
1360 });
1361 } else {
1362 self.validate_cron_expression(expression, *range, &mut diagnostics);
1364 }
1365 }
1366 JobMacro::FixDelay { seconds, range } => {
1367 if *seconds == 0 {
1369 diagnostics.push(lsp_types::Diagnostic {
1370 range: *range,
1371 severity: Some(lsp_types::DiagnosticSeverity::WARNING),
1372 code: Some(lsp_types::NumberOrString::String("W001".to_string())),
1373 source: Some("spring-lsp".to_string()),
1374 message: "延迟秒数为 0 可能不是预期的行为".to_string(),
1375 related_information: None,
1376 tags: None,
1377 code_description: None,
1378 data: None,
1379 });
1380 }
1381 }
1382 JobMacro::FixRate { seconds, range } => {
1383 if *seconds == 0 {
1385 diagnostics.push(lsp_types::Diagnostic {
1386 range: *range,
1387 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1388 code: Some(lsp_types::NumberOrString::String("E014".to_string())),
1389 source: Some("spring-lsp".to_string()),
1390 message: "频率秒数不能为 0".to_string(),
1391 related_information: None,
1392 tags: None,
1393 code_description: None,
1394 data: None,
1395 });
1396 }
1397 }
1398 }
1399
1400 diagnostics
1401 }
1402
1403 fn validate_cron_expression(
1407 &self,
1408 expression: &str,
1409 range: Range,
1410 diagnostics: &mut Vec<lsp_types::Diagnostic>,
1411 ) {
1412 let parts: Vec<&str> = expression.split_whitespace().collect();
1413
1414 if parts.len() != 6 {
1416 diagnostics.push(lsp_types::Diagnostic {
1417 range,
1418 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
1419 code: Some(lsp_types::NumberOrString::String("E015".to_string())),
1420 source: Some("spring-lsp".to_string()),
1421 message: format!(
1422 "Cron 表达式应该包含 6 个部分(秒 分 时 日 月 星期),当前有 {} 个部分",
1423 parts.len()
1424 ),
1425 related_information: None,
1426 tags: None,
1427 code_description: None,
1428 data: None,
1429 });
1430 }
1431 }
1432}
1433
1434impl Default for MacroAnalyzer {
1435 fn default() -> Self {
1436 Self::new()
1437 }
1438}
1439
1440#[cfg(test)]
1441mod tests;