Skip to main content

wowlab_tidy/languages/rust/rules/interop/
ffi_thin_glue.rs

1#[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    // #t(fn: rust_duplicate_strings) This linter scenario intentionally repeats source fixtures used to verify independent paths.
196    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}