<|start_filename|>src/main/java/dev/blachut/svelte/lang/directives/SvelteDirectiveTypes.kt<|end_filename|>
package dev.blachut.svelte.lang.directives

import dev.blachut.svelte.lang.directives.SvelteDirectiveUtil.DirectiveTarget
import dev.blachut.svelte.lang.isSvelteComponentTag
import dev.blachut.svelte.lang.psi.SvelteJSLazyElementTypes


@Suppress("MemberVisibilityCanBePrivate")
object SvelteDirectiveTypes {
    val BIND = DirectiveType(
        prefix = "bind",
        target = DirectiveTarget.BOTH,
        shorthandReferenceFactory = ::ScopeReference,
        shorthandCompletionFactory = ::getPropCompletions,
        longhandReferenceFactory = ::PropReference,
        longhandCompletionFactory = ::getPropCompletions,
        valueElementType = SvelteJSLazyElementTypes.ATTRIBUTE_EXPRESSION // TODO only variable references
    )
    val ON = DirectiveType(
        prefix = "on",
        target = DirectiveTarget.BOTH,
        modifiers = setOf(
            "preventDefault",
            "stopPropagation",
            "capture",
            "once",
            "self",
            "passive",
            "nonpassive",
            "trusted",
        ),
        shorthandReferenceFactory = ::EventReference,
        shorthandCompletionFactory = ::getEventCompletions,
        longhandReferenceFactory = ::EventReference,
        longhandCompletionFactory = ::getEventCompletions,
    )
    val CLASS = DirectiveType(
        prefix = "class",
        target = DirectiveTarget.ELEMENT,
        shorthandReferenceFactory = ::ScopeAndClassReference,
        shorthandCompletionFactory = ::getScopeCompletions,
        longhandReferenceFactory = ::getClassReference,
        longhandCompletionFactory = ::getClassCompletions,
    )
    val USE = DirectiveType(
        prefix = "use",
        target = DirectiveTarget.ELEMENT,
        nestedSpecifiers = 1,
        shorthandReferenceFactory = ::ScopeReference,
        shorthandCompletionFactory = ::getScopeCompletions,
        longhandReferenceFactory = ::ScopeReference,
        longhandCompletionFactory = ::getScopeCompletions,
    )
    val TRANSITION = TransitionInOutDirectiveType(prefix = "transition")
    val IN = TransitionInOutDirectiveType(prefix = "in")
    val OUT = TransitionInOutDirectiveType(prefix = "out")
    val ANIMATE = DirectiveType(
        prefix = "animate",
        target = DirectiveTarget.ELEMENT,
        targetValidator = { true }, // TODO only directly in keyed each
        shorthandReferenceFactory = ::ScopeReference,
        shorthandCompletionFactory = ::getScopeCompletions,
        longhandReferenceFactory = ::ScopeReference,
        longhandCompletionFactory = ::getScopeCompletions,
    )
    val LET = DirectiveType(
        prefix = "let",
        target = DirectiveTarget.BOTH,
        targetValidator = { isSvelteComponentTag(it.name) || it.getAttributeValue("slot") != null },
        valueElementType = SvelteJSLazyElementTypes.ATTRIBUTE_PARAMETER,
        shorthandReferenceFactory = ::ShorthandLetReference,
        shorthandCompletionFactory = null,
        longhandReferenceFactory = null,
        longhandCompletionFactory = null,
    )

    val ALL = setOf(BIND, ON, CLASS, USE, TRANSITION, IN, OUT, ANIMATE, LET)
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/SvelteJSAnalysisHandlersFactory.kt<|end_filename|>
// 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

import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.lang.ecmascript6.validation.ES6AnalysisHandlersFactory
import com.intellij.lang.ecmascript6.validation.ES6AnnotatingVisitor
import com.intellij.lang.javascript.DialectOptionHolder
import com.intellij.lang.javascript.psi.JSLabeledStatement
import com.intellij.lang.javascript.validation.JSAnnotatingVisitor
import com.intellij.lang.javascript.validation.JSKeywordHighlighterVisitor
import com.intellij.lang.javascript.validation.JSProblemReporter
import com.intellij.lang.javascript.validation.JSReferenceChecker
import com.intellij.psi.PsiElement
import dev.blachut.svelte.lang.codeInsight.SvelteJSReferenceChecker
import dev.blachut.svelte.lang.codeInsight.SvelteReactiveDeclarationsUtil

class SvelteJSAnalysisHandlersFactory : ES6AnalysisHandlersFactory() {
    override fun getReferenceChecker(reporter: JSProblemReporter<*>): JSReferenceChecker {
        return SvelteJSReferenceChecker(reporter)
    }

    override fun createKeywordHighlighterVisitor(
        holder: HighlightInfoHolder,
        dialectOptionHolder: DialectOptionHolder
    ): JSKeywordHighlighterVisitor {
        return SvelteKeywordHighlighterVisitor(holder)
    }

