diff --git a/its/plugin/tests/src/test/java/com/sonar/javascript/it/plugin/EslintBasedRulesTest.java b/its/plugin/tests/src/test/java/com/sonar/javascript/it/plugin/EslintBasedRulesTest.java index 5177e05fc64..30a09ea1c67 100644 --- a/its/plugin/tests/src/test/java/com/sonar/javascript/it/plugin/EslintBasedRulesTest.java +++ b/its/plugin/tests/src/test/java/com/sonar/javascript/it/plugin/EslintBasedRulesTest.java @@ -141,7 +141,7 @@ void test_exclusion_filter() throws Exception { .setSourceEncoding("UTF-8") .setSourceDirs(".") .setProjectDir(TestUtils.projectDirNoCopy("file-filter/excluded_dir/project")) - .setProperty("sonar.javascript.exclusions", "excluded_dir/**"); + .setProperty("sonar.javascript.exclusions", "excluded_dir/**,**/node_modules"); OrchestratorStarter.setProfile(projectKey, jsProfile, "js"); diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptPlugin.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptPlugin.java index 1bccbe58f00..e798f3386c8 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptPlugin.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptPlugin.java @@ -39,7 +39,7 @@ import org.sonar.plugins.javascript.analysis.HtmlSensor; import org.sonar.plugins.javascript.analysis.JsTsChecks; import org.sonar.plugins.javascript.analysis.JsTsSensor; -import org.sonar.plugins.javascript.analysis.TsConfigCacheImpl; +import org.sonar.plugins.javascript.sonarlint.TsConfigCacheImpl; import org.sonar.plugins.javascript.analysis.TsConfigProvider; import org.sonar.plugins.javascript.analysis.YamlSensor; import org.sonar.plugins.javascript.bridge.AnalysisWarningsWrapper; @@ -59,7 +59,6 @@ import org.sonar.plugins.javascript.rules.JavaScriptRulesDefinition; import org.sonar.plugins.javascript.rules.TslintRulesDefinition; import org.sonar.plugins.javascript.rules.TypeScriptRulesDefinition; -import org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingCheckerImpl; public class JavaScriptPlugin implements Plugin { @@ -338,7 +337,6 @@ public void addSonarLintExtensions( SonarLintPluginAPIVersion sonarLintPluginAPIVersion ) { if (sonarLintPluginAPIVersion.isDependencyAvailable()) { - context.addExtension(SonarLintTypeCheckingCheckerImpl.class); context.addExtension(TsConfigCacheImpl.class); } else { LOG.debug("Error while trying to inject SonarLint extensions"); diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AnalysisWithWatchProgram.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AnalysisWithWatchProgram.java index 097e1c40adb..b1cf4666989 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AnalysisWithWatchProgram.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AnalysisWithWatchProgram.java @@ -26,6 +26,7 @@ import org.sonar.api.scanner.ScannerSide; import org.sonar.plugins.javascript.bridge.AnalysisWarningsWrapper; import org.sonar.plugins.javascript.bridge.BridgeServer; +import org.sonar.plugins.javascript.sonarlint.TsConfigCache; import org.sonar.plugins.javascript.utils.ProgressReport; import org.sonarsource.api.sonarlint.SonarLintSide; diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/JsTsSensor.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/JsTsSensor.java index afd7bd0ec30..9f0348c9f2b 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/JsTsSensor.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/JsTsSensor.java @@ -24,7 +24,6 @@ import java.io.IOException; import java.util.List; import java.util.stream.StreamSupport; -import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.DependedUpon; @@ -37,7 +36,7 @@ import org.sonar.plugins.javascript.TypeScriptLanguage; import org.sonar.plugins.javascript.bridge.AnalysisMode; import org.sonar.plugins.javascript.bridge.BridgeServer; -import org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingChecker; +import org.sonar.plugins.javascript.sonarlint.TsConfigCache; @DependedUpon("js-analysis") public class JsTsSensor extends AbstractBridgeSensor { @@ -46,41 +45,20 @@ public class JsTsSensor extends AbstractBridgeSensor { private final AnalysisWithProgram analysisWithProgram; private final AnalysisWithWatchProgram analysisWithWatchProgram; private final JsTsChecks checks; - private final SonarLintTypeCheckingChecker javaScriptProjectChecker; private final AnalysisConsumers consumers; private final TsConfigCache tsConfigCache; - // Constructor for SonarCloud without the optional dependency (Pico doesn't support optional dependencies) public JsTsSensor( JsTsChecks checks, BridgeServer bridgeServer, AnalysisWithProgram analysisWithProgram, AnalysisWithWatchProgram analysisWithWatchProgram, AnalysisConsumers consumers - ) { - this( - checks, - bridgeServer, - null, - analysisWithProgram, - analysisWithWatchProgram, - consumers - ); - } - - public JsTsSensor( - JsTsChecks checks, - BridgeServer bridgeServer, - @Nullable SonarLintTypeCheckingChecker javaScriptProjectChecker, - AnalysisWithProgram analysisWithProgram, - AnalysisWithWatchProgram analysisWithWatchProgram, - AnalysisConsumers consumers ) { super(bridgeServer, "JS/TS"); this.analysisWithProgram = analysisWithProgram; this.analysisWithWatchProgram = analysisWithWatchProgram; this.checks = checks; - this.javaScriptProjectChecker = javaScriptProjectChecker; this.consumers = consumers; this.tsConfigCache = analysisWithWatchProgram.tsConfigCache; } @@ -115,7 +93,6 @@ protected void analyzeFiles(List inputFiles) throws IOException { var tsConfigs = getTsConfigs( contextUtils, - javaScriptProjectChecker, this::createTsConfigFile, tsConfigCache ); diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingFilter.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/LookupConfigProviderFilter.java similarity index 91% rename from sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingFilter.java rename to sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/LookupConfigProviderFilter.java index fa98c66289c..43c0f0ae1a6 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingFilter.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/LookupConfigProviderFilter.java @@ -17,22 +17,22 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.plugins.javascript.sonarlint; +package org.sonar.plugins.javascript.analysis; -import static java.util.Arrays.stream; -import static java.util.stream.Stream.concat; +import org.sonar.api.config.Configuration; +import org.sonar.api.utils.WildcardPattern; +import org.sonar.plugins.javascript.JavaScriptLanguage; +import org.sonar.plugins.javascript.JavaScriptPlugin; +import org.sonar.plugins.javascript.TypeScriptLanguage; import java.nio.file.Path; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.function.Predicate; -import org.sonar.api.config.Configuration; -import org.sonar.api.utils.WildcardPattern; -import org.sonar.plugins.javascript.JavaScriptLanguage; -import org.sonar.plugins.javascript.JavaScriptPlugin; -import org.sonar.plugins.javascript.TypeScriptLanguage; -import org.sonar.plugins.javascript.filter.JavaScriptExclusionsFileFilter; + +import static java.util.Arrays.stream; +import static java.util.stream.Stream.concat; /** * This class partially reproduces the behavior of JavaScriptExclusionsFileFilter's implementation. @@ -48,9 +48,8 @@ * * @see JavaScriptExclusionsFileFilter */ -public class SonarLintTypeCheckingFilter { - - private SonarLintTypeCheckingFilter() {} +public class LookupConfigProviderFilter { + private LookupConfigProviderFilter() {} static class FileFilter implements Predicate { @@ -104,13 +103,13 @@ public PathFilter(Configuration config) { private static boolean isExclusionOverridden(Configuration config) { return ( config.get(JavaScriptPlugin.JS_EXCLUSIONS_KEY).isPresent() || - config.get(JavaScriptPlugin.TS_EXCLUSIONS_KEY).isPresent() + config.get(JavaScriptPlugin.TS_EXCLUSIONS_KEY).isPresent() ); } @Override public boolean test(Path path) { - return WildcardPattern.match(exclusions, path.toString().replaceAll("[\\\\/]", "/")); + return !WildcardPattern.match(exclusions, path.toString().replaceAll("[\\\\/]", "/")); } } } diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigProvider.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigProvider.java index e2709c0e4d7..c68ff5e1f37 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigProvider.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigProvider.java @@ -44,8 +44,10 @@ import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorContext; +import static org.sonar.plugins.javascript.analysis.LookupConfigProviderFilter.FileFilter; +import static org.sonar.plugins.javascript.analysis.LookupConfigProviderFilter.PathFilter; import org.sonar.plugins.javascript.JavaScriptFilePredicate; -import org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingChecker; +import org.sonar.plugins.javascript.sonarlint.TsConfigCache; import org.sonarsource.analyzer.commons.FileProvider; public class TsConfigProvider { @@ -80,12 +82,11 @@ interface TsConfigFileCreator { */ static List getTsConfigs( ContextUtils contextUtils, - @Nullable SonarLintTypeCheckingChecker javaScriptProjectChecker, TsConfigProvider.TsConfigFileCreator tsConfigFileCreator, @Nullable TsConfigCache tsConfigCache ) throws IOException { var defaultProvider = contextUtils.isSonarLint() - ? new TsConfigProvider.WildcardTsConfigProvider(javaScriptProjectChecker, tsConfigFileCreator) + ? new TsConfigProvider.WildcardTsConfigProvider(tsConfigCache, tsConfigFileCreator) : new TsConfigProvider.DefaultTsConfigProvider(tsConfigFileCreator, JavaScriptFilePredicate::getJsTsPredicate); @@ -192,9 +193,11 @@ public List tsconfigs(SensorContext context) { if (tsconfigs != null) { return tsconfigs; } - } var fs = context.fileSystem(); + var fileCount = 0; + var fileFilter = new FileFilter(context.config()); + var pathFilter = new PathFilter(context.config()); var tsconfigs = new ArrayList(); var dirs = new ArrayDeque(); dirs.add(fs.baseDir()); @@ -205,14 +208,24 @@ public List tsconfigs(SensorContext context) { continue; } for (var file : files) { - if (file.isDirectory() && !"node_modules".equals(file.getName())) { + if (!pathFilter.test(file.toPath())) { + continue; + } + if (file.isDirectory()) { dirs.add(file); - } else if ("tsconfig.json".equals(file.getName())) { - tsconfigs.add(file.getAbsolutePath()); + } else { + if (fileFilter.test(file.toPath())) { + fileCount++; + } else if ("tsconfig.json".equals(file.getName())) { + tsconfigs.add(file.getAbsolutePath()); + } } } } LOG.info("Found {} tsconfig.json file(s): {}", tsconfigs.size(), tsconfigs); + if (cache != null) { + cache.setProjectSize(fileCount); + } return tsconfigs; } @@ -306,31 +319,34 @@ private File writeToJsonFile(TsConfig tsConfig) throws IOException { } static class WildcardTsConfigProvider extends GeneratedTsConfigFileProvider { - private static String getProjectRoot(SensorContext context) { - var projectBaseDir = context.fileSystem().baseDir().getAbsolutePath(); - return "/".equals(File.separator) - ? projectBaseDir - : projectBaseDir.replace(File.separator, "/"); - } + static final String MAX_FILES_PROPERTY = "sonar.javascript.sonarlint.typechecking.maxfiles"; + static final int DEFAULT_MAX_FILES_FOR_TYPE_CHECKING = 20_000; private static final Map> defaultWildcardTsConfig = new ConcurrentHashMap<>(); + final TsConfigCache tsConfigCache; final TsConfigFileCreator tsConfigFileCreator; - SonarLintTypeCheckingChecker checker; WildcardTsConfigProvider( - @Nullable SonarLintTypeCheckingChecker checker, + @Nullable TsConfigCache tsConfigCache, TsConfigFileCreator tsConfigFileCreator ) { super(SonarProduct.SONARLINT); + this.tsConfigCache = tsConfigCache; this.tsConfigFileCreator = tsConfigFileCreator; - this.checker = checker; + } + + private static String getProjectRoot(SensorContext context) { + var projectBaseDir = context.fileSystem().baseDir().getAbsolutePath(); + return "/".equals(File.separator) + ? projectBaseDir + : projectBaseDir.replace(File.separator, "/"); } @Override List getDefaultTsConfigs(SensorContext context) { - boolean deactivated = checker == null || checker.isBeyondLimit(context); + boolean deactivated = tsConfigCache == null || isBeyondLimit(context, tsConfigCache.getProjectSize()); if (deactivated) { return emptyList(); } else { @@ -347,5 +363,40 @@ List writeTsConfigFileFor(String root) { LOG.debug("Using generated tsconfig.json file using wildcards {}", file); return file; } + + static boolean isBeyondLimit(SensorContext context, int projectSize) { + var typeCheckingLimit = getTypeCheckingLimit(context); + + var beyondLimit = projectSize >= typeCheckingLimit; + if (!beyondLimit) { + LOG.info("Turning on type-checking of JavaScript files"); + } else { + // TypeScript type checking mechanism creates performance issues for large projects. Analyzing a file can take more than a minute in + // SonarLint, and it can even lead to runtime errors due to Node.js being out of memory during the process. + LOG.warn( + "Turning off type-checking of JavaScript files due to the project size exceeding the limit ({} files)", + typeCheckingLimit + ); + LOG.warn("This may cause rules dependent on type information to not behave as expected"); + LOG.warn( + "Check the list of impacted rules at https://rules.sonarsource.com/javascript/tag/type-dependent" + ); + LOG.warn( + "To turn type-checking back on, increase the \"{}\" property value", + MAX_FILES_PROPERTY + ); + LOG.warn( + "Please be aware that this could potentially impact the performance of the analysis" + ); + } + return beyondLimit; + } + + static int getTypeCheckingLimit(SensorContext context) { + return Math.max( + context.config().getInt(MAX_FILES_PROPERTY).orElse(DEFAULT_MAX_FILES_FOR_TYPE_CHECKING), + 0 + ); + } } } diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingChecker.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingChecker.java deleted file mode 100644 index e74046284c9..00000000000 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingChecker.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SonarQube JavaScript Plugin - * Copyright (C) 2011-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.plugins.javascript.sonarlint; - -import org.sonar.api.batch.sensor.SensorContext; - -public interface SonarLintTypeCheckingChecker { - boolean isBeyondLimit(SensorContext context); -} diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingCheckerImpl.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingCheckerImpl.java deleted file mode 100644 index 4b54a1b9eb9..00000000000 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingCheckerImpl.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SonarQube JavaScript Plugin - * Copyright (C) 2011-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.plugins.javascript.sonarlint; - -import static org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingFilter.FileFilter; -import static org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingFilter.PathFilter; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.stream.Stream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.plugins.javascript.utils.PathWalker; -import org.sonarsource.api.sonarlint.SonarLintSide; - -@SonarLintSide(lifespan = "MODULE") -public class SonarLintTypeCheckingCheckerImpl implements SonarLintTypeCheckingChecker { - - private static final Logger LOG = LoggerFactory.getLogger(SonarLintTypeCheckingCheckerImpl.class); - static final String MAX_FILES_PROPERTY = "sonar.javascript.sonarlint.typechecking.maxfiles"; - static final int DEFAULT_MAX_FILES_FOR_TYPE_CHECKING = 20_000; - private static final int FILE_WALK_MAX_DEPTH = 20; - - private boolean beyondLimit = true; - - private boolean shouldCheck = true; - - public boolean isBeyondLimit(SensorContext context) { - if (shouldCheck) { - checkLimit(context); - shouldCheck = false; - } - return beyondLimit; - } - - private void checkLimit(SensorContext context) { - try { - var typeCheckingLimit = getTypeCheckingLimit(context); - var projectSize = countProjectSize(context, typeCheckingLimit); - - beyondLimit = projectSize >= typeCheckingLimit; - if (!beyondLimit) { - LOG.info("Turning on type-checking of JavaScript files"); - } else { - // TypeScript type checking mechanism creates performance issues for large projects. Analyzing a file can take more than a minute in - // SonarLint, and it can even lead to runtime errors due to Node.js being out of memory during the process. - LOG.warn( - "Turning off type-checking of JavaScript files due to the project size exceeding the limit ({} files)", - typeCheckingLimit - ); - LOG.warn("This may cause rules dependent on type information to not behave as expected"); - LOG.warn( - "Check the list of impacted rules at https://rules.sonarsource.com/javascript/tag/type-dependent" - ); - LOG.warn( - "To turn type-checking back on, increase the \"{}\" property value", - MAX_FILES_PROPERTY - ); - LOG.warn( - "Please be aware that this could potentially impact the performance of the analysis" - ); - } - } catch (RuntimeException e) { - // Any runtime error raised by the SonarLint API would be caught here to let the analyzer proceed with the rules that don't require - // type checking. - LOG.warn("Turning off type-checking of JavaScript files due to unexpected error", e); - } - } - - private static long countProjectSize(SensorContext context, long maxSize) { - var fileFilter = new FileFilter(context.config()); - - try (var files = walkProjectFiles(context)) { - return files - .filter(path -> Files.isRegularFile(path) && fileFilter.test(path)) - .limit(maxSize) - .count(); - } - } - - private static Stream walkProjectFiles(SensorContext context) { - // The Files.walk() is failing on Windows with WSL (see https://bugs.openjdk.org/browse/JDK-8259617) - return PathWalker.stream( - context.fileSystem().baseDir().toPath(), - FILE_WALK_MAX_DEPTH, - new PathFilter(context.config()) - ); - } - - private static int getTypeCheckingLimit(SensorContext context) { - return Math.max( - context.config().getInt(MAX_FILES_PROPERTY).orElse(DEFAULT_MAX_FILES_FOR_TYPE_CHECKING), - 0 - ); - } -} diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigCache.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/TsConfigCache.java similarity index 87% rename from sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigCache.java rename to sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/TsConfigCache.java index 3509ecaaf45..ca092df0fac 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigCache.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/TsConfigCache.java @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.plugins.javascript.analysis; +package org.sonar.plugins.javascript.sonarlint; import java.util.List; import org.sonar.api.batch.fs.InputFile; +import org.sonar.plugins.javascript.analysis.TsConfigOrigin; import org.sonar.plugins.javascript.bridge.TsConfigFile; public interface TsConfigCache { @@ -28,4 +29,7 @@ public interface TsConfigCache { void initializeWith(List tsConfigs, TsConfigOrigin origin); List listCachedTsConfigs(TsConfigOrigin origin); void setOrigin(TsConfigOrigin origin); + + void setProjectSize(int projectSize); + int getProjectSize(); } diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigCacheImpl.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/TsConfigCacheImpl.java similarity index 96% rename from sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigCacheImpl.java rename to sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/TsConfigCacheImpl.java index 9484fbbf2ea..bd1f445324d 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/TsConfigCacheImpl.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/sonarlint/TsConfigCacheImpl.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.plugins.javascript.analysis; +package org.sonar.plugins.javascript.sonarlint; import java.nio.file.Path; import java.util.ArrayDeque; @@ -35,6 +35,7 @@ import org.sonar.api.batch.fs.InputFile; import org.sonar.api.scanner.ScannerSide; import org.sonar.plugins.javascript.JavaScriptFilePredicate; +import org.sonar.plugins.javascript.analysis.TsConfigOrigin; import org.sonar.plugins.javascript.bridge.BridgeServer; import org.sonar.plugins.javascript.bridge.TsConfigFile; import org.sonarsource.api.sonarlint.SonarLintSide; @@ -48,10 +49,11 @@ public class TsConfigCacheImpl implements TsConfigCache, ModuleFileListener { BridgeServer bridgeServer; TsConfigOrigin origin; + int projectSize; Map cacheMap = new EnumMap<>(TsConfigOrigin.class); - TsConfigCacheImpl(BridgeServer bridgeServer) { + public TsConfigCacheImpl(BridgeServer bridgeServer) { this.bridgeServer = bridgeServer; cacheMap.put(TsConfigOrigin.PROPERTY, new Cache()); cacheMap.put(TsConfigOrigin.LOOKUP, new Cache()); @@ -204,4 +206,12 @@ public void process(ModuleFileEvent moduleFileEvent) { cacheMap.values().forEach(Cache::clearFileToTsConfigCache); } } + + public void setProjectSize(int projectSize) { + this.projectSize = projectSize; + } + + public int getProjectSize() { + return projectSize; + } } diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptPluginTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptPluginTest.java index 1b2522534bd..694373ece5a 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptPluginTest.java +++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptPluginTest.java @@ -43,7 +43,7 @@ class JavaScriptPluginTest { private static final int JS_ADDITIONAL_EXTENSIONS = 4; private static final int TS_ADDITIONAL_EXTENSIONS = 3; private static final int CSS_ADDITIONAL_EXTENSIONS = 3; - private static final int SONARLINT_ADDITIONAL_EXTENSIONS = 2; + private static final int SONARLINT_ADDITIONAL_EXTENSIONS = 1; public static final Version LTS_VERSION = Version.create(7, 9); diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JavaScriptEslintBasedSensorTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JavaScriptEslintBasedSensorTest.java index d4622a5fb87..6956089221b 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JavaScriptEslintBasedSensorTest.java +++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JavaScriptEslintBasedSensorTest.java @@ -28,7 +28,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; -import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -78,7 +77,7 @@ import org.sonar.plugins.javascript.bridge.ServerAlreadyFailedException; import org.sonar.plugins.javascript.bridge.TsConfigFile; import org.sonar.plugins.javascript.nodejs.NodeCommandException; -import org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingChecker; +import org.sonar.plugins.javascript.sonarlint.TsConfigCacheImpl; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; @@ -389,7 +388,7 @@ void should_save_only_nosonar_metric_in_sonarlint() throws Exception { when(bridgeServerMock.loadTsConfig(any())).thenReturn(tsConfigFile); context.setRuntime(SonarRuntimeImpl.forSonarLint(Version.create(4, 4))); - var sensor = createSensor(mock(SonarLintTypeCheckingChecker.class)); + var sensor = createSensor(); sensor.execute(context); assertThat(inputFile.hasNoSonarAt(7)).isTrue(); @@ -643,7 +642,7 @@ void should_send_content_on_sonarlint() throws Exception { when(bridgeServerMock.analyzeJavaScript(any())).thenReturn(new AnalysisResponse()); var captor = ArgumentCaptor.forClass(JsAnalysisRequest.class); - createSensor(mock(SonarLintTypeCheckingChecker.class)).execute(ctx); + createSensor().execute(ctx); verify(bridgeServerMock).analyzeJavaScript(captor.capture()); assertThat(captor.getValue().fileContent()) .isEqualTo("if (cond)\n" + "doFoo(); \n" + "else \n" + "doFoo();"); @@ -786,16 +785,9 @@ private DefaultInputFile createTestInputFile(SensorContextTester context) { } private JsTsSensor createSensor() { - return createSensor(null); - } - - private JsTsSensor createSensor( - @Nullable SonarLintTypeCheckingChecker sonarlintTypeCheckingChecker - ) { return new JsTsSensor( checks(ESLINT_BASED_RULE, "S2260", "S1451"), bridgeServerMock, - sonarlintTypeCheckingChecker, analysisWithProgram, analysisWithWatchProgram, new AnalysisConsumers() diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JsTsSensorTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JsTsSensorTest.java index 77aaa7221b2..beab1df632a 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JsTsSensorTest.java +++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JsTsSensorTest.java @@ -41,9 +41,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -97,6 +95,8 @@ import org.sonar.plugins.javascript.bridge.protobuf.Position; import org.sonar.plugins.javascript.bridge.protobuf.Program; import org.sonar.plugins.javascript.bridge.protobuf.SourceLocation; +import org.sonar.plugins.javascript.sonarlint.TsConfigCache; +import org.sonar.plugins.javascript.sonarlint.TsConfigCacheImpl; import static java.util.Collections.emptyList; import static java.util.Collections.singleton; diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingFilterTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/LookupConfigProviderFilterTest.java similarity index 83% rename from sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingFilterTest.java rename to sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/LookupConfigProviderFilterTest.java index 22e7b42dd7c..40c417e8ca0 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingFilterTest.java +++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/LookupConfigProviderFilterTest.java @@ -17,11 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.plugins.javascript.sonarlint; +package org.sonar.plugins.javascript.analysis; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingFilter.FileFilter; -import static org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingFilter.PathFilter; +import static org.sonar.plugins.javascript.analysis.LookupConfigProviderFilter.FileFilter; +import static org.sonar.plugins.javascript.analysis.LookupConfigProviderFilter.PathFilter; import java.io.IOException; import java.nio.file.Files; @@ -34,7 +34,7 @@ import org.sonar.plugins.javascript.JavaScriptPlugin; import org.sonar.plugins.javascript.TypeScriptLanguage; -class SonarLintTypeCheckingFilterTest { +class LookupConfigProviderFilterTest { @TempDir Path baseDir; @@ -73,10 +73,10 @@ void should_filter_default_paths() throws IOException { Configuration config = settings.asConfig(); var filter = new PathFilter(config); - assertThat(filter.test(inputFile("node_modules", "file.js"))).isTrue(); - assertThat(filter.test(inputFile("bower_components", "file.jsx"))).isTrue(); - assertThat(filter.test(inputFile("file.d.ts"))).isTrue(); - assertThat(filter.test(inputFile("file.js"))).isFalse(); + assertThat(filter.test(inputFile("node_modules", "file.js"))).isFalse(); + assertThat(filter.test(inputFile("bower_components", "file.jsx"))).isFalse(); + assertThat(filter.test(inputFile("file.d.ts"))).isFalse(); + assertThat(filter.test(inputFile("file.js"))).isTrue(); } @Test @@ -88,10 +88,10 @@ void should_filter_specific_paths() throws IOException { Configuration config = settings.asConfig(); var filter = new PathFilter(config); - assertThat(filter.test(inputFile("foo", "file.js"))).isTrue(); - assertThat(filter.test(inputFile("bar", "file.ts"))).isTrue(); - assertThat(filter.test(inputFile("qux", "file.cjs"))).isFalse(); - assertThat(filter.test(inputFile("file.vue"))).isFalse(); + assertThat(filter.test(inputFile("foo", "file.js"))).isFalse(); + assertThat(filter.test(inputFile("bar", "file.ts"))).isFalse(); + assertThat(filter.test(inputFile("qux", "file.cjs"))).isTrue(); + assertThat(filter.test(inputFile("file.vue"))).isTrue(); } private Path inputFile(String filename) throws IOException { diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/TsConfigCacheTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/TsConfigCacheTest.java index a494a5c34bd..bfe2ae5e0c2 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/TsConfigCacheTest.java +++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/TsConfigCacheTest.java @@ -53,6 +53,7 @@ import org.sonar.plugins.javascript.TypeScriptLanguage; import org.sonar.plugins.javascript.bridge.BridgeServerImpl; import org.sonar.plugins.javascript.bridge.TsConfigFile; +import org.sonar.plugins.javascript.sonarlint.TsConfigCacheImpl; import org.sonarsource.sonarlint.core.analysis.container.module.DefaultModuleFileEvent; import org.sonarsource.sonarlint.plugin.api.module.file.ModuleFileEvent; @@ -102,7 +103,7 @@ void test() throws Exception { Files.createFile(tsConfigPath); } SensorContextTester ctx = SensorContextTester.create(baseDir); - TsConfigProvider.getTsConfigs(new ContextUtils(ctx), null, this::tsConfigFileCreator, tsConfigCache); + TsConfigProvider.getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); when(bridgeServerMock.loadTsConfig(any())) .thenAnswer(invocationOnMock -> { @@ -158,7 +159,7 @@ void testResolvesReferences() throws IOException { Files.createFile(tsconfig2); SensorContextTester ctx = SensorContextTester.create(baseDir); - TsConfigProvider.getTsConfigs(new ContextUtils(ctx), null, this::tsConfigFileCreator, tsConfigCache); + TsConfigProvider.getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); when(bridgeServerMock.loadTsConfig(any())).thenAnswer(invocationOnMock -> { String tsConfigPath = (String) invocationOnMock.getArguments()[0]; if (tsConfigPath.equals(tsConfigFile1.getFilename())) { @@ -205,7 +206,7 @@ void testPropertyTsConfigChanged() throws IOException { "tsconfig.*.json,tsconfig.json" ) ); - TsConfigProvider.getTsConfigs(new ContextUtils(ctx), null, this::tsConfigFileCreator, tsConfigCache); + TsConfigProvider.getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); when(bridgeServerMock.loadTsConfig(any())).thenReturn(tsConfigFile); var foundTsConfig = tsConfigCache.getTsConfigForInputFile(file1); @@ -219,7 +220,7 @@ void testPropertyTsConfigChanged() throws IOException { var propertyCachedTsConfig = tsConfigCache.listCachedTsConfigs(TsConfigOrigin.PROPERTY); assertThat(propertyCachedTsConfig).containsExactly(tsconfig1.toAbsolutePath().toString()); - TsConfigProvider.getTsConfigs(new ContextUtils(ctx), null, this::tsConfigFileCreator, tsConfigCache); + TsConfigProvider.getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); propertyCachedTsConfig = tsConfigCache.listCachedTsConfigs(TsConfigOrigin.PROPERTY); assertThat(propertyCachedTsConfig).containsExactlyInAnyOrder(tsconfig1.toAbsolutePath().toString(), tsconfig2.toAbsolutePath().toString()); } @@ -231,7 +232,7 @@ private Pair prepareFileAndTsConfig() throws IOExceptio Files.createFile(tsconfig1); SensorContextTester ctx = SensorContextTester.create(baseDir); - TsConfigProvider.getTsConfigs(new ContextUtils(ctx), null, this::tsConfigFileCreator, tsConfigCache); + TsConfigProvider.getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); when(bridgeServerMock.loadTsConfig(any())).thenReturn(tsConfigFile); return Pair.of(file1, tsConfigFile); } diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/TsConfigProviderTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/TsConfigProviderTest.java index 428aeb8f018..0cd45a99455 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/TsConfigProviderTest.java +++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/TsConfigProviderTest.java @@ -36,13 +36,18 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Optional; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.config.Configuration; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.impl.utils.DefaultTempFolder; import org.sonar.api.internal.SonarRuntimeImpl; @@ -50,7 +55,9 @@ import org.sonar.api.utils.TempFolder; import org.sonar.api.utils.Version; import org.sonar.api.utils.log.LoggerLevel; -import org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingChecker; +import org.sonar.plugins.javascript.bridge.BridgeServer; +import org.sonar.plugins.javascript.sonarlint.TsConfigCache; +import org.sonar.plugins.javascript.sonarlint.TsConfigCacheImpl; class TsConfigProviderTest { @@ -88,7 +95,6 @@ void should_lookup_tsconfig_files() throws Exception { List tsconfigs = getTsConfigs( new ContextUtils(ctx), - null, this::tsConfigFileCreator, null ); @@ -117,7 +123,6 @@ void should_use_tsconfig_from_property() throws Exception { List tsconfigs = getTsConfigs( new ContextUtils(ctx), - null, this::tsConfigFileCreator, null ); @@ -141,7 +146,6 @@ void should_use_absolute_path_from_property() throws Exception { List tsconfigs = getTsConfigs( new ContextUtils(ctx), - null, this::tsConfigFileCreator, null ); @@ -167,7 +171,6 @@ void should_use_multiple_tsconfigs_from_property() throws Exception { List tsconfigs = getTsConfigs( new ContextUtils(ctx), - null, this::tsConfigFileCreator, null ); @@ -205,7 +208,6 @@ void should_use_matching_tsconfigs_from_property() throws Exception { List tsconfigs = getTsConfigs( new ContextUtils(ctx), - null, this::tsConfigFileCreator, null ); @@ -228,7 +230,6 @@ void should_use_tsconfigs_from_property_alias() throws Exception { List tsconfigs = getTsConfigs( new ContextUtils(ctx), - null, this::tsConfigFileCreator, null ); @@ -248,18 +249,14 @@ void should_create_tsconfig() throws Exception { List tsconfigs = getTsConfigs( new ContextUtils(ctx), - null, this::tsConfigFileCreator, null ); assertThat(tsconfigs).hasSize(1); - String tsconfig = new String( - Files.readAllBytes(Paths.get(tsconfigs.get(0))), - StandardCharsets.UTF_8 - ); + String tsconfig = Files.readString(Paths.get(tsconfigs.get(0))); assertThat(tsconfig) - .isEqualTo( - "{\"files\":[\"moduleKey/file1.ts\",\"moduleKey/file2.ts\"],\"compilerOptions\":{\"allowJs\":true,\"noImplicitAny\":true}}" + .isEqualToIgnoringCase( + String.format("{\"files\":[\"%s/file1.ts\",\"%s/file2.ts\"],\"compilerOptions\":{\"allowJs\":true,\"noImplicitAny\":true}}", baseDir.toString().replaceAll("[\\\\/]", "/"), baseDir.toString().replaceAll("[\\\\/]", "/")) ); } @@ -269,15 +266,9 @@ void should_create_wildcard_tsconfig() throws Exception { ctx.setRuntime(SonarRuntimeImpl.forSonarLint(Version.create(4, 4))); createInputFile(ctx, "file1.js"); createInputFile(ctx, "file2.js"); + var tsConfigCache = tsConfigCache(); + var tsconfigs = getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); - var checker = mock(SonarLintTypeCheckingChecker.class); - when(checker.isBeyondLimit(ctx)).thenReturn(false); - - var provider = new WildcardTsConfigProvider( - checker, - TsConfigProviderTest::createTsConfigFile - ); - var tsconfigs = provider.tsconfigs(ctx); assertThat(tsconfigs) .hasSize(1) .extracting(path -> Files.readString(Paths.get(path))) @@ -287,42 +278,36 @@ void should_create_wildcard_tsconfig() throws Exception { baseDir.toFile().getAbsolutePath().replace(File.separator, "/") ) ); - - when(checker.isBeyondLimit(ctx)).thenReturn(true); - provider = - new WildcardTsConfigProvider( - checker, - TsConfigProviderTest::createTsConfigFile - ); - assertThat(provider.tsconfigs(ctx)).isEmpty(); - - provider = - new WildcardTsConfigProvider(checker, TsConfigProviderTest::createTsConfigFile); - assertThat(provider.tsconfigs(ctx)).isEmpty(); } @Test - void should_not_recreate_wildcart_tsconfig_in_sonarlint() throws Exception { - List tsconfigs; - Path file; - + void should_not_recreate_wildcard_tsconfig_in_sonarlint() throws Exception { var ctx = SensorContextTester.create(baseDir); ctx.setRuntime(SonarRuntimeImpl.forSonarLint(Version.create(4, 4))); - var checker = mock(SonarLintTypeCheckingChecker.class); - when(checker.isBeyondLimit(ctx)).thenReturn(false); + var tsConfigCache = tsConfigCache(); + var originalTsConfigs = getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); - tsconfigs = + var tsconfigs = new WildcardTsConfigProvider( - checker, + tsConfigCache, TsConfigProviderTest::createTsConfigFile ) .tsconfigs(ctx); - assertThat(tsconfigs).hasSize(1); + assertThat(tsconfigs).isEqualTo(originalTsConfigs); + } + + @Test + void should_not_create_wildcard_tsconfig_in_sonarlint() throws Exception { + var ctx = SensorContextTester.create(baseDir); + ctx.setRuntime(SonarRuntimeImpl.forSonarLint(Version.create(4, 4))); + ctx.setSettings(new MapSettings().setProperty(WildcardTsConfigProvider.MAX_FILES_PROPERTY, 1)); + createInputFile(ctx, "file.js"); + createInputFile(ctx, "file2.js"); - file = Path.of(tsconfigs.get(0)); - assertThat(file).exists(); - Files.delete(file); + var tsConfigCache = tsConfigCache(); + var tsconfigs = getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); + assertThat(tsconfigs).isEmpty(); } @Test @@ -330,25 +315,66 @@ void should_not_fail_on_exception() throws Exception { var ctx = SensorContextTester.create(baseDir); createInputFile(ctx, "file.js"); - var checker = mock(SonarLintTypeCheckingChecker.class); - when(checker.isBeyondLimit(ctx)).thenReturn(false); + var tsConfigCache = tsConfigCache(); + tsConfigCache.setProjectSize(1); var fileWriter = mock(TsConfigFileCreator.class); when(fileWriter.createTsConfigFile(anyString())).thenThrow(IOException.class); var wildcardTsConfigProvider = new WildcardTsConfigProvider( - checker, + tsConfigCache, fileWriter ); assertThat(wildcardTsConfigProvider.tsconfigs(ctx)).isEmpty(); } - private static void createInputFile(SensorContextTester context, String relativePath) { - DefaultInputFile inputFile = new TestInputFileBuilder("moduleKey", relativePath) + @Test + void should_check_javascript_files() throws IOException { + logger.setLevel(LoggerLevel.INFO); + var ctx = SensorContextTester.create(baseDir); + ctx.setRuntime(SonarRuntimeImpl.forSonarLint(Version.create(4, 4))); + createInputFile(ctx, "file.js"); + createInputFile(ctx, "file.css"); + createInputFile(ctx, "file.d.ts"); + Files.createDirectory(Path.of(baseDir.toString(), "node_modules")); + createInputFile(ctx, "node_modules/dep.js"); + + var tsConfigCache = tsConfigCache(); + getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); + assertThat(logger.logs()).contains("Turning on type-checking of JavaScript files"); + } + + @Test + void should_detect_projects_with_too_many_files() throws IOException { + logger.setLevel(LoggerLevel.WARN); + var ctx = SensorContextTester.create(baseDir); + ctx.setSettings( + new MapSettings().setProperty(WildcardTsConfigProvider.MAX_FILES_PROPERTY, 3) + ); + createInputFile(ctx, "file1.js"); + createInputFile(ctx, "file2.ts"); + createInputFile(ctx, "file3.cjs"); + createInputFile(ctx, "file4.cts"); + var tsConfigCache = tsConfigCache(); + getTsConfigs(new ContextUtils(ctx), this::tsConfigFileCreator, tsConfigCache); + assertThat(WildcardTsConfigProvider.isBeyondLimit(ctx, tsConfigCache.getProjectSize())).isTrue(); + assertThat(logger.logs()) + .contains( + "Turning off type-checking of JavaScript files due to the project size exceeding the limit (3 files)", + "This may cause rules dependent on type information to not behave as expected", + "Check the list of impacted rules at https://rules.sonarsource.com/javascript/tag/type-dependent", + "To turn type-checking back on, increase the \"" + WildcardTsConfigProvider.MAX_FILES_PROPERTY + "\" property value", + "Please be aware that this could potentially impact the performance of the analysis" + ); + } + + private void createInputFile(SensorContextTester context, String relativePath) throws IOException { + DefaultInputFile inputFile = new TestInputFileBuilder(baseDir.toString(), relativePath) .setLanguage("ts") .setContents("if (cond)\ndoFoo(); \nelse \ndoFoo();") .build(); context.fileSystem().add(inputFile); + Files.createFile(Paths.get(baseDir.toString(), relativePath)); } private static String createTsConfigFile(String content) throws IOException { @@ -356,4 +382,8 @@ private static String createTsConfigFile(String content) throws IOException { Files.writeString(tempFile, content, StandardCharsets.UTF_8); return tempFile.toAbsolutePath().toString(); } + + private TsConfigCache tsConfigCache() { + return new TsConfigCacheImpl(mock(BridgeServer.class)); + } } diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingCheckerTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingCheckerTest.java deleted file mode 100644 index 4dc55f04169..00000000000 --- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/sonarlint/SonarLintTypeCheckingCheckerTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SonarQube JavaScript Plugin - * Copyright (C) 2011-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.plugins.javascript.sonarlint; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingCheckerImpl.DEFAULT_MAX_FILES_FOR_TYPE_CHECKING; -import static org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingCheckerImpl.MAX_FILES_PROPERTY; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.io.TempDir; -import org.sonar.api.batch.fs.internal.DefaultFileSystem; -import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.config.Configuration; -import org.sonar.api.testfixtures.log.LogTesterJUnit5; -import org.sonar.api.utils.log.LoggerLevel; - -class SonarLintTypeCheckingCheckerTest { - - @RegisterExtension - LogTesterJUnit5 logTester = new LogTesterJUnit5(); - - @TempDir - Path baseDir; - - @Test - void should_check_javascript_files() throws IOException { - logTester.setLevel(LoggerLevel.INFO); - inputFile("file.js"); - inputFile("file.css"); - inputFile("file.d.ts"); - inputFile("node_modules", "dep.js"); - - var checker = new SonarLintTypeCheckingCheckerImpl(); - assertThat(checker.isBeyondLimit(sensorContext(3))).isFalse(); - assertThat(logTester.logs()).contains("Turning on type-checking of JavaScript files"); - } - - @Test - void should_detect_projects_with_too_many_files() throws IOException { - logTester.setLevel(LoggerLevel.WARN); - inputFile("file1.js"); - inputFile("file2.ts"); - inputFile("file3.cjs"); - inputFile("file4.cts"); - var checker = new SonarLintTypeCheckingCheckerImpl(); - - assertThat(checker.isBeyondLimit(sensorContext(3))).isTrue(); - assertThat(logTester.logs()) - .contains( - "Turning off type-checking of JavaScript files due to the project size exceeding the limit (3 files)", - "This may cause rules dependent on type information to not behave as expected", - "Check the list of impacted rules at https://rules.sonarsource.com/javascript/tag/type-dependent", - "To turn type-checking back on, increase the \"" + MAX_FILES_PROPERTY + "\" property value", - "Please be aware that this could potentially impact the performance of the analysis" - ); - } - - @Test - void should_detect_errors() { - logTester.setLevel(LoggerLevel.WARN); - var checker = new SonarLintTypeCheckingCheckerImpl(); - var context = sensorContext(); - when(context.fileSystem().baseDir()).thenThrow(new IllegalArgumentException()); - - assertThat(checker.isBeyondLimit(context)).isTrue(); - assertThat(logTester.logs()) - .containsExactly("Turning off type-checking of JavaScript files due to unexpected error"); - } - - private SensorContext sensorContext() { - return sensorContext(DEFAULT_MAX_FILES_FOR_TYPE_CHECKING); - } - - private SensorContext sensorContext(int maxFiles) { - var config = mock(Configuration.class); - when(config.getInt(MAX_FILES_PROPERTY)).thenReturn(Optional.of(maxFiles)); - - var context = mock(SensorContext.class); - when(context.config()).thenReturn(config); - when(context.fileSystem()).thenReturn(new DefaultFileSystem(baseDir)); - return context; - } - - private void inputFile(String filename) throws IOException { - var path = baseDir.resolve(filename); - Files.writeString(path, "inputFile"); - } - - private Path inputFile(String dir, String filename) throws IOException { - var path = Files.createDirectories(baseDir.resolve(dir)).resolve(filename); - Files.writeString(path, "inputFile"); - return path; - } -}