ripple 0.2.183 → 0.2.185

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.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.183",
6
+ "version": "0.2.185",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -86,6 +86,6 @@
86
86
  "vscode-languageserver-types": "^3.17.5"
87
87
  },
88
88
  "peerDependencies": {
89
- "ripple": "0.2.183"
89
+ "ripple": "0.2.185"
90
90
  }
91
91
  }
@@ -423,6 +423,11 @@ const visitors = {
423
423
  context.visit(node.discriminant, context.state);
424
424
 
425
425
  for (const switch_case of node.cases) {
426
+ // Fallthrough
427
+ if (switch_case.consequent.length === 0) {
428
+ continue;
429
+ }
430
+
426
431
  // Validate that each cases ends in a break statement, except for the last case
427
432
  const last = switch_case.consequent?.[switch_case.consequent.length - 1];
428
433
 
@@ -728,9 +733,9 @@ const visitors = {
728
733
  }
729
734
  if (state.inside_head) {
730
735
  if (node.id.name === 'title') {
731
- const chiildren = normalize_children(node.children, context);
736
+ const children = normalize_children(node.children, context);
732
737
 
733
- if (chiildren.length !== 1 || chiildren[0].type !== 'Text') {
738
+ if (children.length !== 1 || children[0].type !== 'Text') {
734
739
  error(
735
740
  '<title> must have only contain text nodes',
736
741
  state.analysis.module.filename,
@@ -61,17 +61,6 @@ import {
61
61
  } from '../../../../utils/events.js';
62
62
  import { createHash } from 'node:crypto';
63
63
 
64
- /**
65
- * @param {TransformClientContext} context
66
- */
67
- function add_ripple_internal_import(context) {
68
- if (!context.state.to_ts) {
69
- if (!context.state.imports.has(`import * as _$_ from 'ripple/internal/client'`)) {
70
- context.state.imports.add(`import * as _$_ from 'ripple/internal/client'`);
71
- }
72
- }
73
- }
74
-
75
64
  /**
76
65
  *
77
66
  * @param {AST.FunctionDeclaration | AST.FunctionExpression | AST.ArrowFunctionExpression} node
@@ -112,7 +101,6 @@ function visit_function(node, context) {
112
101
  const new_body = [];
113
102
 
114
103
  if (!is_inside_component(context, true) && is_component_level_function(context)) {
115
- add_ripple_internal_import(context);
116
104
  new_body.push(b.var('__block', b.call('_$_.scope')));
117
105
  }
118
106
  if (body.type === 'BlockStatement') {
@@ -397,11 +385,9 @@ const visitors = {
397
385
  context.state.metadata.tracking = true;
398
386
  }
399
387
  if (node.tracked) {
400
- add_ripple_internal_import(context);
401
388
  return b.call('_$_.get', build_getter(node, context));
402
389
  }
403
390
  }
404
- add_ripple_internal_import(context);
405
391
  return build_getter(node, context);
406
392
  }
407
393
  }
@@ -663,8 +649,6 @@ const visitors = {
663
649
 
664
650
  if (node.tracked || (node.property.type === 'Identifier' && node.property.tracked)) {
665
651
  // In TypeScript mode, skip the transformation and let transform_ts_child handle it
666
- add_ripple_internal_import(context);
667
-
668
652
  if (!context.state.to_ts) {
669
653
  return b.call(
670
654
  '_$_.get_property',
@@ -1595,9 +1579,6 @@ const visitors = {
1595
1579
 
1596
1580
  Component(node, context) {
1597
1581
  let prop_statements;
1598
-
1599
- add_ripple_internal_import(context);
1600
-
1601
1582
  const metadata = { await: false };
1602
1583
 
1603
1584
  if (context.state.to_ts) {
@@ -1683,7 +1664,6 @@ const visitors = {
1683
1664
  left.type === 'MemberExpression' &&
1684
1665
  (left.tracked || (left.property.type === 'Identifier' && left.property.tracked))
1685
1666
  ) {
1686
- add_ripple_internal_import(context);
1687
1667
  const operator = node.operator;
1688
1668
  const right = node.right;
1689
1669
 
@@ -1710,7 +1690,6 @@ const visitors = {
1710
1690
  }
1711
1691
 
1712
1692
  if (left.type === 'Identifier' && left.tracked) {
1713
- add_ripple_internal_import(context);
1714
1693
  const operator = node.operator;
1715
1694
  const right = node.right;
1716
1695
 
@@ -1744,7 +1723,6 @@ const visitors = {
1744
1723
  argument.type === 'MemberExpression' &&
1745
1724
  (argument.tracked || (argument.property.type === 'Identifier' && argument.property.tracked))
1746
1725
  ) {
1747
- add_ripple_internal_import(context);
1748
1726
  if (context.state.metadata?.tracking === false) {
1749
1727
  context.state.metadata.tracking = true;
1750
1728
  }
@@ -1854,27 +1832,34 @@ const visitors = {
1854
1832
  let i = 1;
1855
1833
 
1856
1834
  for (const switch_case of node.cases) {
1857
- const consequent_scope =
1858
- context.state.scopes.get(switch_case.consequent) || context.state.scope;
1859
- const consequent_id = context.state.scope.generate(
1860
- 'switch_case_' + (switch_case.test == null ? 'default' : i),
1861
- );
1862
- const consequent = b.block(
1863
- transform_body(switch_case.consequent, {
1864
- ...context,
1865
- state: { ...context.state, scope: consequent_scope },
1866
- }),
1867
- );
1835
+ const case_body = [];
1868
1836
 
1869
- statements.push(b.var(b.id(consequent_id), b.arrow([b.id('__anchor')], consequent)));
1837
+ if (switch_case.consequent.length !== 0) {
1838
+ const consequent_scope =
1839
+ context.state.scopes.get(switch_case.consequent) || context.state.scope;
1840
+ const consequent_id = context.state.scope.generate(
1841
+ 'switch_case_' + (switch_case.test == null ? 'default' : i),
1842
+ );
1843
+ const consequent = b.block(
1844
+ transform_body(switch_case.consequent, {
1845
+ ...context,
1846
+ state: { ...context.state, scope: consequent_scope },
1847
+ }),
1848
+ );
1849
+
1850
+ statements.push(b.var(b.id(consequent_id), b.arrow([b.id('__anchor')], consequent)));
1851
+
1852
+ case_body.push(b.return(b.id(consequent_id)));
1853
+
1854
+ i++;
1855
+ }
1870
1856
 
1871
1857
  cases.push(
1872
1858
  b.switch_case(
1873
1859
  switch_case.test ? /** @type {AST.Expression} */ (context.visit(switch_case.test)) : null,
1874
- [b.return(b.id(consequent_id))],
1860
+ case_body,
1875
1861
  ),
1876
1862
  );
1877
- i++;
1878
1863
  }
1879
1864
 
1880
1865
  statements.push(
@@ -2911,10 +2896,10 @@ function transform_body(body, { visit, state }) {
2911
2896
 
2912
2897
  /**
2913
2898
  * Create a TSX language handler with enhanced TypeScript support
2914
- * @returns {Object} TSX language handler with TypeScript return type support
2899
+ * @returns {Visitors<AST.Node, TransformClientState>} TSX language handler with TypeScript return type support
2915
2900
  */
2916
2901
  function create_tsx_with_typescript_support() {
2917
- const base_tsx = /** @type {ESRap.Visitors<AST.Node>} */ (tsx());
2902
+ const base_tsx = /** @type {Visitors<AST.Node, TransformClientState>} */ (tsx());
2918
2903
 
2919
2904
  // Add custom TypeScript node handlers that aren't in tsx
2920
2905
 
@@ -2922,7 +2907,7 @@ function create_tsx_with_typescript_support() {
2922
2907
  * Shared handler for function-like nodes to support component->function mapping
2923
2908
  * Creates source maps for 'function' keyword by passing node to context.write()
2924
2909
  * @param {AST.Function} node
2925
- * @param {ESRap.Context} context
2910
+ * @param {TransformClientContext} context
2926
2911
  */
2927
2912
  const handle_function = (node, context) => {
2928
2913
  if (node.async) {
@@ -2963,16 +2948,8 @@ function create_tsx_with_typescript_support() {
2963
2948
  }
2964
2949
  };
2965
2950
 
2966
- return {
2951
+ return /** @type {Visitors<AST.Node, TransformClientState>} */ ({
2967
2952
  ...base_tsx,
2968
- /**
2969
- * Custom handler for Property nodes to prevent method shorthand for components
2970
- * When a component is transformed to a FunctionExpression, we want to preserve
2971
- * the explicit syntax (key: function name() {}) instead of method shorthand (key() {})
2972
- * This ensures proper source mapping for the 'function'/'component' keyword
2973
- * @param {AST.Property} node
2974
- * @param {ESRap.Context} context
2975
- */
2976
2953
  Property(node, context) {
2977
2954
  // Check if the value is a function that was originally a component
2978
2955
  const isComponent =
@@ -3019,18 +2996,13 @@ function create_tsx_with_typescript_support() {
3019
2996
  context.write(node.computed ? ']: ' : ': ');
3020
2997
  context.visit(node.value);
3021
2998
  } else {
3022
- base_tsx.Property?.(node, context);
2999
+ base_tsx.Property?.(node, /** @type {ESRap.} */ (context));
3023
3000
  }
3024
3001
  } else {
3025
3002
  // Use default handler for non-component properties
3026
3003
  base_tsx.Property?.(node, context);
3027
3004
  }
3028
3005
  },
3029
- /**
3030
- * Custom handler for JSXClosingElement to ensure closing tag brackets have source mappings
3031
- * @param {ESTreeJSX.JSXClosingElement} node
3032
- * @param {ESRap.Context} context
3033
- */
3034
3006
  JSXClosingElement(node, context) {
3035
3007
  // Set location for '<' then write '</'
3036
3008
  if (node.loc) {
@@ -3050,12 +3022,99 @@ function create_tsx_with_typescript_support() {
3050
3022
  context.write('>');
3051
3023
  }
3052
3024
  },
3053
- /**
3054
- * Custom handler for ArrayPattern to ensure typeAnnotation is visited
3055
- * esrap's TypeScript handler doesn't visit typeAnnotation for ArrayPattern (only for ObjectPattern)
3056
- * @param {AST.ArrayPattern} node
3057
- * @param {ESRap.Context} context
3058
- */
3025
+ MethodDefinition(node, context) {
3026
+ // Check if there are type parameters to handle
3027
+ // @ts-ignore - typeParameters may exist on node
3028
+ const hasTypeParams = node.typeParameters || node.value?.typeParameters;
3029
+
3030
+ if (!hasTypeParams) {
3031
+ // No type parameters, use default handler
3032
+ return base_tsx.MethodDefinition?.(node, context);
3033
+ }
3034
+
3035
+ // Has type parameters - we need to manually handle to ensure they're visited
3036
+ // Write modifiers (static, async, etc.)
3037
+ if (node.static) {
3038
+ context.write('static ');
3039
+ }
3040
+
3041
+ // Handle getters/setters
3042
+ if (node.kind === 'get') {
3043
+ context.write('get ');
3044
+ } else if (node.kind === 'set') {
3045
+ context.write('set ');
3046
+ }
3047
+
3048
+ // Write * for generator methods
3049
+ if (node.value?.generator) {
3050
+ context.write('*');
3051
+ }
3052
+
3053
+ // Write async keyword
3054
+ if (node.value?.async) {
3055
+ context.write('async ');
3056
+ }
3057
+
3058
+ // Write the method key
3059
+ if (node.computed) {
3060
+ context.write('[');
3061
+ context.visit(node.key);
3062
+ context.write(']');
3063
+ } else {
3064
+ context.visit(node.key);
3065
+ }
3066
+
3067
+ // Visit typeParameters if present (THIS IS THE FIX)
3068
+ // TypeParameters can be on either the MethodDefinition or its value (FunctionExpression)
3069
+ if (node.typeParameters) {
3070
+ context.visit(node.typeParameters);
3071
+ } else if (node.value?.typeParameters) {
3072
+ context.visit(node.value.typeParameters);
3073
+ }
3074
+
3075
+ // Write parameters - set location for opening '('
3076
+ if (node.value?.loc) {
3077
+ context.location(node.value.loc.start.line, node.value.loc.start.column);
3078
+ }
3079
+ context.write('(');
3080
+ if (node.value?.params) {
3081
+ for (let i = 0; i < node.value.params.length; i++) {
3082
+ if (i > 0) context.write(', ');
3083
+ context.visit(node.value.params[i]);
3084
+ }
3085
+ }
3086
+ context.write(')');
3087
+
3088
+ // Write return type if present
3089
+ if (node.value?.returnType) {
3090
+ context.visit(node.value.returnType);
3091
+ }
3092
+
3093
+ // Write method body
3094
+ if (node.value?.body) {
3095
+ context.write(' ');
3096
+ context.visit(node.value.body);
3097
+ }
3098
+ },
3099
+ TSTypeParameter(node, context) {
3100
+ // Set location for the type parameter name
3101
+ if (node.loc) {
3102
+ context.location(node.loc.start.line, node.loc.start.column);
3103
+ }
3104
+ if (typeof node.name === 'string') {
3105
+ context.write(node.name);
3106
+ } else if (node.name && node.name.name) {
3107
+ context.write(node.name.name);
3108
+ }
3109
+ if (node.constraint) {
3110
+ context.write(' extends ');
3111
+ context.visit(node.constraint);
3112
+ }
3113
+ if (node.default) {
3114
+ context.write(' = ');
3115
+ context.visit(node.default);
3116
+ }
3117
+ },
3059
3118
  ArrayPattern(node, context) {
3060
3119
  context.write('[');
3061
3120
  for (let i = 0; i < node.elements.length; i++) {
@@ -3070,31 +3129,12 @@ function create_tsx_with_typescript_support() {
3070
3129
  context.visit(node.typeAnnotation);
3071
3130
  }
3072
3131
  },
3073
- /**
3074
- * Custom handler for FunctionDeclaration to support component->function mapping
3075
- * Needed for volar mappings and intellisense on function or component keyword
3076
- * @param {AST.FunctionDeclaration} node
3077
- * @param {ESRap.Context} context
3078
- */
3079
3132
  FunctionDeclaration(node, context) {
3080
3133
  handle_function(node, context);
3081
3134
  },
3082
- /**
3083
- * Custom handler for FunctionExpression to support component->function mapping
3084
- * This is used for components transformed by the Component visitor
3085
- * @param {AST.FunctionExpression} node
3086
- * @param {ESRap.Context} context
3087
- */
3088
3135
  FunctionExpression(node, context) {
3089
3136
  handle_function(node, context);
3090
3137
  },
3091
- /**
3092
- * Custom handler for ImportDeclaration to ensure 'import' keyword has source mapping
3093
- * This creates a source map entry at the start of the import statement
3094
- * Esrap's default handler writes 'import' without passing the node, so no source map entry
3095
- * @param {AST.ImportDeclaration} node
3096
- * @param {ESRap.Context} context
3097
- */
3098
3138
  ImportDeclaration(node, context) {
3099
3139
  // Write 'import' keyword with node location for source mapping
3100
3140
  context.write('import', node);
@@ -3148,13 +3188,6 @@ function create_tsx_with_typescript_support() {
3148
3188
  // Write source
3149
3189
  context.visit(node.source);
3150
3190
  },
3151
- /**
3152
- * Custom handler for JSXOpeningElement to ensure '<' and '>' have source mappings
3153
- * Esrap's default handler only maps the tag name, not the brackets
3154
- * This creates mappings for the brackets so auto-close can find the cursor position
3155
- * @param {ESTreeJSX.JSXOpeningElement} node
3156
- * @param {ESRap.Context} context
3157
- */
3158
3191
  JSXOpeningElement(node, context) {
3159
3192
  // Set location for '<'
3160
3193
  if (node.loc) {
@@ -3182,21 +3215,11 @@ function create_tsx_with_typescript_support() {
3182
3215
  context.write('>');
3183
3216
  }
3184
3217
  },
3185
- /**
3186
- * Custom handler for TSParenthesizedType: (Type)
3187
- * @param {AST.TSParenthesizedType} node
3188
- * @param {ESRap.Context} context
3189
- */
3190
3218
  TSParenthesizedType(node, context) {
3191
3219
  context.write('(');
3192
3220
  context.visit(/** @type {AST.TSTypeAnnotation} */ (node.typeAnnotation));
3193
3221
  context.write(')');
3194
3222
  },
3195
- /**
3196
- * Custom handler for TSMappedType: { [K in keyof T]: T[K] }
3197
- * @param {AST.TSMappedType} node
3198
- * @param {ESRap.Context} context
3199
- */
3200
3223
  TSMappedType(node, context) {
3201
3224
  context.write('{ ');
3202
3225
  if (node.readonly) {
@@ -3228,34 +3251,6 @@ function create_tsx_with_typescript_support() {
3228
3251
  }
3229
3252
  context.write(' }');
3230
3253
  },
3231
- /**
3232
- * Custom handler for TSTypeParameter: K in T (for mapped types)
3233
- * acorn-ts has a bug where `in` is printed as `extends`, so we override it here
3234
- * @param {AST.TSTypeParameter} node
3235
- * @param {ESRap.Context} context
3236
- */
3237
- TSTypeParameter(node, context) {
3238
- // For mapped types, the name is just a string, not an Identifier node
3239
- // Pass the node as second parameter to context.write() to create source map entry
3240
- if (typeof node.name === 'string') {
3241
- context.write(node.name, node);
3242
- } else if (node.name && node.name.name) {
3243
- context.write(node.name.name, node.name);
3244
- }
3245
- if (node.constraint) {
3246
- context.write(' in ');
3247
- context.visit(node.constraint);
3248
- }
3249
- if (node.default) {
3250
- context.write(' = ');
3251
- context.visit(node.default);
3252
- }
3253
- },
3254
- /**
3255
- * Override the ArrowFunctionExpression handler to support TypeScript return types
3256
- * @param {AST.ArrowFunctionExpression} node
3257
- * @param {ESRap.Context} context
3258
- */
3259
3254
  ArrowFunctionExpression(node, context) {
3260
3255
  if (node.async) context.write('async ');
3261
3256
 
@@ -3287,11 +3282,57 @@ function create_tsx_with_typescript_support() {
3287
3282
  context.visit(node.body);
3288
3283
  }
3289
3284
  },
3290
- /**
3291
- * Custom handler for TryStatement to support Ripple's pending block
3292
- * @param {AST.TryStatement} node
3293
- * @param {ESRap.Context} context
3294
- */
3285
+ ClassDeclaration(node, context) {
3286
+ context.write('class ');
3287
+ if (node.id) {
3288
+ context.visit(node.id);
3289
+ }
3290
+ if (node.typeParameters) {
3291
+ context.visit(node.typeParameters);
3292
+ }
3293
+ if (node.superClass) {
3294
+ context.write(' extends ');
3295
+ context.visit(node.superClass);
3296
+ if (node.superTypeArguments) {
3297
+ context.visit(node.superTypeArguments);
3298
+ }
3299
+ }
3300
+ if (node.implements && node.implements.length > 0) {
3301
+ context.write(' implements ');
3302
+ for (let i = 0; i < node.implements.length; i++) {
3303
+ if (i > 0) context.write(', ');
3304
+ context.visit(node.implements[i]);
3305
+ }
3306
+ }
3307
+ context.write(' ');
3308
+ context.visit(node.body);
3309
+ },
3310
+ ClassExpression(node, context) {
3311
+ context.write('class');
3312
+ if (node.id) {
3313
+ context.write(' ');
3314
+ context.visit(node.id);
3315
+ }
3316
+ if (node.typeParameters) {
3317
+ context.visit(node.typeParameters);
3318
+ }
3319
+ if (node.superClass) {
3320
+ context.write(' extends ');
3321
+ context.visit(node.superClass);
3322
+ if (node.superTypeArguments) {
3323
+ context.visit(node.superTypeArguments);
3324
+ }
3325
+ }
3326
+ if (node.implements && node.implements.length > 0) {
3327
+ context.write(' implements ');
3328
+ for (let i = 0; i < node.implements.length; i++) {
3329
+ if (i > 0) context.write(', ');
3330
+ context.visit(node.implements[i]);
3331
+ }
3332
+ }
3333
+ context.write(' ');
3334
+ context.visit(node.body);
3335
+ },
3295
3336
  TryStatement(node, context) {
3296
3337
  context.write('try ');
3297
3338
  context.visit(node.block);
@@ -3325,7 +3366,7 @@ function create_tsx_with_typescript_support() {
3325
3366
  context.visit(node.finalizer);
3326
3367
  }
3327
3368
  },
3328
- };
3369
+ });
3329
3370
  }
3330
3371
 
3331
3372
  /**
@@ -3389,6 +3430,13 @@ export function transform_client(filename, source, analysis, to_ts, minify_css)
3389
3430
  metadata: {},
3390
3431
  };
3391
3432
 
3433
+ // Add ripple internal import once for the entire module
3434
+ // Whatever is unused will be tree-shaken later, including a rare case
3435
+ // where nothing from ripple/internal/client is used
3436
+ if (!to_ts) {
3437
+ state.imports.add(`import * as _$_ from 'ripple/internal/client'`);
3438
+ }
3439
+
3392
3440
  const program = /** @type {AST.Program} */ (walk(analysis.ast, { ...state }, visitors));
3393
3441
 
3394
3442
  for (const hoisted of state.hoisted) {
@@ -3407,7 +3455,9 @@ export function transform_client(filename, source, analysis, to_ts, minify_css)
3407
3455
  );
3408
3456
  }
3409
3457
 
3410
- const language_handler = to_ts ? create_tsx_with_typescript_support() : tsx();
3458
+ const language_handler = to_ts
3459
+ ? create_tsx_with_typescript_support()
3460
+ : /** @type {Visitors<AST.Node, TransformClientState>} */ (tsx());
3411
3461
 
3412
3462
  const js =
3413
3463
  /** @type {ReturnType<typeof print> & { post_processing_changes?: PostProcessingChanges, line_offsets?: number[] }} */ (