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 |
|
34 | var assert = require('assert');
|
35 | var pathval = require('pathval');
|
36 |
|
37 | function getNodeName(node) {
|
38 | if (node.type === 'Identifier') {
|
39 | return node.name;
|
40 | } else {
|
41 | return node.value;
|
42 | }
|
43 | }
|
44 |
|
45 | module.exports = function() {};
|
46 |
|
47 | module.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 | };
|