| 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;
|
| + }
|
| +}
|
|
|