Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

variable_name rule should extend to all identifiers #954

Merged
merged 20 commits into from
Feb 10, 2017
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,10 @@ This release has seen a phenomenal uptake in community contributions!

##### Enhancements

* None.
* `variable_name` rule is now `identifier_name` as it validates other
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now in the wrong section

identifiers as well.
[Marcelo Fabri](https://github.com/marcelofabri)
[#663](https://github.com/realm/SwiftLint/issues/663)

##### Bug Fixes

Expand Down
10 changes: 0 additions & 10 deletions Source/SwiftLintFramework/Extensions/File+SwiftLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,6 @@ extension File {
return matches.filter { !$0.intersects(exclusionRanges) }
}

internal func validateVariableName(_ dictionary: [String: SourceKitRepresentable],
kind: SwiftDeclarationKind) -> (name: String, offset: Int)? {
guard let name = dictionary.name,
let offset = dictionary.offset,
SwiftDeclarationKind.variableKinds().contains(kind) && !name.hasPrefix("$") else {
return nil
}
return (name.nameStrippingLeadingUnderscoreIfPrivate(dictionary), offset)
}

internal func append(_ string: String) {
guard let stringData = string.data(using: .utf8) else {
fatalError("can't encode '\(string)' with UTF8")
Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public let masterRuleList = RuleList(rules:
FunctionBodyLengthRule.self,
FunctionParameterCountRule.self,
GenericTypeNameRule.self,
IdentifierNameRule.self,
ImplicitGetterRule.self,
LargeTupleRule.self,
LeadingWhitespaceRule.self,
Expand Down Expand Up @@ -142,7 +143,6 @@ public let masterRuleList = RuleList(rules:
UnusedOptionalBindingRule.self,
ValidDocsRule.self,
ValidIBInspectableRule.self,
VariableNameRule.self,
VerticalParameterAlignmentRule.self,
VerticalWhitespaceRule.self,
VoidReturnRule.self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct CyclomaticComplexityRule: ASTRule, ConfigurationProviderRule {

public func validate(file: File, kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
if !SwiftDeclarationKind.functionKinds().contains(kind) {
guard SwiftDeclarationKind.functionKinds().contains(kind) else {
return []
}

Expand Down
139 changes: 139 additions & 0 deletions Source/SwiftLintFramework/Rules/IdentifierNameRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//
// IdentifierNameRule.swift
// SwiftLint
//
// Created by JP Simard on 5/16/15.
// Copyright © 2015 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct IdentifierNameRule: ASTRule, ConfigurationProviderRule {

public var configuration = NameConfiguration(minLengthWarning: 3,
minLengthError: 2,
maxLengthWarning: 40,
maxLengthError: 60)

public init() {}

public static let description = RuleDescription(
identifier: "identifier_name",
name: "Identifier Name",
description: "Identifier names should only contain alphanumeric characters and " +
"start with a lowercase character or should only contain capital letters. " +
"In an exception to the above, variable names may start with a capital letter " +
"when they are declared static and immutable. Variable names should not be too " +
"long or too short.",
nonTriggeringExamples: IdentifierNameRuleExamples.swift3NonTriggeringExamples,
triggeringExamples: IdentifierNameRuleExamples.swift3TriggeringExamples,
deprecatedAliases: ["variable_name"]
)

public func validate(file: File, kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard !dictionary.enclosedSwiftAttributes.contains("source.decl.attribute.override") else {
return []
}

return validateName(dictionary: dictionary, kind: kind).map { name, offset in
guard !configuration.excluded.contains(name) else {
return []
}

let isFunction = SwiftDeclarationKind.functionKinds().contains(kind)
let description = type(of: self).description

let type = self.type(for: kind)
if !isFunction {
if !CharacterSet.alphanumerics.isSuperset(ofCharactersIn: name) {
return [
StyleViolation(ruleDescription: description,
severity: .error,
location: Location(file: file, byteOffset: offset),
reason: "\(type) name should only contain alphanumeric " +
"characters: '\(name)'")
]
}

if let severity = severity(forLength: name.characters.count) {
let reason = "\(type) name should be between " +
"\(configuration.minLengthThreshold) and " +
"\(configuration.maxLengthThreshold) characters long: '\(name)'"
return [
StyleViolation(ruleDescription: type(of: self).description,
severity: severity,
location: Location(file: file, byteOffset: offset),
reason: reason)
]
}
}

if kind != .varStatic && name.isViolatingCase && !name.isOperator {
let reason = "\(type) name should start with a lowercase character: '\(name)'"
return [
StyleViolation(ruleDescription: description,
severity: .error,
location: Location(file: file, byteOffset: offset),
reason: reason)
]
}

return []
} ?? []
}

private func validateName(dictionary: [String: SourceKitRepresentable],
kind: SwiftDeclarationKind) -> (name: String, offset: Int)? {
guard let name = dictionary.name,
let offset = dictionary.offset,
kinds(for: .current).contains(kind),
!name.hasPrefix("$") else {
return nil
}

return (name.nameStrippingLeadingUnderscoreIfPrivate(dictionary), offset)
}

private func kinds(for version: SwiftVersion) -> [SwiftDeclarationKind] {
let common = SwiftDeclarationKind.variableKinds() + SwiftDeclarationKind.functionKinds()
switch version {
case .two:
return common
case .three:
return common + [.enumelement]
}
}

private func type(for kind: SwiftDeclarationKind) -> String {
if SwiftDeclarationKind.functionKinds().contains(kind) {
return "Function"
} else if kind == .enumelement {
return "Enum element"
} else {
return "Variable"
}
}
}

fileprivate extension String {
var isViolatingCase: Bool {
let secondIndex = characters.index(after: startIndex)
let firstCharacter = substring(to: secondIndex)
guard firstCharacter.isUppercase() else {
return false
}
guard characters.count > 1 else {
return true
}
let range = secondIndex..<characters.index(after: secondIndex)
let secondCharacter = substring(with: range)
return secondCharacter.isLowercase()
}

var isOperator: Bool {
let operators = ["/", "=", "-", "+", "!", "*", "|", "^", "~", "?", ".", "%", "<", ">", "&"]
return !operators.filter(hasPrefix).isEmpty
}
}
51 changes: 51 additions & 0 deletions Source/SwiftLintFramework/Rules/IdentifierNameRuleExamples.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// IdentifierNameRuleExamples.swift
// SwiftLint
//
// Created by Marcelo Fabri on 12/30/16.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation

internal struct IdentifierNameRuleExamples {
private static let commonNonTriggeringExamples = [
"let myLet = 0",
"var myVar = 0",
"private let _myLet = 0",
"class Abc { static let MyLet = 0 }",
"let URL: NSURL? = nil",
"let XMLString: String? = nil",
"override var i = 0",
"enum Foo { case myEnum }",
"func isOperator(name: String) -> Bool",
"func typeForKind(_ kind: SwiftDeclarationKind) -> String",
"func == (lhs: SyntaxToken, rhs: SyntaxToken) -> Bool",
"override func IsOperator(name: String) -> Bool"
]

static let swift2NonTriggeringExamples = commonNonTriggeringExamples + [
"enum Foo { case MyEnum }"
]

static let swift3NonTriggeringExamples = commonNonTriggeringExamples

private static let commonTriggeringExamples = [
"↓let MyLet = 0",
"↓let _myLet = 0",
"private ↓let myLet_ = 0",
"↓let myExtremelyVeryVeryVeryVeryVeryVeryLongLet = 0",
"↓var myExtremelyVeryVeryVeryVeryVeryVeryLongVar = 0",
"private ↓let _myExtremelyVeryVeryVeryVeryVeryVeryLongLet = 0",
"↓let i = 0",
"↓var id = 0",
"private ↓let _i = 0",
"↓func IsOperator(name: String) -> Bool"
]

static let swift2TriggeringExamples = commonTriggeringExamples

static let swift3TriggeringExamples = commonTriggeringExamples + [
"enum Foo { case ↓MyEnum }"
]
}
36 changes: 18 additions & 18 deletions Source/SwiftLintFramework/Rules/MissingDocsRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ extension File {
}

public enum AccessControlLevel: String, CustomStringConvertible {
case Private = "source.lang.swift.accessibility.private"
case FilePrivate = "source.lang.swift.accessibility.fileprivate"
case Internal = "source.lang.swift.accessibility.internal"
case Public = "source.lang.swift.accessibility.public"
case Open = "source.lang.swift.accessibility.open"
case `private` = "source.lang.swift.accessibility.private"
case `fileprivate` = "source.lang.swift.accessibility.fileprivate"
case `internal` = "source.lang.swift.accessibility.internal"
case `public` = "source.lang.swift.accessibility.public"
case `open` = "source.lang.swift.accessibility.open"

internal init?(description value: String) {
switch value {
case "private": self = .Private
case "fileprivate": self = .FilePrivate
case "internal": self = .Internal
case "public": self = .Public
case "open": self = .Open
case "private": self = .private
case "fileprivate": self = .fileprivate
case "internal": self = .internal
case "public": self = .public
case "open": self = .open
default: return nil
}
}
Expand All @@ -75,17 +75,17 @@ public enum AccessControlLevel: String, CustomStringConvertible {

public var description: String {
switch self {
case .Private: return "private"
case .FilePrivate: return "fileprivate"
case .Internal: return "internal"
case .Public: return "public"
case .Open: return "open"
case .private: return "private"
case .fileprivate: return "fileprivate"
case .internal: return "internal"
case .public: return "public"
case .open: return "open"
}
}

// Returns true if is `private` or `fileprivate`
var isPrivate: Bool {
return self == .Private || self == .FilePrivate
return self == .private || self == .fileprivate
}

}
Expand All @@ -106,8 +106,8 @@ public struct MissingDocsRule: OptInRule {
}

public init() {
parameters = [RuleParameter(severity: .warning, value: .Public),
RuleParameter(severity: .warning, value: .Open)]
parameters = [RuleParameter(severity: .warning, value: .public),
RuleParameter(severity: .warning, value: .open)]
}

public let parameters: [RuleParameter<AccessControlLevel>]
Expand Down
Loading