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![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// ------------------------------------------------------------------------------------------------
565// Public Functions  Warnings
566// ------------------------------------------------------------------------------------------------
567
568#[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// ------------------------------------------------------------------------------------------------
666// Public Functions  Informational
667// ------------------------------------------------------------------------------------------------
668
669#[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
820// ------------------------------------------------------------------------------------------------
821// Implementations
822// ------------------------------------------------------------------------------------------------
823
824impl 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}