diff --git a/package.json b/package.json index 3ac4a7ef7e0..25e7cdfe750 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "_:plugin:prepare-bridge": "npm pack && node tools/check-distribution-filepath-length.js && npm run _:plugin:copy-bridge", "_:plugin-fetch-node": "node tools/fetch-node/scripts/wrapper.mjs", "_:plugin:pre-build": "npm run bridge:build && npm run _:plugin:prepare-bridge && npm run _:plugin-fetch-node", - "_:plugin:copy-bridge": "cpy sonarjs-1.0.0.tgz sonar-plugin/sonar-javascript-plugin/target/classes" + "_:plugin:copy-bridge": "cpy sonarjs-1.0.0.tgz sonar-plugin/sonar-javascript-plugin/target/classes && cpy sonarjs-1.0.0.tgz sonar-plugin/standalone/target/classes" }, "repository": { "type": "git", diff --git a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServer.java b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServer.java index 484a8befa7d..44e350efccf 100644 --- a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServer.java +++ b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServer.java @@ -25,7 +25,6 @@ import org.sonar.api.Startable; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.TextRange; -import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.scanner.ScannerSide; import org.sonar.plugins.javascript.bridge.protobuf.Node; import org.sonarsource.api.sonarlint.SonarLintSide; @@ -35,7 +34,7 @@ @ScannerSide @SonarLintSide(lifespan = INSTANCE) public interface BridgeServer extends Startable { - void startServerLazily(SensorContext context) throws IOException; + void startServerLazily(BridgeServerConfig context) throws IOException; void initLinter(List rules, List environments, List globals, AnalysisMode analysisMode, String baseDir, List exclusions) throws IOException; diff --git a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerConfig.java b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerConfig.java new file mode 100644 index 00000000000..3180255e2b2 --- /dev/null +++ b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerConfig.java @@ -0,0 +1,37 @@ +/* + * 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.bridge; + +import org.sonar.api.SonarProduct; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.config.Configuration; + +/** + * {@link BridgeServerImpl} requires information from {@link org.sonar.api.batch.sensor.SensorContext}. + * Hovewer, {@link org.sonar.api.batch.sensor.SensorContext} is a big object, containing more than what we need. + * This class will contain only information required by {@link BridgeServerImpl}. + * This will reduce the dependency on external API, and ease the testing. + */ +public record BridgeServerConfig(Configuration config, String workDirAbsolutePath, SonarProduct product) { + + public static BridgeServerConfig fromSensorContext(SensorContext context) { + return new BridgeServerConfig(context.config(), context.fileSystem().workDir().getAbsolutePath(), context.runtime().getProduct()); + } +} diff --git a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java index 2a97c370f45..14ddd82398e 100644 --- a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java +++ b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java @@ -19,10 +19,6 @@ */ package org.sonar.plugins.javascript.bridge; -import static java.util.Collections.emptyList; -import static org.sonar.plugins.javascript.bridge.NetUtils.findOpenPort; -import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_EXECUTABLE_PROPERTY; - import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import java.io.File; @@ -51,13 +47,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.SonarProduct; -import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.config.Configuration; import org.sonar.api.utils.TempFolder; import org.sonar.plugins.javascript.nodejs.NodeCommand; import org.sonar.plugins.javascript.nodejs.NodeCommandBuilder; import org.sonar.plugins.javascript.nodejs.NodeCommandException; +import static java.util.Collections.emptyList; +import static org.sonar.plugins.javascript.bridge.NetUtils.findOpenPort; +import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_EXECUTABLE_PROPERTY; + public class BridgeServerImpl implements BridgeServer { private enum Status { @@ -176,7 +175,7 @@ void deploy(Configuration configuration) throws IOException { embeddedNode.deploy(); } - void startServer(SensorContext context, List deployedBundles) throws IOException { + void startServer(BridgeServerConfig serverConfig, List deployedBundles) throws IOException { LOG.debug("Starting server"); long start = System.currentTimeMillis(); port = findOpenPort(); @@ -193,7 +192,7 @@ void startServer(SensorContext context, List deployedBundles) throws IOExc .stream() .map(Path::toString) .collect(Collectors.joining(File.pathSeparator)); - nodeCommand = initNodeCommand(context, scriptFile, bundles); + nodeCommand = initNodeCommand(serverConfig, scriptFile, bundles); nodeCommand.start(); if (!waitServerToStart(timeoutSeconds * 1000)) { @@ -226,12 +225,12 @@ boolean waitServerToStart(int timeoutMs) { return true; } - private NodeCommand initNodeCommand(SensorContext context, File scriptFile, String bundles) + private NodeCommand initNodeCommand(BridgeServerConfig serverConfig, File scriptFile, String bundles) throws IOException { - var workdir = context.fileSystem().workDir().getAbsolutePath(); - var config = context.config(); + var workdir = serverConfig.workDirAbsolutePath(); + var config = serverConfig.config(); var allowTsParserJsFiles = config.getBoolean(ALLOW_TS_PARSER_JS_FILES).orElse(true); - var isSonarLint = context.runtime().getProduct() == SonarProduct.SONARLINT; + var isSonarLint = serverConfig.product() == SonarProduct.SONARLINT; if (isSonarLint) { LOG.info("Running in SonarLint context, metrics will not be computed."); } @@ -243,7 +242,7 @@ private NodeCommand initNodeCommand(SensorContext context, File scriptFile, Stri .embeddedNode(embeddedNode) .pathResolver(bundle) .minNodeVersion(NodeDeprecationWarning.MIN_SUPPORTED_NODE_VERSION) - .configuration(context.config()) + .configuration(serverConfig.config()) .script(scriptFile.getAbsolutePath()) .scriptArgs( String.valueOf(port), @@ -256,7 +255,7 @@ private NodeCommand initNodeCommand(SensorContext context, File scriptFile, Stri ) .env(getEnv()); - context + serverConfig .config() .getInt(MAX_OLD_SPACE_SIZE_PROPERTY) .ifPresent(nodeCommandBuilder::maxOldSpaceSize); @@ -275,7 +274,7 @@ private static Map getEnv() { } @Override - public void startServerLazily(SensorContext context) throws IOException { + public void startServerLazily(BridgeServerConfig serverConfig) throws IOException { if (status == Status.FAILED) { // required for SonarLint context to avoid restarting already failed server throw new ServerAlreadyFailedException(); @@ -296,12 +295,12 @@ public void startServerLazily(SensorContext context) throws IOException { status = Status.FAILED; throw new ServerAlreadyFailedException(); } - deploy(context.config()); + deploy(serverConfig.config()); List deployedBundles = rulesBundles.deploy(temporaryDeployLocation.resolve("package")); rulesBundles .getUcfgRulesBundle() .ifPresent(rulesBundle -> PluginInfo.setUcfgPluginVersion(rulesBundle.bundleVersion())); - startServer(context, deployedBundles); + startServer(serverConfig, deployedBundles); } catch (NodeCommandException e) { status = Status.FAILED; throw e; diff --git a/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java b/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java index 3e6c356f23f..2185301bc83 100644 --- a/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java +++ b/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java @@ -19,22 +19,6 @@ */ package org.sonar.plugins.javascript.bridge; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; -import static org.slf4j.event.Level.DEBUG; -import static org.slf4j.event.Level.ERROR; -import static org.slf4j.event.Level.INFO; -import static org.slf4j.event.Level.WARN; -import static org.sonar.plugins.javascript.bridge.AnalysisMode.DEFAULT_LINTER_ID; -import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_EXECUTABLE_PROPERTY; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -73,6 +57,22 @@ import org.sonar.plugins.javascript.nodejs.ProcessWrapper; import org.sonar.plugins.javascript.nodejs.ProcessWrapperImpl; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.slf4j.event.Level.DEBUG; +import static org.slf4j.event.Level.ERROR; +import static org.slf4j.event.Level.INFO; +import static org.slf4j.event.Level.WARN; +import static org.sonar.plugins.javascript.bridge.AnalysisMode.DEFAULT_LINTER_ID; +import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_EXECUTABLE_PROPERTY; + class BridgeServerImplTest { private static final String START_SERVER_SCRIPT = "startServer.js"; @@ -93,6 +93,7 @@ class BridgeServerImplTest { TempFolder tempFolder; private SensorContextTester context; + private BridgeServerConfig serverConfig; private BridgeServerImpl bridgeServer; private final TestBundle testBundle = new TestBundle(START_SERVER_SCRIPT); @@ -106,6 +107,7 @@ class BridgeServerImplTest { public void setUp() { context = SensorContextTester.create(moduleBase); context.fileSystem().setWorkDir(workDir); + serverConfig = BridgeServerConfig.fromSensorContext(context); tempFolder = new DefaultTempFolder(tempDir, true); unsupportedEmbeddedRuntime = new EmbeddedNode(mock(ProcessWrapper.class), createUnsupportedEnvironment()); @@ -128,7 +130,7 @@ void should_throw_when_not_existing_script() { bridgeServer = createBridgeServer("NOT_EXISTING.js"); List deployedBundles = emptyList(); - assertThatThrownBy(() -> bridgeServer.startServer(context, deployedBundles)) + assertThatThrownBy(() -> bridgeServer.startServer(serverConfig, deployedBundles)) .isInstanceOf(NodeCommandException.class) .hasMessageStartingWith("Node.js script to start the bridge server doesn't exist:"); } @@ -158,7 +160,7 @@ void should_throw_if_failed_to_build_node_command() { ); List deployedBundles = emptyList(); - assertThatThrownBy(() -> bridgeServer.startServer(context, deployedBundles)) + assertThatThrownBy(() -> bridgeServer.startServer(serverConfig, deployedBundles)) .isInstanceOf(NodeCommandException.class) .hasMessage("msg"); } @@ -166,7 +168,7 @@ void should_throw_if_failed_to_build_node_command() { @Test void should_forward_process_streams() throws Exception { bridgeServer = createBridgeServer("logging.js"); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); assertThat(logTester.logs(DEBUG)).contains("testing debug log"); assertThat(logTester.logs(WARN)).contains("testing warn log"); @@ -177,7 +179,7 @@ void should_forward_process_streams() throws Exception { @Test void should_get_answer_from_server() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); DefaultInputFile inputFile = TestInputFileBuilder .create("foo", "foo.js") @@ -191,7 +193,7 @@ void should_get_answer_from_server() throws Exception { @Test void test_init() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); List rules = Collections.singletonList( new EslintRule( @@ -219,7 +221,7 @@ void test_init() throws Exception { @Test void should_get_answer_from_server_for_ts_request() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); DefaultInputFile inputFile = TestInputFileBuilder .create("foo", "foo.ts") @@ -246,7 +248,7 @@ void should_get_answer_from_server_for_ts_request() throws Exception { @Test void should_get_answer_from_server_for_yaml_request() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); DefaultInputFile inputFile = TestInputFileBuilder .create("foo", "foo.yaml") @@ -274,7 +276,7 @@ private static JsAnalysisRequest createRequest(DefaultInputFile inputFile) { @Test void should_get_answer_from_server_for_program_based_requests() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); TsProgram programCreated = bridgeServer.createProgram( new TsProgramRequest("/absolute/path/tsconfig.json") @@ -304,7 +306,7 @@ void should_get_answer_from_server_for_program_based_requests() throws Exception @Test void should_create_tsconfig_files() throws IOException { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); var tsConfig = bridgeServer.createTsConfigFile("{\"include\":[\"/path/to/project/**/*\"]}"); assertThat(tsConfig.filename).isEqualTo("/path/to/tsconfig.json"); @@ -313,7 +315,7 @@ void should_create_tsconfig_files() throws IOException { @Test void should_not_fail_when_error_during_create_program() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); TsProgram programCreated = bridgeServer.createProgram( new TsProgramRequest("/absolute/path/invalid.json") @@ -326,7 +328,7 @@ void should_not_fail_when_error_during_create_program() throws Exception { @Test void should_get_answer_from_server_for_css_request() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); DefaultInputFile inputFile = TestInputFileBuilder .create("foo", "foo.css") @@ -345,7 +347,7 @@ void should_throw_if_failed_to_start() { bridgeServer = createBridgeServer("throw.js"); List deployedBundles = emptyList(); - assertThatThrownBy(() -> bridgeServer.startServer(context, deployedBundles)) + assertThatThrownBy(() -> bridgeServer.startServer(serverConfig, deployedBundles)) .isInstanceOf(NodeCommandException.class) .hasMessage("Failed to start the bridge server (" + TEST_TIMEOUT_SECONDS + "s timeout)"); } @@ -356,7 +358,7 @@ void should_return_command_info() throws Exception { assertThat(bridgeServer.getCommandInfo()) .isEqualTo("Node.js command to start the bridge server was not built yet."); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); assertThat(bridgeServer.getCommandInfo()) .contains("Node.js command to start the bridge server was: ", "node", START_SERVER_SCRIPT); @@ -370,7 +372,8 @@ void should_set_max_old_space_size() throws Exception { .isEqualTo("Node.js command to start the bridge server was not built yet."); context.setSettings(new MapSettings().setProperty("sonar.javascript.node.maxspace", 2048)); - bridgeServer.startServer(context, emptyList()); + BridgeServerConfig serverConfigForMaxSpace = BridgeServerConfig.fromSensorContext(context); + bridgeServer.startServer(serverConfigForMaxSpace, emptyList()); assertThat(bridgeServer.getCommandInfo()).contains("--max-old-space-size=2048"); } @@ -381,7 +384,8 @@ void should_set_allowTsParserJsFiles_to_false() throws Exception { context.setSettings( new MapSettings().setProperty("sonar.javascript.allowTsParserJsFiles", "false") ); - bridgeServer.startServer(context, emptyList()); + BridgeServerConfig serverConfigForAllowTs = BridgeServerConfig.fromSensorContext(context); + bridgeServer.startServer(serverConfigForAllowTs, emptyList()); bridgeServer.stop(); assertThat(logTester.logs()).contains("allowTsParserJsFiles: false"); @@ -390,7 +394,7 @@ void should_set_allowTsParserJsFiles_to_false() throws Exception { @Test void allowTsParserJsFiles_default_value_is_true() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); bridgeServer.stop(); assertThat(logTester.logs()).contains("allowTsParserJsFiles: true"); @@ -400,7 +404,7 @@ void allowTsParserJsFiles_default_value_is_true() throws Exception { void test_isAlive() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); assertThat(bridgeServer.isAlive()).isFalse(); - bridgeServer.startServerLazily(context); + bridgeServer.startServerLazily(serverConfig); assertThat(bridgeServer.isAlive()).isTrue(); bridgeServer.clean(); assertThat(bridgeServer.isAlive()).isFalse(); @@ -411,11 +415,11 @@ void test_lazy_start() throws Exception { String alreadyStarted = "The bridge server is up, no need to start."; String starting = "Creating Node.js process to start the bridge server on port"; bridgeServer = createBridgeServer("startServer.js"); - bridgeServer.startServerLazily(context); + bridgeServer.startServerLazily(serverConfig); assertThat(logTester.logs(DEBUG).stream().anyMatch(s -> s.startsWith(starting))).isTrue(); assertThat(logTester.logs(DEBUG)).doesNotContain(alreadyStarted); logTester.clear(); - bridgeServer.startServerLazily(context); + bridgeServer.startServerLazily(serverConfig); assertThat(logTester.logs(DEBUG).stream().noneMatch(s -> s.startsWith(starting))).isTrue(); assertThat(logTester.logs(DEBUG)).contains(alreadyStarted); } @@ -433,14 +437,14 @@ void test_use_existing_node() throws Exception { bridgeServer = createBridgeServer("startServer.js"); var bridgeServerMock = spy(bridgeServer); doReturn("70000").when(bridgeServerMock).getExistingNodeProcessPort(); - assertThatThrownBy(() -> bridgeServerMock.startServerLazily(context)) + assertThatThrownBy(() -> bridgeServerMock.startServerLazily(serverConfig)) .isInstanceOf(IllegalStateException.class) .hasMessage(wrongPortRange); assertThat(logTester.logs(DEBUG)).doesNotContain(alreadyStarted); assertThat(logTester.logs(DEBUG).stream().noneMatch(s -> s.startsWith(starting))).isTrue(); doReturn("a").when(bridgeServerMock).getExistingNodeProcessPort(); - assertThatThrownBy(() -> bridgeServerMock.startServerLazily(context)) + assertThatThrownBy(() -> bridgeServerMock.startServerLazily(serverConfig)) .isInstanceOf(IllegalStateException.class) .hasMessage(wrongPortValue); assertThat(logTester.logs(DEBUG)).doesNotContain(alreadyStarted); @@ -448,14 +452,14 @@ void test_use_existing_node() throws Exception { //Port 0 will be considered as not set, and a new node process will be started on a random port doReturn("0").when(bridgeServerMock).getExistingNodeProcessPort(); - bridgeServerMock.startServerLazily(context); + bridgeServerMock.startServerLazily(serverConfig); assertThat(logTester.logs(DEBUG).stream().anyMatch(s -> s.startsWith(starting))).isTrue(); assertThat(logTester.logs(DEBUG)).doesNotContain(alreadyStarted); bridgeServerMock.clean(); doReturn("60000").when(bridgeServerMock).getExistingNodeProcessPort(); doReturn(true).when(bridgeServerMock).isAlive(); - bridgeServerMock.startServerLazily(context); + bridgeServerMock.startServerLazily(serverConfig); assertThat(logTester.logs(INFO)).contains(useExisting); assertThat(logTester.logs(DEBUG)).contains(alreadyStarted); } @@ -465,40 +469,40 @@ void should_throw_special_exception_when_failed_start_server_before() { bridgeServer = createBridgeServer("throw.js"); String failedToStartExceptionMessage = "Failed to start the bridge server (" + TEST_TIMEOUT_SECONDS + "s timeout)"; - assertThatThrownBy(() -> bridgeServer.startServerLazily(context)) + assertThatThrownBy(() -> bridgeServer.startServerLazily(serverConfig)) .isInstanceOf(NodeCommandException.class) .hasMessage(failedToStartExceptionMessage); - assertThatThrownBy(() -> bridgeServer.startServerLazily(context)) + assertThatThrownBy(() -> bridgeServer.startServerLazily(serverConfig)) .isInstanceOf(ServerAlreadyFailedException.class); } @Test void should_throw_special_exception_when_failed_start_process_before() { bridgeServer = createBridgeServer("invalid"); - assertThatThrownBy(() -> bridgeServer.startServerLazily(context)) + assertThatThrownBy(() -> bridgeServer.startServerLazily(serverConfig)) .isInstanceOf(NodeCommandException.class) .hasMessageStartingWith("Node.js script to start the bridge server doesn't exist"); - assertThatThrownBy(() -> bridgeServer.startServerLazily(context)) + assertThatThrownBy(() -> bridgeServer.startServerLazily(serverConfig)) .isInstanceOf(ServerAlreadyFailedException.class); } @Test void should_throw_if_server_not_alive() throws Exception { bridgeServer = createBridgeServer("startAndClose.js"); - bridgeServer.startServerLazily(context); + bridgeServer.startServerLazily(serverConfig); bridgeServer.waitFor(); - assertThatThrownBy(() -> bridgeServer.startServerLazily(context)) + assertThatThrownBy(() -> bridgeServer.startServerLazily(serverConfig)) .isInstanceOf(ServerAlreadyFailedException.class); } @Test void should_fail_if_bad_json_response() throws Exception { bridgeServer = createBridgeServer("badResponse.js"); - bridgeServer.startServerLazily(context); + bridgeServer.startServerLazily(serverConfig); DefaultInputFile inputFile = TestInputFileBuilder .create("foo", "foo.js") @@ -527,21 +531,21 @@ void should_not_search_typescript_when_no_ts_file() throws Exception { ctx.fileSystem().setWorkDir(workDir); Path tsDir = moduleBase.resolve("dir/node_modules/typescript"); Files.createDirectories(tsDir); - bridgeServer.startServer(ctx, emptyList()); + bridgeServer.startServer(BridgeServerConfig.fromSensorContext(ctx), emptyList()); assertThat(bridgeServer.getCommandInfo()).doesNotContain("NODE_PATH"); } @Test void should_reload_tsconfig() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); assertThat(bridgeServer.newTsConfig()).isTrue(); } @Test void should_return_files_for_tsconfig() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); String tsconfig = "path/to/tsconfig.json"; BridgeServerImpl.TsConfigResponse tsConfigResponse = bridgeServer.tsConfigFiles(tsconfig); assertThat(tsConfigResponse.files) @@ -556,7 +560,7 @@ void should_return_files_for_tsconfig() throws Exception { @Test void should_return_no_files_for_tsconfig_bad_response() throws Exception { bridgeServer = createBridgeServer("badResponse.js"); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); BridgeServerImpl.TsConfigResponse response = bridgeServer.tsConfigFiles( "path/to/tsconfig.json" ); @@ -567,7 +571,7 @@ void should_return_no_files_for_tsconfig_bad_response() throws Exception { @Test void should_return_no_files_for_tsconfig_no_response() throws Exception { bridgeServer = createBridgeServer("badResponse.js"); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); assertThat(bridgeServer.tsConfigFiles("path/to/tsconfig.json").files).isEmpty(); TsConfigFile tsConfigFile = bridgeServer.loadTsConfig("path/to/tsconfig.json"); assertThat(tsConfigFile.files).isEmpty(); @@ -576,7 +580,7 @@ void should_return_no_files_for_tsconfig_no_response() throws Exception { @Test void should_return_no_files_for_tsconfig_on_error() throws Exception { bridgeServer = createBridgeServer("tsConfigError.js"); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); TsConfigFile tsConfigFile = bridgeServer.loadTsConfig("path/to/tsconfig.json"); assertThat(tsConfigFile.files).isEmpty(); @@ -586,7 +590,7 @@ void should_return_no_files_for_tsconfig_on_error() throws Exception { @Test void log_error_when_timeout() throws Exception { bridgeServer = createBridgeServer("timeout.js"); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); assertThatThrownBy(() -> bridgeServer.loadTsConfig("any.ts")) .isInstanceOf(IllegalStateException.class) @@ -607,7 +611,7 @@ void test_rule_tostring() { @Test void should_load_custom_rules() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, Arrays.asList(Paths.get("bundle1"), Paths.get("bundle2"))); + bridgeServer.startServer(serverConfig, Arrays.asList(Paths.get("bundle1"), Paths.get("bundle2"))); bridgeServer.stop(); assertThat(logTester.logs()) @@ -618,7 +622,8 @@ void should_load_custom_rules() throws Exception { void should_skip_metrics_on_sonarlint() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); context.setRuntime(SonarRuntimeImpl.forSonarLint(Version.create(7, 9))); - bridgeServer.startServer(context, Arrays.asList(Paths.get("bundle1"), Paths.get("bundle2"))); + BridgeServerConfig serverConfigFor79 = BridgeServerConfig.fromSensorContext(context); + bridgeServer.startServer(serverConfigFor79, Arrays.asList(Paths.get("bundle1"), Paths.get("bundle2"))); bridgeServer.stop(); assertThat(logTester.logs()).contains("sonarlint: true"); @@ -628,7 +633,8 @@ void should_skip_metrics_on_sonarlint() throws Exception { void should_pass_debug_memory_option() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); context.setSettings(new MapSettings().setProperty("sonar.javascript.node.debugMemory", "true")); - bridgeServer.startServer(context, Arrays.asList(Paths.get("bundle1"), Paths.get("bundle2"))); + BridgeServerConfig serverConfigForDebugMemory = BridgeServerConfig.fromSensorContext(context); + bridgeServer.startServer(serverConfigForDebugMemory, Arrays.asList(Paths.get("bundle1"), Paths.get("bundle2"))); bridgeServer.stop(); assertThat(logTester.logs()).contains("debugMemory: true"); @@ -693,7 +699,7 @@ void enabled_monitoring() throws Exception { tempFolder, unsupportedEmbeddedRuntime ); - bridgeServer.startServerLazily(context); + bridgeServer.startServerLazily(serverConfig); bridgeServer.stop(); assertThat(logTester.logs(INFO).stream().anyMatch(s -> s.startsWith("no-commented-code"))) .isTrue(); @@ -721,7 +727,7 @@ void test_ucfg_bundle_version() throws Exception { tempFolder, unsupportedEmbeddedRuntime ); - bridgeServer.startServerLazily(context); + bridgeServer.startServerLazily(serverConfig); assertThat(logTester.logs(DEBUG)) .contains("Security Frontend version is available: [some_bundle_version]"); @@ -730,7 +736,7 @@ void test_ucfg_bundle_version() throws Exception { @Test void should_return_an_ast() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(serverConfig, emptyList()); DefaultInputFile inputFile = TestInputFileBuilder .create("foo", "foo.js") @@ -747,7 +753,7 @@ void should_return_an_ast() throws Exception { @Test void should_omit_an_ast_if_skipAst_flag_is_set() throws Exception { bridgeServer = createBridgeServer(START_SERVER_SCRIPT); - bridgeServer.startServer(context, emptyList()); + bridgeServer.startServer(BridgeServerConfig.fromSensorContext(context), emptyList()); DefaultInputFile inputFile = TestInputFileBuilder .create("foo", "foo.js") @@ -773,7 +779,8 @@ void should_not_deploy_runtime_if_sonar_nodejs_executable_is_set() { var existingDoesntMatterScript = "logging.js"; bridgeServer = createBridgeServer(existingDoesntMatterScript); context.setSettings(new MapSettings().setProperty(NODE_EXECUTABLE_PROPERTY, "whatever")); - assertThatThrownBy(() -> bridgeServer.startServerLazily(context)) + BridgeServerConfig serverConfigForExecutableProperty = BridgeServerConfig.fromSensorContext(context); + assertThatThrownBy(() -> bridgeServer.startServerLazily(serverConfigForExecutableProperty)) .isInstanceOf(NodeCommandException.class); assertThat(logTester.logs(INFO)) diff --git a/sonar-plugin/pom.xml b/sonar-plugin/pom.xml index 21b1f3da624..81ec99c5235 100644 --- a/sonar-plugin/pom.xml +++ b/sonar-plugin/pom.xml @@ -28,6 +28,7 @@ css javascript-checks sonar-javascript-plugin + standalone diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AbstractBridgeSensor.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AbstractBridgeSensor.java index a5fdca88ab1..52d568602db 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AbstractBridgeSensor.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AbstractBridgeSensor.java @@ -19,8 +19,6 @@ */ package org.sonar.plugins.javascript.analysis; -import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_EXECUTABLE_PROPERTY; - import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -33,10 +31,13 @@ import org.sonar.plugins.javascript.JavaScriptPlugin; import org.sonar.plugins.javascript.analysis.cache.CacheStrategies; import org.sonar.plugins.javascript.bridge.BridgeServer; +import org.sonar.plugins.javascript.bridge.BridgeServerConfig; import org.sonar.plugins.javascript.bridge.ServerAlreadyFailedException; import org.sonar.plugins.javascript.nodejs.NodeCommandException; import org.sonar.plugins.javascript.utils.Exclusions; +import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_EXECUTABLE_PROPERTY; + public abstract class AbstractBridgeSensor implements Sensor { private static final Logger LOG = LoggerFactory.getLogger(AbstractBridgeSensor.class); @@ -72,7 +73,7 @@ public void execute(SensorContext context) { LOG.info("No input files found for analysis"); return; } - bridgeServer.startServerLazily(context); + bridgeServer.startServerLazily(BridgeServerConfig.fromSensorContext(context)); analyzeFiles(inputFiles); } catch (CancellationException e) { // do not propagate the exception 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 46042e11c61..5f67ced1a73 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 @@ -19,18 +19,6 @@ */ package org.sonar.plugins.javascript.analysis; -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.plugins.javascript.analysis.JsTsSensorTest.PLUGIN_VERSION; - import com.google.gson.Gson; import java.io.File; import java.io.IOException; @@ -92,6 +80,18 @@ import org.sonar.plugins.javascript.nodejs.NodeCommandException; import org.sonar.plugins.javascript.sonarlint.SonarLintTypeCheckingChecker; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sonar.plugins.javascript.analysis.JsTsSensorTest.PLUGIN_VERSION; + class JavaScriptEslintBasedSensorTest { private static final String ESLINT_BASED_RULE = "S3923"; @@ -454,7 +454,7 @@ void should_save_cpd() throws Exception { void should_catch_if_bridge_server_not_started() throws Exception { doThrow(new IllegalStateException("Failed to start the bridge server")) .when(bridgeServerMock) - .startServerLazily(context); + .startServerLazily(any()); var sensor = createSensor(); createInputFile(context); diff --git a/sonar-plugin/standalone/pom.xml b/sonar-plugin/standalone/pom.xml new file mode 100644 index 00000000000..f051fa83bf0 --- /dev/null +++ b/sonar-plugin/standalone/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + org.sonarsource.javascript + sonar-plugin + 10.15.0-SNAPSHOT + + standalone + + SonarQube JavaScript :: Standalone + + + + ${project.groupId} + bridge + + + org.sonarsource.api.plugin + sonar-plugin-api + + + com.google.code.findbugs + jsr305 + + + org.sonarsource.api.plugin + sonar-plugin-api-test-fixtures + + + + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-core + 5.12.0 + test + + + org.assertj + assertj-core + test + + + + diff --git a/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/StandaloneParser.java b/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/StandaloneParser.java new file mode 100644 index 00000000000..8c4ccc94301 --- /dev/null +++ b/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/StandaloneParser.java @@ -0,0 +1,107 @@ +/* + * 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.standalone; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Optional; +import org.sonar.api.SonarProduct; +import org.sonar.plugins.javascript.api.estree.ESTree; +import org.sonar.plugins.javascript.bridge.AnalysisMode; +import org.sonar.plugins.javascript.bridge.AnalysisWarningsWrapper; +import org.sonar.plugins.javascript.bridge.BridgeServer; +import org.sonar.plugins.javascript.bridge.BridgeServerConfig; +import org.sonar.plugins.javascript.bridge.BridgeServerImpl; +import org.sonar.plugins.javascript.bridge.BundleImpl; +import org.sonar.plugins.javascript.bridge.ESTreeFactory; +import org.sonar.plugins.javascript.bridge.EmbeddedNode; +import org.sonar.plugins.javascript.bridge.Environment; +import org.sonar.plugins.javascript.bridge.NodeDeprecationWarning; +import org.sonar.plugins.javascript.bridge.RulesBundles; +import org.sonar.plugins.javascript.bridge.protobuf.Node; +import org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl; +import org.sonar.plugins.javascript.nodejs.ProcessWrapperImpl; + +public class StandaloneParser implements AutoCloseable { + + private final BridgeServerImpl bridge; + + public StandaloneParser() { + ProcessWrapperImpl processWrapper = new ProcessWrapperImpl(); + EmptyConfiguration emptyConfiguration = new EmptyConfiguration(); + bridge = new BridgeServerImpl(new NodeCommandBuilderImpl(processWrapper), new BundleImpl(), new RulesBundles(), + new NodeDeprecationWarning(new AnalysisWarningsWrapper()), new StandaloneTemporaryFolder(), new EmbeddedNode(processWrapper, new Environment(emptyConfiguration))); + try { + bridge.startServerLazily(new BridgeServerConfig(emptyConfiguration, new File(".").getAbsolutePath(), SonarProduct.SONARLINT)); + bridge.initLinter(List.of(), List.of(), List.of(), AnalysisMode.DEFAULT, null, List.of()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public ESTree.Program parse(String code) { + BridgeServer.JsAnalysisRequest request = new BridgeServer.JsAnalysisRequest( + "file.js", + "MAIN", + "js", + code, + true, + null, + null, + AnalysisMode.DEFAULT_LINTER_ID, + false); + try { + BridgeServer.AnalysisResponse result = bridge.analyzeJavaScript(request); + Node ast = result.ast(); + if (ast == null) { + throw new IllegalArgumentException("Failed to parse the code"); + } + return ESTreeFactory.from(ast, ESTree.Program.class); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void close() { + bridge.stop(); + } + + // Visible for testing + static class EmptyConfiguration implements org.sonar.api.config.Configuration { + + @Override + public Optional get(String key) { + return Optional.empty(); + } + + @Override + public boolean hasKey(String key) { + return false; + } + + @Override + public String[] getStringArray(String key) { + return new String[0]; + } + } +} diff --git a/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/StandaloneTemporaryFolder.java b/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/StandaloneTemporaryFolder.java new file mode 100644 index 00000000000..a7d55bf9262 --- /dev/null +++ b/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/StandaloneTemporaryFolder.java @@ -0,0 +1,57 @@ +/* + * 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.standalone; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import javax.annotation.Nullable; + +public class StandaloneTemporaryFolder implements org.sonar.api.utils.TempFolder { + + @Override + public File newDir() { + return newDir("sonarjs"); + } + + @Override + public File newDir(String name) { + try { + return Files.createTempDirectory(name).toFile(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public File newFile() { + return newFile(null, null); + } + + @Override + public File newFile(@Nullable String prefix, @Nullable String suffix) { + try { + return Files.createTempFile(prefix, suffix).toFile(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/package-info.java b/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/package-info.java new file mode 100644 index 00000000000..c5908a23fab --- /dev/null +++ b/sonar-plugin/standalone/src/main/java/org/sonar/plugins/javascript/standalone/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.plugins.javascript.standalone; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-plugin/standalone/src/test/java/org/sonar/plugins/javascript/standalone/StandaloneParserTest.java b/sonar-plugin/standalone/src/test/java/org/sonar/plugins/javascript/standalone/StandaloneParserTest.java new file mode 100644 index 00000000000..44ca7fba057 --- /dev/null +++ b/sonar-plugin/standalone/src/test/java/org/sonar/plugins/javascript/standalone/StandaloneParserTest.java @@ -0,0 +1,77 @@ +/* + * 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.standalone; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.javascript.api.estree.ESTree; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.plugins.javascript.api.estree.ESTree.Program; + +class StandaloneParserTest { + + private static StandaloneParser parser; + + @BeforeAll + static void setUp() { + parser = new StandaloneParser(); + } + + @AfterAll + static void tearDown() { + parser.close(); + } + + @Test + void should_parse_multiple_time() { + Program firstSample = parser.parse("var a = 42;"); + assertThat(firstSample.body()).hasSize(1); + var firstElement = firstSample.body().get(0); + assertThat(firstElement).isInstanceOfSatisfying(ESTree.VariableDeclaration.class, v -> { + assertThat(v.declarations()).hasSize(1); + var declaration = v.declarations().get(0); + assertThat(declaration).isInstanceOfSatisfying(ESTree.VariableDeclarator.class, d -> { + assertThat(d.id()).isInstanceOfSatisfying(ESTree.Identifier.class, i -> assertThat(i.name()).isEqualTo("a")); + assertThat(d.init().get()).isInstanceOfSatisfying(ESTree.SimpleLiteral.class, l -> assertThat(l.value()).isEqualTo(42)); + }); + }); + Program secondSample = parser.parse("let x;"); + assertThat(secondSample.body()).hasSize(1); + } + + @Test + void should_throw_exception_when_fail_to_parse_code() { + assertThatThrownBy(() -> parser.parse("...")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Failed to parse the code"); + } + + @Test + void test_empty_configuration() { + StandaloneParser.EmptyConfiguration emptyConfiguration = new StandaloneParser.EmptyConfiguration(); + assertThat(emptyConfiguration.get("key")).isEmpty(); + assertThat(emptyConfiguration.hasKey("key")).isFalse(); + assertThat(emptyConfiguration.getStringArray("key")).isEmpty(); + } + +} diff --git a/sonar-plugin/standalone/src/test/java/org/sonar/plugins/javascript/standalone/StandaloneTemporaryFolderTest.java b/sonar-plugin/standalone/src/test/java/org/sonar/plugins/javascript/standalone/StandaloneTemporaryFolderTest.java new file mode 100644 index 00000000000..c4762b30568 --- /dev/null +++ b/sonar-plugin/standalone/src/test/java/org/sonar/plugins/javascript/standalone/StandaloneTemporaryFolderTest.java @@ -0,0 +1,56 @@ +/* + * 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.standalone; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; + +class StandaloneTemporaryFolderTest { + + private static final StandaloneTemporaryFolder STANDALONE_TEMPORARY_FOLDER = new StandaloneTemporaryFolder(); + + @Test + void new_dir_can_throw() { + try (MockedStatic mockedFiles = Mockito.mockStatic(Files.class)) { + mockedFiles.when(() -> Files.createTempDirectory(any(String.class))).thenThrow(new IOException("IOException!")); + assertThatThrownBy(STANDALONE_TEMPORARY_FOLDER::newDir) + .isInstanceOf(UncheckedIOException.class) + .hasMessage("java.io.IOException: IOException!"); + } + } + + @Test + void new_file_can_throw() { + try (MockedStatic mockedFiles = Mockito.mockStatic(Files.class)) { + mockedFiles.when(() -> Files.createTempFile(any(), any())).thenThrow(new IOException("IOException!")); + assertThatThrownBy(STANDALONE_TEMPORARY_FOLDER::newFile) + .isInstanceOf(UncheckedIOException.class) + .hasMessage("java.io.IOException: IOException!"); + } + } + +}