Index: Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/RequiredThisAnnotationChecker.java |
diff --git a/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/RequiredThisAnnotationChecker.java b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/RequiredThisAnnotationChecker.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..830c253236d03c3785af0b84beed82d184437a81 |
--- /dev/null |
+++ b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/RequiredThisAnnotationChecker.java |
@@ -0,0 +1,85 @@ |
+package org.chromium.devtools.jsdoc.checks; |
+ |
+import com.google.javascript.rhino.head.Token; |
+import com.google.javascript.rhino.head.ast.Assignment; |
+import com.google.javascript.rhino.head.ast.AstNode; |
+import com.google.javascript.rhino.head.ast.FunctionNode; |
+ |
+import java.util.HashSet; |
+import java.util.Set; |
+ |
+public final class RequiredThisAnnotationChecker extends ContextTrackingChecker { |
+ |
+ private final Set<FunctionRecord> functionsRequiringThisAnnotation = new HashSet<>(); |
+ |
+ @Override |
+ void enterNode(AstNode node) { |
+ if (node.getType() == Token.THIS) { |
+ FunctionRecord record = getState().getCurrentFunctionRecord(); |
+ if (record == null) { |
+ return; |
+ } |
+ if (!record.isTopLevelFunction() |
+ && !record.isConstructor |
+ && (hasEnclosingConstructor(record) |
+ || record.enclosingFunctionRecord.enclosingType != null)) { |
+ functionsRequiringThisAnnotation.add(record); |
+ } |
+ return; |
+ } |
+ } |
+ |
+ private boolean hasEnclosingConstructor(FunctionRecord record) { |
+ FunctionRecord parent = record.enclosingFunctionRecord; |
+ while (parent != null) { |
+ if (parent.isConstructor) { |
+ return true; |
+ } |
+ parent = parent.enclosingFunctionRecord; |
+ } |
+ return false; |
+ } |
+ |
+ @Override |
+ void leaveNode(AstNode node) { |
+ if (node.getType() != Token.FUNCTION) { |
+ return; |
+ } |
+ |
+ ContextTrackingState state = getState(); |
+ FunctionRecord record = state.getCurrentFunctionRecord(); |
+ if (!functionsRequiringThisAnnotation.contains(record)) { |
+ return; |
+ } |
+ FunctionNode functionNode = (FunctionNode) node; |
+ AstNode functionNameNode = AstUtil.getFunctionNameNode(functionNode); |
+ if (functionNameNode != null && shouldAddThisAnnotation(functionNode)) { |
+ state.getContext().reportErrorInNode(functionNameNode, 0, |
+ "@this annotation is required for functions referencing 'this'"); |
+ } |
+ } |
+ |
+ private boolean shouldAddThisAnnotation(FunctionNode node) { |
+ String jsDoc = getJsDoc(node); |
+ return jsDoc == null || !jsDoc.contains("@this"); |
+ } |
+ |
+ private String getJsDoc(FunctionNode functionNode) { |
+ String jsDoc = functionNode.getJsDoc(); |
+ if (jsDoc != null) { |
+ return jsDoc; |
+ } |
+ |
+ // reader.onloadend = function() {...} |
+ if (AstUtil.hasParentOfType(functionNode, Token.ASSIGN)) { |
+ Assignment assignment = (Assignment) functionNode.getParent(); |
+ if (assignment.getRight() == functionNode) { |
+ jsDoc = assignment.getJsDoc(); |
+ if (jsDoc != null) { |
+ return jsDoc; |
+ } |
+ } |
+ } |
+ return null; |
+ } |
+} |