1use blake3::Hasher;
4use serde::{Deserialize, Serialize};
5use std::collections::{BTreeMap, BTreeSet};
6use std::error::Error;
7use std::fmt;
8use std::fmt::Write as _;
9use std::path::Path;
10use std::str::FromStr;
11use std::sync::OnceLock;
12
13pub const LANGUAGE_CAPABILITY_REGISTRY_VERSION: u32 = 5;
15
16pub const SEMANTIC_PROVIDER_CONTRACT_VERSION: u32 = 1;
18
19pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION: u32 = 15;
21
22pub const LANGUAGE_DETECTION_POLICY_VERSION: u32 = 1;
24
25pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V1_DIGEST: &str =
30 "58f2e1e6755464d573df998c1c1cecb2d076c829c0dbe04a463ce14a5a239861";
31
32pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V2_DIGEST: &str =
37 "8397f73a7593b849d0e83b3892e4721874ac4a4fd93f62b42dac9ffe166a2a7c";
38
39pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V3_DIGEST: &str =
44 "a4b69ce4aed2ebf8d28f7b237ead76a53e5363e34c8c97ea5980776ea4217ef4";
45
46pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V4_DIGEST: &str =
51 "e9a952d0b3bc2d2c5db832130d85b7cdfd656aaa07ebbafab1505da6b87d9084";
52
53pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V5_DIGEST: &str =
58 "07a3d2c45a4736bc764e44016a6ba9b7f9ea1b769b0100604702160528679bc7";
59
60pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V6_DIGEST: &str =
65 "e9342f2b06b083a72ecc58af0afe4ba12f0ec33321225199bb0c9be4f4375c7a";
66
67pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V7_DIGEST: &str =
72 "50fcac887dffecc27f1b7d365ff5da991f2a86dc15e0474ef5bcc339c58bfd60";
73
74pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V8_DIGEST: &str =
79 "2b26ae43b74475ea0dcb78d5b182329d500d76c33b2d87470d303400886ead1b";
80
81pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V9_DIGEST: &str =
86 "5fa0073094df29fba7160cecf85afa3ba5a9bcfa7ee4b5a53cde9e371b0077d2";
87
88pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V10_DIGEST: &str =
93 "cbede576a7b2ab4309798075210b59dbece0cf99cc7874a9659fd17f3c2d961f";
94
95pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V11_DIGEST: &str =
100 "3776f19c62b3debfcae13715e3bdc3ec3029978a4f7ba1428b7a06d433524915";
101
102pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V12_DIGEST: &str =
107 "bae01db588d8e6c8666bb1afd66ffcbffb3022c23c68df52f9822c291f9d895c";
108
109pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V13_DIGEST: &str =
111 "63ccc321601fcc207a7540163abb9cff9547d41e43b8b696073e22707db4a3d1";
112
113pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V14_DIGEST: &str =
115 "323321adb18b8f7c9ddc045949fc097bd0e6933422228c2c25a8ee5b07daeac9";
116
117pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V15_DIGEST: &str =
122 "f560cb41478d81ac46b3ac5d79ad9e94a27a235f1b4209091c4e0220298d2b1a";
123
124pub const LANGUAGE_CONTENT_DETECTION_MAX_BYTES: usize = 512;
126
127pub const OPTIONAL_GRAMMAR_CATALOG: &str = "tree-sitter-language-pack";
129
130pub const OPTIONAL_GRAMMAR_CATALOG_VERSION: &str = "1.13.2";
132
133pub const OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION: &str =
135 "6258abac30304283763a0d2dc8a48cb87fbcf438";
136
137pub const OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS: usize = 150;
139
140pub const BROAD_PARSER_PACK_ID: &str = "broad-parser";
142
143pub const LANGUAGE_REGISTRY_REPORT_MAX_BYTES: usize = 32_000;
145
146#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
148#[serde(rename_all = "kebab-case")]
149pub enum LanguageParserSupport {
150 Native,
152 Manifest,
154 Structural,
156 Fallback,
158}
159
160#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
162#[serde(rename_all = "kebab-case")]
163pub enum CapabilitySupportLevel {
164 Unavailable,
166 Fallback,
168 Supported,
170}
171
172impl CapabilitySupportLevel {
173 #[must_use]
175 pub const fn as_str(self) -> &'static str {
176 match self {
177 Self::Unavailable => "unavailable",
178 Self::Fallback => "fallback",
179 Self::Supported => "supported",
180 }
181 }
182}
183
184#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
186pub struct LanguageCapabilitySupport {
187 pub detected: CapabilitySupportLevel,
189 pub parsed: CapabilitySupportLevel,
191 pub symbols: CapabilitySupportLevel,
193 pub semantic: CapabilitySupportLevel,
195 pub benchmarked: CapabilitySupportLevel,
197}
198
199impl LanguageCapabilitySupport {
200 #[must_use]
202 pub const fn new(
203 detected: CapabilitySupportLevel,
204 parsed: CapabilitySupportLevel,
205 symbols: CapabilitySupportLevel,
206 semantic: CapabilitySupportLevel,
207 benchmarked: CapabilitySupportLevel,
208 ) -> Self {
209 Self {
210 detected,
211 parsed,
212 symbols,
213 semantic,
214 benchmarked,
215 }
216 }
217
218 #[must_use]
220 pub const fn meets(self, minimum: Self) -> bool {
221 self.detected as u8 >= minimum.detected as u8
222 && self.parsed as u8 >= minimum.parsed as u8
223 && self.symbols as u8 >= minimum.symbols as u8
224 && self.semantic as u8 >= minimum.semantic as u8
225 && self.benchmarked as u8 >= minimum.benchmarked as u8
226 }
227}
228
229const SUPPORTED_NATIVE: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
231 CapabilitySupportLevel::Supported,
232 CapabilitySupportLevel::Supported,
233 CapabilitySupportLevel::Supported,
234 CapabilitySupportLevel::Unavailable,
235 CapabilitySupportLevel::Unavailable,
236);
237
238const SUPPORTED_SEMANTIC: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
240 CapabilitySupportLevel::Supported,
241 CapabilitySupportLevel::Supported,
242 CapabilitySupportLevel::Supported,
243 CapabilitySupportLevel::Supported,
244 CapabilitySupportLevel::Unavailable,
245);
246
247const SUPPORTED_STRUCTURAL: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
249 CapabilitySupportLevel::Supported,
250 CapabilitySupportLevel::Supported,
251 CapabilitySupportLevel::Unavailable,
252 CapabilitySupportLevel::Unavailable,
253 CapabilitySupportLevel::Unavailable,
254);
255
256const SUPPORTED_FALLBACK: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
258 CapabilitySupportLevel::Supported,
259 CapabilitySupportLevel::Fallback,
260 CapabilitySupportLevel::Fallback,
261 CapabilitySupportLevel::Unavailable,
262 CapabilitySupportLevel::Unavailable,
263);
264
265#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
267#[serde(rename_all = "kebab-case")]
268pub enum TreeSitterGrammar {
269 Rust,
271 Python,
273 JavaScript,
275 TypeScript,
277 Tsx,
279 Java,
281 Kotlin,
283 CSharp,
285 Go,
287 ObjectiveC,
289 Zig,
291 C,
293 Cpp,
295 Php,
297}
298
299impl TreeSitterGrammar {
300 pub const ALL: &'static [Self] = &[
302 Self::Rust,
303 Self::Python,
304 Self::JavaScript,
305 Self::TypeScript,
306 Self::Tsx,
307 Self::Java,
308 Self::Kotlin,
309 Self::CSharp,
310 Self::Go,
311 Self::ObjectiveC,
312 Self::Zig,
313 Self::C,
314 Self::Cpp,
315 Self::Php,
316 ];
317
318 #[must_use]
320 pub const fn package(self) -> &'static str {
321 match self {
322 Self::Rust => "tree-sitter-rust",
323 Self::Python => "tree-sitter-python",
324 Self::JavaScript => "tree-sitter-javascript",
325 Self::TypeScript | Self::Tsx => "tree-sitter-typescript",
326 Self::Java => "tree-sitter-java",
327 Self::Kotlin => "tree-sitter-kotlin-ng",
328 Self::CSharp => "tree-sitter-c-sharp",
329 Self::Go => "tree-sitter-go",
330 Self::ObjectiveC => "tree-sitter-objc",
331 Self::Zig => "tree-sitter-zig",
332 Self::C => "tree-sitter-c",
333 Self::Cpp => "tree-sitter-cpp",
334 Self::Php => "tree-sitter-php",
335 }
336 }
337
338 #[must_use]
340 pub const fn version(self) -> &'static str {
341 match self {
342 Self::Rust | Self::C | Self::Php => "0.24.2",
343 Self::Python | Self::JavaScript | Self::Go => "0.25.0",
344 Self::TypeScript | Self::Tsx => "0.23.2",
345 Self::Java | Self::CSharp => "0.23.5",
346 Self::Kotlin => "1.1.0",
347 Self::ObjectiveC => "3.0.2",
348 Self::Zig => "1.1.2",
349 Self::Cpp => "0.23.4",
350 }
351 }
352}
353
354#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
356#[serde(rename_all = "kebab-case")]
357pub enum SymbolParserOwner {
358 TreeSitter(TreeSitterGrammar),
360 CargoManifest,
362 Vue,
364 PowerShell,
366 Markdown,
368 Document,
370 Fallback,
372 Unavailable,
374}
375
376#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
378#[serde(rename_all = "kebab-case")]
379pub enum SemanticProviderOwner {
380 Rust,
382 EcmaScript,
384 Python,
386 Cargo,
388 Unavailable,
390}
391
392impl SemanticProviderOwner {
393 #[must_use]
395 pub const fn as_str(self) -> &'static str {
396 match self {
397 Self::Rust => "rust",
398 Self::EcmaScript => "ecma-script",
399 Self::Python => "python",
400 Self::Cargo => "cargo",
401 Self::Unavailable => "unavailable",
402 }
403 }
404
405 #[must_use]
411 pub const fn resolution_family(self) -> Option<&'static str> {
412 match self {
413 Self::Rust => Some("rust"),
414 Self::EcmaScript => Some("ecmascript"),
415 Self::Python => Some("python"),
416 Self::Cargo => Some("cargo"),
417 Self::Unavailable => None,
418 }
419 }
420}
421
422#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
424#[serde(rename_all = "kebab-case")]
425pub enum EmbeddedHostKind {
426 HtmlLike,
428 Component,
430 Template,
432}
433
434impl EmbeddedHostKind {
435 #[must_use]
437 pub const fn as_str(self) -> &'static str {
438 match self {
439 Self::HtmlLike => "html-like",
440 Self::Component => "component",
441 Self::Template => "template",
442 }
443 }
444}
445
446#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
448pub struct EmbeddedLanguageCapability {
449 pub host_kind: EmbeddedHostKind,
451 pub semantic_provider: SemanticProviderOwner,
453}
454
455#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
457#[serde(rename_all = "kebab-case")]
458pub enum StructuralSummaryOwner {
459 Markdown,
461 Json,
463 Yaml,
465 Toml,
467 Xml,
469 Css,
471 Html,
473 Toon,
475 PowerShell,
477 ConfigText,
479}
480
481#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
483#[serde(rename_all = "kebab-case")]
484pub enum CapabilityProvenance {
485 ProjectAtlas,
487 TreeSitter(TreeSitterGrammar),
489 PinnedOptionalCatalog,
494}
495
496impl CapabilityProvenance {
497 #[must_use]
499 pub const fn license(self) -> &'static str {
500 match self {
501 Self::ProjectAtlas | Self::TreeSitter(_) | Self::PinnedOptionalCatalog => "MIT",
502 }
503 }
504
505 #[must_use]
507 pub const fn source(self) -> &'static str {
508 match self {
509 Self::ProjectAtlas => "projectatlas",
510 Self::TreeSitter(grammar) => grammar.package(),
511 Self::PinnedOptionalCatalog => OPTIONAL_GRAMMAR_CATALOG,
512 }
513 }
514
515 #[must_use]
517 pub const fn version(self) -> &'static str {
518 match self {
519 Self::ProjectAtlas => env!("CARGO_PKG_VERSION"),
520 Self::TreeSitter(grammar) => grammar.version(),
521 Self::PinnedOptionalCatalog => OPTIONAL_GRAMMAR_CATALOG_VERSION,
522 }
523 }
524}
525
526#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
528#[serde(rename_all = "kebab-case")]
529pub enum RequiredPlatformSet {
530 AllSupported,
532}
533
534#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
536pub struct LanguageCapabilityFixtures {
537 pub positive_path: &'static str,
539 pub negative_path: &'static str,
541}
542
543#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
545#[serde(rename_all = "snake_case")]
546pub enum ContentClassification {
547 Source,
549 Documentation,
551 ConfigurationData,
553 OtherText,
555 Opaque,
557}
558
559impl ContentClassification {
560 pub const ALL: [Self; 5] = [
562 Self::Source,
563 Self::Documentation,
564 Self::ConfigurationData,
565 Self::OtherText,
566 Self::Opaque,
567 ];
568
569 #[must_use]
571 pub const fn as_str(self) -> &'static str {
572 match self {
573 Self::Source => "source",
574 Self::Documentation => "documentation",
575 Self::ConfigurationData => "configuration_data",
576 Self::OtherText => "other_text",
577 Self::Opaque => "opaque",
578 }
579 }
580
581 #[must_use]
583 pub fn from_db(value: &str) -> Option<Self> {
584 match value {
585 "source" => Some(Self::Source),
586 "documentation" => Some(Self::Documentation),
587 "configuration_data" => Some(Self::ConfigurationData),
588 "other_text" => Some(Self::OtherText),
589 "opaque" => Some(Self::Opaque),
590 _ => None,
591 }
592 }
593}
594
595impl fmt::Display for ContentClassification {
596 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
597 formatter.write_str(self.as_str())
598 }
599}
600
601#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
603#[serde(rename_all = "snake_case")]
604pub enum ContentSelection {
605 #[default]
607 #[serde(skip)]
608 UnspecifiedLegacy,
609 Source,
611 Documentation,
613 Both,
615}
616
617impl ContentSelection {
618 #[must_use]
620 pub const fn explicit_value(self) -> Option<&'static str> {
621 match self {
622 Self::UnspecifiedLegacy => None,
623 Self::Source => Some("source"),
624 Self::Documentation => Some("documentation"),
625 Self::Both => Some("both"),
626 }
627 }
628
629 #[must_use]
631 pub const fn includes(self, classification: ContentClassification) -> bool {
632 match self {
633 Self::UnspecifiedLegacy => true,
634 Self::Source => matches!(classification, ContentClassification::Source),
635 Self::Documentation => {
636 matches!(classification, ContentClassification::Documentation)
637 }
638 Self::Both => matches!(
639 classification,
640 ContentClassification::Source | ContentClassification::Documentation
641 ),
642 }
643 }
644}
645
646#[derive(Clone, Debug, Eq, PartialEq)]
648pub struct ContentSelectionParseError {
649 requested: String,
651}
652
653impl ContentSelectionParseError {
654 #[must_use]
656 pub fn requested(&self) -> &str {
657 &self.requested
658 }
659}
660
661impl fmt::Display for ContentSelectionParseError {
662 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
663 write!(
664 formatter,
665 "unsupported content selection {:?}; expected source, documentation, or both",
666 self.requested
667 )
668 }
669}
670
671impl Error for ContentSelectionParseError {}
672
673impl FromStr for ContentSelection {
674 type Err = ContentSelectionParseError;
675
676 fn from_str(value: &str) -> Result<Self, Self::Err> {
677 match value {
678 "source" => Ok(Self::Source),
679 "documentation" => Ok(Self::Documentation),
680 "both" => Ok(Self::Both),
681 _ => Err(ContentSelectionParseError {
682 requested: value.to_owned(),
683 }),
684 }
685 }
686}
687
688#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
690pub struct LanguageCapability {
691 pub id: &'static str,
693 pub aliases: &'static [&'static str],
695 pub classification: ContentClassification,
697 pub parser_support: LanguageParserSupport,
699 pub symbol_parser: SymbolParserOwner,
701 pub semantic_provider: SemanticProviderOwner,
703 pub embedded_language: Option<EmbeddedLanguageCapability>,
705 pub structural_summary: Option<StructuralSummaryOwner>,
707 pub optional_pack: Option<&'static str>,
709 pub support: LanguageCapabilitySupport,
711 pub accepted_minimum: LanguageCapabilitySupport,
713 pub fixtures: LanguageCapabilityFixtures,
715 pub provenance: CapabilityProvenance,
717 pub required_platforms: RequiredPlatformSet,
719}
720
721impl LanguageCapability {
722 #[must_use]
728 pub const fn effective_semantic_provider(self) -> Option<SemanticProviderOwner> {
729 match self.semantic_provider {
730 SemanticProviderOwner::Unavailable => match self.embedded_language {
731 Some(embedded)
732 if !matches!(
733 embedded.semantic_provider,
734 SemanticProviderOwner::Unavailable
735 ) =>
736 {
737 Some(embedded.semantic_provider)
738 }
739 _ => None,
740 },
741 provider => Some(provider),
742 }
743 }
744}
745
746#[derive(Clone, Copy, Debug, Eq, PartialEq)]
748pub struct LanguageSpec {
749 pub language: &'static str,
751 pub parser_support: LanguageParserSupport,
753}
754
755#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
757#[serde(rename_all = "kebab-case")]
758pub enum LanguageDetectionReason {
759 ExplicitOverride,
761 ExactFilename,
763 CompoundExtension,
765 Extension,
767 ContentDialect,
769}
770
771#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
773pub struct LanguageDetection {
774 pub language: &'static str,
776 pub reason: LanguageDetectionReason,
778}
779
780#[derive(Clone, Copy, Debug, Default)]
782pub struct LanguageDetectionRequest<'a> {
783 pub path: &'a str,
785 pub extension: Option<&'a str>,
787 pub explicit_override: Option<&'a str>,
789 pub content_prefix: Option<&'a [u8]>,
791}
792
793impl<'a> LanguageDetectionRequest<'a> {
794 #[must_use]
796 pub const fn new(path: &'a str, extension: Option<&'a str>) -> Self {
797 Self {
798 path,
799 extension,
800 explicit_override: None,
801 content_prefix: None,
802 }
803 }
804}
805
806#[derive(Clone, Debug, Eq, PartialEq)]
808pub struct LanguageDetectionError {
809 requested: String,
811}
812
813impl fmt::Display for LanguageDetectionError {
814 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
815 write!(
816 formatter,
817 "unknown explicit language override {:?}",
818 self.requested
819 )
820 }
821}
822
823impl Error for LanguageDetectionError {}
824
825#[derive(Clone, Debug, Eq, PartialEq)]
827pub struct LanguageRegistryError {
828 message: String,
830}
831
832impl LanguageRegistryError {
833 fn new(message: impl Into<String>) -> Self {
835 Self {
836 message: message.into(),
837 }
838 }
839}
840
841impl fmt::Display for LanguageRegistryError {
842 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
843 formatter.write_str(&self.message)
844 }
845}
846
847impl Error for LanguageRegistryError {}
848
849#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
851pub struct LanguageDetectionRule {
852 pub value: &'static str,
854 pub language: &'static str,
856}
857
858#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
860pub struct LanguageContentRule {
861 pub interpreter: &'static str,
863 pub allow_version_suffix: bool,
865 pub language: &'static str,
867}
868
869macro_rules! semantic_provider_or_unavailable {
871 () => {
872 SemanticProviderOwner::Unavailable
873 };
874 ($provider:expr) => {
875 $provider
876 };
877}
878
879macro_rules! embedded_language_or_none {
881 () => {
882 None
883 };
884 ($capability:expr) => {
885 Some($capability)
886 };
887}
888
889macro_rules! content_classification_or_source {
891 () => {
892 ContentClassification::Source
893 };
894 ($classification:expr) => {
895 $classification
896 };
897}
898
899macro_rules! define_language_registry {
901 (
902 capabilities {
903 $(
904 $id:literal => {
905 aliases: [$($alias:literal),* $(,)?],
906 $(classification: $classification:expr,)?
907 parser_support: $parser_support:ident,
908 symbol_parser: $symbol_parser:expr,
909 structural_summary: $structural_summary:expr,
910 support: $support:expr,
911 $(semantic_provider: $semantic_provider:expr,)?
912 $(embedded_language: $embedded_language:expr,)?
913 positive: $positive:literal,
914 negative: $negative:literal,
915 provenance: $provenance:expr
916 }
917 ),* $(,)?
918 }
919 optional_capabilities {
920 $(
921 $optional_id:literal => {
922 aliases: [$($optional_alias:literal),* $(,)?],
923 $(classification: $optional_classification:expr,)?
924 extension: $optional_extension:literal
925 }
926 ),* $(,)?
927 }
928 exact_filenames { $($exact:literal => $exact_language:literal),* $(,)? }
929 compound_extensions { $($compound:literal => $compound_language:literal),* $(,)? }
930 broad_extensions { $($extension:literal => $extension_language:literal),* $(,)? }
931 additional_extensions { $($additional:literal => $additional_language:literal),* $(,)? }
932 content_interpreters {
933 $(
934 $interpreter:literal => {
935 language: $content_language:literal,
936 version_suffix: $version_suffix:literal
937 }
938 ),* $(,)?
939 }
940 ) => {
941 pub const LANGUAGE_CAPABILITIES: &[LanguageCapability] = &[
943 $(LanguageCapability {
944 id: $id,
945 aliases: &[$($alias),*],
946 classification: content_classification_or_source!($($classification)?),
947 parser_support: LanguageParserSupport::$parser_support,
948 symbol_parser: $symbol_parser,
949 semantic_provider: semantic_provider_or_unavailable!($($semantic_provider)?),
950 embedded_language: embedded_language_or_none!($($embedded_language)?),
951 structural_summary: $structural_summary,
952 optional_pack: None,
953 support: $support,
954 accepted_minimum: $support,
955 fixtures: LanguageCapabilityFixtures {
956 positive_path: $positive,
957 negative_path: $negative,
958 },
959 provenance: $provenance,
960 required_platforms: RequiredPlatformSet::AllSupported,
961 }),*,
962 $(LanguageCapability {
963 id: $optional_id,
964 aliases: &[$($optional_alias),*],
965 classification: content_classification_or_source!($($optional_classification)?),
966 parser_support: LanguageParserSupport::Fallback,
967 symbol_parser: SymbolParserOwner::Fallback,
968 semantic_provider: SemanticProviderOwner::Unavailable,
969 embedded_language: None,
970 structural_summary: None,
971 optional_pack: Some(BROAD_PARSER_PACK_ID),
972 support: SUPPORTED_FALLBACK,
973 accepted_minimum: SUPPORTED_FALLBACK,
974 fixtures: LanguageCapabilityFixtures {
975 positive_path: concat!("fixture", $optional_extension),
976 negative_path: concat!("fixture", $optional_extension, ".bak"),
977 },
978 provenance: CapabilityProvenance::PinnedOptionalCatalog,
979 required_platforms: RequiredPlatformSet::AllSupported,
980 }),*
981 ];
982
983 pub const LANGUAGE_SPECS: &[LanguageSpec] = &[
985 $(LanguageSpec {
986 language: $id,
987 parser_support: LanguageParserSupport::$parser_support,
988 }),*,
989 $(LanguageSpec {
990 language: $optional_id,
991 parser_support: LanguageParserSupport::Fallback,
992 }),*
993 ];
994
995 pub const BROAD_SOURCE_EXTENSIONS: &[&str] = &[$($extension),*];
997
998 pub const DETECTED_SOURCE_EXTENSIONS: &[&str] = &[
1000 $($extension),*,
1001 $($optional_extension),*
1002 ];
1003
1004 pub const EXACT_FILENAME_RULES: &[LanguageDetectionRule] = &[
1006 $(LanguageDetectionRule { value: $exact, language: $exact_language }),*
1007 ];
1008
1009 pub const COMPOUND_EXTENSION_RULES: &[LanguageDetectionRule] = &[
1011 $(LanguageDetectionRule { value: $compound, language: $compound_language }),*
1012 ];
1013
1014 pub const EXTENSION_RULES: &[LanguageDetectionRule] = &[
1016 $(LanguageDetectionRule { value: $extension, language: $extension_language }),*,
1017 $(LanguageDetectionRule { value: $optional_extension, language: $optional_id }),*,
1018 $(LanguageDetectionRule { value: $additional, language: $additional_language }),*
1019 ];
1020
1021 pub const CONTENT_DIALECT_RULES: &[LanguageContentRule] = &[
1023 $(LanguageContentRule {
1024 interpreter: $interpreter,
1025 allow_version_suffix: $version_suffix,
1026 language: $content_language,
1027 }),*
1028 ];
1029
1030 fn exact_filename_language(file_name: &str) -> Option<&'static str> {
1031 match file_name {
1032 $($exact => Some($exact_language),)*
1033 _ => None,
1034 }
1035 }
1036
1037 fn extension_language(extension: &str) -> Option<&'static str> {
1038 match extension {
1039 $($extension => Some($extension_language),)*
1040 $($optional_extension => Some($optional_id),)*
1041 $($additional => Some($additional_language),)*
1042 _ => None,
1043 }
1044 }
1045
1046 #[must_use]
1048 pub fn canonical_language_id(value: &str) -> Option<&'static str> {
1049 let trimmed = value.trim();
1050 if let Some(canonical) = canonical_language_id_exact(trimmed) {
1051 return Some(canonical);
1052 }
1053 let normalized = trimmed.to_ascii_lowercase();
1054 canonical_language_id_exact(&normalized)
1055 }
1056
1057 fn canonical_language_id_exact(value: &str) -> Option<&'static str> {
1059 match value {
1060 $($id $(| $alias)* => Some($id),)*
1061 $($optional_id $(| $optional_alias)* => Some($optional_id),)*
1062 _ => None,
1063 }
1064 }
1065 };
1066}
1067
1068define_language_registry! {
1069 capabilities {
1070 "rust" => { aliases: ["rs"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Rust), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::Rust, positive: "fixture.rs", negative: "fixture.rs.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Rust) },
1071 "rust-build-script" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Rust), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::Rust, positive: "build.rs", negative: "build.rs.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Rust) },
1072 "python" => { aliases: ["py"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Python), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::Python, positive: "fixture.py", negative: "fixture.py.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Python) },
1073 "javascript" => { aliases: ["js"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::JavaScript), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::EcmaScript, positive: "fixture.js", negative: "fixture.js.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::JavaScript) },
1074 "typescript" => { aliases: ["ts"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::TypeScript), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::EcmaScript, positive: "fixture.ts", negative: "fixture.ts.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::TypeScript) },
1075 "tsx" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Tsx), structural_summary: None, support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::EcmaScript, positive: "fixture.tsx", negative: "fixture.tsx.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Tsx) },
1076 "java" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Java), structural_summary: None, support: SUPPORTED_NATIVE, positive: "Fixture.java", negative: "Fixture.java.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Java) },
1077 "kotlin" => { aliases: ["kt"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Kotlin), structural_summary: None, support: SUPPORTED_NATIVE, positive: "Fixture.kt", negative: "Fixture.kt.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Kotlin) },
1078 "csharp" => { aliases: ["c#", "cs"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::CSharp), structural_summary: None, support: SUPPORTED_NATIVE, positive: "Fixture.cs", negative: "Fixture.cs.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::CSharp) },
1079 "go" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Go), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.go", negative: "fixture.go.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Go) },
1080 "objective-c" => { aliases: ["objc"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::ObjectiveC), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.m", negative: "fixture.m.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::ObjectiveC) },
1081 "zig" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Zig), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.zig", negative: "fixture.zig.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Zig) },
1082 "c" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::C), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.c", negative: "fixture.c.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::C) },
1083 "cpp" => { aliases: ["c++"], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Cpp), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.cpp", negative: "fixture.cpp.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Cpp) },
1084 "h" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::C), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.h", negative: "fixture.h.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::C) },
1085 "hpp" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Cpp), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.hpp", negative: "fixture.hpp.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Cpp) },
1086 "cargo-manifest" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Manifest, symbol_parser: SymbolParserOwner::CargoManifest, structural_summary: Some(StructuralSummaryOwner::Toml), support: SUPPORTED_SEMANTIC, semantic_provider: SemanticProviderOwner::Cargo, positive: "Cargo.toml", negative: "Cargo.toml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1087 "cargo-lock" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Manifest, symbol_parser: SymbolParserOwner::CargoManifest, structural_summary: None, support: SUPPORTED_NATIVE, positive: "Cargo.lock", negative: "Cargo.lock.bak", provenance: CapabilityProvenance::ProjectAtlas },
1088 "vue" => { aliases: [], parser_support: Structural, symbol_parser: SymbolParserOwner::Vue, structural_summary: None, support: SUPPORTED_NATIVE, embedded_language: EmbeddedLanguageCapability { host_kind: EmbeddedHostKind::Component, semantic_provider: SemanticProviderOwner::EcmaScript }, positive: "Fixture.vue", negative: "Fixture.vue.bak", provenance: CapabilityProvenance::ProjectAtlas },
1089 "markdown" => { aliases: ["md"], classification: ContentClassification::Documentation, parser_support: Structural, symbol_parser: SymbolParserOwner::Markdown, structural_summary: Some(StructuralSummaryOwner::Markdown), support: SUPPORTED_NATIVE, positive: "fixture.md", negative: "fixture.md.bak", provenance: CapabilityProvenance::ProjectAtlas },
1090 "pdf" => { aliases: [], classification: ContentClassification::Documentation, parser_support: Structural, symbol_parser: SymbolParserOwner::Document, structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.pdf", negative: "fixture.pdf.bak", provenance: CapabilityProvenance::ProjectAtlas },
1091 "docx" => { aliases: [], classification: ContentClassification::Documentation, parser_support: Structural, symbol_parser: SymbolParserOwner::Document, structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.docx", negative: "fixture.docx.bak", provenance: CapabilityProvenance::ProjectAtlas },
1092 "json" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Json), support: SUPPORTED_STRUCTURAL, positive: "fixture.json", negative: "fixture.json.bak", provenance: CapabilityProvenance::ProjectAtlas },
1093 "yaml" => { aliases: ["yml"], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Yaml), support: SUPPORTED_STRUCTURAL, positive: "fixture.yml", negative: "fixture.yml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1094 "css" => { aliases: [], parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Css), support: SUPPORTED_STRUCTURAL, positive: "fixture.css", negative: "fixture.css.bak", provenance: CapabilityProvenance::ProjectAtlas },
1095 "html" => { aliases: [], parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Html), support: SUPPORTED_STRUCTURAL, embedded_language: EmbeddedLanguageCapability { host_kind: EmbeddedHostKind::HtmlLike, semantic_provider: SemanticProviderOwner::EcmaScript }, positive: "fixture.html", negative: "fixture.html.bak", provenance: CapabilityProvenance::ProjectAtlas },
1096 "toon" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Toon), support: SUPPORTED_STRUCTURAL, positive: "fixture.toon", negative: "fixture.toon.bak", provenance: CapabilityProvenance::ProjectAtlas },
1097 "dockerfile" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "Dockerfile", negative: "Dockerfile.bak", provenance: CapabilityProvenance::ProjectAtlas },
1098 "makefile" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "Makefile", negative: "Makefile.bak", provenance: CapabilityProvenance::ProjectAtlas },
1099 "text" => { aliases: ["txt"], classification: ContentClassification::OtherText, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::ConfigText), support: SUPPORTED_STRUCTURAL, positive: "fixture.txt", negative: "fixture.txt.bak", provenance: CapabilityProvenance::ProjectAtlas },
1100 "toml" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Toml), support: SUPPORTED_STRUCTURAL, positive: "fixture.toml", negative: "fixture.toml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1101 "xml" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Xml), support: SUPPORTED_STRUCTURAL, positive: "fixture.xml", negative: "fixture.xml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1102 "svelte" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, embedded_language: EmbeddedLanguageCapability { host_kind: EmbeddedHostKind::Template, semantic_provider: SemanticProviderOwner::EcmaScript }, positive: "Fixture.svelte", negative: "Fixture.svelte.bak", provenance: CapabilityProvenance::ProjectAtlas },
1103 "astro" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "Fixture.astro", negative: "Fixture.astro.bak", provenance: CapabilityProvenance::ProjectAtlas },
1104 "jsp" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.jsp", negative: "fixture.jsp.bak", provenance: CapabilityProvenance::ProjectAtlas },
1105 "jsp-tag" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.tag", negative: "fixture.tag.bak", provenance: CapabilityProvenance::ProjectAtlas },
1106 "gsp" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.gsp", negative: "fixture.gsp.bak", provenance: CapabilityProvenance::ProjectAtlas },
1107 "groovy" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.groovy", negative: "fixture.groovy.bak", provenance: CapabilityProvenance::ProjectAtlas },
1108 "protobuf" => { aliases: ["proto"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.proto", negative: "fixture.proto.bak", provenance: CapabilityProvenance::ProjectAtlas },
1109 "handlebars" => { aliases: ["hbs"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.hbs", negative: "fixture.hbs.bak", provenance: CapabilityProvenance::ProjectAtlas },
1110 "ejs" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.ejs", negative: "fixture.ejs.bak", provenance: CapabilityProvenance::ProjectAtlas },
1111 "pug" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.pug", negative: "fixture.pug.bak", provenance: CapabilityProvenance::ProjectAtlas },
1112 "freemarker" => { aliases: ["ftl"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.ftl", negative: "fixture.ftl.bak", provenance: CapabilityProvenance::ProjectAtlas },
1113 "mustache" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.mustache", negative: "fixture.mustache.bak", provenance: CapabilityProvenance::ProjectAtlas },
1114 "liquid" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.liquid", negative: "fixture.liquid.bak", provenance: CapabilityProvenance::ProjectAtlas },
1115 "erb" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.erb", negative: "fixture.erb.bak", provenance: CapabilityProvenance::ProjectAtlas },
1116 "sql" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.sql", negative: "fixture.sql.bak", provenance: CapabilityProvenance::ProjectAtlas },
1117 "graphql" => { aliases: ["gql"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.gql", negative: "fixture.gql.bak", provenance: CapabilityProvenance::ProjectAtlas },
1118 "config" => { aliases: [], classification: ContentClassification::ConfigurationData, parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::ConfigText), support: SUPPORTED_STRUCTURAL, positive: "fixture.ini", negative: "fixture.ini.bak", provenance: CapabilityProvenance::ProjectAtlas },
1119 "ruby" => { aliases: ["rb"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.rb", negative: "fixture.rb.bak", provenance: CapabilityProvenance::ProjectAtlas },
1120 "php" => { aliases: [], parser_support: Native, symbol_parser: SymbolParserOwner::TreeSitter(TreeSitterGrammar::Php), structural_summary: None, support: SUPPORTED_NATIVE, positive: "fixture.php", negative: "fixture.php.bak", provenance: CapabilityProvenance::TreeSitter(TreeSitterGrammar::Php) },
1121 "swift" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.swift", negative: "fixture.swift.bak", provenance: CapabilityProvenance::ProjectAtlas },
1122 "scala" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.scala", negative: "fixture.scala.bak", provenance: CapabilityProvenance::ProjectAtlas },
1123 "shell" => { aliases: ["sh"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.sh", negative: "fixture.sh.bak", provenance: CapabilityProvenance::ProjectAtlas },
1124 "powershell" => { aliases: ["pwsh"], parser_support: Fallback, symbol_parser: SymbolParserOwner::PowerShell, structural_summary: Some(StructuralSummaryOwner::PowerShell), support: SUPPORTED_NATIVE, positive: "fixture.ps1", negative: "fixture.ps1.bak", provenance: CapabilityProvenance::ProjectAtlas },
1125 "batch" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.bat", negative: "fixture.bat.bak", provenance: CapabilityProvenance::ProjectAtlas },
1126 "r" => { aliases: ["rscript"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.r", negative: "fixture.r.bak", provenance: CapabilityProvenance::ProjectAtlas },
1127 "perl" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.pl", negative: "fixture.pl.bak", provenance: CapabilityProvenance::ProjectAtlas },
1128 "lua" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.lua", negative: "fixture.lua.bak", provenance: CapabilityProvenance::ProjectAtlas },
1129 "dart" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.dart", negative: "fixture.dart.bak", provenance: CapabilityProvenance::ProjectAtlas },
1130 "haskell" => { aliases: ["hs"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.hs", negative: "fixture.hs.bak", provenance: CapabilityProvenance::ProjectAtlas },
1131 "ocaml" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.ml", negative: "fixture.ml.bak", provenance: CapabilityProvenance::ProjectAtlas },
1132 "fsharp" => { aliases: ["f#"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.fs", negative: "fixture.fs.bak", provenance: CapabilityProvenance::ProjectAtlas },
1133 "clojure" => { aliases: ["clj"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.clj", negative: "fixture.clj.bak", provenance: CapabilityProvenance::ProjectAtlas },
1134 "vim" => { aliases: ["vimscript"], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.vim", negative: "fixture.vim.bak", provenance: CapabilityProvenance::ProjectAtlas }
1135 }
1136 optional_capabilities {
1137 "abl" => { aliases: [], extension: ".p" },
1138 "actionscript" => { aliases: [], extension: ".as" },
1139 "ada" => { aliases: [], extension: ".ada" },
1140 "agda" => { aliases: [], extension: ".agda" },
1141 "al" => { aliases: [], extension: ".al" },
1142 "arduino" => { aliases: [], extension: ".ino" },
1143 "asciidoc" => { aliases: [], classification: ContentClassification::Documentation, extension: ".adoc" },
1144 "asm" => { aliases: [], extension: ".s" },
1145 "awk" => { aliases: [], extension: ".awk" },
1146 "beancount" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".beancount" },
1147 "bibtex" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".bib" },
1148 "bicep" => { aliases: [], extension: ".bicep" },
1149 "bitbake" => { aliases: [], extension: ".bb" },
1150 "blade" => { aliases: [], extension: ".blade" },
1151 "brightscript" => { aliases: [], extension: ".brs" },
1152 "bsl" => { aliases: [], extension: ".bsl" },
1153 "c3" => { aliases: [], extension: ".c3" },
1154 "caddy" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".caddyfile" },
1155 "cairo" => { aliases: [], extension: ".cairo" },
1156 "capnp" => { aliases: [], extension: ".capnp" },
1157 "cedar" => { aliases: [], extension: ".cedar" },
1158 "cedarschema" => { aliases: [], extension: ".cedarschema" },
1159 "cel" => { aliases: [], extension: ".cel" },
1160 "cfml" => { aliases: [], extension: ".cfc" },
1161 "chatito" => { aliases: [], extension: ".chatito" },
1162 "chuck" => { aliases: [], extension: ".ck" },
1163 "circom" => { aliases: [], extension: ".circom" },
1164 "clarity" => { aliases: [], extension: ".clar" },
1165 "cmake" => { aliases: [], extension: ".cmake" },
1166 "cobol" => { aliases: [], extension: ".cobol" },
1167 "commonlisp" => { aliases: [], extension: ".lisp" },
1168 "cooklang" => { aliases: [], extension: ".cook" },
1169 "corn" => { aliases: [], extension: ".corn" },
1170 "cpon" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".cpon" },
1171 "crystal" => { aliases: [], extension: ".cr" },
1172 "cst" => { aliases: [], extension: ".cst" },
1173 "csv" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".csv" },
1174 "cuda" => { aliases: [], extension: ".cu" },
1175 "cue" => { aliases: [], extension: ".cue" },
1176 "cylc" => { aliases: [], extension: ".cylc" },
1177 "d" => { aliases: [], extension: ".d" },
1178 "desktop" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".desktop" },
1179 "devicetree" => { aliases: [], extension: ".dts" },
1180 "dhall" => { aliases: [], extension: ".dhall" },
1181 "diff" => { aliases: [], classification: ContentClassification::OtherText, extension: ".diff" },
1182 "djot" => { aliases: [], classification: ContentClassification::Documentation, extension: ".dj" },
1183 "dot" => { aliases: [], extension: ".dot" },
1184 "dtd" => { aliases: [], extension: ".dtd" },
1185 "ebnf" => { aliases: [], extension: ".ebnf" },
1186 "eds" => { aliases: [], extension: ".eds" },
1187 "eex" => { aliases: [], extension: ".eex" },
1188 "elisp" => { aliases: [], extension: ".el" },
1189 "elixir" => { aliases: [], extension: ".ex" },
1190 "elm" => { aliases: [], extension: ".elm" },
1191 "elsa" => { aliases: [], extension: ".lc" },
1192 "elvish" => { aliases: [], extension: ".elv" },
1193 "enforce" => { aliases: [], extension: ".enforce" },
1194 "erlang" => { aliases: [], extension: ".erl" },
1195 "facility" => { aliases: [], extension: ".fsd" },
1196 "faust" => { aliases: [], extension: ".dsp" },
1197 "fennel" => { aliases: [], extension: ".fnl" },
1198 "fidl" => { aliases: [], extension: ".fidl" },
1199 "firrtl" => { aliases: [], extension: ".fir" },
1200 "fish" => { aliases: [], extension: ".fish" },
1201 "forth" => { aliases: [], extension: ".fth" },
1202 "fortran" => { aliases: [], extension: ".f90" },
1203 "fsharp_signature" => { aliases: [], extension: ".fsi" },
1204 "func" => { aliases: [], extension: ".fc" },
1205 "gap" => { aliases: [], extension: ".g" },
1206 "gdscript" => { aliases: [], extension: ".gd" },
1207 "gdshader" => { aliases: [], extension: ".gdshader" },
1208 "gherkin" => { aliases: [], extension: ".feature" },
1209 "gitattributes" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".gitattributes" },
1210 "gleam" => { aliases: [], extension: ".gleam" },
1211 "glsl" => { aliases: [], extension: ".glsl" },
1212 "gn" => { aliases: [], extension: ".gn" },
1213 "gnuplot" => { aliases: [], extension: ".gp" },
1214 "godot_resource" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".tres" },
1215 "gomod" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".mod" },
1216 "gotmpl" => { aliases: [], extension: ".gotmpl" },
1217 "gren" => { aliases: [], extension: ".gren" },
1218 "hack" => { aliases: [], extension: ".hack" },
1219 "hare" => { aliases: [], extension: ".hare" },
1220 "haxe" => { aliases: [], extension: ".hx" },
1221 "hcl" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".hcl" },
1222 "heex" => { aliases: [], extension: ".heex" },
1223 "hjson" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".hjson" },
1224 "hlsl" => { aliases: [], extension: ".hlsl" },
1225 "hocon" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".hocon" },
1226 "hoon" => { aliases: [], extension: ".hoon" },
1227 "http" => { aliases: [], extension: ".http" },
1228 "hurl" => { aliases: [], extension: ".hurl" },
1229 "idris" => { aliases: [], extension: ".idr" },
1230 "ispc" => { aliases: [], extension: ".ispc" },
1231 "jai" => { aliases: [], extension: ".jai" },
1232 "janet" => { aliases: [], extension: ".janet" },
1233 "jinja2" => { aliases: [], extension: ".j2" },
1234 "jq" => { aliases: [], extension: ".jq" },
1235 "json5" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".json5" },
1236 "jsonnet" => { aliases: [], extension: ".jsonnet" },
1237 "julia" => { aliases: [], extension: ".jl" },
1238 "just" => { aliases: [], extension: ".just" },
1239 "kcl" => { aliases: [], extension: ".k" },
1240 "kdl" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".kdl" },
1241 "latex" => { aliases: [], classification: ContentClassification::Documentation, extension: ".tex" },
1242 "lean" => { aliases: [], extension: ".lean" },
1243 "ledger" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".ldg" },
1244 "linkerscript" => { aliases: [], extension: ".lds" },
1245 "llvm" => { aliases: [], extension: ".ll" },
1246 "luau" => { aliases: [], extension: ".luau" },
1247 "magik" => { aliases: [], extension: ".magik" },
1248 "make" => { aliases: [], extension: ".mk" },
1249 "matlab" => { aliases: [], extension: ".matlab" },
1250 "mermaid" => { aliases: [], extension: ".mmd" },
1251 "meson" => { aliases: [], extension: ".meson" },
1252 "mlir" => { aliases: [], extension: ".mlir" },
1253 "mojo" => { aliases: [], extension: ".mojo" },
1254 "move" => { aliases: [], extension: ".move" },
1255 "nasm" => { aliases: [], extension: ".nasm" },
1256 "netlinx" => { aliases: [], extension: ".axs" },
1257 "nginx" => { aliases: [], extension: ".nginx" },
1258 "nickel" => { aliases: [], extension: ".ncl" },
1259 "nim" => { aliases: [], extension: ".nim" },
1260 "ninja" => { aliases: [], extension: ".ninja" },
1261 "nix" => { aliases: [], extension: ".nix" },
1262 "norg" => { aliases: [], classification: ContentClassification::Documentation, extension: ".norg" },
1263 "nqc" => { aliases: [], extension: ".nqc" },
1264 "nushell" => { aliases: [], extension: ".nu" },
1265 "ocamllex" => { aliases: [], extension: ".mll" },
1266 "odin" => { aliases: [], extension: ".odin" },
1267 "openscad" => { aliases: [], extension: ".scad" },
1268 "org" => { aliases: [], classification: ContentClassification::Documentation, extension: ".org" },
1269 "pascal" => { aliases: [], extension: ".pas" },
1270 "pem" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".pem" },
1271 "pgn" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".pgn" },
1272 "pkl" => { aliases: [], extension: ".pkl" },
1273 "po" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".po" },
1274 "poe_filter" => { aliases: [], extension: ".filter" },
1275 "pony" => { aliases: [], extension: ".pony" },
1276 "postscript" => { aliases: [], extension: ".ps" },
1277 "prisma" => { aliases: [], extension: ".prisma" },
1278 "prolog" => { aliases: [], extension: ".pro" },
1279 "promql" => { aliases: [], extension: ".promql" },
1280 "prql" => { aliases: [], extension: ".prql" },
1281 "psv" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".psv" },
1282 "puppet" => { aliases: [], extension: ".pp" },
1283 "purescript" => { aliases: [], extension: ".purs" },
1284 "ql" => { aliases: [], extension: ".ql" },
1285 "qmljs" => { aliases: [], extension: ".qml" },
1286 "racket" => { aliases: [], extension: ".rkt" },
1287 "rasi" => { aliases: [], extension: ".rasi" },
1288 "razor" => { aliases: [], extension: ".razor" },
1289 "rbs" => { aliases: [], extension: ".rbs" },
1290 "re2c" => { aliases: [], extension: ".re" },
1291 "rego" => { aliases: [], extension: ".rego" },
1292 "rescript" => { aliases: [], extension: ".res" },
1293 "robot" => { aliases: [], extension: ".robot" },
1294 "roc" => { aliases: [], extension: ".roc" },
1295 "ron" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".ron" },
1296 "rst" => { aliases: [], classification: ContentClassification::Documentation, extension: ".rst" },
1297 "rtf" => { aliases: [], classification: ContentClassification::Documentation, extension: ".rtf" },
1298 "scheme" => { aliases: [], extension: ".scm" },
1299 "slang" => { aliases: [], extension: ".slang" },
1300 "smali" => { aliases: [], extension: ".smali" },
1301 "smalltalk" => { aliases: [], extension: ".st" },
1302 "smithy" => { aliases: [], extension: ".smithy" },
1303 "sml" => { aliases: [], extension: ".sml" },
1304 "snakemake" => { aliases: [], extension: ".smk" },
1305 "solidity" => { aliases: [], extension: ".sol" },
1306 "souffle" => { aliases: [], extension: ".dl" },
1307 "sourcepawn" => { aliases: [], extension: ".sp" },
1308 "sql_bigquery" => { aliases: [], extension: ".bq" },
1309 "squirrel" => { aliases: [], extension: ".squirrel" },
1310 "stan" => { aliases: [], extension: ".stan" },
1311 "starlark" => { aliases: [], extension: ".star" },
1312 "superhtml" => { aliases: [], extension: ".shtml" },
1313 "sway" => { aliases: [], extension: ".sw" },
1314 "systemverilog" => { aliases: [], extension: ".sv" },
1315 "tablegen" => { aliases: [], extension: ".td" },
1316 "tact" => { aliases: [], extension: ".tact" },
1317 "tcl" => { aliases: [], extension: ".tcl" },
1318 "teal" => { aliases: [], extension: ".tl" },
1319 "templ" => { aliases: [], extension: ".templ" },
1320 "tera" => { aliases: [], extension: ".tera" },
1321 "terraform" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".tf" },
1322 "textproto" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".textproto" },
1323 "thrift" => { aliases: [], extension: ".thrift" },
1324 "tlaplus" => { aliases: [], extension: ".tla" },
1325 "todotxt" => { aliases: [], classification: ContentClassification::OtherText, extension: ".todotxt" },
1326 "tsv" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".tsv" },
1327 "turtle" => { aliases: [], classification: ContentClassification::ConfigurationData, extension: ".ttl" },
1328 "twig" => { aliases: [], extension: ".twig" },
1329 "typespec" => { aliases: [], extension: ".tsp" },
1330 "typoscript" => { aliases: [], extension: ".typoscript" },
1331 "typst" => { aliases: [], classification: ContentClassification::Documentation, extension: ".typst" },
1332 "uxntal" => { aliases: [], extension: ".tal" },
1333 "v" => { aliases: [], extension: ".v" },
1334 "vb" => { aliases: [], extension: ".vb" },
1335 "verilog" => { aliases: [], extension: ".verilog" },
1336 "vhdl" => { aliases: [], extension: ".vhdl" },
1337 "vhs" => { aliases: [], extension: ".tape" },
1338 "vrl" => { aliases: [], extension: ".vrl" },
1339 "wast" => { aliases: [], extension: ".wast" },
1340 "wat" => { aliases: [], extension: ".wat" },
1341 "wgsl" => { aliases: [], extension: ".wgsl" },
1342 "wit" => { aliases: [], extension: ".wit" },
1343 "yuck" => { aliases: [], extension: ".yuck" },
1344 "ziggy" => { aliases: [], extension: ".ziggy" },
1345 }
1346 exact_filenames {
1347 "Cargo.toml" => "cargo-manifest",
1348 "Cargo.lock" => "cargo-lock",
1349 "build.rs" => "rust-build-script",
1350 "Dockerfile" => "dockerfile",
1351 "Makefile" => "makefile"
1352 }
1353 compound_extensions { ".d.ts" => "typescript" }
1354 broad_extensions {
1355 ".py" => "python", ".pyw" => "python", ".js" => "javascript", ".jsx" => "javascript", ".ts" => "typescript", ".tsx" => "tsx", ".mjs" => "javascript", ".cjs" => "javascript", ".d.ts" => "typescript", ".java" => "java", ".c" => "c", ".cpp" => "cpp", ".h" => "h", ".hpp" => "hpp", ".cxx" => "cpp", ".cc" => "cpp", ".hxx" => "hpp", ".hh" => "hpp", ".cs" => "csharp", ".go" => "go", ".m" => "objective-c", ".mm" => "objective-c", ".rb" => "ruby", ".php" => "php", ".swift" => "swift", ".kt" => "kotlin", ".kts" => "kotlin", ".rs" => "rust", ".scala" => "scala", ".sh" => "shell", ".bash" => "shell", ".zsh" => "shell", ".ps1" => "powershell", ".psm1" => "powershell", ".psd1" => "powershell", ".bat" => "batch", ".cmd" => "batch", ".r" => "r", ".R" => "r", ".pl" => "perl", ".pm" => "perl", ".lua" => "lua", ".dart" => "dart", ".hs" => "haskell", ".ml" => "ocaml", ".mli" => "ocaml", ".fs" => "fsharp", ".fsx" => "fsharp", ".clj" => "clojure", ".cljs" => "clojure", ".vim" => "vim", ".zig" => "zig", ".zon" => "zig", ".html" => "html", ".htm" => "html", ".css" => "css", ".scss" => "css", ".sass" => "css", ".less" => "css", ".stylus" => "css", ".styl" => "css", ".md" => "markdown", ".mdx" => "markdown", ".json" => "json", ".jsonc" => "json", ".xml" => "xml", ".yml" => "yaml", ".yaml" => "yaml", ".toml" => "toml", ".toon" => "toon", ".txt" => "text", ".ini" => "config", ".cfg" => "config", ".conf" => "config", ".vue" => "vue", ".svelte" => "svelte", ".astro" => "astro", ".jsp" => "jsp", ".jspx" => "jsp", ".jspf" => "jsp", ".tag" => "jsp-tag", ".tagx" => "jsp-tag", ".gsp" => "gsp", ".properties" => "config", ".gradle" => "groovy", ".groovy" => "groovy", ".proto" => "protobuf", ".hbs" => "handlebars", ".handlebars" => "handlebars", ".ejs" => "ejs", ".pug" => "pug", ".ftl" => "freemarker", ".mustache" => "mustache", ".liquid" => "liquid", ".erb" => "erb", ".sql" => "sql", ".ddl" => "sql", ".dml" => "sql", ".mysql" => "sql", ".postgresql" => "sql", ".psql" => "sql", ".sqlite" => "sql", ".mssql" => "sql", ".oracle" => "sql", ".ora" => "sql", ".db2" => "sql", ".proc" => "sql", ".procedure" => "sql", ".func" => "sql", ".function" => "sql", ".view" => "sql", ".trigger" => "sql", ".index" => "sql", ".migration" => "sql", ".seed" => "sql", ".fixture" => "sql", ".schema" => "sql", ".cql" => "sql", ".cypher" => "sql", ".sparql" => "sql", ".gql" => "graphql", ".liquibase" => "sql", ".flyway" => "sql"
1356 }
1357 additional_extensions {
1358 ".env" => "config", ".gitignore" => "config", ".dockerignore" => "config", ".editorconfig" => "config", ".pdf" => "pdf", ".docx" => "docx"
1359 }
1360 content_interpreters {
1361 "python" => { language: "python", version_suffix: true },
1362 "pythonw" => { language: "python", version_suffix: true },
1363 "node" => { language: "javascript", version_suffix: false },
1364 "deno" => { language: "javascript", version_suffix: false },
1365 "powershell" => { language: "powershell", version_suffix: false },
1366 "pwsh" => { language: "powershell", version_suffix: false },
1367 "ruby" => { language: "ruby", version_suffix: true },
1368 "perl" => { language: "perl", version_suffix: true },
1369 "lua" => { language: "lua", version_suffix: true },
1370 "rscript" => { language: "r", version_suffix: false },
1371 "sh" => { language: "shell", version_suffix: false },
1372 "bash" => { language: "shell", version_suffix: false },
1373 "dash" => { language: "shell", version_suffix: false },
1374 "ash" => { language: "shell", version_suffix: false },
1375 "zsh" => { language: "shell", version_suffix: false },
1376 "ksh" => { language: "shell", version_suffix: false },
1377 "mksh" => { language: "shell", version_suffix: false },
1378 "fish" => { language: "shell", version_suffix: false }
1379 }
1380}
1381
1382#[must_use]
1384pub fn language_spec(language: &str) -> Option<&'static LanguageSpec> {
1385 let canonical = canonical_language_id(language)?;
1386 LANGUAGE_SPECS
1387 .iter()
1388 .find(|spec| spec.language == canonical)
1389}
1390
1391#[must_use]
1393pub fn language_capability(language: &str) -> Option<&'static LanguageCapability> {
1394 static BY_ID: OnceLock<BTreeMap<&'static str, &'static LanguageCapability>> = OnceLock::new();
1395 let canonical = canonical_language_id(language)?;
1396 BY_ID
1397 .get_or_init(|| {
1398 LANGUAGE_CAPABILITIES
1399 .iter()
1400 .map(|capability| (capability.id, capability))
1401 .collect()
1402 })
1403 .get(canonical)
1404 .copied()
1405}
1406
1407#[must_use]
1412pub fn content_classification(language: Option<&str>, valid_utf8: bool) -> ContentClassification {
1413 if let Some(classification) = language
1414 .and_then(language_capability)
1415 .map(|capability| capability.classification)
1416 {
1417 return classification;
1418 }
1419 if valid_utf8 {
1420 ContentClassification::OtherText
1421 } else {
1422 ContentClassification::Opaque
1423 }
1424}
1425
1426#[must_use]
1428pub const fn language_documentation_rows() -> &'static [LanguageCapability] {
1429 LANGUAGE_CAPABILITIES
1430}
1431
1432#[must_use]
1434pub fn tree_sitter_grammar(language: &str) -> Option<TreeSitterGrammar> {
1435 match language_capability(language)?.symbol_parser {
1436 SymbolParserOwner::TreeSitter(grammar) => Some(grammar),
1437 SymbolParserOwner::CargoManifest
1438 | SymbolParserOwner::Vue
1439 | SymbolParserOwner::PowerShell
1440 | SymbolParserOwner::Markdown
1441 | SymbolParserOwner::Document
1442 | SymbolParserOwner::Fallback
1443 | SymbolParserOwner::Unavailable => None,
1444 }
1445}
1446
1447#[must_use]
1449pub fn builtin_tree_sitter_language_ids() -> &'static [&'static str] {
1450 static IDS: OnceLock<Box<[&'static str]>> = OnceLock::new();
1451 IDS.get_or_init(|| {
1452 LANGUAGE_CAPABILITIES
1453 .iter()
1454 .filter_map(|capability| {
1455 matches!(capability.symbol_parser, SymbolParserOwner::TreeSitter(_))
1456 .then_some(capability.id)
1457 })
1458 .collect::<Vec<_>>()
1459 .into_boxed_slice()
1460 })
1461}
1462
1463#[must_use]
1465pub fn compound_language_extension(path: &str) -> Option<&'static str> {
1466 let file_name = path.rsplit(['/', '\\']).next().unwrap_or(path);
1467 let lower = file_name.to_ascii_lowercase();
1468 COMPOUND_EXTENSION_RULES
1469 .iter()
1470 .find(|rule| lower.ends_with(rule.value))
1471 .map(|rule| rule.value)
1472}
1473
1474#[must_use]
1476pub fn normalized_language_extension(path: &Path) -> Option<String> {
1477 let file_name = path.file_name()?.to_string_lossy();
1478 if let Some(compound) = compound_language_extension(&file_name) {
1479 return Some(compound.to_string());
1480 }
1481 path.extension()
1482 .map(|extension| format!(".{}", extension.to_string_lossy().to_lowercase()))
1483}
1484
1485pub fn detect_language_request(
1491 request: LanguageDetectionRequest<'_>,
1492) -> Result<Option<LanguageDetection>, LanguageDetectionError> {
1493 if let Some(explicit) = request.explicit_override {
1494 let Some(language) = canonical_language_id(explicit) else {
1495 return Err(LanguageDetectionError {
1496 requested: explicit.to_string(),
1497 });
1498 };
1499 return Ok(Some(LanguageDetection {
1500 language,
1501 reason: LanguageDetectionReason::ExplicitOverride,
1502 }));
1503 }
1504
1505 let file_name = request
1506 .path
1507 .rsplit(['/', '\\'])
1508 .next()
1509 .unwrap_or(request.path);
1510 if let Some(language) = exact_filename_language(file_name) {
1511 return Ok(Some(LanguageDetection {
1512 language,
1513 reason: LanguageDetectionReason::ExactFilename,
1514 }));
1515 }
1516
1517 let lower_file_name = file_name.to_ascii_lowercase();
1518 if let Some(rule) = COMPOUND_EXTENSION_RULES
1519 .iter()
1520 .find(|rule| lower_file_name.ends_with(rule.value))
1521 {
1522 return Ok(Some(LanguageDetection {
1523 language: rule.language,
1524 reason: LanguageDetectionReason::CompoundExtension,
1525 }));
1526 }
1527
1528 let normalized_extension = request.extension.map(str::to_ascii_lowercase).or_else(|| {
1529 file_name
1530 .rsplit_once('.')
1531 .map(|(_, extension)| format!(".{}", extension.to_ascii_lowercase()))
1532 });
1533 if let Some(extension) = normalized_extension
1534 && let Some(language) = extension_language(&extension)
1535 {
1536 return Ok(Some(LanguageDetection {
1537 language,
1538 reason: LanguageDetectionReason::Extension,
1539 }));
1540 }
1541
1542 Ok(
1543 content_dialect_language(request.content_prefix).map(|language| LanguageDetection {
1544 language,
1545 reason: LanguageDetectionReason::ContentDialect,
1546 }),
1547 )
1548}
1549
1550#[must_use]
1552pub fn detect_language(extension: Option<&str>) -> Option<String> {
1553 detect_language_request(LanguageDetectionRequest::new("", extension))
1554 .ok()
1555 .flatten()
1556 .map(|detected| detected.language.to_string())
1557}
1558
1559#[must_use]
1561pub fn detect_language_for_path(path: &str, extension: Option<&str>) -> Option<String> {
1562 detect_language_request(LanguageDetectionRequest::new(path, extension))
1563 .ok()
1564 .flatten()
1565 .map(|detected| detected.language.to_string())
1566}
1567
1568fn content_dialect_language(content_prefix: Option<&[u8]>) -> Option<&'static str> {
1570 let prefix = content_prefix?;
1571 let bounded = &prefix[..prefix.len().min(LANGUAGE_CONTENT_DETECTION_MAX_BYTES)];
1572 let line_end = bounded
1573 .iter()
1574 .position(|byte| *byte == b'\n')
1575 .unwrap_or(bounded.len());
1576 let first_line = std::str::from_utf8(&bounded[..line_end])
1577 .ok()?
1578 .trim_end_matches('\r');
1579 let shebang = first_line.strip_prefix("#!")?.trim();
1580 let interpreter = shebang_interpreter(shebang)?;
1581 CONTENT_DIALECT_RULES
1582 .iter()
1583 .find(|rule| content_interpreter_matches(&interpreter, rule))
1584 .map(|rule| rule.language)
1585}
1586
1587fn shebang_interpreter(shebang: &str) -> Option<String> {
1589 let mut tokens = shebang.split_ascii_whitespace();
1590 let first = tokens.next()?;
1591 let first_basename = command_basename(first);
1592 let selected = if first_basename.eq_ignore_ascii_case("env") {
1593 tokens.find(|token| !token.starts_with('-') && !token.contains('='))?
1594 } else if first_basename.eq_ignore_ascii_case("busybox") {
1595 tokens.next()?
1596 } else {
1597 first
1598 };
1599 let normalized = command_basename(selected).to_ascii_lowercase();
1600 Some(
1601 normalized
1602 .strip_suffix(".exe")
1603 .unwrap_or(&normalized)
1604 .to_string(),
1605 )
1606}
1607
1608fn command_basename(command: &str) -> &str {
1610 command.rsplit(['/', '\\']).next().unwrap_or(command)
1611}
1612
1613fn content_interpreter_matches(interpreter: &str, rule: &LanguageContentRule) -> bool {
1615 if interpreter == rule.interpreter {
1616 return true;
1617 }
1618 rule.allow_version_suffix
1619 && interpreter
1620 .strip_prefix(rule.interpreter)
1621 .is_some_and(|suffix| {
1622 !suffix.is_empty()
1623 && suffix.split('.').all(|segment| {
1624 !segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_digit())
1625 })
1626 })
1627}
1628
1629#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
1631pub struct CapabilityLevelCounts {
1632 pub unavailable: usize,
1634 pub fallback: usize,
1636 pub supported: usize,
1638}
1639
1640#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
1642pub struct LanguageCapabilityCounts {
1643 pub accepted: usize,
1645 pub built_in: usize,
1647 pub optional_candidates: usize,
1649 pub detected: CapabilityLevelCounts,
1651 pub parsed: CapabilityLevelCounts,
1653 pub symbols: CapabilityLevelCounts,
1655 pub semantic: CapabilityLevelCounts,
1657 pub benchmarked: CapabilityLevelCounts,
1659}
1660
1661#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1663pub struct LanguageCapabilityReportRow {
1664 pub id: &'static str,
1666 pub aliases: &'static [&'static str],
1668 pub classification: ContentClassification,
1670 pub exact_filenames: Vec<&'static str>,
1672 pub compound_extensions: Vec<&'static str>,
1674 pub extensions: Vec<&'static str>,
1676 pub content_interpreters: Vec<&'static str>,
1678 pub parser_support: LanguageParserSupport,
1680 pub symbol_parser: SymbolParserOwner,
1682 pub semantic_provider: SemanticProviderOwner,
1684 pub embedded_language: Option<EmbeddedLanguageCapability>,
1686 pub structural_summary: Option<StructuralSummaryOwner>,
1688 pub optional_pack: Option<&'static str>,
1690 pub support: LanguageCapabilitySupport,
1692 pub accepted_minimum: LanguageCapabilitySupport,
1694 pub fixtures: LanguageCapabilityFixtures,
1696 pub provenance_source: &'static str,
1698 pub provenance_version: &'static str,
1700 pub provenance_license: &'static str,
1702 pub required_platforms: RequiredPlatformSet,
1704}
1705
1706#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
1708pub struct LanguageCatalogReport {
1709 pub name: &'static str,
1711 pub version: &'static str,
1713 pub revision: &'static str,
1715 pub metadata_license: &'static str,
1717 pub minimum_additional_grammars: usize,
1719}
1720
1721#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1723pub struct LanguageRegistryReport {
1724 pub registry_version: u32,
1726 pub accepted_set_version: u32,
1728 pub detection_policy_version: u32,
1730 pub registry_digest: String,
1732 pub accepted_set_digest: String,
1734 pub semantic_provider_digest: String,
1736 pub counts: LanguageCapabilityCounts,
1738 pub optional_catalog: LanguageCatalogReport,
1740}
1741
1742#[must_use]
1744pub fn language_registry_report() -> LanguageRegistryReport {
1745 LanguageRegistryReport {
1746 registry_version: LANGUAGE_CAPABILITY_REGISTRY_VERSION,
1747 accepted_set_version: ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION,
1748 detection_policy_version: LANGUAGE_DETECTION_POLICY_VERSION,
1749 registry_digest: language_registry_digest(),
1750 accepted_set_digest: accepted_language_capability_digest(),
1751 semantic_provider_digest: semantic_provider_digest(),
1752 counts: language_capability_counts(),
1753 optional_catalog: LanguageCatalogReport {
1754 name: OPTIONAL_GRAMMAR_CATALOG,
1755 version: OPTIONAL_GRAMMAR_CATALOG_VERSION,
1756 revision: OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION,
1757 metadata_license: "MIT",
1758 minimum_additional_grammars: OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS,
1759 },
1760 }
1761}
1762
1763#[must_use]
1765pub fn language_capability_report_rows() -> Vec<LanguageCapabilityReportRow> {
1766 LANGUAGE_CAPABILITIES
1767 .iter()
1768 .map(|capability| LanguageCapabilityReportRow {
1769 id: capability.id,
1770 aliases: capability.aliases,
1771 classification: capability.classification,
1772 exact_filenames: rules_for_language(EXACT_FILENAME_RULES, capability.id),
1773 compound_extensions: rules_for_language(COMPOUND_EXTENSION_RULES, capability.id),
1774 extensions: rules_for_language(EXTENSION_RULES, capability.id),
1775 content_interpreters: CONTENT_DIALECT_RULES
1776 .iter()
1777 .filter_map(|rule| (rule.language == capability.id).then_some(rule.interpreter))
1778 .collect(),
1779 parser_support: capability.parser_support,
1780 symbol_parser: capability.symbol_parser,
1781 semantic_provider: capability.semantic_provider,
1782 embedded_language: capability.embedded_language,
1783 structural_summary: capability.structural_summary,
1784 optional_pack: capability.optional_pack,
1785 support: capability.support,
1786 accepted_minimum: capability.accepted_minimum,
1787 fixtures: capability.fixtures,
1788 provenance_source: capability.provenance.source(),
1789 provenance_version: capability.provenance.version(),
1790 provenance_license: capability.provenance.license(),
1791 required_platforms: capability.required_platforms,
1792 })
1793 .collect()
1794}
1795
1796pub fn render_language_support_markdown() -> Result<String, fmt::Error> {
1802 let report = language_registry_report();
1803 let capability_rows = language_capability_report_rows();
1804 let mut output = String::new();
1805 output.push_str("# ProjectAtlas Language Support\n\n");
1806 output.push_str(
1807 "This document is generated from the versioned Rust language capability registry. \
1808Do not edit the capability table or totals by hand. Canonical rows count once; aliases and \
1809extensions never increase a capability total.\n\n",
1810 );
1811 write!(
1812 &mut output,
1813 "Registry version: `{}`. Accepted capability-set version: `{}`. Detection policy \
1814version: `{}`. \
1815Registry digest: `{}`. Accepted-set digest: `{}`. Semantic-provider digest: `{}`.\n\n",
1816 report.registry_version,
1817 report.accepted_set_version,
1818 report.detection_policy_version,
1819 report.registry_digest,
1820 report.accepted_set_digest,
1821 report.semantic_provider_digest
1822 )?;
1823 write!(
1824 &mut output,
1825 "Optional catalog input: `{}@{}` revision `{}` under `{}` metadata license. \
1826This catalog identity is not a grammar-license or runtime-support claim.\n\n",
1827 report.optional_catalog.name,
1828 report.optional_catalog.version,
1829 report.optional_catalog.revision,
1830 report.optional_catalog.metadata_license
1831 )?;
1832 write!(
1833 &mut output,
1834 "The registry contains **{}** canonical rows: **{}** default-core rows and **{}** \
1835optional-pack candidates. Detection is supported for {} rows. Parsing is supported for {}, \
1836fallback for {}, and unavailable for {}. Symbols are supported for {}, fallback for {}, and \
1837unavailable for {}. Semantic resolution and benchmark coverage are reported independently.\n\n",
1838 report.counts.accepted,
1839 report.counts.built_in,
1840 report.counts.optional_candidates,
1841 report.counts.detected.supported,
1842 report.counts.parsed.supported,
1843 report.counts.parsed.fallback,
1844 report.counts.parsed.unavailable,
1845 report.counts.symbols.supported,
1846 report.counts.symbols.fallback,
1847 report.counts.symbols.unavailable
1848 )?;
1849 output.push_str(
1850 "Rows marked `broad-parser` are detected and, when explicitly admitted to the scan \
1851policy, remain usable through the conservative default-core fallback while the optional pack is \
1852absent. Catalog recognition alone does not add these extensions to the default scan surface. The \
1853pinned catalog is provenance for detection metadata only. A row becomes grammar-backed parsed \
1854support only after its exact \
1855grammar binary, subtree license, ABI/export, fixtures, and every accepted optional-pack target \
1856pass the separate acceptance gates. The v0.4 optional-pack targets are Linux x86-64 and Windows \
1857x86-64; macOS keeps the full built-in surface and reports `unsupported_containment` for optional-\
1858pack activation. Built-in owners always retain precedence.\n\n",
1859 );
1860 output.push_str(
1861 "Broad candidate rows are admitted only when the pinned catalog supplies a stable \
1862canonical grammar identity and at least one ordinary extension that does not conflict with an \
1863already accepted detector owner. Extensionless, ambiguous, duplicate, pseudo, or conflicting \
1864catalog entries remain unadvertised until a separate deterministic rule and evidence exist.\n\n",
1865 );
1866 output.push_str(
1867 "| Language | Classification | Aliases | Detection rules | Parser owner | Parsed | Symbols | Semantic | Embedded source | Benchmarked | Optional pack | Provenance | License |\n",
1868 );
1869 output.push_str(
1870 "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n",
1871 );
1872 for row in &capability_rows {
1873 let rules = detector_rule_summary(row);
1874 let aliases = list_or_dash(row.aliases.iter().copied());
1875 let optional_pack = row.optional_pack.unwrap_or("—");
1876 let provenance = format!("{}@{}", row.provenance_source, row.provenance_version);
1877 writeln!(
1878 &mut output,
1879 "| `{}` | `{}` | {} | {} | {} | {} | {} | {} | {} | {} | {} | `{}` | `{}` |",
1880 row.id,
1881 row.classification,
1882 aliases,
1883 rules,
1884 symbol_parser_label(row.symbol_parser),
1885 row.support.parsed.as_str(),
1886 row.support.symbols.as_str(),
1887 semantic_support_label(row.support.semantic, row.semantic_provider),
1888 embedded_language_label(row.embedded_language),
1889 row.support.benchmarked.as_str(),
1890 optional_pack,
1891 provenance,
1892 row.provenance_license
1893 )?;
1894 }
1895 crate::support_catalog::append_support_catalog_markdown(&mut output)?;
1896 Ok(output)
1897}
1898
1899fn detector_rule_summary(row: &LanguageCapabilityReportRow) -> String {
1901 let mut rules = Vec::new();
1902 rules.extend(
1903 row.exact_filenames
1904 .iter()
1905 .map(|value| format!("exact `{value}`")),
1906 );
1907 rules.extend(
1908 row.compound_extensions
1909 .iter()
1910 .map(|value| format!("compound `{value}`")),
1911 );
1912 rules.extend(row.extensions.iter().map(|value| format!("`{value}`")));
1913 rules.extend(
1914 row.content_interpreters
1915 .iter()
1916 .map(|value| format!("shebang `{value}`")),
1917 );
1918 if rules.is_empty() {
1919 "—".to_string()
1920 } else {
1921 rules.join(", ")
1922 }
1923}
1924
1925fn symbol_parser_label(owner: SymbolParserOwner) -> String {
1927 match owner {
1928 SymbolParserOwner::TreeSitter(grammar) => {
1929 format!("{}@{}", grammar.package(), grammar.version())
1930 }
1931 SymbolParserOwner::CargoManifest => "projectatlas:cargo-manifest".to_string(),
1932 SymbolParserOwner::Vue => "projectatlas:vue".to_string(),
1933 SymbolParserOwner::PowerShell => "projectatlas:powershell".to_string(),
1934 SymbolParserOwner::Markdown => "projectatlas:markdown".to_string(),
1935 SymbolParserOwner::Document => "projectatlas:documents".to_string(),
1936 SymbolParserOwner::Fallback => "projectatlas:fallback".to_string(),
1937 SymbolParserOwner::Unavailable => "unavailable".to_string(),
1938 }
1939}
1940
1941fn semantic_support_label(support: CapabilitySupportLevel, owner: SemanticProviderOwner) -> String {
1943 match owner {
1944 SemanticProviderOwner::Unavailable => support.as_str().to_string(),
1945 _ => format!("{} ({})", support.as_str(), owner.as_str()),
1946 }
1947}
1948
1949fn embedded_language_label(capability: Option<EmbeddedLanguageCapability>) -> String {
1951 capability.map_or_else(
1952 || "—".to_string(),
1953 |capability| {
1954 format!(
1955 "{} → {}",
1956 capability.host_kind.as_str(),
1957 capability.semantic_provider.as_str()
1958 )
1959 },
1960 )
1961}
1962
1963fn list_or_dash<'a>(values: impl Iterator<Item = &'a str>) -> String {
1965 let values = values.collect::<Vec<_>>();
1966 if values.is_empty() {
1967 "—".to_string()
1968 } else {
1969 values
1970 .into_iter()
1971 .map(|value| format!("`{value}`"))
1972 .collect::<Vec<_>>()
1973 .join(", ")
1974 }
1975}
1976
1977fn rules_for_language(rules: &[LanguageDetectionRule], language: &str) -> Vec<&'static str> {
1979 rules
1980 .iter()
1981 .filter_map(|rule| (rule.language == language).then_some(rule.value))
1982 .collect()
1983}
1984
1985#[must_use]
1987pub fn language_capability_counts() -> LanguageCapabilityCounts {
1988 let mut counts = LanguageCapabilityCounts {
1989 accepted: LANGUAGE_CAPABILITIES.len(),
1990 ..LanguageCapabilityCounts::default()
1991 };
1992 for capability in LANGUAGE_CAPABILITIES {
1993 if capability.optional_pack.is_some() {
1994 counts.optional_candidates += 1;
1995 } else {
1996 counts.built_in += 1;
1997 }
1998 increment_level(&mut counts.detected, capability.support.detected);
1999 increment_level(&mut counts.parsed, capability.support.parsed);
2000 increment_level(&mut counts.symbols, capability.support.symbols);
2001 increment_level(&mut counts.semantic, capability.support.semantic);
2002 increment_level(&mut counts.benchmarked, capability.support.benchmarked);
2003 }
2004 counts
2005}
2006
2007fn increment_level(counts: &mut CapabilityLevelCounts, level: CapabilitySupportLevel) {
2009 match level {
2010 CapabilitySupportLevel::Unavailable => counts.unavailable += 1,
2011 CapabilitySupportLevel::Fallback => counts.fallback += 1,
2012 CapabilitySupportLevel::Supported => counts.supported += 1,
2013 }
2014}
2015
2016#[must_use]
2018pub fn language_registry_digest() -> String {
2019 hash_language_registry(false)
2020}
2021
2022#[must_use]
2024pub fn accepted_language_capability_digest() -> String {
2025 hash_language_registry(true)
2026}
2027
2028#[must_use]
2035pub fn semantic_provider_digest() -> String {
2036 let mut hasher = Hasher::new();
2037 hasher.update(&SEMANTIC_PROVIDER_CONTRACT_VERSION.to_le_bytes());
2038 for capability in LANGUAGE_CAPABILITIES {
2039 let effective_provider = capability.effective_semantic_provider();
2040 if effective_provider.is_none() && capability.embedded_language.is_none() {
2041 continue;
2042 }
2043 hash_value(&mut hasher, capability.id);
2044 hash_value(&mut hasher, capability.semantic_provider.as_str());
2045 hash_value(
2046 &mut hasher,
2047 capability
2048 .semantic_provider
2049 .resolution_family()
2050 .unwrap_or("unavailable"),
2051 );
2052 hash_value(
2053 &mut hasher,
2054 effective_provider
2055 .and_then(SemanticProviderOwner::resolution_family)
2056 .unwrap_or("unavailable"),
2057 );
2058 if let Some(embedded) = capability.embedded_language {
2059 hash_value(&mut hasher, embedded.host_kind.as_str());
2060 hash_value(&mut hasher, embedded.semantic_provider.as_str());
2061 hash_value(
2062 &mut hasher,
2063 embedded
2064 .semantic_provider
2065 .resolution_family()
2066 .unwrap_or("unavailable"),
2067 );
2068 } else {
2069 hash_value(&mut hasher, "no-embedded-provider");
2070 }
2071 hash_value(&mut hasher, capability.support.semantic.as_str());
2072 }
2073 hasher.finalize().to_hex().to_string()
2074}
2075
2076fn hash_language_registry(accepted_only: bool) -> String {
2078 hash_language_registry_with_content_rules(accepted_only, CONTENT_DIALECT_RULES)
2079}
2080
2081fn hash_language_registry_with_content_rules(
2083 accepted_only: bool,
2084 content_rules: &[LanguageContentRule],
2085) -> String {
2086 let mut hasher = Hasher::new();
2087 hasher.update(&LANGUAGE_CAPABILITY_REGISTRY_VERSION.to_le_bytes());
2088 hasher.update(&ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION.to_le_bytes());
2089 hasher.update(&LANGUAGE_DETECTION_POLICY_VERSION.to_le_bytes());
2090 hasher.update(&(LANGUAGE_CONTENT_DETECTION_MAX_BYTES as u64).to_le_bytes());
2091 hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG);
2092 hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG_VERSION);
2093 hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION);
2094 hasher.update(&(OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS as u64).to_le_bytes());
2095 for capability in LANGUAGE_CAPABILITIES {
2096 hash_value(&mut hasher, capability.id);
2097 for alias in capability.aliases {
2098 hash_value(&mut hasher, alias);
2099 }
2100 hash_value(&mut hasher, capability.classification.as_str());
2101 let support = if accepted_only {
2102 capability.accepted_minimum
2103 } else {
2104 capability.support
2105 };
2106 hash_support(&mut hasher, support);
2107 hash_value(&mut hasher, &format!("{:?}", capability.parser_support));
2108 hash_value(&mut hasher, &format!("{:?}", capability.symbol_parser));
2109 hash_value(&mut hasher, &format!("{:?}", capability.semantic_provider));
2110 hash_value(&mut hasher, &format!("{:?}", capability.embedded_language));
2111 hash_value(&mut hasher, &format!("{:?}", capability.structural_summary));
2112 hash_value(
2113 &mut hasher,
2114 capability.optional_pack.unwrap_or("default-core"),
2115 );
2116 hash_value(&mut hasher, capability.fixtures.positive_path);
2117 hash_value(&mut hasher, capability.fixtures.negative_path);
2118 hash_value(&mut hasher, capability.provenance.source());
2119 hash_value(&mut hasher, capability.provenance.version());
2120 hash_value(&mut hasher, capability.provenance.license());
2121 hash_value(&mut hasher, &format!("{:?}", capability.required_platforms));
2122 }
2123 for (kind, rules) in [
2124 ("exact", EXACT_FILENAME_RULES),
2125 ("compound", COMPOUND_EXTENSION_RULES),
2126 ("extension", EXTENSION_RULES),
2127 ] {
2128 for rule in rules {
2129 hash_value(&mut hasher, kind);
2130 hash_value(&mut hasher, rule.value);
2131 hash_value(&mut hasher, rule.language);
2132 }
2133 }
2134 for rule in content_rules {
2135 hash_value(&mut hasher, "content-interpreter");
2136 hash_value(&mut hasher, rule.interpreter);
2137 hasher.update(&[u8::from(rule.allow_version_suffix)]);
2138 hash_value(&mut hasher, rule.language);
2139 }
2140 hasher.finalize().to_hex().to_string()
2141}
2142
2143fn hash_support(hasher: &mut Hasher, support: LanguageCapabilitySupport) {
2145 hasher.update(&[
2146 support.detected as u8,
2147 support.parsed as u8,
2148 support.symbols as u8,
2149 support.semantic as u8,
2150 support.benchmarked as u8,
2151 ]);
2152}
2153
2154fn hash_value(hasher: &mut Hasher, value: &str) {
2156 hasher.update(&(value.len() as u64).to_le_bytes());
2157 hasher.update(value.as_bytes());
2158}
2159
2160pub fn validate_language_registry() -> Result<(), LanguageRegistryError> {
2166 let mut canonical = BTreeSet::new();
2167 let mut names = BTreeMap::new();
2168 for capability in LANGUAGE_CAPABILITIES {
2169 if capability.id.is_empty() {
2170 return Err(LanguageRegistryError::new("empty canonical language ID"));
2171 }
2172 if !canonical.insert(capability.id) {
2173 return Err(LanguageRegistryError::new(format!(
2174 "duplicate canonical language ID {:?}",
2175 capability.id
2176 )));
2177 }
2178 register_owner(&mut names, capability.id, capability.id, "language name")?;
2179 for alias in capability.aliases {
2180 register_owner(&mut names, alias, capability.id, "language alias")?;
2181 }
2182 if !capability.support.meets(capability.accepted_minimum) {
2183 return Err(LanguageRegistryError::new(format!(
2184 "language {:?} is weaker than accepted capability set version {}",
2185 capability.id, ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION
2186 )));
2187 }
2188 if capability.fixtures.positive_path.is_empty()
2189 || capability.fixtures.negative_path.is_empty()
2190 || capability.fixtures.positive_path == capability.fixtures.negative_path
2191 {
2192 return Err(LanguageRegistryError::new(format!(
2193 "language {:?} lacks distinct natural positive and negative fixtures",
2194 capability.id
2195 )));
2196 }
2197 if capability.provenance.license().is_empty() {
2198 return Err(LanguageRegistryError::new(format!(
2199 "language {:?} lacks a provenance license input",
2200 capability.id
2201 )));
2202 }
2203 if capability
2204 .optional_pack
2205 .is_some_and(|owner| owner != BROAD_PARSER_PACK_ID)
2206 {
2207 return Err(LanguageRegistryError::new(format!(
2208 "optional language {:?} is assigned to unknown pack owner {:?}",
2209 capability.id, capability.optional_pack
2210 )));
2211 }
2212 if capability.optional_pack.is_some()
2213 && capability.provenance != CapabilityProvenance::PinnedOptionalCatalog
2214 {
2215 return Err(LanguageRegistryError::new(format!(
2216 "optional language {:?} lacks pinned catalog provenance",
2217 capability.id
2218 )));
2219 }
2220 if capability.optional_pack.is_none()
2221 && capability.provenance == CapabilityProvenance::PinnedOptionalCatalog
2222 {
2223 return Err(LanguageRegistryError::new(format!(
2224 "default-core language {:?} incorrectly claims optional catalog provenance",
2225 capability.id
2226 )));
2227 }
2228 match (capability.support.symbols, capability.symbol_parser) {
2229 (CapabilitySupportLevel::Unavailable, SymbolParserOwner::Unavailable)
2230 | (CapabilitySupportLevel::Fallback, SymbolParserOwner::Fallback)
2231 | (
2232 CapabilitySupportLevel::Supported,
2233 SymbolParserOwner::TreeSitter(_)
2234 | SymbolParserOwner::CargoManifest
2235 | SymbolParserOwner::Vue
2236 | SymbolParserOwner::PowerShell
2237 | SymbolParserOwner::Markdown
2238 | SymbolParserOwner::Document,
2239 ) => {}
2240 (support, owner) => {
2241 return Err(LanguageRegistryError::new(format!(
2242 "language {:?} advertises symbol support {support:?} with incompatible owner {owner:?}",
2243 capability.id
2244 )));
2245 }
2246 }
2247 match (capability.support.semantic, capability.semantic_provider) {
2248 (CapabilitySupportLevel::Unavailable, SemanticProviderOwner::Unavailable)
2249 | (
2250 CapabilitySupportLevel::Supported,
2251 SemanticProviderOwner::Rust
2252 | SemanticProviderOwner::EcmaScript
2253 | SemanticProviderOwner::Python
2254 | SemanticProviderOwner::Cargo,
2255 ) => {}
2256 (support, owner) => {
2257 return Err(LanguageRegistryError::new(format!(
2258 "language {:?} advertises semantic support {support:?} with incompatible owner {owner:?}",
2259 capability.id
2260 )));
2261 }
2262 }
2263 if let Some(embedded) = capability.embedded_language {
2264 if embedded.semantic_provider == SemanticProviderOwner::Unavailable {
2265 return Err(LanguageRegistryError::new(format!(
2266 "embedded language host {:?} lacks a semantic provider owner",
2267 capability.id
2268 )));
2269 }
2270 if capability.support.semantic != CapabilitySupportLevel::Unavailable
2271 || capability.semantic_provider != SemanticProviderOwner::Unavailable
2272 {
2273 return Err(LanguageRegistryError::new(format!(
2274 "embedded language host {:?} conflates host and embedded semantic support",
2275 capability.id
2276 )));
2277 }
2278 }
2279 if capability.optional_pack.is_some()
2280 && (capability.semantic_provider != SemanticProviderOwner::Unavailable
2281 || capability.embedded_language.is_some())
2282 {
2283 return Err(LanguageRegistryError::new(format!(
2284 "optional language {:?} advertises unvalidated semantic capability",
2285 capability.id
2286 )));
2287 }
2288 }
2289
2290 validate_rules("exact filename", EXACT_FILENAME_RULES, &canonical, false)?;
2291 validate_rules(
2292 "compound extension",
2293 COMPOUND_EXTENSION_RULES,
2294 &canonical,
2295 true,
2296 )?;
2297 validate_rules("extension", EXTENSION_RULES, &canonical, true)?;
2298 validate_content_rules(&canonical)?;
2299
2300 let mut detected: BTreeSet<_> = EXACT_FILENAME_RULES
2301 .iter()
2302 .chain(COMPOUND_EXTENSION_RULES)
2303 .chain(EXTENSION_RULES)
2304 .map(|rule| rule.language)
2305 .collect();
2306 detected.extend(CONTENT_DIALECT_RULES.iter().map(|rule| rule.language));
2307 for capability in LANGUAGE_CAPABILITIES {
2308 if capability.support.detected == CapabilitySupportLevel::Supported
2309 && !detected.contains(capability.id)
2310 {
2311 return Err(LanguageRegistryError::new(format!(
2312 "accepted language {:?} is a ghost row with no detector rule",
2313 capability.id
2314 )));
2315 }
2316 let detected_fixture = detect_language_request(LanguageDetectionRequest::new(
2317 capability.fixtures.positive_path,
2318 None,
2319 ))
2320 .map_err(|source| LanguageRegistryError::new(source.to_string()))?;
2321 if detected_fixture.map(|result| result.language) != Some(capability.id) {
2322 return Err(LanguageRegistryError::new(format!(
2323 "positive fixture {:?} does not select owning language {:?}",
2324 capability.fixtures.positive_path, capability.id
2325 )));
2326 }
2327 let negative_fixture = detect_language_request(LanguageDetectionRequest::new(
2328 capability.fixtures.negative_path,
2329 None,
2330 ))
2331 .map_err(|source| LanguageRegistryError::new(source.to_string()))?;
2332 if negative_fixture.map(|result| result.language) == Some(capability.id) {
2333 return Err(LanguageRegistryError::new(format!(
2334 "negative fixture {:?} still selects owning language {:?}",
2335 capability.fixtures.negative_path, capability.id
2336 )));
2337 }
2338 }
2339 let optional_candidates = LANGUAGE_CAPABILITIES
2340 .iter()
2341 .filter(|capability| capability.optional_pack.is_some())
2342 .count();
2343 if optional_candidates < OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS {
2344 return Err(LanguageRegistryError::new(format!(
2345 "optional catalog exposes only {optional_candidates} distinct candidate rows; at least {OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS} are required"
2346 )));
2347 }
2348 let accepted_digest = accepted_language_capability_digest();
2349 let expected_accepted_digest = match ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION {
2350 1 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V1_DIGEST,
2351 2 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V2_DIGEST,
2352 3 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V3_DIGEST,
2353 4 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V4_DIGEST,
2354 5 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V5_DIGEST,
2355 6 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V6_DIGEST,
2356 7 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V7_DIGEST,
2357 8 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V8_DIGEST,
2358 9 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V9_DIGEST,
2359 10 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V10_DIGEST,
2360 11 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V11_DIGEST,
2361 12 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V12_DIGEST,
2362 13 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V13_DIGEST,
2363 14 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V14_DIGEST,
2364 15 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V15_DIGEST,
2365 version => {
2366 return Err(LanguageRegistryError::new(format!(
2367 "accepted language capability-set version {version} lacks a historical digest seal"
2368 )));
2369 }
2370 };
2371 if accepted_digest != expected_accepted_digest {
2372 return Err(LanguageRegistryError::new(format!(
2373 "accepted language capability-set version {ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION} changed from {expected_accepted_digest} to {accepted_digest}; bump the set version for an explicit compatibility decision"
2374 )));
2375 }
2376 Ok(())
2377}
2378
2379fn register_owner<'a>(
2381 owners: &mut BTreeMap<String, &'a str>,
2382 value: &str,
2383 owner: &'a str,
2384 kind: &str,
2385) -> Result<(), LanguageRegistryError> {
2386 let normalized = value.to_ascii_lowercase();
2387 if let Some(previous) = owners.insert(normalized.clone(), owner) {
2388 return Err(LanguageRegistryError::new(format!(
2389 "conflicting {kind} {normalized:?} is owned by both {previous:?} and {owner:?}"
2390 )));
2391 }
2392 Ok(())
2393}
2394
2395fn validate_rules(
2397 kind: &str,
2398 rules: &[LanguageDetectionRule],
2399 canonical: &BTreeSet<&str>,
2400 normalize_case: bool,
2401) -> Result<(), LanguageRegistryError> {
2402 let mut owners = BTreeMap::new();
2403 let mut previous_compound_length = usize::MAX;
2404 for rule in rules {
2405 if !canonical.contains(rule.language) {
2406 return Err(LanguageRegistryError::new(format!(
2407 "{kind} rule {:?} targets missing language {:?}",
2408 rule.value, rule.language
2409 )));
2410 }
2411 let key = if normalize_case {
2412 rule.value.to_ascii_lowercase()
2413 } else {
2414 rule.value.to_string()
2415 };
2416 if let Some(previous) = owners.insert(key.clone(), rule.language)
2417 && previous != rule.language
2418 {
2419 return Err(LanguageRegistryError::new(format!(
2420 "conflicting {kind} rule {key:?} is owned by both {previous:?} and {:?}",
2421 rule.language
2422 )));
2423 }
2424 if kind == "compound extension" {
2425 if rule.value.len() > previous_compound_length {
2426 return Err(LanguageRegistryError::new(format!(
2427 "compound extension rules are not longest-first at {:?}",
2428 rule.value
2429 )));
2430 }
2431 previous_compound_length = rule.value.len();
2432 }
2433 }
2434 Ok(())
2435}
2436
2437fn validate_content_rules(canonical: &BTreeSet<&str>) -> Result<(), LanguageRegistryError> {
2439 let mut owners = BTreeMap::new();
2440 for rule in CONTENT_DIALECT_RULES {
2441 if !canonical.contains(rule.language) {
2442 return Err(LanguageRegistryError::new(format!(
2443 "content interpreter {:?} targets missing language {:?}",
2444 rule.interpreter, rule.language
2445 )));
2446 }
2447 if rule.interpreter.is_empty()
2448 || !rule
2449 .interpreter
2450 .bytes()
2451 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
2452 {
2453 return Err(LanguageRegistryError::new(format!(
2454 "content interpreter {:?} is not a normalized basename",
2455 rule.interpreter
2456 )));
2457 }
2458 if let Some(previous) = owners.insert(rule.interpreter, rule.language) {
2459 return Err(LanguageRegistryError::new(format!(
2460 "conflicting content interpreter {:?} is owned by both {previous:?} and {:?}",
2461 rule.interpreter, rule.language
2462 )));
2463 }
2464 }
2465 Ok(())
2466}
2467
2468#[cfg(test)]
2469mod tests {
2470 use super::*;
2471 use serde::Deserialize;
2472 use std::io;
2473
2474 #[derive(Debug, Deserialize)]
2476 struct DetectionCompatibilityFixture {
2477 extensions: Vec<String>,
2479 exact_filenames: Vec<String>,
2481 }
2482
2483 #[derive(Debug, Deserialize)]
2485 struct OptionalDetectionCatalogFixture {
2486 catalog: String,
2488 version: String,
2490 revision: String,
2492 rows: Vec<String>,
2494 }
2495
2496 fn require_test(condition: bool, message: impl Into<String>) -> Result<(), Box<dyn Error>> {
2497 if condition {
2498 Ok(())
2499 } else {
2500 Err(io::Error::other(message.into()).into())
2501 }
2502 }
2503
2504 #[test]
2505 fn content_classification_is_registry_owned_and_utf8_safe() -> Result<(), Box<dyn Error>> {
2506 require_test(
2507 content_classification(Some("rust"), true) == ContentClassification::Source,
2508 "Rust did not retain its source classification",
2509 )?;
2510 require_test(
2511 content_classification(Some("markdown"), true) == ContentClassification::Documentation,
2512 "Markdown did not retain its documentation classification",
2513 )?;
2514 require_test(
2515 content_classification(Some("json"), true) == ContentClassification::ConfigurationData,
2516 "JSON did not retain its configuration/data classification",
2517 )?;
2518 require_test(
2519 content_classification(None, true) == ContentClassification::OtherText,
2520 "unknown UTF-8 content was not classified as other text",
2521 )?;
2522 require_test(
2523 content_classification(Some("rust"), false) == ContentClassification::Source,
2524 "known language classification did not take precedence over UTF-8 fallback",
2525 )?;
2526 require_test(
2527 content_classification(None, false) == ContentClassification::Opaque,
2528 "unknown invalid UTF-8 content was not opaque",
2529 )?;
2530 require_test(
2531 language_capability("rst").map(|row| row.classification)
2532 == Some(ContentClassification::Documentation),
2533 "optional documentation rows lost registry-owned classification",
2534 )?;
2535 require_test(
2536 language_capability("csv").map(|row| row.classification)
2537 == Some(ContentClassification::ConfigurationData),
2538 "optional structured-data rows lost registry-owned classification",
2539 )?;
2540
2541 let rendered = render_language_support_markdown()?;
2542 require_test(
2543 rendered.contains("| Language | Classification | Aliases |"),
2544 "generated capability matrix omitted the classification column",
2545 )?;
2546 require_test(
2547 rendered.contains("| `markdown` | `documentation` |"),
2548 "generated capability matrix omitted Markdown's documentation role",
2549 )
2550 }
2551
2552 #[test]
2553 fn content_classification_storage_and_selection_contracts_are_closed()
2554 -> Result<(), Box<dyn Error>> {
2555 for classification in ContentClassification::ALL {
2556 require_test(
2557 ContentClassification::from_db(classification.as_str()) == Some(classification),
2558 format!("classification {classification} did not round-trip"),
2559 )?;
2560 require_test(
2561 serde_json::from_str::<ContentClassification>(&serde_json::to_string(
2562 &classification,
2563 )?)? == classification,
2564 format!("classification {classification} wire spelling did not round-trip"),
2565 )?;
2566 }
2567 require_test(
2568 ContentClassification::from_db("generated").is_none(),
2569 "unsupported generated classification was accepted",
2570 )?;
2571
2572 require_test(
2573 ContentSelection::default() == ContentSelection::UnspecifiedLegacy
2574 && ContentSelection::default().explicit_value().is_none(),
2575 "omitted selection no longer preserves the distinct legacy state",
2576 )?;
2577 require_test(
2578 ContentSelection::UnspecifiedLegacy.includes(ContentClassification::ConfigurationData)
2579 && ContentSelection::Source.includes(ContentClassification::Source)
2580 && !ContentSelection::Source.includes(ContentClassification::Documentation)
2581 && ContentSelection::Documentation.includes(ContentClassification::Documentation)
2582 && ContentSelection::Both.includes(ContentClassification::Source)
2583 && ContentSelection::Both.includes(ContentClassification::Documentation)
2584 && !ContentSelection::Both.includes(ContentClassification::OtherText),
2585 "content selection admitted the wrong classification",
2586 )?;
2587 require_test(
2588 "source".parse::<ContentSelection>()? == ContentSelection::Source
2589 && "documentation".parse::<ContentSelection>()? == ContentSelection::Documentation
2590 && "both".parse::<ContentSelection>()? == ContentSelection::Both,
2591 "an explicit selection failed to parse",
2592 )?;
2593 let invalid = "".parse::<ContentSelection>().err().ok_or_else(|| {
2594 std::io::Error::other("empty explicit content selection was accepted")
2595 })?;
2596 require_test(
2597 invalid.requested().is_empty()
2598 && invalid
2599 .to_string()
2600 .contains("source, documentation, or both"),
2601 "invalid selection did not retain the typed allowed-value diagnostic",
2602 )?;
2603 require_test(
2604 serde_json::to_string(&ContentSelection::Source)? == "\"source\""
2605 && serde_json::to_string(&ContentSelection::UnspecifiedLegacy).is_err()
2606 && serde_json::from_str::<ContentSelection>("\"unspecified_legacy\"").is_err(),
2607 "the internal legacy state leaked into the caller-visible wire contract",
2608 )
2609 }
2610
2611 #[test]
2612 fn accepted_registry_and_generated_projections_validate() -> Result<(), Box<dyn Error>> {
2613 validate_language_registry()?;
2614 require_test(
2615 LANGUAGE_CAPABILITIES.len() == LANGUAGE_SPECS.len(),
2616 "language capability and parser metadata projections differ",
2617 )?;
2618 let report = language_registry_report();
2619 require_test(
2620 report.counts.accepted == LANGUAGE_CAPABILITIES.len(),
2621 "accepted language count is not registry-derived",
2622 )?;
2623 let canonical_ids = LANGUAGE_CAPABILITIES
2624 .iter()
2625 .map(|capability| capability.id)
2626 .collect::<BTreeSet<_>>();
2627 require_test(
2628 report.counts.accepted == canonical_ids.len(),
2629 "aliases or extensions inflated the canonical capability count",
2630 )?;
2631 require_test(
2632 LANGUAGE_CAPABILITIES
2633 .iter()
2634 .any(|capability| !capability.aliases.is_empty())
2635 && !EXTENSION_RULES.is_empty(),
2636 "alias/extension non-inflation check became vacuous",
2637 )?;
2638 require_test(
2639 report.counts.built_in + report.counts.optional_candidates == report.counts.accepted,
2640 "built-in and optional language counts do not cover the accepted set",
2641 )?;
2642 require_test(
2643 language_capability_report_rows().len() == report.counts.accepted,
2644 "documentation projection does not cover the accepted set",
2645 )?;
2646 require_test(
2647 report.counts.optional_candidates >= OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS,
2648 "optional catalog projection is below the accepted breadth floor",
2649 )?;
2650 require_test(
2651 !report.registry_digest.is_empty(),
2652 "language registry digest is empty",
2653 )?;
2654 require_test(
2655 !report.accepted_set_digest.is_empty(),
2656 "accepted language-set digest is empty",
2657 )?;
2658 require_test(
2659 report.semantic_provider_digest.len() == 64
2660 && report
2661 .semantic_provider_digest
2662 .bytes()
2663 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)),
2664 "semantic provider digest is not a bounded lowercase hexadecimal identity",
2665 )?;
2666 let encoded = serde_json::to_vec(&report)?;
2667 require_test(
2668 encoded.len() <= LANGUAGE_REGISTRY_REPORT_MAX_BYTES,
2669 "language registry settings projection exceeded its content-free output budget",
2670 )?;
2671 Ok(())
2672 }
2673
2674 #[test]
2675 fn semantic_provider_and_embedded_host_claims_are_honest() -> Result<(), Box<dyn Error>> {
2676 let report = language_registry_report();
2677 let semantic_rows = LANGUAGE_CAPABILITIES
2678 .iter()
2679 .filter(|capability| capability.support.semantic == CapabilitySupportLevel::Supported)
2680 .count();
2681 require_test(
2682 report.counts.semantic.supported == semantic_rows,
2683 "semantic provider rows did not determine the semantic capability count",
2684 )?;
2685
2686 for capability in LANGUAGE_CAPABILITIES
2687 .iter()
2688 .filter(|capability| capability.optional_pack.is_some())
2689 {
2690 require_test(
2691 capability.semantic_provider == SemanticProviderOwner::Unavailable
2692 && capability.embedded_language.is_none()
2693 && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2694 format!(
2695 "optional candidate {:?} advertised semantic capability",
2696 capability.id
2697 ),
2698 )?;
2699 }
2700
2701 for (language, expected) in [
2702 (
2703 "html",
2704 EmbeddedLanguageCapability {
2705 host_kind: EmbeddedHostKind::HtmlLike,
2706 semantic_provider: SemanticProviderOwner::EcmaScript,
2707 },
2708 ),
2709 (
2710 "vue",
2711 EmbeddedLanguageCapability {
2712 host_kind: EmbeddedHostKind::Component,
2713 semantic_provider: SemanticProviderOwner::EcmaScript,
2714 },
2715 ),
2716 (
2717 "svelte",
2718 EmbeddedLanguageCapability {
2719 host_kind: EmbeddedHostKind::Template,
2720 semantic_provider: SemanticProviderOwner::EcmaScript,
2721 },
2722 ),
2723 ] {
2724 let capability = language_capability(language)
2725 .ok_or_else(|| io::Error::other(format!("missing {language} capability")))?;
2726 require_test(
2727 capability.embedded_language == Some(expected)
2728 && capability.semantic_provider == SemanticProviderOwner::Unavailable
2729 && capability.effective_semantic_provider()
2730 == Some(SemanticProviderOwner::EcmaScript)
2731 && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2732 format!("{language} host/embedded capability drifted"),
2733 )?;
2734 }
2735
2736 for language in ["cargo-lock", "java", "c", "go"] {
2737 let capability = language_capability(language)
2738 .ok_or_else(|| io::Error::other(format!("missing {language} capability")))?;
2739 require_test(
2740 capability.semantic_provider == SemanticProviderOwner::Unavailable
2741 && capability.effective_semantic_provider().is_none()
2742 && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2743 format!("unsupported semantic language {language:?} was promoted"),
2744 )?;
2745 }
2746 require_test(
2747 SemanticProviderOwner::EcmaScript.resolution_family() == Some("ecmascript")
2748 && SemanticProviderOwner::Unavailable
2749 .resolution_family()
2750 .is_none(),
2751 "semantic provider resolution-family identity drifted",
2752 )?;
2753 Ok(())
2754 }
2755
2756 #[test]
2757 fn detects_every_broad_source_extension() {
2758 for extension in BROAD_SOURCE_EXTENSIONS {
2759 assert!(
2760 detect_language(Some(extension)).is_some(),
2761 "missing broad source extension support for {extension}"
2762 );
2763 }
2764 }
2765
2766 #[test]
2767 fn detects_every_accepted_registry_source_extension() {
2768 for extension in DETECTED_SOURCE_EXTENSIONS {
2769 assert!(
2770 detect_language(Some(extension)).is_some(),
2771 "missing accepted source extension support for {extension}"
2772 );
2773 }
2774 }
2775
2776 #[test]
2777 fn generated_language_support_document_is_current() -> Result<(), Box<dyn Error>> {
2778 let rendered = render_language_support_markdown()?;
2779 require_test(
2780 include_str!("../../../docs/language-support.md") == rendered,
2781 "checked-in language support document is stale",
2782 )?;
2783 require_test(
2784 include_str!(
2785 "../../../plugins/projectatlas/skills/projectatlas/references/language-support.md"
2786 ) == rendered,
2787 "bundled skill language support reference is stale",
2788 )
2789 }
2790
2791 #[test]
2792 fn built_in_grammar_provenance_matches_workspace_pins() {
2793 let workspace = include_str!("../../../Cargo.toml");
2794 let mut checked = BTreeSet::new();
2795 for grammar in TreeSitterGrammar::ALL {
2796 let package = grammar.package();
2797 if checked.insert(package) {
2798 let pin = format!("{package} = \"={}\"", grammar.version());
2799 assert!(
2800 workspace.lines().any(|line| line.trim() == pin),
2801 "workspace dependency pin drifted from registry provenance: {pin}"
2802 );
2803 }
2804 }
2805 }
2806
2807 #[test]
2808 fn frozen_v0326_detection_corpus_remains_exact() -> Result<(), Box<dyn Error>> {
2809 let fixture: DetectionCompatibilityFixture = serde_json::from_str(include_str!(
2810 "../../../fixtures/languages/v0.3.26-detection.json"
2811 ))?;
2812 let ordered_extensions = fixture
2813 .extensions
2814 .iter()
2815 .map(|row| row.split_once('=').map(|(extension, _)| extension))
2816 .collect::<Option<Vec<_>>>()
2817 .ok_or_else(|| io::Error::other("invalid extension compatibility row"))?;
2818 if ordered_extensions != BROAD_SOURCE_EXTENSIONS {
2819 return Err(io::Error::other(
2820 "ordered 0.3.26 broad extension compatibility corpus changed",
2821 )
2822 .into());
2823 }
2824 for row in fixture.extensions {
2825 let (extension, expected) = row
2826 .split_once('=')
2827 .ok_or_else(|| io::Error::other("invalid extension compatibility row"))?;
2828 if detect_language(Some(extension)).as_deref() != Some(expected) {
2829 return Err(io::Error::other(format!(
2830 "0.3.26 extension {extension:?} no longer selects {expected:?}"
2831 ))
2832 .into());
2833 }
2834 }
2835 for row in fixture.exact_filenames {
2836 let (path, expected) = row
2837 .split_once('=')
2838 .ok_or_else(|| io::Error::other("invalid exact filename compatibility row"))?;
2839 if detect_language_for_path(path, None).as_deref() != Some(expected)
2840 || detect_language_for_path(&format!("folder\\{path}"), None).as_deref()
2841 != Some(expected)
2842 {
2843 return Err(io::Error::other(format!(
2844 "0.3.26 exact filename {path:?} no longer selects {expected:?}"
2845 ))
2846 .into());
2847 }
2848 }
2849 Ok(())
2850 }
2851
2852 #[test]
2853 fn accepted_optional_detection_subset_matches_independent_catalog_projection()
2854 -> Result<(), Box<dyn Error>> {
2855 let fixture: OptionalDetectionCatalogFixture = serde_json::from_str(include_str!(
2856 "../../../fixtures/languages/accepted-optional-detection-catalog.json"
2857 ))?;
2858 require_test(
2859 fixture.catalog == OPTIONAL_GRAMMAR_CATALOG,
2860 "optional catalog identity drifted",
2861 )?;
2862 require_test(
2863 fixture.version == OPTIONAL_GRAMMAR_CATALOG_VERSION,
2864 "optional catalog version drifted",
2865 )?;
2866 require_test(
2867 fixture.revision == OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION,
2868 "optional catalog revision drifted",
2869 )?;
2870 let projected = LANGUAGE_CAPABILITIES
2871 .iter()
2872 .filter(|capability| capability.optional_pack.is_some())
2873 .map(|capability| {
2874 let extensions = rules_for_language(EXTENSION_RULES, capability.id);
2875 match extensions.as_slice() {
2876 [extension] => Ok(format!("{}={extension}", capability.id)),
2877 _ => Err(io::Error::other(format!(
2878 "optional detection row {:?} must own exactly one accepted extension",
2879 capability.id
2880 ))),
2881 }
2882 })
2883 .collect::<Result<Vec<_>, _>>()?;
2884 require_test(
2885 fixture.rows == projected,
2886 "accepted optional catalog projection drifted",
2887 )
2888 }
2889
2890 #[test]
2891 fn preserves_representative_broad_source_extensions() {
2892 assert_eq!(
2893 detect_language(Some(".d.ts")).as_deref(),
2894 Some("typescript")
2895 );
2896 assert_eq!(detect_language(Some(".pyw")).as_deref(), Some("python"));
2897 assert_eq!(detect_language(Some(".kts")).as_deref(), Some("kotlin"));
2898 assert_eq!(
2899 detect_language(Some(".psm1")).as_deref(),
2900 Some("powershell")
2901 );
2902 assert_eq!(detect_language(Some(".zon")).as_deref(), Some("zig"));
2903 assert_eq!(detect_language(Some(".proto")).as_deref(), Some("protobuf"));
2904 assert_eq!(detect_language(Some(".R")).as_deref(), Some("r"));
2905 assert_eq!(detect_language(Some(".ini")).as_deref(), Some("config"));
2906 assert_eq!(detect_language(Some(".liquibase")).as_deref(), Some("sql"));
2907 assert_eq!(detect_language(Some(".toon")).as_deref(), Some("toon"));
2908 }
2909
2910 #[test]
2911 fn preserves_exact_filename_case_and_compound_precedence() {
2912 let exact =
2913 detect_language_request(LanguageDetectionRequest::new("Cargo.toml", Some(".toml")));
2914 assert!(matches!(
2915 exact,
2916 Ok(Some(LanguageDetection {
2917 language: "cargo-manifest",
2918 reason: LanguageDetectionReason::ExactFilename,
2919 }))
2920 ));
2921 assert_eq!(
2922 detect_language_for_path("cargo.toml", Some(".toml")).as_deref(),
2923 Some("toml")
2924 );
2925 let compound = detect_language_request(LanguageDetectionRequest::new(
2926 "types/index.d.ts",
2927 Some(".d.ts"),
2928 ));
2929 assert!(matches!(
2930 compound,
2931 Ok(Some(LanguageDetection {
2932 language: "typescript",
2933 reason: LanguageDetectionReason::CompoundExtension,
2934 }))
2935 ));
2936 }
2937
2938 #[test]
2939 fn typed_precedence_prefers_override_and_bounds_content() -> Result<(), Box<dyn Error>> {
2940 let override_result = detect_language_request(LanguageDetectionRequest {
2941 path: "Cargo.toml",
2942 extension: Some(".toml"),
2943 explicit_override: Some("py"),
2944 content_prefix: Some(b"#!/usr/bin/env node\n"),
2945 })?
2946 .ok_or_else(|| io::Error::other("override detection missing"))?;
2947 require_test(
2948 override_result.language == "python",
2949 "explicit alias override did not select Python",
2950 )?;
2951 require_test(
2952 override_result.reason == LanguageDetectionReason::ExplicitOverride,
2953 "explicit override did not retain its typed reason",
2954 )?;
2955
2956 let content_result = detect_language_request(LanguageDetectionRequest {
2957 path: "tool",
2958 extension: None,
2959 explicit_override: None,
2960 content_prefix: Some(b"#!/usr/bin/env python\nprint('ok')\n"),
2961 })?
2962 .ok_or_else(|| io::Error::other("content detection missing"))?;
2963 require_test(
2964 content_result.language == "python",
2965 "bounded shebang did not select Python",
2966 )?;
2967 require_test(
2968 content_result.reason == LanguageDetectionReason::ContentDialect,
2969 "bounded shebang did not retain its typed reason",
2970 )?;
2971 let extension_result =
2972 detect_language_request(LanguageDetectionRequest::new("module.py", Some(".py")))?
2973 .ok_or_else(|| io::Error::other("extension detection missing"))?;
2974 require_test(
2975 extension_result.language == "python",
2976 "extension did not select Python",
2977 )?;
2978 require_test(
2979 extension_result.reason == LanguageDetectionReason::Extension,
2980 "extension did not retain its typed reason",
2981 )?;
2982 require_test(
2983 detect_language_request(LanguageDetectionRequest {
2984 path: "tool",
2985 extension: None,
2986 explicit_override: Some("missing-language"),
2987 content_prefix: None,
2988 })
2989 .is_err(),
2990 "unknown explicit override did not fail closed",
2991 )?;
2992 Ok(())
2993 }
2994
2995 #[test]
2996 fn every_detected_language_has_compatible_parser_metadata() -> Result<(), Box<dyn Error>> {
2997 for rule in EXACT_FILENAME_RULES
2998 .iter()
2999 .chain(COMPOUND_EXTENSION_RULES)
3000 .chain(EXTENSION_RULES)
3001 {
3002 let spec = language_spec(rule.language).ok_or_else(|| {
3003 io::Error::other(format!("missing parser coverage for {}", rule.language))
3004 })?;
3005 require_test(
3006 spec.language == rule.language,
3007 format!("parser coverage ownership drifted for {}", rule.language),
3008 )?;
3009 }
3010 for rule in CONTENT_DIALECT_RULES {
3011 let spec = language_spec(rule.language).ok_or_else(|| {
3012 io::Error::other(format!("missing parser coverage for {}", rule.language))
3013 })?;
3014 require_test(
3015 spec.language == rule.language,
3016 format!("content detector ownership drifted for {}", rule.language),
3017 )?;
3018 }
3019 Ok(())
3020 }
3021
3022 #[test]
3023 fn structural_summary_rows_do_not_advertise_symbols() {
3024 for capability in LANGUAGE_CAPABILITIES
3025 .iter()
3026 .filter(|capability| capability.parser_support == LanguageParserSupport::Structural)
3027 .filter(|capability| capability.symbol_parser == SymbolParserOwner::Unavailable)
3028 {
3029 assert_eq!(
3030 capability.support.symbols,
3031 CapabilitySupportLevel::Unavailable,
3032 "structural summary row {:?} advertised symbols without an owner",
3033 capability.id
3034 );
3035 }
3036 }
3037
3038 #[test]
3039 fn shebang_rules_are_exact_bounded_and_utf8_boundary_safe() -> Result<(), Box<dyn Error>> {
3040 for rule in CONTENT_DIALECT_RULES {
3041 let source = format!("#!/usr/bin/env {}\n", rule.interpreter);
3042 let detected = detect_language_request(LanguageDetectionRequest {
3043 path: "tool",
3044 extension: None,
3045 explicit_override: None,
3046 content_prefix: Some(source.as_bytes()),
3047 })?
3048 .ok_or_else(|| io::Error::other("declared shebang was not detected"))?;
3049 require_test(
3050 detected.language == rule.language,
3051 format!("shebang owner drifted for {}", rule.interpreter),
3052 )?;
3053 require_test(
3054 detected.reason == LanguageDetectionReason::ContentDialect,
3055 format!("shebang reason drifted for {}", rule.interpreter),
3056 )?;
3057 }
3058
3059 for near_miss in ["wish", "mesh-agent", "python-helper", "nodejs"] {
3060 let source = format!("#!/usr/bin/env {near_miss}\n");
3061 let detected = detect_language_request(LanguageDetectionRequest {
3062 path: "tool",
3063 extension: None,
3064 explicit_override: None,
3065 content_prefix: Some(source.as_bytes()),
3066 })?;
3067 require_test(
3068 detected.is_none(),
3069 format!("near-miss interpreter {near_miss:?} was classified"),
3070 )?;
3071 }
3072
3073 for (interpreter, expected) in [
3074 ("python3", "python"),
3075 ("python3.12", "python"),
3076 ("ruby3.3", "ruby"),
3077 ("lua5.4", "lua"),
3078 ] {
3079 let source = format!("#!/usr/bin/env {interpreter}\n");
3080 let detected = detect_language_request(LanguageDetectionRequest {
3081 path: "tool",
3082 extension: None,
3083 explicit_override: None,
3084 content_prefix: Some(source.as_bytes()),
3085 })?;
3086 require_test(
3087 detected.map(|result| result.language) == Some(expected),
3088 format!("valid versioned interpreter {interpreter:?} was not classified"),
3089 )?;
3090 }
3091
3092 for near_miss in ["python.", "python..3", "ruby...", "lua."] {
3093 let source = format!("#!/usr/bin/env {near_miss}\n");
3094 let detected = detect_language_request(LanguageDetectionRequest {
3095 path: "tool",
3096 extension: None,
3097 explicit_override: None,
3098 content_prefix: Some(source.as_bytes()),
3099 })?;
3100 require_test(
3101 detected.is_none(),
3102 format!("invalid versioned interpreter {near_miss:?} was classified"),
3103 )?;
3104 }
3105
3106 let mut split_utf8_after_shebang = b"#!/usr/bin/env python\n".to_vec();
3107 split_utf8_after_shebang.resize(LANGUAGE_CONTENT_DETECTION_MAX_BYTES, b'x');
3108 split_utf8_after_shebang[LANGUAGE_CONTENT_DETECTION_MAX_BYTES - 1] = 0xc3;
3109 let detected = detect_language_request(LanguageDetectionRequest {
3110 path: "tool",
3111 extension: None,
3112 explicit_override: None,
3113 content_prefix: Some(&split_utf8_after_shebang),
3114 })?;
3115 require_test(
3116 detected.map(|result| result.language) == Some("python"),
3117 "valid shebang was lost at a later UTF-8 split boundary",
3118 )?;
3119
3120 let mut after_bound = vec![b' '; LANGUAGE_CONTENT_DETECTION_MAX_BYTES];
3121 after_bound.extend_from_slice(b"#!/usr/bin/env python\n");
3122 let detected = detect_language_request(LanguageDetectionRequest {
3123 path: "tool",
3124 extension: None,
3125 explicit_override: None,
3126 content_prefix: Some(&after_bound),
3127 })?;
3128 require_test(
3129 detected.is_none(),
3130 "content detection inspected bytes beyond its accepted bound",
3131 )
3132 }
3133
3134 #[test]
3135 fn content_rule_changes_advance_registry_digest() {
3136 let baseline = hash_language_registry_with_content_rules(false, CONTENT_DIALECT_RULES);
3137 let mut changed = CONTENT_DIALECT_RULES.to_vec();
3138 changed[0].language = "ruby";
3139 assert_ne!(
3140 baseline,
3141 hash_language_registry_with_content_rules(false, &changed)
3142 );
3143 }
3144
3145 #[test]
3146 fn built_in_tree_sitter_projection_is_registry_derived() {
3147 let projected = builtin_tree_sitter_language_ids();
3148 let expected = LANGUAGE_CAPABILITIES
3149 .iter()
3150 .filter(|capability| {
3151 matches!(capability.symbol_parser, SymbolParserOwner::TreeSitter(_))
3152 })
3153 .count();
3154 assert_eq!(projected.len(), expected);
3155 assert!(projected.contains(&"rust"));
3156 assert!(!projected.contains(&"markdown"));
3157 }
3158
3159 #[test]
3160 fn conflicting_detection_rules_report_both_owners() -> Result<(), Box<dyn Error>> {
3161 let canonical = BTreeSet::from(["python", "rust"]);
3162 let rules = [
3163 LanguageDetectionRule {
3164 value: ".mixed",
3165 language: "rust",
3166 },
3167 LanguageDetectionRule {
3168 value: ".MIXED",
3169 language: "python",
3170 },
3171 ];
3172 let error = validate_rules("extension", &rules, &canonical, true)
3173 .err()
3174 .ok_or_else(|| io::Error::other("case-normalized conflicting owners must fail"))?;
3175 let message = error.to_string();
3176 require_test(
3177 message.contains("rust") && message.contains("python"),
3178 "conflict diagnostic did not name both owners",
3179 )
3180 }
3181}