Parent: #904 Follow-up to: #930
Invalid modules accepted
Node v24.18.0 rejects each module with SyntaxError; current zig-js (f2bafeb3) parses the first one successfully in the standalone parser test root, proving the module early-error path accepts it.
| module | Node | zig-js |
|---|---|---|
let x; { var x; } | SyntaxError | accepts |
export let x; { var x; } | SyntaxError | accepts |
function f(){} { var f; } | SyntaxError | accepts |
import {x} from "m"; { var x; } | SyntaxError | accepts |
export default function f(){} { var f; } | SyntaxError | accepts |
Node accepts the function-boundary controls let x; function f(){ var x; }, export let x; export function f(){ var x; }, and import {x} from "m"; const f = function(){ var x; };.
Cause
checkModuleEarlyErrors collects the ModuleBody's top-level lexical names, but creates an empty LexicalScope before calling recurseScope over nested statements. A var inside a nested block therefore cannot see an import, lexical declaration, module-scoped function, or named default export that remains open in the module's var scope.
Script parsing does not have this gap: checkLexicalDupes opens the ScriptBody's lexical names before the same walk.
This was listed as an unexecuted reading in #930 because test262 --eval has no module mode. It is now executed against Node's vm.SourceTextModule and zig-js's parser directly.
Definition of done
- Open the already-collected module lexical names once in the shared scope before walking ModuleItems; do not re-collect subtrees.
- Preserve the fresh var-scope boundary for functions.
- Pin the invalid and valid cases above.
- Compare affected test262 module and declaration subtrees before/after with nonzero denominators and zero regressions.