diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/TestNGJavadocTagsCheckSample.java b/java-checks-test-sources/default/src/test/java/checks/tests/TestNGJavadocTagsCheckSample.java new file mode 100644 index 00000000000..5109230f5f5 --- /dev/null +++ b/java-checks-test-sources/default/src/test/java/checks/tests/TestNGJavadocTagsCheckSample.java @@ -0,0 +1,216 @@ +package checks.tests; + +class TestNGJavadocTagsCheckSample { + + /** + * @test + */ + public void shouldValidateInput() { // Noncompliant {{Replace this "@test" Javadoc tag with the TestNG "@Test" annotation.}} +// ^^^^^^^^^^^^^^^^^^^ + } + + /** + * @beforeMethod + */ + public void setUp() { // Noncompliant {{Replace this "@beforeMethod" Javadoc tag with the TestNG "@BeforeMethod" annotation.}} +// ^^^^^ + } + + /** + * @afterMethod + */ + public void tearDown() { // Noncompliant {{Replace this "@afterMethod" Javadoc tag with the TestNG "@AfterMethod" annotation.}} +// ^^^^^^^^ + } + + /** + * @beforeSuite + */ + public void suiteSetUp() { // Noncompliant {{Replace this "@beforeSuite" Javadoc tag with the TestNG "@BeforeSuite" annotation.}} +// ^^^^^^^^^^ + } + + /** + * @afterSuite + */ + public void suiteTearDown() { // Noncompliant {{Replace this "@afterSuite" Javadoc tag with the TestNG "@AfterSuite" annotation.}} +// ^^^^^^^^^^^^^ + } + + /** + * @beforeTest + */ + public void testSetUp() { // Noncompliant {{Replace this "@beforeTest" Javadoc tag with the TestNG "@BeforeTest" annotation.}} +// ^^^^^^^^^ + } + + /** + * @afterTest + */ + public void testTearDown() { // Noncompliant {{Replace this "@afterTest" Javadoc tag with the TestNG "@AfterTest" annotation.}} +// ^^^^^^^^^^^^ + } + + /** + * @beforeGroups + */ + public void groupSetUp() { // Noncompliant {{Replace this "@beforeGroups" Javadoc tag with the TestNG "@BeforeGroups" annotation.}} +// ^^^^^^^^^^ + } + + /** + * @afterGroups + */ + public void groupTearDown() { // Noncompliant {{Replace this "@afterGroups" Javadoc tag with the TestNG "@AfterGroups" annotation.}} +// ^^^^^^^^^^^^^ + } + + /** + * @dataProvider + */ + public Object[][] provideData() { // Noncompliant {{Replace this "@dataProvider" Javadoc tag with the TestNG "@DataProvider" annotation.}} +// ^^^^^^^^^^^ + return new Object[][] {}; + } + + /** + * @factory + */ + public Object[] createInstances() { // Noncompliant {{Replace this "@factory" Javadoc tag with the TestNG "@Factory" annotation.}} +// ^^^^^^^^^^^^^^^ + return new Object[] {}; + } + + /** + * @Test + */ + public void caseInsensitiveUpperCase() { // Noncompliant {{Replace this "@Test" Javadoc tag with the TestNG "@Test" annotation.}} +// ^^^^^^^^^^^^^^^^^^^^^^^^ + } + + /** + * @TEST + */ + public void caseInsensitiveAllCaps() { // Noncompliant {{Replace this "@TEST" Javadoc tag with the TestNG "@Test" annotation.}} +// ^^^^^^^^^^^^^^^^^^^^^^ + } + + /** + * Validates user input. + * @param input the input string + * @test + */ + public void mixedWithStandardTags(String input) { // Noncompliant {{Replace this "@test" Javadoc tag with the TestNG "@Test" annotation.}} +// ^^^^^^^^^^^^^^^^^^^^^ + } + + /** + * Sets up resources. + * @test + * @beforeMethod + */ + public void multipleTestNGTags() { // Noncompliant {{Replace this "@test" Javadoc tag with the TestNG "@Test" annotation.}} [[secondary=112]] +// ^^^^^^^^^^^^^^^^^^ + } + + /** @test */ + public void singleLineJavadoc() { // Noncompliant {{Replace this "@test" Javadoc tag with the TestNG "@Test" annotation.}} +// ^^^^^^^^^^^^^^^^^ + } + + /** + * @beforeClass + */ + public void classSetUp() { // Noncompliant {{Replace this "@beforeClass" Javadoc tag with the TestNG "@BeforeClass" annotation.}} +// ^^^^^^^^^^ + } + + /** + * @afterClass + */ + public void classTearDown() { // Noncompliant {{Replace this "@afterClass" Javadoc tag with the TestNG "@AfterClass" annotation.}} +// ^^^^^^^^^^^^^ + } + + /** + * Example of TestNG usage: + *
+ * @Test
+ * public void exampleTest() {}
+ *
+ */
+ public void withPreBlock() { // compliant
+ }
+
+ /**
+ * How to use annotations:
+ * {@code @Test public void test() {}}
+ */
+ public void withCodeTag() { // compliant
+ }
+
+ /**
+ * How to use annotations:
+ * {@code
+ * @Test
+ * void example() {}
+ * }
+ */
+ public void withMultilineCodeTag() { // compliant
+ }
+
+ // --- Compliant cases ---
+
+ /**
+ * @param input the input string
+ * @return the result
+ * @throws IllegalArgumentException if input is invalid
+ */
+ public String standardJavadocTags(String input) { // compliant
+ return input;
+ }
+
+ /**
+ * @see String
+ * @since 1.0
+ * @deprecated Use another method
+ */
+ public void otherStandardTags() { // compliant
+ }
+
+ public void noJavadoc() { // compliant
+ }
+
+ /* @test - not a Javadoc comment */
+ public void blockComment() { // compliant
+ }
+
+ // @test - not a Javadoc comment
+ public void lineComment() { // compliant
+ }
+
+ /**
+ * Processes input data.
+ */
+ public void javadocWithoutTags() { // compliant
+ }
+
+ /**
+ * @author John Doe
+ * @version 1.0
+ */
+ public void authorAndVersionTags() { // compliant
+ }
+
+ /**
+ * @listeners - Type-only annotation, not applicable to methods
+ */
+ public void typeOnlyTag() { // compliant
+ }
+
+ /**
+ * @parameters - Type-only annotation, not applicable to methods
+ */
+ public void anotherTypeOnlyTag() { // compliant
+ }
+}
diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java
new file mode 100644
index 00000000000..598539c7466
--- /dev/null
+++ b/java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java
@@ -0,0 +1,108 @@
+/*
+ * SonarQube Java
+ * Copyright (C) SonarSource Sàrl
+ * mailto:info AT sonarsource DOT com
+ *
+ * You can redistribute and/or modify this program under the terms of
+ * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonar.java.checks.tests;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.JavaFileScannerContext;
+import org.sonar.plugins.java.api.tree.MethodTree;
+import org.sonar.plugins.java.api.tree.SyntaxTrivia;
+import org.sonar.plugins.java.api.tree.Tree;
+import org.sonarsource.analyzer.commons.collections.MapBuilder;
+
+@Rule(key = "S9387")
+public class TestNGJavadocTagsCheck extends IssuableSubscriptionVisitor {
+
+ private static final Pattern BLOCK_TAG_PATTERN = Pattern.compile("(?:^|/\\*\\*)[\\t ]*+\\*?[\\t ]*+@(\\w+)", Pattern.MULTILINE);
+ private static final Pattern PRE_BLOCK_PATTERN = Pattern.compile(".*?", Pattern.DOTALL); + private static final Pattern CODE_TAG_PATTERN = Pattern.compile("\\{@code\\s[^}]*+\\}"); + + private static final Map
An issue is raised when a test method uses documentation comment markers (like comment-based tags) instead of proper metadata attributes or +decorators that the testing framework requires to identify and execute tests.
+In Java TestNG specifically, this means using Javadoc-style tags like @test or @beforeMethod in comments instead of the
+proper Java annotations @Test and @BeforeMethod.
Testing frameworks initially supported documentation comment tags for configuring test methods. For example, developers could write special markers +in documentation comments to designate methods as tests. While this approach worked in older framework versions, it has significant drawbacks compared +to modern declarative configuration mechanisms.
+Documentation comment tags are simply strings embedded in comments. The compiler doesn't check them, and development environments cannot validate +them during development. This means typos or incorrect usage won't be caught until runtime, making bugs harder to find.
+Modern testing frameworks use first-class language constructs that declare test configuration directly in code. These are part of the language +itself, so the compiler validates them at compile time. This catches errors early and makes the code more reliable.
+Language-native declarations also integrate better with development tools. IDEs can provide autocomplete, navigation, and refactoring support. When +you rename a test class or reorganize code, these declarations are automatically updated, while comment-based strings remain unchanged.
+Using documentation comment tags in new code creates technical debt. The code appears outdated and may confuse developers who are familiar with +standard framework practices. It also makes the codebase inconsistent if some tests use language-native declarations while others use comment +tags.
+In Java, TestNG uses annotations like @Test, @BeforeMethod, and @AfterMethod instead of the older
+Javadoc-style tags such as /** @test */.
Using documentation comments for metadata instead of structured metadata mechanisms reduces code maintainability and increases the risk of +errors:
+Replace Javadoc tags with the corresponding TestNG annotations. The most common conversions are:
+/** @test */ becomes @Test/** @beforeMethod */ becomes @BeforeMethod/** @afterMethod */ becomes @AfterMethod/** @beforeSuite */ becomes @BeforeSuite/** @afterSuite */ becomes @AfterSuite/** @beforeTest */ becomes @BeforeTest/** @afterTest */ becomes @AfterTest/** @beforeGroups */ becomes @BeforeGroups/** @afterGroups */ becomes @AfterGroups/** @dataProvider */ becomes @DataProvider/** @factory */ becomes @FactoryImport the annotation classes from org.testng.annotations package. Remove the Javadoc comment or keep it for documentation purposes
+without the tag.
+/**
+ * @test
+ */
+public void shouldValidateInput() { // Noncompliant
+ // test code
+}
+
+
+import org.testng.annotations.Test;
+
+@Test
+public void shouldValidateInput() {
+ // test code
+}
+
+