Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **BREAKING:** Replace the exported `MaxCyclomaticOptions` and `MaxCognitiveOptions` types with `ComplexityOptions`. Use `cyclomatic` and `cognitive` instead of the obsolete `max` field.
- **BREAKING:** Remove public exports for `createCognitiveVisitor`, `createCyclomaticVisitor`, `createComplexityVisitor`, `VisitorContext`, and `FunctionScope`. Use `createCombinedComplexityVisitor` for AST analysis or `/standalone` for source strings. Individual visitor migrations change loop labels and cognitive recursion point locations and ordering; see the README migration guide.
- Unify cognitive complexity handlers and route fixture tests through the lint rule's visitor.

### Fixed
Expand Down
28 changes: 12 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ function processData(items, mode, config) {
}
```

TypeScript users can import `ComplexityOptions` from `oxlint-plugin-complexity` to type the rule options.

### Cyclomatic Complexity

Counts decision points in code. [Learn more](https://en.wikipedia.org/wiki/Cyclomatic_complexity)
Expand Down Expand Up @@ -218,24 +220,18 @@ for (const file of result.files) {
}
```

## Migration from v0.x
## Migration to v4 (for library usage)

Replace the removed `max-cyclomatic` / `max-cognitive` rules with the combined `complexity` rule:
- Replace `MaxCyclomaticOptions` / `MaxCognitiveOptions` with `ComplexityOptions`, using `cyclomatic`
and `cognitive` instead of `max`.
- Replace `createCognitiveVisitor` / `createCyclomaticVisitor` with
`createCombinedComplexityVisitor(context, callback)`. Results contain `cyclomatic`, `cognitive`,
`cyclomaticPoints`, and `cognitivePoints`; both metrics now require an oxlint `Context`.
- `createComplexityVisitor`, `VisitorContext`, and `FunctionScope` are now internal.

```diff
// .oxlintrc.json
{
"jsPlugins": ["oxlint-plugin-complexity"],
"rules": {
- "complexity/max-cyclomatic": ["error", { "max": 20 }],
- "complexity/max-cognitive": ["error", { "max": 15 }]
+ "complexity/complexity": ["error", {
+ "cyclomatic": 20,
+ "cognitive": 15
+ }]
}
}
```
When migrating individual visitors, loop labels become `for-in`, `for-of`, and `do-while`.
Cognitive recursion points now identify the first recursive call in traversal order, rather than the
whole function on exit. The `/standalone` and `/diff` APIs are unchanged by this cleanup.

## Attribution

Expand Down
2 changes: 1 addition & 1 deletion src/cognitive/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface CognitiveFunctionScope extends FunctionScope {
hasRecursiveCall: boolean;
}

// Preserve each public visitor's existing diagnostic presentation, not separate scoring rules.
// Preserve the internal extraction visitor's and combined visitor's existing point formats.
const POINT_FORMATS = {
standalone: {
forIn: 'for...in',
Expand Down
14 changes: 2 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,12 @@ export type {
Rule,
Context,
Visitor,
FunctionScope,
ComplexityPoint,
ComplexityResult,
MaxCyclomaticOptions,
MaxCognitiveOptions,
ComplexityOptions,
} from './types.js';

// Re-export visitor factory for advanced usage
export { createComplexityVisitor } from './visitor.js';
export type { VisitorContext } from './visitor.js';

// Re-export calculators for programmatic use
export { createCyclomaticVisitor } from './cyclomatic.js';
export { createCognitiveVisitor } from './cognitive/visitor.js';

// Re-export combined visitor for advanced usage
// Supported AST API for calculating both metrics in one traversal
export { createCombinedComplexityVisitor } from './combined-visitor.js';
export type { CombinedComplexityResult } from './combined-visitor.js';

Expand Down
14 changes: 5 additions & 9 deletions src/rules/complexity.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { defineRule } from '@oxlint/plugins';
import { defineRule, type RuleOptionsSchema } from '@oxlint/plugins';
import type {
Rule,
Context,
FunctionNode,
MaxCognitiveOptions,
ComplexityOptions,
VisitorWithHooks,
ESTreeNode,
} from '../types.js';
Expand All @@ -23,11 +23,7 @@ const DEFAULT_CYCLOMATIC = 20;
const DEFAULT_COGNITIVE = 15;
const DEFAULT_MIN_LINES = 10;

interface CombinedComplexityOptions extends Omit<MaxCognitiveOptions, 'max'> {
cyclomatic?: number;
cognitive?: number;
minLines?: number;
}
type OptionSchema = Extract<RuleOptionsSchema, unknown[]>[number];

/**
* Enforce maximum cyclomatic and cognitive complexity (RECOMMENDED).
Expand Down Expand Up @@ -68,7 +64,7 @@ export const complexity: Rule = defineRule({
description: 'Minimum lines to analyze (default: 10, 0 = analyze all)',
},
...EXTRACTION_SCHEMA_PROPERTIES,
},
} satisfies Record<keyof ComplexityOptions, OptionSchema>,
additionalProperties: false,
},
],
Expand Down Expand Up @@ -138,7 +134,7 @@ export const complexity: Rule = defineRule({

return {
before() {
const options = (context.options[0] ?? {}) as CombinedComplexityOptions;
const options = (context.options[0] ?? {}) as ComplexityOptions;
maxCyclomatic = options.cyclomatic ?? DEFAULT_CYCLOMATIC;
maxCognitive = options.cognitive ?? DEFAULT_COGNITIVE;
minLines = options.minLines ?? DEFAULT_MIN_LINES;
Expand Down
4 changes: 2 additions & 2 deletions src/rules/shared.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Context, ComplexityPoint, ESTreeNode, MaxCognitiveOptions } from '../types.js';
import type { Context, ComplexityPoint, ESTreeNode, ComplexityOptions } from '../types.js';
import { type BreakdownOptions } from '../utils.js';
import {
analyzeExtractionOpportunities,
Expand Down Expand Up @@ -49,7 +49,7 @@ export const EXTRACTION_SCHEMA_PROPERTIES = {
},
} as const;

type ExtractionSchemaOptions = Omit<MaxCognitiveOptions, 'max'>;
type ExtractionSchemaOptions = Pick<ComplexityOptions, keyof typeof EXTRACTION_SCHEMA_PROPERTIES>;

export interface ParsedExtractionOptions {
enableExtraction: boolean;
Expand Down
10 changes: 4 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@ export interface FunctionScope {
points: ComplexityPoint[];
}

export interface MaxCyclomaticOptions {
max?: number;
}

export interface MaxCognitiveOptions {
max?: number;
export interface ComplexityOptions {
cyclomatic?: number;
cognitive?: number;
minLines?: number;
enableExtraction?: boolean;
extractionMultiplier?: number;
minExtractionPercentage?: number;
Expand Down
2 changes: 1 addition & 1 deletion tests/utils/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function parseAndPrepareAst(
}

/**
* Exercise the separate public visitor factories for compatibility checks.
* Exercise the internal single-metric visitor factories for compatibility checks.
*/
function calculateStandaloneComplexityWithVisitor(
code: string,
Expand Down
Loading