smol-toml 1.5.2 → 1.6.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.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # smol-toml
2
- [![TOML 1.0.0](https://img.shields.io/badge/TOML-1.0.0-9c4221?style=flat-square)](https://toml.io/en/v1.0.0)
2
+ [![TOML 1.1.0](https://img.shields.io/badge/TOML-1.1.0-9c4221?style=flat-square)](https://toml.io/en/v1.1.0)
3
3
  [![License](https://img.shields.io/github/license/squirrelchat/smol-toml.svg?style=flat-square)](https://github.com/squirrelchat/smol-toml/blob/mistress/LICENSE)
4
4
  [![npm](https://img.shields.io/npm/v/smol-toml?style=flat-square)](https://npm.im/smol-toml)
5
5
  [![Build](https://img.shields.io/github/actions/workflow/status/squirrelchat/smol-toml/build.yaml?style=flat-square&logo=github)](https://github.com/squirrelchat/smol-toml/actions/workflows/build.yaml)
@@ -8,7 +8,7 @@
8
8
  [![Weekly downloads](https://img.shields.io/npm/dw/smol-toml?style=flat-square)](https://npm.im/smol-toml)
9
9
  [![Monthly downloads](https://img.shields.io/npm/dm/smol-toml?style=flat-square)](https://npm.im/smol-toml)
10
10
 
11
- A small, fast, and correct TOML parser and serializer. smol-toml is fully(ish) spec-compliant with TOML v1.0.0.
11
+ A small, fast, and correct TOML parser and serializer. smol-toml is fully(ish) spec-compliant with TOML v1.1.0.
12
12
 
13
13
  Why yet another TOML parser? Well, the ecosystem of TOML parsers in JavaScript is quite underwhelming, most likely due
14
14
  to a lack of interest. With most parsers being outdated, unmaintained, non-compliant, or a combination of these, a new
@@ -144,8 +144,11 @@ const localTime = TomlDate.wrapAsLocalTime(jsDate)
144
144
 
145
145
  ## Performance
146
146
  > [!NOTE]
147
- > These benchmarks are starting to get a bit old. They will be updated in the (hopefully near) future to better
148
- > reflect numbers of the latest version of smol-toml on the latest version of Node.js.
147
+ > These benchmarks are starting to get quite old and were ran prior to the release of TOML 1.1.0 which changed subtle
148
+ > implementation details.
149
+ >
150
+ > They will be updated in the (hopefully near) future to better reflect numbers of the latest version of smol-toml
151
+ > on the latest version of Node.js.
149
152
 
150
153
  A note on these performance numbers: in some highly synthetic tests, other parsers such as `fast-toml` greatly
151
154
  outperform other parsers, mostly due to their lack of compliance with the spec. For example, to parse a string,
package/dist/date.js CHANGED
@@ -25,7 +25,7 @@
25
25
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
26
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
- let DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}:\d{2}(?:\.\d+)?)?(Z|[-+]\d{2}:\d{2})?$/i;
28
+ let DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
29
29
  export class TomlDate extends Date {
30
30
  #hasDate = false;
31
31
  #hasTime = false;
package/dist/extract.js CHANGED
@@ -27,9 +27,9 @@
27
27
  */
28
28
  import { parseString, parseValue } from './primitive.js';
29
29
  import { parseArray, parseInlineTable } from './struct.js';
30
- import { indexOfNewline, skipVoid, skipUntil, skipComment, getStringEnd } from './util.js';
30
+ import { skipVoid, skipUntil, skipComment, getStringEnd } from './util.js';
31
31
  import { TomlError } from './error.js';
32
- function sliceAndTrimEndOf(str, startPtr, endPtr, allowNewLines) {
32
+ function sliceAndTrimEndOf(str, startPtr, endPtr) {
33
33
  let value = str.slice(startPtr, endPtr);
34
34
  let commentIdx = value.indexOf('#');
35
35
  if (commentIdx > -1) {
@@ -38,17 +38,7 @@ function sliceAndTrimEndOf(str, startPtr, endPtr, allowNewLines) {
38
38
  skipComment(str, commentIdx);
39
39
  value = value.slice(0, commentIdx);
40
40
  }
41
- let trimmed = value.trimEnd();
42
- if (!allowNewLines) {
43
- let newlineIdx = value.indexOf('\n', trimmed.length);
44
- if (newlineIdx > -1) {
45
- throw new TomlError('newlines are not allowed in inline tables', {
46
- toml: str,
47
- ptr: startPtr + newlineIdx
48
- });
49
- }
50
- }
51
- return [trimmed, commentIdx];
41
+ return [value.trimEnd(), commentIdx];
52
42
  }
53
43
  export function extractValue(str, ptr, end, depth, integersAsBigInt) {
54
44
  if (depth === 0) {
@@ -62,24 +52,25 @@ export function extractValue(str, ptr, end, depth, integersAsBigInt) {
62
52
  let [value, endPtr] = c === '['
63
53
  ? parseArray(str, ptr, depth, integersAsBigInt)
64
54
  : parseInlineTable(str, ptr, depth, integersAsBigInt);
65
- let newPtr = end ? skipUntil(str, endPtr, ',', end) : endPtr;
66
- if (endPtr - newPtr && end === '}') {
67
- let nextNewLine = indexOfNewline(str, endPtr, newPtr);
68
- if (nextNewLine > -1) {
69
- throw new TomlError('newlines are not allowed in inline tables', {
55
+ if (end) {
56
+ endPtr = skipVoid(str, endPtr);
57
+ if (str[endPtr] === ',')
58
+ endPtr++;
59
+ else if (str[endPtr] !== end) {
60
+ throw new TomlError('expected comma or end of structure', {
70
61
  toml: str,
71
- ptr: nextNewLine
62
+ ptr: endPtr,
72
63
  });
73
64
  }
74
65
  }
75
- return [value, newPtr];
66
+ return [value, endPtr];
76
67
  }
77
68
  let endPtr;
78
69
  if (c === '"' || c === "'") {
79
70
  endPtr = getStringEnd(str, ptr);
80
71
  let parsed = parseString(str, ptr, endPtr);
81
72
  if (end) {
82
- endPtr = skipVoid(str, endPtr, end !== ']');
73
+ endPtr = skipVoid(str, endPtr);
83
74
  if (str[endPtr] && str[endPtr] !== ',' && str[endPtr] !== end && str[endPtr] !== '\n' && str[endPtr] !== '\r') {
84
75
  throw new TomlError('unexpected character encountered', {
85
76
  toml: str,
@@ -91,7 +82,7 @@ export function extractValue(str, ptr, end, depth, integersAsBigInt) {
91
82
  return [parsed, endPtr];
92
83
  }
93
84
  endPtr = skipUntil(str, ptr, ',', end);
94
- let slice = sliceAndTrimEndOf(str, ptr, endPtr - (+(str[endPtr - 1] === ',')), end === ']');
85
+ let slice = sliceAndTrimEndOf(str, ptr, endPtr - (+(str[endPtr - 1] === ',')));
95
86
  if (!slice[0]) {
96
87
  throw new TomlError('incomplete key-value declaration: no value specified', {
97
88
  toml: str,
package/dist/index.cjs CHANGED
@@ -99,9 +99,14 @@ function skipComment(str, ptr) {
99
99
  }
100
100
  function skipVoid(str, ptr, banNewLines, banComments) {
101
101
  let c;
102
- while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n"))
103
- ptr++;
104
- return banComments || c !== "#" ? ptr : skipVoid(str, skipComment(str, ptr), banNewLines);
102
+ while (1) {
103
+ while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n"))
104
+ ptr++;
105
+ if (banComments || c !== "#")
106
+ break;
107
+ ptr = skipComment(str, ptr);
108
+ }
109
+ return ptr;
105
110
  }
106
111
  function skipUntil(str, ptr, sep, end, banNewLines = false) {
107
112
  if (!end) {
@@ -143,7 +148,7 @@ function getStringEnd(str, seek) {
143
148
  }
144
149
 
145
150
  // dist/date.js
146
- var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}:\d{2}(?:\.\d+)?)?(Z|[-+]\d{2}:\d{2})?$/i;
151
+ var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
147
152
  var TomlDate = class _TomlDate extends Date {
148
153
  #hasDate = false;
149
154
  #hasTime = false;
@@ -238,13 +243,14 @@ var TomlDate = class _TomlDate extends Date {
238
243
  var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
239
244
  var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
240
245
  var LEADING_ZERO = /^[+-]?0[0-9_]/;
241
- var ESCAPE_REGEX = /^[0-9a-f]{4,8}$/i;
246
+ var ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
242
247
  var ESC_MAP = {
243
248
  b: "\b",
244
249
  t: " ",
245
250
  n: "\n",
246
251
  f: "\f",
247
252
  r: "\r",
253
+ e: "\x1B",
248
254
  '"': '"',
249
255
  "\\": "\\"
250
256
  };
@@ -279,8 +285,8 @@ function parseString(str, ptr = 0, endPtr = str.length) {
279
285
  }
280
286
  if (isEscape) {
281
287
  isEscape = false;
282
- if (c === "u" || c === "U") {
283
- let code = str.slice(ptr, ptr += c === "u" ? 4 : 8);
288
+ if (c === "x" || c === "u" || c === "U") {
289
+ let code = str.slice(ptr, ptr += c === "x" ? 2 : c === "u" ? 4 : 8);
284
290
  if (!ESCAPE_REGEX.test(code)) {
285
291
  throw new TomlError("invalid unicode escape", {
286
292
  toml: str,
@@ -373,24 +379,14 @@ function parseValue(value, toml, ptr, integersAsBigInt) {
373
379
  }
374
380
 
375
381
  // dist/extract.js
376
- function sliceAndTrimEndOf(str, startPtr, endPtr, allowNewLines) {
382
+ function sliceAndTrimEndOf(str, startPtr, endPtr) {
377
383
  let value = str.slice(startPtr, endPtr);
378
384
  let commentIdx = value.indexOf("#");
379
385
  if (commentIdx > -1) {
380
386
  skipComment(str, commentIdx);
381
387
  value = value.slice(0, commentIdx);
382
388
  }
383
- let trimmed = value.trimEnd();
384
- if (!allowNewLines) {
385
- let newlineIdx = value.indexOf("\n", trimmed.length);
386
- if (newlineIdx > -1) {
387
- throw new TomlError("newlines are not allowed in inline tables", {
388
- toml: str,
389
- ptr: startPtr + newlineIdx
390
- });
391
- }
392
- }
393
- return [trimmed, commentIdx];
389
+ return [value.trimEnd(), commentIdx];
394
390
  }
395
391
  function extractValue(str, ptr, end, depth, integersAsBigInt) {
396
392
  if (depth === 0) {
@@ -402,24 +398,25 @@ function extractValue(str, ptr, end, depth, integersAsBigInt) {
402
398
  let c = str[ptr];
403
399
  if (c === "[" || c === "{") {
404
400
  let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
405
- let newPtr = end ? skipUntil(str, endPtr2, ",", end) : endPtr2;
406
- if (endPtr2 - newPtr && end === "}") {
407
- let nextNewLine = indexOfNewline(str, endPtr2, newPtr);
408
- if (nextNewLine > -1) {
409
- throw new TomlError("newlines are not allowed in inline tables", {
401
+ if (end) {
402
+ endPtr2 = skipVoid(str, endPtr2);
403
+ if (str[endPtr2] === ",")
404
+ endPtr2++;
405
+ else if (str[endPtr2] !== end) {
406
+ throw new TomlError("expected comma or end of structure", {
410
407
  toml: str,
411
- ptr: nextNewLine
408
+ ptr: endPtr2
412
409
  });
413
410
  }
414
411
  }
415
- return [value, newPtr];
412
+ return [value, endPtr2];
416
413
  }
417
414
  let endPtr;
418
415
  if (c === '"' || c === "'") {
419
416
  endPtr = getStringEnd(str, ptr);
420
417
  let parsed = parseString(str, ptr, endPtr);
421
418
  if (end) {
422
- endPtr = skipVoid(str, endPtr, end !== "]");
419
+ endPtr = skipVoid(str, endPtr);
423
420
  if (str[endPtr] && str[endPtr] !== "," && str[endPtr] !== end && str[endPtr] !== "\n" && str[endPtr] !== "\r") {
424
421
  throw new TomlError("unexpected character encountered", {
425
422
  toml: str,
@@ -431,7 +428,7 @@ function extractValue(str, ptr, end, depth, integersAsBigInt) {
431
428
  return [parsed, endPtr];
432
429
  }
433
430
  endPtr = skipUntil(str, ptr, ",", end);
434
- let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","), end === "]");
431
+ let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","));
435
432
  if (!slice[0]) {
436
433
  throw new TomlError("incomplete key-value declaration: no value specified", {
437
434
  toml: str,
@@ -521,17 +518,16 @@ function parseInlineTable(str, ptr, depth, integersAsBigInt) {
521
518
  let res = {};
522
519
  let seen = /* @__PURE__ */ new Set();
523
520
  let c;
524
- let comma = 0;
525
521
  ptr++;
526
522
  while ((c = str[ptr++]) !== "}" && c) {
527
- let err = { toml: str, ptr: ptr - 1 };
528
- if (c === "\n") {
529
- throw new TomlError("newlines are not allowed in inline tables", err);
530
- } else if (c === "#") {
531
- throw new TomlError("inline tables cannot contain comments", err);
532
- } else if (c === ",") {
533
- throw new TomlError("expected key-value, found comma", err);
534
- } else if (c !== " " && c !== " ") {
523
+ if (c === ",") {
524
+ throw new TomlError("expected value, found comma", {
525
+ toml: str,
526
+ ptr: ptr - 1
527
+ });
528
+ } else if (c === "#")
529
+ ptr = skipComment(str, ptr);
530
+ else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
535
531
  let k;
536
532
  let t = res;
537
533
  let hasOwn = false;
@@ -560,15 +556,8 @@ function parseInlineTable(str, ptr, depth, integersAsBigInt) {
560
556
  seen.add(value);
561
557
  t[k] = value;
562
558
  ptr = valueEndPtr;
563
- comma = str[ptr - 1] === "," ? ptr - 1 : 0;
564
559
  }
565
560
  }
566
- if (comma) {
567
- throw new TomlError("trailing commas are not allowed in inline tables", {
568
- toml: str,
569
- ptr: comma
570
- });
571
- }
572
561
  if (!c) {
573
562
  throw new TomlError("unfinished table encountered", {
574
563
  toml: str,
package/dist/primitive.js CHANGED
@@ -31,13 +31,14 @@ import { TomlError } from './error.js';
31
31
  let INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
32
32
  let FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
33
33
  let LEADING_ZERO = /^[+-]?0[0-9_]/;
34
- let ESCAPE_REGEX = /^[0-9a-f]{4,8}$/i;
34
+ let ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
35
35
  let ESC_MAP = {
36
36
  b: '\b',
37
37
  t: '\t',
38
38
  n: '\n',
39
39
  f: '\f',
40
40
  r: '\r',
41
+ e: '\x1b',
41
42
  '"': '"',
42
43
  '\\': '\\',
43
44
  };
@@ -73,9 +74,9 @@ export function parseString(str, ptr = 0, endPtr = str.length) {
73
74
  }
74
75
  if (isEscape) {
75
76
  isEscape = false;
76
- if (c === 'u' || c === 'U') {
77
+ if (c === 'x' || c === 'u' || c === 'U') {
77
78
  // Unicode escape
78
- let code = str.slice(ptr, (ptr += (c === 'u' ? 4 : 8)));
79
+ let code = str.slice(ptr, (ptr += (c === 'x' ? 2 : c === 'u' ? 4 : 8)));
79
80
  if (!ESCAPE_REGEX.test(code)) {
80
81
  throw new TomlError('invalid unicode escape', {
81
82
  toml: str,
package/dist/struct.js CHANGED
@@ -106,20 +106,17 @@ export function parseInlineTable(str, ptr, depth, integersAsBigInt) {
106
106
  let res = {};
107
107
  let seen = new Set();
108
108
  let c;
109
- let comma = 0;
110
109
  ptr++;
111
110
  while ((c = str[ptr++]) !== '}' && c) {
112
- let err = { toml: str, ptr: ptr - 1 };
113
- if (c === '\n') {
114
- throw new TomlError('newlines are not allowed in inline tables', err);
115
- }
116
- else if (c === '#') {
117
- throw new TomlError('inline tables cannot contain comments', err);
118
- }
119
- else if (c === ',') {
120
- throw new TomlError('expected key-value, found comma', err);
111
+ if (c === ',') {
112
+ throw new TomlError('expected value, found comma', {
113
+ toml: str,
114
+ ptr: ptr - 1,
115
+ });
121
116
  }
122
- else if (c !== ' ' && c !== '\t') {
117
+ else if (c === '#')
118
+ ptr = skipComment(str, ptr);
119
+ else if (c !== ' ' && c !== '\t' && c !== '\n' && c !== '\r') {
123
120
  let k;
124
121
  let t = res;
125
122
  let hasOwn = false;
@@ -148,15 +145,8 @@ export function parseInlineTable(str, ptr, depth, integersAsBigInt) {
148
145
  seen.add(value);
149
146
  t[k] = value;
150
147
  ptr = valueEndPtr;
151
- comma = str[ptr - 1] === ',' ? ptr - 1 : 0;
152
148
  }
153
149
  }
154
- if (comma) {
155
- throw new TomlError('trailing commas are not allowed in inline tables', {
156
- toml: str,
157
- ptr: comma,
158
- });
159
- }
160
150
  if (!c) {
161
151
  throw new TomlError('unfinished table encountered', {
162
152
  toml: str,
package/dist/util.js CHANGED
@@ -56,11 +56,16 @@ export function skipComment(str, ptr) {
56
56
  }
57
57
  export function skipVoid(str, ptr, banNewLines, banComments) {
58
58
  let c;
59
- while ((c = str[ptr]) === ' ' || c === '\t' || (!banNewLines && (c === '\n' || c === '\r' && str[ptr + 1] === '\n')))
60
- ptr++;
61
- return banComments || c !== '#'
62
- ? ptr
63
- : skipVoid(str, skipComment(str, ptr), banNewLines);
59
+ while (1) {
60
+ while ((c = str[ptr]) === ' ' || c === '\t' || (!banNewLines && (c === '\n' || c === '\r' && str[ptr + 1] === '\n')))
61
+ ptr++;
62
+ // Tucking the return statement here would save 5 characters >:)
63
+ // But TypeScript fails to detect there is no way to exit the loop so it complains about the lack of final return
64
+ if (banComments || c !== '#')
65
+ break;
66
+ ptr = skipComment(str, ptr);
67
+ }
68
+ return ptr;
64
69
  }
65
70
  export function skipUntil(str, ptr, sep, end, banNewLines = false) {
66
71
  if (!end) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "smol-toml",
3
3
  "license": "BSD-3-Clause",
4
- "version": "1.5.2",
4
+ "version": "1.6.1",
5
5
  "description": "A small, fast, and correct TOML parser/serializer",
6
6
  "author": "Cynthia <cyyynthia@borkenware.com>",
7
7
  "repository": "github:squirrelchat/smol-toml",
@@ -19,15 +19,15 @@
19
19
  "devDependencies": {
20
20
  "@iarna/toml": "3.0.0",
21
21
  "@ltd/j-toml": "^1.38.0",
22
- "@tsconfig/node-lts": "^22.0.3",
22
+ "@tsconfig/node-lts": "^24.0.0",
23
23
  "@tsconfig/strictest": "^2.0.8",
24
- "@types/node": "^24.10.1",
25
- "@vitest/ui": "^4.0.8",
26
- "esbuild": "^0.27.0",
24
+ "@types/node": "^25.5.0",
25
+ "@vitest/ui": "^4.1.1",
26
+ "esbuild": "^0.27.4",
27
27
  "fast-toml": "^0.5.4",
28
28
  "pin-github-action": "^3.4.0",
29
29
  "typescript": "^5.9.3",
30
- "vitest": "^4.0.8"
30
+ "vitest": "^4.1.1"
31
31
  },
32
32
  "main": "./dist/index.cjs",
33
33
  "module": "./dist/index.js",