wowlab_tidy/languages/rust/rules/interop/
ffi_thin_glue.rs1#[cfg(test)]
2use googletest::prelude::*;
3use ra_ap_syntax::{
4 AstNode,
5 ast::{self, HasName},
6};
7
8use crate::{AstCtx, Example, Violation};
9
10#[rustfmt::skip]
11const EXAMPLES: &[Example] = &[
12 Example {
13 label: "thin translation glue",
14 code: "pub extern \"C\" fn transmit(data: *const u8, len: usize) -> u8 {\n match core_transmit(data, len) {\n Ok(()) => 0,\n Err(_) => 1,\n }\n}",
15 pass: true,
16 },
17 Example {
18 label: "extern C fn at the line threshold",
19 code: "pub extern \"C\" fn at_limit() {
20 let a01 = ();
21 let a02 = ();
22 let a03 = ();
23 let a04 = ();
24 let a05 = ();
25 let a06 = ();
26 let a07 = ();
27 let a08 = ();
28 let a09 = ();
29 let a10 = ();
30 let a11 = ();
31 let a12 = ();
32 let a13 = ();
33 let a14 = ();
34 let a15 = ();
35 let a16 = ();
36 let a17 = ();
37 let a18 = ();
38 let a19 = ();
39 let a20 = ();
40 let a21 = ();
41 let a22 = ();
42 let a23 = ();
43 let a24 = ();
44}",
45 pass: true,
46 },
47 Example {
48 label: "extern C fn over the line threshold",
49 code: "pub extern \"C\" fn business_logic() {
50 let a01 = ();
51 let a02 = ();
52 let a03 = ();
53 let a04 = ();
54 let a05 = ();
55 let a06 = ();
56 let a07 = ();
57 let a08 = ();
58 let a09 = ();
59 let a10 = ();
60 let a11 = ();
61 let a12 = ();
62 let a13 = ();
63 let a14 = ();
64 let a15 = ();
65 let a16 = ();
66 let a17 = ();
67 let a18 = ();
68 let a19 = ();
69 let a20 = ();
70 let a21 = ();
71 let a22 = ();
72 let a23 = ();
73 let a24 = ();
74 let a25 = ();
75 let a26 = ();
76}",
77 pass: false,
78 },
79 Example {
80 label: "long plain fn is not glue",
81 code: "fn core_logic() {
82 let a01 = ();
83 let a02 = ();
84 let a03 = ();
85 let a04 = ();
86 let a05 = ();
87 let a06 = ();
88 let a07 = ();
89 let a08 = ();
90 let a09 = ();
91 let a10 = ();
92 let a11 = ();
93 let a12 = ();
94 let a13 = ();
95 let a14 = ();
96 let a15 = ();
97 let a16 = ();
98 let a17 = ();
99 let a18 = ();
100 let a19 = ();
101 let a20 = ();
102 let a21 = ();
103 let a22 = ();
104 let a23 = ();
105 let a24 = ();
106 let a25 = ();
107 let a26 = ();
108}",
109 pass: true,
110 },
111];
112
113crate::ast_rule!(
114 ffi_thin_glue,
115 "Flag `extern \"C\"` functions in `*-ffi` crates whose body exceeds the line threshold.",
116 "FFI glue only translates between C and Rust constructs; operational logic belongs in the core crate as idiomatic, safe, testable Rust (M-FFI-TRANSLATES).",
117 Low,
118 params {
119 threshold: i64 = 25
120 },
121);
122
123fn check_ffi_thin_glue(ctx: &AstCtx<'_>) -> Vec<Violation> {
124 let Some(name) = super::crate_name(ctx.file.rel) else {
125 return Vec::new();
126 };
127
128 if !super::is_ffi_crate(name) {
129 return Vec::new();
130 }
131
132 let threshold = ctx.file.config.get_usize("rust_ffi_thin_glue", &PARAMS[0]);
133
134 ctx.nodes::<ast::Fn>()
135 .filter(|function| {
136 super::support::is_item_or_impl_fn(function)
137 && !ctx.is_in_test(function)
138 && super::is_extern_c(function.abi())
139 })
140 .filter_map(|function| {
141 let body = function.body()?;
142 let start = ctx.line_of(&body);
143 let end = ctx
144 .line_index
145 .line_col(body.syntax().text_range().end())
146 .line as usize
147 + 1;
148 let lines = end.saturating_sub(start);
149
150 (lines > threshold).then(|| {
151 let name = function.name()?;
152
153 Some(ctx.violation(
154 &name,
155 format!(
156 "C glue fn `{name}` spans {lines} lines (max {threshold}) — glue translates, business logic belongs in the core crate"
157 ),
158 ))
159 })?
160 })
161 .collect()
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 const FFI_REL: &str = "crates/engine-ffi/src/lib.rs";
169
170 fn run_at(rel: &str, source: &str) -> Vec<Violation> {
171 crate::test_support::check_source_ast_at(rel, source, check_ffi_thin_glue)
172 }
173
174 #[gtest]
175 fn examples() -> Result<()> {
176 for ex in EXAMPLES {
177 let violations = run_at(FFI_REL, ex.code);
178
179 verify_eq!(violations.is_empty(), ex.pass)?;
180 }
181
182 Ok(())
183 }
184
185 #[gtest]
186 fn underscore_ffi_crates_are_gated_too() -> Result<()> {
187 let fail = EXAMPLES.iter().find(|ex| !ex.pass).or_fail()?;
188
189 verify_false!(run_at("crates/engine_ffi/src/lib.rs", fail.code).is_empty())?;
190
191 Ok(())
192 }
193
194 #[gtest]
195 fn non_ffi_crates_are_skipped() -> Result<()> {
197 let fail = EXAMPLES.iter().find(|ex| !ex.pass).or_fail()?;
198
199 verify_true!(run_at("crates/engine/src/lib.rs", fail.code).is_empty())?;
200 verify_true!(run_at("clean.rs", fail.code).is_empty())?;
201
202 Ok(())
203 }
204}