Chromium Code Reviews| Index: Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/DisallowedGlobalPropertiesChecker.java |
| diff --git a/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/DisallowedGlobalPropertiesChecker.java b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/DisallowedGlobalPropertiesChecker.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f19653984674fc6cb6fabbb9538daf6d3fcb4a5b |
| --- /dev/null |
| +++ b/Source/devtools/scripts/jsdoc-validator/src/org/chromium/devtools/jsdoc/checks/DisallowedGlobalPropertiesChecker.java |
| @@ -0,0 +1,170 @@ |
| +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 java.util.ArrayList; |
| +import java.util.HashMap; |
| +import java.util.HashSet; |
| +import java.util.List; |
| +import java.util.Map; |
| +import java.util.Set; |
| + |
| +public final class DisallowedGlobalPropertiesChecker extends ContextTrackingChecker { |
| + |
| + private static final Set<String> GLOBAL_OBJECT_NAMES = new HashSet<>(); |
| + private static final Set<String> DISALLOWED_PROPERTIES = new HashSet<>(); |
| + static { |
| + GLOBAL_OBJECT_NAMES.add("window"); |
| + GLOBAL_OBJECT_NAMES.add("self"); |
| + DISALLOWED_PROPERTIES.add("document"); |
| + DISALLOWED_PROPERTIES.add("addEventListener"); |
| + DISALLOWED_PROPERTIES.add("removeEventListener"); |
| + } |
| + |
| + private static final FunctionRecord TOP_LEVEL_FUNCTION = new FunctionRecord(); |
| + |
| + private final Map<FunctionRecord, Set<String>> declaredLocalVariables = new HashMap<>(); |
| + private final Map<FunctionRecord, List<Node>> globalPropertyAccessNodes = new HashMap<>(); |
| + |
| + @Override |
| + protected void enterNode(Node node) { |
| + switch (node.getType()) { |
| + case Token.VAR: |
| + handleVar(node); |
| + break; |
| + case Token.NAME: |
| + handleName(node); |
| + break; |
| + case Token.STRING: |
| + handleString(node); |
| + break; |
| + case Token.FUNCTION: |
| + case Token.SCRIPT: |
| + enterFunctionOrScript(); |
| + break; |
| + default: |
| + break; |
| + } |
| + } |
| + |
| + @Override |
| + protected void leaveNode(Node node) { |
| + switch (node.getType()) { |
| + case Token.FUNCTION: |
| + case Token.SCRIPT: |
| + leaveFunctionOrScript(); |
| + break; |
| + default: |
| + break; |
| + } |
| + } |
| + |
| + private void enterFunctionOrScript() { |
| + FunctionRecord function = getCurrentFunction(); |
| + declaredLocalVariables.put(function, new HashSet<String>()); |
| + globalPropertyAccessNodes.put(function, new ArrayList<Node>()); |
| + } |
| + |
| + private void leaveFunctionOrScript() { |
| + FunctionRecord function = getCurrentFunction(); |
| + if (!function.suppressesGlobalPropertiesCheck()) { |
| + checkAccessNodes(globalPropertyAccessNodes.get(function)); |
| + } |
| + declaredLocalVariables.remove(function); |
| + globalPropertyAccessNodes.remove(function); |
| + } |
| + |
| + private void checkAccessNodes(List<Node> nodes) { |
| + FunctionRecord function = getCurrentFunction(); |
| + for (Node node : nodes) { |
| + String name = getContext().getNodeText(node); |
| + if (!functionHasVisibleIdentifier(function, name)) { |
| + reportErrorAtNodeStart(node, String.format( |
| + "access to \"%s\" property of global object is disallowed", name)); |
|
apavlov
2014/10/17 13:21:20
Error messages are capitalized elsewhere, so "acce
dgozman
2014/10/17 13:59:06
Done.
|
| + } |
| + } |
| + } |
| + |
| + private void handleVar(Node varNode) { |
| + Node nameNode = varNode.getFirstChild(); |
| + if (nameNode == null) { |
| + return; |
| + } |
| + String name = getContext().getNodeText(nameNode); |
| + if (name != null) { |
| + declaredLocalVariables.get(getCurrentFunction()).add(name); |
|
apavlov
2014/10/17 13:21:20
This will break for hoisted vars, like
function f
dgozman
2014/10/17 13:59:06
This works, since we check property access at the
|
| + } |
| + } |
| + |
| + private void handleName(Node nameNode) { |
| + Node parent = nameNode.getParent(); |
| + if (parent != null && parent.getType() == Token.FUNCTION) { |
| + return; |
| + } |
| + |
| + String name = getContext().getNodeText(nameNode); |
| + if (!DISALLOWED_PROPERTIES.contains(name)) { |
| + return; |
| + } |
| + |
| + if (parent != null && parent.getType() == Token.GETPROP) { |
| + boolean isGlobalPropertyAccess = parent.getFirstChild() == nameNode; |
| + if (!isGlobalPropertyAccess) { |
| + return; |
| + } |
| + } |
| + globalPropertyAccessNodes.get(getCurrentFunction()).add(nameNode); |
| + } |
| + |
| + private void handleString(Node stringNode) { |
| + String name = getContext().getNodeText(stringNode); |
| + if (!DISALLOWED_PROPERTIES.contains(name)) { |
| + return; |
| + } |
| + |
| + Node parent = stringNode.getParent(); |
| + if (parent == null || parent.getType() != Token.GETPROP) { |
| + return; |
| + } |
| + |
| + Node objectNode = parent.getFirstChild(); |
| + boolean isGlobalObjectAccess = |
| + objectNode != null && isGlobalObject(objectNode) && objectNode.getNext() == stringNode; |
| + if (isGlobalObjectAccess) { |
| + globalPropertyAccessNodes.get(getCurrentFunction()).add(stringNode); |
| + } |
| + } |
| + |
| + private FunctionRecord getCurrentFunction() { |
|
apavlov
2014/10/17 13:21:20
excellent!
|
| + FunctionRecord function = getState().getCurrentFunctionRecord(); |
| + return function == null ? TOP_LEVEL_FUNCTION : function; |
| + } |
| + |
| + private boolean isGlobalObject(Node node) { |
| + String name = getContext().getNodeText(node); |
| + if (!GLOBAL_OBJECT_NAMES.contains(name)) { |
| + return false; |
| + } |
| + return node.getType() == Token.NAME |
| + && !functionHasVisibleIdentifier(getCurrentFunction(), name); |
| + } |
| + |
| + private boolean functionHasVisibleIdentifier(FunctionRecord function, String name) { |
| + if (functionHasLocalIdentifier(function, name)) { |
| + return true; |
| + } |
| + if (function == TOP_LEVEL_FUNCTION) { |
| + return false; |
| + } |
| + FunctionRecord parent = function.enclosingFunctionRecord; |
| + return functionHasVisibleIdentifier(parent == null ? TOP_LEVEL_FUNCTION : parent, name); |
| + } |
| + |
| + private boolean functionHasLocalIdentifier(FunctionRecord function, String name) { |
| + return function.parameterNames.contains(name) |
| + || declaredLocalVariables.get(function).contains(name); |
| + } |
| +} |