Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inspection to check commit's adherence to the standard #21

Merged
merged 20 commits into from
Apr 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions .idea/dictionaries/Edoardo.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
}

group = "com.github.lppedd"
version = "0.9.0"
version = "0.10.0"

repositories {
maven("https://dl.bintray.com/kotlin/kotlin-eap")
Expand All @@ -16,6 +16,7 @@ repositories {
}

dependencies {
implementation("cglib:cglib-nodep:3.3.0")
implementation("org.json", "json", "20190722")
implementation("com.github.everit-org.json-schema", "org.everit.json.schema", "1.12.1")

Expand All @@ -37,6 +38,8 @@ tasks {
val kotlinSettings: KotlinCompile.() -> Unit = {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.freeCompilerArgs += listOf(
"-Xno-call-assertions",
"-Xno-receiver-assertions",
"-Xno-param-assertions",
"-Xjvm-default=enable",
"-Xallow-kotlin-package",
Expand Down
14 changes: 14 additions & 0 deletions change-notes/0_10_0.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<h3>0.10.0 (06/04/2020)</h3>
<p>
Fifteenth alpha release.<br/>
New features
</p>
<ul>
<li>
Provided a new <strong>inspection</strong> to highlight where the commit message
doesn't follow the Conventional Commit standard.<br>
The inspection is enabled by default so you can try it out, but you may disable
it via <em>Version Control > Commit > Commit message inspections</em>
</li>
<li>Provided a new experimental <strong>Extension Point</strong> to contribute with inspections: <code>commitInspectionProvider</code></li>
</ul>
2 changes: 1 addition & 1 deletion src/main/kotlin/com/github/lppedd/cc/CCExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ internal inline operator fun StringBuilder.plusAssign(string: String) {
// endregion
// region CharSequence

private val WHITESPACE_REGEX = "\\s+".toRegex()
internal val WHITESPACE_REGEX: Regex = "\\s+".toRegex()

@InlineOnly
internal inline fun CharSequence.flattenWhitespaces(): String =
Expand Down
7 changes: 7 additions & 0 deletions src/main/kotlin/com/github/lppedd/cc/CCSwing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ package com.github.lppedd.cc
import com.intellij.uiDesigner.core.GridConstraints
import com.intellij.uiDesigner.core.GridConstraints.*
import java.awt.Dimension
import javax.swing.BorderFactory
import javax.swing.border.Border
import kotlin.internal.InlineOnly

@InlineOnly
@Suppress("RedundantNotNullExtensionReceiverOfInline")
internal inline fun Border.concat(border: Border): Border =
BorderFactory.createCompoundBorder(this, border)

@InlineOnly
internal inline fun gridConstraints(
row: Int = 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.lppedd.cc.api

import com.github.lppedd.cc.inspection.CommitBaseInspection
import com.intellij.openapi.extensions.ExtensionPointName

internal val INSPECTION_EP = ExtensionPointName<CommitInspectionProvider>(
"com.github.lppedd.idea-conventional-commit.commitInspectionProvider"
)

/**
* @author Edoardo Luppi
*/
interface CommitInspectionProvider {
fun getInspections(): Collection<CommitBaseInspection>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.lppedd.cc.api

import com.github.lppedd.cc.inspection.CommitBaseInspection
import com.github.lppedd.cc.inspection.CommitFormatInspection

/**
* @author Edoardo Luppi
*/
private class DefaultInspectionProvider : CommitInspectionProvider {
override fun getInspections(): Collection<CommitBaseInspection> =
listOf(CommitFormatInspection())
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private class CommitCompletionContributor : CompletionContributor() {
}

fun fillResultSetWithScopes(context: ScopeCommitContext) {
val rs = resultSet.withPrefixMatcher(context.scope.trimStart())
val rs = resultSet.withPrefixMatcher(context.scope.trim())
safelyReleaseSemaphore(parameters.process)

SCOPE_EP.getExtensions(project)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal class CCConfigService : PersistentStateComponent<CCConfigService> {

var completionType: CompletionType = CompletionType.POPUP
var customFilePath: String? = null
var scopeReplaceChar: String = "-"

@XMap(
propertyElementName = "commitTypes",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.github.lppedd.cc.inspection

import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.editor.Document
import com.intellij.psi.PsiFile
import com.intellij.vcs.commit.message.BaseCommitMessageInspection

/**
* @author Edoardo Luppi
*/
abstract class CommitBaseInspection : BaseCommitMessageInspection() {
final override fun getGroupDisplayName(): String =
super.getGroupDisplayName()

final override fun getStaticDescription(): String? =
super.getStaticDescription()

final override fun checkFile(
file: PsiFile,
manager: InspectionManager,
isOnTheFly: Boolean,
): Array<ProblemDescriptor>? =
super.checkFile(file, manager, isOnTheFly)

abstract override fun checkFile(
file: PsiFile,
document: Document,
manager: InspectionManager,
isOnTheFly: Boolean,
): Array<ProblemDescriptor>

@Suppress("EXPOSED_SUPER_CLASS")
object ConventionalCommitReformatQuickFix : ReformatCommitMessageQuickFix()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.github.lppedd.cc.inspection

import com.intellij.codeInspection.LocalQuickFixBase
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.editor.Document
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement

/**
* @author Edoardo Luppi
*/
abstract class CommitBaseQuickFix(name: String) : LocalQuickFixBase(name) {
final override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
applyFix(project, getDocument(descriptor.psiElement) ?: return, descriptor)
}

@Suppress("INAPPLICABLE_JVM_NAME")
@get:JvmName("canReformat")
abstract val canReformat: Boolean
abstract fun applyFix(project: Project, document: Document, descriptor: ProblemDescriptor)

private fun getDocument(element: PsiElement): Document? =
PsiDocumentManager
.getInstance(element.project)
.getDocument(element.containingFile)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package com.github.lppedd.cc.inspection

import com.github.lppedd.cc.*
import com.github.lppedd.cc.configuration.CCConfigService
import com.github.lppedd.cc.inspection.quickfix.AddWsQuickFix
import com.github.lppedd.cc.inspection.quickfix.RemoveWsQuickFix
import com.github.lppedd.cc.inspection.quickfix.ReplaceWsQuickFix
import com.github.lppedd.cc.parser.CCParser
import com.github.lppedd.cc.parser.ValidToken
import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING
import com.intellij.openapi.editor.Document
import com.intellij.openapi.options.ConfigurableUi
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile

/**
* @author Edoardo Luppi
*/
internal class CommitFormatInspection : CommitBaseInspection() {
override fun getDisplayName(): String =
CCBundle["cc.inspection.nonStdMessage.description"]

override fun isEnabledByDefault(): Boolean =
true

override fun createOptionsConfigurable(): ConfigurableUi<Project> =
CommitFormatInspectionOptions()

override fun checkFile(
file: PsiFile,
document: Document,
manager: InspectionManager,
isOnTheFly: Boolean,
): Array<ProblemDescriptor> {
if (document.lineCount > 0) {
return checkHeader(file, document, manager).toTypedArray()
}

return emptyArray()
}

private fun checkHeader(
psiFile: PsiFile,
document: Document,
manager: InspectionManager,
): List<ProblemDescriptor> {
val firstLine = document.getLine(0)
val (type, scope, _, _, subject) = CCParser.parseHeader(firstLine)
val problems = mutableListOf<ProblemDescriptor>()

if (type is ValidToken) {
handleType(type, manager, psiFile, firstLine).let { problems += it }
}

if (scope is ValidToken) {
problems += handleScope(scope, manager, psiFile)
}

if (subject is ValidToken) {
handleSubject(subject, manager, psiFile)?.let { problems += it }
}

return problems
}

private fun handleType(
type: ValidToken,
manager: InspectionManager,
psiFile: PsiFile,
firstLine: CharSequence,
): List<ProblemDescriptor> {
val start = type.range.first

if (start == 0) {
return emptyList()
}

return WHITESPACE_REGEX.findAll(firstLine.take(start))
.map(MatchResult::range)
.map { TextRange(it.first, it.last + 1) }
.map {
val quickFixes = if (it.startOffset == 0) {
arrayOf(RemoveWsQuickFix(0), ConventionalCommitReformatQuickFix)
} else {
arrayOf(RemoveWsQuickFix(0, false))
}

manager.createProblemDescriptor(
psiFile,
it,
CCBundle["cc.inspection.nonStdMessage.text"],
GENERIC_ERROR_OR_WARNING,
true,
*quickFixes
)
}.toList()
}

private fun handleScope(
scope: ValidToken,
manager: InspectionManager,
psiFile: PsiFile,
): List<ProblemDescriptor> {
val (start, end) = scope.range
return WHITESPACE_REGEX.findAll(scope.value)
.map(MatchResult::range)
.map { TextRange(start + it.first, start + it.last + 1) }
.map {
val quickFix =
if (it.startOffset == start || it.endOffset == end) {
RemoveWsQuickFix(0)
} else {
val config = CCConfigService.getInstance(manager.project)
ReplaceWsQuickFix(config.scopeReplaceChar)
}

manager.createProblemDescriptor(
psiFile,
it,
CCBundle["cc.inspection.nonStdMessage.text"],
GENERIC_ERROR_OR_WARNING,
true,
quickFix,
ConventionalCommitReformatQuickFix
)
}.toList()
}

private fun handleSubject(
subject: ValidToken,
manager: InspectionManager,
psiFile: PsiFile,
): ProblemDescriptor? {
val value = subject.value
val (start, end) = subject.range
return when {
value.startsWith(" ") -> {
val nonWsIndex = value.indexOfFirst { !it.isWhitespace() }
val newEnd = if (nonWsIndex < 0) end else start + nonWsIndex
manager.createProblemDescriptor(
psiFile,
TextRange(start + 1, newEnd),
CCBundle["cc.inspection.nonStdMessage.text"],
GENERIC_ERROR_OR_WARNING,
true,
RemoveWsQuickFix(0),
ConventionalCommitReformatQuickFix
)
}
value.isNotEmpty() && !value.firstIsWhitespace() -> {
manager.createProblemDescriptor(
psiFile,
TextRange(start, start + 1),
CCBundle["cc.inspection.nonStdMessage.text"],
GENERIC_ERROR_OR_WARNING,
true,
AddWsQuickFix(1),
ConventionalCommitReformatQuickFix
)
}
else -> null
}
}

override fun canReformat(project: Project, document: Document): Boolean =
hasProblems(project, document)

override fun reformat(project: Project, document: Document) {
val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document) ?: return
val problemsToQuickFixes =
checkFile(psiFile, document, InspectionManager.getInstance(project), false)
.map {
it to it.fixes
?.filterIsInstance<CommitBaseQuickFix>()
?.filter(CommitBaseQuickFix::canReformat)
}.asReversed()

for ((problemDescriptor, quickFixes) in problemsToQuickFixes) {
quickFixes?.asReversed()?.forEach {
it.applyFix(project, document, problemDescriptor)
}
}
}
}
Loading