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_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// ------------------------------------------------------------------------------------------------
709// Public Functions  Warnings
710// ------------------------------------------------------------------------------------------------
711
712#[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// ------------------------------------------------------------------------------------------------
810// Public Functions  Informational
811// ------------------------------------------------------------------------------------------------
812
813#[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
964// ------------------------------------------------------------------------------------------------
965// Implementations
966// ------------------------------------------------------------------------------------------------
967
968impl 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}