Reporting more than Java code in Sonar (Part I)

8:57 AM , , 2 Comments

Of course, anyone that has done static analysis on their project in the past has found certain bad practices that are out of their tools reach to spot. Some examples are:
  • Front-end code, like CSS and HTML
  • Configuration files, like a Maven pom.xml or a Spring applicationContext.xml
  • Localization files
While not supported out-of-the-box, Sonar makes reaching and reporting on these areas of your project much easier. Basically, here are the steps to getting Sonar to report on additional languages:
  1. Make Sonar aware of your new language.
  2. Attach quality rules for that language to an existing Quality Profile.
  3. Create/use a tool that will detect the bad practices you are looking for.
  4. Hook that tool together with Sonar either via the tool's Maven build plug-in or by invoking it programmatically within the Sonar plug-in framework.

Make Sonar Aware of Your New Language

First, it is easy to make Sonar aware of an additional language. In our case, we wanted to address configuration found in various xml application files for Spring, Maven, JSF, and the like. The following seven files are required:
  • XmlPlugin.java - What you are making in the most basic sense is a Sonar plug-in. For each sonar plug-in, there is a main plugin file like this one. I won't go into this here. Instead, I'd recommend you look at the Sonar Plug-in Documentation.
  • Xml.java - This file is what the rest of Sonar will refer to when it asks what language a file is, etc. Ours looks like this:

    public class Xml extends AbstractLanguage {
    protected static final String[] EXTENSIONS = { "xml", "xhtml" };
    public static final Xml INSTANCE = new Xml();

    public Xml() {
    super("xml", "XML"); // 'key' and 'name', or, in other words, internal and external names
    }

    public String[] getFileSuffixes() {
    return EXTENSIONS.clone();
    }

    // ... some other helper methods
    }


  • XmlFile.java and XmlPackage.java - These two classes represent the xml file and directory metadata. They aren't a way to get at the contents of the file, but instead its name, location, etc. The both extend org.sonar.api.resources.Resource.

  • XmlSourceImporter.java - This file is in charge of looking up all the "xml" files in the project and notifying Sonar about them. Because we also want to include the main pom.xml file, which is outside of the source directory, this class is a little more complicated. However, you can easily pull out the basics from it:

    public class XmlSourceImporter extends AbstractSourceImporter {
    public XmlSourceImporter() {
    super(Xml.INSTANCE);
    }

    public void analyse(Project project, SensorContext context) {
    try {
    doAnalyse(project, context);
    } catch ( IOException e ) {
    throw new SonarException("Parsing source files ended poorly", e);
    }
    }

    protected XmlFile createResource(File file, List sourceDirs, boolean unitTest) {
    ... create an XmlFile object ...
    }

    /* Depending on needs, one might ask the kind of project that it is or something. In this case, though, we want to execute this importer on every project, since we are anticipating the existence of xml files in every project. */
    public boolean shouldExecuteOnProject(Project project) {
    return isEnabled(project);
    }

    protected void doAnalyse(Project project, SensorContext context) throws IOException {
    ProjectFileSystem fileSystem = project.getFileSystem();
    File root = fileSystem.getBasedir();
    List sourceDirs = fileSystem.getSourceDirs();
    sourceDirs.add(root);
    List xmlFiles = ...magical method call that looks recursively in the sourceDirs for xml files...
    for ( File xmlFile : xmlFiles ) {
    // if it is in a derived directory, like the build directory, we don't want it
    if ( DefaultProjectFileSystem.getRelativePath(xmlFile, fileSystem.getBuildDir()) == null ) {
    sourceFiles.add(xmlFile);
    }
    }

    }
    }


Attach Quality Rules

Now, what is not so obvious is how to get Sonar to report on all languages, Java and otherwise, on one dashboard. In fact, in an email that I sent to the the Sonar developers, they apparently don't officially support it, yet. But, we found a way! And it works great for us.

The key lies in creating a rules repository that contributes to the existing Java quality profile that you already have set up. A rules repository is just another Sonar extension, one that represents an xml rule configuration file and marshals the contents of that file into a RulesProfile object.

Here is an example of what the getProvidedProfiles might look like:


public List getProvidedProfiles() {
RulesProfile profile = new RulesProfile("My Profile", Java.KEY);
profile.setDefaultProfile(true);
profile.setProvided(true);

List rules = getInitialReferential();
List activeRules = new ArrayList();
for (Rule rule : rules) {
activeRules.add(new ActiveRule(profile, rule, rule.getPriority()));
}
profile.setActiveRules(activeRules);

return Arrays.asList(profile);
}


In the end, it's a little bit of a hack.

The only other thing that is necessary from Sonar's perspective is a way to read the violations file that your analysis tool creates. We modeled ours after PMD's violations file. Here, you can extend AbstractViolationsXmlParser and follow the pattern in the PMD Sonar Plugin.


Enhanced by Zemanta

Josh Cummings

"I love to teach, as a painter loves to paint, as a singer loves to sing, as a musician loves to play" - William Lyon Phelps

2 comments:

  1. Unfortunately, it is still not so obvious. What file should have this getProvidedProfiles? I can't find it in the API docs, and it certainly isn't part of RulesProfile. There is definitely some magic that goes on at several points in the code you've posted. Would it be possible to post your complete source, or at least as much as doesn't constitute an NDA violation? I'm having great difficulty getting Sonar to recognize rules for my company's proprietary language.

    ReplyDelete
  2. Thanks for insight. As Michael mentioned, it is still not clear
    Any more insight into creating new plugin would help

    ReplyDelete