forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLiteralExpressionEndIdentationRule.swift
106 lines (96 loc) · 3.6 KB
/
LiteralExpressionEndIdentationRule.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//
// LiteralExpressionEndIdentationRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 10/02/17.
// Copyright © 2017 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct LiteralExpressionEndIdentationRule: ASTRule, ConfigurationProviderRule, OptInRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
identifier: "literal_expression_end_indentation",
name: "Literal Expression End Indentation",
description: "Array and dictionary literal end should have the same indentation as the line that started it.",
kind: .style,
nonTriggeringExamples: [
"[1, 2, 3]",
"[1,\n" +
" 2\n" +
"]",
"[\n" +
" 1,\n" +
" 2\n" +
"]",
"[\n" +
" 1,\n" +
" 2]\n",
" let x = [\n" +
" 1,\n" +
" 2\n" +
" ]",
"[key: 2, key2: 3]",
"[key: 1,\n" +
" key2: 2\n" +
"]",
"[\n" +
" key: 0,\n" +
" key2: 20\n" +
"]"
],
triggeringExamples: [
"let x = [\n" +
" 1,\n" +
" 2\n" +
" ↓]",
" let x = [\n" +
" 1,\n" +
" 2\n" +
"↓]",
"let x = [\n" +
" key: value\n" +
" ↓]"
]
)
private static let notWhitespace = regex("[^\\s]")
public func validate(file: File, kind: SwiftExpressionKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard kind == .dictionary || kind == .array else {
return []
}
let elements = dictionary.elements.filter { $0.kind == "source.lang.swift.structure.elem.expr" }
let contents = file.contents.bridge()
guard !elements.isEmpty,
let offset = dictionary.offset,
let length = dictionary.length,
let (startLine, _) = contents.lineAndCharacter(forByteOffset: offset),
let firstParamOffset = elements[0].offset,
let (firstParamLine, _) = contents.lineAndCharacter(forByteOffset: firstParamOffset),
startLine != firstParamLine,
let lastParamOffset = elements.last?.offset,
let (lastParamLine, _) = contents.lineAndCharacter(forByteOffset: lastParamOffset),
case let endOffset = offset + length - 1,
let (endLine, endPosition) = contents.lineAndCharacter(forByteOffset: endOffset),
lastParamLine != endLine else {
return []
}
let range = file.lines[startLine - 1].range
let regex = LiteralExpressionEndIdentationRule.notWhitespace
let actual = endPosition - 1
guard let match = regex.firstMatch(in: file.contents, options: [], range: range)?.range,
case let expected = match.location - range.location,
expected != actual else {
return []
}
let reason = "\(LiteralExpressionEndIdentationRule.description.description) " +
"Expected \(expected), got \(actual)."
return [
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: endOffset),
reason: reason)
]
}
}