Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- [#84](https://github.com/green-code-initiative/creedengo-javascript/pull/84) Add rule GCI535 "No imported number format library"
- Add SonarQube-only rule GCI2536 "Optimize the browserslist tag in package.json"

## [3.1.0] - 2026-05-10

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>main-SNAPSHOT</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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public void define(Context context) {
context.addExtensions(
ESLintRulesBundle.class,
JavaScriptRulesDefinition.class,
OptimizeBrowserslistTagInPackageJsonSensor.class,
JavaScriptRuleRepository.class,
TypeScriptRulesDefinition.class,
TypeScriptRuleRepository.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.sonar.api.server.rule.RulesDefinition;
import org.sonarsource.analyzer.commons.RuleMetadataLoader;

import static java.util.List.of;

public class JavaScriptRulesDefinition implements RulesDefinition {

private static final String METADATA_LOCATION = "org/green-code-initiative/rules/javascript";
Expand All @@ -49,6 +51,8 @@ public void define(Context context) {
ruleMetadataLoader.addRulesByAnnotatedClass(repository, checks);
DeprecatedEcoCodeRule.addOnRepository(repository, JavaScriptRuleRepository.OLD_KEY, checks);

ruleMetadataLoader.addRulesByAnnotatedClass(repository, of(OptimizeBrowserslistTagInPackageJsonRule.class));

repository.done();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* 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 org.greencodeinitiative.creedengo.javascript;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.sonar.check.Rule;

@Rule(key = OptimizeBrowserslistTagInPackageJsonRule.KEY)
public final class OptimizeBrowserslistTagInPackageJsonRule {

public static final String KEY = "GCI2536";

static final String ISSUE_MESSAGE = "Move the browserslist configuration to a \"production\" target.";

private static final Pattern BROWSERSLIST_PATTERN = Pattern.compile(
"\"browserslist\"\\s*:\\s*(\\{.*?\\}|\\[.*?\\]|\".*?\")", Pattern.DOTALL);

private OptimizeBrowserslistTagInPackageJsonRule() {
}

static boolean isNonCompliant(String packageJsonContents) {
Matcher matcher = BROWSERSLIST_PATTERN.matcher(packageJsonContents);
if (!matcher.find()) {
return false;
}

String browserslistConfiguration = matcher.group(1).trim();
if (browserslistConfiguration.startsWith("{")) {
return !browserslistConfiguration.contains("\"production\"");
}

return true;
}

static int browserslistLineNumber(String packageJsonContents) {
Matcher matcher = BROWSERSLIST_PATTERN.matcher(packageJsonContents);
if (!matcher.find()) {
return 1;
}

return lineNumberAt(packageJsonContents, matcher.start());
}

private static int lineNumberAt(String text, int index) {
return 1 + (int) text.substring(0, index).chars().filter(c -> c == '\n').count();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* 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 org.greencodeinitiative.creedengo.javascript;

import java.io.IOException;

import org.sonar.api.batch.fs.FilePredicate;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.scanner.sensor.ProjectSensor;

public class OptimizeBrowserslistTagInPackageJsonSensor implements ProjectSensor {

private static final RuleKey RULE_KEY = RuleKey.of(JavaScriptRuleRepository.KEY, OptimizeBrowserslistTagInPackageJsonRule.KEY);

private final FileSystem fileSystem;

public OptimizeBrowserslistTagInPackageJsonSensor(FileSystem fileSystem) {
this.fileSystem = fileSystem;
}

@Override
public void describe(SensorDescriptor descriptor) {
descriptor
.name("Optimize browserslist tag in package.json")
.createIssuesForRuleRepository(JavaScriptRuleRepository.KEY);
}

@Override
public void execute(SensorContext context) {
InputFile packageJson = findPackageJson();
if (packageJson == null || packageJson.isEmpty()) {
return;
}

try {
String contents = packageJson.contents();
if (!OptimizeBrowserslistTagInPackageJsonRule.isNonCompliant(contents)) {
return;
}

NewIssue issue = context.newIssue().forRule(RULE_KEY);
NewIssueLocation location = issue.newLocation()
.on(packageJson)
.at(packageJson.selectLine(OptimizeBrowserslistTagInPackageJsonRule.browserslistLineNumber(contents)))
.message(OptimizeBrowserslistTagInPackageJsonRule.ISSUE_MESSAGE);

issue.at(location).save();
} catch (IOException exception) {
throw new IllegalStateException("Unable to read package.json", exception);
}
}

private InputFile findPackageJson() {
FilePredicate packageJsonPredicate = fileSystem.predicates().hasRelativePath("package.json");
return fileSystem.inputFile(packageJsonPredicate);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"GCI505",
"GCI523",
"GCI530",
"GCI535"
"GCI535",
"GCI2536"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void extensions() {
SonarRuntime sonarRuntime = mock(SonarRuntime.class);
Plugin.Context context = new Plugin.Context(sonarRuntime);
new JavaScriptPlugin().define(context);
assertThat(context.getExtensions()).hasSize(5);
assertThat(context.getExtensions()).hasSize(6);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ void createRepository() {
assertThat(repository.isExternal()).isFalse();
assertThat(repository.language()).isEqualTo("js");
assertThat(repository.key()).isEqualTo("creedengo-javascript");
assertThat(repository.rule(OptimizeBrowserslistTagInPackageJsonRule.KEY)).isNotNull();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Creedengo JavaScript plugin - Provides rules to reduce the environmental footprint of your JavaScript programs
* Copyright © 2023 Green Code Initiative (https://green-code-initiative.org)
*
* 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 org.greencodeinitiative.creedengo.javascript;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class OptimizeBrowserslistTagInPackageJsonRuleTest {

@Test
void detectNonCompliantBrowserslistArray() {
String packageJson = """
{
"name": "app",
"browserslist": [
"defaults"
]
}
""";

assertThat(OptimizeBrowserslistTagInPackageJsonRule.isNonCompliant(packageJson)).isTrue();
assertThat(OptimizeBrowserslistTagInPackageJsonRule.browserslistLineNumber(packageJson)).isEqualTo(3);
}

@Test
void acceptProductionBrowserslistObject() {
String packageJson = """
{
"name": "app",
"browserslist": {
"production": [
"last 2 Chrome versions"
],
"development": [
"last 1 Chrome version"
]
}
}
""";

assertThat(OptimizeBrowserslistTagInPackageJsonRule.isNonCompliant(packageJson)).isFalse();
}

@Test
void acceptPackageJsonWithoutBrowserslist() {
String packageJson = """
{
"name": "app"
}
""";

assertThat(OptimizeBrowserslistTagInPackageJsonRule.isNonCompliant(packageJson)).isFalse();
assertThat(OptimizeBrowserslistTagInPackageJsonRule.browserslistLineNumber(packageJson)).isEqualTo(1);
}

@Test
void detectNonCompliantBrowserslistObjectWithoutProductionKey() {
String packageJson = """
{
"name": "app",
"browserslist": {
"development": [
"last 1 Chrome version"
]
}
}
""";

assertThat(OptimizeBrowserslistTagInPackageJsonRule.isNonCompliant(packageJson)).isTrue();
}

@Test
void detectNonCompliantBrowserslistString() {
String packageJson = """
{
"name": "app",
"browserslist": "defaults and > 0.5%"
}
""";

assertThat(OptimizeBrowserslistTagInPackageJsonRule.isNonCompliant(packageJson)).isTrue();
assertThat(OptimizeBrowserslistTagInPackageJsonRule.browserslistLineNumber(packageJson)).isEqualTo(3);
}

}
Loading
Loading