• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Rule to warn when a function expression does not have a name.
3 * @author Kyle T. Nunery
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14/**
15 * Checks whether or not a given variable is a function name.
16 * @param {eslint-scope.Variable} variable A variable to check.
17 * @returns {boolean} `true` if the variable is a function name.
18 */
19function isFunctionName(variable) {
20    return variable && variable.defs[0].type === "FunctionName";
21}
22
23//------------------------------------------------------------------------------
24// Rule Definition
25//------------------------------------------------------------------------------
26
27module.exports = {
28    meta: {
29        type: "suggestion",
30
31        docs: {
32            description: "require or disallow named `function` expressions",
33            category: "Stylistic Issues",
34            recommended: false,
35            url: "https://eslint.org/docs/rules/func-names"
36        },
37
38        schema: {
39            definitions: {
40                value: {
41                    enum: [
42                        "always",
43                        "as-needed",
44                        "never"
45                    ]
46                }
47            },
48            items: [
49                {
50                    $ref: "#/definitions/value"
51                },
52                {
53                    type: "object",
54                    properties: {
55                        generators: {
56                            $ref: "#/definitions/value"
57                        }
58                    },
59                    additionalProperties: false
60                }
61            ]
62        },
63
64        messages: {
65            unnamed: "Unexpected unnamed {{name}}.",
66            named: "Unexpected named {{name}}."
67        }
68    },
69
70    create(context) {
71
72        const sourceCode = context.getSourceCode();
73
74        /**
75         * Returns the config option for the given node.
76         * @param {ASTNode} node A node to get the config for.
77         * @returns {string} The config option.
78         */
79        function getConfigForNode(node) {
80            if (
81                node.generator &&
82                context.options.length > 1 &&
83                context.options[1].generators
84            ) {
85                return context.options[1].generators;
86            }
87
88            return context.options[0] || "always";
89        }
90
91        /**
92         * Determines whether the current FunctionExpression node is a get, set, or
93         * shorthand method in an object literal or a class.
94         * @param {ASTNode} node A node to check.
95         * @returns {boolean} True if the node is a get, set, or shorthand method.
96         */
97        function isObjectOrClassMethod(node) {
98            const parent = node.parent;
99
100            return (parent.type === "MethodDefinition" || (
101                parent.type === "Property" && (
102                    parent.method ||
103                    parent.kind === "get" ||
104                    parent.kind === "set"
105                )
106            ));
107        }
108
109        /**
110         * Determines whether the current FunctionExpression node has a name that would be
111         * inferred from context in a conforming ES6 environment.
112         * @param {ASTNode} node A node to check.
113         * @returns {boolean} True if the node would have a name assigned automatically.
114         */
115        function hasInferredName(node) {
116            const parent = node.parent;
117
118            return isObjectOrClassMethod(node) ||
119                (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
120                (parent.type === "Property" && parent.value === node) ||
121                (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
122                (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node);
123        }
124
125        /**
126         * Reports that an unnamed function should be named
127         * @param {ASTNode} node The node to report in the event of an error.
128         * @returns {void}
129         */
130        function reportUnexpectedUnnamedFunction(node) {
131            context.report({
132                node,
133                messageId: "unnamed",
134                loc: astUtils.getFunctionHeadLoc(node, sourceCode),
135                data: { name: astUtils.getFunctionNameWithKind(node) }
136            });
137        }
138
139        /**
140         * Reports that a named function should be unnamed
141         * @param {ASTNode} node The node to report in the event of an error.
142         * @returns {void}
143         */
144        function reportUnexpectedNamedFunction(node) {
145            context.report({
146                node,
147                messageId: "named",
148                loc: astUtils.getFunctionHeadLoc(node, sourceCode),
149                data: { name: astUtils.getFunctionNameWithKind(node) }
150            });
151        }
152
153        /**
154         * The listener for function nodes.
155         * @param {ASTNode} node function node
156         * @returns {void}
157         */
158        function handleFunction(node) {
159
160            // Skip recursive functions.
161            const nameVar = context.getDeclaredVariables(node)[0];
162
163            if (isFunctionName(nameVar) && nameVar.references.length > 0) {
164                return;
165            }
166
167            const hasName = Boolean(node.id && node.id.name);
168            const config = getConfigForNode(node);
169
170            if (config === "never") {
171                if (hasName && node.type !== "FunctionDeclaration") {
172                    reportUnexpectedNamedFunction(node);
173                }
174            } else if (config === "as-needed") {
175                if (!hasName && !hasInferredName(node)) {
176                    reportUnexpectedUnnamedFunction(node);
177                }
178            } else {
179                if (!hasName && !isObjectOrClassMethod(node)) {
180                    reportUnexpectedUnnamedFunction(node);
181                }
182            }
183        }
184
185        return {
186            "FunctionExpression:exit": handleFunction,
187            "ExportDefaultDeclaration > FunctionDeclaration": handleFunction
188        };
189    }
190};
191