1#[derive(Debug, Clone)]
7pub struct DiagnosticsConfig {
8 pub enabled: bool,
10 pub undefined_variables: bool,
12 pub undefined_functions: bool,
14 pub undefined_classes: bool,
16 pub arity_errors: bool,
18 pub type_errors: bool,
20 pub deprecated_calls: bool,
22 pub duplicate_declarations: bool,
24 pub unused_symbols: bool,
29 pub missing_types: bool,
33 pub mixed_usage: bool,
37}
38
39impl Default for DiagnosticsConfig {
40 fn default() -> Self {
41 DiagnosticsConfig {
42 enabled: true,
43 undefined_variables: true,
44 undefined_functions: true,
45 undefined_classes: true,
46 arity_errors: true,
47 type_errors: true,
48 deprecated_calls: true,
49 duplicate_declarations: true,
50 unused_symbols: false,
51 missing_types: false,
52 mixed_usage: false,
53 }
54 }
55}
56
57impl DiagnosticsConfig {
58 #[cfg(test)]
61 pub fn all_enabled() -> Self {
62 DiagnosticsConfig {
63 enabled: true,
64 ..DiagnosticsConfig::default()
65 }
66 }
67
68 pub(crate) fn from_value(v: &serde_json::Value) -> Self {
69 let mut cfg = DiagnosticsConfig::default();
70 let Some(obj) = v.as_object() else { return cfg };
71 let flag = |key: &str| obj.get(key).and_then(|x| x.as_bool()).unwrap_or(true);
72 cfg.enabled = obj.get("enabled").and_then(|x| x.as_bool()).unwrap_or(true);
73 cfg.undefined_variables = flag("undefinedVariables");
74 cfg.undefined_functions = flag("undefinedFunctions");
75 cfg.undefined_classes = flag("undefinedClasses");
76 cfg.arity_errors = flag("arityErrors");
77 cfg.type_errors = flag("typeErrors");
78 cfg.deprecated_calls = flag("deprecatedCalls");
79 cfg.duplicate_declarations = flag("duplicateDeclarations");
80 cfg.unused_symbols = obj
81 .get("unusedSymbols")
82 .and_then(|x| x.as_bool())
83 .unwrap_or(false);
84 cfg.missing_types = obj
85 .get("missingTypes")
86 .and_then(|x| x.as_bool())
87 .unwrap_or(false);
88 cfg.mixed_usage = obj
89 .get("mixedUsage")
90 .and_then(|x| x.as_bool())
91 .unwrap_or(false);
92 cfg
93 }
94}
95
96#[derive(Debug, Clone)]
99pub struct FeaturesConfig {
100 pub completion: bool,
101 pub hover: bool,
102 pub definition: bool,
103 pub declaration: bool,
104 pub references: bool,
105 pub document_symbols: bool,
106 pub workspace_symbols: bool,
107 pub rename: bool,
108 pub signature_help: bool,
109 pub inlay_hints: bool,
110 pub semantic_tokens: bool,
111 pub selection_range: bool,
112 pub call_hierarchy: bool,
113 pub document_highlight: bool,
114 pub implementation: bool,
115 pub code_action: bool,
116 pub type_definition: bool,
117 pub code_lens: bool,
118 pub formatting: bool,
119 pub range_formatting: bool,
120 pub on_type_formatting: bool,
121 pub document_link: bool,
122 pub linked_editing_range: bool,
123 pub inline_values: bool,
124}
125
126impl Default for FeaturesConfig {
127 fn default() -> Self {
128 FeaturesConfig {
129 completion: true,
130 hover: true,
131 definition: true,
132 declaration: true,
133 references: true,
134 document_symbols: true,
135 workspace_symbols: true,
136 rename: true,
137 signature_help: true,
138 inlay_hints: true,
139 semantic_tokens: true,
140 selection_range: true,
141 call_hierarchy: true,
142 document_highlight: true,
143 implementation: true,
144 code_action: true,
145 type_definition: true,
146 code_lens: true,
147 formatting: true,
148 range_formatting: true,
149 on_type_formatting: true,
150 document_link: true,
151 linked_editing_range: true,
152 inline_values: true,
153 }
154 }
155}
156
157impl FeaturesConfig {
158 pub(crate) fn from_value(v: &serde_json::Value) -> Self {
159 let mut cfg = FeaturesConfig::default();
160 let Some(obj) = v.as_object() else { return cfg };
161 let flag = |key: &str| obj.get(key).and_then(|x| x.as_bool()).unwrap_or(true);
162 cfg.completion = flag("completion");
163 cfg.hover = flag("hover");
164 cfg.definition = flag("definition");
165 cfg.declaration = flag("declaration");
166 cfg.references = flag("references");
167 cfg.document_symbols = flag("documentSymbols");
168 cfg.workspace_symbols = flag("workspaceSymbols");
169 cfg.rename = flag("rename");
170 cfg.signature_help = flag("signatureHelp");
171 cfg.inlay_hints = flag("inlayHints");
172 cfg.semantic_tokens = flag("semanticTokens");
173 cfg.selection_range = flag("selectionRange");
174 cfg.call_hierarchy = flag("callHierarchy");
175 cfg.document_highlight = flag("documentHighlight");
176 cfg.implementation = flag("implementation");
177 cfg.code_action = flag("codeAction");
178 cfg.type_definition = flag("typeDefinition");
179 cfg.code_lens = flag("codeLens");
180 cfg.formatting = flag("formatting");
181 cfg.range_formatting = flag("rangeFormatting");
182 cfg.on_type_formatting = flag("onTypeFormatting");
183 cfg.document_link = flag("documentLink");
184 cfg.linked_editing_range = flag("linkedEditingRange");
185 cfg.inline_values = flag("inlineValues");
186 cfg
187 }
188}
189
190pub const MAX_INDEXED_FILES: usize = 50_000;
193
194#[derive(Debug, Clone)]
196pub struct LspConfig {
197 pub php_version: Option<String>,
200 pub exclude_paths: Vec<String>,
202 pub include_paths: Vec<String>,
206 pub diagnostics: DiagnosticsConfig,
208 pub features: FeaturesConfig,
210 pub max_indexed_files: usize,
214 pub index_vendor: bool,
225}
226
227impl Default for LspConfig {
228 fn default() -> Self {
229 LspConfig {
230 php_version: None,
231 exclude_paths: Vec::new(),
232 include_paths: Vec::new(),
233 diagnostics: DiagnosticsConfig::default(),
234 features: FeaturesConfig::default(),
235 max_indexed_files: MAX_INDEXED_FILES,
236 index_vendor: false,
237 }
238 }
239}
240
241impl LspConfig {
242 pub(crate) fn merge_project_configs(
250 file: Option<&serde_json::Value>,
251 editor: Option<&serde_json::Value>,
252 ) -> serde_json::Value {
253 let mut merged = file
254 .cloned()
255 .unwrap_or(serde_json::Value::Object(Default::default()));
256 let Some(editor_obj) = editor.and_then(|e| e.as_object()) else {
257 return merged;
258 };
259 let merged_obj = merged
260 .as_object_mut()
261 .expect("merged base is always an object");
262 for (key, val) in editor_obj {
263 if key == "excludePaths" || key == "includePaths" {
265 let file_arr = merged_obj
266 .get(key)
267 .and_then(|v| v.as_array())
268 .cloned()
269 .unwrap_or_default();
270 let editor_arr = val.as_array().cloned().unwrap_or_default();
271 merged_obj.insert(
272 key.clone(),
273 serde_json::Value::Array([file_arr, editor_arr].concat()),
274 );
275 } else {
276 merged_obj.insert(key.clone(), val.clone());
277 }
278 }
279 merged
280 }
281
282 pub(crate) fn from_value(v: &serde_json::Value) -> Self {
283 let mut cfg = LspConfig::default();
284 if let Some(ver) = v.get("phpVersion").and_then(|x| x.as_str()) {
285 if crate::autoload::is_valid_php_version(ver) {
286 cfg.php_version = Some(ver.to_string());
287 } else {
288 cfg.php_version = Some(crate::autoload::PHP_8_5.to_string());
290 }
291 }
292 if let Some(arr) = v.get("excludePaths").and_then(|x| x.as_array()) {
293 cfg.exclude_paths = arr
294 .iter()
295 .filter_map(|x| x.as_str().map(str::to_string))
296 .collect();
297 }
298 if let Some(arr) = v.get("includePaths").and_then(|x| x.as_array()) {
299 cfg.include_paths = arr
300 .iter()
301 .filter_map(|x| x.as_str().map(str::to_string))
302 .collect();
303 }
304 if let Some(diag_val) = v.get("diagnostics") {
305 cfg.diagnostics = DiagnosticsConfig::from_value(diag_val);
306 }
307 if let Some(feat_val) = v.get("features") {
308 cfg.features = FeaturesConfig::from_value(feat_val);
309 }
310 if let Some(n) = v.get("maxIndexedFiles").and_then(|x| x.as_u64()) {
311 cfg.max_indexed_files = n as usize;
312 }
313 if let Some(b) = v.get("indexVendor").and_then(|x| x.as_bool()) {
314 cfg.index_vendor = b;
315 }
316 cfg
317 }
318}