code_moniker_core/lang/
mod.rs1pub mod callable;
2pub mod canonical_walker;
3pub mod cs;
4pub mod extractor;
5pub mod go;
6pub mod java;
7pub mod kinds;
8pub mod python;
9pub mod rs;
10pub mod sql;
11pub mod strategy;
12pub mod tree_util;
13pub mod ts;
14
15pub use extractor::LangExtractor;
16#[cfg(test)]
17pub use extractor::assert_conformance;
18
19macro_rules! define_languages {
31 ($($(#[$attr:meta])* $variant:ident => $module:ty),* $(,)?) => {
32 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
33 pub enum Lang {
34 $(
35 $(#[$attr])*
36 $variant,
37 )*
38 }
39
40 impl Lang {
41 pub const ALL: &'static [Lang] = &[
42 $(
43 $(#[$attr])*
44 Self::$variant,
45 )*
46 ];
47
48 pub fn from_tag(s: &str) -> Option<Self> {
49 $(
50 $(#[$attr])*
51 if s == <$module as $crate::lang::LangExtractor>::LANG_TAG {
52 return Some(Self::$variant);
53 }
54 )*
55 None
56 }
57
58 pub fn tag(self) -> &'static str {
59 match self {
60 $(
61 $(#[$attr])*
62 Self::$variant => <$module as $crate::lang::LangExtractor>::LANG_TAG,
63 )*
64 }
65 }
66
67 pub fn allowed_kinds(self) -> &'static [&'static str] {
68 match self {
69 $(
70 $(#[$attr])*
71 Self::$variant => <$module as $crate::lang::LangExtractor>::ALLOWED_KINDS,
72 )*
73 }
74 }
75
76 pub fn allowed_visibilities(self) -> &'static [&'static str] {
77 match self {
78 $(
79 $(#[$attr])*
80 Self::$variant => <$module as $crate::lang::LangExtractor>::ALLOWED_VISIBILITIES,
81 )*
82 }
83 }
84
85 pub fn ignores_visibility(self) -> bool {
86 self.allowed_visibilities().is_empty()
87 }
88 }
89
90 #[cfg(test)]
91 mod _conformance_dispatch {
92 use $crate::lang::LangExtractor;
93
94 pub(crate) fn for_each_language(
97 mut f: impl FnMut(&'static str, &'static [&'static str], &'static [&'static str]),
98 ) {
99 $(
100 $(#[$attr])*
101 f(
102 <$module as LangExtractor>::LANG_TAG,
103 <$module as LangExtractor>::ALLOWED_KINDS,
104 <$module as LangExtractor>::ALLOWED_VISIBILITIES,
105 );
106 )*
107 }
108 }
109 };
110}
111
112define_languages! {
113 Ts => crate::lang::ts::Lang,
114 Rs => crate::lang::rs::Lang,
115 Java => crate::lang::java::Lang,
116 Python => crate::lang::python::Lang,
117 Go => crate::lang::go::Lang,
118 Cs => crate::lang::cs::Lang,
119 Sql => crate::lang::sql::Lang,
120}
121
122#[cfg(test)]
123pub(crate) use _conformance_dispatch::for_each_language;
124
125#[cfg(test)]
126mod schema_sync_tests {
127 use super::for_each_language;
128 use serde_json::Value;
129
130 const SCHEMA_JSON: &str = include_str!("../../../../docs/declare_schema.json");
131
132 fn profile_name_for(tag: &str) -> String {
133 let mut chars = tag.chars();
134 let first = chars.next().unwrap().to_uppercase().collect::<String>();
135 format!("{first}{}Profile", chars.as_str())
136 }
137
138 fn enum_at<'a>(schema: &'a Value, profile: &str, field: &str) -> Vec<&'a str> {
139 schema
140 .get("$defs")
141 .and_then(|d| d.get(profile))
142 .and_then(|p| p.get("properties"))
143 .and_then(|p| p.get("symbols"))
144 .and_then(|s| s.get("items"))
145 .and_then(|i| i.get("properties"))
146 .and_then(|p| p.get(field))
147 .and_then(|f| f.get("enum"))
148 .and_then(|e| e.as_array())
149 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
150 .unwrap_or_default()
151 }
152
153 #[test]
154 fn declare_schema_matches_trait_constants() {
155 let schema: Value =
156 serde_json::from_str(SCHEMA_JSON).expect("docs/declare_schema.json must be valid JSON");
157
158 let mut visited = 0usize;
159 for_each_language(|tag, kinds, visibilities| {
160 visited += 1;
161 let profile = profile_name_for(tag);
162
163 let schema_kinds = enum_at(&schema, &profile, "kind");
164 let trait_kinds: Vec<&str> = kinds.to_vec();
165 assert_eq!(
166 sort(&schema_kinds),
167 sort(&trait_kinds),
168 "declare_schema.json {profile}.kind enum drifted from `{tag}` trait ALLOWED_KINDS"
169 );
170
171 if visibilities.is_empty() {
172 let schema_vis = enum_at(&schema, &profile, "visibility");
173 assert!(
174 schema_vis.is_empty(),
175 "declare_schema.json {profile} declares visibilities but extractor profile is empty"
176 );
177 } else {
178 let schema_vis = enum_at(&schema, &profile, "visibility");
179 let trait_vis: Vec<&str> = visibilities.to_vec();
180 assert_eq!(
181 sort(&schema_vis),
182 sort(&trait_vis),
183 "declare_schema.json {profile}.visibility enum drifted from `{tag}` trait ALLOWED_VISIBILITIES"
184 );
185 }
186 });
187
188 assert_eq!(
189 visited,
190 super::Lang::ALL.len(),
191 "for_each_language visited {visited} languages but Lang::ALL contains {}; the cfg gates of the dispatch table and the macro variants are out of sync",
192 super::Lang::ALL.len()
193 );
194 }
195
196 fn sort<'a>(xs: &[&'a str]) -> Vec<&'a str> {
197 let mut v: Vec<&str> = xs.to_vec();
198 v.sort_unstable();
199 v
200 }
201}
202
203#[cfg(test)]
204mod shape_coverage_tests {
205 use super::for_each_language;
206 use crate::core::shape::shape_of;
207
208 #[test]
209 fn every_allowed_kind_has_a_shape() {
210 let mut missing: Vec<(String, String)> = Vec::new();
211 for_each_language(|tag, kinds, _| {
212 for k in kinds {
213 if shape_of(k.as_bytes()).is_none() {
214 missing.push((tag.to_string(), (*k).to_string()));
215 }
216 }
217 });
218 assert!(
219 missing.is_empty(),
220 "kinds in ALLOWED_KINDS without an entry in core::shape::SHAPE_TABLE: {missing:?}"
221 );
222 }
223
224 #[test]
225 fn internal_kinds_have_a_shape() {
226 for k in [b"module".as_slice(), b"comment", b"local", b"param"] {
227 assert!(
228 shape_of(k).is_some(),
229 "internal kind {:?} must have a shape entry",
230 std::str::from_utf8(k).unwrap()
231 );
232 }
233 }
234}