Repository: smithy-lang/smithy
Pull request #2274 (2024-05-03 20:23:04)

Add .Appended id subparts to enum additions
#### Background
* Adds a hierarchical subpart of ".Appended.[index]" for enum additions at the end of the list

#### Testing
unit tests

#### Links
* Links to additional context, if necessary
* Issue #, if applicable (see [here](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for a list of keywords to use for linking issues)

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


mtdowling: What’s the use case?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2778a445c..b95d0290f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,28 @@
 # Smithy Changelog
 
+## 1.49.0 (2024-05-08)
+
+### Features
+
+* Added list and map shapes to directed codegen. ([#2273](https://github.com/smithy-lang/smithy/pull/2273))
+* Added support for defaults on trait member values in trait-codegen. ([#2267](https://github.com/smithy-lang/smithy/pull/2267))
+* Added support for string-arrays for as parameters in rules-engine. ([#2266](https://github.com/smithy-lang/smithy/pull/2266))
+* Added new trait (`@operationContextParams`) for binding array parameters to nested operation inputs in the rules-engine. ([#2264](https://github.com/smithy-lang/smithy/pull/2264))
+
+### Bug Fixes
+
+* Fixed bug in the formatter where comments in operation errors were not being handled correctly. ([#2283](https://github.com/smithy-lang/smithy/pull/2283))
+* Fixed bug in the formatter where certain documentation comments were being converted to line-comments. ([#2277](https://github.com/smithy-lang/smithy/pull/2277))
+* Fixed incorrect status code in restXml response protocol test. ([#2272](https://github.com/smithy-lang/smithy/pull/2272))
+* Fixed ec2query protocol test for empty list serialization. ([#2269](https://github.com/smithy-lang/smithy/pull/2269))
+* Fixed Javadoc integration for documentation in members and enum variants in trait-codegen. ([#2265](https://github.com/smithy-lang/smithy/pull/2265)) 
+* Fixed timing issue in the delay calculation for waiter retries. ([#2259](https://github.com/smithy-lang/smithy/pull/2259))
+
+### Documentation
+
+* Corrected a typo in RPCv2 CBOR specificaiton. ([#2278](https://github.com/smithy-lang/smithy/pull/2278))
+* Updated casing of aggregate shape names in shape type table. ([#2271](https://github.com/smithy-lang/smithy/pull/2271))
+
 ## 1.48.0 (2024-04-24)
 
 ### Features
diff --git a/VERSION b/VERSION
index 9db5ea12f..7f3a46a84 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-1.48.0
+1.49.0
diff --git a/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
index 2b2e4664b..40479383a 100644
--- a/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
+++ b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
@@ -298,8 +298,8 @@ headers for responses:
       - Required
       - The value of ``rpc-v2-cbor``.
     * - ``Content-Type``
-      - Required with request bodies
-      - The value of ``application/cbor``. For event streaming requests, this
+      - Required with response bodies
+      - The value of ``application/cbor``. For event streaming responses, this
         is ``application/vnd.amazon.eventstream``.
     * - ``Content-Length``
       - Conditional
diff --git a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
index de5bf6117..06917e347 100644
--- a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
+++ b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
@@ -43,7 +43,7 @@ final class AddDefaultConfigSettings implements ApiGatewayMapper {
     public void updateDefaultSettings(Model model, OpenApiConfig openApiConfig) {
         ApiGatewayConfig config = openApiConfig.getExtensions(ApiGatewayConfig.class);
         if (config.getApiGatewayDefaults() == null) {
-            LOGGER.warning(String.format("`apiGatewayDefaults` configuration not set for opeanapi plugin. Assuming %s.",
+            LOGGER.warning(String.format("`apiGatewayDefaults` configuration not set for openapi plugin. Assuming %s.",
                     DEFAULT_VERSION));
             config.setApiGatewayDefaults(DEFAULT_VERSION);
         }
diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java
index 81e024138..3b3647913 100644
--- a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java
+++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java
@@ -39,6 +39,8 @@ import software.amazon.smithy.model.node.NodeMapper;
 import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.shapes.EnumShape;
 import software.amazon.smithy.model.shapes.IntEnumShape;
+import software.amazon.smithy.model.shapes.ListShape;
+import software.amazon.smithy.model.shapes.MapShape;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.ResourceShape;
 import software.amazon.smithy.model.shapes.ServiceShape;
@@ -556,6 +558,20 @@ public final class CodegenDirector<
             return null;
         }
 
+        @Override
+        public Void listShape(ListShape shape) {
+            LOGGER.finest(() -> "Generating list " + shape.getId());
+            directedCodegen.generateList(new GenerateListDirective<>(context, serviceShape, shape));
+            return null;
+        }
+
+        @Override
+        public Void mapShape(MapShape shape) {
+            LOGGER.finest(() -> "Generating map " + shape.getId());
+            directedCodegen.generateMap(new GenerateMapDirective<>(context, serviceShape, shape));
+            return null;
+        }
+
         @Override
         public Void stringShape(StringShape shape) {
             if (shape.hasTrait(EnumTrait.class)) {
diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/DirectedCodegen.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/DirectedCodegen.java
index e321473e7..469c81bfa 100644
--- a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/DirectedCodegen.java
+++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/DirectedCodegen.java
@@ -99,6 +99,20 @@ public interface DirectedCodegen<C extends CodegenContext<S, ?, I>, S, I extends
      */
     void generateUnion(GenerateUnionDirective<C, S> directive);
 
+    /**
+     * Generates any code needed for a list shape.
+     *
+     * @param directive Directive to perform.
+     */
+    default void generateList(GenerateListDirective<C, S> directive) {}
+
+    /**
+     * Generates any code needed for a map shape.
+     *
+     * @param directive Directive to perform.
+     */
+    default void generateMap(GenerateMapDirective<C, S> directive) {}
+
     /**
      * Generates the code needed for an enum shape, whether it's a string shape
      * marked with the enum trait, or a proper enum shape introduced in Smithy
diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/GenerateListDirective.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/GenerateListDirective.java
new file mode 100644
index 000000000..e3bbc76b4
--- /dev/null
+++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/GenerateListDirective.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.codegen.core.directed;
+
+import software.amazon.smithy.codegen.core.CodegenContext;
+import software.amazon.smithy.model.shapes.ListShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+
+/**
+ * Directive used to generate a list.
+ *
+ * @param <C> CodegenContext type.
+ * @param <S> Codegen settings type.
+ * @see DirectedCodegen#generateList
+ */
+public class GenerateListDirective<C extends CodegenContext<S, ?, ?>, S>
+        extends ShapeDirective<ListShape, C, S> {
+    GenerateListDirective(C context, ServiceShape service, ListShape shape) {
+        super(context, service, shape);
+    }
+}
diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/GenerateMapDirective.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/GenerateMapDirective.java
new file mode 100644
index 000000000..1c2f16abe
--- /dev/null
+++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/GenerateMapDirective.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.codegen.core.directed;
+
+import software.amazon.smithy.codegen.core.CodegenContext;
+import software.amazon.smithy.model.shapes.MapShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+
+/**
+ * Directive used to generate a map.
+ *
+ * @param <C> CodegenContext type.
+ * @param <S> Codegen settings type.
+ * @see DirectedCodegen#generateMap
+ */
+public class GenerateMapDirective<C extends CodegenContext<S, ?, ?>, S>
+        extends ShapeDirective<MapShape, C, S> {
+    GenerateMapDirective(C context, ServiceShape service, MapShape shape) {
+        super(context, service, shape);
+    }
+}
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedEnumTrait.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedEnumTrait.java
index 72cba13cd..75a494e20 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedEnumTrait.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedEnumTrait.java
@@ -43,6 +43,7 @@ public final class ChangedEnumTrait extends AbstractDiffEvaluator {
     private static final String ORDER_CHANGED = ".OrderChanged.";
     private static final String NAME_CHANGED = ".NameChanged.";
     private static final String REMOVED = ".Removed.";
+    private static final String APPENDED = ".Appended.";
 
     @Override
     public List<ValidationEvent> evaluate(Differences differences) {
@@ -135,8 +136,14 @@ public final class ChangedEnumTrait extends AbstractDiffEvaluator {
                     if (newTrait.isSynthetic()) {
                         appendedSource = change.getNewShape().getSourceLocation();
                     }
-                    events.add(note(change.getNewShape(), appendedSource, String.format(
-                            "Enum value `%s` was appended", definition.getValue())));
+                    events.add(
+                            ValidationEvent.builder()
+                                    .severity(Severity.NOTE)
+                                    .message(String.format("Enum value `%s` was appended", definition.getValue()))
+                                    .shape(change.getNewShape())
+                                    .sourceLocation(appendedSource)
+                                    .id(getEventId() + APPENDED + newPosition)
+                                    .build());
                 }
             }
 
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/BracketFormatter.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/BracketFormatter.java
index 68c494ff8..03899eac2 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/BracketFormatter.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/BracketFormatter.java
@@ -38,7 +38,37 @@ final class BracketFormatter {
             Function<TreeCursor, Doc> visitor,
             Function<TreeCursor, Stream<TreeCursor>> mapper
     ) {
-        return new Extractor(visitor, mapper);
+        return extractor(visitor, mapper, TreeCursor::children);
+    }
+
+    static Function<TreeCursor, Stream<Doc>> extractor(
+            Function<TreeCursor, Doc> visitor,
+            Function<TreeCursor, Stream<TreeCursor>> mapper,
+            Function<TreeCursor, Stream<TreeCursor>> childrenSupplier
+    ) {
+        return (cursor) -> childrenSupplier.apply(cursor)
+                .flatMap(c -> {
+                    TreeType type = c.getTree().getType();
+                    return type == TreeType.WS || type == TreeType.COMMENT ? Stream.of(c) : mapper.apply(c);
+                })
+                .flatMap(c -> {
+                    // If the child extracts WS, then filter it down to just comments.
+                    return c.getTree().getType() == TreeType.WS
+                            ? c.getChildrenByType(TreeType.COMMENT).stream()
+                            : Stream.of(c);
+                })
+                .map(visitor)
+                .filter(doc -> doc != Doc.empty());
+    }
+
+    static Function<TreeCursor, Stream<TreeCursor>> byTypeMapper(TreeType childType) {
+        return child -> child.getTree().getType() == childType
+                ? Stream.of(child)
+                : Stream.empty();
+    }
+
+    static Function<TreeCursor, Stream<TreeCursor>> siblingChildrenSupplier() {
+        return cursor -> cursor.remainingSiblings().stream();
     }
 
     // Brackets children of childType between open and closed brackets. If the children can fit together
@@ -47,9 +77,7 @@ final class BracketFormatter {
             TreeType childType,
             Function<TreeCursor, Doc> visitor
     ) {
-        return extractor(visitor, child -> child.getTree().getType() == childType
-                                           ? Stream.of(child)
-                                           : Stream.empty());
+        return extractor(visitor, byTypeMapper(childType));
     }
 
     BracketFormatter open(Doc open) {
@@ -138,36 +166,4 @@ final class BracketFormatter {
         }
         return lineDoc;
     }
-
-    private static final class Extractor implements Function<TreeCursor, Stream<Doc>> {
-        private final Function<TreeCursor, Stream<TreeCursor>> mapper;
-        private final Function<TreeCursor, Doc> visitor;
-
-        private Extractor(
-                Function<TreeCursor, Doc> visitor,
-                Function<TreeCursor, Stream<TreeCursor>> mapper
-        ) {
-            this.visitor = visitor;
-            this.mapper = mapper;
-        }
-
-        @Override
-        public Stream<Doc> apply(TreeCursor cursor) {
-            SmithyBuilder.requiredState("childExtractor", mapper);
-            SmithyBuilder.requiredState("visitor", visitor);
-            return cursor.children()
-                    .flatMap(c -> {
-                        TreeType type = c.getTree().getType();
-                        return type == TreeType.WS || type == TreeType.COMMENT ? Stream.of(c) : mapper.apply(c);
-                    })
-                    .flatMap(c -> {
-                        // If the child extracts WS, then filter it down to just comments.
-                        return c.getTree().getType() == TreeType.WS
-                               ? c.getChildrenByType(TreeType.COMMENT).stream()
-                               : Stream.of(c);
-                    })
-                    .map(visitor)
-                    .filter(doc -> doc != Doc.empty());
-        }
-    }
 }
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FixBadDocComments.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FixBadDocComments.java
index 7a62655cf..d4e180f6c 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FixBadDocComments.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FixBadDocComments.java
@@ -67,6 +67,11 @@ final class FixBadDocComments implements Function<TokenTree, TokenTree> {
             updateNestedChildren(cursor);
         }
 
+        // Doc comments should not appear in TRAIT_STATEMENTS.
+        for (TreeCursor cursor : shapeSection.findChildrenByType(TreeType.TRAIT_STATEMENTS)) {
+            updateNestedChildren(cursor);
+        }
+
         // Fix doc comments that come before apply statements.
         TreeCursor shapeStatements = shapeSection.getFirstChild(TreeType.SHAPE_STATEMENTS);
         if (shapeStatements != null) {
@@ -77,11 +82,15 @@ final class FixBadDocComments implements Function<TokenTree, TokenTree> {
                     updateNestedChildren(br);
                 }
             }
-
-            // Remove trailing doc comments in member bodies.
+            // Fix any trailing doc comments in shape bodies
             for (TreeCursor members : shapeStatements.findChildrenByType(TreeType.SHAPE_MEMBERS)) {
-                TreeCursor ws = members.getLastChild(TreeType.WS);
-                updateDirectChildren(ws);
+                TreeCursor closeBrace = members.getLastChild();
+                if (closeBrace != null) {
+                    TreeCursor possibleTrailingWs = closeBrace.getPreviousSibling();
+                    if (possibleTrailingWs != null && possibleTrailingWs.getTree().getType() == TreeType.WS) {
+                        updateDirectChildren(possibleTrailingWs);
+                    }
+                }
             }
         }
 
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FormatVisitor.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FormatVisitor.java
index a9b1a47f6..2f8eadfe4 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FormatVisitor.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/FormatVisitor.java
@@ -263,15 +263,29 @@ final class FormatVisitor {
             }
 
             case OPERATION_ERRORS: {
-                return skippedComments(cursor, false)
-                        .append(Doc.text("errors: "))
+                // Pull out any comments that come after 'errors' but before the opening '[' so they
+                // can be placed before 'errors: ['
+                Doc comments = Doc.empty();
+                TreeCursor child = cursor.getFirstChild(); // 'errors'
+                if (child.getNextSibling().getTree().getType() == TreeType.WS) {
+                    comments = comments.append(skippedComments(child.getNextSibling(), false));
+                    child = child.getNextSibling(); // skip ws
+                }
+                child = child.getNextSibling(); // ':'
+                if (child.getNextSibling().getTree().getType() == TreeType.WS) {
+                    comments = comments.append(skippedComments(child.getNextSibling(), false));
+                    child = child.getNextSibling();
+                }
+                return comments.append(Doc.text("errors: ")
                         .append(new BracketFormatter()
                                         .open(Formatter.LBRACKET)
                                         .close(Formatter.RBRACKET)
-                                        .extractChildren(cursor, BracketFormatter.extractByType(TreeType.SHAPE_ID,
-                                                                                                this::visit))
+                                        .extractChildren(child, BracketFormatter.extractor(
+                                                this::visit,
+                                                BracketFormatter.byTypeMapper(TreeType.SHAPE_ID),
+                                                BracketFormatter.siblingChildrenSupplier()))
                                         .forceLineBreaks() // always put each error on separate lines.
-                                        .write());
+                                        .write()));
             }
 
             case MIXINS: {
diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TreeCursor.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TreeCursor.java
index 1e2536133..3174f64eb 100644
--- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TreeCursor.java
+++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TreeCursor.java
@@ -318,6 +318,19 @@ public final class TreeCursor implements FromSourceLocation {
         }
     }
 
+    /**
+     * @return All siblings after this cursor, in order.
+     */
+    public List<TreeCursor> remainingSiblings() {
+        List<TreeCursor> remaining = new ArrayList<>();
+        TreeCursor nextSibling = getNextSibling();
+        while (nextSibling != null) {
+            remaining.add(nextSibling);
+            nextSibling = nextSibling.getNextSibling();
+        }
+        return remaining;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {


Test patch:
diff --git a/smithy-aws-protocol-tests/model/restXml/errors.smithy b/smithy-aws-protocol-tests/model/restXml/errors.smithy
index b623cd5b2..65c4c05e7 100644
--- a/smithy-aws-protocol-tests/model/restXml/errors.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/errors.smithy
@@ -103,7 +103,7 @@ apply ComplexError @httpResponseTests([
                 Foo: "bar"
             }
         },
-        code: 400,
+        code: 403,
         headers: {
             "Content-Type": "application/xml",
             "X-Header": "Header",
diff --git a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java
index df320005b..f4a8bbc12 100644
--- a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java
+++ b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java
@@ -99,6 +99,16 @@ public class CodegenDirectorTest {
             generatedShapes.add(directive.shape().getId());
         }
 
+        @Override
+        public void generateList(GenerateListDirective<TestContext, TestSettings> directive) {
+            generatedShapes.add(directive.shape().getId());
+        }
+
+        @Override
+        public void generateMap(GenerateMapDirective<TestContext, TestSettings> directive) {
+            generatedShapes.add(directive.shape().getId());
+        }
+
         @Override
         public void generateEnumShape(GenerateEnumDirective<TestContext, TestSettings> directive) {
             generatedShapes.add(directive.shape().getId());
@@ -187,6 +197,9 @@ public class CodegenDirectorTest {
                 ShapeId.from("smithy.example#TheFoo"),
                 ShapeId.from("smithy.example#ListFooInput"),
                 ShapeId.from("smithy.example#ListFooOutput"),
+                ShapeId.from("smithy.example#FooStructure"),
+                ShapeId.from("smithy.example#FooList"),
+                ShapeId.from("smithy.example#StringMap"),
                 ShapeId.from("smithy.example#Status"),
                 ShapeId.from("smithy.example#FaceCard"),
                 ShapeId.from("smithy.example#Instruction"),
@@ -229,6 +242,9 @@ public class CodegenDirectorTest {
                 ShapeId.from("smithy.example#TheFoo"),
                 ShapeId.from("smithy.example#ListFooInput"),
                 ShapeId.from("smithy.example#ListFooOutput"),
+                ShapeId.from("smithy.example#FooStructure"),
+                ShapeId.from("smithy.example#FooList"),
+                ShapeId.from("smithy.example#StringMap"),
                 ShapeId.from("smithy.example#Status"),
                 ShapeId.from("smithy.example#FaceCard"),
                 ShapeId.from("smithy.example#Instruction"),
diff --git a/smithy-codegen-core/src/test/resources/software/amazon/smithy/codegen/core/directed/directed-model.smithy b/smithy-codegen-core/src/test/resources/software/amazon/smithy/codegen/core/directed/directed-model.smithy
index 48d6c51fc..686af4988 100644
--- a/smithy-codegen-core/src/test/resources/software/amazon/smithy/codegen/core/directed/directed-model.smithy
+++ b/smithy-codegen-core/src/test/resources/software/amazon/smithy/codegen/core/directed/directed-model.smithy
@@ -21,14 +21,24 @@ operation ListFoo {
     }
     output:= with [Paginated] {
         status: Status
-        items: StringList
+        items: FooList
         instruction: Instruction
         facecard: FaceCard
     }
 }
 
-list StringList {
-    member: String
+structure FooStructure {
+    id: String
+    tags: StringMap
+}
+
+map StringMap {
+    key: String
+    value: String
+}
+
+list FooList {
+    member: FooStructure
 }
 
 @enum([
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedEnumTraitTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedEnumTraitTest.java
index a1d6fc755..9907f615a 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedEnumTraitTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedEnumTraitTest.java
@@ -47,6 +47,7 @@ public class ChangedEnumTraitTest {
                 .addTrait(EnumTrait.builder()
                         .addEnum(EnumDefinition.builder().value("foo").build())
                         .addEnum(EnumDefinition.builder().value("baz").build())
+                        .addEnum(EnumDefinition.builder().value("bar").build())
                         .sourceLocation(source)
                         .build())
                 .build();
@@ -54,9 +55,14 @@ public class ChangedEnumTraitTest {
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(1));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(0).getSeverity(), equalTo(Severity.NOTE));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(0).getSourceLocation(), equalTo(source));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(2));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended").size(), equalTo(2));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.2").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended").stream()
+                .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended").stream()
+                .allMatch(e -> source.equals(e.getSourceLocation())), equalTo(true));
     }
 
     @Test
@@ -86,9 +92,9 @@ public class ChangedEnumTraitTest {
                 equalTo(Severity.ERROR));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSourceLocation(),
                 equalTo(source));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(1).getSeverity(),
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.1").get(0).getSeverity(),
                 equalTo(Severity.NOTE));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(1).getSourceLocation(),
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.1").get(0).getSourceLocation(),
                 equalTo(source));
     }
 
@@ -115,10 +121,10 @@ public class ChangedEnumTraitTest {
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.1").size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(0).getSourceLocation(),
                 equalTo(source));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").stream()
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended").stream()
                 .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
     }
 
@@ -455,16 +461,15 @@ public class ChangedEnumTraitTest {
 
         List<ValidationEvent> changeEvents = TestHelper.findEvents(allEvents, "ChangedEnumTrait");
         assertThat(changeEvents.size(), equalTo(2));
-        assertThat(TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
-        assertThat(TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Removed.1").get(0).getSourceLocation(),
-                equalTo(beforeSource));
 
-        ValidationEvent removedEvent = changeEvents.get(0);
+        assertThat(TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
+        ValidationEvent removedEvent = TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Removed.1").get(0);
         assertThat(removedEvent.getSeverity(), equalTo(Severity.ERROR));
         assertThat(removedEvent.getMessage(), stringContainsInOrder("Enum value `old2` was removed"));
         assertThat(removedEvent.getSourceLocation(), equalTo(beforeSource));
 
-        ValidationEvent appendedEvent = changeEvents.get(1);
+        assertThat(TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Appended.2").size(), equalTo(1));
+        ValidationEvent appendedEvent = TestHelper.findEvents(changeEvents, "ChangedEnumTrait.Appended.2").get(0);
         assertThat(appendedEvent.getSeverity(), equalTo(Severity.NOTE));
         assertThat(appendedEvent.getMessage(), stringContainsInOrder("Enum value `new1` was appended"));
         assertThat(appendedEvent.getSourceLocation(), equalTo(afterSource));
@@ -512,7 +517,7 @@ public class ChangedEnumTraitTest {
                 equalTo(afterSource));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").subList(0, 3).stream()
                 .allMatch(e -> e.getSeverity() == Severity.ERROR), equalTo(true));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").subList(3, 4).stream()
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.2").stream()
                 .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
     }
 
@@ -553,7 +558,7 @@ public class ChangedEnumTraitTest {
                 equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").stream()
                 .allMatch(e -> e.getSeverity() == Severity.ERROR), equalTo(true));
-        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").subList(1, 2).stream()
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Appended.2").stream()
                 .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
     }
 }
diff --git a/smithy-syntax/src/test/java/software/amazon/smithy/syntax/ParseAndFormatTest.java b/smithy-syntax/src/test/java/software/amazon/smithy/syntax/ParseAndFormatTest.java
index 3eb26da41..e87105486 100644
--- a/smithy-syntax/src/test/java/software/amazon/smithy/syntax/ParseAndFormatTest.java
+++ b/smithy-syntax/src/test/java/software/amazon/smithy/syntax/ParseAndFormatTest.java
@@ -1,7 +1,6 @@
 package software.amazon.smithy.syntax;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.io.IOException;
 import java.nio.file.Files;
@@ -54,7 +53,7 @@ public class ParseAndFormatTest {
         String formatted = Formatter.format(tree, 120);
         String expected = IoUtils.readUtf8File(formattedFile);
 
-        assertThat(formatted, equalTo(expected));
+        assertEquals(expected, formatted);
     }
 
     public static List<Path> tests() throws Exception {
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/fixes-invalid-doc-comments.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/fixes-invalid-doc-comments.formatted.smithy
index b6984e10c..e7e8d0d29 100644
--- a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/fixes-invalid-doc-comments.formatted.smithy
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/fixes-invalid-doc-comments.formatted.smithy
@@ -23,19 +23,24 @@ use smithy.api#String
 
 /// 12 (keep)
 @deprecated
-/// 13 (change)
+// 13 (change)
 structure Foo {
     /// 14 (keep)
     @length(
         // 15 (change)
         min: 1
     )
-    /// 16 (change)
+    // 16 (change)
     @since("1.x")
-    /// 17 (TODO: change)
+    // 17 (change)
     bar: String
 
-    // 18 (change)
+    /// 17a (keep)
+    @length(min: 1)
+    baz: String = ""
+
+    /// 18 (TODO: Fix trailing comment after VALUE_ASSIGNMENT)
+
 }
 
 // 19 (change)
@@ -45,4 +50,10 @@ apply Foo @tags(["a"])
 list Baz {
     member: Integer
 }
-// 21 (change)
+
+structure Foo2 {
+    foo2: String
+
+    // 21 (change)
+}
+// 22 (change)
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/fixes-invalid-doc-comments.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/fixes-invalid-doc-comments.smithy
index e16e23e1c..5aeb29841 100644
--- a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/fixes-invalid-doc-comments.smithy
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/fixes-invalid-doc-comments.smithy
@@ -32,9 +32,14 @@ structure Foo {
     )
     /// 16 (change)
     @since("1.x")
-    /// 17 (TODO: change)
+    /// 17 (change)
     bar: String
-    /// 18 (change)
+
+    /// 17a (keep)
+    @length(min: 1)
+    baz: String = ""
+
+    /// 18 (TODO: Fix trailing comment after VALUE_ASSIGNMENT)
 }
 
 /// 19 (change)
@@ -45,4 +50,10 @@ list Baz {
     member: Integer
 }
 
-/// 21 (change)
+structure Foo2 {
+    foo2: String
+
+    /// 21 (change)
+}
+
+/// 22 (change)
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/operation-errors-comments.formatted.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/operation-errors-comments.formatted.smithy
new file mode 100644
index 000000000..76fbcdd0c
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/operation-errors-comments.formatted.smithy
@@ -0,0 +1,27 @@
+$version: "2.0"
+
+namespace smithy.example
+
+operation Foo {
+    errors: [
+        // a
+        E1
+        // c
+        E2
+        // d
+    ]
+}
+
+operation Bar {
+    // a
+    // b
+    errors: [
+        // c
+    ]
+}
+
+@error("client")
+structure E1 {}
+
+@error("client")
+structure E2 {}
diff --git a/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/operation-errors-comments.smithy b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/operation-errors-comments.smithy
new file mode 100644
index 000000000..6b66e0448
--- /dev/null
+++ b/smithy-syntax/src/test/resources/software/amazon/smithy/syntax/formatter/operation-errors-comments.smithy
@@ -0,0 +1,27 @@
+$version: "2.0"
+
+namespace smithy.example
+
+operation Foo {
+    errors: [
+        // a
+        E1
+        // c
+        E2
+        // d
+    ]
+}
+
+operation Bar {
+    errors // a
+    : // b
+    [
+        // c
+    ]
+}
+
+@error("client")
+structure E1 {}
+
+@error("client")
+structure E2 {}