1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
use base_db::{Config, Document, TexDocumentData};
use multimap::MultiMap;
use rowan::{ast::AstNode, NodeOrToken, TextRange};
use syntax::latex;
use url::Url;
use crate::types::{Diagnostic, TexError};
pub fn update(
document: &Document,
config: &Config,
results: &mut MultiMap<Url, Diagnostic>,
) -> Option<()> {
let data = document.data.as_tex()?;
if !document.uri.as_str().ends_with(".tex") {
return None;
}
let mut analyzer = Analyzer {
data,
config,
diagnostics: Vec::new(),
};
analyzer.analyze_root();
*results
.entry(document.uri.clone())
.or_insert_vec(Vec::new()) = analyzer.diagnostics;
Some(())
}
struct Analyzer<'a> {
data: &'a TexDocumentData,
config: &'a Config,
diagnostics: Vec<Diagnostic>,
}
impl<'a> Analyzer<'a> {
fn analyze_root(&mut self) {
let verbatim_envs = &self.config.syntax.verbatim_environments;
let mut traversal = self.data.root_node().preorder();
while let Some(event) = traversal.next() {
match event {
rowan::WalkEvent::Enter(node) => {
if let Some(environment) = latex::Environment::cast(node.clone()) {
if environment
.begin()
.and_then(|begin| begin.name())
.and_then(|name| name.key())
.map_or(false, |name| verbatim_envs.contains(&name.to_string()))
{
traversal.skip_subtree();
continue;
}
}
self.analyze_environment(node.clone())
.or_else(|| self.analyze_curly_group(node.clone()))
.or_else(|| self.analyze_curly_braces(node));
}
rowan::WalkEvent::Leave(_) => {
continue;
}
};
}
}
fn analyze_environment(&mut self, node: latex::SyntaxNode) -> Option<()> {
let environment = latex::Environment::cast(node)?;
let begin = environment.begin()?.name()?.key()?;
let end = environment.end()?.name()?.key()?;
if begin != end {
self.diagnostics.push(Diagnostic::Tex(
latex::small_range(&begin),
TexError::MismatchedEnvironment,
));
}
Some(())
}
fn analyze_curly_group(&mut self, node: latex::SyntaxNode) -> Option<()> {
if !matches!(
node.kind(),
latex::CURLY_GROUP
| latex::CURLY_GROUP_COMMAND
| latex::CURLY_GROUP_KEY_VALUE
| latex::CURLY_GROUP_WORD
| latex::CURLY_GROUP_WORD_LIST
) {
return None;
}
if !node
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.any(|token| token.kind() == latex::R_CURLY)
{
self.diagnostics.push(Diagnostic::Tex(
TextRange::empty(node.text_range().end()),
TexError::ExpectingRCurly,
));
}
Some(())
}
fn analyze_curly_braces(&mut self, node: latex::SyntaxNode) -> Option<()> {
if node.kind() == latex::ERROR && node.first_token()?.text() == "}" {
self.diagnostics.push(Diagnostic::Tex(
node.text_range(),
TexError::UnexpectedRCurly,
));
Some(())
} else {
None
}
}
}
|