From 7cfc2720439c55842a9a7fd7214fc6cf4a452790 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 13:51:50 +0000 Subject: [PATCH] fix: support ESLint 10 by using context.sourceCode ESLint 10 removed the deprecated `context.getSourceCode()` method, so `no-complex-conditionals` and `max-function-length` threw `TypeError: context.getSourceCode is not a function` when linting under ESLint 10. Add a `getSourceCode()` helper that prefers the `context.sourceCode` property and only falls back to the method when the property is absent, and use it in both rules. Adds regression tests that drive the rules with an ESLint 10 style context that has no `getSourceCode` method. Fixes #14 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GHzmaqnfUrfLno7pHBPeSq --- rules/max-function-length.ts | 3 +- rules/no-complex-conditionals.ts | 5 +- tests/eslint10-compatibility.test.ts | 73 ++++++++++++++++++++++++++++ utils/node-helpers.ts | 17 ++++++- 4 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 tests/eslint10-compatibility.test.ts diff --git a/rules/max-function-length.ts b/rules/max-function-length.ts index 323b51c..bc06db5 100644 --- a/rules/max-function-length.ts +++ b/rules/max-function-length.ts @@ -1,6 +1,7 @@ import { Rule } from 'eslint'; import { MaxFunctionLengthOptions } from '../types/rule-options.js'; import { createRuleMeta, RULE_CATEGORIES } from '../utils/rule-meta.js'; +import { getSourceCode } from '../utils/node-helpers.js'; const rule: Rule.RuleModule = { meta: createRuleMeta('max-function-length', { @@ -44,7 +45,7 @@ Long functions are hard to understand and test. Consider: const skipBlankLines = options.skipBlankLines !== undefined ? options.skipBlankLines : true; const skipComments = options.skipComments !== undefined ? options.skipComments : true; - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); function countLines(node: any): number { const body = node.body; diff --git a/rules/no-complex-conditionals.ts b/rules/no-complex-conditionals.ts index 292e968..fb26a60 100644 --- a/rules/no-complex-conditionals.ts +++ b/rules/no-complex-conditionals.ts @@ -6,6 +6,7 @@ import { Rule } from 'eslint'; import { ComplexConditionalsOptions } from '../types/rule-options.js'; import { createRuleMeta, RULE_CATEGORIES } from '../utils/rule-meta.js'; +import { getSourceCode } from '../utils/node-helpers.js'; const rule: Rule.RuleModule = { meta: createRuleMeta('no-complex-conditionals', { @@ -56,7 +57,7 @@ Refactoring suggestions: // Suggest a name based on context const suggestName = (node: any): string => { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const text = sourceCode.getText(node).toLowerCase(); if (text.includes('valid') || text.includes('invalid')) return 'isValid'; @@ -72,7 +73,7 @@ Refactoring suggestions: // Format condition breakdown for message const formatConditionBreakdown = (node: any): string => { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); // Split by operators to show each part const parts: string[] = []; diff --git a/tests/eslint10-compatibility.test.ts b/tests/eslint10-compatibility.test.ts new file mode 100644 index 0000000..8f0dc75 --- /dev/null +++ b/tests/eslint10-compatibility.test.ts @@ -0,0 +1,73 @@ +/** + * @fileoverview Regression tests for ESLint 10 compatibility + * + * ESLint 10 removed the deprecated `context.getSourceCode()` method, so rules + * must read the `context.sourceCode` property instead. These tests drive the + * rules with a context that only exposes the property, which is what ESLint 10 + * hands to a rule. + * + * @see https://github.com/aryelu/eslint-plugin-code-complete/issues/14 + */ + +import { describe, it, expect } from 'vitest'; +import { Rule, SourceCode } from 'eslint'; +import * as parser from '@typescript-eslint/parser'; + +import noComplexConditionals from '../rules/no-complex-conditionals'; +import maxFunctionLength from '../rules/max-function-length'; + +/** + * Builds a rule context that mimics ESLint 10: `sourceCode` is present and + * `getSourceCode` does not exist at all. + */ +function createEslint10Context(code: string, options: unknown[] = []) { + const { ast } = parser.parseForESLint(code, { + loc: true, + range: true, + tokens: true, + comment: true + }); + + const sourceCode = new SourceCode({ text: code, ast: ast as any }); + const reports: any[] = []; + + const context = { + options, + sourceCode, + report: (descriptor: any) => reports.push(descriptor) + } as unknown as Rule.RuleContext; + + expect((context as any).getSourceCode).toBeUndefined(); + + return { ast, context, reports }; +} + +describe('ESLint 10 compatibility', () => { + it('no-complex-conditionals reports without context.getSourceCode', () => { + const { ast, context, reports } = createEslint10Context('if (a && b && c && d) {}'); + + const listener = noComplexConditionals.create(context); + (listener.IfStatement as any)(ast.body[0]); + + expect(reports).toHaveLength(1); + expect(reports[0].messageId).toBe('complexConditional'); + expect(reports[0].data.conditionBreakdown).toContain('1. a'); + expect(reports[0].data.suggestedName).toBe('shouldProceed'); + }); + + it('max-function-length reports without context.getSourceCode', () => { + const body = Array.from({ length: 5 }, (_unused, index) => ` const value${index} = ${index};`).join('\n'); + const { ast, context, reports } = createEslint10Context( + `function longFunction() {\n${body}\n}`, + [{ maxLines: 3 }] + ); + + const listener = maxFunctionLength.create(context); + (listener.FunctionDeclaration as any)(ast.body[0]); + + expect(reports).toHaveLength(1); + expect(reports[0].messageId).toBe('maxFunctionLength'); + expect(reports[0].data.name).toBe('longFunction'); + expect(reports[0].data.lines).toBe('5'); + }); +}); diff --git a/utils/node-helpers.ts b/utils/node-helpers.ts index 46f274d..32f284e 100644 --- a/utils/node-helpers.ts +++ b/utils/node-helpers.ts @@ -3,6 +3,8 @@ * @author eslint-plugin-code-complete */ +import type { Rule, SourceCode } from 'eslint'; + /** * Checks if a parameter name starts with an allowed prefix * @param {string} name - Parameter name @@ -226,4 +228,17 @@ export function countFunctionParameters( } return count; -} \ No newline at end of file +} +/** + * Gets the SourceCode object for the current lint run. + * + * ESLint 10 removed the deprecated `context.getSourceCode()` method in favour of + * the `context.sourceCode` property, so prefer the property and only fall back to + * the method for older versions that predate it. + * + * @param {Rule.RuleContext} context - The rule context + * @returns {SourceCode} - The source code object + */ +export function getSourceCode(context: Rule.RuleContext): SourceCode { + return context.sourceCode ?? context.getSourceCode(); +}