Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

- [#113](https://github.com/green-code-initiative/creedengo-javascript/pull/113) Extend rule GCI530 "no-torch" to detect HTML5 Web API usage

## [3.1.0] - 2026-05-10

### Added
Expand Down
13 changes: 12 additions & 1 deletion eslint-plugin/docs/rules/no-torch.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,27 @@ 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

### 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)
42 changes: 40 additions & 2 deletions eslint-plugin/lib/rules/no-torch.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@

"use strict";

const getPropertyValue = (propName) => (obj) => {
if (obj.type !== "ObjectExpression") return null;
return obj.properties.find((p) => {
if (p.type !== "Property") return false;
const name = p.key.type === "Identifier" ? p.key.name : p.key.value;
return name === propName;
})?.value;
};

const hasTorchTrueInAdvanced = (arg) => {
const advanced = getPropertyValue("advanced")(arg);
if (!advanced || advanced.type !== "ArrayExpression") return false;

return advanced.elements.some((el) => {
const torch = getPropertyValue("torch")(el);
return torch?.type === "Literal" && torch.value === true;
});
};

/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
meta: {
Expand All @@ -38,8 +57,27 @@ module.exports = {

return {
ImportDeclaration(node) {
const currentLibrary = node.source.value;
if (currentLibrary === reactNativeTorchLibrary) {
if (node.source.value === reactNativeTorchLibrary) {
context.report({
node,
messageId: "ShouldNotProgrammaticallyEnablingTorchMode",
});
}
},

CallExpression(node) {
const { callee } = node;

const isApplyConstraints =
callee.type === "MemberExpression" &&
((callee.computed &&
callee.property.type === "Literal" &&
callee.property.value === "applyConstraints") ||
(!callee.computed &&
callee.property.name === "applyConstraints")) &&
node.arguments.length > 0;

if (isApplyConstraints && hasTorchTrueInAdvanced(node.arguments[0])) {
context.report({
node,
messageId: "ShouldNotProgrammaticallyEnablingTorchMode",
Expand Down
65 changes: 62 additions & 3 deletions eslint-plugin/tests/lib/rules/no-torch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,75 @@ const expectedError = {

const tests = {
valid: [
`
import axios from 'axios';
`,
// Import tests
`import axios from 'axios';`,
`import * as torch from 'other-package';`,
`import { torch } from 'other-package';`,

// applyConstraints with torch: false
`track.applyConstraints({ advanced: [{ torch: false }] });`,
`track.applyConstraints({ advanced: [{ torch: false }, { facingMode: 'user' }] });`,

// applyConstraints without torch property
`track.applyConstraints({ advanced: [{ facingMode: 'environment' }] });`,
`track.applyConstraints({ advanced: [{ facingMode: 'environment' }, { width: 640 }] });`,

// applyConstraints with empty advanced
`track.applyConstraints({ advanced: [] });`,

// applyConstraints with standard constraints (no advanced)
`track.applyConstraints({ width: 1280, height: 720 });`,

// Non-applyConstraints methods
`doSomething({ advanced: [{ torch: true }] });`,
],

invalid: [
// Import react-native-torch
{
code: "import Torch from 'react-native-torch';",
errors: [expectedError],
},
{
code: "import { torch } from 'react-native-torch';",
errors: [expectedError],
},
{
code: "import * as ReactNativeTorch from 'react-native-torch';",
errors: [expectedError],
},

// applyConstraints with torch: true
{
code: "track.applyConstraints({ advanced: [{ torch: true }] });",
errors: [expectedError],
},

// torch: true with other properties in advanced
{
code: "track.applyConstraints({ advanced: [{ facingMode: 'environment' }, { torch: true }] });",
errors: [expectedError],
},
{
code: "track.applyConstraints({ advanced: [{ torch: true }, { facingMode: 'user' }] });",
errors: [expectedError],
},
{
code: "track.applyConstraints({ advanced: [{ width: 640 }, { torch: true }, { facingMode: 'environment' }] });",
errors: [expectedError],
},

// Multiple torch: true entries
{
code: "track.applyConstraints({ advanced: [{ torch: true }, { torch: true }] });",
errors: [expectedError],
},

// torch: true with extra properties in same object
{
code: "track.applyConstraints({ advanced: [{ torch: true, width: 640 }] });",
errors: [expectedError],
},
],
};

Expand Down
2 changes: 1 addition & 1 deletion sonar-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<project.build.sourceEncoding>${encoding}</project.build.sourceEncoding>
<project.reporting.outputEncoding>${encoding}</project.reporting.outputEncoding>

<version.creedengo-rules-specifications>3.0.0</version.creedengo-rules-specifications>
<version.creedengo-rules-specifications>3.1.0</version.creedengo-rules-specifications>
<version.sonarqube>13.0.0.3026</version.sonarqube>
<version.sonar-javascript>11.8.0.37897</version.sonar-javascript>
<version.sonar-packaging>1.25.1.3002</version.sonar-packaging>
Expand Down
15 changes: 15 additions & 0 deletions test-project/src/no-torch.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
// 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();

await track.applyConstraints({ advanced: [{ torch: true }] }); // Non-compliant: programmatically enables torch via advanced constraints

await track.applyConstraints({ advanced: [{ torch: false }] }); // Compliant: programmatically disables torch via advanced constraints

await track.applyConstraints({ advanced: [{ facingMode: "environment" }] }); // Compliant: no torch constraint
}
4 changes: 2 additions & 2 deletions test-project/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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=5bb3d4&locator=creedengo-javascript-test-project%40workspace%3A."
peerDependencies:
eslint: ^9.0.0 || ^10.0.0
checksum: 10c0/35357906578cb26b758f44fb8fdb12656de3a6d3297d97002f3a1c755066b81e1321badd1f4a01a9e9d156b0545b5611774c2ed83e80600168c134f0dc0846fd
checksum: 10c0/bfe290b4ff43b71e4adcd9713fbd8f2fed54b34f5ca13db3c0d81225114f84a61bcb08d6756b2cedbdfc0ef19f2442011c1b771d2eae3885b031f74977ee03fc
languageName: node
linkType: hard

Expand Down