Skip to content

Commit

Permalink
Merge pull request #176 from Carrione/feature/homogenizeGuardStatements_
Browse files Browse the repository at this point in the history
homogenization of guard statements
  • Loading branch information
twostraws committed Feb 4, 2021
2 parents 9f4d22a + 50db2f7 commit 018d6c4
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 0 deletions.
87 changes: 87 additions & 0 deletions Unwrap/Extensions/String-Variables.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ extension String {
if replaced.isEmpty {
return replaced
}

// Homogenize guard statement to reduce the number of possible solutions
replaced = homogenizeGuardStatements()

// Homogenize trailing closures: array.map { … } is always preferred to array.map({ … })
replaced = replaced.replacingOccurrences(of: #"\(\{(.*?)\}\)"#, with: "{$1}", options: .regularExpression)
Expand Down Expand Up @@ -124,6 +127,90 @@ extension String {

return replaced
}

/**
Homogenize guard statements in code to reduce the number of possible solutions
If an else statement of each guard is suitable for expression in one line, new lines after { and before } and extra spaces in the else block will be removed.
- parameter code: code
- returns: Homogenized statement
# Example #
```
guard ... else { \n return something \n } will be changed to guard ... else { return something }
guard ... else { \n return \n } will be changed to guard ... else { return }
guard ... else {\n continue \n } will be changed to guard ... else { continue }
```
*/
func homogenizeGuardStatements() -> String {
var replaced = self

replaced = replacingOccurrences(of: #"else *\{ *\n *return"#,
with: "else { return",
in: replaced,
rangeRegexPattern: #"guard[ \w.()!=&|\n\t\\"\[\]]*else *\{ *\n *return *\w* *\n *\}"#)

replaced = replacingOccurrences(of: #"else *\{ *\n *continue"#,
with: "else { continue",
in: replaced,
rangeRegexPattern: #"guard[ \w.()!=&|\n\t\\"\[\]]*else *\{ *\n *continue *\w* *\n *\}"#)

replaced = replacingOccurrences(of: #" *\n *\}"#,
with: " }",
in: replaced,
rangeRegexPattern: #"guard[ \w.()!=&|\n\t\\"\[\]]*else *\{ *(return|continue) *\w* *\n *\}"#)

replaced = replacingOccurrences(of: #" {2,}"#,
with: " ",
in: replaced,
rangeRegexPattern: #"guard[ \w.()!=&|\n\t\\"\[\]]*else *\{ *(return|continue) *\w* *\}"#)

return replaced
}

/**
Replaces all occurrences of a target regex with a replacement in text parts that are the same as a range regex.
- parameter targetRegexPattern: regex pattern for replace
- parameter replacement: replacement text
- parameter text: text in which we want to make changes
- parameter rangeRegexPattern: regex pattern to find the parts of the text where the replacement will be made
- returns: new text
*/
fileprivate func replacingOccurrences(of targetRegexPattern: String, with replacement: String, in text: String, rangeRegexPattern: String) -> String {

var result = text

// Calculate a range regex and its matches in the result.
guard let rangeRegex = try? NSRegularExpression(pattern: rangeRegexPattern, options: []) else { return text }
let rangeMatches = rangeRegex.matches(in: result, options: [], range: NSRange(location: 0, length: result.utf16.count))

// This is a counter of a recalculated matches from loop.
var recalculatedMatchesCount = 0

// Loop over the range matches and in each iteration recalculates the range and makes changes to the result with that recalculated range.
for index in rangeMatches.indices {

// The result could be changed and we need to get new matches with a new length of result otherwise we could get an error.
let recalculatedMatches = rangeRegex.matches(in: result, options: [], range: NSRange(location: 0, length: result.utf16.count))

// In the first iteration, set the recalculated matches counter to the initial value.
if index == 0 {
recalculatedMatchesCount = recalculatedMatches.count
}

// Get a recalculated range from the recalculated regex matches.
// In some cases, the change in the result affects the number of matches found (there will be a loss in the previous iteration of the processed match due to the incorporated changes). In this case, the number of matches will be reduced and we must work with the match on index 0 in the current iteration.
let i = recalculatedMatches.count == recalculatedMatchesCount ? index : 0
let range = Range(recalculatedMatches[i].range, in: result)

// Set the recalculated matches counter to the current value.
recalculatedMatchesCount = recalculatedMatches.count

// Make changes to the result with the recalculated range.
result = result.replacingOccurrences(of: targetRegexPattern, with: replacement, options: .regularExpression, range: range)
}
return result
}

/// Replaces one instances of a search with a replacement wrapper. For example, it might be asked to replace all variable names using a replacement wrapper &, so it will replace "foo", "bar", and "baz" with &1&, &2&, and &3&.
fileprivate func anonymizingComponent(_ regexString: String, replacementWrapper: String) -> String {
Expand Down
46 changes: 46 additions & 0 deletions UnwrapTests/ExtensionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,50 @@ class ExtensionTests: XCTestCase {
let homogenizedString19 = "func myFunction(input1:String,input2:String) -> Bool{ input1.lowercased() == input2.lowercased() }"
XCTAssertEqual(cleanString19.solveOptionalReturnStatements(), homogenizedString19)
}

func testHomogenizeGuardStatements() {
let cleanString1 = "guard s==a else { \n return \n }\nreturn somethingelse\n}"
let homogenizedString1 = "guard s==a else { return }\nreturn somethingelse\n}"
XCTAssertEqual(cleanString1.homogenizeGuardStatements(), homogenizedString1)

let cleanString2 = "guardinput1else { \n return something \n }\nreturn somethingelse\n}"
let homogenizedString2 = "guardinput1else { return something }\nreturn somethingelse\n}"
XCTAssertEqual(cleanString2.homogenizeGuardStatements(), homogenizedString2)

let cleanString3 = "func myFunction(input1:String,input2:String) -> Bool{\nguard input1.uppercased() != input2.uppercased() else { \n return something \n }\nreturn false\n}"
let homogenizedString3 = "func myFunction(input1:String,input2:String) -> Bool{\nguard input1.uppercased() != input2.uppercased() else { return something }\nreturn false\n}"
XCTAssertEqual(cleanString3.homogenizeGuardStatements(), homogenizedString3)

let cleanString4 = "func myFunction(input1:String,input2:String) -> Bool{\nguard input1.uppercased() != input2.uppercased() else { \n return \n }\nreturn false\n}"
let homogenizedString4 = "func myFunction(input1:String,input2:String) -> Bool{\nguard input1.uppercased() != input2.uppercased() else { return }\nreturn false\n}"
XCTAssertEqual(cleanString4.homogenizeGuardStatements(), homogenizedString4)

let cleanString5 = "func myFunction(input1:String,input2:String) -> Bool{\nguard input1.uppercased() !=&|\n input2.uppercased() else {\nreturn true\n}\nreturn false\n}"
let homogenizedString5 = "func myFunction(input1:String,input2:String) -> Bool{\nguard input1.uppercased() !=&|\n input2.uppercased() else { return true }\nreturn false\n}"
XCTAssertEqual(cleanString5.homogenizeGuardStatements(), homogenizedString5)

let cleanString6 = "func myFunction(input1:String,input2:String) -> Bool{\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else {\nreturn true\n}\nreturn false\n}"
let homogenizedString6 = "func myFunction(input1:String,input2:String) -> Bool{\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { return true }\nreturn false\n}"
XCTAssertEqual(cleanString6.homogenizeGuardStatements(), homogenizedString6)

let cleanString7 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { \n return \n }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { \n return true \n }\nreturn false\n}"
let homogenizedString7 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { return }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { return true }\nreturn false\n}"
XCTAssertEqual(cleanString7.homogenizeGuardStatements(), homogenizedString7)

let cleanString8 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { \n return \n }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { \n return \n }\nreturn false\n}"
let homogenizedString8 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { return }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { return }\nreturn false\n}"
XCTAssertEqual(cleanString8.homogenizeGuardStatements(), homogenizedString8)

let cleanString9 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { \n continue \n }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { \n return \n }\nreturn false\n}"
let homogenizedString9 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { continue }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { return }\nreturn false\n}"
XCTAssertEqual(cleanString9.homogenizeGuardStatements(), homogenizedString9)

let cleanString10 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { \n continue \n }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { \n return true \n }\nreturn false\n}"
let homogenizedString10 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { continue }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { return true }\nreturn false\n}"
XCTAssertEqual(cleanString10.homogenizeGuardStatements(), homogenizedString10)

let cleanString11 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { \n continue \n }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { \n return true \n }\nreturn false\nguard s==a else { \n continue \n }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { \n return true \n }\nreturn false\n}"
let homogenizedString11 = "func myFunction(input1:String,input2:String) -> Bool{\nguard s==a else { continue }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { return true }\nreturn false\nguard s==a else { continue }\nif number == 8 {\nprint()\n} else {\nprint(\"something\")\n}\nguard input1.uppercased() !=&|\n input2.uppercased() else { return true }\nreturn false\n}"
XCTAssertEqual(cleanString11.homogenizeGuardStatements(), homogenizedString11)
}
}

0 comments on commit 018d6c4

Please sign in to comment.