diff --git a/CHANGELOG.md b/CHANGELOG.md index 77731ad48..c5d5109d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ ### Enhancements +* Add `include_variables` configuration option to `non_optional_string_data_conversion` rule. + When enabled, the rule will trigger on variables, properties, and function calls in addition + to string literals. Defaults to `false` for backward compatibility. + [SimplyDanny](https://github.com/SimplyDanny) + [#6094](https://github.com/realm/SwiftLint/issues/6094) + * Add Sendable conformance to Rule.Type for building with Swift 6. [erikkerber](https://github.com/erikkerber) [#issue_number](https://github.com/realm/SwiftLint/issues/issue_number) diff --git a/Source/SwiftLintBuiltInRules/Rules/Lint/NonOptionalStringDataConversionRule.swift b/Source/SwiftLintBuiltInRules/Rules/Lint/NonOptionalStringDataConversionRule.swift index 514171737..bb690ef81 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Lint/NonOptionalStringDataConversionRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Lint/NonOptionalStringDataConversionRule.swift @@ -1,18 +1,35 @@ +import SwiftLintCore import SwiftSyntax @SwiftSyntaxRule struct NonOptionalStringDataConversionRule: Rule { - var configuration = SeverityConfiguration(.warning) + var configuration = NonOptionalStringDataConversionConfiguration() + + private static let variablesIncluded = ["include_variables": true] + static let description = RuleDescription( identifier: "non_optional_string_data_conversion", name: "Non-optional String -> Data Conversion", description: "Prefer non-optional `Data(_:)` initializer when converting `String` to `Data`", kind: .lint, nonTriggeringExamples: [ - Example("Data(\"foo\".utf8)") + Example("Data(\"foo\".utf8)"), + Example("Data(string.utf8)"), + Example("\"foo\".data(using: .ascii)"), + Example("string.data(using: .unicode)"), + Example("Data(\"foo\".utf8)", configuration: variablesIncluded), + Example("Data(string.utf8)", configuration: variablesIncluded), + Example("\"foo\".data(using: .ascii)", configuration: variablesIncluded), + Example("string.data(using: .unicode)", configuration: variablesIncluded), ], triggeringExamples: [ - Example("\"foo\".data(using: .utf8)") + Example("↓\"foo\".data(using: .utf8)"), + Example("↓\"foo\".data(using: .utf8)", configuration: variablesIncluded), + Example("↓string.data(using: .utf8)", configuration: variablesIncluded), + Example("↓property.data(using: .utf8)", configuration: variablesIncluded), + Example("↓obj.property.data(using: .utf8)", configuration: variablesIncluded), + Example("↓getString().data(using: .utf8)", configuration: variablesIncluded), + Example("↓getValue()?.data(using: .utf8)", configuration: variablesIncluded), ] ) } @@ -20,12 +37,13 @@ struct NonOptionalStringDataConversionRule: Rule { private extension NonOptionalStringDataConversionRule { final class Visitor: ViolationsSyntaxVisitor { override func visitPost(_ node: MemberAccessExprSyntax) { - if node.base?.is(StringLiteralExprSyntax.self) == true, - node.declName.baseName.text == "data", + if node.declName.baseName.text == "data", let parent = node.parent?.as(FunctionCallExprSyntax.self), let argument = parent.arguments.onlyElement, argument.label?.text == "using", - argument.expression.as(MemberAccessExprSyntax.self)?.isUTF8 == true { + argument.expression.as(MemberAccessExprSyntax.self)?.isUTF8 == true, + let base = node.base, + base.is(StringLiteralExprSyntax.self) || configuration.includeVariables { violations.append(node.positionAfterSkippingLeadingTrivia) } } diff --git a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/NonOptionalStringDataConversionConfiguration.swift b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/NonOptionalStringDataConversionConfiguration.swift new file mode 100644 index 000000000..ef21f1ac3 --- /dev/null +++ b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/NonOptionalStringDataConversionConfiguration.swift @@ -0,0 +1,12 @@ +import SwiftLintCore + +@AutoConfigParser +struct NonOptionalStringDataConversionConfiguration: SeverityBasedRuleConfiguration { + // swiftlint:disable:previous type_name + typealias Parent = NonOptionalStringDataConversionRule + + @ConfigurationElement(key: "severity") + private(set) var severityConfiguration = SeverityConfiguration(.warning) + @ConfigurationElement(key: "include_variables") + private(set) var includeVariables = false +} diff --git a/Tests/IntegrationTests/default_rule_configurations.yml b/Tests/IntegrationTests/default_rule_configurations.yml index aaeffb97b..094ad89c0 100644 --- a/Tests/IntegrationTests/default_rule_configurations.yml +++ b/Tests/IntegrationTests/default_rule_configurations.yml @@ -737,6 +737,7 @@ no_space_in_method_call: correctable: true non_optional_string_data_conversion: severity: warning + include_variables: false meta: opt-in: false correctable: false