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_language_tag<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
296where
297 S: Into<String>,
298{
299 new_diagnostic!(
300 InvalidLanguageTag,
301 |diagnostic: Diagnostic| if let Some(location) = location {
302 diagnostic.with_labels(vec![
303 Label::primary(file_id, location).with_message(i18n!("lbl_this_language_tag"))
304 ])
305 } else {
306 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
307 }
308 )
309}
310
311#[inline]
312#[allow(clippy::redundant_closure_call)]
313pub fn invalid_value_for_type<S1, S2>(
314 value_file_id: FileId,
315 value_location: Option<Span>,
316 value: S1,
317 type_file_id: FileId,
318 type_location: Option<Span>,
319 type_name: S2,
320) -> Diagnostic
321where
322 S1: Into<String>,
323 S2: Into<String>,
324{
325 new_diagnostic!(InvalidValueForType, |diagnostic: Diagnostic| {
326 let diagnostic = if let Some(location) = value_location {
327 diagnostic.with_labels(vec![
328 Label::primary(value_file_id, location).with_message(i18n!("lbl_this_value"))
329 ])
330 } else {
331 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
332 };
333 if let Some(location) = type_location {
334 diagnostic.with_labels(vec![
335 Label::primary(type_file_id, location).with_message(i18n!("lbl_this_type"))
336 ])
337 } else {
338 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
339 }
340 })
341}
342
343#[inline]
344#[allow(clippy::redundant_closure_call)]
345pub fn invalid_value_for_type_named<S1, S2, E>(
346 value_file_id: FileId,
347 value_location: Option<Span>,
348 value: S1,
349 type_name: S2,
350 rust_error: Option<E>,
351) -> Diagnostic
352where
353 S1: Into<String>,
354 S2: Into<String>,
355 E: Error,
356{
357 new_diagnostic!(InvalidValueForType, |diagnostic: Diagnostic| {
358 let diagnostic = if let Some(location) = value_location {
359 diagnostic
360 .with_labels(vec![
361 Label::primary(value_file_id, location).with_message(i18n!("lbl_this_value"))
362 ])
363 .with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
364 } else {
365 diagnostic.with_notes(vec![
366 i18n!("lbl_value", val = value.into()),
367 i18n!("lbl_type_name", name = type_name.into()),
368 ])
369 };
370 if let Some(rust_error) = rust_error {
371 diagnostic.with_notes(vec![i18n!(
372 "lbl_specific_error",
373 err = rust_error.to_string()
374 )])
375 } else {
376 diagnostic
377 }
378 })
379}
380
381#[inline]
382#[allow(clippy::redundant_closure_call)]
383pub fn definition_not_found<S>(
384 file_id: FileId,
385 reference_location: Option<Span>,
386 name: S,
387) -> Diagnostic
388where
389 S: Into<String>,
390{
391 new_diagnostic!(
392 DefinitionNotFound,
393 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
394 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
395 .with_message(i18n!("lbl_this_reference"))])
396 } else {
397 diagnostic.with_notes(vec![i18n!("lbl_definition_name", name = name.into())])
398 }
399 )
400}
401
402#[inline]
403#[allow(clippy::redundant_closure_call)]
404pub fn type_definition_not_found<S>(
405 file_id: FileId,
406 reference_location: Option<Span>,
407 name: S,
408) -> Diagnostic
409where
410 S: Into<String>,
411{
412 new_diagnostic!(TypeDefinitionNotFound, |diagnostic: Diagnostic| {
413 if let Some(reference_location) = reference_location {
414 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
415 .with_message(i18n!("lbl_this_reference"))])
416 } else {
417 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
418 }
419 .with_notes(vec![i18n!("help_type_definition_not_found")])
420 })
421}
422
423#[inline]
424#[allow(clippy::redundant_closure_call)]
425pub fn datatype_invalid_base_type<S>(
426 file_id: FileId,
427 reference_location: Option<Span>,
428 name: S,
429) -> Diagnostic
430where
431 S: Into<String>,
432{
433 new_diagnostic!(DatatypeInvalidBase, |diagnostic: Diagnostic| {
434 if let Some(reference_location) = reference_location {
435 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
436 .with_message(i18n!("lbl_this_reference"))])
437 } else {
438 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
439 }
440 .with_notes(vec![i18n!("help_datatype_invalid_base_type")])
441 })
442}
443
444#[inline]
445#[allow(clippy::redundant_closure_call)]
446pub fn type_class_incompatible_usage<S>(
447 file_id: FileId,
448 reference_location: Option<Span>,
449 name: S,
450) -> Diagnostic
451where
452 S: Into<String>,
453{
454 new_diagnostic!(TypeClassIncompatible, |diagnostic: Diagnostic| if let Some(
455 reference_location,
456 ) = reference_location
457 {
458 diagnostic.with_labels(vec![
459 Label::primary(file_id, reference_location).with_message(i18n!("lbl_this_usage"))
460 ])
461 } else {
462 diagnostic.with_notes(vec![i18n!("lbl_typeclass_name", name = name.into())])
463 })
464}
465
466#[inline]
467#[allow(clippy::redundant_closure_call)]
468pub fn property_incompatible_usage<S>(
469 file_id: FileId,
470 reference_location: Option<Span>,
471 name: S,
472) -> Diagnostic
473where
474 S: Into<String>,
475{
476 new_diagnostic!(
477 PropertyIncompatible,
478 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
479 diagnostic
480 .with_labels(vec![Label::primary(file_id, reference_location)
481 .with_message(i18n!("lbl_this_usage"))])
482 } else {
483 diagnostic.with_notes(vec![i18n!("lbl_property_name", name = name.into())])
484 }
485 )
486}
487
488#[inline]
489#[allow(clippy::redundant_closure_call)]
490pub fn rdf_definition_incompatible_usage<S>(
491 file_id: FileId,
492 reference_location: Option<Span>,
493 name: S,
494) -> Diagnostic
495where
496 S: Into<String>,
497{
498 new_diagnostic!(
499 RdfDefinitionIncompatible,
500 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
501 diagnostic
502 .with_labels(vec![Label::primary(file_id, reference_location)
503 .with_message(i18n!("lbl_this_usage"))])
504 } else {
505 diagnostic.with_notes(vec![i18n!("lbl_rdf_name", name = name.into())])
506 }
507 )
508}
509
510#[inline]
511#[allow(clippy::redundant_closure_call)]
512pub fn feature_set_not_a_union<S>(
513 file_id: FileId,
514 reference_location: Option<Span>,
515 name: S,
516) -> Diagnostic
517where
518 S: Into<String>,
519{
520 new_diagnostic!(FeatureSetNotUnion, |diagnostic: Diagnostic| {
521 if let Some(reference_location) = reference_location {
522 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
523 .with_message(i18n!("lbl_this_reference"))])
524 } else {
525 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
526 }
527 .with_notes(vec![i18n!("help_feature_set_not_a_union")])
528 })
529}
530
531#[inline]
532#[allow(clippy::redundant_closure_call)]
533pub fn property_reference_not_property<S>(
534 file_id: FileId,
535 reference_location: Option<Span>,
536 name: S,
537) -> Diagnostic
538where
539 S: Into<String>,
540{
541 new_diagnostic!(PropertyReferenceNotProperty, |diagnostic: Diagnostic| {
542 if let Some(reference_location) = reference_location {
543 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
544 .with_message(i18n!("lbl_this_reference"))])
545 } else {
546 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
547 }
548 .with_notes(vec![i18n!("help_property_reference_not_property")])
549 })
550}
551
552#[inline]
553#[allow(clippy::redundant_closure_call)]
554pub fn library_definition_not_allowed<S>(
555 file_id: FileId,
556 reference_location: Option<Span>,
557 name: S,
558) -> Diagnostic
559where
560 S: Into<String>,
561{
562 new_diagnostic!(LibraryDefinitionNotAllowed, |diagnostic: Diagnostic| {
563 if let Some(reference_location) = reference_location {
564 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
565 .with_message(i18n!("lbl_this_reference"))])
566 } else {
567 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
568 }
569 })
570}
571
572#[inline]
573#[allow(clippy::redundant_closure_call)]
574pub fn dimension_parent_not_entity<S>(
575 file_id: FileId,
576 reference_location: Option<Span>,
577 name: S,
578) -> Diagnostic
579where
580 S: Into<String>,
581{
582 new_diagnostic!(DimensionParentNotEntity, |diagnostic: Diagnostic| {
583 if let Some(reference_location) = reference_location {
584 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
585 .with_message(i18n!("lbl_this_reference"))])
586 } else {
587 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
588 }
589 })
590}
591
592#[inline]
593#[allow(clippy::redundant_closure_call)]
594pub fn source_entity_not_entity<S>(
595 file_id: FileId,
596 reference_location: Option<Span>,
597 name: S,
598) -> Diagnostic
599where
600 S: Into<String>,
601{
602 new_diagnostic!(SourceEntityNotEntity, |diagnostic: Diagnostic| {
603 if let Some(reference_location) = reference_location {
604 diagnostic
605 .with_labels(vec![Label::primary(file_id, reference_location)
606 .with_message(i18n!("lbl_this_reference"))])
607 .with_notes(vec![i18n!("help_source_reference_not_entity")])
608 } else {
609 diagnostic.with_notes(vec![
610 i18n!("lbl_type_name", name = name.into()),
611 i18n!("help_source_reference_not_entity"),
612 ])
613 }
614 })
615}
616
617#[inline]
618#[allow(clippy::redundant_closure_call)]
619pub fn source_entity_missing_member<S>(
620 file_id: FileId,
621 reference_location: Option<Span>,
622 name: S,
623) -> Diagnostic
624where
625 S: Into<String>,
626{
627 new_diagnostic!(SourceEntityMissingMember, |diagnostic: Diagnostic| {
628 if let Some(reference_location) = reference_location {
629 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
630 .with_message(i18n!("lbl_this_member_name"))])
631 } else {
632 diagnostic.with_notes(vec![i18n!("lbl_member_name", name = name.into())])
633 }
634 })
635}
636
637#[inline]
642#[allow(clippy::redundant_closure_call)]
643pub fn duplicate_module_import(file_id: FileId, first: Span, second: Span) -> Diagnostic {
644 new_diagnostic!(DuplicateModuleImport, |diagnostic: Diagnostic| diagnostic
645 .with_labels(vec![
646 Label::primary(file_id, second).with_message(i18n!("lbl_this_module")),
647 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_imported_here")),
648 ]))
649}
650
651#[inline]
652#[allow(clippy::redundant_closure_call)]
653pub fn duplicate_definition_import(file_id: FileId, first: Span, second: Span) -> Diagnostic {
654 new_diagnostic!(DuplicateDefinitionImport, |diagnostic: Diagnostic| {
655 diagnostic.with_labels(vec![
656 Label::primary(file_id, second).with_message(i18n!("lbl_this_member")),
657 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_imported_here")),
658 ])
659 })
660}
661
662#[inline]
663#[allow(clippy::redundant_closure_call)]
664pub fn type_validation_incomplete<S>(
665 file_id: FileId,
666 location: Option<Span>,
667 type_name: S,
668) -> Diagnostic
669where
670 S: Into<String>,
671{
672 new_diagnostic!(
673 ValidationIncomplete,
674 |diagnostic: Diagnostic| if let Some(location) = location {
675 diagnostic.with_labels(vec![
676 Label::primary(file_id, location).with_message(i18n!("lbl_this_definition"))
677 ])
678 } else {
679 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
680 }
681 )
682}
683
684#[inline]
685#[allow(clippy::redundant_closure_call)]
686pub fn module_version_info_empty(file_id: FileId, location: Option<Span>) -> Diagnostic {
687 new_diagnostic!(
688 ModuleVersionInfoEmpty,
689 |diagnostic: Diagnostic| if let Some(location) = location {
690 diagnostic.with_labels(vec![
691 Label::primary(file_id, location).with_message(i18n!("lbl_this_value"))
692 ])
693 } else {
694 diagnostic
695 }
696 )
697}
698
699#[inline]
700#[allow(clippy::redundant_closure_call)]
701pub fn deprecated_term_used<S>(
702 file_id: FileId,
703 location: Option<Span>,
704 value: S,
705 term_name: &str,
706 alternative_terms: &[String],
707 reason: Option<&String>,
708) -> Diagnostic
709where
710 S: Into<String>,
711{
712 new_diagnostic!(DeprecatedTermUsed, |diagnostic: Diagnostic| {
713 let diagnostic = if let Some(location) = location {
714 diagnostic.with_labels(vec![
715 Label::primary(file_id, location).with_message(i18n!("lbl_here"))
716 ])
717 } else {
718 diagnostic.with_notes(vec![
719 i18n!("lbl_term_name", name = term_name),
720 i18n!("lbl_in_this", val = value.into()),
721 ])
722 }
723 .with_notes(vec![i18n!(
724 "help_alternative_terms",
725 terms = alternative_terms.join(", ")
726 )]);
727 if let Some(reason) = reason {
728 diagnostic.with_notes(vec![i18n!(
729 "help_deprecated_term_reason",
730 reason = reason.as_str()
731 )])
732 } else {
733 diagnostic
734 }
735 })
736}
737
738#[inline]
743#[allow(clippy::redundant_closure_call)]
744pub fn module_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
745where
746 S: Into<String>,
747{
748 new_diagnostic!(
749 IncompleteModule,
750 |diagnostic: Diagnostic| if let Some(location) = location {
751 diagnostic.with_labels(vec![
752 Label::primary(file_id, location).with_message(i18n!("lbl_this_module"))
753 ])
754 } else {
755 diagnostic.with_notes(vec![i18n!("lbl_module_name", name = name.into())])
756 }
757 )
758}
759
760#[inline]
761#[allow(clippy::redundant_closure_call)]
762pub fn definition_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
763where
764 S: Into<String>,
765{
766 new_diagnostic!(
767 IncompleteDefinition,
768 |diagnostic: Diagnostic| if let Some(location) = location {
769 diagnostic.with_labels(vec![
770 Label::primary(file_id, location).with_message(i18n!("lbl_this_definition"))
771 ])
772 } else {
773 diagnostic.with_notes(vec![i18n!("lbl_definition_name", name = name.into())])
774 }
775 )
776}
777
778#[inline]
779#[allow(clippy::redundant_closure_call)]
780pub fn member_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
781where
782 S: Into<String>,
783{
784 new_diagnostic!(
785 IncompleteMember,
786 |diagnostic: Diagnostic| if let Some(location) = location {
787 diagnostic.with_labels(vec![
788 Label::primary(file_id, location).with_message(i18n!("lbl_this_member"))
789 ])
790 } else {
791 diagnostic.with_notes(vec![i18n!("lbl_member_name", name = name.into())])
792 }
793 )
794}
795
796#[inline]
797#[allow(clippy::redundant_closure_call)]
798pub fn string_without_language<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
799where
800 S: Into<String>,
801{
802 new_diagnostic!(
803 StringWithoutLanguage,
804 |diagnostic: Diagnostic| if let Some(location) = location {
805 diagnostic.with_labels(vec![
806 Label::primary(file_id, location).with_message(i18n!("lbl_this_value"))
807 ])
808 } else {
809 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
810 }
811 )
812}
813
814#[inline]
815#[allow(clippy::redundant_closure_call)]
816pub fn using_unconstrained_datatype<S>(
817 file_id: FileId,
818 location: Option<Span>,
819 name: S,
820) -> Diagnostic
821where
822 S: Into<String>,
823{
824 new_diagnostic!(
825 UnconstrainedDatatype,
826 |diagnostic: Diagnostic| if let Some(location) = location {
827 diagnostic.with_labels(vec![
828 Label::primary(file_id, location).with_message(i18n!("lbl_this_type"))
829 ])
830 } else {
831 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
832 }
833 )
834}
835
836#[inline]
837#[allow(clippy::redundant_closure_call)]
838pub fn double_underscored_identifier<S>(
839 file_id: FileId,
840 location: Option<Span>,
841 name: S,
842) -> Diagnostic
843where
844 S: Into<String>,
845{
846 new_diagnostic!(
847 DoubleUnderscoredIdentifier,
848 |diagnostic: Diagnostic| if let Some(location) = location {
849 diagnostic.with_labels(vec![
850 Label::primary(file_id, location).with_message(i18n!("lbl_this_identifier"))
851 ])
852 } else {
853 diagnostic.with_notes(vec![i18n!("lbl_identifier", name = name.into())])
854 }
855 )
856}
857
858#[inline]
859#[allow(clippy::redundant_closure_call)]
860pub fn identifier_not_preferred_case<S>(
861 file_id: FileId,
862 location: Option<Span>,
863 name: S,
864 case: IdentifierCaseConvention,
865) -> Diagnostic
866where
867 S: Into<String>,
868{
869 new_diagnostic!(IdentifierNotPreferredCase, |diagnostic: Diagnostic| {
870 if let Some(location) = location {
871 diagnostic.with_labels(vec![
872 Label::primary(file_id, location).with_message(i18n!("lbl_this_identifier"))
873 ])
874 } else {
875 diagnostic.with_notes(vec![i18n!("lbl_identifier", name = name.into())])
876 }
877 .with_notes(vec![i18n!(
878 "lbl_expected_case",
879 case = match case {
880 IdentifierCaseConvention::Module => i18n!("lbl_case_module"),
881 IdentifierCaseConvention::Member => i18n!("lbl_case_member"),
882 IdentifierCaseConvention::ImportedMember => i18n!("lbl_case_imported_member"),
883 IdentifierCaseConvention::DatatypeDefinition => i18n!("lbl_case_datatype"),
884 IdentifierCaseConvention::PropertyDefinition => i18n!("lbl_case_property"),
885 IdentifierCaseConvention::RdfDefinition => i18n!("lbl_case_rdf"),
886 IdentifierCaseConvention::TypeDefinition => i18n!("lbl_case_type_defn"),
887 IdentifierCaseConvention::ValueVariant => i18n!("lbl_case_value_variant"),
888 }
889 )])
890 })
891}
892
893impl IdentifierCaseConvention {
898 pub fn is_valid<S>(&self, id: S) -> bool
899 where
900 S: Into<String>,
901 {
902 let id = id.into();
903 match self {
904 Self::Module => id == Self::to_snake_case(&id),
905 Self::Member => id == Self::to_snake_case(&id) || id == Self::to_lower_camel_case(&id),
906 Self::ImportedMember => {
907 id == Self::to_snake_case(&id)
908 || id == Self::to_lower_camel_case(&id)
909 || id == Self::to_upper_camel_case(&id)
910 }
911 Self::DatatypeDefinition => {
912 id == Self::to_snake_case(&id)
913 || id == Self::to_lower_camel_case(&id)
914 || id == Self::to_upper_camel_case(&id)
915 }
916 Self::PropertyDefinition => {
917 id == Self::to_snake_case(&id) || id == Self::to_lower_camel_case(&id)
918 }
919 Self::RdfDefinition => {
920 id == Self::to_snake_case(&id)
921 || id == Self::to_lower_camel_case(&id)
922 || id == Self::to_upper_camel_case(&id)
923 }
924 Self::TypeDefinition => id == Self::to_upper_camel_case(&id),
925 Self::ValueVariant => {
926 id == Self::to_upper_camel_case(&id) || id == Self::to_shouty_snake_case(&id)
927 }
928 }
929 }
930
931 fn to_snake_case(id: &str) -> String {
932 id.to_snake_case()
933 }
934
935 fn to_upper_camel_case(id: &str) -> String {
936 id.to_upper_camel_case()
937 }
938
939 fn to_lower_camel_case(id: &str) -> String {
940 id.to_lower_camel_case()
941 }
942
943 fn to_shouty_snake_case(id: &str) -> String {
944 id.to_shouty_snake_case()
945 }
946}