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(); +}