Java tutorial
/******************************************************************************* * Copyright (c) 2000, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Stephan Herrmann - Contribution for * Bug 400874 - [1.8][compiler] Inference infrastructure should evolve to meet JLS8 18.x (Part G of JSR335 spec) * Bug 429958 - [1.8][null] evaluate new DefaultLocation attribute of @NonNullByDefault *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.*; import org.eclipse.jdt.internal.compiler.parser.JavadocTagConstants; /** * Node representing a structured Javadoc comment */ public class Javadoc extends ASTNode { public JavadocSingleNameReference[] paramReferences; // @param public JavadocSingleTypeReference[] paramTypeParameters; // @param public TypeReference[] exceptionReferences; // @throws, @exception public JavadocReturnStatement returnStatement; // @return public Expression[] seeReferences; // @see public IJavadocTypeReference[] usesReferences; // @uses public IJavadocTypeReference[] providesReferences; // @provides public long[] inheritedPositions = null; // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600 // Store param references for tag with invalid syntax public JavadocSingleNameReference[] invalidParameters; // @param // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=153399 // Store value tag positions public long valuePositions = -1; public Javadoc(int sourceStart, int sourceEnd) { this.sourceStart = sourceStart; this.sourceEnd = sourceEnd; this.bits |= ASTNode.ResolveJavadoc; } /** * Returns whether a type can be seen at a given visibility level or not. * * @param visibility Level of visiblity allowed to see references * @param modifiers modifiers of java element to be seen * @return true if the type can be seen, false otherwise */ boolean canBeSeen(int visibility, int modifiers) { if (modifiers < 0) return true; switch (modifiers & ExtraCompilerModifiers.AccVisibilityMASK) { case ClassFileConstants.AccPublic: return true; case ClassFileConstants.AccProtected: return (visibility != ClassFileConstants.AccPublic); case ClassFileConstants.AccDefault: return (visibility == ClassFileConstants.AccDefault || visibility == ClassFileConstants.AccPrivate); case ClassFileConstants.AccPrivate: return (visibility == ClassFileConstants.AccPrivate); } return true; } /* * Search node with a given staring position in javadoc objects arrays. */ public ASTNode getNodeStartingAt(int start) { int length = 0; // parameters array if (this.paramReferences != null) { length = this.paramReferences.length; for (int i = 0; i < length; i++) { JavadocSingleNameReference param = this.paramReferences[i]; if (param.sourceStart == start) { return param; } } } // array of invalid syntax tags parameters if (this.invalidParameters != null) { length = this.invalidParameters.length; for (int i = 0; i < length; i++) { JavadocSingleNameReference param = this.invalidParameters[i]; if (param.sourceStart == start) { return param; } } } // type parameters array if (this.paramTypeParameters != null) { length = this.paramTypeParameters.length; for (int i = 0; i < length; i++) { JavadocSingleTypeReference param = this.paramTypeParameters[i]; if (param.sourceStart == start) { return param; } } } // thrown exception array if (this.exceptionReferences != null) { length = this.exceptionReferences.length; for (int i = 0; i < length; i++) { TypeReference typeRef = this.exceptionReferences[i]; if (typeRef.sourceStart == start) { return typeRef; } } } // references array if (this.seeReferences != null) { length = this.seeReferences.length; for (int i = 0; i < length; i++) { org.eclipse.jdt.internal.compiler.ast.Expression expression = this.seeReferences[i]; if (expression.sourceStart == start) { return expression; } else if (expression instanceof JavadocAllocationExpression) { JavadocAllocationExpression allocationExpr = (JavadocAllocationExpression) this.seeReferences[i]; // if binding is valid then look at arguments if (allocationExpr.binding != null && allocationExpr.binding.isValidBinding()) { if (allocationExpr.arguments != null) { for (int j = 0, l = allocationExpr.arguments.length; j < l; j++) { if (allocationExpr.arguments[j].sourceStart == start) { return allocationExpr.arguments[j]; } } } } } else if (expression instanceof JavadocMessageSend) { JavadocMessageSend messageSend = (JavadocMessageSend) this.seeReferences[i]; // if binding is valid then look at arguments if (messageSend.binding != null && messageSend.binding.isValidBinding()) { if (messageSend.arguments != null) { for (int j = 0, l = messageSend.arguments.length; j < l; j++) { if (messageSend.arguments[j].sourceStart == start) { return messageSend.arguments[j]; } } } } } } } return null; } /* * @see org.eclipse.jdt.internal.compiler.ast.ASTNode#print(int, java.lang.StringBuffer) */ @Override public StringBuffer print(int indent, StringBuffer output) { printIndent(indent, output).append("/**\n"); //$NON-NLS-1$ if (this.paramReferences != null) { for (int i = 0, length = this.paramReferences.length; i < length; i++) { printIndent(indent + 1, output).append(" * @param "); //$NON-NLS-1$ this.paramReferences[i].print(indent, output).append('\n'); } } if (this.paramTypeParameters != null) { for (int i = 0, length = this.paramTypeParameters.length; i < length; i++) { printIndent(indent + 1, output).append(" * @param <"); //$NON-NLS-1$ this.paramTypeParameters[i].print(indent, output).append(">\n"); //$NON-NLS-1$ } } if (this.returnStatement != null) { printIndent(indent + 1, output).append(" * @"); //$NON-NLS-1$ this.returnStatement.print(indent, output).append('\n'); } if (this.exceptionReferences != null) { for (int i = 0, length = this.exceptionReferences.length; i < length; i++) { printIndent(indent + 1, output).append(" * @throws "); //$NON-NLS-1$ this.exceptionReferences[i].print(indent, output).append('\n'); } } if (this.seeReferences != null) { for (int i = 0, length = this.seeReferences.length; i < length; i++) { printIndent(indent + 1, output).append(" * @see "); //$NON-NLS-1$ this.seeReferences[i].print(indent, output).append('\n'); } } printIndent(indent, output).append(" */\n"); //$NON-NLS-1$ return output; } /* * Resolve type javadoc */ public void resolve(ClassScope scope) { if ((this.bits & ASTNode.ResolveJavadoc) == 0) { return; } this.bits &= ~ASTNode.ResolveJavadoc;// avoid double resolution // https://bugs.eclipse.org/bugs/show_bug.cgi?id=247037, @inheritDoc tag cannot // be used in the documentation comment for a class or interface. if (this.inheritedPositions != null) { int length = this.inheritedPositions.length; for (int i = 0; i < length; ++i) { int start = (int) (this.inheritedPositions[i] >>> 32); int end = (int) this.inheritedPositions[i]; scope.problemReporter().javadocUnexpectedTag(start, end); } } // @param tags int paramTagsSize = this.paramReferences == null ? 0 : this.paramReferences.length; for (int i = 0; i < paramTagsSize; i++) { JavadocSingleNameReference param = this.paramReferences[i]; scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd); } resolveTypeParameterTags(scope, true); // @return tags if (this.returnStatement != null) { scope.problemReporter().javadocUnexpectedTag(this.returnStatement.sourceStart, this.returnStatement.sourceEnd); } // @throws/@exception tags int throwsTagsLength = this.exceptionReferences == null ? 0 : this.exceptionReferences.length; for (int i = 0; i < throwsTagsLength; i++) { TypeReference typeRef = this.exceptionReferences[i]; int start, end; if (typeRef instanceof JavadocSingleTypeReference) { JavadocSingleTypeReference singleRef = (JavadocSingleTypeReference) typeRef; start = singleRef.tagSourceStart; end = singleRef.tagSourceEnd; } else if (typeRef instanceof JavadocQualifiedTypeReference) { JavadocQualifiedTypeReference qualifiedRef = (JavadocQualifiedTypeReference) typeRef; start = qualifiedRef.tagSourceStart; end = qualifiedRef.tagSourceEnd; } else { start = typeRef.sourceStart; end = typeRef.sourceEnd; } scope.problemReporter().javadocUnexpectedTag(start, end); } // @see tags int seeTagsLength = this.seeReferences == null ? 0 : this.seeReferences.length; for (int i = 0; i < seeTagsLength; i++) { resolveReference(this.seeReferences[i], scope); } // @value tag boolean source15 = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5; if (!source15 && this.valuePositions != -1) { scope.problemReporter().javadocUnexpectedTag((int) (this.valuePositions >>> 32), (int) this.valuePositions); } } /* * Resolve compilation unit javadoc */ public void resolve(CompilationUnitScope unitScope) { if ((this.bits & ASTNode.ResolveJavadoc) == 0) { return; } // Do nothing - This is to mimic the SDK's javadoc tool behavior, which neither // sanity checks nor generates documentation using comments at the CU scope // (unless the unit happens to be package-info.java - in which case we don't come here.) } /* * Resolve method javadoc */ public void resolve(MethodScope methScope) { if ((this.bits & ASTNode.ResolveJavadoc) == 0) { return; } this.bits &= ~ASTNode.ResolveJavadoc;// avoid double resolution // get method declaration AbstractMethodDeclaration methDecl = methScope.referenceMethod(); boolean overriding = methDecl == null /* field declaration */ || methDecl.binding == null /* compiler error */ ? false : !methDecl.binding.isStatic() && ((methDecl.binding.modifiers & (ExtraCompilerModifiers.AccImplementing | ExtraCompilerModifiers.AccOverriding)) != 0); // @see tags int seeTagsLength = this.seeReferences == null ? 0 : this.seeReferences.length; boolean superRef = false; for (int i = 0; i < seeTagsLength; i++) { // Resolve reference resolveReference(this.seeReferences[i], methScope); // see whether we can have a super reference if (methDecl != null && !superRef) { if (!methDecl.isConstructor()) { if (overriding && this.seeReferences[i] instanceof JavadocMessageSend) { JavadocMessageSend messageSend = (JavadocMessageSend) this.seeReferences[i]; // if binding is valid then look if we have a reference to an overriden method/constructor if (messageSend.binding != null && messageSend.binding.isValidBinding() && messageSend.actualReceiverType instanceof ReferenceBinding) { ReferenceBinding methodReceiverType = (ReferenceBinding) messageSend.actualReceiverType; TypeBinding superType = methDecl.binding.declaringClass .findSuperTypeOriginatingFrom(methodReceiverType); if (superType != null && TypeBinding.notEquals(superType.original(), methDecl.binding.declaringClass) && CharOperation.equals(messageSend.selector, methDecl.selector)) { if (methScope.environment().methodVerifier().doesMethodOverride(methDecl.binding, messageSend.binding.original())) { superRef = true; } } } } } else if (this.seeReferences[i] instanceof JavadocAllocationExpression) { JavadocAllocationExpression allocationExpr = (JavadocAllocationExpression) this.seeReferences[i]; // if binding is valid then look if we have a reference to an overriden method/constructor if (allocationExpr.binding != null && allocationExpr.binding.isValidBinding()) { ReferenceBinding allocType = (ReferenceBinding) allocationExpr.resolvedType.original(); ReferenceBinding superType = (ReferenceBinding) methDecl.binding.declaringClass .findSuperTypeOriginatingFrom(allocType); if (superType != null && TypeBinding.notEquals(superType.original(), methDecl.binding.declaringClass)) { MethodBinding superConstructor = methScope.getConstructor(superType, methDecl.binding.parameters, allocationExpr); if (superConstructor.isValidBinding() && superConstructor.original() == allocationExpr.binding.original()) { MethodBinding current = methDecl.binding; // work 'against' better inference in 1.8 (otherwise comparing (G<T> with G<Object>) would fail): if (methScope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_8 && current.typeVariables != Binding.NO_TYPE_VARIABLES) { current = current.asRawMethod(methScope.environment()); } if (superConstructor.areParametersEqual(current)) { superRef = true; } } } } } } } // Look at @Override annotations if (!superRef && methDecl != null && methDecl.annotations != null) { int length = methDecl.annotations.length; for (int i = 0; i < length && !superRef; i++) { superRef = (methDecl.binding.tagBits & TagBits.AnnotationOverride) != 0; } } // Store if a reference exists to an overriden method/constructor or the method is in a local type, boolean reportMissing = methDecl == null || !((overriding && this.inheritedPositions != null) || superRef || (methDecl.binding.declaringClass != null && methDecl.binding.declaringClass.isLocalType())); if (!overriding && this.inheritedPositions != null) { int length = this.inheritedPositions.length; for (int i = 0; i < length; ++i) { int start = (int) (this.inheritedPositions[i] >>> 32); int end = (int) this.inheritedPositions[i]; methScope.problemReporter().javadocUnexpectedTag(start, end); } } // @param tags CompilerOptions compilerOptions = methScope.compilerOptions(); resolveParamTags(methScope, reportMissing, compilerOptions.reportUnusedParameterIncludeDocCommentReference /* considerParamRefAsUsage*/); resolveTypeParameterTags(methScope, reportMissing && compilerOptions.reportMissingJavadocTagsMethodTypeParameters); // @return tags if (this.returnStatement == null) { if (reportMissing && methDecl != null) { if (methDecl.isMethod()) { MethodDeclaration meth = (MethodDeclaration) methDecl; if (meth.binding.returnType != TypeBinding.VOID) { // method with return should have @return tag methScope.problemReporter().javadocMissingReturnTag(meth.returnType.sourceStart, meth.returnType.sourceEnd, methDecl.binding.modifiers); } } } } else { this.returnStatement.resolve(methScope); } // @throws/@exception tags resolveThrowsTags(methScope, reportMissing); // @value tag boolean source15 = compilerOptions.sourceLevel >= ClassFileConstants.JDK1_5; if (!source15 && methDecl != null && this.valuePositions != -1) { methScope.problemReporter().javadocUnexpectedTag((int) (this.valuePositions >>> 32), (int) this.valuePositions); } // Resolve param tags with invalid syntax int length = this.invalidParameters == null ? 0 : this.invalidParameters.length; for (int i = 0; i < length; i++) { this.invalidParameters[i].resolve(methScope, false, false); } if (methScope.isModuleScope()) { resolveUsesTags(methScope, reportMissing); resolveProvidesTags(methScope, reportMissing); } } private void resolveReference(Expression reference, Scope scope) { // Perform resolve int problemCount = scope.referenceContext().compilationResult().problemCount; switch (scope.kind) { case Scope.METHOD_SCOPE: reference.resolveType((MethodScope) scope); break; case Scope.CLASS_SCOPE: reference.resolveType((ClassScope) scope); break; } boolean hasProblems = scope.referenceContext().compilationResult().problemCount > problemCount; // Verify field references boolean source15 = scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5; int scopeModifiers = -1; if (reference instanceof JavadocFieldReference) { JavadocFieldReference fieldRef = (JavadocFieldReference) reference; // Verify if this is a method reference // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51911 if (fieldRef.methodBinding != null) { // cannot refer to method for @value tag if (fieldRef.tagValue == JavadocTagConstants.TAG_VALUE_VALUE) { if (scopeModifiers == -1) scopeModifiers = scope.getDeclarationModifiers(); scope.problemReporter().javadocInvalidValueReference(fieldRef.sourceStart, fieldRef.sourceEnd, scopeModifiers); } else if (fieldRef.actualReceiverType != null) { if (scope.enclosingSourceType().isCompatibleWith(fieldRef.actualReceiverType)) { fieldRef.bits |= ASTNode.SuperAccess; } ReferenceBinding resolvedType = (ReferenceBinding) fieldRef.actualReceiverType; if (CharOperation.equals(resolvedType.sourceName(), fieldRef.token)) { fieldRef.methodBinding = scope.getConstructor(resolvedType, Binding.NO_TYPES, fieldRef); } else { fieldRef.methodBinding = scope.findMethod(resolvedType, fieldRef.token, Binding.NO_TYPES, fieldRef, false); } } } // Verify whether field ref should be static or not (for @value tags) else if (source15 && fieldRef.binding != null && fieldRef.binding.isValidBinding()) { if (fieldRef.tagValue == JavadocTagConstants.TAG_VALUE_VALUE && !fieldRef.binding.isStatic()) { if (scopeModifiers == -1) scopeModifiers = scope.getDeclarationModifiers(); scope.problemReporter().javadocInvalidValueReference(fieldRef.sourceStart, fieldRef.sourceEnd, scopeModifiers); } } // Verify type references if (!hasProblems && fieldRef.binding != null && fieldRef.binding.isValidBinding() && fieldRef.actualReceiverType instanceof ReferenceBinding) { ReferenceBinding resolvedType = (ReferenceBinding) fieldRef.actualReceiverType; verifyTypeReference(fieldRef, fieldRef.receiver, scope, source15, resolvedType, fieldRef.binding.modifiers); } // That's it for field references return; } // Verify type references if (!hasProblems && (reference instanceof JavadocSingleTypeReference || reference instanceof JavadocQualifiedTypeReference) && reference.resolvedType instanceof ReferenceBinding) { ReferenceBinding resolvedType = (ReferenceBinding) reference.resolvedType; verifyTypeReference(reference, reference, scope, source15, resolvedType, resolvedType.modifiers); } // Verify that message reference are not used for @value tags if (reference instanceof JavadocMessageSend) { JavadocMessageSend msgSend = (JavadocMessageSend) reference; // tag value if (source15 && msgSend.tagValue == JavadocTagConstants.TAG_VALUE_VALUE) { // cannot refer to method for @value tag if (scopeModifiers == -1) scopeModifiers = scope.getDeclarationModifiers(); scope.problemReporter().javadocInvalidValueReference(msgSend.sourceStart, msgSend.sourceEnd, scopeModifiers); } // Verify type references if (!hasProblems && msgSend.binding != null && msgSend.binding.isValidBinding() && msgSend.actualReceiverType instanceof ReferenceBinding) { ReferenceBinding resolvedType = (ReferenceBinding) msgSend.actualReceiverType; verifyTypeReference(msgSend, msgSend.receiver, scope, source15, resolvedType, msgSend.binding.modifiers); } } // Verify that constructor reference are not used for @value tags else if (reference instanceof JavadocAllocationExpression) { JavadocAllocationExpression alloc = (JavadocAllocationExpression) reference; // tag value if (source15 && alloc.tagValue == JavadocTagConstants.TAG_VALUE_VALUE) { // cannot refer to method for @value tag if (scopeModifiers == -1) scopeModifiers = scope.getDeclarationModifiers(); scope.problemReporter().javadocInvalidValueReference(alloc.sourceStart, alloc.sourceEnd, scopeModifiers); } // Verify type references if (!hasProblems && alloc.binding != null && alloc.binding.isValidBinding() && alloc.resolvedType instanceof ReferenceBinding) { ReferenceBinding resolvedType = (ReferenceBinding) alloc.resolvedType; verifyTypeReference(alloc, alloc.type, scope, source15, resolvedType, alloc.binding.modifiers); } } // Verify that there's no type variable reference // (javadoc does not accept them and this is not a referenced bug or requested enhancement) else if (reference instanceof JavadocSingleTypeReference && reference.resolvedType != null && reference.resolvedType.isTypeVariable()) { scope.problemReporter().javadocInvalidReference(reference.sourceStart, reference.sourceEnd); } } /* * Resolve @param tags while method scope */ private void resolveParamTags(MethodScope scope, boolean reportMissing, boolean considerParamRefAsUsage) { AbstractMethodDeclaration methodDecl = scope.referenceMethod(); int paramTagsSize = this.paramReferences == null ? 0 : this.paramReferences.length; // If no referenced method (field initializer for example) then report a problem for each param tag if (methodDecl == null) { for (int i = 0; i < paramTagsSize; i++) { JavadocSingleNameReference param = this.paramReferences[i]; scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd); } return; } // If no param tags then report a problem for each method argument int argumentsSize = methodDecl.arguments == null ? 0 : methodDecl.arguments.length; if (paramTagsSize == 0) { if (reportMissing) { for (int i = 0; i < argumentsSize; i++) { Argument arg = methodDecl.arguments[i]; scope.problemReporter().javadocMissingParamTag(arg.name, arg.sourceStart, arg.sourceEnd, methodDecl.binding.modifiers); } } } else { LocalVariableBinding[] bindings = new LocalVariableBinding[paramTagsSize]; int maxBindings = 0; // Scan all @param tags for (int i = 0; i < paramTagsSize; i++) { JavadocSingleNameReference param = this.paramReferences[i]; param.resolve(scope, true, considerParamRefAsUsage); if (param.binding != null && param.binding.isValidBinding()) { // Verify duplicated tags boolean found = false; for (int j = 0; j < maxBindings && !found; j++) { if (bindings[j] == param.binding) { scope.problemReporter().javadocDuplicatedParamTag(param.token, param.sourceStart, param.sourceEnd, methodDecl.binding.modifiers); found = true; } } if (!found) { bindings[maxBindings++] = (LocalVariableBinding) param.binding; } } } // Look for undocumented arguments if (reportMissing) { for (int i = 0; i < argumentsSize; i++) { Argument arg = methodDecl.arguments[i]; boolean found = false; for (int j = 0; j < maxBindings && !found; j++) { LocalVariableBinding binding = bindings[j]; if (arg.binding == binding) { found = true; } } if (!found) { scope.problemReporter().javadocMissingParamTag(arg.name, arg.sourceStart, arg.sourceEnd, methodDecl.binding.modifiers); } } } } } /* * Resolve @uses tags while block scope */ private void resolveUsesTags(BlockScope scope, boolean reportMissing) { ModuleDeclaration moduleDecl = (ModuleDeclaration) scope.referenceContext(); int usesTagsSize = this.usesReferences == null ? 0 : this.usesReferences.length; // If no referenced module then report a problem for each uses tag if (moduleDecl == null) { for (int i = 0; i < usesTagsSize; i++) { IJavadocTypeReference uses = this.usesReferences[i]; scope.problemReporter().javadocUnexpectedTag(uses.getTagSourceStart(), uses.getTagSourceEnd()); } return; } // If no uses tags then report a problem for each uses reference int usesSize = moduleDecl.usesCount; if (usesTagsSize == 0) { if (reportMissing) { for (int i = 0; i < usesSize; i++) { UsesStatement uses = moduleDecl.uses[i]; scope.problemReporter().javadocMissingUsesTag(uses.serviceInterface, uses.sourceStart, uses.sourceEnd, moduleDecl.binding.modifiers); } } } else { TypeBinding[] bindings = new TypeBinding[usesTagsSize]; int maxBindings = 0; // Scan all @uses tags for (int i = 0; i < usesTagsSize; i++) { TypeReference usesRef = (TypeReference) this.usesReferences[i]; try { usesRef.resolve(scope); if (usesRef.resolvedType != null && usesRef.resolvedType.isValidBinding()) { // Verify duplicated tags boolean found = false; for (int j = 0; j < maxBindings && !found; j++) { if (bindings[j].equals(usesRef.resolvedType)) { scope.problemReporter().javadocDuplicatedUsesTag(usesRef.sourceStart, usesRef.sourceEnd); found = true; } } if (!found) { bindings[maxBindings++] = usesRef.resolvedType; } } } catch (Exception e) { scope.problemReporter().javadocInvalidUsesClass(usesRef.sourceStart, usesRef.sourceEnd); } } // Look for undocumented uses if (reportMissing) { for (int i = 0; i < usesSize; i++) { UsesStatement uses = moduleDecl.uses[i]; boolean found = false; for (int j = 0; j < maxBindings && !found; j++) { TypeBinding binding = bindings[j]; if (uses.serviceInterface.getTypeBinding(scope).equals(binding)) { found = true; } } if (!found) { scope.problemReporter().javadocMissingUsesTag(uses.serviceInterface, uses.sourceStart, uses.sourceEnd, moduleDecl.binding.modifiers); } } } } } /* * Resolve @provides tags while block scope */ private void resolveProvidesTags(BlockScope scope, boolean reportMissing) { ModuleDeclaration moduleDecl = (ModuleDeclaration) scope.referenceContext(); int providesTagsSize = this.providesReferences == null ? 0 : this.providesReferences.length; // If no referenced module then report a problem for each uses tag if (moduleDecl == null) { for (int i = 0; i < providesTagsSize; i++) { IJavadocTypeReference provides = this.providesReferences[i]; scope.problemReporter().javadocUnexpectedTag(provides.getTagSourceStart(), provides.getTagSourceEnd()); } return; } // If no uses tags then report a problem for each uses reference int providesSize = moduleDecl.servicesCount; if (providesTagsSize == 0) { if (reportMissing) { for (int i = 0; i < providesSize; i++) { ProvidesStatement provides = moduleDecl.services[i]; scope.problemReporter().javadocMissingProvidesTag(provides.serviceInterface, provides.sourceStart, provides.sourceEnd, moduleDecl.binding.modifiers); } } } else { TypeBinding[] bindings = new TypeBinding[providesTagsSize]; int maxBindings = 0; // Scan all @provides tags for (int i = 0; i < providesTagsSize; i++) { TypeReference providesRef = (TypeReference) this.providesReferences[i]; try { providesRef.resolve(scope); if (providesRef.resolvedType != null && providesRef.resolvedType.isValidBinding()) { // Verify duplicated tags boolean found = false; for (int j = 0; j < maxBindings && !found; j++) { if (bindings[j].equals(providesRef.resolvedType)) { scope.problemReporter().javadocDuplicatedProvidesTag(providesRef.sourceStart, providesRef.sourceEnd); found = true; } } if (!found) { bindings[maxBindings++] = providesRef.resolvedType; } } } catch (Exception e) { scope.problemReporter().javadocInvalidProvidesClass(providesRef.sourceStart, providesRef.sourceEnd); } } // Look for undocumented uses if (reportMissing) { for (int i = 0; i < providesSize; i++) { ProvidesStatement provides = moduleDecl.services[i]; boolean found = false; for (int j = 0; j < maxBindings && !found; j++) { TypeBinding binding = bindings[j]; if (provides.serviceInterface.getTypeBinding(scope).equals(binding)) { found = true; } } if (!found) { scope.problemReporter().javadocMissingProvidesTag(provides.serviceInterface, provides.sourceStart, provides.sourceEnd, moduleDecl.binding.modifiers); } } } } } /* * Resolve @param tags for type parameters */ private void resolveTypeParameterTags(Scope scope, boolean reportMissing) { int paramTypeParamLength = this.paramTypeParameters == null ? 0 : this.paramTypeParameters.length; // Get declaration infos TypeParameter[] parameters = null; TypeVariableBinding[] typeVariables = null; int modifiers = -1; switch (scope.kind) { case Scope.METHOD_SCOPE: AbstractMethodDeclaration methodDeclaration = ((MethodScope) scope).referenceMethod(); // If no referenced method (field initializer for example) then report a problem for each param tag if (methodDeclaration == null) { for (int i = 0; i < paramTypeParamLength; i++) { JavadocSingleTypeReference param = this.paramTypeParameters[i]; scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd); } return; } parameters = methodDeclaration.typeParameters(); typeVariables = methodDeclaration.binding.typeVariables; modifiers = methodDeclaration.binding.modifiers; break; case Scope.CLASS_SCOPE: TypeDeclaration typeDeclaration = ((ClassScope) scope).referenceContext; parameters = typeDeclaration.typeParameters; typeVariables = typeDeclaration.binding.typeVariables; modifiers = typeDeclaration.binding.modifiers; break; } // If no type variables then report a problem for each param type parameter tag if (typeVariables == null || typeVariables.length == 0) { for (int i = 0; i < paramTypeParamLength; i++) { JavadocSingleTypeReference param = this.paramTypeParameters[i]; scope.problemReporter().javadocUnexpectedTag(param.tagSourceStart, param.tagSourceEnd); } return; } // 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 reportMissing = reportMissing && scope.compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5; int typeParametersLength = parameters.length; if (paramTypeParamLength == 0) { if (reportMissing) { for (int i = 0, l = typeParametersLength; i < l; i++) { scope.problemReporter().javadocMissingParamTag(parameters[i].name, parameters[i].sourceStart, parameters[i].sourceEnd, modifiers); } } // Otherwise verify that all param tags match type parameters } else if (typeVariables.length == typeParametersLength) { TypeVariableBinding[] bindings = new TypeVariableBinding[paramTypeParamLength]; // Scan all @param tags for (int i = 0; i < paramTypeParamLength; i++) { JavadocSingleTypeReference param = this.paramTypeParameters[i]; TypeBinding paramBindind = param.internalResolveType(scope, 0); if (paramBindind != null && paramBindind.isValidBinding()) { if (paramBindind.isTypeVariable()) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=397888 if (scope.compilerOptions().reportUnusedParameterIncludeDocCommentReference) { TypeVariableBinding typeVariableBinding = (TypeVariableBinding) paramBindind; typeVariableBinding.modifiers |= ExtraCompilerModifiers.AccLocallyUsed; } // Verify duplicated tags boolean duplicate = false; for (int j = 0; j < i && !duplicate; j++) { if (TypeBinding.equalsEquals(bindings[j], param.resolvedType)) { scope.problemReporter().javadocDuplicatedParamTag(param.token, param.sourceStart, param.sourceEnd, modifiers); duplicate = true; } } if (!duplicate) { bindings[i] = (TypeVariableBinding) param.resolvedType; } } else { scope.problemReporter().javadocUndeclaredParamTagName(param.token, param.sourceStart, param.sourceEnd, modifiers); } } } // Look for undocumented type parameters for (int i = 0; i < typeParametersLength; i++) { TypeParameter parameter = parameters[i]; boolean found = false; for (int j = 0; j < paramTypeParamLength && !found; j++) { if (TypeBinding.equalsEquals(parameter.binding, bindings[j])) { found = true; bindings[j] = null; } } if (!found && reportMissing) { scope.problemReporter().javadocMissingParamTag(parameter.name, parameter.sourceStart, parameter.sourceEnd, modifiers); } } // Report invalid param for (int i = 0; i < paramTypeParamLength; i++) { if (bindings[i] != null) { JavadocSingleTypeReference param = this.paramTypeParameters[i]; scope.problemReporter().javadocUndeclaredParamTagName(param.token, param.sourceStart, param.sourceEnd, modifiers); } } } } } /* * Resolve @throws/@exception tags while method scope */ private void resolveThrowsTags(MethodScope methScope, boolean reportMissing) { AbstractMethodDeclaration md = methScope.referenceMethod(); int throwsTagsLength = this.exceptionReferences == null ? 0 : this.exceptionReferences.length; // If no referenced method (field initializer for example) then report a problem for each throws tag if (md == null) { for (int i = 0; i < throwsTagsLength; i++) { TypeReference typeRef = this.exceptionReferences[i]; int start = typeRef.sourceStart; int end = typeRef.sourceEnd; if (typeRef instanceof JavadocQualifiedTypeReference) { start = ((JavadocQualifiedTypeReference) typeRef).tagSourceStart; end = ((JavadocQualifiedTypeReference) typeRef).tagSourceEnd; } else if (typeRef instanceof JavadocSingleTypeReference) { start = ((JavadocSingleTypeReference) typeRef).tagSourceStart; end = ((JavadocSingleTypeReference) typeRef).tagSourceEnd; } methScope.problemReporter().javadocUnexpectedTag(start, end); } return; } // If no throws tags then report a problem for each method thrown exception int boundExceptionLength = (md.binding == null) ? 0 : md.binding.thrownExceptions.length; int thrownExceptionLength = md.thrownExceptions == null ? 0 : md.thrownExceptions.length; if (throwsTagsLength == 0) { if (reportMissing) { for (int i = 0; i < boundExceptionLength; i++) { ReferenceBinding exceptionBinding = md.binding.thrownExceptions[i]; if (exceptionBinding != null && exceptionBinding.isValidBinding()) { // flag only valid class name int j = i; while (j < thrownExceptionLength && TypeBinding.notEquals(exceptionBinding, md.thrownExceptions[j].resolvedType)) j++; if (j < thrownExceptionLength) { methScope.problemReporter().javadocMissingThrowsTag(md.thrownExceptions[j], md.binding.modifiers); } } } } } else { int maxRef = 0; TypeReference[] typeReferences = new TypeReference[throwsTagsLength]; // Scan all @throws tags for (int i = 0; i < throwsTagsLength; i++) { TypeReference typeRef = this.exceptionReferences[i]; typeRef.resolve(methScope); TypeBinding typeBinding = typeRef.resolvedType; if (typeBinding != null && typeBinding.isValidBinding() && typeBinding.isClass()) { // accept only valid class binding typeReferences[maxRef++] = typeRef; } } // Look for undocumented thrown exception for (int i = 0; i < boundExceptionLength; i++) { ReferenceBinding exceptionBinding = md.binding.thrownExceptions[i]; if (exceptionBinding != null) exceptionBinding = (ReferenceBinding) exceptionBinding.erasure(); boolean found = false; for (int j = 0; j < maxRef && !found; j++) { if (typeReferences[j] != null) { TypeBinding typeBinding = typeReferences[j].resolvedType; if (TypeBinding.equalsEquals(exceptionBinding, typeBinding)) { found = true; typeReferences[j] = null; } } } if (!found && reportMissing) { if (exceptionBinding != null && exceptionBinding.isValidBinding()) { // flag only valid class name int k = i; while (k < thrownExceptionLength && TypeBinding.notEquals(exceptionBinding, md.thrownExceptions[k].resolvedType)) k++; if (k < thrownExceptionLength) { methScope.problemReporter().javadocMissingThrowsTag(md.thrownExceptions[k], md.binding.modifiers); } } } } // Verify additional @throws tags for (int i = 0; i < maxRef; i++) { TypeReference typeRef = typeReferences[i]; if (typeRef != null) { boolean compatible = false; // thrown exceptions subclasses are accepted for (int j = 0; j < thrownExceptionLength && !compatible; j++) { TypeBinding exceptionBinding = md.thrownExceptions[j].resolvedType; if (exceptionBinding != null) { compatible = typeRef.resolvedType.isCompatibleWith(exceptionBinding); } } // If not compatible only complain on unchecked exception if (!compatible && !typeRef.resolvedType.isUncheckedException(false)) { methScope.problemReporter().javadocInvalidThrowsClassName(typeRef, md.binding.modifiers); } } } } } private void verifyTypeReference(Expression reference, Expression typeReference, Scope scope, boolean source15, ReferenceBinding resolvedType, int modifiers) { if (resolvedType.isValidBinding()) { int scopeModifiers = -1; // reference must have enough visibility to be used if (!canBeSeen(scope.problemReporter().options.reportInvalidJavadocTagsVisibility, modifiers)) { scope.problemReporter().javadocHiddenReference(typeReference.sourceStart, reference.sourceEnd, scope, modifiers); return; } // type reference must have enough visibility to be used if (reference != typeReference) { if (!canBeSeen(scope.problemReporter().options.reportInvalidJavadocTagsVisibility, resolvedType.modifiers)) { scope.problemReporter().javadocHiddenReference(typeReference.sourceStart, typeReference.sourceEnd, scope, resolvedType.modifiers); return; } } // member types if (resolvedType.isMemberType()) { ReferenceBinding topLevelType = resolvedType; // rebuild and store (in reverse order) compound name to handle embedded inner class int packageLength = topLevelType.fPackage.compoundName.length; int depth = resolvedType.depth(); int idx = depth + packageLength; char[][] computedCompoundName = new char[idx + 1][]; computedCompoundName[idx] = topLevelType.sourceName; while (topLevelType.enclosingType() != null) { topLevelType = topLevelType.enclosingType(); computedCompoundName[--idx] = topLevelType.sourceName; } // add package information for (int i = packageLength; --i >= 0;) { computedCompoundName[--idx] = topLevelType.fPackage.compoundName[i]; } ClassScope topLevelScope = scope.classScope(); // when scope is not on compilation unit type, then inner class may not be visible... if (topLevelScope.parent.kind != Scope.COMPILATION_UNIT_SCOPE || !CharOperation.equals(topLevelType.sourceName, topLevelScope.referenceContext.name)) { topLevelScope = topLevelScope.outerMostClassScope(); if (typeReference instanceof JavadocSingleTypeReference) { // inner class single reference can only be done in same unit if ((!source15 && depth == 1) || TypeBinding.notEquals(topLevelType, topLevelScope.referenceContext.binding)) { // search for corresponding import boolean hasValidImport = false; if (source15) { CompilationUnitScope unitScope = topLevelScope.compilationUnitScope(); ImportBinding[] imports = unitScope.imports; int length = imports == null ? 0 : imports.length; mainLoop: for (int i = 0; i < length; i++) { char[][] compoundName = imports[i].compoundName; int compoundNameLength = compoundName.length; if ((imports[i].onDemand && compoundNameLength == computedCompoundName.length - 1) || (compoundNameLength == computedCompoundName.length)) { for (int j = compoundNameLength; --j >= 0;) { if (CharOperation.equals(imports[i].compoundName[j], computedCompoundName[j])) { if (j == 0) { hasValidImport = true; ImportReference importReference = imports[i].reference; if (importReference != null) { importReference.bits |= ASTNode.Used; } break mainLoop; } } else { break; } } } } if (!hasValidImport) { if (scopeModifiers == -1) scopeModifiers = scope.getDeclarationModifiers(); scope.problemReporter().javadocInvalidMemberTypeQualification( typeReference.sourceStart, typeReference.sourceEnd, scopeModifiers); } } else { if (scopeModifiers == -1) scopeModifiers = scope.getDeclarationModifiers(); scope.problemReporter().javadocInvalidMemberTypeQualification( typeReference.sourceStart, typeReference.sourceEnd, scopeModifiers); return; } } } } if (typeReference instanceof JavadocQualifiedTypeReference && !scope.isDefinedInSameUnit(resolvedType)) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=222188 // partially qualified references from a different CU should be warned char[][] typeRefName = ((JavadocQualifiedTypeReference) typeReference).getTypeName(); int skipLength = 0; if (topLevelScope.getCurrentPackage() == resolvedType.getPackage() && typeRefName.length < computedCompoundName.length) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=221539: references can be partially qualified // in same package and hence if the package name is not given, ignore package name check skipLength = resolvedType.fPackage.compoundName.length; } boolean valid = true; if (typeRefName.length == computedCompoundName.length - skipLength) { checkQualification: for (int i = 0; i < typeRefName.length; i++) { if (!CharOperation.equals(typeRefName[i], computedCompoundName[i + skipLength])) { valid = false; break checkQualification; } } } else { valid = false; } // report invalid reference if (!valid) { if (scopeModifiers == -1) scopeModifiers = scope.getDeclarationModifiers(); scope.problemReporter().javadocInvalidMemberTypeQualification(typeReference.sourceStart, typeReference.sourceEnd, scopeModifiers); return; } } } /* * https://bugs.eclipse.org/bugs/show_bug.cgi?id=286918 * * We are concerned only about the Single type references (i.e. without any package) If they are not in default package, * then report an error. References with qualified yet incorrect names would have already been taken care of. */ if (scope.referenceCompilationUnit().isPackageInfo() && typeReference instanceof JavadocSingleTypeReference) { if (resolvedType.fPackage.compoundName.length > 0) { scope.problemReporter().javadocInvalidReference(typeReference.sourceStart, typeReference.sourceEnd); return; // Not really needed - just in case more code added in future } } } } @Override public void traverse(ASTVisitor visitor, BlockScope scope) { if (visitor.visit(this, scope)) { if (this.paramReferences != null) { for (int i = 0, length = this.paramReferences.length; i < length; i++) { this.paramReferences[i].traverse(visitor, scope); } } if (this.paramTypeParameters != null) { for (int i = 0, length = this.paramTypeParameters.length; i < length; i++) { this.paramTypeParameters[i].traverse(visitor, scope); } } if (this.returnStatement != null) { this.returnStatement.traverse(visitor, scope); } if (this.exceptionReferences != null) { for (int i = 0, length = this.exceptionReferences.length; i < length; i++) { this.exceptionReferences[i].traverse(visitor, scope); } } if (this.seeReferences != null) { for (int i = 0, length = this.seeReferences.length; i < length; i++) { this.seeReferences[i].traverse(visitor, scope); } } } visitor.endVisit(this, scope); } public void traverse(ASTVisitor visitor, ClassScope scope) { if (visitor.visit(this, scope)) { if (this.paramReferences != null) { for (int i = 0, length = this.paramReferences.length; i < length; i++) { this.paramReferences[i].traverse(visitor, scope); } } if (this.paramTypeParameters != null) { for (int i = 0, length = this.paramTypeParameters.length; i < length; i++) { this.paramTypeParameters[i].traverse(visitor, scope); } } if (this.returnStatement != null) { this.returnStatement.traverse(visitor, scope); } if (this.exceptionReferences != null) { for (int i = 0, length = this.exceptionReferences.length; i < length; i++) { this.exceptionReferences[i].traverse(visitor, scope); } } if (this.seeReferences != null) { for (int i = 0, length = this.seeReferences.length; i < length; i++) { this.seeReferences[i].traverse(visitor, scope); } } } visitor.endVisit(this, scope); } }