Repository: lo1tuma/eslint-plugin-mocha (ts)

Issue:
Feature Request: allow users to provide variables to protect from `.only`
I'm using `ember-cli-mocha` which provides some functions that build on top of `describe`, such as `describeModule`.  I would love to be able to provide an array of additional things to disallow `.only` from being called.

Pull request description:
Allow custom test functions
This feature is useful if using this plugin with a package like `ember-mocha`, which provides its own set of `describe` functions. A custom array can be provided so that those extensions can also be protected from having `only` called.

One thought about my changes is that it introduces a configuration object rather than an array.  We could make the option parameter just an array, but I kind of like the extra context provided by the named keys.  The difference is basically between this (how I have it right now)

```json
{
     "rules": {
         "mocha/no-exclusive-tests": ["error", {
             "additionalTestFunctions": [
                 "describeModule"
             ]
         }]
     }
 }
```

and this (which I think provides less context and it more confusing)

```json
{
     "rules": {
         "mocha/no-exclusive-tests": ["error", [
             "describeModule"
         ]]
     }
 }
```

Closes #80

Patch:
diff --git a/lib/util/settings.js b/lib/util/settings.js
new file mode 100644
index 0000000..749d287
--- /dev/null
+++ b/lib/util/settings.js
@@ -0,0 +1,17 @@
+/* eslint-env node*/
+
+'use strict';
+
+function settingFor(propertyName) {
+  return function (settings) {
+    var value = settings['mocha/' + propertyName],
+        mochaSettings = settings.mocha || {};
+
+    return value || mochaSettings[propertyName] || [];
+  };
+}
+
+module.exports = {
+  getAdditionalTestFunctions: settingFor('additionalTestFunctions'),
+  getAdditionalXFunctions: settingFor('additionalXFunctions')
+};


Test patch:
diff --git a/docs/rules/no-exclusive-tests.md b/docs/rules/no-exclusive-tests.md
index cb586b9..d9f4fc9 100644
--- a/docs/rules/no-exclusive-tests.md
+++ b/docs/rules/no-exclusive-tests.md
@@ -49,6 +49,25 @@ suite.skip("bar", function () {});
 test.skip("bar", function () {});
 ```
 
+# Options
+
+This rule supports the following shared configuration options:
+
+* `additionalTestFunctions`: An array of extra test functions to protect.  This might be used with a custom Mocha extension, such as [`ember-mocha`](https://github.com/switchfly/ember-mocha)
+
+```json
+{
+    "rules": {
+        "mocha/no-exclusive-tests": "error"
+    },
+    "settings": {
+       "mocha/additionalTestFunctions": [
+           "describeModule"
+       ]
+    }
+}
+```
+
 ## When Not To Use It
 
 * If you really want to execute only one test-suite or test-case because all other tests should not be executed, turn this rule off.
diff --git a/docs/rules/no-skipped-tests.md b/docs/rules/no-skipped-tests.md
index 11f7374..69d5427 100644
--- a/docs/rules/no-skipped-tests.md
+++ b/docs/rules/no-skipped-tests.md
@@ -44,6 +44,28 @@ suite.only("bar", function () {});
 test.only("bar", function () {});
 ```
 
