@ripple-ts/typescript-plugin 0.2.216 → 0.3.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @ripple-ts/typescript-plugin
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#779](https://github.com/Ripple-TS/ripple/pull/779)
8
+ [`74a10cc`](https://github.com/Ripple-TS/ripple/commit/74a10cc5701962cd7c72b144d59b35ecb76263a3)
9
+ Thanks [@leonidaz](https://github.com/leonidaz)! - Introduces #ripple namespace
10
+ for creating ripple reactive entities without imports, such as array, object,
11
+ map, set, date, url, urlSearchParams, mediaQuery. Adds track, untrack,
12
+ trackSplit, effect, context, server, style to the namespace. Deprecates #[] and
13
+ #{} in favor of #ripple[] and #ripple{}. Renames types and actual reactive
14
+ imports for TrackedX entities, such as TrackedArray, TrackedObject, etc. into
15
+ RippleArray, RippleObjec, etc.
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+ [[`61271cb`](https://github.com/Ripple-TS/ripple/commit/61271cb1c4777f2ab9093c6c89a5ad771ec98b7d),
21
+ [`21dd402`](https://github.com/Ripple-TS/ripple/commit/21dd4029d7e027a0706cb133b09530a722feb73d),
22
+ [`c2dbefe`](https://github.com/Ripple-TS/ripple/commit/c2dbefe5645c0c4f6e0ff4dc00d9c4de81616667),
23
+ [`74a10cc`](https://github.com/Ripple-TS/ripple/commit/74a10cc5701962cd7c72b144d59b35ecb76263a3)]:
24
+ - ripple@0.3.0
25
+
3
26
  ## 0.2.216
4
27
 
5
28
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "TypeScript plugin for Ripple",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.216",
6
+ "version": "0.3.0",
7
7
  "main": "src/index.js",
8
8
  "homepage": "https://ripple-ts.com",
9
9
  "repository": {
@@ -19,11 +19,12 @@
19
19
  "@volar/typescript": "~2.4.28"
20
20
  },
21
21
  "devDependencies": {
22
- "tsdown": "^0.15.4"
22
+ "@types/node": "^24.3.0",
23
+ "tsdown": "^0.20.3"
23
24
  },
24
25
  "peerDependencies": {
25
- "typescript": "^5.9.2",
26
- "ripple": "0.2.216"
26
+ "typescript": "^5.9.3",
27
+ "ripple": "0.3.0"
27
28
  },
28
29
  "publishConfig": {
29
30
  "access": "public"
package/src/language.js CHANGED
@@ -1,7 +1,6 @@
1
- // @ts-expect-error ESM import
2
1
  /** @import { CodeMapping } from 'ripple/compiler' */
3
- // @ts-expect-error ESM import
4
- /** @import {RippleCompiler, RippleCompileError, VolarMappingsResult} from 'ripple/compiler' */
2
+ /** @import {RippleCompileError, VolarMappingsResult} from 'ripple/compiler' */
3
+ /** @import * as RippleCompiler from 'ripple/compiler' */
5
4
 
6
5
  /** @typedef {Map<string, CodeMapping>} CachedMappings */
7
6
  /** @typedef {import('typescript').CompilerOptions} CompilerOptions */
@@ -191,7 +190,7 @@ class RippleVirtualCode {
191
190
  const charBeforeDot = newEnd > 1 ? newCode[newEnd - 2] : '';
192
191
  log(' Char before dot:', JSON.stringify(charBeforeDot));
193
192
 
194
- if (/[a-zA-Z0-9_\)\]\}]/.test(charBeforeDot)) {
193
+ if (/[$#_\u200C\u200D\p{ID_Continue}\)\]\}]/u.test(charBeforeDot)) {
195
194
  isDotTyped = true;
196
195
  dotPosition = newEnd - 1; // Position of the dot
197
196
  log('ChangeRange detected dot typed at position', dotPosition);
@@ -200,40 +199,25 @@ class RippleVirtualCode {
200
199
  }
201
200
 
202
201
  try {
203
- // If user typed a ".", use placeholder technique to get completions
202
+ // If user typed a ".", compile without it and then stitch it back into
203
+ // the generated output so completions can still resolve.
204
204
  if (isDotTyped && dotPosition >= 0) {
205
- const charBeforeDot = newCode[dotPosition - 1];
206
- const codeWithPlaceholder =
207
- newCode.substring(0, dotPosition) + charBeforeDot + newCode.substring(dotPosition + 1);
205
+ const codeWithoutDot =
206
+ newCode.substring(0, dotPosition) + newCode.substring(dotPosition + 1);
208
207
 
209
- log('Using placeholder technique for dot at position', dotPosition);
210
- transpiled = this.ripple.compile_to_volar_mappings(codeWithPlaceholder, this.fileName);
211
- log('Compilation with placeholder successful');
208
+ log('Compiling without typed dot at position', dotPosition);
209
+ transpiled = this.ripple.compile_to_volar_mappings(codeWithoutDot, this.fileName, {
210
+ loose: true,
211
+ });
212
+ log('Compilation without dot successful');
212
213
 
213
- // Find where the placeholder ended up in generated code and replace with "."
214
214
  if (transpiled && transpiled.code && transpiled.mappings.length > 0) {
215
- let placeholderMapping = null;
216
- for (const mapping of transpiled.mappings) {
217
- const sourceStart = mapping.sourceOffsets[0];
218
- const sourceEnd = sourceStart + mapping.lengths[0];
219
-
220
- if (dotPosition >= sourceStart && dotPosition < sourceEnd) {
221
- placeholderMapping = mapping;
222
- break;
223
- }
224
- }
215
+ const insertedDotPosition = restore_typed_dot_in_transpiled_code(transpiled, dotPosition);
225
216
 
226
- if (placeholderMapping) {
227
- const offsetInMapping = dotPosition - placeholderMapping.sourceOffsets[0];
228
- const placeholderPosInGenerated =
229
- placeholderMapping.generatedOffsets[0] + offsetInMapping;
230
-
231
- transpiled.code =
232
- transpiled.code.substring(0, placeholderPosInGenerated) +
233
- '.' +
234
- transpiled.code.substring(placeholderPosInGenerated + 1);
235
-
236
- log('Replaced placeholder at position', placeholderPosInGenerated, 'with dot');
217
+ if (insertedDotPosition === null) {
218
+ logWarning('Failed to restore typed dot into transpiled output');
219
+ } else {
220
+ log('Inserted typed dot at generated position', insertedDotPosition);
237
221
  }
238
222
  }
239
223
  } else {
@@ -325,7 +309,7 @@ class RippleVirtualCode {
325
309
  generatedLengths: [newCode.length],
326
310
  data: {
327
311
  verification: true, // disable TS since we're using source code as generated code
328
- customData: { generatedLengths: [newCode.length] },
312
+ customData: {},
329
313
  },
330
314
  },
331
315
  ];
@@ -355,7 +339,7 @@ class RippleVirtualCode {
355
339
  mapping = this.mappings[i];
356
340
 
357
341
  genStart = mapping.generatedOffsets[0];
358
- genLength = mapping.data.customData.generatedLengths[0];
342
+ genLength = mapping.generatedLengths[0];
359
343
  genEnd = genStart + genLength;
360
344
  genKey = `${genStart}-${genEnd}`;
361
345
  this.#mappingGenToSource.set(genKey, mapping);
@@ -427,7 +411,6 @@ function extractCssFromSource(code) {
427
411
  structure: true,
428
412
  format: false,
429
413
  customData: {
430
- generatedLengths: [cssLength],
431
414
  content: cssContent,
432
415
  embeddedId: `style_${index}`,
433
416
  },
@@ -456,6 +439,75 @@ function extractCssFromSource(code) {
456
439
  return embeddedCodes;
457
440
  }
458
441
 
442
+ /**
443
+ * Insert a typed dot back into the transpiled code and update mappings so the
444
+ * source and generated offsets stay aligned for completion requests.
445
+ * @param {VolarMappingsResult} transpiled
446
+ * @param {number} dotPosition
447
+ * @returns {number | null}
448
+ */
449
+ function restore_typed_dot_in_transpiled_code(transpiled, dotPosition) {
450
+ let dot_mapping = null;
451
+
452
+ for (const mapping of transpiled.mappings) {
453
+ const source_end = mapping.sourceOffsets[0] + mapping.lengths[0];
454
+ if (source_end === dotPosition) {
455
+ dot_mapping = mapping;
456
+ break;
457
+ }
458
+ }
459
+
460
+ if (!dot_mapping) {
461
+ return null;
462
+ }
463
+
464
+ const generated_length = dot_mapping.generatedLengths[0];
465
+ const insertedDotPosition = dot_mapping.generatedOffsets[0] + generated_length;
466
+
467
+ transpiled.code =
468
+ transpiled.code.substring(0, insertedDotPosition) +
469
+ '.' +
470
+ transpiled.code.substring(insertedDotPosition);
471
+
472
+ // Create a separate 1:1 mapping for the dot character instead of extending
473
+ // the existing mapping. When source and generated lengths differ (e.g.
474
+ // #ripple → _$__u0023_ripple), Volar's translateOffset uses
475
+ // Math.min(relativePos, toLength) which would map the cursor after the dot
476
+ // to the middle of the generated identifier instead of after it.
477
+
478
+ /** @type {CodeMapping} */
479
+ const new_dot_mapping = {
480
+ sourceOffsets: [dotPosition],
481
+ generatedOffsets: [insertedDotPosition],
482
+ lengths: [1],
483
+ generatedLengths: [1],
484
+ data: { ...dot_mapping.data },
485
+ };
486
+
487
+ // Find the index to insert after dot_mapping
488
+ const dot_mapping_index = transpiled.mappings.indexOf(dot_mapping);
489
+ transpiled.mappings.splice(dot_mapping_index + 1, 0, new_dot_mapping);
490
+
491
+ for (const mapping of transpiled.mappings) {
492
+ if (
493
+ mapping !== dot_mapping &&
494
+ mapping !== new_dot_mapping &&
495
+ mapping.generatedOffsets[0] >= insertedDotPosition
496
+ ) {
497
+ mapping.generatedOffsets[0] += 1;
498
+ }
499
+ if (
500
+ mapping !== dot_mapping &&
501
+ mapping !== new_dot_mapping &&
502
+ mapping.sourceOffsets[0] >= dotPosition
503
+ ) {
504
+ mapping.sourceOffsets[0] += 1;
505
+ }
506
+ }
507
+
508
+ return insertedDotPosition;
509
+ }
510
+
459
511
  /**
460
512
  * @template T
461
513
  * @param {{ options?: CompilerOptions } & T} config
package/src/utils.js CHANGED
@@ -1,4 +1,6 @@
1
1
  const DEBUG = process.env.RIPPLE_DEBUG === 'true';
2
+ // Matches valid JS/CSS identifier characters: word chars, dashes (CSS), $, and # (Ripple shorthands)
3
+ const charAllowedWordRegex = /[\w\-$#]/;
2
4
 
3
5
  /**
4
6
  * Create a logging utility with a specific label
@@ -29,7 +31,34 @@ function createLogging(label) {
29
31
  };
30
32
  }
31
33
 
34
+ /**
35
+ * Get the word at a specific position in the text
36
+ * @param {string} text
37
+ * @param {number} start
38
+ * @returns {{word: string, start: number, end: number}}
39
+ */
40
+ function getWordFromPosition(text, start) {
41
+ let wordStart = start;
42
+ let wordEnd = start;
43
+ while (wordStart > 0 && charAllowedWordRegex.test(text[wordStart - 1])) {
44
+ wordStart--;
45
+ }
46
+ while (wordEnd < text.length && charAllowedWordRegex.test(text[wordEnd])) {
47
+ wordEnd++;
48
+ }
49
+
50
+ const word = text.substring(wordStart, wordEnd);
51
+
52
+ return {
53
+ word,
54
+ start: wordStart,
55
+ end: wordEnd,
56
+ };
57
+ }
58
+
32
59
  module.exports = {
33
60
  createLogging,
61
+ getWordFromPosition,
62
+ charAllowedWordRegex,
34
63
  DEBUG,
35
64
  };
package/tsconfig.json CHANGED
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "module": "node20",
4
+ "moduleResolution": "node16",
5
+ "target": "es2021",
6
+ "lib": ["es2021"],
3
7
  "allowJs": true,
4
- "noEmit": true
8
+ "checkJs": true,
9
+ "noEmit": true,
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "resolveJsonModule": true,
13
+ "types": ["node"]
5
14
  },
6
15
  "include": ["./src/**/*", "./tests/**/*"],
7
16
  "exclude": ["dist", "node_modules"]
package/tsdown.config.js CHANGED
@@ -1,8 +1,10 @@
1
- const { defineConfig } = require('tsdown');
1
+ import { defineConfig } from 'tsdown';
2
2
 
3
- module.exports = defineConfig({
3
+ export default defineConfig({
4
+ inlineOnly: false,
4
5
  entry: ['src/index.js'],
5
6
  format: ['cjs'],
7
+ fixedExtension: false,
6
8
  platform: 'node',
7
9
  target: 'node20',
8
10
  outDir: 'dist',