Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / scripts / jsdoc-validator / src / org / chromium / devtools / jsdoc / checks / ContextTrackingValidationCheck.java
1 package org.chromium.devtools.jsdoc.checks;
2
3 import com.google.javascript.rhino.head.Token;
4 import com.google.javascript.rhino.head.ast.Assignment;
5 import com.google.javascript.rhino.head.ast.AstNode;
6 import com.google.javascript.rhino.head.ast.Comment;
7 import com.google.javascript.rhino.head.ast.FunctionNode;
8
9 import org.chromium.devtools.jsdoc.ValidationCheck;
10 import org.chromium.devtools.jsdoc.ValidatorContext;
11 import org.chromium.devtools.jsdoc.checks.TypeRecord.InheritanceEntry;
12
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.regex.Matcher;
17 import java.util.regex.Pattern;
18
19 public class ContextTrackingValidationCheck extends ValidationCheck {
20
21     private static final Pattern EXTENDS_PATTERN =
22             Pattern.compile("@extends\\s+\\{\\s*([^\\s}]+)\\s*\\}");
23     private static final Pattern RETURN_PATTERN =
24             Pattern.compile("@return\\s+\\{\\s*(.+)\\s*\\}");
25     private ContextTrackingState state;
26     private final List<ContextTrackingChecker> clients = new ArrayList<>(5);
27
28     @Override
29     protected void setContext(ValidatorContext context) {
30         super.setContext(context);
31         state = new ContextTrackingState(context);
32         registerClient(new ProtoFollowsExtendsChecker());
33         registerClient(new ReturnAnnotationChecker());
34         registerClient(new FunctionReceiverChecker());
35     }
36
37     @Override
38     public void doVisit(AstNode node) {
39         switch (node.getType()) {
40         case Token.ASSIGN:
41             enterAssignNode((Assignment) node);
42             break;
43         case Token.FUNCTION:
44             enterFunctionNode((FunctionNode) node);
45             break;
46         default:
47             break;
48         }
49
50         enterNode(node);
51     }
52
53     @Override
54     public void didVisit(AstNode node) {
55         leaveNode(node);
56
57         switch (node.getType()) {
58         case Token.ASSIGN:
59             leaveAssignNode((Assignment) node);
60             break;
61         case Token.FUNCTION:
62             leaveFunctionNode((FunctionNode) node);
63             break;
64         default:
65             break;
66         }
67     }
68
69     public void registerClient(ContextTrackingChecker client) {
70         this.clients.add(client);
71         client.setState(state);
72     }
73
74     private void enterNode(AstNode node) {
75         for (ContextTrackingChecker client : clients) {
76             client.enterNode(node);
77         }
78     }
79
80     private void leaveNode(AstNode node) {
81         for (ContextTrackingChecker client : clients) {
82             client.leaveNode(node);
83         }
84     }
85
86     private void enterFunctionNode(FunctionNode node) {
87         Comment jsDocNode = getJsDocNode(node);
88         AstNode nameNode = AstUtil.getFunctionNameNode(node);
89
90         // It can be a type declaration: /** @constructor */ function MyType() {...}.
91         String functionName = getNodeText(nameNode);
92         boolean isConstructor =
93                 functionName != null && rememberTypeRecordIfNeeded(functionName, jsDocNode);
94         TypeRecord parentType = state.getCurrentFunctionRecord() == null
95                 ? state.getCurrentTypeRecord()
96                 : null;
97         state.pushFunctionRecord(new FunctionRecord(
98                 node,
99                 functionName,
100                 isConstructor,
101                 getReturnType(jsDocNode),
102                 parentType,
103                 state.getCurrentFunctionRecord()));
104     }
105
106     @SuppressWarnings("unused")
107     private void leaveFunctionNode(FunctionNode node) {
108         state.functionRecords.removeLast();
109     }
110
111     private String getReturnType(Comment jsDocNode) {
112         if (jsDocNode == null) {
113             return null;
114         }
115         String jsDoc = getNodeText(jsDocNode);
116         Matcher m = RETURN_PATTERN.matcher(jsDoc);
117         if (!m.find()) {
118             return null;
119         }
120         return m.group(1);
121     }
122
123     private void enterAssignNode(Assignment assignment) {
124         String assignedTypeName = getAssignedTypeName(assignment);
125         if (assignedTypeName == null) {
126             return;
127         }
128         if (AstUtil.isPrototypeName(assignedTypeName)) {
129             // MyType.prototype = ...
130             String typeName = AstUtil.getTypeNameFromPrototype(assignedTypeName);
131             TypeRecord typeRecord = state.typeRecordsByTypeName.get(typeName);
132             // We should push anything here to maintain a valid current type record.
133             state.pushTypeRecord(typeRecord);
134             state.pushFunctionRecord(null);
135             return;
136         }
137
138         if (assignment.getRight().getType() == Token.FUNCTION) {
139             // MyType = function() {...}
140             rememberTypeRecordIfNeeded(assignedTypeName, getJsDocNode(assignment));
141         }
142
143     }
144
145     private void leaveAssignNode(Assignment assignment) {
146         String assignedTypeName = getAssignedTypeName(assignment);
147         if (assignedTypeName == null) {
148             return;
149         }
150         if (AstUtil.isPrototypeName(assignedTypeName)) {
151             // Remove the current type record when leaving prototype object.
152             state.typeRecords.removeLast();
153             state.functionRecords.removeLast();
154             return;
155         }
156     }
157
158     private String getAssignedTypeName(Assignment assignment) {
159         AstNode node = AstUtil.getAssignedTypeNameNode(assignment);
160         return getNodeText(node);
161     }
162
163     private boolean rememberTypeRecordIfNeeded(String typeName, Comment jsDocNode) {
164         String jsDoc = getNodeText(jsDocNode);
165         if (!isConstructor(jsDoc) && !isInterface(jsDoc)) {
166             return false;
167         }
168         TypeRecord record = new TypeRecord(
169                 typeName,
170                 isInterface(jsDoc),
171                 getExtendsEntries(jsDocNode));
172         state.typeRecordsByTypeName.put(typeName, record);
173         return true;
174     }
175
176     private static boolean isInterface(String jsDoc) {
177         return jsDoc != null && jsDoc.contains("@interface");
178     }
179
180     private static boolean isConstructor(String jsDoc) {
181         return jsDoc != null && jsDoc.contains("@constructor");
182     }
183
184     private static Comment getJsDocNode(AstNode node) {
185         if (node.getType() == Token.FUNCTION) {
186             return AstUtil.getJsDocNode((FunctionNode) node);
187         }
188         return node.getJsDocNode();
189     }
190
191     private List<InheritanceEntry> getExtendsEntries(Comment jsDocNode) {
192         if (jsDocNode == null) {
193             return Collections.emptyList();
194         }
195         List<InheritanceEntry> result = new ArrayList<>(2);
196         Matcher matcher = EXTENDS_PATTERN.matcher(getNodeText(jsDocNode));
197         while (matcher.find()) {
198             result.add(new InheritanceEntry(matcher.group(1), jsDocNode, matcher.start(1)));
199         }
200
201         return result;
202     }
203 }