• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Disallow renaming import, export, and destructured assignments to the same name.
3 * @author Kai Cataldo
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13    meta: {
14        type: "suggestion",
15
16        docs: {
17            description: "disallow renaming import, export, and destructured assignments to the same name",
18            category: "ECMAScript 6",
19            recommended: false,
20            url: "https://eslint.org/docs/rules/no-useless-rename"
21        },
22
23        fixable: "code",
24
25        schema: [
26            {
27                type: "object",
28                properties: {
29                    ignoreDestructuring: { type: "boolean", default: false },
30                    ignoreImport: { type: "boolean", default: false },
31                    ignoreExport: { type: "boolean", default: false }
32                },
33                additionalProperties: false
34            }
35        ],
36
37        messages: {
38            unnecessarilyRenamed: "{{type}} {{name}} unnecessarily renamed."
39        }
40    },
41
42    create(context) {
43        const sourceCode = context.getSourceCode(),
44            options = context.options[0] || {},
45            ignoreDestructuring = options.ignoreDestructuring === true,
46            ignoreImport = options.ignoreImport === true,
47            ignoreExport = options.ignoreExport === true;
48
49        //--------------------------------------------------------------------------
50        // Helpers
51        //--------------------------------------------------------------------------
52
53        /**
54         * Reports error for unnecessarily renamed assignments
55         * @param {ASTNode} node node to report
56         * @param {ASTNode} initial node with initial name value
57         * @param {ASTNode} result node with new name value
58         * @param {string} type the type of the offending node
59         * @returns {void}
60         */
61        function reportError(node, initial, result, type) {
62            const name = initial.type === "Identifier" ? initial.name : initial.value;
63
64            return context.report({
65                node,
66                messageId: "unnecessarilyRenamed",
67                data: {
68                    name,
69                    type
70                },
71                fix(fixer) {
72                    if (sourceCode.commentsExistBetween(initial, result)) {
73                        return null;
74                    }
75
76                    const replacementText = result.type === "AssignmentPattern"
77                        ? sourceCode.getText(result)
78                        : name;
79
80                    return fixer.replaceTextRange([
81                        initial.range[0],
82                        result.range[1]
83                    ], replacementText);
84                }
85            });
86        }
87
88        /**
89         * Checks whether a destructured assignment is unnecessarily renamed
90         * @param {ASTNode} node node to check
91         * @returns {void}
92         */
93        function checkDestructured(node) {
94            if (ignoreDestructuring) {
95                return;
96            }
97
98            for (const property of node.properties) {
99
100                /*
101                 * TODO: Remove after babel-eslint removes ExperimentalRestProperty
102                 * https://github.com/eslint/eslint/issues/12335
103                 */
104                if (property.type === "ExperimentalRestProperty") {
105                    continue;
106                }
107
108                /**
109                 * Properties using shorthand syntax and rest elements can not be renamed.
110                 * If the property is computed, we have no idea if a rename is useless or not.
111                 */
112                if (property.shorthand || property.type === "RestElement" || property.computed) {
113                    continue;
114                }
115
116                const key = (property.key.type === "Identifier" && property.key.name) || (property.key.type === "Literal" && property.key.value);
117                const renamedKey = property.value.type === "AssignmentPattern" ? property.value.left.name : property.value.name;
118
119                if (key === renamedKey) {
120                    reportError(property, property.key, property.value, "Destructuring assignment");
121                }
122            }
123        }
124
125        /**
126         * Checks whether an import is unnecessarily renamed
127         * @param {ASTNode} node node to check
128         * @returns {void}
129         */
130        function checkImport(node) {
131            if (ignoreImport) {
132                return;
133            }
134
135            if (node.imported.name === node.local.name &&
136                    node.imported.range[0] !== node.local.range[0]) {
137                reportError(node, node.imported, node.local, "Import");
138            }
139        }
140
141        /**
142         * Checks whether an export is unnecessarily renamed
143         * @param {ASTNode} node node to check
144         * @returns {void}
145         */
146        function checkExport(node) {
147            if (ignoreExport) {
148                return;
149            }
150
151            if (node.local.name === node.exported.name &&
152                    node.local.range[0] !== node.exported.range[0]) {
153                reportError(node, node.local, node.exported, "Export");
154            }
155
156        }
157
158        //--------------------------------------------------------------------------
159        // Public
160        //--------------------------------------------------------------------------
161
162        return {
163            ObjectPattern: checkDestructured,
164            ImportSpecifier: checkImport,
165            ExportSpecifier: checkExport
166        };
167    }
168};
169