1use crate::diagnostics::{Diagnostic, ErrorCode};
13use crate::{FileId, Span};
14use codespan_reporting::diagnostic::Label;
15use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
16use std::error::Error;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub enum IdentifierCaseConvention {
24 Module,
25 Member,
26 ImportedMember,
27 DatatypeDefinition,
28 PropertyDefinition,
29 RdfDefinition,
30 TypeDefinition,
31 ValueVariant,
32}
33
34macro_rules! new_diagnostic {
39 ($code: ident, $callback_fn: expr) => {
40 $callback_fn(Diagnostic::from(ErrorCode::$code)).with_notes(vec![i18n!(
41 "help_more_details_url",
42 url = ErrorCode::$code.url_string()
43 )])
44 };
45}
46
47#[inline]
55#[allow(clippy::redundant_closure_call)]
56pub fn found_error_node<S>(file_id: FileId, location: Span, in_rule: S) -> Diagnostic
57where
58 S: Into<String>,
59{
60 new_diagnostic!(TreeSitterErrorNode, |diagnostic: Diagnostic| diagnostic
61 .with_labels(vec![
62 Label::primary(file_id, location).with_message(i18n!("lbl_here"))
63 ])
64 .with_notes(vec![
65 i18n!("lbl_in_grammar_rule", name = in_rule.into()),
66 i18n!("help_error_node"),
67 ]))
68}
69
70#[inline]
74#[allow(clippy::redundant_closure_call)]
75pub fn unexpected_node_kind<S1, S2, S3>(
76 file_id: FileId,
77 location: Span,
78 in_rule: S1,
79 expecting: S2,
80 got: S3,
81) -> Diagnostic
82where
83 S1: Into<String>,
84 S2: Into<String>,
85 S3: Into<String>,
86{
87 let expecting = expecting.into();
88 let expecting = if expecting.contains('|') {
89 i18n!("lbl_expecting_one_of_node_kind", kind = expecting)
90 } else {
91 i18n!("lbl_expecting_node_kind", kind = expecting)
92 };
93 new_diagnostic!(TreeSitterUnexpectedNode, |diagnostic: Diagnostic| {
94 diagnostic.with_labels(vec![
95 Label::primary(file_id, location.clone())
96 .with_message(i18n!("lbl_actual_node_kind", kind = got.into())),
97 Label::secondary(file_id, location).with_message(expecting),
98 ])
99 })
100 .with_notes(vec![i18n!("lbl_in_grammar_rule", name = in_rule.into())])
101}
102
103#[inline]
107#[allow(clippy::redundant_closure_call)]
108pub fn missing_node<S1, S2, S3>(
109 file_id: FileId,
110 location: Span,
111 in_rule: S1,
112 expecting: S2,
113 field_name: Option<S3>,
114) -> Diagnostic
115where
116 S1: Into<String>,
117 S2: Into<String>,
118 S3: Into<String>,
119{
120 let message = if let Some(field_name) = field_name {
121 i18n!(
122 "lbl_missing_node_kind_in_variable",
123 kind = expecting.into(),
124 field_name = field_name.into()
125 )
126 } else {
127 i18n!("lbl_missing_node_kind", kind = expecting.into())
128 };
129 new_diagnostic!(TreeSitterMissingNode, |diagnostic: Diagnostic| diagnostic
130 .with_labels(vec![
131 Label::primary(file_id, location).with_message(message)
132 ]))
133 .with_notes(vec![i18n!("lbl_in_grammar_rule", name = in_rule.into())])
134}
135
136#[inline]
141#[allow(clippy::redundant_closure_call)]
142pub fn module_not_found<S>(name: S) -> Diagnostic
143where
144 S: Into<String>,
145{
146 new_diagnostic!(ModuleNotFound, |diagnostic: Diagnostic| diagnostic
147 .with_notes(vec![
148 i18n!("lbl_module_name", name = name.into()),
149 i18n!("help_check_resolver_path")
150 ]))
151}
152
153#[inline]
154#[allow(clippy::redundant_closure_call)]
155pub fn imported_module_not_found<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
156where
157 S: Into<String>,
158{
159 new_diagnostic!(
160 ImportedModuleNotFound,
161 |diagnostic: Diagnostic| if let Some(location) = location {
162 diagnostic
163 .with_labels(vec![
164 Label::primary(file_id, location).with_message(i18n!("lbl_this_import"))
165 ])
166 .with_notes(vec![i18n!("help_check_resolver_path")])
167 } else {
168 diagnostic.with_notes(vec![
169 i18n!("lbl_module_name", name = name.into()),
170 i18n!("help_check_resolver_path"),
171 ])
172 }
173 )
174}
175
176#[inline]
177#[allow(clippy::redundant_closure_call)]
178pub fn module_version_not_found<S1, S2>(
179 file_id: FileId,
180 expecting_location: Option<Span>,
181 expecting: S1,
182 actual_file_id: FileId,
183 actual_location: Option<Span>,
184 actual: S2,
185) -> Diagnostic
186where
187 S1: Into<String>,
188 S2: Into<String>,
189{
190 new_diagnostic!(
191 ModuleVersionNotFound,
192 |diagnostic: Diagnostic| if let Some(location) = expecting_location {
193 let diagnostic = diagnostic.with_labels(vec![
194 Label::primary(file_id, location).with_message(i18n!("lbl_this_import"))
195 ]);
196 if let Some(location) = actual_location {
197 diagnostic.with_labels(vec![Label::secondary(actual_file_id, location)
198 .with_message(i18n!("lbl_this_module"))])
199 } else {
200 diagnostic
201 }
202 } else {
203 diagnostic.with_notes(vec![
204 i18n!("lbl_expected_version_uri", url = expecting.into()),
205 i18n!("lbl_module_name", name = actual.into()),
206 ])
207 }
208 )
209}
210
211#[inline]
212#[allow(clippy::redundant_closure_call)]
213pub fn module_version_mismatch<S1, S2>(
214 file_id: FileId,
215 expecting_location: Option<Span>,
216 expecting: S1,
217 actual_file_id: FileId,
218 actual_location: Option<Span>,
219 actual: S2,
220) -> Diagnostic
221where
222 S1: Into<String>,
223 S2: Into<String>,
224{
225 new_diagnostic!(
226 ModuleVersionMismatch,
227 |diagnostic: Diagnostic| if let Some(location) = expecting_location {
228 let diagnostic = diagnostic.with_labels(vec![Label::primary(file_id, location)
229 .with_message(i18n!("lbl_expected_this_version_uri"))]);
230 if let Some(location) = actual_location {
231 diagnostic.with_labels(vec![Label::secondary(actual_file_id, location)
232 .with_message(i18n!("lbl_actual_this_version_uri"))])
233 } else {
234 diagnostic
235 }
236 } else {
237 diagnostic.with_notes(vec![
238 i18n!("lbl_expected_version_uri", url = expecting.into()),
239 i18n!("lbl_actual_version_uri", url = actual.into()),
240 ])
241 }
242 )
243}
244
245#[inline]
246#[allow(clippy::redundant_closure_call)]
247pub fn duplicate_definition(file_id: FileId, first: Span, second: Span) -> Diagnostic {
248 new_diagnostic!(DuplicateDefinitionName, |diagnostic: Diagnostic| diagnostic
249 .with_labels(vec![
250 Label::primary(file_id, second).with_message(i18n!("lbl_this_definition_name")),
251 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_defined_here")),
252 ]))
253}
254
255#[inline]
256#[allow(clippy::redundant_closure_call)]
257pub fn duplicate_member(file_id: FileId, first: Span, second: Span) -> Diagnostic {
258 new_diagnostic!(DuplicateMemberName, |diagnostic: Diagnostic| diagnostic
259 .with_labels(vec![
260 Label::primary(file_id, second).with_message(i18n!("lbl_this_member_name")),
261 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_defined_here")),
262 ]))
263}
264
265#[inline]
266#[allow(clippy::redundant_closure_call)]
267pub fn duplicate_variant(file_id: FileId, first: Span, second: Span) -> Diagnostic {
268 new_diagnostic!(DuplicateVariantName, |diagnostic: Diagnostic| diagnostic
269 .with_labels(vec![
270 Label::primary(file_id, second).with_message(i18n!("lbl_this_variant_name")),
271 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_defined_here")),
272 ]))
273}
274
275#[inline]
276#[allow(clippy::redundant_closure_call)]
277pub fn invalid_identifier<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
278where
279 S: Into<String>,
280{
281 new_diagnostic!(
282 InvalidIdentifier,
283 |diagnostic: Diagnostic| if let Some(location) = location {
284 diagnostic.with_labels(vec![
285 Label::primary(file_id, location).with_message(i18n!("lbl_this_identifier"))
286 ])
287 } else {
288 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
289 }
290 )
291}
292
293#[inline]
294#[allow(clippy::redundant_closure_call)]
295pub fn invalid_module_base_uri<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
296where
297 S: Into<String>,
298{
299 new_diagnostic!(
300 InvalidModuleBaseUrl,
301 |diagnostic: Diagnostic| if let Some(location) = location {
302 diagnostic.with_labels(vec![
303 Label::primary(file_id, location).with_message(i18n!("lbl_this_uri"))
304 ])
305 } else {
306 diagnostic.with_notes(vec![
307 i18n!("lbl_value", val = value.into()),
308 i18n!("help_namespace_uri"),
309 ])
310 }
311 )
312}
313
314#[inline]
315#[allow(clippy::redundant_closure_call)]
316pub fn invalid_module_version_uri<S>(
317 file_id: FileId,
318 location: Option<Span>,
319 value: S,
320) -> Diagnostic
321where
322 S: Into<String>,
323{
324 new_diagnostic!(
325 InvalidModuleVersionUrl,
326 |diagnostic: Diagnostic| if let Some(location) = location {
327 diagnostic.with_labels(vec![
328 Label::primary(file_id, location).with_message(i18n!("lbl_this_uri"))
329 ])
330 } else {
331 diagnostic.with_notes(vec![
332 i18n!("lbl_value", val = value.into()),
333 i18n!("help_namespace_uri"),
334 ])
335 }
336 )
337}
338
339#[inline]
340#[allow(clippy::redundant_closure_call)]
341pub fn invalid_language_tag<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
342where
343 S: Into<String>,
344{
345 new_diagnostic!(
346 InvalidLanguageTag,
347 |diagnostic: Diagnostic| if let Some(location) = location {
348 diagnostic.with_labels(vec![
349 Label::primary(file_id, location).with_message(i18n!("lbl_this_language_tag"))
350 ])
351 } else {
352 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
353 }
354 )
355}
356
357#[inline]
358#[allow(clippy::redundant_closure_call)]
359pub fn invalid_value_for_type<S1, S2>(
360 value_file_id: FileId,
361 value_location: Option<Span>,
362 value: S1,
363 type_file_id: FileId,
364 type_location: Option<Span>,
365 type_name: S2,
366) -> Diagnostic
367where
368 S1: Into<String>,
369 S2: Into<String>,
370{
371 new_diagnostic!(InvalidValueForType, |diagnostic: Diagnostic| {
372 let diagnostic = if let Some(location) = value_location {
373 diagnostic.with_labels(vec![
374 Label::primary(value_file_id, location).with_message(i18n!("lbl_this_value"))
375 ])
376 } else {
377 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
378 };
379 if let Some(location) = type_location {
380 diagnostic.with_labels(vec![
381 Label::primary(type_file_id, location).with_message(i18n!("lbl_this_type"))
382 ])
383 } else {
384 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
385 }
386 })
387}
388
389#[inline]
390#[allow(clippy::redundant_closure_call)]
391pub fn invalid_value_for_type_named<S1, S2, E>(
392 value_file_id: FileId,
393 value_location: Option<Span>,
394 value: S1,
395 type_name: S2,
396 rust_error: Option<E>,
397) -> Diagnostic
398where
399 S1: Into<String>,
400 S2: Into<String>,
401 E: Error,
402{
403 new_diagnostic!(InvalidValueForType, |diagnostic: Diagnostic| {
404 let diagnostic = if let Some(location) = value_location {
405 diagnostic
406 .with_labels(vec![
407 Label::primary(value_file_id, location).with_message(i18n!("lbl_this_value"))
408 ])
409 .with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
410 } else {
411 diagnostic.with_notes(vec![
412 i18n!("lbl_value", val = value.into()),
413 i18n!("lbl_type_name", name = type_name.into()),
414 ])
415 };
416 if let Some(rust_error) = rust_error {
417 diagnostic.with_notes(vec![i18n!(
418 "lbl_specific_error",
419 err = rust_error.to_string()
420 )])
421 } else {
422 diagnostic
423 }
424 })
425}
426
427#[inline]
428#[allow(clippy::redundant_closure_call)]
429pub fn definition_not_found<S>(
430 file_id: FileId,
431 reference_location: Option<Span>,
432 name: S,
433) -> Diagnostic
434where
435 S: Into<String>,
436{
437 new_diagnostic!(
438 DefinitionNotFound,
439 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
440 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
441 .with_message(i18n!("lbl_this_reference"))])
442 } else {
443 diagnostic.with_notes(vec![i18n!("lbl_definition_name", name = name.into())])
444 }
445 )
446}
447
448#[inline]
449#[allow(clippy::redundant_closure_call)]
450pub fn type_definition_not_found<S>(
451 file_id: FileId,
452 reference_location: Option<Span>,
453 name: S,
454) -> Diagnostic
455where
456 S: Into<String>,
457{
458 new_diagnostic!(TypeDefinitionNotFound, |diagnostic: Diagnostic| {
459 if let Some(reference_location) = reference_location {
460 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
461 .with_message(i18n!("lbl_this_reference"))])
462 } else {
463 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
464 }
465 .with_notes(vec![i18n!("help_type_definition_not_found")])
466 })
467}
468
469#[inline]
470#[allow(clippy::redundant_closure_call)]
471pub fn datatype_invalid_base_type<S>(
472 file_id: FileId,
473 reference_location: Option<Span>,
474 name: S,
475) -> Diagnostic
476where
477 S: Into<String>,
478{
479 new_diagnostic!(DatatypeInvalidBase, |diagnostic: Diagnostic| {
480 if let Some(reference_location) = reference_location {
481 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
482 .with_message(i18n!("lbl_this_reference"))])
483 } else {
484 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
485 }
486 .with_notes(vec![i18n!("help_datatype_invalid_base_type")])
487 })
488}
489
490#[inline]
491#[allow(clippy::redundant_closure_call)]
492pub fn type_class_incompatible_usage<S>(
493 file_id: FileId,
494 reference_location: Option<Span>,
495 name: S,
496) -> Diagnostic
497where
498 S: Into<String>,
499{
500 new_diagnostic!(TypeClassIncompatible, |diagnostic: Diagnostic| if let Some(
501 reference_location,
502 ) = reference_location
503 {
504 diagnostic.with_labels(vec![
505 Label::primary(file_id, reference_location).with_message(i18n!("lbl_this_usage"))
506 ])
507 } else {
508 diagnostic.with_notes(vec![i18n!("lbl_typeclass_name", name = name.into())])
509 })
510}
511
512#[inline]
513#[allow(clippy::redundant_closure_call)]
514pub fn property_incompatible_usage<S>(
515 file_id: FileId,
516 reference_location: Option<Span>,
517 name: S,
518) -> Diagnostic
519where
520 S: Into<String>,
521{
522 new_diagnostic!(
523 PropertyIncompatible,
524 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
525 diagnostic
526 .with_labels(vec![Label::primary(file_id, reference_location)
527 .with_message(i18n!("lbl_this_usage"))])
528 } else {
529 diagnostic.with_notes(vec![i18n!("lbl_property_name", name = name.into())])
530 }
531 )
532}
533
534#[inline]
535#[allow(clippy::redundant_closure_call)]
536pub fn rdf_definition_incompatible_usage<S>(
537 file_id: FileId,
538 reference_location: Option<Span>,
539 name: S,
540) -> Diagnostic
541where
542 S: Into<String>,
543{
544 new_diagnostic!(
545 RdfDefinitionIncompatible,
546 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
547 diagnostic
548 .with_labels(vec![Label::primary(file_id, reference_location)
549 .with_message(i18n!("lbl_this_usage"))])
550 } else {
551 diagnostic.with_notes(vec![i18n!("lbl_rdf_name", name = name.into())])
552 }
553 )
554}
555
556#[inline]
557#[allow(clippy::redundant_closure_call)]
558pub fn feature_set_not_a_union<S>(
559 file_id: FileId,
560 reference_location: Option<Span>,
561 name: S,
562) -> Diagnostic
563where
564 S: Into<String>,
565{
566 new_diagnostic!(FeatureSetNotUnion, |diagnostic: Diagnostic| {
567 if let Some(reference_location) = reference_location {
568 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
569 .with_message(i18n!("lbl_this_reference"))])
570 } else {
571 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
572 }
573 .with_notes(vec![i18n!("help_feature_set_not_a_union")])
574 })
575}
576
577#[inline]
578#[allow(clippy::redundant_closure_call)]
579pub fn property_reference_not_property<S>(
580 file_id: FileId,
581 reference_location: Option<Span>,
582 name: S,
583) -> Diagnostic
584where
585 S: Into<String>,
586{
587 new_diagnostic!(PropertyReferenceNotProperty, |diagnostic: Diagnostic| {
588 if let Some(reference_location) = reference_location {
589 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
590 .with_message(i18n!("lbl_this_reference"))])
591 } else {
592 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
593 }
594 .with_notes(vec![i18n!("help_property_reference_not_property")])
595 })
596}
597
598#[inline]
599#[allow(clippy::redundant_closure_call)]
600pub fn library_definition_not_allowed_in<S1, S2>(
601 file_id: FileId,
602 reference_location: Option<Span>,
603 name: S1,
604 module: S2,
605) -> Diagnostic
606where
607 S1: Into<String>,
608 S2: Into<String>,
609{
610 new_diagnostic!(LibraryDefinitionNotAllowed, |diagnostic: Diagnostic| {
611 if let Some(reference_location) = reference_location {
612 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
613 .with_message(i18n!("lbl_this_reference"))])
614 } else {
615 diagnostic.with_notes(vec![
616 i18n!("lbl_type_name", name = name.into()),
617 i18n!("lbl_module_name", name = module.into()),
618 ])
619 }
620 })
621}
622
623#[inline]
624#[allow(clippy::redundant_closure_call)]
625pub fn library_definition_not_allowed<S>(
626 file_id: FileId,
627 reference_location: Option<Span>,
628 name: S,
629) -> Diagnostic
630where
631 S: Into<String>,
632{
633 new_diagnostic!(LibraryDefinitionNotAllowed, |diagnostic: Diagnostic| {
634 if let Some(reference_location) = reference_location {
635 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
636 .with_message(i18n!("lbl_this_reference"))])
637 } else {
638 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
639 }
640 })
641}
642
643#[inline]
644#[allow(clippy::redundant_closure_call)]
645pub fn dimension_parent_not_entity<S>(
646 file_id: FileId,
647 reference_location: Option<Span>,
648 name: S,
649) -> Diagnostic
650where
651 S: Into<String>,
652{
653 new_diagnostic!(DimensionParentNotEntity, |diagnostic: Diagnostic| {
654 if let Some(reference_location) = reference_location {
655 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
656 .with_message(i18n!("lbl_this_reference"))])
657 } else {
658 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
659 }
660 })
661}
662
663#[inline]
664#[allow(clippy::redundant_closure_call)]
665pub fn source_entity_not_entity<S>(
666 file_id: FileId,
667 reference_location: Option<Span>,
668 name: S,
669) -> Diagnostic
670where
671 S: Into<String>,
672{
673 new_diagnostic!(SourceEntityNotEntity, |diagnostic: Diagnostic| {
674 if let Some(reference_location) = reference_location {
675 diagnostic
676 .with_labels(vec![Label::primary(file_id, reference_location)
677 .with_message(i18n!("lbl_this_reference"))])
678 .with_notes(vec![i18n!("help_source_reference_not_entity")])
679 } else {
680 diagnostic.with_notes(vec![
681 i18n!("lbl_type_name", name = name.into()),
682 i18n!("help_source_reference_not_entity"),
683 ])
684 }
685 })
686}
687
688#[inline]
689#[allow(clippy::redundant_closure_call)]
690pub fn source_entity_missing_member<S>(
691 file_id: FileId,
692 reference_location: Option<Span>,
693 name: S,
694) -> Diagnostic
695where
696 S: Into<String>,
697{
698 new_diagnostic!(SourceEntityMissingMember, |diagnostic: Diagnostic| {
699 if let Some(reference_location) = reference_location {
700 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
701 .with_message(i18n!("lbl_this_member_name"))])
702 } else {
703 diagnostic.with_notes(vec![i18n!("lbl_member_name", name = name.into())])
704 }
705 })
706}
707
708#[inline]
713#[allow(clippy::redundant_closure_call)]
714pub fn duplicate_module_import(file_id: FileId, first: Span, second: Span) -> Diagnostic {
715 new_diagnostic!(DuplicateModuleImport, |diagnostic: Diagnostic| diagnostic
716 .with_labels(vec![
717 Label::primary(file_id, second).with_message(i18n!("lbl_this_module")),
718 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_imported_here")),
719 ]))
720}
721
722#[inline]
723#[allow(clippy::redundant_closure_call)]
724pub fn duplicate_definition_import(file_id: FileId, first: Span, second: Span) -> Diagnostic {
725 new_diagnostic!(DuplicateDefinitionImport, |diagnostic: Diagnostic| {
726 diagnostic.with_labels(vec![
727 Label::primary(file_id, second).with_message(i18n!("lbl_this_member")),
728 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_imported_here")),
729 ])
730 })
731}
732
733#[inline]
734#[allow(clippy::redundant_closure_call)]
735pub fn type_validation_incomplete<S>(
736 file_id: FileId,
737 location: Option<Span>,
738 type_name: S,
739) -> Diagnostic
740where
741 S: Into<String>,
742{
743 new_diagnostic!(
744 ValidationIncomplete,
745 |diagnostic: Diagnostic| if let Some(location) = location {
746 diagnostic.with_labels(vec![
747 Label::primary(file_id, location).with_message(i18n!("lbl_this_definition"))
748 ])
749 } else {
750 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
751 }
752 )
753}
754
755#[inline]
756#[allow(clippy::redundant_closure_call)]
757pub fn module_version_info_empty(file_id: FileId, location: Option<Span>) -> Diagnostic {
758 new_diagnostic!(
759 ModuleVersionInfoEmpty,
760 |diagnostic: Diagnostic| if let Some(location) = location {
761 diagnostic.with_labels(vec![
762 Label::primary(file_id, location).with_message(i18n!("lbl_this_value"))
763 ])
764 } else {
765 diagnostic
766 }
767 )
768}
769
770#[inline]
771#[allow(clippy::redundant_closure_call)]
772pub fn deprecated_term_used<S>(
773 file_id: FileId,
774 location: Option<Span>,
775 value: S,
776 term_name: &str,
777 alternative_terms: &[String],
778 reason: Option<&String>,
779) -> Diagnostic
780where
781 S: Into<String>,
782{
783 new_diagnostic!(DeprecatedTermUsed, |diagnostic: Diagnostic| {
784 let diagnostic = if let Some(location) = location {
785 diagnostic.with_labels(vec![
786 Label::primary(file_id, location).with_message(i18n!("lbl_here"))
787 ])
788 } else {
789 diagnostic.with_notes(vec![
790 i18n!("lbl_term_name", name = term_name),
791 i18n!("lbl_in_this", val = value.into()),
792 ])
793 }
794 .with_notes(vec![i18n!(
795 "help_alternative_terms",
796 terms = alternative_terms.join(", ")
797 )]);
798 if let Some(reason) = reason {
799 diagnostic.with_notes(vec![i18n!(
800 "help_deprecated_term_reason",
801 reason = reason.as_str()
802 )])
803 } else {
804 diagnostic
805 }
806 })
807}
808
809#[inline]
814#[allow(clippy::redundant_closure_call)]
815pub fn module_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
816where
817 S: Into<String>,
818{
819 new_diagnostic!(
820 IncompleteModule,
821 |diagnostic: Diagnostic| if let Some(location) = location {
822 diagnostic.with_labels(vec![
823 Label::primary(file_id, location).with_message(i18n!("lbl_this_module"))
824 ])
825 } else {
826 diagnostic.with_notes(vec![i18n!("lbl_module_name", name = name.into())])
827 }
828 )
829}
830
831#[inline]
832#[allow(clippy::redundant_closure_call)]
833pub fn definition_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
834where
835 S: Into<String>,
836{
837 new_diagnostic!(
838 IncompleteDefinition,
839 |diagnostic: Diagnostic| if let Some(location) = location {
840 diagnostic.with_labels(vec![
841 Label::primary(file_id, location).with_message(i18n!("lbl_this_definition"))
842 ])
843 } else {
844 diagnostic.with_notes(vec![i18n!("lbl_definition_name", name = name.into())])
845 }
846 )
847}
848
849#[inline]
850#[allow(clippy::redundant_closure_call)]
851pub fn member_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
852where
853 S: Into<String>,
854{
855 new_diagnostic!(
856 IncompleteMember,
857 |diagnostic: Diagnostic| if let Some(location) = location {
858 diagnostic.with_labels(vec![
859 Label::primary(file_id, location).with_message(i18n!("lbl_this_member"))
860 ])
861 } else {
862 diagnostic.with_notes(vec![i18n!("lbl_member_name", name = name.into())])
863 }
864 )
865}
866
867#[inline]
868#[allow(clippy::redundant_closure_call)]
869pub fn string_without_language<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
870where
871 S: Into<String>,
872{
873 new_diagnostic!(
874 StringWithoutLanguage,
875 |diagnostic: Diagnostic| if let Some(location) = location {
876 diagnostic.with_labels(vec![
877 Label::primary(file_id, location).with_message(i18n!("lbl_this_value"))
878 ])
879 } else {
880 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
881 }
882 )
883}
884
885#[inline]
886#[allow(clippy::redundant_closure_call)]
887pub fn using_unconstrained_datatype<S>(
888 file_id: FileId,
889 location: Option<Span>,
890 name: S,
891) -> Diagnostic
892where
893 S: Into<String>,
894{
895 new_diagnostic!(
896 UnconstrainedDatatype,
897 |diagnostic: Diagnostic| if let Some(location) = location {
898 diagnostic.with_labels(vec![
899 Label::primary(file_id, location).with_message(i18n!("lbl_this_type"))
900 ])
901 } else {
902 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
903 }
904 )
905}
906
907#[inline]
908#[allow(clippy::redundant_closure_call)]
909pub fn double_underscored_identifier<S>(
910 file_id: FileId,
911 location: Option<Span>,
912 name: S,
913) -> Diagnostic
914where
915 S: Into<String>,
916{
917 new_diagnostic!(
918 DoubleUnderscoredIdentifier,
919 |diagnostic: Diagnostic| if let Some(location) = location {
920 diagnostic.with_labels(vec![
921 Label::primary(file_id, location).with_message(i18n!("lbl_this_identifier"))
922 ])
923 } else {
924 diagnostic.with_notes(vec![i18n!("lbl_identifier", name = name.into())])
925 }
926 )
927}
928
929#[inline]
930#[allow(clippy::redundant_closure_call)]
931pub fn identifier_not_preferred_case<S>(
932 file_id: FileId,
933 location: Option<Span>,
934 name: S,
935 case: IdentifierCaseConvention,
936) -> Diagnostic
937where
938 S: Into<String>,
939{
940 new_diagnostic!(IdentifierNotPreferredCase, |diagnostic: Diagnostic| {
941 if let Some(location) = location {
942 diagnostic.with_labels(vec![
943 Label::primary(file_id, location).with_message(i18n!("lbl_this_identifier"))
944 ])
945 } else {
946 diagnostic.with_notes(vec![i18n!("lbl_identifier", name = name.into())])
947 }
948 .with_notes(vec![i18n!(
949 "lbl_expected_case",
950 case = match case {
951 IdentifierCaseConvention::Module => i18n!("lbl_case_module"),
952 IdentifierCaseConvention::Member => i18n!("lbl_case_member"),
953 IdentifierCaseConvention::ImportedMember => i18n!("lbl_case_imported_member"),
954 IdentifierCaseConvention::DatatypeDefinition => i18n!("lbl_case_datatype"),
955 IdentifierCaseConvention::PropertyDefinition => i18n!("lbl_case_property"),
956 IdentifierCaseConvention::RdfDefinition => i18n!("lbl_case_rdf"),
957 IdentifierCaseConvention::TypeDefinition => i18n!("lbl_case_type_defn"),
958 IdentifierCaseConvention::ValueVariant => i18n!("lbl_case_value_variant"),
959 }
960 )])
961 })
962}
963
964impl IdentifierCaseConvention {
969 pub fn is_valid<S>(&self, id: S) -> bool
970 where
971 S: Into<String>,
972 {
973 let id = id.into();
974 match self {
975 Self::Module => id == Self::to_snake_case(&id),
976 Self::Member => id == Self::to_snake_case(&id) || id == Self::to_lower_camel_case(&id),
977 Self::ImportedMember => {
978 id == Self::to_snake_case(&id)
979 || id == Self::to_lower_camel_case(&id)
980 || id == Self::to_upper_camel_case(&id)
981 }
982 Self::DatatypeDefinition => {
983 id == Self::to_snake_case(&id)
984 || id == Self::to_lower_camel_case(&id)
985 || id == Self::to_upper_camel_case(&id)
986 }
987 Self::PropertyDefinition => {
988 id == Self::to_snake_case(&id) || id == Self::to_lower_camel_case(&id)
989 }
990 Self::RdfDefinition => {
991 id == Self::to_snake_case(&id)
992 || id == Self::to_lower_camel_case(&id)
993 || id == Self::to_upper_camel_case(&id)
994 }
995 Self::TypeDefinition => id == Self::to_upper_camel_case(&id),
996 Self::ValueVariant => {
997 id == Self::to_upper_camel_case(&id) || id == Self::to_shouty_snake_case(&id)
998 }
999 }
1000 }
1001
1002 fn to_snake_case(id: &str) -> String {
1003 id.to_snake_case()
1004 }
1005
1006 fn to_upper_camel_case(id: &str) -> String {
1007 id.to_upper_camel_case()
1008 }
1009
1010 fn to_lower_camel_case(id: &str) -> String {
1011 id.to_lower_camel_case()
1012 }
1013
1014 fn to_shouty_snake_case(id: &str) -> String {
1015 id.to_shouty_snake_case()
1016 }
1017}