Skip to content

Commit

Permalink
Update: Add regex support for exceptions (fixes #5187) (#6883)
Browse files Browse the repository at this point in the history
  • Loading branch information
annie authored and ilyavolodin committed Aug 12, 2016
1 parent 055742c commit df01c4f
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 4 deletions.
24 changes: 24 additions & 0 deletions docs/rules/new-cap.md
Expand Up @@ -40,7 +40,9 @@ This rule has an object option:
* `"capIsNew": true` (default) requires all uppercase-started functions to be called with `new` operators.
* `"capIsNew": false` allows uppercase-started functions to be called without `new` operators.
* `"newIsCapExceptions"` allows specified lowercase-started function names to be called with the `new` operator.
* `"newIsCapExceptionPattern"` allows any lowercase-started function names that match the specified regex pattern to be called with the `new` operator.
* `"capIsNewExceptions"` allows specified uppercase-started function names to be called without the `new` operator.
* `"capIsNewExceptionPattern"` allows any uppercase-started function names that match the specified regex pattern to be called without the `new` operator.
* `"properties": true` (default) enables checks on object properties
* `"properties": false` disables checks on object properties

Expand Down Expand Up @@ -108,6 +110,17 @@ var events = require('events');
var emitter = new events();
```

### newIsCapExceptionPattern

Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "^person\.." }` option:

```js
/*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\.." }]*/

var friend = new person.acquaintance();
var bestFriend = new person.friend();
```

### capIsNewExceptions

Examples of additional **correct** code for this rule with the `{ "capIsNewExceptions": ["Person"] }` option:
Expand All @@ -120,6 +133,17 @@ function foo(arg) {
}
```

### capIsNewExceptionPattern

Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^Person\.." }` option:

```js
/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Person\.." }]*/

var friend = person.Acquaintance();
var bestFriend = person.Friend();
```

### properties

Examples of **incorrect** code for this rule with the default `{ "properties": true }` option:
Expand Down
23 changes: 19 additions & 4 deletions lib/rules/new-cap.js
Expand Up @@ -96,12 +96,18 @@ module.exports = {
type: "string"
}
},
newIsCapExceptionPattern: {
type: "string"
},
capIsNewExceptions: {
type: "array",
items: {
type: "string"
}
},
capIsNewExceptionPattern: {
type: "string"
},
properties: {
type: "boolean"
}
Expand All @@ -120,8 +126,10 @@ module.exports = {
const skipProperties = config.properties === false;

const newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern) : null;

const capIsNewExceptions = calculateCapIsNewExceptions(config);
const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern) : null;

const listeners = {};

Expand Down Expand Up @@ -182,10 +190,17 @@ module.exports = {
* @param {Object} allowedMap Object mapping calleeName to a Boolean
* @param {ASTNode} node CallExpression node
* @param {string} calleeName Capitalized callee name from a CallExpression
* @param {Object} pattern RegExp object from options pattern
* @returns {boolean} Returns true if the callee may be capitalized
*/
function isCapAllowed(allowedMap, node, calleeName) {
if (allowedMap[calleeName] || allowedMap[sourceCode.getText(node.callee)]) {
function isCapAllowed(allowedMap, node, calleeName, pattern) {
const sourceText = sourceCode.getText(node.callee);

if (allowedMap[calleeName] || allowedMap[sourceText]) {
return true;
}

if (pattern && pattern.test(sourceText)) {
return true;
}

Expand Down Expand Up @@ -226,7 +241,7 @@ module.exports = {

if (constructorName) {
const capitalization = getCap(constructorName);
const isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName);
const isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName, newIsCapExceptionPattern);

if (!isAllowed) {
report(node, "A constructor name should not start with a lowercase letter.");
Expand All @@ -242,7 +257,7 @@ module.exports = {

if (calleeName) {
const capitalization = getCap(calleeName);
const isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName);
const isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName, capIsNewExceptionPattern);

if (!isAllowed) {
report(node, "A function with a name starting with an uppercase letter should only be used as a constructor.");
Expand Down
14 changes: 14 additions & 0 deletions tests/lib/rules/new-cap.js
Expand Up @@ -55,13 +55,17 @@ ruleTester.run("new-cap", rule, {
"var o = { 1: function() {} }; o[1]();",
"var o = { 1: function() {} }; new o[1]();",
{ code: "var x = Foo(42);", options: [{ capIsNew: true, capIsNewExceptions: ["Foo"] }] },
{ code: "var x = Foo(42);", options: [{ capIsNewExceptionPattern: "^Foo" }] },
{ code: "var x = new foo(42);", options: [{ newIsCap: true, newIsCapExceptions: ["foo"] }] },
{ code: "var x = new foo(42);", options: [{ newIsCapExceptionPattern: "^foo" }] },
{ code: "var x = Object(42);", options: [{ capIsNewExceptions: ["Foo"] }] },

{ code: "var x = Foo.Bar(42);", options: [{ capIsNewExceptions: ["Bar"] }] },
{ code: "var x = Foo.Bar(42);", options: [{ capIsNewExceptions: ["Foo.Bar"] }] },
{ code: "var x = Foo.Bar(42);", options: [{ capIsNewExceptionPattern: "^Foo\.." }] },
{ code: "var x = new foo.bar(42);", options: [{ newIsCapExceptions: ["bar"] }] },
{ code: "var x = new foo.bar(42);", options: [{ newIsCapExceptions: ["foo.bar"] }] },
{ code: "var x = new foo.bar(42);", options: [{ newIsCapExceptionPattern: "^foo\.." }] },
{ code: "var x = new foo.bar(42);", options: [{ properties: false }] },
{ code: "var x = Foo.bar(42);", options: [{ properties: false }] },
{ code: "var x = foo.Bar(42);", options: [{ capIsNew: false, properties: false }] }
Expand Down Expand Up @@ -137,10 +141,20 @@ ruleTester.run("new-cap", rule, {
options: [{capIsNewExceptions: ["Foo"]}],
errors: [{type: "CallExpression", message: "A function with a name starting with an uppercase letter should only be used as a constructor."}]
},
{
code: "var x = Bar.Foo(42);",
options: [{capIsNewExceptionPattern: "^Foo\.."}],
errors: [{type: "CallExpression", message: "A function with a name starting with an uppercase letter should only be used as a constructor."}]
},
{
code: "var x = new foo.bar(42);",
options: [{newIsCapExceptions: ["foo"]}],
errors: [{type: "NewExpression", message: "A constructor name should not start with a lowercase letter."}]
},
{
code: "var x = new bar.foo(42);",
options: [{newIsCapExceptionPattern: "^foo\.."}],
errors: [{type: "NewExpression", message: "A constructor name should not start with a lowercase letter."}]
}
]
});

0 comments on commit df01c4f

Please sign in to comment.