Repository: smithy-lang/smithy
Pull request #2001 (2023-10-05 20:19:46)

Set source locations on smithy-diff events
This PR updates smithy-diff evaluators to set source locations on the emitted events where the source location was missing previously.

For events where something is removed from a model (member, trait definition, etc), the location from the old model is used. For events where something is added or modified within a model, the location from the new model is used.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedOperationError.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedOperationError.java
index 1f431156c..3bbf2e327 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedOperationError.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedOperationError.java
@@ -21,6 +21,8 @@ import java.util.List;
 import java.util.stream.Collectors;
 import software.amazon.smithy.diff.ChangedShape;
 import software.amazon.smithy.diff.Differences;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.validation.Severity;
@@ -33,17 +35,18 @@ public final class AddedOperationError extends AbstractDiffEvaluator {
     @Override
     public List<ValidationEvent> evaluate(Differences differences) {
         return differences.changedShapes(OperationShape.class)
-                .flatMap(change -> createErrorViolations(change).stream())
+                .flatMap(change -> createErrorViolations(change, differences.getNewModel()).stream())
                 .collect(Collectors.toList());
     }
 
-    private List<ValidationEvent> createErrorViolations(ChangedShape<OperationShape> change) {
+    private List<ValidationEvent> createErrorViolations(ChangedShape<OperationShape> change, Model newModel) {
         if (change.getOldShape().getErrors().equals(change.getNewShape().getErrors())) {
             return Collections.emptyList();
         }
 
         List<ValidationEvent> events = new ArrayList<>();
         for (ShapeId error : change.getNewShape().getErrors()) {
+            SourceLocation errorSource = newModel.expectShape(error).getSourceLocation();
             if (!change.getOldShape().getErrors().contains(error)) {
                 events.add(
                         ValidationEvent.builder()
@@ -57,6 +60,7 @@ public final class AddedOperationError extends AbstractDiffEvaluator {
                                                 + "parameter to an operation).",
                                         error, change.getShapeId()))
                                 .shape(change.getNewShape())
+                                .sourceLocation(errorSource)
                                 .build()
                 );
             }
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedRequiredMember.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedRequiredMember.java
index ad48555bf..7d3cb192a 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedRequiredMember.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/AddedRequiredMember.java
@@ -52,7 +52,7 @@ public class AddedRequiredMember extends AbstractDiffEvaluator {
     private ValidationEvent emit(MemberShape memberShape) {
         return ValidationEvent.builder()
                 .id(getEventId())
-                .shapeId(memberShape.getId())
+                .shape(memberShape)
                 .message("Adding a new member with the `required` trait "
                         + "but not the `default` trait is backwards-incompatible.")
                 .severity(Severity.ERROR)
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 d9d06cbfe..72cba13cd 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
@@ -23,6 +23,7 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 import software.amazon.smithy.diff.ChangedShape;
 import software.amazon.smithy.diff.Differences;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.traits.EnumDefinition;
 import software.amazon.smithy.model.traits.EnumTrait;
@@ -85,6 +86,7 @@ public final class ChangedEnumTrait extends AbstractDiffEvaluator {
                                 .severity(Severity.ERROR)
                                 .message(String.format("Enum value `%s` was removed", definition.getValue()))
                                 .shape(change.getNewShape())
+                                .sourceLocation(oldTrait.getSourceLocation())
                                 .id(getEventId() + REMOVED + enumIndex)
                                 .build()
                 );
@@ -101,6 +103,7 @@ public final class ChangedEnumTrait extends AbstractDiffEvaluator {
                                             newValue.getName().orElse(null),
                                             definition.getValue()))
                                     .shape(change.getNewShape())
+                                    .sourceLocation(change.getNewShape().getSourceLocation())
                                     .id(getEventId() + NAME_CHANGED + enumIndex)
                                     .build()
                     );
@@ -120,12 +123,19 @@ public final class ChangedEnumTrait extends AbstractDiffEvaluator {
                                             + "can cause compatibility issues when ordinal values are used for "
                                             + "iteration, serialization, etc.", definition.getValue()))
                                     .shape(change.getNewShape())
+                                    .sourceLocation(oldTrait.getSourceLocation())
                                     .id(getEventId() + ORDER_CHANGED + newPosition)
                                     .build()
                     );
                     oldEndPosition++;
                 } else {
-                    events.add(note(change.getNewShape(), String.format(
+                    SourceLocation appendedSource = newTrait.getSourceLocation();
+                    // If the new trait is synthetic, its source will always be N/A.
+                    // Fall back to the shape's location.
+                    if (newTrait.isSynthetic()) {
+                        appendedSource = change.getNewShape().getSourceLocation();
+                    }
+                    events.add(note(change.getNewShape(), appendedSource, String.format(
                             "Enum value `%s` was appended", definition.getValue())));
                 }
             }
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedNullability.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedNullability.java
index 8d2963ee5..7808ab9c6 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedNullability.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ChangedNullability.java
@@ -25,6 +25,7 @@ import java.util.stream.Stream;
 import software.amazon.smithy.diff.ChangedShape;
 import software.amazon.smithy.diff.Differences;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.knowledge.NullableIndex;
 import software.amazon.smithy.model.shapes.MemberShape;
 import software.amazon.smithy.model.shapes.Shape;
@@ -96,33 +97,36 @@ public class ChangedNullability extends AbstractDiffEvaluator {
         boolean oldHasInput = hasInputTrait(differences.getOldModel(), oldMember);
         boolean newHasInput = hasInputTrait(differences.getNewModel(), newMember);
         ShapeId shape = change.getShapeId();
+        SourceLocation oldMemberSourceLocation = oldMember.getSourceLocation();
         Shape newTarget = differences.getNewModel().expectShape(newMember.getTarget());
         List<ValidationEvent> eventsToAdd = new ArrayList<>();
+        SourceLocation newMemberContainerSource = differences.getNewModel().expectShape(newMember.getContainer())
+                .getSourceLocation();
 
         if (oldHasInput && !newHasInput) {
             // If there was an input trait before, but not now, then the nullability must have
             // changed from nullable to non-nullable.
-            eventsToAdd.add(emit(Severity.ERROR, "RemovedInputTrait", shape, message,
+            eventsToAdd.add(emit(Severity.ERROR, "RemovedInputTrait", shape, newMemberContainerSource, message,
                             "The @input trait was removed from " + newMember.getContainer()));
         } else if (!oldHasInput && newHasInput) {
             // If there was no input trait before, but there is now, then the nullability must have
             // changed from non-nullable to nullable.
-            eventsToAdd.add(emit(Severity.DANGER, "AddedInputTrait", shape, message,
+            eventsToAdd.add(emit(Severity.DANGER, "AddedInputTrait", shape, newMemberContainerSource, message,
                             "The @input trait was added to " + newMember.getContainer()));
         } else if (!newHasInput) {
             // Can't add clientOptional to a preexisting required member.
             if (change.isTraitAdded(ClientOptionalTrait.ID) && change.isTraitInBoth(RequiredTrait.ID)) {
-                eventsToAdd.add(emit(Severity.ERROR, "AddedClientOptionalTrait", shape, message,
-                                "The @clientOptional trait was added to a @required member."));
+                eventsToAdd.add(emit(Severity.ERROR, "AddedClientOptionalTrait", shape, oldMemberSourceLocation,
+                        message, "The @clientOptional trait was added to a @required member."));
             }
             // Can't add required to a member unless the member is marked as @clientOptional or part of @input.
             if (change.isTraitAdded(RequiredTrait.ID) && !newMember.hasTrait(ClientOptionalTrait.ID)) {
-                eventsToAdd.add(emit(Severity.ERROR, "AddedRequiredTrait", shape, message,
+                eventsToAdd.add(emit(Severity.ERROR, "AddedRequiredTrait", shape, oldMemberSourceLocation, message,
                                 "The @required trait was added to a member."));
             }
             // Can't add the default trait to a member unless the member was previously required.
             if (change.isTraitAdded(DefaultTrait.ID) && !change.isTraitRemoved(RequiredTrait.ID)) {
-                eventsToAdd.add(emit(Severity.ERROR, "AddedDefaultTrait", shape, message,
+                eventsToAdd.add(emit(Severity.ERROR, "AddedDefaultTrait", shape, oldMemberSourceLocation, message,
                                 "The @default trait was added to a member that was not previously @required."));
             }
             // Can only remove the required trait if the member was nullable or replaced by the default trait.
@@ -131,20 +135,21 @@ public class ChangedNullability extends AbstractDiffEvaluator {
                     && !oldMember.hasTrait(ClientOptionalTrait.ID)) {
                 if (newTarget.isStructureShape() || newTarget.isUnionShape()) {
                     eventsToAdd.add(emit(Severity.WARNING, "RemovedRequiredTrait.StructureOrUnion", shape,
-                                    message, "The @required trait was removed from a member that targets a "
-                                    + newTarget.getType() + ". This is backward compatible in generators that "
-                                    + "always treat structures and unions as optional (e.g., AWS generators)"));
+                                    oldMemberSourceLocation, message, "The @required trait was removed from a member "
+                                    + "that targets a " + newTarget.getType() + ". This is backward compatible in "
+                                    + "generators that always treat structures and unions as optional "
+                                    + "(e.g., AWS generators)"));
                 } else {
-                    eventsToAdd.add(emit(Severity.ERROR, "RemovedRequiredTrait", shape, message,
-                                    "The @required trait was removed and not replaced with the @default trait and "
-                                    + "@addedDefault trait."));
+                    eventsToAdd.add(emit(Severity.ERROR, "RemovedRequiredTrait", shape, oldMemberSourceLocation,
+                                    message, "The @required trait was removed and not replaced with the @default "
+                                    + "trait and @addedDefault trait."));
                 }
             }
         }
 
         // If not specific event was emitted, emit a generic event.
         if (eventsToAdd.isEmpty()) {
-            eventsToAdd.add(emit(Severity.ERROR, null, shape, null, message));
+            eventsToAdd.add(emit(Severity.ERROR, null, shape, oldMemberSourceLocation, null, message));
         }
 
         events.addAll(eventsToAdd);
@@ -158,6 +163,7 @@ public class ChangedNullability extends AbstractDiffEvaluator {
             Severity severity,
             String eventIdSuffix,
             ShapeId shape,
+            SourceLocation sourceLocation,
             String prefixMessage,
             String message
     ) {
@@ -166,6 +172,7 @@ public class ChangedNullability extends AbstractDiffEvaluator {
         return ValidationEvent.builder()
                 .id(actualId)
                 .shapeId(shape)
+                .sourceLocation(sourceLocation)
                 .message(actualMessage)
                 .severity(severity)
                 .build();
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ModifiedTrait.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ModifiedTrait.java
index 3a792e724..fc51e84a0 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ModifiedTrait.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/ModifiedTrait.java
@@ -273,6 +273,7 @@ public final class ModifiedTrait extends AbstractDiffEvaluator {
                                                          .id(getValidationEventId(this, trait))
                                                          .severity(severity)
                                                          .shape(shape)
+                                                         .sourceLocation(left.getSourceLocation())
                                                          .message(message)
                                                          .build());
             }
diff --git a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedTraitDefinition.java b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedTraitDefinition.java
index 87fe377e5..5364f6f8d 100644
--- a/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedTraitDefinition.java
+++ b/smithy-diff/src/main/java/software/amazon/smithy/diff/evaluators/RemovedTraitDefinition.java
@@ -34,6 +34,7 @@ public final class RemovedTraitDefinition extends AbstractDiffEvaluator {
                         .id(getEventId())
                         .severity(Severity.ERROR)
                         .shape(shape)
+                        .sourceLocation(shape.expectTrait(TraitDefinition.class).getSourceLocation())
                         .message(String.format("Trait definition `%s` was removed", shape.getId()))
                         .build())
                 .collect(Collectors.toList());
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EnumShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EnumShape.java
index 95f8d7666..39eeb3c02 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EnumShape.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EnumShape.java
@@ -24,6 +24,7 @@ import java.util.function.Consumer;
 import java.util.logging.Logger;
 import java.util.regex.Pattern;
 import software.amazon.smithy.model.SourceException;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.traits.DeprecatedTrait;
 import software.amazon.smithy.model.traits.DocumentationTrait;
 import software.amazon.smithy.model.traits.EnumDefinition;
@@ -517,5 +518,10 @@ public final class EnumShape extends StringShape {
             members(NamedMemberUtils.flattenMixins(members.get(), getMixins(), getId(), getSourceLocation()));
             return (Builder) super.flattenMixins();
         }
+
+        @Override
+        public Builder source(SourceLocation source) {
+            return (Builder) super.source(source);
+        }
     }
 }
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java
index 08ae9d87f..ec6283390 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java
@@ -647,7 +647,11 @@ public final class SmithyIdlModelSerializer {
             codeWriter.writeOptionalIdList("operations", shape.getIntroducedOperations());
             codeWriter.writeOptionalIdList("collectionOperations", shape.getCollectionOperations());
             codeWriter.writeOptionalIdList("resources", shape.getIntroducedResources());
-
+            if (shape.hasProperties()) {
+              codeWriter.openBlock("properties: {");
+              shape.getProperties().forEach((name, shapeId) -> codeWriter.write("$L: $I", name, shapeId));
+              codeWriter.closeBlock("}");
+            }
             codeWriter.closeBlock("}");
             codeWriter.write("");
             return null;


Test patch:
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedOperationErrorTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedOperationErrorTest.java
index 38fa859a3..032c6f12f 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedOperationErrorTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedOperationErrorTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.StructureShape;
@@ -31,13 +32,17 @@ import software.amazon.smithy.model.validation.ValidationEvent;
 public class AddedOperationErrorTest {
     @Test
     public void detectsAddedErrors() {
+        SourceLocation s1 = new SourceLocation("main.smithy", 1, 2);
+        SourceLocation s2 = new SourceLocation("main.smithy", 3, 4);
         Shape e1 = StructureShape.builder()
                 .id("foo.baz#E1")
                 .addTrait(new ErrorTrait("client"))
+                .source(s1)
                 .build();
         Shape e2 = StructureShape.builder()
                 .id("foo.baz#E2")
                 .addTrait(new ErrorTrait("client"))
+                .source(s2)
                 .build();
         OperationShape operation1 = OperationShape.builder().id("foo.baz#Operation").build();
         Shape operation2 = operation1.toBuilder().addError(e1.getId()).addError(e2.getId()).build();
@@ -47,6 +52,8 @@ public class AddedOperationErrorTest {
 
         assertThat(TestHelper.findEvents(events, "AddedOperationError").size(), equalTo(2));
         assertThat(TestHelper.findEvents(events, "AddedOperationError.E1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "AddedOperationError.E1").stream().findFirst().get().getSourceLocation(), equalTo(s1));
         assertThat(TestHelper.findEvents(events, "AddedOperationError.E2").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "AddedOperationError.E2").stream().findFirst().get().getSourceLocation(), equalTo(s2));
     }
 }
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedRequiredMemberTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedRequiredMemberTest.java
index ff5fc3587..3f76ce5b6 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedRequiredMemberTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedRequiredMemberTest.java
@@ -5,6 +5,7 @@ import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.node.StringNode;
+import software.amazon.smithy.model.shapes.MemberShape;
 import software.amazon.smithy.model.shapes.StringShape;
 import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.traits.DefaultTrait;
@@ -20,8 +21,15 @@ public class AddedRequiredMemberTest {
         StringShape s = StringShape.builder().id("smithy.example#Str").build();
         StructureShape a = StructureShape.builder().id("smithy.example#A")
                 .build();
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
+        MemberShape member = MemberShape.builder()
+                .id(a.getId().withMember("foo"))
+                .target(s.getId())
+                .addTrait(new RequiredTrait())
+                .source(source)
+                .build();
         StructureShape b = StructureShape.builder().id("smithy.example#A")
-                .addMember("foo", s.getId(), b2 -> b2.addTrait(new RequiredTrait()))
+                .addMember(member)
                 .build();
         Model model1 = Model.builder().addShapes(s, a).build();
         Model model2 = Model.builder().addShapes(s, b).build();
@@ -34,6 +42,8 @@ public class AddedRequiredMemberTest {
         assertThat(TestHelper.findEvents(result.getDiffEvents(), "AddedRequiredMember").get(0).getMessage(),
                 equalTo("Adding a new member with the `required` trait " +
                         "but not the `default` trait is backwards-incompatible."));
+        assertThat(TestHelper.findEvents(result.getDiffEvents(), "AddedRequiredMember").get(0).getSourceLocation(),
+                equalTo(source));
     }
 
     @Test
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedTraitDefinitionTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedTraitDefinitionTest.java
index bdff64d47..f4c85d1ba 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedTraitDefinitionTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/AddedTraitDefinitionTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.StringShape;
 import software.amazon.smithy.model.traits.TraitDefinition;
@@ -30,9 +31,11 @@ import software.amazon.smithy.model.validation.ValidationEvent;
 public class AddedTraitDefinitionTest {
     @Test
     public void detectsAddedTraitDefinition() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         Shape definition = StringShape.builder()
                 .id("foo.baz#bam")
                 .addTrait(TraitDefinition.builder().build())
+                .source(source)
                 .build();
 
         Model modelA = Model.assembler().assemble().unwrap();
@@ -40,5 +43,6 @@ public class AddedTraitDefinitionTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "AddedTraitDefinition").size(), equalTo(1));
+        assertThat(events.get(0).getSourceLocation(), equalTo(source));
     }
 }
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 ec3bf1853..a1d6fc755 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
@@ -16,16 +16,15 @@
 package software.amazon.smithy.diff.evaluators;
 
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.stringContainsInOrder;
 
 import java.util.List;
 
-import org.hamcrest.core.Every;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.EnumShape;
 import software.amazon.smithy.model.shapes.StringShape;
 import software.amazon.smithy.model.traits.EnumDefinition;
@@ -36,6 +35,7 @@ import software.amazon.smithy.model.validation.ValidationEvent;
 public class ChangedEnumTraitTest {
     @Test
     public void detectsAppendedEnums() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -47,6 +47,7 @@ public class ChangedEnumTraitTest {
                 .addTrait(EnumTrait.builder()
                         .addEnum(EnumDefinition.builder().value("foo").build())
                         .addEnum(EnumDefinition.builder().value("baz").build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
@@ -55,10 +56,12 @@ public class ChangedEnumTraitTest {
 
         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));
     }
 
     @Test
     public void detectsAppendedEnumsEnumTraitNoNameToEnumShape() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -71,6 +74,7 @@ public class ChangedEnumTraitTest {
                 .id("foo.baz#Baz")
                 .addMember("foo", "foo")
                 .addMember("baz", "baz")
+                .source(source)
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
@@ -80,12 +84,17 @@ public class ChangedEnumTraitTest {
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSeverity(),
                 equalTo(Severity.ERROR));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(1).getSeverity(),
                 equalTo(Severity.NOTE));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").get(1).getSourceLocation(),
+                equalTo(source));
     }
 
     @Test
     public void detectsAppendedEnumsEnumTraitWithNameToEnumShape() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -93,30 +102,36 @@ public class ChangedEnumTraitTest {
                                 .name("foo")
                                 .value("foo")
                                 .build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         EnumShape s2 = EnumShape.builder()
                 .id("foo.baz#Baz")
                 .addMember("foo", "foo")
                 .addMember("baz", "baz")
+                .source(source)
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
         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).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").stream()
                 .allMatch(e -> e.getSeverity() == Severity.NOTE), equalTo(true));
     }
 
     @Test
     public void detectsRemovedEnums() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
                         .addEnum(EnumDefinition.builder().value("foo").build())
                         .addEnum(EnumDefinition.builder().value("baz").build())
                         .addEnum(EnumDefinition.builder().value("bat").build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         StringShape s2 = StringShape.builder()
@@ -130,12 +145,18 @@ public class ChangedEnumTraitTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.2").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.2").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(2));
     }
 
     @Test
     public void detectsRemovedEnumsEnumTraitNoNameToEnumShape() {
+        SourceLocation beforeSource = new SourceLocation("before.smithy", 1, 2);
+        SourceLocation afterSource = new SourceLocation("after.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -145,24 +166,31 @@ public class ChangedEnumTraitTest {
                         .addEnum(EnumDefinition.builder()
                                 .value("baz")
                                 .build())
+                        .sourceLocation(beforeSource)
                         .build())
                 .build();
         EnumShape s2 = EnumShape.builder()
                 .id("foo.baz#Baz")
                 .addMember("foo", "foo")
+                .source(afterSource)
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").get(0).getSourceLocation(),
+                equalTo(beforeSource));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSourceLocation(),
+                equalTo(afterSource));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").stream()
                 .allMatch(e -> e.getSeverity() == Severity.ERROR), equalTo(true));
     }
 
     @Test
     public void detectsRemovedEnumsEnumTraitWithNameToEnumShape() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -174,6 +202,7 @@ public class ChangedEnumTraitTest {
                                 .name("baz")
                                 .value("baz")
                                 .build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         EnumShape s2 = EnumShape.builder()
@@ -185,12 +214,15 @@ public class ChangedEnumTraitTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").stream()
                 .allMatch(e -> e.getSeverity() == Severity.ERROR), equalTo(true));
     }
 
     @Test
     public void detectsRenamedEnums() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -204,18 +236,24 @@ public class ChangedEnumTraitTest {
                         .addEnum(EnumDefinition.builder().value("foo").name("NEW1").build())
                         .addEnum(EnumDefinition.builder().value("baz").name("NEW2").build())
                         .build())
+                .source(source)
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.1").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(2));
     }
 
     @Test
     public void detectsRenamedEnumsEnumTraitNoNameToEnumShape() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -227,18 +265,22 @@ public class ChangedEnumTraitTest {
         EnumShape s2 = EnumShape.builder()
                 .id("foo.baz#Baz")
                 .addMember("NEW", "foo")
+                .source(source)
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSeverity(),
                 equalTo(Severity.ERROR));
     }
 
     @Test
     public void detectsRenamedEnumsEnumTraitWithNameToEnumShape() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -251,22 +293,27 @@ public class ChangedEnumTraitTest {
         EnumShape s2 = EnumShape.builder()
                 .id("foo.baz#Baz")
                 .addMember("NEW", "foo")
+                .source(source)
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSeverity(),
                 equalTo(Severity.ERROR));
     }
 
     @Test
     public void detectsInsertedEnums() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
                         .addEnum(EnumDefinition.builder().value("foo").build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         StringShape s2 = StringShape.builder()
@@ -282,16 +329,22 @@ public class ChangedEnumTraitTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.1").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(2));
     }
 
     @Test
     public void detectsInsertedEnumsBeforeAppendedEnums() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
                         .addEnum(EnumDefinition.builder().value("foo").build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         StringShape s2 = StringShape.builder()
@@ -308,18 +361,24 @@ public class ChangedEnumTraitTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").get(0)
+                .getSourceLocation(), equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.1").get(0)
+                .getSourceLocation(), equalTo(source));
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(2));
     }
 
     @Test
     public void detectsInsertedEnumsEnumTraitNoNameToEnumShape() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
                         .addEnum(EnumDefinition.builder()
                                 .value("foo")
                                 .build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         EnumShape s2 = EnumShape.builder()
@@ -333,12 +392,15 @@ public class ChangedEnumTraitTest {
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(2));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").stream()
                 .allMatch(e -> e.getSeverity() == Severity.ERROR), equalTo(true));
     }
 
     @Test
     public void detectsInsertedEnumsEnumTraitWithNameToEnumShape() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -346,6 +408,7 @@ public class ChangedEnumTraitTest {
                                 .name("foo")
                                 .value("foo")
                                 .build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         EnumShape s2 = EnumShape.builder()
@@ -358,18 +421,23 @@ public class ChangedEnumTraitTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.OrderChanged.0").stream()
                 .allMatch(e -> e.getSeverity() == Severity.ERROR), equalTo(true));
     }
 
     @Test
     public void detectsAppendedEnumsAfterRemovedEnums() {
+        SourceLocation beforeSource = new SourceLocation("before.smithy", 1, 2);
+        SourceLocation afterSource   = new SourceLocation("after.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
                         .addEnum(EnumDefinition.builder().value("old1").build())
                         .addEnum(EnumDefinition.builder().value("old2").build())
                         .addEnum(EnumDefinition.builder().value("old3").build())
+                        .sourceLocation(beforeSource)
                         .build())
                 .build();
         StringShape s2 = StringShape.builder()
@@ -378,6 +446,7 @@ public class ChangedEnumTraitTest {
                         .addEnum(EnumDefinition.builder().value("old1").build())
                         .addEnum(EnumDefinition.builder().value("old3").build())
                         .addEnum(EnumDefinition.builder().value("new1").build())
+                        .sourceLocation(afterSource)
                         .build())
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
@@ -387,18 +456,24 @@ 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(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(appendedEvent.getSeverity(), equalTo(Severity.NOTE));
         assertThat(appendedEvent.getMessage(), stringContainsInOrder("Enum value `new1` was appended"));
+        assertThat(appendedEvent.getSourceLocation(), equalTo(afterSource));
     }
 
     @Test
     public void detectsAppendedEnumsAfterRemovedEnumsEnumTraitNoNameToEnumShape() {
+        SourceLocation beforeSource = new SourceLocation("before.smithy", 1, 2);
+        SourceLocation afterSource = new SourceLocation("after.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -411,6 +486,7 @@ public class ChangedEnumTraitTest {
                         .addEnum(EnumDefinition.builder()
                                 .value("old3")
                                 .build())
+                        .sourceLocation(beforeSource)
                         .build())
                 .build();
         EnumShape s2 = EnumShape.builder()
@@ -418,6 +494,7 @@ public class ChangedEnumTraitTest {
                 .addMember("old1", "old1")
                 .addMember("old3", "old3")
                 .addMember("new1", "new1")
+                .source(afterSource)
                 .build();
         Model modelA = Model.assembler().addShape(s1).assemble().unwrap();
         Model modelB = Model.assembler().addShape(s2).assemble().unwrap();
@@ -425,8 +502,14 @@ public class ChangedEnumTraitTest {
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(4));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.0").get(0).getSourceLocation(),
+                equalTo(afterSource));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").get(0).getSourceLocation(),
+                equalTo(beforeSource));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.2").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.NameChanged.2").get(0).getSourceLocation(),
+                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()
@@ -435,6 +518,7 @@ public class ChangedEnumTraitTest {
 
     @Test
     public void detectsAppendedEnumsAfterRemovedEnumsEnumTraitWithNameToEnumShape() {
+        SourceLocation source = new SourceLocation("main.smithy", 1, 2);
         StringShape s1 = StringShape.builder()
                 .id("foo.baz#Baz")
                 .addTrait(EnumTrait.builder()
@@ -450,6 +534,7 @@ public class ChangedEnumTraitTest {
                                 .name("old3")
                                 .value("old3")
                                 .build())
+                        .sourceLocation(source)
                         .build())
                 .build();
         EnumShape s2 = EnumShape.builder()
@@ -464,6 +549,8 @@ public class ChangedEnumTraitTest {
 
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait").size(), equalTo(2));
         assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedEnumTrait.Removed.1").get(0).getSourceLocation(),
+                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()
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedMemberTargetTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedMemberTargetTest.java
index d1d16ae48..a405893f1 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedMemberTargetTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedMemberTargetTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.ListShape;
 import software.amazon.smithy.model.shapes.MemberShape;
 import software.amazon.smithy.model.shapes.Shape;
@@ -38,17 +39,21 @@ import software.amazon.smithy.model.validation.ValidationEvent;
 public class ChangedMemberTargetTest {
     @Test
     public void detectsIncompatibleTypeChanges() {
+        SourceLocation source = new SourceLocation("a.smithy", 2, 3);
         Shape shape1 = StringShape.builder().id("foo.baz#String").build();
         MemberShape member1 = MemberShape.builder().id("foo.baz#List$member").target(shape1.getId()).build();
         ListShape list1 = ListShape.builder().id("foo.baz#List").member(member1).build();
         Shape shape2 = TimestampShape.builder().id("foo.baz#Timestamp").build();
-        MemberShape member2 = MemberShape.builder().id("foo.baz#List$member").target(shape2.getId()).build();
+        MemberShape member2 = MemberShape.builder().id("foo.baz#List$member").target(shape2.getId()).source(source)
+                .build();
         ListShape list2 = ListShape.builder().id("foo.baz#List").member(member2).build();
         Model modelA = Model.assembler().addShapes(shape1, shape2, member1, list1).assemble().unwrap();
         Model modelB = Model.assembler().addShapes(shape1, shape2, member2, list2).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "ChangedMemberTarget").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "ChangedMemberTarget").get(0).getSourceLocation(),
+                equalTo(source));
         assertThat(TestHelper.findEvents(events, member2.getId()).size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1));
     }
