@cdxgen/cdxgen 13.3.0 → 13.4.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.
Files changed (255) hide show
  1. package/README.md +9 -6
  2. package/bin/cdxgen.js +90 -26
  3. package/bin/evinse.js +18 -1
  4. package/bin/repl.js +86 -0
  5. package/bin/sign.js +45 -15
  6. package/bin/tracebom.js +5 -1
  7. package/bin/validate.js +84 -18
  8. package/bin/verify.js +155 -86
  9. package/data/helpers/init.gradle +46 -21
  10. package/data/remediations.json +61 -0
  11. package/data/rules/dependency-sources.yaml +67 -0
  12. package/data/rules/scala-evidence.yaml +115 -0
  13. package/lib/audit/index.js +38 -6
  14. package/lib/cli/index.js +113 -54
  15. package/lib/cli/jsBom.js +50 -29
  16. package/lib/cli/jvmBom.js +665 -138
  17. package/lib/cli/managedBom.js +320 -200
  18. package/lib/cli/nativeBom.js +139 -30
  19. package/lib/core/activity.js +12 -0
  20. package/lib/core/env.js +17 -9
  21. package/lib/core/fs.js +3 -2
  22. package/lib/core/httpClient.js +159 -24
  23. package/lib/core/pluginRun.js +184 -0
  24. package/lib/core/processTree.js +100 -0
  25. package/lib/core/runState.js +39 -0
  26. package/lib/core/toml.js +72 -0
  27. package/lib/ecosystems/asarutils.js +4 -2
  28. package/lib/ecosystems/bunutils.js +5 -7
  29. package/lib/ecosystems/cargoCache.js +231 -8
  30. package/lib/ecosystems/cmakeResolver.js +10 -21
  31. package/lib/ecosystems/cppBuildContext.js +631 -0
  32. package/lib/ecosystems/cppEvidence.js +337 -22
  33. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  34. package/lib/ecosystems/denoutils.js +93 -66
  35. package/lib/ecosystems/ecosystems.js +1967 -788
  36. package/lib/ecosystems/golem.js +28 -12
  37. package/lib/ecosystems/gradleutils.js +314 -30
  38. package/lib/ecosystems/jsEvidence.js +85 -24
  39. package/lib/ecosystems/kosi.js +21 -4
  40. package/lib/ecosystems/npmutils.js +64 -0
  41. package/lib/ecosystems/parsers-dotnet.js +567 -151
  42. package/lib/ecosystems/parsers-elm.js +46 -28
  43. package/lib/ecosystems/parsers-gleam.js +3 -4
  44. package/lib/ecosystems/parsers-go.js +324 -61
  45. package/lib/ecosystems/parsers-js.js +92 -26
  46. package/lib/ecosystems/parsers-julia.js +2 -3
  47. package/lib/ecosystems/parsers-jvm.js +204 -12
  48. package/lib/ecosystems/parsers-misc.js +182 -55
  49. package/lib/ecosystems/parsers-python.js +141 -10
  50. package/lib/ecosystems/parsers-rust.js +4 -4
  51. package/lib/ecosystems/parsers-terraform.js +1573 -19
  52. package/lib/ecosystems/pythonutils.js +102 -6
  53. package/lib/ecosystems/remote/huggingface.js +3 -0
  54. package/lib/ecosystems/rubyutils.js +67 -17
  55. package/lib/ecosystems/rusi.js +22 -9
  56. package/lib/ecosystems/sbtutils.js +243 -187
  57. package/lib/ecosystems/scalacliutils.js +342 -0
  58. package/lib/ecosystems/scalasem.js +2618 -0
  59. package/lib/ecosystems/terraformRegistry.js +288 -0
  60. package/lib/ecosystems/utils.js +2 -0
  61. package/lib/ecosystems/vendoredCode.js +156 -0
  62. package/lib/evinser/evinser.js +578 -69
  63. package/lib/helpers/agenticToolutils.js +2 -2
  64. package/lib/helpers/bomSigner.js +576 -128
  65. package/lib/inventory/aiCollector.js +4 -1
  66. package/lib/inventory/analyzer.js +110 -127
  67. package/lib/inventory/atomUtils.js +171 -48
  68. package/lib/inventory/auditCategories.js +7 -0
  69. package/lib/inventory/buildToolRateLimit.js +144 -0
  70. package/lib/inventory/cacheCommands.js +2 -1
  71. package/lib/inventory/cmakeBuildDirs.js +254 -0
  72. package/lib/inventory/deps.js +501 -124
  73. package/lib/inventory/dosai.js +248 -44
  74. package/lib/inventory/dosaiParsers.js +310 -15
  75. package/lib/inventory/dynamic.js +52 -6
  76. package/lib/inventory/evidenceUtils.js +154 -109
  77. package/lib/inventory/fetchBatch.js +574 -38
  78. package/lib/inventory/fetchDiskCache.js +597 -0
  79. package/lib/inventory/forgeEnricher.js +14 -5
  80. package/lib/inventory/jvmLocalRepos.js +917 -0
  81. package/lib/inventory/mcpConfigParser.js +2 -3
  82. package/lib/inventory/parentSelection.js +146 -0
  83. package/lib/inventory/rustFormulationParser.js +3 -4
  84. package/lib/inventory/scalaCoords.js +153 -0
  85. package/lib/inventory/source.js +8 -8
  86. package/lib/inventory/spdx.js +101 -0
  87. package/lib/inventory/traceRunner.js +88 -7
  88. package/lib/inventory/tracedCrypto.js +294 -0
  89. package/lib/managers/docker.js +33 -32
  90. package/lib/managers/oci.js +24 -2
  91. package/lib/parsers/cmakeCache.js +69 -0
  92. package/lib/parsers/cmakeLists.js +16 -2
  93. package/lib/parsers/cmakePresets.js +400 -0
  94. package/lib/parsers/compileCommands.js +557 -0
  95. package/lib/parsers/strictJson.js +322 -0
  96. package/lib/parsers/terraformConfig.js +1073 -0
  97. package/lib/parsers/terraformSource.js +652 -0
  98. package/lib/server/server.js +23 -0
  99. package/lib/stages/postgen/introspection/markers.js +1 -1
  100. package/lib/stages/postgen/postgen.js +15 -1
  101. package/lib/stages/postgen/specVersionCompat.js +32 -0
  102. package/lib/validator/index.js +25 -10
  103. package/lib/validator/reporters/json.js +1 -0
  104. package/package.json +21 -21
  105. package/types/lib/audit/index.d.ts.map +1 -1
  106. package/types/lib/cli/index.d.ts.map +1 -1
  107. package/types/lib/cli/jsBom.d.ts.map +1 -1
  108. package/types/lib/cli/jvmBom.d.ts +24 -1
  109. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  110. package/types/lib/cli/managedBom.d.ts +7 -4
  111. package/types/lib/cli/managedBom.d.ts.map +1 -1
  112. package/types/lib/cli/nativeBom.d.ts +3 -1
  113. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  114. package/types/lib/core/activity.d.ts.map +1 -1
  115. package/types/lib/core/env.d.ts +9 -0
  116. package/types/lib/core/env.d.ts.map +1 -1
  117. package/types/lib/core/fs.d.ts.map +1 -1
  118. package/types/lib/core/httpClient.d.ts +13 -8
  119. package/types/lib/core/httpClient.d.ts.map +1 -1
  120. package/types/lib/core/pluginRun.d.ts +97 -0
  121. package/types/lib/core/pluginRun.d.ts.map +1 -0
  122. package/types/lib/core/processTree.d.ts +36 -0
  123. package/types/lib/core/processTree.d.ts.map +1 -0
  124. package/types/lib/core/runState.d.ts +29 -0
  125. package/types/lib/core/runState.d.ts.map +1 -0
  126. package/types/lib/core/toml.d.ts +20 -0
  127. package/types/lib/core/toml.d.ts.map +1 -0
  128. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  129. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  130. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  131. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  132. package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
  133. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  134. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  135. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  136. package/types/lib/ecosystems/cppEvidence.d.ts +18 -2
  137. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  139. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  140. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  141. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  143. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  144. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  145. package/types/lib/ecosystems/gradleutils.d.ts +50 -0
  146. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  147. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  148. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  149. package/types/lib/ecosystems/npmutils.d.ts +15 -0
  150. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  151. package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
  152. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  153. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  154. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  155. package/types/lib/ecosystems/parsers-go.d.ts +44 -2
  156. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  157. package/types/lib/ecosystems/parsers-js.d.ts +6 -1
  158. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  159. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  160. package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
  161. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  162. package/types/lib/ecosystems/parsers-misc.d.ts +3 -1
  163. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  164. package/types/lib/ecosystems/parsers-python.d.ts +53 -3
  165. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  166. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  167. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  168. package/types/lib/ecosystems/pythonutils.d.ts +27 -2
  169. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  170. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  171. package/types/lib/ecosystems/rubyutils.d.ts +21 -1
  172. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  173. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  174. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  175. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  176. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  177. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  178. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  179. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  180. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  181. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  182. package/types/lib/ecosystems/utils.d.ts +2 -2
  183. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  184. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  185. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  186. package/types/lib/evinser/evinser.d.ts +75 -8
  187. package/types/lib/evinser/evinser.d.ts.map +1 -1
  188. package/types/lib/helpers/bomSigner.d.ts +80 -4
  189. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  190. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  191. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  192. package/types/lib/inventory/atomUtils.d.ts +82 -10
  193. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  194. package/types/lib/inventory/auditCategories.d.ts +3 -0
  195. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  196. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  197. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  198. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  199. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  200. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  201. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  202. package/types/lib/inventory/deps.d.ts +59 -1
  203. package/types/lib/inventory/deps.d.ts.map +1 -1
  204. package/types/lib/inventory/dosai.d.ts +57 -8
  205. package/types/lib/inventory/dosai.d.ts.map +1 -1
  206. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  207. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  208. package/types/lib/inventory/dynamic.d.ts +10 -0
  209. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  210. package/types/lib/inventory/evidenceUtils.d.ts +19 -4
  211. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  212. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  213. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  214. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  215. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  216. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  217. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  218. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  219. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  220. package/types/lib/inventory/parentSelection.d.ts +34 -0
  221. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  222. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  223. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  224. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  225. package/types/lib/inventory/source.d.ts.map +1 -1
  226. package/types/lib/inventory/spdx.d.ts +9 -0
  227. package/types/lib/inventory/spdx.d.ts.map +1 -1
  228. package/types/lib/inventory/traceRunner.d.ts +12 -0
  229. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  230. package/types/lib/inventory/tracedCrypto.d.ts +42 -0
  231. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  232. package/types/lib/managers/docker.d.ts.map +1 -1
  233. package/types/lib/managers/oci.d.ts +3 -1
  234. package/types/lib/managers/oci.d.ts.map +1 -1
  235. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  236. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  237. package/types/lib/parsers/cmakeLists.d.ts +2 -1
  238. package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
  239. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  240. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  241. package/types/lib/parsers/compileCommands.d.ts +115 -0
  242. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  243. package/types/lib/parsers/strictJson.d.ts +46 -0
  244. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  245. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  246. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  247. package/types/lib/parsers/terraformSource.d.ts +66 -0
  248. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  249. package/types/lib/server/server.d.ts.map +1 -1
  250. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  252. package/types/lib/validator/index.d.ts +8 -0
  253. package/types/lib/validator/index.d.ts.map +1 -1
  254. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  255. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Strict JSON reading for signed documents.
