You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
JavaScript ignores FieldDeclaration.IsStatic/IsConstant: static class fields become per-instance, and a file-scope field is an undeclared assignment that throws in an ES module #104
JavaScriptGenerator.GenerateFieldDeclaration (Coder/Languages/JavaScriptGenerator.cs:273-287) writes name = value; and nothing else. It never reads IsStatic or IsConstant, and it writes the same text wherever the field stands:
Inside a class, a static or constant field becomes an ordinary instance field. Counter.MAX is undefined, and every instance gets its own copy of what was meant to be shared state. The VariableDeclaration-as-field path in the same file (GenerateField, :391-409) already writes static for a constant, with a comment explaining why. The FieldDeclaration path, which is the node the AST documents for fields, does not.
At file or namespace scope, the declaration has no const/let, so it is a bare assignment to an undeclared name. The generator writes imports as ES module imports (SpellImport at :107 gives import "...";), and modules are always strict. In a module that assignment throws ReferenceError at load. In a sloppy-mode script it silently creates a global instead.
The other targets all honour these flags for the same AST:
Target
File-scope constant
Static field
C++
inline constexpr int Limit = 10;
inline static int created = 0;
C#
—
public static int created / static readonly
Go
const Limit int = 10
var CounterCreated int = 0
Rust's equivalent gap is tracked separately in #81; this issue is JavaScript only.
Run as an ES module (node gen.mjs), node 22 fails on line 1:
Limit = 10;
^
ReferenceError: Limit is not defined
Run as a classic script, it loads, but console.log(Counter.MAX, globalThis.Limit) prints undefined 10. The static field is missing from the class, and the file-scope constant leaked onto the global object.
Constant tables and shared counters are among the main things the AST is documented to generate (see FieldDeclaration.IsStatic/IsConstant in CLAUDE.md). In JavaScript they either crash the module at load, or quietly change from one shared value to one value per instance.
Suggested fix / acceptance criteria
Inside a class body, write static when IsStatic || IsConstant, matching GenerateField for VariableDeclaration.
Outside a class, meaning directly in a SourceFile or NamespaceDeclaration, write const for IsConstant and let otherwise, as GenerateVariableDeclaration already does for locals.
Add tests covering: a static field in a class, a constant field in a class, and a constant field at file scope. Ideally run the output with node when it is available and check Counter.MAX === 99.
Priority: High. A file-scope field makes a generated ES module throw ReferenceError at load. Static and constant class fields quietly become per-instance, so Counter.MAX is undefined. Both are silent miscompilations of documented AST features.
Area / suggested assignee: JavaScript generator, JavaScriptGenerator.GenerateFieldDeclaration. Owner: @matt-edmondson
Activity
matt-edmondson commentedon Sep 27, 2026
Triage
ReferenceErrorat load. Static and constant class fields quietly become per-instance, soCounter.MAXisundefined. Both are silent miscompilations of documented AST features.JavaScriptGenerator.GenerateFieldDeclaration. Owner: @matt-edmondsonnew Derived()throws ReferenceError #103 (missingsuper()) is also a JavaScript class-emission bug and would fit in the same PR. Open PRs Write a Rust type's constant fields as associated constants #94 and Read .inf, -.inf and .nan back as double literals #95 cover Rust and literal parsing, not this.staticwhenIsStatic || IsConstantinside a class andconst/letat file or namespace scope, with tests, ideally run under node.Generated by Claude Code
[patch] Honour IsStatic and IsConstant on JavaScript fields