@@ -5,10 +5,11 @@ import {should_exclude_path} from '@fuzdev/fuz_util/path.js';
55import { escape_js_string } from '@fuzdev/fuz_util/string.js' ;
66import {
77 find_attribute ,
8- evaluate_static_expr ,
98 extract_static_string ,
9+ try_extract_conditional_chain ,
1010 build_static_bindings ,
1111 resolve_component_names ,
12+ handle_preprocess_error ,
1213 type ResolvedComponentImport ,
1314} from '@fuzdev/fuz_util/svelte_preprocess_helpers.js' ;
1415
@@ -136,7 +137,7 @@ const try_highlight = (
136137 html = syntax_styler . stylize ( text , lang ) ;
137138 options . cache ?. set ( cache_key , html ) ;
138139 } catch ( error ) {
139- handle_error ( error , options ) ;
140+ handle_preprocess_error ( error , '[fuz-code]' , options . filename , options . on_error ) ;
140141 return null ;
141142 }
142143 }
@@ -200,69 +201,46 @@ const find_code_usages = (
200201 return ;
201202 }
202203
203- // Try conditional expression with static string branches
204- const conditional = try_extract_conditional (
204+ // Try conditional chain (handles both simple and nested ternaries)
205+ const chain = try_extract_conditional_chain (
205206 content_attr . value ,
206207 options . source ,
207208 options . bindings ,
208209 ) ;
209- if ( conditional ) {
210- const html_a = try_highlight ( conditional . consequent , lang_value , syntax_styler , options ) ;
211- const html_b = try_highlight ( conditional . alternate , lang_value , syntax_styler , options ) ;
212- if ( html_a === null || html_b === null ) return ;
213- if ( html_a === conditional . consequent && html_b === conditional . alternate ) return ;
210+ if ( chain ) {
211+ // Highlight all branches
212+ const highlighted : Array < { html : string ; original : string } > = [ ] ;
213+ let any_changed = false ;
214+ for ( const branch of chain ) {
215+ const html = try_highlight ( branch . value , lang_value , syntax_styler , options ) ;
216+ if ( html === null ) return ;
217+ if ( html !== branch . value ) any_changed = true ;
218+ highlighted . push ( { html, original : branch . value } ) ;
219+ }
220+ if ( ! any_changed ) return ;
221+
222+ // Build nested ternary expression for dangerous_raw_html
223+ // chain: [{test_source: 'a', value: ...}, {test_source: 'b', value: ...}, {test_source: null, value: ...}]
224+ // → a ? 'html_a' : b ? 'html_b' : 'html_c'
225+ let expr = '' ;
226+ for ( let i = 0 ; i < chain . length ; i ++ ) {
227+ const branch = chain [ i ] ! ;
228+ const html = highlighted [ i ] ! . html ;
229+ if ( branch . test_source !== null ) {
230+ expr += `${ branch . test_source } ? '${ escape_js_string ( html ) } ' : ` ;
231+ } else {
232+ expr += `'${ escape_js_string ( html ) } '` ;
233+ }
234+ }
235+
214236 transformations . push ( {
215237 start : content_attr . start ,
216238 end : content_attr . end ,
217- replacement : `dangerous_raw_html={${ conditional . test_source } ? ' ${ escape_js_string ( html_a ) } ' : ' ${ escape_js_string ( html_b ) } ' }` ,
239+ replacement : `dangerous_raw_html={${ expr } }` ,
218240 } ) ;
219241 }
220242 } ,
221243 } ) ;
222244
223245 return transformations ;
224246} ;
225-
226- type AttributeValue = AST . Attribute [ 'value' ] ;
227-
228- interface ConditionalStaticStrings {
229- test_source : string ;
230- consequent : string ;
231- alternate : string ;
232- }
233-
234- /**
235- * Try to extract a conditional expression where both branches are static strings.
236- * Returns the condition source text and both branch values, or `null` if not applicable.
237- */
238- const try_extract_conditional = (
239- value : AttributeValue ,
240- source : string ,
241- bindings : ReadonlyMap < string , string > ,
242- ) : ConditionalStaticStrings | null => {
243- if ( value === true || Array . isArray ( value ) ) return null ;
244- const expr = value . expression ;
245- if ( expr . type !== 'ConditionalExpression' ) return null ;
246-
247- const consequent = evaluate_static_expr ( expr . consequent , bindings ) ;
248- if ( consequent === null ) return null ;
249- const alternate = evaluate_static_expr ( expr . alternate , bindings ) ;
250- if ( alternate === null ) return null ;
251-
252- const test = expr . test as any ;
253- const test_source = source . slice ( test . start , test . end ) ;
254- return { test_source, consequent, alternate} ;
255- } ;
256-
257- /**
258- * Handle errors during highlighting.
259- */
260- const handle_error = ( error : unknown , options : FindCodeUsagesOptions ) : void => {
261- const message = `[fuz-code] Highlighting failed${ options . filename ? ` in ${ options . filename } ` : '' } : ${ error instanceof Error ? error . message : String ( error ) } ` ;
262-
263- if ( options . on_error === 'throw' ) {
264- throw new Error ( message ) ;
265- }
266- // eslint-disable-next-line no-console
267- console . error ( message ) ;
268- } ;
0 commit comments