mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-06-16 10:34:34 +00:00
Improve preferForLoop identifier selection logic
This commit is contained in:
@@ -1224,7 +1224,7 @@ Convert functional `forEach` calls to for loops.
|
||||
Option | Description
|
||||
--- | ---
|
||||
`--anonymousforeach` | Convert anonymous forEach: "convert" (default) or "ignore".
|
||||
`--onelineforeach` | Convert one-line forEach: "preserve" (default) or "wrap".
|
||||
`--onelineforeach` | Convert one-line forEach: "convert" or "ignore" (default).
|
||||
|
||||
<details>
|
||||
<summary>Examples</summary>
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
//
|
||||
// Source from v0.0.3 of the GrammaticalNumber package
|
||||
// https://github.com/Cosmo/GrammaticalNumber/releases/tag/0.0.3
|
||||
//
|
||||
// `public` symbols have been replaced with `internal`
|
||||
// so this internal dependency isn't visible to consumers.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum GrammaticalNumberRule {
|
||||
case plural(_ rule: String, _ replacement: String)
|
||||
case singular(_ rule: String, _ replacement: String)
|
||||
case irregular(_ singular: String, _ plural: String)
|
||||
case uncountable(_ rule: String)
|
||||
|
||||
static var defaultLanguage = "en"
|
||||
|
||||
static func add(_ rule: GrammaticalNumberRule, language: String = defaultLanguage) {
|
||||
if rules[language] == nil {
|
||||
rules[language] = []
|
||||
}
|
||||
rules[language]?.append(rule)
|
||||
}
|
||||
|
||||
static func clear() {
|
||||
rules = [:]
|
||||
}
|
||||
|
||||
static var rules: [String: [GrammaticalNumberRule]] = [
|
||||
defaultLanguage: [
|
||||
.plural(#"$"#, "s"),
|
||||
.plural(#"s$"#, "s"),
|
||||
.plural(#"^(ax|test)is$"#, #"$1es"#),
|
||||
.plural(#"(octop|vir)us$"#, #"$1i"#),
|
||||
.plural(#"(octop|vir)i$"#, #"$1i"#),
|
||||
.plural(#"(alias|status)$"#, #"$1es"#),
|
||||
.plural(#"(bu)s$"#, #"$1ses"#),
|
||||
.plural(#"(buffal|tomat)o$"#, #"$1oes"#),
|
||||
.plural(#"([ti])um$"#, #"$1a"#),
|
||||
.plural(#"([ti])a$"#, #"$1a"#),
|
||||
.plural(#"sis$"#, "ses"),
|
||||
.plural(#"(?:([^f])fe|([lr])f)$"#, #"$1$2ves"#),
|
||||
.plural(#"(hive)$"#, #"$1s"#),
|
||||
.plural(#"([^aeiouy]|qu)y$"#, #"$1ies"#),
|
||||
.plural(#"(x|ch|ss|sh)$"#, #"$1es"#),
|
||||
.plural(#"(matr|vert|ind)(?:ix|ex)$"#, #"$1ices"#),
|
||||
.plural(#"^(m|l)ouse$"#, #"$1ice"#),
|
||||
.plural(#"^(m|l)ice$"#, #"$1ice"#),
|
||||
.plural(#"^(ox)$"#, #"$1en"#),
|
||||
.plural(#"^(oxen)$"#, #"$1"#),
|
||||
.plural(#"(quiz)$"#, #"$1zes"#),
|
||||
|
||||
.singular(#"s$"#, ""),
|
||||
.singular(#"(ss)$"#, #"$1"#),
|
||||
.singular(#"(n)ews$"#, #"$1ews"#),
|
||||
.singular(#"([ti])a$"#, #"$1um"#),
|
||||
.singular(#"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$"#, #"$1sis"#),
|
||||
.singular(#"(^analy)(sis|ses)$"#, #"$1sis"#),
|
||||
.singular(#"([^f])ves$"#, #"$1fe"#),
|
||||
.singular(#"(hive)s$"#, #"$1"#),
|
||||
.singular(#"(tive)s$"#, #"$1"#),
|
||||
.singular(#"([lr])ves$"#, #"$1f"#),
|
||||
.singular(#"([^aeiouy]|qu)ies$"#, #"$1y"#),
|
||||
.singular(#"(s)eries$"#, #"$1eries"#),
|
||||
.singular(#"(m)ovies$"#, #"$1ovie"#),
|
||||
.singular(#"(x|ch|ss|sh)es$"#, #"$1"#),
|
||||
.singular(#"^(m|l)ice$"#, #"$1ouse"#),
|
||||
.singular(#"(bus)(es)?$"#, #"$1"#),
|
||||
.singular(#"(o)es$"#, #"$1"#),
|
||||
.singular(#"(shoe)s$"#, #"$1"#),
|
||||
.singular(#"(cris|test)(is|es)$"#, #"$1is"#),
|
||||
.singular(#"^(a)x[ie]s$"#, #"$1xis"#),
|
||||
.singular(#"(octop|vir)(us|i)$"#, #"$1us"#),
|
||||
.singular(#"(alias|status)(es)?$"#, #"$1"#),
|
||||
.singular(#"^(ox)en"#, #"$1"#),
|
||||
.singular(#"(vert|ind)ices$"#, #"$1ex"#),
|
||||
.singular(#"(matr)ices$"#, #"$1ix"#),
|
||||
.singular(#"(quiz)zes$"#, #"$1"#),
|
||||
.singular(#"(database)s$"#, #"$1"#),
|
||||
|
||||
.irregular("person", "people"),
|
||||
.irregular("man", "men"),
|
||||
.irregular("child", "children"),
|
||||
.irregular("sex", "sexes"),
|
||||
.irregular("move", "moves"),
|
||||
.irregular("zombie", "zombies"),
|
||||
.irregular("radius", "radii"),
|
||||
.irregular("tooth", "teeth"),
|
||||
.irregular("goose", "geese"),
|
||||
|
||||
.uncountable("equipment"),
|
||||
.uncountable("information"),
|
||||
.uncountable("rice"),
|
||||
.uncountable("money"),
|
||||
.uncountable("species"),
|
||||
.uncountable("series"),
|
||||
.uncountable("fish"),
|
||||
.uncountable("sheep"),
|
||||
.uncountable("jeans"),
|
||||
.uncountable("police"),
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
extension String {
|
||||
private func prependCount(_ count: Int?) -> String {
|
||||
guard let count = count else { return self }
|
||||
return [String(count), self].joined(separator: " ")
|
||||
}
|
||||
|
||||
func pluralized(count: Int? = nil, language: String = GrammaticalNumberRule.defaultLanguage) -> String {
|
||||
var word = self
|
||||
|
||||
if count == 1 {
|
||||
return word.matchCase(self).prependCount(count)
|
||||
}
|
||||
|
||||
guard let rules = GrammaticalNumberRule.rules[language] else {
|
||||
return word.matchCase(self).prependCount(count)
|
||||
}
|
||||
|
||||
guard let rule = (rules.reversed().first { grammaticalNumberRule -> Bool in
|
||||
switch grammaticalNumberRule {
|
||||
case let .uncountable(rule), let .irregular(rule, _):
|
||||
return self.lowercased().contains(rule.lowercased())
|
||||
case let .plural(rule, _):
|
||||
return self.range(of: rule, options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil
|
||||
default: return false
|
||||
}
|
||||
}) else {
|
||||
return word.matchCase(self).prependCount(count)
|
||||
}
|
||||
|
||||
switch rule {
|
||||
case let .irregular(rule, replacement), let .plural(rule, replacement):
|
||||
word = word.replacingOccurrences(of: rule, with: replacement, options: [.regularExpression, .caseInsensitive])
|
||||
default: break
|
||||
}
|
||||
|
||||
return word.matchCase(self).prependCount(count)
|
||||
}
|
||||
|
||||
func singularized(language: String = GrammaticalNumberRule.defaultLanguage) -> String {
|
||||
var word = self
|
||||
|
||||
guard let rules = GrammaticalNumberRule.rules[language] else {
|
||||
return self
|
||||
}
|
||||
|
||||
guard let rule = (rules.reversed().first { grammaticalNumberRule -> Bool in
|
||||
switch grammaticalNumberRule {
|
||||
case let .uncountable(rule), let .irregular(_, rule):
|
||||
return self.lowercased().contains(rule.lowercased())
|
||||
case let .singular(rule, _):
|
||||
return self.range(of: rule, options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil
|
||||
default: return false
|
||||
}
|
||||
}) else {
|
||||
return word
|
||||
}
|
||||
|
||||
switch rule {
|
||||
case let .irregular(replacement, rule), let .singular(rule, replacement):
|
||||
word = word.replacingOccurrences(of: rule, with: replacement, options: [.regularExpression, .caseInsensitive])
|
||||
default: break
|
||||
}
|
||||
|
||||
return word.matchCase(self)
|
||||
}
|
||||
|
||||
func matchCase(_ input: String) -> String {
|
||||
if input.allSatisfy({ $0.isUppercase }) {
|
||||
return uppercased()
|
||||
} else if input.allSatisfy({ $0.isLowercase }) {
|
||||
return lowercased()
|
||||
} else if let first = input.first, first.isUppercase {
|
||||
return capitalized
|
||||
}
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -936,7 +936,7 @@ struct _Descriptors {
|
||||
let preserveSingleLineForEach = OptionDescriptor(
|
||||
argumentName: "onelineforeach",
|
||||
displayName: "Single-line forEach closures",
|
||||
help: "Convert one-line forEach: \"preserve\" (default) or \"wrap\".",
|
||||
help: "Convert one-line forEach: \"convert\" or \"ignore\" (default).",
|
||||
keyPath: \.preserveSingleLineForEach,
|
||||
trueValues: ["ignore", "preserve"],
|
||||
falseValues: ["convert"]
|
||||
|
||||
+7
-17
@@ -7468,26 +7468,16 @@ public struct _FormatRules {
|
||||
return
|
||||
}
|
||||
|
||||
// We can't introduce an identifier that already exists in the for each body,
|
||||
// We can't introduce an identifier that already exists in the loop body
|
||||
// so choose the first eligible option from a set of potential names
|
||||
let eligibleValueNames: [String]
|
||||
if let existingPluralIdentifier = forLoopSubjectIdentifier {
|
||||
eligibleValueNames = [
|
||||
existingPluralIdentifier.singularized(),
|
||||
existingPluralIdentifier.singularized() + "Item",
|
||||
existingPluralIdentifier.singularized() + "Value",
|
||||
"item",
|
||||
"value",
|
||||
]
|
||||
} else {
|
||||
eligibleValueNames = ["item", "value"]
|
||||
var eligibleValueNames = ["item", "element", "value"]
|
||||
if let identifier = forLoopSubjectIdentifier?.singularized() {
|
||||
eligibleValueNames = [identifier] + eligibleValueNames
|
||||
}
|
||||
|
||||
guard let chosenValueName = eligibleValueNames.first(where: { potentialValueName in
|
||||
// The chosen name should be different than the existing plural identifier
|
||||
forLoopSubjectIdentifier != potentialValueName
|
||||
// And the chosen name shouldn't already exist in the closure body
|
||||
&& !formatter.tokens[closureOpenBraceIndex ... closureCloseBraceIndex].contains(where: { $0.string == potentialValueName })
|
||||
// The chosen name shouldn't already exist in the closure body
|
||||
guard let chosenValueName = eligibleValueNames.first(where: { name in
|
||||
!formatter.tokens[closureOpenBraceIndex ... closureCloseBraceIndex].contains(where: { $0.string == name })
|
||||
}) else { return }
|
||||
|
||||
forEachValueNames = [chosenValueName]
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Singularize.swift
|
||||
// SwiftFormat
|
||||
//
|
||||
// Created by Nick Lockwood on 02/01/2024.
|
||||
// Copyright 2024 Nick Lockwood
|
||||
//
|
||||
// Distributed under the permissive MIT license
|
||||
// Get the latest version from here:
|
||||
//
|
||||
// https://github.com/nicklockwood/SwiftFormat
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Inspired by https://github.com/Cosmo/GrammaticalNumber
|
||||
private let rules: [(String, replacement: String)] = [
|
||||
("s$", ""),
|
||||
("ae$", "a"),
|
||||
("eaux$", "eau"),
|
||||
("era$", "us"),
|
||||
("(e)ae$", "$1us"),
|
||||
("(ss)$", "$1"),
|
||||
("(tt?)i$", "$1o"),
|
||||
("([tivdl])a$", "$1um"),
|
||||
("(i)ves$", "$1fe"),
|
||||
("([alr]|oo|ie)ves$", "$1f"),
|
||||
("([th]ive)s$", "$1"),
|
||||
("([^aeiouy]|qu)ies$", "$1y"),
|
||||
("(ov|mb)ies$", "$1ie"),
|
||||
("(x|ch|ss|sh)es$", "$1"),
|
||||
("([ml])ice$", "$1ouse"),
|
||||
("(d)ice$", "$1ie"),
|
||||
("(bus|ato)es$", "$1"),
|
||||
("(cris|test)es$", "$1is"),
|
||||
("^(pra|a)xes$", "$1xis"),
|
||||
("([sz])es$", "$1"),
|
||||
("(z)zes$", "$1"),
|
||||
("((ba|grou)se)s$", "$1"),
|
||||
("(x)en", "$1"),
|
||||
("(oa|ly|gno|op|the|ip)ses$", "$1sis"),
|
||||
("(rt|ind|ap|cod)ices$", "$1ex"),
|
||||
("(tr|end)ices$", "$1ix"),
|
||||
("(oc|radi|vir|octop|alumn|cill|cact|fung|ul|ab)i$", "$1us"),
|
||||
("(vir)ii$", "$1us"),
|
||||
("(quiz)zes$", "$1"),
|
||||
("(pe)ople$", "$1rson"),
|
||||
("(m)en$", "$1an"),
|
||||
("(child)ren$", "$1"),
|
||||
("(t)eeth$", "$1ooth"),
|
||||
("(g)eese$", "$1oose"),
|
||||
("(iteri|en)a$", "$1on"),
|
||||
// Uncountable
|
||||
("(bison|craft|deer|equipment|fish|fruit|grouse|money|news|offspring)$", "$1"),
|
||||
("(info(rmation)?|rice|salmon|series|sheep|shrimp|species|swine|trout|tuna)$", "$1"),
|
||||
// It's very rare to actually want "datum" or "medium"
|
||||
("(data|media)$", "$1"),
|
||||
]
|
||||
|
||||
extension String {
|
||||
func singularized() -> String? {
|
||||
guard let (rule, replacement) = (rules.reversed().first { rule, _ in
|
||||
range(of: rule, options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil
|
||||
}) else {
|
||||
return nil
|
||||
}
|
||||
return replacingOccurrences(of: rule, with: replacement, options: [.regularExpression, .caseInsensitive])
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,14 @@
|
||||
01426E4E23AA29B100E7D871 /* ParsingHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01426E4D23AA29B100E7D871 /* ParsingHelpersTests.swift */; };
|
||||
0142C77023C3FB6D005D5832 /* LintFileCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0142C76F23C3FB6D005D5832 /* LintFileCommand.swift */; };
|
||||
0142F06F1D72FE10007D66CC /* SwiftFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0142F06E1D72FE10007D66CC /* SwiftFormatTests.swift */; };
|
||||
015243E22B04B0A600F65221 /* GrammaticalNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* GrammaticalNumber.swift */; };
|
||||
015243E32B04B0A600F65221 /* GrammaticalNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* GrammaticalNumber.swift */; };
|
||||
015243E42B04B0A700F65221 /* GrammaticalNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* GrammaticalNumber.swift */; };
|
||||
015243E22B04B0A600F65221 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; };
|
||||
015243E32B04B0A600F65221 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; };
|
||||
015243E42B04B0A700F65221 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; };
|
||||
01567D2F225B2BFD00B22D41 /* ParsingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */; };
|
||||
01567D30225B2BFD00B22D41 /* ParsingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */; };
|
||||
015AF2C01DC6A538008F0A8C /* SwiftFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01A0EAA41D5DB4CF00A0A8E3 /* SwiftFormat.framework */; };
|
||||
015AF2CA1DC6A58C008F0A8C /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015AF2C91DC6A58C008F0A8C /* PerformanceTests.swift */; };
|
||||
015CE8B12B448CCE00924504 /* SingularizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015CE8B02B448CCE00924504 /* SingularizeTests.swift */; };
|
||||
015D3A562995A0340065B2D9 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015D3A552995A0340065B2D9 /* AboutViewController.swift */; };
|
||||
018541CF1DBA0F17000F82E3 /* XCSourceTextBuffer+SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018541CE1DBA0F17000F82E3 /* XCSourceTextBuffer+SwiftFormat.swift */; };
|
||||
018E82751D62E730008CA0F8 /* TokenizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018E82741D62E730008CA0F8 /* TokenizerTests.swift */; };
|
||||
@@ -77,7 +78,7 @@
|
||||
01F3DF8D1DB9FD3F00454944 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; };
|
||||
01F3DF8E1DB9FD3F00454944 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; };
|
||||
01F3DF901DBA003E00454944 /* InferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */; };
|
||||
2E7D30A42A7940C500C32174 /* GrammaticalNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* GrammaticalNumber.swift */; };
|
||||
2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; };
|
||||
37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; };
|
||||
37D828AC24BF77DA0012FC0A /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
9028F7831DA4B435009FE5B4 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; };
|
||||
@@ -187,6 +188,7 @@
|
||||
01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsingHelpers.swift; sourceTree = "<group>"; };
|
||||
015AF2C51DC6A538008F0A8C /* SwiftFormatPerfTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftFormatPerfTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
015AF2C91DC6A58C008F0A8C /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = "<group>"; };
|
||||
015CE8B02B448CCE00924504 /* SingularizeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingularizeTests.swift; sourceTree = "<group>"; };
|
||||
015D3A552995A0340065B2D9 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
018541CE1DBA0F17000F82E3 /* XCSourceTextBuffer+SwiftFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCSourceTextBuffer+SwiftFormat.swift"; sourceTree = "<group>"; };
|
||||
018E82741D62E730008CA0F8 /* TokenizerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenizerTests.swift; sourceTree = "<group>"; };
|
||||
@@ -222,7 +224,7 @@
|
||||
01F17E841E258A4900DCD359 /* CommandLineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLineTests.swift; sourceTree = "<group>"; };
|
||||
01F3DF8B1DB9FD3F00454944 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = "<group>"; };
|
||||
01F3DF8F1DBA003E00454944 /* InferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InferenceTests.swift; sourceTree = "<group>"; };
|
||||
2E7D30A32A7940C500C32174 /* GrammaticalNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GrammaticalNumber.swift; sourceTree = "<group>"; };
|
||||
2E7D30A32A7940C500C32174 /* Singularize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Singularize.swift; sourceTree = "<group>"; };
|
||||
37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
90C4B6CA1DA4B04A009EB000 /* SwiftFormat for Xcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftFormat for Xcode.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
90C4B6CC1DA4B04A009EB000 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -359,11 +361,11 @@
|
||||
01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */,
|
||||
01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */,
|
||||
01ACAE04220CD90F003F3CCF /* Examples.swift */,
|
||||
2E7D30A32A7940C500C32174 /* Singularize.swift */,
|
||||
01A0EAA71D5DB4CF00A0A8E3 /* SwiftFormat.h */,
|
||||
01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */,
|
||||
01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */,
|
||||
01BBD85821DAA2A000457380 /* Globs.swift */,
|
||||
2E7D30A32A7940C500C32174 /* GrammaticalNumber.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -392,6 +394,7 @@
|
||||
01C209AE2502CD3C00E728A2 /* RulesTests+Spacing.swift */,
|
||||
01EF830E25616089003F6F2D /* RulesTests+Syntax.swift */,
|
||||
01C209B82502D3C500E728A2 /* RulesTests+Wrapping.swift */,
|
||||
015CE8B02B448CCE00924504 /* SingularizeTests.swift */,
|
||||
0142F06E1D72FE10007D66CC /* SwiftFormatTests.swift */,
|
||||
01BBD85D21DAA30700457380 /* GlobsTests.swift */,
|
||||
018E82741D62E730008CA0F8 /* TokenizerTests.swift */,
|
||||
@@ -786,7 +789,7 @@
|
||||
A3DF48252620E03600F45A5F /* JSONReporter.swift in Sources */,
|
||||
01A0EAC11D5DB4F700A0A8E3 /* Rules.swift in Sources */,
|
||||
01A0EAC51D5DB54A00A0A8E3 /* SwiftFormat.swift in Sources */,
|
||||
2E7D30A42A7940C500C32174 /* GrammaticalNumber.swift in Sources */,
|
||||
2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */,
|
||||
01B3987D1D763493009ADE61 /* Formatter.swift in Sources */,
|
||||
01F17E821E25870700DCD359 /* CommandLine.swift in Sources */,
|
||||
01F3DF8C1DB9FD3F00454944 /* Options.swift in Sources */,
|
||||
@@ -822,6 +825,7 @@
|
||||
01F3DF901DBA003E00454944 /* InferenceTests.swift in Sources */,
|
||||
01C209B32502CF8300E728A2 /* RulesTests+Indentation.swift in Sources */,
|
||||
01C209B12502CEF700E728A2 /* RulesTests+Linebreaks.swift in Sources */,
|
||||
015CE8B12B448CCE00924504 /* SingularizeTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -843,7 +847,7 @@
|
||||
A3DF48262620E03600F45A5F /* JSONReporter.swift in Sources */,
|
||||
01A8320724EC7F7600A9D0EB /* FormattingHelpers.swift in Sources */,
|
||||
01F17E831E25870700DCD359 /* CommandLine.swift in Sources */,
|
||||
015243E22B04B0A600F65221 /* GrammaticalNumber.swift in Sources */,
|
||||
015243E22B04B0A600F65221 /* Singularize.swift in Sources */,
|
||||
01ACAE06220CD914003F3CCF /* Examples.swift in Sources */,
|
||||
01A0EACD1D5DB5F500A0A8E3 /* main.swift in Sources */,
|
||||
DD9AD3A42999FCC8001C2C0E /* Reporter.swift in Sources */,
|
||||
@@ -863,7 +867,7 @@
|
||||
E487211D201D885A0014845E /* RulesViewController.swift in Sources */,
|
||||
01045A9B2119979400D2BE3D /* Arguments.swift in Sources */,
|
||||
E4872114201D3B8C0014845E /* Tokenizer.swift in Sources */,
|
||||
015243E32B04B0A600F65221 /* GrammaticalNumber.swift in Sources */,
|
||||
015243E32B04B0A600F65221 /* Singularize.swift in Sources */,
|
||||
E4872112201D3B860014845E /* Rules.swift in Sources */,
|
||||
E4962DE0203F3CD500A02013 /* OptionsStore.swift in Sources */,
|
||||
01ACAE07220CD915003F3CCF /* Examples.swift in Sources */,
|
||||
@@ -893,7 +897,7 @@
|
||||
0142C77023C3FB6D005D5832 /* LintFileCommand.swift in Sources */,
|
||||
E4E4D3CC2033F17C000D7CB1 /* EnumAssociable.swift in Sources */,
|
||||
E4FABAD8202FEF060065716E /* OptionDescriptor.swift in Sources */,
|
||||
015243E42B04B0A700F65221 /* GrammaticalNumber.swift in Sources */,
|
||||
015243E42B04B0A700F65221 /* Singularize.swift in Sources */,
|
||||
9028F7841DA4B435009FE5B4 /* Tokenizer.swift in Sources */,
|
||||
90F16AFB1DA5ED9A00EB4EA1 /* CommandErrors.swift in Sources */,
|
||||
01A8320924EC7F7800A9D0EB /* FormattingHelpers.swift in Sources */,
|
||||
|
||||
@@ -85,14 +85,14 @@ class GlobsTests: XCTestCase {
|
||||
func testExpandPathWithWildcardAtStart() {
|
||||
let path = "*Tests.swift"
|
||||
let directory = URL(fileURLWithPath: #file).deletingLastPathComponent()
|
||||
XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 14)
|
||||
XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 15)
|
||||
}
|
||||
|
||||
func testExpandPathWithSubdirectoryAndWildcard() {
|
||||
let path = "Tests/*Tests.swift"
|
||||
let directory = URL(fileURLWithPath: #file)
|
||||
.deletingLastPathComponent().deletingLastPathComponent()
|
||||
XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 14)
|
||||
XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 15)
|
||||
}
|
||||
|
||||
func testSingleWildcardDoesNotMatchDirectorySlash() {
|
||||
|
||||
@@ -3977,7 +3977,9 @@ class SyntaxTests: RulesTests {
|
||||
let nestedArrays = [[1, 2], [3, 4]]
|
||||
nestedArrays.forEach {
|
||||
$0.forEach {
|
||||
print($0)
|
||||
$0.forEach {
|
||||
print($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -3985,8 +3987,10 @@ class SyntaxTests: RulesTests {
|
||||
let output = """
|
||||
let nestedArrays = [[1, 2], [3, 4]]
|
||||
for nestedArray in nestedArrays {
|
||||
for nestedArrayItem in nestedArray {
|
||||
print(nestedArrayItem)
|
||||
for item in nestedArray {
|
||||
for item in item {
|
||||
print(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -4005,9 +4009,9 @@ class SyntaxTests: RulesTests {
|
||||
|
||||
let output = """
|
||||
let placeholderStrings = ["foo", "bar", "baaz"]
|
||||
for placeholderStringItem in placeholderStrings {
|
||||
let placeholderString = placeholderStringItem.uppercased()
|
||||
print(placeholderString, placeholderStringItem)
|
||||
for item in placeholderStrings {
|
||||
let placeholderString = item.uppercased()
|
||||
print(placeholderString, item)
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -4101,8 +4105,8 @@ class SyntaxTests: RulesTests {
|
||||
|
||||
let output = """
|
||||
let bar = foo.bar
|
||||
for dictionaryItem in foo.item().bar[2].dictionary["myValue"] {
|
||||
print(dictionaryItem)
|
||||
for item in foo.item().bar[2].dictionary["myValue"] {
|
||||
print(item)
|
||||
}
|
||||
"""
|
||||
testFormatting(for: input, output, rule: FormatRules.preferForLoop)
|
||||
@@ -4135,8 +4139,8 @@ class SyntaxTests: RulesTests {
|
||||
|
||||
let output = """
|
||||
let quux = foo.bar.baaz.quux
|
||||
for fooItem in foo(bar)(baaz)["item"] {
|
||||
print(fooItem)
|
||||
for item in foo(bar)(baaz)["item"] {
|
||||
print(item)
|
||||
}
|
||||
"""
|
||||
testFormatting(for: input, output, rule: FormatRules.preferForLoop)
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// SingularizeTests.swift
|
||||
// SwiftFormatTests
|
||||
//
|
||||
// Created by Nick Lockwood on 02/01/2024.
|
||||
// Copyright © 2024 Nick Lockwood. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftFormat
|
||||
|
||||
class SingularizeTests: XCTestCase {
|
||||
func testSingularization() {
|
||||
XCTAssertEqual("addenda".singularized(), "addendum")
|
||||
XCTAssertEqual("aircraft".singularized(), "aircraft")
|
||||
XCTAssertEqual("aliases".singularized(), "alias")
|
||||
XCTAssertEqual("alumnae".singularized(), "alumna")
|
||||
XCTAssertEqual("alumni".singularized(), "alumnus")
|
||||
XCTAssertEqual("analyses".singularized(), "analysis")
|
||||
XCTAssertEqual("antennae".singularized(), "antenna")
|
||||
XCTAssertEqual("antennas".singularized(), "antenna")
|
||||
XCTAssertEqual("apexes".singularized(), "apex")
|
||||
XCTAssertEqual("apices".singularized(), "apex")
|
||||
XCTAssertEqual("appendices".singularized(), "appendix")
|
||||
XCTAssertEqual("axes".singularized(), "axis")
|
||||
XCTAssertEqual("bacilli".singularized(), "bacillus")
|
||||
XCTAssertEqual("backhoes".singularized(), "backhoe")
|
||||
XCTAssertEqual("bacteria".singularized(), "bacterium")
|
||||
XCTAssertEqual("bases".singularized(), "base")
|
||||
XCTAssertEqual("bison".singularized(), "bison")
|
||||
XCTAssertEqual("buses".singularized(), "bus")
|
||||
XCTAssertEqual("bureaux".singularized(), "bureau")
|
||||
XCTAssertEqual("cacti".singularized(), "cactus")
|
||||
XCTAssertEqual("centers".singularized(), "center")
|
||||
XCTAssertEqual("children".singularized(), "child")
|
||||
XCTAssertEqual("cilia".singularized(), "cilium")
|
||||
XCTAssertEqual("codices".singularized(), "codex")
|
||||
XCTAssertEqual("curricula".singularized(), "curriculum")
|
||||
XCTAssertEqual("criteria".singularized(), "criterion")
|
||||
XCTAssertEqual("criteria".singularized(), "criterion")
|
||||
XCTAssertEqual("deer".singularized(), "deer")
|
||||
XCTAssertEqual("diagnoses".singularized(), "diagnosis")
|
||||
XCTAssertEqual("dice".singularized(), "die")
|
||||
XCTAssertEqual("dogs".singularized(), "dog")
|
||||
XCTAssertEqual("doormice".singularized(), "doormouse")
|
||||
XCTAssertEqual("dwarves".singularized(), "dwarf")
|
||||
XCTAssertEqual("ellipses".singularized(), "ellipsis")
|
||||
XCTAssertEqual("errata".singularized(), "erratum")
|
||||
XCTAssertEqual("fairies".singularized(), "fairy")
|
||||
XCTAssertEqual("faxes".singularized(), "fax")
|
||||
XCTAssertEqual("fezes".singularized(), "fez")
|
||||
XCTAssertEqual("fezzes".singularized(), "fez")
|
||||
XCTAssertEqual("fish".singularized(), "fish")
|
||||
XCTAssertEqual("fishes".singularized(), "fish")
|
||||
XCTAssertEqual("foci".singularized(), "focus")
|
||||
XCTAssertEqual("foes".singularized(), "foe")
|
||||
XCTAssertEqual("formulae".singularized(), "formula")
|
||||
XCTAssertEqual("foxes".singularized(), "fox")
|
||||
XCTAssertEqual("fruit".singularized(), "fruit")
|
||||
XCTAssertEqual("fruits".singularized(), "fruit")
|
||||
XCTAssertEqual("fungi".singularized(), "fungus")
|
||||
XCTAssertEqual("funguses".singularized(), "fungus")
|
||||
XCTAssertEqual("geese".singularized(), "goose")
|
||||
XCTAssertEqual("genera".singularized(), "genus")
|
||||
XCTAssertEqual("graffiti".singularized(), "graffito")
|
||||
XCTAssertEqual("grandchildren".singularized(), "grandchild")
|
||||
XCTAssertEqual("grouse".singularized(), "grouse")
|
||||
XCTAssertEqual("grouses".singularized(), "grouse")
|
||||
XCTAssertEqual("halves".singularized(), "half")
|
||||
XCTAssertEqual("hooves".singularized(), "hoof")
|
||||
XCTAssertEqual("indices".singularized(), "index")
|
||||
XCTAssertEqual("items".singularized(), "item")
|
||||
XCTAssertEqual("knives".singularized(), "knife")
|
||||
XCTAssertEqual("larvae".singularized(), "larva")
|
||||
XCTAssertEqual("larvas".singularized(), "larva")
|
||||
XCTAssertEqual("leaves".singularized(), "leaf")
|
||||
XCTAssertEqual("libretti".singularized(), "libretto")
|
||||
XCTAssertEqual("librettos".singularized(), "libretto")
|
||||
XCTAssertEqual("lives".singularized(), "life")
|
||||
XCTAssertEqual("loaves".singularized(), "loaf")
|
||||
XCTAssertEqual("loci".singularized(), "locus")
|
||||
XCTAssertEqual("matrices".singularized(), "matrix")
|
||||
XCTAssertEqual("matrixes".singularized(), "matrix")
|
||||
XCTAssertEqual("memoranda".singularized(), "memorandum")
|
||||
XCTAssertEqual("mice".singularized(), "mouse")
|
||||
XCTAssertEqual("millennia".singularized(), "millennium")
|
||||
XCTAssertEqual("minutiae".singularized(), "minutia")
|
||||
XCTAssertEqual("moves".singularized(), "move")
|
||||
XCTAssertEqual("movies".singularized(), "movie")
|
||||
XCTAssertEqual("noobies".singularized(), "nooby")
|
||||
XCTAssertEqual("nebulae".singularized(), "nebula")
|
||||
XCTAssertEqual("nucleae".singularized(), "nucleus")
|
||||
XCTAssertEqual("oases".singularized(), "oasis")
|
||||
XCTAssertEqual("octopuses".singularized(), "octopus")
|
||||
XCTAssertEqual("octopi".singularized(), "octopus")
|
||||
XCTAssertEqual("offspring".singularized(), "offspring")
|
||||
XCTAssertEqual("opera".singularized(), "opus")
|
||||
XCTAssertEqual("opuses".singularized(), "opus")
|
||||
XCTAssertEqual("ova".singularized(), "ovum")
|
||||
XCTAssertEqual("oxen".singularized(), "ox")
|
||||
XCTAssertEqual("parentheses".singularized(), "parenthesis")
|
||||
XCTAssertEqual("people".singularized(), "person")
|
||||
XCTAssertEqual("phenomena".singularized(), "phenomenon")
|
||||
XCTAssertEqual("phenomenons".singularized(), "phenomenon")
|
||||
XCTAssertEqual("phyla".singularized(), "phylum")
|
||||
XCTAssertEqual("potatoes".singularized(), "potato")
|
||||
XCTAssertEqual("praxes".singularized(), "praxis")
|
||||
XCTAssertEqual("quizzes".singularized(), "quiz")
|
||||
XCTAssertEqual("radii".singularized(), "radius")
|
||||
XCTAssertEqual("referenda".singularized(), "referendum")
|
||||
XCTAssertEqual("referendums".singularized(), "referendum")
|
||||
XCTAssertEqual("salmon".singularized(), "salmon")
|
||||
XCTAssertEqual("scarfs".singularized(), "scarf")
|
||||
XCTAssertEqual("scarves".singularized(), "scarf")
|
||||
XCTAssertEqual("selves".singularized(), "self")
|
||||
XCTAssertEqual("series".singularized(), "series")
|
||||
XCTAssertEqual("sheep".singularized(), "sheep")
|
||||
XCTAssertEqual("shoes".singularized(), "shoe")
|
||||
XCTAssertEqual("shrimp".singularized(), "shrimp")
|
||||
XCTAssertEqual("shrimps".singularized(), "shrimp")
|
||||
XCTAssertEqual("sofas".singularized(), "sofa")
|
||||
XCTAssertEqual("species".singularized(), "species")
|
||||
XCTAssertEqual("statuses".singularized(), "status")
|
||||
XCTAssertEqual("stimuli".singularized(), "stimulus")
|
||||
XCTAssertEqual("strata".singularized(), "stratum")
|
||||
XCTAssertEqual("syllabi".singularized(), "syllabus")
|
||||
XCTAssertEqual("syllabuses".singularized(), "syllabus")
|
||||
XCTAssertEqual("symposia".singularized(), "symposium")
|
||||
XCTAssertEqual("symposiums".singularized(), "symposium")
|
||||
XCTAssertEqual("synopses".singularized(), "synopsis")
|
||||
XCTAssertEqual("swine".singularized(), "swine")
|
||||
XCTAssertEqual("tableaux".singularized(), "tableau")
|
||||
XCTAssertEqual("taxes".singularized(), "tax")
|
||||
XCTAssertEqual("teeth".singularized(), "tooth")
|
||||
XCTAssertEqual("theses".singularized(), "thesis")
|
||||
XCTAssertEqual("thieves".singularized(), "thief")
|
||||
XCTAssertEqual("toes".singularized(), "toe")
|
||||
XCTAssertEqual("tomatoes".singularized(), "tomato")
|
||||
XCTAssertEqual("trout".singularized(), "trout")
|
||||
XCTAssertEqual("trouts".singularized(), "trout")
|
||||
XCTAssertEqual("tuna".singularized(), "tuna")
|
||||
XCTAssertEqual("tunas".singularized(), "tuna")
|
||||
XCTAssertEqual("vertebrae".singularized(), "vertebra")
|
||||
XCTAssertEqual("vertebras".singularized(), "vertebra")
|
||||
XCTAssertEqual("vertices".singularized(), "vertex")
|
||||
XCTAssertEqual("villae".singularized(), "villa")
|
||||
XCTAssertEqual("viruses".singularized(), "virus")
|
||||
XCTAssertEqual("virii".singularized(), "virus")
|
||||
XCTAssertEqual("viri".singularized(), "virus")
|
||||
XCTAssertEqual("vitae".singularized(), "vita")
|
||||
XCTAssertEqual("vortexes".singularized(), "vortex")
|
||||
XCTAssertEqual("vortices".singularized(), "vortex")
|
||||
XCTAssertEqual("wharfs".singularized(), "wharf")
|
||||
XCTAssertEqual("wharves".singularized(), "wharf")
|
||||
XCTAssertEqual("wives".singularized(), "wife")
|
||||
XCTAssertEqual("wolves".singularized(), "wolf")
|
||||
XCTAssertEqual("women".singularized(), "woman")
|
||||
XCTAssertEqual("zombies".singularized(), "zombie")
|
||||
}
|
||||
|
||||
func testPreserveCase() {
|
||||
XCTAssertEqual("Axes".singularized(), "Axis")
|
||||
XCTAssertEqual("COMPUTERS".singularized(), "COMPUTER")
|
||||
XCTAssertEqual("Teeth".singularized(), "Tooth")
|
||||
}
|
||||
|
||||
func testStripAllPrefix() {
|
||||
XCTAssertEqual("allWindows".singularized(), "window")
|
||||
XCTAssertEqual("AllTargets".singularized(), "Target")
|
||||
}
|
||||
|
||||
func testDontUseDatumOrMedium() {
|
||||
XCTAssertEqual("data".singularized(), "data")
|
||||
XCTAssertEqual("media".singularized(), "media")
|
||||
XCTAssertEqual("allMedia".singularized(), "media")
|
||||
}
|
||||
|
||||
func testDontSingularizeNonPlurals() {
|
||||
XCTAssertNil("uppercase".singularized())
|
||||
XCTAssertNil("uppercased".singularized())
|
||||
XCTAssertNil("rotate".singularized())
|
||||
XCTAssertNil("map".singularized())
|
||||
XCTAssertNil("filter".singularized())
|
||||
XCTAssertNil("capitalize".singularized())
|
||||
XCTAssertNil("tessellate".singularized())
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ class SwiftFormatTests: XCTestCase {
|
||||
return { files.append(inputURL) }
|
||||
}
|
||||
XCTAssertEqual(errors.count, 0)
|
||||
XCTAssertEqual(files.count, 68)
|
||||
XCTAssertEqual(files.count, 69)
|
||||
}
|
||||
|
||||
func testInputFilesMatchOutputFilesForSameOutput() {
|
||||
@@ -78,7 +78,7 @@ class SwiftFormatTests: XCTestCase {
|
||||
return { files.append(inputURL) }
|
||||
}
|
||||
XCTAssertEqual(errors.count, 0)
|
||||
XCTAssertEqual(files.count, 68)
|
||||
XCTAssertEqual(files.count, 69)
|
||||
}
|
||||
|
||||
func testInputFileNotEnumeratedWhenExcluded() {
|
||||
|
||||
Reference in New Issue
Block a user