jsonc-effect 0.3.0 → 0.3.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.
Files changed (4) hide show
  1. package/README.md +17 -11
  2. package/format.js +33 -18
  3. package/package.json +1 -1
  4. package/parse.js +40 -15
package/README.md CHANGED
@@ -1,27 +1,31 @@
1
1
  # jsonc-effect
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/jsonc-effect)](https://www.npmjs.com/package/jsonc-effect)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue)](https://www.typescriptlang.org/)
3
+ [![npm](https://img.shields.io/npm/v/jsonc-effect?label=npm&color=cb3837)](https://www.npmjs.com/package/jsonc-effect)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-4caf50.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js %3E%3D24.11.0](https://img.shields.io/badge/Node.js-%3E%3D24.11.0-5fa04e.svg)](https://nodejs.org/)
6
+ [![TypeScript 6.0](https://img.shields.io/badge/TypeScript-6.0-3178c6.svg)](https://www.typescriptlang.org/)
6
7
 
7
- Pure [Effect](https://effect.website) JSONC (JSON with Comments) parser with no external parser dependencies. Scanner, parser, AST, and formatting are all implemented natively.
8
+ Pure [Effect](https://effect.website) JSONC (JSON with Comments) parser with no external parser dependencies. Scanner, parser, AST and formatting are all implemented natively.
8
9
 
9
10
  ## Features
10
11
 
11
- - **Effect-native** -- typed errors, Schema integration, and composable pipelines
12
+ - **Effect-native** -- typed errors, Schema integration and composable pipelines
12
13
  - **Zero parser dependencies** -- `effect` is the sole runtime dependency
13
14
  - **Schema integration** -- parse JSONC strings directly into validated types
14
- - **Full toolchain** -- scanner, parser, AST navigation, visitor stream, formatting, and modification
15
- - **Equality comparisons** -- compare JSONC documents semantically, ignoring comments, formatting, and key ordering
15
+ - **Full toolchain** -- scanner, parser, AST navigation, visitor stream, formatting and modification
16
+ - **Byte-minimal edits** -- `modify` targets exactly the value's span, preserving surrounding whitespace and comments
17
+ - **Equality comparisons** -- compare JSONC documents semantically, ignoring comments, formatting and key ordering
16
18
  - **Safe by default** -- returns `unknown` (not `any`) and `Option` (not `undefined`)
17
19
 
18
- ## Installation
20
+ ## Install
19
21
 
20
22
  ```bash
21
23
  npm install jsonc-effect effect
24
+ # or
25
+ pnpm add jsonc-effect effect
22
26
  ```
23
27
 
24
- ## Quick Start
28
+ ## Quick start
25
29
 
26
30
  ```typescript
27
31
  import { parse } from "jsonc-effect"
@@ -59,8 +63,10 @@ This library is for Effect-based programs that need deeper introspection and edi
59
63
 
60
64
  ## Documentation
61
65
 
62
- For API reference, advanced usage, and examples, see [docs](./docs/README.md).
66
+ - [Getting started](./docs/01-getting-started.md) installation, first example, core concepts
67
+ - [Examples](./docs/02-examples.md) — real-world usage patterns
68
+ - [API reference](./docs/03-api-reference.md) — all exports with signatures and descriptions
63
69
 
64
70
  ## License
65
71
 
66
- [MIT](./LICENSE)
72
+ [MIT](LICENSE)
package/format.js CHANGED
@@ -302,9 +302,13 @@ function modifyImpl(text, path, value, formattingOptions) {
302
302
  }
303
303
  const scanner = createScanner(text, true);
304
304
  let currentToken = scanner.scan();
305
+ function tokenEnd() {
306
+ return scanner.getTokenOffset() + scanner.getTokenLength();
307
+ }
305
308
  function skipValue() {
306
309
  switch (currentToken) {
307
310
  case "OpenBrace": {
311
+ let end = tokenEnd();
308
312
  currentToken = scanner.scan();
309
313
  let first = true;
310
314
  while (currentToken !== "CloseBrace" && currentToken !== "EOF") {
@@ -313,28 +317,40 @@ function modifyImpl(text, path, value, formattingOptions) {
313
317
  currentToken = scanner.scan();
314
318
  if (currentToken === "Colon") {
315
319
  currentToken = scanner.scan();
316
- skipValue();
320
+ end = skipValue();
317
321
  }
318
- } else currentToken = scanner.scan();
322
+ } else {
323
+ end = tokenEnd();
324
+ currentToken = scanner.scan();
325
+ }
319
326
  first = false;
320
327
  }
321
- if (currentToken === "CloseBrace") currentToken = scanner.scan();
322
- break;
328
+ if (currentToken === "CloseBrace") {
329
+ end = tokenEnd();
330
+ currentToken = scanner.scan();
331
+ }
332
+ return end;
323
333
  }
324
334
  case "OpenBracket": {
335
+ let end = tokenEnd();
325
336
  currentToken = scanner.scan();
326
337
  let first = true;
327
338
  while (currentToken !== "CloseBracket" && currentToken !== "EOF") {
328
339
  if (!first && currentToken === "Comma") currentToken = scanner.scan();
329
- skipValue();
340
+ end = skipValue();
330
341
  first = false;
331
342
  }
332
- if (currentToken === "CloseBracket") currentToken = scanner.scan();
333
- break;
343
+ if (currentToken === "CloseBracket") {
344
+ end = tokenEnd();
345
+ currentToken = scanner.scan();
346
+ }
347
+ return end;
334
348
  }
335
- default:
349
+ default: {
350
+ const end = tokenEnd();
336
351
  currentToken = scanner.scan();
337
- break;
352
+ return end;
353
+ }
338
354
  }
339
355
  }
340
356
  let depth = 0;
@@ -357,8 +373,7 @@ function modifyImpl(text, path, value, formattingOptions) {
357
373
  if (depth === path.length) {
358
374
  const valueStart = scanner.getTokenOffset();
359
375
  const prevEnd = valueStart;
360
- skipValue();
361
- const valueEnd = scanner.getTokenOffset();
376
+ const valueEnd = skipValue();
362
377
  if (value === void 0) {
363
378
  let removeStart = valueStart;
364
379
  let removeEnd = valueEnd;
@@ -385,9 +400,11 @@ function modifyImpl(text, path, value, formattingOptions) {
385
400
  }
386
401
  break;
387
402
  }
388
- skipValue();
389
- } else currentToken = scanner.scan();
390
- lastValueEnd = scanner.getTokenOffset();
403
+ lastValueEnd = skipValue();
404
+ } else {
405
+ currentToken = scanner.scan();
406
+ lastValueEnd = scanner.getTokenOffset();
407
+ }
391
408
  isFirst = false;
392
409
  }
393
410
  if (!found && depth === path.length && value !== void 0) {
@@ -410,8 +427,7 @@ function modifyImpl(text, path, value, formattingOptions) {
410
427
  if (idx === segment) {
411
428
  if (depth === path.length) {
412
429
  const valueStart = scanner.getTokenOffset();
413
- skipValue();
414
- const valueEnd = scanner.getTokenOffset();
430
+ const valueEnd = skipValue();
415
431
  if (value === void 0) {
416
432
  let removeEnd = valueEnd;
417
433
  if (text.substring(removeEnd).trimStart().startsWith(",")) removeEnd = text.indexOf(",", removeEnd) + 1;
@@ -430,8 +446,7 @@ function modifyImpl(text, path, value, formattingOptions) {
430
446
  }
431
447
  break;
432
448
  }
433
- skipValue();
434
- lastEnd = scanner.getTokenOffset();
449
+ lastEnd = skipValue();
435
450
  idx++;
436
451
  }
437
452
  if (idx <= segment && depth === path.length && value !== void 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsonc-effect",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "description": "Pure Effect-TS implementation of a JSONC (JSON with Comments) parser",
6
6
  "keywords": [
package/parse.js CHANGED
@@ -263,6 +263,9 @@ function parseInternal(text, options, buildTree) {
263
263
  }
264
264
  }
265
265
  }
266
+ function tokenEnd() {
267
+ return scanner.getTokenOffset() + scanner.getTokenLength();
268
+ }
266
269
  function handleError(code, skipUntilAfter = [], skipUntil = []) {
267
270
  errors.push(new JsoncParseErrorDetail({
268
271
  code,
@@ -372,8 +375,9 @@ function parseInternal(text, options, buildTree) {
372
375
  length: 0,
373
376
  value: scanner.getTokenValue()
374
377
  };
378
+ const end = tokenEnd();
375
379
  scanNext();
376
- node.length = scanner.getTokenOffset() - node.offset;
380
+ node.length = end - node.offset;
377
381
  return node;
378
382
  }
379
383
  case "Number": {
@@ -383,8 +387,9 @@ function parseInternal(text, options, buildTree) {
383
387
  length: 0,
384
388
  value: Number.parseFloat(scanner.getTokenValue())
385
389
  };
390
+ const end = tokenEnd();
386
391
  scanNext();
387
- node.length = scanner.getTokenOffset() - node.offset;
392
+ node.length = end - node.offset;
388
393
  return node;
389
394
  }
390
395
  case "True": {
@@ -394,8 +399,9 @@ function parseInternal(text, options, buildTree) {
394
399
  length: 0,
395
400
  value: true
396
401
  };
402
+ const end = tokenEnd();
397
403
  scanNext();
398
- node.length = scanner.getTokenOffset() - node.offset;
404
+ node.length = end - node.offset;
399
405
  return node;
400
406
  }
401
407
  case "False": {
@@ -405,8 +411,9 @@ function parseInternal(text, options, buildTree) {
405
411
  length: 0,
406
412
  value: false
407
413
  };
414
+ const end = tokenEnd();
408
415
  scanNext();
409
- node.length = scanner.getTokenOffset() - node.offset;
416
+ node.length = end - node.offset;
410
417
  return node;
411
418
  }
412
419
  case "Null": {
@@ -416,8 +423,9 @@ function parseInternal(text, options, buildTree) {
416
423
  length: 0,
417
424
  value: null
418
425
  };
426
+ const end = tokenEnd();
419
427
  scanNext();
420
- node.length = scanner.getTokenOffset() - node.offset;
428
+ node.length = end - node.offset;
421
429
  return node;
422
430
  }
423
431
  default: return;
@@ -443,9 +451,15 @@ function parseInternal(text, options, buildTree) {
443
451
  else handleError("ValueExpected", [], ["CloseBracket", "Comma"]);
444
452
  needsComma = true;
445
453
  }
446
- if (token() !== "CloseBracket") handleError("CloseBracketExpected");
447
- else scanNext();
448
- node.length = scanner.getTokenOffset() - node.offset;
454
+ let end;
455
+ if (token() !== "CloseBracket") {
456
+ handleError("CloseBracketExpected");
457
+ end = scanner.getTokenOffset();
458
+ } else {
459
+ end = tokenEnd();
460
+ scanNext();
461
+ }
462
+ node.length = end - node.offset;
449
463
  return node;
450
464
  }
451
465
  function parseObjectTree() {
@@ -479,8 +493,9 @@ function parseInternal(text, options, buildTree) {
479
493
  length: 0,
480
494
  value: scanner.getTokenValue()
481
495
  };
496
+ const keyEnd = tokenEnd();
482
497
  scanNext();
483
- keyNode.length = scanner.getTokenOffset() - keyNode.offset;
498
+ keyNode.length = keyEnd - keyNode.offset;
484
499
  property.children.push(keyNode);
485
500
  if (token() !== "Colon") {
486
501
  handleError("ColonExpected", [], ["CloseBrace", "Comma"]);
@@ -491,15 +506,25 @@ function parseInternal(text, options, buildTree) {
491
506
  property.colonOffset = scanner.getTokenOffset();
492
507
  scanNext();
493
508
  const valueNode = parseValueTree();
494
- if (valueNode) property.children.push(valueNode);
495
- else handleError("ValueExpected", [], ["CloseBrace", "Comma"]);
496
- property.length = scanner.getTokenOffset() - property.offset;
509
+ if (valueNode) {
510
+ property.children.push(valueNode);
511
+ property.length = valueNode.offset + valueNode.length - property.offset;
512
+ } else {
513
+ handleError("ValueExpected", [], ["CloseBrace", "Comma"]);
514
+ property.length = scanner.getTokenOffset() - property.offset;
515
+ }
497
516
  node.children.push(property);
498
517
  needsComma = true;
499
518
  }
500
- if (token() !== "CloseBrace") handleError("CloseBraceExpected");
501
- else scanNext();
502
- node.length = scanner.getTokenOffset() - node.offset;
519
+ let end;
520
+ if (token() !== "CloseBrace") {
521
+ handleError("CloseBraceExpected");
522
+ end = scanner.getTokenOffset();
523
+ } else {
524
+ end = tokenEnd();
525
+ scanNext();
526
+ }
527
+ node.length = end - node.offset;
503
528
  return node;
504
529
  }
505
530
  scanNext();