Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.intellij.openapi.util.KeyWithDefaultValue
import com.intellij.plugin.applescript.lang.dictionary.project.AppleScriptProjectDictionaryService
import com.intellij.plugin.applescript.lang.sdef.ApplicationDictionary
import com.intellij.plugin.applescript.lang.sdef.DictionaryComponent
import com.intellij.plugin.applescript.lang.sdef.DictionaryTermCatalog
import com.intellij.plugin.applescript.psi.AppleScriptBuiltInClassIdentifier
import com.intellij.plugin.applescript.psi.AppleScriptDictionaryClassIdentifierPlural
import com.intellij.plugin.applescript.psi.AppleScriptDictionaryClassName
Expand Down Expand Up @@ -182,20 +183,20 @@ class AppleScriptDictionaryResolveProcessor : AppleScriptPsiScopeProcessor {

fun getFilteredResult(): List<DictionaryComponent> {
val result = ArrayList<DictionaryComponent>()
var filterStdCocoaTerminologyFlag = false // true once we've collected from a non-additions dictionary
var shouldFilterCocoa = false
val dictionaryRegistry = myProject.getService(AppleScriptProjectDictionaryService::class.java)
for (collectedDictionary in collectedDictionaries) {
collectAllComponentsFromDictionary(collectedDictionary, result, filterStdCocoaTerminologyFlag)
filterStdCocoaTerminologyFlag = filterStdCocoaTerminologyFlag ||
collectTerms(collectedDictionary, result, shouldFilterCocoa)
shouldFilterCocoa = shouldFilterCocoa ||
collectedDictionary.getName() != ApplicationDictionary.SCRIPTING_ADDITIONS_LIBRARY
}
if (dictionaryRegistry != null) {
appendResultsIfNeeded(
appendFallbackTerms(
result,
myProject,
mySortedUseStatements.isNotEmpty(),
collectedDictionaries.contains(dictionaryRegistry.getScriptingAdditionsTerminology()),
filterStdCocoaTerminologyFlag,
shouldFilterCocoa,
)
}
return result
Expand All @@ -211,81 +212,43 @@ class AppleScriptDictionaryResolveProcessor : AppleScriptPsiScopeProcessor {
private val LOG: Logger =
Logger.getInstance("#${AppleScriptDictionaryResolveProcessor::class.java.name}")

private fun appendResultsIfNeeded(
private fun appendFallbackTerms(
result: MutableCollection<DictionaryComponent>,
project: Project,
areThereUseStatements: Boolean,
filterStandardAdditions: Boolean,
filterCocoaStandard: Boolean,
hasUseStatements: Boolean,
shouldFilterAdditions: Boolean,
shouldFilterCocoa: Boolean,
) {
val dictionaryRegistry =
project.getService(AppleScriptProjectDictionaryService::class.java)
?: return
if (!filterStandardAdditions && !areThereUseStatements) {
if (!shouldFilterAdditions && !hasUseStatements) {
val scriptAdditions = dictionaryRegistry.getScriptingAdditionsTerminology()
collectAllComponentsFromDictionary(scriptAdditions, result, false)
collectTerms(scriptAdditions, result, false)
}
if (!filterCocoaStandard) {
if (!shouldFilterCocoa) {
val cocoaStandardTerminology = dictionaryRegistry.getCocoaStandardTerminology()
collectAllComponentsFromDictionary(cocoaStandardTerminology, result, false)
collectTerms(cocoaStandardTerminology, result, false)
}
}

/**
* Gather all term declarations from [importedDict].
*
* @param withCocoaStdLibFiltering if true, omit terms already present in the Standard Terminology
*/
private fun collectAllComponentsFromDictionary(
importedDict: ApplicationDictionary?,
private fun collectTerms(
dictionary: ApplicationDictionary?,
dictionaryComponents: MutableCollection<DictionaryComponent>,
withCocoaStdLibFiltering: Boolean,
shouldFilterCocoa: Boolean,
) {
if (importedDict == null) {
return
}

if (withCocoaStdLibFiltering) {
collectComponentsMissingFromCocoa(importedDict, dictionaryComponents)
if (dictionary == null) return

if (shouldFilterCocoa) {
val cocoaStandard =
dictionary.project
.getService(AppleScriptProjectDictionaryService::class.java)
?.getCocoaStandardTerminology()
dictionaryComponents.addAll(DictionaryTermCatalog.missingTerms(dictionary, cocoaStandard))
} else {
collectAllComponents(importedDict, dictionaryComponents)
dictionaryComponents.addAll(DictionaryTermCatalog.allTerms(dictionary))
}
}

private fun collectComponentsMissingFromCocoa(
importedDict: ApplicationDictionary,
dictionaryComponents: MutableCollection<DictionaryComponent>,
) {
val cocoaStandard =
importedDict.project
.getService(AppleScriptProjectDictionaryService::class.java)
?.getCocoaStandardTerminology()

if (cocoaStandard != null) {
importedDict.dictionaryEnumeratorMap.values.filterTo(dictionaryComponents) {
cocoaStandard.findEnumerator(it.getName()) == null
}
importedDict.dictionaryClassMap.values.filterTo(dictionaryComponents) {
cocoaStandard.findClass(it.getName()) == null
}
importedDict.allCommands.filterTo(dictionaryComponents) {
cocoaStandard.findCommand(it.getName()) == null
}
importedDict.dictionaryPropertyMap.values.filterTo(dictionaryComponents) {
cocoaStandard.findCommand(it.getName()) == null
}
}
}

private fun collectAllComponents(
importedDict: ApplicationDictionary,
dictionaryComponents: MutableCollection<DictionaryComponent>,
) {
dictionaryComponents.addAll(importedDict.dictionaryEnumeratorMap.values)
dictionaryComponents.addAll(importedDict.dictionaryPropertyMap.values)
dictionaryComponents.addAll(importedDict.dictionaryClassMap.values)
dictionaryComponents.addAll(importedDict.allCommands)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.intellij.plugin.applescript.lang.sdef

internal object DictionaryTermCatalog {
fun allTerms(dictionary: ApplicationDictionary): List<DictionaryComponent> =
buildList {
addAll(dictionary.dictionaryEnumeratorMap.values)
addAll(dictionary.dictionaryPropertyMap.values)
addAll(dictionary.dictionaryClassMap.values)
addAll(dictionary.allCommands)
}

fun missingTerms(
dictionary: ApplicationDictionary,
baseline: ApplicationDictionary?,
): List<DictionaryComponent> {
val availableBaseline = baseline ?: return emptyList()
return buildList {
dictionary.dictionaryEnumeratorMap.values.filterTo(this) {
availableBaseline.findEnumerator(it.getName()) == null
}
dictionary.dictionaryClassMap.values.filterTo(this) {
availableBaseline.findClass(it.getName()) == null
}
dictionary.allCommands.filterTo(this) {
availableBaseline.findCommand(it.getName()) == null
}
dictionary.dictionaryPropertyMap.values.filterTo(this) {
availableBaseline.findProperty(it.getName()) == null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package com.intellij.plugin.applescript.test.sdef

import com.intellij.plugin.applescript.AppleScriptFileType
import com.intellij.plugin.applescript.lang.dictionary.project.AppleScriptProjectDictionaryService
import com.intellij.plugin.applescript.lang.sdef.AppleScriptPropertyDefinition
import com.intellij.plugin.applescript.lang.sdef.ApplicationDictionary
import com.intellij.plugin.applescript.lang.sdef.DictionaryTermCatalog
import com.intellij.testFramework.fixtures.BasePlatformTestCase

class DictionaryTermCatalogTest : BasePlatformTestCase() {
fun testPropertyMatchesByKind() {
val imported = buildTestDictionary(project, IMPORTED_TERMS, "Calendar Plus", "calendar-plus.sdef")
val cocoa = buildTestDictionary(project, COCOA_TERMS, "Standard Terminology", "cocoa-standard.sdef")

val propertyNames =
DictionaryTermCatalog
.missingTerms(imported, cocoa)
.filterIsInstance<AppleScriptPropertyDefinition>()
.map { property -> property.getName() }

assertEquals(listOf("shared label"), propertyNames)
}

fun testAllTermsKeepOrder() {
val dictionary = buildTestDictionary(project, ORDERED_TERMS, "Calendar Plus", "ordered-terms.sdef")

val termNames = DictionaryTermCatalog.allTerms(dictionary).map { term -> term.getName() }

assertEquals(listOf("accepted", "summary", "event", "create event"), termNames)
}

fun testFilteredOrder() {
val dictionary = buildTestDictionary(project, ORDERED_TERMS, "Calendar Plus", "ordered-terms.sdef")
val emptyBaseline = buildTestDictionary(project, EMPTY_TERMS, "Standard Terminology", "empty-cocoa.sdef")

val termNames = DictionaryTermCatalog.missingTerms(dictionary, emptyBaseline).map { term -> term.getName() }

assertEquals(listOf("accepted", "event", "create event", "summary"), termNames)
}

fun testCompletionKeepsProperty() {
val projectDictionaries = project.getService(AppleScriptProjectDictionaryService::class.java)
try {
cacheCompletionDictionaries(projectDictionaries)
myFixture.configureByText(
AppleScriptFileType,
"""
use application "Calendar Primer"
use application "Calendar Plus"
set selectedValue to <caret>
""".trimIndent(),
)

myFixture.completeBasic()
val lookupStrings = requireNotNull(myFixture.lookupElementStrings)

assertTrue("Imported property must remain available", lookupStrings.contains("shared label"))
assertFalse("Cocoa property must be filtered", lookupStrings.contains("standard label"))
} finally {
projectDictionaries.clearCachedDictionariesForTests()
}
}

fun testCocoaRequired() {
val dictionary = buildTestDictionary(project, IMPORTED_TERMS, "Calendar Plus", "calendar-plus.sdef")

val terms = DictionaryTermCatalog.missingTerms(dictionary, null)

assertEmpty("Filtered terms require a Cocoa baseline", terms)
}

fun testNoUseFallbacks() {
val projectDictionaries = project.getService(AppleScriptProjectDictionaryService::class.java)

try {
projectDictionaries.clearCachedDictionariesForTests()
projectDictionaries.cacheDictionaryForTests(
ApplicationDictionary.SCRIPTING_ADDITIONS_LIBRARY,
buildTestDictionary(
project,
ADDITIONS_TERMS,
ApplicationDictionary.SCRIPTING_ADDITIONS_LIBRARY,
"additions.sdef",
),
)
projectDictionaries.cacheDictionaryForTests(
ApplicationDictionary.COCOA_STANDARD_LIBRARY,
buildTestDictionary(
project,
COCOA_TERMS,
ApplicationDictionary.COCOA_STANDARD_LIBRARY,
"cocoa.sdef",
),
)
myFixture.configureByText(AppleScriptFileType, "set selectedValue to <caret>")

myFixture.completeBasic()
val lookupStrings = requireNotNull(myFixture.lookupElementStrings)

assertTrue(
"No-use completion must include Scripting Additions",
lookupStrings.contains("addition fallback"),
)
assertTrue(
"No-use completion must include Cocoa terms",
lookupStrings.contains("standard label"),
)
} finally {
projectDictionaries.clearCachedDictionariesForTests()
}
}

private fun cacheCompletionDictionaries(projectDictionaries: AppleScriptProjectDictionaryService) {
projectDictionaries.clearCachedDictionariesForTests()
projectDictionaries.cacheDictionaryForTests(
"Calendar Primer",
buildTestDictionary(project, PRIMER_TERMS, "Calendar Primer", "calendar-primer.sdef"),
)
projectDictionaries.cacheDictionaryForTests(
"Calendar Plus",
buildTestDictionary(project, IMPORTED_TERMS, "Calendar Plus", "calendar-plus.sdef"),
)
projectDictionaries.cacheDictionaryForTests(
ApplicationDictionary.COCOA_STANDARD_LIBRARY,
buildTestDictionary(project, COCOA_TERMS, ApplicationDictionary.COCOA_STANDARD_LIBRARY, "cocoa.sdef"),
)
}

companion object {
private val IMPORTED_TERMS =
"""
<dictionary title="Calendar Plus Terminology">
<suite name="Calendar Plus Suite" code="calp">
<class name="event" code="cevt">
<property name="shared label" code="pshl" type="text"/>
<property name="standard label" code="pstl" type="text"/>
</class>
</suite>
</dictionary>
""".trimIndent()

private val PRIMER_TERMS =
"""
<dictionary title="Calendar Primer Terminology">
<suite name="Calendar Primer Suite" code="calr">
<class name="calendar source" code="csrc"/>
</suite>
</dictionary>
""".trimIndent()

private val COCOA_TERMS =
"""
<dictionary title="Standard Terminology">
<suite name="Standard Suite" code="cstd">
<command name="shared label" code="shlb"/>
<class name="application" code="capp">
<property name="standard label" code="stlb" type="text"/>
</class>
</suite>
</dictionary>
""".trimIndent()

private val ORDERED_TERMS =
"""
<dictionary title="Calendar Plus Terminology">
<suite name="Calendar Plus Suite" code="calp">
<command name="create event" code="crev"/>
<class name="event" code="cevt">
<property name="summary" code="psmm" type="text"/>
</class>
<enumeration name="participation status" code="psts">
<enumerator name="accepted" code="acpt"/>
</enumeration>
</suite>
</dictionary>
""".trimIndent()

private val EMPTY_TERMS =
"""
<dictionary title="Empty Terminology">
<suite name="Empty Suite" code="empt"/>
</dictionary>
""".trimIndent()

private val ADDITIONS_TERMS =
"""
<dictionary title="Scripting Additions Terminology">
<suite name="Scripting Additions Suite" code="ascr">
<command name="addition fallback" code="adfb"/>
</suite>
</dictionary>
""".trimIndent()
}
}
Loading