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![i18n!("lbl_module_name", name = name.into())]))
148}
149
150#[inline]
151#[allow(clippy::redundant_closure_call)]
152pub fn imported_module_not_found<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
153where
154 S: Into<String>,
155{
156 new_diagnostic!(
157 ImportedModuleNotFound,
158 |diagnostic: Diagnostic| if let Some(location) = location {
159 diagnostic.with_labels(vec![
160 Label::primary(file_id, location).with_message(i18n!("lbl_this_import"))
161 ])
162 } else {
163 diagnostic.with_notes(vec![i18n!("lbl_module_name", name = name.into())])
164 }
165 )
166}
167
168#[inline]
169#[allow(clippy::redundant_closure_call)]
170pub fn module_version_not_found<S1, S2>(
171 file_id: FileId,
172 expecting_location: Option<Span>,
173 expecting: S1,
174 actual_file_id: FileId,
175 actual_location: Option<Span>,
176 actual: S2,
177) -> Diagnostic
178where
179 S1: Into<String>,
180 S2: Into<String>,
181{
182 new_diagnostic!(
183 ModuleVersionNotFound,
184 |diagnostic: Diagnostic| if let Some(location) = expecting_location {
185 let diagnostic = diagnostic.with_labels(vec![
186 Label::primary(file_id, location).with_message(i18n!("lbl_this_import"))
187 ]);
188 if let Some(location) = actual_location {
189 diagnostic.with_labels(vec![Label::secondary(actual_file_id, location)
190 .with_message(i18n!("lbl_this_module"))])
191 } else {
192 diagnostic
193 }
194 } else {
195 diagnostic.with_notes(vec![
196 i18n!("lbl_expected_version_uri", url = expecting.into()),
197 i18n!("lbl_module_name", name = actual.into()),
198 ])
199 }
200 )
201}
202
203#[inline]
204#[allow(clippy::redundant_closure_call)]
205pub fn module_version_mismatch<S1, S2>(
206 file_id: FileId,
207 expecting_location: Option<Span>,
208 expecting: S1,
209 actual_file_id: FileId,
210 actual_location: Option<Span>,
211 actual: S2,
212) -> Diagnostic
213where
214 S1: Into<String>,
215 S2: Into<String>,
216{
217 new_diagnostic!(
218 ModuleVersionMismatch,
219 |diagnostic: Diagnostic| if let Some(location) = expecting_location {
220 let diagnostic = diagnostic.with_labels(vec![Label::primary(file_id, location)
221 .with_message(i18n!("lbl_expected_this_version_uri"))]);
222 if let Some(location) = actual_location {
223 diagnostic.with_labels(vec![Label::secondary(actual_file_id, location)
224 .with_message(i18n!("lbl_actual_this_version_uri"))])
225 } else {
226 diagnostic
227 }
228 } else {
229 diagnostic.with_notes(vec![
230 i18n!("lbl_expected_version_uri", url = expecting.into()),
231 i18n!("lbl_actual_version_uri", url = actual.into()),
232 ])
233 }
234 )
235}
236
237#[inline]
238#[allow(clippy::redundant_closure_call)]
239pub fn duplicate_definition(file_id: FileId, first: Span, second: Span) -> Diagnostic {
240 new_diagnostic!(DuplicateDefinitionName, |diagnostic: Diagnostic| diagnostic
241 .with_labels(vec![
242 Label::primary(file_id, second).with_message(i18n!("lbl_this_definition_name")),
243 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_defined_here")),
244 ]))
245}
246
247#[inline]
248#[allow(clippy::redundant_closure_call)]
249pub fn duplicate_member(file_id: FileId, first: Span, second: Span) -> Diagnostic {
250 new_diagnostic!(DuplicateMemberName, |diagnostic: Diagnostic| diagnostic
251 .with_labels(vec![
252 Label::primary(file_id, second).with_message(i18n!("lbl_this_member_name")),
253 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_defined_here")),
254 ]))
255}
256
257#[inline]
258#[allow(clippy::redundant_closure_call)]
259pub fn duplicate_variant(file_id: FileId, first: Span, second: Span) -> Diagnostic {
260 new_diagnostic!(DuplicateVariantName, |diagnostic: Diagnostic| diagnostic
261 .with_labels(vec![
262 Label::primary(file_id, second).with_message(i18n!("lbl_this_variant_name")),
263 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_defined_here")),
264 ]))
265}
266
267#[inline]
268#[allow(clippy::redundant_closure_call)]
269pub fn invalid_identifier<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
270where
271 S: Into<String>,
272{
273 new_diagnostic!(
274 InvalidIdentifier,
275 |diagnostic: Diagnostic| if let Some(location) = location {
276 diagnostic.with_labels(vec![
277 Label::primary(file_id, location).with_message(i18n!("lbl_this_identifier"))
278 ])
279 } else {
280 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
281 }
282 )
283}
284
285#[inline]
286#[allow(clippy::redundant_closure_call)]
287pub fn invalid_language_tag<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
288where
289 S: Into<String>,
290{
291 new_diagnostic!(
292 InvalidLanguageTag,
293 |diagnostic: Diagnostic| if let Some(location) = location {
294 diagnostic.with_labels(vec![
295 Label::primary(file_id, location).with_message(i18n!("lbl_this_language_tag"))
296 ])
297 } else {
298 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
299 }
300 )
301}
302
303#[inline]
304#[allow(clippy::redundant_closure_call)]
305pub fn invalid_value_for_type<S1, S2>(
306 value_file_id: FileId,
307 value_location: Option<Span>,
308 value: S1,
309 type_file_id: FileId,
310 type_location: Option<Span>,
311 type_name: S2,
312) -> Diagnostic
313where
314 S1: Into<String>,
315 S2: Into<String>,
316{
317 new_diagnostic!(InvalidValueForType, |diagnostic: Diagnostic| {
318 let diagnostic = if let Some(location) = value_location {
319 diagnostic.with_labels(vec![
320 Label::primary(value_file_id, location).with_message(i18n!("lbl_this_value"))
321 ])
322 } else {
323 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
324 };
325 if let Some(location) = type_location {
326 diagnostic.with_labels(vec![
327 Label::primary(type_file_id, location).with_message(i18n!("lbl_this_type"))
328 ])
329 } else {
330 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
331 }
332 })
333}
334
335#[inline]
336#[allow(clippy::redundant_closure_call)]
337pub fn invalid_value_for_type_named<S1, S2, E>(
338 value_file_id: FileId,
339 value_location: Option<Span>,
340 value: S1,
341 type_name: S2,
342 rust_error: Option<E>,
343) -> Diagnostic
344where
345 S1: Into<String>,
346 S2: Into<String>,
347 E: Error,
348{
349 new_diagnostic!(InvalidValueForType, |diagnostic: Diagnostic| {
350 let diagnostic = if let Some(location) = value_location {
351 diagnostic
352 .with_labels(vec![
353 Label::primary(value_file_id, location).with_message(i18n!("lbl_this_value"))
354 ])
355 .with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
356 } else {
357 diagnostic.with_notes(vec![
358 i18n!("lbl_value", val = value.into()),
359 i18n!("lbl_type_name", name = type_name.into()),
360 ])
361 };
362 if let Some(rust_error) = rust_error {
363 diagnostic.with_notes(vec![i18n!(
364 "lbl_specific_error",
365 err = rust_error.to_string()
366 )])
367 } else {
368 diagnostic
369 }
370 })
371}
372
373#[inline]
374#[allow(clippy::redundant_closure_call)]
375pub fn definition_not_found<S>(
376 file_id: FileId,
377 reference_location: Option<Span>,
378 name: S,
379) -> Diagnostic
380where
381 S: Into<String>,
382{
383 new_diagnostic!(
384 DefinitionNotFound,
385 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
386 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
387 .with_message(i18n!("lbl_this_reference"))])
388 } else {
389 diagnostic.with_notes(vec![i18n!("lbl_definition_name", name = name.into())])
390 }
391 )
392}
393
394#[inline]
395#[allow(clippy::redundant_closure_call)]
396pub fn type_definition_not_found<S>(
397 file_id: FileId,
398 reference_location: Option<Span>,
399 name: S,
400) -> Diagnostic
401where
402 S: Into<String>,
403{
404 new_diagnostic!(TypeDefinitionNotFound, |diagnostic: Diagnostic| {
405 if let Some(reference_location) = reference_location {
406 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
407 .with_message(i18n!("lbl_this_reference"))])
408 } else {
409 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
410 }
411 .with_notes(vec![i18n!("help_type_definition_not_found")])
412 })
413}
414
415#[inline]
416#[allow(clippy::redundant_closure_call)]
417pub fn datatype_invalid_base_type<S>(
418 file_id: FileId,
419 reference_location: Option<Span>,
420 name: S,
421) -> Diagnostic
422where
423 S: Into<String>,
424{
425 new_diagnostic!(DatatypeInvalidBase, |diagnostic: Diagnostic| {
426 if let Some(reference_location) = reference_location {
427 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
428 .with_message(i18n!("lbl_this_reference"))])
429 } else {
430 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
431 }
432 .with_notes(vec![i18n!("help_datatype_invalid_base_type")])
433 })
434}
435
436#[inline]
437#[allow(clippy::redundant_closure_call)]
438pub fn type_class_incompatible_usage<S>(
439 file_id: FileId,
440 reference_location: Option<Span>,
441 name: S,
442) -> Diagnostic
443where
444 S: Into<String>,
445{
446 new_diagnostic!(TypeClassIncompatible, |diagnostic: Diagnostic| if let Some(
447 reference_location,
448 ) = reference_location
449 {
450 diagnostic.with_labels(vec![
451 Label::primary(file_id, reference_location).with_message(i18n!("lbl_this_usage"))
452 ])
453 } else {
454 diagnostic.with_notes(vec![i18n!("lbl_typeclass_name", name = name.into())])
455 })
456}
457
458#[inline]
459#[allow(clippy::redundant_closure_call)]
460pub fn property_incompatible_usage<S>(
461 file_id: FileId,
462 reference_location: Option<Span>,
463 name: S,
464) -> Diagnostic
465where
466 S: Into<String>,
467{
468 new_diagnostic!(
469 PropertyIncompatible,
470 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
471 diagnostic
472 .with_labels(vec![Label::primary(file_id, reference_location)
473 .with_message(i18n!("lbl_this_usage"))])
474 } else {
475 diagnostic.with_notes(vec![i18n!("lbl_property_name", name = name.into())])
476 }
477 )
478}
479
480#[inline]
481#[allow(clippy::redundant_closure_call)]
482pub fn rdf_definition_incompatible_usage<S>(
483 file_id: FileId,
484 reference_location: Option<Span>,
485 name: S,
486) -> Diagnostic
487where
488 S: Into<String>,
489{
490 new_diagnostic!(
491 RdfDefinitionIncompatible,
492 |diagnostic: Diagnostic| if let Some(reference_location) = reference_location {
493 diagnostic
494 .with_labels(vec![Label::primary(file_id, reference_location)
495 .with_message(i18n!("lbl_this_usage"))])
496 } else {
497 diagnostic.with_notes(vec![i18n!("lbl_rdf_name", name = name.into())])
498 }
499 )
500}
501
502#[inline]
503#[allow(clippy::redundant_closure_call)]
504pub fn feature_set_not_a_union<S>(
505 file_id: FileId,
506 reference_location: Option<Span>,
507 name: S,
508) -> Diagnostic
509where
510 S: Into<String>,
511{
512 new_diagnostic!(FeatureSetNotUnion, |diagnostic: Diagnostic| {
513 if let Some(reference_location) = reference_location {
514 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
515 .with_message(i18n!("lbl_this_reference"))])
516 } else {
517 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
518 }
519 .with_notes(vec![i18n!("help_feature_set_not_a_union")])
520 })
521}
522
523#[inline]
524#[allow(clippy::redundant_closure_call)]
525pub fn property_reference_not_property<S>(
526 file_id: FileId,
527 reference_location: Option<Span>,
528 name: S,
529) -> Diagnostic
530where
531 S: Into<String>,
532{
533 new_diagnostic!(PropertyReferenceNotProperty, |diagnostic: Diagnostic| {
534 if let Some(reference_location) = reference_location {
535 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
536 .with_message(i18n!("lbl_this_reference"))])
537 } else {
538 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
539 }
540 .with_notes(vec![i18n!("help_property_reference_not_property")])
541 })
542}
543
544#[inline]
545#[allow(clippy::redundant_closure_call)]
546pub fn library_definition_not_allowed<S>(
547 file_id: FileId,
548 reference_location: Option<Span>,
549 name: S,
550) -> Diagnostic
551where
552 S: Into<String>,
553{
554 new_diagnostic!(LibraryDefinitionNotAllowed, |diagnostic: Diagnostic| {
555 if let Some(reference_location) = reference_location {
556 diagnostic.with_labels(vec![Label::primary(file_id, reference_location)
557 .with_message(i18n!("lbl_this_reference"))])
558 } else {
559 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
560 }
561 })
562}
563
564#[inline]
569#[allow(clippy::redundant_closure_call)]
570pub fn duplicate_module_import(file_id: FileId, first: Span, second: Span) -> Diagnostic {
571 new_diagnostic!(DuplicateModuleImport, |diagnostic: Diagnostic| diagnostic
572 .with_labels(vec![
573 Label::primary(file_id, second).with_message(i18n!("lbl_this_module")),
574 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_imported_here")),
575 ]))
576}
577
578#[inline]
579#[allow(clippy::redundant_closure_call)]
580pub fn duplicate_definition_import(file_id: FileId, first: Span, second: Span) -> Diagnostic {
581 new_diagnostic!(DuplicateDefinitionImport, |diagnostic: Diagnostic| {
582 diagnostic.with_labels(vec![
583 Label::primary(file_id, second).with_message(i18n!("lbl_this_member")),
584 Label::secondary(file_id, first).with_message(i18n!("lbl_previously_imported_here")),
585 ])
586 })
587}
588
589#[inline]
590#[allow(clippy::redundant_closure_call)]
591pub fn type_validation_incomplete<S>(
592 file_id: FileId,
593 location: Option<Span>,
594 type_name: S,
595) -> Diagnostic
596where
597 S: Into<String>,
598{
599 new_diagnostic!(
600 ValidationIncomplete,
601 |diagnostic: Diagnostic| if let Some(location) = location {
602 diagnostic.with_labels(vec![
603 Label::primary(file_id, location).with_message(i18n!("lbl_this_definition"))
604 ])
605 } else {
606 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = type_name.into())])
607 }
608 )
609}
610
611#[inline]
612#[allow(clippy::redundant_closure_call)]
613pub fn module_version_info_empty(file_id: FileId, location: Option<Span>) -> Diagnostic {
614 new_diagnostic!(
615 ModuleVersionInfoEmpty,
616 |diagnostic: Diagnostic| if let Some(location) = location {
617 diagnostic.with_labels(vec![
618 Label::primary(file_id, location).with_message(i18n!("lbl_this_value"))
619 ])
620 } else {
621 diagnostic
622 }
623 )
624}
625
626#[inline]
627#[allow(clippy::redundant_closure_call)]
628pub fn deprecated_term_used<S>(
629 file_id: FileId,
630 location: Option<Span>,
631 value: S,
632 term_name: &str,
633 alternative_terms: &[String],
634 reason: Option<&String>,
635) -> Diagnostic
636where
637 S: Into<String>,
638{
639 new_diagnostic!(DeprecatedTermUsed, |diagnostic: Diagnostic| {
640 let diagnostic = if let Some(location) = location {
641 diagnostic.with_labels(vec![
642 Label::primary(file_id, location).with_message(i18n!("lbl_here"))
643 ])
644 } else {
645 diagnostic.with_notes(vec![
646 i18n!("lbl_term_name", name = term_name),
647 i18n!("lbl_in_this", val = value.into()),
648 ])
649 }
650 .with_notes(vec![i18n!(
651 "help_alternative_terms",
652 terms = alternative_terms.join(", ")
653 )]);
654 if let Some(reason) = reason {
655 diagnostic.with_notes(vec![i18n!(
656 "help_deprecated_term_reason",
657 reason = reason.as_str()
658 )])
659 } else {
660 diagnostic
661 }
662 })
663}
664
665#[inline]
670#[allow(clippy::redundant_closure_call)]
671pub fn module_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
672where
673 S: Into<String>,
674{
675 new_diagnostic!(
676 IncompleteModule,
677 |diagnostic: Diagnostic| if let Some(location) = location {
678 diagnostic.with_labels(vec![
679 Label::primary(file_id, location).with_message(i18n!("lbl_this_module"))
680 ])
681 } else {
682 diagnostic.with_notes(vec![i18n!("lbl_module_name", name = name.into())])
683 }
684 )
685}
686
687#[inline]
688#[allow(clippy::redundant_closure_call)]
689pub fn definition_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
690where
691 S: Into<String>,
692{
693 new_diagnostic!(
694 IncompleteDefinition,
695 |diagnostic: Diagnostic| if let Some(location) = location {
696 diagnostic.with_labels(vec![
697 Label::primary(file_id, location).with_message(i18n!("lbl_this_definition"))
698 ])
699 } else {
700 diagnostic.with_notes(vec![i18n!("lbl_definition_name", name = name.into())])
701 }
702 )
703}
704
705#[inline]
706#[allow(clippy::redundant_closure_call)]
707pub fn member_is_incomplete<S>(file_id: FileId, location: Option<Span>, name: S) -> Diagnostic
708where
709 S: Into<String>,
710{
711 new_diagnostic!(
712 IncompleteMember,
713 |diagnostic: Diagnostic| if let Some(location) = location {
714 diagnostic.with_labels(vec![
715 Label::primary(file_id, location).with_message(i18n!("lbl_this_member"))
716 ])
717 } else {
718 diagnostic.with_notes(vec![i18n!("lbl_member_name", name = name.into())])
719 }
720 )
721}
722
723#[inline]
724#[allow(clippy::redundant_closure_call)]
725pub fn string_without_language<S>(file_id: FileId, location: Option<Span>, value: S) -> Diagnostic
726where
727 S: Into<String>,
728{
729 new_diagnostic!(
730 StringWithoutLanguage,
731 |diagnostic: Diagnostic| if let Some(location) = location {
732 diagnostic.with_labels(vec![
733 Label::primary(file_id, location).with_message(i18n!("lbl_this_value"))
734 ])
735 } else {
736 diagnostic.with_notes(vec![i18n!("lbl_value", val = value.into())])
737 }
738 )
739}
740
741#[inline]
742#[allow(clippy::redundant_closure_call)]
743pub fn using_unconstrained_datatype<S>(
744 file_id: FileId,
745 location: Option<Span>,
746 name: S,
747) -> Diagnostic
748where
749 S: Into<String>,
750{
751 new_diagnostic!(
752 UnconstrainedDatatype,
753 |diagnostic: Diagnostic| if let Some(location) = location {
754 diagnostic.with_labels(vec![
755 Label::primary(file_id, location).with_message(i18n!("lbl_this_type"))
756 ])
757 } else {
758 diagnostic.with_notes(vec![i18n!("lbl_type_name", name = name.into())])
759 }
760 )
761}
762
763#[inline]
764#[allow(clippy::redundant_closure_call)]
765pub fn double_underscored_identifier<S>(
766 file_id: FileId,
767 location: Option<Span>,
768 name: S,
769) -> Diagnostic
770where
771 S: Into<String>,
772{
773 new_diagnostic!(
774 DoubleUnderscoredIdentifier,
775 |diagnostic: Diagnostic| if let Some(location) = location {
776 diagnostic.with_labels(vec![
777 Label::primary(file_id, location).with_message(i18n!("lbl_this_identifier"))
778 ])
779 } else {
780 diagnostic.with_notes(vec![i18n!("lbl_identifier", name = name.into())])
781 }
782 )
783}
784
785#[inline]
786#[allow(clippy::redundant_closure_call)]
787pub fn identifier_not_preferred_case<S>(
788 file_id: FileId,
789 location: Option<Span>,
790 name: S,
791 case: IdentifierCaseConvention,
792) -> Diagnostic
793where
794 S: Into<String>,
795{
796 new_diagnostic!(IdentifierNotPreferredCase, |diagnostic: Diagnostic| {
797 if let Some(location) = location {
798 diagnostic.with_labels(vec![
799 Label::primary(file_id, location).with_message(i18n!("lbl_this_identifier"))
800 ])
801 } else {
802 diagnostic.with_notes(vec![i18n!("lbl_identifier", name = name.into())])
803 }
804 .with_notes(vec![i18n!(
805 "lbl_expected_case",
806 case = match case {
807 IdentifierCaseConvention::Module => i18n!("lbl_case_module"),
808 IdentifierCaseConvention::Member => i18n!("lbl_case_member"),
809 IdentifierCaseConvention::ImportedMember => i18n!("lbl_case_imported_member"),
810 IdentifierCaseConvention::DatatypeDefinition => i18n!("lbl_case_datatype"),
811 IdentifierCaseConvention::PropertyDefinition => i18n!("lbl_case_property"),
812 IdentifierCaseConvention::RdfDefinition => i18n!("lbl_case_rdf"),
813 IdentifierCaseConvention::TypeDefinition => i18n!("lbl_case_type_defn"),
814 IdentifierCaseConvention::ValueVariant => i18n!("lbl_case_value_variant"),
815 }
816 )])
817 })
818}
819
820impl IdentifierCaseConvention {
825 pub fn is_valid<S>(&self, id: S) -> bool
826 where
827 S: Into<String>,
828 {
829 let id = id.into();
830 match self {
831 Self::Module => id == Self::to_snake_case(&id),
832 Self::Member => id == Self::to_snake_case(&id) || id == Self::to_lower_camel_case(&id),
833 Self::ImportedMember => {
834 id == Self::to_snake_case(&id)
835 || id == Self::to_lower_camel_case(&id)
836 || id == Self::to_upper_camel_case(&id)
837 }
838 Self::DatatypeDefinition => {
839 id == Self::to_snake_case(&id)
840 || id == Self::to_lower_camel_case(&id)
841 || id == Self::to_upper_camel_case(&id)
842 }
843 Self::PropertyDefinition => {
844 id == Self::to_snake_case(&id) || id == Self::to_lower_camel_case(&id)
845 }
846 Self::RdfDefinition => {
847 id == Self::to_snake_case(&id)
848 || id == Self::to_lower_camel_case(&id)
849 || id == Self::to_upper_camel_case(&id)
850 }
851 Self::TypeDefinition => id == Self::to_upper_camel_case(&id),
852 Self::ValueVariant => {
853 id == Self::to_upper_camel_case(&id) || id == Self::to_shouty_snake_case(&id)
854 }
855 }
856 }
857
858 fn to_snake_case(id: &str) -> String {
859 id.to_snake_case()
860 }
861
862 fn to_upper_camel_case(id: &str) -> String {
863 id.to_upper_camel_case()
864 }
865
866 fn to_lower_camel_case(id: &str) -> String {
867 id.to_lower_camel_case()
868 }
869
870 fn to_shouty_snake_case(id: &str) -> String {
871 id.to_shouty_snake_case()
872 }
873}