1 | /**
|
2 | * require `$on` and `$watch` deregistration callbacks to be saved in a variable
|
3 | *
|
4 | * Watch and On methods on the scope object should be assigned to a variable, in order to be deleted in a $destroy event handler
|
5 | * @version 0.1.0
|
6 | * @category bestPractice
|
7 | * @sinceAngularVersion 1.x
|
8 | */
|
9 | ;
|
10 |
|
11 | module.exports = {
|
12 | meta: {
|
13 | schema: []
|
14 | },
|
15 | create: function(context) {
|
16 | function report(node, method) {
|
17 | context.report(node, 'The "{{method}}" call should be assigned to a variable, in order to be destroyed during the $destroy event', {
|
18 | method: method
|
19 | });
|
20 | }
|
21 |
|
22 | /**
|
23 | * Return true if the given node is a call expression calling a function
|
24 | * named '$on' or '$watch' on an object named '$scope', '$rootScope' or
|
25 | * 'scope'.
|
26 | */
|
27 | function isScopeOnOrWatch(node, scopes) {
|
28 | if (node.type !== 'CallExpression') {
|
29 | return false;
|
30 | }
|
31 |
|
32 | var calledFunction = node.callee;
|
33 | if (calledFunction.type !== 'MemberExpression') {
|
34 | return false;
|
35 | }
|
36 |
|
37 | // can only easily tell what name was used if a simple
|
38 | // identifiers were used to access it.
|
39 | var parentObject = calledFunction.object;
|
40 | var accessedFunction = calledFunction.property;
|
41 |
|
42 | // cannot check name of the parent object if it is returned from a
|
43 | // complex expression.
|
44 | if (parentObject.type !== 'Identifier' ||
|
45 | accessedFunction.type !== 'Identifier') {
|
46 | return false;
|
47 | }
|
48 |
|
49 | var objectName = parentObject.name;
|
50 | var functionName = accessedFunction.name;
|
51 |
|
52 | return scopes.indexOf(objectName) >= 0 && (functionName === '$on' ||
|
53 | functionName === '$watch');
|
54 | }
|
55 |
|
56 | /**
|
57 | * Return true if the given node is a call expression that has a first
|
58 | * argument of the string '$destroy'.
|
59 | */
|
60 | function isFirstArgDestroy(node) {
|
61 | var args = node.arguments;
|
62 |
|
63 | return (args.length >= 1 &&
|
64 | args[0].type === 'Literal' &&
|
65 | args[0].value === '$destroy');
|
66 | }
|
67 |
|
68 | return {
|
69 |
|
70 | CallExpression: function(node) {
|
71 | if (isScopeOnOrWatch(node, ['$rootScope']) && !isFirstArgDestroy(node)) {
|
72 | if (node.parent.type !== 'VariableDeclarator' &&
|
73 | node.parent.type !== 'AssignmentExpression' &&
|
74 | !(isScopeOnOrWatch(node.parent, ['$rootScope', '$scope', 'scope']) &&
|
75 | isFirstArgDestroy(node.parent))) {
|
76 | report(node, node.callee.property.name);
|
77 | }
|
78 | }
|
79 | }
|
80 | };
|
81 | }
|
82 | };
|