    override fun createAnnotatingVisitor(psiElement: PsiElement, holder: AnnotationHolder): JSAnnotatingVisitor {
        return object : ES6AnnotatingVisitor(psiElement, holder) {
            override fun visitJSLabeledStatement(node: JSLabeledStatement) {
                if (node.label != SvelteReactiveDeclarationsUtil.REACTIVE_LABEL) {
                    super.visitJSLabeledStatement(node)
                }
            }
        }
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/spellchecker/SvelteSpellcheckingDictionaryProvider.kt<|end_filename|>
// 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.spellchecker

import com.intellij.spellchecker.BundledDictionaryProvider

class SvelteSpellcheckingDictionaryProvider : BundledDictionaryProvider {
  override fun getBundledDictionaries(): Array<String> = arrayOf("svelte.dic")
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/completion/SvelteReactiveDeclarationsCompletionProvider.kt<|end_filename|>
package dev.blachut.svelte.lang.completion

import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionProvider
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.lang.javascript.completion.JSCompletionUtil
import com.intellij.lang.javascript.completion.JSReferenceCompletionProvider
import com.intellij.lang.javascript.psi.JSEmbeddedContent
import com.intellij.lang.javascript.psi.JSReferenceExpression
import com.intellij.lang.javascript.psi.impl.JSReferenceExpressionImpl
import com.intellij.lang.javascript.psi.resolve.CompletionResultSink
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil
import com.intellij.psi.util.parentOfType
import com.intellij.util.ProcessingContext
import dev.blachut.svelte.lang.codeInsight.SvelteReactiveDeclarationsUtil
import dev.blachut.svelte.lang.psi.SvelteJSEmbeddedContentImpl

class SvelteReactiveDeclarationsCompletionProvider: CompletionProvider<CompletionParameters>() {
    override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
        val parent = parameters.position.parent
        assert(parent is JSReferenceExpression)
        if (JSReferenceCompletionProvider.skipReferenceCompletionByContext(parameters.position)) return
        val referenceExpression = parent as JSReferenceExpression

        if (referenceExpression.hasQualifier()) return
        if (referenceExpression.parentOfType<JSEmbeddedContent>() is SvelteJSEmbeddedContentImpl) {
            // only complete in Svelte expressions and blocks,
            return
        }

        calcReactiveVariants(referenceExpression, parameters, result)
    }

    // based on JSReferenceCompletionUtil.calcDefaultVariants
    private fun calcReactiveVariants(expression: JSReferenceExpression, parameters: CompletionParameters, resultSet: CompletionResultSet) {
        val parent = expression.parent
        if (JSResolveUtil.isSelfReference(parent, expression)) {
            return  // Prevent Rulezz to appear
        }
        val sink = CompletionResultSink(expression, resultSet.prefixMatcher, emptySet(), !parameters.isExtendedCompletion, false)
        // custom processor is crucial for reactive declaration completions. Would be best to make JS core customisable.
        val localProcessor = SvelteReactiveDeclarationsUtil.SvelteSinkResolveProcessor(sink.name, sink.place!!, sink)
        JSReferenceExpressionImpl.doProcessLocalDeclarations(expression, null, localProcessor, false, true, null)
        val results = sink.resultsAsObjects
        if (results.isNotEmpty()) {
            // results will contain everything in scope, later LookupElement duplicates from JS core get merged
            JSCompletionUtil.pushVariants(results, emptySet(), resultSet)
        }
    }
}


<|start_filename|>src/test/java/dev/blachut/svelte/lang/codeInsight/SvelteCompletionTest.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.fixtures.BasePlatformTestCase

class SvelteCompletionTest : BasePlatformTestCase() {
    fun testContextModuleAttribute() {
        myFixture.configureByText("foo.svelte", "<script c<caret>></script>")
        hasElements(myFixture.completeBasic(), "context=\"module\"")
    }

    fun testExistContextAttribute() {
        myFixture.configureByText("foo.svelte", "<script context=\"module\" c<caret>></script>")
        noElements(myFixture.completeBasic(), "context=\"module\"")
    }

    fun testStyleLang() {
        myFixture.configureByText("foo.svelte", "<style lang=\"<caret>\"></style>")
        hasElements(myFixture.completeBasic(), "css")
    }

    fun testSimpleTagAttribute() {
        myFixture.configureByText("foo.svelte", "<div c<caret>></div>")
        noElements(myFixture.completeBasic(), "context=\"module\"")
    }

    fun testSimpleTag() {
        myFixture.configureByText("Hello.svelte", "<h1>Test</h1>")
        myFixture.configureByText("foo.svelte", "<<caret>")
        hasElements(myFixture.completeBasic(), "div", "main", "svelte:body", "svelte:self")
    }

    fun testSimpleTagNested() {
        myFixture.configureByText("Hello.svelte", "<h1>Test</h1>")
        myFixture.configureByText("foo.svelte", "<div><<caret></div>")
        hasElements(myFixture.completeBasic(), "div", "svelte:body", "svelte:self")
    }

    fun testSimpleSvelteNamespace() {
        myFixture.configureByText("Hello.svelte", "<h1>Test</h1>")
        myFixture.configureByText("foo.svelte", "<svelte:<caret>")
        hasElements(myFixture.completeBasic(), "body", "self")
    }

    fun testComponentImportNoScript() {
        myFixture.configureByText("Hello.svelte", "<h1>Test</h1>")
        myFixture.configureByText("Usage.svelte", "<h1><Hel<caret></h1>")
        myFixture.completeBasic()
        myFixture.checkResult(
            """
                <script>
                    import Hello from "./Hello.svelte";
                </script>

                <h1><Hello</h1>
                """.trimIndent())
    }

    fun testComponentImportWithScript() {
        myFixture.configureByText("Hello.svelte", "<h1>Test</h1>")
        myFixture.configureByText("Usage.svelte",
            """
                <h1><Hel<caret></h1>
                <script>
                    console.log("hello")
                </script>
                """.trimIndent())
        myFixture.completeBasic()
        myFixture.checkResult(
            """
                <h1><Hello</h1>
                <script>
                    import Hello from "./Hello.svelte";

                    console.log("hello")
                </script>
                """.trimIndent())
    }

    fun testComponentsTopLevel() {
        myFixture.configureByText("Hello1.svelte", "<h1>Test</h1>")
        myFixture.configureByText("Hello2.svelte", "<h1>Test</h1>")
        myFixture.configureByText("Usage.svelte", "<Hel<caret>")
        hasElements(myFixture.completeBasic(), "Hello1", "Hello2")
    }

    fun testComponentExportedAttributes() {
        myFixture.configureByText("Hello1.svelte",
            """
                <script>
                    export let hello11;
                    let hello11NotAvailable = 10;
                </script>
                """.trimIndent())
        myFixture.configureByText("Hello2.svelte",
            """
                <script>
                    export let hello22;
                    let hello22NotAvailable = 10;
                </script>
                """.trimIndent())

        myFixture.configureByText("Usage.svelte",
                                  """
                <script>
                    import Hello1 from "./Hello1.svelte";
                </script>
                <Hello1 <caret>
                """.trimIndent())
        val items = myFixture.completeBasic()
        hasElements(items, "slot", "hello11")
        noElements(items, "hello11NotAvailable", "hello22NotAvailable", "hello22")
    }

    fun testReactiveStatement() {
        myFixture.configureByText("Test.svelte", """
            <script>
                export let hello11 = "src"
                let hello11Local = "src";
                $: hello11Reactive = 20;

                $: true && hello<caret>
            </script>
        """.trimIndent())
        val items = myFixture.completeBasic()
        hasElements(items, "hello11", "hello11Local", "hello11Reactive")
        UsefulTestCase.assertSize(3, items)
    }

    fun testInterpolation() {
        myFixture.configureByText("Test.svelte", """
            <script>
                export let hello11;
                let hello11Local = 10;
                $: hello11Reactive = 20;
            </script>
            <div>{h<caret>}</div>
        """.trimIndent())
        val items = myFixture.completeBasic()
        hasElements(items, "hello11", "hello11Local", "hello11Reactive")
    }

    fun testInterpolationInAttribute() {
        myFixture.configureByText("Test.svelte", """
            <script>
                export let hello11 = "src"
                let hello11Local = "src";
                $: hello11Reactive = 20;
            </script>
            <img src={hello<caret>}>
        """.trimIndent())
        val items = myFixture.completeBasic()
        hasElements(items, "hello11", "hello11Local", "hello11Reactive")
        UsefulTestCase.assertSize(3, items)
    }

    fun testKeywords() {
        myFixture.configureByText("Test.svelte", """
             <div>{<caret>}</div>
        """.trimIndent())
        val items = myFixture.completeBasic()
        hasElements(items, "#if", "#await", "@html")
    }

    fun testKeywords2() {
        myFixture.configureByText("Test.svelte", """
             <div>{#aw<caret>}</div>
        """.trimIndent())
        val items = myFixture.completeBasic()
        assertNull(items)
        myFixture.checkResult("""
             <div>{#await <caret>}</div>
        """.trimIndent())
    }

    fun testMustache() {
        myFixture.configureByText("Hello.svelte",
            """
                <script></script>
                {#if <caret>}
                {/if}
                """.trimIndent()
        )
        hasElements(myFixture.completeBasic(), "true", "false")
    }

    fun testDirectivesOnElement() {
        myFixture.configureByText("Usage.svelte", "<h1 <caret>>Test</h1>")
        val items = myFixture.completeBasic()
        hasElements(items, "bind:", "on:", "class:", "use:", "transition:", "in:", "out:", "animate:", "let:")
    }

    fun testDirectivesOnComponent() {
        myFixture.configureByText("Usage.svelte", "<Box <caret>>Test</Box>")
        val items = myFixture.completeBasic()
        hasElements(items, "bind:", "on:", "let:")
        noElements(items, "class:", "use:", "transition:", "in:", "out:", "animate:")
    }

    fun testBindDirective() {
        myFixture.configureByText("Usage.svelte",
            """
                <script>
                    let localVariable = "localVariableValue"
                </script>
                <h1 bind:<caret>>Hello</h1>
                """.trimIndent()
        )
        val items = myFixture.completeBasic()
        hasElements(items, "title")
        noElements(items, "onclick", "localVariable")
    }

    fun testOnDirective() {
        myFixture.configureByText("Usage.svelte",
            """
                <script>
                    let localVariable = "localVariableValue"
                </script>
                <h1 on:<caret>>Hello</h1>
                """.trimIndent()
        )
        val items = myFixture.completeBasic()
        hasElements(items, "click")
        noElements(items, "onclick", "localVariable")
    }

    fun testClassDirective() {
        myFixture.configureByText("Usage.svelte",
            """
                <script>
                    let localVariable = "localVariableValue"
                </script>
                <h1 class:<caret>>Hello</h1>
                <style>
                    .localClass {}
                </style>
                """.trimIndent()
        )
        val items = myFixture.completeBasic()
        hasElements(items, "localVariable", "localClass")
    }

    fun testUseDirective() {
        myFixture.configureByText("Usage.svelte",
            """
                <script>
	                function action() {}
                </script>
                <h1 use:<caret>>Hello</h1>
                """.trimIndent()
        )
        val items = myFixture.completeBasic()
        hasElements(items, "action")
    }

    fun testTransitionDirective() {
        myFixture.configureByText("Usage.svelte",
            """
                <script>
	                function fade() {}
                </script>
                <h1 transition:<caret>>Hello</h1>
                """.trimIndent()
        )
        val items = myFixture.completeBasic()
        hasElements(items, "fade")
    }

    fun testInDirective() {
        myFixture.configureByText("Usage.svelte",
            """
                <script>
	                function fade() {}
                </script>
                <h1 in:<caret>>Hello</h1>
                """.trimIndent()
        )
        val items = myFixture.completeBasic()
        hasElements(items, "fade")
    }

    fun testOutDirective() {
        myFixture.configureByText("Usage.svelte",
            """
                <script>
	                function fade() {}
                </script>
                <h1 out:<caret>>Hello</h1>
                """.trimIndent()
        )
        val items = myFixture.completeBasic()
        hasElements(items, "fade")
    }

    fun testAnimateDirective() {
        myFixture.configureByText("Usage.svelte",
            """
                <script>
	                function fade() {}
                </script>
                <h1 animate:<caret>>Hello</h1>
                """.trimIndent()
        )
        val items = myFixture.completeBasic()
        hasElements(items, "fade")
    }

    fun testPrefetchAttribute() {
        myFixture.configureByText("Test.svelte", """
            <a svp<caret>>Hello</a>
        """.trimIndent())
        myFixture.completeBasic()
        myFixture.checkResult("""
            <a sveltekit:prefetch<caret>>Hello</a>
        """.trimIndent())
    }

    private fun checkElements(items: Array<LookupElement>, expected: Boolean, vararg variants: String) {
        val toCheck = setOf(*variants)
        val matched = mutableSetOf<String>()
        for (e in items) {
            val lookupString = e.lookupString
            if (toCheck.contains(lookupString)) {
                matched.add(lookupString)
            }
            if (matched.size == variants.size) break
        }
        if (expected) {
            UsefulTestCase.assertTrue("Actual: ${items.map { it.lookupString }}", matched.size == variants.size)
        }
        else {
            UsefulTestCase.assertTrue("Actual: ${items.map { it.lookupString }}", matched.isEmpty())
        }
    }

    private fun hasElements(items: Array<LookupElement>, vararg variants: String) {
        checkElements(items, true, *variants)
    }

    private fun noElements(items: Array<LookupElement>, vararg variants: String) {
        checkElements(items, false, *variants)
    }

}


<|start_filename|>src/test/java/dev/blachut/svelte/lang/codeInsight/SvelteTypeScriptHighlightingTest.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.testFramework.fixtures.BasePlatformTestCase

class SvelteTypeScriptHighlightingTest : BasePlatformTestCase() {
    override fun getTestDataPath(): String = "src/test/resources"
    override fun getBasePath(): String = "dev/blachut/svelte/lang/codeInsight/tsHighlighting"

    override fun setUp() {
        super.setUp()
        myFixture.enableInspections(*SvelteHighlightingTest.configureDefaultLocalInspectionTools().toTypedArray())
    }

    fun testSimpleWithType() {
        myFixture.configureByText(
            "Usage.svelte",
            """
                <script lang="ts">
                    export let testName: string;
                    console.log(${"$$"}props);
                    declare let test:number;
                    if (test) {
                        test = <error>"hello"</error>;
                    } else {
                        test = 2;
                    }
                    console.log(test);
                </script>
                <title>{testName}</title>
                """.trimIndent()
        )
        myFixture.testHighlighting()
    }

    fun testSimpleImport() {
        myFixture.configureByText("hel.ts", "export const count = 1")
        myFixture.configureByText(
            "Usage.svelte",
            """
                <script lang="ts">
                    import {count} from "./hel";

                    ${"$"}: ${"$"}count && console.log('changed');
                </script>
                <title>test</title>
                """.trimIndent()
        )
        myFixture.testHighlighting()
    }

    fun testTsconfigDirectAncestorDiscovery() {
        val base = basePath + "/" + getTestName(true)
        myFixture.configureByFiles("$base/src/App.svelte", "$base/tsconfig.json")

        myFixture.testHighlighting()
    }

    fun testTsconfigIndirectAncestorDiscovery() {
        val base = basePath + "/" + getTestName(true)
        myFixture.configureByFiles("$base/src/nested/Child.svelte", "$base/tsconfig.json")

        myFixture.testHighlighting()
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteTagProvider.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.PrioritizedLookupElement
import com.intellij.codeInsight.completion.XmlTagInsertHandler
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.lang.javascript.completion.JSImportCompletionUtil
import com.intellij.lang.javascript.frameworks.react.ReactXmlExtension
import com.intellij.lang.javascript.modules.JSImportPlaceInfo
import com.intellij.lang.javascript.modules.imports.JSImportCandidate
import com.intellij.lang.javascript.modules.imports.providers.JSImportCandidatesProvider
import com.intellij.lang.javascript.psi.JSDefinitionExpression
import com.intellij.lang.javascript.psi.JSExpressionCodeFragment
import com.intellij.lang.javascript.psi.JSNamedElement
import com.intellij.lang.javascript.psi.ecma6.TypeScriptCompileTimeType
import com.intellij.lang.javascript.psi.ecmal4.JSClass
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil
import com.intellij.lang.javascript.psi.resolve.ResolveProcessor
import com.intellij.lang.javascript.psi.resolve.processors.JSResolveProcessor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.NlsSafe
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.ResolveState
import com.intellij.psi.impl.source.xml.XmlElementDescriptorProvider
import com.intellij.psi.xml.XmlTag
import com.intellij.xml.XmlElementDescriptor
import com.intellij.xml.XmlTagNameProvider
import dev.blachut.svelte.lang.icons.SvelteIcons
import dev.blachut.svelte.lang.isSvelteComponentTag
import dev.blachut.svelte.lang.psi.SvelteHtmlTag
import java.util.function.Predicate

// Vue plugin uses 100, it's ok for now
const val highPriority = 100.0
const val mediumPriority = 50.0

const val svelteNamespace = "svelte"
const val sveltePrefix = "$svelteNamespace:"

val svelteTagNames = arrayOf("self", "component", "window", "body", "head", "options", "fragment")

// TODO Merge with svelteBareTagLookupElements
// TODO Use XmlTagInsertHandler
val svelteNamespaceTagLookupElements = svelteTagNames.map {
    LookupElementBuilder.create(sveltePrefix + it).withIcon(SvelteIcons.GRAY)
}

val slotLookupElement: LookupElementBuilder = LookupElementBuilder.create("slot").withIcon(SvelteIcons.GRAY)
    .withInsertHandler(XmlTagInsertHandler.INSTANCE)

/**
 * When user auto completes after writing colon in "svelte", editor will produce i.e. "svelte:svelte:self".
 */
val svelteBareTagLookupElements = svelteTagNames.map {
    val lookupElement = LookupElementBuilder.create(it).withIcon(SvelteIcons.GRAY)
    PrioritizedLookupElement.withPriority(lookupElement, mediumPriority)
}

/**
 * interface XmlTagNameProvider feeds data for name completion popup
 *
 * interface XmlElementDescriptorProvider enables, among others, navigation from tag to component file
 */
class SvelteTagProvider : XmlElementDescriptorProvider, XmlTagNameProvider {
    override fun getDescriptor(tag: XmlTag): XmlElementDescriptor? {
        if (tag !is SvelteHtmlTag) return null

        if (tag.namespacePrefix == svelteNamespace && svelteTagNames.contains(tag.localName) || tag.name == "slot") {
            return SvelteElementDescriptor(tag)
        }

        if (!isSvelteComponentTag(tag.name)) return null

        return SvelteComponentTagDescriptor(tag.name, tag)
    }

    override fun addTagNameVariants(resultElements: MutableList<LookupElement>, tag: XmlTag, namespacePrefix: String) {
        if (tag !is SvelteHtmlTag) return

        if (svelteNamespace == namespacePrefix) {
            resultElements.addAll(svelteBareTagLookupElements)
        }
        else if (namespacePrefix.isEmpty()) {
            resultElements.addAll(svelteNamespaceTagLookupElements)
            resultElements.add(slotLookupElement)

            val localNames = mutableSetOf<String>()
            addLocalComponents(tag, localNames, resultElements)
            addExportedComponents(tag, localNames, resultElements)
        }
    }

    private fun addLocalComponents(tag: XmlTag, localNames: MutableSet<String>, resultElements: MutableList<LookupElement>) {
        val processor = createLocalCompletionProcessor(localNames, resultElements)
        JSResolveUtil.treeWalkUp(processor, tag, null, tag)
    }

    // based on ReactComponentCompletionContributor.createCompletionProcessor, maybe unify it?
    private fun createLocalCompletionProcessor(collectedNames: MutableSet<String>,
                                               resultElements: MutableList<LookupElement>): JSResolveProcessor {
        return object : JSResolveProcessor {
            override fun getName(): String? {
                return null
            }

            override fun execute(element: PsiElement, state: ResolveState): Boolean {
                // omit reactive declarations
                if (element is JSDefinitionExpression) return true

                val name = ResolveProcessor.getName(element) ?: return true
                if (isSvelteComponentTag(name) && !collectedNames.contains(name) /*&& prefixMatcher.prefixMatches(name)*/) {
                    collectedNames.add(name)
                    for (declaration in ReactXmlExtension.getElementsByImport(element)) {
                        ProgressManager.checkCanceled()
                        val isComponent = true // todo filter by type, after *.svelte type is evaluated correctly
                        if (isComponent) {
                            resultElements.add(createLookupElement(name, declaration, null))
                            break
                        }
                    }
                }
                return true
            }
        }
    }

    private fun addExportedComponents(tag: SvelteHtmlTag, localNames: MutableSet<String>, resultElements: MutableList<LookupElement>) {
        // todo possibly base on ReactComponentCompletionContributor.addExportedComponents
        // todo and/or JSImportCompletionUtil.processExportedElements
        // todo look into case sensitivity

        // todo handle multiple files with same name, JSImportCompletionUtil.IMPORT_PRIORITY, merge lookup elements etc

        val placeInfo = JSImportPlaceInfo(tag)

        //val lowercaseName = StringUtil.trimEnd(tag.name, CompletionUtil.DUMMY_IDENTIFIER_TRIMMED).toLowerCase()
        val keyFilter = Predicate { name: String ->
            isSvelteComponentTag(name) && !localNames.contains(name)
            // && name.toLowerCase().contains(lowercaseName)
            // && prefixMatcher.prefixMatches(name)
        }

        val providers = JSImportCandidatesProvider.getProviders(placeInfo)

        JSImportCompletionUtil.processExportedElements(tag, providers, keyFilter) { candidates: Collection<JSImportCandidate>, name ->
            var seenJSCandidate = false
            var seenSvelteCandidate = false
            var bestLookup: LookupElement? = null

            candidates.forEach { candidate ->
                if (seenJSCandidate) return@forEach

                val element = candidate.element // for Svelte files will be null, used by JSLookupElementRenderer to display useful info

                if (element != null) {
                    for (declaration in ReactXmlExtension.getElementsByImport(element)) {
                        // todo namespaced components will require loosening of the condition
                        if (!(declaration is JSClass && declaration !is TypeScriptCompileTimeType)) continue // or Svelte file
                        val recordType = declaration.jsType.asRecordType()
                        // older libraries may not contain all 3 properties
                        if (!recordType.propertyNames.containsAll(listOf("\$set", "\$on", "\$destroy"))) continue
                        val lookupElement = createLookupElement(name, declaration, createInsertHandler(tag.containingFile, candidate))
                        bestLookup = lookupElement
                        seenJSCandidate = true
                        break
                    }
                }
                else if (!seenSvelteCandidate) {
                    // JSImportCandidate for SvelteHtmlFile does not contain PsiElement
                    val lookupElement = createLookupElement(name, null, createInsertHandler(tag.containingFile, candidate))
                    seenSvelteCandidate = true
                    bestLookup = lookupElement
                }
            }

            if (bestLookup != null) {
                resultElements.add(bestLookup!!)
            }

            true
        }
    }

    private fun createLookupElement(name: @NlsSafe String,
                                    element: PsiElement?,
                                    insertHandler: InsertHandler<LookupElement>?): LookupElement {
        //val tailText = " (${candidate.containerText})"
        val presentation = if (element is JSNamedElement) element.presentation else null

        val builder = if (element != null) LookupElementBuilder.create(element, name) else LookupElementBuilder.create(name)
        val lookupElement = builder
            .withIcon(SvelteIcons.COLOR)
            //.withTailText(tailText, true)
            .withTailText(presentation?.locationString, true) // todo add space
            //.withTypeText("SvelteComponent") // can't use type text because there are false positives here
            .withInsertHandler(insertHandler)
            //.let { JSLookupElementRenderer(name, JSImportCompletionUtil.IMPORT_PRIORITY, false, null).applyToBuilder(it) } // does not resolve imports unfortunately, JSImportCompletionUtil.expandElements handles that
            .let { PrioritizedLookupElement.withPriority(it, highPriority) } // todo use proximity?

        return lookupElement
    }

    private fun createInsertHandler(containingFile: PsiFile, candidate: JSImportCandidate): InsertHandler<LookupElement>? {
        if (containingFile is JSExpressionCodeFragment) return null

        if (!candidate.useAutoImport()) return null

        // todo e.g. React uses both XmlTagInsertHandler & addReactImportInsertHandler
        return SvelteComponentInsertHandler(candidate)
    }
}

class SvelteComponentInsertHandler(private val candidate: JSImportCandidate) : InsertHandler<LookupElement> {
    override fun handleInsert(context: InsertionContext, item: LookupElement) {
        JSImportCompletionUtil.insertLookupItem(context, item, candidate, null)
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteXmlExtension.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.lang.ASTNode
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.xml.TagNameReference
import com.intellij.psi.xml.XmlTag
import com.intellij.xml.HtmlXmlExtension
import dev.blachut.svelte.lang.SvelteHTMLLanguage
import dev.blachut.svelte.lang.directives.SvelteDirectiveUtil
import dev.blachut.svelte.lang.directives.SvelteDirectiveUtil.DIRECTIVE_SEPARATOR
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 `<tag/>` is correct
     */
    override fun isSelfClosingTagAllowed(tag: XmlTag): Boolean {
        return isSvelteComponentTag(tag.name) || collapsibleTags.contains(tag.name) || super.isSelfClosingTagAllowed(tag)
    }

    /**
     * Whether should warn when writing `<tag></tag>` with empty body, with quick fix that replaces it with `<tag/>`
     */
    override fun isCollapsibleTag(tag: XmlTag): Boolean {
        return isSvelteComponentTag(tag.name) || super.isCollapsibleTag(tag)
    }

    /**
     * Whether tag follows stricter rules of XML. Single tags are e.g. <p> & <li>, they don't require closing tag
     */
    override fun isSingleTagException(tag: XmlTag): Boolean = isSvelteComponentTag(tag.name) || tag.name == "slot"

    override fun isRequiredAttributeImplicitlyPresent(tag: XmlTag, attrName: String): Boolean {
        for (attribute in tag.attributes) {
            if (attribute.name == attrName && attribute.nameElement.text[0] == '{') {
                return true
            }
        }
        return super.isRequiredAttributeImplicitlyPresent(tag, attrName)
    }

    override fun createTagNameReference(nameElement: ASTNode, startTagFlag: Boolean): TagNameReference? {
        return if (isSvelteComponentTag(nameElement.text)) {
            SvelteTagNameReference(nameElement, startTagFlag)
        }
        else {
            super.createTagNameReference(nameElement, startTagFlag)
        }
    }

    override fun getAttributeValuePresentation(
        tag: XmlTag?,
        attributeName: String,
        defaultAttributeQuote: String
    ): AttributeValuePresentation {
        if (attributeName == "slot") {
            return super.getAttributeValuePresentation(tag, attributeName, defaultAttributeQuote)
        }

        val prefix = if (attributeName.contains(DIRECTIVE_SEPARATOR)) attributeName.split(DIRECTIVE_SEPARATOR).firstOrNull() else null
        if (prefix != null && SvelteDirectiveUtil.directivePrefixes.contains(prefix)) {
            return object : AttributeValuePresentation {
                override fun getPrefix(): String = "{"
                override fun getPostfix(): String = "}"
            }
        }

        // non-directive attribute
        // TODO Return empty presentation after auto matching {} and ""
        return super.getAttributeValuePresentation(tag, attributeName, defaultAttributeQuote)
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/compatibility/SvelteHtmlInspectionSuppressor.kt<|end_filename|>
package dev.blachut.svelte.lang.compatibility

import com.intellij.codeInsight.daemon.impl.analysis.HtmlUnknownAnchorTargetInspection
import com.intellij.codeInsight.daemon.impl.analysis.HtmlUnknownTargetInspection
import com.intellij.codeInsight.daemon.impl.analysis.XmlUnboundNsPrefixInspection
import com.intellij.codeInspection.DefaultXmlSuppressionProvider
import com.intellij.codeInspection.htmlInspections.HtmlUnknownAttributeInspection
import com.intellij.codeInspection.htmlInspections.HtmlUnknownTagInspection
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.xml.XmlAttribute
import dev.blachut.svelte.lang.directives.SvelteDirectiveUtil
import dev.blachut.svelte.lang.equalsName
import dev.blachut.svelte.lang.isSvelteComponentTag
import dev.blachut.svelte.lang.isSvelteContext

class SvelteHtmlInspectionSuppressor : DefaultXmlSuppressionProvider() {
    private val scriptAttributes = listOf("context")
    private val styleAttributes = listOf("src", "global")
    private val legacyAAttributes = listOf("sapper:noscroll", "sapper:prefetch") // not descriptors, since we don't want completions

    override fun isProviderAvailable(file: PsiFile): Boolean {
        return isSvelteContext(file)
    }

    override fun isSuppressedFor(element: PsiElement, inspectionId: String): Boolean {
        if (inspectionId.equalsName<XmlUnboundNsPrefixInspection>()) {
            return true
        }

        if (inspectionId.equalsName<HtmlUnknownTargetInspection>() || inspectionId.equalsName<HtmlUnknownAnchorTargetInspection>()) {
            return true
        }

        if (inspectionId.equalsName<HtmlUnknownAttributeInspection>()) {
            val attribute = element.parent
            if (attribute is XmlAttribute) {
                if (SvelteDirectiveUtil.directivePrefixes.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
                if (attribute.parent.name == "a" && legacyAAttributes.contains(attribute.name)) return true
            }
        }

        if (inspectionId.equalsName<HtmlUnknownTagInspection>()) {
            if (isSvelteComponentTag(element.text)) {
                return true
            }
        }

        return super.isSuppressedFor(element, inspectionId)
    }
}


<|start_filename|>src/test/java/dev/blachut/svelte/lang/codeInsight/SvelteNavigationTest.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.psi.PsiFile
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import junit.framework.TestCase

class SvelteNavigationTest : BasePlatformTestCase() {
    fun testComponentNameJS() {
        myFixture.configureWidgetFile()
        myFixture.configureByText("Example.svelte", """
            <script>
                import Widget from "./Widget.svelte";
            </script>

            <Wid<caret>get />
        """.trimIndent())

        val prevCaretOffset = myFixture.caretOffset
        myFixture.performEditorAction(IdeActions.ACTION_GOTO_DECLARATION)
        // If navigation goes to different file, caret in initiating editor stays in place, otherwise it’s moved to declaration and the test fails.
        TestCase.assertEquals(prevCaretOffset, myFixture.caretOffset)
    }

    fun testComponentNameTS() {
        myFixture.configureWidgetFile()
        myFixture.configureByText("Example.svelte", """
            <script lang="ts">
                import Widget from "./Widget.svelte";
            </script>

            <Wid<caret>get />
        """.trimIndent())

        val prevCaretOffset = myFixture.caretOffset
        myFixture.performEditorAction(IdeActions.ACTION_GOTO_DECLARATION)
        // If navigation goes to different file, caret in initiating editor stays in place, otherwise it’s moved to declaration and the test fails.
        TestCase.assertEquals(prevCaretOffset, myFixture.caretOffset)

        // can be combined with to test e.g. component props JSTestUtils.getGotoDeclarationTarget(myFixture, expectedTargetFile)
    }

    private fun CodeInsightTestFixture.configureWidgetFile(): PsiFile {
        return configureByText("Widget.svelte", """
            <script>
                export let aProp = "";
            </script>
        """.trimIndent())
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/parsing/html/SvelteJSExpressionLexer.kt<|end_filename|>
package dev.blachut.svelte.lang.parsing.html

import com.intellij.lang.javascript.DialectOptionHolder
import com.intellij.lang.javascript.JSFlexAdapter
import com.intellij.lexer.LayeredLexer
import dev.blachut.svelte.lang.psi.SvelteTokenTypes

/**
 * Used to differentiate #if as Svelte tag prefix, and ES private identifier.
 * Too powerful, relies on input starting with `{`, could be refactored and inlined.
 */
class SvelteJSExpressionLexer(assumeExternalBraces: Boolean) : LayeredLexer(SvelteHtmlBaseLexer(assumeExternalBraces)) {
    init {
        registerLayer(JSFlexAdapter(DialectOptionHolder.ECMA_6), SvelteTokenTypes.CODE_FRAGMENT)
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteKitModuleReferenceContributor.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.javascript.JSModuleBaseReference
import com.intellij.lang.javascript.JSStringUtil
import com.intellij.lang.javascript.frameworks.commonjs.CommonJSUtil
import com.intellij.lang.javascript.frameworks.modules.JSResolvableModuleReferenceContributor
import com.intellij.lang.javascript.library.JSLibraryUtil
import com.intellij.lang.javascript.psi.ecma6.TypeScriptModule
import com.intellij.lang.javascript.psi.resolve.JSResolveResult
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil
import com.intellij.lang.typescript.resolve.TypeScriptClassResolver
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.ResolveResult
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiUtilCore
import com.intellij.util.containers.ContainerUtil
import dev.blachut.svelte.lang.isSvelteContext

class SvelteKitModuleReferenceContributor : JSResolvableModuleReferenceContributor() {
    override fun isApplicable(host: PsiElement): Boolean {
        return super.isApplicable(host) && isSvelteContext(host)
    }

    override fun isAcceptableText(unquotedRefText: String): Boolean {
        val requiredModuleName = FileUtilRt.toSystemIndependentName(unquotedRefText)
        return StringUtil.startsWith(requiredModuleName, "\$app/");
    }

    override fun getDefaultWeight(): Int {
        return JSModuleBaseReference.ModuleTypes.TS_MODULE.weight()
    }

    override fun resolveElement(element: PsiElement, text: String): Array<ResolveResult> {
        return findExternalModule(element.containingFile.originalFile, text)
    }

    // based on TypeScriptUtil.findExternalModule
    private fun findExternalModule(refFile: PsiFile, unquotedEscapedModuleText: String): Array<ResolveResult> {
        ApplicationManager.getApplication().assertReadAccessAllowed()

        val quoted = StringUtil.wrapWithDoubleQuote(JSStringUtil.unescapeStringLiteralValue(unquotedEscapedModuleText))
        val unifiedName = CommonJSUtil.unifyModuleName(quoted)
        val scope = createFilterByNodeModuleScope(JSResolveUtil.getResolveScope(refFile), refFile)

        val modules = TypeScriptClassResolver.getInstance().findGlobalElementsByQName(refFile.project, unifiedName, scope)
        if (!modules.isEmpty()) {
            val result: MutableList<ResolveResult> = ArrayList()
            for (module in modules) {
                if (module is TypeScriptModule && !module.isAugmentation) {
                    result.add(JSResolveResult(module))
                }
            }
            if (!ContainerUtil.isEmpty(result)) return result.toTypedArray()
        }
        return ResolveResult.EMPTY_ARRAY
    }

    // based on TypeScriptUtil.createFilterByNodeModuleScope
    private fun createFilterByNodeModuleScope(scope: GlobalSearchScope, context: PsiElement): GlobalSearchScope {
        var context = context
        if (context is PsiFile) context = context.originalFile
        val contextFile = PsiUtilCore.getVirtualFile(context)
        val index = ProjectFileIndex.getInstance(context.project)
        val resultScope = if (contextFile == null) scope
        else object : DelegatingGlobalSearchScope(scope) {
            override fun contains(file: VirtualFile): Boolean {
                if (!super.contains(file)) return false
                val currentNodeModules = JSLibraryUtil.findAncestorLibraryDir(file, JSLibraryUtil.NODE_MODULES) ?: return true
                val parentDir = currentNodeModules.parent

                //for global libraries and node core libraries
                val isPartOfProject = index.isInContent(parentDir) || index.isInLibrary(parentDir)
                return !isPartOfProject || VfsUtilCore.isAncestor(parentDir, contextFile, true)
            }
        }

        return resultScope
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/SvelteFrameworkHandler.kt<|end_filename|>
package dev.blachut.svelte.lang

import com.intellij.lang.javascript.index.FrameworkIndexingHandler
import com.intellij.lang.javascript.psi.JSFunctionType
import com.intellij.lang.javascript.psi.JSVariable
import com.intellij.lang.javascript.psi.resolve.JSEvaluateContext
import com.intellij.lang.javascript.psi.resolve.JSTypeEvaluator
import com.intellij.psi.PsiElement
import dev.blachut.svelte.lang.psi.SvelteJSReferenceExpression

class SvelteFrameworkHandler : FrameworkIndexingHandler() {
    override fun addTypeFromResolveResult(
        evaluator: JSTypeEvaluator,
        context: JSEvaluateContext,
        result: PsiElement
    ): Boolean {
        val expression = context.processedExpression
        if (result is JSVariable && expression is SvelteJSReferenceExpression && expression.isSubscribedReference) {
            try {
                val storeType = result.jsType?.asRecordType() ?: return false
                val subscribeMethod =
                    storeType.findPropertySignature("subscribe")?.jsType as? JSFunctionType ?: return false
                val subscriberFunction =
                    subscribeMethod.parameters[0].inferredType?.substitute() as? JSFunctionType ?: return false
                val storeContentType = subscriberFunction.parameters[0].inferredType ?: return false

                evaluator.addType(storeContentType)
                return true
            } catch (e: Exception) {
            }
        }
        return false
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlLexer.kt<|end_filename|>
package dev.blachut.svelte.lang.parsing.html

import com.intellij.html.embedding.HtmlEmbeddedContentProvider
import com.intellij.lexer.HtmlLexer
import com.intellij.lexer.HtmlScriptStyleEmbeddedContentProvider

class SvelteHtmlLexer : HtmlLexer(SvelteHtmlBaseLexer(), false) {

    override fun start(buffer: CharSequence, startOffset: Int, endOffset: Int, initialState: Int) {
        // TODO Verify if those masks don't clash with ones used in BaseHtmlLexer.initState
        val baseState = initialState and 0xffff
        val nestingLevel = initialState shr 16
        (delegate as SvelteHtmlBaseLexer).flexLexer.bracesNestingLevel = nestingLevel
        super.start(buffer, startOffset, endOffset, baseState)
    }

    override fun getState(): Int {
        val nestingLevel = (delegate as SvelteHtmlBaseLexer).flexLexer.bracesNestingLevel
        return (nestingLevel shl 16) or (super.getState() and 0xffff)
    }

    override fun acceptEmbeddedContentProvider(provider: HtmlEmbeddedContentProvider): Boolean =
        provider::class != HtmlScriptStyleEmbeddedContentProvider::class

    override fun isHtmlTagState(state: Int): Boolean {
        return state == _SvelteHtmlLexer.START_TAG_NAME || state == _SvelteHtmlLexer.END_TAG_NAME
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/compatibility/SvelteInspectionSuppressor.kt<|end_filename|>
package dev.blachut.svelte.lang.compatibility

import com.intellij.codeInspection.InspectionSuppressor
import com.intellij.codeInspection.SuppressQuickFix
import com.intellij.lang.javascript.inspections.JSConstantReassignmentInspection
import com.intellij.lang.javascript.psi.ecma6.impl.JSXXmlLiteralExpressionImpl
import com.intellij.lang.typescript.inspection.TypeScriptMissingConfigOptionInspection
import com.intellij.psi.PsiElement
import com.sixrr.inspectjs.assignment.SillyAssignmentJSInspection
import com.sixrr.inspectjs.confusing.PointlessBooleanExpressionJSInspection
import com.sixrr.inspectjs.control.UnnecessaryLabelJSInspection
import com.sixrr.inspectjs.validity.BadExpressionStatementJSInspection
import dev.blachut.svelte.lang.codeInsight.SvelteReactiveDeclarationsUtil
import dev.blachut.svelte.lang.equalsName
import dev.blachut.svelte.lang.psi.SvelteHtmlFile
import dev.blachut.svelte.lang.psi.SvelteJSReferenceExpression

class SvelteInspectionSuppressor : InspectionSuppressor {
    override fun isSuppressedFor(element: PsiElement, inspectionId: String): Boolean {
        if (element.containingFile !is SvelteHtmlFile) return false

        if (inspectionId.equalsName<UnnecessaryLabelJSInspection>()) {
            return element.textMatches(SvelteReactiveDeclarationsUtil.REACTIVE_LABEL)
        }
        if (inspectionId.equalsName<BadExpressionStatementJSInspection>()) {
            return true
        }
        if (inspectionId.equalsName<JSConstantReassignmentInspection>()) {
            val parent = element.parent
            if (parent is SvelteJSReferenceExpression && parent.isSubscribedReference) {
                // TODO check if store is writable
                return true
            }
        }
        if (inspectionId.equalsName<SillyAssignmentJSInspection>()) {
            // TODO check if resolved variable is declared directly in instance script
            return true
        }
        if (inspectionId.equalsName<PointlessBooleanExpressionJSInspection>()) {
            // TODO check if reference expression resolves to prop
            return true
        }
        if (inspectionId.equalsName<TypeScriptMissingConfigOptionInspection>()) {
            // JSXXmlLiteralExpressionImpl are invalid in Svelte anyway
            if (element is JSXXmlLiteralExpressionImpl) return true
        }

        return false
    }

    override fun getSuppressActions(element: PsiElement?, toolId: String): Array<SuppressQuickFix> = emptyArray()
}


<|start_filename|>src/test/java/dev/blachut/svelte/lang/codeInsight/SvelteHighlightingTest.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.codeInsight.daemon.impl.analysis.XmlUnboundNsPrefixInspection
import com.intellij.codeInspection.InspectionProfileEntry
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.htmlInspections.HtmlUnknownAttributeInspection
import com.intellij.codeInspection.htmlInspections.HtmlUnknownBooleanAttributeInspection
import com.intellij.codeInspection.htmlInspections.HtmlUnknownTagInspection
import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspection
import com.intellij.lang.javascript.inspection.JSObjectNullOrUndefinedInspection
import com.intellij.lang.javascript.inspection.JSSuspiciousTypeGuardInspection
import com.intellij.lang.javascript.inspection.JSUnusedAssignmentInspection
import com.intellij.lang.javascript.inspections.*
import com.intellij.lang.javascript.modules.ES6CheckImportInspection
import com.intellij.lang.javascript.modules.TypeScriptCheckImportInspection
import com.intellij.lang.typescript.inspections.*
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import com.intellij.xml.util.XmlDuplicatedIdInspection
import com.intellij.xml.util.XmlInvalidIdInspection
import com.sixrr.inspectjs.assignment.SillyAssignmentJSInspection
import com.sixrr.inspectjs.confusing.PointlessBooleanExpressionJSInspection
import com.sixrr.inspectjs.validity.UnreachableCodeJSInspection
import dev.blachut.svelte.lang.inspections.SvelteUnresolvedComponentInspection

class SvelteHighlightingTest : BasePlatformTestCase() {

    override fun setUp() {
        super.setUp()
        myFixture.enableInspections(*configureDefaultLocalInspectionTools().toTypedArray())
    }

    private fun configureComponentWithAttribute(name: String) {
        myFixture.configureByText(name, "<script>export var test=\"\"</script>")
    }

    fun testUnresolvedComponent() {
        configureComponentWithAttribute("Foo.svelte")
        myFixture.configureByText("Usage.svelte", "<<error>Fo<caret>o</error> />")
        myFixture.testHighlighting()
        myFixture.launchAction(myFixture.findSingleIntention("Insert 'import Foo from \"./Foo.svelte\"'"))
        myFixture.checkResult(
            """
                <script>
                    import Foo from "./Foo.svelte";
                </script>

                <Foo />
                """.trimIndent()
        )
        myFixture.testHighlighting() // no errors after import
    }

    fun testComponentAttribute() {
        configureComponentWithAttribute("Foo.svelte")
        myFixture.configureByText("Usage.svelte",
            """
                <script>
                import Usage from "./Usage.svelte";
                </script>
                <Usage test="" />
                <div class="someName" <warning>unknowAttr</warning>="">test</div>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testShorthandAttributeRequired() {
        configureComponentWithAttribute("Foo.svelte")
        myFixture.configureByText("Usage.svelte", """
            <script>
                let src = 'tutorial/image.gif';
                let alt = '<NAME>';
            </script>

            <img {src} {alt}>
        """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testHandler() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    function handleClick() {}
                </script>
                <button on:click={handleClick}>hello</button>
                <svelte:window on:keydown={handleClick}/>
                <button on:click={<error>unknownHandleClick</error>}>hello</button>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testHtmlDirectiveTag() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    let withHtml="<div>hello</div>"
                </script>
                <p>{@html withHtml}</p>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testUnknownTag() {
        myFixture.configureByText("Foo.svelte", "<<warning>unknown</warning> />")
        myFixture.testHighlighting()
    }

    fun testUnusedImport() {
        configureComponentWithAttribute("Foo.svelte")
        myFixture.configureByText("Usage.svelte",
            """
                <script>
                    <warning>import Usage from "./Usage.svelte";</warning>
                </script>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testUnusedVariable() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    function handleClick() {
                        var <warning>unusedVar</warning> = '1'
                        function <warning>unusedFunction</warning>() {}
                    }
                </script>
                <button on:click={handleClick}>hello</button>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testSpread() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    import Foo from './Foo.svelte';
                    const spread = {};
                </script>
                <Foo {...spread} />
                <Foo {...<error>unknownSpread</error>} />
                """.trimIndent()
        )
        myFixture.testHighlighting()
    }

    fun testConditionIfBlock() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>const test = true;</script>
                {#if test || <error>test2</error>}
                <button>
                test
                </button>
                {/if}
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testConditionIfElse() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>const test = true;</script>
                {#if test}
                <button>
                test
                </button>
                {:else}
                <br>
                {/if}
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testConditionIfElseIf() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>const test = true;</script>
                {#if test}
                <button>
                test
                </button>
                {:else if test}
                <br>
                {/if}
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testEach() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    const cats = [{id:111, name:"test"}];
                </script>
                <ul>
                	{#each cats as cat}
                		<li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
                			{cat.name}
                			{cat.<weak_warning>unknownName</weak_warning>}
                		</a></li>
                	{/each}
                </ul>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testEachWithId() {
        configureComponentWithAttribute("Foo.svelte")
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    import Foo from './Foo.svelte';
                    const cats = [{id:111, name:"test"}];
                </script>
                {#each cats as cat (cat.id)}
                	<Foo test={cat.name} />
                {/each}
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testAwaitPromise() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    async function myF() {return 1}
                    let promise11 = myF()
                </script>
                {#await promise11}
                	<p>...waiting</p>
                {:then num11}
                	<p>The number is {num11}</p>
                {:catch error}
                	<p style="color: red">{error.message}</p>
                {/await}
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testShortAwaitPromise() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    async function myF() {return 1}
                    let promise11 = myF()
                </script>
                {#await promise11 then value11}
	                <p>the value is {value11}</p>
                {/await}
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testEventsWithModifiers() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
	                function handleClick() {}
                </script>
                <button on:click|once={handleClick}>
	                Click me
                </button>
                <button on:click|once|capture={handleClick}>
	                Click me1
                </button>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testMultipleSelect() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
	                const flavours = [];
                </script>
                <select multiple bind:value={flavours}>
	                {#each <error>menu1</error> as flavour}
		                <option value={flavour}>
			                {flavour}
		                </option>
	                {/each}
                </select>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testStoreShorthandAssignment() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    import { writable } from 'svelte/store';
                    const store = writable();
                    console.log(${"$"}store); // TODO required before fixing JSUnusedAssignment inspection
                    ${"$"}store = 1;
                    console.log(${"$"}store);
                    <error>store</error> = writable();
                </script>
                {${"$"}store}
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testArrayReassignment() {
        // TODO check if resolved variable is declared directly in instance script
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    let users = [];

                    function addUser(user) {
                        users.push(user);
                        users = users;
                    }
                </script>
                <button on:click={addUser}>Hello</button>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testNullablePropFallback() {
        myFixture.configureByText("Foo.svelte", """
            <script>
                export let nullable = null;

                if (nullable) {
                    nullable = {};
                }
            </script>
        """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testBindDirective() {
        myFixture.configureByText("Foo.svelte",
                                  """
                <script>
                    let title = "text";
                    let otherTitle = "text2";
                </script>
                <button bind:title>Click me</button>
                <button bind:<error>unknownTitle</error>>Click me</button>
                <button bind:name={otherTitle}>Click me</button>
                <button bind:name={<error>unknownTitle</error>}>Click me</button>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testClassDirective() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
                    let active = true;
                </script>
                <button class:active>Click me</button>
                <style>
                    .active {
                        background-color: cadetblue;
                    }
                </style>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testUseDirective() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
	                function action() {}
                </script>
                <button use:action>Click me</button>
                <button use:action={{param: true}}>Click me</button>
                <button use:<error>unknownAction</error>>Click me</button>
                <button use:<error>unknownAction</error>={{param: true}}>Click me</button>
                <button use:<error>unknownAction</error>.nested>Click me</button>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testTransitionDirective() {
        myFixture.configureByText("Foo.svelte",
            """
                <script>
	                function fade() {}
                </script>
                <button transition:fade>Click me</button>
                <button transition:fade|local>Click me</button>
                <button transition:fade={{param: true}}>Click me</button>
                <button transition:fade|local={{param: true}}>Click me</button>
                <button transition:<error>unknownFade</error>>Click me</button>
                <button transition:<error>unknownFade</error>|local>Click me</button>
                <button transition:<error>unknownFade</error>={{param: true}}>Click me</button>
                <button transition:<error>unknownFade</error>|local={{param: true}}>Click me</button>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testLetDirectiveShorthand() {
        myFixture.configureByText("Foo.svelte",
            """
                <div let:opened>Lorem ipsum {opened}</div>
                """.trimIndent())
        myFixture.testHighlighting()
    }

    fun testExpressionInStyleAttribute() {
        myFixture.configureByText("Foo.svelte", """<div style="--custom: {'dynamic'}"></div>""")
        myFixture.testHighlighting()
    }

    companion object {
        fun configureDefaultLocalInspectionTools(): List<InspectionProfileEntry> {
            val l = mutableListOf<LocalInspectionTool>()
            l.add(RequiredAttributesInspection())
            l.add(JSConstantReassignmentInspection())
            l.add(ES6UnusedImportsInspection())
            l.add(JSUnresolvedFunctionInspection())
            l.add(JSUnresolvedVariableInspection())
            l.add(JSValidateTypesInspection())
            l.add(JSIncompatibleTypesComparisonInspection())
            l.add(SillyAssignmentJSInspection())
            val functionSignaturesInspection = JSCheckFunctionSignaturesInspection()
            functionSignaturesInspection.myCheckGuessedTypes = true
            l.add(functionSignaturesInspection)
            l.add(JSValidateJSDocInspection())
            l.add(JSUndeclaredVariableInspection())
            l.add(XmlDuplicatedIdInspection())
            l.add(ES6CheckImportInspection())
            l.add(XmlInvalidIdInspection())
            l.add(HtmlUnknownTagInspection())
            l.add(HtmlUnknownBooleanAttributeInspection())
            l.add(HtmlUnknownAttributeInspection())
            l.add(XmlUnboundNsPrefixInspection())
            l.add(JSUnusedLocalSymbolsInspection())
            l.add(JSPotentiallyInvalidConstructorUsageInspection())
            l.add(JSUnnecessarySemicolonInspection())
            l.add(JSLastCommaInArrayLiteralInspection())
            l.add(JSLastCommaInObjectLiteralInspection())
            l.add(JSReferencingMutableVariableFromClosureInspection())
            l.add(JSPotentiallyInvalidUsageOfThisInspection())
            l.add(JSPotentiallyInvalidUsageOfClassThisInspection())
            l.add(JSPotentiallyInvalidTargetOfIndexedPropertyAccess())
            l.add(JSUndefinedPropertyAssignmentInspection())
            l.add(JSClosureCompilerSyntaxInspection())
            l.add(JSCommentMatchesSignatureInspection())
            l.add(JSFileReferencesInspection())
            l.add(JSUnusedGlobalSymbolsInspection())
            l.add(JSAnnotatorInspection())
            l.add(JSUnusedAssignmentInspection())
            l.add(UnreachableCodeJSInspection())
            l.add(TypeScriptValidateTypesInspection())
            l.add(TypeScriptValidateJSTypesInspection())
            l.add(TypeScriptUnresolvedFunctionInspection())
            l.add(TypeScriptUnresolvedVariableInspection())
            l.add(TypeScriptCheckImportInspection())
            l.add(TypeScriptDuplicateUnionOrIntersectionTypeInspection())
            l.add(PointlessBooleanExpressionJSInspection())
            l.add(TypeScriptValidateGenericTypesInspection())
            l.add(TypeScriptRedundantGenericTypeInspection())

            // CF inspections
            l.add(JSSuspiciousTypeGuardInspection())
            l.add(JSObjectNullOrUndefinedInspection())
            l.add(SvelteUnresolvedComponentInspection())
            return l
        }
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteAddImportExecutor.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.lang.ecmascript6.actions.ES6AddImportExecutor
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import dev.blachut.svelte.lang.psi.SvelteHtmlFile
import dev.blachut.svelte.lang.psi.findAncestorScript
import dev.blachut.svelte.lang.psi.getJsEmbeddedContent

class SvelteAddImportExecutor(editor: Editor?, place: PsiElement) : ES6AddImportExecutor(editor, place) {
    override fun prepareScopeToAdd(place: PsiElement, fromExternalModule: Boolean): PsiElement? {
        val parentScript = findAncestorScript(place)
        if (parentScript != null) {
            // inside module or instance script
            return getJsEmbeddedContent(parentScript)
        }

        // inside Svelte expression
        val containingFile = place.containingFile as? SvelteHtmlFile ?: return null

        return prepareInstanceScriptContent(containingFile)
    }
}


<|start_filename|>src/test/java/dev/blachut/svelte/lang/codeInsight/SvelteKitTest.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.lang.javascript.inspections.JSUnusedGlobalSymbolsInspection
import com.intellij.lang.javascript.inspections.JSUnusedLocalSymbolsInspection
import com.intellij.lang.javascript.psi.ecma6.TypeScriptModule
import com.intellij.lang.javascript.psi.resolve.JSResolveResult
import com.intellij.psi.PsiPolyVariantReference
import com.intellij.testFramework.fixtures.BasePlatformTestCase

class SvelteKitTest : BasePlatformTestCase() {
    override fun getTestDataPath(): String = "src/test/resources"
    override fun getBasePath(): String = "dev/blachut/svelte/lang/codeInsight/svelteKit"

    fun testResolveAppModules() {
        val base = basePath + "/" + getTestName(true)
        myFixture.copyDirectoryToProject(base, "")
        myFixture.copyDirectoryToProject("dev/blachut/svelte/lang/_npm", "node_modules")
        myFixture.configureFromTempProjectFile("src/routes/index.svelte")

        val ref = myFixture.getReferenceAtCaretPositionWithAssertion() as PsiPolyVariantReference

        assertInstanceOf(JSResolveResult.resolve(ref.multiResolve(false)), TypeScriptModule::class.java)
    }

    fun testImplicitlyUsedExports() {
        myFixture.enableInspections(JSUnusedLocalSymbolsInspection::class.java)
        myFixture.enableInspections(JSUnusedGlobalSymbolsInspection::class.java)

        myFixture.configureByText("index.svelte", """
            <script context="module" lang="ts">
                const <warning>router</warning> = true;
                export const prerender = true;
                export const ssr = true;
                export const hydrate = true;
                export const <warning>hydrate2</warning> = true;
            </script>
            <script lang="ts">
                export const <warning>hydrate</warning> = true;
            </script>
        """.trimIndent())
        myFixture.testHighlighting()
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlHighlightingLexer.kt<|end_filename|>
package dev.blachut.svelte.lang.parsing.html

import com.intellij.html.embedding.HtmlEmbeddedContentProvider
import com.intellij.lexer.HtmlHighlightingLexer
import com.intellij.lexer.HtmlScriptStyleEmbeddedContentProvider

class SvelteHtmlHighlightingLexer : HtmlHighlightingLexer(SvelteHtmlBaseLexer(), false, null) {

    override fun acceptEmbeddedContentProvider(provider: HtmlEmbeddedContentProvider): Boolean =
        provider::class != HtmlScriptStyleEmbeddedContentProvider::class

    override fun isHtmlTagState(state: Int): Boolean {
        return state == _SvelteHtmlLexer.START_TAG_NAME || state == _SvelteHtmlLexer.END_TAG_NAME
    }

}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteKitImplicitUsageProvider.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.codeInsight.daemon.ImplicitUsageProvider
import com.intellij.lang.javascript.psi.JSElementBase
import com.intellij.lang.javascript.psi.JSFunction
import com.intellij.lang.javascript.psi.JSVariable
import com.intellij.psi.PsiElement
import com.intellij.psi.html.HtmlTag
import com.intellij.psi.util.parentOfType
import dev.blachut.svelte.lang.psi.SvelteHtmlFile
import dev.blachut.svelte.lang.psi.isModuleScript

class SvelteKitImplicitUsageProvider : ImplicitUsageProvider {
    private val functionNames = setOf("load")
    private val variableNames = setOf("hydrate", "prerender", "router", "ssr")

    override fun isImplicitUsage(element: PsiElement): Boolean {
        if (element is JSElementBase && element.containingFile is SvelteHtmlFile && element.isExported) {
            if (element is JSVariable && variableNames.contains(element.name) ||
                element is JSFunction && functionNames.contains(element.name)) {
                return isModuleScript(element.parentOfType<HtmlTag>())
            }
        }

        // todo hooks

        return false
    }

    override fun isImplicitRead(element: PsiElement): Boolean {
        return false
    }

    override fun isImplicitWrite(element: PsiElement): Boolean {
        return false
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteAttributeDescriptorProvider.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.psi.xml.XmlTag
import com.intellij.xml.XmlAttributeDescriptor
import com.intellij.xml.XmlAttributeDescriptorsProvider
import com.intellij.xml.impl.schema.AnyXmlAttributeDescriptor

class SvelteAttributeDescriptorProvider : XmlAttributeDescriptorsProvider {
    private val aAttributes = arrayOf("sveltekit:noscroll", "sveltekit:prefetch")
        .map {
            object : AnyXmlAttributeDescriptor(it) {
                override fun isEnumerated(): Boolean = true
                override fun getEnumeratedValues(): Array<String> {
                    return arrayOf(it)
                }
            }
        }.toTypedArray<XmlAttributeDescriptor>()

    override fun getAttributeDescriptors(context: XmlTag): Array<XmlAttributeDescriptor> {
        if (context.name == "a") {
            return aAttributes
        }

        return XmlAttributeDescriptor.EMPTY
    }

    override fun getAttributeDescriptor(attributeName: String?, context: XmlTag?): XmlAttributeDescriptor? {
        return aAttributes.find { it.name == attributeName }
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/codeInsight/SvelteJSReferenceChecker.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.lang.javascript.DialectOptionHolder
import com.intellij.lang.javascript.psi.JSEmbeddedContent
import com.intellij.lang.javascript.psi.JSExpression
import com.intellij.lang.javascript.psi.JSReferenceExpression
import com.intellij.lang.javascript.validation.JSProblemReporter
import com.intellij.lang.javascript.validation.TypedJSReferenceChecker
import com.intellij.lang.javascript.validation.fixes.CreateJSFunctionIntentionAction
import com.intellij.lang.javascript.validation.fixes.CreateJSVariableIntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pair
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.ResolveResult
import com.intellij.psi.SmartPointerManager
import com.intellij.psi.util.parentOfType
import dev.blachut.svelte.lang.psi.SvelteHtmlFile
import dev.blachut.svelte.lang.psi.SvelteJSEmbeddedContentImpl

/**
 * No TypeScript counterpart because right now JSEmbeddedContent is JS-only
 */
class SvelteJSReferenceChecker(reporter: JSProblemReporter<*>) : TypedJSReferenceChecker(reporter) {
    override fun addCreateFromUsageFixes(node: JSReferenceExpression,
                                         resolveResults: Array<out ResolveResult>,
                                         fixes: MutableList<LocalQuickFix>,
                                         inTypeContext: Boolean,
                                         ecma: Boolean): Boolean {
        if (node.parentOfType<JSEmbeddedContent>() is SvelteJSEmbeddedContentImpl) {
            // only override Svelte expressions and blocks
            return super.addCreateFromUsageFixes(node, resolveResults, fixes, inTypeContext, ecma)
        }

        fixes.add(SvelteCreateJSVariableIntentionAction(node))

        return inTypeContext
    }

    override fun addFunctionFixes(node: JSReferenceExpression,
                                  fixes: MutableList<LocalQuickFix>,
                                  refName: String?,
                                  dialect: DialectOptionHolder?,
                                  qualifier: JSExpression?) {
        if (node.parentOfType<JSEmbeddedContent>() is SvelteJSEmbeddedContentImpl) {
            // only override Svelte expressions and blocks
            return super.addFunctionFixes(node, fixes, refName, dialect, qualifier)
        }

        fixes.add(SvelteCreateJSFunctionIntentionAction(node))
    }
}

class SvelteCreateJSVariableIntentionAction(reference: JSReferenceExpression)
    : CreateJSVariableIntentionAction(reference.referenceName, false, false, false) {
    private val myRefExpressionPointer = SmartPointerManager.getInstance(reference.project).createSmartPsiElementPointer(reference)

    override fun calculateAnchors(psiElement: PsiElement): Pair<JSReferenceExpression?, PsiElement?> {
        return Pair.create(myRefExpressionPointer.element, psiElement.lastChild)
    }

    override fun applyFix(project: Project, psiElement: PsiElement, file: PsiFile, editor: Editor?) {
        // assumes psiElement isn't inside script tag
        val containingFile = file as? SvelteHtmlFile ?: return
        val embeddedContent = prepareInstanceScriptContent(containingFile)

        doApplyFix(project, embeddedContent, file, editor)
    }
}

class SvelteCreateJSFunctionIntentionAction(reference: JSReferenceExpression)
    : CreateJSFunctionIntentionAction(reference.referenceName, false, false, false) {
    private val myRefExpressionPointer = SmartPointerManager.getInstance(reference.project).createSmartPsiElementPointer(reference)

    override fun calculateAnchors(psiElement: PsiElement): Pair<JSReferenceExpression?, PsiElement?> {
        return Pair.create(myRefExpressionPointer.element, psiElement.lastChild)
    }

    override fun applyFix(project: Project?, psiElement: PsiElement?, file: PsiFile, editor: Editor?) {
        // assumes psiElement isn't inside script tag
        val containingFile = file as? SvelteHtmlFile ?: return
        val embeddedContent = prepareInstanceScriptContent(containingFile)

        doApplyFix(project, embeddedContent, file, editor)
    }
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/codeInsight/svelteRefactorings.kt<|end_filename|>
package dev.blachut.svelte.lang.codeInsight

import com.intellij.lang.javascript.psi.JSEmbeddedContent
import com.intellij.lang.javascript.psi.impl.JSChangeUtil
import com.intellij.openapi.application.WriteAction
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.XmlElementFactory
import com.intellij.psi.xml.XmlTag
import com.intellij.xml.util.XmlUtil
import dev.blachut.svelte.lang.SvelteHTMLLanguage
import dev.blachut.svelte.lang.psi.SvelteHtmlFile
import dev.blachut.svelte.lang.psi.getJsEmbeddedContent

fun prepareInstanceScriptContent(containingFile: SvelteHtmlFile): JSEmbeddedContent {
    var instanceScript = containingFile.instanceScript
    if (instanceScript == null) {
        val elementFactory = XmlElementFactory.getInstance(containingFile.project)
        val emptyInstanceScript = elementFactory.createTagFromText("<script>\n</script>", SvelteHTMLLanguage.INSTANCE)
        val moduleScript = containingFile.moduleScript
        val xmlDocument = containingFile.document!!

        instanceScript = WriteAction.compute<XmlTag, Throwable> {
            val createdScript = if (moduleScript != null) {
                xmlDocument.addAfter(emptyInstanceScript, moduleScript) as XmlTag
            }
            else {
                xmlDocument.addBefore(emptyInstanceScript, xmlDocument.firstChild) as XmlTag
            }

            if (createdScript.nextSibling !is PsiWhiteSpace) {
                // todo refine
                // todo add enter between module script and created instance script
                JSChangeUtil.addWsAfter(xmlDocument, createdScript, "\n\n")
            }

            return@compute createdScript
        }!!
    }

    val embeddedContent = getJsEmbeddedContent(instanceScript)
    if (embeddedContent != null) return embeddedContent

    // instanceScript is empty, we need to insert something in order to get JSEmbeddedContent
    WriteAction.run<Throwable> {
        XmlUtil.expandTag(instanceScript)

        val documentManager = PsiDocumentManager.getInstance(containingFile.project)
        val document = documentManager.getDocument(containingFile)!!
        documentManager.doPostponedOperationsAndUnblockDocument(document)
        document.insertString(instanceScript.value.textRange.startOffset, "\n")
        documentManager.commitDocument(document)
    }

    return getJsEmbeddedContent(instanceScript)!!
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/psi/SvelteTagElementTypes.kt<|end_filename|>
package dev.blachut.svelte.lang.psi

import com.intellij.lang.PsiBuilder
import com.intellij.lang.javascript.JSTokenTypes
import com.intellij.lang.javascript.parsing.JavaScriptParser
import com.intellij.psi.tree.TokenSet
import dev.blachut.svelte.lang.parsing.html.SvelteTagParsing

object SvelteTagElementTypes {
    val IF_START = object : SvelteJSBlockLazyElementType("IF_START") {
        override val noTokensErrorMessage = "expression expected"

        override fun parseTokens(builder: PsiBuilder, parser: JavaScriptParser<*, *, *, *>) {
            builder.advanceLexer() // JSTokenTypes.SHARP
            SvelteTagParsing.parseNotAllowedWhitespace(builder, "#")
            builder.advanceLexer() // SvelteTokenTypes.IF_KEYWORD

            parser.expressionParser.parseExpression()
        }
    }

    val ELSE_CLAUSE = object : SvelteJSBlockLazyElementType("ELSE_CLAUSE") {
        override val noTokensErrorMessage = "expression expected"

        override fun parseTokens(builder: PsiBuilder, parser: JavaScriptParser<*, *, *, *>) {
            builder.advanceLexer() // JSTokenTypes.COLON
            SvelteTagParsing.parseNotAllowedWhitespace(builder, ":")
            builder.advanceLexer() // SvelteTokenTypes.ELSE_KEYWORD

            if (builder.tokenType === SvelteTokenTypes.IF_KEYWORD) {
                builder.advanceLexer()
                parser.expressionParser.parseExpression()
            }
        }
    }

    val EACH_START = object : SvelteJSBlockLazyElementType("EACH_START") {
        override val noTokensErrorMessage = "expression expected"

        override fun parseTokens(builder: PsiBuilder, parser: JavaScriptParser<*, *, *, *>) {
            builder.advanceLexer() // JSTokenTypes.SHARP
            SvelteTagParsing.parseNotAllowedWhitespace(builder, "#")
            builder.remapCurrentToken(SvelteTokenTypes.EACH_KEYWORD) // todo might be okay to remove all those remapCurrentToken
            builder.advanceLexer() // JSTokenTypes.IDENTIFIER -- fake EACH_KEYWORD

            parser.expressionParser.parseExpression()

            if (builder.tokenType === SvelteTokenTypes.AS_KEYWORD) {
                builder.advanceLexer()
            } else {
                builder.error("as expected")
            }

            parser.expressionParser.parseDestructuringElement(SvelteJSElementTypes.PARAMETER, false, false)

            if (builder.tokenType === JSTokenTypes.COMMA) {
                builder.advanceLexer()
                // TODO disallow destructuring
                parser.expressionParser.parseDestructuringElement(SvelteJSElementTypes.PARAMETER, false, false)
            }

            if (builder.tokenType === JSTokenTypes.LPAR) {
                val keyExpressionMarker = builder.mark()
                builder.advanceLexer()
                parser.expressionParser.parseExpression()

                if (builder.tokenType === JSTokenTypes.RPAR) {
                    builder.advanceLexer()
                } else {
                    builder.error(") expected")
                }
                keyExpressionMarker.done(TAG_DEPENDENT_EXPRESSION)
            }
        }
    }

    val AWAIT_START = object : SvelteJSBlockLazyElementType("AWAIT_START") {
        override val noTokensErrorMessage = "expression expected"

        override fun parseTokens(builder: PsiBuilder, parser: JavaScriptParser<*, *, *, *>) {
            builder.advanceLexer() // JSTokenTypes.SHARP
            SvelteTagParsing.parseNotAllowedWhitespace(builder, "#")
            builder.advanceLexer() // SvelteTokenTypes.AWAIT_KEYWORD

            parser.expressionParser.parseExpression()

            if (builder.tokenType === JSTokenTypes.IDENTIFIER && builder.tokenText == "then") {
                builder.remapCurrentToken(SvelteTokenTypes.THEN_KEYWORD)
                builder.advanceLexer()

                parser.expressionParser.parseDestructuringElement(SvelteJSElementTypes.PARAMETER, false, false)
            }

            if (builder.tokenType === SvelteTokenTypes.CATCH_KEYWORD) {
                builder.advanceLexer()
                parser.expressionParser.parseDestructuringElement(SvelteJSElementTypes.PARAMETER, false, false)
            }
        }
    }

    val KEY_START = object : SvelteJSBlockLazyElementType("KEY_START") {
        override val noTokensErrorMessage = "expression expected"

        override fun parseTokens(builder: PsiBuilder, parser: JavaScriptParser<*, *, *, *>) {
            builder.advanceLexer() // JSTokenTypes.SHARP
            SvelteTagParsing.parseNotAllowedWhitespace(builder, "#")
            builder.advanceLexer() // SvelteTokenTypes.KEY_KEYWORD

            parser.expressionParser.parseExpression()
        }
    }

    val THEN_CLAUSE = object : SvelteJSBlockLazyElementType("THEN_CLAUSE") {
        override val noTokensErrorMessage = "expression expected"

        override fun parseTokens(builder: PsiBuilder, parser: JavaScriptParser<*, *, *, *>) {
            builder.advanceLexer() // JSTokenTypes.COLON
            SvelteTagParsing.parseNotAllowedWhitespace(builder, ":")
            builder.remapCurrentToken(SvelteTokenTypes.THEN_KEYWORD)
            builder.advanceLexer() // JSTokenTypes.IDENTIFIER -- fake THEN_KEYWORD

            // TODO Check weird RBRACE placement
            parser.expressionParser.parseDestructuringElement(SvelteJSElementTypes.PARAMETER, false, false)
        }
    }

    val CATCH_CLAUSE = object : SvelteJSBlockLazyElementType("CATCH_CLAUSE") {
        override val noTokensErrorMessage = "expression expected"

        override fun parseTokens(builder: PsiBuilder, parser: JavaScriptParser<*, *, *, *>) {
            builder.advanceLexer() // JSTokenTypes.COLON
            SvelteTagParsing.parseNotAllowedWhitespace(builder, ":")
            builder.advanceLexer() // SvelteTokenTypes.CATCH_KEYWORD

            parser.expressionParser.parseDestructuringElement(SvelteJSElementTypes.PARAMETER, false, false)
        }
    }

    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")
    val KEY_END = SvelteJSElementType("KEY_END")

    val START_TAGS = TokenSet.create(IF_START, EACH_START, AWAIT_START, KEY_START)
    val INNER_TAGS = TokenSet.create(ELSE_CLAUSE, THEN_CLAUSE, CATCH_CLAUSE)
    val END_TAGS = TokenSet.create(IF_END, EACH_END, AWAIT_END, KEY_END)
    val INITIAL_TAGS = TokenSet.orSet(START_TAGS, INNER_TAGS)
    val TAIL_TAGS = TokenSet.orSet(INNER_TAGS, END_TAGS)
}


<|start_filename|>src/main/java/dev/blachut/svelte/lang/parsing/html/SvelteHtmlBaseLexer.kt<|end_filename|>
package dev.blachut.svelte.lang.parsing.html

import com.intellij.lexer.FlexAdapter
import com.intellij.lexer.MergingLexerAdapter
import com.intellij.psi.tree.TokenSet
import com.intellij.psi.xml.XmlTokenType
import dev.blachut.svelte.lang.psi.SvelteTokenTypes

class SvelteHtmlBaseLexer(private val assumeExternalBraces: Boolean = false)
    : MergingLexerAdapter(FlexAdapter(_SvelteHtmlLexer()), TOKENS_TO_MERGE) {

    override fun start(buffer: CharSequence, startOffset: Int, endOffset: Int, initialState: Int) {
        var correctedState = initialState
        if (assumeExternalBraces && initialState == _SvelteHtmlLexer.YYINITIAL) {
            correctedState = _SvelteHtmlLexer.SVELTE_INTERPOLATION_START
        }
        super.start(buffer, startOffset, endOffset, correctedState)
    }

    val flexLexer: _SvelteHtmlLexer
        get() = (original as FlexAdapter).flex as _SvelteHtmlLexer
}

private val TOKENS_TO_MERGE = TokenSet.create(
    XmlTokenType.XML_COMMENT_CHARACTERS, XmlTokenType.XML_WHITE_SPACE, XmlTokenType.XML_REAL_WHITE_SPACE,
    XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN, XmlTokenType.XML_DATA_CHARACTERS,
    XmlTokenType.XML_TAG_CHARACTERS,
    SvelteTokenTypes.CODE_FRAGMENT
)
