swls_core/systems/
namespace_properties.rs1use bevy_ecs::prelude::*;
11use sophia_api::{quad::Quad, term::TermKind};
12
13use crate::{
14 feature::{code_action::CodeActionRequest, diagnostics::DiagnosticPublisher},
15 lsp_types::{
16 CodeAction, CodeActionKind, Command, Diagnostic, DiagnosticSeverity, Range,
17 TextDocumentItem,
18 },
19 prelude::*,
20 util::offset_to_position,
21};
22
23pub const ALLOW_PROPERTY_COMMAND: &str = "swls.allowProperty";
25
26fn unknown_namespace_properties(
30 triples: &Triples,
31 rope: &RopeC,
32 ontologies: &Ontologies,
33 closed_namespaces: &std::collections::HashSet<String>,
34 allowed_properties: &std::collections::HashSet<String>,
35 disabled: bool,
36) -> Vec<(String, Range)> {
37 if closed_namespaces.is_empty() || disabled {
38 return Vec::new();
39 }
40
41 let mut out = Vec::new();
42 for quad in triples.0.iter() {
43 for term in &[quad.s(), quad.p(), quad.o()] {
44 if term.ty != Some(TermKind::Iri) {
45 continue;
46 }
47 let iri = term.as_str();
48 if !closed_namespaces.iter().any(|ns| iri.starts_with(ns)) {
49 continue;
50 }
51 if allowed_properties.contains(iri)
52 || ontologies.properties.contains_key(iri)
53 || ontologies.classes.values().any(|c| c.term.value == iri)
54 {
55 continue;
56 }
57
58 let span = &term.span;
59 if span.is_empty() {
60 continue;
61 }
62 if let (Some(start), Some(end)) = (
63 offset_to_position(span.start, &rope.0),
64 offset_to_position(span.end, &rope.0),
65 ) {
66 out.push((iri.to_string(), Range::new(start, end)));
67 }
68 }
69 }
70 out
71}
72
73pub fn validate_namespace_properties(
76 query: Query<(&Triples, &RopeC, &Wrapped<TextDocumentItem>), With<Open>>,
77 ontologies: Res<Ontologies>,
78 config: Res<ServerConfig>,
79 mut client: ResMut<DiagnosticPublisher>,
80) {
81 let closed = &config.config.local.closed_namespaces;
82 let allowed = &config.config.local.allowed_properties;
83 let disabled = config.config.local.is_disabled(Disabled::NamespaceProperties);
84
85 for (triples, rope, item) in &query {
86 let violations =
87 unknown_namespace_properties(triples, rope, &ontologies, closed, allowed, disabled);
88
89 let diagnostics: Vec<Diagnostic> = violations
90 .into_iter()
91 .map(|(iri, range)| Diagnostic {
92 range,
93 severity: Some(DiagnosticSeverity::WARNING),
94 message: format!(
95 "Property \"{}\" is not defined in the ontology for its namespace",
96 iri
97 ),
98 ..Default::default()
99 })
100 .collect();
101
102 let _ = client.publish(&item.0, diagnostics, "namespace_properties");
103 }
104}
105
106pub fn unknown_property_code_action(
114 mut query: Query<(&Triples, &RopeC, &PositionComponent, &mut CodeActionRequest), With<Open>>,
115 ontologies: Res<Ontologies>,
116 config: Res<ServerConfig>,
117) {
118 let closed = &config.config.local.closed_namespaces;
119 let allowed = &config.config.local.allowed_properties;
120 let disabled = config.config.local.is_disabled(Disabled::NamespaceProperties);
121
122 for (triples, rope, position, mut req) in &mut query {
123 let cursor = position.0;
124 let mut seen = std::collections::HashSet::new();
125 for (iri, range) in
126 unknown_namespace_properties(triples, rope, &ontologies, closed, allowed, disabled)
127 {
128 if !position_in_range(cursor, range) {
129 continue;
130 }
131 if !seen.insert(iri.clone()) {
132 continue;
133 }
134 req.0.push(CodeAction {
135 title: format!("Mark \"{}\" as a known property", iri),
136 kind: Some(CodeActionKind::QUICKFIX),
137 command: Some(Command {
138 title: "Allow property".to_string(),
139 command: ALLOW_PROPERTY_COMMAND.to_string(),
140 arguments: Some(vec![serde_json::Value::String(iri)]),
141 }),
142 ..Default::default()
143 });
144 }
145 }
146}
147
148fn position_in_range(pos: crate::lsp_types::Position, range: Range) -> bool {
150 pos >= range.start && pos <= range.end
151}