Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.1] - 2024-05-14

## Fixed

- Resolve language suffix conflict between the plugin's custom YAML/JSON support and SonarQube's built-in language detection.

## [1.2.0] - 2024-05-05

## Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

# 🛠️ Sonar OpenApi (plugin) ![Release](https://img.shields.io/badge/release-1.2.0-purple) ![Swagger](https://img.shields.io/badge/-openapi-%23Clojure?style=flat&logo=swagger&logoColor=white) ![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=flat&logo=openjdk&logoColor=white) [![License: LGPL v3](https://img.shields.io/badge/license-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
# 🛠️ Sonar OpenApi (plugin) ![Release](https://img.shields.io/badge/release-1.2.1-purple) ![Swagger](https://img.shields.io/badge/-openapi-%23Clojure?style=flat&logo=swagger&logoColor=white) ![Java](https://img.shields.io/badge/java-%23ED8B00.svg?style=flat&logo=openjdk&logoColor=white) [![License: LGPL v3](https://img.shields.io/badge/license-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)

Sonar OpenApi (plugin) is a code analyzer for OpenAPI specifications, is the spiritual successor of [SonarOpenApi](https://github.com/societe-generale/sonar-openapi), carrying on from the point where it left off with support of Apiaddicts community.

Expand Down
2 changes: 1 addition & 1 deletion its/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.0</version>
<version>1.2.1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
2 changes: 1 addition & 1 deletion openapi-checks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.0</version>
<version>1.2.1</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
import java.util.List;

public final class CheckList {
public static final String REPOSITORY_KEY = "openapi";
public static final String YAML_REPOSITORY_KEY = "openapi-yaml";
public static final String JSON_REPOSITORY_KEY = "openapi-json";
public static final String YAML_LANGUAGE = "yaml";
public static final String JSON_LANGUAGE = "json";

private CheckList() {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* doSonarAPI: SonarQube OpenAPI Plugin
* Copyright (C) 2021-2022 Apiaddicts
* contacta AT apiaddicts DOT org
*
* 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.apiaddicts.apitools.dosonarapi.checks;

import java.util.List;
import org.junit.Test;

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

public class CheckListTest {

@Test
public void returns_all_check_classes() {
List<Class<?>> checks = CheckList.getChecks();
assertThat(checks).isNotEmpty().contains(
PathMaskeradingCheck.class,
MediaTypeCheck.class,
ParsingErrorCheck.class,
DefaultResponseCheck.class,
DefinedResponseCheck.class,
DeclaredTagCheck.class,
DocumentedTagCheck.class,
AtMostOneBodyParameterCheck.class,
NoUnusedDefinitionCheck.class,
NoContentIn204Check.class,
ProvideOpSummaryCheck.class,
ContactValidEmailCheck.class,
DescriptionDiffersSummaryCheck.class
);
}

@Test
public void constants_have_expected_values() {
assertThat(CheckList.YAML_REPOSITORY_KEY).isEqualTo("openapi-yaml");
assertThat(CheckList.JSON_REPOSITORY_KEY).isEqualTo("openapi-json");
assertThat(CheckList.YAML_LANGUAGE).isEqualTo("yaml");
assertThat(CheckList.JSON_LANGUAGE).isEqualTo("json");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@

public class DocumentedTagCheckTest {
@Test
public void verify_media_type_in_v2() {
OpenApiCheckVerifier.verify("src/test/resources/checks/v2/declared-tag.yaml", new DeclaredTagCheck(), true, false, false);
public void verify_documented_tag_in_v2() {
OpenApiCheckVerifier.verify("src/test/resources/checks/v2/documented-tag.yaml", new DocumentedTagCheck(), true, false, false);
}

@Test
public void verify_media_type_in_v3() {
OpenApiCheckVerifier.verify("src/test/resources/checks/v3/declared-tag.yaml", new DeclaredTagCheck(), false, true, false);
public void verify_documented_tag_in_v3() {
OpenApiCheckVerifier.verify("src/test/resources/checks/v3/documented-tag.yaml", new DocumentedTagCheck(), false, true, false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@
package org.apiaddicts.apitools.dosonarapi.checks;

import com.sonar.sslr.api.RecognitionException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Test;
import org.apiaddicts.apitools.dosonarapi.api.OpenApiFile;
import org.apiaddicts.apitools.dosonarapi.api.OpenApiVisitorContext;
import org.apiaddicts.apitools.dosonarapi.api.PreciseIssue;
import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration;
import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.ValidationException;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
Expand All @@ -43,6 +48,33 @@ public void reports_parsing_errors() {
.contains(tuple(3, "Parsing exception message"));
}

@Test
public void reports_validation_exception_causes_as_issues() {
String invalidYaml =
"openapi: \"3.0.0\"\n" +
"info:\n" +
" title: Test API\n" +
"paths:\n" +
" /pets:\n" +
" post:\n" +
" description: missing responses\n";

OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, true);
YamlParser parser = OpenApiParser.createV3(config);
OpenApiVisitorContext context;
try {
parser.parse(invalidYaml);
context = new OpenApiVisitorContext(new TestFile(), (RecognitionException) null);
} catch (ValidationException e) {
context = new OpenApiVisitorContext(new TestFile(), e);
}

ParsingErrorCheck check = new ParsingErrorCheck();
List<PreciseIssue> issues = check.scanFileForIssues(context);

assertThat(issues).isNotEmpty();
}

private static class TestFile implements OpenApiFile {

@Override
Expand Down
2 changes: 1 addition & 1 deletion openapi-front-end/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.apiaddicts.apitools.dosonarapi</groupId>
<artifactId>dosonarapi</artifactId>
<version>1.2.0</version>
<version>1.2.1</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ private static void buildServer(YamlGrammarBuilder b) {
private static void buildInfo(YamlGrammarBuilder b) {
b.rule(INFO).is(b.object(
b.mandatoryProperty("title", b.string()),
b.property("summary", b.string()),
b.property("description", DESCRIPTION),
b.property("termsOfService", b.string()),
b.property("contact", CONTACT),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,44 @@ public void compare_precise_location_differents_objects(){
assertThat(issueLocation1.equals(issueLocation2)).isFalse();
}

@Test
public void both_null_messages_are_equal() {
IssueLocation loc1 = IssueLocation.atLineLevel(null, 10);
IssueLocation loc2 = IssueLocation.atLineLevel(null, 10);
assertThat(loc1.equals(loc2)).isTrue();
assertThat(loc1).hasSameHashCodeAs(loc2);
}

@Test
public void null_message_not_equal_to_non_null_message() {
IssueLocation loc1 = IssueLocation.atLineLevel(null, 10);
IssueLocation loc2 = IssueLocation.atLineLevel(MESSAGE, 10);
assertThat(loc1.equals(loc2)).isFalse();
assertThat(loc2.equals(loc1)).isFalse();
}

@Test
public void hashcode_with_null_message() {
IssueLocation loc = IssueLocation.atLineLevel(null, 5);
int hash = loc.hashCode();
assertThat(hash).isEqualTo(IssueLocation.atLineLevel(null, 5).hashCode());
}

@Test
public void precise_location_with_multiline_value() {
JsonNode root = parser.parse("swagger: \"2.0\"\n" +
"info:\n" +
" version: 1.0.0\n" +
" title: T\n" +
" description: |\n" +
" line one\n" +
" line two\n" +
"paths:\n" +
" /pets: {}");
JsonNode descNode = root.at("/info/description").value();
IssueLocation loc = IssueLocation.preciseLocation(MESSAGE, descNode);
assertThat(loc.startLine()).isGreaterThan(0);
assertThat(loc.endLine()).isGreaterThanOrEqualTo(loc.startLine());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@
import com.google.common.collect.Sets;
import com.sonar.sslr.api.AstNodeType;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.junit.Test;
import org.sonar.check.Rule;
import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar;
import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration;
import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser;

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

Expand Down Expand Up @@ -76,4 +82,40 @@ public void skips_rules_on_x_nosonar() {
assertThat(rule2.visitedNodes).isEmpty();
assertThat(rule3.visitedNodes).containsOnly("/paths/~1pets/get", "/paths/~1pets/get/parameters/0");
}

private static class LineIssueCheck extends OpenApiCheck {
@Override
public Set<AstNodeType> subscribedKinds() {
return Collections.emptySet();
}

@Override
protected void visitFile(JsonNode root) {
addLineIssue("line problem", 3);
}
}

private static class NoAnnotationCheck extends OpenApiCheck {}

@Test
public void add_line_issue_creates_issue_on_given_line() {
OpenApiConfiguration config = new OpenApiConfiguration(StandardCharsets.UTF_8, false);
YamlParser parser = OpenApiParser.createV3(config);
JsonNode root = parser.parse("openapi: \"3.0.0\"\ninfo:\n title: T\n version: 1.0\npaths: {}");
OpenApiFile file = new OpenApiFile() {
@Override public String content() { return ""; }
@Override public String fileName() { return "test.yaml"; }
};
OpenApiVisitorContext ctx = new OpenApiVisitorContext(root, parser.getIssues(), file);

LineIssueCheck check = new LineIssueCheck();
List<PreciseIssue> issues = check.scanFileForIssues(ctx);
assertThat(issues).hasSize(1);
assertThat(issues.get(0).primaryLocation().startLine()).isEqualTo(3);
}

@Test
public void no_rule_annotation_returns_empty_rule_id() {
assertThat(new NoAnnotationCheck().getRuleId()).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,92 @@
*/
package org.apiaddicts.apitools.dosonarapi.api;

import org.apiaddicts.apitools.dosonarapi.api.IssueLocation;
import org.apiaddicts.apitools.dosonarapi.api.PreciseIssue;
import org.junit.Test;

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

public class PreciseIssueTest {

private static final String MESSAGE = "Test Message";

@Test
public void compare_equals_objects(){
public void compare_equals_objects() {
PreciseIssue preciseIssue1 = new PreciseIssue(IssueLocation.atLineLevel(null, 42000)).withCost(5);
PreciseIssue preciseIssue2 = new PreciseIssue(IssueLocation.atLineLevel(null, 42000)).withCost(5);
assertThat(preciseIssue1.equals(preciseIssue2)).isTrue();
assertThat(preciseIssue1.hashCode() == preciseIssue2.hashCode()).isTrue();
}

@Test
public void not_equal_to_non_precise_issue() {
PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
assertThatObject(issue).isNotEqualTo("not an issue");
assertThatObject(issue).isNotEqualTo(null);
}

@Test
public void not_equal_when_costs_differ() {
PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)).withCost(1);
PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1)).withCost(2);
assertThat(issue1.equals(issue2)).isFalse();
}

@Test
public void not_equal_when_primary_location_differs() {
PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 2));
assertThat(issue1.equals(issue2)).isFalse();
}

@Test
public void secondary_location_via_issue_location() {
IssueLocation loc = IssueLocation.atLineLevel("secondary", 5);
PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("primary", 1));
issue.secondary(loc);
assertThat(issue.secondaryLocations()).hasSize(1);
assertThat(issue.secondaryLocations().get(0)).isEqualTo(loc);
}

@Test
public void cost_is_null_by_default() {
PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
assertThat(issue.cost()).isNull();
}

@Test
public void hashcode_without_cost() {
PreciseIssue issue1 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
assertThat(issue1).hasSameHashCodeAs(issue2);
}

@Test
public void equals_with_null_primary_location() {
PreciseIssue issue1 = new PreciseIssue(null);
PreciseIssue issue2 = new PreciseIssue(null);
assertThat(issue1.equals(issue2)).isTrue();
assertThat(issue1).hasSameHashCodeAs(issue2);
}

@Test
public void null_primary_location_not_equal_to_non_null() {
PreciseIssue issue1 = new PreciseIssue(null);
PreciseIssue issue2 = new PreciseIssue(IssueLocation.atLineLevel("msg", 1));
assertThat(issue1.equals(issue2)).isFalse();
assertThat(issue2.equals(issue1)).isFalse();
}

@Test
public void secondary_location_via_node() {
java.nio.charset.Charset utf8 = java.nio.charset.StandardCharsets.UTF_8;
org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration config =
new org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration(utf8, true);
org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser parser =
org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser.createV2(config);
org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode root =
parser.parse("swagger: \"2.0\"\ninfo:\n version: 1.0.0\n title: T\npaths:\n /pets: {}");
org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode node = root.at("/paths/~1pets").value();
PreciseIssue issue = new PreciseIssue(IssueLocation.atLineLevel("primary", 1));
issue.secondary(node, "secondary message");
assertThat(issue.secondaryLocations()).hasSize(1);
}
}
Loading
Loading