viewpoint_js/
lib.rs

1//! # Viewpoint JS - Compile-Time JavaScript Validation
2//!
3//! This crate provides the `js!` macro for compile-time JavaScript validation,
4//! catching syntax errors before they reach the browser. Similar to how
5//! `serde_json::json!` validates JSON at compile time.
6//!
7//! ## Features
8//!
9//! - **Compile-time validation**: JavaScript syntax errors are caught during compilation
10//! - **Value interpolation**: Embed Rust values using `#{expr}` syntax (quoted/escaped)
11//! - **Raw interpolation**: Inject pre-built JavaScript using `@{expr}` syntax (unquoted)
12//! - **Zero runtime overhead**: Static strings when no interpolation is used
13//! - **Clear error messages**: Points to the exact location of syntax errors
14//! - **Full JavaScript syntax**: Single-quoted strings, template literals, regex, XPath, and more
15//!
16//! ## Quick Start
17//!
18//! ```no_run
19//! use viewpoint_js::js;
20//! use viewpoint_js_core::ToJsValue;
21//!
22//! // Simple expression - produces &'static str
23//! let code = js!{ 1 + 2 };
24//! assert_eq!(code, "1 + 2");
25//!
26//! // Arrow function
27//! let code = js!{ () => window.innerWidth };
28//!
29//! // With value interpolation (requires ToJsValue in scope)
30//! let selector = ".my-class";
31//! let code = js!{ document.querySelector(#{selector}) };
32//!
33//! // Multi-line function
34//! let code = js!{
35//!     (() => {
36//!         const items = document.querySelectorAll("li");
37//!         return items.length;
38//!     })()
39//! };
40//! ```
41//!
42//! ## Value Interpolation (`#{expr}`)
43//!
44//! Use `#{expr}` to embed Rust values into JavaScript. Values are automatically
45//! converted to JavaScript representations via the [`ToJsValue`] trait:
46//!
47//! - Strings are quoted and escaped
48//! - Numbers are inserted as-is
49//! - Booleans become `true` or `false`
50//! - `Option::None` becomes `null`
51//!
52//! ```no_run
53//! use viewpoint_js::js;
54//! use viewpoint_js_core::ToJsValue;
55//!
56//! let name = "John";
57//! let age = 25;
58//! let active = true;
59//! let optional: Option<i32> = None;
60//!
61//! // Strings are quoted: document.querySelector("John")
62//! let code = js!{ document.querySelector(#{name}) };
63//!
64//! // Numbers as-is: console.log(25)
65//! let code = js!{ console.log(#{age}) };
66//!
67//! // Booleans: element.disabled = true
68//! let code = js!{ element.disabled = #{active} };
69//!
70//! // None becomes null: setConfig(null)
71//! let code = js!{ setConfig(#{optional}) };
72//! ```
73//!
74//! ## Raw Interpolation (`@{expr}`)
75//!
76//! Use `@{expr}` to inject pre-built JavaScript expressions directly without
77//! quoting or escaping. The expression must return something that implements
78//! `AsRef<str>`. This is useful for:
79//!
80//! - Injecting dynamically-built selector expressions
81//! - Composing JavaScript from multiple parts
82//! - Including pre-validated JavaScript fragments
83//!
84//! ```no_run
85//! use viewpoint_js::js;
86//!
87//! // Build a selector dynamically
88//! let selector_expr = "'.item-' + index";
89//! let code = js!{ document.querySelector(@{selector_expr}) };
90//! // Produces: document.querySelector('.item-' + index)
91//!
92//! // Compose JavaScript fragments
93//! let function_call = "myFunction()";
94//! let code = js!{ const result = @{function_call} };
95//! // Produces: const result = myFunction()
96//! ```
97//!
98//! ## Output Type
99//!
100//! - **Without interpolation**: Returns `&'static str` (zero runtime cost)
101//! - **With interpolation**: Returns `String` (runtime string building)
102//!
103//! ```no_run
104//! use viewpoint_js::js;
105//! use viewpoint_js_core::ToJsValue;
106//!
107//! // Static string, no allocation
108//! let code: &'static str = js!{ 1 + 2 };
109//!
110//! // Dynamic string due to interpolation
111//! let x = 5;
112//! let code: String = js!{ 1 + #{x} };
113//! ```
114//!
115//! ## Compile-Time Error Detection
116//!
117//! Invalid JavaScript produces clear compile-time errors:
118//!
119//! ```text
120//! // This will produce a compile-time error because the JavaScript is invalid
121//! use viewpoint_js::js;
122//! let code = js!{ function( };
123//! // Error: unexpected end of input
124//! ```
125//!
126//! ## Supported JavaScript Syntax
127//!
128//! The macro supports a wide range of JavaScript syntax:
129//!
130//! ```text
131//! use viewpoint_js::js;
132//!
133//! // Single-quoted strings
134//! let code = js!{ document.querySelector('div') };
135//!
136//! // Template literals
137//! let code = js!{ `Hello ${name}` };
138//!
139//! // Arrow functions
140//! let code = js!{ (x) => x * 2 };
141//!
142//! // Object literals
143//! let code = js!{ { key: "value", nested: { x: 1 } } };
144//!
145//! // Array literals
146//! let code = js!{ [1, 2, 3].map(x => x * 2) };
147//!
148//! // Regular expressions
149//! let code = js!{ /pattern/gi };
150//!
151//! // XPath expressions
152//! let code = js!{ document.evaluate("//div", document) };
153//!
154//! // Async/await
155//! let code = js!{ async () => await fetch('/api') };
156//!
157//! // Classes
158//! let code = js!{ class Foo extends Bar { constructor() { super(); } } };
159//! ```
160//!
161//! ## Integration with Viewpoint Core
162//!
163//! The `js!` macro is designed for use with Viewpoint's JavaScript evaluation:
164//!
165//! ```ignore
166//! use viewpoint_core::Page;
167//! use viewpoint_js::js;
168//! use viewpoint_js_core::ToJsValue;
169//!
170//! # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
171//! // Evaluate simple expression
172//! let width: i32 = page.evaluate(js!{ window.innerWidth }).await?;
173//!
174//! // Evaluate with interpolation
175//! let selector = "button.primary";
176//! let result: serde_json::Value = page.evaluate(
177//!     &js!{ document.querySelector(#{selector})?.textContent }
178//! ).await?;
179//!
180//! // Multi-line script
181//! let items: Vec<String> = page.evaluate(js!{
182//!     Array.from(document.querySelectorAll("li"))
183//!         .map(el => el.textContent)
184//! }).await?;
185//! # Ok(())
186//! # }
187//! ```
188//!
189//! [`ToJsValue`]: viewpoint_js_core::ToJsValue
190
191use proc_macro::TokenStream;
192
193mod interpolation;
194mod js_macro;
195mod parser;
196mod scanner;
197
198/// A macro that validates JavaScript syntax at compile time.
199///
200/// This macro accepts JavaScript code and validates its syntax during compilation.
201/// If the JavaScript is invalid, a compile-time error is produced with details
202/// about the syntax error.
203///
204/// # Output Type
205///
206/// - Without interpolation: Returns `&'static str`
207/// - With interpolation: Returns `String`
208///
209/// # Examples
210///
211/// ## Simple Expression
212///
213/// ```no_run
214/// use viewpoint_js::js;
215///
216/// let code: &str = js!{ 1 + 2 };
217/// assert_eq!(code, "1 + 2");
218/// ```
219///
220/// ## Arrow Function
221///
222/// ```no_run
223/// use viewpoint_js::js;
224///
225/// let code = js!{ () => window.innerWidth };
226/// ```
227///
228/// ## With Interpolation
229///
230/// ```no_run
231/// use viewpoint_js::js;
232/// use viewpoint_js_core::ToJsValue;
233///
234/// let selector = ".my-class";
235/// let code: String = js!{ document.querySelector(#{selector}) };
236/// ```
237///
238/// ## Invalid JavaScript (Compile Error)
239///
240/// ```text
241/// // This will produce a compile-time error because the JavaScript is invalid
242/// use viewpoint_js::js;
243/// let code = js!{ function( };
244/// // Error: unexpected end of input
245/// ```
246#[proc_macro]
247pub fn js(input: TokenStream) -> TokenStream {
248    js_macro::js_impl(input)
249}