ripple 0.2.188 → 0.2.189

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.188",
6
+ "version": "0.2.189",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -38,6 +38,12 @@
38
38
  "require": "./src/compiler/index.js",
39
39
  "default": "./src/compiler/index.js"
40
40
  },
41
+ "./compiler/internal/import": {
42
+ "types": "./src/compiler/types/import.d.ts"
43
+ },
44
+ "./compiler/internal/import/utils": {
45
+ "default": "./src/compiler/import-utils.js"
46
+ },
41
47
  "./validator": {
42
48
  "types": "./types/index.d.ts",
43
49
  "require": "./validator/index.js",
@@ -86,6 +92,6 @@
86
92
  "vscode-languageserver-types": "^3.17.5"
87
93
  },
88
94
  "peerDependencies": {
89
- "ripple": "0.2.188"
95
+ "ripple": "0.2.189"
90
96
  }
91
97
  }
@@ -0,0 +1,51 @@
1
+ export const IMPORT_OBFUSCATION_PREFIX = '_$_';
2
+
3
+ /**
4
+ * @param {string} name
5
+ * @returns {string}
6
+ */
7
+ export function obfuscate_imported(name) {
8
+ let start = 0;
9
+ if (name[0] === name[0].toUpperCase()) {
10
+ start = 1;
11
+ }
12
+ const index = find_next_uppercase(name, start);
13
+
14
+ const first_part = name.slice(0, index);
15
+ const second_part = name.slice(index);
16
+
17
+ return IMPORT_OBFUSCATION_PREFIX + (second_part ? second_part + '__' + first_part : first_part);
18
+ }
19
+
20
+ /**
21
+ * @param {string} name
22
+ * @returns {boolean}
23
+ */
24
+ export function is_imported_obfuscated(name) {
25
+ return name.startsWith(IMPORT_OBFUSCATION_PREFIX);
26
+ }
27
+
28
+ /**
29
+ * @param {string} name
30
+ * @returns {string}
31
+ */
32
+ export function deobfuscate_imported(name) {
33
+ name = name.replace(IMPORT_OBFUSCATION_PREFIX, '');
34
+ const parts = name.split('__');
35
+ return (parts[1] ? parts[1] : '') + parts[0];
36
+ }
37
+
38
+ /**
39
+ * Finds the next uppercase character or returns name.length
40
+ * @param {string} name
41
+ * @param {number} start
42
+ * @returns {number}
43
+ */
44
+ function find_next_uppercase(name, start) {
45
+ for (let i = start; i < name.length; i++) {
46
+ if (name[i] === name[i].toUpperCase()) {
47
+ return i;
48
+ }
49
+ }
50
+ return name.length;
51
+ }
@@ -1,4 +1,4 @@
1
- import type { Program } from 'estree';
1
+ import type * as AST from 'estree';
2
2
  import type {
3
3
  CodeInformation as VolarCodeInformation,
4
4
  Mapping as VolarMapping,
@@ -14,7 +14,7 @@ import type { SourceMapMappings } from '@jridgewell/sourcemap-codec';
14
14
  */
15
15
  export interface CompileResult {
16
16
  /** The transformed AST */
17
- ast: Program;
17
+ ast: AST.Program;
18
18
  /** The generated JavaScript code with source map */
19
19
  js: {
20
20
  code: string;
@@ -49,6 +49,14 @@ export interface PluginActionOverrides {
49
49
  description?: string; // just for reference
50
50
  // Generic location for embedded content (CSS, etc.)
51
51
  location?: DefinitionLocation;
52
+ // Replace the type name in hover/definition with a different name
53
+ // And provide the path to import the type definitions from
54
+ // the `ripple` package directory, e.g. `types/index.d.ts`
55
+ // Currently only supported by the definition plugin
56
+ typeReplace?: {
57
+ name: string;
58
+ path: string;
59
+ };
52
60
  }
53
61
  | false;
54
62
  }
@@ -94,7 +102,7 @@ export interface AnalyzeOptions extends ParseOptions, Pick<CompileOptions, 'mode
94
102
 
95
103
  export interface VolarCompileOptions extends ParseOptions, SharedCompileOptions {}
96
104
 
97
- export function parse(source: string, options?: ParseOptions): Program;
105
+ export function parse(source: string, options?: ParseOptions): AST.Program;
98
106
 
99
107
  export function compile(source: string, filename: string, options?: CompileOptions): CompileResult;
100
108
 
@@ -1,4 +1,4 @@
1
- /** @import { Program } from 'estree' */
1
+ /** @import * as AST from 'estree' */
2
2
 
3
3
  import { parse as parse_module } from './phases/1-parse/index.js';
4
4
  import { analyze } from './phases/2-analyze/index.js';
@@ -9,7 +9,7 @@ import { convert_source_map_to_mappings } from './phases/3-transform/segments.js
9
9
  /**
10
10
  * Parse Ripple source code to ESTree AST
11
11
  * @param {string} source
12
- * @returns {Program}
12
+ * @returns {AST.Program}
13
13
  */
14
14
  export function parse(source) {
15
15
  return parse_module(source, undefined);
@@ -342,8 +342,12 @@ function RipplePlugin(config) {
342
342
  if (this.input.slice(this.pos, this.pos + 4) === '#Map') {
343
343
  const charAfter =
344
344
  this.pos + 4 < this.input.length ? this.input.charCodeAt(this.pos + 4) : -1;
345
- if (charAfter === 40) {
346
- // ( character
345
+ if (charAfter === 40 || charAfter === 60) {
346
+ // ( or < character (for generics like #Map<string, number>)
347
+ this.pos += 4; // consume '#Map'
348
+ return this.finishToken(tt.name, '#Map');
349
+ } else if (this.#loose) {
350
+ // In loose mode, produce token even without parens (incomplete syntax)
347
351
  this.pos += 4; // consume '#Map'
348
352
  return this.finishToken(tt.name, '#Map');
349
353
  }
@@ -351,8 +355,12 @@ function RipplePlugin(config) {
351
355
  if (this.input.slice(this.pos, this.pos + 4) === '#Set') {
352
356
  const charAfter =
353
357
  this.pos + 4 < this.input.length ? this.input.charCodeAt(this.pos + 4) : -1;
354
- if (charAfter === 40) {
355
- // ( character
358
+ if (charAfter === 40 || charAfter === 60) {
359
+ // ( or < character (for generics like #Set<number>)
360
+ this.pos += 4; // consume '#Set'
361
+ return this.finishToken(tt.name, '#Set');
362
+ } else if (this.#loose) {
363
+ // In loose mode, produce token even without parens (incomplete syntax)
356
364
  this.pos += 4; // consume '#Set'
357
365
  return this.finishToken(tt.name, '#Set');
358
366
  }
@@ -379,8 +387,9 @@ function RipplePlugin(config) {
379
387
  }
380
388
 
381
389
  // Check if this is an invalid #Identifier pattern
382
- // Valid patterns: #[, #{, #Map(, #Set(, #server
390
+ // Valid patterns: #[, #{, #Map(, #Map<, #Set(, #Set<, #server
383
391
  // If we see # followed by an uppercase letter that isn't Map or Set, it's an error
392
+ // In loose mode, allow incomplete identifiers like #M, #Ma, #S, #Se for autocomplete
384
393
  if (nextChar >= 65 && nextChar <= 90) {
385
394
  // A-Z
386
395
  // Extract the identifier name
@@ -401,12 +410,34 @@ function RipplePlugin(config) {
401
410
  }
402
411
  const identName = this.input.slice(this.pos + 1, identEnd);
403
412
  if (identName !== 'Map' && identName !== 'Set') {
404
- this.raise(
405
- this.pos,
406
- `Invalid tracked syntax '#${identName}'. Only #Map and #Set are currently supported using shorthand tracked syntax.`,
407
- );
413
+ // In loose mode, allow incomplete identifiers (prefixes of Map/Set)
414
+ // This supports autocomplete scenarios where user is still typing
415
+ const isIncompleteMap = 'Map'.startsWith(identName);
416
+ const isIncompleteSet = 'Set'.startsWith(identName);
417
+
418
+ if (!this.#loose || (!isIncompleteMap && !isIncompleteSet)) {
419
+ this.raise(
420
+ this.pos,
421
+ `Invalid tracked syntax '#${identName}'. Only #Map and #Set are currently supported using shorthand tracked syntax.`,
422
+ );
423
+ } else {
424
+ // In loose mode with valid prefix, consume the token and return it
425
+ // This allows the parser to handle incomplete syntax gracefully
426
+ this.pos = identEnd; // consume '#' + identifier
427
+ return this.finishToken(tt.name, '#' + identName);
428
+ }
408
429
  }
409
430
  }
431
+
432
+ // In loose mode, handle bare # or # followed by unrecognized characters
433
+ if (this.#loose) {
434
+ this.pos++; // consume '#'
435
+ return this.finishToken(tt.name, '#');
436
+ }
437
+ } else if (this.#loose) {
438
+ // In loose mode, handle bare # at EOF
439
+ this.pos++; // consume '#'
440
+ return this.finishToken(tt.name, '#');
410
441
  }
411
442
  }
412
443
  if (code === 64) {
@@ -610,6 +641,20 @@ function RipplePlugin(config) {
610
641
  return this.parseTrackedCollectionExpression(type);
611
642
  }
612
643
 
644
+ // In loose mode, handle incomplete #Map/#Set prefixes (e.g., #M, #Ma, #S, #Se)
645
+ if (
646
+ this.#loose &&
647
+ this.type === tt.name &&
648
+ typeof this.value === 'string' &&
649
+ this.value.startsWith('#')
650
+ ) {
651
+ // Return an Identifier node for incomplete tracked syntax
652
+ const node = /** @type {AST.Identifier} */ (this.startNode());
653
+ node.name = this.value;
654
+ this.next();
655
+ return this.finishNode(node, 'Identifier');
656
+ }
657
+
613
658
  // Check if this is a tuple literal starting with #[
614
659
  if (this.type === tt.bracketL && this.value === '#[') {
615
660
  return this.parseTrackedArrayExpression();
@@ -728,8 +773,10 @@ function RipplePlugin(config) {
728
773
  );
729
774
  }
730
775
 
731
- if (this.type === tt.parenL) {
732
- // Don't consume parens - they belong to NewExpression
776
+ // Don't consume parens or generics - they belong to NewExpression
777
+ // When used as "new #Map(...)" the next token is '('
778
+ // When used as "new #Map<K,V>(...)" the next token is '<' (relational)
779
+ if (this.type === tt.parenL || (this.type === tt.relational && this.value === '<')) {
733
780
  node.arguments = [];
734
781
  return this.finishNode(node, type);
735
782
  }
@@ -151,6 +151,10 @@ export function validate_nesting(element, context) {
151
151
  context.state.analysis.module.filename,
152
152
  element,
153
153
  );
154
+ } else {
155
+ // if my parent has a set of invalid children
156
+ // and i'm not in it, then i'm valid
157
+ return;
154
158
  }
155
159
  }
156
160
  }
@@ -52,6 +52,7 @@ import {
52
52
  determine_namespace_for_children,
53
53
  index_to_key,
54
54
  } from '../../../utils.js';
55
+ import { obfuscate_imported } from '../../../import-utils.js';
55
56
  import is_reference from 'is-reference';
56
57
  import { object } from '../../../../utils/ast.js';
57
58
  import { render_stylesheets } from '../stylesheet.js';
@@ -314,14 +315,13 @@ function visit_title_element(node, context) {
314
315
  * @param {TransformClientContext} context
315
316
  * @returns {string}
316
317
  */
317
- function import_from_ripple_if_needed(name, context) {
318
- const alias = context.state.ripple_user_imports?.get?.(name);
319
-
320
- if (!alias && !context.state.imports.has(`import { ${name} } from 'ripple'`)) {
321
- context.state.imports.add(`import { ${name} } from 'ripple'`);
318
+ function set_hidden_import_from_ripple(name, context) {
319
+ name = obfuscate_imported(name);
320
+ if (!context.state.imports.has(`import { ${name} } from 'ripple/compiler/internal/import'`)) {
321
+ context.state.imports.add(`import { ${name} } from 'ripple/compiler/internal/import'`);
322
322
  }
323
323
 
324
- return alias ?? name;
324
+ return name;
325
325
  }
326
326
 
327
327
  /** @type {Visitors<AST.Node, TransformClientState>} */
@@ -531,25 +531,31 @@ const visitors = {
531
531
  }
532
532
 
533
533
  // Special handling for TrackedMapExpression and TrackedSetExpression
534
- // When source is "new #Map(...)", the callee is TrackedMapExpression with empty arguments
535
- // and the actual arguments are in NewExpression.arguments
534
+ // When source is "new #Map(...)" or "new #Map<K,V>(...)", the callee is TrackedMapExpression
535
+ // with empty arguments and the actual arguments are in NewExpression.arguments
536
536
  if (callee.type === 'TrackedMapExpression' || callee.type === 'TrackedSetExpression') {
537
537
  // Use NewExpression's arguments (the callee has empty arguments from parser)
538
538
  const argsToUse = node.arguments.length > 0 ? node.arguments : callee.arguments;
539
539
 
540
540
  if (context.state.to_ts) {
541
541
  const className = callee.type === 'TrackedMapExpression' ? 'TrackedMap' : 'TrackedSet';
542
- const alias = import_from_ripple_if_needed(className, context);
542
+ const alias = set_hidden_import_from_ripple(className, context);
543
543
  const calleeId = b.id(alias);
544
544
  calleeId.loc = callee.loc;
545
545
  calleeId.metadata = {
546
546
  tracked_shorthand: callee.type === 'TrackedMapExpression' ? '#Map' : '#Set',
547
547
  path: [...context.path],
548
548
  };
549
- return b.new(
549
+ /** @type {AST.NewExpression} */
550
+ const newExpr = b.new(
550
551
  calleeId,
551
552
  .../** @type {AST.Expression[]} */ (argsToUse.map((arg) => context.visit(arg))),
552
553
  );
554
+ // Preserve typeArguments for generics syntax like new #Map<string, number>()
555
+ if (node.typeArguments) {
556
+ newExpr.typeArguments = node.typeArguments;
557
+ }
558
+ return newExpr;
553
559
  }
554
560
 
555
561
  const helperName = callee.type === 'TrackedMapExpression' ? 'tracked_map' : 'tracked_set';
@@ -590,7 +596,7 @@ const visitors = {
590
596
 
591
597
  TrackedArrayExpression(node, context) {
592
598
  if (context.state.to_ts) {
593
- const arrayAlias = import_from_ripple_if_needed('TrackedArray', context);
599
+ const arrayAlias = set_hidden_import_from_ripple('TrackedArray', context);
594
600
 
595
601
  return b.call(
596
602
  b.member(b.id(arrayAlias), b.id('from')),
@@ -615,7 +621,7 @@ const visitors = {
615
621
 
616
622
  TrackedObjectExpression(node, context) {
617
623
  if (context.state.to_ts) {
618
- const objectAlias = import_from_ripple_if_needed('TrackedObject', context);
624
+ const objectAlias = set_hidden_import_from_ripple('TrackedObject', context);
619
625
 
620
626
  return b.new(
621
627
  b.id(objectAlias),
@@ -1282,7 +1288,7 @@ const visitors = {
1282
1288
  b.stmt(b.call('_$_.set_class', id, key, hash_arg, b.literal(is_html))),
1283
1289
  expression,
1284
1290
  identity: attr_value,
1285
- initial: b.literal(''),
1291
+ initial: b.call(b.id('Symbol')),
1286
1292
  });
1287
1293
  } else {
1288
1294
  state.init?.push(
@@ -2254,7 +2260,7 @@ function transform_ts_child(node, context) {
2254
2260
  const argument = visit(attr.argument, { ...state, metadata });
2255
2261
  return b.jsx_spread_attribute(/** @type {AST.Expression} */ (argument));
2256
2262
  } else if (attr.type === 'RefAttribute') {
2257
- const createRefKeyAlias = import_from_ripple_if_needed('createRefKey', context);
2263
+ const createRefKeyAlias = set_hidden_import_from_ripple('createRefKey', context);
2258
2264
  const metadata = { await: false };
2259
2265
  const argument = visit(attr.argument, { ...state, metadata });
2260
2266
  const wrapper = b.object([
@@ -3148,8 +3154,18 @@ function create_tsx_with_typescript_support() {
3148
3154
  handle_function(node, context);
3149
3155
  },
3150
3156
  ImportDeclaration(node, context) {
3151
- // Write 'import' keyword with node location for source mapping
3152
- context.write('import', node);
3157
+ const loc = /** @type {AST.SourceLocation} */ (node.loc);
3158
+ // Write 'import' keyword with source location
3159
+ // to mark the beginning of the import statement for a full import mapping
3160
+ // The semicolon at the end with location will mark the end of the import statement
3161
+ context.location(loc.start.line, loc.start.column);
3162
+ context.write('import');
3163
+
3164
+ // Handle 'import type' syntax (importKind on the declaration itself)
3165
+ if (node.importKind === 'type') {
3166
+ context.write(' type');
3167
+ }
3168
+
3153
3169
  context.write(' ');
3154
3170
 
3155
3171
  // Write specifiers - handle default, namespace, and named imports
@@ -3199,6 +3215,29 @@ function create_tsx_with_typescript_support() {
3199
3215
 
3200
3216
  // Write source
3201
3217
  context.visit(node.source);
3218
+ // Write semicolon at the end
3219
+ // and record its position to mark the end of the import statement
3220
+ // This should work regardless of whether the source has a semi or not
3221
+ context.location(loc.end.line, loc.end.column - 1);
3222
+ context.write(';');
3223
+ },
3224
+ ImportDefaultSpecifier(node, context) {
3225
+ context.visit(node.local);
3226
+ },
3227
+ ImportNamespaceSpecifier(node, context) {
3228
+ context.write('* as ');
3229
+ context.visit(node.local);
3230
+ },
3231
+ ImportSpecifier(node, context) {
3232
+ if (node.importKind === 'type') {
3233
+ context.write('type ');
3234
+ }
3235
+ context.visit(node.imported);
3236
+ // Only write 'as local' if imported !== local
3237
+ if (/** @type {AST.Identifier} */ (node.imported).name !== node.local.name) {
3238
+ context.write(' as ');
3239
+ context.visit(node.local);
3240
+ }
3202
3241
  },
3203
3242
  JSXOpeningElement(node, context) {
3204
3243
  // Set location for '<'
@@ -3242,9 +3281,28 @@ function create_tsx_with_typescript_support() {
3242
3281
  }
3243
3282
  }
3244
3283
  context.write('[');
3245
- // Visit the entire type parameter (TSTypeParameter node)
3284
+ // Handle TSTypeParameter inline - mapped types use 'in' not 'extends'
3246
3285
  if (node.typeParameter) {
3247
- context.visit(node.typeParameter);
3286
+ const tp = node.typeParameter;
3287
+ if (tp.loc) {
3288
+ context.location(tp.loc.start.line, tp.loc.start.column);
3289
+ }
3290
+ // Write the parameter name
3291
+ if (typeof tp.name === 'string') {
3292
+ context.write(tp.name);
3293
+ } else if (tp.name && tp.name.name) {
3294
+ context.write(tp.name.name);
3295
+ }
3296
+ // In mapped types, constraint uses 'in' instead of 'extends'
3297
+ if (tp.constraint) {
3298
+ context.write(' in ');
3299
+ context.visit(tp.constraint);
3300
+ }
3301
+ // Handle 'as' clause for key remapping (e.g., { [K in Keys as NewKey]: V })
3302
+ if (node.nameType) {
3303
+ context.write(' as ');
3304
+ context.visit(node.nameType);
3305
+ }
3248
3306
  }
3249
3307
  context.write(']');
3250
3308
  if (node.optional) {
@@ -3255,11 +3313,9 @@ function create_tsx_with_typescript_support() {
3255
3313
  }
3256
3314
  }
3257
3315
  context.write(': ');
3258
- // Visit the value type - could be either typeAnnotation or nameType
3316
+ // Visit the value type
3259
3317
  if (node.typeAnnotation) {
3260
3318
  context.visit(node.typeAnnotation);
3261
- } else if (node.nameType) {
3262
- context.visit(node.nameType);
3263
3319
  }
3264
3320
  context.write(' }');
3265
3321
  },
@@ -3391,39 +3447,9 @@ function create_tsx_with_typescript_support() {
3391
3447
  * @returns {{ ast: AST.Program, js: { code: string, map: SourceMapMappings, post_processing_changes?: PostProcessingChanges, line_offsets?: LineOffsets }, css: string }}
3392
3448
  */
3393
3449
  export function transform_client(filename, source, analysis, to_ts, minify_css) {
3394
- /**
3395
- * User's named imports from 'ripple' so we can reuse them in TS output
3396
- * when transforming shorthand syntax. E.g., if the user has already imported
3397
- * TrackedArray, we want to reuse that import instead of importing it again
3398
- * if we encounter `#[]`. It's a Map of export name to local name in case the
3399
- * user renamed something when importing.
3400
- * @type {Map<string, string>}
3401
- */
3402
- const ripple_user_imports = new Map(); // exported -> local
3403
- if (analysis && analysis.ast && Array.isArray(analysis.ast.body)) {
3404
- for (const stmt of analysis.ast.body) {
3405
- if (
3406
- stmt &&
3407
- stmt.type === 'ImportDeclaration' &&
3408
- stmt.source &&
3409
- stmt.source.value === 'ripple'
3410
- ) {
3411
- for (const spec of stmt.specifiers || []) {
3412
- if (spec.type === 'ImportSpecifier' && spec.imported && spec.local) {
3413
- ripple_user_imports.set(
3414
- /** @type {AST.Identifier} */ (spec.imported).name,
3415
- spec.local.name,
3416
- );
3417
- }
3418
- }
3419
- }
3420
- }
3421
- }
3422
-
3423
3450
  /** @type {TransformClientState} */
3424
3451
  const state = {
3425
3452
  imports: new Set(),
3426
- ripple_user_imports,
3427
3453
  events: new Set(),
3428
3454
  template: null,
3429
3455
  hoisted: [],
@@ -24,7 +24,6 @@
24
24
  @typedef {{
25
25
  source: string | null | undefined;
26
26
  generated: string;
27
- is_full_import_statement?: boolean;
28
27
  loc: AST.SourceLocation;
29
28
  end_loc?: AST.SourceLocation;
30
29
  metadata?: PluginActionOverrides;
@@ -327,17 +326,6 @@ export function convert_source_map_to_mappings(
327
326
  const src_line_offsets = build_line_offsets(source);
328
327
  const gen_line_offsets = build_line_offsets(generated_code);
329
328
 
330
- /**
331
- * Convert generated line/column to byte offset using pre-computed line_offsets
332
- * @param {number} line
333
- * @param {number} column
334
- * @returns {number}
335
- */
336
- const gen_loc_to_offset = (line, column) => {
337
- if (line === 1) return column;
338
- return line_offsets[line - 1] + column;
339
- };
340
-
341
329
  const [src_to_gen_map] = build_src_to_gen_map(
342
330
  source_map,
343
331
  post_processing_changes,
@@ -382,8 +370,19 @@ export function convert_source_map_to_mappings(
382
370
  loc: node.loc,
383
371
  });
384
372
  } else {
373
+ const token = /** @type {Token} */ ({
374
+ source: node.name,
375
+ generated: node.name,
376
+ loc: node.loc,
377
+ });
378
+ if (node.name === '#') {
379
+ // Suppress 'Invalid character' to allow typing out the shorthands
380
+ token.metadata = {
381
+ suppressedDiagnostics: [1127],
382
+ };
383
+ }
385
384
  // No transformation - source and generated names are the same
386
- tokens.push({ source: node.name, generated: node.name, loc: node.loc });
385
+ tokens.push(token);
387
386
  }
388
387
  }
389
388
  return; // Leaf node, don't traverse further
@@ -409,15 +408,37 @@ export function convert_source_map_to_mappings(
409
408
  } else if (node.type === 'ImportDeclaration') {
410
409
  isImportDeclarationPresent = true;
411
410
 
412
- // Add import declaration as a special token for full-statement mapping
413
- // TypeScript reports unused imports with diagnostics covering the entire statement
414
- if (node.loc && node.source?.loc) {
411
+ // Add 'import' keyword token to anchor statement-level diagnostics
412
+ // And the last character of the statement (semicolon or closing brace)
413
+ // (e.g., when ALL imports are unused, TS reports on the whole statement)
414
+ // We only map the 'import' and the last character
415
+ // to avoid overlapping with individual specifier mappings
416
+ // which would interfere when only SOME imports are unused.
417
+ if (node.loc) {
415
418
  tokens.push({
416
- source: '',
417
- generated: '',
418
- loc: node.loc,
419
- is_full_import_statement: true,
420
- end_loc: node.source.loc,
419
+ source: 'import',
420
+ generated: 'import',
421
+ loc: {
422
+ start: node.loc.start,
423
+ end: {
424
+ line: node.loc.start.line,
425
+ column: node.loc.start.column + 'import'.length,
426
+ },
427
+ },
428
+ });
429
+
430
+ tokens.push({
431
+ source:
432
+ source[loc_to_offset(node.loc.end.line, node.loc.end.column - 1, src_line_offsets)],
433
+ // we always add `;' in the generated import
434
+ generated: ';',
435
+ loc: {
436
+ start: {
437
+ line: node.loc.end.line,
438
+ column: node.loc.end.column - 1,
439
+ },
440
+ end: node.loc.end,
441
+ },
421
442
  });
422
443
  }
423
444
 
@@ -536,6 +557,7 @@ export function convert_source_map_to_mappings(
536
557
  sourceOffsets: [sourceOffset],
537
558
  generatedOffsets: [offset],
538
559
  lengths: [length],
560
+ generatedLengths: [length],
539
561
  data: {
540
562
  ...mapping_data,
541
563
  customData: {
@@ -813,10 +835,12 @@ export function convert_source_map_to_mappings(
813
835
  // '```ripple\npending\n```\n\nRipple-specific keyword for try/pending blocks.\n\nThe `pending` block executes while async operations inside the `try` block are awaiting. This provides a built-in loading state for async components.',
814
836
  // },
815
837
 
816
- // TODO: Definition is not implemented yet, leaving for future use
838
+ // Example of a custom definition and its type definition file
817
839
  // definition: {
818
- // description:
819
- // 'Ripple pending block - executes during async operations in the try block',
840
+ // typeReplace: {
841
+ // name: 'SomeType',
842
+ // path: 'types/index.d.ts',
843
+ // },
820
844
  // },
821
845
  },
822
846
  });
@@ -1623,90 +1647,50 @@ export function convert_source_map_to_mappings(
1623
1647
  for (const token of tokens) {
1624
1648
  const source_text = token.source ?? '';
1625
1649
  const gen_text = token.generated;
1626
-
1627
1650
  const source_start = loc_to_offset(
1628
1651
  token.loc.start.line,
1629
1652
  token.loc.start.column,
1630
1653
  src_line_offsets,
1631
1654
  );
1655
+ const source_length = source_text.length;
1656
+ const gen_length = gen_text.length;
1657
+ const gen_line_col = get_generated_position(
1658
+ token.loc.start.line,
1659
+ token.loc.start.column,
1660
+ src_to_gen_map,
1661
+ );
1662
+ const gen_start = loc_to_offset(gen_line_col.line, gen_line_col.column, gen_line_offsets);
1632
1663
 
1633
- let source_length = source_text.length;
1634
- let gen_length = gen_text.length;
1635
- /** @type {MappingData} */
1636
- let data;
1637
- /** @type {number} */
1638
- let gen_start;
1639
-
1640
- if (token.is_full_import_statement) {
1641
- const end_loc = /** @type {AST.SourceLocation} */ (token.end_loc).end;
1642
- const source_end = loc_to_offset(end_loc.line, end_loc.column, src_line_offsets);
1643
-
1644
- // Look up where import keyword and source literal map to in generated code
1645
- const gen_start_pos = get_generated_position(
1646
- token.loc.start.line,
1647
- token.loc.start.column,
1648
- src_to_gen_map,
1649
- );
1650
- const gen_end_pos = get_generated_position(end_loc.line, end_loc.column, src_to_gen_map);
1651
-
1652
- gen_start = gen_loc_to_offset(gen_start_pos.line, gen_start_pos.column);
1653
- const gen_end = gen_loc_to_offset(gen_end_pos.line, gen_end_pos.column);
1654
-
1655
- source_length = source_end - source_start;
1656
- gen_length = gen_end - gen_start;
1657
-
1658
- data = {
1659
- // we only want verification here, like unused imports
1660
- // since this is synthetic and otherwise we'll get duplicated actions like intellisense
1661
- // each imported specifier has its own mapping
1662
- verification: true,
1663
- customData: {
1664
- generatedLengths: [gen_length],
1665
- },
1666
- };
1667
- } else {
1668
- const gen_line_col = get_generated_position(
1669
- token.loc.start.line,
1670
- token.loc.start.column,
1671
- src_to_gen_map,
1672
- );
1673
- gen_start = gen_loc_to_offset(gen_line_col.line, gen_line_col.column);
1674
-
1675
- /** @type {CustomMappingData} */
1676
- const customData = {
1677
- generatedLengths: [gen_length],
1678
- };
1679
-
1680
- // Add optional metadata from token if present
1681
- if (token.metadata) {
1682
- if ('wordHighlight' in token.metadata) {
1683
- customData.wordHighlight = token.metadata.wordHighlight;
1684
- }
1664
+ /** @type {CustomMappingData} */
1665
+ const customData = {
1666
+ generatedLengths: [gen_length],
1667
+ };
1685
1668
 
1686
- if ('suppressedDiagnostics' in token.metadata) {
1687
- customData.suppressedDiagnostics = token.metadata.suppressedDiagnostics;
1688
- }
1689
- if ('hover' in token.metadata) {
1690
- customData.hover = token.metadata.hover;
1691
- }
1692
- if ('definition' in token.metadata) {
1693
- customData.definition = token.metadata.definition;
1694
- }
1669
+ // Add optional metadata from token if present
1670
+ if (token.metadata) {
1671
+ if ('wordHighlight' in token.metadata) {
1672
+ customData.wordHighlight = token.metadata.wordHighlight;
1673
+ }
1674
+ if ('suppressedDiagnostics' in token.metadata) {
1675
+ customData.suppressedDiagnostics = token.metadata.suppressedDiagnostics;
1676
+ }
1677
+ if ('hover' in token.metadata) {
1678
+ customData.hover = token.metadata.hover;
1679
+ }
1680
+ if ('definition' in token.metadata) {
1681
+ customData.definition = token.metadata.definition;
1695
1682
  }
1696
-
1697
- data = {
1698
- ...mapping_data,
1699
- customData,
1700
- };
1701
1683
  }
1702
1684
 
1703
- // !IMPORTANT: don't set generatedLengths, otherwise Volar will use that vs our source
1704
- // We're adding it to our custom metadata instead as we need it for patching positions
1705
1685
  mappings.push({
1706
1686
  sourceOffsets: [source_start],
1707
1687
  generatedOffsets: [gen_start],
1708
1688
  lengths: [source_length],
1709
- data,
1689
+ generatedLengths: [gen_length],
1690
+ data: {
1691
+ ...mapping_data,
1692
+ customData,
1693
+ },
1710
1694
  });
1711
1695
  }
1712
1696
 
@@ -1720,6 +1704,7 @@ export function convert_source_map_to_mappings(
1720
1704
  sourceOffsets: [0],
1721
1705
  generatedOffsets: [0],
1722
1706
  lengths: [1],
1707
+ generatedLengths: [1],
1723
1708
  data: {
1724
1709
  ...mapping_data,
1725
1710
  customData: {
@@ -1737,6 +1722,7 @@ export function convert_source_map_to_mappings(
1737
1722
  sourceOffsets: [region.start],
1738
1723
  generatedOffsets: [0],
1739
1724
  lengths: [region.content.length],
1725
+ generatedLengths: [region.content.length],
1740
1726
  data: {
1741
1727
  ...mapping_data,
1742
1728
  customData: {
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Types for Hidden Imports in TSX Generated Code
3
+ *
4
+ * Adds uniquely named exports to account for hidden imports
5
+ * in the tsx generated code for language server / IDE support.
6
+ * This is necessary because we need to keep hidden imports named differently
7
+ * for full TS support including adding missing imports in source and
8
+ * property reporting missing imports.
9
+ *
10
+ * The types are obfuscated to avoid name collisions and provide
11
+ * sufficiently different names so that TS cannot attempt to infer that
12
+ * the user made a mistake when the user is missing an import.
13
+ *
14
+ * e.g.
15
+ * // import { TrackedMap } from 'ripple'; -- assume TrackedMap import is missing
16
+ * const map = new TrackedMap();
17
+ *
18
+ * If a type in the hidden import contains 'TrackedMap', e.g. '__TrackedMap',
19
+ * TS would suggest to the user that they meant to use '__TrackedMap' instead of 'TrackedMap'.
20
+ *
21
+ * Add additional types as needed if they are used in hidden imports.
22
+ *
23
+ * This file is used by the package.json in exports
24
+ * The exports path is used by the TS compiler to resolve types.
25
+ *
26
+ * The intellisense is intercepted by hover language plugin
27
+ * to replace the obfuscated names with the actual types.
28
+ *
29
+ * Do not rename or move without updating those paths.
30
+ */
31
+
32
+ import {
33
+ TrackedMap as _$_Map__Tracked,
34
+ TrackedSet as _$_Set__Tracked,
35
+ TrackedArray as _$_Array__Tracked,
36
+ TrackedObject as _$_Object__Tracked,
37
+ TrackedURL as _$_URL__Tracked,
38
+ TrackedURLSearchParams as _$_URLSearchParams__Tracked,
39
+ TrackedDate as _$_Date__Tracked,
40
+ createRefKey as _$_RefKey__create,
41
+ } from 'ripple';
42
+
43
+ export {
44
+ _$_Map__Tracked,
45
+ _$_Set__Tracked,
46
+ _$_Array__Tracked,
47
+ _$_Object__Tracked,
48
+ _$_URL__Tracked,
49
+ _$_URLSearchParams__Tracked,
50
+ _$_Date__Tracked,
51
+ _$_RefKey__create,
52
+ };
@@ -149,7 +149,9 @@ declare module 'estree' {
149
149
  interface ImportSpecifier {
150
150
  importKind: TSESTree.ImportSpecifier['importKind'];
151
151
  }
152
- interface ExportNamedDeclaration {
152
+ interface ExportNamedDeclaration
153
+ // doesn't seem we're using parent and assertions, removing to avoid builders errors
154
+ extends Omit<TSESTree.ExportNamedDeclaration, 'exportKind' | 'parent' | 'assertions'> {
153
155
  exportKind: TSESTree.ExportNamedDeclaration['exportKind'];
154
156
  }
155
157
 
@@ -1161,7 +1163,6 @@ export interface TransformClientState extends BaseState {
1161
1163
  init: Array<AST.Statement> | null;
1162
1164
  metadata: BaseStateMetaData;
1163
1165
  namespace: NameSpace;
1164
- ripple_user_imports: Map<string, string>;
1165
1166
  setup: Array<AST.Statement> | null;
1166
1167
  stylesheets: Array<AST.CSS.StyleSheet>;
1167
1168
  template: Array<string | AST.Expression> | null;
@@ -60,7 +60,7 @@ declare module 'zimmerframe' {
60
60
  export function walk(
61
61
  node: AST.CSS.Node,
62
62
  state: any,
63
- visitors: RippleCompiler.Visitors<(AST.CSS.Node), any>,
63
+ visitors: RippleCompiler.Visitors<AST.CSS.Node, any>,
64
64
  ): AST.CSS.Node;
65
65
  }
66
66
 
@@ -611,12 +611,12 @@ export namespace Parse {
611
611
  * @param type The node type string (e.g., "Identifier", "BinaryExpression")
612
612
  * @returns The finished node
613
613
  */
614
- finishNode<T extends AST.Node>(node: T, type: string): T;
614
+ finishNode<T extends AST.Node>(node: T, type: T['type']): T;
615
615
 
616
616
  /**
617
617
  * Finish a node at a specific position
618
618
  */
619
- finishNodeAt<T extends AST.Node>(node: T, type: string, pos: number, loc: AST.Position): T;
619
+ finishNodeAt<T extends AST.Node>(node: T, type: T['type'], pos: number, loc: AST.Position): T;
620
620
 
621
621
  /**
622
622
  * Start a new node at current position
@@ -925,7 +925,8 @@ export namespace Parse {
925
925
  | AST.TrackedSetExpression
926
926
  | AST.TrackedArrayExpression
927
927
  | AST.TrackedObjectExpression
928
- | AST.Component;
928
+ | AST.Component
929
+ | AST.Identifier;
929
930
 
930
931
  /** Default handler for parseExprAtom when no other case matches */
931
932
  parseExprAtomDefault(): AST.Expression;
@@ -64,7 +64,7 @@ describe('basic client > attribute rendering', () => {
64
64
  [
65
65
  'ccc',
66
66
  'ddd',
67
- {eee: true, fff: false},
67
+ { eee: true, fff: false },
68
68
  ],
69
69
  ]}
70
70
  >
@@ -107,7 +107,7 @@ describe('basic client > attribute rendering', () => {
107
107
  >
108
108
  {'Toggle'}
109
109
  </button>
110
- <div class={{active: @active, inactive: !@active}}>{'Dynamic Class'}</div>
110
+ <div class={{ active: @active, inactive: !@active }}>{'Dynamic Class'}</div>
111
111
 
112
112
  <style>
113
113
  .active {
@@ -139,6 +139,35 @@ describe('basic client > attribute rendering', () => {
139
139
  expect(div.classList.contains('active')).toBe(false);
140
140
  });
141
141
 
142
+ it('applies scoped ripple classs to multiple elements with dynamic class expressions', () => {
143
+ component Basic() {
144
+ let selected = track(1);
145
+
146
+ <div class={@selected === 0 ? 'selected' : ''}>{`div 1`}</div>
147
+ <div class={@selected === 0 ? 'selected' : ''}>{`div 2`}</div>
148
+
149
+ <style>
150
+ div {
151
+ background: green;
152
+ color: white;
153
+ }
154
+ div.selected {
155
+ background: indigo;
156
+ }
157
+ </style>
158
+ }
159
+
160
+ render(Basic);
161
+
162
+ const divs = container.querySelectorAll('div');
163
+
164
+ divs.forEach((div) => {
165
+ expect(Array.from(div.classList).some((className) => className.startsWith('ripple-'))).toBe(
166
+ true,
167
+ );
168
+ });
169
+ });
170
+
142
171
  it('render dynamic id attribute', () => {
143
172
  component Basic() {
144
173
  let count = track(0);
@@ -211,7 +240,7 @@ describe('basic client > attribute rendering', () => {
211
240
  >
212
241
  {'Change Color'}
213
242
  </button>
214
- <div style={{color: @color, fontWeight: 'bold'}}>{'Dynamic Style'}</div>
243
+ <div style={{ color: @color, fontWeight: 'bold' }}>{'Dynamic Style'}</div>
215
244
  }
216
245
 
217
246
  render(Basic);
@@ -265,7 +294,7 @@ describe('basic client > attribute rendering', () => {
265
294
  }
266
295
 
267
296
  <button onClick={toggleColor}>{'Change Color'}</button>
268
- <div style={{...@style}}>{'Dynamic Style'}</div>
297
+ <div style={{ ...@style }}>{'Dynamic Style'}</div>
269
298
  }
270
299
 
271
300
  render(Basic);
@@ -1,6 +1,8 @@
1
1
  import { parse, compile, compile_to_volar_mappings } from 'ripple/compiler';
2
+ import { obfuscate_imported } from 'ripple/compiler/internal/import/utils';
3
+ import type * as AST from 'estree';
2
4
 
3
- function count_occurrences(string, subString) {
5
+ function count_occurrences(string: string, subString: string): number {
4
6
  let count = 0;
5
7
  let pos = string.indexOf(subString);
6
8
 
@@ -25,15 +27,21 @@ describe('compiler > basics', () => {
25
27
 
26
28
  let input = source.replace('#style', style1);
27
29
  let ast = parse(input);
28
- expect(ast.body[0].declaration.css.source).toEqual(style1);
30
+ expect(
31
+ ((ast.body[0] as AST.ExportNamedDeclaration).declaration as AST.Component).css.source,
32
+ ).toEqual(style1);
29
33
 
30
34
  input = source.replace('#style', style2);
31
35
  ast = parse(input);
32
- expect(ast.body[0].declaration.css.source).toEqual(style2);
36
+ expect(
37
+ ((ast.body[0] as AST.ExportNamedDeclaration).declaration as AST.Component).css.source,
38
+ ).toEqual(style2);
33
39
 
34
40
  input = source.replace('#style', style3);
35
41
  ast = parse(input);
36
- expect(ast.body[0].declaration.css.source).toEqual(style3);
42
+ expect(
43
+ ((ast.body[0] as AST.ExportNamedDeclaration).declaration as AST.Component).css.source,
44
+ ).toEqual(style3);
37
45
  });
38
46
 
39
47
  it('renders without crashing', () => {
@@ -113,7 +121,7 @@ describe('compiler > basics', () => {
113
121
  },
114
122
  };
115
123
 
116
- const config3: { [key: RecordKey]: RecordValue } = {
124
+ const config3: { [key: string]: RecordValue } = {
117
125
  test: {
118
126
  a: 'test3',
119
127
  b: 3,
@@ -192,6 +200,10 @@ describe('compiler > basics', () => {
192
200
  method<T>(): T {
193
201
  return this.value;
194
202
  }
203
+
204
+ constructor(value: T) {
205
+ this.value = value;
206
+ }
195
207
  }
196
208
 
197
209
  let flag = true;
@@ -219,8 +231,10 @@ describe('compiler > basics', () => {
219
231
  const result = compile(source, 'test.ripple', { mode: 'client' });
220
232
  });
221
233
 
222
- it('doesn\'t add duplicate imports when encountering shorthand syntax', () => {
223
- const source = `
234
+ it(
235
+ 'imports and uses only obfuscated Tracked imports when encountering only shorthand syntax',
236
+ () => {
237
+ const source = `
224
238
  import {
225
239
  TrackedArray,
226
240
  TrackedObject,
@@ -238,17 +252,18 @@ component App() {
238
252
  <div {ref () => {}} />
239
253
  }
240
254
  `;
241
- const result = compile_to_volar_mappings(source, 'test.ripple').code;
255
+ const result = compile_to_volar_mappings(source, 'test.ripple').code;
242
256
 
243
- expect(count_occurrences(result, 'TrackedArray')).toBe(2);
244
- expect(count_occurrences(result, 'TrackedObject')).toBe(2);
245
- expect(count_occurrences(result, 'TrackedSet')).toBe(2);
246
- expect(count_occurrences(result, 'TrackedMap')).toBe(2);
247
- expect(count_occurrences(result, 'createRefKey')).toBe(2);
248
- });
257
+ expect(count_occurrences(result, 'TrackedArray')).toBe(1);
258
+ expect(count_occurrences(result, 'TrackedObject')).toBe(1);
259
+ expect(count_occurrences(result, 'TrackedSet')).toBe(1);
260
+ expect(count_occurrences(result, 'TrackedMap')).toBe(1);
261
+ expect(count_occurrences(result, 'createRefKey')).toBe(1);
262
+ },
263
+ );
249
264
 
250
265
  it(
251
- 'doesn\'t add duplicate imports for renamed imports when encountering shorthand syntax',
266
+ 'adds obfuscated imports and keeps renamed Tracked imports intact when encountering shorthand syntax',
252
267
  () => {
253
268
  const source = `
254
269
  import {
@@ -270,43 +285,21 @@ component App() {
270
285
  `;
271
286
  const result = compile_to_volar_mappings(source, 'test.ripple').code;
272
287
 
273
- expect(count_occurrences(result, 'TrackedArray')).toBe(1);
274
- expect(count_occurrences(result, 'TA')).toBe(2);
275
- expect(count_occurrences(result, 'TrackedObject')).toBe(1);
276
- expect(count_occurrences(result, 'TO')).toBe(2);
277
- expect(count_occurrences(result, 'TrackedSet')).toBe(1);
278
- expect(count_occurrences(result, 'TS')).toBe(2);
279
- expect(count_occurrences(result, 'TrackedMap')).toBe(1);
280
- expect(count_occurrences(result, 'TM')).toBe(2);
281
- expect(count_occurrences(result, 'createRefKey')).toBe(1);
282
- expect(count_occurrences(result, 'crk')).toBe(2);
288
+ expect(count_occurrences(result, obfuscate_imported('TrackedArray'))).toBe(2);
289
+ expect(count_occurrences(result, 'TA')).toBe(1);
290
+ expect(count_occurrences(result, obfuscate_imported('TrackedObject'))).toBe(2);
291
+ expect(count_occurrences(result, 'TO')).toBe(1);
292
+ expect(count_occurrences(result, obfuscate_imported('TrackedSet'))).toBe(2);
293
+ expect(count_occurrences(result, 'TS')).toBe(1);
294
+ expect(count_occurrences(result, obfuscate_imported('TrackedMap'))).toBe(2);
295
+ expect(count_occurrences(result, 'TM')).toBe(1);
296
+ expect(count_occurrences(result, obfuscate_imported('createRefKey'))).toBe(2);
297
+ expect(count_occurrences(result, 'crk')).toBe(1);
283
298
  },
284
299
  );
285
300
 
286
- it('adds missing imports for shorthand syntax', () => {
287
- const source = `
288
- component App() {
289
- const items = #[1, 2, 3];
290
- const obj = #{ a: 1, b: 2, c: 3 };
291
- const set = new #Set([1, 2, 3]);
292
- const map = new #Map([['a', 1], ['b', 2], ['c', 3]]);
293
-
294
- <div {ref () => {}} />
295
- }
296
- `;
297
- const result = compile_to_volar_mappings(source, 'test.ripple').code;
298
-
299
- expect(count_occurrences(result, 'TrackedArray')).toBe(2);
300
- expect(count_occurrences(result, 'TrackedObject')).toBe(2);
301
- expect(count_occurrences(result, 'TrackedSet')).toBe(2);
302
- expect(count_occurrences(result, 'TrackedMap')).toBe(2);
303
- expect(count_occurrences(result, 'createRefKey')).toBe(2);
304
- });
305
-
306
- it('only adds missing imports for shorthand syntax, reusing existing ones', () => {
301
+ it('adds hidden obfuscated imports for shorthand syntax', () => {
307
302
  const source = `
308
- import { TrackedArray, TrackedMap, createRefKey as crk } from 'ripple';
309
-
310
303
  component App() {
311
304
  const items = #[1, 2, 3];
312
305
  const obj = #{ a: 1, b: 2, c: 3 };
@@ -318,12 +311,11 @@ component App() {
318
311
  `;
319
312
  const result = compile_to_volar_mappings(source, 'test.ripple').code;
320
313
 
321
- expect(count_occurrences(result, 'TrackedArray')).toBe(2);
322
- expect(count_occurrences(result, 'TrackedObject')).toBe(2);
323
- expect(count_occurrences(result, 'TrackedSet')).toBe(2);
324
- expect(count_occurrences(result, 'TrackedMap')).toBe(2);
325
- expect(count_occurrences(result, 'createRefKey')).toBe(1);
326
- expect(count_occurrences(result, 'crk')).toBe(2);
314
+ expect(count_occurrences(result, obfuscate_imported('TrackedArray'))).toBe(2);
315
+ expect(count_occurrences(result, obfuscate_imported('TrackedObject'))).toBe(2);
316
+ expect(count_occurrences(result, obfuscate_imported('TrackedSet'))).toBe(2);
317
+ expect(count_occurrences(result, obfuscate_imported('TrackedMap'))).toBe(2);
318
+ expect(count_occurrences(result, obfuscate_imported('createRefKey'))).toBe(2);
327
319
  });
328
320
 
329
321
  it('should not error on having js below markup in the same scope', () => {
package/tsconfig.json CHANGED
@@ -1,32 +1,32 @@
1
1
  {
2
- "compilerOptions": {
3
- "module": "esnext",
4
- "lib": ["esnext", "dom", "dom.iterable"],
5
- "target": "esnext",
6
- "noEmit": true,
7
- "moduleResolution": "bundler",
8
- "resolveJsonModule": true,
9
- "noEmitOnError": true,
10
- "noErrorTruncation": true,
11
- "allowSyntheticDefaultImports": true,
12
- "verbatimModuleSyntax": true,
13
- "types": ["node", "vitest/globals"],
14
- "jsx": "preserve",
15
- "jsxImportSource": "ripple",
16
- "strict": true,
17
- "allowJs": true,
18
- "checkJs": true,
19
- "paths": {
20
- "ripple": ["./types/index.d.ts"],
2
+ "compilerOptions": {
3
+ "module": "esnext",
4
+ "lib": ["esnext", "dom", "dom.iterable"],
5
+ "target": "esnext",
6
+ "noEmit": true,
7
+ "moduleResolution": "bundler",
8
+ "resolveJsonModule": true,
9
+ "noEmitOnError": true,
10
+ "noErrorTruncation": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "verbatimModuleSyntax": true,
13
+ "types": ["node", "vitest/globals"],
14
+ "jsx": "preserve",
15
+ "jsxImportSource": "ripple",
16
+ "strict": true,
17
+ "allowJs": true,
18
+ "checkJs": true,
19
+ "paths": {
20
+ "ripple": ["./types/index.d.ts"],
21
21
  "rollup": ["./shims/rollup-estree-types.d.ts"],
22
22
  "rollup/*": ["./shims/rollup-estree-types.d.ts"]
23
- }
24
- },
25
- "include": [
26
- "./*.js",
27
- "./src/",
28
- "./tests/**/*.test.ripple",
29
- "./tests/**/*.d.ts",
30
- "./tests/**/*.js"
31
- ]
23
+ }
24
+ },
25
+ "include": [
26
+ "./*.js",
27
+ "./src/",
28
+ "./tests/**/*.test.ripple",
29
+ "./tests/**/*.d.ts",
30
+ "./tests/**/*.js"
31
+ ]
32
32
  }
package/types/index.d.ts CHANGED
@@ -10,18 +10,18 @@ export type CompatOptions = {
10
10
  [key: string]: CompatApi;
11
11
  };
12
12
 
13
- export declare function mount(
13
+ export function mount(
14
14
  component: () => void,
15
15
  options: { target: HTMLElement; props?: Record<string, any>; compat?: CompatOptions },
16
16
  ): () => void;
17
17
 
18
- export declare function tick(): Promise<void>;
18
+ export function tick(): Promise<void>;
19
19
 
20
- export declare function untrack<T>(fn: () => T): T;
20
+ export function untrack<T>(fn: () => T): T;
21
21
 
22
- export declare function flushSync<T>(fn?: () => T): T;
22
+ export function flushSync<T>(fn?: () => T): T;
23
23
 
24
- export declare function effect(fn: (() => void) | (() => () => void)): void;
24
+ export function effect(fn: (() => void) | (() => () => void)): void;
25
25
 
26
26
  export interface TrackedArrayConstructor {
27
27
  new <T>(...elements: T[]): TrackedArray<T>; // must be used with `new`
@@ -32,16 +32,16 @@ export interface TrackedArrayConstructor {
32
32
 
33
33
  export interface TrackedArray<T> extends Array<T> {}
34
34
 
35
- export declare const TrackedArray: TrackedArrayConstructor;
35
+ export const TrackedArray: TrackedArrayConstructor;
36
36
 
37
- export declare class Context<T> {
37
+ export class Context<T> {
38
38
  constructor(initial_value: T);
39
39
  get(): T;
40
40
  set(value: T): void;
41
41
  #private;
42
42
  }
43
43
 
44
- export declare class TrackedSet<T> extends Set<T> {
44
+ export class TrackedSet<T> extends Set<T> {
45
45
  isDisjointFrom<U>(other: ReadonlySetLike<U> | TrackedSet<U>): boolean;
46
46
  isSubsetOf<U>(other: ReadonlySetLike<U> | TrackedSet<U>): boolean;
47
47
  isSupersetOf<U>(other: ReadonlySetLike<U> | TrackedSet<U>): boolean;
@@ -53,7 +53,7 @@ export declare class TrackedSet<T> extends Set<T> {
53
53
  #private;
54
54
  }
55
55
 
56
- export declare class TrackedMap<K, V> extends Map<K, V> {
56
+ export class TrackedMap<K, V> extends Map<K, V> {
57
57
  toJSON(): [K, V][];
58
58
  #private;
59
59
  }
@@ -81,7 +81,7 @@ declare global {
81
81
  };
82
82
  }
83
83
 
84
- export declare function createRefKey(): symbol;
84
+ export function createRefKey(): symbol;
85
85
 
86
86
  // Base Tracked interface - all tracked values have a '#v' property containing the actual value
87
87
  export interface Tracked<V> {
@@ -115,24 +115,20 @@ type SplitResult<T extends Props, K extends readonly (keyof T)[]> = [
115
115
  Tracked<RestKeys<T, K>>,
116
116
  ];
117
117
 
118
- export declare function get<V>(tracked: Tracked<V>): V;
118
+ export function get<V>(tracked: Tracked<V>): V;
119
119
 
120
- export declare function set<V>(tracked: Tracked<V>, value: V): void;
120
+ export function set<V>(tracked: Tracked<V>, value: V): void;
121
121
 
122
122
  // Overload for function values - infers the return type of the function
123
- export declare function track<V>(
123
+ export function track<V>(
124
124
  value: () => V,
125
125
  get?: (v: InferComponent<V>) => InferComponent<V>,
126
126
  set?: (next: InferComponent<V>, prev: InferComponent<V>) => InferComponent<V>,
127
127
  ): Tracked<InferComponent<V>>;
128
128
  // Overload for non-function values
129
- export declare function track<V>(
130
- value?: V,
131
- get?: (v: V) => V,
132
- set?: (next: V, prev: V) => V,
133
- ): Tracked<V>;
129
+ export function track<V>(value?: V, get?: (v: V) => V, set?: (next: V, prev: V) => V): Tracked<V>;
134
130
 
135
- export declare function trackSplit<V extends Props, const K extends readonly (keyof V)[]>(
131
+ export function trackSplit<V extends Props, const K extends readonly (keyof V)[]>(
136
132
  value: V,
137
133
  splitKeys: K,
138
134
  ): SplitResult<V, K>;
@@ -219,7 +215,7 @@ export interface TrackedObjectConstructor {
219
215
  new <T extends object>(obj: T): TrackedObject<T>;
220
216
  }
221
217
 
222
- export declare const TrackedObject: TrackedObjectConstructor;
218
+ export const TrackedObject: TrackedObjectConstructor;
223
219
 
224
220
  export class TrackedDate extends Date {
225
221
  constructor(...params: any[]);
@@ -252,7 +248,7 @@ export interface MediaQuery extends Tracked<boolean> {
252
248
  _brand: void;
253
249
  }
254
250
 
255
- export declare const MediaQuery: {
251
+ export const MediaQuery: {
256
252
  new (query: string, fallback?: boolean | undefined): Tracked<boolean>;
257
253
  };
258
254
 
@@ -268,7 +264,7 @@ export function Portal<V = HTMLElement>({
268
264
  * @param {Tracked<V>} tracked
269
265
  * @returns {(node: HTMLInputElement | HTMLSelectElement) => void}
270
266
  */
271
- export declare function bindValue<V>(
267
+ export function bindValue<V>(
272
268
  tracked: Tracked<V>,
273
269
  ): (node: HTMLInputElement | HTMLSelectElement) => void;
274
270
 
@@ -276,36 +272,34 @@ export declare function bindValue<V>(
276
272
  * @param {Tracked<V>} tracked
277
273
  * @returns {(node: HTMLInputElement) => void}
278
274
  */
279
- export declare function bindChecked<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
275
+ export function bindChecked<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
280
276
 
281
- export declare function bindClientWidth<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
277
+ export function bindClientWidth<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
282
278
 
283
- export declare function bindClientHeight<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
279
+ export function bindClientHeight<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
284
280
 
285
- export declare function bindContentRect<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
281
+ export function bindContentRect<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
286
282
 
287
- export declare function bindContentBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
283
+ export function bindContentBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
288
284
 
289
- export declare function bindBorderBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
285
+ export function bindBorderBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
290
286
 
291
- export declare function bindDevicePixelContentBoxSize<V>(
292
- tracked: Tracked<V>,
293
- ): (node: HTMLElement) => void;
287
+ export function bindDevicePixelContentBoxSize<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
294
288
 
295
- export declare function bindInnerHTML<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
289
+ export function bindInnerHTML<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
296
290
 
297
- export declare function bindInnerText<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
291
+ export function bindInnerText<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
298
292
 
299
- export declare function bindTextContent<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
293
+ export function bindTextContent<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
300
294
 
301
- export declare function bindNode<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
295
+ export function bindNode<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
302
296
 
303
- export declare function bindGroup<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
297
+ export function bindGroup<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
304
298
 
305
- export declare function bindOffsetHeight<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
299
+ export function bindOffsetHeight<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
306
300
 
307
- export declare function bindOffsetWidth<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
301
+ export function bindOffsetWidth<V>(tracked: Tracked<V>): (node: HTMLElement) => void;
308
302
 
309
- export declare function bindIndeterminate<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
303
+ export function bindIndeterminate<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
310
304
 
311
- export declare function bindFiles<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;
305
+ export function bindFiles<V>(tracked: Tracked<V>): (node: HTMLInputElement) => void;