1212//! failure it is `{"error": "..."}` so the playground can parse it
1313//! uniformly and render diagnostics.
1414
15+ use cellscript:: error:: { CompileError , Span } ;
16+ use serde:: Serialize ;
1517use wasm_bindgen:: prelude:: * ;
1618
19+ #[ derive( Serialize ) ]
20+ struct CompileDiagnosticRange {
21+ start : CompileDiagnosticPosition ,
22+ end : CompileDiagnosticPosition ,
23+ }
24+
25+ #[ derive( Serialize ) ]
26+ struct CompileDiagnosticPosition {
27+ line : usize ,
28+ column : usize ,
29+ offset : usize ,
30+ }
31+
32+ #[ derive( Serialize ) ]
33+ struct CompileDiagnostic {
34+ message : String ,
35+ severity : & ' static str ,
36+ code : Option < String > ,
37+ range : Option < CompileDiagnosticRange > ,
38+ }
39+
40+ #[ derive( Serialize ) ]
41+ struct CompileDiagnosticResult < T : Serialize > {
42+ metadata : Option < T > ,
43+ diagnostics : Vec < CompileDiagnostic > ,
44+ }
45+
46+ #[ derive( Serialize ) ]
47+ struct LanguageServiceResult {
48+ completions : Vec < cellscript:: lsp:: CompletionItem > ,
49+ hover : Option < cellscript:: lsp:: Hover > ,
50+ definition : Option < cellscript:: lsp:: Location > ,
51+ diagnostics : Vec < cellscript:: lsp:: Diagnostic > ,
52+ }
53+
1754/// Compile CellScript source to metadata JSON (path A, no ELF).
1855///
1956/// Returns a JSON string. On success this is the serialized
@@ -31,6 +68,51 @@ pub fn compile_metadata_json(source: &str, target: Option<String>) -> String {
3168 }
3269}
3370
71+ /// Compile CellScript source and return a stable result envelope for tools.
72+ ///
73+ /// On success the response is:
74+ /// `{ "metadata": <CompileMetadata>, "diagnostics": [] }`
75+ ///
76+ /// On failure the response is:
77+ /// `{ "metadata": null, "diagnostics": [{ message, severity, code, range }, ...] }`
78+ ///
79+ /// `range` is omitted when the compiler error is not tied to a source
80+ /// span. Offsets are UTF-8 byte offsets from the original source; line and
81+ /// column are 1-based.
82+ #[ wasm_bindgen]
83+ pub fn compile_metadata_json_diagnostics ( source : & str , target : Option < String > ) -> String {
84+ let report = cellscript:: compile_metadata_with_diagnostics ( source, target) ;
85+ let result = CompileDiagnosticResult {
86+ metadata : report. metadata ,
87+ diagnostics : report. diagnostics . iter ( ) . map ( |error| diagnostic_from_error ( error, source) ) . collect ( ) ,
88+ } ;
89+ serde_json:: to_string ( & result)
90+ . unwrap_or_else ( |e| diagnostic_error_json ( & format ! ( "failed to serialize diagnostic report: {e}" ) , source) )
91+ }
92+
93+ /// Query the in-process CellScript language service for browser tooling.
94+ ///
95+ /// `line` and `character` are zero-based UTF-16 positions, matching LSP.
96+ /// The result contains completion, hover, definition and current document
97+ /// diagnostics in one JSON payload so the playground can avoid multiple
98+ /// WASM calls per cursor move.
99+ #[ wasm_bindgen]
100+ pub fn language_service_json ( source : & str , line : u32 , character : u32 ) -> String {
101+ let uri = "file:///playground.cell" ;
102+ let position = cellscript:: lsp:: Position { line, character } ;
103+ let mut server = cellscript:: lsp:: LspServer :: new ( ) ;
104+ server. open_document ( uri. to_string ( ) , source. to_string ( ) ) ;
105+ let result = LanguageServiceResult {
106+ completions : server. completion ( uri, position) ,
107+ hover : server. hover ( uri, position) ,
108+ definition : server. goto_definition ( uri, position) ,
109+ diagnostics : server. get_diagnostics ( uri) ,
110+ } ;
111+ serde_json:: to_string ( & result) . unwrap_or_else ( |error| {
112+ serde_json:: json!( { "error" : format!( "failed to serialize language service result: {error}" ) } ) . to_string ( )
113+ } )
114+ }
115+
34116/// Return the compiler version string (e.g. "0.17.0").
35117#[ wasm_bindgen]
36118pub fn version ( ) -> String {
@@ -40,3 +122,54 @@ pub fn version() -> String {
40122fn error_json ( message : & str ) -> String {
41123 serde_json:: json!( { "error" : message } ) . to_string ( )
42124}
125+
126+ fn diagnostic_error_json ( message : & str , source : & str ) -> String {
127+ let result: CompileDiagnosticResult < serde_json:: Value > = CompileDiagnosticResult {
128+ metadata : None ,
129+ diagnostics : vec ! [ diagnostic_from_error( & CompileError :: without_span( message) , source) ] ,
130+ } ;
131+ serde_json:: to_string ( & result) . unwrap_or_else ( |_| {
132+ serde_json:: json!( { "metadata" : null, "diagnostics" : [ { "message" : message, "severity" : "error" } ] } ) . to_string ( )
133+ } )
134+ }
135+
136+ fn diagnostic_from_error ( error : & CompileError , source : & str ) -> CompileDiagnostic {
137+ CompileDiagnostic {
138+ message : error. message . clone ( ) ,
139+ severity : error. severity . label ( ) ,
140+ code : error. code . clone ( ) ,
141+ range : span_range ( error. span , source) ,
142+ }
143+ }
144+
145+ fn span_range ( span : Span , source : & str ) -> Option < CompileDiagnosticRange > {
146+ if span. line == 0 || span. column == 0 {
147+ return None ;
148+ }
149+ let source_len = source. len ( ) ;
150+ let start = span. start . min ( source_len) ;
151+ let end = span. end . min ( source_len) . max ( start) ;
152+ let ( end_line, end_column) = line_column_at ( source, end) ;
153+ Some ( CompileDiagnosticRange {
154+ start : CompileDiagnosticPosition { line : span. line , column : span. column , offset : start } ,
155+ end : CompileDiagnosticPosition { line : end_line, column : end_column, offset : end } ,
156+ } )
157+ }
158+
159+ fn line_column_at ( source : & str , byte_offset : usize ) -> ( usize , usize ) {
160+ let mut line = 1 ;
161+ let mut column = 1 ;
162+ let capped_offset = byte_offset. min ( source. len ( ) ) ;
163+ for ( offset, ch) in source. char_indices ( ) {
164+ if offset >= capped_offset {
165+ break ;
166+ }
167+ if ch == '\n' {
168+ line += 1 ;
169+ column = 1 ;
170+ } else {
171+ column += 1 ;
172+ }
173+ }
174+ ( line, column)
175+ }
0 commit comments