Improve preferForLoop identifier selection logic

This commit is contained in:
Nick Lockwood
2024-01-08 17:27:20 +00:00
parent 5222db8c50
commit a6e8e95331
10 changed files with 315 additions and 225 deletions
+1 -1
View File
@@ -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>
-182
View File
@@ -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
}
}
+1 -1
View File
@@ -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
View File
@@ -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]
+87
View File
@@ -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])
}
}
+14 -10
View File
@@ -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 */,
+2 -2
View File
@@ -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() {
+14 -10
View File
@@ -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)
+187
View File
@@ -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())
}
}
+2 -2
View File
@@ -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() {