1// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4/* eslint-disable */
5
6/**
7 * @fileoverview Rule to flag non-camelcased identifiers
8 * @author Nicholas C. Zakas
9 */
10
11'use strict';
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17module.exports = {
18    meta: {
19        docs: {
20            description: "enforce Catapult camelcase naming convention",
21            category: "Stylistic Issues",
22            recommended: false
23        },
24
25        schema: [
26            {
27                type: "object",
28                properties: {
29                    properties: {
30                        enum: ["always", "never"]
31                    }
32                },
33                additionalProperties: false
34            }
35        ]
36    },
37
38    create(context) {
39
40        //--------------------------------------------------------------------------
41        // Helpers
42        //--------------------------------------------------------------------------
43
44        // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
45        var reported = [];
46
47        /**
48         * Checks if a string contains an underscore and isn't all upper-case
49         * @param {string} name The string to check.
50         * @returns {boolean} if the string is underscored
51         * @private
52         */
53        function isUnderscored(name) {
54
55            // if there's an underscore, it might be A_VARANT, which is okay
56            return name.indexOf("_") > -1 && name !== name.toUpperCase();
57        }
58
59        /**
60         * Reports an AST node as a rule violation.
61         * @param {ASTNode} node The node to report.
62         * @returns {void}
63         * @private
64         */
65        function report(node) {
66            if (reported.indexOf(node) < 0) {
67                reported.push(node);
68                context.report(node, "Identifier '{{name}}' is not in camel case.", { name: node.name });
69            }
70        }
71
72        var options = context.options[0] || {};
73        let properties = options.properties || "";
74
75        if (properties !== "always" && properties !== "never") {
76            properties = "always";
77        }
78
79        return {
80
81            Identifier(node) {
82
83                /*
84                 * Leading and trailing underscores are commonly used to flag
85                 * private/protected identifiers, strip them.
86                 *
87                 * NOTE: This has four Catapult-specific style exceptions:
88                 *
89                 *   - The prefix opt_
90                 *   - The prefix g_
91                 *   - The suffix _smallerIsBetter
92                 *   - The suffix _biggerIsBetter
93                 */
94                var name = node.name.replace(/(?:^opt_)|^(?:^g_)|^_+|_+$|(?:_smallerIsBetter)$|(?:_biggerIsBetter)$/g, ""),
95                    effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
96
97                // MemberExpressions get special rules
98                if (node.parent.type === "MemberExpression") {
99
100                    // "never" check properties
101                    if (properties === "never") {
102                        return;
103                    }
104
105                    // Always report underscored object names
106                    if (node.parent.object.type === "Identifier" &&
107                            node.parent.object.name === node.name &&
108                            isUnderscored(name)) {
109                        report(node);
110
111                    // Report AssignmentExpressions only if they are the left side of the assignment
112                    } else if (effectiveParent.type === "AssignmentExpression" &&
113                            isUnderscored(name) &&
114                            (effectiveParent.right.type !== "MemberExpression" ||
115                            effectiveParent.left.type === "MemberExpression" &&
116                            effectiveParent.left.property.name === node.name)) {
117                        report(node);
118                    }
119
120                // Properties have their own rules
121                } else if (node.parent.type === "Property") {
122
123                    // "never" check properties
124                    if (properties === "never") {
125                        return;
126                    }
127
128                    if (node.parent.parent && node.parent.parent.type === "ObjectPattern" &&
129                            node.parent.key === node && node.parent.value !== node) {
130                        return;
131                    }
132
133                    if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
134                        report(node);
135                    }
136
137                // Check if it's an import specifier
138                } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].indexOf(node.parent.type) >= 0) {
139
140                    // Report only if the local imported identifier is underscored
141                    if (node.parent.local && node.parent.local.name === node.name && isUnderscored(name)) {
142                        report(node);
143                    }
144
145                // Report anything that is underscored that isn't a CallExpression
146                } else if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
147                    report(node);
148                }
149            }
150
151        };
152
153    }
154};
155