1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
//! Pins BOTH geometry arms of C++ `errorExpected`
//! (JSParserImpl.cpp:201-225) at the unit level, independent of whether
//! `hermesc` is present. `sema_differential.rs`'s `parse-error.js` (S1 task
//! 2) already exercises this end to end against the real oracle; this test
//! exists so the behavior stays pinned even in environments without a
//! `cmake-build-asan/bin/hermesc` (e.g. a plain `cargo test`).
//!
//! `var 1x;` reproduces the exact bug found while wiring up S1 task 2's
//! error-epilogue parity: `parseVariableDeclaration`'s
//! `errorExpected(identifier, "in declaration", "declaration started
//! here", declLoc)` call (JSParserImpl.cpp:1244-1250) has `declLoc` (the
//! `var` keyword's start) on the SAME source line as the error token
//! (`1`), so C++ renders ONE combined-range diagnostic: the caret sits at
//! the error token but the underline stretches back to `declLoc`
//! (`~~~~^`). The Rust port previously dropped `declLoc` entirely and
//! rendered only the current token's own range (` ^~`) — this test
//! pins the fixed, byte-identical-to-hermesc output.
use Context;
use JSParserImpl;
use ;
use CollectingHandler;
use SourceErrorManager;
use render_diagnostic;
/// Parse `src` (which must fail) as `name` and return every diagnostic it
/// produced, rendered exactly as the driver would print it.
/// Like `render_parse_errors`, but with Flow syntax enabled (`-parse-flow`
/// on both `ctx.set_parse_flow` and `ctx.set_parse_flow_ambiguous`, matching
/// how `tools/src/bin/ast_dump.rs` wires up `--parse-flow`).
/// The same-line arm (cpp:212-219): `whatLoc` (the `(`) and the error token
/// (`;`) share a line, so C++ emits ONE diagnostic whose underline is
/// `combineIntoRange(whatLoc, errorLoc)` — tildes from the `(` up to the
/// caret. Verified byte-for-byte against `hermesc -dump-sema`.
/// The different-line arm (cpp:220-225): `whatLoc` (the `try`) is on an
/// earlier line than the error token (`xyz`), so C++ emits a bare
/// point-caret error — NOT the error token's own range — followed by a
/// separate `note` carrying `what` at `whatLoc`. Verified byte-for-byte
/// against `hermesc -dump-sema`.
/// The Flow generic-arrow "no `=>` follows" diagnostic (cpp:6468-6477):
/// `errorExpected(equalgreater, "in generic arrow function", "start of
/// function", typeParams->getStartLoc())`. `typeParams` (the `<T>`) and the
/// error token (`foobar`) share line 1, so this is the same-line arm: ONE
/// combined-range diagnostic, tildes from `<` through one past `foobar`'s
/// start.
///
/// ORACLE-FREE BY NECESSITY, not preference: `hermesc -dump-ast
/// -dump-source-location=both -parse-flow` on this exact input exits 2 with
/// EMPTY stdout/stderr (verified directly) — C++ reaches this call while
/// still inside the `CollectMessagesRAII` scope opened for the type-param
/// speculative retry (JSParserImpl.cpp:6292; `parseAssignmentExpression`,
/// the flow-typed-arrow-head backtrack), and that scope's destructor
/// discards every message on this path (only the FIRST attempt's success
/// path, cpp:6306-6309, calls `setDiscardMessages(false)` — the retry
/// itself never does, even when it succeeds). A corpus/differential file
/// compares actual hermesc stderr byte-for-byte, so it cannot pin a
/// rendering hermesc itself never produces.
///
/// The second message below ("type parameters must be used...", the
/// pre-existing port of cpp:6329-6330) is real, deterministic output of the
/// current parser, not test noise: both messages fire while the Rust port's
/// own `begin_collecting`/`end_collecting` pairing around the retry
/// (`expressions.rs`, `parse_assignment_expression`'s `run_level` closure,
/// ~line 442-461) is already closed by the time either one is emitted — the
/// Rust port ends+discards the FIRST attempt's collection scope before the
/// retry starts, rather than keeping ONE collection scope open across the
/// whole speculative block the way C++'s single RAII object does. So,
/// unlike C++, neither message here is ever collected/discardable in the
/// Rust port — both reach the handler directly. That ordering gap is a
/// separate, pre-existing bug (predates this call site's geometry
/// restoration); this test pins today's real rendering and documents the
/// gap rather than silently masking it. Fixing it is a
/// `begin_collecting`/`end_collecting` restructure (keep one collection
/// scope open across the whole retry, mirroring the single C++
/// `CollectMessagesRAII`), not a geometry change — tracked here pending a
/// dedicated backlog entry.
/// `parseBindingElement`'s no-identifier branch (JSParserImpl.cpp:1374-1376):
/// `error(tok_->getStartLoc(), "identifier, '{' or '[' expected in binding
/// pattern")` is the POINT overload — a bare caret, not the current token's
/// underlined range. Verified byte-for-byte against `hermesc -dump-ast`.
/// The labeled-`FunctionDeclaration` check (JSParserImpl.cpp:1653-1655):
/// `error(optFunc.getValue()->getSourceRange().Start, ...)` is the POINT
/// overload (a `.Start` member access on a range, not the range itself) —
/// a bare caret, not an underline over the whole labeled declaration.
/// Verified byte-for-byte against `hermesc -dump-ast`.
/// The post-assignment-expression check (JSParserImpl.cpp:6535-6536):
/// `error(tok_->getStartLoc(), "unexpected token after assignment
/// expression")` is the POINT overload — a bare caret, not the current
/// token's underlined range. Verified byte-for-byte against
/// `hermesc -dump-ast`.
/// "invalid destructuring target" (`reparseObjectAssignmentPattern`,
/// JSParserImpl.cpp:6095-6098): the underline is `SourceErrorManager::
/// combineIntoRange(propNode->getStartLoc(), propNode->_key->getStartLoc())`
/// — from the property's own start through ONE PAST the key's start (`+1`,
/// header:601-607), i.e. `get b` (5 chars: `g`,`e`,`t`,` `,`b`), not `get `
/// (4 chars, the off-by-one this test guards against: an earlier version of
/// this port hand-inlined `combineIntoRange` and dropped the `+1`, ending
/// the range AT the key's start instead of one past it). Verified
/// byte-for-byte against `hermesc -dump-ast`.
/// "location of optional chain" note (the tagged-template-in-optional-chain
/// check, JSParserImpl.cpp:3573-3576): `sm_.note(expr->getSourceRange(),
/// "location of optional chain")` passes the WHOLE range of `expr` (the
/// optional-chain member expression), not a bare point — an earlier version
/// of this port passed `None` for the range (collapsing it to a point,
/// `^` instead of `^~~~~~`), the reverse of every other fix in this file.
/// Verified byte-for-byte against `hermesc -dump-ast`.