aboutsummaryrefslogtreecommitdiff
path: root/tools/eslint/lib/rules/new-cap.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/eslint/lib/rules/new-cap.js')
-rw-r--r--tools/eslint/lib/rules/new-cap.js197
1 files changed, 197 insertions, 0 deletions
diff --git a/tools/eslint/lib/rules/new-cap.js b/tools/eslint/lib/rules/new-cap.js
new file mode 100644
index 0000000000..1474fc8028
--- /dev/null
+++ b/tools/eslint/lib/rules/new-cap.js
@@ -0,0 +1,197 @@
+/**
+ * @fileoverview Rule to flag use of constructors without capital letters
+ * @author Nicholas C. Zakas
+ * @copyright 2014 Jordan Harband. All rights reserved.
+ * @copyright 2013-2014 Nicholas C. Zakas. All rights reserved.
+ */
+
+"use strict";
+
+var CAPS_ALLOWED = [
+ "Array",
+ "Boolean",
+ "Date",
+ "Error",
+ "Function",
+ "Number",
+ "Object",
+ "RegExp",
+ "String",
+ "Symbol"
+];
+
+/**
+ * Ensure that if the key is provided, it must be an array.
+ * @param {Object} obj Object to check with `key`.
+ * @param {string} key Object key to check on `obj`.
+ * @param {*} fallback If obj[key] is not present, this will be returned.
+ * @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback`
+ */
+function checkArray(obj, key, fallback) {
+ if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
+ throw new TypeError(key + ", if provided, must be an Array");
+ }
+ return obj[key] || fallback;
+}
+
+/**
+ * A reducer function to invert an array to an Object mapping the string form of the key, to `true`.
+ * @param {Object} map Accumulator object for the reduce.
+ * @param {string} key Object key to set to `true`.
+ * @returns {Object} Returns the updated Object for further reduction.
+ */
+function invert(map, key) {
+ map[key] = true;
+ return map;
+}
+
+/**
+ * Creates an object with the cap is new exceptions as its keys and true as their values.
+ * @param {Object} config Rule configuration
+ * @returns {Object} Object with cap is new exceptions.
+ */
+function calculateCapIsNewExceptions(config) {
+ var capIsNewExceptions = checkArray(config, "capIsNewExceptions", CAPS_ALLOWED);
+
+ if (capIsNewExceptions !== CAPS_ALLOWED) {
+ capIsNewExceptions = capIsNewExceptions.concat(CAPS_ALLOWED);
+ }
+
+ return capIsNewExceptions.reduce(invert, {});
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var config = context.options[0] || {};
+ config.newIsCap = config.newIsCap === false ? false : true;
+ config.capIsNew = config.capIsNew === false ? false : true;
+
+ var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
+
+ var capIsNewExceptions = calculateCapIsNewExceptions(config);
+
+ var listeners = {};
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Get exact callee name from expression
+ * @param {ASTNode} node CallExpression or NewExpression node
+ * @returns {string} name
+ */
+ function extractNameFromExpression(node) {
+
+ var name = "",
+ property;
+
+ if (node.callee.type === "MemberExpression") {
+ property = node.callee.property;
+
+ if (property.type === "Literal" && (typeof property.value === "string")) {
+ name = property.value;
+ } else if (property.type === "Identifier" && !node.callee.computed) {
+ name = property.name;
+ }
+ } else {
+ name = node.callee.name;
+ }
+ return name;
+ }
+
+ /**
+ * Returns the capitalization state of the string -
+ * Whether the first character is uppercase, lowercase, or non-alphabetic
+ * @param {string} str String
+ * @returns {string} capitalization state: "non-alpha", "lower", or "upper"
+ */
+ function getCap(str) {
+ var firstChar = str.charAt(0);
+
+ var firstCharLower = firstChar.toLowerCase();
+ var firstCharUpper = firstChar.toUpperCase();
+
+ if (firstCharLower === firstCharUpper) {
+ // char has no uppercase variant, so it's non-alphabetic
+ return "non-alpha";
+ } else if (firstChar === firstCharLower) {
+ return "lower";
+ } else {
+ return "upper";
+ }
+ }
+
+ /**
+ * Check if capitalization is allowed for a CallExpression
+ * @param {Object} allowedMap Object mapping calleeName to a Boolean
+ * @param {ASTNode} node CallExpression node
+ * @param {string} calleeName Capitalized callee name from a CallExpression
+ * @returns {Boolean} Returns true if the callee may be capitalized
+ */
+ function isCapAllowed(allowedMap, node, calleeName) {
+ if (allowedMap[calleeName]) {
+ return true;
+ }
+ if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
+ // allow if callee is Date.UTC
+ return node.callee.object.type === "Identifier" &&
+ node.callee.object.name === "Date";
+ }
+ return false;
+ }
+
+ /**
+ * Reports the given message for the given node. The location will be the start of the property or the callee.
+ * @param {ASTNode} node CallExpression or NewExpression node.
+ * @param {string} message The message to report.
+ * @returns {void}
+ */
+ function report(node, message) {
+ var callee = node.callee;
+
+ if (callee.type === "MemberExpression") {
+ callee = callee.property;
+ }
+
+ context.report(node, callee.loc.start, message);
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ if (config.newIsCap) {
+ listeners.NewExpression = function(node) {
+
+ var constructorName = extractNameFromExpression(node);
+ if (constructorName) {
+ var capitalization = getCap(constructorName);
+ var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName);
+ if (!isAllowed) {
+ report(node, "A constructor name should not start with a lowercase letter.");
+ }
+ }
+ };
+ }
+
+ if (config.capIsNew) {
+ listeners.CallExpression = function(node) {
+
+ var calleeName = extractNameFromExpression(node);
+ if (calleeName) {
+ var capitalization = getCap(calleeName);
+ var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName);
+ if (!isAllowed) {
+ report(node, "A function with a name starting with an uppercase letter should only be used as a constructor.");
+ }
+ }
+ };
+ }
+
+ return listeners;
+};