What happens
JavaScriptGenerator.MemberName prefixes # onto a member declared Visibility.Private:
// JavaScriptGenerator.cs:185-186
private static string MemberName(string name, Visibility visibility) =>
visibility == Visibility.Private ? $"#{name}" : name;
It is applied to field declarations (GenerateField) and method declarations (GenerateMethod). Nothing applies it anywhere else, and VariableReference is emitted verbatim by the shared path every generator uses:
// LanguageGeneratorBase.cs:109-111
case VariableReference varRef:
code.Write(varRef.Name);
return true;
So the declaration is renamed and every reference to it is not.
Failure scenario
A ClassDeclaration with a private field count and a method whose body assigns to a VariableReference("count") generates:
class Counter {
#count = 0;
increment() {
count = count + 1; // <- refers to nothing; #count is what was declared
}
}
Under a module's implicit strict mode this is a ReferenceError at run time. The generated file is not usable, and the failure is silent at generation time.
The codebase already argues this is wrong
PythonGenerator's own class remark gives exactly this reason for not applying Python's leading-underscore convention:
The conventions Python does have for both — a leading underscore for a non-public member, an upper-case name for a constant — are spellings of the identifier rather than modifiers on the declaration, and renaming a declaration here would leave every VariableReference to it naming something that no longer exists.
JavaScriptGenerator's own remark concedes the same premise — "# is a part of the name in JavaScript rather than a modifier in front of it" — and then renames anyway. The two generators reached opposite conclusions from an identical observation, and Python's is the one that produces working code.
Test coverage gap
VisibilityTests pins the declaration side only — JavaScript_SpellsAPrivateMethodWithAHash asserts #recompute() {, and the field test asserts #x = 0;. No test generates a class whose body references a private member, which is why this passes. Python_DropsVisibilityRatherThanRenaming sits directly beneath them asserting the opposite policy.
Options
- Match Python — drop the prefix. Smallest change, consistent with the reasoning already written down, and costs only that JavaScript output does not express privacy. Given the generator already drops the types the AST carries, this is defensible.
- Rename references too. Requires the JavaScript generator to override the
VariableReference case with knowledge of the enclosing class's private members, and to prefix the member side of any member access. Genuinely more faithful output, meaningfully more work, and #name is only legal inside the class body — a reference from outside would have to be caught and reported rather than emitted.
Either resolution should also add a test that generates a private member and a reference to it, so the two halves cannot drift apart again.
What happens
JavaScriptGenerator.MemberNameprefixes#onto a member declaredVisibility.Private:It is applied to field declarations (
GenerateField) and method declarations (GenerateMethod). Nothing applies it anywhere else, andVariableReferenceis emitted verbatim by the shared path every generator uses:So the declaration is renamed and every reference to it is not.
Failure scenario
A
ClassDeclarationwith a private fieldcountand a method whose body assigns to aVariableReference("count")generates:Under a module's implicit strict mode this is a
ReferenceErrorat run time. The generated file is not usable, and the failure is silent at generation time.The codebase already argues this is wrong
PythonGenerator's own class remark gives exactly this reason for not applying Python's leading-underscore convention:JavaScriptGenerator's own remark concedes the same premise — "#is a part of the name in JavaScript rather than a modifier in front of it" — and then renames anyway. The two generators reached opposite conclusions from an identical observation, and Python's is the one that produces working code.Test coverage gap
VisibilityTestspins the declaration side only —JavaScript_SpellsAPrivateMethodWithAHashasserts#recompute() {, and the field test asserts#x = 0;. No test generates a class whose body references a private member, which is why this passes.Python_DropsVisibilityRatherThanRenamingsits directly beneath them asserting the opposite policy.Options
VariableReferencecase with knowledge of the enclosing class's private members, and to prefix the member side of any member access. Genuinely more faithful output, meaningfully more work, and#nameis only legal inside the class body — a reference from outside would have to be caught and reported rather than emitted.Either resolution should also add a test that generates a private member and a reference to it, so the two halves cannot drift apart again.