Skip to main content

spring_lsp/
toml_analyzer.rs

1//! TOML 配置文件分析模块
2
3use lsp_types::{
4    Diagnostic, DiagnosticSeverity, Hover, HoverContents, MarkupContent, MarkupKind, Position,
5    Range,
6};
7use std::collections::HashMap;
8use taplo::dom::node::IntegerValue;
9
10use crate::schema::{PropertySchema, SchemaProvider, TypeInfo};
11
12/// TOML 文档
13///
14/// 表示解析后的 TOML 配置文件,包含环境变量引用、配置节和属性等信息
15#[derive(Debug, Clone)]
16pub struct TomlDocument {
17    /// taplo 的 DOM 根节点
18    pub root: taplo::dom::Node,
19    /// 提取的环境变量引用
20    pub env_vars: Vec<EnvVarReference>,
21    /// 提取的配置节(键为配置前缀)
22    pub config_sections: HashMap<String, ConfigSection>,
23    /// 原始内容(用于计算行列位置)
24    pub content: String,
25}
26
27/// 环境变量引用
28///
29/// 表示 TOML 配置中的环境变量插值,格式为 `${VAR:default}` 或 `${VAR}`
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct EnvVarReference {
32    /// 环境变量名称
33    pub name: String,
34    /// 默认值(可选)
35    pub default: Option<String>,
36    /// 在文档中的位置范围
37    pub range: Range,
38}
39
40/// 配置节
41///
42/// 表示 TOML 配置文件中的一个配置节,如 `[web]` 或 `[redis]`
43#[derive(Debug, Clone)]
44pub struct ConfigSection {
45    /// 配置前缀(节名称)
46    pub prefix: String,
47    /// 配置属性映射(键为属性名)
48    pub properties: HashMap<String, ConfigProperty>,
49    /// 在文档中的位置范围
50    pub range: Range,
51}
52
53/// 配置属性
54///
55/// 表示配置节中的单个属性,如 `host = "localhost"`
56#[derive(Debug, Clone)]
57pub struct ConfigProperty {
58    /// 属性键
59    pub key: String,
60    /// 属性值
61    pub value: ConfigValue,
62    /// 在文档中的位置范围
63    pub range: Range,
64}
65
66/// 配置值
67///
68/// 表示 TOML 配置属性的值,支持多种类型
69#[derive(Debug, Clone, PartialEq)]
70pub enum ConfigValue {
71    /// 字符串值
72    String(String),
73    /// 整数值
74    Integer(i64),
75    /// 浮点数值
76    Float(f64),
77    /// 布尔值
78    Boolean(bool),
79    /// 数组值
80    Array(Vec<ConfigValue>),
81    /// 表(对象)值
82    Table(HashMap<String, ConfigValue>),
83}
84
85/// TOML 分析器
86///
87/// 负责解析 TOML 配置文件,提取环境变量引用和配置节
88pub struct TomlAnalyzer {
89    /// Schema 提供者
90    schema_provider: SchemaProvider,
91}
92
93impl TomlAnalyzer {
94    /// 创建新的 TOML 分析器
95    pub fn new(schema_provider: SchemaProvider) -> Self {
96        Self { schema_provider }
97    }
98
99    /// 获取 Schema 提供者的引用
100    pub fn schema_provider(&self) -> &SchemaProvider {
101        &self.schema_provider
102    }
103
104    /// 提供悬停提示
105    ///
106    /// 当用户悬停在 TOML 配置项或环境变量上时,显示相关文档和信息
107    ///
108    /// # 参数
109    ///
110    /// * `doc` - 已解析的 TOML 文档
111    /// * `position` - 光标位置
112    ///
113    /// # 返回
114    ///
115    /// 如果光标位置有可显示的信息,返回 `Some(Hover)`,否则返回 `None`
116    ///
117    /// # 功能
118    ///
119    /// 1. 配置项悬停:显示配置项的文档、类型信息、默认值等
120    /// 2. 环境变量悬停:显示环境变量的当前值(如果可用)
121    pub fn hover(&self, doc: &TomlDocument, position: Position) -> Option<Hover> {
122        // 首先检查是否悬停在环境变量上
123        if let Some(hover) = self.hover_env_var(doc, position) {
124            return Some(hover);
125        }
126
127        // 然后检查是否悬停在配置项上
128        if let Some(hover) = self.hover_config_property(doc, position) {
129            return Some(hover);
130        }
131
132        None
133    }
134
135    /// 为环境变量提供悬停提示
136    fn hover_env_var(&self, doc: &TomlDocument, position: Position) -> Option<Hover> {
137        // 查找光标位置的环境变量
138        for env_var in &doc.env_vars {
139            if self.position_in_range(position, env_var.range) {
140                let mut hover_text = String::new();
141
142                // 添加标题
143                hover_text.push_str("# 环境变量\n\n");
144
145                // 添加变量名
146                hover_text.push_str(&format!("**变量名**: `{}`\n\n", env_var.name));
147
148                // 添加默认值(如果有)
149                if let Some(default) = &env_var.default {
150                    hover_text.push_str(&format!("**默认值**: `{}`\n\n", default));
151                }
152
153                // 尝试获取环境变量的当前值
154                if let Ok(value) = std::env::var(&env_var.name) {
155                    hover_text.push_str(&format!("**当前值**: `{}`\n\n", value));
156                } else {
157                    hover_text.push_str("**当前值**: *未设置*\n\n");
158                }
159
160                // 添加说明
161                hover_text.push_str("**说明**:\n\n");
162                hover_text.push_str("环境变量插值允许在配置文件中引用系统环境变量。\n\n");
163                hover_text.push_str("**格式**:\n");
164                hover_text.push_str("- `${VAR}` - 引用环境变量,如果未设置则报错\n");
165                hover_text.push_str("- `${VAR:default}` - 引用环境变量,如果未设置则使用默认值\n");
166
167                return Some(Hover {
168                    contents: HoverContents::Markup(MarkupContent {
169                        kind: MarkupKind::Markdown,
170                        value: hover_text,
171                    }),
172                    range: Some(env_var.range),
173                });
174            }
175        }
176
177        None
178    }
179
180    /// 为配置项提供悬停提示
181    fn hover_config_property(&self, doc: &TomlDocument, position: Position) -> Option<Hover> {
182        // 遍历所有配置节
183        for (prefix, section) in &doc.config_sections {
184            // 检查是否悬停在配置节的某个属性上
185            for (key, property) in &section.properties {
186                if self.position_in_range(position, property.range) {
187                    // 获取该配置项的 Schema
188                    if let Some(plugin_schema) = self.schema_provider.get_plugin_schema(prefix) {
189                        if let Some(property_schema) = plugin_schema.properties.get(key) {
190                            return Some(self.create_property_hover(
191                                prefix,
192                                key,
193                                property,
194                                property_schema,
195                            ));
196                        }
197                    }
198
199                    // 如果没有找到 Schema,返回基本信息
200                    return Some(self.create_basic_property_hover(prefix, key, property));
201                }
202            }
203        }
204
205        None
206    }
207
208    /// 创建配置项的悬停提示(有 Schema)
209    fn create_property_hover(
210        &self,
211        prefix: &str,
212        key: &str,
213        property: &ConfigProperty,
214        schema: &PropertySchema,
215    ) -> Hover {
216        let mut hover_text = String::new();
217
218        // 添加标题
219        hover_text.push_str(&format!("# 配置项: `{}.{}`\n\n", prefix, key));
220
221        // 添加描述
222        if !schema.description.is_empty() {
223            hover_text.push_str(&format!("{}\n\n", schema.description));
224        }
225
226        // 添加类型信息
227        hover_text.push_str(&format!(
228            "**类型**: {}\n\n",
229            self.type_info_to_string(&schema.type_info)
230        ));
231
232        // 添加当前值
233        hover_text.push_str(&format!(
234            "**当前值**: `{}`\n\n",
235            self.config_value_to_string(&property.value)
236        ));
237
238        // 添加默认值(如果有)
239        if let Some(default) = &schema.default {
240            hover_text.push_str(&format!(
241                "**默认值**: `{}`\n\n",
242                self.value_to_string(default)
243            ));
244        }
245
246        // 添加是否必需
247        if schema.required {
248            hover_text.push_str("**必需**: 是\n\n");
249        }
250
251        // 添加枚举值(如果有)
252        if let TypeInfo::String {
253            enum_values: Some(enum_vals),
254            ..
255        } = &schema.type_info
256        {
257            hover_text.push_str("**允许的值**:\n");
258            for val in enum_vals {
259                hover_text.push_str(&format!("- `{}`\n", val));
260            }
261            hover_text.push('\n');
262        }
263
264        // 添加范围限制(如果有)
265        match &schema.type_info {
266            TypeInfo::Integer { min, max } => {
267                if min.is_some() || max.is_some() {
268                    hover_text.push_str("**值范围**:\n");
269                    if let Some(min_val) = min {
270                        hover_text.push_str(&format!("- 最小值: `{}`\n", min_val));
271                    }
272                    if let Some(max_val) = max {
273                        hover_text.push_str(&format!("- 最大值: `{}`\n", max_val));
274                    }
275                    hover_text.push('\n');
276                }
277            }
278            TypeInfo::Float { min, max } => {
279                if min.is_some() || max.is_some() {
280                    hover_text.push_str("**值范围**:\n");
281                    if let Some(min_val) = min {
282                        hover_text.push_str(&format!("- 最小值: `{}`\n", min_val));
283                    }
284                    if let Some(max_val) = max {
285                        hover_text.push_str(&format!("- 最大值: `{}`\n", max_val));
286                    }
287                    hover_text.push('\n');
288                }
289            }
290            TypeInfo::String {
291                min_length,
292                max_length,
293                ..
294            } => {
295                if min_length.is_some() || max_length.is_some() {
296                    hover_text.push_str("**长度限制**:\n");
297                    if let Some(min_len) = min_length {
298                        hover_text.push_str(&format!("- 最小长度: `{}`\n", min_len));
299                    }
300                    if let Some(max_len) = max_length {
301                        hover_text.push_str(&format!("- 最大长度: `{}`\n", max_len));
302                    }
303                    hover_text.push('\n');
304                }
305            }
306            _ => {}
307        }
308
309        // 添加示例代码(如果有)
310        if let Some(example) = &schema.example {
311            hover_text.push_str("**示例**:\n\n");
312            hover_text.push_str("```toml\n");
313            hover_text.push_str(example);
314            hover_text.push_str("\n```\n\n");
315        }
316
317        // 添加废弃警告(如果有)
318        if let Some(deprecated_msg) = &schema.deprecated {
319            hover_text.push_str(&format!("⚠️ **已废弃**: {}\n\n", deprecated_msg));
320        }
321
322        // 添加配置文件位置提示
323        hover_text.push_str("---\n\n");
324        hover_text.push_str(&format!("*配置节*: `[{}]`\n", prefix));
325        hover_text.push_str("*配置文件*: `config/app.toml`\n");
326
327        Hover {
328            contents: HoverContents::Markup(MarkupContent {
329                kind: MarkupKind::Markdown,
330                value: hover_text,
331            }),
332            range: Some(property.range),
333        }
334    }
335
336    /// 创建配置项的基本悬停提示(无 Schema)
337    fn create_basic_property_hover(
338        &self,
339        prefix: &str,
340        key: &str,
341        property: &ConfigProperty,
342    ) -> Hover {
343        let mut hover_text = String::new();
344
345        // 添加标题
346        hover_text.push_str(&format!("# 配置项: `{}.{}`\n\n", prefix, key));
347
348        // 添加当前值
349        hover_text.push_str(&format!(
350            "**当前值**: `{}`\n\n",
351            self.config_value_to_string(&property.value)
352        ));
353
354        // 添加类型
355        hover_text.push_str(&format!(
356            "**类型**: {}\n\n",
357            self.config_value_type_name(&property.value)
358        ));
359
360        // 添加警告
361        hover_text.push_str("⚠️ **警告**: 此配置项未在 Schema 中定义\n\n");
362
363        // 添加配置文件位置提示
364        hover_text.push_str("---\n\n");
365        hover_text.push_str(&format!("*配置节*: `[{}]`\n", prefix));
366        hover_text.push_str("*配置文件*: `config/app.toml`\n");
367
368        Hover {
369            contents: HoverContents::Markup(MarkupContent {
370                kind: MarkupKind::Markdown,
371                value: hover_text,
372            }),
373            range: Some(property.range),
374        }
375    }
376
377    /// 检查位置是否在范围内
378    fn position_in_range(&self, position: Position, range: Range) -> bool {
379        // 检查行号
380        if position.line < range.start.line || position.line > range.end.line {
381            return false;
382        }
383
384        // 如果在同一行,检查字符位置
385        if position.line == range.start.line && position.character < range.start.character {
386            return false;
387        }
388
389        if position.line == range.end.line && position.character > range.end.character {
390            return false;
391        }
392
393        true
394    }
395
396    /// 将配置值转换为字符串
397    fn config_value_to_string(&self, value: &ConfigValue) -> String {
398        match value {
399            ConfigValue::String(s) => format!("\"{}\"", s),
400            ConfigValue::Integer(i) => i.to_string(),
401            ConfigValue::Float(f) => f.to_string(),
402            ConfigValue::Boolean(b) => b.to_string(),
403            ConfigValue::Array(arr) => {
404                let items: Vec<String> =
405                    arr.iter().map(|v| self.config_value_to_string(v)).collect();
406                format!("[{}]", items.join(", "))
407            }
408            ConfigValue::Table(table) => {
409                let items: Vec<String> = table
410                    .iter()
411                    .map(|(k, v)| format!("{} = {}", k, self.config_value_to_string(v)))
412                    .collect();
413                format!("{{ {} }}", items.join(", "))
414            }
415        }
416    }
417
418    /// 将 Schema 中的值转换为字符串
419    fn value_to_string(&self, value: &crate::schema::Value) -> String {
420        match value {
421            crate::schema::Value::String(s) => format!("\"{}\"", s),
422            crate::schema::Value::Integer(n) => n.to_string(),
423            crate::schema::Value::Float(f) => f.to_string(),
424            crate::schema::Value::Boolean(b) => b.to_string(),
425            crate::schema::Value::Array(arr) => {
426                let items: Vec<String> = arr.iter().map(|v| self.value_to_string(v)).collect();
427                format!("[{}]", items.join(", "))
428            }
429            crate::schema::Value::Table(obj) => {
430                let items: Vec<String> = obj
431                    .iter()
432                    .map(|(k, v)| format!("{} = {}", k, self.value_to_string(v)))
433                    .collect();
434                format!("{{ {} }}", items.join(", "))
435            }
436        }
437    }
438
439    /// 验证配置文件
440    ///
441    /// 根据 Schema 验证配置文件,生成诊断信息
442    ///
443    /// # 参数
444    ///
445    /// * `doc` - 已解析的 TOML 文档
446    ///
447    /// # 返回
448    ///
449    /// 诊断信息列表,包含错误、警告等
450    ///
451    /// # 验证项
452    ///
453    /// 1. 配置项定义检查:检查配置项是否在 Schema 中定义
454    /// 2. 类型验证:检查配置值类型是否匹配
455    /// 3. 必需项检查:检查必需的配置项是否存在
456    /// 4. 废弃项检查:检查是否使用了废弃的配置项
457    /// 5. 环境变量语法验证:检查环境变量插值语法是否正确
458    /// 6. 值范围验证:检查配置值是否在允许的范围内
459    pub fn validate(&self, doc: &TomlDocument) -> Vec<Diagnostic> {
460        let mut diagnostics = Vec::new();
461
462        // 1. 验证环境变量语法
463        diagnostics.extend(self.validate_env_var_syntax(&doc.env_vars));
464
465        // 2. 验证配置节和属性
466        for (prefix, section) in &doc.config_sections {
467            // 检查配置节是否在 Schema 中定义
468            if let Some(plugin_schema) = self.schema_provider.get_plugin_schema(prefix) {
469                // 验证配置节中的属性
470                diagnostics.extend(self.validate_section(section, &plugin_schema));
471
472                // 检查必需的配置项是否存在
473                diagnostics.extend(self.validate_required_properties(section, &plugin_schema));
474            } else {
475                // 配置节未在 Schema 中定义
476                diagnostics.push(Diagnostic {
477                    range: section.range,
478                    severity: Some(DiagnosticSeverity::ERROR),
479                    code: Some(lsp_types::NumberOrString::String(
480                        "undefined-section".to_string(),
481                    )),
482                    message: format!("配置节 '{}' 未在 Schema 中定义", prefix),
483                    source: Some("spring-lsp".to_string()),
484                    ..Default::default()
485                });
486            }
487        }
488
489        diagnostics
490    }
491
492    /// 验证环境变量语法
493    fn validate_env_var_syntax(&self, env_vars: &[EnvVarReference]) -> Vec<Diagnostic> {
494        let mut diagnostics = Vec::new();
495
496        for env_var in env_vars {
497            // 检查变量名是否为空
498            if env_var.name.is_empty() {
499                diagnostics.push(Diagnostic {
500                    range: env_var.range,
501                    severity: Some(DiagnosticSeverity::ERROR),
502                    code: Some(lsp_types::NumberOrString::String(
503                        "empty-var-name".to_string(),
504                    )),
505                    message: "环境变量名不能为空".to_string(),
506                    source: Some("spring-lsp".to_string()),
507                    ..Default::default()
508                });
509            }
510
511            // 检查变量名是否符合命名规范(大写字母、数字、下划线)
512            if !env_var
513                .name
514                .chars()
515                .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
516            {
517                diagnostics.push(Diagnostic {
518                    range: env_var.range,
519                    severity: Some(DiagnosticSeverity::WARNING),
520                    code: Some(lsp_types::NumberOrString::String(
521                        "invalid-var-name".to_string(),
522                    )),
523                    message: format!(
524                        "环境变量名 '{}' 不符合命名规范,建议使用大写字母、数字和下划线",
525                        env_var.name
526                    ),
527                    source: Some("spring-lsp".to_string()),
528                    ..Default::default()
529                });
530            }
531        }
532
533        diagnostics
534    }
535
536    /// 验证配置节中的属性
537    fn validate_section(
538        &self,
539        section: &ConfigSection,
540        plugin_schema: &crate::schema::PluginSchema,
541    ) -> Vec<Diagnostic> {
542        let mut diagnostics = Vec::new();
543
544        for (key, property) in &section.properties {
545            if let Some(property_schema) = plugin_schema.properties.get(key) {
546                // 检查是否废弃
547                if let Some(deprecated_msg) = &property_schema.deprecated {
548                    diagnostics.push(Diagnostic {
549                        range: property.range,
550                        severity: Some(DiagnosticSeverity::WARNING),
551                        code: Some(lsp_types::NumberOrString::String(
552                            "deprecated-property".to_string(),
553                        )),
554                        message: format!("配置项 '{}' 已废弃: {}", key, deprecated_msg),
555                        source: Some("spring-lsp".to_string()),
556                        ..Default::default()
557                    });
558                }
559
560                // 验证类型
561                diagnostics.extend(self.validate_property_type(property, property_schema));
562
563                // 验证值范围
564                diagnostics.extend(self.validate_property_range(property, property_schema));
565            } else {
566                // 配置项未在 Schema 中定义
567                diagnostics.push(Diagnostic {
568                    range: property.range,
569                    severity: Some(DiagnosticSeverity::ERROR),
570                    code: Some(lsp_types::NumberOrString::String(
571                        "undefined-property".to_string(),
572                    )),
573                    message: format!("配置项 '{}' 未在 Schema 中定义", key),
574                    source: Some("spring-lsp".to_string()),
575                    ..Default::default()
576                });
577            }
578        }
579
580        diagnostics
581    }
582
583    /// 验证配置属性类型
584    fn validate_property_type(
585        &self,
586        property: &ConfigProperty,
587        schema: &PropertySchema,
588    ) -> Vec<Diagnostic> {
589        let mut diagnostics = Vec::new();
590
591        let type_matches = matches!(
592            (&property.value, &schema.type_info),
593            (ConfigValue::String(_), TypeInfo::String { .. })
594                | (ConfigValue::Integer(_), TypeInfo::Integer { .. })
595                | (ConfigValue::Float(_), TypeInfo::Float { .. })
596                | (ConfigValue::Boolean(_), TypeInfo::Boolean)
597                | (ConfigValue::Array(_), TypeInfo::Array { .. })
598                | (ConfigValue::Table(_), TypeInfo::Object { .. })
599        );
600
601        if !type_matches {
602            let expected_type = self.type_info_to_string(&schema.type_info);
603            let actual_type = self.config_value_type_name(&property.value);
604
605            diagnostics.push(Diagnostic {
606                range: property.range,
607                severity: Some(DiagnosticSeverity::ERROR),
608                code: Some(lsp_types::NumberOrString::String(
609                    "type-mismatch".to_string(),
610                )),
611                message: format!(
612                    "配置项 '{}' 的类型不匹配:期望 {},实际 {}",
613                    property.key, expected_type, actual_type
614                ),
615                source: Some("spring-lsp".to_string()),
616                ..Default::default()
617            });
618        }
619
620        diagnostics
621    }
622
623    /// 验证配置属性值范围
624    fn validate_property_range(
625        &self,
626        property: &ConfigProperty,
627        schema: &PropertySchema,
628    ) -> Vec<Diagnostic> {
629        let mut diagnostics = Vec::new();
630
631        match (&property.value, &schema.type_info) {
632            // 验证字符串长度和枚举值
633            (
634                ConfigValue::String(s),
635                TypeInfo::String {
636                    enum_values,
637                    min_length,
638                    max_length,
639                },
640            ) => {
641                // 检查枚举值
642                if let Some(enum_vals) = enum_values {
643                    if !enum_vals.contains(s) {
644                        diagnostics.push(Diagnostic {
645                            range: property.range,
646                            severity: Some(DiagnosticSeverity::ERROR),
647                            code: Some(lsp_types::NumberOrString::String(
648                                "invalid-enum-value".to_string(),
649                            )),
650                            message: format!(
651                                "配置项 '{}' 的值 '{}' 不在允许的枚举值中:{:?}",
652                                property.key, s, enum_vals
653                            ),
654                            source: Some("spring-lsp".to_string()),
655                            ..Default::default()
656                        });
657                    }
658                }
659
660                // 检查最小长度
661                if let Some(min_len) = min_length {
662                    if s.len() < *min_len {
663                        diagnostics.push(Diagnostic {
664                            range: property.range,
665                            severity: Some(DiagnosticSeverity::ERROR),
666                            code: Some(lsp_types::NumberOrString::String(
667                                "string-too-short".to_string(),
668                            )),
669                            message: format!(
670                                "配置项 '{}' 的值长度 {} 小于最小长度 {}",
671                                property.key,
672                                s.len(),
673                                min_len
674                            ),
675                            source: Some("spring-lsp".to_string()),
676                            ..Default::default()
677                        });
678                    }
679                }
680
681                // 检查最大长度
682                if let Some(max_len) = max_length {
683                    if s.len() > *max_len {
684                        diagnostics.push(Diagnostic {
685                            range: property.range,
686                            severity: Some(DiagnosticSeverity::ERROR),
687                            code: Some(lsp_types::NumberOrString::String(
688                                "string-too-long".to_string(),
689                            )),
690                            message: format!(
691                                "配置项 '{}' 的值长度 {} 超过最大长度 {}",
692                                property.key,
693                                s.len(),
694                                max_len
695                            ),
696                            source: Some("spring-lsp".to_string()),
697                            ..Default::default()
698                        });
699                    }
700                }
701            }
702
703            // 验证整数范围
704            (ConfigValue::Integer(i), TypeInfo::Integer { min, max }) => {
705                if let Some(min_val) = min {
706                    if *i < *min_val {
707                        diagnostics.push(Diagnostic {
708                            range: property.range,
709                            severity: Some(DiagnosticSeverity::ERROR),
710                            code: Some(lsp_types::NumberOrString::String(
711                                "value-too-small".to_string(),
712                            )),
713                            message: format!(
714                                "配置项 '{}' 的值 {} 小于最小值 {}",
715                                property.key, i, min_val
716                            ),
717                            source: Some("spring-lsp".to_string()),
718                            ..Default::default()
719                        });
720                    }
721                }
722
723                if let Some(max_val) = max {
724                    if *i > *max_val {
725                        diagnostics.push(Diagnostic {
726                            range: property.range,
727                            severity: Some(DiagnosticSeverity::ERROR),
728                            code: Some(lsp_types::NumberOrString::String(
729                                "value-too-large".to_string(),
730                            )),
731                            message: format!(
732                                "配置项 '{}' 的值 {} 超过最大值 {}",
733                                property.key, i, max_val
734                            ),
735                            source: Some("spring-lsp".to_string()),
736                            ..Default::default()
737                        });
738                    }
739                }
740            }
741
742            // 验证浮点数范围
743            (ConfigValue::Float(f), TypeInfo::Float { min, max }) => {
744                if let Some(min_val) = min {
745                    if *f < *min_val {
746                        diagnostics.push(Diagnostic {
747                            range: property.range,
748                            severity: Some(DiagnosticSeverity::ERROR),
749                            code: Some(lsp_types::NumberOrString::String(
750                                "value-too-small".to_string(),
751                            )),
752                            message: format!(
753                                "配置项 '{}' 的值 {} 小于最小值 {}",
754                                property.key, f, min_val
755                            ),
756                            source: Some("spring-lsp".to_string()),
757                            ..Default::default()
758                        });
759                    }
760                }
761
762                if let Some(max_val) = max {
763                    if *f > *max_val {
764                        diagnostics.push(Diagnostic {
765                            range: property.range,
766                            severity: Some(DiagnosticSeverity::ERROR),
767                            code: Some(lsp_types::NumberOrString::String(
768                                "value-too-large".to_string(),
769                            )),
770                            message: format!(
771                                "配置项 '{}' 的值 {} 超过最大值 {}",
772                                property.key, f, max_val
773                            ),
774                            source: Some("spring-lsp".to_string()),
775                            ..Default::default()
776                        });
777                    }
778                }
779            }
780
781            _ => {}
782        }
783
784        diagnostics
785    }
786
787    /// 验证必需的配置项
788    fn validate_required_properties(
789        &self,
790        section: &ConfigSection,
791        plugin_schema: &crate::schema::PluginSchema,
792    ) -> Vec<Diagnostic> {
793        let mut diagnostics = Vec::new();
794
795        for (key, property_schema) in &plugin_schema.properties {
796            if property_schema.required && !section.properties.contains_key(key) {
797                diagnostics.push(Diagnostic {
798                    range: section.range,
799                    severity: Some(DiagnosticSeverity::WARNING),
800                    code: Some(lsp_types::NumberOrString::String(
801                        "missing-required-property".to_string(),
802                    )),
803                    message: format!("缺少必需的配置项 '{}'", key),
804                    source: Some("spring-lsp".to_string()),
805                    ..Default::default()
806                });
807            }
808        }
809
810        diagnostics
811    }
812
813    /// 将 TypeInfo 转换为字符串描述
814    fn type_info_to_string(&self, type_info: &TypeInfo) -> String {
815        match type_info {
816            TypeInfo::String { .. } => "字符串".to_string(),
817            TypeInfo::Integer { .. } => "整数".to_string(),
818            TypeInfo::Float { .. } => "浮点数".to_string(),
819            TypeInfo::Boolean => "布尔值".to_string(),
820            TypeInfo::Array { .. } => "数组".to_string(),
821            TypeInfo::Object { .. } => "对象".to_string(),
822        }
823    }
824
825    /// 获取配置值的类型名称
826    fn config_value_type_name(&self, value: &ConfigValue) -> String {
827        match value {
828            ConfigValue::String(_) => "字符串".to_string(),
829            ConfigValue::Integer(_) => "整数".to_string(),
830            ConfigValue::Float(_) => "浮点数".to_string(),
831            ConfigValue::Boolean(_) => "布尔值".to_string(),
832            ConfigValue::Array(_) => "数组".to_string(),
833            ConfigValue::Table(_) => "对象".to_string(),
834        }
835    }
836
837    /// 解析 TOML 文档
838    ///
839    /// 使用 taplo 解析 TOML 内容,提取环境变量引用和配置节
840    ///
841    /// # 参数
842    ///
843    /// * `content` - TOML 文件内容
844    ///
845    /// # 返回
846    ///
847    /// 成功时返回 `TomlDocument`,失败时返回错误信息
848    ///
849    /// # 示例
850    ///
851    /// ```
852    /// use spring_lsp::toml_analyzer::TomlAnalyzer;
853    /// use spring_lsp::schema::SchemaProvider;
854    ///
855    /// let schema_provider = SchemaProvider::new();
856    /// let analyzer = TomlAnalyzer::new(schema_provider);
857    /// let doc = analyzer.parse("[web]\nhost = \"localhost\"").unwrap();
858    /// assert_eq!(doc.config_sections.len(), 1);
859    /// ```
860    pub fn parse(&self, content: &str) -> Result<TomlDocument, String> {
861        // 使用 taplo 解析 TOML
862        let parse_result = taplo::parser::parse(content);
863
864        // 检查语法错误
865        if !parse_result.errors.is_empty() {
866            let error_messages: Vec<String> = parse_result
867                .errors
868                .iter()
869                .map(|e| format!("{:?}:{:?} - {}", e.range.start(), e.range.end(), e.message))
870                .collect();
871            return Err(format!("TOML 语法错误: {}", error_messages.join("; ")));
872        }
873
874        // 转换为 DOM
875        let root = parse_result.into_dom();
876
877        // 提取环境变量引用
878        let env_vars = self.extract_env_vars(content);
879
880        // 提取配置节
881        let config_sections = self.extract_config_sections(&root, content);
882
883        Ok(TomlDocument {
884            root,
885            env_vars,
886            config_sections,
887            content: content.to_string(),
888        })
889    }
890
891    /// 提取环境变量引用
892    ///
893    /// 识别 `${VAR:default}` 或 `${VAR}` 格式的环境变量插值
894    fn extract_env_vars(&self, content: &str) -> Vec<EnvVarReference> {
895        let mut env_vars = Vec::new();
896        let mut line = 0;
897        let mut line_start = 0;
898
899        for (byte_offset, _) in content.char_indices() {
900            // 更新行号和行起始位置
901            if content[byte_offset..].starts_with('\n') {
902                line += 1;
903                line_start = byte_offset + 1;
904            }
905
906            // 查找 ${
907            if content[byte_offset..].starts_with("${") {
908                // 查找对应的 }
909                if let Some(end_offset) = content[byte_offset + 2..].find('}') {
910                    let end_offset = byte_offset + 2 + end_offset;
911                    let var_content = &content[byte_offset + 2..end_offset];
912
913                    // 解析变量名和默认值
914                    let (name, default) = if let Some(colon_pos) = var_content.find(':') {
915                        let name = var_content[..colon_pos].to_string();
916                        let default = Some(var_content[colon_pos + 1..].to_string());
917                        (name, default)
918                    } else {
919                        (var_content.to_string(), None)
920                    };
921
922                    // 计算位置
923                    let start_char = byte_offset - line_start;
924                    let end_char = end_offset + 1 - line_start;
925
926                    env_vars.push(EnvVarReference {
927                        name,
928                        default,
929                        range: Range {
930                            start: lsp_types::Position {
931                                line: line as u32,
932                                character: start_char as u32,
933                            },
934                            end: lsp_types::Position {
935                                line: line as u32,
936                                character: end_char as u32,
937                            },
938                        },
939                    });
940                }
941            }
942        }
943
944        env_vars
945    }
946
947    /// 提取配置节
948    ///
949    /// 遍历 TOML DOM 树,提取所有配置节和属性
950    fn extract_config_sections(
951        &self,
952        root: &taplo::dom::Node,
953        content: &str,
954    ) -> HashMap<String, ConfigSection> {
955        let mut sections = HashMap::new();
956
957        // 获取根表
958        if let Some(table) = root.as_table() {
959            let entries = table.entries();
960
961            // 使用 get() 获取 Arc 引用,然后迭代
962            let entries_arc = entries.get();
963            for (key, value) in entries_arc.iter() {
964                let prefix = key.value().to_string();
965
966                // 只处理表类型的节(配置节)
967                if value.as_table().is_some() {
968                    let properties = self.extract_properties(value, content);
969                    let range = self.node_to_range(value, content);
970
971                    sections.insert(
972                        prefix.clone(),
973                        ConfigSection {
974                            prefix,
975                            properties,
976                            range,
977                        },
978                    );
979                }
980            }
981        }
982
983        sections
984    }
985
986    /// 提取配置属性
987    ///
988    /// 从 TOML 节点中提取所有属性
989    fn extract_properties(
990        &self,
991        node: &taplo::dom::Node,
992        content: &str,
993    ) -> HashMap<String, ConfigProperty> {
994        let mut properties = HashMap::new();
995
996        // 获取表节点
997        if let Some(table) = node.as_table() {
998            let entries = table.entries();
999
1000            // 使用 get() 获取 Arc 引用,然后迭代
1001            let entries_arc = entries.get();
1002            for (key, value) in entries_arc.iter() {
1003                let key_str = key.value().to_string();
1004                let config_value = self.node_to_config_value(value);
1005                let range = self.node_to_range(value, content);
1006
1007                properties.insert(
1008                    key_str.clone(),
1009                    ConfigProperty {
1010                        key: key_str,
1011                        value: config_value,
1012                        range,
1013                    },
1014                );
1015            }
1016        }
1017
1018        properties
1019    }
1020
1021    /// 将 TOML 节点转换为配置值
1022    fn node_to_config_value(&self, node: &taplo::dom::Node) -> ConfigValue {
1023        match node {
1024            taplo::dom::Node::Bool(b) => ConfigValue::Boolean(b.value()),
1025            taplo::dom::Node::Str(s) => ConfigValue::String(s.value().to_string()),
1026            taplo::dom::Node::Integer(i) => {
1027                // IntegerValue 需要转换为 i64
1028                match i.value() {
1029                    IntegerValue::Positive(v) => ConfigValue::Integer(v as i64),
1030                    IntegerValue::Negative(v) => ConfigValue::Integer(v),
1031                }
1032            }
1033            taplo::dom::Node::Float(f) => ConfigValue::Float(f.value()),
1034            taplo::dom::Node::Array(arr) => {
1035                let items = arr.items();
1036                let mut values = Vec::new();
1037
1038                // 使用 get() 获取 Arc 引用,然后迭代
1039                let items_arc = items.get();
1040                for item in items_arc.iter() {
1041                    values.push(self.node_to_config_value(item));
1042                }
1043
1044                ConfigValue::Array(values)
1045            }
1046            taplo::dom::Node::Table(table) => {
1047                let entries = table.entries();
1048                let mut map = HashMap::new();
1049
1050                // 使用 get() 获取 Arc 引用,然后迭代
1051                let entries_arc = entries.get();
1052                for (key, value) in entries_arc.iter() {
1053                    let key_str = key.value().to_string();
1054                    map.insert(key_str, self.node_to_config_value(value));
1055                }
1056
1057                ConfigValue::Table(map)
1058            }
1059            _ => ConfigValue::String(String::new()), // 默认值
1060        }
1061    }
1062
1063    /// 将 TOML 节点转换为 LSP 范围
1064    ///
1065    /// 将 taplo 提供的字节偏移量转换为行号和字符位置
1066    fn node_to_range(&self, node: &taplo::dom::Node, content: &str) -> Range {
1067        // taplo 的 text_ranges 返回一个迭代器
1068        let mut text_ranges = node.text_ranges();
1069        if let Some(first_range) = text_ranges.next() {
1070            let start: usize = first_range.start().into();
1071            let end: usize = first_range.end().into();
1072
1073            // 将字节偏移量转换为行号和字符位置
1074            let start_pos = self.byte_offset_to_position(content, start);
1075            let end_pos = self.byte_offset_to_position(content, end);
1076
1077            Range {
1078                start: start_pos,
1079                end: end_pos,
1080            }
1081        } else {
1082            // 默认范围
1083            Range {
1084                start: lsp_types::Position {
1085                    line: 0,
1086                    character: 0,
1087                },
1088                end: lsp_types::Position {
1089                    line: 0,
1090                    character: 0,
1091                },
1092            }
1093        }
1094    }
1095
1096    /// 将字节偏移量转换为 LSP Position
1097    ///
1098    /// 遍历内容,计算字节偏移量对应的行号和字符位置
1099    fn byte_offset_to_position(&self, content: &str, byte_offset: usize) -> lsp_types::Position {
1100        let mut line = 0;
1101        let mut character = 0;
1102        let mut current_offset = 0;
1103
1104        for ch in content.chars() {
1105            if current_offset >= byte_offset {
1106                break;
1107            }
1108
1109            if ch == '\n' {
1110                line += 1;
1111                character = 0;
1112            } else {
1113                character += 1;
1114            }
1115
1116            current_offset += ch.len_utf8();
1117        }
1118
1119        lsp_types::Position {
1120            line: line as u32,
1121            character: character as u32,
1122        }
1123    }
1124}