Skip to content

Commit

Permalink
feat(base): Add base plugin to build on
Browse files Browse the repository at this point in the history
Base plugin adds a scala language, plugin and sensor.
Base metrics consist of Non-comments lines of codes count and
comment lines count.
It is meant as a skeleton to build on.
  • Loading branch information
Augustin Borsu committed Apr 30, 2016
1 parent 8598589 commit e085c25
Show file tree
Hide file tree
Showing 8 changed files with 536 additions and 0 deletions.
161 changes: 161 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.sonarsource.parent</groupId>
<artifactId>parent</artifactId>
<version>31</version>
</parent>

<artifactId>sonar-scala-plugin</artifactId>
<packaging>sonar-plugin</packaging>
<version>0.0.1-SNAPSHOT</version>

<name>Sonar Scala Plugin</name>
<description>Enables analysis of Scala projects into Sonar.</description>
<url>http://github.com/sagacify/sonar-scala</url>
<inceptionYear>2016</inceptionYear>

<licenses>
<license>
<name>GNU LGPL 3</name>
<url>http://www.gnu.org/licenses/lgpl.txt</url>
<distribution>repo</distribution>
</license>
</licenses>

<organization>
<name>Sagacify</name>
<url>https://www.sagacify.com</url>
</organization>

<developers>
<developer>
<id>aborsu</id>
<name>Augustin Borsu</name>
<email>[email protected]</email>
<url>http://www.acelpb.com/</url>
</developer>
</developers>

<scm>
<connection>scm:git:[email protected]:sagacify/sonar-scala.git</connection>
<developerConnection>scm:git:[email protected]:sagacify/sonar-scala.git</developerConnection>
<url>https://github.com/sagacify/sonar-scala</url>
<tag>HEAD</tag>
</scm>

<issueManagement>
<url>https://github.com/sagacify/sonar-scala/issues</url>
</issueManagement>

<properties>
<sonar.version>5.4</sonar.version>
<sonar.pluginKey>scala</sonar.pluginKey>
<sonar.pluginName>Scala</sonar.pluginName>
<sonar.pluginClass>com.sagacify.sonar.scala.ScalaPlugin</sonar.pluginClass>

<scala.version>2.11.8</scala.version>
<scala.major.version>2.11</scala.major.version>

</properties>

