Skip to main content

nginx_lint_plugin/
native.rs

1//! Native plugin adapter.
2//!
3//! Provides [`NativePluginRule<P>`] which wraps a [`Plugin`] implementation
4//! into a [`LintRule`](nginx_lint_common::linter::LintRule), allowing WASM plugins to be run
5//! natively without WASM VM or serialization overhead.
6//!
7//! This is used internally by nginx-lint to embed builtin plugins directly
8//! into the binary when built with the `wasm-builtin-plugins` feature.
9//!
10//! # Example
11//!
12//! ```
13//! use nginx_lint_plugin::prelude::*;
14//! use nginx_lint_plugin::native::NativePluginRule;
15//!
16//! # #[derive(Default)]
17//! # struct MyPlugin;
18//! # impl Plugin for MyPlugin {
19//! #     fn spec(&self) -> PluginSpec {
20//! #         PluginSpec::new("my-rule", "test", "Test rule")
21//! #     }
22//! #     fn check(&self, config: &Config, _path: &str) -> Vec<LintError> {
23//! #         Vec::new()
24//! #     }
25//! # }
26//! // Wrap a plugin as a native lint rule
27//! let rule = NativePluginRule::<MyPlugin>::new();
28//! // `rule` now implements LintRule and can be registered in the linter
29//! ```
30
31use crate::types::{
32    Fix as PluginFix, LintError as PluginLintError, Plugin, Severity as PluginSeverity,
33};
34use nginx_lint_common::linter::{
35    Fix as CommonFix, LintError as CommonLintError, LintRule, Severity as CommonSeverity,
36};
37use nginx_lint_common::parser::ast::Config;
38use std::path::Path;
39
40/// Convert a plugin Fix to a common Fix
41fn convert_fix(fix: PluginFix) -> CommonFix {
42    CommonFix {
43        line: fix.line,
44        old_text: fix.old_text,
45        new_text: fix.new_text,
46        delete_line: fix.delete_line,
47        insert_after: fix.insert_after,
48        start_offset: fix.start_offset,
49        end_offset: fix.end_offset,
50    }
51}
52
53/// Convert a plugin LintError to a common LintError
54fn convert_lint_error(err: PluginLintError) -> CommonLintError {
55    let severity = match err.severity {
56        PluginSeverity::Error => CommonSeverity::Error,
57        PluginSeverity::Warning => CommonSeverity::Warning,
58    };
59
60    let mut common = CommonLintError::new(&err.rule, &err.category, &err.message, severity);
61
62    if let (Some(line), Some(column)) = (err.line, err.column) {
63        common = common.with_location(line, column);
64    } else if let Some(line) = err.line {
65        common = common.with_location(line, 1);
66    }
67
68    for fix in err.fixes {
69        common = common.with_fix(convert_fix(fix));
70    }
71
72    common
73}
74
75/// Adapter that wraps a `Plugin` implementation into a `LintRule`.
76///
77/// This allows running WASM plugin code natively, bypassing the
78/// serialization/deserialization and WASM VM overhead.
79pub struct NativePluginRule<P: Plugin> {
80    plugin: P,
81    name: &'static str,
82    category: &'static str,
83    description: &'static str,
84    severity: Option<&'static str>,
85    why: Option<&'static str>,
86    bad_example: Option<&'static str>,
87    good_example: Option<&'static str>,
88    references: Option<Vec<String>>,
89    min_nginx_version: Option<&'static str>,
90    max_nginx_version: Option<&'static str>,
91}
92
93impl<P: Plugin> Default for NativePluginRule<P> {
94    fn default() -> Self {
95        Self::new()
96    }
97}
98
99impl<P: Plugin> NativePluginRule<P> {
100    pub fn new() -> Self {
101        Self::with_plugin(P::default())
102    }
103
104    /// Create a NativePluginRule with a pre-configured plugin instance
105    pub fn with_plugin(plugin: P) -> Self {
106        let spec = plugin.spec();
107
108        // Leak strings for 'static lifetime (same approach as ComponentLintRule)
109        let name: &'static str = Box::leak(spec.name.into_boxed_str());
110        let category: &'static str = Box::leak(spec.category.into_boxed_str());
111        let description: &'static str = Box::leak(spec.description.into_boxed_str());
112        let severity: Option<&'static str> = spec.severity.map(|s| &*Box::leak(s.into_boxed_str()));
113        let why: Option<&'static str> = spec.why.map(|s| &*Box::leak(s.into_boxed_str()));
114        let bad_example: Option<&'static str> =
115            spec.bad_example.map(|s| &*Box::leak(s.into_boxed_str()));
116        let good_example: Option<&'static str> =
117            spec.good_example.map(|s| &*Box::leak(s.into_boxed_str()));
118        let references = spec.references;
119        let min_nginx_version: Option<&'static str> = spec
120            .min_nginx_version
121            .map(|s| &*Box::leak(s.into_boxed_str()));
122        let max_nginx_version: Option<&'static str> = spec
123            .max_nginx_version
124            .map(|s| &*Box::leak(s.into_boxed_str()));
125
126        Self {
127            plugin,
128            name,
129            category,
130            description,
131            severity,
132            why,
133            bad_example,
134            good_example,
135            references,
136            min_nginx_version,
137            max_nginx_version,
138        }
139    }
140}
141
142impl<P: Plugin + Send + Sync> LintRule for NativePluginRule<P> {
143    fn name(&self) -> &'static str {
144        self.name
145    }
146
147    fn category(&self) -> &'static str {
148        self.category
149    }
150
151    fn description(&self) -> &'static str {
152        self.description
153    }
154
155    fn check(&self, config: &Config, path: &Path) -> Vec<CommonLintError> {
156        let path_str = path.to_string_lossy();
157        let errors = self.plugin.check(config, &path_str);
158        errors.into_iter().map(convert_lint_error).collect()
159    }
160
161    fn severity(&self) -> Option<&str> {
162        self.severity
163    }
164
165    fn why(&self) -> Option<&str> {
166        self.why
167    }
168
169    fn bad_example(&self) -> Option<&str> {
170        self.bad_example
171    }
172
173    fn good_example(&self) -> Option<&str> {
174        self.good_example
175    }
176
177    fn references(&self) -> Option<Vec<String>> {
178        self.references.clone()
179    }
180
181    fn min_nginx_version(&self) -> Option<&str> {
182        self.min_nginx_version
183    }
184
185    fn max_nginx_version(&self) -> Option<&str> {
186        self.max_nginx_version
187    }
188}