Allow trailing commas in builtin JSONC schemas (#43854)

Ian Chamberlain created

The JSON language server looks for a top-level `allowTrailingCommas`
flag to decide whether it should warn for trailing commas. Since the
JSONC parser for these builtin files can handles trailing commas, adding
this flag to the schema also prevents a warning for those commas.

I don't think there's an issue that is only for this specific issue, but
it relates to *many* existing / older issues:
- #18509
- #17487
- #40970
- #18509
- #21303

Release Notes:

- Suppress warning for trailing commas in builtin JSON files
(`settings.json`, `keymap.json`, etc.)

Change summary

crates/settings/src/keymap_file.rs    |  5 ++++-
crates/settings/src/settings_store.rs |  3 ++-
crates/snippet_provider/src/format.rs |  3 ++-
crates/task/src/debug_format.rs       |  1 +
crates/task/src/task_template.rs      |  3 ++-
crates/util/src/schemars.rs           | 17 +++++++++++++++++
6 files changed, 28 insertions(+), 4 deletions(-)

Detailed changes

crates/settings/src/keymap_file.rs 🔗

@@ -15,6 +15,7 @@ use util::ResultExt as _;
 use util::{
     asset_str,
     markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString},
+    schemars::AllowTrailingCommas,
 };
 
 use crate::SettingsAssets;
@@ -451,7 +452,9 @@ impl KeymapFile {
     /// Creates a JSON schema generator, suitable for generating json schemas
     /// for actions
     pub fn action_schema_generator() -> schemars::SchemaGenerator {
-        schemars::generate::SchemaSettings::draft2019_09().into_generator()
+        schemars::generate::SchemaSettings::draft2019_09()
+            .with_transform(AllowTrailingCommas)
+            .into_generator()
     }
 
     pub fn generate_json_schema_for_registered_actions(cx: &mut App) -> Value {

crates/settings/src/settings_store.rs 🔗

@@ -25,7 +25,7 @@ use std::{
 use util::{
     ResultExt as _,
     rel_path::RelPath,
-    schemars::{DefaultDenyUnknownFields, replace_subschema},
+    schemars::{AllowTrailingCommas, DefaultDenyUnknownFields, replace_subschema},
 };
 
 pub type EditorconfigProperties = ec4rs::Properties;
@@ -1010,6 +1010,7 @@ impl SettingsStore {
     pub fn json_schema(&self, params: &SettingsJsonSchemaParams) -> Value {
         let mut generator = schemars::generate::SchemaSettings::draft2019_09()
             .with_transform(DefaultDenyUnknownFields)
+            .with_transform(AllowTrailingCommas)
             .into_generator();
 
         UserSettingsContent::json_schema(&mut generator);

crates/snippet_provider/src/format.rs 🔗

@@ -2,7 +2,7 @@ use collections::HashMap;
 use schemars::{JsonSchema, json_schema};
 use serde::Deserialize;
 use std::borrow::Cow;
-use util::schemars::DefaultDenyUnknownFields;
+use util::schemars::{AllowTrailingCommas, DefaultDenyUnknownFields};
 
 #[derive(Deserialize)]
 pub struct VsSnippetsFile {
@@ -14,6 +14,7 @@ impl VsSnippetsFile {
     pub fn generate_json_schema() -> serde_json::Value {
         let schema = schemars::generate::SchemaSettings::draft2019_09()
             .with_transform(DefaultDenyUnknownFields)
+            .with_transform(AllowTrailingCommas)
             .into_generator()
             .root_schema_for::<Self>();
 

crates/task/src/debug_format.rs 🔗

@@ -357,6 +357,7 @@ impl DebugTaskFile {
             "$schema": meta_schema,
             "title": "Debug Configurations",
             "description": "Configuration for debug scenarios",
+            "allowTrailingCommas": true,
             "type": "array",
             "items": {
                 "type": "object",

crates/task/src/task_template.rs 🔗

@@ -4,7 +4,7 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use sha2::{Digest, Sha256};
 use std::path::PathBuf;
-use util::schemars::DefaultDenyUnknownFields;
+use util::schemars::{AllowTrailingCommas, DefaultDenyUnknownFields};
 use util::serde::default_true;
 use util::{ResultExt, truncate_and_remove_front};
 
@@ -118,6 +118,7 @@ impl TaskTemplates {
     pub fn generate_json_schema() -> serde_json::Value {
         let schema = schemars::generate::SchemaSettings::draft2019_09()
             .with_transform(DefaultDenyUnknownFields)
+            .with_transform(AllowTrailingCommas)
             .into_generator()
             .root_schema_for::<Self>();
 

crates/util/src/schemars.rs 🔗

@@ -53,3 +53,20 @@ impl schemars::transform::Transform for DefaultDenyUnknownFields {
         transform_subschemas(self, schema);
     }
 }
+
+/// Defaults `allowTrailingCommas` to `true`, for use with `json-language-server`.
+/// This can be applied to any schema that will be treated as `jsonc`.
+///
+/// Note that this is non-recursive and only applied to the root schema.
+#[derive(Clone)]
+pub struct AllowTrailingCommas;
+
+impl schemars::transform::Transform for AllowTrailingCommas {
+    fn transform(&mut self, schema: &mut schemars::Schema) {
+        if let Some(object) = schema.as_object_mut()
+            && !object.contains_key("allowTrailingCommas")
+        {
+            object.insert("allowTrailingCommas".to_string(), true.into());
+        }
+    }
+}