Merge pull request babel/eslint-plugin-babel#2 from mathieumg/newcap_decorators
Added support for decorators in the new-cap rule.
This commit is contained in:
parent
e9e6dee39a
commit
382c3f2bb9
@ -26,6 +26,7 @@ Finally enable all the rules you like to use (remember to disable the originals
|
||||
"babel/object-shorthand": 1,
|
||||
"babel/generator-star": 1,
|
||||
"babel/generator-star-spacing": 1,
|
||||
"babel/new-cap": 1,
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -36,3 +37,4 @@ Each rule cooresponds to a core eslint rule, and has the same options.
|
||||
- `babel/object-shorthand`: doesn't fail when using object spread (`...obj`)
|
||||
- `babel/generator-star`: Handles async/await functions correctly
|
||||
- `babel/generator-star-spacing`: Handles async/await functions correctly
|
||||
- `babel/new-cap`: Ignores capitalized decorators (`@Decorator`)
|
||||
|
||||
@ -4,11 +4,13 @@ module.exports = {
|
||||
rules: {
|
||||
'object-shorthand': require('./rules/object-shorthand'),
|
||||
'generator-star-spacing': require('./rules/generator-star-spacing'),
|
||||
'generator-star': require('./rules/generator-star')
|
||||
'generator-star': require('./rules/generator-star'),
|
||||
'new-cap': require('./rules/new-cap')
|
||||
},
|
||||
rulesConfig: {
|
||||
'generator-star-spacing': 0,
|
||||
'generator-star': 0,
|
||||
'object-shorthand': 0
|
||||
'object-shorthand': 0,
|
||||
'new-cap': 0
|
||||
}
|
||||
};
|
||||
|
||||
234
eslint/babel-eslint-plugin/rules/new-cap.js
Normal file
234
eslint/babel-eslint-plugin/rules/new-cap.js
Normal file
@ -0,0 +1,234 @@
|
||||
/**
|
||||
* @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) {
|
||||
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;
|
||||
config.capIsNew = 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]) {
|
||||
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" || 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
|
||||
}
|
||||
];
|
||||
122
eslint/babel-eslint-plugin/tests/new-cap.js
Normal file
122
eslint/babel-eslint-plugin/tests/new-cap.js
Normal file
@ -0,0 +1,122 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* @fileoverview Tests for new-cap rule.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
var linter = require('eslint').linter
|
||||
, ESLintTester = require('eslint-tester')
|
||||
, eslintTester = new ESLintTester(linter);
|
||||
|
||||
|
||||
eslintTester.addRuleTest("rules/new-cap", {
|
||||
valid: [
|
||||
// Original test cases.
|
||||
"var x = new Constructor();",
|
||||
"var x = new a.b.Constructor();",
|
||||
"var x = new a.b['Constructor']();",
|
||||
"var x = new a.b[Constructor]();",
|
||||
"var x = new a.b[constructor]();",
|
||||
"var x = new function(){};",
|
||||
"var x = new _;",
|
||||
"var x = new $;",
|
||||
"var x = new Σ;",
|
||||
"var x = new _x;",
|
||||
"var x = new $x;",
|
||||
"var x = new this;",
|
||||
"var x = Array(42)",
|
||||
"var x = Boolean(42)",
|
||||
"var x = Date(42)",
|
||||
"var x = Date.UTC(2000, 0)",
|
||||
"var x = Error('error')",
|
||||
"var x = Function('return 0')",
|
||||
"var x = Number(42)",
|
||||
"var x = Object(null)",
|
||||
"var x = RegExp(42)",
|
||||
"var x = String(42)",
|
||||
"var x = Symbol('symbol')",
|
||||
"var x = _();",
|
||||
"var x = $();",
|
||||
{ code: "var x = Foo(42)", args: [1, {"capIsNew": false}] },
|
||||
{ code: "var x = bar.Foo(42)", args: [1, {"capIsNew": false}] },
|
||||
"var x = bar[Foo](42)",
|
||||
{code: "var x = bar['Foo'](42)", args: [1, {"capIsNew": false}] },
|
||||
"var x = Foo.bar(42)",
|
||||
{ code: "var x = new foo(42)", args: [1, {"newIsCap": false}] },
|
||||
"var o = { 1: function () {} }; o[1]();",
|
||||
"var o = { 1: function () {} }; new o[1]();",
|
||||
{ code: "var x = Foo(42);", args: [1, { capIsNew: true, capIsNewExceptions: ["Foo"] }] },
|
||||
{ code: "var x = new foo(42);", args: [1, { newIsCap: true, newIsCapExceptions: ["foo"] }] },
|
||||
{ code: "var x = Object(42);", args: [1, { capIsNewExceptions: ["Foo"] }] },
|
||||
|
||||
// Babel-specific test cases.
|
||||
{ code: "@MyDecorator(123) class MyClass{}", parser: "babel-eslint" },
|
||||
],
|
||||
invalid: [
|
||||
{ code: "var x = new c();", errors: [{ message: "A constructor name should not start with a lowercase letter.", type: "NewExpression"}] },
|
||||
{ code: "var x = new φ;", errors: [{ message: "A constructor name should not start with a lowercase letter.", type: "NewExpression"}] },
|
||||
{ code: "var x = new a.b.c;", errors: [{ message: "A constructor name should not start with a lowercase letter.", type: "NewExpression"}] },
|
||||
{ code: "var x = new a.b['c'];", errors: [{ message: "A constructor name should not start with a lowercase letter.", type: "NewExpression"}] },
|
||||
{ code: "var b = Foo();", errors: [{ message: "A function with a name starting with an uppercase letter should only be used as a constructor.", type: "CallExpression"}] },
|
||||
{ code: "var b = a.Foo();", errors: [{ message: "A function with a name starting with an uppercase letter should only be used as a constructor.", type: "CallExpression"}] },
|
||||
{ code: "var b = a['Foo']();", errors: [{ message: "A function with a name starting with an uppercase letter should only be used as a constructor.", type: "CallExpression"}] },
|
||||
{ code: "var b = a.Date.UTC();", errors: [{ message: "A function with a name starting with an uppercase letter should only be used as a constructor.", type: "CallExpression"}] },
|
||||
{ code: "var b = UTC();", errors: [{ message: "A function with a name starting with an uppercase letter should only be used as a constructor.", type: "CallExpression"}] },
|
||||
{
|
||||
code: "var a = B.C();",
|
||||
errors: [
|
||||
{
|
||||
message: "A function with a name starting with an uppercase letter should only be used as a constructor.",
|
||||
type: "CallExpression",
|
||||
line: 1,
|
||||
column: 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
code: "var a = B\n.C();",
|
||||
errors: [
|
||||
{
|
||||
message: "A function with a name starting with an uppercase letter should only be used as a constructor.",
|
||||
type: "CallExpression",
|
||||
line: 2,
|
||||
column: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
code: "var a = new B.c();",
|
||||
errors: [
|
||||
{
|
||||
message: "A constructor name should not start with a lowercase letter.",
|
||||
type: "NewExpression",
|
||||
line: 1,
|
||||
column: 14
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
code: "var a = new B.\nc();",
|
||||
errors: [
|
||||
{
|
||||
message: "A constructor name should not start with a lowercase letter.",
|
||||
type: "NewExpression",
|
||||
line: 2,
|
||||
column: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
code: "var a = new c();",
|
||||
errors: [
|
||||
{
|
||||
message: "A constructor name should not start with a lowercase letter.",
|
||||
type: "NewExpression",
|
||||
line: 1,
|
||||
column: 12
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user