diff --git a/Coder.Test/Ast/ConstantDeclarationTests.cs b/Coder.Test/Ast/ConstantDeclarationTests.cs
index b024ee7..9337898 100644
--- a/Coder.Test/Ast/ConstantDeclarationTests.cs
+++ b/Coder.Test/Ast/ConstantDeclarationTests.cs
@@ -100,10 +100,12 @@ public void JavaScript_WritesStaticForAMember()
}
///
- /// Tests that a private constant member is spelled with both, since the two are independent.
+ /// Tests that a private constant member is still spelled static, and under the name it was
+ /// declared with: constancy is a modifier JavaScript has, and visibility is one it does not, so
+ /// asking for the second leaves the first alone rather than renaming what it modifies.
///
[TestMethod]
- public void JavaScript_CombinesStaticWithAPrivateName()
+ public void JavaScript_WritesStaticOnAPrivateConstantWithoutRenamingIt()
{
ClassDeclaration declaration = new("Limits");
declaration.Members.Add(new VariableDeclaration("MAX", "int", Literal.Number(10))
@@ -112,7 +114,10 @@ public void JavaScript_CombinesStaticWithAPrivateName()
Visibility = Visibility.Private,
});
- StringAssert.Contains(new JavaScriptGenerator().Generate(declaration), "static #MAX = 10;", StringComparison.Ordinal);
+ string code = new JavaScriptGenerator().Generate(declaration);
+
+ Assert.Contains("static MAX = 10;", code, StringComparison.Ordinal);
+ Assert.DoesNotContain("#MAX", code, StringComparison.Ordinal);
}
///
diff --git a/Coder.Test/Ast/FunctionModifierTests.cs b/Coder.Test/Ast/FunctionModifierTests.cs
index dc18595..12d4b44 100644
--- a/Coder.Test/Ast/FunctionModifierTests.cs
+++ b/Coder.Test/Ast/FunctionModifierTests.cs
@@ -139,18 +139,20 @@ public void JavaScript_WritesStaticOnAMethod()
}
///
- /// A static private member keeps both spellings, which are separate parts of the same declaration.
+ /// A static private member keeps the one spelling JavaScript has for it. static is a
+ /// modifier in front of the name and survives; privacy is a # on the name itself, so
+ /// writing it would rename the method and leave every call to it naming the old one.
///
[TestMethod]
- public void JavaScript_WritesStaticAlongsideThePrivatePrefix()
+ public void JavaScript_WritesStaticOnAPrivateMethodWithoutRenamingIt()
{
ClassDeclaration declaration = SampleClass();
((FunctionDeclaration)declaration.Members[0]).Visibility = Visibility.Private;
- Assert.Contains(
- "static #distance(scale)",
- new JavaScriptGenerator().Generate(declaration),
- StringComparison.Ordinal);
+ string code = new JavaScriptGenerator().Generate(declaration);
+
+ Assert.Contains("static distance(scale)", code, StringComparison.Ordinal);
+ Assert.DoesNotContain("#distance", code, StringComparison.Ordinal);
}
///
diff --git a/Coder.Test/Ast/VisibilityTests.cs b/Coder.Test/Ast/VisibilityTests.cs
index f06a037..7d4dc1d 100644
--- a/Coder.Test/Ast/VisibilityTests.cs
+++ b/Coder.Test/Ast/VisibilityTests.cs
@@ -14,8 +14,9 @@ namespace ktsu.Coder.Test.Ast;
///
///
/// Visibility is the first thing the AST models that no two languages spell the same way — a C#
-/// keyword, a C++ access label, a JavaScript name prefix, and nothing at all in Python — so these
-/// cover each spelling rather than asserting one shape four times.
+/// keyword, a C++ access label, and nothing at all in Python or JavaScript, both of whose only
+/// convention is a spelling of the name itself and so would rename a declaration out from under
+/// every reference to it — so these cover each spelling rather than asserting one shape four times.
///
[TestClass]
public class VisibilityTests
@@ -144,32 +145,68 @@ public void Cpp_TreatsInternalAsPublic()
}
///
- /// Tests that JavaScript spells a private member with the # prefix its own private syntax
- /// uses, and leaves the others as ordinary members.
+ /// Tests that JavaScript drops visibility rather than renaming the declaration, since its private
+ /// syntax is a # on the name rather than a modifier in front of it.
///
[TestMethod]
- public void JavaScript_SpellsAPrivateMemberWithAHash()
+ public void JavaScript_DropsVisibilityRatherThanRenaming()
{
string code = new JavaScriptGenerator().Generate(SampleClass());
- StringAssert.Contains(code, "#x = 0;", StringComparison.Ordinal);
- StringAssert.Contains(code, "origin = 0;", StringComparison.Ordinal);
+ Assert.Contains("x = 0;", code, StringComparison.Ordinal);
+ Assert.Contains("origin = 0;", code, StringComparison.Ordinal);
- // protected has no JavaScript spelling, so the method is an ordinary one.
- StringAssert.Contains(code, "area() {", StringComparison.Ordinal);
+ // protected has no JavaScript spelling either, so the method is an ordinary one.
+ Assert.Contains("area() {", code, StringComparison.Ordinal);
+
+ Assert.DoesNotContain("#", code, StringComparison.Ordinal);
+ Assert.DoesNotContain("private", code, StringComparison.Ordinal);
}
///
- /// Tests that a private method is spelled with the prefix too, since JavaScript's # applies
- /// to any class member rather than to fields alone.
+ /// Tests that a private method is left alone too, since the prefix applies to any class member
+ /// rather than to fields alone and so would strand a call to one just as it strands a read.
///
[TestMethod]
- public void JavaScript_SpellsAPrivateMethodWithAHash()
+ public void JavaScript_DropsVisibilityOnAMethodToo()
{
ClassDeclaration declaration = new("Point");
declaration.Members.Add(new FunctionDeclaration("recompute") { ReturnType = "void", Visibility = Visibility.Private });
- StringAssert.Contains(new JavaScriptGenerator().Generate(declaration), "#recompute() {", StringComparison.Ordinal);
+ string code = new JavaScriptGenerator().Generate(declaration);
+
+ Assert.Contains("recompute() {", code, StringComparison.Ordinal);
+ Assert.DoesNotContain("#", code, StringComparison.Ordinal);
+ }
+
+ ///
+ /// Tests that a private member and the body that reads it come out naming the same identifier.
+ ///
+ ///
+ /// This is the half the declaration tests above cannot see. # is a part of the name, so
+ /// prefixing a declaration renames it, and nothing prefixes a —
+ /// the shared path writes one verbatim. A class whose method reads a private field would then
+ /// declare #x and read x, which resolves to nothing and throws a
+ /// ReferenceError under a module's implicit strict mode. Every name the body mentions has
+ /// to be one the class body declares, whichever way the generator spells it.
+ ///
+ [TestMethod]
+ public void JavaScript_ReferencesThePrivateMemberItDeclared()
+ {
+ ClassDeclaration declaration = new("Counter");
+ declaration.Members.Add(new VariableDeclaration("count", "int", Literal.Number(0)) { Visibility = Visibility.Private });
+
+ FunctionDeclaration increment = new("increment") { ReturnType = "void" };
+ increment.Body.Add(new ReturnStatement(new VariableReference("count")));
+ declaration.Members.Add(increment);
+
+ string code = new JavaScriptGenerator().Generate(declaration);
+
+ // The declared name and the referenced name are the same one, so the body reads what the
+ // class holds rather than an identifier nothing declares.
+ Assert.Contains("count = 0;", code, StringComparison.Ordinal);
+ Assert.Contains("return count;", code, StringComparison.Ordinal);
+ Assert.DoesNotContain("#count", code, StringComparison.Ordinal);
}
///
diff --git a/Coder/Languages/JavaScriptGenerator.cs b/Coder/Languages/JavaScriptGenerator.cs
index 5d993f8..a05b963 100644
--- a/Coder/Languages/JavaScriptGenerator.cs
+++ b/Coder/Languages/JavaScriptGenerator.cs
@@ -279,7 +279,7 @@ protected override void GenerateFieldDeclaration(FieldDeclaration field, CodeBlo
Ensure.NotNull(code);
GenerateDocumentation(field, code);
- code.Write(MemberName(field.Name ?? "unnamed", field.Visibility));
+ code.Write(field.Name ?? "unnamed");
if (field.InitialValue is not null)
{
@@ -317,9 +317,12 @@ protected override void GenerateFunctionDeclaration(FunctionDeclaration funcDecl
/// JavaScript's function keyword is a syntax error there. That is why the members are
/// emitted here rather than through .
///
- /// A private member is spelled with the # prefix, which is JavaScript's own private syntax
- /// and enforced by the runtime. The other three visibilities have no spelling: JavaScript draws
- /// the line at private, and a protected or internal member is an ordinary one.
+ /// No visibility is spelled, private included. JavaScript's # is a part of the name rather
+ /// than a modifier in front of it, so writing it renames the declaration, and nothing renames the
+ /// references: a is emitted verbatim by the shared path, leaving
+ /// a body that names an identifier the class no longer declares. That is the reason
+ /// drops its leading underscore and notes
+ /// the case of a name rather than changing it, and it is the same reason here.
///
///
protected override void GenerateClassDeclaration(ClassDeclaration classDecl, CodeBlocker code)
@@ -398,7 +401,7 @@ private void GenerateField(VariableDeclaration field, CodeBlocker code)
code.Write(StaticKeyword);
}
- code.Write(MemberName(field.Name, field.Visibility));
+ code.Write(field.Name);
if (field.InitialValue is not null)
{
@@ -438,7 +441,7 @@ private void GenerateMethod(FunctionDeclaration method, CodeBlocker code)
code.Write(method.Kind == FunctionKind.Constructor
? "constructor"
- : MemberName(method.Name ?? "unnamedMethod", method.Visibility));
+ : method.Name ?? "unnamedMethod");
code.Write("(");
GenerateParameterList(method.Parameters, code);
@@ -503,19 +506,6 @@ protected override void GenerateVariableDeclaration(VariableDeclaration varDecl,
EndStatement(code);
}
- ///
- /// Spells a class member's name for its visibility.
- ///
- /// The member's name in the AST.
- /// The visibility it was declared with.
- /// The name as the class body should spell it.
- ///
- /// # is a part of the name in JavaScript rather than a modifier in front of it, so private
- /// members are spelled here rather than by writing a keyword before the declaration.
- ///
- private static string MemberName(string name, Visibility visibility) =>
- visibility == Visibility.Private ? $"#{name}" : name;
-
///
///
/// JavaScript has no entry point of its own — a module runs top to bottom — so the function is
diff --git a/README.md b/README.md
index 45b855e..3612db6 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ rather than the modifier's text because no two languages spell visibility the sa
| C | Nothing inside a struct, which has no access control; a `Private` declaration at file scope is `static`, which is the internal linkage C has instead |
| Rust | `pub`, or `pub(crate)` for `Internal` and `Protected`; `Private` writes nothing, which is already Rust's default. `Unspecified` is `pub`, since a generated type nothing outside the module can read is not what saying nothing asked for |
| Go | Nothing. Go exports a name whose first letter is a capital and has no keyword at all, so a declaration whose name disagrees with what it asked for gets a note — renaming it would not rename the references to it. `Internal` is exactly Go's unexported, and `Private` and `Protected` are as near as there is |
-| JavaScript | A private class member takes the `#` prefix, which is JavaScript's own private syntax; nothing for the rest |
+| JavaScript | Nothing — the `#` that makes a class member private is a part of the name rather than a modifier in front of it, so writing one renames the declaration and leaves every reference to it naming something the class no longer declares |
| Python | Nothing — Python has no access modifiers, and its leading-underscore convention renames the declaration rather than modifying it |
### Constants and entry points
@@ -211,7 +211,7 @@ directly.
|---|---|---|---|
| `python` | `PythonGenerator` | `py` | Type hints, `None` for void, `pass` for an empty body or class; `self` on methods; `main` with its `__main__` guard |
| `csharp` | `CSharpGenerator` | `cs` | Mapped type names, `var` for inferred declarations, visibility keywords, `const`, `static Main` |
-| `javascript` | `JavaScriptGenerator` | `js` | Untyped; `const`/`let`; strict `===` and `!==`; method, `static` and `#private` syntax inside a class |
+| `javascript` | `JavaScriptGenerator` | `js` | Untyped; `const`/`let`; strict `===` and `!==`; method and `static` syntax inside a class |
| `cpp` | `CppGenerator` | `cpp` | Mapped type spellings (`str` → `std::string`); `auto` for inferred declarations; access labels, `static constexpr` members and a terminating `;` on a class |
| `c` | `CGenerator` | `c` | `typedef struct` for every kind of type; a member function is a free `Type_name(Type* self, …)`; an interface is a struct of function pointers; a base type is the first member; enumeration members are qualified by their enumeration; `_Static_assert`, `main(void)`, and `static const` for a constant |
| `rust` | `RustGenerator` | `rs` | A `struct` for the data and an `impl` block for the behaviour; an interface is a `trait` and a base type on one is a supertrait; a destructor is `impl Drop`, an operator is its `std::ops` trait, a conversion is `impl From`, and a specialisation is `impl Trait for Type`; `#[repr]`, `#[must_use]`, `const fn`, and `const _: () = assert!(…)` |