Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
Copyright (c) 2021 Kevin Jones, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
*/
package com.nawforce.apexlink.plugins
import com.nawforce.apexlink.cst._
import com.nawforce.apexlink.org.OPM
import com.nawforce.apexlink.plugins.UnusedPlugin._
import com.nawforce.apexlink.types.apex.{ApexFieldLike, ApexMethodLike, FullDeclaration}
import com.nawforce.apexlink.types.core.{
Dependent,
DependentType,
MethodDeclaration,
TypeDeclaration
}
import com.nawforce.pkgforce.diagnostics.{Diagnostic, DiagnosticCategory, Issue, UNUSED_CATEGORY}
import com.nawforce.pkgforce.modifiers._
import com.nawforce.pkgforce.parsers.{CLASS_NATURE, ENUM_NATURE, FIELD_NATURE, PROPERTY_NATURE}
import scala.collection.immutable.ArraySeq
import scala.collection.mutable
/** Provides plugin for generating unused warnings on a single type
* @param td type being handled by this plugin
*/
class UnusedPlugin(td: DependentType) extends Plugin(td) {
override def onClassValidated(td: ClassDeclaration): Seq[DependentType] = reportUnused(td)
override def onEnumValidated(td: EnumDeclaration): Seq[DependentType] = reportUnused(td)
override def onInterfaceValidated(td: InterfaceDeclaration): Seq[DependentType] = reportUnused(td)
private def reportUnused(td: FullDeclaration): Seq[DependentType] = {
if (td.outerTypeName.isEmpty) {
// Only update if we don't have errors, to reduce noise
val existingIssues = td.paths.flatMap(td.module.pkg.org.issues.issuesForFileInternal)
val hasErrors =
existingIssues.exists(issue => DiagnosticCategory.isErrorType(issue.diagnostic.category))
if (hasErrors) {
td.module.pkg.org.issues.replaceUnusedIssues(td.paths.head, Seq())
} else {
// This is a bit messy, we need to preserve unused locals are they are pre-computed
// via onBlockValidate. They need to be handled that way for local suppression to work.
val localUnused =
existingIssues.filter(_.diagnostic.message.startsWith("Unused local variable"))
td.module.pkg.org.issues.replaceUnusedIssues(td.paths.head, td.unusedIssues ++ localUnused)
}
// Return all our dependents so they are re-validated for unused as well
val dependents = mutable.Set[Dependent]()
td.collectDependencies(dependents)
dependents
.collect { case td: TypeDeclaration => td }
.map(_.outermostTypeDeclaration)
.collect { case dt: DependentType => dt }
.toSeq
} else {
Seq.empty
}
}
override def onBlockValidated(
block: Block,
isStatic: Boolean,
context: BlockVerifyContext
): Unit = {
context.declaredVars
.filter(localVar =>
!context.referencedVars.contains(localVar._1) && localVar._2.definition.nonEmpty
)
.foreach(localVar => {
val definition = localVar._2.definition.get
context.log(
new Issue(
definition.location.path,
Diagnostic(
UNUSED_CATEGORY,
definition.location.location,
s"Unused local variable '${localVar._1}'"
)
)
)
})
}
private implicit class DeclarationOps(td: FullDeclaration) {
/** Generates unused issues for a type, see doc/Unused.md for details.
*
* @return the issues
*/
def unusedIssues: ArraySeq[Issue] = {
// Hack: Unused calculation requires a methodMap as it establishes shadow relationships
td.methodMap
// Ignore page controllers, although we parse VF we don't establish use relationships yet
if (td.isPageController)
return ArraySeq.empty
// Ignore if suppressed
if (td.modifiers.exists(suppressModifiers.contains))
return ArraySeq.empty
// Get body declaration issues, we exclude initializers as they are really part of the type
val issues =
td.nestedTypes.flatMap(ad => ad.unusedIssues) ++
td.unusedFields ++
td.unusedMethods
// Bail early if we found nothing of interest, hopefully the common case
val childCount = td.nestedTypes.length + td.localFields.length + td.localMethods.length
if (issues.isEmpty && childCount > 0)
return ArraySeq.empty
// Check if need to promote the used to the type level to reduce noise in output
if (canPromoteUnusedToType(issues.length)) {
val onlyTestReferenced = td.hasHolders || (issues.nonEmpty && issues
.forall(_.diagnostic.message.contains(onlyTestCodeReferenceText)))
// Classes should likely be @isTest, but you can't use that on enum/interfaces
val suffix = new StringBuilder()
if (onlyTestReferenced) {
suffix.append(", only referenced by test code")
td.nature match {
case CLASS_NATURE =>
suffix.append(", consider using @isTest or @SuppressWarnings('Unused') if needed")
case _ => suffix.append(", consider using @SuppressWarnings('Unused') if needed")
}
}
ArraySeq(
new Issue(
td.location.path,
Diagnostic(
UNUSED_CATEGORY,
td.idLocation,
s"Unused ${td.nature.value} '${td.typeName}'$suffix"
)
)
)
} else {
issues
}
}
private def canPromoteUnusedToType(issueCount: Int): Boolean = {
// If the type has holders itself then we need to report on each body declaration
val hasHolders = if (td.inTest) td.hasHolders else td.hasNonTestHolders
if (hasHolders)
return false
// Don't promote for global as these are implicitly used
if (td.visibility == GLOBAL_MODIFIER)
return false
// Exclude reporting on empty outers, that is just a bit harsh
val childCount = td.nestedTypes.length + td.localFields.length +
(if (td.nature == ENUM_NATURE) 0 else td.localMethods.length)
if (td.outerTypeName.isEmpty && childCount == 0)
return false
// Finally if we got issues for each child say yes
childCount == issueCount
}
def unusedFields: ArraySeq[Issue] = {
td.localFields
.filterNot(_.isUsed(td.inTest))
.map(field => {
val nature = field.nature match {
case FIELD_NATURE => "field"
case PROPERTY_NATURE => "property"
case _ => "field or property"
}
val suffix = if (field.hasHolders) s", $onlyTestCodeReferenceText" else ""
new Issue(
field.location.path,
Diagnostic(UNUSED_CATEGORY, field.idLocation, s"Unused $nature '${field.name}'$suffix")
)
})
}
def unusedMethods: ArraySeq[Issue] = {
td.localMethods
.flatMap {
case am: ApexMethodLike if !am.isUsed(td.module, td.inTest) => Some(am)
case _ => None
}
.map(method => {
val suffix = if (method.hasHolders) s", $onlyTestCodeReferenceText" else ""
new Issue(
method.location.path,
Diagnostic(
UNUSED_CATEGORY,
method.idLocation,
s"Unused ${method.visibility.name} method '${method.signature}'$suffix"
)
)
})
}
}
private implicit class FieldOps(field: ApexFieldLike) {
def isUsed(inTest: Boolean): Boolean = {
if (inTest)
field.hasHolders || field.modifiers.exists(excludedTestFieldModifiers)
else
field.hasNonTestHolders || field.modifiers.exists(excludedFieldModifiers)
}
}
private implicit class MethodOps(method: ApexMethodLike) {
/** Is the method in use, NOTE: requires a MethodMap is constructed for shadow support first! */
def isUsed(module: OPM.Module, inTest: Boolean): Boolean = {
method.isSynthetic ||
(if (inTest)
method.hasHolders || method.modifiers.exists(excludedTestMethodModifiers.contains)
else
method.hasNonTestHolders || method.modifiers.exists(excludedMethodModifiers.contains)) ||
method.shadows.exists({
case am: ApexMethodLike => am.isUsed(module, inTest)
case _: MethodDeclaration => true
case _ => false
}) ||
method.parameters.exists(parameter => module.isGhostedType(parameter.typeName))
}
}
}
object UnusedPlugin {
val onlyTestCodeReferenceText =
"only referenced by test code, remove or make private @TestVisible"
val suppressModifiers: Set[Modifier] =
Set(SUPPRESS_WARNINGS_ANNOTATION_PMD, SUPPRESS_WARNINGS_ANNOTATION_UNUSED)
val excludedMethodModifiers: Set[Modifier] =
Set(
TEST_VISIBLE_ANNOTATION,
GLOBAL_MODIFIER,
AURA_ENABLED_ANNOTATION,
SUPPRESS_WARNINGS_ANNOTATION_PMD,
SUPPRESS_WARNINGS_ANNOTATION_UNUSED
)
val excludedTestMethodModifiers: Set[Modifier] =
Set(
ISTEST_ANNOTATION,
TEST_SETUP_ANNOTATION,
TEST_METHOD_MODIFIER,
SUPPRESS_WARNINGS_ANNOTATION_PMD,
SUPPRESS_WARNINGS_ANNOTATION_UNUSED
)
val excludedFieldModifiers: Set[Modifier] =
Set(
TEST_VISIBLE_ANNOTATION,
GLOBAL_MODIFIER,
AURA_ENABLED_ANNOTATION,
SUPPRESS_WARNINGS_ANNOTATION_PMD,
SUPPRESS_WARNINGS_ANNOTATION_UNUSED
)
val excludedTestFieldModifiers: Set[Modifier] =
Set(SUPPRESS_WARNINGS_ANNOTATION_PMD, SUPPRESS_WARNINGS_ANNOTATION_UNUSED)
}