@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.11

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 (276) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +181 -341
  5. package/dist/src/augmentations.js +106 -13
  6. package/dist/src/automatic-types.js +10 -29
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +2 -3
  10. package/dist/src/config.js +6 -82
  11. package/dist/src/configuration-files.js +28 -176
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +1 -1
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +73 -3
  20. package/dist/src/conventions.js +36 -66
  21. package/dist/src/dependencies.js +103 -40
  22. package/dist/src/edges.js +38 -122
  23. package/dist/src/element-access.js +4 -6
  24. package/dist/src/entry-point-gaps.js +2 -8
  25. package/dist/src/entry-points.js +11 -27
  26. package/dist/src/enum-group.js +3 -10
  27. package/dist/src/erased-stores.js +4 -6
  28. package/dist/src/exempt-classes.js +1 -63
  29. package/dist/src/exempt.js +21 -40
  30. package/dist/src/failure.js +36 -0
  31. package/dist/src/file-facts.js +2 -25
  32. package/dist/src/findings/completion.js +139 -72
  33. package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
  34. package/dist/src/findings/emitters.js +5 -5
  35. package/dist/src/findings/interfaces.js +4 -5
  36. package/dist/src/findings/intra-function.js +13 -10
  37. package/dist/src/findings/non-code-artifacts.js +7 -8
  38. package/dist/src/findings/reads-and-writes.js +16 -13
  39. package/dist/src/findings/self-check.js +10 -12
  40. package/dist/src/findings/unused-declarations.js +41 -32
  41. package/dist/src/findings/visibility-narrowing.js +19 -16
  42. package/dist/src/findings-pass.js +8 -15
  43. package/dist/src/framework-lifecycle.js +7 -7
  44. package/dist/src/fs/document.js +52 -0
  45. package/dist/src/fs/manifest.js +72 -0
  46. package/dist/src/fs/tree.js +88 -0
  47. package/dist/src/generated-file.js +7 -4
  48. package/dist/src/git-ignore.js +39 -26
  49. package/dist/src/graph.js +5 -3
  50. package/dist/src/html-entries.js +6 -33
  51. package/dist/src/in-operands.js +2 -1
  52. package/dist/src/index.js +1 -1
  53. package/dist/src/injection-container.js +1 -1
  54. package/dist/src/intra-function-parts.js +63 -43
  55. package/dist/src/inventory.js +2 -21
  56. package/dist/src/javascript-file.js +33 -0
  57. package/dist/src/json-document.js +198 -67
  58. package/dist/src/keyof-comparisons.js +4 -6
  59. package/dist/src/kinds.js +3 -366
  60. package/dist/src/load/classify.js +270 -0
  61. package/dist/src/load/diagnostics.js +80 -0
  62. package/dist/src/load/discover.js +277 -0
  63. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  64. package/dist/src/load/expected-module.js +136 -0
  65. package/dist/src/load/load.js +27 -0
  66. package/dist/src/load/module-lookup.js +37 -0
  67. package/dist/src/load/plan.js +313 -0
  68. package/dist/src/load/specifiers.js +115 -0
  69. package/dist/src/load/synthesize.js +140 -0
  70. package/dist/src/load/test-globals.js +207 -0
  71. package/dist/src/load/test-joins.js +82 -0
  72. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  73. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +89 -66
  74. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +23 -65
  75. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  76. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  77. package/dist/src/manifest.js +49 -54
  78. package/dist/src/matrix.js +76 -4
  79. package/dist/src/mdx-entries.js +3 -26
  80. package/dist/src/outside-tests.js +1 -1
  81. package/dist/src/partial.js +2 -1
  82. package/dist/src/paths.js +26 -15
  83. package/dist/src/position.js +2 -1
  84. package/dist/src/print.js +11 -79
  85. package/dist/src/product.js +13 -0
  86. package/dist/src/query.js +1 -0
  87. package/dist/src/ref.js +1 -0
  88. package/dist/src/references.js +48 -30
  89. package/dist/src/report-schema.js +51 -0
  90. package/dist/src/report.js +57 -41
  91. package/dist/src/reporters/annotations.js +15 -8
  92. package/dist/src/reporters/sarif.js +16 -10
  93. package/dist/src/reporters/template.js +4 -19
  94. package/dist/src/resolve.js +245 -729
  95. package/dist/src/roots.js +273 -87
  96. package/dist/src/run.js +66 -77
  97. package/dist/src/schema.js +320 -343
  98. package/dist/src/scope.js +38 -115
  99. package/dist/src/serialization-contract.js +595 -21
  100. package/dist/src/session.js +34 -29
  101. package/dist/src/source-files.js +1 -1
  102. package/dist/src/suppress-file.js +25 -28
  103. package/dist/src/suppress-inline.js +3 -2
  104. package/dist/src/suppress.js +33 -51
  105. package/dist/src/sweep.js +7 -9
  106. package/dist/src/symbol-keys.js +3 -5
  107. package/dist/src/template-field.js +9 -19
  108. package/dist/src/test-support.js +8 -11
  109. package/dist/src/text/compare.js +26 -0
  110. package/dist/src/text/glob.js +194 -0
  111. package/dist/src/text/named-files.js +54 -0
  112. package/dist/src/text/shell.js +159 -0
  113. package/dist/src/text/yaml.js +393 -0
  114. package/dist/src/type-errors.js +11 -232
  115. package/dist/src/unseen-loads.js +100 -0
  116. package/dist/src/unwrap.js +23 -0
  117. package/dist/src/usage.js +10 -0
  118. package/dist/src/verbs/analyze.js +166 -126
  119. package/dist/src/verbs/describe.js +9 -16
  120. package/dist/src/verbs/explain.js +10 -7
  121. package/dist/src/verbs/print-config.js +2 -1
  122. package/dist/src/verbs/print-projects.js +6 -4
  123. package/dist/src/verbs/print-retained.js +4 -2
  124. package/dist/src/verbs/print-roots.js +4 -2
  125. package/dist/src/verbs/verb.js +1 -5
  126. package/dist/src/verbs/version.js +14 -3
  127. package/dist/src/workflow-steps.js +62 -64
  128. package/jsr.json +1 -1
  129. package/package.json +5 -4
  130. package/src/analysis.ts +245 -502
  131. package/src/augmentations.ts +135 -17
  132. package/src/automatic-types.ts +11 -29
  133. package/src/build-scripts.ts +543 -0
  134. package/src/component-layer.ts +4 -2
  135. package/src/components.ts +2 -4
  136. package/src/config.ts +31 -302
  137. package/src/configuration-files.ts +34 -179
  138. package/src/configured-declarations.ts +18 -23
  139. package/src/conformance-record.ts +1 -1
  140. package/src/consumers.ts +6 -57
  141. package/src/contextual-uses.ts +64 -12
  142. package/src/contract/config.generated.ts +818 -0
  143. package/src/contract/contract.generated.ts +1308 -0
  144. package/src/contract/report.generated.ts +1237 -0
  145. package/src/convention-rows.ts +85 -3
  146. package/src/conventions.ts +46 -84
  147. package/src/dependencies.ts +118 -46
  148. package/src/edges.ts +41 -140
  149. package/src/element-access.ts +4 -7
  150. package/src/entry-point-gaps.ts +3 -9
  151. package/src/entry-points.ts +12 -38
  152. package/src/enum-group.ts +2 -11
  153. package/src/erased-stores.ts +3 -7
  154. package/src/exempt-classes.ts +1 -75
  155. package/src/exempt.ts +36 -50
  156. package/src/failure.ts +63 -0
  157. package/src/file-facts.ts +9 -24
  158. package/src/finding.ts +14 -9
  159. package/src/findings/boundary.ts +2 -0
  160. package/src/findings/completion.ts +181 -84
  161. package/src/findings/dependencies-and-module-machinery.ts +4 -7
  162. package/src/findings/emitter.ts +8 -9
  163. package/src/findings/emitters.ts +7 -7
  164. package/src/findings/interfaces.ts +4 -6
  165. package/src/findings/intra-function.ts +13 -11
  166. package/src/findings/non-code-artifacts.ts +9 -11
  167. package/src/findings/reads-and-writes.ts +20 -16
  168. package/src/findings/self-check.ts +11 -17
  169. package/src/findings/unused-declarations.ts +45 -32
  170. package/src/findings/visibility-narrowing.ts +20 -17
  171. package/src/findings-pass.ts +10 -19
  172. package/src/framework-lifecycle.ts +7 -7
  173. package/src/fs/document.ts +70 -0
  174. package/src/fs/manifest.ts +92 -0
  175. package/src/fs/tree.ts +110 -0
  176. package/src/generated-file.ts +7 -4
  177. package/src/git-ignore.ts +50 -26
  178. package/src/graph.ts +5 -3
  179. package/src/host.ts +12 -5
  180. package/src/html-entries.ts +6 -33
  181. package/src/in-operands.ts +2 -1
  182. package/src/index.ts +1 -1
  183. package/src/injection-container.ts +1 -1
  184. package/src/intra-function-parts.ts +73 -44
  185. package/src/inventory.ts +2 -20
  186. package/src/javascript-file.ts +37 -0
  187. package/src/json-document.ts +241 -76
  188. package/src/keyof-comparisons.ts +3 -7
  189. package/src/kinds.ts +5 -413
  190. package/src/load/classify.ts +397 -0
  191. package/src/load/diagnostics.ts +144 -0
  192. package/src/load/discover.ts +455 -0
  193. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  194. package/src/load/expected-module.ts +188 -0
  195. package/src/load/load.ts +73 -0
  196. package/src/load/module-lookup.ts +50 -0
  197. package/src/load/plan.ts +504 -0
  198. package/src/load/specifiers.ts +168 -0
  199. package/src/load/synthesize.ts +211 -0
  200. package/src/load/test-globals.ts +251 -0
  201. package/src/load/test-joins.ts +98 -0
  202. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  203. package/src/{workspace-programs.ts → load/workspace-programs.ts} +128 -85
  204. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +33 -76
  205. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  206. package/src/{workspace.ts → load/workspace.ts} +90 -134
  207. package/src/manifest.ts +81 -75
  208. package/src/matrix.ts +122 -4
  209. package/src/mdx-entries.ts +3 -26
  210. package/src/outside-tests.ts +1 -1
  211. package/src/partial.ts +3 -2
  212. package/src/paths.ts +27 -15
  213. package/src/position.ts +2 -1
  214. package/src/print.ts +12 -92
  215. package/src/product.ts +18 -0
  216. package/src/query.ts +11 -0
  217. package/src/ref.ts +1 -0
  218. package/src/references.ts +52 -31
  219. package/src/report-schema.ts +68 -0
  220. package/src/report.ts +101 -224
  221. package/src/reporters/annotations.ts +22 -11
  222. package/src/reporters/json.ts +1 -1
  223. package/src/reporters/reporter.ts +2 -4
  224. package/src/reporters/reporters.ts +1 -1
  225. package/src/reporters/sarif.ts +29 -16
  226. package/src/reporters/template.ts +4 -20
  227. package/src/reporters/text.ts +3 -3
  228. package/src/resolve.ts +283 -1114
  229. package/src/roots.ts +339 -104
  230. package/src/run.ts +75 -91
  231. package/src/schema.ts +391 -388
  232. package/src/scope.ts +46 -138
  233. package/src/serialization-contract.ts +763 -22
  234. package/src/session.ts +70 -54
  235. package/src/source-files.ts +1 -1
  236. package/src/suppress-file.ts +24 -33
  237. package/src/suppress-inline.ts +2 -2
  238. package/src/suppress.ts +43 -58
  239. package/src/sweep.ts +23 -9
  240. package/src/symbol-keys.ts +2 -5
  241. package/src/template-field.ts +15 -25
  242. package/src/test-support.ts +17 -13
  243. package/src/text/compare.ts +27 -0
  244. package/src/text/glob.ts +210 -0
  245. package/src/text/named-files.inputs.json +1522 -0
  246. package/src/text/named-files.ts +68 -0
  247. package/src/text/shell.ts +161 -0
  248. package/src/text/yaml.ts +423 -0
  249. package/src/type-errors.ts +18 -288
  250. package/src/unseen-loads.ts +134 -0
  251. package/src/unwrap.ts +34 -0
  252. package/src/usage.ts +10 -0
  253. package/src/verbs/analyze.ts +224 -162
  254. package/src/verbs/describe.ts +14 -15
  255. package/src/verbs/explain.ts +10 -11
  256. package/src/verbs/print-config.ts +2 -1
  257. package/src/verbs/print-projects.ts +6 -4
  258. package/src/verbs/print-retained.ts +4 -2
  259. package/src/verbs/print-roots.ts +4 -2
  260. package/src/verbs/verb.ts +2 -8
  261. package/src/verbs/version.ts +15 -3
  262. package/src/workflow-steps.ts +74 -64
  263. package/dist/src/base-url.js +0 -18
  264. package/dist/src/discover.js +0 -468
  265. package/dist/src/glob.js +0 -65
  266. package/dist/src/pnpm-workspace.js +0 -120
  267. package/dist/src/setup-failure.js +0 -23
  268. package/dist/src/test-joins.js +0 -101
  269. package/dist/src/version.js +0 -7
  270. package/src/base-url.ts +0 -24
  271. package/src/discover.ts +0 -653
  272. package/src/glob.ts +0 -67
  273. package/src/pnpm-workspace.ts +0 -135
  274. package/src/setup-failure.ts +0 -39
  275. package/src/test-joins.ts +0 -120
  276. package/src/version.ts +0 -7
