diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ed53d222..05a3115b30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,11 @@ [Ornithologist Coder](https://github.com/ornithocoder) [#1987](https://github.com/realm/SwiftLint/issues/1987) +* Adds `prefixed_toplevel_constant` opt-in rule which encourages top-level + constants to be prefixed by `k`. + [Ornithologist Coder](https://github.com/ornithocoder) + [#1907](https://github.com/realm/SwiftLint/issues/1907) + ##### Bug Fixes * Fix false positives in `control_statement` rule when methods with keyword diff --git a/Rules.md b/Rules.md index a345a70e45..3cded3e549 100644 --- a/Rules.md +++ b/Rules.md @@ -75,6 +75,7 @@ * [Overridden methods call super](#overridden-methods-call-super) * [Override in Extension](#override-in-extension) * [Pattern Matching Keywords](#pattern-matching-keywords) +* [Prefixed Top-Level Constant](#prefixed-top-level-constant) * [Private Actions](#private-actions) * [Private Outlets](#private-outlets) * [Private over fileprivate](#private-over-fileprivate) @@ -8960,6 +8961,85 @@ switch foo { +## Prefixed Top-Level Constant + +Identifier | Enabled by default | Supports autocorrection | Kind +--- | --- | --- | --- +`prefixed_toplevel_constant` | Disabled | No | style + +Top-level constants should be prefixed by `k`. + +### Examples + +
+Non Triggering Examples + +```swift +private let kFoo = 20.0 +``` + +```swift +public let kFoo = false +``` + +```swift +internal let kFoo = "Foo" +``` + +```swift +let kFoo = true +``` + +```swift +struct Foo { + let bar = 20.0 +} +``` + +```swift +private var foo = 20.0 +``` + +```swift +public var foo = false +``` + +```swift +internal var foo = "Foo" +``` + +```swift +var foo = true +``` + +
+
+Triggering Examples + +```swift +private let ↓Foo = 20.0 +``` + +```swift +public let ↓Foo = false +``` + +```swift +internal let ↓Foo = "Foo" +``` + +```swift +let ↓Foo = true +``` + +```swift +let ↓foo = 2, ↓bar = true +``` + +
+ + + ## Private Actions Identifier | Enabled by default | Supports autocorrection | Kind diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 97b8aa1b6e..47e9495c11 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -83,6 +83,7 @@ public let masterRuleList = RuleList(rules: [ OverriddenSuperCallRule.self, OverrideInExtensionRule.self, PatternMatchingKeywordsRule.self, + PrefixedTopLevelConstantRule.self, PrivateActionRule.self, PrivateOutletRule.self, PrivateOverFilePrivateRule.self, diff --git a/Source/SwiftLintFramework/Rules/PrefixedTopLevelConstantRule.swift b/Source/SwiftLintFramework/Rules/PrefixedTopLevelConstantRule.swift new file mode 100644 index 0000000000..11038e7c64 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/PrefixedTopLevelConstantRule.swift @@ -0,0 +1,62 @@ +// +// PrefixedConstantRule.swift +// SwiftLint +// +// Created by Ornithologist Coder on 1/5/18. +// Copyright © 2018 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct PrefixedTopLevelConstantRule: ASTRule, OptInRule, ConfigurationProviderRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "prefixed_toplevel_constant", + name: "Prefixed Top-Level Constant", + description: "Top-level constants should be prefixed by `k`.", + kind: .style, + nonTriggeringExamples: [ + "private let kFoo = 20.0", + "public let kFoo = false", + "internal let kFoo = \"Foo\"", + "let kFoo = true", + "struct Foo {\n" + + " let bar = 20.0\n" + + "}", + "private var foo = 20.0", + "public var foo = false", + "internal var foo = \"Foo\"", + "var foo = true" + ], + triggeringExamples: [ + "private let ↓Foo = 20.0", + "public let ↓Foo = false", + "internal let ↓Foo = \"Foo\"", + "let ↓Foo = true", + "let ↓foo = 2, ↓bar = true" + ] + ) + + public func validate(file: File, + kind: SwiftDeclarationKind, + dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { + guard + kind == .varGlobal, + let offset = dictionary.offset, + let nameOffset = dictionary.nameOffset, + let name = dictionary.name, + let content = file.contents.bridge().substringWithByteRange(start: offset, length: nameOffset - offset), + content.hasPrefix("let") && !name.hasPrefix("k") + else { + return [] + } + + return [StyleViolation(ruleDescription: type(of: self).description, + severity: configuration.severity, + location: Location(file: file, byteOffset: nameOffset))] + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 1d56303ca1..161613df16 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ 629C60D91F43906700B4AF92 /* SingleTestClassRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */; }; 62A498561F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */; }; 62A6E7931F3317E3003A0479 /* JoinedDefaultRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */; }; + 62DADC481FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DADC471FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift */; }; 62DEA1661FB21A9E00BCCCC6 /* PrivateActionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DEA1651FB21A9E00BCCCC6 /* PrivateActionRule.swift */; }; 67932E2D1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */; }; 67EB4DFA1E4CC111004E9ACD /* CyclomaticComplexityConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4DF81E4CC101004E9ACD /* CyclomaticComplexityConfiguration.swift */; }; @@ -430,6 +431,7 @@ 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitConfiguration.swift; sourceTree = ""; }; 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedDefaultRule.swift; sourceTree = ""; }; 62AF35D71F30B183009B11EE /* DiscouragedDirectInitRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRuleTests.swift; sourceTree = ""; }; + 62DADC471FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefixedTopLevelConstantRule.swift; sourceTree = ""; }; 62DEA1651FB21A9E00BCCCC6 /* PrivateActionRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateActionRule.swift; sourceTree = ""; }; 62E54FED1F93AD57005B367B /* QuickDiscouragedFocusedTestRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDiscouragedFocusedTestRule.swift; sourceTree = ""; }; 65454F451B14D73800319A6C /* ControlStatementRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlStatementRule.swift; sourceTree = ""; }; @@ -1092,6 +1094,7 @@ 78F032441D7C877800BE709A /* OverriddenSuperCallRule.swift */, D40FE89C1F867BFF006433E2 /* OverrideInExtensionRule.swift */, D403A4A21F4DB5510020CA02 /* PatternMatchingKeywordsRule.swift */, + 62DADC471FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift */, 62DEA1651FB21A9E00BCCCC6 /* PrivateActionRule.swift */, 094385021D5D4F78009168CF /* PrivateOutletRule.swift */, 1E3C2D701EE36C6F00C8386D /* PrivateOverFilePrivateRule.swift */, @@ -1557,6 +1560,7 @@ 57ED827B1CF656E3002B3513 /* JUnitReporter.swift in Sources */, D43B04691E072291004016AF /* ColonConfiguration.swift in Sources */, E82367E01ED3BD1E0040A88E /* Configuration+Cache.swift in Sources */, + 62DADC481FFF0423002B6319 /* PrefixedTopLevelConstantRule.swift in Sources */, D4130D991E16CC1300242361 /* TypeNameRuleExamples.swift in Sources */, 24E17F721B14BB3F008195BE /* File+Cache.swift in Sources */, 47ACC8981E7DC74E0088EEB2 /* ImplicitlyUnwrappedOptionalConfiguration.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 2c928abac0..abd1ebf63b 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -438,6 +438,7 @@ extension RulesTests { ("testOperatorUsageWhitespace", testOperatorUsageWhitespace), ("testOverrideInExtension", testOverrideInExtension), ("testPatternMatchingKeywords", testPatternMatchingKeywords), + ("testPrefixedTopLevelConstant", testPrefixedTopLevelConstant), ("testPrivateAction", testPrivateAction), ("testPrivateOutlet", testPrivateOutlet), ("testPrivateUnitTest", testPrivateUnitTest), diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index 0e7eeef313..e8e7bec673 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -259,6 +259,10 @@ class RulesTests: XCTestCase { verifyRule(PatternMatchingKeywordsRule.description) } + func testPrefixedTopLevelConstant() { + verifyRule(PrefixedTopLevelConstantRule.description) + } + func testPrivateAction() { verifyRule(PrivateActionRule.description) }