sdml_errors/diagnostics/
functions.rs

1/*!
2One-line description.
3
4More detailed description, with
5
6# Example
7
8End of file during parsingSymbol’s value as variable is void: rustEnd of file during parsing
9
10 */
11
12use 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// ------------------------------------------------------------------------------------------------
19// Public Types
20// ------------------------------------------------------------------------------------------------
21
22#[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
34// ------------------------------------------------------------------------------------------------
35// Private Macros
36// ------------------------------------------------------------------------------------------------
37
38macro_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// ------------------------------------------------------------------------------------------------
48// Public Functions  Bugs
49// ------------------------------------------------------------------------------------------------
50
51///
52/// Note: tree-sitter originated errors will *always* have a location.
53///
54#[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///
71/// Note: tree-sitter originated errors will *always* have a location.
72///
73#[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///
104/// Note: tree-sitter originated errors will *always* have a location.
105///
106#[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// ------------------------------------------------------------------------------------------------
137// Public Functions  Errors
138// ------------------------------------------------------------------------------------------------
139
140#[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// ------------------------------------------------------------------------------------------------
638// Public Functions  Warnings
639// ------------------------------------------------------------------------------------------------
640
641#[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// ------------------------------------------------------------------------------------------------
739// Public Functions  Informational
740// ------------------------------------------------------------------------------------------------
741
742#[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
893// ------------------------------------------------------------------------------------------------
894// Implementations
895// ------------------------------------------------------------------------------------------------
896
897impl 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}