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