Files
SwiftLint/Source/SwiftLintFramework/Rules/CommaRule.swift
T
Norio Nomura 40828dff03 Merge branch 'master' into swift3.0
* master: (41 commits)
  Fix formatting in CHANGELOG.md
  release 0.13.0
  Update CHANGELOG.md
  Fix check for trailing whitespace to return early
  Fix checks for some inline comments
  Replace check for comments to use SyntaxKind
  Add configuration for trailing_whitespace to ignore comments
  Unwanted space removed
  - Lint issues fixed
  Updated HTML Reporter
  PR feedback
  Add check on autocorrect for disabled range
  Use `utf8.count` instead of `utf16.count` to byte range
  Re-write `ExplicitInitRule` to `ASTRule`
  added ExplicitInitRule
  Updated CHANGELOG
  HTML Reporter added
  HTML Reporter added
  Adds information about SwiftLint plugin for AppCode into README.md
  added reasons why a new rule should be opt in
  ...

# Conflicts:
#	Source/SwiftLintFramework/Extensions/File+SwiftLint.swift
#	Source/SwiftLintFramework/Extensions/Structure+SwiftLint.swift
#	Source/SwiftLintFramework/Rules/ColonRule.swift
#	Source/SwiftLintFramework/Rules/CommaRule.swift
#	Source/SwiftLintFramework/Rules/LegacyCGGeometryFunctionsRule.swift
#	Source/SwiftLintFramework/Rules/LegacyConstantRule.swift
#	Source/SwiftLintFramework/Rules/LegacyConstructorRule.swift
#	Source/SwiftLintFramework/Rules/LegacyNSGeometryFunctionsRule.swift
#	Source/SwiftLintFramework/Rules/LineLengthRule.swift
#	Source/SwiftLintFramework/Rules/OperatorFunctionWhitespaceRule.swift
#	Source/SwiftLintFramework/Rules/ReturnArrowWhitespaceRule.swift
#	Source/SwiftLintFramework/Rules/RuleConfigurations/StatementPositionConfiguration.swift
#	Source/SwiftLintFramework/Rules/StatementPositionRule.swift
#	Source/SwiftLintFramework/Rules/TrailingWhitespaceRule.swift
#	Tests/SwiftLintFramework/RuleConfigurationTests.swift
2016-11-04 21:40:56 +09:00

141 lines
6.0 KiB
Swift

//
// Comma.swift
// SwiftLint
//
// Created by Alex Culeva on 10/22/15.
// Copyright © 2015 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct CommaRule: CorrectableRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.Warning)
public init() {}
public static let description = RuleDescription(
identifier: "comma",
name: "Comma Spacing",
description: "There should be no space before and one after any comma.",
nonTriggeringExamples: [
"func abc(a: String, b: String) { }",
"abc(a: \"string\", b: \"string\"",
"enum a { case a, b, c }",
"func abc(\n a: String, // comment\n bcd: String // comment\n) {\n}\n",
"func abc(\n a: String,\n bcd: String\n) {\n}\n",
],
triggeringExamples: [
"func abc(a: String↓ ,b: String) { }",
"abc(a: \"string\"↓,b: \"string\"",
"enum a { case a↓ ,b }",
"let result = plus(\n first: 3↓ , // #683\n second: 4\n)\n",
],
corrections: [
"func abc(a: String,b: String) {}\n": "func abc(a: String, b: String) {}\n",
"abc(a: \"string\",b: \"string\"\n": "abc(a: \"string\", b: \"string\"\n",
"abc(a: \"string\" , b: \"string\"\n": "abc(a: \"string\", b: \"string\"\n",
"enum a { case a ,b }\n": "enum a { case a, b }\n",
"let a = [1,1]\nlet b = 1\nf(1, b)\n": "let a = [1, 1]\nlet b = 1\nf(1, b)\n",
]
)
public func validateFile(_ file: File) -> [StyleViolation] {
return violationRangesInFile(file).map {
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, characterOffset: $0.location))
}
}
public func correctFile(_ file: File) -> [Correction] {
let violations = violationRangesInFile(file)
let matches = file.ruleEnabledViolatingRanges(violations, forRule: self)
if matches.isEmpty { return [] }
var contents = file.contents as NSString
let description = type(of: self).description
var corrections = [Correction]()
for range in matches.reversed() {
contents = contents.replacingCharacters(in: range, with: ", ") as NSString
let location = Location(file: file, characterOffset: range.location)
corrections.append(Correction(ruleDescription: description, location: location))
}
file.write(contents as String)
return corrections
}
// captures spaces and comma only
// http://userguide.icu-project.org/strings/regexp
fileprivate static let pattern =
"\\S" + // not whitespace
"(" + // start first capure
"\\s+" + // followed by whitespace
"," + // to the left of a comma
"[\\t\\p{Z}]*" + // followed by any amount of tab or space.
"|" + // or
"," + // immediately followed by a comma
"(?:[\\t\\p{Z}]{0}|" + // followed by 0
"[\\t\\p{Z}]{2,})" + // or 2+ tab or space characters.
")" + // end capture
"(\\S)" // second capture is not whitespace.
// swiftlint:disable:next force_try
fileprivate static let regularExpression = try! NSRegularExpression(pattern: pattern)
fileprivate static let excludingSyntaxKindsForFirstCapture = SyntaxKind.commentAndStringKinds()
.map { $0.rawValue }
fileprivate static let excludingSyntaxKindsForSecondCapture = SyntaxKind.commentKinds()
.map { $0.rawValue }
fileprivate func violationRangesInFile(_ file: File) -> [NSRange] {
let contents = file.contents
let range = NSRange(location: 0, length: contents.utf16.count)
let syntaxMap = file.syntaxMap
return CommaRule.regularExpression
.matches(in: contents, options: [], range: range)
.flatMap { match -> NSRange? in
if match.numberOfRanges != 3 { return nil }
// check first captured range
let firstRange = match.rangeAt(1)
guard let matchByteFirstRange = contents
.NSRangeToByteRange(start: firstRange.location, length: firstRange.length)
else { return nil }
// first captured range won't match tokens if it is not comment neither string
let tokensInFirstRange = syntaxMap.tokensIn(matchByteFirstRange)
.filter { CommaRule.excludingSyntaxKindsForFirstCapture.contains($0.type) }
// If not empty, first captured range is comment or string
if !tokensInFirstRange.isEmpty {
return nil
}
// If the first range does not start with comma, it already violates this rule
// no matter what is contained in the second range.
if !(contents as NSString).substring(with: firstRange).hasPrefix(",") {
return firstRange
}
// check second captured range
let secondRange = match.rangeAt(2)
guard let matchByteSecondRange = contents
.NSRangeToByteRange(start: secondRange.location, length: secondRange.length)
else { return nil }
// second captured range won't match tokens if it is not comment
let tokensInSecondRange = syntaxMap.tokensIn(matchByteSecondRange)
.filter { CommaRule.excludingSyntaxKindsForSecondCapture.contains($0.type) }
// If not empty, second captured range is comment
if !tokensInSecondRange.isEmpty {
return nil
}
// return first captured range
return firstRange
}
}
}