···11+@rem
22+@rem Copyright 2015 the original author or authors.
33+@rem
44+@rem Licensed under the Apache License, Version 2.0 (the "License");
55+@rem you may not use this file except in compliance with the License.
66+@rem You may obtain a copy of the License at
77+@rem
88+@rem https://www.apache.org/licenses/LICENSE-2.0
99+@rem
1010+@rem Unless required by applicable law or agreed to in writing, software
1111+@rem distributed under the License is distributed on an "AS IS" BASIS,
1212+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+@rem See the License for the specific language governing permissions and
1414+@rem limitations under the License.
1515+@rem
1616+@rem SPDX-License-Identifier: Apache-2.0
1717+@rem
1818+1919+@if "%DEBUG%"=="" @echo off
2020+@rem ##########################################################################
2121+@rem
2222+@rem Gradle startup script for Windows
2323+@rem
2424+@rem ##########################################################################
2525+2626+@rem Set local scope for the variables with windows NT shell
2727+if "%OS%"=="Windows_NT" setlocal
2828+2929+set DIRNAME=%~dp0
3030+if "%DIRNAME%"=="" set DIRNAME=.
3131+@rem This is normally unused
3232+set APP_BASE_NAME=%~n0
3333+set APP_HOME=%DIRNAME%
3434+3535+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
3636+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
3737+3838+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
3939+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
4040+4141+@rem Find java.exe
4242+if defined JAVA_HOME goto findJavaFromJavaHome
4343+4444+set JAVA_EXE=java.exe
4545+%JAVA_EXE% -version >NUL 2>&1
4646+if %ERRORLEVEL% equ 0 goto execute
4747+4848+echo. 1>&2
4949+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
5050+echo. 1>&2
5151+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
5252+echo location of your Java installation. 1>&2
5353+5454+goto fail
5555+5656+:findJavaFromJavaHome
5757+set JAVA_HOME=%JAVA_HOME:"=%
5858+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
5959+6060+if exist "%JAVA_EXE%" goto execute
6161+6262+echo. 1>&2
6363+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
6464+echo. 1>&2
6565+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
6666+echo location of your Java installation. 1>&2
6767+6868+goto fail
6969+7070+:execute
7171+@rem Setup the command line
7272+7373+7474+7575+@rem Execute Gradle
7676+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
7777+7878+:end
7979+@rem End local scope for the variables with windows NT shell
8080+if %ERRORLEVEL% equ 0 goto mainEnd
8181+8282+:fail
8383+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
8484+rem the _cmd.exe /c_ return code!
8585+set EXIT_CODE=%ERRORLEVEL%
8686+if %EXIT_CODE% equ 0 set EXIT_CODE=1
8787+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
8888+exit /b %EXIT_CODE%
8989+9090+:mainEnd
9191+if "%OS%"=="Windows_NT" endlocal
9292+9393+:omega
+21
license.txt
···11+MIT License
22+33+Copyright (c) 2026 nayrid
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+21
license_header.txt
···11+This file is part of checks, licensed under the MIT License.
22+33+Copyright (c) 2026 nayrid
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
···11+/*
22+ * This file is part of checks, licensed under the MIT License.
33+ *
44+ * Copyright (c) 2026 nayrid
55+ *
66+ * Permission is hereby granted, free of charge, to any person obtaining a copy
77+ * of this software and associated documentation files (the "Software"), to deal
88+ * in the Software without restriction, including without limitation the rights
99+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1010+ * copies of the Software, and to permit persons to whom the Software is
1111+ * furnished to do so, subject to the following conditions:
1212+ *
1313+ * The above copyright notice and this permission notice shall be included in all
1414+ * copies or substantial portions of the Software.
1515+ *
1616+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1717+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1818+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1919+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2020+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2121+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2222+ * SOFTWARE.
2323+ */
2424+package com.nayrid.checks;
2525+2626+import com.puppycrawl.tools.checkstyle.api.DetailNode;
2727+import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
2828+import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
2929+import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
3030+3131+/**
3232+ * Requires the presence of the `@since` tag in Javadoc.
3333+ *
3434+ * @since 1.0.0
3535+ */
3636+public final class RequireSinceCheck extends AbstractJavadocCheck {
3737+3838+ private static final String MSG_KEY = "missingSince";
3939+4040+ private boolean checkPackage = false;
4141+4242+ /**
4343+ * Sets the {@code checkPackage} flag.
4444+ *
4545+ * @param checkPackage new state
4646+ * @since 1.0.0
4747+ */
4848+ @SuppressWarnings("MethodName")
4949+ public void setCheckPackage(final boolean checkPackage) {
5050+ this.checkPackage = checkPackage;
5151+ }
5252+5353+ @Override
5454+ public int[] getDefaultJavadocTokens() {
5555+ return new int[]{JavadocCommentsTokenTypes.JAVADOC_CONTENT};
5656+ }
5757+5858+ @Override
5959+ public void visitJavadocToken(final DetailNode ast) {
6060+ if (CheckUtil.isPackageInfo(this.getFilePath()) && !checkPackage) {
6161+ return;
6262+ }
6363+6464+ if (!containsSince(ast)) {
6565+ log(ast.getLineNumber(), MSG_KEY);
6666+ }
6767+ }
6868+6969+ private static boolean containsSince(final DetailNode content) {
7070+ for (DetailNode child = content.getFirstChild();
7171+ child != null;
7272+ child = child.getNextSibling()) {
7373+7474+ if (child.getType() == JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG) {
7575+7676+ for (DetailNode tag = child.getFirstChild();
7777+ tag != null;
7878+ tag = tag.getNextSibling()) {
7979+8080+ if (tag.getType()
8181+ == JavadocCommentsTokenTypes.SINCE_BLOCK_TAG) {
8282+ return true;
8383+ }
8484+ }
8585+ }
8686+ }
8787+ return false;
8888+ }
8989+9090+}
···11+/*
22+ * This file is part of checks, licensed under the MIT License.
33+ *
44+ * Copyright (c) 2026 nayrid
55+ *
66+ * Permission is hereby granted, free of charge, to any person obtaining a copy
77+ * of this software and associated documentation files (the "Software"), to deal
88+ * in the Software without restriction, including without limitation the rights
99+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1010+ * copies of the Software, and to permit persons to whom the Software is
1111+ * furnished to do so, subject to the following conditions:
1212+ *
1313+ * The above copyright notice and this permission notice shall be included in all
1414+ * copies or substantial portions of the Software.
1515+ *
1616+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1717+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1818+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1919+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2020+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2121+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2222+ * SOFTWARE.
2323+ */
2424+package com.nayrid.checks;
2525+2626+import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
2727+import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
2828+import org.junit.jupiter.api.Test;
2929+3030+import static com.puppycrawl.tools.checkstyle.utils.CommonUtil.EMPTY_STRING_ARRAY;
3131+3232+public class RequireSinceCheckTest extends AbstractModuleTestSupport {
3333+3434+ @Override
3535+ public String getPackageLocation() {
3636+ return "com/nayrid/checks/requiresince";
3737+ }
3838+3939+ @Test
4040+ public void testMissingSince() throws Exception {
4141+ final String[] expected = {
4242+ "3: Javadoc must contain a `@since` tag.",
4343+ };
4444+4545+ verify(
4646+ createModuleConfig(RequireSinceCheck.class),
4747+ getPath("MissingSince.java"),
4848+ expected
4949+ );
5050+ }
5151+5252+ @Test
5353+ public void testHasSince() throws Exception {
5454+ verify(
5555+ createModuleConfig(RequireSinceCheck.class),
5656+ getPath("HasSince.java"),
5757+ EMPTY_STRING_ARRAY
5858+ );
5959+ }
6060+6161+ @Test
6262+ public void testPackageIgnoredByDefault() throws Exception {
6363+ final DefaultConfiguration config =
6464+ createModuleConfig(RequireSinceCheck.class);
6565+6666+ verify(
6767+ config,
6868+ getPath("package-info.java"),
6969+ EMPTY_STRING_ARRAY
7070+ );
7171+ }
7272+7373+ @Test
7474+ public void testPackageCheckedWhenEnabled() throws Exception {
7575+ final DefaultConfiguration config =
7676+ createModuleConfig(RequireSinceCheck.class);
7777+7878+ config.addProperty("checkPackage", "true");
7979+8080+ final String[] expected = {
8181+ "1: Javadoc must contain a `@since` tag.",
8282+ };
8383+8484+ verify(
8585+ config,
8686+ getPath("package-info.java"),
8787+ expected
8888+ );
8989+ }
9090+}
···11+package com.nayrid.checks.requiresince;
22+33+/**
44+ * A class with a `since` tag.
55+ *
66+ * @since 1.0.0
77+ */
88+public class HasSince {
99+}