Commit 91fae4bb authored by apavlov@chromium.org's avatar apavlov@chromium.org

DevTools: [JsDocValidator] Fix checking of receivers specified as arguments

r169450 has resulted in multiple failures of existing code where functions
annotated with @this are passed as arguments along with their receivers.

The cases include:
- Array.prototype.forEach and other iteration-with-callback methods.
- RemoteObject.prototype.callFunction[JSON], whose functionDeclaration
  can be evaluated on any Object (but expect a certain type thereof).
- WebInspector.Object.prototype.{add,remove}EventListener, which accepts
  the callback receiver as its third argument.

This patch introduces receiver specification detection in these cases,
or resorts to IGNORE in ambiguous situations.

R=aandrey, sergeyv, vsevik
NOTRY=true

Review URL: https://codereview.chromium.org/203443010

git-svn-id: svn://svn.chromium.org/blink/trunk@169549 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent deb95da5
...@@ -465,6 +465,7 @@ WebInspector.ElementsPanel.prototype = { ...@@ -465,6 +465,7 @@ WebInspector.ElementsPanel.prototype = {
/** /**
* @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number}} * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number}}
* @suppressReceiverCheck
* @this {!Element} * @this {!Element}
*/ */
function dimensions() function dimensions()
......
...@@ -701,6 +701,7 @@ WebInspector.ElementsTreeOutline.prototype = { ...@@ -701,6 +701,7 @@ WebInspector.ElementsTreeOutline.prototype = {
/** /**
* @param {?string} pseudoType * @param {?string} pseudoType
* @suppressReceiverCheck
* @this {!Element} * @this {!Element}
*/ */
function toggleClassAndInjectStyleRule(pseudoType) function toggleClassAndInjectStyleRule(pseudoType)
...@@ -2396,6 +2397,7 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -2396,6 +2397,7 @@ WebInspector.ElementsTreeElement.prototype = {
function scrollIntoViewCallback(object) function scrollIntoViewCallback(object)
{ {
/** /**
* @suppressReceiverCheck
* @this {!Element} * @this {!Element}
*/ */
function scrollIntoView() function scrollIntoView()
......
...@@ -740,6 +740,7 @@ WebInspector.ArrayGroupingTreeElement._populateRanges = function(treeElement, ob ...@@ -740,6 +740,7 @@ WebInspector.ArrayGroupingTreeElement._populateRanges = function(treeElement, ob
object.callFunctionJSON(packRanges, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._bucketThreshold}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], callback); object.callFunctionJSON(packRanges, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._bucketThreshold}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], callback);
/** /**
* @suppressReceiverCheck
* @this {Object} * @this {Object}
* @param {number=} fromIndex // must declare optional * @param {number=} fromIndex // must declare optional
* @param {number=} toIndex // must declare optional * @param {number=} toIndex // must declare optional
...@@ -839,6 +840,7 @@ WebInspector.ArrayGroupingTreeElement._populateAsFragment = function(treeElement ...@@ -839,6 +840,7 @@ WebInspector.ArrayGroupingTreeElement._populateAsFragment = function(treeElement
object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], processArrayFragment.bind(this)); object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], processArrayFragment.bind(this));
/** /**
* @suppressReceiverCheck
* @this {Object} * @this {Object}
* @param {number=} fromIndex // must declare optional * @param {number=} fromIndex // must declare optional
* @param {number=} toIndex // must declare optional * @param {number=} toIndex // must declare optional
...@@ -901,7 +903,10 @@ WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties = function(tre ...@@ -901,7 +903,10 @@ WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties = function(tre
{ {
object.callFunction(buildObjectFragment, undefined, processObjectFragment.bind(this)); object.callFunction(buildObjectFragment, undefined, processObjectFragment.bind(this));
/** @this {Object} */ /**
* @suppressReceiverCheck
* @this {Object}
*/
function buildObjectFragment() function buildObjectFragment()
{ {
var result = Object.create(this.__proto__); var result = Object.create(this.__proto__);
......
...@@ -59,6 +59,7 @@ WebInspector.PropertiesSidebarPane.prototype = { ...@@ -59,6 +59,7 @@ WebInspector.PropertiesSidebarPane.prototype = {
return; return;
/** /**
* @suppressReceiverCheck
* @this {*} * @this {*}
*/ */
function protoList() function protoList()
......
...@@ -284,6 +284,7 @@ WebInspector.RemoteObjectImpl.prototype = { ...@@ -284,6 +284,7 @@ WebInspector.RemoteObjectImpl.prototype = {
{ {
/** /**
* @param {string} arrayStr * @param {string} arrayStr
* @suppressReceiverCheck
* @this {Object} * @this {Object}
*/ */
function remoteFunction(arrayStr) function remoteFunction(arrayStr)
......
...@@ -254,6 +254,7 @@ WebInspector.RuntimeModel.prototype = { ...@@ -254,6 +254,7 @@ WebInspector.RuntimeModel.prototype = {
/** /**
* @param {string} primitiveType * @param {string} primitiveType
* @suppressReceiverCheck
* @this {WebInspector.RuntimeModel} * @this {WebInspector.RuntimeModel}
*/ */
function getCompletions(primitiveType) function getCompletions(primitiveType)
......
...@@ -15,6 +15,7 @@ import java.io.IOException; ...@@ -15,6 +15,7 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
...@@ -177,6 +178,7 @@ public class Runner { ...@@ -177,6 +178,7 @@ public class Runner {
protected CompilerOptions createOptions() { protected CompilerOptions createOptions() {
CompilerOptions options = super.createOptions(); CompilerOptions options = super.createOptions();
options.setIdeMode(true); options.setIdeMode(true);
options.setExtraAnnotationNames(Collections.singletonList("suppressReceiverCheck"));
return options; return options;
} }
......
61eae58d8ddc4e962f98143aa6b244663bf52fa311d4a78e94302dfeafa314b8 src b5a448d248ee8e610b5559db732412030879a045401a93d6aa9ea638a9ae89aa src
025d06d67547086a28dea803763983ffbfe8d5069a3cd9bd5d61c6e475bfd38a jsdoc-validator.jar 81609682686690d48af5d13b3ab005f4a1adb4d90ee53ac2264a5ba9509f186f jsdoc-validator.jar
...@@ -90,10 +90,5 @@ public class AstUtil { ...@@ -90,10 +90,5 @@ public class AstUtil {
return null; return null;
} }
static boolean hasThisAnnotation(FunctionNode node, ValidatorContext context) {
Comment comment = AstUtil.getJsDocNode(node);
return comment != null && context.getNodeText(comment).contains("@this");
}
private AstUtil() {} private AstUtil() {}
} }
package org.chromium.devtools.jsdoc.checks; package org.chromium.devtools.jsdoc.checks;
import com.google.javascript.rhino.head.ast.AstNode; 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 org.chromium.devtools.jsdoc.ValidatorContext; import org.chromium.devtools.jsdoc.ValidatorContext;
import java.util.regex.Pattern;
abstract class ContextTrackingChecker { abstract class ContextTrackingChecker {
private ContextTrackingState state; private ContextTrackingState state;
...@@ -23,7 +27,13 @@ abstract class ContextTrackingChecker { ...@@ -23,7 +27,13 @@ abstract class ContextTrackingChecker {
return state.getContext(); return state.getContext();
} }
void reportErrorAtNodeStart(AstNode node, String errorText) { protected boolean hasAnnotationTag(FunctionNode node, String tagName) {
Comment comment = AstUtil.getJsDocNode(node);
return comment != null &&
Pattern.matches("(?s).*@" + tagName + "\\b.*", getContext().getNodeText(comment));
}
protected void reportErrorAtNodeStart(AstNode node, String errorText) {
getContext().reportErrorInNode(node, 0, errorText); getContext().reportErrorInNode(node, 0, errorText);
} }
} }
...@@ -14,9 +14,22 @@ import java.util.Set; ...@@ -14,9 +14,22 @@ import java.util.Set;
public final class FunctionReceiverChecker extends ContextTrackingChecker { public final class FunctionReceiverChecker extends ContextTrackingChecker {
private static final Set<String> FUNCTIONS_WITH_CALLBACK_RECEIVER_AS_SECOND_ARGUMENT =
new HashSet<>();
private static final String SUPPRESSION_HINT = "This check can be suppressed using "
+ "@suppressReceiverCheck annotation on function declaration.";
static {
// Array.prototype methods.
FUNCTIONS_WITH_CALLBACK_RECEIVER_AS_SECOND_ARGUMENT.add("every");
FUNCTIONS_WITH_CALLBACK_RECEIVER_AS_SECOND_ARGUMENT.add("filter");
FUNCTIONS_WITH_CALLBACK_RECEIVER_AS_SECOND_ARGUMENT.add("forEach");
FUNCTIONS_WITH_CALLBACK_RECEIVER_AS_SECOND_ARGUMENT.add("map");
FUNCTIONS_WITH_CALLBACK_RECEIVER_AS_SECOND_ARGUMENT.add("some");
}
private final Map<String, FunctionRecord> nestedFunctionsByName = new HashMap<>(); private final Map<String, FunctionRecord> nestedFunctionsByName = new HashMap<>();
private final Map<String, Set<CallSite>> callSitesByFunctionName = new HashMap<>(); private final Map<String, Set<CallSite>> callSitesByFunctionName = new HashMap<>();
private final Map<String, Set<AstNode>> argumentNodesByName = new HashMap<>(); private final Map<String, Set<SymbolicArgument>> symbolicArgumentsByName = new HashMap<>();
@Override @Override
void enterNode(AstNode node) { void enterNode(AstNode node) {
...@@ -30,7 +43,7 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker { ...@@ -30,7 +43,7 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
break; break;
} }
if (function.isTopLevelFunction()) { if (function.isTopLevelFunction()) {
argumentNodesByName.clear(); symbolicArgumentsByName.clear();
} else { } else {
AstNode nameNode = AstUtil.getFunctionNameNode((FunctionNode) node); AstNode nameNode = AstUtil.getFunctionNameNode((FunctionNode) node);
if (nameNode == null) { if (nameNode == null) {
...@@ -45,18 +58,18 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker { ...@@ -45,18 +58,18 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
} }
private void handleCall(FunctionCall functionCall) { private void handleCall(FunctionCall functionCall) {
String[] targetParts = getContext().getNodeText(functionCall.getTarget()).split("\\."); String[] callParts = getContext().getNodeText(functionCall.getTarget()).split("\\.");
String firstPart = targetParts[0]; String firstPart = callParts[0];
int partCount = targetParts.length; List<AstNode> argumentNodes = functionCall.getArguments();
List<String> arguments = new ArrayList<>(functionCall.getArguments().size()); List<String> actualArguments = argumentsForCall(argumentNodes);
for (AstNode argumentNode : functionCall.getArguments()) { int partCount = callParts.length;
String argumentText = getContext().getNodeText(argumentNode); String functionName = callParts[partCount - 1];
arguments.add(argumentText);
getOrCreateSetByKey(argumentNodesByName, argumentText).add(argumentNode); saveSymbolicArguments(functionName, argumentNodes, actualArguments);
}
boolean hasBind = partCount > 1 && "bind".equals(targetParts[partCount - 1]); boolean isBindCall = partCount > 1 && "bind".equals(functionName);
if (hasBind && partCount == 3 && "this".equals(firstPart) && if (isBindCall && partCount == 3 && "this".equals(firstPart) &&
!(arguments.size() > 0 && "this".equals(arguments.get(0)))) { !(actualArguments.size() > 0 && "this".equals(actualArguments.get(0)))) {
reportErrorAtNodeStart(functionCall, reportErrorAtNodeStart(functionCall,
"Member function can only be bound to 'this' as the receiver"); "Member function can only be bound to 'this' as the receiver");
return; return;
...@@ -64,14 +77,55 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker { ...@@ -64,14 +77,55 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
if (partCount > 2 || "this".equals(firstPart)) { if (partCount > 2 || "this".equals(firstPart)) {
return; return;
} }
boolean hasReceiver = hasBind && isReceiverSpecified(arguments); boolean hasReceiver = isBindCall && isReceiverSpecified(actualArguments);
hasReceiver |= (partCount == 2) && hasReceiver |= (partCount == 2) &&
("call".equals(targetParts[1]) || "apply".equals(targetParts[1])) && ("call".equals(functionName) || "apply".equals(functionName)) &&
isReceiverSpecified(arguments); isReceiverSpecified(actualArguments);
getOrCreateSetByKey(callSitesByFunctionName, firstPart) getOrCreateSetByKey(callSitesByFunctionName, firstPart)
.add(new CallSite(hasReceiver, functionCall)); .add(new CallSite(hasReceiver, functionCall));
} }
private List<String> argumentsForCall(List<AstNode> argumentNodes) {
int argumentCount = argumentNodes.size();
List<String> arguments = new ArrayList<>(argumentCount);
for (AstNode argumentNode : argumentNodes) {
arguments.add(getContext().getNodeText(argumentNode));
}
return arguments;
}
private void saveSymbolicArguments(
String functionName, List<AstNode> argumentNodes, List<String> arguments) {
int argumentCount = arguments.size();
CheckedReceiverPresence receiverPresence = CheckedReceiverPresence.MISSING;
if (FUNCTIONS_WITH_CALLBACK_RECEIVER_AS_SECOND_ARGUMENT.contains(functionName)) {
if (argumentCount >= 2) {
receiverPresence = CheckedReceiverPresence.PRESENT;
}
} else if ("addEventListener".equals(functionName) ||
"removeEventListener".equals(functionName)) {
String receiverArgument = argumentCount < 3 ? "" : arguments.get(2);
switch (receiverArgument) {
case "":
case "true":
case "false":
receiverPresence = CheckedReceiverPresence.MISSING;
break;
case "this":
receiverPresence = CheckedReceiverPresence.PRESENT;
break;
default:
receiverPresence = CheckedReceiverPresence.IGNORE;
}
}
for (int i = 0; i < argumentCount; ++i) {
String argumentText = arguments.get(i);
getOrCreateSetByKey(symbolicArgumentsByName, argumentText).add(
new SymbolicArgument(receiverPresence, argumentNodes.get(i)));
}
}
private static <K, T> Set<T> getOrCreateSetByKey(Map<K, Set<T>> map, K key) { private static <K, T> Set<T> getOrCreateSetByKey(Map<K, Set<T>> map, K key) {
Set<T> set = map.get(key); Set<T> set = map.get(key);
if (set == null) { if (set == null) {
...@@ -97,31 +151,22 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker { ...@@ -97,31 +151,22 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
} }
for (FunctionRecord record : nestedFunctionsByName.values()) { for (FunctionRecord record : nestedFunctionsByName.values()) {
processNestedFunction(record); processFunctionUsesAsArgument(record, symbolicArgumentsByName.get(record.name));
processFunctionCallSites(record, callSitesByFunctionName.get(record.name));
} }
nestedFunctionsByName.clear(); nestedFunctionsByName.clear();
callSitesByFunctionName.clear(); callSitesByFunctionName.clear();
argumentNodesByName.clear(); symbolicArgumentsByName.clear();
} }
private void processNestedFunction(FunctionRecord record) { private void processFunctionCallSites(FunctionRecord function, Set<CallSite> callSites) {
String name = record.name;
Set<AstNode> argumentUsages = argumentNodesByName.get(name);
Set<CallSite> callSites = callSitesByFunctionName.get(name);
boolean hasThisAnnotation = AstUtil.hasThisAnnotation(record.functionNode, getContext());
if (hasThisAnnotation && argumentUsages != null) {
for (AstNode argumentNode : argumentUsages) {
reportErrorAtNodeStart(argumentNode,
"Function annotated with @this used as argument without " +
"bind(<non-null-receiver>)");
}
}
if (callSites == null) { if (callSites == null) {
return; return;
} }
boolean hasThisAnnotation = hasAnnotationTag(function.functionNode, "this");
for (CallSite callSite : callSites) { for (CallSite callSite : callSites) {
if (hasThisAnnotation == callSite.hasReceiver || record.isConstructor) { if (hasThisAnnotation == callSite.hasReceiver || function.isConstructor) {
continue; continue;
} }
if (callSite.hasReceiver) { if (callSite.hasReceiver) {
...@@ -134,6 +179,51 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker { ...@@ -134,6 +179,51 @@ public final class FunctionReceiverChecker extends ContextTrackingChecker {
} }
} }
private void processFunctionUsesAsArgument(
FunctionRecord function, Set<SymbolicArgument> argumentUses) {
if (argumentUses == null ||
hasAnnotationTag(function.functionNode, "suppressReceiverCheck")) {
return;
}
boolean hasThisAnnotation = hasAnnotationTag(function.functionNode, "this");
for (SymbolicArgument argument : argumentUses) {
if (argument.receiverPresence == CheckedReceiverPresence.IGNORE) {
continue;
}
boolean receiverProvided =
argument.receiverPresence == CheckedReceiverPresence.PRESENT;
if (hasThisAnnotation == receiverProvided) {
continue;
}
if (hasThisAnnotation) {
reportErrorAtNodeStart(argument.node,
"Function annotated with @this used as argument without " +
"a receiver. " + SUPPRESSION_HINT);
} else {
reportErrorAtNodeStart(argument.node,
"Function not annotated with @this used as argument with " +
"a receiver. " + SUPPRESSION_HINT);
}
}
}
private static enum CheckedReceiverPresence {
PRESENT,
MISSING,
IGNORE
}
private static class SymbolicArgument {
CheckedReceiverPresence receiverPresence;
AstNode node;
public SymbolicArgument(CheckedReceiverPresence receiverPresence, AstNode node) {
this.receiverPresence = receiverPresence;
this.node = node;
}
}
private static class CallSite { private static class CallSite {
boolean hasReceiver; boolean hasReceiver;
FunctionCall callNode; FunctionCall callNode;
......
...@@ -37,7 +37,7 @@ public final class RequiredThisAnnotationChecker extends ContextTrackingChecker ...@@ -37,7 +37,7 @@ public final class RequiredThisAnnotationChecker extends ContextTrackingChecker
if (!functionsRequiringThisAnnotation.contains(function)) { if (!functionsRequiringThisAnnotation.contains(function)) {
AstNode functionNameNode = AstUtil.getFunctionNameNode(functionNode); AstNode functionNameNode = AstUtil.getFunctionNameNode(functionNode);
if (functionNameNode != null && !function.isTopLevelFunction() && if (functionNameNode != null && !function.isTopLevelFunction() &&
AstUtil.hasThisAnnotation(functionNode, getContext())) { hasAnnotationTag(functionNode, "this")) {
reportErrorAtNodeStart( reportErrorAtNodeStart(
functionNameNode, functionNameNode,
"@this annotation found for function not referencing 'this'"); "@this annotation found for function not referencing 'this'");
...@@ -45,7 +45,7 @@ public final class RequiredThisAnnotationChecker extends ContextTrackingChecker ...@@ -45,7 +45,7 @@ public final class RequiredThisAnnotationChecker extends ContextTrackingChecker
return; return;
} }
AstNode functionNameNode = AstUtil.getFunctionNameNode(functionNode); AstNode functionNameNode = AstUtil.getFunctionNameNode(functionNode);
if (functionNameNode != null && !AstUtil.hasThisAnnotation(functionNode, getContext())) { if (functionNameNode != null && !hasAnnotationTag(functionNode, "this")) {
reportErrorAtNodeStart( reportErrorAtNodeStart(
functionNameNode, functionNameNode,
"@this annotation is required for functions referencing 'this'"); "@this annotation is required for functions referencing 'this'");
......
...@@ -234,7 +234,7 @@ function badFuncAnnotatedButNoReturnValue() // ERROR - no returned value. ...@@ -234,7 +234,7 @@ function badFuncAnnotatedButNoReturnValue() // ERROR - no returned value.
callbackNoThis.bind(this); // ERROR - Function has no @this annotation. callbackNoThis.bind(this); // ERROR - Function has no @this annotation.
^ ^
/usr/local/google/home/apavlov/dev/blink/src/third_party/WebKit/Source/devtools/scripts/jsdoc-validator/tests/this.js:227: ERROR - Function annotated with @this used as argument without bind(<non-null-receiver>) /usr/local/google/home/apavlov/dev/blink/src/third_party/WebKit/Source/devtools/scripts/jsdoc-validator/tests/this.js:227: ERROR - Function annotated with @this used as argument without a receiver. This check can be suppressed using @suppressReceiverCheck annotation on function declaration.
this.memberTwo(callbackWithThis); // ERROR - Used as argument with no bound receiver. this.memberTwo(callbackWithThis); // ERROR - Used as argument with no bound receiver.
^ ^
...@@ -250,4 +250,24 @@ function badFuncAnnotatedButNoReturnValue() // ERROR - no returned value. ...@@ -250,4 +250,24 @@ function badFuncAnnotatedButNoReturnValue() // ERROR - no returned value.
this.memberTwo(callbackNoThis.bind(foo)); // ERROR - Bound to a receiver but has no @this annotation. this.memberTwo(callbackNoThis.bind(foo)); // ERROR - Bound to a receiver but has no @this annotation.
^ ^
Total errors: 63 /usr/local/google/home/apavlov/dev/blink/src/third_party/WebKit/Source/devtools/scripts/jsdoc-validator/tests/this.js:237: ERROR - Function annotated with @this used as argument without a receiver. This check can be suppressed using @suppressReceiverCheck annotation on function declaration.
array.forEach(callbackWithThis); // ERROR - No receiver.
^
/usr/local/google/home/apavlov/dev/blink/src/third_party/WebKit/Source/devtools/scripts/jsdoc-validator/tests/this.js:238: ERROR - Function not annotated with @this used as argument with a receiver. This check can be suppressed using @suppressReceiverCheck annotation on function declaration.
array.forEach(callbackNoThis, this); // ERROR - Receiver for callback with no @this annotation.
^
/usr/local/google/home/apavlov/dev/blink/src/third_party/WebKit/Source/devtools/scripts/jsdoc-validator/tests/this.js:247: ERROR - Function not annotated with @this used as argument with a receiver. This check can be suppressed using @suppressReceiverCheck annotation on function declaration.
element.addEventListener("click", callbackNoThis, this); // ERROR.
^
/usr/local/google/home/apavlov/dev/blink/src/third_party/WebKit/Source/devtools/scripts/jsdoc-validator/tests/this.js:253: ERROR - Function annotated with @this used as argument without a receiver. This check can be suppressed using @suppressReceiverCheck annotation on function declaration.
element.addEventListener("click", callbackWithThis, true); // ERROR.
^
/usr/local/google/home/apavlov/dev/blink/src/third_party/WebKit/Source/devtools/scripts/jsdoc-validator/tests/this.js:254: ERROR - Function annotated with @this used as argument without a receiver. This check can be suppressed using @suppressReceiverCheck annotation on function declaration.
element.addEventListener("click", callbackWithThis, false); // ERROR.
^
Total errors: 68
...@@ -228,6 +228,42 @@ ReceiverTest.prototype = { ...@@ -228,6 +228,42 @@ ReceiverTest.prototype = {
this.memberTwo(callbackWithThis.bind(null, 2)); // ERROR - Used as argument with no bound receiver (null means "no receiver"). this.memberTwo(callbackWithThis.bind(null, 2)); // ERROR - Used as argument with no bound receiver (null means "no receiver").
this.memberTwo(callbackNoThis.bind(this)); // ERROR - Bound to a receiver but has no @this annotation. this.memberTwo(callbackNoThis.bind(this)); // ERROR - Bound to a receiver but has no @this annotation.
this.memberTwo(callbackNoThis.bind(foo)); // ERROR - Bound to a receiver but has no @this annotation. this.memberTwo(callbackNoThis.bind(foo)); // ERROR - Bound to a receiver but has no @this annotation.
// Callback receivers specified as arguments.
array.forEach(callbackWithThis, this);
array.forEach(callbackNoThis);
array.forEach(callbackWithThis); // ERROR - No receiver.
array.forEach(callbackNoThis, this); // ERROR - Receiver for callback with no @this annotation.
var isMultiline = false;
element.addEventListener("click", callbackNoThis);
element.addEventListener("click", callbackNoThis, true);
element.addEventListener("click", callbackNoThis, false);
element.addEventListener("click", callbackNoThis, isMultiline); // OK - ignored.
element.addEventListener("click", callbackNoThis, this); // ERROR.
element.addEventListener("click", callbackWithThis, this);
element.addEventListener("click", callbackWithThis, foo); // OK - ignored.
element.addEventListener("click", callbackWithThis, isMultiline); // OK - ignored.
element.addEventListener("click", callbackWithThis, true); // ERROR.
element.addEventListener("click", callbackWithThis, false); // ERROR.
// DevTools-specific.
/**
* @suppressReceiverCheck
* @this {Object}
*/
function ignoredCallbackWithThis()
{
this.foo = 1;
}
object.callFunction(func, [], ignoredCallbackWithThis); // OK - ignored.
}, },
memberTwo: function(arg) {} memberTwo: function(arg) {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment