Files

116 lines
3.7 KiB
JavaScript

import { UnreachableCaseError } from './unreachable-case-error.js'
import { getEslintDisabledRules } from './get-eslint-disabled-rules.js'
/**
* Determines which lines have the specified ESLint rule disabled via comments.
*
* Parses ESLint disable comments in the source code to identify lines where a
* specific rule should not be enforced. Handles all ESLint disable directives:
*
* - `eslint-disable-next-line` - Disables the rule for the next line
* - `eslint-disable-line` - Disables the rule for the current line
* - `eslint-disable` - Disables the rule from this point forward
* - `eslint-enable` - Re-enables the rule after a previous disable.
*
* The function correctly handles:
*
* - Rule-specific disables (e.g., `eslint-disable-next-line rule-name`)
* - Global disables (e.g., `eslint-disable-next-line` without specific rules)
* - Nested disable/enable pairs.
*
* @example
*
* ```ts
* // Source code with disable comments:
* // eslint-disable-next-line perfectionist/sort-imports
* import { z } from 'zod'
* import { a } from 'a'
*
* // eslint-disable perfectionist/sort-imports
* import { y } from 'y'
* import { b } from 'b'
* // eslint-enable perfectionist/sort-imports
*
* getEslintDisabledLines({
* sourceCode,
* ruleName: 'perfectionist/sort-imports',
* })
* // Returns: [2, 5, 6] (lines where the rule is disabled)
* ```
*
* @param props - Configuration object.
* @param props.sourceCode - ESLint source code object containing comments.
* @param props.ruleName - Name of the rule to check for disable directives.
* @returns Array of line numbers (1-indexed) where the rule is disabled.
*/
function getEslintDisabledLines(props) {
let { sourceCode, ruleName } = props
let returnValue = []
let lineRulePermanentlyDisabled = null
for (let comment of sourceCode.getAllComments()) {
let eslintDisabledRules = getEslintDisabledRules(comment.value)
if (!eslintDisabledRules) {
continue
}
/* v8 ignore next 3 -- @preserve Hard to test this false branch. */
if (
!(
eslintDisabledRules.rules === 'all' ||
eslintDisabledRules.rules.includes(ruleName)
)
) {
continue
}
switch (eslintDisabledRules.eslintDisableDirective) {
case 'eslint-disable-next-line':
returnValue.push(comment.loc.end.line + 1)
continue
case 'eslint-disable-line':
returnValue.push(comment.loc.start.line)
continue
case 'eslint-disable':
lineRulePermanentlyDisabled ??= comment.loc.start.line
break
case 'eslint-enable':
/* v8 ignore next -- @preserve Hard to cover in test without raising another ESLint error. */
if (!lineRulePermanentlyDisabled) {
continue
}
returnValue.push(
...createArrayFromTo(
lineRulePermanentlyDisabled + 1,
comment.loc.start.line,
),
)
lineRulePermanentlyDisabled = null
break
/* v8 ignore next 2 -- @preserve Exhaustive guard. */
default:
throw new UnreachableCaseError(
eslintDisabledRules.eslintDisableDirective,
)
}
}
return returnValue
}
/**
* Creates an array of consecutive integers from start to end (inclusive).
*
* Helper function to generate an array of line numbers for ranges disabled by
* eslint-disable/eslint-enable comment pairs.
*
* @example
*
* ```ts
* createArrayFromTo(5, 8)
* // Returns: [5, 6, 7, 8]
* ```
*
* @param i - Starting number (inclusive).
* @param index - Ending number (inclusive).
* @returns Array of consecutive integers from i to index.
*/
function createArrayFromTo(i, index) {
return Array.from({ length: index - i + 1 }, (_, item) => i + item)
}
export { getEslintDisabledLines }