UNPKG

4.27 kBJavaScriptView Raw
1/**
2 * require and specify a capture variable for `this` in controllers
3 *
4 * You should use a capture variable for 'this' when using the controllerAs syntax.
5 * The second parameter specifies the capture variable you want to use in your application.
6 * The third parameter can be a Regexp for identifying controller functions (when using something like Browserify)
7 *
8 * ### Options
9 *
10 * - The name that should be used for the view model.
11 *
12 * @styleguideReference {johnpapa} `y032` controllerAs with vm
13 * @version 0.1.0
14 * @category bestPractice
15 * @sinceAngularVersion 1.x
16 */
17'use strict';
18
19var utils = require('./utils/utils');
20
21module.exports = {
22 meta: {
23 schema: [{
24 type: 'string'
25 }, {
26 type: 'string'
27 }]
28 },
29 create: function(context) {
30 var badStatements = [];
31 var badCaptureStatements = [];
32 var controllerFunctions = [];
33
34 var viewModelName = context.options[0] || 'vm';
35 // If your Angular code is written so that controller functions are in
36 // separate files from your .controller() calls, you can specify a regex for your controller function names
37 var controllerNameMatcher = context.options[1];
38 if (controllerNameMatcher && utils.isStringRegexp(controllerNameMatcher)) {
39 controllerNameMatcher = utils.convertStringToRegex(controllerNameMatcher);
40 }
41
42 // check node against known controller functions or pattern if specified
43 function isControllerFunction(node) {
44 return controllerFunctions.indexOf(node) >= 0 ||
45 (controllerNameMatcher && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') &&
46 node.id && controllerNameMatcher.test(node.id.name));
47 }
48
49 // for each of the bad uses, find any parent nodes that are controller functions
50 function reportBadUses() {
51 if (controllerFunctions.length > 0 || controllerNameMatcher) {
52 badCaptureStatements.forEach(function(item) {
53 item.parents.filter(isControllerFunction).forEach(function() {
54 context.report(item.stmt, 'You should assign "this" to a consistent variable across your project: {{capture}}',
55 {
56 capture: viewModelName
57 }
58 );
59 });
60 });
61 badStatements.forEach(function(item) {
62 item.parents.filter(isControllerFunction).forEach(function() {
63 context.report(item.stmt, 'You should not use "this" directly. Instead, assign it to a variable called "{{capture}}"',
64 {
65 capture: viewModelName
66 }
67 );
68 });
69 });
70 }
71 }
72
73 function isClassDeclaration(ancestors) {
74 return ancestors.findIndex(function(ancestor) {
75 return ancestor.type === 'ClassDeclaration';
76 }) > -1;
77 }
78
79 return {
80 // Looking for .controller() calls here and getting the associated controller function
81 'CallExpression:exit': function(node) {
82 if (utils.isAngularControllerDeclaration(node)) {
83 controllerFunctions.push(utils.getControllerDefinition(context, node));
84 }
85 },
86 // statements are checked here for bad uses of $scope
87 ThisExpression: function(stmt) {
88 var parents = context.getAncestors();
89 if (!isClassDeclaration(parents)) {
90 if (stmt.parent.type === 'VariableDeclarator') {
91 if (!stmt.parent.id || stmt.parent.id.name !== viewModelName) {
92 badCaptureStatements.push({parents: parents, stmt: stmt});
93 }
94 } else {
95 badStatements.push({parents: parents, stmt: stmt});
96 }
97 }
98 },
99 'Program:exit': function() {
100 reportBadUses();
101 }
102 };
103 }
104};