| Index: Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/FunctionReceiverChecker.java
|
| diff --git a/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/FunctionReceiverChecker.java b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/FunctionReceiverChecker.java
|
| index 85cb9ad9e40b26f7cd4e6a1fe529048ee09190dc..637fc923017262cb2827f567414021033eadca50 100644
|
| --- a/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/FunctionReceiverChecker.java
|
| +++ b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/FunctionReceiverChecker.java
|
| @@ -30,6 +30,7 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
|
| private final Map<String, FunctionRecord> nestedFunctionsByName = new HashMap<>();
|
| private final Map<String, Set<CallSite>> callSitesByFunctionName = new HashMap<>();
|
| private final Map<String, Set<SymbolicArgument>> symbolicArgumentsByName = new HashMap<>();
|
| + private final Set<FunctionRecord> functionsRequiringThisAnnotation = new HashSet<>();
|
|
|
| @Override
|
| void enterNode(AstNode node) {
|
| @@ -37,21 +38,14 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
|
| case Token.CALL:
|
| handleCall((FunctionCall) node);
|
| break;
|
| - case Token.FUNCTION:
|
| - FunctionRecord function = getState().getCurrentFunctionRecord();
|
| - if (function == null) {
|
| - break;
|
| - }
|
| - if (function.isTopLevelFunction()) {
|
| - symbolicArgumentsByName.clear();
|
| - } else {
|
| - AstNode nameNode = AstUtil.getFunctionNameNode((FunctionNode) node);
|
| - if (nameNode == null) {
|
| - break;
|
| - }
|
| - nestedFunctionsByName.put(getContext().getNodeText(nameNode), function);
|
| - }
|
| + case Token.FUNCTION: {
|
| + handleFunction((FunctionNode) node);
|
| + break;
|
| + }
|
| + case Token.THIS: {
|
| + handleThis();
|
| break;
|
| + }
|
| default:
|
| break;
|
| }
|
| @@ -85,6 +79,33 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
|
| .add(new CallSite(hasReceiver, functionCall));
|
| }
|
|
|
| +
|
| + private void handleFunction(FunctionNode node) {
|
| + FunctionRecord function = getState().getCurrentFunctionRecord();
|
| + if (function == null) {
|
| + return;
|
| + }
|
| + if (function.isTopLevelFunction()) {
|
| + symbolicArgumentsByName.clear();
|
| + } else {
|
| + AstNode nameNode = AstUtil.getFunctionNameNode(node);
|
| + if (nameNode == null) {
|
| + return;
|
| + }
|
| + nestedFunctionsByName.put(getContext().getNodeText(nameNode), function);
|
| + }
|
| + }
|
| +
|
| + private void handleThis() {
|
| + FunctionRecord function = getState().getCurrentFunctionRecord();
|
| + if (function == null) {
|
| + return;
|
| + }
|
| + if (!function.isTopLevelFunction() && !function.isConstructor) {
|
| + functionsRequiringThisAnnotation.add(function);
|
| + }
|
| + }
|
| +
|
| private List<String> argumentsForCall(List<AstNode> argumentNodes) {
|
| int argumentCount = argumentNodes.size();
|
| List<String> arguments = new ArrayList<>(argumentCount);
|
| @@ -145,8 +166,15 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
|
| return;
|
| }
|
|
|
| - FunctionRecord function = getState().getCurrentFunctionRecord();
|
| - if (function == null || !function.isTopLevelFunction()) {
|
| + ContextTrackingState state = getState();
|
| + FunctionRecord function = state.getCurrentFunctionRecord();
|
| + if (function == null) {
|
| + return;
|
| + }
|
| + checkThisAnnotation(function);
|
| +
|
| + // The nested function checks are only run when leaving a top-level function.
|
| + if (!function.isTopLevelFunction()) {
|
| return;
|
| }
|
|
|
| @@ -160,21 +188,48 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
|
| symbolicArgumentsByName.clear();
|
| }
|
|
|
| + private void checkThisAnnotation(FunctionRecord function) {
|
| + AstNode functionNameNode = AstUtil.getFunctionNameNode(function.functionNode);
|
| + if (functionNameNode == null) {
|
| + return;
|
| + }
|
| + boolean hasThisAnnotation = hasAnnotationTag(function.functionNode, "this");
|
| + if (hasThisAnnotation == functionReferencesThis(function)) {
|
| + return;
|
| + }
|
| + if (hasThisAnnotation) {
|
| + if (!function.isTopLevelFunction()) {
|
| + reportErrorAtNodeStart(
|
| + functionNameNode,
|
| + "@this annotation found for function not referencing 'this'");
|
| + }
|
| + return;
|
| + } else {
|
| + reportErrorAtNodeStart(
|
| + functionNameNode,
|
| + "@this annotation is required for functions referencing 'this'");
|
| + }
|
| + }
|
| +
|
| + private boolean functionReferencesThis(FunctionRecord function) {
|
| + return functionsRequiringThisAnnotation.contains(function);
|
| + }
|
| +
|
| private void processFunctionCallSites(FunctionRecord function, Set<CallSite> callSites) {
|
| if (callSites == null) {
|
| return;
|
| }
|
| - boolean hasThisAnnotation = hasAnnotationTag(function.functionNode, "this");
|
| + boolean functionReferencesThis = functionReferencesThis(function);
|
| for (CallSite callSite : callSites) {
|
| - if (hasThisAnnotation == callSite.hasReceiver || function.isConstructor) {
|
| + if (functionReferencesThis == callSite.hasReceiver || function.isConstructor) {
|
| continue;
|
| }
|
| if (callSite.hasReceiver) {
|
| reportErrorAtNodeStart(callSite.callNode,
|
| - "Receiver specified for a function not annotated with @this");
|
| + "Receiver specified for a function not referencing 'this'");
|
| } else {
|
| reportErrorAtNodeStart(callSite.callNode,
|
| - "Receiver not specified for a function annotated with @this");
|
| + "Receiver not specified for a function referencing 'this'");
|
| }
|
| }
|
| }
|
| @@ -186,23 +241,23 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
|
| return;
|
| }
|
|
|
| - boolean hasThisAnnotation = hasAnnotationTag(function.functionNode, "this");
|
| + boolean referencesThis = functionReferencesThis(function);
|
| for (SymbolicArgument argument : argumentUses) {
|
| if (argument.receiverPresence == CheckedReceiverPresence.IGNORE) {
|
| continue;
|
| }
|
| boolean receiverProvided =
|
| argument.receiverPresence == CheckedReceiverPresence.PRESENT;
|
| - if (hasThisAnnotation == receiverProvided) {
|
| + if (referencesThis == receiverProvided) {
|
| continue;
|
| }
|
| - if (hasThisAnnotation) {
|
| + if (referencesThis) {
|
| reportErrorAtNodeStart(argument.node,
|
| - "Function annotated with @this used as argument without " +
|
| + "Function referencing 'this' used as argument without " +
|
| "a receiver. " + SUPPRESSION_HINT);
|
| } else {
|
| reportErrorAtNodeStart(argument.node,
|
| - "Function not annotated with @this used as argument with " +
|
| + "Function not referencing 'this' used as argument with " +
|
| "a receiver. " + SUPPRESSION_HINT);
|
| }
|
| }
|
|
|