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()
    }
}