+# Options
+
+This rule supports the following shared configuration options:
+
+* `additionalTestFunctions`: An array of extra test functions to protect.  This might be used with a custom Mocha extension, such as [`ember-mocha`](https://github.com/switchfly/ember-mocha)
+* `additionalXFunctions`: An array of extra x-function to protect
+
+```json
+{
+    "rules": {
+        "mocha/no-skipped-tests": "error"
+    },
+    "settings": {
+       "mocha/additionalTestFunctions": [
+           "describeModule"
+       ],
+       "mocha/additionalXFunctions": [
+           "xdescribeModule"
+       ]
+    }
+}
+```
 ## When Not To Use It
 
 * If you really want to commit skipped tests to your repo, turn this rule off.
diff --git a/lib/rules/no-exclusive-tests.js b/lib/rules/no-exclusive-tests.js
index 891d50d..c151052 100644
--- a/lib/rules/no-exclusive-tests.js
+++ b/lib/rules/no-exclusive-tests.js
@@ -1,14 +1,20 @@
 'use strict';
 
+var getAdditionalTestFunctions = require('../util/settings').getAdditionalTestFunctions;
+
 module.exports = function (context) {
     var mochaTestFunctions = [
-        'it',
-        'describe',
-        'suite',
-        'test',
-        'context',
-        'specify'
-    ];
+            'it',
+            'describe',
+            'suite',
+            'test',
+            'context',
+            'specify'
+        ],
+        settings = context.settings,
+        additionalTestFunctions = getAdditionalTestFunctions(settings);
+
+    mochaTestFunctions = mochaTestFunctions.concat(additionalTestFunctions);
 
     function matchesMochaTestFunction(object) {
         return object && mochaTestFunctions.indexOf(object.name) !== -1;
@@ -20,8 +26,8 @@ module.exports = function (context) {
 
     function isCallToMochasOnlyFunction(callee) {
         return callee.type === 'MemberExpression' &&
-           matchesMochaTestFunction(callee.object) &&
-           isPropertyNamedOnly(callee.property);
+            matchesMochaTestFunction(callee.object) &&
+            isPropertyNamedOnly(callee.property);
     }
 
     return {
@@ -37,3 +43,19 @@ module.exports = function (context) {
         }
     };
 };
+
+module.exports.schema = [
+    {
+        type: 'object',
+        properties: {
+            additionalTestFunctions: {
+                type: 'array',
+                items: {
+                    type: 'string'
+                },
+                minItems: 1,
+                uniqueItems: true
+            }
+        }
+    }
+];
diff --git a/lib/rules/no-skipped-tests.js b/lib/rules/no-skipped-tests.js
index 30c5b40..0438902 100644
--- a/lib/rules/no-skipped-tests.js
+++ b/lib/rules/no-skipped-tests.js
@@ -1,60 +1,69 @@
 'use strict';
 
-module.exports = function (context) {
-    var mochaTestFunctions = [
-            'it',
-            'describe',
-            'suite',
-            'test',
-            'context',
-            'specify'
-        ],
-        mochaXFunctions = [
-            'xit',
-            'xdescribe',
-            'xcontext',
-            'xspecify'
-        ];
+var getAdditionalTestFunctions = require('../util/settings').getAdditionalTestFunctions,
+    getAdditionalXFunctions = require('../util/settings').getAdditionalXFunctions,
+    mochaTestFunctions,
+    mochaXFunctions;
 
-    function matchesMochaTestFunction(object) {
-        return object && mochaTestFunctions.indexOf(object.name) !== -1;
-    }
+function matchesMochaTestFunction(object) {
+    return object && mochaTestFunctions.indexOf(object.name) !== -1;
+}
 
-    function isPropertyNamedSkip(property) {
-        return property && (property.name === 'skip' || property.value === 'skip');
-    }
+function isPropertyNamedSkip(property) {
+    return property && (property.name === 'skip' || property.value === 'skip');
+}
 
-    function isCallToMochasSkipFunction(callee) {
-        return callee.type === 'MemberExpression' &&
-           matchesMochaTestFunction(callee.object) &&
-           isPropertyNamedSkip(callee.property);
-    }
+function isCallToMochasSkipFunction(callee) {
+    return callee.type === 'MemberExpression' &&
+       matchesMochaTestFunction(callee.object) &&
+       isPropertyNamedSkip(callee.property);
+}
 
-    function isMochaXFunction(name) {
-        return mochaXFunctions.indexOf(name) !== -1;
-    }
+function createSkipAutofixFunction(callee) {
+    var endRangeOfMemberExpression = callee.range[1],
+        endRangeOfMemberExpressionObject = callee.object.range[1],
+        rangeToRemove = [ endRangeOfMemberExpressionObject, endRangeOfMemberExpression ];
 
-    function isCallToMochaXFunction(callee) {
-        return callee.type === 'Identifier' && isMochaXFunction(callee.name);
-    }
+    return function removeSkipProperty(fixer) {
+        return fixer.removeRange(rangeToRemove);
+    };
+}
 
-    function createSkipAutofixFunction(callee) {
-        var endRangeOfMemberExpression = callee.range[1],
-            endRangeOfMemberExpressionObject = callee.object.range[1],
-            rangeToRemove = [ endRangeOfMemberExpressionObject, endRangeOfMemberExpression ];
+function createXAutofixFunction(callee) {
+    var rangeToRemove = [ callee.range[0], callee.range[0] + 1 ];
 
-        return function removeSkipProperty(fixer) {
-            return fixer.removeRange(rangeToRemove);
-        };
-    }
+    return function removeXPrefix(fixer) {
+        return fixer.removeRange(rangeToRemove);
+    };
+}
 
-    function createXAutofixFunction(callee) {
-        var rangeToRemove = [ callee.range[0], callee.range[0] + 1 ];
+function isMochaXFunction(name) {
+    return mochaXFunctions.indexOf(name) !== -1;
+}
 
-        return function removeXPrefix(fixer) {
-            return fixer.removeRange(rangeToRemove);
-        };
-    }
+function isCallToMochaXFunction(callee) {
+    return callee.type === 'Identifier' && isMochaXFunction(callee.name);
+}
+
+module.exports = function (context) {
+    var settings = context.settings,
+        additionalTestFunctions = getAdditionalTestFunctions(settings),
+        additionalXFunctions = getAdditionalXFunctions(settings);
+
+    mochaTestFunctions = [
+        'it',
+        'describe',
+        'suite',
+        'test',
+        'context',
+        'specify'
+    ].concat(additionalTestFunctions);
+    mochaXFunctions = [
+        'xit',
+        'xdescribe',
+        'xcontext',
+        'xspecify'
+    ].concat(additionalXFunctions);
 
     return {
         CallExpression: function (node) {
@@ -76,3 +85,27 @@ module.exports = function (context) {
         }
     };
 };
+
+module.exports.schema = [
+    {
+        type: 'object',
+        properties: {
+            additionalTestFunctions: {
+                type: 'array',
+                items: {
+                    type: 'string'
+                },
+                minItems: 1,
+                uniqueItems: true
+            },
+            additionalXFunctions: {
+                type: 'array',
+                items: {
+                    type: 'string'
+                },
+                minItems: 1,
+                uniqueItems: true
+            }
+        }
+    }
+];
diff --git a/test/rules/no-exclusive-tests.js b/test/rules/no-exclusive-tests.js
index 34469df..9a1d686 100644
--- a/test/rules/no-exclusive-tests.js
+++ b/test/rules/no-exclusive-tests.js
@@ -73,6 +73,38 @@ ruleTester.run('no-exclusive-tests', rules['no-exclusive-tests'], {
         {
             code: 'specify["only"]()',
             errors: [ { message: expectedErrorMessage, column: 9, line: 1 } ]
+        },
+        {
+            code: 'custom.only()',
+            settings: {
+                'mocha/additionalTestFunctions': [ 'custom' ]
+            },
+            errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ]
+        },
+        {
+            code: 'custom["only"]()',
+            settings: {
+                'mocha/additionalTestFunctions': [ 'custom' ]
+            },
+            errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ]
+        },
+        {
+            code: 'custom.only()',
+            settings: {
+                mocha: {
+                    additionalTestFunctions: [ 'custom' ]
+                }
+            },
+            errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ]
+        },
+        {
+            code: 'custom["only"]()',
+            settings: {
+                mocha: {
+                    additionalTestFunctions: [ 'custom' ]
+                }
+            },
+            errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ]
         }
     ]
 
diff --git a/test/rules/no-skipped-tests.js b/test/rules/no-skipped-tests.js
index 8232d20..446708f 100644
--- a/test/rules/no-skipped-tests.js
+++ b/test/rules/no-skipped-tests.js
@@ -98,6 +98,60 @@ ruleTester.run('no-skipped-tests', rules['no-skipped-tests'], {
             code: 'xspecify()',
             errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ],
             output: 'specify()'
+        },
+        {
+            code: 'custom.skip()',
+            settings: {
+                'mocha/additionalTestFunctions': [ 'custom' ]
+            },
+            errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ],
+            output: 'custom()'
+        },
+        {
+            code: 'custom["skip"]()',
+            settings: {
+                'mocha/additionalTestFunctions': [ 'custom' ]
+            },
+            errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ],
+            output: 'custom()'
+        },
+        {
+            code: 'xcustom()',
+            settings: {
+                'mocha/additionalXFunctions': [ 'xcustom' ]
+            },
+            errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ],
+            output: 'custom()'
+        },
+        {
+            code: 'custom.skip()',
+            settings: {
+                mocha: {
+                    additionalTestFunctions: [ 'custom' ]
+                }
+            },
+            errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ],
+            output: 'custom()'
+        },
+        {
+            code: 'custom["skip"]()',
+            settings: {
+                mocha: {
+                    additionalTestFunctions: [ 'custom' ]
+                }
+            },
+            errors: [ { message: expectedErrorMessage, column: 8, line: 1 } ],
+            output: 'custom()'
+        },
+        {
+            code: 'xcustom()',
+            settings: {
+                mocha: {
+                    additionalXFunctions: [ 'xcustom' ]
+                }
+            },
+            errors: [ { message: expectedErrorMessage, column: 1, line: 1 } ],
+            output: 'custom()'
         }
 
     ]