3
+ *
4
+ * JSF signs the RFC 8785 (JCS) form of a document, and JCS is defined over
5
+ * I-JSON (RFC 7493) input. `JSON.parse` and lenient UTF-8 decoding accept
6
+ * things I-JSON rules out, and each lets two parsers read different values
7
+ * from the same signed bytes:
8
+ *
9
+ * - a member name repeated within one object, where `JSON.parse` keeps the last
10
+ * value and other parsers keep the first or refuse the document;
11
+ * - a number an IEEE 754 double cannot hold exactly, which JavaScript rounds
12
+ * while arbitrary-precision parsers keep every digit;
13
+ * - bytes that are not UTF-8, which lenient decoders turn into U+FFFD while
14
+ * others refuse them or read another encoding;
15
+ * - an unpaired UTF-16 surrogate escape, which some parsers keep and others
16
+ * replace.
17
+ *
18
+ * {@link parseStrictJson} rejects all of them, so the values a verifier checks
19
+ * are the values every consumer of the document reads.
20
+ */
21
+
22
+ /**
23
+ * Raised when JSON text is well-formed but breaks one of the rules above.
24
+ */
25
+ export class StrictJsonError extends Error {
26
+ /**
27
+ * @param {string} message - Description of the problem
28
+ * @param {string} pointer - RFC 6901 JSON Pointer to the offending member or value
29
+ */
30
+ constructor(message, pointer) {
31
+ super(message);
32
+ this.name = "StrictJsonError";
33
+ this.pointer = pointer;
34
+ }
35
+ }
36
+
37
+ const QUOTE = 0x22;
38
+ const BACKSLASH = 0x5c;
39
+ const OPEN_OBJECT = 0x7b;
40
+ const CLOSE_OBJECT = 0x7d;
41
+ const OPEN_ARRAY = 0x5b;
42
+ const CLOSE_ARRAY = 0x5d;
43
+ const COMMA = 0x2c;
44
+ const MINUS = 0x2d;
45
+ const PLUS = 0x2b;
46
+ const DOT = 0x2e;
47
+ const DIGIT_0 = 0x30;
48
+ const DIGIT_9 = 0x39;
49
+ const LOWER_E = 0x65;
50
+ const UPPER_E = 0x45;
51
+ const LOWER_U = 0x75;
52
+
53
+ // Integers with at most 15 digits are below 2^53, so a double holds them exactly.
54
+ const EXACT_INTEGER_DIGITS = 15;
55
+ const MAX_SHOWN_NUMBER_LENGTH = 40;
56
+
57
+ function isDigit(code) {
58
+ return code >= DIGIT_0 && code <= DIGIT_9;
59
+ }
60
+
61
+ function escapePointerToken(token) {
62
+ return token.replaceAll("~", "~0").replaceAll("/", "~1");
63
+ }
64
+
65
+ function pointerOf(frames, depth) {
66
+ let pointer = "";
67
+ for (let i = 0; i < depth; i++) {
68
+ const frame = frames[i];
69
+ pointer += `/${frame.object ? escapePointerToken(frame.key) : frame.index}`;
70
+ }
71
+ return pointer;
72
+ }
73
+
74
+ function showNumber(token) {
75
+ return token.length > MAX_SHOWN_NUMBER_LENGTH
76
+ ? `${token.slice(0, MAX_SHOWN_NUMBER_LENGTH)}…`
77
+ : token;
78
+ }
79
+
80
+ /**
81
+ * Reduces a JSON or ECMAScript number literal to its exact decimal value as a
82
+ * significand without leading or trailing zeros and a power-of-ten exponent.
83
+ * The sign is ignored: a finite non-zero double keeps the sign of its literal,
84
+ * and zero compares equal to negative zero.
85
+ */
86
+ function decimalValue(literal) {
87
+ let i = literal.charCodeAt(0) === MINUS ? 1 : 0;
88
+ let digits = "";
89
+ let fractionLength = 0;
90
+ while (i < literal.length && isDigit(literal.charCodeAt(i))) {
91
+ digits += literal[i++];
92
+ }
93
+ if (literal.charCodeAt(i) === DOT) {
94
+ i++;
95
+ while (i < literal.length && isDigit(literal.charCodeAt(i))) {
96
+ digits += literal[i++];
97
+ fractionLength++;
98
+ }
99
+ }
100
+ let exponent = 0;
101
+ const marker = literal.charCodeAt(i);
102
+ if (marker === LOWER_E || marker === UPPER_E) {
103
+ exponent = Number(literal.slice(i + 1));
104
+ }
105
+ let start = 0;
106
+ while (start < digits.length && digits.charCodeAt(start) === DIGIT_0) {
107
+ start++;
108
+ }
109
+ let end = digits.length;
110
+ while (end > start && digits.charCodeAt(end - 1) === DIGIT_0) {
111
+ end--;
112
+ }
113
+ if (start === end) {
114
+ return { significand: "", exponent: 0 };
115
+ }
116
+ return {
117
+ significand: digits.slice(start, end),
118
+ exponent: exponent - fractionLength + (digits.length - end),
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Returns a description of why a JSON number token does not denote exactly the
124
+ * double that `JSON.parse` produces for it, or undefined when it does.
125
+ */
126
+ function numberProblem(token) {
127
+ const negative = token.charCodeAt(0) === MINUS;
128
+ const digitCount = token.length - (negative ? 1 : 0);
129
+ if (digitCount <= EXACT_INTEGER_DIGITS) {
130
+ let integer = true;
131
+ for (let i = negative ? 1 : 0; i < token.length; i++) {
132
+ if (!isDigit(token.charCodeAt(i))) {
133
+ integer = false;
134
+ break;
135
+ }
136
+ }
137
+ if (integer) {
138
+ return undefined;
139
+ }
140
+ }
141
+ const value = Number(token);
142
+ if (!Number.isFinite(value)) {
143
+ return "is outside the range of an IEEE 754 double";
144
+ }
145
+ const written = decimalValue(token);
146
+ const parsed = decimalValue(String(value));
147
+ if (
148
+ written.significand === parsed.significand &&
149
+ (written.significand === "" || written.exponent === parsed.exponent)
150
+ ) {
151
+ return undefined;
152
+ }
153
+ return `cannot be held exactly by an IEEE 754 double; JSON.parse reads it as ${String(value)}`;
154
+ }
155
+
156
+ function isHighSurrogate(unit) {
157
+ return unit >= 0xd800 && unit <= 0xdbff;
158
+ }
159
+
160
+ function isLowSurrogate(unit) {
161
+ return unit >= 0xdc00 && unit <= 0xdfff;
162
+ }
163
+
164
+ /**
165
+ * Finds the end of the string token starting at `start`, and whether it holds
166
+ * escapes or a UTF-16 surrogate, written raw or as `\u` escape, without its
167
+ * other half.
168
+ */
169
+ function stringEnd(text, start) {
170
+ let i = start + 1;
171
+ let escaped = false;
172
+ let unpaired = false;
173
+ let pendingHigh = false;
174
+ for (;;) {
175
+ const code = text.charCodeAt(i);
176
+ if (code === QUOTE) {
177
+ return { end: i + 1, escaped, unpaired: unpaired || pendingHigh };
178
+ }
179
+ let unit = code;
180
+ let width = 1;
181
+ if (code === BACKSLASH) {
182
+ escaped = true;
183
+ if (text.charCodeAt(i + 1) === LOWER_U) {
184
+ unit = Number.parseInt(text.slice(i + 2, i + 6), 16);
185
+ width = 6;
186
+ } else {
187
+ unit = 0;
188
+ width = 2;
189
+ }
190
+ }
191
+ const low = isLowSurrogate(unit);
192
+ if (pendingHigh !== low) {
193
+ unpaired = true;
194
+ }
195
+ pendingHigh = isHighSurrogate(unit);
196
+ i += width;
197
+ }
198
+ }
199
+
200
+ function numberEnd(text, start) {
201
+ let i = start;
202
+ while (i < text.length) {
203
+ const code = text.charCodeAt(i);
204
+ if (
205
+ isDigit(code) ||
206
+ code === MINUS ||
207
+ code === PLUS ||
208
+ code === DOT ||
209
+ code === LOWER_E ||
210
+ code === UPPER_E
211
+ ) {
212
+ i++;
213
+ } else {
214
+ break;
215
+ }
216
+ }
217
+ return i;
218
+ }
219
+
220
+ /**
221
+ * Walks well-formed JSON text without recursion and throws a StrictJsonError
222
+ * at the first repeated member name, inexact number, or unpaired surrogate.
223
+ */
224
+ function checkStrictJson(text) {
225
+ const frames = [];
226
+ let i = 0;
227
+ while (i < text.length) {
228
+ const code = text.charCodeAt(i);
229
+ if (code === OPEN_OBJECT || code === OPEN_ARRAY) {
230
+ frames.push({
231
+ object: code === OPEN_OBJECT,
232
+ names: code === OPEN_OBJECT ? new Set() : undefined,
233
+ key: undefined,
234
+ index: 0,
235
+ expectName: code === OPEN_OBJECT,
236
+ });
237
+ i++;
238
+ } else if (code === CLOSE_OBJECT || code === CLOSE_ARRAY) {
239
+ frames.pop();
240
+ i++;
241
+ } else if (code === COMMA) {
242
+ const top = frames.at(-1);
243
+ if (top.object) {
244
+ top.expectName = true;
245
+ } else {
246
+ top.index++;
247
+ }
248
+ i++;
249
+ } else if (code === QUOTE) {
250
+ const { end, escaped, unpaired } = stringEnd(text, i);
251
+ const top = frames.at(-1);
252
+ const isName = Boolean(top?.object && top.expectName);
253
+ if (unpaired) {
254
+ const pointer = isName
255
+ ? `${pointerOf(frames, frames.length - 1)}/${escapePointerToken(JSON.parse(text.slice(i, end)))}`
256
+ : pointerOf(frames, frames.length);
257
+ throw new StrictJsonError(
258
+ `The ${isName ? "member name" : "string"} at ${pointer || "the document root"} holds an unpaired UTF-16 surrogate, which JSON parsers keep, replace, or reject differently.`,
259
+ pointer,
260
+ );
261
+ }
262
+ if (isName) {
263
+ const name = escaped
264
+ ? JSON.parse(text.slice(i, end))
265
+ : text.slice(i + 1, end - 1);
266
+ if (top.names.has(name)) {
267
+ throw new StrictJsonError(
268
+ `Duplicate member name at ${pointerOf(frames, frames.length - 1)}/${escapePointerToken(name)}. JSON parsers disagree on which of the repeated values the document holds.`,
269
+ `${pointerOf(frames, frames.length - 1)}/${escapePointerToken(name)}`,
270
+ );
271
+ }
272
+ top.names.add(name);
273
+ top.key = name;
274
+ top.expectName = false;
275
+ }
276
+ i = end;
277
+ } else if (code === MINUS || isDigit(code)) {
278
+ const end = numberEnd(text, i);
279
+ const token = text.slice(i, end);
280
+ const problem = numberProblem(token);
281
+ if (problem) {
282
+ const pointer = pointerOf(frames, frames.length);
283
+ throw new StrictJsonError(
284
+ `The number ${showNumber(token)} at ${pointer || "the document root"} ${problem}.`,
285
+ pointer,
286
+ );
287
+ }
288
+ i = end;
289
+ } else {
290
+ i++;
291
+ }
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Parses JSON the way `JSON.parse` does, but refuses bytes that are not UTF-8,
297
+ * repeated member names within an object, numbers that an IEEE 754 double
298
+ * cannot hold exactly, and unpaired UTF-16 surrogates.
299
+ *
300
+ * Pass the raw bytes of a document rather than text decoded elsewhere, so that
301
+ * invalid UTF-8 is detected instead of being replaced by U+FFFD.
302
+ *
303
+ * @param {string|Uint8Array} input - JSON text, or its UTF-8 bytes
304
+ * @returns {any} - The parsed value
305
+ * @throws {SyntaxError} When the text is not JSON
306
+ * @throws {StrictJsonError} When the input breaks one of the rules above
307
+ */
308
+ export function parseStrictJson(input) {
309
+ let text = input;
310
+ if (typeof input !== "string") {
311
+ try {
312
+ text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
313
+ input,
314
+ );
315
+ } catch {
316
+ throw new StrictJsonError("The document is not valid UTF-8.", "");
317
+ }
318
+ }
319
+ const value = JSON.parse(text);
320
+ checkStrictJson(text);
321
+ return value;
322
+ }