[mdn] WEB-72460 Move Mdn strings to separate bundle; automatically generate compat descriptions; fix issues with HTML entities

(cherry picked from commit e1e16d2dcfd26fcf8a864fc4a5eaf8df68fc5b2f)

IJ-CR-162639

GitOrigin-RevId: 0b6af7982728f3163d407e10a2dd9f1232b86004
This commit is contained in:
Piotr Tomiak
2025-05-07 09:56:11 +02:00
committed by intellij-monorepo-bot
parent d1b3810a10
commit bb85bd4afc
6 changed files with 134 additions and 125 deletions

View File

@@ -49,50 +49,4 @@ element.is.not.closed=Element is not closed
# suppress inspection "UnusedProperty"
rename.start.tag.name.intention=Rename start tag ''{0}'' to ''{1}''
# suppress inspection "UnusedProperty"
rename.end.tag.name.intention=Rename end tag ''{0}'' to ''{1}''
# Dynamic usage in MdnSymbolDocumentationAdapter
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.browser_compatibility=Supported by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.flex_context=Supported in<br>Flex context by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.grid_context=Supported in<br>Grid context by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.multicol_context=Supported in<br>Multi-column Layout by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.paged_context=Supported in<br>Paged Media by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.supported_in_grid_layout="Supported in<br>Grid Layout by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.support_of_multi-keyword_values=Multiple values<br>supported by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.supported_for_width_and_other_sizing_properties=Sizing properties<br>supported by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.support_of_ruby_values=<code>ruby-*</code> supported by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.support_of_table_values=<code>table-*</code> supported by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.support_of_WOFF=<code>WOFF</code> supported by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.support_of_WOFF_2=<code>WOFF 2</code> supported by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.2d_context=Supported in<br>2D context by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.3d_context=Supported in<br>3D context by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.bitmaprenderer_context=Supported in<br><code>bitmaprenderer</code> context by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.webgl2_context=Supported in<br>WebGL 2 context by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.webgl_context=Supported in<br>WebGL context by
# suppress inspection "UnusedProperty"
mdn.documentation.section.compat.webgpu_context=Supported in<br>Web GPU context by
mdn.documentation.section.compat.support_of=<code>{0}</code> supported by
mdn.documentation.section.compat.supported_by.none=none
mdn.documentation.section.parameters=Params
mdn.documentation.section.returns=Returns
mdn.documentation.section.throws=Throws
mdn.documentation.section.syntax=Syntax
mdn.documentation.section.values=Values
rename.end.tag.name.intention=Rename end tag ''{0}'' to ''{1}''

View File

@@ -17,10 +17,15 @@
<orderEntry type="module" module-name="intellij.relaxng" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.javascript.impl" />
<orderEntry type="module" module-name="intellij.xml.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.xml.impl" />
<orderEntry type="library" name="jackson-databind" level="project" />
<orderEntry type="library" name="jackson" level="project" />
<orderEntry type="library" name="jackson-module-kotlin" level="project" />
<orderEntry type="module" module-name="intellij.xml.langInjection" />
<orderEntry type="module" module-name="intellij.platform.codeStyle" />
<orderEntry type="module" module-name="intellij.xml.parser" />
<orderEntry type="module" module-name="intellij.javascript" />
<orderEntry type="module" module-name="intellij.platform.indexing" />
<orderEntry type="module" module-name="intellij.platform.analysis.impl" />
</component>
</module>

View File

