")) {
+ if (audioRecorderStarted && !audioRecorderStopped) {
+ this.recordIssue(id.getStart().getStartIndex(), DEFAULT_ISSUE_MESSAGE);
+ }
+ audioRecorderStarted = false;
+ audioRecorderStopped = false;
+ importExist = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/swift-lang/src/main/resources/ecocode_swift_profile.json b/swift-lang/src/main/resources/ecocode_swift_profile.json
index f9c9f0d..ef3480d 100644
--- a/swift-lang/src/main/resources/ecocode_swift_profile.json
+++ b/swift-lang/src/main/resources/ecocode_swift_profile.json
@@ -6,6 +6,7 @@
"EC512",
"EC513",
"EC514",
+ "EC515",
"EC519",
"EC520",
"EC522",
diff --git a/swift-lang/src/main/resources/io/ecocode/rules/swift/EC515.html b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC515.html
new file mode 100644
index 0000000..0994c7b
--- /dev/null
+++ b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC515.html
@@ -0,0 +1,58 @@
+Creation of an AVAudioRecorder
object is used to record audio. This class has methods to stop recording
+ and release resources.
+
+ In addition to unnecessary resources (such as memory and instances of codecs) being held, failure to properly stop
+ and release these objects if they are no longer needed may also lead to continuous battery consumption for mobile
+ devices.
+Noncompliant Code Example
+
+import AVFoundation
+
+var audioRecorder: AVAudioRecorder?
+
+func startRecording() {
+ let settings = [
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
+ AVSampleRateKey: 12000,
+ AVNumberOfChannelsKey: 1,
+ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
+ ]
+
+ do {
+ audioRecorder = try AVAudioRecorder(url: getDocumentsDirectory().appendingPathComponent("recording.m4a"), settings: settings)
+ audioRecorder?.record()
+ } catch {
+ // Handle error
+ }
+}
+
+
+Compliant Code Example
+
+import AVFoundation
+
+var audioRecorder: AVAudioRecorder?
+
+func startRecording() {
+ let settings = [
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
+ AVSampleRateKey: 12000,
+ AVNumberOfChannelsKey: 1,
+ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
+ ]
+
+ do {
+ audioRecorder = try AVAudioRecorder(url: getDocumentsDirectory().appendingPathComponent("recording.m4a"), settings: settings)
+ audioRecorder?.record()
+ } catch {
+ // Handle error
+ }
+}
+
+func stopRecording() {
+ if let recorder = audioRecorder, recorder.isRecording {
+ recorder.stop()
+ audioRecorder = nil
+ }
+}
+
\ No newline at end of file
diff --git a/swift-lang/src/main/resources/io/ecocode/rules/swift/EC515.json b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC515.json
new file mode 100644
index 0000000..ec95af4
--- /dev/null
+++ b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC515.json
@@ -0,0 +1,19 @@
+{
+ "key": "EC515",
+ "title": "Leakage: Audio and Video Recorder Leak",
+ "defaultSeverity": "Major",
+ "description": "Creation of an AVAudioRecorder object is used to record audio. This class has methods to stop recording and release resources. In addition to unnecessary resources (such as memory and instances of codecs) being held, failure to properly stop and release these objects if they are no longer needed may also lead to continuous battery consumption for mobile devices.",
+ "status": "ready",
+ "remediation": {
+ "func": "Constant/Issue",
+ "constantCost": "7min"
+ },
+ "tags": [
+ "sobriety",
+ "environment",
+ "ecocode",
+ "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 6d51e57..8e3f299 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(13);
+ assertThat(repository.rules()).hasSize(14);
}
@Test
diff --git a/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/AudioRecorderLeakCheckTest.java b/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/AudioRecorderLeakCheckTest.java
new file mode 100644
index 0000000..c311c75
--- /dev/null
+++ b/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/AudioRecorderLeakCheckTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.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 java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AudioRecorderLeakCheckTest {
+ private static final String TEST_AUDIO_CASE_MISSING_RELEASE_CALL = "checks/sobriety/AudioRecorderLeak_trigger.swift";
+ private static final String TEST_AUDIO_CASE_COMPLIANT = "checks/sobriety/AudioRecorderLeak_no_trigger.swift";
+
+ private static final String TESTED_RULE_ID = "EC515";
+ private static final String TEST_REPOSITORY = "ecoCode-swift";
+
+ @Test
+ public void audioLeakCheck_missing_release_trigger() {
+ SensorContextTester context = CheckTestHelper.analyzeTestFile(TEST_AUDIO_CASE_MISSING_RELEASE_CALL);
+ assertThat(context.allIssues()).hasSize(1);
+ Optional issue = context.allIssues().stream().findFirst();
+ issue.ifPresent(i -> {
+ assertThat(i.ruleKey().rule()).isEqualTo(TESTED_RULE_ID);
+ assertThat(i.ruleKey().repository()).isEqualTo(TEST_REPOSITORY);
+ IssueLocation location = i.primaryLocation();
+ assertThat(location.textRange().start().line()).isEqualTo(15);
+ });
+ }
+
+ @Test
+ public void audioLeakCheck_no_trigger() {
+ SensorContextTester context = CheckTestHelper.analyzeTestFile(TEST_AUDIO_CASE_COMPLIANT);
+ assertThat(context.allIssues()).isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/swift-lang/src/test/resources/checks/sobriety/AudioRecorderLeak_no_trigger.swift b/swift-lang/src/test/resources/checks/sobriety/AudioRecorderLeak_no_trigger.swift
new file mode 100644
index 0000000..82bc749
--- /dev/null
+++ b/swift-lang/src/test/resources/checks/sobriety/AudioRecorderLeak_no_trigger.swift
@@ -0,0 +1,26 @@
+import AVFoundation
+
+var audioRecorder: AVAudioRecorder?
+
+func startRecording() {
+ let settings = [
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
+ AVSampleRateKey: 12000,
+ AVNumberOfChannelsKey: 1,
+ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
+ ]
+
+ do {
+ audioRecorder = try AVAudioRecorder(url: getDocumentsDirectory().appendingPathComponent("recording.m4a"), settings: settings)
+ audioRecorder?.record()
+ } catch {
+ // Handle error
+ }
+}
+
+func stopRecording() {
+ if let recorder = audioRecorder, recorder.isRecording {
+ recorder.stop()
+ audioRecorder = nil
+ }
+}
\ No newline at end of file
diff --git a/swift-lang/src/test/resources/checks/sobriety/AudioRecorderLeak_trigger.swift b/swift-lang/src/test/resources/checks/sobriety/AudioRecorderLeak_trigger.swift
new file mode 100644
index 0000000..87a5c8c
--- /dev/null
+++ b/swift-lang/src/test/resources/checks/sobriety/AudioRecorderLeak_trigger.swift
@@ -0,0 +1,19 @@
+import AVFoundation
+
+var audioRecorder: AVAudioRecorder?
+
+func startRecording() {
+ let settings = [
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
+ AVSampleRateKey: 12000,
+ AVNumberOfChannelsKey: 1,
+ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
+ ]
+
+ do {
+ audioRecorder = try AVAudioRecorder(url: getDocumentsDirectory().appendingPathComponent("recording.m4a"), settings: settings)
+ audioRecorder?.record()
+ } catch {
+ // Handle error
+ }
+}
\ No newline at end of file