Skip to content

Commit

Permalink
Support type casting on configuration option values defined by enviro…
Browse files Browse the repository at this point in the history
…nment variables (#5860)
  • Loading branch information
SimplyDanny authored Nov 17, 2024
1 parent 025b1e7 commit 2c7e723
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 27 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
[Martin Redington](https://github.com/mildm8nnered)
[#5801](https://github.com/realm/SwiftLint/issues/5801)

* Support type casting on configuration option values defined by environment variables.
Without a cast, these values would always be treated as strings leading to a potentially
invalid configuration.
[SimplyDanny](https://github.com/SimplyDanny)
[#5774](https://github.com/realm/SwiftLint/issues/5774)

* The `redundant_type_annotation` rule gains a new option,
`ignore_properties`, that skips enforcement on members in a
type declaration (like a `struct`). This helps the rule coexist with
Expand Down
42 changes: 15 additions & 27 deletions Source/SwiftLintCore/Models/YamlParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,40 +31,28 @@ private extension Constructor {

static func customScalarMap(env: [String: String]) -> ScalarMap {
var map = defaultScalarMap
map[.str] = String.constructExpandingEnvVars(env: env)
map[.bool] = Bool.constructUsingOnlyTrueAndFalse

map[.str] = { $0.string.expandingEnvVars(env: env) }
map[.bool] = {
switch $0.string.expandingEnvVars(env: env).lowercased() {
case "true": true
case "false": false
default: nil
}
}
map[.int] = { Int($0.string.expandingEnvVars(env: env)) }
map[.float] = { Double($0.string.expandingEnvVars(env: env)) }
return map
}
}

private extension String {
static func constructExpandingEnvVars(env: [String: String]) -> (_ scalar: Node.Scalar) -> String? {
{ (scalar: Node.Scalar) -> String? in
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)
guard contains("${") else {
// No environment variables used.
return self
}

return result
}
}

private extension Bool {
// swiftlint:disable:next discouraged_optional_boolean
static func constructUsingOnlyTrueAndFalse(from scalar: Node.Scalar) -> Bool? {
switch scalar.string.lowercased() {
case "true":
return true
case "false":
return false
default:
return nil
return env.reduce(into: self) { result, envVar in
result = result.replacingOccurrences(of: "${\(envVar.key)}", with: envVar.value)
}
}
}
44 changes: 44 additions & 0 deletions Tests/SwiftLintFrameworkTests/YamlParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,48 @@ final class YamlParserTests: SwiftLintTestCase {
_ = try YamlParser.parse("|\na", env: [:])
}
}

func testTreatAllEnvVarsAsStringsWithoutCasting() throws {
let env = [
"INT": "1",
"FLOAT": "1.0",
"BOOL": "true",
"STRING": "string",
]
let string = """
int: ${INT}
float: ${FLOAT}
bool: ${BOOL}
string: ${STRING}
"""

let result = try YamlParser.parse(string, env: env)

XCTAssertEqual(result["int"] as? String, "1")
XCTAssertEqual(result["float"] as? String, "1.0")
XCTAssertEqual(result["bool"] as? String, "true")
XCTAssertEqual(result["string"] as? String, "string")
}

func testRespectCastsOnEnvVars() throws {
let env = [
"INT": "1",
"FLOAT": "1.0",
"BOOL": "true",
"STRING": "string",
]
let string = """
int: !!int ${INT}
float: !!float ${FLOAT}
bool: !!bool ${BOOL}
string: !!str ${STRING}
"""

let result = try YamlParser.parse(string, env: env)

XCTAssertEqual(result["int"] as? Int, 1)
XCTAssertEqual(result["float"] as? Double, 1.0)
XCTAssertEqual(result["bool"] as? Bool, true)
XCTAssertEqual(result["string"] as? String, "string")
}
}

0 comments on commit 2c7e723

Please sign in to comment.