@@ -152,7 +157,7 @@ public class ChangedMemberTargetTest {
 
     @Test
     public void detectsTraitAddedToMemberTarget() {
-                StringShape shape1 = StringShape.builder().id("foo.baz#String1").build();
+        StringShape shape1 = StringShape.builder().id("foo.baz#String1").build();
         MemberShape member1 = MemberShape.builder().id("foo.baz#List$member").target(shape1.getId()).build();
         ListShape list1 = ListShape.builder().id("foo.baz#List").member(member1).build();
 
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedNullabilityTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedNullabilityTest.java
index 0205a2990..c14298087 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedNullabilityTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ChangedNullabilityTest.java
@@ -11,6 +11,7 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.shapes.MemberShape;
 import software.amazon.smithy.model.shapes.ModelSerializer;
@@ -18,6 +19,7 @@ import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.shapes.StringShape;
 import software.amazon.smithy.model.shapes.StructureShape;
+import software.amazon.smithy.model.shapes.UnionShape;
 import software.amazon.smithy.model.traits.BoxTrait;
 import software.amazon.smithy.model.traits.ClientOptionalTrait;
 import software.amazon.smithy.model.traits.DefaultTrait;
@@ -75,10 +77,12 @@ public class ChangedNullabilityTest {
 
     @Test
     public void detectsInvalidAdditionOfDefaultTrait() {
+        SourceLocation source = new SourceLocation("a.smithy", 5, 6);
         StringShape s = StringShape.builder().id("smithy.example#Str").build();
         StructureShape a = StructureShape.builder()
                 .id("smithy.example#A")
-                .addMember("foo", s.getId())
+                .addMember("foo", s.getId(), builder -> builder.source(source))
+                .source(source)
                 .build();
         StructureShape b = StructureShape.builder()
                 .id("smithy.example#A")
@@ -93,6 +97,7 @@ public class ChangedNullabilityTest {
                            .filter(event -> event.getSeverity() == Severity.ERROR)
                            .filter(event -> event.getId().equals("ChangedNullability.AddedDefaultTrait"))
                            .filter(event -> event.getShapeId().get().equals(a.getAllMembers().get("foo").getId()))
+                           .filter(event -> event.getSourceLocation().equals(source))
                            .filter(event -> event.getMessage().contains("The @default trait was added to a member that "
                                                                         + "was not previously @required"))
                            .count(), equalTo(1L));
@@ -122,17 +127,21 @@ public class ChangedNullabilityTest {
 
     @Test
     public void detectsInvalidRemovalOfRequired() {
+        SourceLocation memberSource = new SourceLocation("a.smithy", 7, 7);
         StringShape s = StringShape.builder().id("smithy.example#Str").build();
+        MemberShape requiredM = MemberShape.builder().id("smithy.example#A$foo").target(s).addTrait(new RequiredTrait())
+                .source(memberSource).build();
+        MemberShape m = MemberShape.builder().id("smithy.example#A$foo").target(s).source(memberSource).build();
         StructureShape a = StructureShape.builder()
                 .id("smithy.example#A")
-                .addMember("foo", s.getId(), b1 -> b1.addTrait(new RequiredTrait()))
+                .addMember(requiredM)
                 .build();
         StructureShape b = StructureShape.builder()
                 .id("smithy.example#A")
-                .addMember("foo", s.getId())
+                .addMember(m)
                 .build();
-        Model model1 = Model.builder().addShapes(s, a).build();
-        Model model2 = Model.builder().addShapes(s, b).build();
+        Model model1 = Model.builder().addShapes(s, a, requiredM).build();
+        Model model2 = Model.builder().addShapes(s, b, m).build();
         ModelDiff.Result result = ModelDiff.builder().oldModel(model1).newModel(model2).compare();
 
         assertThat(result.isDiffBreaking(), is(true));
@@ -140,6 +149,7 @@ public class ChangedNullabilityTest {
                            .filter(event -> event.getSeverity() == Severity.ERROR)
                            .filter(event -> event.getId().equals("ChangedNullability.RemovedRequiredTrait"))
                            .filter(event -> event.getShapeId().get().equals(a.getAllMembers().get("foo").getId()))
+                           .filter(event -> event.getSourceLocation().equals(memberSource))
                            .filter(event -> event.getMessage().contains("The @required trait was removed and not "
                                                                         + "replaced with the @default trait"))
                            .count(), equalTo(1L));
@@ -147,7 +157,9 @@ public class ChangedNullabilityTest {
 
     @Test
     public void detectAdditionOfRequiredTrait() {
-        MemberShape member1 = MemberShape.builder().id("foo.baz#Baz$bam").target("foo.baz#String").build();
+        SourceLocation memberSource = new SourceLocation("a.smithy", 5, 6);
+        MemberShape member1 = MemberShape.builder().id("foo.baz#Baz$bam").target("foo.baz#String").source(memberSource)
+                .build();
         MemberShape member2 = member1.toBuilder().addTrait(new RequiredTrait()).build();
         StructureShape shapeA1 = StructureShape.builder().id("foo.baz#Baz").addMember(member1).build();
         StructureShape shapeA2 = StructureShape.builder().id("foo.baz#Baz").addMember(member2).build();
@@ -159,16 +171,19 @@ public class ChangedNullabilityTest {
         assertThat(events.stream()
                            .filter(event -> event.getSeverity() == Severity.ERROR)
                            .filter(event -> event.getId().equals("ChangedNullability.AddedRequiredTrait"))
+                           .filter(event -> event.getSourceLocation().equals(memberSource))
                            .filter(event -> event.getMessage().contains("The @required trait was added to a member"))
                            .count(), equalTo(1L));
     }
 
     @Test
     public void detectAdditionOfClientOptionalTrait() {
+        SourceLocation memberSource = new SourceLocation("a.smithy", 5, 6);
         MemberShape member1 = MemberShape.builder()
                 .id("foo.baz#Baz$bam")
                 .target("foo.baz#String")
                 .addTrait(new RequiredTrait())
+                .source(memberSource)
                 .build();
         MemberShape member2 = member1.toBuilder().addTrait(new ClientOptionalTrait()).build();
         StructureShape shapeA1 = StructureShape.builder().id("foo.baz#Baz").addMember(member1).build();
@@ -181,6 +196,7 @@ public class ChangedNullabilityTest {
         assertThat(events.stream()
                            .filter(event -> event.getSeverity() == Severity.ERROR)
                            .filter(event -> event.getId().equals("ChangedNullability.AddedClientOptionalTrait"))
+                           .filter(event -> event.getSourceLocation().equals(memberSource))
                            .filter(event -> event.getMessage().contains("The @clientOptional trait was added to a "
                                                                         + "@required member"))
                            .count(), equalTo(1L));
@@ -188,6 +204,7 @@ public class ChangedNullabilityTest {
 
     @Test
     public void detectsAdditionOfInputTrait() {
+        SourceLocation structureSource = new SourceLocation("a.smithy", 5, 6);
         MemberShape member1 = MemberShape.builder()
                 .id("foo.baz#Baz$bam")
                 .target("foo.baz#String")
@@ -196,7 +213,7 @@ public class ChangedNullabilityTest {
         MemberShape member2 = member1.toBuilder().addTrait(new DocumentationTrait("docs")).build();
         StructureShape shapeA1 = StructureShape.builder().id("foo.baz#Baz").addMember(member1).build();
         StructureShape shapeA2 = StructureShape.builder().id("foo.baz#Baz").addMember(member2)
-                                                         .addTrait(new InputTrait()).build();
+                                                         .addTrait(new InputTrait()).source(structureSource).build();
         StringShape target = StringShape.builder().id("foo.baz#String").build();
         Model modelA = Model.assembler().addShapes(shapeA1, member1, target).assemble().unwrap();
         Model modelB = Model.assembler().addShapes(shapeA2, member2, target).assemble().unwrap();
@@ -205,15 +222,18 @@ public class ChangedNullabilityTest {
         assertThat(events.stream()
                            .filter(event -> event.getSeverity() == Severity.DANGER)
                            .filter(event -> event.getId().equals("ChangedNullability.AddedInputTrait"))
+                           .filter(event -> event.getSourceLocation().equals(structureSource))
                            .filter(event -> event.getMessage().contains("The @input trait was added to"))
                            .count(), equalTo(1L));
         assertThat(events.stream()
                             .filter(event -> event.getId().contains("ChangedNullability"))
+                            .filter(event -> event.getSourceLocation().equals(structureSource))
                             .count(), equalTo(1L));
     }
 
     @Test
     public void detectsRemovalOfInputTrait() {
+        SourceLocation structureSource = new SourceLocation("a.smithy", 5, 6);
         MemberShape member1 = MemberShape.builder()
                 .id("foo.baz#Baz$bam")
                 .target("foo.baz#String")
@@ -230,6 +250,7 @@ public class ChangedNullabilityTest {
         StructureShape shapeA2 = StructureShape.builder()
                 .id("foo.baz#Baz")
                 .addMember(member2)
+                .source(structureSource)
                 .build();
         StringShape target = StringShape.builder().id("foo.baz#String").build();
         Model modelA = Model.assembler().addShapes(shapeA1, member1, target).assemble().unwrap();
@@ -239,6 +260,7 @@ public class ChangedNullabilityTest {
         assertThat(events.stream()
                            .filter(event -> event.getSeverity() == Severity.ERROR)
                            .filter(event -> event.getId().equals("ChangedNullability.RemovedInputTrait"))
+                           .filter(event -> event.getSourceLocation().equals(structureSource))
                            .filter(event -> event.getMessage().contains("The @input trait was removed from"))
                            .count(), equalTo(1L));
     }
@@ -324,15 +346,12 @@ public class ChangedNullabilityTest {
 
     @Test
     public void specialHandlingForRequiredStructureMembers() {
-        String originalModel =
-                "$version: \"2.0\"\n"
-                + "namespace smithy.example\n"
-                + "structure Baz {}\n"
-                + "structure Foo {\n"
-                + "    @required\n"
-                + "    baz: Baz\n"
-                + "}\n";
-        Model oldModel = Model.assembler().addUnparsedModel("foo.smithy", originalModel).assemble().unwrap();
+        SourceLocation memberSource = new SourceLocation("a.smithy", 8, 2);
+        StructureShape structBaz = StructureShape.builder().id("smithy.example#Baz").build();
+        MemberShape memberBaz = MemberShape.builder().id("smithy.example#Foo$baz").addTrait(new RequiredTrait())
+                .target(structBaz).source(memberSource).build();
+        StructureShape structFoo = StructureShape.builder().id("smithy.example#Foo").addMember(memberBaz).build();
+        Model oldModel = Model.assembler().addShapes(structFoo, structBaz, memberBaz).assemble().unwrap();
         Model newModel = ModelTransformer.create().replaceShapes(oldModel, ListUtils.of(
                 Shape.shapeToBuilder(oldModel.expectShape(ShapeId.from("smithy.example#Foo$baz")))
                         .removeTrait(RequiredTrait.ID)
@@ -343,22 +362,19 @@ public class ChangedNullabilityTest {
 
         assertThat(events, hasSize(1));
         assertThat(events.get(0).getSeverity(), is(Severity.WARNING));
+        assertThat(events.get(0).getSourceLocation(), equalTo(memberSource));
     }
 
     @Test
     public void specialHandlingForRequiredUnionMembers() {
-        String originalModel =
-                "$version: \"2.0\"\n"
-                + "namespace smithy.example\n"
-                + "union Baz {\n"
-                + "    a: String\n"
-                + "    b: String\n"
-                + "}\n"
-                + "structure Foo {\n"
-                + "    @required\n"
-                + "    baz: Baz\n"
-                + "}\n";
-        Model oldModel = Model.assembler().addUnparsedModel("foo.smithy", originalModel).assemble().unwrap();
+        SourceLocation memberSource = new SourceLocation("b.smithy", 3, 4);
+        MemberShape memberA = MemberShape.builder().id("smithy.example#Baz$a").target("smithy.api#String").build();
+        MemberShape memberB = MemberShape.builder().id("smithy.example#Baz$B").target("smithy.api#String").build();
+        UnionShape union = UnionShape.builder().id("smithy.example#Baz").addMember(memberA).addMember(memberB).build();
+        MemberShape memberBaz = MemberShape.builder().id("smithy.example#Foo$baz").addTrait(new RequiredTrait())
+                .target(union).source(memberSource).build();
+        StructureShape struct = StructureShape.builder().id("smithy.example#Foo").addMember(memberBaz).build();
+        Model oldModel = Model.assembler().addShapes(union, struct, memberA, memberB, memberBaz).assemble().unwrap();
         Model newModel = ModelTransformer.create().replaceShapes(oldModel, ListUtils.of(
                 Shape.shapeToBuilder(oldModel.expectShape(ShapeId.from("smithy.example#Foo$baz")))
                         .removeTrait(RequiredTrait.ID)
@@ -369,6 +385,7 @@ public class ChangedNullabilityTest {
 
         assertThat(events, hasSize(1));
         assertThat(events.get(0).getSeverity(), is(Severity.WARNING));
+        assertThat(events.get(0).getSourceLocation(), equalTo(memberSource));
     }
 
     @Test
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ModifiedTraitTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ModifiedTraitTest.java
index 1034860b1..e27be2901 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ModifiedTraitTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ModifiedTraitTest.java
@@ -153,7 +153,11 @@ public class ModifiedTraitTest {
 
         assertThat(events, hasSize(3));
         assertThat(events.stream().filter(e -> e.getSeverity() == Severity.WARNING).count(), equalTo(1L));
+        assertThat(events.stream().filter(e -> e.getSourceLocation().getFilename().endsWith("a.smithy")).count(),
+                equalTo(1L));
         assertThat(events.stream().filter(e -> e.getSeverity() == Severity.NOTE).count(), equalTo(2L));
+        assertThat(events.stream().filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(),
+                equalTo(2L));
 
         assertThat(messages, containsInAnyOrder(
                 "Changed trait `smithy.example#b` from \"hello\" to \"hello!\"",
@@ -178,6 +182,8 @@ public class ModifiedTraitTest {
         assertThat(events, hasSize(2));
         assertThat(events.stream().filter(e -> e.getSeverity() == Severity.ERROR).count(), equalTo(1L));
         assertThat(events.stream().filter(e -> e.getSeverity() == Severity.WARNING).count(), equalTo(1L));
+        assertThat(events.stream().filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(),
+                equalTo(2L));
 
         assertThat(messages, containsInAnyOrder(
                 "Added trait contents to `smithy.example#aTrait` at path `/bar` with value \"no\"",
@@ -199,6 +205,10 @@ public class ModifiedTraitTest {
         List<String> messages = events.stream().map(ValidationEvent::getMessage).collect(Collectors.toList());
 
         assertThat(events, hasSize(4));
+        assertThat(events.stream().filter(e -> e.getMessage().contains("Removed"))
+                .filter(e -> e.getSourceLocation().getFilename().endsWith("a.smithy")).count(), equalTo(2L));
+        assertThat(events.stream().filter(e -> !e.getMessage().contains("Removed"))
+                .filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(), equalTo(2L));
         assertThat(messages, containsInAnyOrder(
                 "Changed trait contents of `smithy.example#aTrait` at path `/foo/1` from \"b\" to \"B\"",
                 "Added trait contents to `smithy.example#aTrait` at path `/foo/3` with value \"4\"",
@@ -222,6 +232,10 @@ public class ModifiedTraitTest {
         List<String> messages = events.stream().map(ValidationEvent::getMessage).collect(Collectors.toList());
 
         assertThat(events, hasSize(4));
+        assertThat(events.stream().filter(e -> e.getMessage().contains("Removed"))
+                .filter(e -> e.getSourceLocation().getFilename().endsWith("a.smithy")).count(), equalTo(2L));
+        assertThat(events.stream().filter(e -> !e.getMessage().contains("Removed"))
+                .filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(), equalTo(2L));
         assertThat(messages, containsInAnyOrder(
                 "Changed trait contents of `smithy.example#aTrait` at path `/foo/1` from \"b\" to \"B\"",
                 "Added trait contents to `smithy.example#aTrait` at path `/foo/3` with value \"4\"",
@@ -244,6 +258,10 @@ public class ModifiedTraitTest {
         List<String> messages = events.stream().map(ValidationEvent::getMessage).collect(Collectors.toList());
 
         assertThat(events, hasSize(4));
+        assertThat(events.stream().filter(e -> e.getMessage().contains("Removed"))
+                .filter(e -> e.getSourceLocation().getFilename().endsWith("a.smithy")).count(), equalTo(2L));
+        assertThat(events.stream().filter(e -> !e.getMessage().contains("Removed"))
+                .filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(), equalTo(2L));
         assertThat(messages, containsInAnyOrder(
                 "Changed trait contents of `smithy.example#aTrait` at path `/foo/bam` from \"b\" to \"B\"",
                 "Removed trait contents from `smithy.example#aTrait` at path `/foo`. Removed value: {\n    \"baz\": \"1\",\n    \"bam\": \"2\",\n    \"boo\": \"3\"\n}",
@@ -266,6 +284,8 @@ public class ModifiedTraitTest {
         List<String> messages = events.stream().map(ValidationEvent::getMessage).collect(Collectors.toList());
 
         assertThat(events, hasSize(2));
+        assertThat(events.stream().filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(),
+                equalTo(2L));
         assertThat(messages, containsInAnyOrder(
                 "Changed trait contents of `smithy.example#aTrait` at path `/baz/foo` from \"a\" to \"b\"",
                 "Changed trait contents of `smithy.example#aTrait` at path `/baz/baz` from \"a\" to \"b\""
@@ -286,6 +306,10 @@ public class ModifiedTraitTest {
         List<String> messages = events.stream().map(ValidationEvent::getMessage).collect(Collectors.toList());
 
         assertThat(events, hasSize(12));
+        assertThat(events.stream().filter(e -> e.getMessage().contains("Removed"))
+                .filter(e -> e.getSourceLocation().getFilename().endsWith("a.smithy")).count(), equalTo(3L));
+        assertThat(events.stream().filter(e -> e.getMessage().contains("Changed") || e.getMessage().contains("Added"))
+                .filter(e -> e.getSourceLocation().getFilename().endsWith("b.smithy")).count(), equalTo(9L));
         assertThat(messages, containsInAnyOrder(
                 "Added trait contents to `smithy.example#aTrait` at path `/a` with value \"a\"",
                 "Removed trait contents from `smithy.example#aTrait` at path `/b`. Removed value: \"a\"",
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedEntityBindingTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedEntityBindingTest.java
index 36c22dfa7..7d1ce1cc2 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedEntityBindingTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedEntityBindingTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.ResourceShape;
 import software.amazon.smithy.model.shapes.ServiceShape;
@@ -30,11 +31,13 @@ import software.amazon.smithy.model.validation.ValidationEvent;
 public class RemovedEntityBindingTest {
     @Test
     public void detectsRemovedOperationFromService() {
+        SourceLocation source = new SourceLocation("foo.smithy");
         OperationShape o = OperationShape.builder().id("foo.baz#Operation").build();
         ServiceShape service1 = ServiceShape.builder()
                 .version("1")
                 .id("foo.baz#Service")
                 .addOperation(o.getId())
+                .source(source)
                 .build();
         ServiceShape service2 = service1.toBuilder().clearOperations().build();
         Model modelA = Model.assembler().addShapes(service1, o).assemble().unwrap();
@@ -42,27 +45,32 @@ public class RemovedEntityBindingTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "RemovedOperationBinding.FromService.Operation").size(), equalTo(1));
+        assertThat(events.get(0).getSourceLocation(), equalTo(source));
     }
 
     @Test
     public void detectsRemovedOperationFromResource() {
+        SourceLocation source = new SourceLocation("foo.smithy");
         OperationShape o = OperationShape.builder().id("foo.baz#Operation").build();
-        ResourceShape r1 = ResourceShape.builder().id("foo.baz#Resource").addOperation(o.getId()).build();
+        ResourceShape r1 = ResourceShape.builder().id("foo.baz#Resource").addOperation(o.getId()).source(source).build();
         ResourceShape r2 = r1.toBuilder().clearOperations().build();
         Model modelA = Model.assembler().addShapes(r1, o).assemble().unwrap();
         Model modelB = Model.assembler().addShapes(r2, o).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "RemovedOperationBinding.FromResource.Operation").size(), equalTo(1));
+        assertThat(events.get(0).getSourceLocation(), equalTo(source));
     }
 
     @Test
     public void detectsRemovedResourceFromService() {
+        SourceLocation source = new SourceLocation("foo.smithy");
         ResourceShape r = ResourceShape.builder().id("foo.baz#Resource").build();
         ServiceShape service1 = ServiceShape.builder()
                 .id("foo.baz#Service")
                 .version("1")
                 .addResource(r.getId())
+                .source(source)
                 .build();
         ServiceShape service2 = service1.toBuilder().clearResources().build();
         Model modelA = Model.assembler().addShapes(service1, r).assemble().unwrap();
@@ -70,17 +78,20 @@ public class RemovedEntityBindingTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "RemovedResourceBinding.FromService.Resource").size(), equalTo(1));
+        assertThat(events.get(0).getSourceLocation(), equalTo(source));
     }
 
     @Test
     public void detectsRemovedResourceFromResource() {
+        SourceLocation source = new SourceLocation("foo.smithy");
         ResourceShape child = ResourceShape.builder().id("foo.baz#C").build();
-        ResourceShape p1 = ResourceShape.builder().id("foo.baz#P").addResource(child.getId()).build();
+        ResourceShape p1 = ResourceShape.builder().id("foo.baz#P").addResource(child.getId()).source(source).build();
         ResourceShape p2 = p1.toBuilder().clearResources().build();
         Model modelA = Model.assembler().addShapes(p1, child).assemble().unwrap();
         Model modelB = Model.assembler().addShapes(p2, child).assemble().unwrap();
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "RemovedResourceBinding.FromResource.C").size(), equalTo(1));
+        assertThat(events.get(0).getSourceLocation(), equalTo(source));
     }
 }
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationErrorTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationErrorTest.java
index 3c6ff908a..70af4d2f8 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationErrorTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedOperationErrorTest.java
@@ -23,6 +23,7 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.StructureShape;
@@ -32,6 +33,7 @@ import software.amazon.smithy.model.validation.ValidationEvent;
 public class RemovedOperationErrorTest {
     @Test
     public void detectsRemovedErrors() {
+        SourceLocation source = new SourceLocation("foo.smithy");
         Shape e1 = StructureShape.builder()
                 .id("foo.baz#E1")
                 .addTrait(new ErrorTrait("client"))
@@ -44,6 +46,7 @@ public class RemovedOperationErrorTest {
                 .id("foo.baz#Operation")
                 .addError(e1)
                 .addError(e2)
+                .source(source)
                 .build();
         Shape operation2 = operation1.toBuilder().clearErrors().build();
         Model modelA = Model.assembler().addShapes(operation1, e1, e2).assemble().unwrap();
@@ -52,6 +55,7 @@ public class RemovedOperationErrorTest {
 
         // Emits an event for each removal.
         assertThat(TestHelper.findEvents(events, "RemovedOperationError").size(), equalTo(2));
+        assertThat(events.stream().filter(e -> e.getSourceLocation().equals(source)).count(), equalTo(2L));
         assertThat(TestHelper.findEvents(events, "RemovedOperationError.E1").size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, "RemovedOperationError.E2").size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, "RemovedOperationError").get(0).toString(),
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedTraitDefinitionTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedTraitDefinitionTest.java
index 59b27c7d1..5fdf3d2eb 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedTraitDefinitionTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/RemovedTraitDefinitionTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.StringShape;
 import software.amazon.smithy.model.traits.TraitDefinition;
@@ -30,9 +31,10 @@ import software.amazon.smithy.model.validation.ValidationEvent;
 public class RemovedTraitDefinitionTest {
     @Test
     public void detectsRemovedTraitDefinition() {
+        SourceLocation source = new SourceLocation("bar.smithy");
         Shape definition = StringShape.builder()
                 .id("foo.baz#bam")
-                .addTrait(TraitDefinition.builder().build())
+                .addTrait(TraitDefinition.builder().sourceLocation(source).build())
                 .build();
 
         Model modelA = Model.assembler().addShape(definition).assemble().unwrap();
@@ -40,5 +42,7 @@ public class RemovedTraitDefinitionTest {
         List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);
 
         assertThat(TestHelper.findEvents(events, "RemovedTraitDefinition").size(), equalTo(1));
+        assertThat(TestHelper.findEvents(events, "RemovedTraitDefinition").get(0).getSourceLocation(),
+                equalTo(source));
     }
 }
diff --git a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ServiceRenameTest.java b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ServiceRenameTest.java
index f94d57208..aa9a584d6 100644
--- a/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ServiceRenameTest.java
+++ b/smithy-diff/src/test/java/software/amazon/smithy/diff/evaluators/ServiceRenameTest.java
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.diff.ModelDiff;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.SourceLocation;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.ServiceShape;
 import software.amazon.smithy.model.validation.Severity;
@@ -48,6 +49,7 @@ public class ServiceRenameTest {
 
     @Test
     public void detectsRenameRemoved() {
+        SourceLocation source = new SourceLocation("foo.smithy");
         ServiceShape service1 = service.toBuilder()
                 .putRename(operation.getId(), "O1")
                 .build();
@@ -57,6 +59,7 @@ public class ServiceRenameTest {
 
         ServiceShape service2 = service.toBuilder()
                 .clearRename()
+                .source(source)
                 .build();
         Model modelB = modelA.toBuilder().addShape(service2).build();
 
@@ -64,10 +67,12 @@ public class ServiceRenameTest {
         assertThat(TestHelper.findEvents(events, "ServiceRename").size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, service2.getId()).size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1));
+        assertThat(events.get(0).getSourceLocation(), equalTo(source));
     }
 
     @Test
     public void detectsRenameChange() {
+        SourceLocation source = new SourceLocation("foo.smithy");
         ServiceShape service1 = service.toBuilder()
                 .putRename(operation.getId(), "O1")
                 .build();
@@ -77,6 +82,7 @@ public class ServiceRenameTest {
 
         ServiceShape service2 = service.toBuilder()
                 .putRename(operation.getId(), "O2")
+                .source(source)
                 .build();
         Model modelB = modelA.toBuilder().addShape(service2).build();
 
@@ -84,16 +90,19 @@ public class ServiceRenameTest {
         assertThat(TestHelper.findEvents(events, "ServiceRename").size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, service2.getId()).size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1));
+        assertThat(events.get(0).getSourceLocation(), equalTo(source));
     }
 
     @Test
     public void detectsRenameAdded() {
+        SourceLocation source = new SourceLocation("foo.smithy");
         Model modelA = Model.builder()
                 .addShapes(operation, service)
                 .build();
 
         ServiceShape service2 = service.toBuilder()
                 .putRename(operation.getId(), "O2")
+                .source(source)
                 .build();
         Model modelB = modelA.toBuilder().addShape(service2).build();
 
@@ -102,6 +111,7 @@ public class ServiceRenameTest {
         assertThat(TestHelper.findEvents(events, "ServiceRename").size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, service2.getId()).size(), equalTo(1));
         assertThat(TestHelper.findEvents(events, Severity.ERROR).size(), equalTo(1));
+        assertThat(events.get(0).getSourceLocation(), equalTo(source));
     }
 
     @Test
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-shapes.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-shapes.smithy
index 226d86778..6dcf0e75a 100644
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-shapes.smithy
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-shapes.smithy
@@ -24,20 +24,24 @@ resource MyResource {
         id: String
     }
     put: ResourceOperation
-    create: ResourceOperation
+    create: EmptyOperation
     read: ReadonlyResourceOperation
     update: ResourceOperation
     delete: ResourceOperation
-    list: ReadonlyResourceOperation
+    list: CollectionResourceOperation
     operations: [
-        ResourceOperation
+        CollectionResourceOperation
     ]
     collectionOperations: [
-        ResourceOperation
+        CollectionResourceOperation
     ]
     resources: [
         SubResource
     ]
+    properties: {
+        value: String
+        other: String
+    }
 }
 
 resource SubResource {
@@ -46,6 +50,15 @@ resource SubResource {
     }
 }
 
+@readonly
+operation CollectionResourceOperation {
+    input := {}
+    output := {}
+    errors: [
+        Error
+    ]
+}
+
 operation EmptyOperation {
     input: Unit
     output: Unit
@@ -62,6 +75,7 @@ operation MyOperation {
 @readonly
 operation ReadonlyResourceOperation {
     input := {
+        @required
         id: String
     }
     output: Unit
@@ -70,9 +84,13 @@ operation ReadonlyResourceOperation {
 @idempotent
 operation ResourceOperation {
     input := {
+        @required
         id: String
     }
-    output: Unit
+    output := {
+        value: String
+        other: String
+    }
 }
 
 @error("client")