001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.annotation; 021 022import java.util.HashSet; 023import java.util.Set; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 031 032/** 033 * <div> 034 * Verifies that explicitly declared record accessor methods include 035 * the {@code @Override} annotation. 036 * </div> 037 * 038 * <p> 039 * Per <a href="https://openjdk.org/jeps/395">JEP 395</a>, the meaning of the 040 * {@code @Override} annotation was extended to include explicitly declared 041 * accessor methods for record components. 042 * </p> 043 * 044 * <p> 045 * This check focuses only on record component accessor methods. It does not 046 * attempt to detect general method overrides from interfaces or superclasses, 047 * due to Checkstyle's single-file analysis limitations. 048 * </p> 049 * 050 * @since 13.1.0 051 */ 052@StatelessCheck 053public class MissingOverrideOnRecordAccessorCheck extends AbstractCheck { 054 055 /** 056 * A key is pointing to the warning message text in "messages.properties" file. 057 */ 058 public static final String MSG_KEY = "annotation.missing.override.record.accessor"; 059 060 /** 061 * Creates a new {@code MissingOverrideOnRecordAccessorCheck} instance. 062 */ 063 public MissingOverrideOnRecordAccessorCheck() { 064 // no code by default 065 } 066 067 @Override 068 public int[] getDefaultTokens() { 069 return getRequiredTokens(); 070 } 071 072 @Override 073 public int[] getAcceptableTokens() { 074 return getRequiredTokens(); 075 } 076 077 @Override 078 public int[] getRequiredTokens() { 079 return new int[] {TokenTypes.METHOD_DEF}; 080 } 081 082 @Override 083 public void visitToken(DetailAST ast) { 084 if (isRecordAccessorMethod(ast) && !AnnotationUtil.hasOverrideAnnotation(ast)) { 085 log(ast, MSG_KEY); 086 } 087 } 088 089 /** 090 * Checks if the given method is an explicitly declared accessor for a record component. 091 * 092 * @param ast the METHOD_DEF AST node 093 * @return true if this method is a record accessor 094 */ 095 private static boolean isRecordAccessorMethod(DetailAST ast) { 096 boolean result = false; 097 final DetailAST grandParent = ast.getParent().getParent(); 098 if (grandParent != null && grandParent.getType() == TokenTypes.RECORD_DEF) { 099 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS); 100 if (parameters.getChildCount() == 0) { 101 final String methodName = getMethodName(ast); 102 result = getRecordComponentNames(grandParent).contains(methodName); 103 } 104 } 105 return result; 106 } 107 108 /** 109 * Gets the method name from a method definition AST. 110 * 111 * @param methodDef the METHOD_DEF AST node 112 * @return the method name 113 */ 114 private static String getMethodName(DetailAST methodDef) { 115 return TokenUtil.getIdent(methodDef).getText(); 116 } 117 118 /** 119 * Gets the set of record component names from a record definition. 120 * 121 * @param recordDef the RECORD_DEF AST node 122 * @return set of component names 123 */ 124 private static Set<String> getRecordComponentNames(DetailAST recordDef) { 125 final Set<String> names = new HashSet<>(); 126 final DetailAST recordComponents = recordDef.findFirstToken(TokenTypes.RECORD_COMPONENTS); 127 DetailAST child = recordComponents.getFirstChild(); 128 while (child != null) { 129 if (child.getType() == TokenTypes.RECORD_COMPONENT_DEF) { 130 names.add(TokenUtil.getIdent(child).getText()); 131 } 132 child = child.getNextSibling(); 133 } 134 return names; 135 } 136 137}