Skip to content

Commit 666834b

Browse files
committed
Add basic setup for JSON validation through schemas
1 parent eb84f96 commit 666834b

File tree

11 files changed

+198
-18
lines changed

11 files changed

+198
-18
lines changed

src/main/kotlin/com/demonwav/mcdev/i18n/I18nAnnotator.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import com.demonwav.mcdev.i18n.intentions.RemoveUnmatchedEntryIntention
1515
import com.demonwav.mcdev.i18n.intentions.TrimKeyIntention
1616
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry
1717
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes
18-
import com.demonwav.mcdev.util.mcDomain
18+
import com.demonwav.mcdev.util.resourceDomain
1919
import com.intellij.lang.annotation.AnnotationHolder
2020
import com.intellij.lang.annotation.Annotator
2121
import com.intellij.openapi.util.TextRange
@@ -50,7 +50,7 @@ class I18nAnnotator : Annotator {
5050
}
5151

5252
private fun checkEntryMatchesDefault(entry: I18nEntry, annotations: AnnotationHolder) {
53-
if (entry.project.findDefaultLangEntries(domain = entry.containingFile.virtualFile.mcDomain).any { it.key == entry.key }) {
53+
if (entry.project.findDefaultLangEntries(domain = entry.containingFile.virtualFile.resourceDomain).any { it.key == entry.key }) {
5454
return
5555
}
5656
annotations.createWarningAnnotation(entry.textRange, "Translation key not included in default localization file.")

src/main/kotlin/com/demonwav/mcdev/i18n/I18nEditorNotificationProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes
1616
import com.demonwav.mcdev.i18n.sorting.I18nSorter
1717
import com.demonwav.mcdev.i18n.sorting.Ordering
1818
import com.demonwav.mcdev.util.applyWriteAction
19-
import com.demonwav.mcdev.util.mcDomain
19+
import com.demonwav.mcdev.util.resourceDomain
2020
import com.intellij.openapi.editor.colors.EditorColors
2121
import com.intellij.openapi.editor.colors.EditorColorsManager
2222
import com.intellij.openapi.fileEditor.FileEditor
@@ -80,7 +80,7 @@ class I18nEditorNotificationProvider(private val project: Project) : EditorNotif
8080
}
8181

8282
private fun getMissingEntries(file: VirtualFile): Map<String, I18nEntry> {
83-
val defaultEntries = project.findDefaultLangEntries(scope = Scope.PROJECT, domain = file.mcDomain)
83+
val defaultEntries = project.findDefaultLangEntries(scope = Scope.PROJECT, domain = file.resourceDomain)
8484
val entries = project.findLangEntries(file = file, scope = Scope.PROJECT)
8585
val keys = entries.map { it.key }
8686
val missingEntries = defaultEntries.associate { it.key to it }.toMutableMap()

src/main/kotlin/com/demonwav/mcdev/i18n/I18nElementFactory.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import com.demonwav.mcdev.i18n.lang.I18nFileType
1515
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry
1616
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes
1717
import com.demonwav.mcdev.util.applyWriteAction
18-
import com.demonwav.mcdev.util.mcDomain
18+
import com.demonwav.mcdev.util.resourceDomain
1919
import com.intellij.ide.DataManager
2020
import com.intellij.openapi.actionSystem.DataContext
2121
import com.intellij.openapi.module.Module
@@ -50,7 +50,7 @@ object I18nElementFactory {
5050

5151
val files = FileTypeIndex.getFiles(I18nFileType, GlobalSearchScope.moduleScope(module))
5252
if (files.count { it.nameWithoutExtension.toLowerCase(Locale.ROOT) == I18nConstants.DEFAULT_LOCALE } > 1) {
53-
val choices = files.mapNotNull { it.mcDomain }.distinct().sorted()
53+
val choices = files.mapNotNull { it.resourceDomain }.distinct().sorted()
5454
val swingList = JBList(choices)
5555
DataManager.getInstance().dataContextFromFocus.doWhenDone(Consumer<DataContext> {
5656
JBPopupFactory.getInstance()

src/main/kotlin/com/demonwav/mcdev/i18n/i18n-utils.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ package com.demonwav.mcdev.i18n
1313
import com.demonwav.mcdev.i18n.lang.I18nFile
1414
import com.demonwav.mcdev.i18n.lang.I18nFileType
1515
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry
16-
import com.demonwav.mcdev.util.mcDomain
16+
import com.demonwav.mcdev.util.resourceDomain
1717
import com.intellij.openapi.project.Project
1818
import com.intellij.openapi.vfs.VirtualFile
1919
import com.intellij.psi.PsiManager
@@ -47,7 +47,7 @@ fun Project.findLangEntries(scope: Scope = Scope.GLOBAL, key: String? = null, fi
4747
{
4848
it.virtualFile != null
4949
&& (file == null || it.virtualFile.path == file.path)
50-
&& (domain == null || it.virtualFile.mcDomain == domain)
50+
&& (domain == null || it.virtualFile.resourceDomain == domain)
5151
},
5252
{ key == null || it.key == key }
5353
)
@@ -58,7 +58,7 @@ fun Project.findDefaultLangEntries(scope: Scope = Scope.GLOBAL, key: String? = n
5858
{
5959
it.virtualFile != null && it.virtualFile.nameWithoutExtension.toLowerCase(Locale.ROOT) == I18nConstants.DEFAULT_LOCALE
6060
&& (file == null || it.virtualFile.path == file.path)
61-
&& (domain == null || it.virtualFile.mcDomain == domain)
61+
&& (domain == null || it.virtualFile.resourceDomain == domain)
6262
},
6363
{ key == null || it.key == key }
6464
)
@@ -69,4 +69,4 @@ fun Project.findDefaultLangFile(domain: String? = null) =
6969
I18nConstants.DEFAULT_LOCALE_FILE,
7070
false,
7171
GlobalSearchScope.projectScope(this)
72-
).firstOrNull { domain == null || it.mcDomain == domain }
72+
).firstOrNull { domain == null || it.resourceDomain == domain }

src/main/kotlin/com/demonwav/mcdev/i18n/lang/I18nCompletionContributor.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import com.demonwav.mcdev.i18n.findDefaultLangEntries
1717
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry
1818
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes
1919
import com.demonwav.mcdev.util.getSimilarity
20-
import com.demonwav.mcdev.util.mcDomain
20+
import com.demonwav.mcdev.util.resourceDomain
2121
import com.intellij.codeInsight.completion.CompletionContributor
2222
import com.intellij.codeInsight.completion.CompletionParameters
2323
import com.intellij.codeInsight.completion.CompletionResultSet
@@ -55,7 +55,7 @@ class I18nCompletionContributor : CompletionContributor() {
5555

5656
if (KEY_PATTERN.accepts(position) || DUMMY_PATTERN.accepts(position)) {
5757
val text = position.text.let { it.substring(0, it.length - CompletionUtil.DUMMY_IDENTIFIER.length) }
58-
val domain = file.mcDomain
58+
val domain = file.resourceDomain
5959
handleKey(text, position, domain, result)
6060
}
6161
}

src/main/kotlin/com/demonwav/mcdev/i18n/sorting/I18nSorter.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import com.demonwav.mcdev.i18n.findDefaultLangFile
1717
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nEntry
1818
import com.demonwav.mcdev.i18n.lang.gen.psi.I18nTypes
1919
import com.demonwav.mcdev.util.lexicographical
20-
import com.demonwav.mcdev.util.mcDomain
20+
import com.demonwav.mcdev.util.resourceDomain
2121
import com.demonwav.mcdev.util.runWriteAction
2222
import com.intellij.openapi.project.Project
2323
import com.intellij.psi.PsiElement
@@ -32,7 +32,7 @@ object I18nSorter {
3232
private val descendingComparator = ascendingComparator.reversed()
3333

3434
fun query(project: Project, file: PsiFile, defaultSelection: Ordering = Ordering.ASCENDING) {
35-
val defaultFileMissing = project.findDefaultLangFile(file.virtualFile.mcDomain ?: return) == null
35+
val defaultFileMissing = project.findDefaultLangFile(file.virtualFile.resourceDomain ?: return) == null
3636
val isDefaultFile = file.name == I18nConstants.DEFAULT_LOCALE_FILE
3737
val (order, comments) = TranslationSortOrderDialog.show(defaultFileMissing || isDefaultFile, defaultSelection)
3838
if (order == null) {
@@ -47,7 +47,7 @@ object I18nSorter {
4747
Ordering.ASCENDING -> I18nElementFactory.assembleElements(project, it.sortedWith(ascendingComparator), keepComments)
4848
Ordering.DESCENDING -> I18nElementFactory.assembleElements(project, it.sortedWith(descendingComparator), keepComments)
4949
Ordering.TEMPLATE -> sortByTemplate(project, TemplateManager.getProjectTemplate(project), it, keepComments)
50-
else -> sortByTemplate(project, buildDefaultTemplate(project, file.virtualFile.mcDomain) ?: return, it, keepComments)
50+
else -> sortByTemplate(project, buildDefaultTemplate(project, file.virtualFile.resourceDomain) ?: return, it, keepComments)
5151
}
5252
}
5353

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2017 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.json
12+
13+
import com.demonwav.mcdev.util.resourceDomain
14+
import com.demonwav.mcdev.util.resourcePath
15+
import com.intellij.openapi.project.Project
16+
import com.intellij.openapi.vfs.VirtualFile
17+
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider
18+
import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory
19+
import com.jetbrains.jsonSchema.extension.SchemaType
20+
21+
class SchemaProviderFactory : JsonSchemaProviderFactory {
22+
override fun getProviders(project: Project) =
23+
listOf(SoundsSchemaProvider(), BlockstatesSchemaProvider())
24+
}
25+
26+
class SoundsSchemaProvider : JsonSchemaFileProvider {
27+
companion object {
28+
val FILE = JsonSchemaProviderFactory.getResourceFile(SchemaProviderFactory::class.java, "/jsonSchemas/sounds.schema.json")
29+
}
30+
31+
override fun getName() = "Minecraft Sounds JSON"
32+
33+
override fun isAvailable(file: VirtualFile) = file.resourceDomain != null && file.resourcePath == "sounds.json"
34+
35+
override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema
36+
37+
override fun getSchemaFile(): VirtualFile = FILE
38+
}
39+
40+
class BlockstatesSchemaProvider : JsonSchemaFileProvider {
41+
companion object {
42+
val FILE = JsonSchemaProviderFactory.getResourceFile(SchemaProviderFactory::class.java, "/jsonSchemas/blockstates.schema.json")
43+
}
44+
45+
override fun getName() = "Minecraft Blockstates JSON"
46+
47+
override fun isAvailable(file: VirtualFile) = file.resourceDomain != null && file.resourcePath?.startsWith("blockstates/") == true
48+
49+
override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema
50+
51+
override fun getSchemaFile(): VirtualFile = FILE
52+
}

src/main/kotlin/com/demonwav/mcdev/util/files.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ val VirtualFile.manifest: Manifest?
2929
}
3030

3131
// Technically resource domains are much more restricted ([a-z0-9_-]+) in modern versions, but we want to support as much as possible
32-
private val DOMAIN_PATTERN = Regex("^.*?/assets/([^/]+)/lang.*?$")
32+
private val RESOURCE_PATTERN = Regex("^.*?/assets/([^/]+)/(.*?)\$")
33+
34+
val VirtualFile.resourceDomain: String?
35+
get() = RESOURCE_PATTERN.matchEntire(this.path)?.groupValues?.get(1)
36+
37+
val VirtualFile.resourcePath: String?
38+
get() = RESOURCE_PATTERN.matchEntire(this.path)?.groupValues?.get(2)
3339

34-
val VirtualFile.mcDomain: String?
35-
get() = DOMAIN_PATTERN.matchEntire(this.path)?.groupValues?.get(1)
3640

3741
operator fun Manifest.get(attribute: String): String? = mainAttributes.getValue(attribute)
3842
operator fun Manifest.get(attribute: Attributes.Name): String? = mainAttributes.getValue(attribute)

src/main/resources/META-INF/plugin.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,10 @@
701701
<projectResolve implementation="com.demonwav.mcdev.platform.forge.gradle.ForgePatcherProjectResolverExtension"/>
702702
</extensions>
703703

704+
<extensions defaultExtensionNs="JavaScript.JsonSchema">
705+
<ProviderFactory implementation="com.demonwav.mcdev.json.SchemaProviderFactory"/>
706+
</extensions>
707+
704708
<application-components>
705709
<component>
706710
<implementation-class>com.demonwav.mcdev.i18n.I18nFileListener</implementation-class>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Minecraft Blockstates JSON",
4+
"definitions": {
5+
"variantDefinition": {
6+
"type": "object",
7+
"additionalProperties": {
8+
"oneOf": [
9+
{
10+
"type": "object",
11+
"additionalProperties": { "$ref": "#/definitions/variantObject" }
12+
},
13+
{
14+
"type": "array",
15+
"items": { "$ref": "#/definitions/variantObject" }
16+
}
17+
]
18+
}
19+
},
20+
"variantObject": {
21+
"type": "object",
22+
"properties": {
23+
"__comment": {
24+
"type": "string"
25+
},
26+
"model": { "type": "string" },
27+
"textures": {
28+
"type": "object",
29+
"additionalProperties": {
30+
"type": "string"
31+
}
32+
}
33+
},
34+
"additionalProperties": false
35+
}
36+
},
37+
"oneOf": [
38+
{
39+
"type": "object",
40+
"properties": {
41+
"__comment": {
42+
"type": "string"
43+
},
44+
"variants": {
45+
"type": "object",
46+
"additionalProperties": { "$ref": "#/definitions/variantObject" }
47+
}
48+
},
49+
"additionalProperties": false,
50+
"required": [ "variants" ],
51+
"not": { "required": [ "forge_marker" ] }
52+
},
53+
{
54+
"type": "object",
55+
"properties": {
56+
"__comment": {
57+
"type": "string"
58+
},
59+
"forge_marker": {
60+
"type": "integer",
61+
"enum": [ 1 ]
62+
},
63+
"variants": { "$ref": "#/definitions/variantDefinition" },
64+
"defaults": { "$ref": "#/definitions/variantObject" }
65+
},
66+
"additionalProperties": false,
67+
"required": [ "forge_marker", "variants" ]
68+
}
69+
]
70+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Minecraft Sounds JSON",
4+
"type": "object",
5+
"additionalProperties": {
6+
"type": "object",
7+
"properties": {
8+
"category": { "type": "string" },
9+
"replace": { "type": "boolean" },
10+
"subtitle": { "type": "string" },
11+
"sounds": {
12+
"type": "array",
13+
"items": {
14+
"oneOf": [
15+
{ "type": "string" },
16+
{
17+
"type": "object",
18+
"properties": {
19+
"name": { "type": "string" },
20+
"volume": {
21+
"type": "number",
22+
"minimum": 0,
23+
"maximum": 1,
24+
"default": 1
25+
},
26+
"pitch": {
27+
"type": "number",
28+
"default": 1
29+
},
30+
"weight": {
31+
"type": "number",
32+
"default": 1
33+
},
34+
"stream": {
35+
"type": "boolean",
36+
"default": false
37+
},
38+
"type": {
39+
"enum": ["sound", "event"],
40+
"default": "sound"
41+
}
42+
}
43+
}
44+
]
45+
},
46+
"uniqueItems": true
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)