-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from jduigoudev/feature/ESOB007-Animation-free
Feature/esob007 animation free
- Loading branch information
Showing
15 changed files
with
356 additions
and
2 deletions.
There are no files selected for viewing
60 changes: 60 additions & 0 deletions
60
swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/AnimationFreeCheck.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* ecoCode iOS plugin - Help the earth, adopt this green plugin for your applications | ||
* Copyright © 2023 green-code-initiative (https://www.ecocode.io/) | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package io.ecocode.ios.swift.checks.sobriety; | ||
|
||
import io.ecocode.ios.swift.SwiftRuleCheck; | ||
import io.ecocode.ios.swift.antlr.generated.Swift5Parser; | ||
import org.antlr.v4.runtime.tree.ParseTree; | ||
import org.sonar.check.Rule; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
|
||
/** | ||
* Check the use of "UIScreen.main.brightness" and triggers when set. | ||
*/ | ||
@Rule(key = "ESOB007") | ||
public class AnimationFreeCheck extends SwiftRuleCheck { | ||
|
||
private static final String DEFAULT_ISSUE_MESSAGE = "Usage of Animations must absolutely be avoided"; | ||
|
||
private static final List<String> ANIMATION_METHODS = Arrays.asList("UIView.animate", "UIView.animateKeyframes", | ||
"UIView.transition", "CABasicAnimation", "CAKeyframeAnimation", "CATransition"); | ||
|
||
private static final List<String> SWIFTUI_ANIMATION_METHODS = Arrays.asList("withAnimation", "Animation", | ||
"AnyTransition"); | ||
|
||
|
||
public void apply(ParseTree tree) { | ||
if (tree instanceof Swift5Parser.ExpressionContext) { | ||
Swift5Parser.ExpressionContext id = (Swift5Parser.ExpressionContext) tree; | ||
String expressionText = id.getText(); | ||
|
||
boolean containsAnimationMethod = ANIMATION_METHODS.stream() | ||
.anyMatch(expressionText::contains); | ||
|
||
boolean containsUISwiftAnimationMethod = SWIFTUI_ANIMATION_METHODS.stream() | ||
.anyMatch(expressionText::contains); | ||
|
||
if (containsAnimationMethod || containsUISwiftAnimationMethod) { | ||
this.recordIssue(id.getStart().getStartIndex(), DEFAULT_ISSUE_MESSAGE); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
"ESOB002", | ||
"ESOB003", | ||
"ESOB005", | ||
"ESOB006" | ||
"ESOB006", | ||
"ESOB007" | ||
] | ||
} |
26 changes: 26 additions & 0 deletions
26
swift-lang/src/main/resources/io/ecocode/rules/swift/ESOB007.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<img src="http://www.neomades.com/extern/partage/ecoCode/2sur5_1x.png"> | ||
<p>Animations play a crucial role in creating a dynamic and engaging user interface in iOS applications. Both UIKit and SwiftUI provide powerful tools to create smooth and appealing animations. Developers need to be familiar with various methods and properties to implement animations effectively.</p> | ||
<h2>Animation Methods in UIKit and SwiftUI</h2> | ||
<h2>Noncompliant Code Example UIKit</h2> | ||
<ul> | ||
<li><code>UIView.animate(withDuration:animations:)</code></li> | ||
<li><code>UIView.animate(withDuration:animations:completion:)</code></li> | ||
<li><code>UIView.animate(withDuration:delay:options:animations:completion:)</code></li> | ||
<li><code>UIView.animateKeyframes(withDuration:delay:options:animations:completion:)</code></li> | ||
<li><code>UIView.transition(with:duration:options:animations:completion:)</code></li> | ||
<li><code>CABasicAnimation</code></li> | ||
<li><code>CAKeyframeAnimation</code></li> | ||
<li><code>CATransition</code></li> | ||
</ul> | ||
<h2>Noncompliant Code Example SwiftUI</h2> | ||
<ul> | ||
<li><code>withAnimation</code></li> | ||
<li><code>Animation</code></li> | ||
<li><code>AnyTransition</code></li> | ||
<li><code>.animation(Animation?)</code></li> | ||
<li><code>.transition(AnyTransition)</code></li> | ||
<li><code>.onAppear(perform:)</code></li> | ||
<li><code>.onDisappear(perform:)</code></li> | ||
</ul> | ||
<p>To ensure animations are used effectively without draining the device's battery, it's important to check for unnecessary animations and optimize them. This can be done by reviewing the use of functions like <code>withAnimation(::)</code>, the <code>animation(_:value:)</code> view modifier, and the <code>binding’s animation(_:) method</code> in SwiftUI, as well as checking the use of UIKit's animation methods.</p> | ||
This revised HTML content now focuses on the animation functionalities provided by UIKit and SwiftUI, which are essential for iOS app development. The original content related to screen brightness has |
19 changes: 19 additions & 0 deletions
19
swift-lang/src/main/resources/io/ecocode/rules/swift/ESOB007.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"key": "ESOB007", | ||
"title": "Animation Methods in UIKit and SwiftUI", | ||
"defaultSeverity": "Major", | ||
"description": "Animations play a crucial role in creating a dynamic and engaging user interface in iOS applications. Both UIKit and SwiftUI provide powerful tools to create smooth and appealing animations. Developers need to be familiar with various methods and properties to implement animations effectively.\n\nAnimation Methods in UIKit and SwiftUI\n\nNoncompliant Code Example UIKit\n\n- UIView.animate(withDuration:animations:)\n- UIView.animate(withDuration:animations:completion:)\n- UIView.animate(withDuration:delay:options:animations:completion:)\n- UIView.animateKeyframes(withDuration:delay:options:animations:completion:)\n- UIView.transition(with:duration:options:animations:completion:)\n- CABasicAnimation\n- CAKeyframeAnimation\n- CATransition\n\nNoncompliant Code Example SwiftUI\n\n- withAnimation\n- Animation\n- AnyTransition\n- .animation(Animation?)\n- .transition(AnyTransition)\n- .onAppear(perform:)\n- .onDisappear(perform:)\n\nTo ensure animations are used effectively without draining the device's battery, it's important to check for unnecessary animations and optimize them. This can be done by reviewing the use of functions like withAnimation(::), the animation(_:value:) view modifier, and the binding’s animation(_:) method in SwiftUI, as well as checking the use of UIKit's animation methods.", | ||
"status": "ready", | ||
"remediation": { | ||
"func": "Constant/Issue", | ||
"constantCost": "5min" | ||
}, | ||
"tags": [ | ||
"sobriety", | ||
"environment", | ||
"ecocode", | ||
"eco-design" | ||
], | ||
"type": "CODE_SMELL" | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/AnimationFreeCheckTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* ecoCode iOS plugin - Help the earth, adopt this green plugin for your applications | ||
* Copyright © 2023 green-code-initiative (https://www.ecocode.io/) | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package io.ecocode.ios.swift.checks.sobriety; | ||
|
||
import io.ecocode.ios.swift.checks.CheckTestHelper; | ||
|
||
import org.junit.Test; | ||
import org.sonar.api.batch.sensor.internal.SensorContextTester; | ||
import org.sonar.api.batch.sensor.issue.Issue; | ||
import org.sonar.api.batch.sensor.issue.IssueLocation; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class AnimationFreeCheckTest { | ||
|
||
// @Test | ||
// public void shouldTriggerOnAnyTransition() { | ||
// assertAnimationFreeIssue("checks/sobriety/AnimationFreeCheck_AnyTransition_trigger.swift", 11); | ||
// } | ||
|
||
@Test | ||
public void shouldTriggerOnCAKeyframeAnimation() { | ||
assertAnimationFreeIssue("checks/sobriety/AnimationFreeCheck_CAKeyframeAnimation_trigger.swift", 11); | ||
} | ||
|
||
@Test | ||
public void shouldTriggerOnCABasicAnimation() { | ||
assertAnimationFreeIssue("checks/sobriety/AnimationFreeCheck_CABasicAnimation_trigger.swift", 11); | ||
} | ||
|
||
@Test | ||
public void shouldTriggerOnCATransition() { | ||
assertAnimationFreeIssue("checks/sobriety/AnimationFreeCheck_CATransition_trigger.swift", 11); | ||
} | ||
|
||
@Test | ||
public void shouldTriggerOnUIViewAnimate() { | ||
assertAnimationFreeIssue("checks/sobriety/AnimationFreeCheck_UIViewAnimate_trigger.swift", 11); | ||
} | ||
|
||
@Test | ||
public void shouldTriggerOnUIViewAnimateKeyframes() { | ||
assertAnimationFreeIssue("checks/sobriety/AnimationFreeCheck_UIViewAnimateKeyframes_trigger.swift", 11); | ||
} | ||
|
||
@Test | ||
public void shouldTriggerOnUIViewTransition() { | ||
assertAnimationFreeIssue("checks/sobriety/AnimationFreeCheck_UIViewTransition_trigger.swift", 11); | ||
} | ||
|
||
@Test | ||
public void shouldTriggerOnWithAnimation() { | ||
assertAnimationFreeIssue("checks/sobriety/AnimationFreeCheck_WithAnimation_trigger.swift", 10); | ||
} | ||
|
||
@Test | ||
public void shouldNoDetectAnimationUsage() { | ||
SensorContextTester context = CheckTestHelper.analyzeTestFile("checks/sobriety/AnimationFreeCheck_no_trigger.swift"); | ||
assertThat(context.allIssues()).isEmpty(); | ||
} | ||
|
||
private void assertAnimationFreeIssue(String file, int line) { | ||
SensorContextTester context = CheckTestHelper.analyzeTestFile(file); | ||
assertThat(context.allIssues()).hasSize(1) | ||
.allSatisfy(issue -> { | ||
assertIssue(issue, line); | ||
}); | ||
} | ||
|
||
private void assertIssue(Issue issue, int line) { | ||
assertThat(issue.ruleKey().rule()).isEqualTo("ESOB007"); | ||
assertThat(issue.ruleKey().repository()).isEqualTo("ecoCode-swift"); | ||
IssueLocation location = issue.primaryLocation(); | ||
assertThat(location.textRange().start().line()).isEqualTo(line); | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
swift-lang/src/test/resources/checks/sobriety/AnimationFreeCheck_AnyTransition_trigger.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
|
||
// Should trigger | ||
AnyTransition.opacity.animation(.easeInOut(duration: 0.5)) | ||
|
||
return true | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...lang/src/test/resources/checks/sobriety/AnimationFreeCheck_CABasicAnimation_trigger.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
|
||
// Should trigger | ||
let animation = CABasicAnimations(keyPath: "opacity") | ||
animation.fromValue = 1.0 | ||
animation.toValue = 0.0 | ||
animation.duration = 1.0 | ||
view.layer.add(animation, forKey: "opacity") | ||
|
||
return true | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...g/src/test/resources/checks/sobriety/AnimationFreeCheck_CAKeyframeAnimation_trigger.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
|
||
// Should trigger | ||
let animation = CAKeyframeAnimation(keyPath: "position") | ||
animation.values = [NSValue(cgPoint: CGPoint(x: 0, y: 0)), NSValue(cgPoint: CGPoint(x: 50, y: 50)), NSValue(cgPoint: CGPoint(x: 100, y: 0)), NSValue(cgPoint: CGPoint(x: 0, y: 0))] | ||
animation.duration = 2.0 | ||
animation.repeatCount = .infinity | ||
view.layer.add(animation, forKey: "position") | ||
|
||
return true | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
swift-lang/src/test/resources/checks/sobriety/AnimationFreeCheck_CATransition_trigger.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
|
||
// Should trigger | ||
let transition = CATransition() | ||
transition.type = .push | ||
transition.subtype = .fromRight | ||
transition.duration = 0.5 | ||
|
||
return true | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
...rc/test/resources/checks/sobriety/AnimationFreeCheck_UIViewAnimateKeyframes_trigger.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
|
||
// Should trigger | ||
UIView.animateKeyframes(withDuration: 2.0, delay: 0.0, options: [], animations: { | ||
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25) { | ||
view.transform = CGAffineTransform(scaleX: 1.1, y: 1.1) | ||
} | ||
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.5) { | ||
view.transform = CGAffineTransform(rotationAngle: .pi) | ||
} | ||
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) { | ||
view.transform = CGAffineTransform.identity | ||
} | ||
}, completion: nil) | ||
return true | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
swift-lang/src/test/resources/checks/sobriety/AnimationFreeCheck_UIViewAnimate_trigger.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
|
||
// Should trigger | ||
UIView.animate(withDuration: 0.5) | ||
view.alpha = 0.0 | ||
return true | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...lang/src/test/resources/checks/sobriety/AnimationFreeCheck_UIViewTransition_trigger.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
|
||
// Should trigger | ||
let test = UIView.transition(with: containerView, duration: 0.5, options: [.transitionFlipFromLeft], animations: { | ||
containerView.addSubview(view) | ||
}, completion: nil) | ||
|
||
return true | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
swift-lang/src/test/resources/checks/sobriety/AnimationFreeCheck_WithAnimation_trigger.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
// Should trigger | ||
withAnimation { | ||
self.isAnimating.toggle() | ||
} | ||
|
||
return true | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
swift-lang/src/test/resources/checks/sobriety/AnimationFreeCheck_no_trigger copy.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import Foundation | ||
import SwiftUI | ||
|
||
final class AppDelegate: NSObject, UIApplicationDelegate { | ||
func application( | ||
_ application: UIApplication, | ||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil | ||
) -> Bool { | ||
|
||
// No trigger | ||
|
||
return true | ||
} | ||
} |