diff --git a/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/FeedbackGeneratorUsageCheck.java b/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/FeedbackGeneratorUsageCheck.java new file mode 100644 index 0000000..5ae62e9 --- /dev/null +++ b/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/FeedbackGeneratorUsageCheck.java @@ -0,0 +1,71 @@ +/* + * 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 . + */ +package io.ecocode.ios.swift.checks.sobriety; + +import io.ecocode.ios.swift.SwiftRuleCheck; +import io.ecocode.ios.swift.antlr.generated.Swift5Parser; +import io.ecocode.ios.swift.checks.CheckHelper; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNodeImpl; +import org.sonar.check.Rule; + +import java.util.Objects; + +@Rule(key = "EC528") +public class FeedbackGeneratorUsageCheck extends SwiftRuleCheck { + private static final String DEFAULT_ISSUE_MESSAGE = "Avoid using the device vibrator to use less energy."; + public static final String UI_KIT = "UIKit"; + protected boolean isUIKitImported; + + protected Swift5Parser.ExpressionContext id; + + protected boolean isFeedbackGeneratorInstantiated; + protected boolean isImpactMethodCalled; + + @Override + public void apply(ParseTree tree) { + + isUIKitImported = isUIKitImported || CheckHelper.isImportExisting(tree, UI_KIT); + + isFeedbackGeneratorInstantiated = isFeedbackGeneratorInstantiated || + (isUIKitImported && + tree instanceof Swift5Parser.ExpressionContext && + (tree.getText().contains("UIImpactFeedbackGenerator"))); + + isImpactMethodCalled = isImpactMethodCalled || + (isFeedbackGeneratorInstantiated && + (tree.getText().contains(".impactOccurred("))); + + if (Objects.isNull(id) && + tree instanceof Swift5Parser.ExpressionContext && + isImpactMethodCalled + ) { + id = (Swift5Parser.ExpressionContext) tree; + } + + if (tree instanceof TerminalNodeImpl && tree.getText().equals("")) { + if (isImpactMethodCalled) { + this.recordIssue(id.getStart().getStartIndex(), DEFAULT_ISSUE_MESSAGE); + } + isUIKitImported = false; + isFeedbackGeneratorInstantiated = false; + isImpactMethodCalled = false; + } + } +} + diff --git a/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/LocationLeakCheck.java b/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/LocationLeakCheck.java index 9d2243a..6cf1e83 100644 --- a/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/LocationLeakCheck.java +++ b/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/LocationLeakCheck.java @@ -34,7 +34,6 @@ public class LocationLeakCheck extends SwiftRuleCheck { @Override public void apply(ParseTree tree) { - if (tree instanceof Swift5Parser.ExpressionContext && (tree.getText().contains(".startUpdatingLocation()"))) { firstCallExist = true; id = (Swift5Parser.ExpressionContext) tree; diff --git a/swift-lang/src/main/resources/ecocode_swift_profile.json b/swift-lang/src/main/resources/ecocode_swift_profile.json index ef3480d..4464de3 100644 --- a/swift-lang/src/main/resources/ecocode_swift_profile.json +++ b/swift-lang/src/main/resources/ecocode_swift_profile.json @@ -11,6 +11,7 @@ "EC520", "EC522", "EC524", + "EC528", "EC530", "EC533", "EC534", diff --git a/swift-lang/src/main/resources/io/ecocode/rules/swift/EC528.html b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC528.html new file mode 100644 index 0000000..ad329b6 --- /dev/null +++ b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC528.html @@ -0,0 +1,14 @@ +

+ Shaking or vibrating an iOS device is possible using UIFeedbackGenerator. + Behind this effect stands a specific hardware component, the Taptic Engine, which consumes power. + As a consequence, its usage should be carefully considered, especially if its added value is not clear. +

Noncompliant Code Example

+ +
+    import UIKit
+
+    func triggerVibration() {
+        let generator = UIImpactFeedbackGenerator(style: .heavy)
+        generator.impactOccurred()
+    }
+
\ No newline at end of file diff --git a/swift-lang/src/main/resources/io/ecocode/rules/swift/EC528.json b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC528.json new file mode 100644 index 0000000..d527290 --- /dev/null +++ b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC528.json @@ -0,0 +1,18 @@ +{ + "key": "EC528", + "title": "Sobriety: Vibration Free", + "defaultSeverity": "Major", + "description": "Avoid using the device vibrator to use less energy.", + "status": "ready", + "remediation": { + "func": "Constant/Issue", + "constantCost": "5min" + }, + "tags": [ + "ecocode", + "environment", + "sobriety", + "eco-design" + ], + "type": "CODE_SMELL" +} \ No newline at end of file diff --git a/swift-lang/src/test/java/io/ecocode/ios/swift/EcoCodeSwiftRulesDefinitionTest.java b/swift-lang/src/test/java/io/ecocode/ios/swift/EcoCodeSwiftRulesDefinitionTest.java index 8e3f299..89cea99 100644 --- a/swift-lang/src/test/java/io/ecocode/ios/swift/EcoCodeSwiftRulesDefinitionTest.java +++ b/swift-lang/src/test/java/io/ecocode/ios/swift/EcoCodeSwiftRulesDefinitionTest.java @@ -55,7 +55,7 @@ public void testMetadata() { @Test public void testRegisteredRules() { - assertThat(repository.rules()).hasSize(14); + assertThat(repository.rules()).hasSize(15); } @Test diff --git a/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/FeedbackGeneratorUsageCheckTest.java b/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/FeedbackGeneratorUsageCheckTest.java new file mode 100644 index 0000000..bd0e70b --- /dev/null +++ b/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/FeedbackGeneratorUsageCheckTest.java @@ -0,0 +1,57 @@ +/* + * 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 . + */ + +package io.ecocode.ios.swift.checks.sobriety; + +import io.ecocode.ios.swift.checks.CheckTestHelper; +import org.assertj.core.api.ObjectAssert; +import org.junit.Test; +import org.sonar.api.batch.fs.TextPointer; +import org.sonar.api.batch.fs.TextRange; +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 org.sonar.api.rule.RuleKey; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FeedbackGeneratorUsageCheckTest { + + private static final String TEST_CASE_FEEDBACK_GENERATOR_USE = "checks/sobriety/FeedbackGeneratorUsageCheck_abusive_use_trigger.swift"; + private static final String TEST_CASE_COMPLIANT = "checks/sobriety/FeedbackGeneratorUsage_compliant_no_trigger.swift"; + @Test + public void feedbackGeneratorUsageCheck_usage_trigger(){ + SensorContextTester context = CheckTestHelper.analyzeTestFile(TEST_CASE_FEEDBACK_GENERATOR_USE); + ObjectAssert issue = assertThat(context.allIssues()).hasSize(1) + .first(); + issue.extracting(Issue::ruleKey).extracting(RuleKey::rule).isEqualTo("EC528"); + issue.extracting(Issue::ruleKey).extracting(RuleKey::repository) + .isEqualTo("ecoCode-swift"); + issue.extracting(Issue::primaryLocation) + .extracting(IssueLocation::textRange) + .extracting(TextRange::start) + .extracting(TextPointer::line) + .isEqualTo(5); + } + + @Test + public void locationLeakCheck_compliant_no_trigger(){ + SensorContextTester context = CheckTestHelper.analyzeTestFile(TEST_CASE_COMPLIANT); + assertThat(context.allIssues()).isEmpty(); + } +} diff --git a/swift-lang/src/test/resources/checks/sobriety/FeedbackGeneratorUsageCheck_abusive_use_trigger.swift b/swift-lang/src/test/resources/checks/sobriety/FeedbackGeneratorUsageCheck_abusive_use_trigger.swift new file mode 100644 index 0000000..e05a04d --- /dev/null +++ b/swift-lang/src/test/resources/checks/sobriety/FeedbackGeneratorUsageCheck_abusive_use_trigger.swift @@ -0,0 +1,6 @@ +import UIKit + +func triggerVibration() { + let generator = UIImpactFeedbackGenerator(style: .heavy) + generator.impactOccurred() +} \ No newline at end of file diff --git a/swift-lang/src/test/resources/checks/sobriety/FeedbackGeneratorUsage_compliant_no_trigger.swift b/swift-lang/src/test/resources/checks/sobriety/FeedbackGeneratorUsage_compliant_no_trigger.swift new file mode 100644 index 0000000..2e54bf5 --- /dev/null +++ b/swift-lang/src/test/resources/checks/sobriety/FeedbackGeneratorUsage_compliant_no_trigger.swift @@ -0,0 +1,5 @@ +import UIKit + +func hello() { + print("Hello, world!") +} \ No newline at end of file