@fuzdev/fuz_code 0.44.0 → 0.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"svelte_preprocess_fuz_code.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_fuz_code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,iBAAiB,EAAW,MAAM,iBAAiB,CAAC;AAexE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,wBAAwB;IACxC,gCAAgC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAEjC,0DAA0D;IAC1D,aAAa,CAAC,EAAE,YAAY,CAAC;IAE7B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAElC;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,0BAA0B,GACtC,UAAS,wBAA6B,KACpC,iBA6DF,CAAC"}
1
+ {"version":3,"file":"svelte_preprocess_fuz_code.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_fuz_code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,iBAAiB,EAAW,MAAM,iBAAiB,CAAC;AAgBxE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,wBAAwB;IACxC,gCAAgC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAEjC,0DAA0D;IAC1D,aAAa,CAAC,EAAE,YAAY,CAAC;IAE7B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAElC;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,0BAA0B,GACtC,UAAS,wBAA6B,KACpC,iBA6DF,CAAC"}
@@ -3,7 +3,7 @@ import MagicString from 'magic-string';
3
3
  import { walk } from 'zimmerframe';
4
4
  import { should_exclude_path } from '@fuzdev/fuz_util/path.js';
5
5
  import { escape_js_string } from '@fuzdev/fuz_util/string.js';
6
- import { find_attribute, evaluate_static_expr, extract_static_string, build_static_bindings, resolve_component_names, } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
6
+ import { find_attribute, extract_static_string, try_extract_conditional_chain, build_static_bindings, resolve_component_names, handle_preprocess_error, } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
7
7
  import { syntax_styler_global } from './syntax_styler_global.js';
8
8
  export const svelte_preprocess_fuz_code = (options = {}) => {
9
9
  const { exclude = [], syntax_styler = syntax_styler_global, cache = true, component_imports = ['@fuzdev/fuz_code/Code.svelte'], on_error = process.env.CI === 'true' ? 'throw' : 'log', } = options;
@@ -63,7 +63,7 @@ const try_highlight = (text, lang, syntax_styler, options) => {
63
63
  options.cache?.set(cache_key, html);
64
64
  }
65
65
  catch (error) {
66
- handle_error(error, options);
66
+ handle_preprocess_error(error, '[fuz-code]', options.filename, options.on_error);
67
67
  return null;
68
68
  }
69
69
  }
@@ -117,53 +117,43 @@ const find_code_usages = (ast, syntax_styler, code_names, options) => {
117
117
  });
118
118
  return;
119
119
  }
120
- // Try conditional expression with static string branches
121
- const conditional = try_extract_conditional(content_attr.value, options.source, options.bindings);
122
- if (conditional) {
123
- const html_a = try_highlight(conditional.consequent, lang_value, syntax_styler, options);
124
- const html_b = try_highlight(conditional.alternate, lang_value, syntax_styler, options);
125
- if (html_a === null || html_b === null)
126
- return;
127
- if (html_a === conditional.consequent && html_b === conditional.alternate)
120
+ // Try conditional chain (handles both simple and nested ternaries)
121
+ const chain = try_extract_conditional_chain(content_attr.value, options.source, options.bindings);
122
+ if (chain) {
123
+ // Highlight all branches
124
+ const highlighted = [];
125
+ let any_changed = false;
126
+ for (const branch of chain) {
127
+ const html = try_highlight(branch.value, lang_value, syntax_styler, options);
128
+ if (html === null)
129
+ return;
130
+ if (html !== branch.value)
131
+ any_changed = true;
132
+ highlighted.push({ html, original: branch.value });
133
+ }
134
+ if (!any_changed)
128
135
  return;
136
+ // Build nested ternary expression for dangerous_raw_html
137
+ // chain: [{test_source: 'a', value: ...}, {test_source: 'b', value: ...}, {test_source: null, value: ...}]
138
+ // → a ? 'html_a' : b ? 'html_b' : 'html_c'
139
+ let expr = '';
140
+ for (let i = 0; i < chain.length; i++) {
141
+ const branch = chain[i];
142
+ const html = highlighted[i].html;
143
+ if (branch.test_source !== null) {
144
+ expr += `${branch.test_source} ? '${escape_js_string(html)}' : `;
145
+ }
146
+ else {
147
+ expr += `'${escape_js_string(html)}'`;
148
+ }
149
+ }
129
150
  transformations.push({
130
151
  start: content_attr.start,
131
152
  end: content_attr.end,
132
- replacement: `dangerous_raw_html={${conditional.test_source} ? '${escape_js_string(html_a)}' : '${escape_js_string(html_b)}'}`,
153
+ replacement: `dangerous_raw_html={${expr}}`,
133
154
  });
134
155
  }
135
156
  },
