Skip to content

Commit

Permalink
Merge pull request babel/eslint-plugin-babel#138 from babel/composer
Browse files Browse the repository at this point in the history
Refactor rules to use eslint-rule-composer
  • Loading branch information
existentialism committed Mar 30, 2018
1 parent 15c5245 commit b41b3af
Show file tree
Hide file tree
Showing 9 changed files with 1,234 additions and 1,648 deletions.
727 changes: 0 additions & 727 deletions eslint/babel-eslint-plugin/ast-utils.js

This file was deleted.

7 changes: 5 additions & 2 deletions eslint/babel-eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@
},
"homepage": "https://github.com/babel/eslint-plugin-babel#readme",
"peerDependencies": {
"eslint": ">=3.0.0"
"eslint": ">=4.0.0"
},
"dependencies": {
"eslint-rule-composer": "^0.1.1"
},
"devDependencies": {
"babel-eslint": "^7.1.0",
"eslint": "^3.0.0",
"eslint": "^4.19.1",
"lodash.clonedeep": "^4.5.0",
"mocha": "^3.0.0"
}
Expand Down
240 changes: 12 additions & 228 deletions eslint/babel-eslint-plugin/rules/new-cap.js
Original file line number Diff line number Diff line change
@@ -1,235 +1,19 @@
/**
* @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.
* @copyright 2015 Mathieu M-Gosselin. 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) {
/* istanbul ignore if */
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;
}
const ruleComposer = require('eslint-rule-composer');
const eslint = require('eslint');
const newCapRule = new eslint.Linter().getRules().get('new-cap');

/**
* 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.
* Returns whether a node is under a decorator or not.
* @param {ASTNode} node CallExpression node
* @returns {Boolean} Returns true if the node is under a decorator.
*/
function invert(map, key) {
map[key] = true;
return map;
function isDecorator(node) {
return node.parent.type === "Decorator";
}

/**
* 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] || {};
var NEW_IS_CAP = config.newIsCap !== false;
var CAP_IS_NEW = config.capIsNew !== false;

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";
}
}

/**
* Returns whether a node is under a decorator or not.
* @param {ASTNode} node CallExpression node
* @returns {Boolean} Returns true if the node is under a decorator.
*/
function isDecorator(node) {
return node.parent.type === "Decorator";
}

/**
* 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] || allowedMap[context.getSource(node.callee)]) {
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 (NEW_IS_CAP) {
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 (CAP_IS_NEW) {
listeners.CallExpression = function(node) {

var calleeName = extractNameFromExpression(node);
if (calleeName) {
var capitalization = getCap(calleeName);
var isAllowed = capitalization !== "upper" || isDecorator(node) || 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;
};

module.exports.schema = [
{
"type": "object",
"properties": {
"newIsCap": {
"type": "boolean"
},
"capIsNew": {
"type": "boolean"
},
"newIsCapExceptions": {
"type": "array",
"items": {
"type": "string"
}
},
"capIsNewExceptions": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
}
];
module.exports = ruleComposer.filterReports(
newCapRule,
(problem, metadata) => !isDecorator(problem.node)
);
Loading

0 comments on commit b41b3af

Please sign in to comment.