Bug 561139 - [14] Report missing param tag for component of record

- add logic to Javadoc.resolveTypeParameterTags to handle record
  parameters to add checking for duplicates, missing tags, and
  invalid param tags
- add tests to JavadocTestForRecord

Change-Id: I59c4c66b5f437f85b18a5524b173679400364f46
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForRecord.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForRecord.java
index b1f5219..4d56617 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForRecord.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JavadocTestForRecord.java
@@ -33,6 +33,8 @@
 
 	String docCommentSupport = CompilerOptions.ENABLED;
 	String reportInvalidJavadoc = CompilerOptions.ERROR;
+	String reportInvalidJavadocTags = CompilerOptions.ENABLED;
+	String reportInavlidJavadocTagsVisibility = CompilerOptions.PRIVATE;
 	String reportInvalidJavadocVisibility = CompilerOptions.PRIVATE;
 	String reportMissingJavadocTags = CompilerOptions.ERROR;
 	String reportMissingJavadocTagsOverriding = CompilerOptions.ENABLED;
@@ -83,6 +85,8 @@
 		}
 		if (this.reportMissingJavadocComments != null)
 			options.put(CompilerOptions.OPTION_ReportMissingJavadocComments, this.reportMissingJavadocComments);
+		options.put(CompilerOptions.OPTION_ReportInvalidJavadocTags, this.reportInvalidJavadocTags);
+		options.put(CompilerOptions.OPTION_ReportInvalidJavadocTagsVisibility, this.reportInavlidJavadocTagsVisibility);
 		options.put(CompilerOptions.OPTION_ReportFieldHiding, CompilerOptions.IGNORE);
 		options.put(CompilerOptions.OPTION_ReportSyntheticAccessEmulation, CompilerOptions.IGNORE);
 		options.put(CompilerOptions.OPTION_ReportDeprecation, CompilerOptions.ERROR);
@@ -129,6 +133,8 @@
 		super.setUp();
 		this.docCommentSupport = CompilerOptions.ENABLED;
 		this.reportInvalidJavadoc = CompilerOptions.ERROR;
+		this.reportInvalidJavadocTags = CompilerOptions.ENABLED;
+		this.reportInvalidJavadocVisibility = CompilerOptions.PRIVATE;
 		this.reportInvalidJavadocVisibility = CompilerOptions.PRIVATE;
 		this.reportMissingJavadocTags = CompilerOptions.ERROR;
 		this.reportMissingJavadocComments = CompilerOptions.ERROR;
@@ -170,4 +176,98 @@
 				"0");
 	}
 
+	public void test004() {
+		if(this.complianceLevel < ClassFileConstants.JDK14) {
+			return;
+		}
+		runConformTest(new String[] { "X.java",
+				"		/**  \n" +
+				"		 * @param a\n" +
+				"		 */  \n" +
+				"		public record X(int a) {\n" +
+				"			/**  \n" +
+				"			 *   @param args \n" + "		 */  \n" +
+				"			public static void main(String[] args){\n" +
+				"				System.out.println(0);\n" +
+				"			}\n" +
+				"		}" },
+				"0");
+	}
+
+	public void test005() {
+		if(this.complianceLevel < ClassFileConstants.JDK14) {
+			return;
+		}
+		runNegativeTest(new String[] { "X.java",
+				"		/**  \n" +
+				"		 */  \n" +
+				"		public record X(int a) {\n" +
+				"			/**  \n" +
+				"			 *   @param args \n" +
+				"			 */  \n" +
+				"			public static void main(String[] args){\n" +
+				"				System.out.println(0);\n" +
+				"			}\n" +
+				"		}" },
+				"----------\n" +
+				"1. ERROR in X.java (at line 3)\n" +
+				"	public record X(int a) {\n" +
+				"	                    ^\n" +
+				"Javadoc: Missing tag for parameter a\n" +
+				"----------\n",
+				JavacTestOptions.Excuse.EclipseWarningConfiguredAsError);
+	}
+
+	public void test006() {
+		if(this.complianceLevel < ClassFileConstants.JDK14) {
+			return;
+		}
+		runNegativeTest(new String[] { "X.java",
+				"		/**  \n" +
+				"		 * @param a\n" +
+				"		 * @param a\n" +
+				"		 */  \n" +
+				"		public record X(int a) {\n" +
+				"			/**  \n" +
+				"			 *   @param args \n" +
+				"			 */  \n" +
+				"			public static void main(String[] args){\n" +
+				"				System.out.println(0);\n" +
+				"			}\n" +
+				"		}" },
+				"----------\n" +
+				"1. ERROR in X.java (at line 3)\n" +
+				"	* @param a\n" +
+				"	         ^\n" +
+				"Javadoc: Duplicate tag for parameter\n" +
+				"----------\n",
+				JavacTestOptions.Excuse.EclipseWarningConfiguredAsError);
+	}
+
+	public void test007() {
+		if(this.complianceLevel < ClassFileConstants.JDK14) {
+			return;
+		}
+		runNegativeTest(new String[] { "X.java",
+				"		/**  \n" +
+				"		 * @param a\n" +
+				"		 * @param b\n" +
+				"		 */  \n" +
+				"		public record X(int a) {\n" +
+				"			/**  \n" +
+				"			 *   @param args \n" +
+				"			 */  \n" +
+				"			public static void main(String[] args){\n" +
+				"				System.out.println(0);\n" +
+				"			}\n" +
+				"		}" },
+				"----------\n" +
+				"1. ERROR in X.java (at line 3)\n" +
+				"	* @param b\n" +
+				"	         ^\n" +
+				"Javadoc: Invalid param tag name\n" +
+				"----------\n",
+				JavacTestOptions.Excuse.EclipseWarningConfiguredAsError);
+	}
+
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java
index f4a013c..63939ca 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Javadoc.java
@@ -735,10 +735,12 @@
 	 */
 	private void resolveTypeParameterTags(Scope scope, boolean reportMissing) {
 		int paramTypeParamLength = this.paramTypeParameters == null ? 0 : this.paramTypeParameters.length;
+		int paramReferencesLength = this.paramReferences == null ? 0 : this.paramReferences.length;
 
 		// Get declaration infos
 		TypeParameter[] parameters = null;
 		TypeVariableBinding[] typeVariables = null;
+		Argument[] recordParameters = null;
 		int modifiers = -1;
 		switch (scope.kind) {
 			case Scope.METHOD_SCOPE:
@@ -760,11 +762,16 @@
 				parameters = typeDeclaration.typeParameters;
 				typeVariables = typeDeclaration.binding.typeVariables;
 				modifiers = typeDeclaration.binding.modifiers;
+				if (typeDeclaration instanceof RecordDeclaration) {
+					RecordDeclaration recordDecl = (RecordDeclaration)typeDeclaration;
+					recordParameters = recordDecl.getArgs();
+				}
 				break;
 		}
 
 		// If no type variables then report a problem for each param type parameter tag
-		if (typeVariables == null || typeVariables.length == 0) {
+		if ((recordParameters == null || recordParameters.length == 0)
+				&& (typeVariables == null || typeVariables.length == 0)) {
 			for (int i = 0; i < paramTypeParamLength; i++) {
 				JavadocSingleTypeReference param = this.paramTypeParameters[i];
 				scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd);
@@ -772,6 +779,70 @@
 			return;
 		}
 
+		// If no param tags then report a problem for each record parameter
+		if (recordParameters != null) {
+			reportMissing = reportMissing && scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5;
+			int recordParametersLength = recordParameters.length;
+			String argNames[] = new String[paramReferencesLength];
+			if (paramReferencesLength == 0) {
+				if (reportMissing) {
+					for (int i = 0, l=recordParametersLength; i<l; i++) {
+						scope.problemReporter().javadocMissingParamTag(recordParameters[i].name, recordParameters[i].sourceStart, recordParameters[i].sourceEnd, modifiers);
+					}
+				}
+			} else {
+				// Otherwise verify that all param tags match record args
+				// Scan all @param tags
+				for (int i = 0; i < paramReferencesLength; ++i) {
+					JavadocSingleNameReference param = this.paramReferences[i];
+					String paramName = new String(param.getName()[0]);
+					// Verify duplicated tags
+					boolean duplicate = false;
+					for (int j = 0; j < i && !duplicate; j++) {
+						if (paramName.equals(argNames[j])) {
+							scope.problemReporter().javadocDuplicatedParamTag(param.token, param.sourceStart, param.sourceEnd, modifiers);
+							duplicate = true;
+						}
+					}
+					if (!duplicate) {
+						argNames[i] = paramName;
+					}
+				}
+				// Look for undocumented arguments
+				if (reportMissing) {
+					for (int i = 0; i < recordParameters.length; i++) {
+						Argument arg = recordParameters[i];
+						boolean found = false;
+						for (int j = 0; j < paramReferencesLength && !found; j++) {
+							JavadocSingleNameReference param = this.paramReferences[j];
+							String paramName = new String(param.getName()[0]);
+							if (paramName.equals(new String(arg.name))) {
+								found = true;
+							}
+						}
+						if (!found) {
+							scope.problemReporter().javadocMissingParamTag(arg.name, arg.sourceStart, arg.sourceEnd, modifiers);
+						}
+					}
+				}
+				// Look for param tags that specify non-existent arguments
+				for (int i = 0; i < paramReferencesLength; i++) {
+					JavadocSingleNameReference param = this.paramReferences[i];
+					String paramName = new String(param.getName()[0]);
+					boolean found = false;
+					for (int j = 0; j < recordParameters.length; j++) {
+						Argument arg = recordParameters[j];
+						if (paramName.equals(new String(arg.name))) {
+							found = true;
+						}
+					}
+					if (!found) {
+						scope.problemReporter().javadocInvalidParamTagName(param.sourceStart, param.sourceEnd);
+					}
+				}
+			}
+		}
+
 		// If no param tags then report a problem for each declaration type parameter
 		if (parameters != null) {
 			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=324850, avoid secondary errors when <= 1.4