php_lsp/analysis/
diagnostics.rs1use std::sync::Arc;
2
3use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range};
4
5use crate::document::ast::ParsedDoc;
6
7pub const PHP_LSP_SOURCE: &str = "php-lsp";
8
9pub fn parse_document_no_diags(source: &str) -> ParsedDoc {
16 ParsedDoc::parse(Arc::from(source))
17}
18
19pub fn diagnostics_from_doc(doc: &ParsedDoc) -> Vec<Diagnostic> {
23 let sv = doc.view();
24 doc.errors
25 .iter()
26 .map(|e| {
27 let span = e.span();
28 let start = sv.position_of(span.start);
29 let end = if span.end > span.start {
30 sv.position_of(span.end)
31 } else {
32 let ch_width = sv.source()[span.start as usize..]
36 .chars()
37 .next()
38 .map(|c| c.len_utf16() as u32)
39 .unwrap_or(1);
40 Position {
41 line: start.line,
42 character: start.character + ch_width,
43 }
44 };
45 Diagnostic {
46 range: Range { start, end },
47 severity: Some(DiagnosticSeverity::ERROR),
48 source: Some(PHP_LSP_SOURCE.to_string()),
49 message: e.to_string(),
50 ..Default::default()
51 }
52 })
53 .collect()
54}
55
56pub fn merge_file_diagnostics(
63 parse: Vec<Diagnostic>,
64 semantic: Vec<Diagnostic>,
65) -> Vec<Diagnostic> {
66 let mut all = parse;
67 all.extend(semantic);
68 all
69}
70
71pub fn parse_document(source: &str) -> (ParsedDoc, Vec<Diagnostic>) {
73 let doc = parse_document_no_diags(source);
74 let diagnostics = diagnostics_from_doc(&doc);
75 (doc, diagnostics)
76}
77
78#[cfg(test)]
79mod tests {
80
81 #[test]
85 fn probe_zero_width_spans() {
86 let cases: &[(&str, &str)] = &[
87 ("class_no_name", "<?php\nclass {"),
88 ("fn_no_name", "<?php\nfunction ("),
89 ("assign_no_rhs", "<?php\n$x ="),
90 ("bare_emoji", "<?php\n\u{1F600}"),
91 ("emoji_class", "<?php\nclass \u{1F600} {"),
92 ("emoji_then_valid", "<?php\n\u{1F600}\nfunction f() {}"),
94 ("emoji_in_string_ctx", "<?php\n$x = \u{1F600};"),
95 ];
96 for (label, src) in cases {
97 let doc = crate::document::ast::ParsedDoc::parse(src.to_string());
98 for e in &doc.errors {
99 let span = e.span();
100 let ch = src[span.start as usize..].chars().next();
101 println!(
102 "{label}: span=({},{}) zero_width={} char={ch:?} src_len={}",
103 span.start,
104 span.end,
105 span.end == span.start,
106 src.len(),
107 );
108 }
109 }
110 }
111}