diff --git a/Sources/ProjectSpec/SpecLoader.swift b/Sources/ProjectSpec/SpecLoader.swift index 56c31a5c..0545b88d 100644 --- a/Sources/ProjectSpec/SpecLoader.swift +++ b/Sources/ProjectSpec/SpecLoader.swift @@ -51,7 +51,17 @@ public class SpecLoader { private extension Dictionary where Key == String, Value: Any { func validateWarnings() throws { - let errors: [SpecValidationError.ValidationError] = [] + var errors: [SpecValidationError.ValidationError] = [] + + if let targets = self["targets"] as? [String: Any] { + for (targetName, targetValue) in targets { + guard let targetDict = targetValue as? JSONDictionary else { continue } + let unknownKeys = Set(targetDict.keys).subtracting(Target.validYAMLKeys) + for key in unknownKeys.sorted() { + errors.append(.unknownTargetKey(target: targetName, key: key)) + } + } + } if !errors.isEmpty { throw SpecValidationError(errors: errors) diff --git a/Sources/ProjectSpec/SpecValidationError.swift b/Sources/ProjectSpec/SpecValidationError.swift index 7f16d2ad..f5545264 100644 --- a/Sources/ProjectSpec/SpecValidationError.swift +++ b/Sources/ProjectSpec/SpecValidationError.swift @@ -44,6 +44,7 @@ public struct SpecValidationError: Error, CustomStringConvertible { case invalidPluginPackageReference(plugin: String, package: String) case emptySourcePath(target: String) case invalidProjectFormat(String) + case unknownTargetKey(target: String, key: String) public var description: String { switch self { @@ -116,6 +117,8 @@ public struct SpecValidationError: Error, CustomStringConvertible { case let .invalidProjectFormat(projectFormat): let validValues = ProjectFormat.allCases.map(\.rawValue).joined(separator: ", ") return "Invalid projectFormat \(projectFormat.quoted). Valid values are: \(validValues)" + case let .unknownTargetKey(target, key): + return "Target \(target.quoted) has an unrecognized key \(key.quoted). This key will be ignored. Check the XcodeGen documentation for valid target keys." } } } diff --git a/Sources/ProjectSpec/Target.swift b/Sources/ProjectSpec/Target.swift index 9d94c7d8..d89cd7ca 100644 --- a/Sources/ProjectSpec/Target.swift +++ b/Sources/ProjectSpec/Target.swift @@ -275,6 +275,20 @@ extension LegacyTarget: JSONEncodable { extension Target: NamedJSONDictionaryConvertible { + /// All YAML keys recognized at the top level of a target definition. + public static let validYAMLKeys: Set = [ + "name", "productName", "type", "platform", "supportedDestinations", + "isMultiPlatformTarget", "deploymentTarget", "settings", "configFiles", + "sources", "dependencies", "buildToolPlugins", "info", "entitlements", + "transitivelyLinkDependencies", "directlyEmbedCarthageDependencies", + "requiresObjCLinking", "preBuildScripts", "prebuildScripts", + "postCompileScripts", "postBuildScripts", "postbuildScripts", + "buildRules", "scheme", "legacy", "attributes", + "onlyCopyFilesOnInstall", "putResourcesBeforeSourcesBuildPhase", + // Template-related keys consumed before Target.init is called. + "templates", "templateAttributes", + ] + public init(name: String, jsonDictionary: JSONDictionary) throws { let resolvedName: String = jsonDictionary.json(atKeyPath: "name") ?? name self.name = resolvedName diff --git a/Tests/ProjectSpecTests/SpecLoadingTests.swift b/Tests/ProjectSpecTests/SpecLoadingTests.swift index 2efee3ba..ebd257bc 100644 --- a/Tests/ProjectSpecTests/SpecLoadingTests.swift +++ b/Tests/ProjectSpecTests/SpecLoadingTests.swift @@ -423,6 +423,36 @@ class SpecLoadingTests: XCTestCase { throw failure("Expected to not throw a validation error. Got: \(error)") } } + + $0.it("warns about unrecognized target keys") { + let dictionary: [String: Any] = [ + "name": "TestUnknownTargetKey", + "targets": [ + "App": [ + "type": "application", + "platform": "iOS", + "sources": ["Sources/App"], + "resources": ["Resources"], + ], + ], + ] + try? Yams.dump(object: dictionary).write(toFile: path.string, atomically: true, encoding: .utf8) + let specLoader = SpecLoader(version: "1.1.0") + do { + _ = try specLoader.loadProject(path: path) + } catch { + throw failure("loadProject should not throw for unknown keys. Got: \(error)") + } + do { + try specLoader.validateProjectDictionaryWarnings() + throw failure("Expected a validation error for the unknown 'resources' key") + } catch let error as SpecValidationError { + let expected = SpecValidationError.ValidationError.unknownTargetKey(target: "App", key: "resources") + try expect(error.errors.contains(expected)) == true + } catch { + throw failure("Unexpected error type: \(error)") + } + } } }