UNPKG

5.49 kBJavaScriptView Raw
1/**
2 * Requires function names to match member and property names.
3 *
4 * It doesn't affect anonymous functions nor functions assigned to members or
5 * properties named with a reserved word. Assigning to `module.exports` is also
6 * ignored, unless `includeModuleExports: true` is configured.
7 *
8 * Types: `Boolean` or `Object`
9 *
10 * Values: `true` or Object with `includeModuleExports: true`
11 *
12 * #### Example
13 *
14 * ```js
15 * "requireMatchingFunctionName": true
16 * ```
17 *
18 * ##### Valid
19 *
20 * ```js
21 * var test = {};
22 * test.foo = function foo() {};
23 * ```
24 *
25 * ```js
26 * var test = {};
27 * test['foo'] = function foo() {};
28 * ```
29 *
30 * ```js
31 * var test = {foo: function foo() {}};
32 * ```
33 *
34 * ```js
35 * module.exports = function foo() {};
36 * ```
37 *
38 * ```js
39 * module['exports'] = function foo() {};
40 * ```
41 *
42 * ##### Invalid
43 *
44 * ```js
45 * var test = {};
46 * test.foo = function bar() {};
47 * ```
48 *
49 * ```js
50 * var test = {};
51 * test['foo'] = function bar() {};
52 * ```
53 *
54 * ```js
55 * var test = {foo: function bar() {}};
56 * ```
57 *
58 * ```js
59 * var test = {module: {}};
60 * test.module.exports = function foo() {};
61 * ```
62 *
63 * #### Example
64 *
65 * ```js
66 * "requireMatchingFunctionName": { "includeModuleExports": true }
67 * ```
68 *
69 * ##### Invalid
70 *
71 * ```js
72 * module.exports = function foo() {};
73 * ```
74 *
75 * ```js
76 * module['exports'] = function foo() {};
77 * ```
78 */
79
80var assert = require('assert');
81var reservedWords = require('reserved-words');
82
83module.exports = function() {};
84
85module.exports.prototype = {
86 configure: function(requireMatchingFunctionName) {
87 if (typeof requireMatchingFunctionName === 'object') {
88 assert(requireMatchingFunctionName.includeModuleExports === true,
89 'requireMatchingFunctionName option requires includeModuleExports property to be true for object');
90 this._includeModuleExports = requireMatchingFunctionName.includeModuleExports;
91 } else {
92 assert(
93 requireMatchingFunctionName === true,
94 'requireMatchingFunctionName option requires true value or should be removed'
95 );
96 }
97 },
98
99 getOptionName: function() {
100 return 'requireMatchingFunctionName';
101 },
102
103 check: function(file, errors) {
104 var _includeModuleExports = this._includeModuleExports;
105 file.iterateNodesByType(['FunctionExpression'], function(node) {
106 switch (node.parentElement.type) {
107 // var foo = function bar() {}
108 // object.foo = function bar() {}
109 // object['foo'] = function bar() {}
110 case 'AssignmentExpression':
111 if (_includeModuleExports || !_isModuleExports(node.parentElement.left)) {
112 checkForMember(node.parentElement, skip, errors);
113 }
114 break;
115
116 // object = {foo: function bar() {}}
117 case 'ObjectProperty':
118 checkForProperty(node.parentElement, skip, errors);
119 break;
120 }
121 });
122
123 function skip(key, value) {
124 // We don't care about anonymous functions as
125 // those should be enforced by separate rule
126 if (!value.id) {
127 return true;
128 }
129
130 // Relax a bit when reserved word is detected
131 if (reservedWords.check(key, file.getDialect(), true)) {
132 return true;
133 }
134 }
135 }
136};
137
138function _isModuleExports(pattern) {
139 if (pattern.type === 'MemberExpression') {
140 // must be module.sth
141 if (pattern.object.type === 'Identifier' &&
142 pattern.object.name === 'module') {
143
144 if (pattern.property.type === 'Identifier' &&
145 pattern.property.name === 'exports') {
146 // sth.exports
147 return true;
148 } else if (pattern.property.type === 'StringLiteral' &&
149 pattern.property.value === 'exports') {
150 // sth["exports"]
151 return true;
152 }
153 }
154 }
155 return false;
156}
157
158/**
159 * Fetching name from a Pattern
160 *
161 * @param {Pattern} pattern - E.g. left side of AssignmentExpression
162 * @returns {String|Boolean} - Resolved name or false (if there is an Expression)
163 */
164function _resolvePatternName(pattern) {
165 switch (pattern.type) {
166 case 'Identifier':
167 // prop = ...;
168 return pattern.name;
169 case 'StringLiteral':
170 // obj['prop'] = ...;
171 return pattern.value;
172 case 'MemberExpression':
173 // obj.prop = ...;
174 return _resolvePatternName(pattern.property);
175 default:
176 // Something unhandy like obj['x' + 2] = ...;
177 return false;
178 }
179}
180
181function checkForMember(assignment, skip, errors) {
182 var _name = _resolvePatternName(assignment.left);
183 if (_name === false || skip(_name, assignment.right)) {
184 return;
185 }
186
187 if (_name !== assignment.right.id.name) {
188 errors.add(
189 'Function name does not match member name',
190 assignment
191 );
192 }
193}
194
195function checkForProperty(property, skip, errors) {
196 var _name = _resolvePatternName(property.key);
197 if (_name === false || skip(_name, property.value)) {
198 return;
199 }
200
201 if (_name !== property.value.id.name) {
202 errors.add(
203 'Function name does not match property name',
204 property
205 );
206 }
207}