ESLint plugin that prevents committing focused tests by detecting .only blocks in test files
npx @tessl/cli install tessl/npm-eslint-plugin-no-only-tests@3.3.0ESLint Plugin No Only Tests is a focused ESLint plugin that prevents developers from accidentally committing focused tests (using .only methods) to version control. It supports multiple JavaScript testing frameworks including Mocha, Jest, Jasmine, and others by detecting test blocks like describe.only, it.only, test.only, and similar patterns.
npm install --save-dev eslint-plugin-no-only-testsimport('eslint') and import('estree') typesThis package is designed to be consumed as an ESLint plugin and is configured in your ESLint configuration rather than imported directly into your code.
ESLint configuration (.eslintrc.json):
{
"plugins": ["no-only-tests"],
"rules": {
"no-only-tests/no-only-tests": "error"
}
}After installing and configuring the plugin, ESLint will automatically detect and report focused test blocks:
// This will trigger an error
describe.only("Some test suite", function() {
it("should pass", function() {
// test code
});
});
// This will trigger an error
it.only("Some test", function() {
// test code
});
// This is allowed
describe("Some test suite", function() {
it("should pass", function() {
// test code
});
});The plugin follows the standard ESLint plugin architecture:
.only callsMain plugin export providing ESLint with available rules.
module.exports = {
rules: {
"no-only-tests": require("./rules/no-only-tests")
}
};The core ESLint rule that detects and optionally fixes focused test patterns.
/** @type {import('eslint').Rule.RuleModule} */
const ESLintRuleModule = {
meta: {
docs: {
description: "disallow .only blocks in tests";
category: "Possible Errors";
recommended: true;
url: "https://github.com/levibuzolic/eslint-plugin-no-only-tests";
};
fixable: "code";
schema: [{
type: "object";
properties: {
block: {
type: "array";
items: { type: "string" };
uniqueItems: true;
default: defaultOptions.block;
};
focus: {
type: "array";
items: { type: "string" };
uniqueItems: true;
default: defaultOptions.focus;
};
functions: {
type: "array";
items: { type: "string" };
uniqueItems: true;
default: defaultOptions.functions;
};
fix: {
type: "boolean";
default: defaultOptions.fix;
};
};
additionalProperties: false;
}];
};
create(context: import('eslint').Rule.RuleContext): {
Identifier(node: import('estree').Node): void;
};
};The rule accepts comprehensive configuration options for customizing detection patterns.
/** @typedef {{block?: string[], focus?: string[], functions?: string[], fix?: boolean}} RuleOptions */
interface RuleOptions {
/** Array of test block names to detect (supports wildcard matching with '*' suffix) */
block?: string[];
/** Array of focus method names to detect */
focus?: string[];
/** Array of forbidden function names (e.g., 'fit', 'xit') */
functions?: string[];
/** Enable auto-fixing to remove .only calls */
fix?: boolean;
}Default Options:
/** @type {RuleOptions} */
const defaultOptions = {
block: [
"describe", "it", "context", "test", "tape", "fixture", "serial",
"Feature", "Scenario", "Given", "And", "When", "Then"
],
focus: ["only"],
functions: [],
fix: false
};Basic Configuration:
{
"rules": {
"no-only-tests/no-only-tests": "error"
}
}Custom Test Blocks and Focus Methods:
{
"rules": {
"no-only-tests/no-only-tests": [
"error",
{
"block": ["test", "it", "assert"],
"focus": ["only", "focus"]
}
]
}
}Wildcard Block Matching:
{
"rules": {
"no-only-tests/no-only-tests": [
"error",
{
"block": ["test*"]
}
]
}
}Function Blacklisting:
{
"rules": {
"no-only-tests/no-only-tests": [
"error",
{
"functions": ["fit", "xit"]
}
]
}
}Auto-fixing Enabled:
{
"rules": {
"no-only-tests/no-only-tests": [
"error",
{
"fix": true
}
]
}
}The plugin supports detection patterns for multiple JavaScript testing frameworks:
describe.only, it.only, context.onlydescribe.only, it.only, test.onlydescribe.only, it.onlytape.onlyFeature.only, Scenario.only, Given.only, And.only, When.only, Then.onlyPattern-based Errors:
"{callPath} not permitted" (e.g., "describe.only not permitted")"it.default.before.only not permitted"Function-based Errors:
"{functionName} not permitted" (e.g., "fit not permitted")Auto-fixing Behavior:
fix: true is enabled, automatically removes the focus method (e.g., converts describe.only(...) to describe(...))fixer.removeRange([rangeStart - 1, rangeEnd]) to remove the focus method including the preceding dotnode.range must be available)Wildcard Matching:
* enable prefix matching using block.replace(/\*$/, "")"test*" matches testResource.only, testExample.only${block}. prefix matchCall Chain Analysis:
it.default.before.onlygetCallPath() functionAST Node Processing:
find() for block matching, indexOf() for focus/function matchingRecursively constructs call path strings from ESTree AST nodes for pattern matching.
/**
* Recursively constructs call path from AST node
* @param {import('estree').Node} node - The AST node to analyze
* @param {string[]} path - Accumulated path array (defaults to empty array)
* @returns {string[]} Array of path components representing the call chain
*/
function getCallPath(node, path = []);Implementation Details:
object, property, callee, and name node propertiesit.default.before.onlyThe plugin relies on external TypeScript type definitions:
// Core ESLint types
import('eslint').Rule.RuleModule;
import('eslint').Rule.RuleContext;
// ESTree AST node types
import('estree').Node;Key ESLint Interfaces:
Rule.RuleModule - Complete rule definition structureRule.RuleContext - Runtime context provided by ESLintRuleFixer - Auto-fix interface with removeRange() methodESTree Node Properties:
node.range?.[0] and node.range?.[1] - Source location rangesnode.parent - Parent AST node referencenode.name - Identifier name for Identifier nodesnode.object and node.property - Member expression components