From a189591667f4aae54513ea662049092dcb42301c Mon Sep 17 00:00:00 2001 From: Sebastien Date: Wed, 20 May 2026 10:27:19 +0200 Subject: [PATCH 1/2] testing --- sonar-plugin/pom.xml | 2 +- .../greencodeinitiative/creedengo/javascript/CheckList.java | 5 +++++ .../creedengo/javascript/JavaScriptPlugin.java | 4 +++- .../creedengo/javascript/JavaScriptRulesDefinition.java | 2 ++ .../creedengo/javascript/TypeScriptRulesDefinition.java | 2 ++ .../creedengo/profiles/javascript_profile.json | 3 ++- .../creedengo/profiles/typescript_profile.json | 2 +- .../creedengo/javascript/JavaScriptPluginTest.java | 2 +- test-project/package.json | 6 +++++- test-project/sonar-project.properties | 2 +- test-project/yarn.lock | 4 ++-- 11 files changed, 25 insertions(+), 9 deletions(-) diff --git a/sonar-plugin/pom.xml b/sonar-plugin/pom.xml index 712eec7..72bc07f 100644 --- a/sonar-plugin/pom.xml +++ b/sonar-plugin/pom.xml @@ -48,7 +48,7 @@ ${encoding} ${encoding} - 3.0.0 + 2536-JS-SNAPSHOT 13.0.0.3026 11.8.0.37897 1.25.1.3002 diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java index 820272a..3421e60 100644 --- a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import java.util.Collections; public class CheckList { @@ -67,4 +68,8 @@ private static List> filterHooksByAnnotation(Class> getSensorChecks() { + return Collections.singletonList(OptimizeBrowserslistTagInPackageJsonRule.class); + } + } diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPlugin.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPlugin.java index 01f9fb9..eb6e0e3 100644 --- a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPlugin.java +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPlugin.java @@ -17,6 +17,7 @@ */ package org.greencodeinitiative.creedengo.javascript; +import org.greencodeinitiative.creedengo.javascript.checks.OptimizeBrowserslistTagInPackageJsonSensor; import org.sonar.api.Plugin; public class JavaScriptPlugin implements Plugin { @@ -30,7 +31,8 @@ public void define(Context context) { JavaScriptRulesDefinition.class, JavaScriptRuleRepository.class, TypeScriptRulesDefinition.class, - TypeScriptRuleRepository.class + TypeScriptRuleRepository.class, + OptimizeBrowserslistTagInPackageJsonSensor.class ); } diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptRulesDefinition.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptRulesDefinition.java index 871afa0..be754ab 100644 --- a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptRulesDefinition.java +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptRulesDefinition.java @@ -49,6 +49,8 @@ public void define(Context context) { ruleMetadataLoader.addRulesByAnnotatedClass(repository, checks); DeprecatedEcoCodeRule.addOnRepository(repository, JavaScriptRuleRepository.OLD_KEY, checks); + ruleMetadataLoader.addRulesByAnnotatedClass(repository, CheckList.getSensorChecks()); + repository.done(); } diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/TypeScriptRulesDefinition.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/TypeScriptRulesDefinition.java index accd104..8e5c8f7 100644 --- a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/TypeScriptRulesDefinition.java +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/TypeScriptRulesDefinition.java @@ -49,6 +49,8 @@ public void define(Context context) { ruleMetadataLoader.addRulesByAnnotatedClass(repository, checks); DeprecatedEcoCodeRule.addOnRepository(repository, TypeScriptRuleRepository.OLD_KEY, checks); + ruleMetadataLoader.addRulesByAnnotatedClass(repository, CheckList.getSensorChecks()); + repository.done(); } diff --git a/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json b/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json index c542671..a411343 100644 --- a/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json +++ b/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/javascript_profile.json @@ -14,6 +14,7 @@ "GCI505", "GCI523", "GCI530", - "GCI535" + "GCI535", + "GCI2536" ] } diff --git a/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/typescript_profile.json b/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/typescript_profile.json index 92a9900..62276e8 100644 --- a/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/typescript_profile.json +++ b/sonar-plugin/src/main/resources/org/greencodeinitiative/creedengo/profiles/typescript_profile.json @@ -1,4 +1,4 @@ { "name": "Creedengo", - "ruleKeys": ["GCI13"] + "ruleKeys": ["GCI13", "GCI2536"] } diff --git a/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPluginTest.java b/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPluginTest.java index c6d07b9..5c842fb 100644 --- a/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPluginTest.java +++ b/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPluginTest.java @@ -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); } } diff --git a/test-project/package.json b/test-project/package.json index 48eccd6..9492522 100644 --- a/test-project/package.json +++ b/test-project/package.json @@ -23,5 +23,9 @@ "typescript": "~5.9.3", "typescript-eslint": "^8.59.1" }, - "packageManager": "yarn@4.14.1" + "packageManager": "yarn@4.14.1", + "browserslist": [ + "defaults", + "not ie 11" + ] } diff --git a/test-project/sonar-project.properties b/test-project/sonar-project.properties index 764219c..1866cc0 100644 --- a/test-project/sonar-project.properties +++ b/test-project/sonar-project.properties @@ -1,3 +1,3 @@ sonar.exclusions=node_modules -sonar.sources=src +sonar.sources=src,package.json sonar.projectKey=creedengo-javascript-test-project diff --git a/test-project/yarn.lock b/test-project/yarn.lock index 4cd76f6..4b25748 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=b22b11&locator=creedengo-javascript-test-project%40workspace%3A." peerDependencies: eslint: ^9.0.0 || ^10.0.0 - checksum: 10c0/35357906578cb26b758f44fb8fdb12656de3a6d3297d97002f3a1c755066b81e1321badd1f4a01a9e9d156b0545b5611774c2ed83e80600168c134f0dc0846fd + checksum: 10c0/b46e04628e19ea19cec1c78d01cbed468c031304326eedb1a30d39aa8451e1347daee2c7fd823a509f14df81d2801fba82e6d4a0fcecfad832b5d69eeed7bd17 languageName: node linkType: hard From ec3cdea46b5a60d3ca3a39dcd2aac7f3662183f2 Mon Sep 17 00:00:00 2001 From: Sebastien Date: Wed, 20 May 2026 10:55:20 +0200 Subject: [PATCH 2/2] feat: add GCI2536 rule class and sensor implementation --- CHANGELOG.md | 1 + .../creedengo/javascript/CheckList.java | 5 + .../javascript/JavaScriptPlugin.java | 4 +- ...imizeBrowserslistTagInPackageJsonRule.java | 94 +++++++++++ ...izeBrowserslistTagInPackageJsonSensor.java | 83 ++++++++++ .../javascript/checks/PackageJsonCheck.java | 27 ++++ .../javascript/checks/PackageJsonSensor.java | 57 +++++++ ...eBrowserslistTagInPackageJsonRuleTest.java | 101 ++++++++++++ ...rowserslistTagInPackageJsonSensorTest.java | 150 ++++++++++++++++++ .../checks/PackageJsonSensorTest.java | 108 +++++++++++++ 10 files changed, 628 insertions(+), 2 deletions(-) create mode 100644 sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonRule.java create mode 100644 sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonSensor.java create mode 100644 sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonCheck.java create mode 100644 sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonSensor.java create mode 100644 sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonRuleTest.java create mode 100644 sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonSensorTest.java create mode 100644 sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonSensorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d22fe0..6dc6451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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" +- [#118](https://github.com/green-code-initiative/creedengo-javascript/pull/118) Add rule GCI2536 "Move the browserslist configuration to a production target in package.json" ## [3.1.0] - 2026-05-10 diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java index 3421e60..6a02b6c 100644 --- a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/CheckList.java @@ -18,6 +18,7 @@ package org.greencodeinitiative.creedengo.javascript; import org.greencodeinitiative.creedengo.javascript.checks.*; +import org.greencodeinitiative.creedengo.javascript.checks.PackageJsonCheck; import org.sonar.plugins.javascript.api.EslintHook; import org.sonar.plugins.javascript.api.JavaScriptRule; import org.sonar.plugins.javascript.api.TypeScriptRule; @@ -68,6 +69,10 @@ private static List> filterHooksByAnnotation(Class getPackageJsonChecks() { + return Collections.singletonList(new OptimizeBrowserslistTagInPackageJsonRule()); + } + public static List> getSensorChecks() { return Collections.singletonList(OptimizeBrowserslistTagInPackageJsonRule.class); } diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPlugin.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPlugin.java index eb6e0e3..dcef385 100644 --- a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPlugin.java +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/JavaScriptPlugin.java @@ -17,7 +17,7 @@ */ package org.greencodeinitiative.creedengo.javascript; -import org.greencodeinitiative.creedengo.javascript.checks.OptimizeBrowserslistTagInPackageJsonSensor; +import org.greencodeinitiative.creedengo.javascript.checks.PackageJsonSensor; import org.sonar.api.Plugin; public class JavaScriptPlugin implements Plugin { @@ -32,7 +32,7 @@ public void define(Context context) { JavaScriptRuleRepository.class, TypeScriptRulesDefinition.class, TypeScriptRuleRepository.class, - OptimizeBrowserslistTagInPackageJsonSensor.class + PackageJsonSensor.class ); } diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonRule.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonRule.java new file mode 100644 index 0000000..384f930 --- /dev/null +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonRule.java @@ -0,0 +1,94 @@ +/* + * 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 . + */ +package org.greencodeinitiative.creedengo.javascript.checks; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.greencodeinitiative.creedengo.javascript.JavaScriptRuleRepository; +import org.greencodeinitiative.creedengo.javascript.TypeScriptRuleRepository; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.NewIssue; +import org.sonar.api.rule.RuleKey; +import org.sonar.check.Rule; + +@Rule(key = OptimizeBrowserslistTagInPackageJsonRule.KEY) +public final class OptimizeBrowserslistTagInPackageJsonRule implements PackageJsonCheck { + + public static final String KEY = "GCI2536"; + + public 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); + + @Override + public void analyze(SensorContext context, InputFile inputFile, String contents) { + RuleKey jsRuleKey = RuleKey.of(JavaScriptRuleRepository.KEY, KEY); + RuleKey tsRuleKey = RuleKey.of(TypeScriptRuleRepository.KEY, KEY); + + RuleKey activeRuleKey = null; + if (context.activeRules().find(jsRuleKey) != null) { + activeRuleKey = jsRuleKey; + } else if (context.activeRules().find(tsRuleKey) != null) { + activeRuleKey = tsRuleKey; + } + + if (activeRuleKey == null || !isNonCompliant(contents)) { + return; + } + + int line = browserslistLineNumber(contents); + NewIssue issue = context.newIssue().forRule(activeRuleKey); + issue.at( + issue.newLocation() + .on(inputFile) + .at(inputFile.selectLine(line)) + .message(ISSUE_MESSAGE) + ).save(); + } + + public 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; + } + + public 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(); + } + +} diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonSensor.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonSensor.java new file mode 100644 index 0000000..b4d9fe4 --- /dev/null +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonSensor.java @@ -0,0 +1,83 @@ +/* + * 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 . + */ +package org.greencodeinitiative.creedengo.javascript.checks; + +import java.io.IOException; + +import org.greencodeinitiative.creedengo.javascript.JavaScriptRuleRepository; +import org.greencodeinitiative.creedengo.javascript.TypeScriptRuleRepository; +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.Sensor; +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.rule.RuleKey; + +public class OptimizeBrowserslistTagInPackageJsonSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .name("Creedengo - Optimize browserslist tag in package.json") + .createIssuesForRuleRepository(JavaScriptRuleRepository.KEY, TypeScriptRuleRepository.KEY); + } + + @Override + public void execute(SensorContext context) { + RuleKey jsRuleKey = RuleKey.of(JavaScriptRuleRepository.KEY, OptimizeBrowserslistTagInPackageJsonRule.KEY); + RuleKey tsRuleKey = RuleKey.of(TypeScriptRuleRepository.KEY, OptimizeBrowserslistTagInPackageJsonRule.KEY); + + RuleKey activeRuleKey = null; + if (context.activeRules().find(jsRuleKey) != null) { + activeRuleKey = jsRuleKey; + } else if (context.activeRules().find(tsRuleKey) != null) { + activeRuleKey = tsRuleKey; + } + + if (activeRuleKey == null) { + return; + } + + FileSystem fs = context.fileSystem(); + FilePredicate predicate = fs.predicates().matchesPathPattern("**/package.json"); + for (InputFile inputFile : fs.inputFiles(predicate)) { + analyzeFile(context, inputFile, activeRuleKey); + } + } + + private void analyzeFile(SensorContext context, InputFile inputFile, RuleKey ruleKey) { + try { + String contents = inputFile.contents(); + if (OptimizeBrowserslistTagInPackageJsonRule.isNonCompliant(contents)) { + int line = OptimizeBrowserslistTagInPackageJsonRule.browserslistLineNumber(contents); + NewIssue issue = context.newIssue().forRule(ruleKey); + issue.at( + issue.newLocation() + .on(inputFile) + .at(inputFile.selectLine(line)) + .message(OptimizeBrowserslistTagInPackageJsonRule.ISSUE_MESSAGE) + ).save(); + } + } catch (IOException e) { + // skip unreadable file + } + } + +} diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonCheck.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonCheck.java new file mode 100644 index 0000000..9e376ae --- /dev/null +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonCheck.java @@ -0,0 +1,27 @@ +/* + * 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 . + */ +package org.greencodeinitiative.creedengo.javascript.checks; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.SensorContext; + +public interface PackageJsonCheck { + + void analyze(SensorContext context, InputFile inputFile, String contents); + +} diff --git a/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonSensor.java b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonSensor.java new file mode 100644 index 0000000..3d3c37b --- /dev/null +++ b/sonar-plugin/src/main/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonSensor.java @@ -0,0 +1,57 @@ +/* + * 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 . + */ +package org.greencodeinitiative.creedengo.javascript.checks; + +import java.io.IOException; + +import org.greencodeinitiative.creedengo.javascript.CheckList; +import org.greencodeinitiative.creedengo.javascript.JavaScriptRuleRepository; +import org.greencodeinitiative.creedengo.javascript.TypeScriptRuleRepository; +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.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; + +public class PackageJsonSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .name("Creedengo - package.json checks") + .createIssuesForRuleRepository(JavaScriptRuleRepository.KEY, TypeScriptRuleRepository.KEY); + } + + @Override + public void execute(SensorContext context) { + FileSystem fs = context.fileSystem(); + FilePredicate predicate = fs.predicates().matchesPathPattern("**/package.json"); + for (InputFile inputFile : fs.inputFiles(predicate)) { + try { + String contents = inputFile.contents(); + for (PackageJsonCheck check : CheckList.getPackageJsonChecks()) { + check.analyze(context, inputFile, contents); + } + } catch (IOException e) { + // skip unreadable file + } + } + } + +} diff --git a/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonRuleTest.java b/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonRuleTest.java new file mode 100644 index 0000000..4da3c46 --- /dev/null +++ b/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonRuleTest.java @@ -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 . + */ +package org.greencodeinitiative.creedengo.javascript.checks; + +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); + } + +} \ No newline at end of file diff --git a/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonSensorTest.java b/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonSensorTest.java new file mode 100644 index 0000000..39f8970 --- /dev/null +++ b/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/OptimizeBrowserslistTagInPackageJsonSensorTest.java @@ -0,0 +1,150 @@ +/* + * 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 . + */ +package org.greencodeinitiative.creedengo.javascript.checks; + +import org.greencodeinitiative.creedengo.javascript.JavaScriptRuleRepository; +import org.greencodeinitiative.creedengo.javascript.TypeScriptRuleRepository; +import org.junit.jupiter.api.Test; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.rule.ActiveRule; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.NewIssue; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.rule.RuleKey; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class OptimizeBrowserslistTagInPackageJsonSensorTest { + + private final OptimizeBrowserslistTagInPackageJsonRule rule = new OptimizeBrowserslistTagInPackageJsonRule(); + + @Test + void analyzeSkipsWhenRuleNotActive() { + ActiveRules activeRules = mock(ActiveRules.class); + when(activeRules.find(any(RuleKey.class))).thenReturn(null); + + SensorContext context = mock(SensorContext.class); + when(context.activeRules()).thenReturn(activeRules); + + rule.analyze(context, mock(InputFile.class), "{\"name\": \"app\", \"browserslist\": [\"defaults\"]}"); + + verify(context, never()).newIssue(); + } + + @Test + void analyzeCreatesIssueForNonCompliantPackageJson() { + String contents = """ + { + "name": "app", + "browserslist": [ + "defaults" + ] + } + """; + + RuleKey ruleKey = RuleKey.of(JavaScriptRuleRepository.KEY, OptimizeBrowserslistTagInPackageJsonRule.KEY); + ActiveRules activeRules = mock(ActiveRules.class); + when(activeRules.find(ruleKey)).thenReturn(mock(ActiveRule.class)); + + TextRange textRange = mock(TextRange.class); + InputFile inputFile = mock(InputFile.class); + when(inputFile.selectLine(3)).thenReturn(textRange); + + NewIssueLocation location = mock(NewIssueLocation.class); + when(location.on(inputFile)).thenReturn(location); + when(location.at(textRange)).thenReturn(location); + when(location.message(any())).thenReturn(location); + + NewIssue newIssue = mock(NewIssue.class); + when(newIssue.forRule(ruleKey)).thenReturn(newIssue); + when(newIssue.newLocation()).thenReturn(location); + when(newIssue.at(location)).thenReturn(newIssue); + + SensorContext context = mock(SensorContext.class); + when(context.activeRules()).thenReturn(activeRules); + when(context.newIssue()).thenReturn(newIssue); + + rule.analyze(context, inputFile, contents); + + verify(newIssue).save(); + } + + @Test + void analyzeSkipsCompliantPackageJson() { + String contents = """ + { + "browserslist": { + "production": ["last 2 Chrome versions"], + "development": ["last 1 Chrome version"] + } + } + """; + + RuleKey ruleKey = RuleKey.of(JavaScriptRuleRepository.KEY, OptimizeBrowserslistTagInPackageJsonRule.KEY); + ActiveRules activeRules = mock(ActiveRules.class); + when(activeRules.find(ruleKey)).thenReturn(mock(ActiveRule.class)); + + SensorContext context = mock(SensorContext.class); + when(context.activeRules()).thenReturn(activeRules); + + rule.analyze(context, mock(InputFile.class), contents); + + verify(context, never()).newIssue(); + } + + @Test + void analyzeFallsBackToTypeScriptRepository() { + String contents = """ + { + "browserslist": ["defaults"] + } + """; + + RuleKey jsKey = RuleKey.of(JavaScriptRuleRepository.KEY, OptimizeBrowserslistTagInPackageJsonRule.KEY); + RuleKey tsKey = RuleKey.of(TypeScriptRuleRepository.KEY, OptimizeBrowserslistTagInPackageJsonRule.KEY); + ActiveRules activeRules = mock(ActiveRules.class); + when(activeRules.find(jsKey)).thenReturn(null); + when(activeRules.find(tsKey)).thenReturn(mock(ActiveRule.class)); + + TextRange textRange = mock(TextRange.class); + InputFile inputFile = mock(InputFile.class); + when(inputFile.selectLine(2)).thenReturn(textRange); + + NewIssueLocation location = mock(NewIssueLocation.class); + when(location.on(inputFile)).thenReturn(location); + when(location.at(textRange)).thenReturn(location); + when(location.message(any())).thenReturn(location); + + NewIssue newIssue = mock(NewIssue.class); + when(newIssue.forRule(tsKey)).thenReturn(newIssue); + when(newIssue.newLocation()).thenReturn(location); + when(newIssue.at(location)).thenReturn(newIssue); + + SensorContext context = mock(SensorContext.class); + when(context.activeRules()).thenReturn(activeRules); + when(context.newIssue()).thenReturn(newIssue); + + rule.analyze(context, inputFile, contents); + + verify(newIssue).save(); + } + +} diff --git a/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonSensorTest.java b/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonSensorTest.java new file mode 100644 index 0000000..fe9db77 --- /dev/null +++ b/sonar-plugin/src/test/java/org/greencodeinitiative/creedengo/javascript/checks/PackageJsonSensorTest.java @@ -0,0 +1,108 @@ +/* + * 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 . + */ +package org.greencodeinitiative.creedengo.javascript.checks; + +import java.io.IOException; +import java.util.List; + +import org.greencodeinitiative.creedengo.javascript.JavaScriptRuleRepository; +import org.greencodeinitiative.creedengo.javascript.TypeScriptRuleRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +class PackageJsonSensorTest { + + private SensorContext context; + private FilePredicate predicate; + private FileSystem fs; + private PackageJsonSensor sensor; + + @BeforeEach + void setUp() { + sensor = new PackageJsonSensor(); + + predicate = mock(FilePredicate.class); + FilePredicates predicates = mock(FilePredicates.class); + when(predicates.matchesPathPattern("**/package.json")).thenReturn(predicate); + + fs = mock(FileSystem.class); + when(fs.predicates()).thenReturn(predicates); + + ActiveRules activeRules = mock(ActiveRules.class); + when(activeRules.find(any())).thenReturn(null); + + context = mock(SensorContext.class); + when(context.fileSystem()).thenReturn(fs); + when(context.activeRules()).thenReturn(activeRules); + } + + @Test + void describe() { + SensorDescriptor descriptor = mock(SensorDescriptor.class); + when(descriptor.name(anyString())).thenReturn(descriptor); + + sensor.describe(descriptor); + + verify(descriptor).name("Creedengo - package.json checks"); + verify(descriptor).createIssuesForRuleRepository(JavaScriptRuleRepository.KEY, TypeScriptRuleRepository.KEY); + } + + @Test + void executeCallsChecksForEachPackageJson() throws IOException { + InputFile inputFile = mock(InputFile.class); + when(inputFile.contents()).thenReturn("{\"name\": \"app\"}"); + when(fs.inputFiles(predicate)).thenReturn(List.of(inputFile)); + + sensor.execute(context); + + verify(inputFile).contents(); + } + + @Test + void executeSkipsUnreadableFile() throws IOException { + InputFile inputFile = mock(InputFile.class); + when(inputFile.contents()).thenThrow(new IOException("disk error")); + when(fs.inputFiles(predicate)).thenReturn(List.of(inputFile)); + + sensor.execute(context); + + verify(inputFile).contents(); + } + + @Test + void executeDoesNothingWhenNoPackageJson() throws IOException { + InputFile inputFile = mock(InputFile.class); + when(fs.inputFiles(predicate)).thenReturn(List.of()); + + sensor.execute(context); + + verify(inputFile, never()).contents(); + } + +}