eslint/eslint · error · Error

`SourceCode#${methodName}()` cannot be called inside a rule.

Error message

`SourceCode#${methodName}()` cannot be called inside a rule.

What it means

Thrown by throwForbiddenMethodError() at lib/rule-tester/rule-tester.js:323 for the methods in forbiddenMethods: applyInlineConfig, applyLanguageOptions, finalize. RuleTester monkeypatches SourceCode.prototype so each of these methods can be called at most once per SourceCode instance — the first call (the legitimate one by the linter's config-apply pipeline) is allowed through, any subsequent call (i.e. a rule calling it) throws.

Source

Thrown at lib/rule-tester/rule-tester.js:323

 * @param {string} methodName The name of the method to forbid.
 * @param {Function} prototype The prototype with the original method to call.
 * @returns {Function} The function that throws the error.
 */
function throwForbiddenMethodError(methodName, prototype) {
	const original = prototype[methodName];

	return function (...args) {
		const called = forbiddenMethodCalls.get(methodName);

		/* eslint-disable no-invalid-this -- needed to operate as a method. */
		if (!called.has(this)) {
			called.add(this);

			return original.apply(this, args);
		}
		/* eslint-enable no-invalid-this -- not needed past this point */

		throw new Error(
			`\`SourceCode#${methodName}()\` cannot be called inside a rule.`,
		);
	};
}

/**
 * Extracts names of {{ placeholders }} from the reported message.
 * @param   {string} message Reported message
 * @returns {string[]} Array of placeholder names
 */
function getMessagePlaceholders(message) {
	const matcher = getPlaceholderMatcher();

	return Array.from(message.matchAll(matcher), ([, name]) => name.trim());
}

/**
 * Returns the placeholders in the reported messages but

View on GitHub (pinned to f131c034ad)

Solutions

  1. Remove the call to applyInlineConfig / applyLanguageOptions / finalize from the rule body — these are the linter's responsibility.
  2. If the rule needs language options, read them from `context.languageOptions` instead of forcing application.
  3. If the rule needs inline config effects, that indicates a design issue; restructure so the rule reads already-applied state.

Example fix

// before
create(context) {
  context.sourceCode.applyInlineConfig();
  return { Program(node) { /* ... */ } };
}

// after
create(context) {
  return { Program(node) { /* ... */ } };
}
Defensive patterns

Strategy: validation

Validate before calling

const FORBIDDEN = new Set(["applyInlineConfig","applyLanguageOptions","finalize"]);
const ruleSrc = require('node:fs').readFileSync(rulePath,'utf8');
for (const m of FORBIDDEN) {
  if (ruleSrc.includes(`sourceCode.${m}`) || ruleSrc.includes(`.sourceCode.${m}`)) {
    throw new Error(`Rule must not call SourceCode#${m}()`);
  }
}

Type guard

/** @param {string} method */
function isForbiddenSourceCodeMethod(method) {
  return ["applyInlineConfig","applyLanguageOptions","finalize"].includes(method);
}

Prevention

When it happens

Trigger: Inside a rule's `create()` handler, the rule explicitly calls `context.sourceCode.applyInlineConfig()`, `.applyLanguageOptions()`, or `.finalize()`, causing a second invocation of that method on the same SourceCode instance during a test. These methods are part of the linter's lifecycle and are not meant to be invoked by rule code.

Common situations: A rule author mistakenly thinks they need to apply inline config or finalize the source code themselves; copy-paste from the core Linter pipeline into a rule; a rule that mutates SourceCode state expecting re-initialization.

Related errors


AI-assisted analysis of eslint/eslint@f131c034ad (2026-08-03). Data as JSON: /data/errors/3d058c16cd4030de.json. Report an issue: GitHub.