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}
90
91impl<P: Plugin> Default for NativePluginRule<P> {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl<P: Plugin> NativePluginRule<P> {
98    pub fn new() -> Self {
99        Self::with_plugin(P::default())
100    }
101
102    /// Create a NativePluginRule with a pre-configured plugin instance
103    pub fn with_plugin(plugin: P) -> Self {
104        let spec = plugin.spec();
105
106        // Leak strings for 'static lifetime (same approach as ComponentLintRule)
107        let name: &'static str = Box::leak(spec.name.into_boxed_str());
108        let category: &'static str = Box::leak(spec.category.into_boxed_str());
109        let description: &'static str = Box::leak(spec.description.into_boxed_str());
110        let severity: Option<&'static str> = spec.severity.map(|s| &*Box::leak(s.into_boxed_str()));
111        let why: Option<&'static str> = spec.why.map(|s| &*Box::leak(s.into_boxed_str()));
112        let bad_example: Option<&'static str> =
113            spec.bad_example.map(|s| &*Box::leak(s.into_boxed_str()));
114        let good_example: Option<&'static str> =
115            spec.good_example.map(|s| &*Box::leak(s.into_boxed_str()));
116        let references = spec.references;
117
118        Self {
119            plugin,
120            name,
121            category,
122            description,
123            severity,
124            why,
125            bad_example,
126            good_example,
127            references,
128        }
129    }
130}
131
132impl<P: Plugin + Send + Sync> LintRule for NativePluginRule<P> {
133    fn name(&self) -> &'static str {
134        self.name
135    }
136
137    fn category(&self) -> &'static str {
138        self.category
139    }
140
141    fn description(&self) -> &'static str {
142        self.description
143    }
144
145    fn check(&self, config: &Config, path: &Path) -> Vec<CommonLintError> {
146        let path_str = path.to_string_lossy();
147        let errors = self.plugin.check(config, &path_str);
148        errors.into_iter().map(convert_lint_error).collect()
149    }
150
151    fn severity(&self) -> Option<&str> {
152        self.severity
153    }
154
155    fn why(&self) -> Option<&str> {
156        self.why
157    }
158
159    fn bad_example(&self) -> Option<&str> {
160        self.bad_example
161    }
162
163    fn good_example(&self) -> Option<&str> {
164        self.good_example
165    }
166
167    fn references(&self) -> Option<Vec<String>> {
168        self.references.clone()
169    }
170}