From 333ca3602b3fb55655656f6579a120113391e4ac Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Feb 2026 21:16:01 +0000
Subject: [PATCH 1/3] Initial plan
From ac3491cf5ac5fb509e5bf84d9c6f6fdc2f6194c7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Feb 2026 21:39:01 +0000
Subject: [PATCH 2/3] Add SerializableMethodsCheck for JsonSerializable and
XmlSerializable validation
Co-authored-by: srnagar <51379715+srnagar@users.noreply.github.com>
---
.../checkstyle/clientcore/checkstyle.xml | 4 +
.../checkstyle/track2/checkstyle.xml | 4 +
.../checkstyle/vnext/checkstyle.xml | 4 +
.../checks/SerializableMethodsCheck.java | 171 ++++++++++++++
.../checks/SerializableMethodsCheckTest.java | 212 ++++++++++++++++++
5 files changed, 395 insertions(+)
create mode 100644 sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck.java
create mode 100644 sdk/tools/linting-extensions/src/test/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheckTest.java
diff --git a/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml b/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml
index 18abe9430410..b85291b9e9eb 100644
--- a/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml
+++ b/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml
@@ -426,5 +426,9 @@
+
+
+
+
diff --git a/eng/lintingconfigs/checkstyle/track2/checkstyle.xml b/eng/lintingconfigs/checkstyle/track2/checkstyle.xml
index 6c6c6082878e..8717a8d674e3 100644
--- a/eng/lintingconfigs/checkstyle/track2/checkstyle.xml
+++ b/eng/lintingconfigs/checkstyle/track2/checkstyle.xml
@@ -420,5 +420,9 @@
+
+
+
+
diff --git a/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml b/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml
index 6432e8cc91ba..1519ce4b7134 100644
--- a/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml
+++ b/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml
@@ -425,5 +425,9 @@
+
+
+
+
diff --git a/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck.java b/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck.java
new file mode 100644
index 000000000000..2614323041b3
--- /dev/null
+++ b/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck.java
@@ -0,0 +1,171 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.linting.extensions.checkstyle.checks;
+
+import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Validates serialization method completeness for JsonSerializable and XmlSerializable implementations.
+ */
+public class SerializableMethodsCheck extends AbstractCheck {
+ static final String ERR_NO_TO_JSON = "Class implementing JsonSerializable must provide a toJson method.";
+ static final String ERR_NO_FROM_JSON = "Class implementing JsonSerializable must provide a static fromJson method.";
+ static final String ERR_NO_TO_XML = "Class implementing XmlSerializable must provide a toXml method.";
+ static final String ERR_NO_FROM_XML = "Class implementing XmlSerializable must provide a static fromXml method.";
+
+ private List snapshotArchive;
+
+ @Override
+ public int[] getDefaultTokens() {
+ return getRequiredTokens();
+ }
+
+ @Override
+ public int[] getAcceptableTokens() {
+ return getRequiredTokens();
+ }
+
+ @Override
+ public int[] getRequiredTokens() {
+ return new int[] { TokenTypes.CLASS_DEF, TokenTypes.METHOD_DEF };
+ }
+
+ @Override
+ public void beginTree(DetailAST rootNode) {
+ snapshotArchive = new ArrayList<>();
+ }
+
+ @Override
+ public void visitToken(DetailAST currentNode) {
+ int tokenKind = currentNode.getType();
+
+ if (tokenKind == TokenTypes.CLASS_DEF) {
+ TypeSnapshot snapshot = captureTypeSnapshot(currentNode);
+ snapshotArchive.add(snapshot);
+ } else if (tokenKind == TokenTypes.METHOD_DEF) {
+ integrateMethodIntoSnapshot(currentNode);
+ }
+ }
+
+ @Override
+ public void leaveToken(DetailAST currentNode) {
+ if (currentNode.getType() == TokenTypes.CLASS_DEF) {
+ performSnapshotAudit(currentNode);
+ }
+ }
+
+ private TypeSnapshot captureTypeSnapshot(DetailAST classNode) {
+ TypeSnapshot snapshot = new TypeSnapshot();
+ snapshot.classNode = classNode;
+
+ DetailAST interfaceSection = classNode.findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE);
+ if (interfaceSection != null) {
+ digestInterfaceSection(interfaceSection, snapshot);
+ }
+
+ return snapshot;
+ }
+
+ private void digestInterfaceSection(DetailAST interfaceSection, TypeSnapshot snapshot) {
+ DetailAST cursor = interfaceSection.getFirstChild();
+
+ while (cursor != null) {
+ if (cursor.getType() == TokenTypes.IDENT) {
+ String interfaceLabel = cursor.getText();
+
+ if ("JsonSerializable".equals(interfaceLabel)) {
+ snapshot.mandatesJson = true;
+ } else if ("XmlSerializable".equals(interfaceLabel)) {
+ snapshot.mandatesXml = true;
+ }
+ }
+ cursor = cursor.getNextSibling();
+ }
+ }
+
+ private void integrateMethodIntoSnapshot(DetailAST methodNode) {
+ if (snapshotArchive.isEmpty()) {
+ return;
+ }
+
+ TypeSnapshot latestSnapshot = snapshotArchive.get(snapshotArchive.size() - 1);
+
+ if (!latestSnapshot.mandatesJson && !latestSnapshot.mandatesXml) {
+ return;
+ }
+
+ DetailAST nameNode = methodNode.findFirstToken(TokenTypes.IDENT);
+ if (nameNode == null) {
+ return;
+ }
+
+ String methodLabel = nameNode.getText();
+ boolean markedStatic = probeForStaticMarker(methodNode);
+
+ latestSnapshot.digestMethod(methodLabel, markedStatic);
+ }
+
+ private boolean probeForStaticMarker(DetailAST methodNode) {
+ DetailAST modifierBlock = methodNode.findFirstToken(TokenTypes.MODIFIERS);
+
+ if (modifierBlock == null) {
+ return false;
+ }
+
+ return modifierBlock.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
+ }
+
+ private void performSnapshotAudit(DetailAST classNode) {
+ if (snapshotArchive.isEmpty()) {
+ return;
+ }
+
+ TypeSnapshot snapshot = snapshotArchive.remove(snapshotArchive.size() - 1);
+
+ if (snapshot.mandatesJson) {
+ if (!snapshot.observedToJson) {
+ log(classNode, ERR_NO_TO_JSON);
+ }
+ if (!snapshot.observedFromJson) {
+ log(classNode, ERR_NO_FROM_JSON);
+ }
+ }
+
+ if (snapshot.mandatesXml) {
+ if (!snapshot.observedToXml) {
+ log(classNode, ERR_NO_TO_XML);
+ }
+ if (!snapshot.observedFromXml) {
+ log(classNode, ERR_NO_FROM_XML);
+ }
+ }
+ }
+
+ private static class TypeSnapshot {
+ DetailAST classNode;
+ boolean mandatesJson;
+ boolean mandatesXml;
+ boolean observedToJson;
+ boolean observedFromJson;
+ boolean observedToXml;
+ boolean observedFromXml;
+
+ void digestMethod(String methodLabel, boolean markedStatic) {
+ if ("toJson".equals(methodLabel)) {
+ observedToJson = true;
+ } else if ("fromJson".equals(methodLabel) && markedStatic) {
+ observedFromJson = true;
+ } else if ("toXml".equals(methodLabel)) {
+ observedToXml = true;
+ } else if ("fromXml".equals(methodLabel) && markedStatic) {
+ observedFromXml = true;
+ }
+ }
+ }
+}
diff --git a/sdk/tools/linting-extensions/src/test/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheckTest.java b/sdk/tools/linting-extensions/src/test/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheckTest.java
new file mode 100644
index 000000000000..2bab72be5120
--- /dev/null
+++ b/sdk/tools/linting-extensions/src/test/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheckTest.java
@@ -0,0 +1,212 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package io.clientcore.linting.extensions.checkstyle.checks;
+
+import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
+import com.puppycrawl.tools.checkstyle.Checker;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+
+import static io.clientcore.linting.extensions.checkstyle.checks.SerializableMethodsCheck.ERR_NO_FROM_JSON;
+import static io.clientcore.linting.extensions.checkstyle.checks.SerializableMethodsCheck.ERR_NO_FROM_XML;
+import static io.clientcore.linting.extensions.checkstyle.checks.SerializableMethodsCheck.ERR_NO_TO_JSON;
+import static io.clientcore.linting.extensions.checkstyle.checks.SerializableMethodsCheck.ERR_NO_TO_XML;
+
+/**
+ * Tests {@link SerializableMethodsCheck}.
+ */
+public class SerializableMethodsCheckTest extends AbstractModuleTestSupport {
+ private Checker lintingChecker;
+
+ @BeforeEach
+ public void setupChecker() throws Exception {
+ lintingChecker = createChecker(createModuleConfig(SerializableMethodsCheck.class));
+ }
+
+ @AfterEach
+ public void teardownChecker() {
+ lintingChecker.destroy();
+ }
+
+ @Override
+ protected String getPackageLocation() {
+ return "io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck";
+ }
+
+ @Test
+ public void jsonSerializableWithBothMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("jsonComplete", "package com.azure;",
+ "public class JsonComplete implements JsonSerializable {", " public void toJson() {}",
+ " public static JsonComplete fromJson() { return null; }", "}");
+
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath());
+ }
+
+ @Test
+ public void jsonSerializableMissingToJson() throws Exception {
+ File testFile = TestUtils.createCheckFile("jsonMissingTo", "package com.azure;",
+ "public class JsonMissingTo implements JsonSerializable {",
+ " public static JsonMissingTo fromJson() { return null; }", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_TO_JSON };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void jsonSerializableMissingFromJson() throws Exception {
+ File testFile = TestUtils.createCheckFile("jsonMissingFrom", "package com.azure;",
+ "public class JsonMissingFrom implements JsonSerializable {", " public void toJson() {}", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_FROM_JSON };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void jsonSerializableMissingBothMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("jsonMissingBoth", "package com.azure;",
+ "public class JsonMissingBoth implements JsonSerializable {", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_TO_JSON, "2:1: " + ERR_NO_FROM_JSON };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void jsonSerializableWithNonStaticFromJson() throws Exception {
+ File testFile = TestUtils.createCheckFile("jsonNonStaticFrom", "package com.azure;",
+ "public class JsonNonStaticFrom implements JsonSerializable {", " public void toJson() {}",
+ " public JsonNonStaticFrom fromJson() { return null; }", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_FROM_JSON };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void xmlSerializableWithBothMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("xmlComplete", "package com.azure;",
+ "public class XmlComplete implements XmlSerializable {", " public void toXml() {}",
+ " public static XmlComplete fromXml() { return null; }", "}");
+
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath());
+ }
+
+ @Test
+ public void xmlSerializableMissingToXml() throws Exception {
+ File testFile = TestUtils.createCheckFile("xmlMissingTo", "package com.azure;",
+ "public class XmlMissingTo implements XmlSerializable {",
+ " public static XmlMissingTo fromXml() { return null; }", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_TO_XML };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void xmlSerializableMissingFromXml() throws Exception {
+ File testFile = TestUtils.createCheckFile("xmlMissingFrom", "package com.azure;",
+ "public class XmlMissingFrom implements XmlSerializable {", " public void toXml() {}", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_FROM_XML };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void xmlSerializableMissingBothMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("xmlMissingBoth", "package com.azure;",
+ "public class XmlMissingBoth implements XmlSerializable {", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_TO_XML, "2:1: " + ERR_NO_FROM_XML };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void xmlSerializableWithNonStaticFromXml() throws Exception {
+ File testFile = TestUtils.createCheckFile("xmlNonStaticFrom", "package com.azure;",
+ "public class XmlNonStaticFrom implements XmlSerializable {", " public void toXml() {}",
+ " public XmlNonStaticFrom fromXml() { return null; }", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_FROM_XML };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void bothInterfacesWithAllMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("bothComplete", "package com.azure;",
+ "public class BothComplete implements JsonSerializable, XmlSerializable {", " public void toJson() {}",
+ " public static BothComplete fromJson() { return null; }", " public void toXml() {}",
+ " public static BothComplete fromXml() { return null; }", "}");
+
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath());
+ }
+
+ @Test
+ public void bothInterfacesMissingAllMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("bothMissing", "package com.azure;",
+ "public class BothMissing implements JsonSerializable, XmlSerializable {", "}");
+
+ String[] expectedErrors = {
+ "2:1: " + ERR_NO_TO_JSON,
+ "2:1: " + ERR_NO_FROM_JSON,
+ "2:1: " + ERR_NO_TO_XML,
+ "2:1: " + ERR_NO_FROM_XML };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void bothInterfacesMissingJsonMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("bothMissingJson", "package com.azure;",
+ "public class BothMissingJson implements JsonSerializable, XmlSerializable {", " public void toXml() {}",
+ " public static BothMissingJson fromXml() { return null; }", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_TO_JSON, "2:1: " + ERR_NO_FROM_JSON };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void bothInterfacesMissingXmlMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("bothMissingXml", "package com.azure;",
+ "public class BothMissingXml implements JsonSerializable, XmlSerializable {", " public void toJson() {}",
+ " public static BothMissingXml fromJson() { return null; }", "}");
+
+ String[] expectedErrors = { "2:1: " + ERR_NO_TO_XML, "2:1: " + ERR_NO_FROM_XML };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void classNotImplementingInterface() throws Exception {
+ File testFile = TestUtils.createCheckFile("noInterface", "package com.azure;", "public class NoInterface {",
+ " public void someMethod() {}", "}");
+
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath());
+ }
+
+ @Test
+ public void nestedClassWithJsonSerializable() throws Exception {
+ File testFile = TestUtils.createCheckFile("nestedJson", "package com.azure;", "public class OuterClass {",
+ " public static class InnerClass implements JsonSerializable {", " public void toJson() {}",
+ " public static InnerClass fromJson() { return null; }", " }", "}");
+
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath());
+ }
+
+ @Test
+ public void nestedClassMissingMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("nestedMissing", "package com.azure;", "public class OuterClass {",
+ " public static class InnerClass implements JsonSerializable {", " }", "}");
+
+ String[] expectedErrors = { "3:5: " + ERR_NO_TO_JSON, "3:5: " + ERR_NO_FROM_JSON };
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors);
+ }
+
+ @Test
+ public void classWithExtraMethodsAndCorrectSerializationMethods() throws Exception {
+ File testFile = TestUtils.createCheckFile("extraMethods", "package com.azure;",
+ "public class ExtraMethods implements JsonSerializable {", " public void toJson() {}",
+ " public static ExtraMethods fromJson() { return null; }", " public void otherMethod() {}",
+ " public String getData() { return null; }", "}");
+
+ verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath());
+ }
+}
From 90aa153df9d5c2bb0a11b20e23fa43594f5782a1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Feb 2026 21:40:05 +0000
Subject: [PATCH 3/3] Fix duplicate comment headers in checkstyle
configurations
Co-authored-by: srnagar <51379715+srnagar@users.noreply.github.com>
---
eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml | 1 -
eng/lintingconfigs/checkstyle/track2/checkstyle.xml | 1 -
eng/lintingconfigs/checkstyle/vnext/checkstyle.xml | 1 -
3 files changed, 3 deletions(-)
diff --git a/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml b/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml
index b85291b9e9eb..e5f2cafd3123 100644
--- a/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml
+++ b/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml
@@ -427,7 +427,6 @@
-
diff --git a/eng/lintingconfigs/checkstyle/track2/checkstyle.xml b/eng/lintingconfigs/checkstyle/track2/checkstyle.xml
index 8717a8d674e3..ea5bc3edb785 100644
--- a/eng/lintingconfigs/checkstyle/track2/checkstyle.xml
+++ b/eng/lintingconfigs/checkstyle/track2/checkstyle.xml
@@ -421,7 +421,6 @@
-
diff --git a/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml b/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml
index 1519ce4b7134..efd19a736c8a 100644
--- a/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml
+++ b/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml
@@ -426,7 +426,6 @@
-