diff --git a/.swiftlint.yml b/.swiftlint.yml index 064369f2..06a3691d 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,26 +1,22 @@ disabled_rules: # rule identifiers to exclude from running - force_cast - function_body_length - - line_length - todo + - line_length - type_body_length - - variable_name + - cyclomatic_complexity included: # paths to include during linting. `--path` is ignored if present. - - PINModel -excluded: # paths to ignore during linting. - - PINModel/Carthage - - PINModel/CommandKit + - Sources + - Tests + - Utility + # parameterized rules can be customized from this configuration file -line_length: 110 +line_length: 120 + # parameterized rules are first parameterized as a warning level, then error level. type_body_length: - 300 # warning - 400 # error -# parameterized rules are first parameterized as a warning level, then error level. -variable_name_max_length: - - 40 # warning - - 60 # error -# parameterized rules are first parameterized as a warning level, then error level. -variable_name_min_length: - - 3 # warning - - 2 # error + +reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji) + diff --git a/Makefile b/Makefile index 93639674..5631fdff 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ # This makefile exposes targets that unify building, testing and archiving of # PINModel +SWIFT_LINT_EXEC=`which swiftlint` + .PHONY: all clean build test archive all: clean build test archive @@ -8,7 +10,10 @@ all: clean build test archive clean: swift build --clean -build: +lint: + $(SWIFT_LINT_EXEC) lint --reporter emoji + +build: lint swift build build_test_index_linux: @@ -17,7 +22,7 @@ build_test_index_linux: test: build_test_index_linux build swift test -archive: +archive: lint swift build -c release -Xswiftc -static-stdlib archive_linux: clean diff --git a/Sources/Core/FileGenerator.swift b/Sources/Core/FileGenerator.swift index 3de29b66..606a79da 100644 --- a/Sources/Core/FileGenerator.swift +++ b/Sources/Core/FileGenerator.swift @@ -18,7 +18,7 @@ public enum GenerationParameterType { } protocol FileGeneratorManager { - static func filesToGenerate(descriptor: SchemaObjectRoot, generatorParameters: GenerationParameters) -> Array + static func filesToGenerate(descriptor: SchemaObjectRoot, generatorParameters: GenerationParameters) -> [FileGenerator] } protocol FileGenerator { @@ -45,7 +45,7 @@ extension FileGenerator { let header = [ "//", "// \(copy.fileName)", - "// Pinterest", // TODO (allow other copyrights?) + "// Autogenerated by plank", "//", "// DO NOT EDIT - EDITS WILL BE OVERWRITTEN", "// Copyright (c) \(year) Pinterest, Inc. All rights reserved.", @@ -60,7 +60,7 @@ extension FileGenerator { func generateFile(_ schema: SchemaObjectRoot, outputDirectory: URL, generationParameters: GenerationParameters) { for var file in ObjectiveCFileGenerator.filesToGenerate(descriptor: schema, generatorParameters: generationParameters) { - let fileContents = file.renderFile() + "\n" // Ensure there is exactly one new line a the end of the file. // TODO - Have `FilePrinter` take care of things like this. + let fileContents = file.renderFile() + "\n" // Ensure there is exactly one new line a the end of the file do { try fileContents.write( to: URL(string: file.fileName, relativeTo: outputDirectory)!, diff --git a/Sources/Core/ObjCRootsRenderer.swift b/Sources/Core/ObjCRootsRenderer.swift index 616607eb..32fd99c8 100644 --- a/Sources/Core/ObjCRootsRenderer.swift +++ b/Sources/Core/ObjCRootsRenderer.swift @@ -8,7 +8,7 @@ import Foundation -let DateValueTransformerKey = "kPINModelDateValueTransformerKey" +let rootNSObject = SchemaObjectRoot(name: "NSObject", properties: [:], extends: nil, algebraicTypeIdentifier: nil) struct ObjCRootsRenderer { let rootSchema: SchemaObjectRoot @@ -22,340 +22,33 @@ struct ObjCRootsRenderer { // MARK: Properties var className: String { - get { - return self.rootSchema.className(with: self.params) - } + return self.rootSchema.className(with: self.params) } var builderClassName: String { - get { - return "\(self.className)Builder" - } + return "\(self.className)Builder" } var dirtyPropertyOptionName: String { - get { - return "\(self.className)DirtyProperties" - } + return "\(self.className)DirtyProperties" } - var parentDescriptor: Schema? { - get { - return self.rootSchema.extends.flatMap { $0() } - } + var parentDescriptor: Schema? { + return self.rootSchema.extends.flatMap { $0() } } - var properties: [(Parameter, Schema)] { - get { - return self.rootSchema.properties.map { $0 } - } + return self.rootSchema.properties.map { $0 } } var dirtyPropertiesIVarName: String { - get { - return "\(rootSchema.name.snakeCaseToPropertyName())DirtyProperties" - } + return "\(rootSchema.name.snakeCaseToPropertyName())DirtyProperties" } var isBaseClass: Bool { - get { - return rootSchema.extends == nil - } - } - - - // MARK: Model methods - - func renderClassName() -> ObjCIR.Method { - return ObjCIR.method("+ (NSString *)className") { - ["return \(self.className.objcLiteral());"] - } - } - - func renderPolymorphicTypeIdentifier() -> ObjCIR.Method { - return ObjCIR.method("+ (NSString *)polymorphicTypeIdentifier") { - ["return \(self.rootSchema.typeIdentifier.objcLiteral());"] - } - } - - func renderModelObjectWithDictionary() -> ObjCIR.Method { - return ObjCIR.method("+ (instancetype)modelObjectWithDictionary:(NSDictionary *)dictionary") { - ["return [[self alloc] initWithModelDictionary:dictionary];"] - } - } - - - func renderDesignatedInit() -> ObjCIR.Method { - return ObjCIR.method("- (instancetype)init") { - [ - "return [self initWithModelDictionary:@{}];", - ] - } - } - - // MARK: Hash Method - Inspired from Mike Ash article on Equality / Hashing - // https://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html - func renderHash() -> ObjCIR.Method { - func schemaHashStatement(with param: Parameter, for schema: Schema) -> String { - switch schema { - case .Enum(_), .Integer: - // - The value equality statement is sufficient for equality testing - // - All enum types are treated as Integers so we do not need to treat String Enumerations differently - return "_\(param)" - case .Float: - return " [@(_\(param)) hash]" - case .Boolean: - // Values 1231 for true and 1237 for false are adopted from the Java hashCode specification - // http://docs.oracle.com/javase/7/docs/api/java/lang/Boolean.html#hashCode - return "(_\(param) ? 1231 : 1237)" - case .Reference(with: let fn): - switch fn() { - case .some(.Object(let schemaRoot)): - return schemaHashStatement(with: param, for: .Object(schemaRoot)) - default: - fatalError("Bad reference found for schema parameter: \(param)") - } - default: - return "[_\(param) hash]" - } - } - - let rootHashStatement = self.isBaseClass ? ["17"] : ["[super hash]"]; - let propReturnStatements = rootHashStatement + self.properties.map { param, schema -> String in - let formattedParam = param.snakeCaseToPropertyName() - return schemaHashStatement(with: formattedParam, for: schema) - } - - - return ObjCIR.method("- (NSUInteger)hash") {[ - "NSUInteger subhashes[] = {", - -->[propReturnStatements.joined(separator: ",\n")], - "};", - "return PINIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0]));" - ]} - } - - // MARK: Equality Methods inspired from NSHipster article on Equality: http://nshipster.com/equality/ - func renderIsEqualToClass() -> ObjCIR.Method { - func schemaIsEqualStatement(with param: Parameter, for schema: Schema) -> String { - switch schema { - case .Integer, .Float, .Enum(_), .Boolean: - // - The value equality statement is sufficient for equality testing - // - All enum types are treated as Integers so we do not need to treat String Enumerations differently - return "" - case .Map(_): - return ObjCIR.msg("_\(param)", ("isEqualToDictionary", "anObject.\(param)")) - case .String(format: .some(.DateTime)): - return ObjCIR.msg("_\(param)", ("isEqualToDate", "anObject.\(param)")) - case .String(format: .none), - .String(format: .some(.Email)), - .String(format: .some(.Hostname)), - .String(format: .some(.Ipv4)), - .String(format: .some(.Ipv6)): - return ObjCIR.msg("_\(param)", ("isEqualToString", "anObject.\(param)")) - case .OneOf(types:_), .Object(_), .Array(_), .String(format: .some(.Uri)): - return ObjCIR.msg("_\(param)", ("isEqual", "anObject.\(param)")) - case .Reference(with: let fn): - switch fn() { - case .some(.Object(let schemaRoot)): - return schemaIsEqualStatement(with: param, for: .Object(schemaRoot)) - default: - fatalError("Bad reference found for schema parameter: \(param)") - } - } - } - - // Performance optimization - compare primitives before resorting to more expensive `isEqual` calls - let sortedProps = self.properties.sorted { $0.0.1.isObjCPrimitiveType } - - let propReturnStmts = sortedProps.map { param, schema -> String in - let formattedParam = param.snakeCaseToPropertyName() - let pointerEqStmt = "_\(formattedParam) == anObject.\(formattedParam)" - let deepEqStmt = schemaIsEqualStatement(with: formattedParam, for: schema) - return [pointerEqStmt, deepEqStmt].filter { $0 != "" }.joined(separator: " || ") - } - - return ObjCIR.method("- (BOOL)isEqualTo\(self.rootSchema.name.snakeCaseToCamelCase()):(\(self.className) *)anObject") { - [ - "return (", - -->[(["anObject != nil", "self == anObject"] + propReturnStmts) - .map{ "(\($0))" }.joined(separator: " &&\n")], - ");" - ] - } - } - - func renderIsEqual() -> ObjCIR.Method { - return ObjCIR.method("- (BOOL)isEqual:(id)anObject") { - [ - ObjCIR.ifStmt("self == anObject") { ["return YES;"] }, - self.isBaseClass ? "" : ObjCIR.ifStmt("[super isEqual:anObject] == NO") { ["return NO;"] }, - ObjCIR.ifStmt("[anObject isKindOfClass:[\(self.className) class]] == NO") { ["return NO;"] }, - "return [self isEqualTo\(self.rootSchema.name.snakeCaseToCamelCase()):anObject];" - ].filter { $0 != "" } - } - } - - func renderInitWithModelDictionary() -> ObjCIR.Method { - func renderPropertyInit( - _ propertyToAssign: String, - _ rawObjectName: String, - schema: Schema, - firstName: String, // TODO: HACK to get enums to work (not clean) - counter: Int = 0 - ) -> [String] { - switch schema { - case .Array(itemType: .some(let itemType)): - let currentResult = "result\(counter)" - let currentTmp = "tmp\(counter)" - let currentObj = "obj\(counter)" - return [ - "NSArray *items = \(rawObjectName);", - "NSMutableArray *\(currentResult) = [NSMutableArray arrayWithCapacity:items.count];", - ObjCIR.forStmt("id \(currentObj) in items") { [ - ObjCIR.ifStmt("[\(currentObj) isEqual:[NSNull null]] == NO") { [ - "id \(currentTmp) = nil;", - renderPropertyInit(currentTmp, currentObj, schema: itemType, firstName: firstName, counter: counter + 1).joined(separator: "\n"), - ObjCIR.ifStmt("\(currentTmp) != nil") {[ - "[\(currentResult) addObject:\(currentTmp)];" - ]} - ]} - ]}, - "\(propertyToAssign) = \(currentResult);" - ] - case .Map(valueType: .some(let valueType)): - let currentResult = "result\(counter)" - let currentItems = "items\(counter)" - let (currentKey, currentObj, currentStop) = ("key\(counter)", "obj\(counter)", "stop\(counter)") - return [ - "NSDictionary *\(currentItems) = \(rawObjectName);", - "NSMutableDictionary *\(currentResult) = [NSMutableDictionary dictionaryWithCapacity:\(currentItems).count];", - ObjCIR.stmt( - ObjCIR.msg(currentItems, - ("enumerateKeysAndObjectsUsingBlock", - ObjCIR.block(["NSString * _Nonnull \(currentKey)", - "id _Nonnull \(currentObj)", - "__unused BOOL * _Nonnull \(currentStop)"]) { - [ - ObjCIR.ifStmt("\(currentObj) != nil && [\(currentObj) isEqual:[NSNull null]] == NO") { - renderPropertyInit("\(currentResult)[\(currentKey)]", currentObj, schema: valueType, firstName: firstName, counter: counter + 1) - } - ] - }) - ) - ), - "\(propertyToAssign) = \(currentResult);" - ] - case .Float: - return ["\(propertyToAssign) = [\(rawObjectName) doubleValue];"] - case .Integer: - return ["\(propertyToAssign) = [\(rawObjectName) integerValue];"] - case .Boolean: - return ["\(propertyToAssign) = [\(rawObjectName) boolValue];"] - case .String(format: .some(.Uri)): - return ["\(propertyToAssign) = [NSURL URLWithString:\(rawObjectName)];"] - case .String(format: .some(.DateTime)): - return ["\(propertyToAssign) = [[NSValueTransformer valueTransformerForName:\(DateValueTransformerKey)] transformedValue:\(rawObjectName)];"] - case .Reference(with: let refFunc): - return refFunc().map { - renderPropertyInit(propertyToAssign, rawObjectName, schema: $0, firstName: firstName, counter: counter) - } ?? { - assert(false, "TODO: Forward optional across methods") - return [] - }() - case .Enum(.Integer(let variants)): - return renderPropertyInit(propertyToAssign, rawObjectName, schema: .Integer, firstName: firstName, counter: counter) - case .Enum(.String(let variants)): - return ["\(propertyToAssign) = \(enumFromStringMethodName(propertyName: firstName, className: className))(value);"] - case .OneOf(types: let schemas): - func loop(schema: Schema) -> String { - switch schema { - case .Object(let objectRoot): - return ObjCIR.ifStmt("[\(rawObjectName)[\("type".objcLiteral())] isEqualToString:\(objectRoot.typeIdentifier.objcLiteral())]") {[ - "\(propertyToAssign) = [\(objectRoot.className(with: self.params)) modelObjectWithDictionary:\(rawObjectName)];" - ]} - case .Reference(with: let refFunc): - return refFunc().map(loop) ?? { - assert(false, "TODO: Forward optional across methods") - return "" - }() - default: - assert(false, "Unsupported OneOf type (for now)") - return "" - } - } - - return schemas.map(loop) - case .Object(let objectRoot): - return ["\(propertyToAssign) = [\(objectRoot.className(with: self.params)) modelObjectWithDictionary:\(rawObjectName)];"] - default: - return ["\(propertyToAssign) = \(rawObjectName);"] - } - } - - return ObjCIR.method("- (instancetype)initWithModelDictionary:(NSDictionary *)modelDictionary") { - let x: [String] = self.properties.map{ (name, schema) in - ObjCIR.ifStmt("[key isEqualToString:\(name.objcLiteral())]") { - [ - "id value = valueOrNil(modelDictionary, \(name.objcLiteral()));", - ObjCIR.ifStmt("value != nil") { - renderPropertyInit("self->_\(name.snakeCaseToPropertyName())", "value", schema: schema, firstName: name) - }, - "self->_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: name, className: className)) = 1;" - ] - } - } - - return [ - "NSParameterAssert(modelDictionary);", - self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : - "if (!(self = [super initWithModelDictionary:modelDictionary])) { return self; }", - ObjCIR.stmt( - ObjCIR.msg("modelDictionary", - ("enumerateKeysAndObjectsUsingBlock", ObjCIR.block(["NSString * _Nonnull key", - "id _Nonnull obj", - "__unused BOOL * _Nonnull stop"]) { - x - } - ) - )), - ObjCIR.ifStmt("[self class] == [\(self.className) class]") { - ["[self PIModelDidInitialize:PIModelInitTypeDefault];"] - }, - "return self;" - ] - } - } - - func renderInitWithBuilder() -> ObjCIR.Method { - return ObjCIR.method("- (instancetype)initWithBuilder:(\(builderClassName) *)builder") { - [ - "NSParameterAssert(builder);", - "return [self initWithBuilder:builder initType:PIModelInitTypeDefault];" - ] - } + return rootSchema.extends == nil } - func renderInitWithBuilderWithInitType() -> ObjCIR.Method { - return ObjCIR.method("- (instancetype)initWithBuilder:(\(builderClassName) *)builder initType:(PIModelInitType)initType") { - [ - "NSParameterAssert(builder);", - self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : - ObjCIR.ifStmt("!(self = [super initWithBuilder:builder initType:initType])") { ["return self;"] }, - self.properties.map { (name, schema) in - "_\(name.snakeCaseToPropertyName()) = builder.\(name.snakeCaseToPropertyName());" - }.joined(separator: "\n"), - "_\(self.dirtyPropertiesIVarName) = builder.\(self.dirtyPropertiesIVarName);", - ObjCIR.ifStmt("[self class] == [\(self.className) class]") { - ["[self PIModelDidInitialize:initType];"] - }, - "return self;" - ] - } - } - - func renderReferencedClasses() -> Set { // Referenced Classes // The current class header @@ -386,6 +79,83 @@ struct ObjCRootsRenderer { .flatMap(referencedClassNames)) } + func objcClassFromSchema(_ param: String, _ schema: Schema) -> String { + switch schema { + case .Array(itemType: .none): + return "NSArray *" + case .Array(itemType: .some(let itemType)): + return "NSArray<\(objcClassFromSchema(param, itemType))> *" + case .Map(valueType: .none): + return "NSDictionary *" + case .Map(valueType: .some(let valueType)): + return "NSDictionary *" + case .String(format: .none), + .String(format: .some(.Email)), + .String(format: .some(.Hostname)), + .String(format: .some(.Ipv4)), + .String(format: .some(.Ipv6)): + return "NSString *" + case .String(format: .some(.DateTime)): + return "NSDate *" + case .String(format: .some(.Uri)): + return "NSURL *" + case .Integer: + return "NSInteger" + case .Float: + return "double" + case .Boolean: + return "BOOL" + case .Enum(_): + return enumTypeName(propertyName: param, className: self.className) + case .Object(let objSchemaRoot): + return "\(objSchemaRoot.className(with: self.params)) *" + case .Reference(with: let fn): + switch fn() { + case .some(.Object(let schemaRoot)): + return objcClassFromSchema(param, .Object(schemaRoot)) + default: + fatalError("Bad reference found in schema for class: \(self.className)") + } + case .OneOf(types: let schemaTypes): + func inheritanceChain(schema: Schema?) -> [SchemaObjectRoot] { + switch schema { + case .none: + return [] + case .some(.Object(let root)): + return [root] + inheritanceChain(schema: root.extends.flatMap { $0() }) + case .some(.Reference(with: let fn)): + return inheritanceChain(schema: fn()) + default: + fatalError("Unimplemented one of: \(schema)") + } + } + + let chains: [[SchemaObjectRoot]] = schemaTypes + .map(inheritanceChain) + .map { $0.reversed() } + + let commonParent = chains[0].enumerated().filter { idx, val in + chains.filter { $0[idx] == val }.count == chains.count + }.last.map {$0.1} + + return "__kindof \((commonParent ?? rootNSObject).className(with: self.params)) *" + } + } + + // MARK: Model methods + + func renderClassName() -> ObjCIR.Method { + return ObjCIR.method("+ (NSString *)className") { + ["return \(self.className.objcLiteral());"] + } + } + + func renderPolymorphicTypeIdentifier() -> ObjCIR.Method { + return ObjCIR.method("+ (NSString *)polymorphicTypeIdentifier") { + ["return \(self.rootSchema.typeIdentifier.objcLiteral());"] + } + } + func renderDebugDescription() -> ObjCIR.Method { func formatParam(_ param: String, _ schema: Schema) -> String { let propIVarName = "_\(param.snakeCaseToPropertyName())" @@ -414,7 +184,6 @@ struct ObjCRootsRenderer { fatalError("Bad reference found in schema for class: \(self.className)") } } - } let props = self.properties.map { (param, schema) -> String in @@ -435,112 +204,6 @@ struct ObjCRootsRenderer { ]} } - func renderInitWithCoder() -> ObjCIR.Method { - func referencedObjectClasses(_ schema:Schema) -> Set { - switch schema { - case .Array(itemType: .none): - return Set(["NSArray"]) - case .Array(itemType: .some(let itemType)): - return Set(["NSArray"]).union(referencedObjectClasses(itemType)) - case .Map(valueType: .none): - return Set(["NSDictionary"]) - case .Map(valueType: .some(let valueType)): - return Set(["NSDictionary"]).union(referencedObjectClasses(valueType)) - case .String(format: .none), - .String(format: .some(.Email)), - .String(format: .some(.Hostname)), - .String(format: .some(.Ipv4)), - .String(format: .some(.Ipv6)): - return Set(["NSString"]) - case .String(format: .some(.DateTime)): - return Set(["NSDate"]) - case .String(format: .some(.Uri)): - return Set(["NSURL"]) - case .Integer, .Float, .Boolean, .Enum(_): - return Set(["NSNumber"]) - case .Object(let objSchemaRoot): - return Set([objSchemaRoot.className(with: self.params)]) - case .Reference(with: let fn): - switch fn() { - case .some(.Object(let schemaRoot)): - return referencedObjectClasses(.Object(schemaRoot)) - default: - fatalError("Bad reference found in schema for class: \(self.className)") - } - case .OneOf(types: let schemaTypes): - return schemaTypes.map(referencedObjectClasses).reduce(Set(), { s1, s2 in s1.union(s2) }) - } - } - - func formatParam(_ param: String, _ schema: Schema) -> String { - let propIVarName = "_\(param.snakeCaseToPropertyName())" - return "\(propIVarName) = " + { switch schema { - case .Enum(_): - return "[aDecoder decodeIntegerForKey:\(param.objcLiteral())];" - case .Boolean: - return "[aDecoder decodeBoolForKey:\(param.objcLiteral())];" - case .Float: - return "[aDecoder decodeDoubleForKey:\(param.objcLiteral())];" - case .Integer: - return "[aDecoder decodeIntegerForKey:\(param.objcLiteral())];" - case .String(_), .Map(_), .Array(_), .OneOf(_), .Reference(_), .Object(_): - let refObjectClasses = referencedObjectClasses(schema).map { "[\($0) class]" } - let refObjectClassesString = refObjectClasses.count == 1 ? refObjectClasses.joined(separator: ",") : "[NSSet setWithArray:\(refObjectClasses.objcLiteral())]" - if refObjectClasses.count == 0 { fatalError("Can't determine class for decode for \(schema)") } - if refObjectClasses.count == 1 { - return "[aDecoder decodeObjectOfClass:\(refObjectClassesString) forKey:\(param.objcLiteral())];" - } else { - return "[aDecoder decodeObjectOfClasses:\(refObjectClassesString) forKey:\(param.objcLiteral())];" - } - } }() - } - - return ObjCIR.method("- (instancetype)initWithCoder:(NSCoder *)aDecoder") { - [ - self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : - "if (!(self = [super initWithCoder:aDecoder])) { return self; }", - self.properties.map(formatParam).joined(separator: "\n"), - self.properties.map { (param, _) -> String in - "_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className)) = [aDecoder decodeIntForKey:\((param + "_dirty_property").objcLiteral())] & 0x1;" - }.joined(separator: "\n"), - ObjCIR.ifStmt("[self class] == [\(self.className) class]") { - ["[self PIModelDidInitialize:PIModelInitTypeDefault];"] - }, - "return self;" - ] - } - } - - - func renderEncodeWithCoder() -> ObjCIR.Method { - - func formatParam(_ param: String, _ schema: Schema) -> String { - let propGetter = "self.\(param.snakeCaseToPropertyName())" - switch schema { - case .Enum(_): - return "[aCoder encodeInteger:\(propGetter) forKey:\(param.objcLiteral())];" - case .Boolean: - return "[aCoder encodeBool:\(propGetter) forKey:\(param.objcLiteral())];" - case .Float: - return "[aCoder encodeDouble:\(propGetter) forKey:\(param.objcLiteral())];" - case .Integer: - return "[aCoder encodeInteger:\(propGetter) forKey:\(param.objcLiteral())];" - case .String(_), .Map(_), .Array(_), .OneOf(_), .Reference(_), .Object(_): - return "[aCoder encodeObject:\(propGetter) forKey:\(param.objcLiteral())];" - } - } - - return ObjCIR.method("- (void)encodeWithCoder:(NSCoder *)aCoder") { - [ - self.isBaseClass ? "" : "[super encodeWithCoder:aCoder];", - self.properties.map(formatParam).joined(separator: "\n"), - self.properties.map { (param, _) -> String in - "[aCoder encodeInt:_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className)) forKey:\((param + "_dirty_property").objcLiteral())];"}.joined(separator: "\n") - ].filter { $0 != "" } - } - } - - func renderCopyWithBlock() -> ObjCIR.Method { return ObjCIR.method("- (instancetype)copyWithBlock:(PINMODEL_NOESCAPE void (^)(\(self.builderClassName) *builder))block") { [ @@ -552,11 +215,6 @@ struct ObjCRootsRenderer { } } - func renderSupportsSecureCoding() -> ObjCIR.Method { - return ObjCIR.method("+ (BOOL)supportsSecureCoding") { ["return YES;"] } - } - - func renderMergeWithModel() -> ObjCIR.Method { return ObjCIR.method("- (instancetype)mergeWithModel:(\(self.className) *)modelObject") { ["return [self mergeWithModel:modelObject initType:PIModelInitTypeFromMerge];"] @@ -574,144 +232,6 @@ struct ObjCRootsRenderer { } } - - // MARK: Builder methods - - func renderBuilderInitWithModel() -> ObjCIR.Method { - return ObjCIR.method("- (instancetype)initWithModel:(\(self.className) *)modelObject") { - [ - "NSParameterAssert(modelObject);", - self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : - "if (!(self = [super initWithModel:modelObject])) { return self; }", - "struct \(self.dirtyPropertyOptionName) \(dirtyPropertiesIVarName) = modelObject.\(dirtyPropertiesIVarName);", - self.properties.map({ (param, schema) -> String in - ObjCIR.ifStmt("\(self.dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className))") { - ["_\(param.snakeCaseToPropertyName()) = modelObject.\(param.snakeCaseToPropertyName());"] - } - }).joined(separator: "\n"), - "_\(self.dirtyPropertiesIVarName) = \(self.dirtyPropertiesIVarName);", - "return self;" - ] - } - } - - func renderBuilderMergeWithModel() -> ObjCIR.Method { - func formatParam(_ param: String, _ schema: Schema) -> String { - return ObjCIR.ifStmt("modelObject.\(self.dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className))") { - func loop(_ schema: Schema) -> [String] { - switch schema { - case .Object(_): - return [ - "id value = modelObject.\(param.snakeCaseToPropertyName());", - ObjCIR.ifElseStmt("value != nil") {[ - ObjCIR.ifElseStmt("builder.\(param.snakeCaseToPropertyName())") {[ - "builder.\(param.snakeCaseToPropertyName()) = [builder.\(param.snakeCaseToPropertyName()) mergeWithModel:value initType:PIModelInitTypeFromSubmerge];" - ]} {[ - "builder.\(param.snakeCaseToPropertyName()) = value;" - ]} - ]} {[ - "builder.\(param.snakeCaseToPropertyName()) = nil;" - ]} - ] - case .Reference(with: let fn): - switch fn() { - case .some(.Object(let objSchema)): - return loop(.Object(objSchema)) - default: - fatalError("Error identifying reference for \(param) in \(schema)") - } - default: - return ["builder.\(param.snakeCaseToPropertyName()) = modelObject.\(param.snakeCaseToPropertyName());"] - } - } - return loop(schema) - } - } - - return ObjCIR.method("- (void)mergeWithModel:(\(self.className) *)modelObject") { - [ - "NSParameterAssert(modelObject);", - self.isBaseClass ? "" : "[super mergeWithModel:modelObject];", - "\(self.builderClassName) *builder = self;", - self.properties.map(formatParam).joined(separator: "\n") - ].filter { $0 != "" } - } - } - - fileprivate func objcClassFromSchema(_ param: String, _ schema:Schema) -> String { - switch schema { - case .Array(itemType: .none): - return "NSArray *" - case .Array(itemType: .some(let itemType)): - return "NSArray<\(objcClassFromSchema(param, itemType))> *" - case .Map(valueType: .none): - return "NSDictionary *" - case .Map(valueType: .some(let valueType)): - return "NSDictionary *" - case .String(format: .none), - .String(format: .some(.Email)), - .String(format: .some(.Hostname)), - .String(format: .some(.Ipv4)), - .String(format: .some(.Ipv6)): - return "NSString *" - case .String(format: .some(.DateTime)): - return "NSDate *" - case .String(format: .some(.Uri)): - return "NSURL *" - case .Integer: - return "NSInteger" - case .Float: - return "double" - case .Boolean: - return "BOOL" - case .Enum(_): - return enumTypeName(propertyName: param, className: self.className) - case .Object(let objSchemaRoot): - return "\(objSchemaRoot.className(with: self.params)) *" - case .Reference(with: let fn): - switch fn() { - case .some(.Object(let schemaRoot)): - return objcClassFromSchema(param, .Object(schemaRoot)) - default: - fatalError("Bad reference found in schema for class: \(self.className)") - } - case .OneOf(types: let schemaTypes): - func inheritanceChain(schema: Schema?) -> [SchemaObjectRoot] { - switch schema { - case .none: - return [] - case .some(.Object(let root)): - return [root] + inheritanceChain(schema: root.extends.flatMap{ $0() }) - case .some(.Reference(with: let fn)): - return inheritanceChain(schema: fn()) - default: - fatalError("Unimplemented one of: \(schema)") - } - } - - let chains: [[SchemaObjectRoot]] = schemaTypes - .map(inheritanceChain) - .map { $0.reversed() } - - let commonParent = chains[0].enumerated().filter{ idx, val in - chains.filter { $0[idx] == val }.count == chains.count - }.last.map{$0.1} - - return "__kindof \((commonParent ?? RootNSObject).className(with: self.params)) *" - } - } - - func renderBuilderPropertySetters() -> [ObjCIR.Method] { - return self.properties.map({ (param, schema) -> ObjCIR.Method in - ObjCIR.method("- (void)set\(param.snakeCaseToCapitalizedPropertyName()):(\(objcClassFromSchema(param, schema)))\(param.snakeCaseToPropertyName())") { - [ - "_\(param.snakeCaseToPropertyName()) = \(param.snakeCaseToPropertyName());", - "_\(self.dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className)) = 1;" - ] - } - }) - } - func renderStringEnumerationMethods() -> [ObjCIR.Method] { func renderToStringMethod(param: String, enumValues: [EnumValue]) -> ObjCIR.Method { @@ -759,7 +279,7 @@ struct ObjCRootsRenderer { let properties: [(Parameter, Schema)] = rootSchema.properties.map { $0 } // Convert [String:Schema] -> [(String, Schema)] let protocols: [String : [ObjCIR.Method]] = [ - "NSSecureCoding" : [self.renderSupportsSecureCoding(), self.renderInitWithCoder(), self.renderEncodeWithCoder()], + "NSSecureCoding": [self.renderSupportsSecureCoding(), self.renderInitWithCoder(), self.renderEncodeWithCoder()], "NSCopying": [ObjCIR.method("- (id)copyWithZone:(NSZone *)zone") { ["return self;"] }], "PIModelProtocol": [] ] @@ -831,7 +351,7 @@ struct ObjCRootsRenderer { (.Public, ObjCIR.method("- (\(self.className) *)build") { ["return [[\(self.className) alloc] initWithBuilder:self];"] }), - (.Public, self.renderBuilderMergeWithModel()), + (.Public, self.renderBuilderMergeWithModel()) ] + self.renderBuilderPropertySetters().map { (.Private, $0) }, properties: properties.map { param, schema in (param, objcClassFromSchema(param, schema), schema, .ReadWrite) }, protocols: [:]), diff --git a/Sources/Core/ObjectiveCBuilderExtension.swift b/Sources/Core/ObjectiveCBuilderExtension.swift new file mode 100644 index 00000000..83b0f7d4 --- /dev/null +++ b/Sources/Core/ObjectiveCBuilderExtension.swift @@ -0,0 +1,86 @@ +// +// ObjectiveCBuilderExtension.swift +// plank +// +// Created by rmalik on 2/14/17. +// +// + +import Foundation + +extension ObjCRootsRenderer { + + // MARK: Builder methods + + func renderBuilderInitWithModel() -> ObjCIR.Method { + return ObjCIR.method("- (instancetype)initWithModel:(\(self.className) *)modelObject") { + [ + "NSParameterAssert(modelObject);", + self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : + "if (!(self = [super initWithModel:modelObject])) { return self; }", + "struct \(self.dirtyPropertyOptionName) \(dirtyPropertiesIVarName) = modelObject.\(dirtyPropertiesIVarName);", + self.properties.map({ (param, _) -> String in + ObjCIR.ifStmt("\(self.dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className))") { + ["_\(param.snakeCaseToPropertyName()) = modelObject.\(param.snakeCaseToPropertyName());"] + } + }).joined(separator: "\n"), + "_\(self.dirtyPropertiesIVarName) = \(self.dirtyPropertiesIVarName);", + "return self;" + ] + } + } + + func renderBuilderPropertySetters() -> [ObjCIR.Method] { + return self.properties.map({ (param, schema) -> ObjCIR.Method in + ObjCIR.method("- (void)set\(param.snakeCaseToCapitalizedPropertyName()):(\(objcClassFromSchema(param, schema)))\(param.snakeCaseToPropertyName())") { + [ + "_\(param.snakeCaseToPropertyName()) = \(param.snakeCaseToPropertyName());", + "_\(self.dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className)) = 1;" + ] + } + }) + } + + func renderBuilderMergeWithModel() -> ObjCIR.Method { + func formatParam(_ param: String, _ schema: Schema) -> String { + return ObjCIR.ifStmt("modelObject.\(self.dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className))") { + func loop(_ schema: Schema) -> [String] { + switch schema { + case .Object(_): + return [ + "id value = modelObject.\(param.snakeCaseToPropertyName());", + ObjCIR.ifElseStmt("value != nil") {[ + ObjCIR.ifElseStmt("builder.\(param.snakeCaseToPropertyName())") {[ + "builder.\(param.snakeCaseToPropertyName()) = [builder.\(param.snakeCaseToPropertyName()) mergeWithModel:value initType:PIModelInitTypeFromSubmerge];" + ]} {[ + "builder.\(param.snakeCaseToPropertyName()) = value;" + ]} + ]} {[ + "builder.\(param.snakeCaseToPropertyName()) = nil;" + ]} + ] + case .Reference(with: let fn): + switch fn() { + case .some(.Object(let objSchema)): + return loop(.Object(objSchema)) + default: + fatalError("Error identifying reference for \(param) in \(schema)") + } + default: + return ["builder.\(param.snakeCaseToPropertyName()) = modelObject.\(param.snakeCaseToPropertyName());"] + } + } + return loop(schema) + } + } + + return ObjCIR.method("- (void)mergeWithModel:(\(self.className) *)modelObject") { + [ + "NSParameterAssert(modelObject);", + self.isBaseClass ? "" : "[super mergeWithModel:modelObject];", + "\(self.builderClassName) *builder = self;", + self.properties.map(formatParam).joined(separator: "\n") + ].filter { $0 != "" } + } + } +} diff --git a/Sources/Core/ObjectiveCEqualityExtension.swift b/Sources/Core/ObjectiveCEqualityExtension.swift new file mode 100644 index 00000000..69668090 --- /dev/null +++ b/Sources/Core/ObjectiveCEqualityExtension.swift @@ -0,0 +1,113 @@ +// +// ObjectiveCEqualityExtension.swift +// plank +// +// Created by rmalik on 2/14/17. +// +// + +import Foundation + +extension ObjCRootsRenderer { + + // MARK: Hash Method - Inspired from Mike Ash article on Equality / Hashing + // https://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html + func renderHash() -> ObjCIR.Method { + func schemaHashStatement(with param: Parameter, for schema: Schema) -> String { + switch schema { + case .Enum(_), .Integer: + // - The value equality statement is sufficient for equality testing + // - All enum types are treated as Integers so we do not need to treat String Enumerations differently + return "_\(param)" + case .Float: + return " [@(_\(param)) hash]" + case .Boolean: + // Values 1231 for true and 1237 for false are adopted from the Java hashCode specification + // http://docs.oracle.com/javase/7/docs/api/java/lang/Boolean.html#hashCode + return "(_\(param) ? 1231 : 1237)" + case .Reference(with: let fn): + switch fn() { + case .some(.Object(let schemaRoot)): + return schemaHashStatement(with: param, for: .Object(schemaRoot)) + default: + fatalError("Bad reference found for schema parameter: \(param)") + } + default: + return "[_\(param) hash]" + } + } + + let rootHashStatement = self.isBaseClass ? ["17"] : ["[super hash]"] + let propReturnStatements = rootHashStatement + self.properties.map { param, schema -> String in + let formattedParam = param.snakeCaseToPropertyName() + return schemaHashStatement(with: formattedParam, for: schema) + } + + return ObjCIR.method("- (NSUInteger)hash") {[ + "return (", + -->[propReturnStatements.map { "(\($0))" }.joined(separator: " ^\n")], + ");" + ]} + } + + // MARK: Equality Methods inspired from NSHipster article on Equality: http://nshipster.com/equality/ + func renderIsEqualToClass() -> ObjCIR.Method { + func schemaIsEqualStatement(with param: Parameter, for schema: Schema) -> String { + switch schema { + case .Integer, .Float, .Enum(_), .Boolean: + // - The value equality statement is sufficient for equality testing + // - All enum types are treated as Integers so we do not need to treat String Enumerations differently + return "" + case .Map(_): + return ObjCIR.msg("_\(param)", ("isEqualToDictionary", "anObject.\(param)")) + case .String(format: .some(.DateTime)): + return ObjCIR.msg("_\(param)", ("isEqualToDate", "anObject.\(param)")) + case .String(format: .none), + .String(format: .some(.Email)), + .String(format: .some(.Hostname)), + .String(format: .some(.Ipv4)), + .String(format: .some(.Ipv6)): + return ObjCIR.msg("_\(param)", ("isEqualToString", "anObject.\(param)")) + case .OneOf(types:_), .Object(_), .Array(_), .String(format: .some(.Uri)): + return ObjCIR.msg("_\(param)", ("isEqual", "anObject.\(param)")) + case .Reference(with: let fn): + switch fn() { + case .some(.Object(let schemaRoot)): + return schemaIsEqualStatement(with: param, for: .Object(schemaRoot)) + default: + fatalError("Bad reference found for schema parameter: \(param)") + } + } + } + + // Performance optimization - compare primitives before resorting to more expensive `isEqual` calls + let sortedProps = self.properties.sorted { $0.0.1.isObjCPrimitiveType } + + let propReturnStmts = sortedProps.map { param, schema -> String in + let formattedParam = param.snakeCaseToPropertyName() + let pointerEqStmt = "_\(formattedParam) == anObject.\(formattedParam)" + let deepEqStmt = schemaIsEqualStatement(with: formattedParam, for: schema) + return [pointerEqStmt, deepEqStmt].filter { $0 != "" }.joined(separator: " || ") + } + + return ObjCIR.method("- (BOOL)isEqualTo\(self.rootSchema.name.snakeCaseToCamelCase()):(\(self.className) *)anObject") { + [ + "return (", + -->[(["anObject != nil", "self == anObject"] + propReturnStmts) + .map { "(\($0))" }.joined(separator: " &&\n")], + ");" + ] + } + } + + func renderIsEqual() -> ObjCIR.Method { + return ObjCIR.method("- (BOOL)isEqual:(id)anObject") { + [ + ObjCIR.ifStmt("self == anObject") { ["return YES;"] }, + self.isBaseClass ? "" : ObjCIR.ifStmt("[super isEqual:anObject] == NO") { ["return NO;"] }, + ObjCIR.ifStmt("[anObject isKindOfClass:[\(self.className) class]] == NO") { ["return NO;"] }, + "return [self isEqualTo\(self.rootSchema.name.snakeCaseToCamelCase()):anObject];" + ].filter { $0 != "" } + } + } +} diff --git a/Sources/Core/ObjectiveCFileGenerator.swift b/Sources/Core/ObjectiveCFileGenerator.swift index afd3a3e7..edeace96 100644 --- a/Sources/Core/ObjectiveCFileGenerator.swift +++ b/Sources/Core/ObjectiveCFileGenerator.swift @@ -10,8 +10,8 @@ import Foundation // MARK: File Generation Manager -struct ObjectiveCFileGenerator : FileGeneratorManager { - static func filesToGenerate(descriptor: SchemaObjectRoot, generatorParameters: GenerationParameters) -> Array { +struct ObjectiveCFileGenerator: FileGeneratorManager { + static func filesToGenerate(descriptor: SchemaObjectRoot, generatorParameters: GenerationParameters) -> [FileGenerator] { let rootsRenderer = ObjCRootsRenderer(rootSchema: descriptor, params: generatorParameters) @@ -27,9 +27,7 @@ struct ObjCHeaderFile: FileGenerator { let className: String var fileName: String { - get { - return "\(className).h" - } + return "\(className).h" } func renderFile() -> String { @@ -49,9 +47,7 @@ struct ObjCImplementationFile: FileGenerator { let className: String var fileName: String { - get { - return "\(className).m" - } + return "\(className).m" } func renderFile() -> String { diff --git a/Sources/Core/ObjectiveCIR.swift b/Sources/Core/ObjectiveCIR.swift index a877c338..853de5ed 100644 --- a/Sources/Core/ObjectiveCIR.swift +++ b/Sources/Core/ObjectiveCIR.swift @@ -8,8 +8,6 @@ import Foundation -let Indentation = " " // Four space indentation for now. Might be configurable in the future. - public enum ObjCMemoryAssignmentType: String { case Copy = "copy" case Strong = "strong" @@ -27,7 +25,6 @@ public enum ObjCMutabilityType: String { case ReadWrite = "readwrite" } - public enum ObjCPrimitiveType: String { case Float = "float" case Double = "double" @@ -42,7 +39,7 @@ extension String { } func indent() -> String { - return Indentation + self + return " " + self // Four space indentation for now. Might be configurable in the future. } } @@ -53,8 +50,6 @@ extension Sequence { } } - - typealias Argument = String typealias Parameter = String @@ -94,13 +89,11 @@ extension SchemaObjectRoot { extension Schema { var isObjCPrimitiveType: Bool { - get { - switch self { - case .Boolean, .Integer, .Enum(_), .Float: - return true - default: - return false - } + switch self { + case .Boolean, .Integer, .Enum(_), .Float: + return true + default: + return false } } @@ -127,7 +120,7 @@ enum MethodVisibility: Equatable { case Private } -func ==(lhs: MethodVisibility, rhs: MethodVisibility) -> Bool { +func == (lhs: MethodVisibility, rhs: MethodVisibility) -> Bool { switch (lhs, rhs) { case (.Public, .Public): return true case (.Private, .Private): return true @@ -137,8 +130,8 @@ func ==(lhs: MethodVisibility, rhs: MethodVisibility) -> Bool { prefix operator --> -prefix func --> (strs: Array) -> String { - return strs.flatMap { $0.components(separatedBy: "\n").map{$0.indent() } } +prefix func --> (strs: [String]) -> String { + return strs.flatMap { $0.components(separatedBy: "\n").map {$0.indent() } } .joined(separator: "\n") } @@ -154,7 +147,6 @@ struct ObjCIR { return ObjCIR.Method(body: body(), signature: signature) } - static func stmt(_ body: String) -> String { return "\(body);" } @@ -162,7 +154,7 @@ struct ObjCIR { static func msg(_ variable: String, _ messages: (Parameter, Argument)...) -> String { return "[\(variable) " + - messages.map{ (param, arg) in "\(param):\(arg)" }.joined(separator: " ") + + messages.map { (param, arg) in "\(param):\(arg)" }.joined(separator: " ") + "]" } @@ -203,7 +195,7 @@ struct ObjCIR { static func switchStmt(_ switchVariable: String, body: () -> [SwitchCase]) -> String { return [ "switch (\(switchVariable)) {", - body().map{ $0.render() }.joined(separator: "\n"), + body().map { $0.render() }.joined(separator: "\n"), "}" ].joined(separator: "\n") } @@ -241,7 +233,6 @@ struct ObjCIR { return "#import \"\(filename).h\"" } - static func enumStmt(_ enumName: String, body: () -> [String]) -> String { return [ "typedef NS_ENUM(NSInteger, \(enumName)) {", @@ -251,8 +242,8 @@ struct ObjCIR { } struct Method { - let body : [String] - let signature : String + let body: [String] + let signature: String func render() -> [String] { return [ @@ -284,7 +275,6 @@ struct ObjCIR { ) case Enum(name: String, values: EnumType) - func renderHeader() -> [String] { switch self { case .Struct(_, _): @@ -296,19 +286,19 @@ struct ObjCIR { return [ "#import ", parentName.map(ObjCIR.fileImportStmt) ?? "", - "#import ", - ].filter { $0 != "" } + (["\(myName)Builder"] + classNames).sorted().map{ "@class \($0);" } + "#import " + ].filter { $0 != "" } + (["\(myName)Builder"] + classNames).sorted().map { "@class \($0);" } case .Class(let className, let extends, let methods, let properties, let protocols): let protocolList = protocols.keys.sorted().joined(separator: ", ") let protocolDeclarations = protocols.count > 0 ? "<\(protocolList)>" : "" let superClass = extends ?? "NSObject\(protocolDeclarations)" return [ "@interface \(className) : \(superClass)", - properties.map{ (param, typeName, schema, access) in + properties.map { (param, typeName, schema, access) in "@property (\(schema.isObjCPrimitiveType ? "" : "nullable, ")nonatomic, \(schema.memoryAssignmentType().rawValue), \(access.rawValue)) \(typeName) \(param.snakeCaseToPropertyName());" }.joined(separator: "\n"), methods.filter { visibility, _ in visibility == .Public } - .map { $1 }.map{ $0.signature + ";" }.joined(separator: "\n"), + .map { $1 }.map { $0.signature + ";" }.joined(separator: "\n"), "@end" ] case .Category(className: _, categoryName: _, methods: _, properties: _): @@ -333,7 +323,7 @@ struct ObjCIR { case .Struct(name: let name, fields: let fields): return [ "struct \(name) {", - fields.sorted().map{ $0.indent() }.joined(separator: "\n"), + fields.sorted().map { $0.indent() }.joined(separator: "\n"), "};" ] case .Macro(_): @@ -344,12 +334,12 @@ struct ObjCIR { case .Class(name: let className, extends: _, methods: let methods, properties: _, protocols: let protocols): return [ "@implementation \(className)", - methods.flatMap{$1.render()}.joined(separator: "\n"), + methods.flatMap {$1.render()}.joined(separator: "\n"), protocols.flatMap({ (protocolName, methods) -> [String] in - return ["#pragma mark - \(protocolName)"] + methods.flatMap{$0.render()} + return ["#pragma mark - \(protocolName)"] + methods.flatMap {$0.render()} }).joined(separator: "\n"), "@end" - ].map { $0.trimmingCharacters(in: CharacterSet.whitespaces) }.filter{ $0 != "" } + ].map { $0.trimmingCharacters(in: CharacterSet.whitespaces) }.filter { $0 != "" } case .Category(className: let className, categoryName: let categoryName, methods: let methods, properties: let properties): // Only render anonymous categories in the implementation guard categoryName == nil else { return [] } @@ -360,7 +350,7 @@ struct ObjCIR { }.joined(separator: "\n"), methods.map { $0.signature + ";" }.joined(separator: "\n"), "@end" - ].map { $0.trimmingCharacters(in: CharacterSet.whitespaces) }.filter{ $0 != "" } + ].map { $0.trimmingCharacters(in: CharacterSet.whitespaces) }.filter { $0 != "" } case .Function(let method): return method.render() case .Enum(_, _): @@ -370,5 +360,3 @@ struct ObjCIR { } } } - - diff --git a/Sources/Core/ObjectiveCInitExtension.swift b/Sources/Core/ObjectiveCInitExtension.swift new file mode 100644 index 00000000..02e00762 --- /dev/null +++ b/Sources/Core/ObjectiveCInitExtension.swift @@ -0,0 +1,186 @@ +// +// ObjectiveCInitExtension.swift +// plank +// +// Created by rmalik on 2/14/17. +// +// + +import Foundation + +let dateValueTransformerKey = "kPINModelDateValueTransformerKey" + +extension ObjCRootsRenderer { + + func renderModelObjectWithDictionary() -> ObjCIR.Method { + return ObjCIR.method("+ (instancetype)modelObjectWithDictionary:(NSDictionary *)dictionary") { + ["return [[self alloc] initWithModelDictionary:dictionary];"] + } + } + + func renderDesignatedInit() -> ObjCIR.Method { + return ObjCIR.method("- (instancetype)init") { + [ + "return [self initWithModelDictionary:@{}];" + ] + } + } + + func renderInitWithBuilder() -> ObjCIR.Method { + return ObjCIR.method("- (instancetype)initWithBuilder:(\(builderClassName) *)builder") { + [ + "NSParameterAssert(builder);", + "return [self initWithBuilder:builder initType:PIModelInitTypeDefault];" + ] + } + } + + func renderInitWithBuilderWithInitType() -> ObjCIR.Method { + return ObjCIR.method("- (instancetype)initWithBuilder:(\(builderClassName) *)builder initType:(PIModelInitType)initType") { + [ + "NSParameterAssert(builder);", + self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : + ObjCIR.ifStmt("!(self = [super initWithBuilder:builder initType:initType])") { ["return self;"] }, + self.properties.map { (name, _) in + "_\(name.snakeCaseToPropertyName()) = builder.\(name.snakeCaseToPropertyName());" + }.joined(separator: "\n"), + "_\(self.dirtyPropertiesIVarName) = builder.\(self.dirtyPropertiesIVarName);", + ObjCIR.ifStmt("[self class] == [\(self.className) class]") { + ["[self PIModelDidInitialize:initType];"] + }, + "return self;" + ] + } + } + + func renderInitWithModelDictionary() -> ObjCIR.Method { + func renderPropertyInit( + _ propertyToAssign: String, + _ rawObjectName: String, + schema: Schema, + firstName: String, // TODO: HACK to get enums to work (not clean) + counter: Int = 0 + ) -> [String] { + switch schema { + case .Array(itemType: .some(let itemType)): + let currentResult = "result\(counter)" + let currentTmp = "tmp\(counter)" + let currentObj = "obj\(counter)" + return [ + "NSArray *items = \(rawObjectName);", + "NSMutableArray *\(currentResult) = [NSMutableArray arrayWithCapacity:items.count];", + ObjCIR.forStmt("id \(currentObj) in items") { [ + ObjCIR.ifStmt("[\(currentObj) isEqual:[NSNull null]] == NO") { [ + "id \(currentTmp) = nil;", + renderPropertyInit(currentTmp, currentObj, schema: itemType, firstName: firstName, counter: counter + 1).joined(separator: "\n"), + ObjCIR.ifStmt("\(currentTmp) != nil") {[ + "[\(currentResult) addObject:\(currentTmp)];" + ]} + ]} + ]}, + "\(propertyToAssign) = \(currentResult);" + ] + case .Map(valueType: .some(let valueType)): + let currentResult = "result\(counter)" + let currentItems = "items\(counter)" + let (currentKey, currentObj, currentStop) = ("key\(counter)", "obj\(counter)", "stop\(counter)") + return [ + "NSDictionary *\(currentItems) = \(rawObjectName);", + "NSMutableDictionary *\(currentResult) = [NSMutableDictionary dictionaryWithCapacity:\(currentItems).count];", + ObjCIR.stmt( + ObjCIR.msg(currentItems, + ("enumerateKeysAndObjectsUsingBlock", + ObjCIR.block(["NSString * _Nonnull \(currentKey)", + "id _Nonnull \(currentObj)", + "__unused BOOL * _Nonnull \(currentStop)"]) { + [ + ObjCIR.ifStmt("\(currentObj) != nil && [\(currentObj) isEqual:[NSNull null]] == NO") { + renderPropertyInit("\(currentResult)[\(currentKey)]", currentObj, schema: valueType, firstName: firstName, counter: counter + 1) + } + ] + }) + ) + ), + "\(propertyToAssign) = \(currentResult);" + ] + case .Float: + return ["\(propertyToAssign) = [\(rawObjectName) doubleValue];"] + case .Integer: + return ["\(propertyToAssign) = [\(rawObjectName) integerValue];"] + case .Boolean: + return ["\(propertyToAssign) = [\(rawObjectName) boolValue];"] + case .String(format: .some(.Uri)): + return ["\(propertyToAssign) = [NSURL URLWithString:\(rawObjectName)];"] + case .String(format: .some(.DateTime)): + return ["\(propertyToAssign) = [[NSValueTransformer valueTransformerForName:\(dateValueTransformerKey)] transformedValue:\(rawObjectName)];"] + case .Reference(with: let refFunc): + return refFunc().map { + renderPropertyInit(propertyToAssign, rawObjectName, schema: $0, firstName: firstName, counter: counter) + } ?? { + assert(false, "TODO: Forward optional across methods") + return [] + }() + case .Enum(.Integer(let variants)): + return renderPropertyInit(propertyToAssign, rawObjectName, schema: .Integer, firstName: firstName, counter: counter) + case .Enum(.String(let variants)): + return ["\(propertyToAssign) = \(enumFromStringMethodName(propertyName: firstName, className: className))(value);"] + case .OneOf(types: let schemas): + func loop(schema: Schema) -> String { + switch schema { + case .Object(let objectRoot): + return ObjCIR.ifStmt("[\(rawObjectName)[\("type".objcLiteral())] isEqualToString:\(objectRoot.typeIdentifier.objcLiteral())]") {[ + "\(propertyToAssign) = [\(objectRoot.className(with: self.params)) modelObjectWithDictionary:\(rawObjectName)];" + ]} + case .Reference(with: let refFunc): + return refFunc().map(loop) ?? { + assert(false, "TODO: Forward optional across methods") + return "" + }() + default: + assert(false, "Unsupported OneOf type (for now)") + return "" + } + } + + return schemas.map(loop) + case .Object(let objectRoot): + return ["\(propertyToAssign) = [\(objectRoot.className(with: self.params)) modelObjectWithDictionary:\(rawObjectName)];"] + default: + return ["\(propertyToAssign) = \(rawObjectName);"] + } + } + + return ObjCIR.method("- (instancetype)initWithModelDictionary:(NSDictionary *)modelDictionary") { + let x: [String] = self.properties.map { (name, schema) in + ObjCIR.ifStmt("[key isEqualToString:\(name.objcLiteral())]") { + [ + "id value = valueOrNil(modelDictionary, \(name.objcLiteral()));", + ObjCIR.ifStmt("value != nil") { + renderPropertyInit("self->_\(name.snakeCaseToPropertyName())", "value", schema: schema, firstName: name) + }, + "self->_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: name, className: className)) = 1;" + ] + } + } + + return [ + "NSParameterAssert(modelDictionary);", + self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : + "if (!(self = [super initWithModelDictionary:modelDictionary])) { return self; }", + ObjCIR.stmt( + ObjCIR.msg("modelDictionary", + ("enumerateKeysAndObjectsUsingBlock", ObjCIR.block(["NSString * _Nonnull key", + "id _Nonnull obj", + "__unused BOOL * _Nonnull stop"]) { + x + } + ) + )), + ObjCIR.ifStmt("[self class] == [\(self.className) class]") { + ["[self PIModelDidInitialize:PIModelInitTypeDefault];"] + }, + "return self;" + ] + } + } +} diff --git a/Sources/Core/ObjectiveCNSCodingExtension.swift b/Sources/Core/ObjectiveCNSCodingExtension.swift new file mode 100644 index 00000000..b53c70cf --- /dev/null +++ b/Sources/Core/ObjectiveCNSCodingExtension.swift @@ -0,0 +1,121 @@ +// +// ObjectiveCNSCodingExtension.swift +// plank +// +// Created by rmalik on 2/14/17. +// +// + +import Foundation + +extension ObjCRootsRenderer { + + func renderSupportsSecureCoding() -> ObjCIR.Method { + return ObjCIR.method("+ (BOOL)supportsSecureCoding") { ["return YES;"] } + } + + func renderInitWithCoder() -> ObjCIR.Method { + func referencedObjectClasses(_ schema: Schema) -> Set { + switch schema { + case .Array(itemType: .none): + return Set(["NSArray"]) + case .Array(itemType: .some(let itemType)): + return Set(["NSArray"]).union(referencedObjectClasses(itemType)) + case .Map(valueType: .none): + return Set(["NSDictionary"]) + case .Map(valueType: .some(let valueType)): + return Set(["NSDictionary"]).union(referencedObjectClasses(valueType)) + case .String(format: .none), + .String(format: .some(.Email)), + .String(format: .some(.Hostname)), + .String(format: .some(.Ipv4)), + .String(format: .some(.Ipv6)): + return Set(["NSString"]) + case .String(format: .some(.DateTime)): + return Set(["NSDate"]) + case .String(format: .some(.Uri)): + return Set(["NSURL"]) + case .Integer, .Float, .Boolean, .Enum(_): + return Set(["NSNumber"]) + case .Object(let objSchemaRoot): + return Set([objSchemaRoot.className(with: self.params)]) + case .Reference(with: let fn): + switch fn() { + case .some(.Object(let schemaRoot)): + return referencedObjectClasses(.Object(schemaRoot)) + default: + fatalError("Bad reference found in schema for class: \(self.className)") + } + case .OneOf(types: let schemaTypes): + return schemaTypes.map(referencedObjectClasses).reduce(Set(), { s1, s2 in s1.union(s2) }) + } + } + + func formatParam(_ param: String, _ schema: Schema) -> String { + let propIVarName = "_\(param.snakeCaseToPropertyName())" + return "\(propIVarName) = " + { switch schema { + case .Enum(_): + return "[aDecoder decodeIntegerForKey:\(param.objcLiteral())];" + case .Boolean: + return "[aDecoder decodeBoolForKey:\(param.objcLiteral())];" + case .Float: + return "[aDecoder decodeDoubleForKey:\(param.objcLiteral())];" + case .Integer: + return "[aDecoder decodeIntegerForKey:\(param.objcLiteral())];" + case .String(_), .Map(_), .Array(_), .OneOf(_), .Reference(_), .Object(_): + let refObjectClasses = referencedObjectClasses(schema).map { "[\($0) class]" } + let refObjectClassesString = refObjectClasses.count == 1 ? refObjectClasses.joined(separator: ",") : "[NSSet setWithArray:\(refObjectClasses.objcLiteral())]" + if refObjectClasses.count == 0 { fatalError("Can't determine class for decode for \(schema)") } + if refObjectClasses.count == 1 { + return "[aDecoder decodeObjectOfClass:\(refObjectClassesString) forKey:\(param.objcLiteral())];" + } else { + return "[aDecoder decodeObjectOfClasses:\(refObjectClassesString) forKey:\(param.objcLiteral())];" + } + } }() + } + + return ObjCIR.method("- (instancetype)initWithCoder:(NSCoder *)aDecoder") { + [ + self.isBaseClass ? ObjCIR.ifStmt("!(self = [super init])") { ["return self;"] } : + "if (!(self = [super initWithCoder:aDecoder])) { return self; }", + self.properties.map(formatParam).joined(separator: "\n"), + self.properties.map { (param, _) -> String in + "_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className)) = [aDecoder decodeIntForKey:\((param + "_dirty_property").objcLiteral())] & 0x1;" + }.joined(separator: "\n"), + ObjCIR.ifStmt("[self class] == [\(self.className) class]") { + ["[self PIModelDidInitialize:PIModelInitTypeDefault];"] + }, + "return self;" + ] + } + } + + func renderEncodeWithCoder() -> ObjCIR.Method { + + func formatParam(_ param: String, _ schema: Schema) -> String { + let propGetter = "self.\(param.snakeCaseToPropertyName())" + switch schema { + case .Enum(_): + return "[aCoder encodeInteger:\(propGetter) forKey:\(param.objcLiteral())];" + case .Boolean: + return "[aCoder encodeBool:\(propGetter) forKey:\(param.objcLiteral())];" + case .Float: + return "[aCoder encodeDouble:\(propGetter) forKey:\(param.objcLiteral())];" + case .Integer: + return "[aCoder encodeInteger:\(propGetter) forKey:\(param.objcLiteral())];" + case .String(_), .Map(_), .Array(_), .OneOf(_), .Reference(_), .Object(_): + return "[aCoder encodeObject:\(propGetter) forKey:\(param.objcLiteral())];" + } + } + + return ObjCIR.method("- (void)encodeWithCoder:(NSCoder *)aCoder") { + [ + self.isBaseClass ? "" : "[super encodeWithCoder:aCoder];", + self.properties.map(formatParam).joined(separator: "\n"), + self.properties.map { (param, _) -> String in + "[aCoder encodeInt:_\(dirtyPropertiesIVarName).\(dirtyPropertyOption(propertyName: param, className: self.className)) forKey:\((param + "_dirty_property").objcLiteral())];"}.joined(separator: "\n") + ].filter { $0 != "" } + } + } + +} diff --git a/Sources/Core/Schema.swift b/Sources/Core/Schema.swift index b07fb9f5..43b31740 100644 --- a/Sources/Core/Schema.swift +++ b/Sources/Core/Schema.swift @@ -68,7 +68,7 @@ typealias Property = (Parameter, Schema) struct JSONParseError: Error {} extension Dictionary { - init(elements:[(Key, Value)]) { + init(elements: [(Key, Value)]) { self.init() for (key, value) in elements { updateValue(value, forKey: key) @@ -97,22 +97,17 @@ struct SchemaObjectRoot: Equatable { let algebraicTypeIdentifier: String? var typeIdentifier: String { - get { - return algebraicTypeIdentifier ?? name - } + return algebraicTypeIdentifier ?? name } } -func ==(lhs: SchemaObjectRoot, rhs: SchemaObjectRoot) -> Bool { +func == (lhs: SchemaObjectRoot, rhs: SchemaObjectRoot) -> Bool { return lhs.name == rhs.name } - -let RootNSObject = SchemaObjectRoot(name: "NSObject", properties: [:], extends: nil, algebraicTypeIdentifier: nil) - extension SchemaObjectRoot : CustomDebugStringConvertible { public var debugDescription: String { - return (["\(name)\n extends from \(extends.map{ $0()?.debugDescription })\n"] + properties.map { (k, v) in "\t\(k): \(v.debugDescription)\n" }).reduce("", +) + return (["\(name)\n extends from \(extends.map { $0()?.debugDescription })\n"] + properties.map { (k, v) in "\t\(k): \(v.debugDescription)\n" }).reduce("", +) } } @@ -129,7 +124,6 @@ indirect enum Schema { case Reference(with: LazySchemaReference) } - extension Schema : CustomDebugStringConvertible { public var debugDescription: String { switch self { @@ -157,7 +151,6 @@ extension Schema : CustomDebugStringConvertible { } } - extension Schema { // Computed Properties var title: String? { @@ -192,8 +185,8 @@ extension Schema { let enumVals = try? enumValues.map(EnumValue.init) let defaultVal = enumVals?.first(where: { $0.defaultValue == defaultValue }) return enumVals - .flatMap{ v in defaultVal.map{ ($0, v) } } - .map{ defaultVal, enumVals in + .flatMap { v in defaultVal.map { ($0, v) } } + .map { defaultVal, enumVals in Schema.Enum(EnumType.String(enumVals, defaultValue: defaultVal)) } } else { @@ -226,9 +219,9 @@ extension Schema { propertyForType(propertyInfo: $0, source: source) } return (k, schemaOpt) - }.map { (name, optSchema) in optSchema.map{ (name, $0) } } + }.map { (name, optSchema) in optSchema.map { (name, $0) } } let lifted: [Property]? = optTuples.reduce([], { (build: [Property]?, tupleOption: Property?) -> [Property]? in - build.flatMap { (b: [Property]) -> [Property]? in tupleOption.map{ b + [$0] } } + build.flatMap { (b: [Property]) -> [Property]? in tupleOption.map { b + [$0] } } }) let extends = (propertyInfo["extends"] as? JSONObject) .flatMap { ($0["$ref"] as? String).map { ref in { @@ -247,9 +240,9 @@ extension Schema { return (propertyInfo["oneOf"] as? [JSONObject]) // [JSONObject] .map { jsonObjs in jsonObjs.map { propertyForType(propertyInfo: $0, source: source) } } // [Schema?]? .flatMap { schemas in schemas.reduce([], { (build: [Schema]?, tupleOption: Schema?) -> [Schema]? in - build.flatMap { (b: [Schema]) -> [Schema]? in tupleOption.map{ b + [$0] } } + build.flatMap { (b: [Schema]) -> [Schema]? in tupleOption.map { b + [$0] } } }) } - .map{ Schema.OneOf(types: $0) } + .map { Schema.OneOf(types: $0) } } } diff --git a/Sources/Core/SchemaLoader.swift b/Sources/Core/SchemaLoader.swift index 10e29fb2..21e6e0b1 100644 --- a/Sources/Core/SchemaLoader.swift +++ b/Sources/Core/SchemaLoader.swift @@ -18,7 +18,7 @@ class FileSchemaLoader: SchemaLoader { var refs: [URL:Schema] init() { - self.refs = [URL:Schema]() + self.refs = [URL: Schema]() } func loadSchema(_ schemaUrl: URL) -> Schema? { diff --git a/Sources/Core/StringExtensions.swift b/Sources/Core/StringExtensions.swift index 23fcb2cf..60722217 100644 --- a/Sources/Core/StringExtensions.swift +++ b/Sources/Core/StringExtensions.swift @@ -41,7 +41,6 @@ import Foundation } #endif - extension NSObject { // prefix with "pin_" since protocol extensions cannot override parent implementations class func pin_className() -> String { @@ -53,7 +52,7 @@ extension NSObject { } } -let OBJC_RESERVED_WORDS_REPLACEMENTS = [ +let objectiveCReservedWordReplacements = [ "description": "description_text", "id": "identifier" // TODO: Fill out more objc keywords with replacements. @@ -62,7 +61,7 @@ let OBJC_RESERVED_WORDS_REPLACEMENTS = [ extension String { func snakeCaseToCamelCase() -> String { var str: String = self - if let replacementString = OBJC_RESERVED_WORDS_REPLACEMENTS[self] as String? { + if let replacementString = objectiveCReservedWordReplacements[self] as String? { str = replacementString } @@ -76,7 +75,7 @@ extension String { func snakeCaseToPropertyName() -> String { var str: String = self - if let replacementString = OBJC_RESERVED_WORDS_REPLACEMENTS[self] as String? { + if let replacementString = objectiveCReservedWordReplacements[self] as String? { str = replacementString } @@ -100,7 +99,7 @@ extension String { let capitalizedFirstLetter = String(formattedPropName[formattedPropName.startIndex]).uppercased() return capitalizedFirstLetter + String(formattedPropName.characters.dropFirst()) } - + /// Get the last n characters of a string func suffixSubstring(_ length: Int) -> String { return self.substring(from: self.characters.index(self.endIndex, offsetBy: -length)) diff --git a/Sources/plank/Cli.swift b/Sources/plank/Cli.swift index 9bf0f2e6..63ab87cc 100644 --- a/Sources/plank/Cli.swift +++ b/Sources/plank/Cli.swift @@ -37,7 +37,6 @@ extension FlagOptions : HelpCommandOutput { } } - func parseFlag(arguments: ArraySlice) -> ([FlagOptions:String], ArraySlice)? { var remainingArgs = arguments if remainingArgs.isEmpty == false { @@ -60,7 +59,7 @@ func parseFlag(arguments: ArraySlice) -> ([FlagOptions:String], ArraySli } if let flagType = FlagOptions(rawValue: flagName.trimmingCharacters(in: CharacterSet(charactersIn: "-"))) { - return ([flagType : flagValue], remainingArgs) + return ([flagType: flagValue], remainingArgs) } else { print("Error: Unexpected flag \(flagName) with value \(flagValue)") handleHelpCommand() @@ -91,7 +90,7 @@ func parseFlags(fromArguments arguments: ArraySlice) -> ([FlagOptions:St return ([:], arguments) } -func handleGenerateCommand(withArguments arguments:ArraySlice) { +func handleGenerateCommand(withArguments arguments: ArraySlice) { var generationParameters: GenerationParameters = [:] let (flags, args) = parseFlags(fromArguments: arguments) @@ -145,10 +144,8 @@ func handleHelpCommand() { " $ plank [options] file1 file2 ...", "", "Options:", - "\(FlagOptions.printHelp())", + "\(FlagOptions.printHelp())" ].joined(separator: "\n") print(helpDocs) } - - diff --git a/Sources/plank/main.swift b/Sources/plank/main.swift index e244e502..4c18c39c 100644 --- a/Sources/plank/main.swift +++ b/Sources/plank/main.swift @@ -14,4 +14,3 @@ func handleProcess(processInfo: ProcessInfo) { } handleProcess(processInfo: ProcessInfo.processInfo) - diff --git a/Tests/CoreTests/LinuxTestIndex.swift b/Tests/CoreTests/LinuxTestIndex.swift index 18ba1fcf..5b6874cd 100644 --- a/Tests/CoreTests/LinuxTestIndex.swift +++ b/Tests/CoreTests/LinuxTestIndex.swift @@ -3,5 +3,4 @@ import XCTest #if os(Linux) - -#endif \ No newline at end of file +#endif diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 0f011b28..6e69da68 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -4,4 +4,4 @@ import XCTest var tests = [XCTestCaseEntry]() -XCTMain(tests) \ No newline at end of file +XCTMain(tests) diff --git a/Utility/GenerateTestCaseProvider.swift b/Utility/GenerateTestCaseProvider.swift index d48d1866..96094c27 100755 --- a/Utility/GenerateTestCaseProvider.swift +++ b/Utility/GenerateTestCaseProvider.swift @@ -4,7 +4,7 @@ import Foundation var classNames: [String] = [] -func printTestCaseExtension(withClassName className:String, andTestNames testNames:[String]) -> String { +func printTestCaseExtension(withClassName className: String, andTestNames testNames: [String]) -> String { classNames.append(className) let testLines = testNames.map { (testName) -> String in return " (\"\(testName)\", \(testName))" @@ -23,8 +23,7 @@ func printTestCaseExtension(withClassName className:String, andTestNames testNam return output } - -func printLinuxMain(withClassNames classNames:[String]) -> String { +func printLinuxMain(withClassNames classNames: [String]) -> String { let classNameList = classNames.map { (className) -> String in return " tests += [testCase(\(className).allTests)]" }.joined(separator: "\n") @@ -39,7 +38,7 @@ func printLinuxMain(withClassNames classNames:[String]) -> String { ].joined(separator: "\n") } -func processFile(withPath path:String) -> String { +func processFile(withPath path: String) -> String { if path == "GenerateTestCaseProvider.swift" { return "" } @@ -48,7 +47,7 @@ func processFile(withPath path:String) -> String { var output: [String] = [] var currentClassName: String? = nil var testNames: [String] = [] - file.enumerateLines { (currentLine, stop) in + file.enumerateLines { (currentLine, _) in let line = currentLine.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if line.hasPrefix("class") { // class FooBar: XCTestCase @@ -66,13 +65,13 @@ func processFile(withPath path:String) -> String { } else if line.contains("test") && line.contains("func") { let testComponent = line.components(separatedBy: " ") - .map{ $0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) } + .map { $0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) } .filter { $0 != "" } .filter { $0.hasPrefix("test") } if let testName = testComponent.first?.replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "") { testNames.append(testName) - } else { + } else { print("Error parsing test declaration with line: \(line)") } } @@ -87,7 +86,7 @@ func processFile(withPath path:String) -> String { return "" } -func processDirectory(atPath path:String) { +func processDirectory(atPath path: String) { guard path != "" else { return } if let files = try? FileManager.default.contentsOfDirectory(atPath: path) { diff --git a/plank.xcodeproj/project.pbxproj b/plank.xcodeproj/project.pbxproj index eab59310..ca5f5ac9 100644 --- a/plank.xcodeproj/project.pbxproj +++ b/plank.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 812A69D01E53D2A1006A510E /* ObjectiveCEqualityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812A69CF1E53D2A1006A510E /* ObjectiveCEqualityExtension.swift */; }; + 812A69D21E53D30F006A510E /* ObjectiveCInitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812A69D11E53D30F006A510E /* ObjectiveCInitExtension.swift */; }; + 812A69D41E53D35F006A510E /* ObjectiveCBuilderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812A69D31E53D35F006A510E /* ObjectiveCBuilderExtension.swift */; }; + 812A69D61E53D44D006A510E /* ObjectiveCNSCodingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812A69D51E53D44D006A510E /* ObjectiveCNSCodingExtension.swift */; }; OBJ_33 /* FileGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* FileGenerator.swift */; }; OBJ_34 /* ObjCRootsRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* ObjCRootsRenderer.swift */; }; OBJ_35 /* ObjectiveCFileGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* ObjectiveCFileGenerator.swift */; }; @@ -40,6 +44,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 812A69CF1E53D2A1006A510E /* ObjectiveCEqualityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCEqualityExtension.swift; sourceTree = ""; }; + 812A69D11E53D30F006A510E /* ObjectiveCInitExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCInitExtension.swift; sourceTree = ""; }; + 812A69D31E53D35F006A510E /* ObjectiveCBuilderExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCBuilderExtension.swift; sourceTree = ""; }; + 812A69D51E53D44D006A510E /* ObjectiveCNSCodingExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCNSCodingExtension.swift; sourceTree = ""; }; OBJ_10 /* ObjCRootsRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObjCRootsRenderer.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; OBJ_11 /* ObjectiveCFileGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObjectiveCFileGenerator.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; OBJ_12 /* ObjectiveCIR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObjectiveCIR.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -123,7 +131,7 @@ name = Products; sourceTree = BUILT_PRODUCTS_DIR; }; - OBJ_5 /* */ = { + OBJ_5 = { isa = PBXGroup; children = ( OBJ_6 /* Package.swift */, @@ -132,7 +140,6 @@ OBJ_23 /* Utility */, OBJ_24 /* Products */, ); - name = ""; sourceTree = ""; }; OBJ_7 /* Sources */ = { @@ -147,6 +154,9 @@ OBJ_8 /* Core */ = { isa = PBXGroup; children = ( + 812A69D51E53D44D006A510E /* ObjectiveCNSCodingExtension.swift */, + 812A69D31E53D35F006A510E /* ObjectiveCBuilderExtension.swift */, + 812A69D11E53D30F006A510E /* ObjectiveCInitExtension.swift */, OBJ_9 /* FileGenerator.swift */, OBJ_10 /* ObjCRootsRenderer.swift */, OBJ_11 /* ObjectiveCFileGenerator.swift */, @@ -154,6 +164,7 @@ OBJ_13 /* Schema.swift */, OBJ_14 /* SchemaLoader.swift */, OBJ_15 /* StringExtensions.swift */, + 812A69CF1E53D2A1006A510E /* ObjectiveCEqualityExtension.swift */, ); name = Core; path = Sources/Core; @@ -166,6 +177,7 @@ isa = PBXNativeTarget; buildConfigurationList = OBJ_29 /* Build configuration list for PBXNativeTarget "Core" */; buildPhases = ( + 812A69CC1E53D00E006A510E /* ShellScript */, OBJ_32 /* Sources */, OBJ_40 /* Frameworks */, ); @@ -227,7 +239,7 @@ knownRegions = ( en, ); - mainGroup = OBJ_5 /* */; + mainGroup = OBJ_5; productRefGroup = OBJ_24 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -239,18 +251,38 @@ }; /* End PBXProject section */ +/* Begin PBXShellScriptBuildPhase section */ + 812A69CC1E53D00E006A510E /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ OBJ_32 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 0; files = ( OBJ_33 /* FileGenerator.swift in Sources */, + 812A69D21E53D30F006A510E /* ObjectiveCInitExtension.swift in Sources */, OBJ_34 /* ObjCRootsRenderer.swift in Sources */, OBJ_35 /* ObjectiveCFileGenerator.swift in Sources */, OBJ_36 /* ObjectiveCIR.swift in Sources */, OBJ_37 /* Schema.swift in Sources */, + 812A69D41E53D35F006A510E /* ObjectiveCBuilderExtension.swift in Sources */, OBJ_38 /* SchemaLoader.swift in Sources */, OBJ_39 /* StringExtensions.swift in Sources */, + 812A69D01E53D2A1006A510E /* ObjectiveCEqualityExtension.swift in Sources */, + 812A69D61E53D44D006A510E /* ObjectiveCNSCodingExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -291,12 +323,23 @@ OBJ_3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-DXcode"; @@ -305,6 +348,7 @@ SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 3.0; USE_HEADERMAP = NO; }; @@ -353,11 +397,22 @@ OBJ_4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_OPTIMIZATION_LEVEL = s; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_SWIFT_FLAGS = "-DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -365,6 +420,7 @@ SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 3.0; USE_HEADERMAP = NO; };