<dependencies>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-plugin-api</artifactId>
<version>${sonar.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>

<dependency>
<groupId>org.scalariform</groupId>
<artifactId>scalariform_${scala.major.version}</artifactId>
<version>0.1.8</version>
<exclusions>
<exclusion>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.2</version>
<scope>provided</scope>
</dependency>

<!-- unit tests -->
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_${scala.major.version}</artifactId>
<version>2.2.6</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-testing-harness</artifactId>
<version>${sonar.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!-- disable surefire -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!-- enable scalatest -->
<plugin>
<groupId>org.scalatest</groupId>
<artifactId>scalatest-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
<junitxml>.</junitxml>
<filereports>WDF TestSuite.txt</filereports>
</configuration>
<executions>
<execution>
<id>test</id>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- enable scala-maven -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
66 changes: 66 additions & 0 deletions src/main/scala/com/sagacify/sonar/scala/Measures.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.sagacify.sonar.scala

import scala.annotation.tailrec

import scalariform.lexer.ScalaLexer
import scalariform.lexer.Token
import scalariform.lexer.Tokens.LINE_COMMENT
import scalariform.lexer.Tokens.MULTILINE_COMMENT
import scalariform.lexer.Tokens.XML_COMMENT
import scalariform.lexer.Tokens.WS
import scalariform.lexer.Tokens.EOF

object Measures {

/* applied on raw source code */

/* applied on lines of code */

/* applied on tokenised code */

@tailrec
final def count_comment_lines(tokens: List[Token], i: Int = 0): Int = {
tokens match {
case Nil => i
case token :: tail if token.tokenType.isComment => {
token.tokenType match {
case LINE_COMMENT =>
count_comment_lines(tail, i + 1)
case MULTILINE_COMMENT =>
count_comment_lines(tail, i + token.rawText.count(_ == '\n') + 1)
case XML_COMMENT =>
new scala.NotImplementedError("XML ?!"); i
}
}
case _ :: tail => count_comment_lines(tail, i)
}
}

@tailrec
final def count_ncloc(tokens: List[Token], i: Int = 0): Int = {

@tailrec
def get_next_line(tokens: List[Token]): List[Token] = {
tokens match {
case Nil => Nil
case token :: tail if token.tokenType == WS &&
token.text.contains('\n') => tail
case token :: tail if token.tokenType == LINE_COMMENT => tail
case token :: tail => get_next_line(tail)
}
}

tokens match {
case Nil => i
case token :: tail if token.tokenType == WS => count_ncloc(tail, i)
case token :: tail if token.tokenType == EOF => i
case token :: tail =>
if( !token.tokenType.isNewline & !token.tokenType.isComment) {
count_ncloc(get_next_line(tail), i + 1)
} else {
count_ncloc(tail, i)
}
}
}

}
42 changes: 42 additions & 0 deletions src/main/scala/com/sagacify/sonar/scala/ScalaPlugin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.sagacify.sonar.scala

import scala.collection.JavaConversions._
import scala.collection.mutable.ListBuffer

import org.sonar.api.config.Settings
import org.sonar.api.Extension
import org.sonar.api.resources.AbstractLanguage
import org.sonar.api.SonarPlugin
import scalariform.lexer.ScalaLexer
import scalariform.lexer.Token

/**
* Defines Scala as a language for SonarQube.
*/
class Scala(s: Settings) extends AbstractLanguage("scala", "Scala") {

override def getFileSuffixes: Array[String] = Array("scala")

}

object Scala {

def tokenize(sourceCode: String, scalaVersion: String): List[Token] =
ScalaLexer.createRawLexer(sourceCode, false, scalaVersion).toList

}

/**
* Plugin entry point.
*/
class ScalaPlugin extends SonarPlugin {

override def getExtensions: java.util.List[Class[_]] =
ListBuffer[Class[_]] (
classOf[Scala],
classOf[ScalaSensor]
)

override val toString = getClass.getSimpleName

}
52 changes: 52 additions & 0 deletions src/main/scala/com/sagacify/sonar/scala/ScalaSensor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.sagacify.sonar.scala

import scala.io.Source
import scala.collection.JavaConversions._

import org.sonar.api.batch.fs.FileSystem
import org.sonar.api.batch.Sensor
import org.sonar.api.batch.SensorContext
import org.sonar.api.measures.{CoreMetrics => CM}
import org.sonar.api.resources.Project


class ScalaSensor(scala: Scala, fs: FileSystem) extends Sensor {

def shouldExecuteOnProject(project: Project): Boolean = {
return fs.hasFiles(fs.predicates().hasLanguage(scala.getKey()));
}

def analyse(project: Project, context: SensorContext): Unit = {

val charset = fs.encoding().toString()
val version = "2.11.8"

val inputFiles = fs.inputFiles(fs.predicates().hasLanguage(scala.getKey()))

inputFiles.foreach{ inputFile =>
context.saveMeasure(inputFile, CM.FILES, 1.0);

val sourceCode = Source.fromFile(inputFile.file, charset).mkString
val tokens = Scala.tokenize(sourceCode, version)

context.saveMeasure(inputFile,
CM.COMMENT_LINES,
Measures.count_comment_lines(tokens))
context.saveMeasure(inputFile,
CM.NCLOC,
Measures.count_ncloc(tokens))

// context.saveMeasure(input, CM.CLASSES, classes)
// context.saveMeasure(input, CM.FUNCTIONS, methods)
// context.saveMeasure(input, CM.ACCESSORS, accessors)
// context.saveMeasure(input, CM.COMPLEXITY_IN_FUNCTIONS, complexityInMethods)
// context.saveMeasure(input, CM.COMPLEXITY_IN_CLASSES, fileComplexity)
// context.saveMeasure(input, CM.COMPLEXITY, fileComplexity)
// context.saveMeasure(input, CM.PUBLIC_API, publicApiChecker.getPublicApi())
// context.saveMeasure(input, CM.PUBLIC_DOCUMENTED_API_DENSITY, publicApiChecker.getDocumentedPublicApiDensity())
// context.saveMeasure(input, CM.PUBLIC_UNDOCUMENTED_API, publicApiChecker.getUndocumentedPublicApi())

}
}
}

7 changes: 7 additions & 0 deletions src/test/resources/ScalaFile1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class ScalaFile1 {
val value = "value"

def function: Unit = {
println("function called.")
}
}
9 changes: 9 additions & 0 deletions src/test/resources/ScalaFile2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Expected Header Comment

class ScalaFile2 {
val value = "value"

def function: Unit = {
println("function called.")
}
}
Loading

0 comments on commit e085c25

Please sign in to comment.