@@ -3,7 +3,7 @@ import { dirnamePath, joinPath, relativePath } from "./paths.js";
3
3
  import { byPosition, positionKey, renderPosition, writtenLength, } from "./position.js";
4
4
  import { computedComponent, nameComponent, renderRef } from "./ref.js";
5
5
  import { isAnswered, UNANSWERED } from "./query.js";
6
- const MANIFEST = "package.json";
6
+ import { manifestAt } from "./fs/manifest.js";
7
7
  /**
8
8
  * Resolves each file's package scope: the name of the nearest manifest at or above
9
9
  * its directory up to the target root, and the file's path below that manifest.
@@ -17,26 +17,7 @@ const MANIFEST = "package.json";
17
17
  export function packageScope(host, root) {
18
18
  const named = new Map();
19
19
  const manifestName = (dir) => {
20
- let text;
21
- try {
22
- text = host.readFile(joinPath(root, dir, MANIFEST));
23
- }
24
- catch {
25
- return undefined;
26
- }
27
- let value;
28
- try {
29
- value = JSON.parse(text);
30
- }
31
- catch {
32
- // A manifest this program cannot read names no package, which is the same
33
- // answer as an absent one: the file is then scoped by the target root.
34
- return undefined;
35
- }
36
- if (typeof value !== "object" || value === null) {
37
- return undefined;
38
- }
39
- const name = value["name"];
20
+ const name = manifestAt(host, joinPath(root, dir))?.["name"];
40
21
  return typeof name === "string" && name !== "" ? name : undefined;
41
22
  };
42
23
  const nearest = (dir) => {
@@ -0,0 +1,33 @@
1
+ export const JAVASCRIPT_FILE = /\.(?:js|jsx|mjs|cjs)$/u;
2
+ /** A minified file by its name; any other is one by its size and its mean line length. */
3
+ const MINIFIED_NAME = /\.min\.(?:js|mjs|cjs)$/u;
4
+ const MINIFIED_BYTES = 10_240;
5
+ const MINIFIED_LINE = 500;
6
+ /** Whether one name is a minified file's, which decides it without its text. */
7
+ export function minifiedByName(fileName) {
8
+ return MINIFIED_NAME.test(fileName);
9
+ }
10
+ /** Whether one JavaScript file is minified: by its name, or by its text's size and mean line length. */
11
+ export function minified(fileName, text) {
12
+ if (!JAVASCRIPT_FILE.test(fileName)) {
13
+ return false;
14
+ }
15
+ if (minifiedByName(fileName)) {
16
+ return true;
17
+ }
18
+ // A UTF-16 unit encodes to at most three UTF-8 bytes, so a shorter text is below the size.
19
+ if (text.length * 3 < MINIFIED_BYTES) {
20
+ return false;
21
+ }
22
+ const lines = text.split(/\r?\n/u).length;
23
+ return utf8Bytes(text) >= MINIFIED_BYTES && text.length / lines > MINIFIED_LINE;
24
+ }
25
+ /** The length of a text in UTF-8, a lone surrogate counting as the three bytes of U+FFFD. */
26
+ function utf8Bytes(text) {
27
+ let bytes = 0;
28
+ for (const character of text) {
29
+ const point = character.codePointAt(0) ?? 0;
30
+ bytes += point < 0x80 ? 1 : point < 0x800 ? 2 : point < 0x10000 ? 3 : 4;
31
+ }
32
+ return bytes;
33
+ }
@@ -1,106 +1,228 @@
1
1
  /**
2
- * A read of a strict JSON document beside its parse: what a parse into a value cannot
3
- * report, the offset of each record of one array and a member written twice.
2
+ * A text that is not one strict JSON document: a syntax error, data after the value, a
3
+ * member one object writes twice, or nesting past {@link MAX_DEPTH}. `place` is the
4
+ * value the fault is in, followed by the member written twice.
4
5
  */
