IDEA-301191: [DevKit] Inspection to discourage declaration of TokenSets in ParserDefinition

GitOrigin-RevId: cab7298c1babe62754272d529bd4e471cf3b17c6
This commit is contained in:
Karol Lewandowski
2023-03-24 16:49:28 +00:00
committed by intellij-monorepo-bot
parent 29192c80ed
commit 7e7ccd372d
39 changed files with 855 additions and 0 deletions
@@ -0,0 +1,85 @@
<html>
<body>
Reports <code>TokenSet</code> field declarations referencing non-core element types in <code>ParserDefinition</code> classes.
<p>
All languages <code>ParserDefinition</code> are created on the application startup.
Declaring a <code>TokenSet</code> referencing non-core language element types may cause creating and registering
all the language element types in the holder class of the referenced type, even if a project doesn't contain any files in this language.
</p>
<p>Example:</p>
<pre>
// element types holder:
public interface MyLangTokenTypes {
IElementType COMMENT = new MyLangTokenType("COMMENT");
IElementType TYPE1 = new MyLangTokenType("TYPE1");
IElementType TYPE2 = new MyLangTokenType("TYPE2");
// more types...
}
// bad:
public class MyLangParserDefinition implements ParserDefinition {
// this field causes initalizing and registering all the types from MyLangTokenTypes:
private static final TokenSet COMMENTS = TokenSet.create(MyLangTokenTypes.COMMENT);
@NotNull
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
...
}
// good:
public final class MyLangTokenSets {
public static final TokenSet COMMENTS = TokenSet.create(MyLangTokenTypes.COMMENT);
}
public class MyLangParserDefinition implements ParserDefinition {
@NotNull
@Override
public TokenSet getCommentTokens() {
// types are referenced and registered only when this method is called:
return MyLangTokenSets.COMMENTS;
}
...
}
// good (Kotlin):
// top-level declaration is not loaded until getCommentTokens() method is called:
private val COMMENTS = TokenSet.create(MyLangTokenTypes.COMMENT);
class MyLangParserDefinition : ParserDefinition {
override getCommentTokens(): TokenSet {
return COMMENTS;
}
...
}
// good:
public class MyLangParserDefinition implements ParserDefinition {
// allowed core TokenSet:
private static final TokenSet COMMENTS1 = TokenSet.EMPTY;
// allowed core TokenType:
private static final TokenSet COMMENTS2 = TokenSet.create(TokenType.WHITE_SPACE);
@NotNull
@Override
public TokenSet getCommentTokens() {
...
}
...
}
</pre>
<!-- tooltip end -->
<p><small>New in 2023.2</small>
</body>
</html>
@@ -315,6 +315,11 @@
groupPathKey="inspections.group.path" groupKey="inspections.group.code"
implementationClass="org.jetbrains.idea.devkit.inspections.ThreadingConcurrencyInspection"/>
<localInspection language="UAST" enabledByDefault="false" level="WARNING"
key="inspection.token.set.in.parser.definition.display.name"
groupPathKey="inspections.group.path" groupKey="inspections.group.code"
implementationClass="org.jetbrains.idea.devkit.inspections.TokenSetInParserDefinitionInspection"/>
<moduleConfigurationEditorProvider implementation="org.jetbrains.idea.devkit.module.PluginModuleEditorsProvider"/>
<implicitUsageProvider implementation="org.jetbrains.idea.devkit.inspections.DevKitImplicitUsageProvider"/>
@@ -651,3 +651,6 @@ inspection.threading.concurrency.option.group.inside.requires.edt=Check inside @
inspection.threading.concurrency.option.group.inside.requires.edt.check.requires.read.lock=@RequiresReadLock
inspection.threading.concurrency.option.group.inside.requires.edt.check.requires.write.lock=@RequiresWriteLock
inspection.threading.concurrency.option.check.missing.annotations.methods=Check missing annotations for public methods
inspection.token.set.in.parser.definition.display.name=Non-core TokenSet declared in ParserDefinition
inspection.token.set.in.parser.definition=TokenSet in ParserDefinition references non-core classes
@@ -0,0 +1,119 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections
import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.lang.ParserDefinition
import com.intellij.psi.PsiClassType
import com.intellij.psi.TokenType
import com.intellij.psi.tree.TokenSet
import com.intellij.psi.util.InheritanceUtil
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.uast.*
import org.jetbrains.uast.visitor.AbstractUastVisitor
internal class TokenSetInParserDefinitionInspection : DevKitUastInspectionBase(UClass::class.java) {
override fun checkClass(aClass: UClass, manager: InspectionManager, isOnTheFly: Boolean): Array<ProblemDescriptor> {
if (!InheritanceUtil.isInheritor(aClass.javaPsi, ParserDefinition::class.java.name)) return ProblemDescriptor.EMPTY_ARRAY
val problemsHolder = createProblemsHolder(aClass, manager, isOnTheFly)
aClass.fields
.filter { it.isTokenSetField() && it.isIllegal(aClass) }
.forEach { reportField(it, problemsHolder) }
return problemsHolder.resultsArray
}
private fun UField.isTokenSetField(): Boolean {
val fieldType = (this.type as? PsiClassType)?.resolve() ?: return false
return fieldType.qualifiedName == TokenSet::class.java.name
}
private fun UField.isIllegal(aClass: UClass): Boolean {
val initializer = this.uastInitializer
if (initializer != null) {
return initializer.containsIllegalReferences()
}
else {
val constructors = aClass.methods.filter { it.isConstructor }
return constructors.any { it.containsFieldAssignmentWithIllegalReferences(this) } ||
aClass.initializers.any { it.containsFieldAssignmentWithIllegalReferences(this) } ||
companionObjectInitBlockContainsIllegalUsage(aClass)
}
}
private fun UExpression.containsIllegalReferences(): Boolean {
if (this is UResolvable) {
val resolved = this.resolveToUElement() ?: return false
when (resolved) {
is UField -> {
// TokenSet.EMPTY, TokenSet.ANY, etc. are allowed to use
return resolved.getContainingUClass()?.qualifiedName != TokenSet::class.java.name
}
is UMethod -> {
// check if assignment contains any non-core class usage
val nonCoreApiFinder = NonCoreApiFinder()
this.accept(nonCoreApiFinder)
return nonCoreApiFinder.nonCoreApiUsed
}
}
}
return false
}
private fun reportField(field: UField, problemsHolder: ProblemsHolder) {
val anchorPsi = field.getAnchorPsi() ?: return
problemsHolder.registerProblem(anchorPsi, DevKitBundle.message("inspection.token.set.in.parser.definition"))
}
private fun UDeclaration.containsFieldAssignmentWithIllegalReferences(field: UField): Boolean {
var containsIllegalAssignment = false
this.accept(object : AbstractUastVisitor() {
private val checkChildrenFlag = false
private val skipChildrenFlag = true
override fun visitBinaryExpression(node: UBinaryExpression): Boolean {
if (containsIllegalAssignment) return skipChildrenFlag
val resolvedElement = (node.leftOperand as? UReferenceExpression)?.resolveToUElement() ?: return checkChildrenFlag
if (resolvedElement.sourcePsi?.isEquivalentTo(field.sourcePsi) == true && node.rightOperand.containsIllegalReferences()) {
containsIllegalAssignment = true
return skipChildrenFlag
}
return checkChildrenFlag
}
})
return containsIllegalAssignment
}
private fun UField.companionObjectInitBlockContainsIllegalUsage(aClass: UClass): Boolean {
val companionInitBlock = aClass.innerClasses.firstOrNull { it.javaPsi.name == "Companion" }
?.methods?.firstOrNull { it.javaPsi.name == "Companion" }
return companionInitBlock?.containsFieldAssignmentWithIllegalReferences(this) == true
}
private class NonCoreApiFinder : AbstractUastVisitor() {
private val checkChildrenFlag = false
private val skipChildrenFlag = true
var nonCoreApiUsed = false
override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression): Boolean {
return node.checkNonCoreApiUsage()
}
override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression): Boolean {
return node.checkNonCoreApiUsage()
}
private fun UReferenceExpression.checkNonCoreApiUsage(): Boolean {
if (nonCoreApiUsed) return skipChildrenFlag
val resolvedElementContainingClass = this.resolveToUElement()?.getContainingUClass()?.qualifiedName ?: return checkChildrenFlag
if (resolvedElementContainingClass != TokenSet::class.java.name &&
resolvedElementContainingClass != TokenType::class.java.name) {
nonCoreApiUsed = true
return skipChildrenFlag
}
return checkChildrenFlag
}
}
}
@@ -0,0 +1,7 @@
package com.example;
import com.intellij.psi.tree.IElementType;
public final class MyLangTokenTypes {
public static final IElementType COMMENT = null;
}
@@ -0,0 +1,16 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
import com.example.MyLangTokenTypes;
public class ParserDefinitionNonDirectImplementorWithIllegalTokenSet extends CustomParserDefinition {
public static final TokenSet <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = TokenSet.create(MyLangTokenTypes.COMMENT);
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
abstract class CustomParserDefinition implements ParserDefinition {
}
@@ -0,0 +1,12 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
import com.example.MyLangTokenTypes;
public class ParserDefinitionWithIllegalTokenSet implements ParserDefinition {
public static final TokenSet <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = TokenSet.create(MyLangTokenTypes.COMMENT);
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
@@ -0,0 +1,16 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
import com.example.MyLangTokenTypes;
public class ParserDefinitionWithIllegalTokenSetInitializedInConstructor implements ParserDefinition {
public final TokenSet <warning descr="TokenSet in ParserDefinition references non-core classes">comments</warning>;
public ParserDefinitionWithIllegalTokenSetInitializedInConstructor() {
comments = TokenSet.create(MyLangTokenTypes.COMMENT);
}
@Override
public TokenSet getCommentTokens() {
return comments;
}
}
@@ -0,0 +1,16 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
import com.example.MyLangTokenTypes;
public class ParserDefinitionWithIllegalTokenSetInitializedInStaticBlock implements ParserDefinition {
public static final TokenSet <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning>;
static {
COMMENTS = TokenSet.create(MyLangTokenTypes.COMMENT);
}
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
@@ -0,0 +1,12 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
import com.example.MyLangTokenTypes;
public class ParserDefinitionWithIllegalTokenSetWhenComplexCreation implements ParserDefinition {
public static final TokenSet <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = TokenSet.orSet(TokenSet.create(MyLangTokenTypes.COMMENT), TokenSet.create(MyLangTokenTypes.COMMENT));
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
@@ -0,0 +1,14 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
import static com.example.MyLangTokenTypes.COMMENT;
import static com.intellij.psi.tree.TokenSet.create;
public class ParserDefinitionWithIllegalTokenSetWhenStaticImportsUsed implements ParserDefinition {
public static final TokenSet <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = create(COMMENT);
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
@@ -0,0 +1,10 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
public class ParserDefinitionWithLegalCoreTokenSet implements ParserDefinition {
public static final TokenSet COMMENTS = TokenSet.EMPTY;
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
@@ -0,0 +1,15 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
public class ParserDefinitionWithLegalCoreTokenSetInitializedInConstructor implements ParserDefinition {
public final TokenSet comments;
public ParserDefinitionWithLegalCoreTokenSetInitializedInConstructor() {
comments = TokenSet.EMPTY;
}
@Override
public TokenSet getCommentTokens() {
return comments;
}
}
@@ -0,0 +1,15 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
public class ParserDefinitionWithLegalCoreTokenSetInitializedInStaticBlock implements ParserDefinition {
public static final TokenSet COMMENTS;
static {
COMMENTS = TokenSet.EMPTY;
}
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
@@ -0,0 +1,11 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.TokenType;
import com.intellij.psi.tree.TokenSet;
public class ParserDefinitionWithLegalCoreTokenType implements ParserDefinition {
public static final TokenSet COMMENTS = TokenSet.create(TokenType.WHITE_SPACE);
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
@@ -0,0 +1,16 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.TokenType;
import com.intellij.psi.tree.TokenSet;
public class ParserDefinitionWithLegalCoreTokenTypeInitializedInConstructor implements ParserDefinition {
public final TokenSet comments;
public ParserDefinitionWithLegalCoreTokenTypeInitializedInConstructor() {
comments = TokenSet.create(TokenType.WHITE_SPACE);
}
@Override
public TokenSet getCommentTokens() {
return comments;
}
}
@@ -0,0 +1,16 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.TokenType;
import com.intellij.psi.tree.TokenSet;
public class ParserDefinitionWithLegalCoreTokenTypeInitializedInStaticBlock implements ParserDefinition {
public static final TokenSet COMMENTS;
static {
COMMENTS = TokenSet.create(TokenType.WHITE_SPACE);
}
@Override
public TokenSet getCommentTokens() {
return COMMENTS;
}
}
@@ -0,0 +1,14 @@
import com.intellij.lang.ParserDefinition;
import com.intellij.psi.tree.TokenSet;
import com.example.MyLangTokenTypes;
public class ParserDefinitionWithLegalTokenSetInitializedLazilyInMethod implements ParserDefinition {
private TokenSet comments;
@Override
public TokenSet getCommentTokens() {
if (comments == null) {
comments = TokenSet.create(MyLangTokenTypes.COMMENT);
}
return comments;
}
}
@@ -0,0 +1,69 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.DevkitJavaTestsUtil
@TestDataPath("\$CONTENT_ROOT/testData/inspections/tokenSetInParserDefinition")
class TokenSetInParserDefinitionInspectionTest : TokenSetInParserDefinitionInspectionTestBase() {
override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/tokenSetInParserDefinition"
override fun getFileExtension() = "java"
fun testParserDefinitionWithIllegalTokenSet() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetWhenComplexCreation() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetWhenStaticImportsUsed() {
doInspectionTest()
}
fun testParserDefinitionNonDirectImplementorWithIllegalTokenSet() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetInitializedInConstructor() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetInitializedInStaticBlock() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenSet() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenSetInitializedInConstructor() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenSetInitializedInStaticBlock() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenType() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenTypeInitializedInConstructor() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenTypeInitializedInStaticBlock() {
doInspectionTest()
}
fun testParserDefinitionWithLegalTokenSetInitializedLazilyInMethod() {
doInspectionTest()
}
private fun doInspectionTest() {
myFixture.copyFileToProject("MyLangTokenTypes.java")
doTest()
}
}
@@ -0,0 +1,7 @@
package com.example
import com.intellij.psi.tree.IElementType
object MyLangTokenTypes {
val COMMENT: IElementType? = null
}
@@ -0,0 +1,12 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
class ParserDefinitionNonDirectImplementorWithIllegalTokenSet : CustomParserDefinition() {
val <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = TokenSet.create(MyLangTokenTypes.COMMENT)
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
}
abstract class CustomParserDefinition : ParserDefinition
@@ -0,0 +1,10 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
class ParserDefinitionWithIllegalTokenSet : ParserDefinition {
val <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = TokenSet.create(MyLangTokenTypes.COMMENT)
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
}
@@ -0,0 +1,12 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
class ParserDefinitionWithIllegalTokenSetInCompanionObject : ParserDefinition {
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
companion object {
val <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = TokenSet.create(MyLangTokenTypes.COMMENT)
}
}
@@ -0,0 +1,17 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
class ParserDefinitionWithIllegalTokenSetInitializedInCompanionObjectInitBlock : ParserDefinition {
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
companion object {
val <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning>: TokenSet
init {
COMMENTS = TokenSet.create(MyLangTokenTypes.COMMENT)
}
}
}
@@ -0,0 +1,14 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
class ParserDefinitionWithIllegalTokenSetInitializedInConstructor : ParserDefinition {
val <warning descr="TokenSet in ParserDefinition references non-core classes">comments</warning>: TokenSet
constructor() {
comments = TokenSet.create(MyLangTokenTypes.COMMENT)
}
override fun getCommentTokens(): TokenSet {
return comments
}
}
@@ -0,0 +1,15 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
class ParserDefinitionWithIllegalTokenSetInitializedInInitBlock : ParserDefinition {
val <warning descr="TokenSet in ParserDefinition references non-core classes">comments</warning>: TokenSet
init {
comments = TokenSet.create(MyLangTokenTypes.COMMENT)
}
override fun getCommentTokens(): TokenSet {
return comments
}
}
@@ -0,0 +1,10 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
class ParserDefinitionWithIllegalTokenSetWhenComplexCreation : ParserDefinition {
val <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = TokenSet.orSet(TokenSet.create(MyLangTokenTypes.COMMENT), TokenSet.create(MyLangTokenTypes.COMMENT))
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
}
@@ -0,0 +1,11 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.intellij.psi.tree.TokenSet.create
import com.example.MyLangTokenTypes.COMMENT
class ParserDefinitionWithIllegalTokenSetWhenMembersImported : ParserDefinition {
val <warning descr="TokenSet in ParserDefinition references non-core classes">COMMENTS</warning> = create(COMMENT)
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
}
@@ -0,0 +1,9 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
class ParserDefinitionWithLegalCoreTokenSet : ParserDefinition {
val comments = TokenSet.EMPTY
override fun getCommentTokens(): TokenSet {
return comments
}
}
@@ -0,0 +1,11 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
class ParserDefinitionWithLegalCoreTokenSetInCompanionObject : ParserDefinition {
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
companion object {
val COMMENTS = TokenSet.EMPTY
}
}
@@ -0,0 +1,16 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
class ParserDefinitionWithLegalCoreTokenSetInitializedInCompanionObjectInitBlock : ParserDefinition {
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
companion object {
val COMMENTS: TokenSet
init {
COMMENTS = TokenSet.EMPTY
}
}
}
@@ -0,0 +1,14 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
class ParserDefinitionWithLegalCoreTokenSetInitializedInConstructor : ParserDefinition {
val comments: TokenSet
init {
comments = TokenSet.EMPTY
}
override fun getCommentTokens(): TokenSet {
return comments
}
}
@@ -0,0 +1,13 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.TokenType
import com.intellij.psi.tree.TokenSet
class ParserDefinitionWithLegalCoreTokenType : ParserDefinition {
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
companion object {
val COMMENTS = TokenSet.create(TokenType.WHITE_SPACE)
}
}
@@ -0,0 +1,15 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.TokenType
import com.intellij.psi.tree.TokenSet
class ParserDefinitionWithLegalCoreTokenTypeInitializedInConstructor : ParserDefinition {
val comments: TokenSet
init {
comments = TokenSet.create(TokenType.WHITE_SPACE)
}
override fun getCommentTokens(): TokenSet {
return comments
}
}
@@ -0,0 +1,17 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.TokenType
import com.intellij.psi.tree.TokenSet
class ParserDefinitionWithLegalCoreTokenTypeInitializedInStaticBlock : ParserDefinition {
override fun getCommentTokens(): TokenSet {
return COMMENTS
}
companion object {
val COMMENTS: TokenSet
init {
COMMENTS = TokenSet.create(TokenType.WHITE_SPACE)
}
}
}
@@ -0,0 +1,11 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
val comments: TokenSet = TokenSet.create(MyLangTokenTypes.COMMENT)
class ParserDefinitionWithLegalTokenSetDeclaredOnTopLevel : ParserDefinition {
override fun getCommentTokens(): TokenSet {
return comments
}
}
@@ -0,0 +1,13 @@
import com.intellij.lang.ParserDefinition
import com.intellij.psi.tree.TokenSet
import com.example.MyLangTokenTypes
class ParserDefinitionWithLegalTokenSetInitializedLazilyInMethod : ParserDefinition {
private var comments: TokenSet? = null
override fun getCommentTokens(): TokenSet {
if (comments == null) {
comments = TokenSet.create(MyLangTokenTypes.COMMENT)
}
return comments!!
}
}
@@ -0,0 +1,86 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.kotlin.inspections
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.inspections.TokenSetInParserDefinitionInspectionTestBase
import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil
@TestDataPath("/inspections/tokenSetInParserDefinition")
class KtTokenSetInParserDefinitionInspectionTest : TokenSetInParserDefinitionInspectionTestBase() {
override fun getBasePath() = DevkitKtTestsUtil.TESTDATA_PATH + "inspections/tokenSetInParserDefinition"
override fun getFileExtension() = "kt"
fun testParserDefinitionWithIllegalTokenSet() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetWhenComplexCreation() {
doInspectionTest()
}
fun testParserDefinitionNonDirectImplementorWithIllegalTokenSet() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetInCompanionObject() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetInitializedInCompanionObjectInitBlock() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetInitializedInConstructor() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetInitializedInInitBlock() {
doInspectionTest()
}
fun testParserDefinitionWithIllegalTokenSetWhenMembersImported() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenSet() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenSetInCompanionObject() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenSetInitializedInCompanionObjectInitBlock() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenSetInitializedInConstructor() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenType() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenTypeInitializedInConstructor() {
doInspectionTest()
}
fun testParserDefinitionWithLegalCoreTokenTypeInitializedInStaticBlock() {
doInspectionTest()
}
fun testParserDefinitionWithLegalTokenSetDeclaredOnTopLevel() {
doInspectionTest()
}
fun testParserDefinitionWithLegalTokenSetInitializedLazilyInMethod() {
doInspectionTest()
}
private fun doInspectionTest() {
myFixture.copyFileToProject("MyLangTokenTypes.kt")
doTest()
}
}
@@ -0,0 +1,71 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections
import org.jetbrains.idea.devkit.inspections.quickfix.LightDevKitInspectionFixTestBase
abstract class TokenSetInParserDefinitionInspectionTestBase : LightDevKitInspectionFixTestBase() {
override fun setUp() {
super.setUp()
myFixture.addClass("""
package com.intellij.lang;
public abstract class Language {}
""".trimIndent())
myFixture.addClass("""
package com.intellij.psi.tree;
import com.intellij.lang.Language;
public class IElementType {
public IElementType(String debugName, Language language) {
// any
}
}
""".trimIndent())
myFixture.addClass("""
package com.intellij.psi;
import com.intellij.psi.tree.IElementType;
public interface TokenType {
IElementType WHITE_SPACE = null;
}
""".trimIndent())
myFixture.addClass("""
package com.intellij.psi.tree;
public final class TokenSet {
public static final TokenSet EMPTY = null;
public static final TokenSet ANY = null;
public static final TokenSet WHITE_SPACE = null;
public static TokenSet create(IElementType... types) {
return null;
}
public static TokenSet orSet(TokenSet... sets) {
return null;
}
}
""".trimIndent())
myFixture.addClass("""
package com.intellij.lang;
import com.intellij.psi.tree.TokenSet;
public interface ParserDefinition {
// only a single TokenSet-related method for simplicity
TokenSet getCommentTokens();
}
""".trimIndent())
myFixture.enableInspections(TokenSetInParserDefinitionInspection())
}
}