Chromium Code Reviews| Index: Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ContextTrackingValidationCheck.java |
| diff --git a/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ContextTrackingValidationCheck.java b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ContextTrackingValidationCheck.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7401b98063444aaca65958f020a47123b0a32bf7 |
| --- /dev/null |
| +++ b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/ContextTrackingValidationCheck.java |
| @@ -0,0 +1,243 @@ |
| +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.Comment; |
| +import com.google.javascript.rhino.head.ast.FunctionNode; |
| +import com.google.javascript.rhino.head.ast.ObjectProperty; |
| + |
| +import org.chromium.devtools.jsdoc.ValidationCheck; |
| +import org.chromium.devtools.jsdoc.ValidatorContext; |
| +import org.chromium.devtools.jsdoc.checks.TypeRecord.InheritanceEntry; |
| + |
| +import java.util.ArrayList; |
| +import java.util.Collections; |
| +import java.util.List; |
| +import java.util.regex.Matcher; |
| +import java.util.regex.Pattern; |
| + |
| +public class ContextTrackingValidationCheck extends ValidationCheck { |
| + |
| + private static final Pattern EXTENDS_PATTERN = |
| + Pattern.compile("@extends\\s+\\{\\s*([^\\s}]+)\\s*\\}"); |
| + private static final Pattern IMPLEMENTS_PATTERN = |
| + Pattern.compile("@implements\\s+\\{\\s*([^\\s}]+)\\s*\\}"); |
| + private static final Pattern RETURN_PATTERN = |
| + Pattern.compile("@return\\s+\\{\\s*(.+)\\s*\\}"); |
| + private ContextTrackingState state; |
| + private final List<ContextTrackingChecker> clients = new ArrayList<>(5); |
| + |
| + @Override |
| + protected void setContext(ValidatorContext context) { |
| + super.setContext(context); |
| + state = new ContextTrackingState(context); |
| + registerClient(new ProtoFollowsExtendsChecker()); |
| + registerClient(new RequiredThisAnnotationChecker()); |
| + registerClient(new ReturnAnnotationChecker()); |
| + } |
| + |
| + @Override |
| + public void doVisit(AstNode node) { |
| + switch (node.getType()) { |
| + case Token.ASSIGN: |
| + enterAssignNode((Assignment) node); |
| + break; |
| + case Token.FUNCTION: |
| + enterFunctionNode((FunctionNode) node); |
| + break; |
| + } |
| + |
| + enterNode(node); |
| + } |
| + |
| + @Override |
| + public void didVisit(AstNode node) { |
| + leaveNode(node); |
| + |
| + switch (node.getType()) { |
| + case Token.ASSIGN: |
| + leaveAssignNode((Assignment) node); |
| + break; |
| + case Token.FUNCTION: |
| + leaveFunctionNode((FunctionNode) node); |
| + break; |
| + } |
| + } |
| + |
| + public void registerClient(ContextTrackingChecker client) { |
| + this.clients.add(client); |
| + client.setState(state); |
| + } |
| + |
| + private void enterNode(AstNode node) { |
| + for (ContextTrackingChecker client : clients) { |
| + client.enterNode(node); |
| + } |
| + } |
| + |
| + private void leaveNode(AstNode node) { |
| + for (ContextTrackingChecker client : clients) { |
| + client.leaveNode(node); |
| + } |
| + } |
| + |
| + private void enterFunctionNode(FunctionNode node) { |
| + Comment jsDocNode = getJsDocNode(node); |
| + AstNode nameNode = AstUtil.getFunctionNameNode(node); |
| + |
| + // It can be a type declaration: /** @constructor */ function MyType() {...}. |
| + boolean isConstructor = nameNode == null |
|
eustas
2014/01/28 08:47:21
nodeName != null && ...
apavlov
2014/01/28 10:40:17
Done.
|
| + ? false |
| + : rememberTypeRecordIfNeeded(getNodeText(nameNode), jsDocNode); |
| + state.addFunctionRecord(new FunctionRecord( |
| + node, |
| + isConstructor, |
| + getReturnType(jsDocNode), |
| + state.getCurrentTypeRecord(), |
| + state.getCurrentFunctionRecord())); |
| + } |
| + |
| + @SuppressWarnings("unused") |
| + private void leaveFunctionNode(FunctionNode node) { |
| + state.functionRecords.removeLast(); |
| + } |
| + |
| + private String getReturnType(Comment jsDocNode) { |
| + if (jsDocNode == null) { |
| + return null; |
| + } |
| + String jsDoc = getNodeText(jsDocNode); |
| + Matcher m = RETURN_PATTERN.matcher(jsDoc); |
| + if (!m.find()) { |
| + return null; |
| + } |
| + return m.group(1); |
| + } |
| + |
| + private void enterAssignNode(Assignment assignment) { |
| + String assignedTypeName = getAssignedTypeName(assignment); |
| + if (assignedTypeName == null) { |
| + return; |
| + } |
| + if (AstUtil.isPrototypeName(assignedTypeName)) { |
| + // MyType.prototype = ... |
| + String typeName = AstUtil.getTypeNameFromPrototype(assignedTypeName); |
| + TypeRecord typeRecord = state.typeRecordsByTypeName.get(typeName); |
| + if (typeRecord != null) { |
| + typeRecord.setPrototypeNode(assignment.getRight()); |
| + } |
| + // We should push anything here to maintain a valid current type record. |
| + state.addTypeRecord(typeRecord); |
| + return; |
| + } |
| + |
| + if (assignment.getRight().getType() == Token.FUNCTION) { |
| + // MyType = function() {...} |
| + rememberTypeRecordIfNeeded(assignedTypeName, getJsDocNode(assignment)); |
| + } |
| + |
| + } |
| + |
| + private void leaveAssignNode(Assignment assignment) { |
| + String assignedTypeName = getAssignedTypeName(assignment); |
| + if (assignedTypeName == null) { |
| + return; |
| + } |
| + if (AstUtil.isPrototypeName(assignedTypeName) |
| + && assignment.getRight().getType() == Token.OBJECTLIT) { |
| + // Remove the current type record when leaving prototype object. |
| + state.typeRecords.removeLast(); |
| + return; |
| + } |
| + } |
| + |
| + private String getAssignedTypeName(Assignment assignment) { |
| + AstNode node = AstUtil.getAssignedTypeNameNode(assignment); |
| + return getNodeText(node); |
| + } |
| + |
| + private boolean rememberTypeRecordIfNeeded(String typeName, Comment jsDocNode) { |
| + String jsDoc = getNodeText(jsDocNode); |
| + if (!isConstructor(jsDoc) && !isInterface(jsDoc)) { |
| + return false; |
| + } |
| + TypeRecord record = new TypeRecord( |
| + typeName, |
| + isInterface(jsDoc), |
| + getExtendsEntries(jsDocNode), |
| + getImplementsEntries(jsDocNode)); |
| + state.typeRecordsByTypeName.put(typeName, record); |
| + return true; |
| + } |
| + |
| + private static boolean isInterface(String jsDoc) { |
| + return jsDoc != null && jsDoc.contains("@interface"); |
| + } |
| + |
| + private static boolean isConstructor(String jsDoc) { |
| + return jsDoc != null && jsDoc.contains("@constructor"); |
| + } |
| + |
| + private static Comment getJsDocNode(AstNode node) { |
| + if (node.getType() == Token.FUNCTION) { |
| + return getJsDocNode((FunctionNode) node); |
| + } |
| + return node.getJsDocNode(); |
| + } |
| + |
| + private static Comment getJsDocNode(FunctionNode functionNode) { |
| + Comment jsDocNode = functionNode.getJsDocNode(); |
| + if (jsDocNode != null) { |
| + return jsDocNode; |
| + } |
| + |
| + // reader.onloadend = function() {...} |
| + if (AstUtil.hasParentOfType(functionNode, Token.ASSIGN)) { |
| + Assignment assignment = (Assignment) functionNode.getParent(); |
| + if (assignment.getRight() == functionNode) { |
| + jsDocNode = assignment.getJsDocNode(); |
| + if (jsDocNode != null) { |
| + return jsDocNode; |
| + } |
| + } |
| + } |
| + |
| + if (AstUtil.hasParentOfType(functionNode, Token.COLON)) { |
| + jsDocNode = ((ObjectProperty) functionNode.getParent()).getLeft().getJsDocNode(); |
| + if (jsDocNode != null) { |
| + return jsDocNode; |
| + } |
| + } |
| + return null; |
| + } |
| + |
| + private List<InheritanceEntry> getImplementsEntries(Comment jsDocNode) { |
| + if (jsDocNode == null) { |
| + return Collections.emptyList(); |
| + } |
| + String jsDoc = getNodeText(jsDocNode); |
| + if (!isConstructor(jsDoc)) { |
| + return null; |
| + } |
| + return collectInheritanceEntries(IMPLEMENTS_PATTERN, jsDocNode, jsDoc); |
| + } |
| + |
| + private List<InheritanceEntry> getExtendsEntries(Comment jsDocNode) { |
| + if (jsDocNode == null) { |
| + return Collections.emptyList(); |
| + } |
| + return collectInheritanceEntries(EXTENDS_PATTERN, jsDocNode, getNodeText(jsDocNode)); |
| + } |
| + |
| + private List<InheritanceEntry> collectInheritanceEntries( |
| + Pattern pattern, Comment jsDocNode, String jsDoc) { |
| + List<InheritanceEntry> result = new ArrayList<>(2); |
| + Matcher matcher = pattern.matcher(jsDoc); |
| + while (matcher.find()) { |
| + result.add(new InheritanceEntry(matcher.group(1), jsDocNode, matcher.start(1))); |
| + } |
| + |
| + return result; |
| + } |
| +} |