diff --git a/CHANGELOG.md b/CHANGELOG.md index 405e939bd..424f4465a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ found issues. [krin-san](https://github.com/krin-san) [#3177](https://github.com/realm/SwiftLint/pull/3177) +* Add opt-in `ibinspectable_in_extension` rule to lint against `@IBInspectable` + properties in `extensions` + [Keith Smiley](https://github.com/keith) * Add `computed_accessors_order` rule to validate the order of `get` and `set` accessors in computed properties and subscripts. diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 78e8bd657..09134193f 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -69,6 +69,7 @@ public let masterRuleList = RuleList(rules: [ FunctionDefaultParameterAtEndRule.self, FunctionParameterCountRule.self, GenericTypeNameRule.self, + IBInspectableInExtensionRule.self, IdenticalOperandsRule.self, IdentifierNameRule.self, ImplicitGetterRule.self, diff --git a/Source/SwiftLintFramework/Rules/Lint/IBInspectableInExtensionRule.swift b/Source/SwiftLintFramework/Rules/Lint/IBInspectableInExtensionRule.swift new file mode 100644 index 000000000..972ae36d8 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/Lint/IBInspectableInExtensionRule.swift @@ -0,0 +1,52 @@ +import SourceKittenFramework + +public struct IBInspectableInExtensionRule: ConfigurationProviderRule, OptInRule, AutomaticTestableRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "ibinspectable_in_extension", + name: "IBInspectable in Extension", + description: "Extensions shouldn't add @IBInspectable properties.", + kind: .lint, + nonTriggeringExamples: [ + Example(""" + class Foo { + @IBInspectable private var x: Int + } + """) + ], + triggeringExamples: [ + Example(""" + extension Foo { + @IBInspectable private var x: Int + } + """) + ] + ) + + public func validate(file: SwiftLintFile) -> [StyleViolation] { + let collector = NamespaceCollector(dictionary: file.structureDictionary) + let elements = collector.findAllElements(of: [.extension]) + + return elements + .flatMap { element in + return element.dictionary.substructure.compactMap { element -> ByteCount? in + guard element.declarationKind == .varInstance, + element.enclosedSwiftAttributes.contains(.ibinspectable), + let offset = element.offset + else { + return nil + } + + return offset + } + } + .map { + StyleViolation(ruleDescription: type(of: self).description, + severity: configuration.severity, + location: Location(file: file, byteOffset: $0)) + } + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 186672171..e611ff400 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -247,6 +247,7 @@ C25EBBE521078DCE00E27603 /* Glob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25EBBE321078DC700E27603 /* Glob.swift */; }; C26330382073DAC500D7B4FD /* LowerACLThanParentRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26330352073DAA200D7B4FD /* LowerACLThanParentRule.swift */; }; C28B2B3D2106DF730009A0FE /* PrefixedConstantRuleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28B2B3B2106DF210009A0FE /* PrefixedConstantRuleConfiguration.swift */; }; + C2A8D076243C0D0300642BC9 /* IBInspectableInExtensionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A8D075243C0D0300642BC9 /* IBInspectableInExtensionRule.swift */; }; C2B3C1612106F78C00088928 /* ConfigurationAliasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */; }; C328A2F71E6759AE00A9E4D7 /* ExplicitTypeInterfaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328A2F51E67595500A9E4D7 /* ExplicitTypeInterfaceRule.swift */; }; C3D23F1D21E3A33700E9BD1B /* UnusedControlFlowLabelRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D7320C21E15ED4001C07D9 /* UnusedControlFlowLabelRule.swift */; }; @@ -765,6 +766,7 @@ C25EBBE321078DC700E27603 /* Glob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Glob.swift; sourceTree = ""; }; C26330352073DAA200D7B4FD /* LowerACLThanParentRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LowerACLThanParentRule.swift; sourceTree = ""; }; C28B2B3B2106DF210009A0FE /* PrefixedConstantRuleConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefixedConstantRuleConfiguration.swift; sourceTree = ""; }; + C2A8D075243C0D0300642BC9 /* IBInspectableInExtensionRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBInspectableInExtensionRule.swift; sourceTree = ""; }; C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationAliasesTests.swift; sourceTree = ""; }; C328A2F51E67595500A9E4D7 /* ExplicitTypeInterfaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitTypeInterfaceRule.swift; sourceTree = ""; }; C3DE5DAA1E7DF99B00761483 /* FatalErrorMessageRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FatalErrorMessageRule.swift; sourceTree = ""; }; @@ -1198,6 +1200,7 @@ 62A3E95B209E078000547A86 /* EmptyXCTestMethodRule.swift */, 626B01B420A1735900D2C42F /* EmptyXCTestMethodRuleExamples.swift */, 7723A4DE23442D7100F38590 /* RawValueForCamelCasedCodableEnumRule.swift */, + C2A8D075243C0D0300642BC9 /* IBInspectableInExtensionRule.swift */, D4E92D1E2137B4C9002EDD48 /* IdenticalOperandsRule.swift */, D4441A27213279950020896F /* InertDeferRule.swift */, C26330352073DAA200D7B4FD /* LowerACLThanParentRule.swift */, @@ -2208,6 +2211,7 @@ C3D23F1D21E3A33700E9BD1B /* UnusedControlFlowLabelRule.swift in Sources */, 55CE0585231899100023BA72 /* ContainsOverRangeNilComparisonRule.swift in Sources */, 6C1D763221A4E69600DEF783 /* Request+DisableSourceKit.swift in Sources */, + C2A8D076243C0D0300642BC9 /* IBInspectableInExtensionRule.swift in Sources */, 47ACC8981E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift in Sources */, 787CDE39208E7D41005F3D2F /* SwitchCaseAlignmentConfiguration.swift in Sources */, D450D1DD21F199F700E60010 /* TrailingClosureConfiguration.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index cbce0abeb..545dfdcb9 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -664,6 +664,12 @@ extension GlobTests { ] } +extension IBInspectableInExtensionRuleTests { + static var allTests: [(String, (IBInspectableInExtensionRuleTests) -> () throws -> Void)] = [ + ("testWithDefaultConfiguration", testWithDefaultConfiguration) + ] +} + extension IdenticalOperandsRuleTests { static var allTests: [(String, (IdenticalOperandsRuleTests) -> () throws -> Void)] = [ ("testWithDefaultConfiguration", testWithDefaultConfiguration) @@ -1761,6 +1767,7 @@ XCTMain([ testCase(FunctionParameterCountRuleTests.allTests), testCase(GenericTypeNameRuleTests.allTests), testCase(GlobTests.allTests), + testCase(IBInspectableInExtensionRuleTests.allTests), testCase(IdenticalOperandsRuleTests.allTests), testCase(IdentifierNameRuleTests.allTests), testCase(ImplicitGetterRuleTests.allTests), diff --git a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift index b24e9b225..198c53ae7 100644 --- a/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift +++ b/Tests/SwiftLintFrameworkTests/AutomaticRuleTests.generated.swift @@ -264,6 +264,12 @@ class FunctionDefaultParameterAtEndRuleTests: XCTestCase { } } +class IBInspectableInExtensionRuleTests: XCTestCase { + func testWithDefaultConfiguration() { + verifyRule(IBInspectableInExtensionRule.description) + } +} + class IdenticalOperandsRuleTests: XCTestCase { func testWithDefaultConfiguration() { verifyRule(IdenticalOperandsRule.description)