summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/rules/func-names.js
blob: ff3a1f4b5bf890460f76e4eca0968f57f5c89811 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/**
 * @fileoverview Rule to warn when a function expression does not have a name.
 * @author Kyle T. Nunery
 */

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const astUtils = require("./utils/ast-utils");

/**
 * Checks whether or not a given variable is a function name.
 * @param {eslint-scope.Variable} variable - A variable to check.
 * @returns {boolean} `true` if the variable is a function name.
 */
function isFunctionName(variable) {
    return variable && variable.defs[0].type === "FunctionName";
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "require or disallow named `function` expressions",
            category: "Stylistic Issues",
            recommended: false,
            url: "https://eslint.org/docs/rules/func-names"
        },

        schema: {
            definitions: {
                value: {
                    enum: [
                        "always",
                        "as-needed",
                        "never"
                    ]
                }
            },
            items: [
                {
                    $ref: "#/definitions/value"
                },
                {
                    type: "object",
                    properties: {
                        generators: {
                            $ref: "#/definitions/value"
                        }
                    },
                    additionalProperties: false
                }
            ]
        },

        messages: {
            unnamed: "Unexpected unnamed {{name}}.",
            named: "Unexpected named {{name}}."
        }
    },

    create(context) {

        const sourceCode = context.getSourceCode();

        /**
         * Returns the config option for the given node.
         * @param {ASTNode} node - A node to get the config for.
         * @returns {string} The config option.
         */
        function getConfigForNode(node) {
            if (
                node.generator &&
                context.options.length > 1 &&
                context.options[1].generators
            ) {
                return context.options[1].generators;
            }

            return context.options[0] || "always";
        }

        /**
         * Determines whether the current FunctionExpression node is a get, set, or
         * shorthand method in an object literal or a class.
         * @param {ASTNode} node - A node to check.
         * @returns {boolean} True if the node is a get, set, or shorthand method.
         */
        function isObjectOrClassMethod(node) {
            const parent = node.parent;

            return (parent.type === "MethodDefinition" || (
                parent.type === "Property" && (
                    parent.method ||
                    parent.kind === "get" ||
                    parent.kind === "set"
                )
            ));
        }

        /**
         * Determines whether the current FunctionExpression node has a name that would be
         * inferred from context in a conforming ES6 environment.
         * @param {ASTNode} node - A node to check.
         * @returns {boolean} True if the node would have a name assigned automatically.
         */
        function hasInferredName(node) {
            const parent = node.parent;

            return isObjectOrClassMethod(node) ||
                (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
                (parent.type === "Property" && parent.value === node) ||
                (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
                (parent.type === "ExportDefaultDeclaration" && parent.declaration === node) ||
                (parent.type === "AssignmentPattern" && parent.right === node);
        }

        /**
         * Reports that an unnamed function should be named
         * @param {ASTNode} node - The node to report in the event of an error.
         * @returns {void}
         */
        function reportUnexpectedUnnamedFunction(node) {
            context.report({
                node,
                messageId: "unnamed",
                loc: astUtils.getFunctionHeadLoc(node, sourceCode),
                data: { name: astUtils.getFunctionNameWithKind(node) }
            });
        }

        /**
         * Reports that a named function should be unnamed
         * @param {ASTNode} node - The node to report in the event of an error.
         * @returns {void}
         */
        function reportUnexpectedNamedFunction(node) {
            context.report({
                node,
                messageId: "named",
                loc: astUtils.getFunctionHeadLoc(node, sourceCode),
                data: { name: astUtils.getFunctionNameWithKind(node) }
            });
        }

        return {
            "FunctionExpression:exit"(node) {

                // Skip recursive functions.
                const nameVar = context.getDeclaredVariables(node)[0];

                if (isFunctionName(nameVar) && nameVar.references.length > 0) {
                    return;
                }

                const hasName = Boolean(node.id && node.id.name);
                const config = getConfigForNode(node);

                if (config === "never") {
                    if (hasName) {
                        reportUnexpectedNamedFunction(node);
                    }
                } else if (config === "as-needed") {
                    if (!hasName && !hasInferredName(node)) {
                        reportUnexpectedUnnamedFunction(node);
                    }
                } else {
                    if (!hasName && !isObjectOrClassMethod(node)) {
                        reportUnexpectedUnnamedFunction(node);
                    }
                }
            }
        };
    }
};