Files
SwiftLint/Source/SwiftLintFramework/Rules/PrefixedTopLevelConstantRule.swift
T
2018-01-05 06:48:54 +01:00

96 lines
3.0 KiB
Swift

//
// 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)
private let topLevelPrefix = "k"
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",
"var foo = true, bar = true",
"var foo = true, let kFoo = true",
"let\n" +
" kFoo = true"
],
triggeringExamples: [
"private let ↓Foo = 20.0",
"public let ↓Foo = false",
"internal let ↓Foo = \"Foo\"",
"let ↓Foo = true",
"let ↓foo = 2, ↓bar = true",
"var foo = true, let ↓Foo = true",
"let\n" +
" ↓foo = 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
else {
return []
}
let range = NSRange(location: offset, length: nameOffset - offset)
guard isDeclaredAsConstant(in: range, on: file) && !name.hasPrefix(topLevelPrefix) else {
return []
}
return [
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: nameOffset))
]
}
private func isDeclaredAsConstant(in range: NSRange, on file: File) -> Bool {
let tokens = file.syntaxMap.tokens(inByteRange: range)
let contents = file.contents.bridge()
let letKeywords = tokens.filter { token in
guard
SyntaxKind(rawValue: token.type) == .keyword,
let keyword = contents.substringWithByteRange(start: token.offset, length: token.length)
else {
return false
}
return keyword == "let"
}
return !letKeywords.isEmpty
}
}