Skip to content

Wrap long generic requirement clauses - #2684

Open
davdroman wants to merge 13 commits into
nicklockwood:developfrom
davdroman:feat/generic-requirement-wrapping
Open

davdroman wants to merge 13 commits into
nicklockwood:developfrom
davdroman:feat/generic-requirement-wrapping

Conversation

@davdroman

Copy link
Copy Markdown
Contributor

Wrap over-width declaration-level where clauses one generic requirement per line as part of the wrap rule.

Before this PR:

- extension Combined: MirrorableAtomicTransition where TransitionA: MirrorableAtomicTransition, TransitionB: MirrorableAtomicTransition {}
+ extension Combined: MirrorableAtomicTransition where TransitionA: MirrorableAtomicTransition,
+     TransitionB: MirrorableAtomicTransition {}

After this PR:

- extension Combined: MirrorableAtomicTransition where TransitionA: MirrorableAtomicTransition, TransitionB: MirrorableAtomicTransition {}
+ extension Combined: MirrorableAtomicTransition where
+     TransitionA: MirrorableAtomicTransition,
+     TransitionB: MirrorableAtomicTransition {}

@codecov

codecov Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.57%. Comparing base (1ed45c7) to head (8b1b9fe).

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2684      +/-   ##
===========================================
+ Coverage    95.49%   95.57%   +0.07%     
===========================================
  Files          181      181              
  Lines        27680    27788     +108     
===========================================
+ Hits         26433    26557     +124     
+ Misses        1247     1231      -16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Format over-width declaration-level where clauses one requirement per line while preserving comments, formatter directives, partial wrapping, and formatting ranges.
@davdroman
davdroman force-pushed the feat/generic-requirement-wrapping branch from 00c0553 to 8b1b9fe Compare September 4, 2026 21:14
Comment thread Sources/Rules/Wrap.swift
}

extension Formatter {
func wrapGenericRequirements() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this but it should be behind an option, like --list-wrap-threshold. In fact it may make sense to just use --list-wrap-threshold.

Comment thread Sources/Formatter.swift
}

/// Executes a closure without changing the current rule's enablement or options state.
func withPreservedRuleState<T>(_ body: () throws -> T) rethrows -> T {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to avoid introducing something complicated like this. What is the edge case you are trying to fix?

Comment thread Sources/Rules/Wrap.swift
declarationKeywordIndex = functionKeywordIndex
whereClauseRange = parsedRange
} else {
guard let declaration = declarations.declaration(containing: whereIndex),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of iterating over tokens and then finding the containing declaration, it would be better to just use declarations.forEachResursiveDeclaration

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also believe this check as written won't handle nested types, please include a test case for that. forEachResursiveDeclaration will handle that.

if tokens[keywordIndex] == .keyword("init"),
let nextToken = index(of: .nonSpaceOrCommentOrLinebreak, after: keywordIndex),
tokens[nextToken] == .operator("?", .postfix)
tokens[nextToken] == .operator("?", .postfix) || tokens[nextToken] == .operator("!", .postfix)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not know about these 👍🏻

Comment thread Sources/Rules/Wrap.swift
endIndex: whereClauseRange.upperBound,
forceWrap: true,
leadingDelimiter: .delimiter(","),
normalizeSpaceAfterDelimiter: false

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why false? would be simpler to not need to add this extra argument

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given how many changes it takes to use wrapMultilineStatement here, it seems better to just not attempt to use it and instead implement one-off wrapping code directly in this function

@nicklockwood
nicklockwood force-pushed the develop branch 3 times, most recently from 5908303 to 741c3e4 Compare September 16, 2026 06:51
@nicklockwood
nicklockwood force-pushed the develop branch 3 times, most recently from e668206 to e67a08f Compare September 30, 2026 08:20
@nicklockwood

Copy link
Copy Markdown
Owner

I feel like this should also have a before-first/after-first wrap option like the other similar rules

@davdroman

Copy link
Copy Markdown
Contributor Author

@nicklockwood @calda I've been really busy since I made this PR but haven't forgotten. I promise to get around to this asap :)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants