Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to customize additional variable names to be replaced by self with the strongifiedSelf rule #523

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Changes from 2 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
18 changes: 10 additions & 8 deletions Sources/Rules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3758,20 +3758,22 @@ public struct _FormatRules {
}
}

/// Removed backticks from `self` when strongifying
/// Standardize on using self when strongifying self
public let strongifiedSelf = FormatRule(
help: "Remove backticks around `self` in Optional unwrap expressions."
help: "Standardize on using self when strongifying self."
) { formatter in
guard formatter.options.swiftVersion >= "4.2" else {
return
}
formatter.forEach(.identifier("`self`")) { i, _ in
guard let equalIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: {
$0 == .operator("=", .infix)
}), formatter.next(.nonSpaceOrCommentOrLinebreak, after: equalIndex) == .identifier("self") else {
return
["`self`", "ss", "sSelf", "strongSelf"].forEach { idName in
krunk4ever marked this conversation as resolved.
Show resolved Hide resolved
formatter.forEach(.identifier(idName)) { i, _ in
guard let equalIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: {
$0 == .operator("=", .infix)
}), formatter.next(.nonSpaceOrCommentOrLinebreak, after: equalIndex) == .identifier("self") else {
return
}
Copy link
Owner

Choose a reason for hiding this comment

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

I don't think this will work. It only replaces the identifier if it appears in an expression like strongSelf = self, but it won't replace any references to strongSelf in the subsequent code.

Copy link
Author

@krunk4ever krunk4ever Jan 3, 2020

Choose a reason for hiding this comment

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

@nicklockwood, yeah, I also discussed this with my coworkers to see if autocorrect was the right thing to do here. there's actually 3 things that need to be autocorrected:

  1. Rename the disallowed variable name with self
  2. Update any references to the disallowed variable name to self
  3. Fix optionality of existing references to self which are no longer optional

Example for number 3:

{ [weak self] in 
  guard let strongSelf = self else { return }
  self?.doWork()
}

In the above example, we would also need to remove the trailing ? after self:

{ [weak self] in
  guard let self = self else { return }
  self.doWork()
}

(2) and (3) both feel really complicated for autocorrection w/o full understanding of the code graph and would be probably better off with human intervention. However, we still found it to be useful for autocorrect to complete step (1) despite the fact it might leave the code uncompilable.

thoughts?

Copy link
Owner

@nicklockwood nicklockwood Jan 4, 2020

Choose a reason for hiding this comment

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

  1. is similar in complexity to what I already do for removing implicit self. It's certainly complicated because it requires an understanding of scope and tracking variable declarations and shadowing, but it's a local problem (i.e. it doesn't require knowledge of externally-defined symbols and their types) so it can be solved.

  2. is probably too difficult since modifications from optional to non-optional may be nontrivial, but I also think it's a bad idea generally because the developer may have a good reason for referencing self? rather than strongSelf and a formatter shouldn't second-guess that. However it wouldn't be hard to detect the user of self and not replace strongSelf in those cases.

Copy link
Owner

Choose a reason for hiding this comment

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

Just doing 1) and leaving code non-compilable is not acceptable IMO. Now that SwiftFormat has fairly decent linting capability, it might make sense to introduce the concept of "lint-only" rules for cases like this, where it would warn but not actually replace the symbol.

But TBH, it would probably make more sense to make it a SwiftLint rule instead in that case.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, we have a simple SwiftLint custom rule for detecting this for now:

  reuse_self_for_strongified_self:
    name: Reuse self for strongified self
    regex: "(?i) (s|ss|sSelf|strongSelf) = self[\n ,]"
    message: "Reuse self as the variable name when strongifying self as per Airbnb Swift style guide"
    severity: error

If you have an idea of how to autocorrect for (2), that would be great! I can take a look at removing implicit self and see if I can mimic that behavior, but if you have pointers or have the bandwidth to add that, even better!

formatter.replaceToken(at: i, with: .identifier("self"))
}
formatter.replaceToken(at: i, with: .identifier("self"))
}
}

Expand Down