Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions Coder.Editor/CoderEditorApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,35 @@
/// <summary>
/// Builds the document a fresh editor opens with.
/// </summary>
/// <returns>A function with one parameter and an empty body.</returns>
/// <returns>A small class with two fields and two methods that do something with them.</returns>
/// <remarks>
/// An empty function rather than an empty graph: the AST has no node that means "nothing yet",
/// and a user who has just opened the editor is better served by something to attach to than by
/// a blank canvas and no way to start.
/// Something to read rather than something to start from: the AST has no node meaning "nothing
/// yet", and a canvas holding one empty function shows neither what the node kinds are nor how
/// they connect. This one puts a field, a parameter, an assignment, a binary expression, a local
/// and a return on screen at once, so the shape of the graph is legible before anything is added.
/// </remarks>
public static FunctionDeclaration NewDocument()
public static ClassDeclaration NewDocument()
{
FunctionDeclaration function = new("newFunction") { ReturnType = "void" };
function.Parameters.Add(new Parameter("value", "int"));
return function;
ClassDeclaration declaration = new("Counter");

declaration.Members.Add(new VariableDeclaration("count", "int", new LiteralExpression<int>(0)));

Check warning on line 144 in Coder.Editor/CoderEditorApp.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using this literal 'count' 5 times.

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUwTMtubVMe_Rsse&open=AaCARUwTMtubVMe_Rsse&pullRequest=24
declaration.Members.Add(new VariableDeclaration("step", "int", new LiteralExpression<int>(1)));

FunctionDeclaration add = new("Add") { ReturnType = "int" };
add.Parameters.Add(new Parameter("amount", "int"));
add.Body.Add(new AssignmentStatement(
new VariableReference("count"),
new BinaryExpression(new VariableReference("count"), BinaryOperator.Add, new VariableReference("amount"))));
add.Body.Add(new ReturnStatement(new VariableReference("count")));
declaration.Members.Add(add);

FunctionDeclaration next = new("Next") { ReturnType = "int" };
next.Body.Add(new VariableDeclaration("result", "int",
new BinaryExpression(new VariableReference("count"), BinaryOperator.Add, new VariableReference("step"))));
next.Body.Add(new ReturnStatement(new VariableReference("result")));
declaration.Members.Add(next);

return declaration;
}

/// <summary>
Expand Down
63 changes: 52 additions & 11 deletions Coder.Test/Editor/CoderEditorAppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,56 @@
settings ?? new EditorSettings());

/// <summary>
/// Tests that a fresh editor opens with something to attach to rather than a blank canvas.
/// Tests that a document containing an assignment survives being written and read back.
/// </summary>
/// <remarks>
/// AssignmentStatement's deserialization constructor built its placeholder target through the
/// VariableReference overload that rejects an empty name, so every load of a document holding one
/// threw before it could be overwritten. The default document has an assignment in it, so this is
/// the path a user takes by saving and reopening what the editor gave them.
/// </remarks>
[TestMethod]
public void Document_WithAnAssignment_RoundTripsThroughTheFileSystem()
{
DocumentStore store = NewStore();
CoderEditorApp app = NewApp(store);

string path = PathIn("assigning");
Assert.IsTrue(app.Save(path), app.Status);

CoderEditorApp reopened = NewApp(store);
Assert.IsTrue(reopened.Open(path), reopened.Status);

ClassDeclaration reopenedRoot = (ClassDeclaration)reopened.Editor.Graph.Root;
Assert.IsTrue(
reopenedRoot.Members.OfType<FunctionDeclaration>().SelectMany(m => m.Body).OfType<AssignmentStatement>().Any(),
"the reopened document should still hold its assignment");

Check warning on line 91 in Coder.Test/Editor/CoderEditorAppTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.IsNotEmpty' instead of 'Assert.IsTrue'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUsUMtubVMe_RssW&open=AaCARUsUMtubVMe_RssW&pullRequest=24
}

/// <summary>
/// Tests that a fresh editor opens with something to read rather than a blank canvas.
/// </summary>
[TestMethod]
public void NewDocument_IsAFunctionWithSomethingToAttachTo()
public void NewDocument_IsAClassWithFieldsAndMethodsThatDoSomething()
{
FunctionDeclaration document = CoderEditorApp.NewDocument();
ClassDeclaration document = CoderEditorApp.NewDocument();

Assert.AreEqual("Counter", document.Name);

List<VariableDeclaration> fields = [.. document.Members.OfType<VariableDeclaration>()];
Assert.AreEqual(2, fields.Count, "the class should carry a couple of fields");

Check warning on line 105 in Coder.Test/Editor/CoderEditorAppTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.HasCount' instead of 'Assert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUsUMtubVMe_RssX&open=AaCARUsUMtubVMe_RssX&pullRequest=24
Assert.IsTrue(fields.TrueForAll(f => f.InitialValue is not null), "each field should be initialised");

Assert.AreEqual("newFunction", document.Name);
Assert.AreEqual(1, document.Parameters.Count);
List<FunctionDeclaration> methods = [.. document.Members.OfType<FunctionDeclaration>()];
Assert.AreEqual(2, methods.Count, "the class should carry a couple of methods");

Check warning on line 109 in Coder.Test/Editor/CoderEditorAppTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.HasCount' instead of 'Assert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUsUMtubVMe_RssY&open=AaCARUsUMtubVMe_RssY&pullRequest=24
Assert.IsTrue(methods.TrueForAll(m => m.Body.Count > 0), "each method should have a body");

Assert.IsTrue(
methods.SelectMany(m => m.Body).OfType<AssignmentStatement>().Any(a => a.Value is BinaryExpression),
"a method should assign the result of an expression");

Check warning on line 114 in Coder.Test/Editor/CoderEditorAppTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'Assert.IsTrue'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUsUMtubVMe_RssZ&open=AaCARUsUMtubVMe_RssZ&pullRequest=24
Assert.IsTrue(
methods.SelectMany(m => m.Body).OfType<VariableDeclaration>().Any(v => v.InitialValue is BinaryExpression),
"a method should declare a local from an expression");

Check warning on line 117 in Coder.Test/Editor/CoderEditorAppTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'Assert.IsTrue'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUsUMtubVMe_Rssa&open=AaCARUsUMtubVMe_Rssa&pullRequest=24
}

/// <summary>
Expand All @@ -92,8 +133,8 @@
CoderEditorApp reopened = NewApp(store);
Assert.IsTrue(reopened.Open(path), reopened.Status);

Assert.IsInstanceOfType<FunctionDeclaration>(reopened.Editor.Graph.Root);
Assert.AreEqual("newFunction", ((FunctionDeclaration)reopened.Editor.Graph.Root).Name);
Assert.IsInstanceOfType<ClassDeclaration>(reopened.Editor.Graph.Root);
Assert.AreEqual("Counter", ((ClassDeclaration)reopened.Editor.Graph.Root).Name);
Assert.AreEqual(path, reopened.DocumentPath);
}

Expand Down Expand Up @@ -259,11 +300,11 @@
CoderEditorApp app = NewApp(store, settings);

app.Regenerate();
StringAssert.Contains(app.GeneratedCode, "public void newFunction", StringComparison.Ordinal);
StringAssert.Contains(app.GeneratedCode, "public class Counter", StringComparison.Ordinal);

Check warning on line 303 in Coder.Test/Editor/CoderEditorAppTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'StringAssert.Contains'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUsUMtubVMe_Rssb&open=AaCARUsUMtubVMe_Rssb&pullRequest=24

settings.PreviewLanguageId = "python";
app.Regenerate();
StringAssert.Contains(app.GeneratedCode, "def newFunction", StringComparison.Ordinal);
StringAssert.Contains(app.GeneratedCode, "def Add(self, amount: int)", StringComparison.Ordinal);

Check warning on line 307 in Coder.Test/Editor/CoderEditorAppTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'StringAssert.Contains'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUsUMtubVMe_Rssc&open=AaCARUsUMtubVMe_Rssc&pullRequest=24
}

/// <summary>
Expand Down Expand Up @@ -367,7 +408,7 @@

Assert.IsNotNull(written);
Assert.AreEqual(Path.Combine(root, "greeting.cs"), written);
StringAssert.Contains(File.ReadAllText(written), "public void newFunction(int value)", StringComparison.Ordinal);
StringAssert.Contains(File.ReadAllText(written), "public int Add(int amount)", StringComparison.Ordinal);

Check warning on line 411 in Coder.Test/Editor/CoderEditorAppTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'StringAssert.Contains'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Coder&issues=AaCARUsUMtubVMe_Rssd&open=AaCARUsUMtubVMe_Rssd&pullRequest=24
StringAssert.Contains(app.Status, "Wrote", StringComparison.Ordinal);
}

Expand Down Expand Up @@ -521,7 +562,7 @@
CoderEditorApp app = NewApp(store);

// An operand nobody has filled in yet, which is what Validate reports.
FunctionDeclaration document = CoderEditorApp.NewDocument();
FunctionDeclaration document = new("incomplete") { ReturnType = "int" };
document.Body.Add(new ReturnStatement(
new BinaryExpression(AstSchema.Unfilled(), BinaryOperator.Add, AstSchema.Unfilled())));
Assert.IsTrue(app.Open(WriteDocument(store, document, PathIn("incomplete"))), app.Status);
Expand Down
4 changes: 3 additions & 1 deletion Coder/Ast/AssignmentStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ public AssignmentStatement(Expression target, Expression value, AssignmentOperat
/// </summary>
public AssignmentStatement()
{
Target = new VariableReference("");
// VariableReference(string) rejects an empty name, so the placeholder a deserializer overwrites
// has to come from the parameterless constructor instead.
Target = new VariableReference();
Value = new LiteralExpression<string>("");
Operator = AssignmentOperator.Assign;
}
Expand Down
Loading