blob: 49f01c35b31951e520cddca17a14cd25cb9f33ac [file] [log] [blame]
package org.chromium.devtools.jsdoc.checks;
import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import org.chromium.devtools.jsdoc.ValidationCheck;
import org.chromium.devtools.jsdoc.ValidatorContext;
import java.util.ArrayList;
import java.util.List;
public class ContextTrackingValidationCheck extends ValidationCheck {
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 MethodAnnotationChecker());
registerClient(new FunctionReceiverChecker());
registerClient(new DisallowedGlobalPropertiesChecker());
}
@Override
public void doVisit(Node node) {
switch (node.getToken()) {
case ASSIGN:
case VAR:
enterAssignOrVarNode(node);
break;
case FUNCTION:
enterFunctionNode(node);
break;
default:
break;
}
enterNode(node);
}
@Override
public void didVisit(Node node) {
leaveNode(node);
switch (node.getToken()) {
case ASSIGN:
leaveAssignNode(node);
break;
case FUNCTION:
leaveFunctionNode(node);
break;
default:
break;
}
}
public void registerClient(ContextTrackingChecker client) {
this.clients.add(client);
client.setState(state);
}
private void enterNode(Node node) {
for (ContextTrackingChecker client : clients) {
client.enterNode(node);
}
}
private void leaveNode(Node node) {
for (ContextTrackingChecker client : clients) {
client.leaveNode(node);
}
}
private void enterFunctionNode(Node node) {
TypeRecord parentType =
state.getCurrentFunctionRecord() == null ? state.getCurrentTypeRecord() : null;
Node nameNode = AstUtil.getFunctionNameNode(node);
String functionName = nameNode == null ? null : state.getNodeText(nameNode);
FunctionRecord functionRecord = new FunctionRecord(node, functionName,
getFunctionParameterNames(node), parentType, state.getCurrentFunctionRecord());
state.pushFunctionRecord(functionRecord);
rememberTypeRecordIfNeeded(functionName, functionRecord.info);
}
@SuppressWarnings("unused")
private void leaveFunctionNode(Node node) {
state.functionRecords.removeLast();
}
private void enterAssignOrVarNode(Node node) {
String assignedTypeName = getAssignedTypeName(node);
if (assignedTypeName == null) {
return;
}
if (AstUtil.isPrototypeName(assignedTypeName)) {
// MyType.prototype = ...
String typeName = AstUtil.getTypeNameFromPrototype(assignedTypeName);
TypeRecord typeRecord = state.typeRecordsByTypeName.get(typeName);
// We should push anything here to maintain a valid current type record.
state.pushTypeRecord(typeRecord);
state.pushFunctionRecord(null);
return;
}
}
private void leaveAssignNode(Node assignment) {
String assignedTypeName = getAssignedTypeName(assignment);
if (assignedTypeName == null) {
return;
}
if (AstUtil.isPrototypeName(assignedTypeName)) {
// Remove the current type record when leaving prototype object.
state.typeRecords.removeLast();
state.functionRecords.removeLast();
return;
}
}
private String getAssignedTypeName(Node assignment) {
Node node = AstUtil.getAssignedTypeNameNode(assignment);
return getNodeText(node);
}
private List<String> getFunctionParameterNames(Node functionNode) {
List<String> parameterNames = new ArrayList<String>();
Node parametersNode = NodeUtil.getFunctionParameters(functionNode);
for (int i = 0, childCount = parametersNode.getChildCount(); i < childCount; ++i) {
Node paramNode = parametersNode.getChildAtIndex(i);
String paramText = state.getContext().getNodeText(paramNode);
// Handle rest parameters
if ("...".equals(paramText)) continue;
// Handle default parameters (ES6)
String paramName = paramText.split("=")[0].trim();
parameterNames.add(paramName);
}
return parameterNames;
}
private boolean rememberTypeRecordIfNeeded(String typeName, JSDocInfo info) {
if (info == null) {
return false;
}
if (typeName == null) {
return info.isConstructor() || info.isInterface();
}
if (!info.isConstructor() && !info.isInterface()) {
return false;
}
TypeRecord record = new TypeRecord(typeName, info);
state.typeRecordsByTypeName.put(typeName, record);
return true;
}
}