1use blake3::Hasher;
4use serde::Serialize;
5use std::collections::{BTreeMap, BTreeSet};
6use std::error::Error;
7use std::fmt;
8use std::fmt::Write as _;
9use std::path::Path;
10use std::sync::OnceLock;
11
12pub const LANGUAGE_CAPABILITY_REGISTRY_VERSION: u32 = 3;
14
15pub const SEMANTIC_PROVIDER_CONTRACT_VERSION: u32 = 1;
17
18pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION: u32 = 5;
20
21pub const LANGUAGE_DETECTION_POLICY_VERSION: u32 = 1;
23
24pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V1_DIGEST: &str =
29 "58f2e1e6755464d573df998c1c1cecb2d076c829c0dbe04a463ce14a5a239861";
30
31pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V2_DIGEST: &str =
36 "8397f73a7593b849d0e83b3892e4721874ac4a4fd93f62b42dac9ffe166a2a7c";
37
38pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V3_DIGEST: &str =
43 "a4b69ce4aed2ebf8d28f7b237ead76a53e5363e34c8c97ea5980776ea4217ef4";
44
45pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V4_DIGEST: &str =
50 "e9a952d0b3bc2d2c5db832130d85b7cdfd656aaa07ebbafab1505da6b87d9084";
51
52pub const ACCEPTED_LANGUAGE_CAPABILITY_SET_V5_DIGEST: &str =
57 "07a3d2c45a4736bc764e44016a6ba9b7f9ea1b769b0100604702160528679bc7";
58
59pub const LANGUAGE_CONTENT_DETECTION_MAX_BYTES: usize = 512;
61
62pub const OPTIONAL_GRAMMAR_CATALOG: &str = "tree-sitter-language-pack";
64
65pub const OPTIONAL_GRAMMAR_CATALOG_VERSION: &str = "1.13.2";
67
68pub const OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION: &str =
70 "6258abac30304283763a0d2dc8a48cb87fbcf438";
71
72pub const OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS: usize = 150;
74
75pub const BROAD_PARSER_PACK_ID: &str = "broad-parser";
77
78pub const LANGUAGE_REGISTRY_REPORT_MAX_BYTES: usize = 32_000;
80
81#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
83#[serde(rename_all = "kebab-case")]
84pub enum LanguageParserSupport {
85 Native,
87 Manifest,
89 Structural,
91 Fallback,
93}
94
95#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
97#[serde(rename_all = "kebab-case")]
98pub enum CapabilitySupportLevel {
99 Unavailable,
101 Fallback,
103 Supported,
105}
106
107impl CapabilitySupportLevel {
108 #[must_use]
110 pub const fn as_str(self) -> &'static str {
111 match self {
112 Self::Unavailable => "unavailable",
113 Self::Fallback => "fallback",
114 Self::Supported => "supported",
115 }
116 }
117}
118
119#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
121pub struct LanguageCapabilitySupport {
122 pub detected: CapabilitySupportLevel,
124 pub parsed: CapabilitySupportLevel,
126 pub symbols: CapabilitySupportLevel,
128 pub semantic: CapabilitySupportLevel,
130 pub benchmarked: CapabilitySupportLevel,
132}
133
134impl LanguageCapabilitySupport {
135 #[must_use]
137 pub const fn new(
138 detected: CapabilitySupportLevel,
139 parsed: CapabilitySupportLevel,
140 symbols: CapabilitySupportLevel,
141 semantic: CapabilitySupportLevel,
142 benchmarked: CapabilitySupportLevel,
143 ) -> Self {
144 Self {
145 detected,
146 parsed,
147 symbols,
148 semantic,
149 benchmarked,
150 }
151 }
152
153 #[must_use]
155 pub const fn meets(self, minimum: Self) -> bool {
156 self.detected as u8 >= minimum.detected as u8
157 && self.parsed as u8 >= minimum.parsed as u8
158 && self.symbols as u8 >= minimum.symbols as u8
159 && self.semantic as u8 >= minimum.semantic as u8
160 && self.benchmarked as u8 >= minimum.benchmarked as u8
161 }
162}
163
164const SUPPORTED_NATIVE: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
166 CapabilitySupportLevel::Supported,
167 CapabilitySupportLevel::Supported,
168 CapabilitySupportLevel::Supported,
169 CapabilitySupportLevel::Unavailable,
170 CapabilitySupportLevel::Unavailable,
171);
172
173const SUPPORTED_SEMANTIC: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
175 CapabilitySupportLevel::Supported,
176 CapabilitySupportLevel::Supported,
177 CapabilitySupportLevel::Supported,
178 CapabilitySupportLevel::Supported,
179 CapabilitySupportLevel::Unavailable,
180);
181
182const SUPPORTED_STRUCTURAL: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
184 CapabilitySupportLevel::Supported,
185 CapabilitySupportLevel::Supported,
186 CapabilitySupportLevel::Unavailable,
187 CapabilitySupportLevel::Unavailable,
188 CapabilitySupportLevel::Unavailable,
189);
190
191const SUPPORTED_FALLBACK: LanguageCapabilitySupport = LanguageCapabilitySupport::new(
193 CapabilitySupportLevel::Supported,
194 CapabilitySupportLevel::Fallback,
195 CapabilitySupportLevel::Fallback,
196 CapabilitySupportLevel::Unavailable,
197 CapabilitySupportLevel::Unavailable,
198);
199
200#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
202#[serde(rename_all = "kebab-case")]
203pub enum TreeSitterGrammar {
204 Rust,
206 Python,
208 JavaScript,
210 TypeScript,
212 Tsx,
214 Java,
216 Kotlin,
218 CSharp,
220 Go,
222 ObjectiveC,
224 Zig,
226 C,
228 Cpp,
230}
231
232impl TreeSitterGrammar {
233 pub const ALL: &'static [Self] = &[
235 Self::Rust,
236 Self::Python,
237 Self::JavaScript,
238 Self::TypeScript,
239 Self::Tsx,
240 Self::Java,
241 Self::Kotlin,
242 Self::CSharp,
243 Self::Go,
244 Self::ObjectiveC,
245 Self::Zig,
246 Self::C,
247 Self::Cpp,
248 ];
249
250 #[must_use]
252 pub const fn package(self) -> &'static str {
253 match self {
254 Self::Rust => "tree-sitter-rust",
255 Self::Python => "tree-sitter-python",
256 Self::JavaScript => "tree-sitter-javascript",
257 Self::TypeScript | Self::Tsx => "tree-sitter-typescript",
258 Self::Java => "tree-sitter-java",
259 Self::Kotlin => "tree-sitter-kotlin-ng",
260 Self::CSharp => "tree-sitter-c-sharp",
261 Self::Go => "tree-sitter-go",
262 Self::ObjectiveC => "tree-sitter-objc",
263 Self::Zig => "tree-sitter-zig",
264 Self::C => "tree-sitter-c",
265 Self::Cpp => "tree-sitter-cpp",
266 }
267 }
268
269 #[must_use]
271 pub const fn version(self) -> &'static str {
272 match self {
273 Self::Rust | Self::C => "0.24.2",
274 Self::Python | Self::JavaScript | Self::Go => "0.25.0",
275 Self::TypeScript | Self::Tsx => "0.23.2",
276 Self::Java | Self::CSharp => "0.23.5",
277 Self::Kotlin => "1.1.0",
278 Self::ObjectiveC => "3.0.2",
279 Self::Zig => "1.1.2",
280 Self::Cpp => "0.23.4",
281 }
282 }
283}
284
285#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
287#[serde(rename_all = "kebab-case")]
288pub enum SymbolParserOwner {
289 TreeSitter(TreeSitterGrammar),
291 CargoManifest,
293 Vue,
295 PowerShell,
297 Fallback,
299 Unavailable,
301}
302
303#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
305#[serde(rename_all = "kebab-case")]
306pub enum SemanticProviderOwner {
307 Rust,
309 EcmaScript,
311 Python,
313 Cargo,
315 Unavailable,
317}
318
319impl SemanticProviderOwner {
320 #[must_use]
322 pub const fn as_str(self) -> &'static str {
323 match self {
324 Self::Rust => "rust",
325 Self::EcmaScript => "ecma-script",
326 Self::Python => "python",
327 Self::Cargo => "cargo",
328 Self::Unavailable => "unavailable",
329 }
330 }
331
332 #[must_use]
338 pub const fn resolution_family(self) -> Option<&'static str> {
339 match self {
340 Self::Rust => Some("rust"),
341 Self::EcmaScript => Some("ecmascript"),
342 Self::Python => Some("python"),
343 Self::Cargo => Some("cargo"),
344 Self::Unavailable => None,
345 }
346 }
347}
348
349#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
351#[serde(rename_all = "kebab-case")]
352pub enum EmbeddedHostKind {
353 HtmlLike,
355 Component,
357 Template,
359}
360
361impl EmbeddedHostKind {
362 #[must_use]
364 pub const fn as_str(self) -> &'static str {
365 match self {
366 Self::HtmlLike => "html-like",
367 Self::Component => "component",
368 Self::Template => "template",
369 }
370 }
371}
372
373#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
375pub struct EmbeddedLanguageCapability {
376 pub host_kind: EmbeddedHostKind,
378 pub semantic_provider: SemanticProviderOwner,
380}
381
382#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
384#[serde(rename_all = "kebab-case")]
385pub enum StructuralSummaryOwner {
386 Markdown,
388 Json,
390 Yaml,
392 Toml,
394 Xml,
396 Css,
398 Html,
400 Toon,
402 PowerShell,
404 ConfigText,
406}
407
408#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
410#[serde(rename_all = "kebab-case")]
411pub enum CapabilityProvenance {
412 ProjectAtlas,
414 TreeSitter(TreeSitterGrammar),
416 PinnedOptionalCatalog,
421}
422
423impl CapabilityProvenance {
424 #[must_use]
426 pub const fn license(self) -> &'static str {
427 match self {
428 Self::ProjectAtlas | Self::TreeSitter(_) | Self::PinnedOptionalCatalog => "MIT",
429 }
430 }
431
432 #[must_use]
434 pub const fn source(self) -> &'static str {
435 match self {
436 Self::ProjectAtlas => "projectatlas",
437 Self::TreeSitter(grammar) => grammar.package(),
438 Self::PinnedOptionalCatalog => OPTIONAL_GRAMMAR_CATALOG,
439 }
440 }
441
442 #[must_use]
444 pub const fn version(self) -> &'static str {
445 match self {
446 Self::ProjectAtlas => env!("CARGO_PKG_VERSION"),
447 Self::TreeSitter(grammar) => grammar.version(),
448 Self::PinnedOptionalCatalog => OPTIONAL_GRAMMAR_CATALOG_VERSION,
449 }
450 }
451}
452
453#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
455#[serde(rename_all = "kebab-case")]
456pub enum RequiredPlatformSet {
457 AllSupported,
459}
460
461#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
463pub struct LanguageCapabilityFixtures {
464 pub positive_path: &'static str,
466 pub negative_path: &'static str,
468}
469
470#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
472pub struct LanguageCapability {
473 pub id: &'static str,
475 pub aliases: &'static [&'static str],
477 pub parser_support: LanguageParserSupport,
479 pub symbol_parser: SymbolParserOwner,
481 pub semantic_provider: SemanticProviderOwner,
483 pub embedded_language: Option<EmbeddedLanguageCapability>,
485 pub structural_summary: Option<StructuralSummaryOwner>,
487 pub optional_pack: Option<&'static str>,
489 pub support: LanguageCapabilitySupport,
491 pub accepted_minimum: LanguageCapabilitySupport,
493 pub fixtures: LanguageCapabilityFixtures,
495 pub provenance: CapabilityProvenance,
497 pub required_platforms: RequiredPlatformSet,
499}
500
501impl LanguageCapability {
502 #[must_use]
508 pub const fn effective_semantic_provider(self) -> Option<SemanticProviderOwner> {
509 match self.semantic_provider {
510 SemanticProviderOwner::Unavailable => match self.embedded_language {
511 Some(embedded)
512 if !matches!(
513 embedded.semantic_provider,
514 SemanticProviderOwner::Unavailable
515 ) =>
516 {
517 Some(embedded.semantic_provider)
518 }
519 _ => None,
520 },
521 provider => Some(provider),
522 }
523 }
524}
525
526#[derive(Clone, Copy, Debug, Eq, PartialEq)]
528pub struct LanguageSpec {
529 pub language: &'static str,
531 pub parser_support: LanguageParserSupport,
533}
534
535#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
537#[serde(rename_all = "kebab-case")]
538pub enum LanguageDetectionReason {
539 ExplicitOverride,
541 ExactFilename,
543 CompoundExtension,
545 Extension,
547 ContentDialect,
549}
550
551#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
553pub struct LanguageDetection {
554 pub language: &'static str,
556 pub reason: LanguageDetectionReason,
558}
559
560#[derive(Clone, Copy, Debug, Default)]
562pub struct LanguageDetectionRequest<'a> {
563 pub path: &'a str,
565 pub extension: Option<&'a str>,
567 pub explicit_override: Option<&'a str>,
569 pub content_prefix: Option<&'a [u8]>,
571}
572
573impl<'a> LanguageDetectionRequest<'a> {
574 #[must_use]
576 pub const fn new(path: &'a str, extension: Option<&'a str>) -> Self {
577 Self {
578 path,
579 extension,
580 explicit_override: None,
581 content_prefix: None,
582 }
583 }
584}
585
586#[derive(Clone, Debug, Eq, PartialEq)]
588pub struct LanguageDetectionError {
589 requested: String,
591}
592
593impl fmt::Display for LanguageDetectionError {
594 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
595 write!(
596 formatter,
597 "unknown explicit language override {:?}",
598 self.requested
599 )
600 }
601}
602
603impl Error for LanguageDetectionError {}
604
605#[derive(Clone, Debug, Eq, PartialEq)]
607pub struct LanguageRegistryError {
608 message: String,
610}
611
612impl LanguageRegistryError {
613 fn new(message: impl Into<String>) -> Self {
615 Self {
616 message: message.into(),
617 }
618 }
619}
620
621impl fmt::Display for LanguageRegistryError {
622 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
623 formatter.write_str(&self.message)
624 }
625}
626
627impl Error for LanguageRegistryError {}
628
629#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
631pub struct LanguageDetectionRule {
632 pub value: &'static str,
634 pub language: &'static str,
636}
637
638#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
640pub struct LanguageContentRule {
641 pub interpreter: &'static str,
643 pub allow_version_suffix: bool,
645 pub language: &'static str,
647}
648
649macro_rules! semantic_provider_or_unavailable {
651 () => {
652 SemanticProviderOwner::Unavailable
653 };
654 ($provider:expr) => {
655 $provider
656 };
657}
658
659macro_rules! embedded_language_or_none {
661 () => {
662 None
663 };
664 ($capability:expr) => {
665 Some($capability)
666 };
667}
668
669macro_rules! define_language_registry {
671 (
672 capabilities {
673 $(
674 $id:literal => {
675 aliases: [$($alias:literal),* $(,)?],
676 parser_support: $parser_support:ident,
677 symbol_parser: $symbol_parser:expr,
678 structural_summary: $structural_summary:expr,
679 support: $support:expr,
680 $(semantic_provider: $semantic_provider:expr,)?
681 $(embedded_language: $embedded_language:expr,)?
682 positive: $positive:literal,
683 negative: $negative:literal,
684 provenance: $provenance:expr
685 }
686 ),* $(,)?
687 }
688 optional_capabilities {
689 $(
690 $optional_id:literal => {
691 aliases: [$($optional_alias:literal),* $(,)?],
692 extension: $optional_extension:literal
693 }
694 ),* $(,)?
695 }
696 exact_filenames { $($exact:literal => $exact_language:literal),* $(,)? }
697 compound_extensions { $($compound:literal => $compound_language:literal),* $(,)? }
698 broad_extensions { $($extension:literal => $extension_language:literal),* $(,)? }
699 additional_extensions { $($additional:literal => $additional_language:literal),* $(,)? }
700 content_interpreters {
701 $(
702 $interpreter:literal => {
703 language: $content_language:literal,
704 version_suffix: $version_suffix:literal
705 }
706 ),* $(,)?
707 }
708 ) => {
709 pub const LANGUAGE_CAPABILITIES: &[LanguageCapability] = &[
711 $(LanguageCapability {
712 id: $id,
713 aliases: &[$($alias),*],
714 parser_support: LanguageParserSupport::$parser_support,
715 symbol_parser: $symbol_parser,
716 semantic_provider: semantic_provider_or_unavailable!($($semantic_provider)?),
717 embedded_language: embedded_language_or_none!($($embedded_language)?),
718 structural_summary: $structural_summary,
719 optional_pack: None,
720 support: $support,
721 accepted_minimum: $support,
722 fixtures: LanguageCapabilityFixtures {
723 positive_path: $positive,
724 negative_path: $negative,
725 },
726 provenance: $provenance,
727 required_platforms: RequiredPlatformSet::AllSupported,
728 }),*,
729 $(LanguageCapability {
730 id: $optional_id,
731 aliases: &[$($optional_alias),*],
732 parser_support: LanguageParserSupport::Fallback,
733 symbol_parser: SymbolParserOwner::Fallback,
734 semantic_provider: SemanticProviderOwner::Unavailable,
735 embedded_language: None,
736 structural_summary: None,
737 optional_pack: Some(BROAD_PARSER_PACK_ID),
738 support: SUPPORTED_FALLBACK,
739 accepted_minimum: SUPPORTED_FALLBACK,
740 fixtures: LanguageCapabilityFixtures {
741 positive_path: concat!("fixture", $optional_extension),
742 negative_path: concat!("fixture", $optional_extension, ".bak"),
743 },
744 provenance: CapabilityProvenance::PinnedOptionalCatalog,
745 required_platforms: RequiredPlatformSet::AllSupported,
746 }),*
747 ];
748
749 pub const LANGUAGE_SPECS: &[LanguageSpec] = &[
751 $(LanguageSpec {
752 language: $id,
753 parser_support: LanguageParserSupport::$parser_support,
754 }),*,
755 $(LanguageSpec {
756 language: $optional_id,
757 parser_support: LanguageParserSupport::Fallback,
758 }),*
759 ];
760
761 pub const BROAD_SOURCE_EXTENSIONS: &[&str] = &[$($extension),*];
763
764 pub const DETECTED_SOURCE_EXTENSIONS: &[&str] = &[
766 $($extension),*,
767 $($optional_extension),*
768 ];
769
770 pub const EXACT_FILENAME_RULES: &[LanguageDetectionRule] = &[
772 $(LanguageDetectionRule { value: $exact, language: $exact_language }),*
773 ];
774
775 pub const COMPOUND_EXTENSION_RULES: &[LanguageDetectionRule] = &[
777 $(LanguageDetectionRule { value: $compound, language: $compound_language }),*
778 ];
779
780 pub const EXTENSION_RULES: &[LanguageDetectionRule] = &[
782 $(LanguageDetectionRule { value: $extension, language: $extension_language }),*,
783 $(LanguageDetectionRule { value: $optional_extension, language: $optional_id }),*,
784 $(LanguageDetectionRule { value: $additional, language: $additional_language }),*
785 ];
786
787 pub const CONTENT_DIALECT_RULES: &[LanguageContentRule] = &[
789 $(LanguageContentRule {
790 interpreter: $interpreter,
791 allow_version_suffix: $version_suffix,
792 language: $content_language,
793 }),*
794 ];
795
796 fn exact_filename_language(file_name: &str) -> Option<&'static str> {
797 match file_name {
798 $($exact => Some($exact_language),)*
799 _ => None,
800 }
801 }
802
803 fn extension_language(extension: &str) -> Option<&'static str> {
804 match extension {
805 $($extension => Some($extension_language),)*
806 $($optional_extension => Some($optional_id),)*
807 $($additional => Some($additional_language),)*
808 _ => None,
809 }
810 }
811
812 #[must_use]
814 pub fn canonical_language_id(value: &str) -> Option<&'static str> {
815 let trimmed = value.trim();
816 if let Some(canonical) = canonical_language_id_exact(trimmed) {
817 return Some(canonical);
818 }
819 let normalized = trimmed.to_ascii_lowercase();
820 canonical_language_id_exact(&normalized)
821 }
822
823 fn canonical_language_id_exact(value: &str) -> Option<&'static str> {
825 match value {
826 $($id $(| $alias)* => Some($id),)*
827 $($optional_id $(| $optional_alias)* => Some($optional_id),)*
828 _ => None,
829 }
830 }
831 };
832}
833
834define_language_registry! {
835 capabilities {
836 "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) },
837 "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) },
838 "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) },
839 "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) },
840 "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) },
841 "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) },
842 "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) },
843 "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) },
844 "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) },
845 "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) },
846 "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) },
847 "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) },
848 "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) },
849 "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) },
850 "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) },
851 "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) },
852 "cargo-manifest" => { aliases: [], 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 },
853 "cargo-lock" => { aliases: [], parser_support: Manifest, symbol_parser: SymbolParserOwner::CargoManifest, structural_summary: None, support: SUPPORTED_NATIVE, positive: "Cargo.lock", negative: "Cargo.lock.bak", provenance: CapabilityProvenance::ProjectAtlas },
854 "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 },
855 "markdown" => { aliases: ["md"], parser_support: Structural, symbol_parser: SymbolParserOwner::Unavailable, structural_summary: Some(StructuralSummaryOwner::Markdown), support: SUPPORTED_STRUCTURAL, positive: "fixture.md", negative: "fixture.md.bak", provenance: CapabilityProvenance::ProjectAtlas },
856 "json" => { aliases: [], 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 },
857 "yaml" => { aliases: ["yml"], 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 },
858 "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 },
859 "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 },
860 "toon" => { aliases: [], 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 },
861 "dockerfile" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "Dockerfile", negative: "Dockerfile.bak", provenance: CapabilityProvenance::ProjectAtlas },
862 "makefile" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "Makefile", negative: "Makefile.bak", provenance: CapabilityProvenance::ProjectAtlas },
863 "text" => { aliases: ["txt"], 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 },
864 "toml" => { aliases: [], 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 },
865 "xml" => { aliases: [], 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 },
866 "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 },
867 "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 },
868 "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 },
869 "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 },
870 "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 },
871 "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 },
872 "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 },
873 "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 },
874 "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 },
875 "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 },
876 "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 },
877 "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 },
878 "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 },
879 "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 },
880 "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 },
881 "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 },
882 "config" => { aliases: [], 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 },
883 "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 },
884 "php" => { aliases: [], parser_support: Fallback, symbol_parser: SymbolParserOwner::Fallback, structural_summary: None, support: SUPPORTED_FALLBACK, positive: "fixture.php", negative: "fixture.php.bak", provenance: CapabilityProvenance::ProjectAtlas },
885 "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 },
886 "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 },
887 "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 },
888 "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 },
889 "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 },
890 "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 },
891 "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 },
892 "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 },
893 "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 },
894 "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 },
895 "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 },
896 "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 },
897 "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 },
898 "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 }
899 }
900 optional_capabilities {
901 "abl" => { aliases: [], extension: ".p" },
902 "actionscript" => { aliases: [], extension: ".as" },
903 "ada" => { aliases: [], extension: ".ada" },
904 "agda" => { aliases: [], extension: ".agda" },
905 "al" => { aliases: [], extension: ".al" },
906 "arduino" => { aliases: [], extension: ".ino" },
907 "asciidoc" => { aliases: [], extension: ".adoc" },
908 "asm" => { aliases: [], extension: ".s" },
909 "awk" => { aliases: [], extension: ".awk" },
910 "beancount" => { aliases: [], extension: ".beancount" },
911 "bibtex" => { aliases: [], extension: ".bib" },
912 "bicep" => { aliases: [], extension: ".bicep" },
913 "bitbake" => { aliases: [], extension: ".bb" },
914 "blade" => { aliases: [], extension: ".blade" },
915 "brightscript" => { aliases: [], extension: ".brs" },
916 "bsl" => { aliases: [], extension: ".bsl" },
917 "c3" => { aliases: [], extension: ".c3" },
918 "caddy" => { aliases: [], extension: ".caddyfile" },
919 "cairo" => { aliases: [], extension: ".cairo" },
920 "capnp" => { aliases: [], extension: ".capnp" },
921 "cedar" => { aliases: [], extension: ".cedar" },
922 "cedarschema" => { aliases: [], extension: ".cedarschema" },
923 "cel" => { aliases: [], extension: ".cel" },
924 "cfml" => { aliases: [], extension: ".cfc" },
925 "chatito" => { aliases: [], extension: ".chatito" },
926 "chuck" => { aliases: [], extension: ".ck" },
927 "circom" => { aliases: [], extension: ".circom" },
928 "clarity" => { aliases: [], extension: ".clar" },
929 "cmake" => { aliases: [], extension: ".cmake" },
930 "cobol" => { aliases: [], extension: ".cobol" },
931 "commonlisp" => { aliases: [], extension: ".lisp" },
932 "cooklang" => { aliases: [], extension: ".cook" },
933 "corn" => { aliases: [], extension: ".corn" },
934 "cpon" => { aliases: [], extension: ".cpon" },
935 "crystal" => { aliases: [], extension: ".cr" },
936 "cst" => { aliases: [], extension: ".cst" },
937 "csv" => { aliases: [], extension: ".csv" },
938 "cuda" => { aliases: [], extension: ".cu" },
939 "cue" => { aliases: [], extension: ".cue" },
940 "cylc" => { aliases: [], extension: ".cylc" },
941 "d" => { aliases: [], extension: ".d" },
942 "desktop" => { aliases: [], extension: ".desktop" },
943 "devicetree" => { aliases: [], extension: ".dts" },
944 "dhall" => { aliases: [], extension: ".dhall" },
945 "diff" => { aliases: [], extension: ".diff" },
946 "djot" => { aliases: [], extension: ".dj" },
947 "dot" => { aliases: [], extension: ".dot" },
948 "dtd" => { aliases: [], extension: ".dtd" },
949 "ebnf" => { aliases: [], extension: ".ebnf" },
950 "eds" => { aliases: [], extension: ".eds" },
951 "eex" => { aliases: [], extension: ".eex" },
952 "elisp" => { aliases: [], extension: ".el" },
953 "elixir" => { aliases: [], extension: ".ex" },
954 "elm" => { aliases: [], extension: ".elm" },
955 "elsa" => { aliases: [], extension: ".lc" },
956 "elvish" => { aliases: [], extension: ".elv" },
957 "enforce" => { aliases: [], extension: ".enforce" },
958 "erlang" => { aliases: [], extension: ".erl" },
959 "facility" => { aliases: [], extension: ".fsd" },
960 "faust" => { aliases: [], extension: ".dsp" },
961 "fennel" => { aliases: [], extension: ".fnl" },
962 "fidl" => { aliases: [], extension: ".fidl" },
963 "firrtl" => { aliases: [], extension: ".fir" },
964 "fish" => { aliases: [], extension: ".fish" },
965 "forth" => { aliases: [], extension: ".fth" },
966 "fortran" => { aliases: [], extension: ".f90" },
967 "fsharp_signature" => { aliases: [], extension: ".fsi" },
968 "func" => { aliases: [], extension: ".fc" },
969 "gap" => { aliases: [], extension: ".g" },
970 "gdscript" => { aliases: [], extension: ".gd" },
971 "gdshader" => { aliases: [], extension: ".gdshader" },
972 "gherkin" => { aliases: [], extension: ".feature" },
973 "gitattributes" => { aliases: [], extension: ".gitattributes" },
974 "gleam" => { aliases: [], extension: ".gleam" },
975 "glsl" => { aliases: [], extension: ".glsl" },
976 "gn" => { aliases: [], extension: ".gn" },
977 "gnuplot" => { aliases: [], extension: ".gp" },
978 "godot_resource" => { aliases: [], extension: ".tres" },
979 "gomod" => { aliases: [], extension: ".mod" },
980 "gotmpl" => { aliases: [], extension: ".gotmpl" },
981 "gren" => { aliases: [], extension: ".gren" },
982 "hack" => { aliases: [], extension: ".hack" },
983 "hare" => { aliases: [], extension: ".hare" },
984 "haxe" => { aliases: [], extension: ".hx" },
985 "hcl" => { aliases: [], extension: ".hcl" },
986 "heex" => { aliases: [], extension: ".heex" },
987 "hjson" => { aliases: [], extension: ".hjson" },
988 "hlsl" => { aliases: [], extension: ".hlsl" },
989 "hocon" => { aliases: [], extension: ".hocon" },
990 "hoon" => { aliases: [], extension: ".hoon" },
991 "http" => { aliases: [], extension: ".http" },
992 "hurl" => { aliases: [], extension: ".hurl" },
993 "idris" => { aliases: [], extension: ".idr" },
994 "ispc" => { aliases: [], extension: ".ispc" },
995 "jai" => { aliases: [], extension: ".jai" },
996 "janet" => { aliases: [], extension: ".janet" },
997 "jinja2" => { aliases: [], extension: ".j2" },
998 "jq" => { aliases: [], extension: ".jq" },
999 "json5" => { aliases: [], extension: ".json5" },
1000 "jsonnet" => { aliases: [], extension: ".jsonnet" },
1001 "julia" => { aliases: [], extension: ".jl" },
1002 "just" => { aliases: [], extension: ".just" },
1003 "kcl" => { aliases: [], extension: ".k" },
1004 "kdl" => { aliases: [], extension: ".kdl" },
1005 "latex" => { aliases: [], extension: ".tex" },
1006 "lean" => { aliases: [], extension: ".lean" },
1007 "ledger" => { aliases: [], extension: ".ldg" },
1008 "linkerscript" => { aliases: [], extension: ".lds" },
1009 "llvm" => { aliases: [], extension: ".ll" },
1010 "luau" => { aliases: [], extension: ".luau" },
1011 "magik" => { aliases: [], extension: ".magik" },
1012 "make" => { aliases: [], extension: ".mk" },
1013 "matlab" => { aliases: [], extension: ".matlab" },
1014 "mermaid" => { aliases: [], extension: ".mmd" },
1015 "meson" => { aliases: [], extension: ".meson" },
1016 "mlir" => { aliases: [], extension: ".mlir" },
1017 "mojo" => { aliases: [], extension: ".mojo" },
1018 "move" => { aliases: [], extension: ".move" },
1019 "nasm" => { aliases: [], extension: ".nasm" },
1020 "netlinx" => { aliases: [], extension: ".axs" },
1021 "nginx" => { aliases: [], extension: ".nginx" },
1022 "nickel" => { aliases: [], extension: ".ncl" },
1023 "nim" => { aliases: [], extension: ".nim" },
1024 "ninja" => { aliases: [], extension: ".ninja" },
1025 "nix" => { aliases: [], extension: ".nix" },
1026 "norg" => { aliases: [], extension: ".norg" },
1027 "nqc" => { aliases: [], extension: ".nqc" },
1028 "nushell" => { aliases: [], extension: ".nu" },
1029 "ocamllex" => { aliases: [], extension: ".mll" },
1030 "odin" => { aliases: [], extension: ".odin" },
1031 "openscad" => { aliases: [], extension: ".scad" },
1032 "org" => { aliases: [], extension: ".org" },
1033 "pascal" => { aliases: [], extension: ".pas" },
1034 "pem" => { aliases: [], extension: ".pem" },
1035 "pgn" => { aliases: [], extension: ".pgn" },
1036 "pkl" => { aliases: [], extension: ".pkl" },
1037 "po" => { aliases: [], extension: ".po" },
1038 "poe_filter" => { aliases: [], extension: ".filter" },
1039 "pony" => { aliases: [], extension: ".pony" },
1040 "postscript" => { aliases: [], extension: ".ps" },
1041 "prisma" => { aliases: [], extension: ".prisma" },
1042 "prolog" => { aliases: [], extension: ".pro" },
1043 "promql" => { aliases: [], extension: ".promql" },
1044 "prql" => { aliases: [], extension: ".prql" },
1045 "psv" => { aliases: [], extension: ".psv" },
1046 "puppet" => { aliases: [], extension: ".pp" },
1047 "purescript" => { aliases: [], extension: ".purs" },
1048 "ql" => { aliases: [], extension: ".ql" },
1049 "qmljs" => { aliases: [], extension: ".qml" },
1050 "racket" => { aliases: [], extension: ".rkt" },
1051 "rasi" => { aliases: [], extension: ".rasi" },
1052 "razor" => { aliases: [], extension: ".razor" },
1053 "rbs" => { aliases: [], extension: ".rbs" },
1054 "re2c" => { aliases: [], extension: ".re" },
1055 "rego" => { aliases: [], extension: ".rego" },
1056 "rescript" => { aliases: [], extension: ".res" },
1057 "robot" => { aliases: [], extension: ".robot" },
1058 "roc" => { aliases: [], extension: ".roc" },
1059 "ron" => { aliases: [], extension: ".ron" },
1060 "rst" => { aliases: [], extension: ".rst" },
1061 "rtf" => { aliases: [], extension: ".rtf" },
1062 "scheme" => { aliases: [], extension: ".scm" },
1063 "slang" => { aliases: [], extension: ".slang" },
1064 "smali" => { aliases: [], extension: ".smali" },
1065 "smalltalk" => { aliases: [], extension: ".st" },
1066 "smithy" => { aliases: [], extension: ".smithy" },
1067 "sml" => { aliases: [], extension: ".sml" },
1068 "snakemake" => { aliases: [], extension: ".smk" },
1069 "solidity" => { aliases: [], extension: ".sol" },
1070 "souffle" => { aliases: [], extension: ".dl" },
1071 "sourcepawn" => { aliases: [], extension: ".sp" },
1072 "sql_bigquery" => { aliases: [], extension: ".bq" },
1073 "squirrel" => { aliases: [], extension: ".squirrel" },
1074 "stan" => { aliases: [], extension: ".stan" },
1075 "starlark" => { aliases: [], extension: ".star" },
1076 "superhtml" => { aliases: [], extension: ".shtml" },
1077 "sway" => { aliases: [], extension: ".sw" },
1078 "systemverilog" => { aliases: [], extension: ".sv" },
1079 "tablegen" => { aliases: [], extension: ".td" },
1080 "tact" => { aliases: [], extension: ".tact" },
1081 "tcl" => { aliases: [], extension: ".tcl" },
1082 "teal" => { aliases: [], extension: ".tl" },
1083 "templ" => { aliases: [], extension: ".templ" },
1084 "tera" => { aliases: [], extension: ".tera" },
1085 "terraform" => { aliases: [], extension: ".tf" },
1086 "textproto" => { aliases: [], extension: ".textproto" },
1087 "thrift" => { aliases: [], extension: ".thrift" },
1088 "tlaplus" => { aliases: [], extension: ".tla" },
1089 "todotxt" => { aliases: [], extension: ".todotxt" },
1090 "tsv" => { aliases: [], extension: ".tsv" },
1091 "turtle" => { aliases: [], extension: ".ttl" },
1092 "twig" => { aliases: [], extension: ".twig" },
1093 "typespec" => { aliases: [], extension: ".tsp" },
1094 "typoscript" => { aliases: [], extension: ".typoscript" },
1095 "typst" => { aliases: [], extension: ".typst" },
1096 "uxntal" => { aliases: [], extension: ".tal" },
1097 "v" => { aliases: [], extension: ".v" },
1098 "vb" => { aliases: [], extension: ".vb" },
1099 "verilog" => { aliases: [], extension: ".verilog" },
1100 "vhdl" => { aliases: [], extension: ".vhdl" },
1101 "vhs" => { aliases: [], extension: ".tape" },
1102 "vrl" => { aliases: [], extension: ".vrl" },
1103 "wast" => { aliases: [], extension: ".wast" },
1104 "wat" => { aliases: [], extension: ".wat" },
1105 "wgsl" => { aliases: [], extension: ".wgsl" },
1106 "wit" => { aliases: [], extension: ".wit" },
1107 "yuck" => { aliases: [], extension: ".yuck" },
1108 "ziggy" => { aliases: [], extension: ".ziggy" },
1109 }
1110 exact_filenames {
1111 "Cargo.toml" => "cargo-manifest",
1112 "Cargo.lock" => "cargo-lock",
1113 "build.rs" => "rust-build-script",
1114 "Dockerfile" => "dockerfile",
1115 "Makefile" => "makefile"
1116 }
1117 compound_extensions { ".d.ts" => "typescript" }
1118 broad_extensions {
1119 ".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"
1120 }
1121 additional_extensions {
1122 ".env" => "config", ".gitignore" => "config", ".dockerignore" => "config", ".editorconfig" => "config"
1123 }
1124 content_interpreters {
1125 "python" => { language: "python", version_suffix: true },
1126 "pythonw" => { language: "python", version_suffix: true },
1127 "node" => { language: "javascript", version_suffix: false },
1128 "deno" => { language: "javascript", version_suffix: false },
1129 "powershell" => { language: "powershell", version_suffix: false },
1130 "pwsh" => { language: "powershell", version_suffix: false },
1131 "ruby" => { language: "ruby", version_suffix: true },
1132 "perl" => { language: "perl", version_suffix: true },
1133 "lua" => { language: "lua", version_suffix: true },
1134 "rscript" => { language: "r", version_suffix: false },
1135 "sh" => { language: "shell", version_suffix: false },
1136 "bash" => { language: "shell", version_suffix: false },
1137 "dash" => { language: "shell", version_suffix: false },
1138 "ash" => { language: "shell", version_suffix: false },
1139 "zsh" => { language: "shell", version_suffix: false },
1140 "ksh" => { language: "shell", version_suffix: false },
1141 "mksh" => { language: "shell", version_suffix: false },
1142 "fish" => { language: "shell", version_suffix: false }
1143 }
1144}
1145
1146#[must_use]
1148pub fn language_spec(language: &str) -> Option<&'static LanguageSpec> {
1149 let canonical = canonical_language_id(language)?;
1150 LANGUAGE_SPECS
1151 .iter()
1152 .find(|spec| spec.language == canonical)
1153}
1154
1155#[must_use]
1157pub fn language_capability(language: &str) -> Option<&'static LanguageCapability> {
1158 static BY_ID: OnceLock<BTreeMap<&'static str, &'static LanguageCapability>> = OnceLock::new();
1159 let canonical = canonical_language_id(language)?;
1160 BY_ID
1161 .get_or_init(|| {
1162 LANGUAGE_CAPABILITIES
1163 .iter()
1164 .map(|capability| (capability.id, capability))
1165 .collect()
1166 })
1167 .get(canonical)
1168 .copied()
1169}
1170
1171#[must_use]
1173pub const fn language_documentation_rows() -> &'static [LanguageCapability] {
1174 LANGUAGE_CAPABILITIES
1175}
1176
1177#[must_use]
1179pub fn tree_sitter_grammar(language: &str) -> Option<TreeSitterGrammar> {
1180 match language_capability(language)?.symbol_parser {
1181 SymbolParserOwner::TreeSitter(grammar) => Some(grammar),
1182 SymbolParserOwner::CargoManifest
1183 | SymbolParserOwner::Vue
1184 | SymbolParserOwner::PowerShell
1185 | SymbolParserOwner::Fallback
1186 | SymbolParserOwner::Unavailable => None,
1187 }
1188}
1189
1190#[must_use]
1192pub fn builtin_tree_sitter_language_ids() -> &'static [&'static str] {
1193 static IDS: OnceLock<Box<[&'static str]>> = OnceLock::new();
1194 IDS.get_or_init(|| {
1195 LANGUAGE_CAPABILITIES
1196 .iter()
1197 .filter_map(|capability| {
1198 matches!(capability.symbol_parser, SymbolParserOwner::TreeSitter(_))
1199 .then_some(capability.id)
1200 })
1201 .collect::<Vec<_>>()
1202 .into_boxed_slice()
1203 })
1204}
1205
1206#[must_use]
1208pub fn compound_language_extension(path: &str) -> Option<&'static str> {
1209 let file_name = path.rsplit(['/', '\\']).next().unwrap_or(path);
1210 let lower = file_name.to_ascii_lowercase();
1211 COMPOUND_EXTENSION_RULES
1212 .iter()
1213 .find(|rule| lower.ends_with(rule.value))
1214 .map(|rule| rule.value)
1215}
1216
1217#[must_use]
1219pub fn normalized_language_extension(path: &Path) -> Option<String> {
1220 let file_name = path.file_name()?.to_string_lossy();
1221 if let Some(compound) = compound_language_extension(&file_name) {
1222 return Some(compound.to_string());
1223 }
1224 path.extension()
1225 .map(|extension| format!(".{}", extension.to_string_lossy().to_lowercase()))
1226}
1227
1228pub fn detect_language_request(
1234 request: LanguageDetectionRequest<'_>,
1235) -> Result<Option<LanguageDetection>, LanguageDetectionError> {
1236 if let Some(explicit) = request.explicit_override {
1237 let Some(language) = canonical_language_id(explicit) else {
1238 return Err(LanguageDetectionError {
1239 requested: explicit.to_string(),
1240 });
1241 };
1242 return Ok(Some(LanguageDetection {
1243 language,
1244 reason: LanguageDetectionReason::ExplicitOverride,
1245 }));
1246 }
1247
1248 let file_name = request
1249 .path
1250 .rsplit(['/', '\\'])
1251 .next()
1252 .unwrap_or(request.path);
1253 if let Some(language) = exact_filename_language(file_name) {
1254 return Ok(Some(LanguageDetection {
1255 language,
1256 reason: LanguageDetectionReason::ExactFilename,
1257 }));
1258 }
1259
1260 let lower_file_name = file_name.to_ascii_lowercase();
1261 if let Some(rule) = COMPOUND_EXTENSION_RULES
1262 .iter()
1263 .find(|rule| lower_file_name.ends_with(rule.value))
1264 {
1265 return Ok(Some(LanguageDetection {
1266 language: rule.language,
1267 reason: LanguageDetectionReason::CompoundExtension,
1268 }));
1269 }
1270
1271 let normalized_extension = request.extension.map(str::to_ascii_lowercase).or_else(|| {
1272 file_name
1273 .rsplit_once('.')
1274 .map(|(_, extension)| format!(".{}", extension.to_ascii_lowercase()))
1275 });
1276 if let Some(extension) = normalized_extension
1277 && let Some(language) = extension_language(&extension)
1278 {
1279 return Ok(Some(LanguageDetection {
1280 language,
1281 reason: LanguageDetectionReason::Extension,
1282 }));
1283 }
1284
1285 Ok(
1286 content_dialect_language(request.content_prefix).map(|language| LanguageDetection {
1287 language,
1288 reason: LanguageDetectionReason::ContentDialect,
1289 }),
1290 )
1291}
1292
1293#[must_use]
1295pub fn detect_language(extension: Option<&str>) -> Option<String> {
1296 detect_language_request(LanguageDetectionRequest::new("", extension))
1297 .ok()
1298 .flatten()
1299 .map(|detected| detected.language.to_string())
1300}
1301
1302#[must_use]
1304pub fn detect_language_for_path(path: &str, extension: Option<&str>) -> Option<String> {
1305 detect_language_request(LanguageDetectionRequest::new(path, extension))
1306 .ok()
1307 .flatten()
1308 .map(|detected| detected.language.to_string())
1309}
1310
1311fn content_dialect_language(content_prefix: Option<&[u8]>) -> Option<&'static str> {
1313 let prefix = content_prefix?;
1314 let bounded = &prefix[..prefix.len().min(LANGUAGE_CONTENT_DETECTION_MAX_BYTES)];
1315 let line_end = bounded
1316 .iter()
1317 .position(|byte| *byte == b'\n')
1318 .unwrap_or(bounded.len());
1319 let first_line = std::str::from_utf8(&bounded[..line_end])
1320 .ok()?
1321 .trim_end_matches('\r');
1322 let shebang = first_line.strip_prefix("#!")?.trim();
1323 let interpreter = shebang_interpreter(shebang)?;
1324 CONTENT_DIALECT_RULES
1325 .iter()
1326 .find(|rule| content_interpreter_matches(&interpreter, rule))
1327 .map(|rule| rule.language)
1328}
1329
1330fn shebang_interpreter(shebang: &str) -> Option<String> {
1332 let mut tokens = shebang.split_ascii_whitespace();
1333 let first = tokens.next()?;
1334 let first_basename = command_basename(first);
1335 let selected = if first_basename.eq_ignore_ascii_case("env") {
1336 tokens.find(|token| !token.starts_with('-') && !token.contains('='))?
1337 } else if first_basename.eq_ignore_ascii_case("busybox") {
1338 tokens.next()?
1339 } else {
1340 first
1341 };
1342 let normalized = command_basename(selected).to_ascii_lowercase();
1343 Some(
1344 normalized
1345 .strip_suffix(".exe")
1346 .unwrap_or(&normalized)
1347 .to_string(),
1348 )
1349}
1350
1351fn command_basename(command: &str) -> &str {
1353 command.rsplit(['/', '\\']).next().unwrap_or(command)
1354}
1355
1356fn content_interpreter_matches(interpreter: &str, rule: &LanguageContentRule) -> bool {
1358 if interpreter == rule.interpreter {
1359 return true;
1360 }
1361 rule.allow_version_suffix
1362 && interpreter
1363 .strip_prefix(rule.interpreter)
1364 .is_some_and(|suffix| {
1365 !suffix.is_empty()
1366 && suffix.split('.').all(|segment| {
1367 !segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_digit())
1368 })
1369 })
1370}
1371
1372#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
1374pub struct CapabilityLevelCounts {
1375 pub unavailable: usize,
1377 pub fallback: usize,
1379 pub supported: usize,
1381}
1382
1383#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
1385pub struct LanguageCapabilityCounts {
1386 pub accepted: usize,
1388 pub built_in: usize,
1390 pub optional_candidates: usize,
1392 pub detected: CapabilityLevelCounts,
1394 pub parsed: CapabilityLevelCounts,
1396 pub symbols: CapabilityLevelCounts,
1398 pub semantic: CapabilityLevelCounts,
1400 pub benchmarked: CapabilityLevelCounts,
1402}
1403
1404#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1406pub struct LanguageCapabilityReportRow {
1407 pub id: &'static str,
1409 pub aliases: &'static [&'static str],
1411 pub exact_filenames: Vec<&'static str>,
1413 pub compound_extensions: Vec<&'static str>,
1415 pub extensions: Vec<&'static str>,
1417 pub content_interpreters: Vec<&'static str>,
1419 pub parser_support: LanguageParserSupport,
1421 pub symbol_parser: SymbolParserOwner,
1423 pub semantic_provider: SemanticProviderOwner,
1425 pub embedded_language: Option<EmbeddedLanguageCapability>,
1427 pub structural_summary: Option<StructuralSummaryOwner>,
1429 pub optional_pack: Option<&'static str>,
1431 pub support: LanguageCapabilitySupport,
1433 pub accepted_minimum: LanguageCapabilitySupport,
1435 pub fixtures: LanguageCapabilityFixtures,
1437 pub provenance_source: &'static str,
1439 pub provenance_version: &'static str,
1441 pub provenance_license: &'static str,
1443 pub required_platforms: RequiredPlatformSet,
1445}
1446
1447#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
1449pub struct LanguageCatalogReport {
1450 pub name: &'static str,
1452 pub version: &'static str,
1454 pub revision: &'static str,
1456 pub metadata_license: &'static str,
1458 pub minimum_additional_grammars: usize,
1460}
1461
1462#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1464pub struct LanguageRegistryReport {
1465 pub registry_version: u32,
1467 pub accepted_set_version: u32,
1469 pub detection_policy_version: u32,
1471 pub registry_digest: String,
1473 pub accepted_set_digest: String,
1475 pub semantic_provider_digest: String,
1477 pub counts: LanguageCapabilityCounts,
1479 pub optional_catalog: LanguageCatalogReport,
1481}
1482
1483#[must_use]
1485pub fn language_registry_report() -> LanguageRegistryReport {
1486 LanguageRegistryReport {
1487 registry_version: LANGUAGE_CAPABILITY_REGISTRY_VERSION,
1488 accepted_set_version: ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION,
1489 detection_policy_version: LANGUAGE_DETECTION_POLICY_VERSION,
1490 registry_digest: language_registry_digest(),
1491 accepted_set_digest: accepted_language_capability_digest(),
1492 semantic_provider_digest: semantic_provider_digest(),
1493 counts: language_capability_counts(),
1494 optional_catalog: LanguageCatalogReport {
1495 name: OPTIONAL_GRAMMAR_CATALOG,
1496 version: OPTIONAL_GRAMMAR_CATALOG_VERSION,
1497 revision: OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION,
1498 metadata_license: "MIT",
1499 minimum_additional_grammars: OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS,
1500 },
1501 }
1502}
1503
1504#[must_use]
1506pub fn language_capability_report_rows() -> Vec<LanguageCapabilityReportRow> {
1507 LANGUAGE_CAPABILITIES
1508 .iter()
1509 .map(|capability| LanguageCapabilityReportRow {
1510 id: capability.id,
1511 aliases: capability.aliases,
1512 exact_filenames: rules_for_language(EXACT_FILENAME_RULES, capability.id),
1513 compound_extensions: rules_for_language(COMPOUND_EXTENSION_RULES, capability.id),
1514 extensions: rules_for_language(EXTENSION_RULES, capability.id),
1515 content_interpreters: CONTENT_DIALECT_RULES
1516 .iter()
1517 .filter_map(|rule| (rule.language == capability.id).then_some(rule.interpreter))
1518 .collect(),
1519 parser_support: capability.parser_support,
1520 symbol_parser: capability.symbol_parser,
1521 semantic_provider: capability.semantic_provider,
1522 embedded_language: capability.embedded_language,
1523 structural_summary: capability.structural_summary,
1524 optional_pack: capability.optional_pack,
1525 support: capability.support,
1526 accepted_minimum: capability.accepted_minimum,
1527 fixtures: capability.fixtures,
1528 provenance_source: capability.provenance.source(),
1529 provenance_version: capability.provenance.version(),
1530 provenance_license: capability.provenance.license(),
1531 required_platforms: capability.required_platforms,
1532 })
1533 .collect()
1534}
1535
1536pub fn render_language_support_markdown() -> Result<String, fmt::Error> {
1542 let report = language_registry_report();
1543 let capability_rows = language_capability_report_rows();
1544 let mut output = String::new();
1545 output.push_str("# ProjectAtlas Language Support\n\n");
1546 output.push_str(
1547 "This document is generated from the versioned Rust language capability registry. \
1548Do not edit the capability table or totals by hand. Canonical rows count once; aliases and \
1549extensions never increase a capability total.\n\n",
1550 );
1551 write!(
1552 &mut output,
1553 "Registry version: `{}`. Accepted capability-set version: `{}`. Detection policy \
1554version: `{}`. \
1555Registry digest: `{}`. Accepted-set digest: `{}`. Semantic-provider digest: `{}`.\n\n",
1556 report.registry_version,
1557 report.accepted_set_version,
1558 report.detection_policy_version,
1559 report.registry_digest,
1560 report.accepted_set_digest,
1561 report.semantic_provider_digest
1562 )?;
1563 write!(
1564 &mut output,
1565 "Optional catalog input: `{}@{}` revision `{}` under `{}` metadata license. \
1566This catalog identity is not a grammar-license or runtime-support claim.\n\n",
1567 report.optional_catalog.name,
1568 report.optional_catalog.version,
1569 report.optional_catalog.revision,
1570 report.optional_catalog.metadata_license
1571 )?;
1572 write!(
1573 &mut output,
1574 "The registry contains **{}** canonical rows: **{}** default-core rows and **{}** \
1575optional-pack candidates. Detection is supported for {} rows. Parsing is supported for {}, \
1576fallback for {}, and unavailable for {}. Symbols are supported for {}, fallback for {}, and \
1577unavailable for {}. Semantic resolution and benchmark coverage are reported independently.\n\n",
1578 report.counts.accepted,
1579 report.counts.built_in,
1580 report.counts.optional_candidates,
1581 report.counts.detected.supported,
1582 report.counts.parsed.supported,
1583 report.counts.parsed.fallback,
1584 report.counts.parsed.unavailable,
1585 report.counts.symbols.supported,
1586 report.counts.symbols.fallback,
1587 report.counts.symbols.unavailable
1588 )?;
1589 output.push_str(
1590 "Rows marked `broad-parser` are detected and, when explicitly admitted to the scan \
1591policy, remain usable through the conservative default-core fallback while the optional pack is \
1592absent. Catalog recognition alone does not add these extensions to the default scan surface. The \
1593pinned catalog is provenance for detection metadata only. A row becomes grammar-backed parsed \
1594support only after its exact \
1595grammar binary, subtree license, ABI/export, fixtures, and every accepted optional-pack target \
1596pass the separate acceptance gates. The v0.4 optional-pack targets are Linux x86-64 and Windows \
1597x86-64; macOS keeps the full built-in surface and reports `unsupported_containment` for optional-\
1598pack activation. Built-in owners always retain precedence.\n\n",
1599 );
1600 output.push_str(
1601 "Broad candidate rows are admitted only when the pinned catalog supplies a stable \
1602canonical grammar identity and at least one ordinary extension that does not conflict with an \
1603already accepted detector owner. Extensionless, ambiguous, duplicate, pseudo, or conflicting \
1604catalog entries remain unadvertised until a separate deterministic rule and evidence exist.\n\n",
1605 );
1606 output.push_str(
1607 "| Language | Aliases | Detection rules | Parser owner | Parsed | Symbols | Semantic | Embedded source | Benchmarked | Optional pack | Provenance | License |\n",
1608 );
1609 output.push_str("| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n");
1610 for row in &capability_rows {
1611 let rules = detector_rule_summary(row);
1612 let aliases = list_or_dash(row.aliases.iter().copied());
1613 let optional_pack = row.optional_pack.unwrap_or("—");
1614 let provenance = format!("{}@{}", row.provenance_source, row.provenance_version);
1615 writeln!(
1616 &mut output,
1617 "| `{}` | {} | {} | {} | {} | {} | {} | {} | {} | {} | `{}` | `{}` |",
1618 row.id,
1619 aliases,
1620 rules,
1621 symbol_parser_label(row.symbol_parser),
1622 row.support.parsed.as_str(),
1623 row.support.symbols.as_str(),
1624 semantic_support_label(row.support.semantic, row.semantic_provider),
1625 embedded_language_label(row.embedded_language),
1626 row.support.benchmarked.as_str(),
1627 optional_pack,
1628 provenance,
1629 row.provenance_license
1630 )?;
1631 }
1632 crate::support_catalog::append_support_catalog_markdown(&mut output)?;
1633 Ok(output)
1634}
1635
1636fn detector_rule_summary(row: &LanguageCapabilityReportRow) -> String {
1638 let mut rules = Vec::new();
1639 rules.extend(
1640 row.exact_filenames
1641 .iter()
1642 .map(|value| format!("exact `{value}`")),
1643 );
1644 rules.extend(
1645 row.compound_extensions
1646 .iter()
1647 .map(|value| format!("compound `{value}`")),
1648 );
1649 rules.extend(row.extensions.iter().map(|value| format!("`{value}`")));
1650 rules.extend(
1651 row.content_interpreters
1652 .iter()
1653 .map(|value| format!("shebang `{value}`")),
1654 );
1655 if rules.is_empty() {
1656 "—".to_string()
1657 } else {
1658 rules.join(", ")
1659 }
1660}
1661
1662fn symbol_parser_label(owner: SymbolParserOwner) -> String {
1664 match owner {
1665 SymbolParserOwner::TreeSitter(grammar) => {
1666 format!("{}@{}", grammar.package(), grammar.version())
1667 }
1668 SymbolParserOwner::CargoManifest => "projectatlas:cargo-manifest".to_string(),
1669 SymbolParserOwner::Vue => "projectatlas:vue".to_string(),
1670 SymbolParserOwner::PowerShell => "projectatlas:powershell".to_string(),
1671 SymbolParserOwner::Fallback => "projectatlas:fallback".to_string(),
1672 SymbolParserOwner::Unavailable => "unavailable".to_string(),
1673 }
1674}
1675
1676fn semantic_support_label(support: CapabilitySupportLevel, owner: SemanticProviderOwner) -> String {
1678 match owner {
1679 SemanticProviderOwner::Unavailable => support.as_str().to_string(),
1680 _ => format!("{} ({})", support.as_str(), owner.as_str()),
1681 }
1682}
1683
1684fn embedded_language_label(capability: Option<EmbeddedLanguageCapability>) -> String {
1686 capability.map_or_else(
1687 || "—".to_string(),
1688 |capability| {
1689 format!(
1690 "{} → {}",
1691 capability.host_kind.as_str(),
1692 capability.semantic_provider.as_str()
1693 )
1694 },
1695 )
1696}
1697
1698fn list_or_dash<'a>(values: impl Iterator<Item = &'a str>) -> String {
1700 let values = values.collect::<Vec<_>>();
1701 if values.is_empty() {
1702 "—".to_string()
1703 } else {
1704 values
1705 .into_iter()
1706 .map(|value| format!("`{value}`"))
1707 .collect::<Vec<_>>()
1708 .join(", ")
1709 }
1710}
1711
1712fn rules_for_language(rules: &[LanguageDetectionRule], language: &str) -> Vec<&'static str> {
1714 rules
1715 .iter()
1716 .filter_map(|rule| (rule.language == language).then_some(rule.value))
1717 .collect()
1718}
1719
1720#[must_use]
1722pub fn language_capability_counts() -> LanguageCapabilityCounts {
1723 let mut counts = LanguageCapabilityCounts {
1724 accepted: LANGUAGE_CAPABILITIES.len(),
1725 ..LanguageCapabilityCounts::default()
1726 };
1727 for capability in LANGUAGE_CAPABILITIES {
1728 if capability.optional_pack.is_some() {
1729 counts.optional_candidates += 1;
1730 } else {
1731 counts.built_in += 1;
1732 }
1733 increment_level(&mut counts.detected, capability.support.detected);
1734 increment_level(&mut counts.parsed, capability.support.parsed);
1735 increment_level(&mut counts.symbols, capability.support.symbols);
1736 increment_level(&mut counts.semantic, capability.support.semantic);
1737 increment_level(&mut counts.benchmarked, capability.support.benchmarked);
1738 }
1739 counts
1740}
1741
1742fn increment_level(counts: &mut CapabilityLevelCounts, level: CapabilitySupportLevel) {
1744 match level {
1745 CapabilitySupportLevel::Unavailable => counts.unavailable += 1,
1746 CapabilitySupportLevel::Fallback => counts.fallback += 1,
1747 CapabilitySupportLevel::Supported => counts.supported += 1,
1748 }
1749}
1750
1751#[must_use]
1753pub fn language_registry_digest() -> String {
1754 hash_language_registry(false)
1755}
1756
1757#[must_use]
1759pub fn accepted_language_capability_digest() -> String {
1760 hash_language_registry(true)
1761}
1762
1763#[must_use]
1770pub fn semantic_provider_digest() -> String {
1771 let mut hasher = Hasher::new();
1772 hasher.update(&SEMANTIC_PROVIDER_CONTRACT_VERSION.to_le_bytes());
1773 for capability in LANGUAGE_CAPABILITIES {
1774 let effective_provider = capability.effective_semantic_provider();
1775 if effective_provider.is_none() && capability.embedded_language.is_none() {
1776 continue;
1777 }
1778 hash_value(&mut hasher, capability.id);
1779 hash_value(&mut hasher, capability.semantic_provider.as_str());
1780 hash_value(
1781 &mut hasher,
1782 capability
1783 .semantic_provider
1784 .resolution_family()
1785 .unwrap_or("unavailable"),
1786 );
1787 hash_value(
1788 &mut hasher,
1789 effective_provider
1790 .and_then(SemanticProviderOwner::resolution_family)
1791 .unwrap_or("unavailable"),
1792 );
1793 if let Some(embedded) = capability.embedded_language {
1794 hash_value(&mut hasher, embedded.host_kind.as_str());
1795 hash_value(&mut hasher, embedded.semantic_provider.as_str());
1796 hash_value(
1797 &mut hasher,
1798 embedded
1799 .semantic_provider
1800 .resolution_family()
1801 .unwrap_or("unavailable"),
1802 );
1803 } else {
1804 hash_value(&mut hasher, "no-embedded-provider");
1805 }
1806 hash_value(&mut hasher, capability.support.semantic.as_str());
1807 }
1808 hasher.finalize().to_hex().to_string()
1809}
1810
1811fn hash_language_registry(accepted_only: bool) -> String {
1813 hash_language_registry_with_content_rules(accepted_only, CONTENT_DIALECT_RULES)
1814}
1815
1816fn hash_language_registry_with_content_rules(
1818 accepted_only: bool,
1819 content_rules: &[LanguageContentRule],
1820) -> String {
1821 let mut hasher = Hasher::new();
1822 hasher.update(&LANGUAGE_CAPABILITY_REGISTRY_VERSION.to_le_bytes());
1823 hasher.update(&ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION.to_le_bytes());
1824 hasher.update(&LANGUAGE_DETECTION_POLICY_VERSION.to_le_bytes());
1825 hasher.update(&(LANGUAGE_CONTENT_DETECTION_MAX_BYTES as u64).to_le_bytes());
1826 hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG);
1827 hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG_VERSION);
1828 hash_value(&mut hasher, OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION);
1829 hasher.update(&(OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS as u64).to_le_bytes());
1830 for capability in LANGUAGE_CAPABILITIES {
1831 hash_value(&mut hasher, capability.id);
1832 for alias in capability.aliases {
1833 hash_value(&mut hasher, alias);
1834 }
1835 let support = if accepted_only {
1836 capability.accepted_minimum
1837 } else {
1838 capability.support
1839 };
1840 hash_support(&mut hasher, support);
1841 hash_value(&mut hasher, &format!("{:?}", capability.parser_support));
1842 hash_value(&mut hasher, &format!("{:?}", capability.symbol_parser));
1843 hash_value(&mut hasher, &format!("{:?}", capability.semantic_provider));
1844 hash_value(&mut hasher, &format!("{:?}", capability.embedded_language));
1845 hash_value(&mut hasher, &format!("{:?}", capability.structural_summary));
1846 hash_value(
1847 &mut hasher,
1848 capability.optional_pack.unwrap_or("default-core"),
1849 );
1850 hash_value(&mut hasher, capability.fixtures.positive_path);
1851 hash_value(&mut hasher, capability.fixtures.negative_path);
1852 hash_value(&mut hasher, capability.provenance.source());
1853 hash_value(&mut hasher, capability.provenance.version());
1854 hash_value(&mut hasher, capability.provenance.license());
1855 hash_value(&mut hasher, &format!("{:?}", capability.required_platforms));
1856 }
1857 for (kind, rules) in [
1858 ("exact", EXACT_FILENAME_RULES),
1859 ("compound", COMPOUND_EXTENSION_RULES),
1860 ("extension", EXTENSION_RULES),
1861 ] {
1862 for rule in rules {
1863 hash_value(&mut hasher, kind);
1864 hash_value(&mut hasher, rule.value);
1865 hash_value(&mut hasher, rule.language);
1866 }
1867 }
1868 for rule in content_rules {
1869 hash_value(&mut hasher, "content-interpreter");
1870 hash_value(&mut hasher, rule.interpreter);
1871 hasher.update(&[u8::from(rule.allow_version_suffix)]);
1872 hash_value(&mut hasher, rule.language);
1873 }
1874 hasher.finalize().to_hex().to_string()
1875}
1876
1877fn hash_support(hasher: &mut Hasher, support: LanguageCapabilitySupport) {
1879 hasher.update(&[
1880 support.detected as u8,
1881 support.parsed as u8,
1882 support.symbols as u8,
1883 support.semantic as u8,
1884 support.benchmarked as u8,
1885 ]);
1886}
1887
1888fn hash_value(hasher: &mut Hasher, value: &str) {
1890 hasher.update(&(value.len() as u64).to_le_bytes());
1891 hasher.update(value.as_bytes());
1892}
1893
1894pub fn validate_language_registry() -> Result<(), LanguageRegistryError> {
1900 let mut canonical = BTreeSet::new();
1901 let mut names = BTreeMap::new();
1902 for capability in LANGUAGE_CAPABILITIES {
1903 if capability.id.is_empty() {
1904 return Err(LanguageRegistryError::new("empty canonical language ID"));
1905 }
1906 if !canonical.insert(capability.id) {
1907 return Err(LanguageRegistryError::new(format!(
1908 "duplicate canonical language ID {:?}",
1909 capability.id
1910 )));
1911 }
1912 register_owner(&mut names, capability.id, capability.id, "language name")?;
1913 for alias in capability.aliases {
1914 register_owner(&mut names, alias, capability.id, "language alias")?;
1915 }
1916 if !capability.support.meets(capability.accepted_minimum) {
1917 return Err(LanguageRegistryError::new(format!(
1918 "language {:?} is weaker than accepted capability set version {}",
1919 capability.id, ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION
1920 )));
1921 }
1922 if capability.fixtures.positive_path.is_empty()
1923 || capability.fixtures.negative_path.is_empty()
1924 || capability.fixtures.positive_path == capability.fixtures.negative_path
1925 {
1926 return Err(LanguageRegistryError::new(format!(
1927 "language {:?} lacks distinct natural positive and negative fixtures",
1928 capability.id
1929 )));
1930 }
1931 if capability.provenance.license().is_empty() {
1932 return Err(LanguageRegistryError::new(format!(
1933 "language {:?} lacks a provenance license input",
1934 capability.id
1935 )));
1936 }
1937 if capability
1938 .optional_pack
1939 .is_some_and(|owner| owner != BROAD_PARSER_PACK_ID)
1940 {
1941 return Err(LanguageRegistryError::new(format!(
1942 "optional language {:?} is assigned to unknown pack owner {:?}",
1943 capability.id, capability.optional_pack
1944 )));
1945 }
1946 if capability.optional_pack.is_some()
1947 && capability.provenance != CapabilityProvenance::PinnedOptionalCatalog
1948 {
1949 return Err(LanguageRegistryError::new(format!(
1950 "optional language {:?} lacks pinned catalog provenance",
1951 capability.id
1952 )));
1953 }
1954 if capability.optional_pack.is_none()
1955 && capability.provenance == CapabilityProvenance::PinnedOptionalCatalog
1956 {
1957 return Err(LanguageRegistryError::new(format!(
1958 "default-core language {:?} incorrectly claims optional catalog provenance",
1959 capability.id
1960 )));
1961 }
1962 match (capability.support.symbols, capability.symbol_parser) {
1963 (CapabilitySupportLevel::Unavailable, SymbolParserOwner::Unavailable)
1964 | (CapabilitySupportLevel::Fallback, SymbolParserOwner::Fallback)
1965 | (
1966 CapabilitySupportLevel::Supported,
1967 SymbolParserOwner::TreeSitter(_)
1968 | SymbolParserOwner::CargoManifest
1969 | SymbolParserOwner::Vue
1970 | SymbolParserOwner::PowerShell,
1971 ) => {}
1972 (support, owner) => {
1973 return Err(LanguageRegistryError::new(format!(
1974 "language {:?} advertises symbol support {support:?} with incompatible owner {owner:?}",
1975 capability.id
1976 )));
1977 }
1978 }
1979 match (capability.support.semantic, capability.semantic_provider) {
1980 (CapabilitySupportLevel::Unavailable, SemanticProviderOwner::Unavailable)
1981 | (
1982 CapabilitySupportLevel::Supported,
1983 SemanticProviderOwner::Rust
1984 | SemanticProviderOwner::EcmaScript
1985 | SemanticProviderOwner::Python
1986 | SemanticProviderOwner::Cargo,
1987 ) => {}
1988 (support, owner) => {
1989 return Err(LanguageRegistryError::new(format!(
1990 "language {:?} advertises semantic support {support:?} with incompatible owner {owner:?}",
1991 capability.id
1992 )));
1993 }
1994 }
1995 if let Some(embedded) = capability.embedded_language {
1996 if embedded.semantic_provider == SemanticProviderOwner::Unavailable {
1997 return Err(LanguageRegistryError::new(format!(
1998 "embedded language host {:?} lacks a semantic provider owner",
1999 capability.id
2000 )));
2001 }
2002 if capability.support.semantic != CapabilitySupportLevel::Unavailable
2003 || capability.semantic_provider != SemanticProviderOwner::Unavailable
2004 {
2005 return Err(LanguageRegistryError::new(format!(
2006 "embedded language host {:?} conflates host and embedded semantic support",
2007 capability.id
2008 )));
2009 }
2010 }
2011 if capability.optional_pack.is_some()
2012 && (capability.semantic_provider != SemanticProviderOwner::Unavailable
2013 || capability.embedded_language.is_some())
2014 {
2015 return Err(LanguageRegistryError::new(format!(
2016 "optional language {:?} advertises unvalidated semantic capability",
2017 capability.id
2018 )));
2019 }
2020 }
2021
2022 validate_rules("exact filename", EXACT_FILENAME_RULES, &canonical, false)?;
2023 validate_rules(
2024 "compound extension",
2025 COMPOUND_EXTENSION_RULES,
2026 &canonical,
2027 true,
2028 )?;
2029 validate_rules("extension", EXTENSION_RULES, &canonical, true)?;
2030 validate_content_rules(&canonical)?;
2031
2032 let mut detected: BTreeSet<_> = EXACT_FILENAME_RULES
2033 .iter()
2034 .chain(COMPOUND_EXTENSION_RULES)
2035 .chain(EXTENSION_RULES)
2036 .map(|rule| rule.language)
2037 .collect();
2038 detected.extend(CONTENT_DIALECT_RULES.iter().map(|rule| rule.language));
2039 for capability in LANGUAGE_CAPABILITIES {
2040 if capability.support.detected == CapabilitySupportLevel::Supported
2041 && !detected.contains(capability.id)
2042 {
2043 return Err(LanguageRegistryError::new(format!(
2044 "accepted language {:?} is a ghost row with no detector rule",
2045 capability.id
2046 )));
2047 }
2048 let detected_fixture = detect_language_request(LanguageDetectionRequest::new(
2049 capability.fixtures.positive_path,
2050 None,
2051 ))
2052 .map_err(|source| LanguageRegistryError::new(source.to_string()))?;
2053 if detected_fixture.map(|result| result.language) != Some(capability.id) {
2054 return Err(LanguageRegistryError::new(format!(
2055 "positive fixture {:?} does not select owning language {:?}",
2056 capability.fixtures.positive_path, capability.id
2057 )));
2058 }
2059 let negative_fixture = detect_language_request(LanguageDetectionRequest::new(
2060 capability.fixtures.negative_path,
2061 None,
2062 ))
2063 .map_err(|source| LanguageRegistryError::new(source.to_string()))?;
2064 if negative_fixture.map(|result| result.language) == Some(capability.id) {
2065 return Err(LanguageRegistryError::new(format!(
2066 "negative fixture {:?} still selects owning language {:?}",
2067 capability.fixtures.negative_path, capability.id
2068 )));
2069 }
2070 }
2071 let optional_candidates = LANGUAGE_CAPABILITIES
2072 .iter()
2073 .filter(|capability| capability.optional_pack.is_some())
2074 .count();
2075 if optional_candidates < OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS {
2076 return Err(LanguageRegistryError::new(format!(
2077 "optional catalog exposes only {optional_candidates} distinct candidate rows; at least {OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS} are required"
2078 )));
2079 }
2080 let accepted_digest = accepted_language_capability_digest();
2081 let expected_accepted_digest = match ACCEPTED_LANGUAGE_CAPABILITY_SET_VERSION {
2082 1 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V1_DIGEST,
2083 2 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V2_DIGEST,
2084 3 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V3_DIGEST,
2085 4 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V4_DIGEST,
2086 5 => ACCEPTED_LANGUAGE_CAPABILITY_SET_V5_DIGEST,
2087 version => {
2088 return Err(LanguageRegistryError::new(format!(
2089 "accepted language capability-set version {version} lacks a historical digest seal"
2090 )));
2091 }
2092 };
2093 if accepted_digest != expected_accepted_digest {
2094 return Err(LanguageRegistryError::new(format!(
2095 "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"
2096 )));
2097 }
2098 Ok(())
2099}
2100
2101fn register_owner<'a>(
2103 owners: &mut BTreeMap<String, &'a str>,
2104 value: &str,
2105 owner: &'a str,
2106 kind: &str,
2107) -> Result<(), LanguageRegistryError> {
2108 let normalized = value.to_ascii_lowercase();
2109 if let Some(previous) = owners.insert(normalized.clone(), owner) {
2110 return Err(LanguageRegistryError::new(format!(
2111 "conflicting {kind} {normalized:?} is owned by both {previous:?} and {owner:?}"
2112 )));
2113 }
2114 Ok(())
2115}
2116
2117fn validate_rules(
2119 kind: &str,
2120 rules: &[LanguageDetectionRule],
2121 canonical: &BTreeSet<&str>,
2122 normalize_case: bool,
2123) -> Result<(), LanguageRegistryError> {
2124 let mut owners = BTreeMap::new();
2125 let mut previous_compound_length = usize::MAX;
2126 for rule in rules {
2127 if !canonical.contains(rule.language) {
2128 return Err(LanguageRegistryError::new(format!(
2129 "{kind} rule {:?} targets missing language {:?}",
2130 rule.value, rule.language
2131 )));
2132 }
2133 let key = if normalize_case {
2134 rule.value.to_ascii_lowercase()
2135 } else {
2136 rule.value.to_string()
2137 };
2138 if let Some(previous) = owners.insert(key.clone(), rule.language)
2139 && previous != rule.language
2140 {
2141 return Err(LanguageRegistryError::new(format!(
2142 "conflicting {kind} rule {key:?} is owned by both {previous:?} and {:?}",
2143 rule.language
2144 )));
2145 }
2146 if kind == "compound extension" {
2147 if rule.value.len() > previous_compound_length {
2148 return Err(LanguageRegistryError::new(format!(
2149 "compound extension rules are not longest-first at {:?}",
2150 rule.value
2151 )));
2152 }
2153 previous_compound_length = rule.value.len();
2154 }
2155 }
2156 Ok(())
2157}
2158
2159fn validate_content_rules(canonical: &BTreeSet<&str>) -> Result<(), LanguageRegistryError> {
2161 let mut owners = BTreeMap::new();
2162 for rule in CONTENT_DIALECT_RULES {
2163 if !canonical.contains(rule.language) {
2164 return Err(LanguageRegistryError::new(format!(
2165 "content interpreter {:?} targets missing language {:?}",
2166 rule.interpreter, rule.language
2167 )));
2168 }
2169 if rule.interpreter.is_empty()
2170 || !rule
2171 .interpreter
2172 .bytes()
2173 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
2174 {
2175 return Err(LanguageRegistryError::new(format!(
2176 "content interpreter {:?} is not a normalized basename",
2177 rule.interpreter
2178 )));
2179 }
2180 if let Some(previous) = owners.insert(rule.interpreter, rule.language) {
2181 return Err(LanguageRegistryError::new(format!(
2182 "conflicting content interpreter {:?} is owned by both {previous:?} and {:?}",
2183 rule.interpreter, rule.language
2184 )));
2185 }
2186 }
2187 Ok(())
2188}
2189
2190#[cfg(test)]
2191mod tests {
2192 use super::*;
2193 use serde::Deserialize;
2194 use std::io;
2195
2196 #[derive(Debug, Deserialize)]
2198 struct DetectionCompatibilityFixture {
2199 extensions: Vec<String>,
2201 exact_filenames: Vec<String>,
2203 }
2204
2205 #[derive(Debug, Deserialize)]
2207 struct OptionalDetectionCatalogFixture {
2208 catalog: String,
2210 version: String,
2212 revision: String,
2214 rows: Vec<String>,
2216 }
2217
2218 fn require_test(condition: bool, message: impl Into<String>) -> Result<(), Box<dyn Error>> {
2219 if condition {
2220 Ok(())
2221 } else {
2222 Err(io::Error::other(message.into()).into())
2223 }
2224 }
2225
2226 #[test]
2227 fn accepted_registry_and_generated_projections_validate() -> Result<(), Box<dyn Error>> {
2228 validate_language_registry()?;
2229 require_test(
2230 LANGUAGE_CAPABILITIES.len() == LANGUAGE_SPECS.len(),
2231 "language capability and parser metadata projections differ",
2232 )?;
2233 let report = language_registry_report();
2234 require_test(
2235 report.counts.accepted == LANGUAGE_CAPABILITIES.len(),
2236 "accepted language count is not registry-derived",
2237 )?;
2238 let canonical_ids = LANGUAGE_CAPABILITIES
2239 .iter()
2240 .map(|capability| capability.id)
2241 .collect::<BTreeSet<_>>();
2242 require_test(
2243 report.counts.accepted == canonical_ids.len(),
2244 "aliases or extensions inflated the canonical capability count",
2245 )?;
2246 require_test(
2247 LANGUAGE_CAPABILITIES
2248 .iter()
2249 .any(|capability| !capability.aliases.is_empty())
2250 && !EXTENSION_RULES.is_empty(),
2251 "alias/extension non-inflation check became vacuous",
2252 )?;
2253 require_test(
2254 report.counts.built_in + report.counts.optional_candidates == report.counts.accepted,
2255 "built-in and optional language counts do not cover the accepted set",
2256 )?;
2257 require_test(
2258 language_capability_report_rows().len() == report.counts.accepted,
2259 "documentation projection does not cover the accepted set",
2260 )?;
2261 require_test(
2262 report.counts.optional_candidates >= OPTIONAL_PACK_MINIMUM_ADDITIONAL_GRAMMARS,
2263 "optional catalog projection is below the accepted breadth floor",
2264 )?;
2265 require_test(
2266 !report.registry_digest.is_empty(),
2267 "language registry digest is empty",
2268 )?;
2269 require_test(
2270 !report.accepted_set_digest.is_empty(),
2271 "accepted language-set digest is empty",
2272 )?;
2273 require_test(
2274 report.semantic_provider_digest.len() == 64
2275 && report
2276 .semantic_provider_digest
2277 .bytes()
2278 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)),
2279 "semantic provider digest is not a bounded lowercase hexadecimal identity",
2280 )?;
2281 let encoded = serde_json::to_vec(&report)?;
2282 require_test(
2283 encoded.len() <= LANGUAGE_REGISTRY_REPORT_MAX_BYTES,
2284 "language registry settings projection exceeded its content-free output budget",
2285 )?;
2286 Ok(())
2287 }
2288
2289 #[test]
2290 fn semantic_provider_and_embedded_host_claims_are_honest() -> Result<(), Box<dyn Error>> {
2291 let report = language_registry_report();
2292 let semantic_rows = LANGUAGE_CAPABILITIES
2293 .iter()
2294 .filter(|capability| capability.support.semantic == CapabilitySupportLevel::Supported)
2295 .count();
2296 require_test(
2297 report.counts.semantic.supported == semantic_rows,
2298 "semantic provider rows did not determine the semantic capability count",
2299 )?;
2300
2301 for capability in LANGUAGE_CAPABILITIES
2302 .iter()
2303 .filter(|capability| capability.optional_pack.is_some())
2304 {
2305 require_test(
2306 capability.semantic_provider == SemanticProviderOwner::Unavailable
2307 && capability.embedded_language.is_none()
2308 && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2309 format!(
2310 "optional candidate {:?} advertised semantic capability",
2311 capability.id
2312 ),
2313 )?;
2314 }
2315
2316 for (language, expected) in [
2317 (
2318 "html",
2319 EmbeddedLanguageCapability {
2320 host_kind: EmbeddedHostKind::HtmlLike,
2321 semantic_provider: SemanticProviderOwner::EcmaScript,
2322 },
2323 ),
2324 (
2325 "vue",
2326 EmbeddedLanguageCapability {
2327 host_kind: EmbeddedHostKind::Component,
2328 semantic_provider: SemanticProviderOwner::EcmaScript,
2329 },
2330 ),
2331 (
2332 "svelte",
2333 EmbeddedLanguageCapability {
2334 host_kind: EmbeddedHostKind::Template,
2335 semantic_provider: SemanticProviderOwner::EcmaScript,
2336 },
2337 ),
2338 ] {
2339 let capability = language_capability(language)
2340 .ok_or_else(|| io::Error::other(format!("missing {language} capability")))?;
2341 require_test(
2342 capability.embedded_language == Some(expected)
2343 && capability.semantic_provider == SemanticProviderOwner::Unavailable
2344 && capability.effective_semantic_provider()
2345 == Some(SemanticProviderOwner::EcmaScript)
2346 && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2347 format!("{language} host/embedded capability drifted"),
2348 )?;
2349 }
2350
2351 for language in ["cargo-lock", "java", "c", "go"] {
2352 let capability = language_capability(language)
2353 .ok_or_else(|| io::Error::other(format!("missing {language} capability")))?;
2354 require_test(
2355 capability.semantic_provider == SemanticProviderOwner::Unavailable
2356 && capability.effective_semantic_provider().is_none()
2357 && capability.support.semantic == CapabilitySupportLevel::Unavailable,
2358 format!("unsupported semantic language {language:?} was promoted"),
2359 )?;
2360 }
2361 require_test(
2362 SemanticProviderOwner::EcmaScript.resolution_family() == Some("ecmascript")
2363 && SemanticProviderOwner::Unavailable
2364 .resolution_family()
2365 .is_none(),
2366 "semantic provider resolution-family identity drifted",
2367 )?;
2368 Ok(())
2369 }
2370
2371 #[test]
2372 fn detects_every_broad_source_extension() {
2373 for extension in BROAD_SOURCE_EXTENSIONS {
2374 assert!(
2375 detect_language(Some(extension)).is_some(),
2376 "missing broad source extension support for {extension}"
2377 );
2378 }
2379 }
2380
2381 #[test]
2382 fn detects_every_accepted_registry_source_extension() {
2383 for extension in DETECTED_SOURCE_EXTENSIONS {
2384 assert!(
2385 detect_language(Some(extension)).is_some(),
2386 "missing accepted source extension support for {extension}"
2387 );
2388 }
2389 }
2390
2391 #[test]
2392 fn generated_language_support_document_is_current() -> Result<(), Box<dyn Error>> {
2393 let rendered = render_language_support_markdown()?;
2394 require_test(
2395 include_str!("../../../docs/language-support.md") == rendered,
2396 "checked-in language support document is stale",
2397 )
2398 }
2399
2400 #[test]
2401 fn built_in_grammar_provenance_matches_workspace_pins() {
2402 let workspace = include_str!("../../../Cargo.toml");
2403 let mut checked = BTreeSet::new();
2404 for grammar in TreeSitterGrammar::ALL {
2405 let package = grammar.package();
2406 if checked.insert(package) {
2407 let pin = format!("{package} = \"={}\"", grammar.version());
2408 assert!(
2409 workspace.lines().any(|line| line.trim() == pin),
2410 "workspace dependency pin drifted from registry provenance: {pin}"
2411 );
2412 }
2413 }
2414 }
2415
2416 #[test]
2417 fn frozen_v0326_detection_corpus_remains_exact() -> Result<(), Box<dyn Error>> {
2418 let fixture: DetectionCompatibilityFixture = serde_json::from_str(include_str!(
2419 "../../../fixtures/languages/v0.3.26-detection.json"
2420 ))?;
2421 let ordered_extensions = fixture
2422 .extensions
2423 .iter()
2424 .map(|row| row.split_once('=').map(|(extension, _)| extension))
2425 .collect::<Option<Vec<_>>>()
2426 .ok_or_else(|| io::Error::other("invalid extension compatibility row"))?;
2427 if ordered_extensions != BROAD_SOURCE_EXTENSIONS {
2428 return Err(io::Error::other(
2429 "ordered 0.3.26 broad extension compatibility corpus changed",
2430 )
2431 .into());
2432 }
2433 for row in fixture.extensions {
2434 let (extension, expected) = row
2435 .split_once('=')
2436 .ok_or_else(|| io::Error::other("invalid extension compatibility row"))?;
2437 if detect_language(Some(extension)).as_deref() != Some(expected) {
2438 return Err(io::Error::other(format!(
2439 "0.3.26 extension {extension:?} no longer selects {expected:?}"
2440 ))
2441 .into());
2442 }
2443 }
2444 for row in fixture.exact_filenames {
2445 let (path, expected) = row
2446 .split_once('=')
2447 .ok_or_else(|| io::Error::other("invalid exact filename compatibility row"))?;
2448 if detect_language_for_path(path, None).as_deref() != Some(expected)
2449 || detect_language_for_path(&format!("folder\\{path}"), None).as_deref()
2450 != Some(expected)
2451 {
2452 return Err(io::Error::other(format!(
2453 "0.3.26 exact filename {path:?} no longer selects {expected:?}"
2454 ))
2455 .into());
2456 }
2457 }
2458 Ok(())
2459 }
2460
2461 #[test]
2462 fn accepted_optional_detection_subset_matches_independent_catalog_projection()
2463 -> Result<(), Box<dyn Error>> {
2464 let fixture: OptionalDetectionCatalogFixture = serde_json::from_str(include_str!(
2465 "../../../fixtures/languages/accepted-optional-detection-catalog.json"
2466 ))?;
2467 require_test(
2468 fixture.catalog == OPTIONAL_GRAMMAR_CATALOG,
2469 "optional catalog identity drifted",
2470 )?;
2471 require_test(
2472 fixture.version == OPTIONAL_GRAMMAR_CATALOG_VERSION,
2473 "optional catalog version drifted",
2474 )?;
2475 require_test(
2476 fixture.revision == OPTIONAL_GRAMMAR_CATALOG_RELEASE_REVISION,
2477 "optional catalog revision drifted",
2478 )?;
2479 let projected = LANGUAGE_CAPABILITIES
2480 .iter()
2481 .filter(|capability| capability.optional_pack.is_some())
2482 .map(|capability| {
2483 let extensions = rules_for_language(EXTENSION_RULES, capability.id);
2484 match extensions.as_slice() {
2485 [extension] => Ok(format!("{}={extension}", capability.id)),
2486 _ => Err(io::Error::other(format!(
2487 "optional detection row {:?} must own exactly one accepted extension",
2488 capability.id
2489 ))),
2490 }
2491 })
2492 .collect::<Result<Vec<_>, _>>()?;
2493 require_test(
2494 fixture.rows == projected,
2495 "accepted optional catalog projection drifted",
2496 )
2497 }
2498
2499 #[test]
2500 fn preserves_representative_broad_source_extensions() {
2501 assert_eq!(
2502 detect_language(Some(".d.ts")).as_deref(),
2503 Some("typescript")
2504 );
2505 assert_eq!(detect_language(Some(".pyw")).as_deref(), Some("python"));
2506 assert_eq!(detect_language(Some(".kts")).as_deref(), Some("kotlin"));
2507 assert_eq!(
2508 detect_language(Some(".psm1")).as_deref(),
2509 Some("powershell")
2510 );
2511 assert_eq!(detect_language(Some(".zon")).as_deref(), Some("zig"));
2512 assert_eq!(detect_language(Some(".proto")).as_deref(), Some("protobuf"));
2513 assert_eq!(detect_language(Some(".R")).as_deref(), Some("r"));
2514 assert_eq!(detect_language(Some(".ini")).as_deref(), Some("config"));
2515 assert_eq!(detect_language(Some(".liquibase")).as_deref(), Some("sql"));
2516 assert_eq!(detect_language(Some(".toon")).as_deref(), Some("toon"));
2517 }
2518
2519 #[test]
2520 fn preserves_exact_filename_case_and_compound_precedence() {
2521 let exact =
2522 detect_language_request(LanguageDetectionRequest::new("Cargo.toml", Some(".toml")));
2523 assert!(matches!(
2524 exact,
2525 Ok(Some(LanguageDetection {
2526 language: "cargo-manifest",
2527 reason: LanguageDetectionReason::ExactFilename,
2528 }))
2529 ));
2530 assert_eq!(
2531 detect_language_for_path("cargo.toml", Some(".toml")).as_deref(),
2532 Some("toml")
2533 );
2534 let compound = detect_language_request(LanguageDetectionRequest::new(
2535 "types/index.d.ts",
2536 Some(".d.ts"),
2537 ));
2538 assert!(matches!(
2539 compound,
2540 Ok(Some(LanguageDetection {
2541 language: "typescript",
2542 reason: LanguageDetectionReason::CompoundExtension,
2543 }))
2544 ));
2545 }
2546
2547 #[test]
2548 fn typed_precedence_prefers_override_and_bounds_content() -> Result<(), Box<dyn Error>> {
2549 let override_result = detect_language_request(LanguageDetectionRequest {
2550 path: "Cargo.toml",
2551 extension: Some(".toml"),
2552 explicit_override: Some("py"),
2553 content_prefix: Some(b"#!/usr/bin/env node\n"),
2554 })?
2555 .ok_or_else(|| io::Error::other("override detection missing"))?;
2556 require_test(
2557 override_result.language == "python",
2558 "explicit alias override did not select Python",
2559 )?;
2560 require_test(
2561 override_result.reason == LanguageDetectionReason::ExplicitOverride,
2562 "explicit override did not retain its typed reason",
2563 )?;
2564
2565 let content_result = detect_language_request(LanguageDetectionRequest {
2566 path: "tool",
2567 extension: None,
2568 explicit_override: None,
2569 content_prefix: Some(b"#!/usr/bin/env python\nprint('ok')\n"),
2570 })?
2571 .ok_or_else(|| io::Error::other("content detection missing"))?;
2572 require_test(
2573 content_result.language == "python",
2574 "bounded shebang did not select Python",
2575 )?;
2576 require_test(
2577 content_result.reason == LanguageDetectionReason::ContentDialect,
2578 "bounded shebang did not retain its typed reason",
2579 )?;
2580 let extension_result =
2581 detect_language_request(LanguageDetectionRequest::new("module.py", Some(".py")))?
2582 .ok_or_else(|| io::Error::other("extension detection missing"))?;
2583 require_test(
2584 extension_result.language == "python",
2585 "extension did not select Python",
2586 )?;
2587 require_test(
2588 extension_result.reason == LanguageDetectionReason::Extension,
2589 "extension did not retain its typed reason",
2590 )?;
2591 require_test(
2592 detect_language_request(LanguageDetectionRequest {
2593 path: "tool",
2594 extension: None,
2595 explicit_override: Some("missing-language"),
2596 content_prefix: None,
2597 })
2598 .is_err(),
2599 "unknown explicit override did not fail closed",
2600 )?;
2601 Ok(())
2602 }
2603
2604 #[test]
2605 fn every_detected_language_has_compatible_parser_metadata() -> Result<(), Box<dyn Error>> {
2606 for rule in EXACT_FILENAME_RULES
2607 .iter()
2608 .chain(COMPOUND_EXTENSION_RULES)
2609 .chain(EXTENSION_RULES)
2610 {
2611 let spec = language_spec(rule.language).ok_or_else(|| {
2612 io::Error::other(format!("missing parser coverage for {}", rule.language))
2613 })?;
2614 require_test(
2615 spec.language == rule.language,
2616 format!("parser coverage ownership drifted for {}", rule.language),
2617 )?;
2618 }
2619 for rule in CONTENT_DIALECT_RULES {
2620 let spec = language_spec(rule.language).ok_or_else(|| {
2621 io::Error::other(format!("missing parser coverage for {}", rule.language))
2622 })?;
2623 require_test(
2624 spec.language == rule.language,
2625 format!("content detector ownership drifted for {}", rule.language),
2626 )?;
2627 }
2628 Ok(())
2629 }
2630
2631 #[test]
2632 fn structural_summary_rows_do_not_advertise_symbols() {
2633 for capability in LANGUAGE_CAPABILITIES
2634 .iter()
2635 .filter(|capability| capability.parser_support == LanguageParserSupport::Structural)
2636 .filter(|capability| capability.symbol_parser == SymbolParserOwner::Unavailable)
2637 {
2638 assert_eq!(
2639 capability.support.symbols,
2640 CapabilitySupportLevel::Unavailable,
2641 "structural summary row {:?} advertised symbols without an owner",
2642 capability.id
2643 );
2644 }
2645 }
2646
2647 #[test]
2648 fn shebang_rules_are_exact_bounded_and_utf8_boundary_safe() -> Result<(), Box<dyn Error>> {
2649 for rule in CONTENT_DIALECT_RULES {
2650 let source = format!("#!/usr/bin/env {}\n", rule.interpreter);
2651 let detected = detect_language_request(LanguageDetectionRequest {
2652 path: "tool",
2653 extension: None,
2654 explicit_override: None,
2655 content_prefix: Some(source.as_bytes()),
2656 })?
2657 .ok_or_else(|| io::Error::other("declared shebang was not detected"))?;
2658 require_test(
2659 detected.language == rule.language,
2660 format!("shebang owner drifted for {}", rule.interpreter),
2661 )?;
2662 require_test(
2663 detected.reason == LanguageDetectionReason::ContentDialect,
2664 format!("shebang reason drifted for {}", rule.interpreter),
2665 )?;
2666 }
2667
2668 for near_miss in ["wish", "mesh-agent", "python-helper", "nodejs"] {
2669 let source = format!("#!/usr/bin/env {near_miss}\n");
2670 let detected = detect_language_request(LanguageDetectionRequest {
2671 path: "tool",
2672 extension: None,
2673 explicit_override: None,
2674 content_prefix: Some(source.as_bytes()),
2675 })?;
2676 require_test(
2677 detected.is_none(),
2678 format!("near-miss interpreter {near_miss:?} was classified"),
2679 )?;
2680 }
2681
2682 for (interpreter, expected) in [
2683 ("python3", "python"),
2684 ("python3.12", "python"),
2685 ("ruby3.3", "ruby"),
2686 ("lua5.4", "lua"),
2687 ] {
2688 let source = format!("#!/usr/bin/env {interpreter}\n");
2689 let detected = detect_language_request(LanguageDetectionRequest {
2690 path: "tool",
2691 extension: None,
2692 explicit_override: None,
2693 content_prefix: Some(source.as_bytes()),
2694 })?;
2695 require_test(
2696 detected.map(|result| result.language) == Some(expected),
2697 format!("valid versioned interpreter {interpreter:?} was not classified"),
2698 )?;
2699 }
2700
2701 for near_miss in ["python.", "python..3", "ruby...", "lua."] {
2702 let source = format!("#!/usr/bin/env {near_miss}\n");
2703 let detected = detect_language_request(LanguageDetectionRequest {
2704 path: "tool",
2705 extension: None,
2706 explicit_override: None,
2707 content_prefix: Some(source.as_bytes()),
2708 })?;
2709 require_test(
2710 detected.is_none(),
2711 format!("invalid versioned interpreter {near_miss:?} was classified"),
2712 )?;
2713 }
2714
2715 let mut split_utf8_after_shebang = b"#!/usr/bin/env python\n".to_vec();
2716 split_utf8_after_shebang.resize(LANGUAGE_CONTENT_DETECTION_MAX_BYTES, b'x');
2717 split_utf8_after_shebang[LANGUAGE_CONTENT_DETECTION_MAX_BYTES - 1] = 0xc3;
2718 let detected = detect_language_request(LanguageDetectionRequest {
2719 path: "tool",
2720 extension: None,
2721 explicit_override: None,
2722 content_prefix: Some(&split_utf8_after_shebang),
2723 })?;
2724 require_test(
2725 detected.map(|result| result.language) == Some("python"),
2726 "valid shebang was lost at a later UTF-8 split boundary",
2727 )?;
2728
2729 let mut after_bound = vec![b' '; LANGUAGE_CONTENT_DETECTION_MAX_BYTES];
2730 after_bound.extend_from_slice(b"#!/usr/bin/env python\n");
2731 let detected = detect_language_request(LanguageDetectionRequest {
2732 path: "tool",
2733 extension: None,
2734 explicit_override: None,
2735 content_prefix: Some(&after_bound),
2736 })?;
2737 require_test(
2738 detected.is_none(),
2739 "content detection inspected bytes beyond its accepted bound",
2740 )
2741 }
2742
2743 #[test]
2744 fn content_rule_changes_advance_registry_digest() {
2745 let baseline = hash_language_registry_with_content_rules(false, CONTENT_DIALECT_RULES);
2746 let mut changed = CONTENT_DIALECT_RULES.to_vec();
2747 changed[0].language = "ruby";
2748 assert_ne!(
2749 baseline,
2750 hash_language_registry_with_content_rules(false, &changed)
2751 );
2752 }
2753
2754 #[test]
2755 fn built_in_tree_sitter_projection_is_registry_derived() {
2756 let projected = builtin_tree_sitter_language_ids();
2757 let expected = LANGUAGE_CAPABILITIES
2758 .iter()
2759 .filter(|capability| {
2760 matches!(capability.symbol_parser, SymbolParserOwner::TreeSitter(_))
2761 })
2762 .count();
2763 assert_eq!(projected.len(), expected);
2764 assert!(projected.contains(&"rust"));
2765 assert!(!projected.contains(&"markdown"));
2766 }
2767
2768 #[test]
2769 fn conflicting_detection_rules_report_both_owners() -> Result<(), Box<dyn Error>> {
2770 let canonical = BTreeSet::from(["python", "rust"]);
2771 let rules = [
2772 LanguageDetectionRule {
2773 value: ".mixed",
2774 language: "rust",
2775 },
2776 LanguageDetectionRule {
2777 value: ".MIXED",
2778 language: "python",
2779 },
2780 ];
2781 let error = validate_rules("extension", &rules, &canonical, true)
2782 .err()
2783 .ok_or_else(|| io::Error::other("case-normalized conflicting owners must fail"))?;
2784 let message = error.to_string();
2785 require_test(
2786 message.contains("rust") && message.contains("python"),
2787 "conflict diagnostic did not name both owners",
2788 )
2789 }
2790}