Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@
[Marcelo Fabri](https://github.com/marcelofabri)
[#1719](https://github.com/realm/SwiftLint/issues/1719)

* Allow using environment variables in a configuration file in the form of
`${SOME_VARIABLE}`. The variables will be expanded when the configuration
is first loaded.
[Marcelo Fabri](https://github.com/marcelofabri)
[#1512](https://github.com/realm/SwiftLint/issues/1512)

* Treat `yes`, `no`, `on` and `off` as strings (and not booleans) when loading
configuration files.
[Marcelo Fabri](https://github.com/marcelofabri)
[#1424](https://github.com/realm/SwiftLint/issues/1424)

##### Bug Fixes

* Fix false positive on `redundant_discardable_let` rule when using
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@ identifier_name:
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji)
```

You can also use environment variables in your configuration file,
by using `${SOME_VARIABLE}` in a string.

#### Defining Custom Rules

You can define custom regex-based rules in you configuration file using the
Expand Down
67 changes: 60 additions & 7 deletions Source/SwiftLintFramework/Models/YamlParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,87 @@
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation
import Yams

// MARK: - YamlParsingError

internal enum YamlParserError: Error, Equatable {
case yamlParsing(String)
case yamlFlattening
}

internal func == (lhs: YamlParserError, rhs: YamlParserError) -> Bool {
switch (lhs, rhs) {
case (.yamlFlattening, .yamlFlattening):
return true
case (.yamlParsing(let x), .yamlParsing(let y)):
return x == y
default:
return false
}
}

// MARK: - YamlParser

public struct YamlParser {
public static func parse(_ yaml: String) throws -> [String: Any] {
public static func parse(_ yaml: String,
env: [String: String] = ProcessInfo.processInfo.environment) throws -> [String: Any] {
do {
return try Yams.load(yaml: yaml) as? [String: Any] ?? [:]
return try Yams.load(yaml: yaml, .default,
.swiftlintContructor(env: env)) as? [String: Any] ?? [:]
} catch {
throw YamlParserError.yamlParsing("\(error)")
}
}
}

private extension Constructor {
static func swiftlintContructor(env: [String: String]) -> Constructor {
return Constructor(customMap(env: env))
}

static func customMap(env: [String: String]) -> Map {
var map = defaultMap
map[.str] = String.constructExpandingEnvVars(env: env)
map[.bool] = Bool.constructUsingOnlyTrueAndFalse

return map
}
}

private extension String {
static func constructExpandingEnvVars(env: [String: String]) -> (_ node: Node) -> String? {
return { (node: Node) -> String? in
assert(node.isScalar)
return node.scalar!.string.expandingEnvVars(env: env)
}
}

func expandingEnvVars(env: [String: String]) -> String {
var result = self
for (key, value) in env {
result = result.replacingOccurrences(of: "${\(key)}", with: value)
}

return result
}
}

private extension Bool {
static func constructUsingOnlyTrueAndFalse(from node: Node) -> Bool? {
assert(node.isScalar)
switch node.scalar!.string.lowercased() {
case "true":
return true
case "false":
return false
default:
return nil
}
}
}

private extension Node {
var isScalar: Bool {
if case .scalar = self {
return true
}
return false
}
}
5 changes: 5 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,11 @@ extension YamlParserTests {
static var allTests: [(String, (YamlParserTests) -> () throws -> Void)] = [
("testParseEmptyString", testParseEmptyString),
("testParseValidString", testParseValidString),
("testParseReplacesEnvVar", testParseReplacesEnvVar),
("testParseTreatNoAsString", testParseTreatNoAsString),
("testParseTreatYesAsString", testParseTreatYesAsString),
("testParseTreatOnAsString", testParseTreatOnAsString),
("testParseTreatOffAsString", testParseTreatOffAsString),
("testParseInvalidStringThrows", testParseInvalidStringThrows)
]
}
Expand Down
42 changes: 39 additions & 3 deletions Tests/SwiftLintFrameworkTests/YamlParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,54 @@ import XCTest
class YamlParserTests: XCTestCase {

func testParseEmptyString() {
XCTAssertEqual((try YamlParser.parse("")).count, 0,
XCTAssertEqual((try YamlParser.parse("", env: [:])).count, 0,
"Parsing empty YAML string should succeed")
}

func testParseValidString() {
XCTAssertEqual(try YamlParser.parse("a: 1\nb: 2").count, 2,
XCTAssertEqual(try YamlParser.parse("a: 1\nb: 2", env: [:]).count, 2,
"Parsing valid YAML string should succeed")
}

func testParseReplacesEnvVar() throws {
let env = ["PROJECT_NAME": "SwiftLint"]
let string = "excluded:\n - ${PROJECT_NAME}/Extensions"
let result = try YamlParser.parse(string, env: env)

XCTAssertEqual(result["excluded"] as? [String] ?? [], ["SwiftLint/Extensions"])
}

func testParseTreatNoAsString() throws {
let string = "excluded:\n - no"
let result = try YamlParser.parse(string, env: [:])

XCTAssertEqual(result["excluded"] as? [String] ?? [], ["no"])
}

func testParseTreatYesAsString() throws {
let string = "excluded:\n - yes"
let result = try YamlParser.parse(string, env: [:])

XCTAssertEqual(result["excluded"] as? [String] ?? [], ["yes"])
}

func testParseTreatOnAsString() throws {
let string = "excluded:\n - on"
let result = try YamlParser.parse(string, env: [:])

XCTAssertEqual(result["excluded"] as? [String] ?? [], ["on"])
}

func testParseTreatOffAsString() throws {
let string = "excluded:\n - off"
let result = try YamlParser.parse(string, env: [:])

XCTAssertEqual(result["excluded"] as? [String] ?? [], ["off"])
}

func testParseInvalidStringThrows() {
checkError(YamlParserError.yamlParsing("2:1: error: parser: did not find expected <document start>:\na\n^")) {
_ = try YamlParser.parse("|\na")
_ = try YamlParser.parse("|\na", env: [:])
}
}
}