Skip to content

Commit

Permalink
Merge pull request #1035 from marcelofabri/vertical-parameter-alignme…
Browse files Browse the repository at this point in the history
…nt-rule

Vertical parameter alignment rule
  • Loading branch information
marcelofabri authored Dec 23, 2016
2 parents 1bc6c9d + 7937438 commit 1e896a3
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 4 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

##### Enhancements

* None.
* Add `vertical_parameter_alignment` rule that checks if parameters are
vertically aligned for multi-line function declarations.
[Marcelo Fabri](https://github.com/marcelofabri)
[#1033](https://github.com/realm/SwiftLint/issues/1033)

##### Bug Fixes

Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public let masterRuleList = RuleList(rules:
ValidDocsRule.self,
ValidIBInspectableRule.self,
VariableNameRule.self,
VerticalParameterAlignmentRule.self,
VerticalWhitespaceRule.self,
VoidReturnRule.self,
WeakDelegateRule.self
Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/LineLengthRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public struct LineLengthRule: ConfigurationProviderRule, SourceKitFreeRule {
///
/// - returns: sourceString with the given literals replaced by `#`
private func stripLiterals(fromSourceString sourceString: String,
withDelimiter delimiter: String) -> String {
withDelimiter delimiter: String) -> String {
var modifiedString = sourceString

// While copy of content contains literal, replace with a single character
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// VerticalParameterAlignmentRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 12/22/16.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct VerticalParameterAlignmentRule: ASTRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "vertical_parameter_alignment",
name: "Vertical Parameter Alignment",
description: "Function parameters should be aligned vertically if they're in multiple lines in a declaration.",
nonTriggeringExamples: [
"func validateFunction(_ file: File, kind: SwiftDeclarationKind,\n" +
" dictionary: [String: SourceKitRepresentable]) { }\n",
"func validateFunction(_ file: File, kind: SwiftDeclarationKind,\n" +
" dictionary: [String: SourceKitRepresentable]) -> [StyleViolation]\n",
"func foo(bar: Int)\n",
"func foo(bar: Int) -> String \n",
"func validateFunction(_ file: File, kind: SwiftDeclarationKind,\n" +
" dictionary: [String: SourceKitRepresentable])\n" +
" -> [StyleViolation]\n",
"func validateFunction(\n" +
" _ file: File, kind: SwiftDeclarationKind,\n" +
" dictionary: [String: SourceKitRepresentable]) -> [StyleViolation]\n"
],
triggeringExamples: [
"func validateFunction(_ file: File, kind: SwiftDeclarationKind,\n" +
" ↓dictionary: [String: SourceKitRepresentable]) { }\n",
"func validateFunction(_ file: File, kind: SwiftDeclarationKind,\n" +
" ↓dictionary: [String: SourceKitRepresentable]) { }\n",
"func validateFunction(_ file: File,\n" +
" ↓kind: SwiftDeclarationKind,\n" +
" ↓dictionary: [String: SourceKitRepresentable]) { }\n"
]
)

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

let contents = file.contents.bridge()
let pattern = "\\(\\s*(\\S)"

guard let nameOffset = (dictionary["key.nameoffset"] as? Int64).flatMap({ Int($0) }),
let nameLength = (dictionary["key.namelength"] as? Int64).flatMap({ Int($0) }),
let nameRange = contents.byteRangeToNSRange(start: nameOffset, length: nameLength),
let paramStart = regex(pattern).firstMatch(in: file.contents,
options: [], range: nameRange)?.rangeAt(1).location,
let (startLine, startCharacter) = contents.lineAndCharacter(forCharacterOffset: paramStart),
let (endLine, _) = contents.lineAndCharacter(forByteOffset: nameOffset + nameLength - 1),
endLine > startLine else {
return []
}

let linesRange = (startLine + 1)...endLine
let violationLocations = linesRange.flatMap { lineIndex -> Int? in
let line = file.lines[lineIndex - 1]
guard let paramLocation = regex("\\S").firstMatch(in: file.contents, options: [],
range: line.range)?.range.location,
let (_, paramCharacter) = contents.lineAndCharacter(forCharacterOffset: paramLocation),
paramCharacter != startCharacter else {
return nil
}

return paramLocation
}

return violationLocations.map {
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, characterOffset: $0))
}
}
}
8 changes: 6 additions & 2 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@
D42D2B381E09CC0D00CD7A2E /* FirstWhereRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */; };
D4348EEA1C46122C007707FB /* FunctionBodyLengthRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */; };
D43B04641E0620AB004016AF /* UnusedEnumeratedRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */; };
D43B046B1E075905004016AF /* ClosureEndIndentationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */; };
D43B04661E071ED3004016AF /* ColonRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04651E071ED3004016AF /* ColonRuleTests.swift */; };
D43B04691E072291004016AF /* ColonConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B04671E07228D004016AF /* ColonConfiguration.swift */; };
D43B046B1E075905004016AF /* ClosureEndIndentationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */; };
D43DB1081DC573DA00281215 /* ImplicitGetterRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */; };
D44254201DB87CA200492EA4 /* ValidIBInspectableRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */; };
D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */; };
Expand All @@ -108,6 +108,7 @@
D48AE2CC1DFB58C5001C6A4A /* AttributesRulesExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48AE2CB1DFB58C5001C6A4A /* AttributesRulesExamples.swift */; };
D4998DE71DF191380006E05D /* AttributesRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4998DE61DF191380006E05D /* AttributesRuleTests.swift */; };
D4998DE91DF194F20006E05D /* FileHeaderRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */; };
D4B0226F1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B0226E1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift */; };
D4C4A34C1DEA4FF000E0E04C /* AttributesConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C4A34A1DEA4FD700E0E04C /* AttributesConfiguration.swift */; };
D4C4A34E1DEA877200E0E04C /* FileHeaderRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C4A34D1DEA877200E0E04C /* FileHeaderRule.swift */; };
D4C4A3521DEFBBB700E0E04C /* FileHeaderConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */; };
Expand Down Expand Up @@ -327,9 +328,9 @@
D42D2B371E09CC0D00CD7A2E /* FirstWhereRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstWhereRule.swift; sourceTree = "<group>"; };
D4348EE91C46122C007707FB /* FunctionBodyLengthRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionBodyLengthRuleTests.swift; sourceTree = "<group>"; };
D43B04631E0620AB004016AF /* UnusedEnumeratedRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedEnumeratedRule.swift; sourceTree = "<group>"; };
D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureEndIndentationRule.swift; sourceTree = "<group>"; };
D43B04651E071ED3004016AF /* ColonRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColonRuleTests.swift; sourceTree = "<group>"; };
D43B04671E07228D004016AF /* ColonConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColonConfiguration.swift; sourceTree = "<group>"; };
D43B046A1E075905004016AF /* ClosureEndIndentationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureEndIndentationRule.swift; sourceTree = "<group>"; };
D43DB1071DC573DA00281215 /* ImplicitGetterRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitGetterRule.swift; sourceTree = "<group>"; };
D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidIBInspectableRule.swift; sourceTree = "<group>"; };
D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntacticSugarRule.swift; sourceTree = "<group>"; };
Expand All @@ -346,6 +347,7 @@
D48AE2CB1DFB58C5001C6A4A /* AttributesRulesExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributesRulesExamples.swift; sourceTree = "<group>"; };
D4998DE61DF191380006E05D /* AttributesRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributesRuleTests.swift; sourceTree = "<group>"; };
D4998DE81DF194F20006E05D /* FileHeaderRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderRuleTests.swift; sourceTree = "<group>"; };
D4B0226E1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalParameterAlignmentRule.swift; sourceTree = "<group>"; };
D4C4A34A1DEA4FD700E0E04C /* AttributesConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributesConfiguration.swift; sourceTree = "<group>"; };
D4C4A34D1DEA877200E0E04C /* FileHeaderRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderRule.swift; sourceTree = "<group>"; };
D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderConfiguration.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -775,6 +777,7 @@
E81CDE701C00FEAA00B430F6 /* ValidDocsRule.swift */,
D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */,
E88DEA931B099C0900A66CB0 /* VariableNameRule.swift */,
D4B0226E1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift */,
1EC163511D5992D900DD2928 /* VerticalWhitespaceRule.swift */,
D47079AE1DFE520000027086 /* VoidReturnRule.swift */,
094384FF1D5D2382009168CF /* WeakDelegateRule.swift */,
Expand Down Expand Up @@ -1091,6 +1094,7 @@
D46252541DF63FB200BE2CA1 /* NumberSeparatorRule.swift in Sources */,
E315B83C1DFA4BC500621B44 /* DynamicInlineRule.swift in Sources */,
D42D2B381E09CC0D00CD7A2E /* FirstWhereRule.swift in Sources */,
D4B0226F1E0C75F9007E5297 /* VerticalParameterAlignmentRule.swift in Sources */,
D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */,
E88198441BEA93D200333A11 /* ColonRule.swift in Sources */,
E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ class RulesTests: XCTestCase {
verifyRule(VariableNameRule.description)
}

func testVerticalParameterAlignment() {
verifyRule(VerticalParameterAlignmentRule.description)
}

func testVoidReturn() {
verifyRule(VoidReturnRule.description)
}
Expand Down Expand Up @@ -402,6 +406,7 @@ extension RulesTests {
("testUnusedEnumerated", testUnusedEnumerated),
("testValidIBInspectable", testValidIBInspectable),
// ("testVariableName", testVariableName),
("VerticalParameterAlignmentRule", testVerticalParameterAlignment),
("testVoidReturn", testVoidReturn),
("testSuperCall", testSuperCall),
("testWeakDelegate", testWeakDelegate)
Expand Down

0 comments on commit 1e896a3

Please sign in to comment.