Skip to content

Commit 51b23ad

Browse files
authored
Merge pull request #8
* refactor(css): 优化 CSS Modules 类注解器导入顺序和空类检查逻辑 * refactor(css): improve CSS Modules class reference resolution and cac…
1 parent a1db664 commit 51b23ad

11 files changed

Lines changed: 392 additions & 215 deletions

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## [Unreleased]
44

5+
## [1.5.4] - 2025-12-10
6+
7+
- 升级版本号到 1.5.4 并准备发布
8+
- improve CSS Modules class reference resolution and caching mechanism
9+
510
## [1.5.3] - 2025-12-10
611

712
- 将插件名称从 "React Css Modules All" 更改为 "CSS Modules"
@@ -155,7 +160,8 @@
155160
- support a little complex parents selector
156161
- support css selector has pseudo
157162

158-
[Unreleased]: https://github.com/Q-Peppa/react-css-modules-all/compare/v1.5.3...HEAD
163+
[Unreleased]: https://github.com/Q-Peppa/react-css-modules-all/compare/v1.5.4...HEAD
164+
[1.5.4]: https://github.com/Q-Peppa/react-css-modules-all/compare/v1.5.3...v1.5.4
159165
[1.5.3]: https://github.com/Q-Peppa/react-css-modules-all/compare/v1.5.2...v1.5.3
160166
[1.5.2]: https://github.com/Q-Peppa/react-css-modules-all/compare/v1.5.1...v1.5.2
161167
[1.5.1]: https://github.com/Q-Peppa/react-css-modules-all/compare/v1.5.0...v1.5.1

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ pluginGroup = com.peppa.css
44
pluginName = CSS Modules
55
pluginRepositoryUrl = https://github.com/Q-Peppa/react-css-modules-all
66
# SemVer format -> https://semver.org
7-
pluginVersion = 1.5.3
8-
version = 1.5.3
7+
pluginVersion = 1.5.4
8+
version = 1.5.4
99
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
1010
pluginSinceBuild=242
1111
# pluginUntilBuild = 242.* # for Supported all version > 231
Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,49 @@
11
package com.peppa.css.annotator
22

3-
import com.peppa.css.completion.resolveStylesheetFromReference
4-
import com.peppa.css.psi.CssModuleClassReference
5-
import com.peppa.css.psi.isStyleIndex
63
import com.intellij.lang.annotation.AnnotationHolder
74
import com.intellij.lang.annotation.Annotator
85
import com.intellij.lang.annotation.HighlightSeverity
96
import com.intellij.lang.javascript.psi.JSLiteralExpression
107
import com.intellij.lang.javascript.psi.JSReferenceExpression
118
import com.intellij.psi.PsiElement
12-
import com.intellij.psi.css.CssRuleset
13-
import org.jetbrains.annotations.NotNull
14-
import java.util.Objects
15-
9+
import com.peppa.css.completion.resolveStylesheetFromReference
10+
import com.peppa.css.psi.CssModuleClassReference
11+
import com.peppa.css.psi.isStyleIndex
1612

17-
const val MESSAGE = "Selector declarations is Empty"
18-
const val UNKNOWN = "Unknown class name"
13+
private const val MESSAGE_UNKNOWN = "Unknown class name"
1914

2015
class CssModulesClassAnnotator : Annotator {
21-
private fun resolveUnknownClass(holder: AnnotationHolder, psiElement: JSLiteralExpression) {
22-
val cssSelectorName = psiElement.stringValue?.trim().orEmpty()
23-
val reference = psiElement.reference
2416

25-
// Check if the reference is unresolved (CSS class doesn't exist)
26-
if (reference is CssModuleClassReference && reference.isUnresolved()) {
27-
val message = "$UNKNOWN \"$cssSelectorName\""
28-
holder.newAnnotation(HighlightSeverity.WARNING, message)
29-
.range(psiElement)
30-
.withFix(SimpleCssSelectorFix(cssSelectorName, reference.stylesheetFile))
31-
.create()
32-
}
33-
}
17+
override fun annotate(psiElement: PsiElement, holder: AnnotationHolder) {
18+
when (psiElement) {
19+
is JSLiteralExpression if isStyleIndex(psiElement) ->
20+
annotateStyleIndex(psiElement, holder)
3421

35-
private fun resolveEmptyClass(holder: AnnotationHolder, psiElement: JSLiteralExpression) {
36-
val ruleset = psiElement.reference?.resolve()
37-
if (ruleset is CssRuleset) {
38-
val declarations = ruleset.block?.declarations
39-
if (declarations.isNullOrEmpty()) {
40-
holder.newAnnotation(HighlightSeverity.WEAK_WARNING, MESSAGE)
41-
.range(psiElement)
42-
.create()
43-
}
22+
is JSReferenceExpression -> annotateUnresolvedReference(psiElement, holder)
4423
}
4524
}
4625

47-
override fun annotate(@NotNull psiElement: PsiElement, @NotNull holder: AnnotationHolder) {
48-
if (psiElement is JSLiteralExpression && isStyleIndex(psiElement)) {
49-
resolveUnknownClass(holder, psiElement)
50-
resolveEmptyClass(holder, psiElement)
51-
return
26+
private fun annotateStyleIndex(element: JSLiteralExpression, holder: AnnotationHolder) {
27+
val reference = element.reference as? CssModuleClassReference ?: return
28+
val className = element.stringValue?.trim().orEmpty()
29+
30+
when {
31+
reference.isUnresolved() -> holder.newAnnotation(HighlightSeverity.WARNING, "$MESSAGE_UNKNOWN \"$className\"")
32+
.range(element)
33+
.withFix(SimpleCssSelectorFix(className, reference.stylesheetFile))
34+
.create()
5235
}
53-
if (psiElement is JSReferenceExpression && Objects.isNull(psiElement.reference?.resolve())) {
54-
val styleFile = resolveStylesheetFromReference(psiElement) ?: return
36+
}
5537

38+
private fun annotateUnresolvedReference(element: JSReferenceExpression, holder: AnnotationHolder) {
39+
if (element.reference?.resolve() != null) return
5640

57-
holder.newAnnotation(
58-
HighlightSeverity.WEAK_WARNING,
59-
"$UNKNOWN \"${psiElement.lastChild.text}\""
60-
)
61-
.range(psiElement.lastChild)
62-
.withFix(SimpleCssSelectorFix(psiElement.lastChild.text, styleFile))
63-
.create()
41+
val styleFile = resolveStylesheetFromReference(element) ?: return
42+
val className = element.lastChild.text
6443

65-
}
44+
holder.newAnnotation(HighlightSeverity.WEAK_WARNING, "$MESSAGE_UNKNOWN \"$className\"")
45+
.range(element.lastChild)
46+
.withFix(SimpleCssSelectorFix(className, styleFile))
47+
.create()
6648
}
6749
}

src/main/kotlin/com/peppa/css/annotator/SimpleCssSelectorFix.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,26 @@ class SimpleCssSelectorFix(private val key: String, private val stylesheetFile:
2626

2727
override fun invoke(@NotNull project: Project, editor: Editor?, file: PsiFile?) {
2828
if (editor == null || file == null) return
29-
val rulesetText = "\n.$key {\n \n}"
29+
30+
val rulesetText = "\n.$key {\n\t\t\n}"
3031
val ruleset = CssElementFactory.getInstance(project).createRuleset(
3132
rulesetText,
3233
stylesheetFile.language
3334
)
3435

3536
stylesheetFile.navigate(true)
3637
stylesheetFile.add(ruleset)
37-
38-
val newEditor = FileEditorManager.getInstance(project).selectedEditor ?:return;
39-
if(newEditor is TextEditor) {
38+
val newEditor = FileEditorManager.getInstance(project).selectedEditor ?: return;
39+
if (newEditor is TextEditor) {
4040
newEditor.editor.caretModel.moveToLogicalPosition(
41-
LogicalPosition(newEditor.editor.document.lineCount-2, 0)
41+
LogicalPosition(newEditor.editor.document.lineCount - 2, 0)
4242
)
43-
newEditor.editor.scrollingModel.scrollTo(newEditor.editor.caretModel.logicalPosition,ScrollType.MAKE_VISIBLE)
44-
DeclarativeInlayHintsPassFactory.scheduleRecompute(editor,project)
45-
DeclarativeInlayHintsPassFactory.scheduleRecompute(newEditor.editor,project)
43+
newEditor.editor.scrollingModel.scrollTo(
44+
newEditor.editor.caretModel.logicalPosition,
45+
ScrollType.MAKE_VISIBLE
46+
)
47+
DeclarativeInlayHintsPassFactory.scheduleRecompute(editor, project)
48+
DeclarativeInlayHintsPassFactory.scheduleRecompute(newEditor.editor, project)
4649
}
47-
4850
}
4951
}

src/main/kotlin/com/peppa/css/completion/CssModulesClassNameCompletionContributor.kt

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,90 +9,72 @@ import com.intellij.lang.javascript.psi.JSIndexedPropertyAccessExpression
99
import com.intellij.lang.javascript.psi.JSLiteralExpression
1010
import com.intellij.lang.javascript.psi.JSReferenceExpression
1111
import com.intellij.patterns.PlatformPatterns
12-
import com.intellij.psi.PsiElement
1312
import com.intellij.psi.css.StylesheetFile
1413
import com.intellij.util.ProcessingContext
1514

16-
const val SplitChar = "-"
17-
const val DotChar = "."
15+
private const val SPLIT_CHAR = "-"
16+
private const val DOT_CHAR = "."
1817

1918
class CssModulesClassNameCompletionContributor : CompletionContributor() {
2019

2120
init {
22-
val language = PlatformPatterns
23-
.psiElement()
24-
.withLanguage(JavascriptLanguage.INSTANCE)
21+
val jsPattern = PlatformPatterns.psiElement().withLanguage(JavascriptLanguage.INSTANCE)
2522

2623
extend(
2724
CompletionType.BASIC,
28-
language
25+
jsPattern
2926
.withParent(JSLiteralExpression::class.java)
3027
.withSuperParent(2, JSIndexedPropertyAccessExpression::class.java),
31-
CssModulesClassNameCompletionContributorProvider()
28+
IndexAccessCompletionProvider()
3229
)
3330
extend(
3431
CompletionType.BASIC,
35-
language
36-
.withParent(JSReferenceExpression::class.java),
37-
CssModulesClassNameCompletionContributorWithDotProvider()
32+
jsPattern.withParent(JSReferenceExpression::class.java),
33+
DotAccessCompletionProvider()
3834
)
3935
}
4036

41-
private class CssModulesClassNameCompletionContributorProvider : CompletionProvider<CompletionParameters>() {
37+
private class IndexAccessCompletionProvider : CompletionProvider<CompletionParameters>() {
4238
override fun addCompletions(
4339
parameters: CompletionParameters,
4440
context: ProcessingContext,
4541
resultSet: CompletionResultSet
4642
) {
47-
val position = parameters.position
48-
val stylesheetFile = findReferenceStyleFile(position.parent as JSLiteralExpression) ?: return
43+
val literal = parameters.position.parent as? JSLiteralExpression ?: return
44+
val stylesheetFile = findReferenceStyleFile(literal) ?: return
4945
resultSet.addAllElements(generateLookupElementList(stylesheetFile))
5046
}
5147
}
5248

53-
private class CssModulesClassNameCompletionContributorWithDotProvider : CompletionProvider<CompletionParameters>() {
49+
private class DotAccessCompletionProvider : CompletionProvider<CompletionParameters>() {
5450
override fun addCompletions(
5551
parameters: CompletionParameters,
5652
context: ProcessingContext,
5753
resultSet: CompletionResultSet
5854
) {
59-
6055
val position = parameters.position
61-
if (position.prevSibling is PsiElement
62-
&& position.prevSibling.text == DotChar
63-
&& position.prevSibling.prevSibling is JSReferenceExpression
64-
) {
65-
val style = position.prevSibling.prevSibling
66-
style.reference?.resolve()?.let { stylesFileImportStatement ->
67-
if (stylesFileImportStatement !is ES6ImportedBinding || stylesFileImportStatement.findReferencedElements()
68-
.isEmpty()
69-
) return
70-
val first = stylesFileImportStatement.findReferencedElements().firstOrNull()
71-
first?.let {
72-
resultSet.addAllElements(generateLookupElementList(it as StylesheetFile, true).map { element ->
73-
// if choose completion with - , auto make to IndexedAccess
74-
LookupElementDecorator.withInsertHandler(element) { context, item ->
75-
StylesInsertHandler(item.lookupString.contains(SplitChar)).handleInsert(context, item)
76-
}
77-
})
56+
val prevSibling = position.prevSibling ?: return
57+
if (prevSibling.text != DOT_CHAR) return
58+
59+
val styleRef = prevSibling.prevSibling as? JSReferenceExpression ?: return
60+
val binding = styleRef.reference?.resolve() as? ES6ImportedBinding ?: return
61+
val stylesheetFile = binding.findReferencedElements().firstOrNull() as? StylesheetFile ?: return
62+
63+
val elements = generateLookupElementList(stylesheetFile, true).map { element ->
64+
LookupElementDecorator.withInsertHandler(element) { ctx, item ->
65+
if (item.lookupString.contains(SPLIT_CHAR)) {
66+
convertToBracketSyntax(ctx, item.lookupString)
7867
}
7968
}
8069
}
70+
resultSet.addAllElements(elements)
8171
}
82-
}
8372

84-
private class StylesInsertHandler(private val needsBracketSyntax: Boolean) : InsertHandler<LookupElement> {
85-
override fun handleInsert(context: InsertionContext, item: LookupElement) {
86-
if (needsBracketSyntax) {
87-
val editor = context.editor
88-
val document = editor.document
89-
val startOffset = context.startOffset
90-
val dotPosOffset = startOffset - 1
91-
val tailOffset = context.tailOffset
92-
val lookupString = item.lookupString
93-
document.replaceString(dotPosOffset, tailOffset, "[$lookupString]")
94-
editor.caretModel.moveToOffset(dotPosOffset + lookupString.length + 2)
95-
}
73+
private fun convertToBracketSyntax(context: InsertionContext, lookupString: String) {
74+
val document = context.editor.document
75+
val dotOffset = context.startOffset - 1
76+
document.replaceString(dotOffset, context.tailOffset, "['$lookupString']")
77+
context.editor.caretModel.moveToOffset(dotOffset + lookupString.length + 4)
9678
}
9779
}
9880

0 commit comments

Comments
 (0)