@@ -43,59 +43,8 @@ val htmlSpecialMappings = mapOf(
* When adding a known property here, ensure that there is appropriate key in XmlPsiBundle.
*/
const val defaultBcdContext = "\$default_context"
val knownBcdIds = setOf(
defaultBcdContext,
"flex_context",
"grid_context",
"multicol_context",
"paged_context",
"supported_for_width_and_other_sizing_properties",
"supported_in_grid_layout",
// CSS values support - by default not translated
"support_of_multi-keyword_values", // translated
"support_of_flow-root",
"support_of_grid",
"support_of_flex",
"support_of_ruby_values", //translated
"support_of_table_values", //translated
"support_of_inline-table",
"support_of_inline-grid",
"support_of_inline-flex",
"support_of_inline-block",
"support_of_WOFF", //translated
"support_of_WOFF_2", //translated
"support_of_-webkit-resizer",
"support_of_-webkit-scrollbar",
"support_of_-webkit-scrollbar-button",
"support_of_-webkit-scrollbar-corner",
"support_of_-webkit-scrollbar-thumb",
"support_of_-webkit-scrollbar-track",
"support_of_-webkit-scrollbar-track-piece",
"support_of_color-mix",
"support_of_interpolation_color_space",
"support_of_hue_interpolation_method",
"support_of_fit-content",
"support_of_fit-content_function",
"support_of_hsl",
"support_of_hwb",
"support_of_lch",
"support_of_oklch",
//JS - by default not translated
"support_of_compressedTexImage2D",
"support_of_compressedTexImage3D",
"support_of_texParameterf",
"support_of_texParameteri",
"2d_context",
"3d_context",
"bitmaprenderer_context",
"webgl2_context",
"webgl_context",
"webgpu_context"
)
val jsRuntimesMap = MdnJavaScriptRuntime.values().associateBy { it.mdnId }
val jsRuntimesMap = MdnJavaScriptRuntime.entries.associateBy { it.mdnId }
val BROWSER_COMPAT_DATA_PATH = PathManager.getCommunityHomePath() + "/xml/xml-psi-impl/mdn-doc-gen/work/browser-compat-data.json"
val MDN_CONTENT_ROOT = PathManager.getCommunityHomePath() + "/xml/xml-psi-impl/mdn-doc-gen/work/mdn-content/files"
@@ -103,7 +52,8 @@ val YARI_BUILD_PATH = PathManager.getCommunityHomePath() + "/xml/xml-psi-impl/md
const val BUILT_LANG = "en-us"
const val WEB_DOCS = "docs/web"
const val MDN_DOCS_URL_PREFIX = "\$MDN_URL\$"
const val OUTPUT_DIR = "xml/xml-psi-impl/gen/com/intellij/documentation/mdn/"
const val OUTPUT_DIR = "xml/xml-psi-impl/resources-gen/com/intellij/documentation/mdn/"
const val BUNDLE_OUTPUT_DIR = "xml/xml-psi-impl/resources-gen/messages/"
const val SEE_REFERENCE = "\$SEE_REFERENCE\$"
val seePattern = Regex("<p>See <a href=\"/$BUILT_LANG/$WEB_DOCS/([a-z0-9_\\-/]+)(#[a-z]+)?\"><code>[a-z0-9_\\-]+</code></a>\\.</p>",
@@ -121,6 +71,8 @@ val jsWebApiNameFilter: (String) -> Boolean = { name ->
&& name != "index"
}
val bcdIdDescriptions = mutableMapOf<String, String>()
/* It's so much easier to run a test, than to setup the whole IJ environment */
class GenerateMdnDocumentation : BasePlatformTestCase() {
@@ -183,6 +135,7 @@ class GenerateMdnDocumentation : BasePlatformTestCase() {
FileUtil.writeToFile(Path.of(PathManager.getCommunityHomePath(), OUTPUT_DIR, "${MdnApiNamespace.WebApi.name}-index.json").toFile(),
GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create()
.toJson(index))
updateBcdDescriptions()
}
fun testGenJsGlobalObjects() {
@@ -234,6 +187,7 @@ class GenerateMdnDocumentation : BasePlatformTestCase() {
.registerTypeAdapter(Map::class.java, CompatibilityMapSerializer())
.create()
.toJson(additionalMetadata() + data))
updateBcdDescriptions()
}
private fun <T : Any> extractInformationSimple(bcdPath: String, mdnPath: String,
@@ -993,20 +947,20 @@ class GenerateMdnDocumentation : BasePlatformTestCase() {
?.takeIf { map -> map.values.any { it == null || it.isNotEmpty() } }
?.filterValues { it != null }
?.let {
val bcdId = if (id != defaultBcdContext) getBcdId(id) else id
if (bcdId != defaultBcdContext) {
val description = data.compat.description
if (description != null) {
bcdIdDescriptions.putIfAbsent(bcdId, description)
}
}
@Suppress("UNCHECKED_CAST")
Pair(if (id != defaultBcdContext) getBcdId(id) else id,
it.toMap(TreeMap()) as Map<MdnJavaScriptRuntime, String>)
Pair(bcdId, it.toMap(TreeMap()) as Map<MdnJavaScriptRuntime, String>)
}
}
.toMap(TreeMap())
.takeIf { it.isNotEmpty() }
?.let {
it.forEach { (id, _) ->
if (!knownBcdIds.contains(id)) throw AssertionError(
"Unknown BCD id: $id. Add id to the list and possibly add readable caption to Xml bundle (mdn.documentation.section.compat.* strings).")
}
it
}
private fun getBcdId(id: String): String =
id.takeLastWhile { it != '.' }.let {
@@ -1109,6 +1063,25 @@ class GenerateMdnDocumentation : BasePlatformTestCase() {
private fun File.toMdnUrl(): String =
toString().removePrefix(getMdnDir("").toString())
private fun updateBcdDescriptions() {
val bundleFile = Path.of(PathManager.getCommunityHomePath(), BUNDLE_OUTPUT_DIR, "MdnBundle.properties").toFile()
val contents = FileUtil.loadLines(bundleFile)
val existingProperties = contents.asSequence()
.filter { it.startsWith("mdn.documentation.section.compat.")}
.map { it.substring("mdn.documentation.section.compat.".length).substringBefore("=") }
.toSet()
bcdIdDescriptions
.filter { it.key !in existingProperties }
.ifEmpty { return }
.forEach { (id, description) ->
contents.add("mdn.documentation.section.compat.$id=$description")
}
FileUtil.writeToFile(bundleFile, contents.joinToString("\n"));
}
private inner class CssElementInfo(docDir: File) {
val name: String
@@ -1255,8 +1228,11 @@ private fun RawProse.appendOtherSections(indexDataProseValues: List<JsonObject>)
content,
if (try_it_section != null) {
val iframeEnd = try_it_section.indexOf("</iframe>")
if (iframeEnd < 0) throw RuntimeException("Cannot find iframe end - $try_it_section")
try_it_section.substring(iframeEnd + "</iframe>".length).trim()
.takeIf { it > 0 }?.let { it + "</iframe>".length }
?: try_it_section.indexOf("</interactive-example>")
.takeIf { it > 0 }?.let { it + "</interactive-example>".length }
if (iframeEnd == null) throw RuntimeException("Cannot find iframe or interactive-example end - $try_it_section")
try_it_section.substring(iframeEnd).trim()
}
else "",
description_section?.trim() ?: ""
@@ -1283,10 +1259,13 @@ private fun String.patchProse(): String =
.replace("&apos;", "'")
.replace("&quot;", "\"")
.replace("&nbsp;", " ")
.replace("&ldquo","")
.replace("&rdquo","")
.replace("&hellip","")
.replace(Regex("<p>\\s+"), "<p>")
.replace(Regex("(^<p>\\s*)|(\\s*</p>)|(\\s*<img[^>]*>)|(\\s*<figure\\s*class=\"table-container\">\\s*</figure>\\s*)"), "")
.also { fixedProse ->
Regex("&(?!lt|gt|amp)[a-z]*;").find(fixedProse)?.let {
Regex("&(?!lt|gt|amp|shy)[a-z]*;").find(fixedProse)?.let {
throw Exception("Unknown entity found in prose: ${it.value}")
}
}

View File

@@ -0,0 +1,42 @@
mdn.documentation.section.compat.support_of=<code>{0}</code> supported by
mdn.documentation.section.compat.supported_by.none=none
mdn.documentation.section.parameters=Params
mdn.documentation.section.returns=Returns
mdn.documentation.section.throws=Throws
mdn.documentation.section.syntax=Syntax
mdn.documentation.section.values=Values
# Dynamic usage in MdnSymbolDocumentationAdapter
mdn.documentation.section.compat.browser_compatibility=Supported by
mdn.documentation.section.compat.flex_context=Supported in<br>Flex Layout by
mdn.documentation.section.compat.grid_context=Supported in<br>Grid Layout by
mdn.documentation.section.compat.block_context=Supported in<br>Block Layout by
mdn.documentation.section.compat.multicol_context=Supported in<br>Multi-column Layout by
mdn.documentation.section.compat.paged_context=Supported in<br>Paged Media by
mdn.documentation.section.compat.position_absolute_context=Supported in<br>absolute position context by
mdn.documentation.section.compat.supported_in_grid_layout="Supported in<br>Grid Layout by
mdn.documentation.section.compat.2d_context=Supported in<br>2D context by
mdn.documentation.section.compat.3d_context=Supported in<br>3D context by
mdn.documentation.section.compat.bitmaprenderer_context=Supported in<br><code>bitmaprenderer</code> context by
mdn.documentation.section.compat.webgl2_context=Supported in<br>WebGL 2 context by
mdn.documentation.section.compat.webgl_context=Supported in<br>WebGL context by
mdn.documentation.section.compat.webgpu_context=Supported in<br>Web GPU context by
mdn.documentation.section.compat.supported_for_width_and_other_sizing_properties=Sizing properties<br>supported by
mdn.documentation.section.compat.support_of_multi-keyword_values=Multiple values<br>supported by
mdn.documentation.section.compat.support_of_WOFF=<code>WOFF</code> supported by
mdn.documentation.section.compat.support_of_WOFF_2=<code>WOFF 2</code> supported by
mdn.documentation.section.compat.support_of_dialog_elements=<code>beforetoggle</code> event fires at dialog elements
mdn.documentation.section.compat.support_of_details_elements=<code>toggle</code> event fires at details elements
mdn.documentation.section.compat.support_of_-webkit-scrollbar=<code>::-webkit-scrollbar</code>
mdn.documentation.section.compat.support_of_-webkit-scrollbar-button=<code>::-webkit-scrollbar-button</code>
mdn.documentation.section.compat.support_of_-webkit-scrollbar-thumb=<code>::-webkit-scrollbar-thumb</code>
mdn.documentation.section.compat.support_of_-webkit-scrollbar-track=<code>::-webkit-scrollbar-track</code>
mdn.documentation.section.compat.support_of_-webkit-scrollbar-track-piece=<code>::-webkit-scrollbar-track-piece</code>
mdn.documentation.section.compat.support_of_-webkit-scrollbar-corner=<code>::-webkit-scrollbar-corner</code>
mdn.documentation.section.compat.support_of_-webkit-resizer=<code>::-webkit-resizer</code>
mdn.documentation.section.compat.support_of_fit-content=<code>fit-content()</code>
mdn.documentation.section.compat.support_of_fit-content_function=<code>fit-content()</code>
mdn.documentation.section.compat.support_of_hsl=<code>hsl()</code> (HSL color model)
mdn.documentation.section.compat.support_of_hwb=<code>hwb()</code> (HWB color model)
mdn.documentation.section.compat.support_of_lch=<code>lch()</code> (LCH color model)
mdn.documentation.section.compat.support_of_oklch=<code>oklch()</code> (OKLCH color model)

View File

@@ -0,0 +1,30 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.documentation.mdn;
import com.intellij.DynamicBundle;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;
import java.util.function.Supplier;
public final class MdnBundle {
private static final @NonNls String BUNDLE = "messages.MdnBundle";
private static final DynamicBundle INSTANCE = new DynamicBundle(MdnBundle.class, BUNDLE);
private MdnBundle() {
}
public static @NotNull @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
return INSTANCE.getMessage(key, params);
}
public static @NotNull Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
return INSTANCE.getLazyMessage(key, params);
}
public static boolean hasKey(String key) {
return INSTANCE.containsKey(key);
}
}

View File

@@ -26,7 +26,6 @@ import com.intellij.psi.xml.*
import com.intellij.util.asSafely
import com.intellij.webSymbols.WebSymbolApiStatus
import com.intellij.webSymbols.WebSymbolsBundle
import com.intellij.xml.psi.XmlPsiBundle
import com.intellij.xml.util.HtmlUtil
import org.jetbrains.annotations.Nls
import java.util.*
@@ -225,15 +224,15 @@ class MdnSymbolDocumentationAdapter(override val name: String,
doc.compatibility!!.entries.forEach { (id, map) ->
val actualId = if (id == defaultBcdContext) "browser_compatibility" else id
val bundleKey = "mdn.documentation.section.compat.$actualId"
val sectionName: String = if (actualId.startsWith("support_of_") && !XmlPsiBundle.hasKey(bundleKey)) {
XmlPsiBundle.message("mdn.documentation.section.compat.support_of", actualId.substring("support_of_".length))
val sectionName: String = if (actualId.startsWith("support_of_") && !MdnBundle.hasKey(bundleKey)) {
MdnBundle.message("mdn.documentation.section.compat.support_of", actualId.substring("support_of_".length))
}
else {
XmlPsiBundle.message(bundleKey)
MdnBundle.message(bundleKey)
}
result[sectionName] = map.entries
.joinToString(", ") { it.key.displayName + (if (it.value.isNotEmpty()) " " + it.value else "") }
.ifBlank { XmlPsiBundle.message("mdn.documentation.section.compat.supported_by.none") }
.ifBlank { MdnBundle.message("mdn.documentation.section.compat.supported_by.none") }
}
}
doc.status?.asSequence()
@@ -329,11 +328,11 @@ data class MdnJsSymbolDocumentation(override val url: String?,
get() {
val result = mutableMapOf<String, String>()
parameters?.takeIf { it.isNotEmpty() }?.let {
result.put(XmlPsiBundle.message("mdn.documentation.section.parameters"), buildSubSection(it))
result.put(MdnBundle.message("mdn.documentation.section.parameters"), buildSubSection(it))
}
returns?.let { result.put(XmlPsiBundle.message("mdn.documentation.section.returns"), it) }
returns?.let { result.put(MdnBundle.message("mdn.documentation.section.returns"), it) }
throws?.takeIf { it.isNotEmpty() }?.let {
result.put(XmlPsiBundle.message("mdn.documentation.section.throws"), buildSubSection(it))
result.put(MdnBundle.message("mdn.documentation.section.throws"), buildSubSection(it))
}
return result
}
@@ -349,7 +348,7 @@ data class MdnCssBasicSymbolDocumentation(override val url: String?,
get() {
val result = mutableMapOf<String, String>()
formalSyntax?.takeIf { it.isNotEmpty() }?.let {
result.put(XmlPsiBundle.message("mdn.documentation.section.syntax"), "<pre><code>$it</code></pre>")
result.put(MdnBundle.message("mdn.documentation.section.syntax"), "<pre><code>$it</code></pre>")
}
return result
}
@@ -366,7 +365,7 @@ data class MdnCssAtRuleSymbolDocumentation(override val url: String?,
get() {
val result = mutableMapOf<String, String>()
formalSyntax?.takeIf { it.isNotEmpty() }?.let {
result.put(XmlPsiBundle.message("mdn.documentation.section.syntax"), "<pre><code>$it</code></pre>")
result.put(MdnBundle.message("mdn.documentation.section.syntax"), "<pre><code>$it</code></pre>")
}
return result
}
@@ -383,10 +382,10 @@ data class MdnCssPropertySymbolDocumentation(override val url: String?,
get() {
val result = mutableMapOf<String, String>()
formalSyntax?.takeIf { it.isNotEmpty() }?.let {
result.put(XmlPsiBundle.message("mdn.documentation.section.syntax"), "<pre><code>$it</code></pre>")
result.put(MdnBundle.message("mdn.documentation.section.syntax"), "<pre><code>$it</code></pre>")
}
values?.takeIf { it.isNotEmpty() }?.let {
result.put(XmlPsiBundle.message("mdn.documentation.section.values"), buildSubSection(values))
result.put(MdnBundle.message("mdn.documentation.section.values"), buildSubSection(values))
}
return result
}