UNPKG

4.28 kBJavaScriptView Raw
1/**
2 * Require unassigned functions to be named inline
3 *
4 * Types: `Boolean` or `Object`
5 *
6 * Values:
7 * - `true`
8 * - `Object`:
9 * - `allExcept`: array of quoted identifiers
10 *
11 * #### Example
12 *
13 * ```js
14 * "requireNamedUnassignedFunctions": { "allExcept": ["describe", "it"] }
15 * ```
16 *
17 * ##### Valid
18 *
19 * ```js
20 * [].forEach(function x() {});
21 * var y = function() {};
22 * function z() {}
23 * it(function () {});
24 * ```
25 *
26 * ##### Invalid
27 *
28 * ```js
29 * [].forEach(function () {});
30 * before(function () {});
31 * ```
32 */
33
34var assert = require('assert');
35var pathval = require('pathval');
36
37function getNodeName(node) {
38 if (node.type === 'Identifier') {
39 return node.name;
40 } else {
41 return node.value;
42 }
43}
44
45module.exports = function() {};
46
47module.exports.prototype = {
48 configure: function(options) {
49 assert(
50 options === true ||
51 typeof options === 'object',
52 this.getOptionName() + ' option requires true value ' +
53 'or an object with String[] `allExcept` property'
54 );
55
56 // verify first item in `allExcept` property in object (if it's an object)
57 assert(
58 typeof options !== 'object' ||
59 Array.isArray(options.allExcept) &&
60 typeof options.allExcept[0] === 'string',
61 'Property `allExcept` in ' + this.getOptionName() + ' should be an array of strings'
62 );
63
64 if (options.allExcept) {
65 this._allExceptItems = options.allExcept.map(function(item) {
66 var parts = pathval.parse(item).map(function extractPart(part) {
67 return part.i !== undefined ? part.i : part.p;
68 });
69 return JSON.stringify(parts);
70 });
71 }
72 },
73
74 getOptionName: function() {
75 return 'requireNamedUnassignedFunctions';
76 },
77
78 check: function(file, errors) {
79 var _this = this;
80 file.iterateNodesByType('FunctionExpression', function(node) {
81 var parentElement = node.parentElement;
82 // If the function has been named via left hand assignment, skip it
83 // e.g. `var hello = function() {`, `foo.bar = function() {`
84 if (parentElement.type.match(/VariableDeclarator|Property|AssignmentExpression/)) {
85 return;
86 }
87
88 // If the function has been named, skip it
89 // e.g. `[].forEach(function hello() {`
90 if (node.id !== null) {
91 return;
92 }
93
94 // If we have exceptions and the function is being invoked, detect whether we excepted it
95 if (_this._allExceptItems && parentElement.type === 'CallExpression') {
96 // Determine the path that resolves to our call expression
97 // We must cover both direct calls (e.g. `it(function() {`) and
98 // member expressions (e.g. `foo.bar(function() {`)
99 var memberNode = parentElement.callee;
100 var canBeRepresented = true;
101 var fullpathParts = [];
102 while (memberNode) {
103 if (memberNode.type.match(/Identifier|Literal/)) {
104 fullpathParts.unshift(getNodeName(memberNode));
105 } else if (memberNode.type === 'MemberExpression') {
106 fullpathParts.unshift(getNodeName(memberNode.property));
107 } else {
108 canBeRepresented = false;
109 break;
110 }
111 memberNode = memberNode.object;
112 }
113
114 // If the path is not-dynamic (i.e. can be represented by static parts),
115 // then check it against our exceptions
116 if (canBeRepresented) {
117 var fullpath = JSON.stringify(fullpathParts);
118 for (var i = 0, l = _this._allExceptItems.length; i < l; i++) {
119 if (fullpath === _this._allExceptItems[i]) {
120 return;
121 }
122 }
123 }
124 }
125
126 // Complain that this function must be named
127 errors.add('Inline functions need to be named', node);
128 });
129 }
130};