UNPKG

eslint-plugin-mocha

Version:

Eslint rules for mocha.

202 lines 7.5 kB
import { createMochaVisitors } from "../ast/mocha-visitors.js"; import { expectNodeLocation, expectNodeRange } from "../ast/node-location.js"; import { getLastOrThrow } from "../list.js"; import { getRuleOption } from "../rule-options.js"; import { getInterface } from "../settings.js"; const interfaces = ['BDD', 'TDD']; const optionSchema = { type: 'object', properties: { interface: { type: 'string', enum: interfaces } }, additionalProperties: false }; const defaultOption = { interface: 'BDD' }; const interfaceMethodNames = new Set([ 'describe', 'context', 'suite', 'it', 'specify', 'test', 'before', 'after', 'beforeEach', 'afterEach', 'suiteSetup', 'suiteTeardown', 'setup', 'teardown' ]); function getFirstChildScope(scope) { return scope?.childScopes[0]; } function reportUnexpectedInterface(context, node, actualInterface, expectedInterface) { context.report({ node, messageId: 'unexpectedInterface', data: { actualInterface, expectedInterface } }); } function getMochaModuleScope(sourceCode) { const { globalScope } = sourceCode.scopeManager; const maybeModuleScope = getFirstChildScope(globalScope); return maybeModuleScope?.type === 'module' ? maybeModuleScope : null; } function isMochaImportDefinition(importDef) { return importDef?.type === 'ImportBinding' && importDef.parent.source.value === 'mocha' && importDef.node.type === 'ImportSpecifier'; } function getMochaImportBinding(variable) { const importDef = variable.defs[0]; if (!isMochaImportDefinition(importDef)) { return null; } return { importDeclaration: importDef.parent, specifier: importDef.node }; } function getImportedName(specifier) { return specifier.imported.type === 'Identifier' ? specifier.imported.name : String(specifier.imported.value); } function isInterfaceMethodImport(specifier) { return interfaceMethodNames.has(getImportedName(specifier)); } function isCanonicalNamedImportSpecifier(specifier) { return specifier.local.name === getImportedName(specifier); } function isImportSpecifier(specifier) { return specifier.type === 'ImportSpecifier'; } function isFixableImportSpecifier(specifier) { return isCanonicalNamedImportSpecifier(specifier) && isInterfaceMethodImport(specifier); } function hasOnlyNamedImportSpecifiers(importDeclaration) { return importDeclaration.specifiers.every(isImportSpecifier); } function removeFullImportDeclaration(fixer, sourceCode, importDeclaration) { const range = expectNodeRange(importDeclaration); const trailingSource = sourceCode.text.slice(range[1]); const nextTokenOffset = trailingSource.search(/\S/u); const nextRangeStart = range[1] + Math.max(nextTokenOffset, 0); return fixer.removeRange([range[0], nextRangeStart]); } function shouldRemoveFullImportDeclaration(importDeclaration) { return importDeclaration.specifiers.length > 0 && importDeclaration.specifiers.every(function (currentSpecifier) { return isImportSpecifier(currentSpecifier) && isFixableImportSpecifier(currentSpecifier); }); } function getRangeBeforeNextSpecifier(specifier, nextSpecifier) { const specifierRange = expectNodeRange(specifier); const nextSpecifierRange = expectNodeRange(nextSpecifier); return [specifierRange[0], nextSpecifierRange[0]]; } function getRangeAfterPreviousSpecifier(previousSpecifier, specifier) { const previousSpecifierRange = expectNodeRange(previousSpecifier); const specifierRange = expectNodeRange(specifier); return [previousSpecifierRange[1], specifierRange[1]]; } function getImportSpecifierRemovalRange(specifier, specifiers) { const index = specifiers.indexOf(specifier); if (index === 0) { return getRangeBeforeNextSpecifier(specifier, getLastOrThrow(specifiers.slice(1))); } return getRangeAfterPreviousSpecifier(getLastOrThrow(specifiers.slice(index - 1, index)), specifier); } function fixImportSpecifier(fixer, sourceCode, specifier, importDeclaration) { if (shouldRemoveFullImportDeclaration(importDeclaration)) { return removeFullImportDeclaration(fixer, sourceCode, importDeclaration); } return fixer.removeRange(getImportSpecifierRemovalRange(specifier, importDeclaration.specifiers)); } function createUnexpectedImportDescriptor(binding, configuredMochaInterface) { const { specifier } = binding; const loc = expectNodeLocation(specifier.local); return { loc, messageId: 'unexpectedInterface', data: { actualInterface: 'require', expectedInterface: `global ${configuredMochaInterface}` } }; } function reportUnexpectedImportBinding(context, binding, configuredMochaInterface) { if (!isInterfaceMethodImport(binding.specifier)) { return; } const descriptor = createUnexpectedImportDescriptor(binding, configuredMochaInterface); const { importDeclaration, specifier } = binding; if (hasOnlyNamedImportSpecifiers(importDeclaration) && isCanonicalNamedImportSpecifier(specifier)) { context.report({ ...descriptor, fix(fixer) { return fixImportSpecifier(fixer, context.sourceCode, specifier, importDeclaration); } }); return; } context.report(descriptor); } function reportUnexpectedImportBindingsInModule(context, configuredMochaInterface) { const moduleScope = getMochaModuleScope(context.sourceCode); if (moduleScope === null) { return; } for (const variable of moduleScope.variables) { const binding = getMochaImportBinding(variable); if (binding !== null && variable.references.length > 0) { reportUnexpectedImportBinding(context, binding, configuredMochaInterface); } } } export const consistentInterfaceRule = { meta: { type: 'problem', docs: { description: 'Enforces consistent use of mocha interfaces', recommended: false, url: 'https://github.com/lo1tuma/eslint-plugin-mocha/blob/main/documentation/rules/consistent-interface.md' }, fixable: 'code', schema: [optionSchema], defaultOptions: [defaultOption], messages: { unexpectedInterface: 'Unexpected use of {{actualInterface}} interface instead of {{expectedInterface}}' }, languages: ['js/js'] }, create(context) { const { interface: interfaceToUse } = getRuleOption(context); const configuredMochaInterface = getInterface(context.settings); return { Program() { if (configuredMochaInterface === 'require') { return; } reportUnexpectedImportBindingsInModule(context, configuredMochaInterface); }, ...createMochaVisitors(context, { anyTestEntity(visitorContext) { if (visitorContext.interface !== interfaceToUse) { reportUnexpectedInterface(context, visitorContext.node, visitorContext.interface, interfaceToUse); } } }, { includeAllInterfaces: true }) }; } }; //# sourceMappingURL=consistent-interface.js.map