5
- /** The token walk over a document `JSON.parse` accepted, so every token is well formed. */
6
- class Walk {
7
- starts = [];
8
- /** The member written twice, where one is, as the offset of its name. */
9
- twice;
10
- text;
11
- array;
6
+ export class StrictJSONError extends Error {
7
+ offset;
8
+ place;
9
+ constructor(reason, offset, place) {
10
+ super(reason);
11
+ this.name = "StrictJSONError";
12
+ this.offset = offset;
13
+ this.place = place;
14
+ }
15
+ }
16
+ /**
17
+ * How deep values may nest. A document this analyzer reads nests a few levels, and a
18
+ * bound turns a document built to exhaust the stack into a refusal.
19
+ */
20
+ const MAX_DEPTH = 1000;
21
+ const SPACE = /[ \t\n\r]*/uy;
22
+ const STRING = /"(?:[ !#$%&'()*+,\-./0-9:;<=>?@A-Z[\]-\u{10FFFF}]|\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4}))*"/uy;
23
+ const NUMBER = /-?(?:0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/uy;
24
+ const LITERALS = new Map([
25
+ ["true", true],
26
+ ["false", false],
27
+ ["null", null],
28
+ ]);
29
+ /** One member name spelled as a JSON Pointer token. */
30
+ const escaped = (name) => name.replaceAll("~", "~0").replaceAll("/", "~1");
31
+ /** The member names and indexes a JSON Pointer holds, unescaped. */
32
+ export function pointerSegments(pointer) {
33
+ return pointer === ""
34
+ ? []
35
+ : pointer
36
+ .slice(1)
37
+ .split("/")
38
+ .map((token) => token.replaceAll("~1", "/").replaceAll("~0", "~"));
39
+ }
40
+ /** The JSON Pointer of a place written as segments. */
41
+ export function pointerOf(segments) {
42
+ return segments.map((segment) => `/${escaped(segment)}`).join("");
43
+ }
44
+ /**
45
+ * The place a JSON Pointer names inside `value`, followed by `member` where it is not
46
+ * empty: a pointer's text does not say whether a segment of digits is an index, so the
47
+ * value it points into does.
48
+ */
49
+ export function placeIn(value, pointer, member = "") {
50
+ const place = [];
51
+ let held = value;
52
+ for (const segment of pointerSegments(pointer)) {
53
+ if (Array.isArray(held)) {
54
+ place.push(Number(segment));
55
+ held = held[Number(segment)];
56
+ }
57
+ else {
58
+ place.push(segment);
59
+ held = isRecord(held) ? held[segment] : undefined;
60
+ }
61
+ }
62
+ return member === "" ? place : [...place, member];
63
+ }
64
+ /**
65
+ * A place as a refusal names it: member names joined by full stops and an index in
66
+ * square brackets, as `ts.serializers[0].returns`.
67
+ */
68
+ export function dotted(place) {
69
+ let path = "";
70
+ for (const segment of place) {
71
+ path +=
72
+ typeof segment === "number" ? `[${String(segment)}]` : path === "" ? segment : `.${segment}`;
73
+ }
74
+ return path;
75
+ }
76
+ class Reader {
12
77
  at = 0;
13
- constructor(text, array) {
78
+ path = [];
79
+ fractions = new Map();
80
+ starts = new Map();
81
+ text;
82
+ records;
83
+ constructor(text, records) {
14
84
  this.text = text;
15
- this.array = array;
16
- }
17
- run() {
18
- this.value("document");
85
+ this.records = records;
19
86
  }
20
- skip() {
21
- while (/[ \t\r\n]/u.test(this.text[this.at] ?? "")) {
22
- this.at += 1;
87
+ read() {
88
+ const value = this.value(0);
89
+ this.match(SPACE);
90
+ if (this.at < this.text.length) {
91
+ this.fail("data follows the document's value");
23
92
  }
93
+ return value;
24
94
  }
25
- string() {
26
- const start = this.at;
27
- this.at += 1;
28
- while (this.text[this.at] !== '"') {
29
- this.at += this.text[this.at] === "\\" ? 2 : 1;
95
+ fail(reason, offset = this.at, member = "") {
96
+ throw new StrictJSONError(reason, offset, member === "" ? [...this.path] : [...this.path, member]);
97
+ }
98
+ match(pattern) {
99
+ pattern.lastIndex = this.at;
100
+ const found = pattern.exec(this.text)?.[0];
101
+ this.at = found === undefined ? this.at : pattern.lastIndex;
102
+ return found;
103
+ }
104
+ expect(char, what) {
105
+ this.match(SPACE);
106
+ if (this.text[this.at] !== char) {
107
+ this.fail(`${what} is expected`);
30
108
  }
31
109
  this.at += 1;
32
- return JSON.parse(this.text.slice(start, this.at));
33
110
  }
34
- value(role) {
35
- this.skip();
36
- if (role === "record") {
37
- this.starts.push(this.at);
111
+ value(depth) {
112
+ this.match(SPACE);
113
+ if (depth > MAX_DEPTH) {
114
+ this.fail(`values nest deeper than ${String(MAX_DEPTH)} levels`);
38
115
  }
39
116
  const char = this.text[this.at];
40
117
  if (char === "{") {
41
- this.object(role);
118
+ return this.object(depth);
42
119
  }
43
- else if (char === "[") {
44
- this.list(role === "array" ? "record" : "other");
120
+ if (char === "[") {
121
+ return this.array(depth);
45
122
  }
46
- else if (char === '"') {
47
- this.string();
123
+ const string = this.match(STRING);
124
+ if (string !== undefined) {
125
+ return JSON.parse(string);
48
126
  }
49
- else {
50
- while (this.at < this.text.length && !/[,\]} \t\r\n]/u.test(this.text[this.at] ?? "")) {
51
- this.at += 1;
127
+ NUMBER.lastIndex = this.at;
128
+ const number = NUMBER.exec(this.text);
129
+ if (number !== null) {
130
+ this.at = NUMBER.lastIndex;
131
+ if (number[1] !== undefined || number[2] !== undefined) {
132
+ this.fractions.set(pointerOf(this.path.map(String)), number[0]);
52
133
  }
134
+ return Number(number[0]);
53
135
  }
136
+ for (const [literal, held] of LITERALS) {
137
+ if (this.text.startsWith(literal, this.at)) {
138
+ this.at += literal.length;
139
+ return held;
140
+ }
141
+ }
142
+ return this.fail(char === undefined ? "the document ends where a value is expected" : "a value is expected");
54
143
  }
55
- object(role) {
56
- const seen = new Set();
144
+ object(depth) {
145
+ const held = {};
57
146
  this.at += 1;
147
+ this.match(SPACE);
148
+ if (this.text[this.at] === "}") {
149
+ this.at += 1;
150
+ return held;
151
+ }
58
152
  for (;;) {
59
- this.skip();
60
- if (this.text[this.at] === "}") {
61
- this.at += 1;
62
- return;
153
+ this.match(SPACE);
154
+ const offset = this.at;
155
+ const spelled = this.match(STRING);
156
+ if (spelled === undefined) {
157
+ return this.fail("a member name is expected");
63
158
  }
64
- if (this.text[this.at] === ",") {
65
- this.at += 1;
66
- this.skip();
159
+ const name = JSON.parse(spelled);
160
+ if (Object.hasOwn(held, name)) {
161
+ this.fail(`the member ${JSON.stringify(name)} is written twice, so neither value is chosen`, offset, name);
67
162
  }
68
- const offset = this.at;
69
- const name = this.string();
70
- if (seen.has(name) && this.twice === undefined) {
71
- this.twice = { name, offset };
163
+ this.expect(":", "a colon after the member name");
164
+ if (depth === 1 && this.path[0] === this.records) {
165
+ this.match(SPACE);
166
+ this.starts.set(name, this.at);
72
167
  }
73
- seen.add(name);
74
- this.skip();
168
+ this.path.push(name);
169
+ Object.defineProperty(held, name, {
170
+ value: this.value(depth + 1),
171
+ enumerable: true,
172
+ writable: true,
173
+ configurable: true,
174
+ });
175
+ this.path.pop();
176
+ this.match(SPACE);
177
+ const next = this.text[this.at];
75
178
  this.at += 1;
76
- this.value(role === "document" && name === this.array ? "array" : "other");
179
+ if (next === "}") {
180
+ return held;
181
+ }
182
+ if (next !== ",") {
183
+ this.at -= 1;
184
+ this.fail("a comma or a closing brace is expected");
185
+ }
77
186
  }
78
187
  }
79
- list(element) {
188
+ array(depth) {
189
+ const held = [];
190
+ const records = depth === 1 && this.path[0] === this.records;
80
191
  this.at += 1;
192
+ this.match(SPACE);
193
+ if (this.text[this.at] === "]") {
194
+ this.at += 1;
195
+ return held;
196
+ }
81
197
  for (;;) {
82
- this.skip();
83
- if (this.text[this.at] === "]") {
84
- this.at += 1;
85
- return;
198
+ this.match(SPACE);
199
+ if (records) {
200
+ this.starts.set(held.length, this.at);
86
201
  }
87
- if (this.text[this.at] === ",") {
88
- this.at += 1;
202
+ this.path.push(held.length);
203
+ held.push(this.value(depth + 1));
204
+ this.path.pop();
205
+ this.match(SPACE);
206
+ const next = this.text[this.at];
207
+ this.at += 1;
208
+ if (next === "]") {
209
+ return held;
210
+ }
211
+ if (next !== ",") {
212
+ this.at -= 1;
213
+ this.fail("a comma or a closing bracket is expected");
89
214
  }
90
- this.value(element);
91
215
  }
92
216
  }
93
217
  }
94
218
  /**
95
- * Walks a document `JSON.parse` accepted, whose top-level member `array` holds the
96
- * records. A member one object writes twice reads as though it were written once, its
97
- * later value silently replacing the earlier one, so a document holding one is one a
98
- * reader names rather than reads.
219
+ * Reads `text` as one strict JSON document, or throws a {@link StrictJSONError}.
220
+ * `records` names the top-level member whose values' offsets the read keeps.
99
221
  */
100
- export function walkDocument(text, array) {
101
- const walk = new Walk(text, array);
102
- walk.run();
103
- return { starts: walk.starts, twice: walk.twice };
222
+ export function readStrict(text, records) {
223
+ const reader = new Reader(text, records);
224
+ const value = reader.read();
225
+ return { value, fractions: reader.fractions, starts: reader.starts };
104
226
  }
105
227
  /**
106
228
  * One offset of a document as a finding carries it: the line counted from one, and the
@@ -111,3 +233,12 @@ export function positionAt(text, file, offset) {
111
233
  const lineStart = before.lastIndexOf("\n") + 1;
112
234
  return { path: file, line: before.split("\n").length, column: offset - lineStart + 1 };
113
235
  }
236
+ /** A strict read's fault with the line and column it is at, as a refusal states it. */
237
+ export function locatedFault(text, error) {
238
+ const { line, column } = positionAt(text, "", error.offset);
239
+ return `line ${String(line)}, column ${String(column)}: ${error.message}`;
240
+ }
241
+ /** Whether a decoded JSON value is an object: not `null` and not an array. */
242
+ export function isRecord(value) {
243
+ return typeof value === "object" && value !== null && !Array.isArray(value);
244
+ }
@@ -4,11 +4,12 @@
4
4
  * that holds `keyof X`, the literal the other side has names the member of `X` under that
5
5
  * key, because deleting the member makes the comparison a type error.
6
6
  */
7
- import { isBinaryExpression, isCaseClause, isElementAccessExpression, isIdentifier, isParenthesizedExpression, isPropertyAccessExpression, isSwitchStatement, isTypeOperatorNode, SyntaxKind, } from "@typescript/native/ast";
7
+ import { isBinaryExpression, isCaseClause, isElementAccessExpression, isIdentifier, isPropertyAccessExpression, isSwitchStatement, isTypeOperatorNode, SyntaxKind, } from "@typescript/native/ast";
8
8
  import { SymbolFlags } from "@typescript/native/sync";
9
9
  import { nodeKey } from "./inventory.js";
10
10
  import { renderPosition } from "./position.js";
11
11
  import { UNANSWERED } from "./query.js";
12
+ import { parenthesesRemoved } from "./unwrap.js";
12
13
  /** The operators that compare their operands for equality. */
13
14
  const EQUALITIES = new Set([
14
15
  SyntaxKind.EqualsEqualsEqualsToken,
@@ -16,12 +17,9 @@ const EQUALITIES = new Set([
16
17
  SyntaxKind.EqualsEqualsToken,
17
18
  SyntaxKind.ExclamationEqualsToken,
18
19
  ]);
19
- function unwrapped(node) {
20
- return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
21
- }
22
20
  /** The name node an operand's symbol is read at: an identifier, or a property access's name. */
23
21
  function nameOf(node) {
24
- const at = unwrapped(node);
22
+ const at = parenthesesRemoved(node);
25
23
  if (isIdentifier(at)) {
26
24
  return at;
27
25
  }
@@ -96,7 +94,7 @@ export function keyofComparisonReferences(project, held, chains, root, testFiles
96
94
  }
97
95
  const named = comparisons.flatMap((one) => {
98
96
  const name = nameOf(one.keyed);
99
- return name === undefined || isElementAccessExpression(unwrapped(one.other))
97
+ return name === undefined || isElementAccessExpression(parenthesesRemoved(one.other))
100
98
  ? []
101
99
  : [{ ...one, name }];
102
100
  });