From dd4224b2416cf414a50d51f7f5b5b41af5512e86 Mon Sep 17 00:00:00 2001 From: "Hugo.Mo" Date: Tue, 19 May 2026 17:22:33 +0200 Subject: [PATCH 1/4] Extend rule GCI530 "no-torch" to detect HTML5 Web API usage --- CHANGELOG.md | 4 ++ eslint-plugin/docs/rules/no-torch.md | 13 ++++- eslint-plugin/lib/rules/no-torch.js | 57 +++++++++++++++++-- .../tests/lib/rules/no-torch.test.js | 19 ++++++- test-project/src/no-torch.js | 13 +++++ test-project/yarn.lock | 4 +- 6 files changed, 100 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d22fe0..b969d17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#84](https://github.com/green-code-initiative/creedengo-javascript/pull/84) Add rule GCI535 "No imported number format library" +### Changed + +- [#48](https://github.com/green-code-initiative/creedengo-javascript/issues/48) Extend rule GCI530 "no-torch" to detect HTML5 Web API usage (`MediaTrackConstraints` torch constraint via `applyConstraints`) + ## [3.1.0] - 2026-05-10 ### Added diff --git a/eslint-plugin/docs/rules/no-torch.md b/eslint-plugin/docs/rules/no-torch.md index d67cb7e..e698e84 100644 --- a/eslint-plugin/docs/rules/no-torch.md +++ b/eslint-plugin/docs/rules/no-torch.md @@ -12,12 +12,22 @@ As a developer, you should avoid programmatically enabling torch mode. The flashlight can significantly drain the device's battery. If it is turned on without the user's knowledge, it could lead to unwanted battery consumption. +### React Native + ```js import Torch from "react-native-torch"; // Not-compliant + +import axios from "axios"; // Compliant ``` +### HTML5 Web API (MediaTrackConstraints) + ```js -import axios from "axios"; // Compliant +// Not-compliant +await track.applyConstraints({ advanced: [{ torch: true }] }); + +// Compliant +await track.applyConstraints({ advanced: [{ facingMode: "environment" }] }); ``` ## Resources @@ -25,3 +35,4 @@ import axios from "axios"; // Compliant ### Documentation - [CNUMR best practices mobile](https://github.com/cnumr/best-practices-mobile) - Torch free +- [MediaTrackConstraints: torch property (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/torch) diff --git a/eslint-plugin/lib/rules/no-torch.js b/eslint-plugin/lib/rules/no-torch.js index 65d924e..8c824ca 100644 --- a/eslint-plugin/lib/rules/no-torch.js +++ b/eslint-plugin/lib/rules/no-torch.js @@ -18,6 +18,46 @@ "use strict"; +const reactNativeTorchLibrary = "react-native-torch"; + +function getPropertyName(prop) { + if (prop.key.type === "Identifier") return prop.key.name; + if (prop.key.type === "Literal") return String(prop.key.value); + return null; +} + +function findProperty(objectExpression, name) { + return objectExpression.properties.find( + (p) => p.type === "Property" && getPropertyName(p) === name + ); +} + +function objectHasTorchProperty(objectExpression) { + return Boolean(findProperty(objectExpression, "torch")); +} + +function advancedArrayHasTorch(arrayExpression) { + return arrayExpression.elements.some( + (el) => el && el.type === "ObjectExpression" && objectHasTorchProperty(el) + ); +} + +function constraintsArgUsesTorchInAdvanced(arg) { + if (arg.type !== "ObjectExpression") return false; + const advancedProp = findProperty(arg, "advanced"); + if (!advancedProp || advancedProp.value.type !== "ArrayExpression") return false; + return advancedArrayHasTorch(advancedProp.value); +} + +function isApplyConstraintsCall(node) { + const { callee } = node; + if (callee.type !== "MemberExpression") return false; + const methodName = callee.computed + ? callee.property.type === "Literal" && callee.property.value + : callee.property.name; + return methodName === "applyConstraints" && node.arguments.length > 0; +} + /** @type {import("eslint").Rule.RuleModule} */ module.exports = { meta: { @@ -34,12 +74,21 @@ module.exports = { schema: [], }, create: function (context) { - const reactNativeTorchLibrary = "react-native-torch"; - return { ImportDeclaration(node) { - const currentLibrary = node.source.value; - if (currentLibrary === reactNativeTorchLibrary) { + if (node.source.value === reactNativeTorchLibrary) { + context.report({ + node, + messageId: "ShouldNotProgrammaticallyEnablingTorchMode", + }); + } + }, + + CallExpression(node) { + if ( + isApplyConstraintsCall(node) && + constraintsArgUsesTorchInAdvanced(node.arguments[0]) + ) { context.report({ node, messageId: "ShouldNotProgrammaticallyEnablingTorchMode", diff --git a/eslint-plugin/tests/lib/rules/no-torch.test.js b/eslint-plugin/tests/lib/rules/no-torch.test.js index 531f84d..5f628ea 100644 --- a/eslint-plugin/tests/lib/rules/no-torch.test.js +++ b/eslint-plugin/tests/lib/rules/no-torch.test.js @@ -42,9 +42,10 @@ const expectedError = { const tests = { valid: [ - ` - import axios from 'axios'; - `, + `import axios from 'axios';`, + `track.applyConstraints({ advanced: [{ facingMode: 'environment' }] });`, + `track.applyConstraints({ advanced: [] });`, + `track.applyConstraints({ width: 1280, height: 720 });`, ], invalid: [ @@ -52,6 +53,18 @@ const tests = { code: "import Torch from 'react-native-torch';", errors: [expectedError], }, + { + code: "track.applyConstraints({ advanced: [{ torch: true }] });", + errors: [expectedError], + }, + { + code: "track.applyConstraints({ advanced: [{ torch: false }] });", + errors: [expectedError], + }, + { + code: "track.applyConstraints({ advanced: [{ facingMode: 'environment' }, { torch: true }] });", + errors: [expectedError], + }, ], }; diff --git a/test-project/src/no-torch.js b/test-project/src/no-torch.js index 3e6b55e..98b1d7a 100644 --- a/test-project/src/no-torch.js +++ b/test-project/src/no-torch.js @@ -1,3 +1,16 @@ +// React Native import Torch from "react-native-torch"; // Non-compliant: torch should not be enabled Torch.switchState(true); + +// Web API (MediaTrackConstraints) +export async function example() { + const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true }); + const [track] = mediaStream.getVideoTracks(); + + // Non-compliant: programmatically enables torch via advanced constraints + await track.applyConstraints({ advanced: [{ torch: true }] }); + + // Compliant: no torch constraint + await track.applyConstraints({ advanced: [{ facingMode: "environment" }] }); +} diff --git a/test-project/yarn.lock b/test-project/yarn.lock index 4cd76f6..609b97c 100644 --- a/test-project/yarn.lock +++ b/test-project/yarn.lock @@ -21,10 +21,10 @@ __metadata: "@creedengo/eslint-plugin@file:../eslint-plugin::locator=creedengo-javascript-test-project%40workspace%3A.": version: 3.1.0 - resolution: "@creedengo/eslint-plugin@file:../eslint-plugin#../eslint-plugin::hash=30e64e&locator=creedengo-javascript-test-project%40workspace%3A." + resolution: "@creedengo/eslint-plugin@file:../eslint-plugin#../eslint-plugin::hash=9e133d&locator=creedengo-javascript-test-project%40workspace%3A." peerDependencies: eslint: ^9.0.0 || ^10.0.0 - checksum: 10c0/35357906578cb26b758f44fb8fdb12656de3a6d3297d97002f3a1c755066b81e1321badd1f4a01a9e9d156b0545b5611774c2ed83e80600168c134f0dc0846fd + checksum: 10c0/9634bfc45fdc5c550fd0345c0621e3d953906a1578ed0340107cacc275fd5616e65bda298a7d43d6c83544cc764b248f267fd58bc9f5683be57695afd283eceb languageName: node linkType: hard From 9186d91d38c10372435eba51905749b96a01e8a6 Mon Sep 17 00:00:00 2001 From: HuMoreau <73228759+HuMoreau@users.noreply.github.com> Date: Wed, 20 May 2026 14:41:59 +0200 Subject: [PATCH 2/4] Fix link formatting for torch property documentation --- eslint-plugin/docs/rules/no-torch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint-plugin/docs/rules/no-torch.md b/eslint-plugin/docs/rules/no-torch.md index e698e84..2d92a14 100644 --- a/eslint-plugin/docs/rules/no-torch.md +++ b/eslint-plugin/docs/rules/no-torch.md @@ -35,4 +35,4 @@ await track.applyConstraints({ advanced: [{ facingMode: "environment" }] }); ### Documentation - [CNUMR best practices mobile](https://github.com/cnumr/best-practices-mobile) - Torch free -- [MediaTrackConstraints: torch property (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/torch) +- [MediaTrackConstraints: torch property (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#torch) From 9bb0b924a881bbb5a655299da860348024ecbb06 Mon Sep 17 00:00:00 2001 From: HuMoreau <73228759+HuMoreau@users.noreply.github.com> Date: Wed, 20 May 2026 14:43:13 +0200 Subject: [PATCH 3/4] Update comments for torch constraint compliance --- test-project/src/no-torch.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test-project/src/no-torch.js b/test-project/src/no-torch.js index 98b1d7a..7e4e49e 100644 --- a/test-project/src/no-torch.js +++ b/test-project/src/no-torch.js @@ -8,9 +8,7 @@ export async function example() { const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true }); const [track] = mediaStream.getVideoTracks(); - // Non-compliant: programmatically enables torch via advanced constraints - await track.applyConstraints({ advanced: [{ torch: true }] }); + await track.applyConstraints({ advanced: [{ torch: true }] }); // Non-compliant: programmatically enables torch via advanced constraints - // Compliant: no torch constraint - await track.applyConstraints({ advanced: [{ facingMode: "environment" }] }); + await track.applyConstraints({ advanced: [{ facingMode: "environment" }] }); // Compliant: no torch constraint } From fa2f7e6ee7600657440ee64a9da174ffe1975a9c Mon Sep 17 00:00:00 2001 From: "Hugo.Mo" Date: Wed, 20 May 2026 15:05:57 +0200 Subject: [PATCH 4/4] Restore yarn.lock to upstream state --- test-project/yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-project/yarn.lock b/test-project/yarn.lock index 609b97c..4cd76f6 100644 --- a/test-project/yarn.lock +++ b/test-project/yarn.lock @@ -21,10 +21,10 @@ __metadata: "@creedengo/eslint-plugin@file:../eslint-plugin::locator=creedengo-javascript-test-project%40workspace%3A.": version: 3.1.0 - resolution: "@creedengo/eslint-plugin@file:../eslint-plugin#../eslint-plugin::hash=9e133d&locator=creedengo-javascript-test-project%40workspace%3A." + resolution: "@creedengo/eslint-plugin@file:../eslint-plugin#../eslint-plugin::hash=30e64e&locator=creedengo-javascript-test-project%40workspace%3A." peerDependencies: eslint: ^9.0.0 || ^10.0.0 - checksum: 10c0/9634bfc45fdc5c550fd0345c0621e3d953906a1578ed0340107cacc275fd5616e65bda298a7d43d6c83544cc764b248f267fd58bc9f5683be57695afd283eceb + checksum: 10c0/35357906578cb26b758f44fb8fdb12656de3a6d3297d97002f3a1c755066b81e1321badd1f4a01a9e9d156b0545b5611774c2ed83e80600168c134f0dc0846fd languageName: node linkType: hard