Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
The modern replacement is safer, cleaner, Retina-aware and more performant.
[Dimitri Dupuis-Latour](https://github.com/DimDL)
[#6268](https://github.com/realm/SwiftLint/issues/6268)

* Add `prefer_scaled_to_fit` opt-in rule to prefer `scaledToFit()` and
`scaledToFill()` over `aspectRatio(contentMode:)` with a constant
content mode.
[DemiDevv](https://github.com/DemiDevv)
[#5713](https://github.com/realm/SwiftLint/issues/5713)

* Support access level modifiers on imports in `unused_imports` rule.
[SimplyDanny](https://github.com/SimplyDanny)
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ public let builtInRules: [any Rule.Type] = [
PreferConditionListRule.self,
PreferKeyPathRule.self,
PreferNimbleRule.self,
PreferScaledToFitRule.self,
PreferSelfInStaticReferencesRule.self,
PreferSelfTypeOverTypeOfSelfRule.self,
PreferTypeCheckingRule.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import SwiftLintCore
import SwiftSyntax

@SwiftSyntaxRule(optIn: true)
struct PreferScaledToFitRule: Rule {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few Legacy... rules already. I wonder whether we should name this one in a similar way. What do you think?

All of them are enabled by default (not opt-in) and support auto-correction using LegacyFunctionVisitor.

var configuration = SeverityConfiguration<Self>(.warning)

static let description = RuleDescription(
identifier: "prefer_scaled_to_fit",
name: "Prefer Scaled To Fit",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title style:

Suggested change
name: "Prefer Scaled To Fit",
name: "Prefer Scaled to Fit",

description: "Prefer `scaledToFit()` or `scaledToFill()` over " +
"`aspectRatio(contentMode:)` with a constant content mode",
kind: .idiomatic,
nonTriggeringExamples: [
Example("view.aspectRatio(ratio, contentMode: .fit)"),
Example("view.aspectRatio(ratio, contentMode: .fill)"),
Example("view.aspectRatio(contentMode: contentMode)"),
Example("view.aspectRatio(contentMode: shouldFit ? .fit : .fill)"),
Example("view.scaledToFit()"),
Example("view.scaledToFill()"),
],
triggeringExamples: [
Example("view.↓aspectRatio(contentMode: .fit)"),
Example("view.↓aspectRatio(contentMode: .fill)"),
Example("↓aspectRatio(contentMode: .fit)"),
Example("↓aspectRatio(contentMode: .fill)"),
]
)
}

private extension PreferScaledToFitRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override func visitPost(_ node: FunctionCallExprSyntax) {
let functionName: String
let violationPosition: AbsolutePosition

if let memberAccess = node.calledExpression.as(MemberAccessExprSyntax.self) {
functionName = memberAccess.declName.baseName.text
violationPosition = memberAccess.declName.baseName.positionAfterSkippingLeadingTrivia
} else if let declRef = node.calledExpression.as(DeclReferenceExprSyntax.self) {
functionName = declRef.baseName.text
violationPosition = declRef.baseName.positionAfterSkippingLeadingTrivia
} else {
return
}

guard functionName == "aspectRatio" else {
return
}

guard
node.arguments.count == 1,
let argument = node.arguments.first,
Comment on lines +52 to +53

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
node.arguments.count == 1,
let argument = node.arguments.first,
let argument = node.arguments.onlyElement,

argument.label?.text == "contentMode"
else {
return
}

guard
let memberValue = argument.expression.as(MemberAccessExprSyntax.self),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check that base is nil or ContentMode.

let valueName = memberValue.declName.baseName.text as String?,
valueName == "fit" || valueName == "fill"
Comment on lines +61 to +62

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let valueName = memberValue.declName.baseName.text as String?,
valueName == "fit" || valueName == "fill"
["fit, "fill"].contains(memberValue.declName.baseName.text)

else {
return
}

violations.append(violationPosition)
}
}
}
12 changes: 6 additions & 6 deletions Tests/GeneratedTests/GeneratedTests_07.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ final class PreferNimbleRuleGeneratedTests: SwiftLintTestCase {
}
}

final class PreferScaledToFitRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(PreferScaledToFitRule.description)
}
}

final class PreferSelfInStaticReferencesRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(PreferSelfInStaticReferencesRule.description)
Expand Down Expand Up @@ -150,9 +156,3 @@ final class RawValueForCamelCasedCodableEnumRuleGeneratedTests: SwiftLintTestCas
verifyRule(RawValueForCamelCasedCodableEnumRule.description)
}
}

final class ReduceBooleanRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(ReduceBooleanRule.description)
}
}
12 changes: 6 additions & 6 deletions Tests/GeneratedTests/GeneratedTests_08.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers

final class ReduceBooleanRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(ReduceBooleanRule.description)
}
}

final class ReduceIntoRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(ReduceIntoRule.description)
Expand Down Expand Up @@ -150,9 +156,3 @@ final class SortedImportsRuleGeneratedTests: SwiftLintTestCase {
verifyRule(SortedImportsRule.description)
}
}

final class StatementPositionRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(StatementPositionRule.description)
}
}
12 changes: 6 additions & 6 deletions Tests/GeneratedTests/GeneratedTests_09.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers

final class StatementPositionRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(StatementPositionRule.description)
}
}

final class StaticOperatorRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(StaticOperatorRule.description)
Expand Down Expand Up @@ -150,9 +156,3 @@ final class UnneededBreakInSwitchRuleGeneratedTests: SwiftLintTestCase {
verifyRule(UnneededBreakInSwitchRule.description)
}
}

final class UnneededEscapingRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnneededEscapingRule.description)
}
}
12 changes: 6 additions & 6 deletions Tests/GeneratedTests/GeneratedTests_10.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers

final class UnneededEscapingRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnneededEscapingRule.description)
}
}

final class UnneededOverrideRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnneededOverrideRule.description)
Expand Down Expand Up @@ -150,9 +156,3 @@ final class VoidReturnRuleGeneratedTests: SwiftLintTestCase {
verifyRule(VoidReturnRule.description)
}
}

final class WeakDelegateRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(WeakDelegateRule.description)
}
}
6 changes: 6 additions & 0 deletions Tests/GeneratedTests/GeneratedTests_11.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers

final class WeakDelegateRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(WeakDelegateRule.description)
}
}

final class XCTFailMessageRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(XCTFailMessageRule.description)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,11 @@ prefer_nimble:
meta:
opt-in: true
correctable: false
prefer_scaled_to_fit:
severity: warning
meta:
opt-in: true
correctable: false
prefer_self_in_static_references:
severity: warning
meta:
Expand Down
Loading