Skip to main content

scc_core/
languages.rs

1//! Authoritative language-support registry.
2//!
3//! Scan classification, extractor wiring, CLI `scc languages`, and tests
4//! all derive from [`LANGUAGE_REGISTRY`]. Do not hand-maintain a second
5//! list in docs or MCP copy.
6
7use serde::{Deserialize, Serialize};
8
9/// Honest support tier. Parsing syntax is not "supported".
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12// trace:exempt reason=internal-detail
13pub enum LanguageTier {
14    /// Semantic/deep: symbols, refs, routes/state/contracts when present.
15    SemanticDeep,
16    /// Structural: definitions + calls/imports, limited semantics.
17    Structural,
18    /// Index/search only: classified and hashed, no extractor.
19    IndexSearch,
20    /// Data/config/docs: specialized non-AST extractors or classification.
21    DataConfig,
22}
23
24impl LanguageTier {
25    pub fn as_str(self) -> &'static str {
26        match self {
27            LanguageTier::SemanticDeep => "A",
28            LanguageTier::Structural => "B",
29            LanguageTier::IndexSearch => "C",
30            LanguageTier::DataConfig => "D",
31        }
32    }
33}
34
35/// Capability flags for one language. Generated surfaces must read these
36/// rather than assuming "we have a parser, therefore we extract X".
37// trace:exempt reason=internal-detail
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
39pub struct LanguageCapability {
40    pub id: &'static str,
41    pub display: &'static str,
42    pub tier: LanguageTier,
43    pub extensions: &'static [&'static str],
44    pub filenames: &'static [&'static str],
45    pub extractor: bool,
46    pub definitions: bool,
47    pub methods: bool,
48    pub classes: bool,
49    pub fields: bool,
50    pub calls: bool,
51    pub reads_writes: bool,
52    pub imports: bool,
53    pub inheritance: bool,
54    pub type_refs: bool,
55    pub macros: bool,
56    pub routes: bool,
57    pub state: bool,
58    pub contracts: bool,
59    pub tests: bool,
60    pub notes: &'static str,
61}
62
63/// Single source of truth for language support.
64///
65/// Keep `id` identical to [`crate` scan `Language::as_str`] for languages
66/// the indexer classifies. Languages listed here but not classified are
67/// honest "not indexed" rows.
68pub const LANGUAGE_REGISTRY: &[LanguageCapability] = &[
69    cap("python", "Python", LanguageTier::SemanticDeep, &["py", "pyi"], &[], true, true, true, true, true, true, true, true, false, false, false, true, true, true, true, "procedural tree-sitter; store R/W not SSA"),
70    cap("typescript", "TypeScript", LanguageTier::SemanticDeep, &["ts", "tsx", "mts", "cts"], &[], true, true, true, true, true, true, true, true, false, true, false, true, true, true, true, "TS/TSX; implements→REGISTERS not INHERITS"),
71    cap("javascript", "JavaScript", LanguageTier::SemanticDeep, &["js", "jsx", "mjs", "cjs"], &[], true, true, true, true, true, true, true, true, false, false, false, true, true, true, true, "shares TypeScript extractor"),
72    cap("go", "Go", LanguageTier::SemanticDeep, &["go"], &[], true, true, true, true, true, true, true, true, false, false, false, true, true, false, false, "framework-gated routes; tests via path heuristics"),
73    cap("rust", "Rust", LanguageTier::SemanticDeep, &["rs"], &[], true, true, true, true, true, true, true, true, false, false, true, true, true, true, true, "macros as invocations; no INHERITS edges"),
74    cap("java", "Java", LanguageTier::SemanticDeep, &["java"], &[], true, true, true, true, true, true, true, true, false, true, false, true, true, true, true, "Spring routes import-gated; extends→schema composition"),
75    cap("json", "JSON", LanguageTier::DataConfig, &["json", "jsonc"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "named config files only (package.json)"),
76    cap("yaml", "YAML", LanguageTier::DataConfig, &["yaml", "yml"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "compose/helm/github workflows"),
77    cap("toml", "TOML", LanguageTier::DataConfig, &["toml"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; Cargo.toml not a code extractor"),
78    cap("env", "Env", LanguageTier::DataConfig, &["env"], &[".env"], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "secret redaction"),
79    cap("dockerfile", "Dockerfile", LanguageTier::DataConfig, &[], &["dockerfile", "Dockerfile"], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "infra extractor"),
80    cap("compose", "Compose", LanguageTier::DataConfig, &[], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "detected via filename, not extension"),
81    cap("terraform", "Terraform", LanguageTier::DataConfig, &["tf"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "infra extractor"),
82    cap("markdown", "Markdown", LanguageTier::DataConfig, &["md", "mdx", "rst"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "docs; DECLARED_AS mention index"),
83    cap("shell", "Shell", LanguageTier::IndexSearch, &["sh", "bash", "zsh"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
84    cap("sql", "SQL", LanguageTier::IndexSearch, &["sql"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
85    cap("c", "C", LanguageTier::SemanticDeep, &["c", "h"], &[], true, true, false, false, false, true, false, true, false, false, false, false, false, false, false, "procedural tree-sitter; quote-include Step-A; no CHA/fn-ptr/ObjC"),
86    cap("cpp", "C++", LanguageTier::SemanticDeep, &["cc", "cpp", "cxx", "hpp", "hh", "hxx"], &[], true, true, true, true, false, true, false, true, false, false, false, false, false, false, false, "procedural tree-sitter; quote-include Step-A; class methods; no CHA/fn-ptr"),
87    cap("objc", "Objective-C", LanguageTier::IndexSearch, &["m", "mm"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
88    cap("csharp", "C#", LanguageTier::IndexSearch, &["cs"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
89    cap("ruby", "Ruby", LanguageTier::IndexSearch, &["rb"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
90    cap("php", "PHP", LanguageTier::IndexSearch, &["php"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
91    cap("lua", "Lua", LanguageTier::IndexSearch, &["lua"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
92    cap("swift", "Swift", LanguageTier::IndexSearch, &["swift"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
93    cap("kotlin", "Kotlin", LanguageTier::IndexSearch, &["kt", "kts"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, "classified; no AST extractor"),
94    cap("protobuf", "Protocol Buffers", LanguageTier::DataConfig, &["proto"], &[], false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, "service/rpc → CONTRACT; no AST extractor"),
95];
96
97/// Authoritative language-support rows. Scan, CLI `scc languages`, and
98/// tests must read this rather than a second hand-maintained list.
99// trace:v1 id=impl.scc.core.language-registry work=WORK-ripwire-lessons-phase1 satisfies=REQ-cross-lang-semantic-bridges,REQ-language-support-matrix
100pub fn language_registry() -> &'static [LanguageCapability] {
101    LANGUAGE_REGISTRY
102}
103
104#[allow(clippy::too_many_arguments)]
105// trace:exempt reason=internal-detail
106const fn cap(
107    id: &'static str,
108    display: &'static str,
109    tier: LanguageTier,
110    extensions: &'static [&'static str],
111    filenames: &'static [&'static str],
112    extractor: bool,
113    definitions: bool,
114    methods: bool,
115    classes: bool,
116    fields: bool,
117    calls: bool,
118    reads_writes: bool,
119    imports: bool,
120    inheritance: bool,
121    type_refs: bool,
122    macros: bool,
123    routes: bool,
124    state: bool,
125    contracts: bool,
126    tests: bool,
127    notes: &'static str,
128) -> LanguageCapability {
129    LanguageCapability {
130        id,
131        display,
132        tier,
133        extensions,
134        filenames,
135        extractor,
136        definitions,
137        methods,
138        classes,
139        fields,
140        calls,
141        reads_writes,
142        imports,
143        inheritance,
144        type_refs,
145        macros,
146        routes,
147        state,
148        contracts,
149        tests,
150        notes,
151    }
152}
153
154// trace:exempt reason=internal-detail
155pub fn language_by_id(id: &str) -> Option<&'static LanguageCapability> {
156    language_registry().iter().find(|c| c.id == id)
157}
158
159// trace:exempt reason=internal-detail
160pub fn extracted_language_ids() -> Vec<&'static str> {
161    language_registry()
162        .iter()
163        .filter(|c| c.extractor)
164        .map(|c| c.id)
165        .collect()
166}
167
168/// Markdown table generated from the registry (docs/CLI must not duplicate).
169// trace:exempt reason=internal-detail
170pub fn support_matrix_markdown() -> String {
171    let mut out = String::from(
172        "| Language | Tier | Extractor | Defs | Calls | Fields | Imports | Routes | Tests | Notes |\n|---|---|---|---|---|---|---|---|---|---|\n",
173    );
174    for c in language_registry() {
175        out.push_str(&format!(
176            "| {} | {} | {} | {} | {} | {} | {} | {} | {} | {} |\n",
177            c.display,
178            c.tier.as_str(),
179            yn(c.extractor),
180            yn(c.definitions),
181            yn(c.calls),
182            yn(c.fields),
183            yn(c.imports),
184            yn(c.routes),
185            yn(c.tests),
186            c.notes
187        ));
188    }
189    out
190}
191
192fn yn(v: bool) -> &'static str {
193    if v {
194        "yes"
195    } else {
196        "no"
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use std::collections::BTreeSet;
204
205    #[test]
206    // trace:v1 id=test.scc.core.language-registry-unique verifies=REQ-language-support-matrix exercises=impl.scc.core.language-registry
207    fn registry_ids_are_unique_and_extracted_are_tier_a() {
208        let mut ids = BTreeSet::new();
209        for c in language_registry() {
210            assert!(ids.insert(c.id), "duplicate language id {}", c.id);
211            if c.extractor {
212                assert_eq!(
213                    c.tier,
214                    LanguageTier::SemanticDeep,
215                    "{} claims an extractor but is not tier A",
216                    c.id
217                );
218                assert!(c.definitions && c.calls, "{} extractor without defs/calls", c.id);
219            }
220        }
221        assert!(extracted_language_ids().contains(&"python"));
222        assert!(extracted_language_ids().contains(&"rust"));
223        assert!(extracted_language_ids().contains(&"c"));
224        assert!(extracted_language_ids().contains(&"cpp"));
225        assert!(!extracted_language_ids().contains(&"objc"));
226    }
227
228    #[test]
229    // trace:exempt reason=internal-detail
230    fn matrix_is_generated_not_empty() {
231        let md = support_matrix_markdown();
232        assert!(md.contains("| Python |"));
233        assert!(md.contains("| C | A | yes |"));
234        assert!(md.contains("| Objective-C | C | no |"));
235    }
236}