org.sonar.plsqlopen.squid.AstScanner.kt Maven / Gradle / Ivy
The newest version!
/**
* Z PL/SQL Analyzer
* Copyright (C) 2015-2024 Felipe Zorzo
* mailto:felipe AT felipezorzo DOT com DOT br
*
* 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.plsqlopen.squid
import com.felipebz.flr.api.Grammar
import com.felipebz.flr.api.RecognitionException
import com.felipebz.flr.impl.Parser
import org.sonar.plsqlopen.FormsMetadataAwareCheck
import org.sonar.plsqlopen.metadata.FormsMetadata
import org.sonar.plsqlopen.metrics.ComplexityVisitor
import org.sonar.plsqlopen.metrics.FunctionComplexityVisitor
import org.sonar.plsqlopen.metrics.MetricsVisitor
import org.sonar.plsqlopen.parser.PlSqlParser
import org.sonar.plsqlopen.symbols.DefaultTypeSolver
import org.sonar.plsqlopen.symbols.ScopeImpl
import org.sonar.plsqlopen.symbols.SymbolVisitor
import org.sonar.plsqlopen.utils.getAnnotation
import org.sonar.plsqlopen.utils.log.Loggers
import org.sonar.plugins.plsqlopen.api.PlSqlFile
import org.sonar.plugins.plsqlopen.api.PlSqlVisitorContext
import org.sonar.plugins.plsqlopen.api.annotations.RuleInfo
import org.sonar.plugins.plsqlopen.api.checks.PlSqlCheck
import org.sonar.plugins.plsqlopen.api.checks.PlSqlVisitor
import java.io.InterruptedIOException
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class AstScanner(private val checks: Collection,
private val formsMetadata: FormsMetadata?,
isErrorRecoveryEnabled: Boolean,
charset: Charset = StandardCharsets.UTF_8) {
private val parser: Parser = PlSqlParser.create(PlSqlConfiguration(charset, isErrorRecoveryEnabled))
val globalScope = ScopeImpl()
fun scanFile(inputFile: PlSqlFile, extraVisitors: List = emptyList()) : AstScannerResult {
val newVisitorContext = getPlSqlVisitorContext(inputFile)
val metricsVisitor = MetricsVisitor()
val complexityVisitor = ComplexityVisitor()
val functionComplexityVisitor = FunctionComplexityVisitor()
val symbolVisitor = SymbolVisitor(DefaultTypeSolver(), globalScope)
val checksToRun = mutableListOf()
checksToRun.add(symbolVisitor)
if (inputFile.type() == PlSqlFile.Type.MAIN) {
checksToRun.addAll(
checks.filter { check -> formsMetadata != null || check !is FormsMetadataAwareCheck }
.filterIsInstance()
.filter { check -> ruleHasScope(check, RuleInfo.Scope.MAIN) }
.toList())
} else {
checksToRun.addAll(
checks.filterIsInstance()
.filter { check -> ruleHasScope(check, RuleInfo.Scope.TEST) }
.toList())
}
checksToRun.add(metricsVisitor)
checksToRun.add(complexityVisitor)
checksToRun.add(functionComplexityVisitor)
checksToRun.addAll(extraVisitors)
val issues = lock.withLock {
val newWalker = PlSqlAstWalker(checksToRun)
newWalker.walk(newVisitorContext)
checksToRun.flatMap {
(it as PlSqlCheck).issues().map { issue -> ZpaIssue(inputFile, it, issue) }
}
}
return AstScannerResult(
executedChecks = checksToRun,
linesWithNoSonar = metricsVisitor.linesWithNoSonar,
symbols = symbolVisitor.symbols,
numberOfStatements = metricsVisitor.numberOfStatements,
linesOfCode = metricsVisitor.getLinesOfCode().size,
linesOfComments = metricsVisitor.getLinesOfComments().size,
complexity = complexityVisitor.complexity,
numberOfFunctions = functionComplexityVisitor.numberOfFunctions,
executableLines = metricsVisitor.getExecutableLines(),
issues = issues
)
}
private fun ruleHasScope(check: PlSqlVisitor, scope: RuleInfo.Scope): Boolean {
val ruleInfo = getAnnotation(check, RuleInfo::class.java)
if (ruleInfo != null) {
return ruleInfo.scope === RuleInfo.Scope.ALL || ruleInfo.scope === scope
}
return scope === RuleInfo.Scope.MAIN
}
private fun getPlSqlVisitorContext(inputFile: PlSqlFile): PlSqlVisitorContext {
var visitorContext: PlSqlVisitorContext
try {
val root = parser.parse(inputFile.contents())
visitorContext = PlSqlVisitorContext(root, inputFile, formsMetadata)
} catch (e: RecognitionException) {
visitorContext = PlSqlVisitorContext(inputFile, e, formsMetadata)
LOG.error("Unable to parse file: $inputFile\n${e.message}")
} catch (e: Exception) {
checkInterrupted(e)
throw AnalysisException("Unable to analyze file: $inputFile", e)
} catch (e: Throwable) {
throw AnalysisException("Unable to analyze file: $inputFile", e)
}
return visitorContext
}
private fun checkInterrupted(e: Exception) {
val cause = getRootCause(e)
if (cause is InterruptedException || cause is InterruptedIOException) {
throw AnalysisException("Analysis cancelled", e)
}
}
private fun getRootCause(exception: Throwable?): Throwable? {
var rootException = exception
while (rootException?.cause != null) {
rootException = rootException.cause
}
return rootException
}
companion object {
private val LOG = Loggers.getLogger(AstScanner::class.java)
private val lock = ReentrantLock()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy