diff --git a/src/main/java/dev/blachut/svelte/lang/SvelteHtmlInspectionSuppressor.kt b/src/main/java/dev/blachut/svelte/lang/SvelteHtmlInspectionSuppressor.kt index 73507ceb..4065b3bb 100644 --- a/src/main/java/dev/blachut/svelte/lang/SvelteHtmlInspectionSuppressor.kt +++ b/src/main/java/dev/blachut/svelte/lang/SvelteHtmlInspectionSuppressor.kt @@ -6,6 +6,9 @@ import com.intellij.psi.PsiFile import com.intellij.psi.xml.XmlAttribute class SvelteHtmlInspectionSuppressor : DefaultXmlSuppressionProvider() { + private val scriptAttributes = listOf("context") + private val styleAttributes = listOf("src", "global") + override fun isSuppressedFor(element: PsiElement, inspectionId: String): Boolean { if (inspectionId == "XmlUnboundNsPrefix") { return true @@ -14,9 +17,11 @@ class SvelteHtmlInspectionSuppressor : DefaultXmlSuppressionProvider() { if (inspectionId == "HtmlUnknownAttribute") { val attribute = element.parent if (attribute is XmlAttribute) { - if (directives.contains(attribute.namespacePrefix) || suppressedAttributes.contains(attribute.name)) { - return true - } + if (directives.contains(attribute.namespacePrefix)) return true + + // TODO refactor into proper descriptors + if (attribute.parent.name == "script" && scriptAttributes.contains(attribute.name)) return true + if (attribute.parent.name == "style" && styleAttributes.contains(attribute.name)) return true } } @@ -35,4 +40,3 @@ class SvelteHtmlInspectionSuppressor : DefaultXmlSuppressionProvider() { } val directives = listOf("on", "bind", "class", "use", "transition", "in", "out", "animate", "let") -private val suppressedAttributes = listOf("context") diff --git a/src/main/java/dev/blachut/svelte/lang/SvelteKeywordHighlighterVisitor.kt b/src/main/java/dev/blachut/svelte/lang/SvelteKeywordHighlighterVisitor.kt index 619f98ee..14710460 100644 --- a/src/main/java/dev/blachut/svelte/lang/SvelteKeywordHighlighterVisitor.kt +++ b/src/main/java/dev/blachut/svelte/lang/SvelteKeywordHighlighterVisitor.kt @@ -1,7 +1,6 @@ package dev.blachut.svelte.lang import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder -import com.intellij.lang.javascript.JSTokenTypes import com.intellij.lang.javascript.validation.ES6KeywordHighlighterVisitor import dev.blachut.svelte.lang.psi.SvelteInitialTag import dev.blachut.svelte.lang.psi.SvelteJSLazyPsiElement @@ -16,8 +15,8 @@ class SvelteKeywordHighlighterVisitor(holder: HighlightInfoHolder) : ES6KeywordH } override fun visitLazyElement(element: SvelteJSLazyPsiElement) { - // Direct children should be safe to treat as identifiers - highlightChildKeywordOfType(element, JSTokenTypes.IDENTIFIER) // debug, html + highlightChildKeywordOfType(element, SvelteTokenTypes.HTML_KEYWORD) + highlightChildKeywordOfType(element, SvelteTokenTypes.DEBUG_KEYWORD) super.visitLazyElement(element) } diff --git a/src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteXmlExtension.kt b/src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteXmlExtension.kt index 63ed27b4..5b7387fc 100644 --- a/src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteXmlExtension.kt +++ b/src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteXmlExtension.kt @@ -8,13 +8,15 @@ import dev.blachut.svelte.lang.directives import dev.blachut.svelte.lang.isSvelteComponentTag class SvelteXmlExtension : HtmlXmlExtension() { + private val collapsibleTags = setOf("slot", "style", "script") + override fun isAvailable(file: PsiFile): Boolean = file.language is SvelteHTMLLanguage /** * Whether writing self closing `` is correct */ override fun isSelfClosingTagAllowed(tag: XmlTag): Boolean { - return isSvelteComponentTag(tag.name) || tag.name == "slot" || super.isSelfClosingTagAllowed(tag) + return isSvelteComponentTag(tag.name) || collapsibleTags.contains(tag.name) || super.isSelfClosingTagAllowed(tag) } /** @@ -29,7 +31,11 @@ class SvelteXmlExtension : HtmlXmlExtension() { */ override fun isSingleTagException(tag: XmlTag): Boolean = isSvelteComponentTag(tag.name) || tag.name == "slot" - override fun getAttributeValuePresentation(tag: XmlTag?, attributeName: String, defaultAttributeQuote: String): AttributeValuePresentation { + override fun getAttributeValuePresentation( + tag: XmlTag?, + attributeName: String, + defaultAttributeQuote: String + ): AttributeValuePresentation { if (attributeName == "slot") { return super.getAttributeValuePresentation(tag, attributeName, defaultAttributeQuote) } diff --git a/src/main/java/dev/blachut/svelte/lang/completion/SvelteAttributeNameCompletionProvider.kt b/src/main/java/dev/blachut/svelte/lang/completion/SvelteAttributeNameCompletionProvider.kt index 4cd425a3..baa564aa 100644 --- a/src/main/java/dev/blachut/svelte/lang/completion/SvelteAttributeNameCompletionProvider.kt +++ b/src/main/java/dev/blachut/svelte/lang/completion/SvelteAttributeNameCompletionProvider.kt @@ -5,6 +5,7 @@ import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.PrioritizedLookupElement +import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.xml.XmlAttribute @@ -13,18 +14,39 @@ import com.intellij.util.ProcessingContext import dev.blachut.svelte.lang.icons.SvelteIcons class SvelteAttributeNameCompletionProvider : CompletionProvider() { - override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) { + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { val xmlTag = PsiTreeUtil.getParentOfType(parameters.position, XmlTag::class.java, false) val xmlAttribute = parameters.position.parent as? XmlAttribute if (xmlTag == null || xmlAttribute == null) return if (xmlTag.name == "script" && xmlTag.getAttribute("context") == null) { - val element = LookupElementBuilder - .create("context=\"module\"") - .withIcon(SvelteIcons.GRAY) - .let { PrioritizedLookupElement.withPriority(it, 10.0) } + result.addElement(createLookupElement("context=\"module\"", 10)) + } + + // TODO refactor into proper descriptors + if (xmlTag.name == "style" && xmlTag.getAttribute("global") == null) { + result.addElement(createLookupElement("global")) + } - result.addElement(element) + if (xmlTag.name == "style" && xmlTag.getAttribute("src") == null) { + result.addElement(createLookupElement("src")) } } + + private fun createLookupElement(text: String, priority: Int? = null): LookupElement { + return LookupElementBuilder + .create(text) + .withIcon(SvelteIcons.GRAY) + .let { + if (priority != null) { + PrioritizedLookupElement.withPriority(it, priority.toDouble()) + } else { + it + } + } + } } diff --git a/src/main/java/dev/blachut/svelte/lang/editor/SvelteFoldingBuilder.kt b/src/main/java/dev/blachut/svelte/lang/editor/SvelteFoldingBuilder.kt index c90631c9..cdf3dddd 100644 --- a/src/main/java/dev/blachut/svelte/lang/editor/SvelteFoldingBuilder.kt +++ b/src/main/java/dev/blachut/svelte/lang/editor/SvelteFoldingBuilder.kt @@ -42,13 +42,11 @@ class SvelteFoldingBuilder : XmlFoldingBuilder(), DumbAware { for (child in block.children) { if (child is SvelteBranch) { val fragment = child.fragment - val foldingRangeStartOffset = fragment.textRange.startOffset - val foldingRangeEndOffset = fragment.textRange.endOffset - val range = TextRange(foldingRangeStartOffset, foldingRangeEndOffset) + if (fragment.textLength > 0) { + descriptors.add(FoldingDescriptor(block, fragment.textRange)) - descriptors.add(FoldingDescriptor(block, range)) - - doAddForChildren(fragment, descriptors, document) + doAddForChildren(fragment, descriptors, document) + } } } } diff --git a/src/main/java/dev/blachut/svelte/lang/format/SvelteExpressionBlock.kt b/src/main/java/dev/blachut/svelte/lang/format/SvelteExpressionBlock.kt new file mode 100644 index 00000000..77c98d92 --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/format/SvelteExpressionBlock.kt @@ -0,0 +1,75 @@ +package dev.blachut.svelte.lang.format + +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.lang.LanguageFormatting +import com.intellij.lang.javascript.JSTokenTypes +import com.intellij.psi.formatter.common.AbstractBlock +import com.intellij.psi.formatter.xml.AbstractXmlBlock +import com.intellij.psi.formatter.xml.XmlFormattingPolicy +import dev.blachut.svelte.lang.psi.SvelteTagElementTypes +import dev.blachut.svelte.lang.psi.SvelteTokenTypes + +class SvelteExpressionBlock( + node: ASTNode, + private val indent: Indent?, + wrap: Wrap?, + private val policy: XmlFormattingPolicy +) : + AbstractBlock(node, wrap, null) { + override fun isLeaf(): Boolean = false + + override fun buildChildren(): MutableList { + val results = ArrayList(4) + + // borrowed from com.intellij.psi.formatter.common.InjectedLanguageBlockBuilder.addInjectedLanguageBlockWrapper + val nodePsi = myNode.psi + val builder = LanguageFormatting.INSTANCE.forContext(nodePsi.language, nodePsi) + + var child = myNode.firstChildNode + while (child != null) { + if (child.textLength > 0 && !AbstractXmlBlock.containsWhiteSpacesOnly(child)) { + if (child.elementType === JSTokenTypes.LBRACE) { + val startTag = SvelteTagElementTypes.START_TAGS.contains(myNode.elementType) + val wrap = if (startTag) Wrap.createWrap(WrapType.ALWAYS, true) else null + results.add(SvelteLeafBlock(child, wrap = wrap)) + } else if (child.elementType === JSTokenTypes.RBRACE) { + results.add(SvelteLeafBlock(child, indent = Indent.getNoneIndent())) + } else { + if (builder != null) { + val childModel = builder.createModel(child.psi, policy.settings) + results.add(childModel.rootBlock) + } else { + results.add(SvelteLeafBlock(child)) + } + } + } + + child = child.treeNext + } + + return results + } + + override fun getSpacing(child1: Block?, child2: Block): Spacing? { + if (child1 !is ASTBlock || child2 !is ASTBlock) { + return null + } + + val node1 = child1.node ?: return null + val node2 = child2.node ?: return null + + val type1 = node1.elementType + val type2 = node2.elementType + + if (SvelteTokenTypes.KEYWORDS.contains(type1) && type2 !== JSTokenTypes.RBRACE) { + return Spacing.createSpacing(1, 1, 0, true, 0) + } + + return null + } + + override fun getIndent(): Indent? { + return indent + } +} diff --git a/src/main/java/dev/blachut/svelte/lang/format/SvelteFormattingModelBuilder.kt b/src/main/java/dev/blachut/svelte/lang/format/SvelteFormattingModelBuilder.kt index 80273b25..dee0ef0b 100644 --- a/src/main/java/dev/blachut/svelte/lang/format/SvelteFormattingModelBuilder.kt +++ b/src/main/java/dev/blachut/svelte/lang/format/SvelteFormattingModelBuilder.kt @@ -6,7 +6,6 @@ import com.intellij.lang.xml.XmlFormattingModel import com.intellij.psi.PsiElement import com.intellij.psi.codeStyle.CodeStyleSettings import com.intellij.psi.formatter.FormattingDocumentModelImpl -import com.intellij.psi.formatter.xml.HtmlPolicy import com.intellij.psi.impl.source.SourceTreeToPsiMap class SvelteFormattingModelBuilder : FormattingModelBuilder { @@ -15,7 +14,7 @@ class SvelteFormattingModelBuilder : FormattingModelBuilder { val documentModel = FormattingDocumentModelImpl.createOn(psiFile) val astNode = SourceTreeToPsiMap.psiElementToTree(psiFile) - val formattingPolicy = HtmlPolicy(settings, documentModel) + val formattingPolicy = SvelteHtmlPolicy(settings, documentModel) val block = SvelteXmlBlock(astNode, null, null, formattingPolicy, null, null, false) return XmlFormattingModel(psiFile, block, documentModel) diff --git a/src/main/java/dev/blachut/svelte/lang/format/SvelteHtmlPolicy.kt b/src/main/java/dev/blachut/svelte/lang/format/SvelteHtmlPolicy.kt new file mode 100644 index 00000000..02e2da13 --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/format/SvelteHtmlPolicy.kt @@ -0,0 +1,22 @@ +package dev.blachut.svelte.lang.format + +import com.intellij.formatting.FormattingDocumentModel +import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.psi.formatter.xml.HtmlPolicy +import com.intellij.psi.xml.XmlTag + +class SvelteHtmlPolicy(settings: CodeStyleSettings, documentModel: FormattingDocumentModel) : + HtmlPolicy(settings, documentModel) { + + override fun shouldBeWrapped(tag: XmlTag): Boolean { + if (wrappingTags.contains(tag.name)) { + return !tag.value.textRange.isEmpty + } + + return super.shouldBeWrapped(tag) + } + + companion object { + val wrappingTags = setOf("script", "style") + } +} diff --git a/src/main/java/dev/blachut/svelte/lang/format/SvelteLeafBlock.kt b/src/main/java/dev/blachut/svelte/lang/format/SvelteLeafBlock.kt new file mode 100644 index 00000000..0e4a4e0a --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/format/SvelteLeafBlock.kt @@ -0,0 +1,19 @@ +package dev.blachut.svelte.lang.format + +import com.intellij.formatting.Block +import com.intellij.formatting.Indent +import com.intellij.formatting.Spacing +import com.intellij.formatting.Wrap +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock + +class SvelteLeafBlock(node: ASTNode, private val indent: Indent? = null, wrap: Wrap? = null) : + AbstractBlock(node, wrap, null) { + override fun isLeaf(): Boolean = true + + override fun buildChildren(): MutableList = EMPTY + + override fun getSpacing(child1: Block?, child2: Block): Spacing? = null + + override fun getIndent(): Indent? = indent +} diff --git a/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlBlock.kt b/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlBlock.kt deleted file mode 100644 index ea902b7b..00000000 --- a/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlBlock.kt +++ /dev/null @@ -1,193 +0,0 @@ -package dev.blachut.svelte.lang.format - -import com.intellij.formatting.* -import com.intellij.lang.ASTNode -import com.intellij.lang.javascript.JSTokenTypes -import com.intellij.openapi.util.TextRange -import com.intellij.psi.formatter.xml.AbstractXmlBlock -import com.intellij.psi.formatter.xml.XmlBlock -import com.intellij.psi.formatter.xml.XmlFormattingPolicy -import com.intellij.psi.formatter.xml.XmlTagBlock -import com.intellij.psi.impl.source.SourceTreeToPsiMap -import com.intellij.psi.xml.XmlTag -import dev.blachut.svelte.lang.psi.SvelteElementTypes -import dev.blachut.svelte.lang.psi.SvelteEndTag -import dev.blachut.svelte.lang.psi.SvelteTagElementTypes -import dev.blachut.svelte.lang.psi.blocks.SvelteBlock - -class SvelteXmlBlock( - node: ASTNode?, - wrap: Wrap?, - alignment: Alignment?, - policy: XmlFormattingPolicy?, - indent: Indent?, - textRange: TextRange?, - preserveSpace: Boolean -) : XmlBlock(node, wrap, alignment, policy, indent, textRange, preserveSpace) { - override fun getTag(): XmlTag { - return super.getTag() ?: getFakeTag(myNode) - } - - override fun processSimpleChild(child: ASTNode, indent: Indent?, result: MutableList, wrap: Wrap?, alignment: Alignment?) { - if (isSvelteBlock(child)) { - result.add(createTagBlock(child, indent ?: Indent.getNoneIndent(), wrap, alignment)) - } else { - super.processSimpleChild(child, indent, result, wrap, alignment) - } - } - - override fun createSimpleChild(child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlBlock { - return createSimpleChild(myXmlFormattingPolicy, child, indent, wrap, alignment) - } - - override fun createTagBlock(child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlTagBlock { - return createTagBlock(myXmlFormattingPolicy, child, indent, wrap, alignment) - } -} - -open class SvelteXmlTagBlock( - node: ASTNode?, - wrap: Wrap?, - alignment: Alignment?, - policy: XmlFormattingPolicy?, - indent: Indent?, - preserveSpace: Boolean -) : XmlTagBlock(node, wrap, alignment, policy, indent, preserveSpace) { - override fun getTag(): XmlTag { - return super.getTag() ?: getFakeTag(myNode) - } - - override fun getChildAttributes(newChildIndex: Int): ChildAttributes { - if (!isSvelteBlock(myNode)) { - return super.getChildAttributes(newChildIndex) - } - - return ChildAttributes(Indent.getNormalIndent(), null) - } - - override fun getChildrenIndent(): Indent { - if (!isSvelteBlock(myNode)) { - return super.getChildrenIndent() - } - - return Indent.getNormalIndent() - } - - override fun buildChildren(): List? { - if (!isSvelteBlock(myNode)) { - return super.buildChildren() - } - - val textWrap = Wrap.createWrap(AbstractXmlBlock.getWrapType(myXmlFormattingPolicy.getTextWrap(tag)), true) - val results = ArrayList(3) - - var child = myNode.firstChildNode - while (child != null) { - if (!AbstractXmlBlock.containsWhiteSpacesOnly(child) && child.textLength > 0) { - if (SvelteElementTypes.BRANCHES.contains(child.elementType)) { - val tag = child.firstChildNode - val fragment = child.lastChildNode - - processTag(results, tag) - processFragment(results, fragment, textWrap) - } else if (child.psi is SvelteEndTag) { - processTag(results, child) - } - } - - child = child.treeNext - } - - return results - } - - private fun processTag(results: ArrayList, tag: ASTNode) { - val localResults = ArrayList(4) - - var child = tag.firstChildNode - while (child != null) { - if (!AbstractXmlBlock.containsWhiteSpacesOnly(child) && child.textLength > 0) { - if (child.elementType === JSTokenTypes.LBRACE) { - val startTag = SvelteTagElementTypes.START_TAGS.contains(tag.elementType) - val wrap = if (startTag) Wrap.createWrap(WrapType.ALWAYS, true) else null - localResults.add(createSimpleChild(child, null, wrap, null)) - } else if (child.elementType === JSTokenTypes.RBRACE) { - localResults.add(createSimpleChild(child, Indent.getNoneIndent(), null, null)) - } else { - child = processChild(localResults, child, null, null, null) - } - } - - if (child != null) { - child = child.treeNext - } - } - - // Same as XmlTagBlock.createTagDescriptionNode - results.add(createSyntheticBlock(localResults, null)) - } - - private fun processFragment(results: ArrayList, fragment: ASTNode, textWrap: Wrap?) { - val localResults = ArrayList(1) - - var child = fragment.firstChildNode - while (child != null) { - if (!AbstractXmlBlock.containsWhiteSpacesOnly(child) && child.textLength > 0) { - val wrap = chooseWrap(child, null, null, textWrap) - - child = processChild(localResults, child, wrap, null, Indent.getNormalIndent()) - } - - if (child != null) { - child = child.treeNext - } - } - - if (localResults.isNotEmpty()) { - results.add(createSyntheticBlock(localResults, Indent.getNormalIndent())) - } - } - - override fun processSimpleChild(child: ASTNode, indent: Indent?, result: MutableList, wrap: Wrap?, alignment: Alignment?) { - if (isSvelteBlock(child)) { - result.add(createTagBlock(child, indent ?: Indent.getNoneIndent(), wrap, alignment)) - } else { - super.processSimpleChild(child, indent, result, wrap, alignment) - } - } - - override fun createSimpleChild(child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlBlock { - return createSimpleChild(myXmlFormattingPolicy, child, indent, wrap, alignment) - } - - override fun createTagBlock(child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlTagBlock { - return createTagBlock(myXmlFormattingPolicy, child, indent, wrap, alignment) - } - - override fun createSyntheticBlock(localResult: ArrayList?, childrenIndent: Indent?): Block { - return SvelteSyntheticBlock(localResult, this, Indent.getNoneIndent(), myXmlFormattingPolicy, childrenIndent) - } -} - -private fun AbstractXmlBlock.createSimpleChild(xmlFormattingPolicy: XmlFormattingPolicy?, child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlBlock { - return SvelteXmlBlock(child, wrap, alignment, xmlFormattingPolicy, indent, null, isPreserveSpace) -} - -private fun AbstractXmlBlock.createTagBlock(xmlFormattingPolicy: XmlFormattingPolicy?, child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlTagBlock { - val newIndent = indent ?: Indent.getNoneIndent() - return SvelteXmlTagBlock(child, wrap, alignment, xmlFormattingPolicy, newIndent, isPreserveSpace) -} - -private fun getFakeTag(node: ASTNode): XmlTag { - val element = SourceTreeToPsiMap.treeElementToPsi(node) - - if (element is SvelteBlock) { - return SvelteBlockFakeXmlTag(element) - } - - throw AssertionError("Shouldn't happen") -} - -private fun isSvelteBlock(child: ASTNode): Boolean { - return child.psi is SvelteBlock -} diff --git a/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlBlocks.kt b/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlBlocks.kt new file mode 100644 index 00000000..881a969d --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlBlocks.kt @@ -0,0 +1,175 @@ +package dev.blachut.svelte.lang.format + +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.openapi.util.TextRange +import com.intellij.psi.formatter.xml.AbstractXmlBlock +import com.intellij.psi.formatter.xml.XmlBlock +import com.intellij.psi.formatter.xml.XmlFormattingPolicy +import com.intellij.psi.formatter.xml.XmlTagBlock +import com.intellij.psi.impl.source.SourceTreeToPsiMap +import com.intellij.psi.xml.XmlTag +import dev.blachut.svelte.lang.psi.SvelteJSLazyElementTypes.CONTENT_EXPRESSION +import dev.blachut.svelte.lang.psi.blocks.SvelteBlock + +class SvelteXmlBlock( + node: ASTNode?, + wrap: Wrap?, + alignment: Alignment?, + policy: XmlFormattingPolicy?, + indent: Indent?, + textRange: TextRange?, + preserveSpace: Boolean +) : XmlBlock(node, wrap, alignment, policy, indent, textRange, preserveSpace) { + override fun processChild( + result: MutableList, + child: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + indent: Indent? + ): ASTNode? { + if (child.elementType === CONTENT_EXPRESSION) { + result.add(SvelteExpressionBlock(child, indent, wrap, myXmlFormattingPolicy)) + return child + } + + return super.processChild(result, child, wrap, alignment, indent) + } + + override fun processSimpleChild( + child: ASTNode, + indent: Indent?, + result: MutableList, + wrap: Wrap?, + alignment: Alignment? + ) { + if (isSvelteBlock(child)) { + result.add(createTagBlock(child, indent ?: Indent.getNoneIndent(), wrap, alignment)) + } else { + super.processSimpleChild(child, indent, result, wrap, alignment) + } + } + + @Suppress("UnstableApiUsage") + override fun createSimpleChild(child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlBlock { + return createSimpleChild(myXmlFormattingPolicy, child, indent, wrap, alignment) + } + + override fun createTagBlock(child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlTagBlock { + return createTagBlock(myXmlFormattingPolicy, child, indent, wrap, alignment) + } +} + +abstract class SvelteXmlTagBlockBase( + node: ASTNode?, + wrap: Wrap?, + alignment: Alignment?, + policy: XmlFormattingPolicy?, + indent: Indent?, + preserveSpace: Boolean +) : XmlTagBlock(node, wrap, alignment, policy, indent, preserveSpace) { + // start getTag related overrides + override fun getTag(): XmlTag { + return super.getTag() ?: getFakeTag(myNode) + } + + private fun getFakeTag(node: ASTNode): XmlTag { + val element = SourceTreeToPsiMap.treeElementToPsi(node) + + if (element is SvelteBlock) { + return SvelteBlockFakeXmlTag(element) + } + + throw AssertionError("getFakeTag used not for SvelteBlock") + } + + override fun getChildAttributes(newChildIndex: Int): ChildAttributes { + if (isSvelteBlock(myNode)) { + return ChildAttributes(Indent.getNormalIndent(), null) + } + + return super.getChildAttributes(newChildIndex) + } + + override fun getChildrenIndent(): Indent { + if (isSvelteBlock(myNode)) { + return Indent.getNormalIndent() + } + + return super.getChildrenIndent() + } + // end getTag related overrides + + override fun buildChildren(): List { + if (isSvelteBlock(myNode)) { + return buildSvelteChildren() + } + + return super.buildChildren() + } + + abstract fun buildSvelteChildren(): List + + override fun processChild( + result: MutableList, + child: ASTNode, + wrap: Wrap?, + alignment: Alignment?, + indent: Indent? + ): ASTNode? { + if (child.elementType === CONTENT_EXPRESSION) { + result.add(SvelteExpressionBlock(child, indent, wrap, myXmlFormattingPolicy)) + return child + } + + return super.processChild(result, child, wrap, alignment, indent) + } + + override fun processSimpleChild( + child: ASTNode, + indent: Indent?, + result: MutableList, + wrap: Wrap?, + alignment: Alignment? + ) { + if (isSvelteBlock(child)) { + result.add(createTagBlock(child, indent ?: Indent.getNoneIndent(), wrap, alignment)) + } else { + super.processSimpleChild(child, indent, result, wrap, alignment) + } + } + + @Suppress("UnstableApiUsage") + override fun createSimpleChild(child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlBlock { + return createSimpleChild(myXmlFormattingPolicy, child, indent, wrap, alignment) + } + + override fun createTagBlock(child: ASTNode?, indent: Indent?, wrap: Wrap?, alignment: Alignment?): XmlTagBlock { + return createTagBlock(myXmlFormattingPolicy, child, indent, wrap, alignment) + } +} + +private fun AbstractXmlBlock.createSimpleChild( + xmlFormattingPolicy: XmlFormattingPolicy?, + child: ASTNode?, + indent: Indent?, + wrap: Wrap?, + alignment: Alignment? +): XmlBlock { + return SvelteXmlBlock(child, wrap, alignment, xmlFormattingPolicy, indent, null, isPreserveSpace) +} + +private fun AbstractXmlBlock.createTagBlock( + xmlFormattingPolicy: XmlFormattingPolicy?, + child: ASTNode?, + indent: Indent?, + wrap: Wrap?, + alignment: Alignment? +): XmlTagBlock { + val newIndent = indent ?: Indent.getNoneIndent() + return SvelteXmlTagBlock(child, wrap, alignment, xmlFormattingPolicy, newIndent, isPreserveSpace) +} + +private fun isSvelteBlock(child: ASTNode): Boolean { + return child.psi is SvelteBlock +} diff --git a/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlTagBlock.kt b/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlTagBlock.kt new file mode 100644 index 00000000..fd37c2e1 --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/format/SvelteXmlTagBlock.kt @@ -0,0 +1,101 @@ +package dev.blachut.svelte.lang.format + +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.lang.javascript.JSTokenTypes +import com.intellij.psi.formatter.xml.AbstractXmlBlock +import com.intellij.psi.formatter.xml.XmlFormattingPolicy +import com.intellij.psi.xml.XmlTag +import dev.blachut.svelte.lang.psi.SvelteElementTypes +import dev.blachut.svelte.lang.psi.SvelteEndTag +import dev.blachut.svelte.lang.psi.SvelteTagElementTypes + +class SvelteXmlTagBlock( + node: ASTNode?, + wrap: Wrap?, + alignment: Alignment?, + policy: XmlFormattingPolicy?, + indent: Indent?, + preserveSpace: Boolean +) : SvelteXmlTagBlockBase(node, wrap, alignment, policy, indent, preserveSpace) { + override fun canWrapTagEnd(tag: XmlTag): Boolean { + if (SvelteHtmlPolicy.wrappingTags.contains(tag.name)) return true + + return super.canWrapTagEnd(tag) + } + + override fun buildSvelteChildren(): List { + val textWrap = Wrap.createWrap(AbstractXmlBlock.getWrapType(myXmlFormattingPolicy.getTextWrap(tag)), true) + val results = ArrayList(3) + + var child = myNode.firstChildNode + while (child != null) { + if (!AbstractXmlBlock.containsWhiteSpacesOnly(child) && child.textLength > 0) { + if (SvelteElementTypes.BRANCHES.contains(child.elementType)) { + val tag = child.firstChildNode + val fragment = child.lastChildNode + + processTag(results, tag) + processFragment(results, fragment, textWrap) + } else if (child.psi is SvelteEndTag) { + processTag(results, child) + } + } + + child = child.treeNext + } + + return results + } + + private fun processTag(results: ArrayList, tag: ASTNode) { + val localResults = ArrayList(4) + + var child = tag.firstChildNode + while (child != null) { + if (!AbstractXmlBlock.containsWhiteSpacesOnly(child) && child.textLength > 0) { + if (child.elementType === JSTokenTypes.LBRACE) { + val startTag = SvelteTagElementTypes.START_TAGS.contains(tag.elementType) + val wrap = if (startTag) Wrap.createWrap(WrapType.ALWAYS, true) else null + localResults.add(createSimpleChild(child, null, wrap, null)) + } else if (child.elementType === JSTokenTypes.RBRACE) { + localResults.add(createSimpleChild(child, Indent.getNoneIndent(), null, null)) + } else { + child = processChild(localResults, child, null, null, null) + } + } + + if (child != null) { + child = child.treeNext + } + } + + // Same as XmlTagBlock.createTagDescriptionNode + results.add(createSyntheticBlock(localResults, null)) + } + + private fun processFragment(results: ArrayList, fragment: ASTNode, textWrap: Wrap?) { + val localResults = ArrayList(1) + + var child = fragment.firstChildNode + while (child != null) { + if (!AbstractXmlBlock.containsWhiteSpacesOnly(child) && child.textLength > 0) { + val wrap = chooseWrap(child, null, null, textWrap) + + child = processChild(localResults, child, wrap, null, Indent.getNormalIndent()) + } + + if (child != null) { + child = child.treeNext + } + } + + if (localResults.isNotEmpty()) { + results.add(createSyntheticBlock(localResults, Indent.getNormalIndent())) + } + } + + override fun createSyntheticBlock(localResult: ArrayList?, childrenIndent: Indent?): Block { + return SvelteSyntheticBlock(localResult, this, Indent.getNoneIndent(), myXmlFormattingPolicy, childrenIndent) + } +} diff --git a/src/main/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlLexer.flex b/src/main/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlLexer.flex index d2505c64..d728961f 100644 --- a/src/main/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlLexer.flex +++ b/src/main/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlLexer.flex @@ -168,6 +168,7 @@ CONDITIONAL_COMMENT_CONDITION=({ALPHA})({ALPHA}|{WHITE_SPACE_CHARS}|{DIGIT}|"."| "await" { yybegin(SVELTE_INTERPOLATION); return SvelteTokenTypes.AWAIT_KEYWORD; } "then" { yybegin(SVELTE_INTERPOLATION); return SvelteTokenTypes.THEN_KEYWORD; } "catch" { yybegin(SVELTE_INTERPOLATION); return SvelteTokenTypes.CATCH_KEYWORD; } + ({ALPHA})+ { yybegin(SVELTE_INTERPOLATION); yypushback(yylength()); } [^] { yybegin(SVELTE_INTERPOLATION); yypushback(yylength()); } } diff --git a/src/main/java/dev/blachut/svelte/lang/psi/SvelteElementTypes.kt b/src/main/java/dev/blachut/svelte/lang/psi/SvelteElementTypes.kt index 35631cf7..bb2bfac6 100644 --- a/src/main/java/dev/blachut/svelte/lang/psi/SvelteElementTypes.kt +++ b/src/main/java/dev/blachut/svelte/lang/psi/SvelteElementTypes.kt @@ -45,6 +45,8 @@ object SvelteElementTypes { ATTRIBUTE_EXPRESSION -> SveltePsiElement(node) + SvelteTagElementTypes.TAG_DEPENDENT_EXPRESSION -> SvelteTagDependentExpression(node) + SvelteTagElementTypes.IF_END, SvelteTagElementTypes.EACH_END, SvelteTagElementTypes.AWAIT_END -> SvelteEndTag(node) diff --git a/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSBlockLazyElementType.kt b/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSBlockLazyElementType.kt index be5a69f2..f4dd27f9 100644 --- a/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSBlockLazyElementType.kt +++ b/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSBlockLazyElementType.kt @@ -8,6 +8,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.tree.ILazyParseableElementType import dev.blachut.svelte.lang.SvelteJSLanguage +// TODO Merge SvelteJSBlockLazyElementType & SvelteJSLazyElementType abstract class SvelteJSBlockLazyElementType(debugName: String) : ILazyParseableElementType(debugName, SvelteJSLanguage.INSTANCE) { protected abstract val noTokensErrorMessage: String diff --git a/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSLazyElementType.kt b/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSLazyElementType.kt index 7b9ebea8..8c350316 100644 --- a/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSLazyElementType.kt +++ b/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSLazyElementType.kt @@ -3,23 +3,16 @@ package dev.blachut.svelte.lang.psi import com.intellij.lang.ASTNode import com.intellij.lang.PsiBuilder import com.intellij.lang.PsiBuilderFactory -import com.intellij.lang.ecmascript6.parsing.ES6ExpressionParser -import com.intellij.lang.ecmascript6.parsing.ES6FunctionParser -import com.intellij.lang.ecmascript6.parsing.ES6Parser -import com.intellij.lang.ecmascript6.parsing.ES6StatementParser -import com.intellij.lang.javascript.parsing.JSPsiTypeParser import com.intellij.lang.javascript.parsing.JavaScriptParser import com.intellij.psi.PsiElement import com.intellij.psi.tree.ILazyParseableElementType import dev.blachut.svelte.lang.SvelteJSLanguage +// TODO Merge SvelteJSBlockLazyElementType & SvelteJSLazyElementType abstract class SvelteJSLazyElementType(debugName: String) : ILazyParseableElementType(debugName, SvelteJSLanguage.INSTANCE) { protected abstract val noTokensErrorMessage: String protected val excessTokensErrorMessage = "unexpected token" - fun createJavaScriptParser(builder: PsiBuilder) = ES6Parser, ES6StatementParser<*>, - ES6FunctionParser<*>, JSPsiTypeParser<*>>(builder) - override fun createNode(text: CharSequence?): ASTNode? { text ?: return null return SvelteJSLazyPsiElement(this, text) @@ -27,8 +20,8 @@ abstract class SvelteJSLazyElementType(debugName: String) : ILazyParseableElemen override fun doParseContents(chameleon: ASTNode, psi: PsiElement): ASTNode { val project = psi.project - val builder = PsiBuilderFactory.getInstance().createBuilder(project, chameleon, null, SvelteJSLanguage.INSTANCE, chameleon.chars) - val parser = createJavaScriptParser(builder) + val builder = PsiBuilderFactory.getInstance().createBuilder(project, chameleon, null, language, chameleon.chars) + val parser = SvelteJSLanguage.INSTANCE.createParser(builder) val rootMarker = builder.mark() diff --git a/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSLazyElementTypes.kt b/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSLazyElementTypes.kt index 4d4f6d6e..d8598f98 100644 --- a/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSLazyElementTypes.kt +++ b/src/main/java/dev/blachut/svelte/lang/psi/SvelteJSLazyElementTypes.kt @@ -57,8 +57,6 @@ object SvelteJSLazyElementTypes { } } - private val allowedAtModifiers = setOf("html", "debug") - private fun parseAtModifiers(builder: PsiBuilder) { if (builder.tokenType === JSTokenTypes.AT) { builder.advanceLexer() @@ -67,7 +65,11 @@ object SvelteJSLazyElementTypes { builder.error("whitespace is not allowed after @") } - if (builder.tokenType === JSTokenTypes.IDENTIFIER && allowedAtModifiers.contains(builder.tokenText)) { + if (builder.tokenType === JSTokenTypes.IDENTIFIER && builder.tokenText == "html") { + builder.remapCurrentToken(SvelteTokenTypes.HTML_KEYWORD) + builder.advanceLexer() + } else if (builder.tokenType === JSTokenTypes.IDENTIFIER && builder.tokenText == "debug") { + builder.remapCurrentToken(SvelteTokenTypes.DEBUG_KEYWORD) builder.advanceLexer() } else { val errorMarker = builder.mark() @@ -82,7 +84,12 @@ object SvelteJSLazyElementTypes { val errorMarker = builder.mark() builder.advanceLexer() - if (builder.tokenType === JSTokenTypes.IDENTIFIER && allowedAtModifiers.contains(builder.tokenText)) { + // copied from parseAtModifiers above + if (builder.tokenType === JSTokenTypes.IDENTIFIER && builder.tokenText == "html") { + builder.remapCurrentToken(SvelteTokenTypes.HTML_KEYWORD) + builder.advanceLexer() + } else if (builder.tokenType === JSTokenTypes.IDENTIFIER && builder.tokenText == "debug") { + builder.remapCurrentToken(SvelteTokenTypes.DEBUG_KEYWORD) builder.advanceLexer() } diff --git a/src/main/java/dev/blachut/svelte/lang/psi/SvelteTagElementTypes.kt b/src/main/java/dev/blachut/svelte/lang/psi/SvelteTagElementTypes.kt index cdbcddaf..d361e56a 100644 --- a/src/main/java/dev/blachut/svelte/lang/psi/SvelteTagElementTypes.kt +++ b/src/main/java/dev/blachut/svelte/lang/psi/SvelteTagElementTypes.kt @@ -60,6 +60,7 @@ object SvelteTagElementTypes { } if (builder.tokenType === JSTokenTypes.LPAR) { + val keyExpressionMarker = builder.mark() builder.advanceLexer() parser.expressionParser.parseExpression() @@ -68,6 +69,7 @@ object SvelteTagElementTypes { } else { builder.error(") expected") } + keyExpressionMarker.done(TAG_DEPENDENT_EXPRESSION) } } } @@ -88,6 +90,11 @@ object SvelteTagElementTypes { parser.expressionParser.parseDestructuringElement(SvelteJSElementTypes.PARAMETER, false, false) } + + if (builder.tokenType === SvelteTokenTypes.CATCH_KEYWORD) { + builder.advanceLexer() + parser.expressionParser.parseDestructuringElement(SvelteJSElementTypes.PARAMETER, false, false) + } } } @@ -117,6 +124,8 @@ object SvelteTagElementTypes { } } + val TAG_DEPENDENT_EXPRESSION = SvelteJSElementType("TAG_DEPENDENT_EXPRESSION") + val IF_END = SvelteJSElementType("IF_END") val EACH_END = SvelteJSElementType("EACH_END") val AWAIT_END = SvelteJSElementType("AWAIT_END") diff --git a/src/main/java/dev/blachut/svelte/lang/psi/SvelteTokenTypes.kt b/src/main/java/dev/blachut/svelte/lang/psi/SvelteTokenTypes.kt index a0aec3b5..7ce502db 100644 --- a/src/main/java/dev/blachut/svelte/lang/psi/SvelteTokenTypes.kt +++ b/src/main/java/dev/blachut/svelte/lang/psi/SvelteTokenTypes.kt @@ -36,5 +36,21 @@ object SvelteTokenTypes { @JvmField val CATCH_KEYWORD: IElementType = JSTokenTypes.CATCH_KEYWORD - val KEYWORDS = TokenSet.create(IF_KEYWORD, ELSE_KEYWORD, EACH_KEYWORD, AS_KEYWORD, AWAIT_KEYWORD, THEN_KEYWORD, CATCH_KEYWORD) + @JvmField + val HTML_KEYWORD = JSKeywordElementType("html") + + @JvmField + val DEBUG_KEYWORD = JSKeywordElementType("debug") + + val KEYWORDS = TokenSet.create( + IF_KEYWORD, + ELSE_KEYWORD, + EACH_KEYWORD, + AS_KEYWORD, + AWAIT_KEYWORD, + THEN_KEYWORD, + CATCH_KEYWORD, + HTML_KEYWORD, + DEBUG_KEYWORD + ) } diff --git a/src/main/java/dev/blachut/svelte/lang/psi/blocks/SvelteBranch.kt b/src/main/java/dev/blachut/svelte/lang/psi/blocks/SvelteBranch.kt index 632f84ab..25b126ff 100644 --- a/src/main/java/dev/blachut/svelte/lang/psi/blocks/SvelteBranch.kt +++ b/src/main/java/dev/blachut/svelte/lang/psi/blocks/SvelteBranch.kt @@ -8,6 +8,7 @@ import com.intellij.lang.javascript.psi.util.JSDestructuringVisitor import com.intellij.psi.PsiElement import com.intellij.psi.ResolveState import com.intellij.psi.scope.PsiScopeProcessor +import com.intellij.psi.util.contextOfType import dev.blachut.svelte.lang.psi.SveltePsiElement import dev.blachut.svelte.lang.psi.SvelteTag @@ -16,8 +17,13 @@ sealed class SvelteBranch(node: ASTNode) : SveltePsiElement(node), JSElement { val fragment get() = lastChild as SvelteFragment @Suppress("UNUSED_PARAMETER") - protected fun processParameterDeclarations(processor: PsiScopeProcessor, state: ResolveState, lastParent: PsiElement?, place: PsiElement): Boolean { - if (lastParent != null) { + protected fun processParameterDeclarations( + processor: PsiScopeProcessor, + state: ResolveState, + lastParent: PsiElement?, + place: PsiElement + ): Boolean { + if (lastParent != null && (lastParent != tag || place.contextOfType() != null)) { var result = true tag.acceptChildren(object : JSDestructuringVisitor() { override fun visitJSParameter(node: JSParameter) { diff --git a/src/main/java/dev/blachut/svelte/lang/psi/blocks/SvelteTagDependentExpression.kt b/src/main/java/dev/blachut/svelte/lang/psi/blocks/SvelteTagDependentExpression.kt new file mode 100644 index 00000000..4f001ffc --- /dev/null +++ b/src/main/java/dev/blachut/svelte/lang/psi/blocks/SvelteTagDependentExpression.kt @@ -0,0 +1,7 @@ +package dev.blachut.svelte.lang.psi.blocks + +import com.intellij.lang.ASTNode +import com.intellij.lang.javascript.psi.JSElement +import dev.blachut.svelte.lang.psi.SveltePsiElement + +class SvelteTagDependentExpression(node: ASTNode) : SveltePsiElement(node), JSElement diff --git a/src/test/java/dev/blachut/svelte/lang/codeInsight/SvelteResolveTest.kt b/src/test/java/dev/blachut/svelte/lang/codeInsight/SvelteResolveTest.kt new file mode 100644 index 00000000..b4a18546 --- /dev/null +++ b/src/test/java/dev/blachut/svelte/lang/codeInsight/SvelteResolveTest.kt @@ -0,0 +1,96 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package dev.blachut.svelte.lang.codeInsight + +import com.intellij.lang.javascript.psi.JSTagEmbeddedContent +import com.intellij.psi.util.contextOfType +import com.intellij.testFramework.fixtures.BasePlatformTestCase +import junit.framework.TestCase + +class SvelteResolveTest : BasePlatformTestCase() { + override fun getTestDataPath(): String = "src/test/resources" + override fun getBasePath(): String = "dev/blachut/svelte/lang/resolve" + + fun testBlock() { + myFixture.configureByText( + "Example.svelte", """ + + + {#await promise then value} +

{value}

+ {/await} + """.trimIndent() + ) + val reference = myFixture.getReferenceAtCaretPosition() + TestCase.assertNotNull(reference) + + val variable = reference!!.resolve() + TestCase.assertNotNull(variable) + TestCase.assertEquals(variable?.text, "value") + } + + fun testBranchIsolation() { + myFixture.configureByText( + "Example.svelte", """ + + + {#await promise then value} +

{value}

+ {:catch error} +

{value}

+ {/await} + """.trimIndent() + ) + val reference = myFixture.getReferenceAtCaretPosition() + TestCase.assertNotNull(reference) + + val variable = reference!!.resolve() + TestCase.assertNull(variable) + } + + fun testEachKeyExpression() { + myFixture.configureByText( + "Example.svelte", """ + + + {#each items as {name, id} (id)} +
{name}
+ {/each} + """.trimIndent() + ) + val reference = myFixture.getReferenceAtCaretPosition() + TestCase.assertNotNull(reference) + + val variable = reference!!.resolve() + TestCase.assertNotNull(variable) + TestCase.assertEquals(variable?.text, "id") + } + + fun testVariableShadowing() { + myFixture.configureByText( + "Example.svelte", """ + + + {#await then then then} +

{then}

+ {/await} + """.trimIndent() + ) + val reference = myFixture.getReferenceAtCaretPosition() + TestCase.assertNotNull(reference) + + val variable = reference!!.resolve() + TestCase.assertNotNull(variable) + TestCase.assertNotNull(variable!!.contextOfType()) + } +} diff --git a/src/test/java/dev/blachut/svelte/lang/editor/SvelteEditorTest.kt b/src/test/java/dev/blachut/svelte/lang/editor/SvelteEditorTest.kt index 8f64b9e6..d2405883 100644 --- a/src/test/java/dev/blachut/svelte/lang/editor/SvelteEditorTest.kt +++ b/src/test/java/dev/blachut/svelte/lang/editor/SvelteEditorTest.kt @@ -55,10 +55,22 @@ class SvelteEditorTest : BasePlatformTestCase() { } fun testFoldingSvelteTag() { - myFixture.testFoldingWithCollapseStatus(testDataPath + "/" + basePath + "/" + getTestName(false) + ".svelte") + doTestFolding() + } + + fun testFoldingEmptySvelteTag() { + doTestFolding() + } + + fun testFoldingSvelteTagInHtmlTag() { + doTestFolding() } fun testFoldingEditorFold() { + doTestFolding() + } + + private fun doTestFolding() { myFixture.testFoldingWithCollapseStatus(testDataPath + "/" + basePath + "/" + getTestName(false) + ".svelte") } } diff --git a/src/test/java/dev/blachut/svelte/lang/format/SvelteFormatterTest.kt b/src/test/java/dev/blachut/svelte/lang/format/SvelteFormatterTest.kt index 0ed1cd55..2692acf3 100644 --- a/src/test/java/dev/blachut/svelte/lang/format/SvelteFormatterTest.kt +++ b/src/test/java/dev/blachut/svelte/lang/format/SvelteFormatterTest.kt @@ -12,9 +12,15 @@ class SvelteFormatterTest : FormatterTestCase() { } fun testNoSvelteBlocks() = doTest() - fun testScriptContents() = doTest() fun testNestedBlocks() = doTest() fun testNestedBlocksFlat() = doTest() fun testIndentedExpressions() = doTest() fun testOneLineBlock() = doTest() + + fun testScriptContents() = doTest() + fun testScriptContentsSingleLine() = doTest() + fun testScriptStyleEmpty() = doTest() + + fun testMultilineExpression() = doTest() + fun todoMultilineProp() = doTest() } diff --git a/src/test/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlParserTest.kt b/src/test/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlParserTest.kt index 6399021f..f893b9fe 100644 --- a/src/test/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlParserTest.kt +++ b/src/test/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlParserTest.kt @@ -74,7 +74,6 @@ class SvelteHtmlParserTest : ParsingTestCase( fun testAttributeSpread() = doTest() fun testAttributeUnquoted() = doTest() - // TODO Support await...catch blocks fun testBlockAwaitCatch() = doTest() fun testBlockAwaitThenThenThen() = doTest() fun testBlockEachAmbiguousAs() = doTest() diff --git a/src/test/resources/dev/blachut/svelte/lang/editor/FoldingEmptySvelteTag.svelte b/src/test/resources/dev/blachut/svelte/lang/editor/FoldingEmptySvelteTag.svelte new file mode 100644 index 00000000..a8a17124 --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/editor/FoldingEmptySvelteTag.svelte @@ -0,0 +1,5 @@ +{#if name} + > + {name} + > +{:else}{/if} diff --git a/src/test/resources/dev/blachut/svelte/lang/editor/FoldingSvelteTagInHtmlTag.svelte b/src/test/resources/dev/blachut/svelte/lang/editor/FoldingSvelteTagInHtmlTag.svelte new file mode 100644 index 00000000..45cbad87 --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/editor/FoldingSvelteTagInHtmlTag.svelte @@ -0,0 +1,5 @@ +> + {#if test} + {test} + {/if} +> diff --git a/src/test/resources/dev/blachut/svelte/lang/format/EnterInEmptyBlock.svelte b/src/test/resources/dev/blachut/svelte/lang/format/EnterInEmptyBlock.svelte deleted file mode 100644 index 3f931b1d..00000000 --- a/src/test/resources/dev/blachut/svelte/lang/format/EnterInEmptyBlock.svelte +++ /dev/null @@ -1 +0,0 @@ -{#if x}{/if} diff --git a/src/test/resources/dev/blachut/svelte/lang/format/EnterInEmptyBlock_after.svelte b/src/test/resources/dev/blachut/svelte/lang/format/EnterInEmptyBlock_after.svelte deleted file mode 100644 index 25157421..00000000 --- a/src/test/resources/dev/blachut/svelte/lang/format/EnterInEmptyBlock_after.svelte +++ /dev/null @@ -1,3 +0,0 @@ -{#if x} - -{/if} diff --git a/src/test/resources/dev/blachut/svelte/lang/format/MultilineExpression.svelte b/src/test/resources/dev/blachut/svelte/lang/format/MultilineExpression.svelte new file mode 100644 index 00000000..a7ed11ef --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/format/MultilineExpression.svelte @@ -0,0 +1,4 @@ +{@debug { +field: true, + another: "hello" + }} diff --git a/src/test/resources/dev/blachut/svelte/lang/format/MultilineExpression_after.svelte b/src/test/resources/dev/blachut/svelte/lang/format/MultilineExpression_after.svelte new file mode 100644 index 00000000..cf6cb7df --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/format/MultilineExpression_after.svelte @@ -0,0 +1,4 @@ +{@debug { + field: true, + another: "hello" +}} diff --git a/src/test/resources/dev/blachut/svelte/lang/format/MultilineProp.svelte b/src/test/resources/dev/blachut/svelte/lang/format/MultilineProp.svelte new file mode 100644 index 00000000..cdc8d1c9 --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/format/MultilineProp.svelte @@ -0,0 +1,6 @@ + diff --git a/src/test/resources/dev/blachut/svelte/lang/format/MultilineProp_after.svelte b/src/test/resources/dev/blachut/svelte/lang/format/MultilineProp_after.svelte new file mode 100644 index 00000000..5c9640c9 --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/format/MultilineProp_after.svelte @@ -0,0 +1,6 @@ + diff --git a/src/test/resources/dev/blachut/svelte/lang/format/ScriptContentsSingleLine.svelte b/src/test/resources/dev/blachut/svelte/lang/format/ScriptContentsSingleLine.svelte new file mode 100644 index 00000000..1d730f4d --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/format/ScriptContentsSingleLine.svelte @@ -0,0 +1,2 @@ + diff --git a/src/test/resources/dev/blachut/svelte/lang/format/ScriptContentsSingleLine_after.svelte b/src/test/resources/dev/blachut/svelte/lang/format/ScriptContentsSingleLine_after.svelte new file mode 100644 index 00000000..13b84177 --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/format/ScriptContentsSingleLine_after.svelte @@ -0,0 +1,3 @@ + diff --git a/src/test/resources/dev/blachut/svelte/lang/format/ScriptStyleEmpty.svelte b/src/test/resources/dev/blachut/svelte/lang/format/ScriptStyleEmpty.svelte new file mode 100644 index 00000000..d28d7a20 --- /dev/null +++ b/src/test/resources/dev/blachut/svelte/lang/format/ScriptStyleEmpty.svelte @@ -0,0 +1,4 @@ + + +'') @@ -15,7 +15,7 @@ SvelteHtmlFile: Expression.svelte PsiElement(JS:LBRACE)('{') PsiWhiteSpace(' ') PsiElement(JS:AT)('@') - PsiElement(JS:IDENTIFIER)('html') + PsiElement(JS:HTML_KEYWORD)('html') PsiWhiteSpace(' ') JSLiteralExpression PsiElement(JS:STRING_LITERAL)(''hello'') @@ -28,7 +28,7 @@ SvelteHtmlFile: Expression.svelte PsiErrorElement:whitespace is not allowed after @ PsiWhiteSpace(' ') - PsiElement(JS:IDENTIFIER)('html') + PsiElement(JS:HTML_KEYWORD)('html') PsiWhiteSpace(' ') JSLiteralExpression PsiElement(JS:STRING_LITERAL)(''hello'') @@ -37,7 +37,7 @@ SvelteHtmlFile: Expression.svelte SvelteJS: CONTENT_EXPRESSION PsiElement(JS:LBRACE)('{') PsiElement(JS:AT)('@') - PsiElement(JS:IDENTIFIER)('debug') + PsiElement(JS:DEBUG_KEYWORD)('debug') PsiWhiteSpace(' ') JSReferenceExpression PsiElement(JS:IDENTIFIER)('x') @@ -47,7 +47,7 @@ SvelteHtmlFile: Expression.svelte PsiElement(JS:LBRACE)('{') PsiWhiteSpace(' ') PsiElement(JS:AT)('@') - PsiElement(JS:IDENTIFIER)('debug') + PsiElement(JS:DEBUG_KEYWORD)('debug') PsiWhiteSpace(' ') JSReferenceExpression PsiElement(JS:IDENTIFIER)('x') @@ -60,7 +60,7 @@ SvelteHtmlFile: Expression.svelte PsiErrorElement:whitespace is not allowed after @ PsiWhiteSpace(' ') - PsiElement(JS:IDENTIFIER)('debug') + PsiElement(JS:DEBUG_KEYWORD)('debug') PsiWhiteSpace(' ') JSReferenceExpression PsiElement(JS:IDENTIFIER)('x') @@ -74,4 +74,4 @@ SvelteHtmlFile: Expression.svelte PsiWhiteSpace(' ') JSReferenceExpression PsiElement(JS:IDENTIFIER)('x') - PsiElement(JS:RBRACE)('}') \ No newline at end of file + PsiElement(JS:RBRACE)('}')