mago_wasm/lib.rs
1//! # Mago WASM Bindings
2//!
3//! This crate provides [wasm-bindgen] exports that wrap Mago’s internal
4//! functionality (formatter, parser, linter, etc.) so they can be called
5//! from JavaScript in a WebAssembly environment.
6//!
7//! ## Overview
8//!
9//! - **`mago_get_definitions`**: Returns metadata about all available
10//! plugins and rules in Mago.
11//! - **`mago_analysis`**: Parses, lints, and optionally formats a given
12//! PHP snippet and returns structured results.
13//! - **`mago_format`**: Formats a given PHP snippet according to the
14//! specified [FormatSettings].
15//!
16//! See each function’s documentation below for details on usage and
17//! return values.
18
19use mago_formatter::Formatter;
20use wasm_bindgen::prelude::*;
21
22use mago_formatter::settings::FormatSettings;
23use mago_interner::ThreadedInterner;
24use mago_linter::definition::PluginDefinition;
25use mago_linter::definition::RuleDefinition;
26use mago_linter::plugin::Plugin;
27use mago_linter::settings::Settings;
28use mago_php_version::PHPVersion;
29
30use crate::analysis::AnalysisResults;
31
32/// The `analysis` module contains data structures and logic
33/// used to run combined parsing, linting, and formatting
34/// operations within this WASM crate.
35pub mod analysis;
36
37/// Returns an array (serialized to JS via [`JsValue`]) of **all** plugin
38/// definitions and their associated rules.
39///
40/// Each element in the returned array is a tuple:
41/// `(PluginDefinition, Vec<RuleDefinition>)`.
42///
43/// # Errors
44///
45/// Returns a [`JsValue`] error if the serialization to
46/// [`JsValue`] fails (unlikely).
47///
48/// # Example (JS Usage)
49/// ```js
50/// import init, { mago_get_definitions } from 'mago_wasm';
51///
52/// await init();
53/// const defs = mago_get_definitions();
54/// console.log(defs); // An array of plugin/rule definitions
55/// ```
56#[wasm_bindgen]
57pub fn mago_get_definitions() -> Result<JsValue, JsValue> {
58 let mut plugins: Vec<(PluginDefinition, Vec<RuleDefinition>)> = Vec::new();
59
60 // Gather all plugin definitions and their rules
61 mago_linter::foreach_plugin!(|p| {
62 let plugin: Box<dyn Plugin> = Box::new(p);
63 let definition = plugin.get_definition();
64 let rules = plugin.get_rules().into_iter().map(|r| r.get_definition()).collect::<Vec<_>>();
65 plugins.push((definition, rules));
66 });
67
68 // Serialize to JS-friendly output
69 Ok(serde_wasm_bindgen::to_value(&plugins)?)
70}
71
72/// Performs a full “analysis” of the given `code` string:
73/// 1. **Parse** the PHP code and detect any syntax errors.
74/// 2. **Lint** the code using the provided linter settings.
75/// 3. **Optionally** format the code if `format_settings` is provided.
76///
77/// Returns an [`AnalysisResults`] object (serialized to JS), which
78/// contains any parse errors, semantic issues, linter issues, and
79/// the formatted code (if no syntax errors were encountered).
80///
81/// # Arguments
82///
83/// * `code` - A string containing the PHP code to analyze.
84/// * `format_settings` - A [`JsValue`] representing a
85/// [`FormatSettings`](mago_formatter::settings::FormatSettings)
86/// struct, or `null`/`undefined` to use the default settings.
87/// * `linter_settings` - A [`JsValue`] representing a
88/// [`Settings`](mago_linter::settings::Settings) struct, or
89/// `null`/`undefined` to use the default settings (PHP 8.4).
90///
91/// # Errors
92///
93/// Returns a [`JsValue`] (string) error if deserialization of
94/// the provided settings fails, or if parsing/analysis fails.
95///
96/// # Example (JS Usage)
97/// ```js
98/// import init, { mago_analysis } from 'mago_wasm';
99///
100/// await init();
101/// const code = `<?php echo "Hello World"; ?>`;
102/// const formatSettings = { indent_size: 2 };
103/// const linterSettings = { php_version: "8.1" };
104///
105/// const analysis = mago_analysis(code, formatSettings, linterSettings);
106/// console.log(analysis); // { parse_error: null, linter_issues: [...], formatted: "...", etc. }
107/// ```
108#[wasm_bindgen]
109pub fn mago_analysis(code: String, format_settings: JsValue, linter_settings: JsValue) -> Result<JsValue, JsValue> {
110 // Deserialize or use defaults
111 let linter_settings = if !linter_settings.is_undefined() && !linter_settings.is_null() {
112 serde_wasm_bindgen::from_value::<Settings>(linter_settings)?
113 } else {
114 Settings::new(PHPVersion::PHP84)
115 };
116
117 let format_settings = if !format_settings.is_undefined() && !format_settings.is_null() {
118 serde_wasm_bindgen::from_value::<FormatSettings>(format_settings)?
119 } else {
120 FormatSettings::default()
121 };
122
123 // Run analysis
124 let results = AnalysisResults::analyze(code, linter_settings, format_settings);
125
126 // Return the analysis result as a JS object
127 Ok(serde_wasm_bindgen::to_value(&results)?)
128}
129
130/// Formats the provided `code` string with the given
131/// [`FormatSettings`], returning the resulting string.
132///
133/// # Arguments
134///
135/// * `code` - The PHP code to be formatted.
136/// * `format_settings` - A [`JsValue`] representing
137/// a [`FormatSettings`](mago_formatter::settings::FormatSettings)
138/// struct, or `null`/`undefined` to use defaults.
139///
140/// # Errors
141///
142/// Returns a [`JsValue`] (string) error if the code is invalid
143/// (i.e., parse error) or if deserialization of `format_settings` fails.
144///
145/// # Example (JS Usage)
146/// ```js
147/// import init, { mago_format } from 'mago_wasm';
148///
149/// await init();
150/// const code = `<?php echo "Hello"; ?>`;
151/// const fmtSet = { indent_size: 4, max_line_length: 100 };
152///
153/// try {
154/// const formatted = mago_format(code, fmtSet);
155/// console.log(formatted);
156/// } catch (e) {
157/// console.error("Formatting failed:", e);
158/// }
159/// ```
160#[wasm_bindgen]
161pub fn mago_format(code: String, format_settings: JsValue) -> Result<JsValue, JsValue> {
162 // Deserialize or default
163 let settings = if !format_settings.is_undefined() && !format_settings.is_null() {
164 serde_wasm_bindgen::from_value::<FormatSettings>(format_settings)?
165 } else {
166 FormatSettings::default()
167 };
168
169 // Prepare interner and source manager
170 let interner = ThreadedInterner::new();
171
172 let formatter = Formatter::new(&interner, PHPVersion::PHP84, settings);
173 // Format the parsed program
174 formatter
175 .format_code("code.php", &code)
176 .map(|s| JsValue::from_str(&s))
177 .map_err(|err| JsValue::from_str(&err.to_string()))
178}