136
157
  });
137
158
  return transformations;
138
159
  };
139
- /**
140
- * Try to extract a conditional expression where both branches are static strings.
141
- * Returns the condition source text and both branch values, or `null` if not applicable.
142
- */
143
- const try_extract_conditional = (value, source, bindings) => {
144
- if (value === true || Array.isArray(value))
145
- return null;
146
- const expr = value.expression;
147
- if (expr.type !== 'ConditionalExpression')
148
- return null;
149
- const consequent = evaluate_static_expr(expr.consequent, bindings);
150
- if (consequent === null)
151
- return null;
152
- const alternate = evaluate_static_expr(expr.alternate, bindings);
153
- if (alternate === null)
154
- return null;
155
- const test = expr.test;
156
- const test_source = source.slice(test.start, test.end);
157
- return { test_source, consequent, alternate };
158
- };
159
- /**
160
- * Handle errors during highlighting.
161
- */
162
- const handle_error = (error, options) => {
163
- const message = `[fuz-code] Highlighting failed${options.filename ? ` in ${options.filename}` : ''}: ${error instanceof Error ? error.message : String(error)}`;
164
- if (options.on_error === 'throw') {
165
- throw new Error(message);
166
- }
167
- // eslint-disable-next-line no-console
168
- console.error(message);
169
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_code",
3
- "version": "0.44.0",
3
+ "version": "0.44.1",
4
4
  "description": "syntax styling utilities and components for TypeScript, Svelte, and Markdown",
5
5
  "glyph": "🎨",
6
6
  "logo": "logo.svg",
@@ -60,7 +60,7 @@
60
60
  "@changesets/changelog-git": "^0.2.1",
61
61
  "@fuzdev/fuz_css": "^0.47.0",
62
62
  "@fuzdev/fuz_ui": "^0.181.1",
63
- "@fuzdev/fuz_util": "^0.49.2",
63
+ "@fuzdev/fuz_util": "^0.50.0",
64
64
  "@ryanatkn/eslint-config": "^0.9.0",
65
65
  "@ryanatkn/gro": "^0.191.0",
66
66
  "@sveltejs/adapter-static": "^3.0.10",
@@ -5,10 +5,11 @@ import {should_exclude_path} from '@fuzdev/fuz_util/path.js';
5
5
  import {escape_js_string} from '@fuzdev/fuz_util/string.js';
6
6
  import {
7
7
  find_attribute,
8
- evaluate_static_expr,
9
8
  extract_static_string,
9
+ try_extract_conditional_chain,
10
10
  build_static_bindings,
11
11
  resolve_component_names,
12
+ handle_preprocess_error,
12
13
  type ResolvedComponentImport,
13
14
  } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
14
15
 
@@ -136,7 +137,7 @@ const try_highlight = (
136
137
  html = syntax_styler.stylize(text, lang);
137
138
  options.cache?.set(cache_key, html);
138
139
  } catch (error) {
139
- handle_error(error, options);
140
+ handle_preprocess_error(error, '[fuz-code]', options.filename, options.on_error);
140
141
  return null;
141
142
  }
142
143
  }
@@ -165,7 +166,7 @@ const find_code_usages = (
165
166
  if (!code_names.has(node.name)) return;
166
167
 
167
168
  // Skip if spread attributes present — can't determine content statically
168
- if (node.attributes.some((attr: any) => attr.type === 'SpreadAttribute')) return;
169
+ if (node.attributes.some((attr) => attr.type === 'SpreadAttribute')) return;
169
170
 
170
171
  const content_attr = find_attribute(node, 'content');
171
172
  if (!content_attr) return;
@@ -200,21 +201,42 @@ const find_code_usages = (
200
201
  return;
201
202
  }
202
203
 
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(
205
206
  content_attr.value,
206
207
  options.source,
207
208
  options.bindings,
208
209
  );
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
+
214
236
  transformations.push({
215
237
  start: content_attr.start,
216
238
  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}}`,
218
240
  });
219
241
  }
220
242
  },
@@ -222,47 +244,3 @@ const find_code_usages = (
222
244
 
223
245
  return transformations;
224
246
  };
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
- };