From 161b118eac8100a6238e8bd94390d3d30df8ed67 Mon Sep 17 00:00:00 2001 From: Mathias Schreck Date: Thu, 23 Jan 2025 13:30:41 +0100 Subject: [PATCH] Implement new rule consistent-interface --- README.md | 1 + docs/rules/consistent-interface.md | 23 ++++++++ index.js | 7 ++- lib/ast/mochaVisitors.js | 3 +- lib/rules/consistent-interface.js | 38 +++++++++++++ test/rules/consistent-interface.js | 85 ++++++++++++++++++++++++++++++ 6 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 docs/rules/consistent-interface.md create mode 100644 lib/rules/consistent-interface.js create mode 100644 test/rules/consistent-interface.js diff --git a/README.md b/README.md index 32b53e9..7156618 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ See [Configuring Eslint](http://eslint.org/docs/user-guide/configuring) on [esli | Name                              | Description | 💼 | ⚠️ | 🚫 | 🔧 | | :----------------------------------------------------------------------------------- | :---------------------------------------------------------------------- | :- | :- | :- | :- | +| [consistent-interface](docs/rules/consistent-interface.md) | Enforces consistent use of mocha interfaces | | | | | | [consistent-spacing-between-blocks](docs/rules/consistent-spacing-between-blocks.md) | Require consistent spacing between blocks | ✅ | | | 🔧 | | [handle-done-callback](docs/rules/handle-done-callback.md) | Enforces handling of callbacks for async tests | ✅ | | | | | [max-top-level-suites](docs/rules/max-top-level-suites.md) | Enforce the number of top-level suites in a single file | ✅ | | | | diff --git a/docs/rules/consistent-interface.md b/docs/rules/consistent-interface.md new file mode 100644 index 0000000..b47ef3e --- /dev/null +++ b/docs/rules/consistent-interface.md @@ -0,0 +1,23 @@ +# Enforces consistent use of mocha interfaces (`mocha/consistent-interface`) + + + +Mocha has several interfaces such as `BDD`, `TDD`, `Exports`, `QUnit` etc. Usually mocha works by injecting the variables and functions of the selected interface into the global scope. However when using the `Exports` interface one can import functions of any interface. This rule helps to enforce a consistent use of the same interface when using `Exports`. + +## Options + +Example of a custom rule configuration: + +```js +rules: { + "mocha/consistent-interface": ["error", { interface: 'BDD' }] +}, +``` + +where: + +- `interface` can be set to either `TDD` or `BDD` + +## When Not To Use It + +If you are not using the `Exports` interface then this rule doesn’t provide any value. diff --git a/index.js b/index.js index 96d8baf..10ab8eb 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ import globals from 'globals'; +import { consistentInterfaceRule } from './lib/rules/consistent-interface.js'; import { consistentSpacingBetweenBlocksRule } from './lib/rules/consistent-spacing-between-blocks.js'; import { handleDoneCallbackRule } from './lib/rules/handle-done-callback.js'; import { maxTopLevelSuitesRule } from './lib/rules/max-top-level-suites.js'; @@ -46,7 +47,8 @@ const allRules = { 'mocha/valid-suite-description': 'error', 'mocha/valid-test-description': 'error', 'mocha/no-empty-description': 'error', - 'mocha/consistent-spacing-between-blocks': 'error' + 'mocha/consistent-spacing-between-blocks': 'error', + 'mocha/consistent-interface': ['error', { interface: 'BDD' }] }; const recommendedRules = { @@ -99,7 +101,8 @@ const mochaPlugin = { 'valid-suite-description': validSuiteDescriptionRule, 'valid-test-description': validTestDescriptionRule, 'no-empty-description': noEmptyDescriptionRule, - 'consistent-spacing-between-blocks': consistentSpacingBetweenBlocksRule + 'consistent-spacing-between-blocks': consistentSpacingBetweenBlocksRule, + 'consistent-interface': consistentInterfaceRule }, configs: { all: { diff --git a/lib/ast/mochaVisitors.js b/lib/ast/mochaVisitors.js index 857a68e..15e4092 100755 --- a/lib/ast/mochaVisitors.js +++ b/lib/ast/mochaVisitors.js @@ -39,7 +39,8 @@ function createContext(reference) { name: reference.resolvedPath.join('.'), node: reference.node, type: reference.nameDetails.type, - modifier: reference.nameDetails.modifier + modifier: reference.nameDetails.modifier, + interface: reference.nameDetails.interface }; } diff --git a/lib/rules/consistent-interface.js b/lib/rules/consistent-interface.js new file mode 100644 index 0000000..6220e22 --- /dev/null +++ b/lib/rules/consistent-interface.js @@ -0,0 +1,38 @@ +import { createMochaVisitors } from '../ast/mochaVisitors.js'; + +export const consistentInterfaceRule = { + meta: { + type: 'problem', + docs: { + description: 'Enforces consistent use of mocha interfaces', + url: 'https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/docs/rules/consistent-interface.md' + }, + schema: [ + { + type: 'object', + properties: { + interface: { + type: 'string', + enum: ['BDD', 'TDD'], + default: 'BDD' + } + }, + additionalProperties: false + } + ] + }, + create(context) { + const interfaceToUse = context.options[0].interface; + + return createMochaVisitors(context, { + anyTestEntity(visitorContext) { + if (visitorContext.interface !== interfaceToUse) { + context.report({ + node: visitorContext.node, + message: `Unexpected use of ${visitorContext.interface} interface instead of ${interfaceToUse}` + }); + } + } + }); + } +}; diff --git a/test/rules/consistent-interface.js b/test/rules/consistent-interface.js new file mode 100644 index 0000000..7de6890 --- /dev/null +++ b/test/rules/consistent-interface.js @@ -0,0 +1,85 @@ +import { RuleTester } from 'eslint'; +import { consistentInterfaceRule } from '../../lib/rules/consistent-interface.js'; + +const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2020, sourceType: 'module' } }); + +ruleTester.run('consistent-interface', consistentInterfaceRule, { + valid: [ + { + code: `describe('foo', () => { + it('bar', () => {}); + });`, + options: [{ interface: 'BDD' }], + settings: { mocha: { interface: 'BDD' } } + }, + { + code: `suite('foo', () => { + test('bar', () => {}); + });`, + options: [{ interface: 'TDD' }], + settings: { mocha: { interface: 'TDD' } } + }, + { + code: `import {suite, test} from 'mocha'; suite('foo', () => { + test('bar', () => {}); + });`, + options: [{ interface: 'TDD' }], + settings: { mocha: { interface: 'exports' } } + }, + { + code: `import {describe, it} from 'mocha'; describe('foo', () => { + it('bar', () => {}); + });`, + options: [{ interface: 'BDD' }], + settings: { mocha: { interface: 'exports' } } + }, + { + code: `import {describe as foo, it as bar} from 'mocha'; foo('foo', () => { + bar('bar', () => {}); + });`, + options: [{ interface: 'BDD' }], + settings: { mocha: { interface: 'exports' } } + } + ], + + invalid: [ + { + code: `describe('foo', () => { + test('bar', () => {}); + });`, + options: [{ interface: 'BDD' }], + settings: { mocha: { interface: 'BDD' } }, + errors: [{ line: 2, column: 17, message: 'Unexpected use of TDD interface instead of BDD' }] + }, + { + code: `describe('foo', () => { + test('bar', () => {}); + });`, + options: [{ interface: 'TDD' }], + settings: { mocha: { interface: 'TDD' } }, + errors: [{ line: 1, column: 1, message: 'Unexpected use of BDD interface instead of TDD' }] + }, + { + code: `import {suite, test} from 'mocha'; suite('foo', () => { + test('bar', () => {}); + });`, + options: [{ interface: 'BDD' }], + settings: { mocha: { interface: 'exports' } }, + errors: [ + { line: 1, column: 36, message: 'Unexpected use of TDD interface instead of BDD' }, + { line: 2, column: 17, message: 'Unexpected use of TDD interface instead of BDD' } + ] + }, + { + code: `import {describe, it} from 'mocha'; describe('foo', () => { + it('bar', () => {}); + });`, + options: [{ interface: 'TDD' }], + settings: { mocha: { interface: 'exports' } }, + errors: [ + { line: 1, column: 37, message: 'Unexpected use of BDD interface instead of TDD' }, + { line: 2, column: 17, message: 'Unexpected use of BDD interface instead of TDD' } + ] + } + ] +});