diff --git a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/ESTreeFactory.java b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/ESTreeFactory.java index 300c6e7f7e..287b57e223 100644 --- a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/ESTreeFactory.java +++ b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/ESTreeFactory.java @@ -180,6 +180,9 @@ public static T from(Node node, Class clazz) { case UNRECOGNIZED -> throw new IllegalArgumentException("Unknown node type: " + node.getType() + " at " + node.getLoc()); }; + if (!clazz.isInstance(estreeNode)) { + throw new IllegalStateException("Expected " + clazz + " but got " + estreeNode.getClass()); + } return clazz.cast(estreeNode); } diff --git a/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/ESTreeFactoryTest.java b/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/ESTreeFactoryTest.java index 8794373074..c54b632678 100644 --- a/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/ESTreeFactoryTest.java +++ b/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/ESTreeFactoryTest.java @@ -572,4 +572,17 @@ void throw_exception_from_unrecognized_type() { .isInstanceOf(IllegalArgumentException.class) .hasMessageStartingWith("Unknown node type: UNRECOGNIZED"); } + + @Test + void throw_exception_for_incorrect_cast() { + Node block = Node.newBuilder() + .setType(NodeType.BlockStatementType) + .setBlockStatement(BlockStatement.newBuilder().build()) + .build(); + + assertThatThrownBy(() -> ESTreeFactory.from(block, ESTree.Super.class)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Expected class org.sonar.plugins.javascript.api.estree.ESTree$Super " + + "but got class org.sonar.plugins.javascript.api.estree.ESTree$BlockStatement"); + } } diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AbstractAnalysis.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AbstractAnalysis.java index ce57bb0e25..a71c951206 100644 --- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AbstractAnalysis.java +++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AbstractAnalysis.java @@ -113,13 +113,7 @@ protected void analyzeFile(InputFile file, @Nullable List tsConfigs, @Nu CacheAnalysis.fromResponse(response.ucfgPaths(), response.cpdTokens()), file ); - Node responseAst = response.ast(); - if (responseAst != null) { - // When we haven't serialized the AST: - // either because no consumer is listening - // or the file extension or AST nodes are unsupported - consumers.accept(new JsFile(file, ESTreeFactory.from(responseAst, ESTree.Program.class))); - } + acceptAstResponse(response, file); } catch (IOException e) { LOG.error("Failed to get response while analyzing " + file.uri(), e); throw e; @@ -131,6 +125,21 @@ protected void analyzeFile(InputFile file, @Nullable List tsConfigs, @Nu } } + private void acceptAstResponse(BridgeServer.AnalysisResponse response, InputFile file) { + Node responseAst = response.ast(); + if (responseAst != null) { + // When we haven't serialized the AST: + // either because no consumer is listening + // or the file extension or AST nodes are unsupported + try { + ESTree.Program program = ESTreeFactory.from(responseAst, ESTree.Program.class); + consumers.accept(new JsFile(file, program)); + } catch (Exception e) { + LOG.debug("Failed to deserialize AST for file: {}", file.uri(), e); + } + } + } + private BridgeServer.JsAnalysisRequest getJsAnalysisRequest( InputFile file, @Nullable String fileContent, 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 76c576f12c..f40a9f085e 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 @@ -834,6 +834,51 @@ public void doneAnalysis() { assertThat(consumer.done).isTrue(); } + @Test + void should_not_invoke_analysis_consumers_when_cannot_deserialize() throws Exception { + var consumer = new JsAnalysisConsumer() { + final List files = new ArrayList<>(); + boolean done; + + @Override + public void accept(JsFile jsFile) { + files.add(jsFile); + } + + @Override + public void doneAnalysis() { + done = true; + } + }; + + var sensor = new JsTsSensor( + checks(ESLINT_BASED_RULE, "S2260"), + bridgeServerMock, + analysisWithProgram(), + analysisWithWatchProgram(), + new AnalysisConsumers(List.of(consumer)) + ); + + var inputFile = createInputFile(context); + var tsProgram = new TsProgram("1", List.of(inputFile.absolutePath()), List.of(), false, null); + when(bridgeServerMock.createProgram(any())).thenReturn(tsProgram); + + Node erroneousNode = Node.newBuilder() + .setType(NodeType.BlockStatementType) + .build(); + + when(bridgeServerMock.analyzeTypeScript(any())).thenReturn( + new AnalysisResponse(null, List.of(), List.of(), List.of(), new BridgeServer.Metrics(), List.of(), List.of(), erroneousNode) + ); + + sensor.execute(context); + assertThat(consumer.files).isEmpty(); + assertThat(consumer.done).isTrue(); + + assertThat(logTester.logs(Level.DEBUG)) + .contains("Failed to deserialize AST for file: " + inputFile.uri()); + } + private JsTsSensor createSensor() { return new JsTsSensor( checks(ESLINT_BASED_RULE, "S2260"),