mago_wasm/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
use std::collections::HashSet;
use serde::Serialize;
use wasm_bindgen::prelude::*;
use mago_ast::Program;
use mago_formatter::settings::FormatSettings;
use mago_interner::StringIdentifier;
use mago_interner::ThreadedInterner;
use mago_parser::parse_source;
use mago_reporting::Issue;
use mago_reporting::IssueCollection;
use mago_semantics::Semantics;
use mago_source::SourceCategory;
use mago_source::SourceManager;
use mago_symbol_table::get_symbols;
use mago_symbol_table::table::SymbolTable;
/// Represents the result of analyzing and optionally formatting PHP code.
///
/// This struct encapsulates various aspects of the PHP code analysis process,
/// providing detailed insights into the source code, including:
/// - Interned strings used in the code.
/// - Abstract syntax tree (AST).
/// - Parse errors, if any.
/// - Resolved names and their metadata.
/// - Symbol table for classes, functions, constants, etc.
/// - Formatted version of the source code (if no parse errors occurred).
///
/// This struct is serialized into JSON for use in WebAssembly and browser environments.
#[derive(Debug, Clone, Serialize)]
struct CodeInsight<'a> {
/// A set of interned strings used in the source code.
///
/// Each string is represented as a tuple containing a `StringIdentifier` and the string value.
pub strings: HashSet<(StringIdentifier, &'a str)>,
/// The abstract syntax tree (AST) resulting from parsing the source code.
pub program: Program,
/// An optional parse error, if one occurred during parsing.
pub parse_error: Option<Issue>,
/// The resolved names within the source code, used for identifier resolution.
///
/// Each resolved name is represented as a tuple containing a byte offset and
/// a tuple containing a `StringIdentifier` and a boolean flag indicating whether the name was imported.
pub names: HashSet<(&'a usize, &'a (StringIdentifier, bool))>,
/// The symbol table containing definitions of classes, functions, constants, etc.
pub symbols: SymbolTable,
/// A collection of semantic issues found during analysis, such as invalid inheritance,
/// improper returns, duplicate names, etc.
pub semantic_issues: IssueCollection,
/// The formatted version of the source code, if there were no parse errors.
pub formatted: Option<String>,
}
/// Formats PHP code using the Mago formatter.
///
/// This function takes a string of PHP code and optionally a JSON string representing formatting settings.
/// It returns the formatted version of the code. If there are any parser errors, it returns the error message instead of the formatted code.
///
/// # Arguments
///
/// * `code` - A string slice containing the PHP code to format.
/// * `settings` - An optional JSON string specifying formatting settings. If not provided or invalid, default settings will be used.
///
/// # Returns
///
/// A `Result<JsValue, JsValue>`:
/// - On success: The formatted PHP code as a `JsValue` (string).
/// - On failure: A `JsValue` (string) containing the parser error message.
///
/// # Formatting Settings
///
/// The `settings` parameter should be a JSON string matching the structure of the `FormatSettings` Rust struct.
///
/// If the `settings` parameter is not provided, the formatter will use the default settings.
///
/// # Example
///
/// ```javascript
/// import init, { mago_format } from "./pkg/mago_wasm.js";
///
/// async function formatCode(phpCode, formatterSettings) {
/// await init(); // Initialize the WASM module
/// try {
/// const formattedCode = mago_format(phpCode, formatterSettings);
/// console.log("Formatted code:", formattedCode);
/// } catch (err) {
/// console.error("Error formatting code:", err);
/// }
/// }
///
/// const phpCode = "<?php echo 'Hello'; ?>";
///
/// // Example with custom settings
/// const settings = JSON.stringify({
/// print_width: 80,
/// tab_width: 2,
/// use_tabs: true,
/// single_quote: true,
/// });
///
/// formatCode(phpCode, settings);
///
/// // Example with default settings
/// formatCode(phpCode, undefined);
/// ```
///
/// # Errors
///
/// If the input PHP code contains syntax errors or cannot be parsed, the function returns a
/// parser error message as a `JsValue` containing the error description.
///
/// # Note
///
/// This function is intended for use in a browser environment through WebAssembly.
#[wasm_bindgen]
pub fn mago_format(code: String, settings: Option<String>) -> Result<JsValue, JsValue> {
let settings = get_format_settings(settings);
let interner = ThreadedInterner::new();
let manager = SourceManager::new(interner.clone());
let source_id = manager.insert_content("code.php".to_string(), code, SourceCategory::UserDefined);
let source = manager.load(&source_id).map_err(|e| JsValue::from_str(&e.to_string()))?;
let (program, parse_error) = parse_source(&interner, &source);
if let Some(err) = parse_error {
return Err(JsValue::from_str(&err.to_string()));
}
let formatted = mago_formatter::format(&interner, &source, &program, settings);
Ok(JsValue::from_str(&formatted))
}
/// Analyzes PHP code and returns detailed insights into its structure and formatting.
///
/// This function takes PHP code as input and provides a comprehensive analysis of it, including:
///
/// - Abstract syntax tree (AST).
/// - Parse errors (if any).
/// - Resolved names and their metadata.
/// - Symbol table containing definitions of classes, functions, constants, etc.
/// - Formatted code (if no parse errors occurred).
///
/// The result is returned as a `CodeInsight` struct serialized into JSON,
/// making it suitable for browser environments through WebAssembly.
///
/// # Arguments
///
/// - `code` - A string containing the PHP code to analyze.
/// - `format_settings` - An optional JSON string specifying formatting settings. If not provided or invalid, default settings will be used.
///
/// # Returns
///
/// A `Result<JsValue, JsValue>`:
/// - On success: A `JsValue` containing the serialized `CodeInsight` object as JSON.
/// - On failure: A `JsValue` containing an error message.
///
/// # Example
///
/// ```javascript
/// import init, { mago_get_insight } from "./pkg/mago_wasm.js";
///
/// async function getCodeInsight(phpCode, formatterSettings) {
/// await init(); // Initialize the WASM module
/// try {
/// const insights = mago_get_insight(phpCode, formatterSettings);
/// console.log("Code insights:", JSON.parse(insights));
/// } catch (err) {
/// console.error("Error analyzing code:", err);
/// }
/// }
///
/// const phpCode = "<?php echo 'Hello'; ?>";
///
/// // Example with custom settings
/// const settings = JSON.stringify({
/// print_width: 80,
/// tab_width: 2,
/// use_tabs: true,
/// single_quote: true,
/// });
///
/// getCodeInsight(phpCode, settings);
///
/// // Example with default settings
/// getCodeInsight(phpCode, undefined);
/// ```
///
/// # Errors
///
/// If the input PHP code cannot be parsed, or if the source manager fails to load the source,
/// the function returns an error message as a `JsValue`.
///
/// # Notes
///
/// - This function is designed for browser environments through WebAssembly.
/// - It is suitable for interactive playgrounds or tools requiring in-depth PHP code analysis.
#[wasm_bindgen]
pub fn mago_get_insight(code: String, format_settings: Option<String>) -> Result<JsValue, JsValue> {
let settings = get_format_settings(format_settings);
let interner = ThreadedInterner::new();
let manager = SourceManager::new(interner.clone());
let source_id = manager.insert_content("code.php".to_string(), code, SourceCategory::UserDefined);
let source = manager.load(&source_id).map_err(|e| JsValue::from_str(&e.to_string()))?;
let semantics = Semantics::build(&interner, source);
let mut formatted = None;
if semantics.parse_error.is_none() {
formatted = Some(mago_formatter::format(&interner, &semantics.source, &semantics.program, settings));
}
let symbols = get_symbols(&interner, &semantics.program);
Ok(serde_wasm_bindgen::to_value(&CodeInsight {
strings: interner.all(),
program: semantics.program,
parse_error: semantics.parse_error.as_ref().map(|e| e.into()),
names: semantics.names.all(),
symbols,
semantic_issues: semantics.issues,
formatted,
})?)
}
fn get_format_settings(settings: Option<String>) -> FormatSettings {
if let Some(settings_json) = settings {
serde_json::from_str::<FormatSettings>(&settings_json).unwrap_or_default()
} else {
FormatSettings::default()
}
}