Skip to content

Commit

Permalink
Enhance AW support too
Browse files Browse the repository at this point in the history
Pretty much identical to what was done to AT support
  • Loading branch information
RedNesto committed Dec 16, 2024
1 parent 367aa7a commit 1c7e871
Show file tree
Hide file tree
Showing 41 changed files with 1,953 additions and 178 deletions.
24 changes: 12 additions & 12 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

## [Unreleased]

### Changed

- Overhauled Access Transformer and Access Widener support:
- many lexing errors should now be fixed
- class names and member names now have their own references, replacing the custom Goto handler
- SRG names are no longer used on NeoForge 1.20.2+ and a new copy action is available for it
- the usage inspection no longer incorrectly reports methods overridden in your code or entries covering super methods
- suppressing inspections is now possible by adding `# Suppress:AtInspectionName` after an entry or at the start of the file, or using the built-in suppress action
- added an inspection to report unresolved references, to help find out old, superfluous entries
- added an inspection to report duplicate entries in the same file
- added formatting support, class and member names are configured to align by default

### Added

- [#2391](https://github.com/minecraft-dev/MinecraftDev/issues/2391) Project creator template repo and maven repo authorization
Expand Down Expand Up @@ -49,18 +61,6 @@
- [#1813](https://github.com/minecraft-dev/MinecraftDev/issues/1813) Single character Accessor targets aren't inferred correctly
- [#1886](https://github.com/minecraft-dev/MinecraftDev/issues/1886) Sync error in ForgeGradle composite builds

### Changed

- Overhauled Access Transformer support:
- many lexing errors should now be fixed
- class names and member names now have their own references, replacing the custom Goto handler
- SRG names are no longer used on NeoForge 1.20.2+ and a new copy action is available for it
- the usage inspection no longer incorrectly reports methods overridden in your code or entries covering super methods
- suppressing inspections is now possible by adding `# Suppress:AtInspectionName` after an entry or at the start of the file, or using the built-in suppress action
- added an inspection to report unresolved references, to help find out old, superfluous entries
- added an inspection to report duplicate entries in the same file
- added formatting support, class and member names are configured to align by default

## [1.8.1] - 2024-08-10

### Added
Expand Down
7 changes: 5 additions & 2 deletions src/main/grammars/AwLexer.flex
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,23 @@ CLASS_ELEMENT=class
METHOD_ELEMENT=method
FIELD_ELEMENT=field
NAME_ELEMENT=\w+|<init>
CLASS_NAME_ELEMENT=(\w+\/)*\w+(\$\w+)*
CLASS_NAME_ELEMENT=[\w/$]+
COMMENT=#.*
CRLF=\n|\r|\r\n
WHITE_SPACE=\s

%%

{COMMENT} { return COMMENT; }

<YYINITIAL> {
{HEADER_NAME} { yybegin(HEADER); return HEADER_NAME; }
{ACCESS_ELEMENT} { return ACCESS_ELEMENT; }
{CLASS_ELEMENT} { yybegin(CLASS_NAME); return CLASS_ELEMENT; }
{METHOD_ELEMENT} { yybegin(CLASS_NAME); return METHOD_ELEMENT; }
{FIELD_ELEMENT} { yybegin(CLASS_NAME); return FIELD_ELEMENT; }
// Fallback to avoid breaking code highlighting at the access or target kind while editing
\S+ { return NAME_ELEMENT; }
}

<HEADER> {
Expand All @@ -94,5 +98,4 @@ WHITE_SPACE=\s
{CRLF} { yybegin(YYINITIAL); return CRLF; }
{WHITE_SPACE} { return WHITE_SPACE; }

{COMMENT} { return COMMENT; }
[^] { return BAD_CHARACTER; }
48 changes: 23 additions & 25 deletions src/main/grammars/AwParser.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -32,56 +32,52 @@
elementTypeClass="com.demonwav.mcdev.platform.mcp.aw.psi.AwElementType"
tokenTypeClass="com.demonwav.mcdev.platform.mcp.aw.psi.AwTokenType"

consumeTokenMethod="consumeTokenFast"
consumeTokenMethod(".*_recover")="consumeTokenFast"
}

aw_file ::= header_line line*
aw_file ::= header_line? line*

private header_line ::= !<<eof>> header COMMENT? end_line

private line ::= !<<eof>> entry? COMMENT? end_line
private end_line ::= crlf | <<eof>>
private line ::= !<<eof>> line_content end_line
private line_recover ::= !(end_line | COMMENT)
private end_line ::= CRLF | <<eof>>

private line_content ::= entry? COMMENT? {
recoverWhile=line_recover
}

header ::= HEADER_NAME HEADER_VERSION_ELEMENT HEADER_NAMESPACE_ELEMENT {
mixin="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.impl.AwHeaderImplMixin"
implements="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.AwHeaderMixin"
}

private entry ::= class_entry | method_entry | field_entry {
entry ::= class_entry | method_entry | field_entry {
mixin="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.impl.AwEntryImplMixin"
implements="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.AwEntryMixin"
recoverWhile = line_recover
}

class_entry ::= access class_literal class_name {
class_entry ::= ACCESS_ELEMENT CLASS_ELEMENT class_name {
extends=entry
mixin="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.impl.AwClassEntryImplMixin"
implements="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.AwClassEntryMixin"
pin=2
}

method_entry ::= access method_literal class_name member_name method_desc{
method_entry ::= ACCESS_ELEMENT METHOD_ELEMENT class_name member_name method_desc {
extends=entry
mixin="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.impl.AwMethodEntryImplMixin"
implements="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.AwMethodEntryMixin"
pin=2
}

field_entry ::= access field_literal class_name member_name field_desc{
field_entry ::= ACCESS_ELEMENT FIELD_ELEMENT class_name member_name field_desc {
extends=entry
mixin="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.impl.AwFieldEntryImplMixin"
implements="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.AwFieldEntryMixin"
pin=2
}

private line_recover ::= !(end_line | COMMENT)

access ::= ACCESS_ELEMENT {
methods=[
accessElement="ACCESS_ELEMENT"
]
}

class_literal ::= CLASS_ELEMENT

method_literal ::= METHOD_ELEMENT

field_literal ::= FIELD_ELEMENT

class_name ::= CLASS_NAME_ELEMENT {
mixin="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.impl.AwClassNameImplMixin"
implements="com.demonwav.mcdev.platform.mcp.aw.psi.mixins.AwClassNameMixin"
Expand All @@ -98,7 +94,9 @@ member_name ::= NAME_ELEMENT {
]
}

method_desc ::= OPEN_PAREN desc_element* CLOSE_PAREN desc_element
method_desc ::= OPEN_PAREN desc_element* CLOSE_PAREN desc_element {
pin=1
}

field_desc ::= desc_element

Expand All @@ -109,4 +107,4 @@ desc_element ::= PRIMITIVE | CLASS_VALUE {
primitive="PRIMITIVE"
classValue="CLASS_VALUE"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,46 @@
package com.demonwav.mcdev.platform.fabric.reference

import com.demonwav.mcdev.platform.fabric.util.FabricConstants
import com.demonwav.mcdev.platform.mcp.aw.AwFileType
import com.demonwav.mcdev.platform.mcp.fabricloom.FabricLoomData
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.ResolveScopeEnlarger
import com.intellij.psi.search.SearchScope
import org.jetbrains.plugins.gradle.util.GradleUtil

class FabricModJsonResolveScopeEnlarger : ResolveScopeEnlarger() {

override fun getAdditionalResolveScope(file: VirtualFile, project: Project): SearchScope? {
if (file.name != FabricConstants.FABRIC_MOD_JSON) {
return null
if (file.name == FabricConstants.FABRIC_MOD_JSON) {
val module = ModuleUtilCore.findModuleForFile(file, project)
?: return null
return module.moduleWithDependentsScope.union(module.moduleTestsWithDependentsScope)
}

val module = ModuleUtilCore.findModuleForFile(file, project)
?: return null
return module.moduleWithDependentsScope.union(module.moduleTestsWithDependentsScope)
if (file.fileType is AwFileType) {
var module = ModuleUtilCore.findModuleForFile(file, project)
?: return null

val loomData = GradleUtil.findGradleModuleData(module)?.children
?.find { it.key == FabricLoomData.KEY }?.data as? FabricLoomData
?: return null

var moduleManager = ModuleManager.getInstance(project)
var baseModuleName = module.name.substringBeforeLast('.')
var scope = module.moduleWithLibrariesScope
for ((_, sourceSets) in loomData.modSourceSets.orEmpty()) {
for (name in sourceSets) {
val otherModule = moduleManager.findModuleByName("$baseModuleName.$name") ?: continue
scope = scope.union(otherModule.moduleWithLibrariesScope)
}
}

return scope
}

return null
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/platform/mcp/at/AtFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class AtFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, AtLangu
}

fun addHeadComment(text: String) {
val toAdd = text.lines().flatMap { listOf(AtElementFactory.createComment(project, it)) }
val toAdd = text.lines().map { AtElementFactory.createComment(project, it) }
val lastHeadComment = headComments.lastOrNull()
if (lastHeadComment == null) {
for (comment in toAdd.reversed()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import com.intellij.psi.PsiElementVisitor

class AtDuplicateEntryInspection : LocalInspectionTool() {

override fun getStaticDescription(): String? = "Reports duplicate AT entries in the same file"
override fun runForWholeFile(): Boolean = true

override fun getStaticDescription(): String = "Reports duplicate AT entries in the same file"

override fun buildVisitor(
holder: ProblemsHolder,
Expand Down
116 changes: 65 additions & 51 deletions src/main/kotlin/platform/mcp/at/inspections/AtUsageInspection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ package com.demonwav.mcdev.platform.mcp.at.inspections

import com.demonwav.mcdev.platform.mcp.at.AtFileType
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtEntry
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtVisitor
import com.demonwav.mcdev.util.excludeFileTypes
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.fileTypes.FileType
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiReference
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.searches.OverridingMethodsSearch
import com.intellij.psi.search.searches.ReferencesSearch
Expand All @@ -40,81 +45,90 @@ class AtUsageInspection : LocalInspectionTool() {
}

override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
if (element !is AtEntry) {
return
}
return object : AtVisitor() {

private val fixProvider = { it: AtEntry -> RemoveAtEntryFix.forWholeLine(it, true) }

val function = element.function
override fun visitEntry(entry: AtEntry) {
val function = entry.function
if (function != null) {
checkElement(element, function)
checkElement(entry, function, holder, AtFileType, fixProvider) { file, toSkip ->
file.children.asSequence()
.filterIsInstance<AtEntry>()
.filter { it != toSkip }
.mapNotNull { it.function?.reference }
}
return
}

val fieldName = element.fieldName
val fieldName = entry.fieldName
if (fieldName != null) {
checkElement(element, fieldName)
checkElement(entry, fieldName, holder, AtFileType, fixProvider)
return
}

// Only check class names if it is the target of the entry
checkElement(element, element.className)
checkElement(entry, entry.className, holder, AtFileType, fixProvider)
}
}
}

companion object {

@JvmStatic
fun <E: PsiElement> checkElement(
entry: E,
element: PsiElement,
holder: ProblemsHolder,
fileType: FileType,
fixProvider: (entry: E) -> LocalQuickFix,
entriesReferenceProvider: (PsiFile, toSkip: E) -> Sequence<PsiReference> = { _, _ -> emptySequence() }
) {
val referenced = element.reference?.resolve() ?: return
val scope = GlobalSearchScope.projectScope(element.project)
.excludeFileTypes(element.project, fileType)
val query = ReferencesSearch.search(referenced, scope, true)
if (query.any()) {
return
}

private fun checkElement(entry: AtEntry, element: PsiElement) {
val referenced = element.reference?.resolve() ?: return
val scope = GlobalSearchScope.projectScope(element.project)
.excludeFileTypes(element.project, AtFileType)
val query = ReferencesSearch.search(referenced, scope, true)
if (query.any()) {
if (referenced is PsiMethod) {
// The regular references search doesn't cover overridden methods
val overridingQuery = OverridingMethodsSearch.search(referenced, scope, true)
if (overridingQuery.any()) {
return
}

if (referenced is PsiMethod) {
// The regular references search doesn't cover overridden methods
val overridingQuery = OverridingMethodsSearch.search(referenced, scope, true)
if (overridingQuery.any()) {
// Also ignore if other entries cover super methods
val superMethods = referenced.findSuperMethods()
for (reference in entriesReferenceProvider(entry.containingFile, entry)) {
val otherResolved = reference.resolve()
if (superMethods.contains(otherResolved)) {
return
}

// Also ignore if other entries cover super methods
val superMethods = referenced.findSuperMethods()
for (childEntry in entry.containingFile.children) {
if (childEntry !is AtEntry || childEntry == entry) {
continue
}

val function = childEntry.function ?: continue
val otherResolved = function.reference?.resolve()
if (superMethods.contains(otherResolved)) {
return
}
}
}
}

if (referenced is PsiClass) {
// Do not report classes whose members are used in the mod
for (field in referenced.fields) {
if (ReferencesSearch.search(field, scope, true).any()) {
return
}
if (referenced is PsiClass) {
// Do not report classes whose members are used in the mod
for (field in referenced.fields) {
if (ReferencesSearch.search(field, scope, true).any()) {
return
}
for (method in referenced.methods) {
if (ReferencesSearch.search(method, scope, true).any()) {
return
}
}
for (method in referenced.methods) {
if (ReferencesSearch.search(method, scope, true).any()) {
return
}
for (innerClass in referenced.innerClasses) {
if (ReferencesSearch.search(innerClass, scope, true).any()) {
return
}
}
for (innerClass in referenced.innerClasses) {
if (ReferencesSearch.search(innerClass, scope, true).any()) {
return
}
}

val fix = RemoveAtEntryFix.forWholeLine(entry, true)
holder.registerProblem(entry, "Access Transformer entry is never used", fix)
}

holder.registerProblem(entry, "Entry is never used", fixProvider(entry))
}
}
}
Loading

0 comments on commit 1c7e871

Please sign in to comment.