diff --git a/R.swift.xcodeproj/project.pbxproj b/R.swift.xcodeproj/project.pbxproj index 6908438e..65ce1b73 100644 --- a/R.swift.xcodeproj/project.pbxproj +++ b/R.swift.xcodeproj/project.pbxproj @@ -62,9 +62,9 @@ D5B799771C199755009EA901 /* StoryboardGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B799671C1993B7009EA901 /* StoryboardGenerator.swift */; }; D5B799791C19C082009EA901 /* WhiteListedExtensionsResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B799781C19C082009EA901 /* WhiteListedExtensionsResourceType.swift */; }; D5B7997A1C19C1BD009EA901 /* WhiteListedExtensionsResourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B799781C19C082009EA901 /* WhiteListedExtensionsResourceType.swift */; }; - D5B7997D1C1B07C3009EA901 /* SanitizedSwiftName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B7997C1C1B07C3009EA901 /* SanitizedSwiftName.swift */; }; + D5B7997D1C1B07C3009EA901 /* SwiftIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B7997C1C1B07C3009EA901 /* SwiftIdentifier.swift */; }; D5B7997F1C1B07EB009EA901 /* ErrorOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B7997E1C1B07EB009EA901 /* ErrorOutput.swift */; }; - D5B799801C1B0943009EA901 /* SanitizedSwiftName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B7997C1C1B07C3009EA901 /* SanitizedSwiftName.swift */; }; + D5B799801C1B0943009EA901 /* SwiftIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B7997C1C1B07C3009EA901 /* SwiftIdentifier.swift */; }; D5B799811C1B0943009EA901 /* ErrorOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B7997E1C1B07EB009EA901 /* ErrorOutput.swift */; }; D5B799831C1B8C78009EA901 /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B799821C1B8C78009EA901 /* Module.swift */; }; D5B7998A1C1B91A9009EA901 /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B799821C1B8C78009EA901 /* Module.swift */; }; @@ -139,7 +139,7 @@ D5B7996D1C19940F009EA901 /* ResourceFileGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceFileGenerator.swift; sourceTree = ""; }; D5B7996F1C199420009EA901 /* FontGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontGenerator.swift; sourceTree = ""; }; D5B799781C19C082009EA901 /* WhiteListedExtensionsResourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhiteListedExtensionsResourceType.swift; sourceTree = ""; }; - D5B7997C1C1B07C3009EA901 /* SanitizedSwiftName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SanitizedSwiftName.swift; sourceTree = ""; }; + D5B7997C1C1B07C3009EA901 /* SwiftIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftIdentifier.swift; sourceTree = ""; }; D5B7997E1C1B07EB009EA901 /* ErrorOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorOutput.swift; sourceTree = ""; }; D5B799821C1B8C78009EA901 /* Module.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = ""; }; D5C4227D1B711FDF004EA9B9 /* rswiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = rswiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -227,7 +227,7 @@ isa = PBXGroup; children = ( D5B7997E1C1B07EB009EA901 /* ErrorOutput.swift */, - D5B7997C1C1B07C3009EA901 /* SanitizedSwiftName.swift */, + D5B7997C1C1B07C3009EA901 /* SwiftIdentifier.swift */, D56F923E1C2942B400177FF7 /* Struct+ChildValidation.swift */, D58672481C21FC9700A760EC /* TypeSequenceProvider.swift */, D5EA0DFE1A3DF4E300FFEBC4 /* UtilExtensions.swift */, @@ -482,7 +482,7 @@ D5B799731C199755009EA901 /* NibGenerator.swift in Sources */, D5B129B31C3BA75A00A1C5FC /* Let.swift in Sources */, D5F97E491C1819160066D7C0 /* Font.swift in Sources */, - D5B799801C1B0943009EA901 /* SanitizedSwiftName.swift in Sources */, + D5B799801C1B0943009EA901 /* SwiftIdentifier.swift in Sources */, D5B799711C199755009EA901 /* FontGenerator.swift in Sources */, D586D1C61BE20D8600F18FEC /* Extensions.swift in Sources */, D586D1C81BE20D8600F18FEC /* Serialization.swift in Sources */, @@ -539,7 +539,7 @@ D5646DE51BE2016E0034F4D7 /* PBXObject.swift in Sources */, D56DC76D1C41758800623437 /* AccessModifier.swift in Sources */, D5B129AF1C3BA5F900A1C5FC /* Let.swift in Sources */, - D5B7997D1C1B07C3009EA901 /* SanitizedSwiftName.swift in Sources */, + D5B7997D1C1B07C3009EA901 /* SwiftIdentifier.swift in Sources */, D5A0A82A1C47920A0089ED2C /* SwiftCodeConverible.swift in Sources */, D5B799681C1993B7009EA901 /* StoryboardGenerator.swift in Sources */, D5B799701C199420009EA901 /* FontGenerator.swift in Sources */, diff --git a/R.swift/Generators/ColorGenerator.swift b/R.swift/Generators/ColorGenerator.swift index 7efa28e1..ac030857 100644 --- a/R.swift/Generators/ColorGenerator.swift +++ b/R.swift/Generators/ColorGenerator.swift @@ -14,6 +14,9 @@ struct ColorGenerator: Generator { let internalStruct: Struct? = nil init(colorPalettes palettes: [ColorPalette]) { + let groupedPalettes = palettes.groupBySwiftIdentifiers { $0.filename } + groupedPalettes.printWarningsForDuplicatesAndEmpties(source: "color palette", result: "file") + externalStruct = Struct( comments: ["This `R.color` struct is generated, and contains static references to \(palettes.count) color palettes."], type: Type(module: .Host, name: "color"), @@ -21,27 +24,17 @@ struct ColorGenerator: Generator { typealiasses: [], properties: [], functions: [], - structs: palettes.flatMap(ColorGenerator.colorStructFromPalette) + structs: groupedPalettes.uniques.flatMap(ColorGenerator.colorStructFromPalette) ) } private static func colorStructFromPalette(palette: ColorPalette) -> Struct? { if palette.colors.isEmpty { return nil } - let name = sanitizedSwiftName(palette.filename) - let groupedColors = palette.colors.groupBySwiftNames { $0.0 } - - for (sanitizedName, duplicates) in groupedColors.duplicates { - warn("Skipping \(duplicates.count) colors in palette '\(palette.filename)' because symbol '\(sanitizedName)' would be generated for all of these colors: \(duplicates.joinWithSeparator(", "))") - } + let name = SwiftIdentifier(name: palette.filename) + let groupedColors = palette.colors.groupBySwiftIdentifiers { $0.0 } - let empties = groupedColors.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 color in palette '\(palette.filename)' because no swift identifier can be generated for image: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) images in palette '\(palette.filename)' because no swift identifier can be generated for all of these images: \(empties.joinWithSeparator(", "))") - } + groupedColors.printWarningsForDuplicatesAndEmpties(source: "color", container: "in palette '\(palette.filename)'", result: "color") return Struct( comments: ["This `R.color.\(name)` struct is generated, and contains static references to \(groupedColors.uniques.count) colors."], @@ -60,7 +53,7 @@ struct ColorGenerator: Generator { "#\(color.hexString) \(name)" ], isStatic: true, - name: name, + name: SwiftIdentifier(name: name), typeDefinition: .Inferred(Type.ColorResource), value: "ColorResource(name: \"\(name)\", red: \(color.redComponent), green: \(color.greenComponent), blue: \(color.blueComponent), alpha: \(color.alphaComponent))" ) @@ -74,7 +67,7 @@ struct ColorGenerator: Generator { "UIColor(red: \(color.redComponent), green: \(color.greenComponent), blue: \(color.blueComponent), alpha: \(color.alphaComponent))" ], isStatic: true, - name: name, + name: SwiftIdentifier(name: name), generics: nil, parameters: [ Function.Parameter(name: "_", type: Type._Void) diff --git a/R.swift/Generators/FontGenerator.swift b/R.swift/Generators/FontGenerator.swift index f40e0857..a28f1b9b 100644 --- a/R.swift/Generators/FontGenerator.swift +++ b/R.swift/Generators/FontGenerator.swift @@ -13,12 +13,14 @@ struct FontGenerator: Generator { let internalStruct: Struct? = nil init(fonts: [Font]) { + let groupedFonts = fonts.groupBySwiftIdentifiers { $0.name } + groupedFonts.printWarningsForDuplicatesAndEmpties(source: "font resource", result: "file") - let fontProperties: [Property] = fonts.map { + let fontProperties: [Property] = groupedFonts.uniques.map { Let( comments: ["Font `\($0.name)`."], isStatic: true, - name: $0.name, + name: SwiftIdentifier(name: $0.name), typeDefinition: .Inferred(Type.FontResource), value: "FontResource(fontName: \"\($0.name)\")" ) @@ -30,7 +32,7 @@ struct FontGenerator: Generator { implements: [], typealiasses: [], properties: fontProperties, - functions: fonts.map(FontGenerator.fontFunctionFromFont), + functions: groupedFonts.uniques.map(FontGenerator.fontFunctionFromFont), structs: [] ) } @@ -39,14 +41,14 @@ struct FontGenerator: Generator { return Function( comments: ["`UIFont(name: \"\(font.name)\", size: ...)`"], isStatic: true, - name: font.name, + name: SwiftIdentifier(name: font.name), generics: nil, parameters: [ Function.Parameter(name: "size", localName: "size", type: Type._CGFloat) ], doesThrow: false, returnType: Type._UIFont.asOptional(), - body: "return UIFont(resource: \(sanitizedSwiftName(font.name)), size: size)" + body: "return UIFont(resource: \(SwiftIdentifier(name: font.name)), size: size)" ) } } diff --git a/R.swift/Generators/ImageGenerator.swift b/R.swift/Generators/ImageGenerator.swift index c53fe007..a0f1c9d9 100644 --- a/R.swift/Generators/ImageGenerator.swift +++ b/R.swift/Generators/ImageGenerator.swift @@ -13,78 +13,28 @@ struct ImageGenerator: Generator { let internalStruct: Struct? = nil init(assetFolders: [AssetFolder], images: [Image]) { - let assetFolderImageFunctions = assetFolders + let assetFolderImageNames = assetFolders .flatMap { $0.imageAssets } - .map { - Function( - comments: ["`UIImage(named: \"\($0)\", bundle: ..., traitCollection: ...)`"], - isStatic: true, - name: $0, - generics: nil, - parameters: [ - Function.Parameter( - name: "compatibleWithTraitCollection", - localName: "traitCollection", - type: Type._UITraitCollection.asOptional(), - defaultValue: "nil" - ) - ], - doesThrow: false, - returnType: Type._UIImage.asOptional(), - body: "return UIImage(resource: R.image.\(sanitizedSwiftName($0)), compatibleWithTraitCollection: traitCollection)" - ) - } - let uniqueImages = images + let imagesNames = images .groupBy { $0.name } .values - .flatMap { $0.first } - - let imageFunctions = uniqueImages - .map { - Function( - comments: ["`UIImage(named: \"\($0.name)\", bundle: ..., traitCollection: ...)`"], - isStatic: true, - name: $0.name, - generics: nil, - parameters: [ - Function.Parameter( - name: "compatibleWithTraitCollection", - localName: "traitCollection", - type: Type._UITraitCollection.asOptional(), - defaultValue: "nil" - ) - ], - doesThrow: false, - returnType: Type._UIImage.asOptional(), - body: "return \(Type._UIImage.name)(resource: R.image.\(sanitizedSwiftName($0.name)), compatibleWithTraitCollection: traitCollection)" - ) - } - - let allFunctions = assetFolderImageFunctions + imageFunctions - let groupedFunctions = allFunctions.groupBySwiftNames { $0.name } + .flatMap { $0.first?.name } - for (sanitizedName, duplicates) in groupedFunctions.duplicates { - warn("Skipping \(duplicates.count) images because symbol '\(sanitizedName)' would be generated for all of these images: \(duplicates.joinWithSeparator(", "))") - } + let allFunctions = assetFolderImageNames + imagesNames + let groupedFunctions = allFunctions.groupBySwiftIdentifiers { $0 } - let empties = groupedFunctions.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 image because no swift identifier can be generated for image: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) images because no swift identifier can be generated for all of these images: \(empties.joinWithSeparator(", "))") - } + groupedFunctions.printWarningsForDuplicatesAndEmpties(source: "image", result: "image") let imageLets = groupedFunctions .uniques - .map { + .map { name in Let( - comments: ["Image `\($0.name)`."], + comments: ["Image `\(name)`."], isStatic: true, - name: $0.name, + name: SwiftIdentifier(name: name), typeDefinition: .Inferred(Type.ImageResource), - value: "\(Type.ImageResource.name)(bundle: _R.hostingBundle, name: \"\($0.name)\")" + value: "\(Type.ImageResource.name)(bundle: _R.hostingBundle, name: \"\(name)\")" ) } @@ -94,8 +44,28 @@ struct ImageGenerator: Generator { implements: [], typealiasses: [], properties: imageLets.map(anyProperty), - functions: groupedFunctions.uniques, + functions: groupedFunctions.uniques.map(ImageGenerator.functionForImageName), structs: [] ) } + + static func functionForImageName(name: String) -> Function { + return Function( + comments: ["`UIImage(named: \"\(name)\", bundle: ..., traitCollection: ...)`"], + isStatic: true, + name: SwiftIdentifier(name: name), + generics: nil, + parameters: [ + Function.Parameter( + name: "compatibleWithTraitCollection", + localName: "traitCollection", + type: Type._UITraitCollection.asOptional(), + defaultValue: "nil" + ) + ], + doesThrow: false, + returnType: Type._UIImage.asOptional(), + body: "return \(Type._UIImage.name)(resource: R.image.\(SwiftIdentifier(name: name)), compatibleWithTraitCollection: traitCollection)" + ) + } } diff --git a/R.swift/Generators/NibGenerator.swift b/R.swift/Generators/NibGenerator.swift index 085c4340..aeca8456 100644 --- a/R.swift/Generators/NibGenerator.swift +++ b/R.swift/Generators/NibGenerator.swift @@ -36,19 +36,8 @@ struct NibGenerator: Generator { let internalStruct: Struct? init(nibs: [Nib]) { - let groupedNibs = nibs.groupBySwiftNames { $0.name } - - for (name, duplicates) in groupedNibs.duplicates { - warn("Skipping \(duplicates.count) xibs because symbol '\(name)' would be generated for all of these xibs: \(duplicates.joinWithSeparator(", "))") - } - - let empties = groupedNibs.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 xib because no swift identifier can be generated for xib: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) xibs because no swift identifier can be generated for all of these xibs: \(empties.joinWithSeparator(", "))") - } + let groupedNibs = nibs.groupBySwiftIdentifiers { $0.name } + groupedNibs.printWarningsForDuplicatesAndEmpties(source: "xib", result: "file") internalStruct = Struct( type: Type(module: .Host, name: "nib"), @@ -83,24 +72,24 @@ struct NibGenerator: Generator { return Function( comments: ["`UINib(name: \"\(nib.name)\", bundle: ...)`"], isStatic: true, - name: nib.name, + name: SwiftIdentifier(name: nib.name), generics: nil, parameters: [ Function.Parameter(name: "_", type: Type._Void) ], doesThrow: false, returnType: Type._UINib, - body: "return UINib(resource: R.nib.\(sanitizedSwiftName(nib.name)))" + body: "return UINib(resource: R.nib.\(SwiftIdentifier(name: nib.name)))" ) } private static func nibVarForNib(nib: Nib) -> Let { - let nibStructName = sanitizedSwiftName("_\(nib.name)") - let structType = Type(module: .Host, name: "_R.nib.\(nibStructName)") + let nibStructName = SwiftIdentifier(name: "_\(nib.name)") + let structType = Type(module: .Host, name: SwiftIdentifier(rawValue: "_R.nib.\(nibStructName)")) return Let( comments: ["Nib `\(nib.name)`."], isStatic: true, - name: nib.name, + name: SwiftIdentifier(name: nib.name), typeDefinition: .Inferred(structType), value: "\(structType)()" ) @@ -110,7 +99,7 @@ struct NibGenerator: Generator { let instantiateParameters = [ Function.Parameter(name: "owner", localName: "ownerOrNil", type: Type._AnyObject.asOptional()), - Function.Parameter(name: "options", localName: "optionsOrNil", type: Type(module: .StdLib, name: "[NSObject : AnyObject]", optional: true), defaultValue: "nil") + Function.Parameter(name: "options", localName: "optionsOrNil", type: Type(module: .StdLib, name: SwiftIdentifier(rawValue: "[NSObject : AnyObject]"), optional: true), defaultValue: "nil") ] let bundleLet = Let( @@ -134,7 +123,7 @@ struct NibGenerator: Generator { let viewTypeString = viewInfo.view.description return Function( isStatic: false, - name: "\(viewInfo.ordinal.word)View", + name: SwiftIdentifier(name: "\(viewInfo.ordinal.word)View"), generics: nil, parameters: instantiateParameters, doesThrow: false, @@ -161,9 +150,9 @@ struct NibGenerator: Generator { reuseProtocols = [] } - let sanitizedName = sanitizedSwiftName(nib.name, lowercaseFirstCharacter: false) + let sanitizedName = SwiftIdentifier(name: nib.name, lowercaseFirstCharacter: false) return Struct( - type: Type(module: .Host, name: "_\(sanitizedName)"), + type: Type(module: .Host, name: SwiftIdentifier(name: "_\(sanitizedName)")), implements: ([Type.NibResourceType] + reuseProtocols).map(TypePrinter.init), typealiasses: reuseTypealiasses, properties: [bundleLet, nameVar] + reuseIdentifierProperties, diff --git a/R.swift/Generators/ResourceFileGenerator.swift b/R.swift/Generators/ResourceFileGenerator.swift index 7e5ae4b4..109e6fdd 100644 --- a/R.swift/Generators/ResourceFileGenerator.swift +++ b/R.swift/Generators/ResourceFileGenerator.swift @@ -13,19 +13,8 @@ struct ResourceFileGenerator: Generator { let internalStruct: Struct? = nil init(resourceFiles: [ResourceFile]) { - let groupedResourceFiles = resourceFiles.groupBySwiftNames { $0.fullname } - - for (name, duplicates) in groupedResourceFiles.duplicates { - warn("Skipping \(duplicates.count) resource files because symbol '\(name)' would be generated for all of these files: \(duplicates.joinWithSeparator(", "))") - } - - let empties = groupedResourceFiles.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 resource file because no swift identifier can be generated for file: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) resource files because no swift identifier can be generated for all of these files: \(empties.joinWithSeparator(", "))") - } + let groupedResourceFiles = resourceFiles.groupBySwiftIdentifiers { $0.fullname } + groupedResourceFiles.printWarningsForDuplicatesAndEmpties(source: "resource file", result: "file") let resourceFileProperties: [Property] = groupedResourceFiles .uniques @@ -34,7 +23,7 @@ struct ResourceFileGenerator: Generator { return Let( comments: ["Resource file `\($0.fullname)`."], isStatic: true, - name: $0.fullname, + name: SwiftIdentifier(name: $0.fullname), typeDefinition: .Inferred(Type.FileResource), value: "FileResource(bundle: _R.hostingBundle, name: \"\($0.filename)\", pathExtension: \(pathExtensionOrNilString))" ) @@ -50,14 +39,14 @@ struct ResourceFileGenerator: Generator { Function( comments: ["`bundle.URLForResource(\"\(filename)\", withExtension: \(pathExtension))`"], isStatic: true, - name: fullname, + name: SwiftIdentifier(name: fullname), generics: nil, parameters: [ Function.Parameter(name: "_", type: Type._Void) ], doesThrow: false, returnType: Type._NSURL.asOptional(), - body: "let fileResource = R.file.\(sanitizedSwiftName(fullname))\nreturn fileResource.bundle.URLForResource(fileResource)" + body: "let fileResource = R.file.\(SwiftIdentifier(name: fullname))\nreturn fileResource.bundle.URLForResource(fileResource)" ) ] } diff --git a/R.swift/Generators/ReuseIdentifierGenerator.swift b/R.swift/Generators/ReuseIdentifierGenerator.swift index d7c5ccd5..f9af9198 100644 --- a/R.swift/Generators/ReuseIdentifierGenerator.swift +++ b/R.swift/Generators/ReuseIdentifierGenerator.swift @@ -18,19 +18,8 @@ struct ReuseIdentifierGenerator: Generator { .values .flatMap { $0.first } - let groupedReusables = deduplicatedReusables.groupBySwiftNames { $0.identifier } - - for (name, duplicates) in groupedReusables.duplicates { - warn("Skipping \(duplicates.count) reuseIdentifiers because symbol '\(name)' would be generated for all of these reuseIdentifiers: \(duplicates.joinWithSeparator(", "))") - } - - let empties = groupedReusables.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 reuseIdentifier because no swift identifier can be generated for reuseIdentifier: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) reuseIdentifiers because no swift identifier can be generated for all of these reuseIdentifiers: \(empties.joinWithSeparator(", "))") - } + let groupedReusables = deduplicatedReusables.groupBySwiftIdentifiers { $0.identifier } + groupedReusables.printWarningsForDuplicatesAndEmpties(source: "reuseIdentifier", result: "reuseIdentifier") let reuseIdentifierProperties = groupedReusables .uniques @@ -51,7 +40,7 @@ struct ReuseIdentifierGenerator: Generator { return Let( comments: ["Reuse identifier `\(reusable.identifier)`."], isStatic: true, - name: reusable.identifier, + name: SwiftIdentifier(name: reusable.identifier), typeDefinition: .Specified(Type.ReuseIdentifier.withGenericArgs([reusable.type])), value: "\(Type.ReuseIdentifier.name)(identifier: \"\(reusable.identifier)\")" ) diff --git a/R.swift/Generators/SegueGenerator.swift b/R.swift/Generators/SegueGenerator.swift index a1ffa180..44107f5b 100644 --- a/R.swift/Generators/SegueGenerator.swift +++ b/R.swift/Generators/SegueGenerator.swift @@ -44,19 +44,9 @@ struct SegueGenerator: Generator { var structs: [Struct] = [] for (sourceType, seguesBySourceType) in deduplicatedSeguesWithInfo.groupBy({ $0.sourceType }) { - let groupedSeguesWithInfo = seguesBySourceType.groupBySwiftNames { $0.segue.identifier } + let groupedSeguesWithInfo = seguesBySourceType.groupBySwiftIdentifiers { $0.segue.identifier } - for (name, duplicates) in groupedSeguesWithInfo.duplicates { - warn("Skipping \(duplicates.count) segues for '\(sourceType)' because symbol '\(name)' would be generated for all of these segues, but with a different destination or segue type: \(duplicates.joinWithSeparator(", "))") - } - - let empties = groupedSeguesWithInfo.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 segue for '\(sourceType)' because no swift identifier can be generated for segue: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) segues for '\(sourceType)' because no swift identifier can be generated for all of these segues: \(empties.joinWithSeparator(", "))") - } + groupedSeguesWithInfo.printWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType)'", result: "segue") let sts = groupedSeguesWithInfo .uniques @@ -116,7 +106,7 @@ struct SegueGenerator: Generator { return Let( comments: ["Segue identifier `\(segueWithInfo.segue.identifier)`."], isStatic: true, - name: segueWithInfo.segue.identifier, + name: SwiftIdentifier(name: segueWithInfo.segue.identifier), typeDefinition: .Specified(type), value: "StoryboardSegueIdentifier(identifier: \"\(segueWithInfo.segue.identifier)\")" ) @@ -130,7 +120,7 @@ struct SegueGenerator: Generator { "For use inside `prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)`." ], isStatic: true, - name: segueWithInfo.segue.identifier, + name: SwiftIdentifier(name: segueWithInfo.segue.identifier), generics: nil, parameters: [ Function.Parameter.init(name: "segue", localName: "segue", type: Type._UIStoryboardSegue) @@ -139,11 +129,11 @@ struct SegueGenerator: Generator { returnType: Type.TypedStoryboardSegueInfo .asOptional() .withGenericArgs([segueWithInfo.segue.type, segueWithInfo.sourceType, segueWithInfo.destinationType]), - body: "return TypedStoryboardSegueInfo(segueIdentifier: R.segue.\(sanitizedSwiftName(sourceType.description)).\(sanitizedSwiftName(segueWithInfo.segue.identifier)), segue: segue)" + body: "return TypedStoryboardSegueInfo(segueIdentifier: R.segue.\(SwiftIdentifier(name: sourceType.description)).\(SwiftIdentifier(name: segueWithInfo.segue.identifier)), segue: segue)" ) } - let typeName = sanitizedSwiftName(sourceType.description) + let typeName = SwiftIdentifier(name: sourceType.description) return Struct( comments: ["This struct is generated for `\(sourceType.name)`, and contains static references to \(properties.count) segues."], diff --git a/R.swift/Generators/StoryboardGenerator.swift b/R.swift/Generators/StoryboardGenerator.swift index 6be5627a..551743d0 100644 --- a/R.swift/Generators/StoryboardGenerator.swift +++ b/R.swift/Generators/StoryboardGenerator.swift @@ -13,19 +13,8 @@ struct StoryboardGenerator: Generator { let internalStruct: Struct? init(storyboards: [Storyboard]) { - let groupedStoryboards = storyboards.groupBySwiftNames { $0.name } - - for (name, duplicates) in groupedStoryboards.duplicates { - warn("Skipping \(duplicates.count) storyboards because symbol '\(name)' would be generated for all of these storyboards: \(duplicates.joinWithSeparator(", "))") - } - - let empties = groupedStoryboards.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 storyboard because no swift identifier can be generated for storyboard: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) storyboards because no swift identifier can be generated for all of these storyboards: \(empties.joinWithSeparator(", "))") - } + let groupedStoryboards = storyboards.groupBySwiftIdentifiers { $0.name } + groupedStoryboards.printWarningsForDuplicatesAndEmpties(source: "storyboard", result: "file") let storyboardStructs = groupedStoryboards .uniques @@ -108,7 +97,7 @@ struct StoryboardGenerator: Generator { guard let storyboardIdentifier = vc.storyboardIdentifier else { return nil } return (vc, storyboardIdentifier) } - .groupBySwiftNames { $0.identifier } + .groupBySwiftIdentifiers { $0.identifier } for (name, duplicates) in groupedViewControllersWithIdentifier.duplicates { warn("Skipping \(duplicates.count) view controllers because symbol '\(name)' would be generated for all of these view controller identifiers: \(duplicates.joinWithSeparator(", "))") @@ -120,7 +109,7 @@ struct StoryboardGenerator: Generator { vc, Let( isStatic: false, - name: sanitizedSwiftName(identifier), + name: SwiftIdentifier(name: identifier), typeDefinition: .Inferred(Type.StoryboardViewControllerResource), value: "\(Type.StoryboardViewControllerResource.name)<\(vc.type)>(identifier: \"\(identifier)\")" ) @@ -153,7 +142,7 @@ struct StoryboardGenerator: Generator { let validateViewControllersLines = storyboard.viewControllers .flatMap { vc in vc.storyboardIdentifier.map { - "if _R.storyboard.\(sanitizedSwiftName(storyboard.name))().\(sanitizedSwiftName($0))() == nil { throw Rswift.ValidationError(description:\"[R.swift] ViewController with identifier '\(sanitizedSwiftName($0))' could not be loaded from storyboard '\(storyboard.name)' as '\(vc.type)'.\") }" + "if _R.storyboard.\(SwiftIdentifier(name: storyboard.name))().\(SwiftIdentifier(name: $0))() == nil { throw Rswift.ValidationError(description:\"[R.swift] ViewController with identifier '\(SwiftIdentifier(name: $0))' could not be loaded from storyboard '\(storyboard.name)' as '\(vc.type)'.\") }" } } let validateLines = validateImagesLines + validateViewControllersLines @@ -174,7 +163,7 @@ struct StoryboardGenerator: Generator { // Return return Struct( - type: Type(module: .Host, name: sanitizedSwiftName(storyboard.name)), + type: Type(module: .Host, name: SwiftIdentifier(name: storyboard.name)), implements: implements, typealiasses: typealiasses, properties: properties, diff --git a/R.swift/Generators/StringsGenerator.swift b/R.swift/Generators/StringsGenerator.swift index 47dfb836..88bd5ea0 100644 --- a/R.swift/Generators/StringsGenerator.swift +++ b/R.swift/Generators/StringsGenerator.swift @@ -15,19 +15,9 @@ struct StringsGenerator: Generator { init(localizableStrings: [LocalizableStrings]) { let localized = localizableStrings.groupBy { $0.filename } - let groupedLocalized = localized.groupBySwiftNames { $0.0 } + let groupedLocalized = localized.groupBySwiftIdentifiers { $0.0 } - for (sanitizedName, duplicates) in groupedLocalized.duplicates { - warn("Skipping \(duplicates.count) strings files because symbol '\(sanitizedName)' would be generated for all of these filenames: \(duplicates.joinWithSeparator(", "))") - } - - let empties = groupedLocalized.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 strings file because no swift identifier can be generated for filename: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) strings files because no swift identifier can be generated for all of these filenames: \(empties.joinWithSeparator(", "))") - } + groupedLocalized.printWarningsForDuplicatesAndEmpties(source: "strings file", result: "file") externalStruct = Struct( comments: ["This `R.string` struct is generated, and contains static references to \(groupedLocalized.uniques.count) localization tables."], @@ -42,7 +32,7 @@ struct StringsGenerator: Generator { private static func stringStructFromLocalizableStrings(filename: String, strings: [LocalizableStrings]) -> Struct? { - let name = sanitizedSwiftName(filename) + let name = SwiftIdentifier(name: filename) let params = computeParams(filename, strings: strings) return Struct( @@ -73,19 +63,9 @@ struct StringsGenerator: Generator { // Warnings about duplicates and empties for ls in strings { let filenameLocale = ls.locale.withFilename(filename) - let groupedKeys = ls.dictionary.keys.groupBySwiftNames { $0 } + let groupedKeys = ls.dictionary.keys.groupBySwiftIdentifiers { $0 } - for (sanitizedName, duplicates) in groupedKeys.duplicates { - warn("Skipping \(duplicates.count) strings in \(filenameLocale) because symbol '\(sanitizedName)' would be generated for all of these keys: \(duplicates.map { "'\($0)'" }.joinWithSeparator(", "))") - } - - let empties = groupedKeys.empties - if let empty = empties.first where empties.count == 1 { - warn("Skipping 1 string in \(filenameLocale) because no swift identifier can be generated for key: \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) strings in \(filenameLocale) because no swift identifier can be generated for all of these keys: \(empties.joinWithSeparator(", "))") - } + groupedKeys.printWarningsForDuplicatesAndEmpties(source: "string", container: "in \(filenameLocale)", result: "key") // Save uniques for key in groupedKeys.uniques { @@ -186,7 +166,7 @@ struct StringsGenerator: Generator { return Let( comments: values.comments, isStatic: true, - name: values.key, + name: SwiftIdentifier(name: values.key), typeDefinition: .Inferred(Type.StringResource), value: "StringResource(key: \"\(escapedKey)\", tableName: \"\(values.tableName)\", bundle: _R.hostingBundle, locales: [\(locales)])" ) @@ -206,7 +186,7 @@ struct StringsGenerator: Generator { return Function( comments: values.comments, isStatic: true, - name: values.key, + name: SwiftIdentifier(name: values.key), generics: nil, parameters: [ Function.Parameter(name: "_", type: Type._Void) @@ -240,7 +220,7 @@ struct StringsGenerator: Generator { return Function( comments: values.comments, isStatic: true, - name: values.key, + name: SwiftIdentifier(name: values.key), generics: nil, parameters: params, doesThrow: false, diff --git a/R.swift/ResourceTypes/Nib.swift b/R.swift/ResourceTypes/Nib.swift index 609c75d3..8087371a 100644 --- a/R.swift/ResourceTypes/Nib.swift +++ b/R.swift/ResourceTypes/Nib.swift @@ -85,7 +85,9 @@ private class NibParserDelegate: NSObject, NSXMLParserDelegate { let customModuleProvider = attributeDict["customModuleProvider"] let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] let customClass = attributeDict["customClass"] - let customType = customClass.map { Type(module: Module(name: customModule), name: $0, optional: false) } + let customType = customClass + .map { SwiftIdentifier(name: $0, lowercaseFirstCharacter: false) } + .map { Type(module: Module(name: customModule), name: $0, optional: false) } return customType ?? ElementNameToTypeMapping[elementName] ?? Type._UIView } @@ -98,7 +100,9 @@ private class NibParserDelegate: NSObject, NSXMLParserDelegate { let customModuleProvider = attributeDict["customModuleProvider"] let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] let customClass = attributeDict["customClass"] - let customType = customClass.map { Type(module: Module(name: customModule), name: $0, optional: false) } + let customType = customClass + .map { SwiftIdentifier(name: $0, lowercaseFirstCharacter: false) } + .map { Type(module: Module(name: customModule), name: $0, optional: false) } let type = customType ?? ElementNameToTypeMapping[elementName] ?? Type._UIView diff --git a/R.swift/ResourceTypes/Storyboard.swift b/R.swift/ResourceTypes/Storyboard.swift index 29ab1729..de5475d6 100644 --- a/R.swift/ResourceTypes/Storyboard.swift +++ b/R.swift/ResourceTypes/Storyboard.swift @@ -132,7 +132,9 @@ private class StoryboardParserDelegate: NSObject, NSXMLParserDelegate { let customModuleProvider = attributeDict["customModuleProvider"] let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] let customClass = attributeDict["customClass"] - let customType = customClass.map { Type(module: Module(name: customModule), name: $0, optional: false) } + let customType = customClass + .map { SwiftIdentifier(name: $0, lowercaseFirstCharacter: false) } + .map { Type(module: Module(name: customModule), name: $0, optional: false) } if let customType = customType where attributeDict["kind"] != "custom" { warn("Set the segue of class \(customType) with identifier '\(attributeDict["identifier"] ?? "-no identifier-")' to type custom, using segue subclasses with other types can cause crashes on iOS 8 and lower.") @@ -203,7 +205,9 @@ private class StoryboardParserDelegate: NSObject, NSXMLParserDelegate { let customModuleProvider = attributeDict["customModuleProvider"] let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] let customClass = attributeDict["customClass"] - let customType = customClass.map { Type(module: Module(name: customModule), name: $0, optional: false) } + let customType = customClass + .map { SwiftIdentifier(name: $0, lowercaseFirstCharacter: false) } + .map { Type(module: Module(name: customModule), name: $0, optional: false) } let type = customType ?? ElementNameToTypeMapping[elementName] ?? Type._UIViewController @@ -218,7 +222,9 @@ private class StoryboardParserDelegate: NSObject, NSXMLParserDelegate { let customModuleProvider = attributeDict["customModuleProvider"] let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] let customClass = attributeDict["customClass"] - let customType = customClass.map { Type(module: Module(name: customModule), name: $0, optional: false) } + let customType = customClass + .map { SwiftIdentifier(name: $0, lowercaseFirstCharacter: false) } + .map { Type(module: Module(name: customModule), name: $0, optional: false) } let type = customType ?? ElementNameToTypeMapping[elementName] ?? Type._UIView diff --git a/R.swift/SwiftTypes/Function.swift b/R.swift/SwiftTypes/Function.swift index 5847aecb..bbb467ab 100644 --- a/R.swift/SwiftTypes/Function.swift +++ b/R.swift/SwiftTypes/Function.swift @@ -11,14 +11,14 @@ import Foundation struct Function: UsedTypesProvider { let comments: [String] let isStatic: Bool - let name: String + let name: SwiftIdentifier let generics: String? let parameters: [Parameter] let doesThrow: Bool let returnType: Type let body: String - init(isStatic: Bool, name: String, generics: String?, parameters: [Parameter], doesThrow: Bool, returnType: Type, body: String) { + init(isStatic: Bool, name: SwiftIdentifier, generics: String?, parameters: [Parameter], doesThrow: Bool, returnType: Type, body: String) { self.comments = [] self.isStatic = isStatic self.name = name @@ -29,7 +29,7 @@ struct Function: UsedTypesProvider { self.body = body } - init(comments: [String], isStatic: Bool, name: String, generics: String?, parameters: [Parameter], doesThrow: Bool, returnType: Type, body: String) { + init(comments: [String], isStatic: Bool, name: SwiftIdentifier, generics: String?, parameters: [Parameter], doesThrow: Bool, returnType: Type, body: String) { self.comments = comments self.isStatic = isStatic self.name = name @@ -47,10 +47,6 @@ struct Function: UsedTypesProvider { ].flatten() } - var callName: String { - return sanitizedSwiftName(name, lowercaseFirstCharacter: true) - } - var description: String { let commentsString = comments.map { "/// \($0)\n" }.joinWithSeparator("") let staticString = isStatic ? "static " : "" @@ -60,7 +56,7 @@ struct Function: UsedTypesProvider { let returnString = Type._Void == returnType ? "" : " -> \(returnType)" let bodyString = body.indentWithString(IndentationString) - return "\(commentsString)\(staticString)func \(callName)\(genericsString)(\(parameterString))\(throwString)\(returnString) {\n\(bodyString)\n}" + return "\(commentsString)\(staticString)func \(name)\(genericsString)(\(parameterString))\(throwString)\(returnString) {\n\(bodyString)\n}" } struct Parameter: UsedTypesProvider, CustomStringConvertible { @@ -73,12 +69,12 @@ struct Function: UsedTypesProvider { return type.usedTypes } - var swiftName: String { - return sanitizedSwiftName(name, lowercaseFirstCharacter: true) + var swiftIdentifier: SwiftIdentifier { + return SwiftIdentifier(name: name, lowercaseFirstCharacter: true) } var description: String { - let definition = localName.map({ "\(self.swiftName) \($0): \(type)" }) ?? "\(swiftName): \(type)" + let definition = localName.map({ "\(swiftIdentifier) \($0): \(type)" }) ?? "\(swiftIdentifier): \(type)" return defaultValue.map({ "\(definition) = \($0)" }) ?? definition } diff --git a/R.swift/SwiftTypes/Let.swift b/R.swift/SwiftTypes/Let.swift index e01ae02f..52577e9d 100644 --- a/R.swift/SwiftTypes/Let.swift +++ b/R.swift/SwiftTypes/Let.swift @@ -27,11 +27,11 @@ enum TypeDefinition: UsedTypesProvider { struct Let: Property { let comments: [String] let isStatic: Bool - let name: String + let name: SwiftIdentifier let typeDefinition: TypeDefinition let value: String - init(isStatic: Bool, name: String, typeDefinition: TypeDefinition, value: String) { + init(isStatic: Bool, name: SwiftIdentifier, typeDefinition: TypeDefinition, value: String) { self.comments = [] self.isStatic = isStatic self.name = name @@ -39,7 +39,7 @@ struct Let: Property { self.value = value } - init(comments: [String], isStatic: Bool, name: String, typeDefinition: TypeDefinition, value: String) { + init(comments: [String], isStatic: Bool, name: SwiftIdentifier, typeDefinition: TypeDefinition, value: String) { self.comments = comments self.isStatic = isStatic self.name = name @@ -61,6 +61,6 @@ struct Let: Property { case .Inferred: typeString = "" } - return "\(commentsString)\(staticString)let \(callName)\(typeString) = \(value)" + return "\(commentsString)\(staticString)let \(name)\(typeString) = \(value)" } } diff --git a/R.swift/SwiftTypes/Property.swift b/R.swift/SwiftTypes/Property.swift index 5ed8a58f..d7f86f16 100644 --- a/R.swift/SwiftTypes/Property.swift +++ b/R.swift/SwiftTypes/Property.swift @@ -9,13 +9,7 @@ import Foundation protocol Property: UsedTypesProvider, CustomStringConvertible { - var name: String { get } -} - -extension Property { - var callName: String { - return sanitizedSwiftName(name, lowercaseFirstCharacter: true) - } + var name: SwiftIdentifier { get } } /// Type-erasure function diff --git a/R.swift/SwiftTypes/Struct.swift b/R.swift/SwiftTypes/Struct.swift index e9973352..9c6a812f 100644 --- a/R.swift/SwiftTypes/Struct.swift +++ b/R.swift/SwiftTypes/Struct.swift @@ -68,12 +68,14 @@ struct Struct: UsedTypesProvider, CustomStringConvertible { .joinWithSeparator("\n") let varsString = properties - .sort { $0.callName < $1.callName } +// .sort { $0.name.description < $1.name.description } .map { $0.description } + .sort() .joinWithSeparator("\n") let functionsString = functions - .sort { $0.callName < $1.callName } +// .sort { $0.name.description < $1.name.description } .map { $0.description } + .sort() .joinWithSeparator("\n\n") let structsString = structs .sort { $0.type.description < $1.type.description } diff --git a/R.swift/SwiftTypes/Type.swift b/R.swift/SwiftTypes/Type.swift index 53f89d2a..ca188e4f 100644 --- a/R.swift/SwiftTypes/Type.swift +++ b/R.swift/SwiftTypes/Type.swift @@ -61,7 +61,7 @@ struct Type: UsedTypesProvider, CustomStringConvertible, Hashable { static let TypedStoryboardSegueInfo = Type(module: "Rswift", name: "TypedStoryboardSegueInfo", genericArgs: [TypeVar(description: "Segue", usedTypes: []), TypeVar(description: "Source", usedTypes: []), TypeVar(description: "Destination", usedTypes: [])]) let module: Module - let name: String + let name: SwiftIdentifier let genericArgs: [TypeVar] let optional: Bool @@ -77,14 +77,14 @@ struct Type: UsedTypesProvider, CustomStringConvertible, Hashable { return description.hashValue } - init(module: Module, name: String, genericArgs: [TypeVar] = [], optional: Bool = false) { + init(module: Module, name: SwiftIdentifier, genericArgs: [TypeVar] = [], optional: Bool = false) { self.module = module self.name = name self.genericArgs = genericArgs self.optional = optional } - init(module: Module, name: String, genericArgs: [Type], optional: Bool = false) { + init(module: Module, name: SwiftIdentifier, genericArgs: [Type], optional: Bool = false) { self.module = module self.name = name self.genericArgs = genericArgs.map(TypeVar.init) diff --git a/R.swift/SwiftTypes/Var.swift b/R.swift/SwiftTypes/Var.swift index 60b48c82..30e344e0 100644 --- a/R.swift/SwiftTypes/Var.swift +++ b/R.swift/SwiftTypes/Var.swift @@ -10,7 +10,7 @@ import Foundation struct Var: Property { let isStatic: Bool - let name: String + let name: SwiftIdentifier let type: Type let getter: String @@ -20,6 +20,6 @@ struct Var: Property { var description: String { let staticString = isStatic ? "static " : "" - return "\(staticString)var \(callName): \(type) { \(getter) }" + return "\(staticString)var \(name): \(type) { \(getter) }" } } diff --git a/R.swift/Util/SanitizedSwiftName.swift b/R.swift/Util/SanitizedSwiftName.swift deleted file mode 100644 index ce152125..00000000 --- a/R.swift/Util/SanitizedSwiftName.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// SanitizedSwiftName.swift -// R.swift -// -// Created by Mathijs Kadijk on 11-12-15. -// Copyright © 2015 Mathijs Kadijk. All rights reserved. -// - -import Foundation - -/* -Disallowed characters: whitespace, mathematical symbols, arrows, private-use and invalid Unicode points, line- and boxdrawing characters -Special rules: Can't begin with a number -*/ -func sanitizedSwiftName(name: String, lowercaseFirstCharacter: Bool = true) -> String { - var nameComponents = name.componentsSeparatedByCharactersInSet(BlacklistedCharacters) - - let firstComponent = nameComponents.removeAtIndex(0) - let cleanedSwiftName = nameComponents.reduce(firstComponent) { $0 + $1.uppercaseFirstCharacter } - - let regex = try! NSRegularExpression(pattern: "^[0-9]+", options: .CaseInsensitive) - let fullRange = NSRange(location: 0, length: cleanedSwiftName.characters.count) - let sanitizedSwiftName = regex.stringByReplacingMatchesInString(cleanedSwiftName, options: NSMatchingOptions(rawValue: 0), range: fullRange, withTemplate: "") - - let capitalizedSwiftName = lowercaseFirstCharacter ? sanitizedSwiftName.lowercaseFirstCharacter : sanitizedSwiftName - if SwiftKeywords.contains(capitalizedSwiftName) { - return "`\(capitalizedSwiftName)`" - } - - return capitalizedSwiftName // .isEmpty ? nil : capitalizedSwiftName -} - -struct SwiftNameGroups { - let uniques: [T] - let duplicates: [(String, [String])] // Identifiers that result in duplicate Swift names - let empties: [String] // Identifiers (wrapped in quotes) that result in empty swift names -} - -extension SequenceType { - func groupBySwiftNames(identifierSelector: Generator.Element -> String) -> SwiftNameGroups { - var groupedBy = groupBy { sanitizedSwiftName(identifierSelector($0)) } - let empties = groupedBy[""]?.map { "'\(identifierSelector($0))'" }.sort() - groupedBy[""] = nil - - let uniques = Array(groupedBy.values.filter { $0.count == 1 }.flatten()) - let duplicates = groupedBy - .filter { $0.1.count > 1 } - .map { ($0.0, $0.1.map(identifierSelector).sort()) } - - return SwiftNameGroups(uniques: uniques, duplicates: duplicates, empties: empties ?? []) - } -} - -private let BlacklistedCharacters: NSCharacterSet = { - let blacklist = NSMutableCharacterSet(charactersInString: "") - blacklist.formUnionWithCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) - blacklist.formUnionWithCharacterSet(NSCharacterSet.punctuationCharacterSet()) - blacklist.formUnionWithCharacterSet(NSCharacterSet.symbolCharacterSet()) - blacklist.formUnionWithCharacterSet(NSCharacterSet.illegalCharacterSet()) - blacklist.formUnionWithCharacterSet(NSCharacterSet.controlCharacterSet()) - blacklist.removeCharactersInString("_") - - // Emoji ranges, roughly based on http://www.unicode.org/Public/emoji/1.0//emoji-data.txt - [ - 0x2600...0x27BF, - 0x1F300...0x1F6FF, - 0x1F900...0x1F9FF, - 0x1F1E6...0x1F1FF, - ].forEach { - let range = NSRange(location: $0.startIndex, length: $0.endIndex - $0.startIndex) - blacklist.removeCharactersInRange(range) - } - - return blacklist -}() - -// Based on https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413 -private let SwiftKeywords = [ - // Keywords used in declarations - "associatedtype", "class", "deinit", "enum", "extension", "func", "import", "init", "inout", "internal", "let", "operator", "private", "protocol", "public", "static", "struct", "subscript", "typealias", "var", - - // Keywords used in statements - "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch", "where", "while", - - // Keywords used in expressions and types - "as", "catch", "dynamicType", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try", "__COLUMN__", "__FILE__", "__FUNCTION__", "__LINE__", -] - diff --git a/R.swift/Util/SwiftIdentifier.swift b/R.swift/Util/SwiftIdentifier.swift new file mode 100644 index 00000000..b34d37ae --- /dev/null +++ b/R.swift/Util/SwiftIdentifier.swift @@ -0,0 +1,153 @@ +// +// SwiftIdentifier.swift +// R.swift +// +// Created by Mathijs Kadijk on 11-12-15. +// Copyright © 2015 Mathijs Kadijk. All rights reserved. +// + +import Foundation + +private let numberRegex = try! NSRegularExpression(pattern: "^[0-9]+", options: .CaseInsensitive) + +/* + Disallowed characters: whitespace, mathematical symbols, arrows, private-use and invalid Unicode points, line- and boxdrawing characters + Special rules: Can't begin with a number + */ +struct SwiftIdentifier : CustomStringConvertible { + let description: String + + init(name: String, lowercaseFirstCharacter: Bool = true) { + var nameComponents = name.componentsSeparatedByCharactersInSet(BlacklistedCharacters) + + let firstComponent = nameComponents.removeAtIndex(0) + let cleanedSwiftName = nameComponents.reduce(firstComponent) { $0 + $1.uppercaseFirstCharacter } + + let fullRange = NSRange(location: 0, length: cleanedSwiftName.characters.count) + let sanitizedSwiftName = numberRegex.stringByReplacingMatchesInString(cleanedSwiftName, options: [], range: fullRange, withTemplate: "") + + let capitalizedSwiftName = lowercaseFirstCharacter ? sanitizedSwiftName.lowercaseFirstCharacter : sanitizedSwiftName + + if SwiftKeywords.contains(capitalizedSwiftName) { + description = "`\(capitalizedSwiftName)`" + } + else { + description = capitalizedSwiftName + } + } + + init(rawValue: String) { + description = rawValue + } +} + +extension SwiftIdentifier : Hashable { + var hashValue: Int { + return description.hashValue + } +} + +func ==(lhs: SwiftIdentifier, rhs: SwiftIdentifier) -> Bool { + return lhs.description == rhs.description +} + +extension SwiftIdentifier : StringLiteralConvertible { + typealias StringLiteralType = String + typealias UnicodeScalarLiteralType = String + typealias ExtendedGraphemeClusterLiteralType = String + + init(stringLiteral value: StringLiteralType) { + description = value + + if self != SwiftIdentifier(name: value, lowercaseFirstCharacter: false) { + assertionFailure("'\(value)' not a correct SwiftIdentifier") + } + } + + init(unicodeScalarLiteral value: StringLiteralType) { + description = value + } + + init(extendedGraphemeClusterLiteral value: StringLiteralType) { + description = value + } + +} + +struct SwiftNameGroups { + let uniques: [T] + let duplicates: [(SwiftIdentifier, [String])] // Identifiers that result in duplicate Swift names + let empties: [String] // Identifiers (wrapped in quotes) that result in empty swift names + + func printWarningsForDuplicatesAndEmpties(source source: String, container: String? = nil, result: String) { + + let sourceSingular = [source, container].flatMap { $0 }.joinWithSeparator(" ") + let sourcePlural = ["\(source)s", container].flatMap { $0 }.joinWithSeparator(" ") + + let resultSingular = result + let resultPlural = "\(result)s" + + for (sanitizedName, dups) in duplicates { + warn("Skipping \(dups.count) \(sourcePlural) because symbol '\(sanitizedName)' would be generated for all of these \(resultPlural): \(dups.joinWithSeparator(", "))") + } + + if let empty = empties.first where empties.count == 1 { + warn("Skipping 1 \(sourceSingular) because no swift identifier can be generated for \(resultSingular): \(empty)") + } + else if empties.count > 1 { + warn("Skipping \(empties.count) \(sourcePlural) because no swift identifier can be generated for all of these \(resultPlural): \(empties.joinWithSeparator(", "))") + } + } +} + +extension SequenceType { + func groupBySwiftIdentifiers(identifierSelector: Generator.Element -> String) -> SwiftNameGroups { + var groupedBy = groupBy { SwiftIdentifier(name: identifierSelector($0)) } + let empty = SwiftIdentifier(name: "") + let empties = groupedBy[empty]?.map { "'\(identifierSelector($0))'" }.sort() + groupedBy[empty] = nil + + let uniques = Array(groupedBy.values.filter { $0.count == 1 }.flatten()) + let duplicates = groupedBy + .filter { $0.1.count > 1 } + .map { ($0.0, $0.1.map(identifierSelector).sort()) } + + return SwiftNameGroups(uniques: uniques, duplicates: duplicates, empties: empties ?? []) + } +} + +private let BlacklistedCharacters: NSCharacterSet = { + let blacklist = NSMutableCharacterSet(charactersInString: "") + blacklist.formUnionWithCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + blacklist.formUnionWithCharacterSet(NSCharacterSet.punctuationCharacterSet()) + blacklist.formUnionWithCharacterSet(NSCharacterSet.symbolCharacterSet()) + blacklist.formUnionWithCharacterSet(NSCharacterSet.illegalCharacterSet()) + blacklist.formUnionWithCharacterSet(NSCharacterSet.controlCharacterSet()) + blacklist.removeCharactersInString("_") + + // Emoji ranges, roughly based on http://www.unicode.org/Public/emoji/1.0//emoji-data.txt + [ + 0x2600...0x27BF, + 0x1F300...0x1F6FF, + 0x1F900...0x1F9FF, + 0x1F1E6...0x1F1FF, + ].forEach { + let range = NSRange(location: $0.startIndex, length: $0.endIndex - $0.startIndex) + blacklist.removeCharactersInRange(range) + } + + return blacklist +}() + +// Based on https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413 +private let SwiftKeywords = [ + // Keywords used in declarations + "associatedtype", "class", "deinit", "enum", "extension", "func", "import", "init", "inout", "internal", "let", "operator", "private", "protocol", "public", "static", "struct", "subscript", "typealias", "var", + + // Keywords used in statements + "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch", "where", "while", + + // Keywords used in expressions and types + "as", "catch", "dynamicType", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try", "__COLUMN__", "__FILE__", "__FUNCTION__", "__LINE__", +] + diff --git a/R.swiftTests/MainTests.swift b/R.swiftTests/MainTests.swift index 33a19e2e..c0ab5784 100644 --- a/R.swiftTests/MainTests.swift +++ b/R.swiftTests/MainTests.swift @@ -41,7 +41,7 @@ class MainTests: XCTestCase { // Use XCTAssert and related functions to verify your tests produce the correct results. swiftNameData.forEach { - let sanitizedResult = sanitizedSwiftName($0.0, lowercaseFirstCharacter: true) + let sanitizedResult = SwiftIdentifier(name: $0.0, lowercaseFirstCharacter: true).description XCTAssertEqual(sanitizedResult, $0.1) } } @@ -50,7 +50,7 @@ class MainTests: XCTestCase { // This is an example of a performance test case. self.measureBlock { (0...1000).forEach { _ in - sanitizedSwiftName("(looks) easy, but it's not reallY that easy!", lowercaseFirstCharacter: true) + SwiftIdentifier(name: "(looks) easy, but it's not reallY that easy!", lowercaseFirstCharacter: true) } } } diff --git a/ResourceApp/ResourceAppTests/ResourceAppTests.swift b/ResourceApp/ResourceAppTests/ResourceAppTests.swift index 1fb2a64f..194a4876 100644 --- a/ResourceApp/ResourceAppTests/ResourceAppTests.swift +++ b/ResourceApp/ResourceAppTests/ResourceAppTests.swift @@ -14,10 +14,10 @@ class ResourceAppTests: XCTestCase { let expectedWarnings = [ "warning: [R.swift] Skipping 2 images because symbol 'second' would be generated for all of these images: Second, second", - "warning: [R.swift] Skipping 2 xibs because symbol 'duplicate' would be generated for all of these xibs: Duplicate, duplicate", - "warning: [R.swift] Skipping 2 storyboards because symbol 'duplicate' would be generated for all of these storyboards: Duplicate, duplicate", + "warning: [R.swift] Skipping 2 xibs because symbol 'duplicate' would be generated for all of these files: Duplicate, duplicate", + "warning: [R.swift] Skipping 2 storyboards because symbol 'duplicate' would be generated for all of these files: Duplicate, duplicate", "warning: [R.swift] Skipping 2 reuseIdentifiers because symbol 'duplicateCellView' would be generated for all of these reuseIdentifiers: DuplicateCellView, duplicateCellView", - "warning: [R.swift] Skipping 2 segues for 'SecondViewController' because symbol 'toFirst' would be generated for all of these segues, but with a different destination or segue type: ToFirst, toFirst", + "warning: [R.swift] Skipping 2 segues for 'SecondViewController' because symbol 'toFirst' would be generated for all of these segues: ToFirst, toFirst", "warning: [R.swift] Skipping 2 images because symbol 'theAppIcon' would be generated for all of these images: The App Icon, TheAppIcon", "warning: [R.swift] Skipping 2 images because symbol 'second' would be generated for all of these images: Second, second", "warning: [R.swift] Skipping 2 resource files because symbol 'duplicateJson' would be generated for all of these files: Duplicate.json, duplicateJson", @@ -25,8 +25,8 @@ class ResourceAppTests: XCTestCase { "warning: [R.swift] Skipping 1 reuseIdentifier because no swift identifier can be generated for reuseIdentifier: ' '", "warning: [R.swift] Skipping 2 colors in palette 'My R.swift colors' because symbol 'black' would be generated for all of these colors: Black, Black?", - "warning: [R.swift] Skipping 2 strings files because symbol 'duplicate' would be generated for all of these filenames: Duplicate, Duplicate#", - "warning: [R.swift] Skipping 1 strings file because no swift identifier can be generated for filename: '@@'", + "warning: [R.swift] Skipping 2 strings files because symbol 'duplicate' would be generated for all of these files: Duplicate, Duplicate#", + "warning: [R.swift] Skipping 1 strings file because no swift identifier can be generated for file: '@@'", "warning: [R.swift] Skipping 1 string in 'Generic' because no swift identifier can be generated for key: '#'", "warning: [R.swift] Strings file 'Localizable' (en) is missing translations for keys: 'japanese only'", "warning: [R.swift] Strings file 'Localizable' (es) is missing translations for keys: 'japanese only'",