From 608506cb57f80f71892afdd6493579c932b6a406 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Tue, 1 Oct 2024 11:02:41 +0300 Subject: [PATCH] fix(init): fix some schema issues - Validate CamelCase name, TextMate scope - Skip serialization of unused properties - Disallow additional properties in schema --- cli/loader/src/lib.rs | 7 +++- cli/src/init.rs | 8 ++-- cli/src/main.rs | 52 +++++++++++++++++-------- cli/src/templates/gitattributes | 2 + docs/assets/schemas/config.schema.json | 15 +++++-- docs/assets/schemas/grammar.schema.json | 4 ++ 6 files changed, 62 insertions(+), 26 deletions(-) diff --git a/cli/loader/src/lib.rs b/cli/loader/src/lib.rs index c044d710..1b8083c0 100644 --- a/cli/loader/src/lib.rs +++ b/cli/loader/src/lib.rs @@ -159,7 +159,7 @@ impl TreeSitterJSON { pub struct Grammar { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] - pub upper_camel_name: Option, + pub camelcase: Option, pub scope: String, pub path: PathBuf, #[serde(default, skip_serializing_if = "PathsJSON::is_empty")] @@ -192,7 +192,8 @@ pub struct Metadata { pub authors: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub links: Option, - #[serde(skip_serializing_if = "Option::is_none")] + // #[serde(skip_serializing_if = "Option::is_none")] + #[serde(skip)] pub namespace: Option, } @@ -217,7 +218,9 @@ pub struct Links { pub struct Bindings { pub c: bool, pub go: bool, + #[serde(skip)] pub java: bool, + #[serde(skip)] pub kotlin: bool, pub node: bool, pub python: bool, diff --git a/cli/src/init.rs b/cli/src/init.rs index 484e2835..79524c1e 100644 --- a/cli/src/init.rs +++ b/cli/src/init.rs @@ -131,7 +131,7 @@ fn insert_after( #[derive(Serialize, Deserialize, Clone)] pub struct JsonConfigOpts { pub name: String, - pub upper_camel_name: String, + pub camelcase: String, pub description: String, #[serde(skip_serializing_if = "Option::is_none")] pub repository: Option, @@ -151,7 +151,7 @@ impl JsonConfigOpts { TreeSitterJSON { grammars: vec![Grammar { name: self.name.clone(), - upper_camel_name: Some(self.upper_camel_name), + camelcase: Some(self.camelcase), scope: self.scope, path: PathBuf::from("."), external_files: PathsJSON::Empty, @@ -194,7 +194,7 @@ impl Default for JsonConfigOpts { fn default() -> Self { Self { name: String::new(), - upper_camel_name: String::new(), + camelcase: String::new(), description: String::new(), repository: None, scope: String::new(), @@ -245,7 +245,7 @@ pub fn migrate_package_json(repo_path: &Path) -> Result { .into_iter() .map(|l| Grammar { name: name.clone(), - upper_camel_name: Some(name.to_upper_camel_case()), + camelcase: Some(name.to_upper_camel_case()), scope: l.scope.unwrap_or_else(|| format!("source.{name}")), path: l.path, external_files: l.external_files, diff --git a/cli/src/main.rs b/cli/src/main.rs index b45cdb9b..6e884b6d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -469,10 +469,20 @@ impl Init { .interact_text() }; - let upper_camel_name = |name: &str| { + let camelcase_name = |name: &str| { Input::::with_theme(&ColorfulTheme::default()) - .with_prompt("UpperCamelCase name") + .with_prompt("CamelCase name") .default(name.to_upper_camel_case()) + .validate_with(|input: &String| { + if input + .chars() + .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_') + { + Ok(()) + } else { + Err("The name must contain only letters, digits, and underscores") + } + }) .interact_text() }; @@ -506,6 +516,13 @@ impl Init { Input::::with_theme(&ColorfulTheme::default()) .with_prompt("TextMate scope") .default(format!("source.{name}")) + .validate_with(|input: &String| { + if input.starts_with("source.") || input.starts_with("text.") { + Ok(()) + } else { + Err("The scope must start with 'source.' or 'text.'") + } + }) .interact_text() }; @@ -549,20 +566,21 @@ impl Init { let email = || { Input::with_theme(&ColorfulTheme::default()) - .with_prompt("Author email") - .validate_with({ - let mut force = None; - move |input: &String| -> Result<(), &str> { - if input.contains('@') || input.trim().is_empty() || force.as_ref().map_or(false, |old| old == input) { - Ok(()) - } else { - force = Some(input.clone()); - Err("This is not an email address; type the same value again to force use") + .with_prompt("Author email") + .validate_with({ + move |input: &String| -> Result<(), &str> { + if (input.contains('@') && input.contains('.')) + || input.trim().is_empty() + { + Ok(()) + } else { + Err("This is not a valid email address") + } } - } - }) - .allow_empty(true) - .interact_text().map(|e| (!e.trim().is_empty()).then_some(e)) + }) + .allow_empty(true) + .interact_text() + .map(|e| (!e.trim().is_empty()).then_some(e)) }; let url = || { @@ -582,7 +600,7 @@ impl Init { let choices = [ "name", - "upper_camel_name", + "camelcase", "description", "repository", "scope", @@ -599,7 +617,7 @@ impl Init { ($choice:expr) => { match $choice { "name" => opts.name = name()?, - "upper_camel_name" => opts.upper_camel_name = upper_camel_name(&opts.name)?, + "camelcase" => opts.camelcase = camelcase_name(&opts.name)?, "description" => opts.description = description(&opts.name)?, "repository" => opts.repository = Some(repository(&opts.name)?), "scope" => opts.scope = scope(&opts.name)?, diff --git a/cli/src/templates/gitattributes b/cli/src/templates/gitattributes index 4cb10583..9d5c5d49 100644 --- a/cli/src/templates/gitattributes +++ b/cli/src/templates/gitattributes @@ -8,4 +8,6 @@ bindings/** linguist-generated binding.gyp linguist-generated setup.py linguist-generated Makefile linguist-generated +CMakeLists.txt linguist-generated Package.swift linguist-generated +go.mod linguist-generated diff --git a/docs/assets/schemas/config.schema.json b/docs/assets/schemas/config.schema.json index acc31ff7..98760d07 100644 --- a/docs/assets/schemas/config.schema.json +++ b/docs/assets/schemas/config.schema.json @@ -2,6 +2,9 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { + "$schema": { + "type": "string" + }, "grammars": { "type": "array", "items": { @@ -132,6 +135,7 @@ "description": "A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file." } }, + "additionalProperties": false, "required": [ "name", "scope" @@ -174,6 +178,7 @@ "description": "The project's homepage." } }, + "additionalProperties": false, "required": [ "repository" ] @@ -196,6 +201,7 @@ "format": "uri" } }, + "additionalProperties": false, "required": [ "name" ] @@ -209,6 +215,7 @@ "$comment": "Used as is in the Maven/Gradle group name and transformed accordingly for the package names and directories (e.g. io.github.treesitter.jtreesitter.html - src/main/java/io/github/treesitter/jtreesitter/html)." } }, + "additionalProperties": false, "required": [ "version", "links" @@ -230,11 +237,11 @@ }, "java": { "type": "boolean", - "default": true + "default": false }, "kotlin": { "type": "boolean", - "default": true + "default": false }, "node": { "type": "boolean", @@ -256,9 +263,11 @@ "type": "boolean", "default": true } - } + }, + "additionalProperties": false } }, + "additionalProperties": false, "required": [ "grammars", "metadata" diff --git a/docs/assets/schemas/grammar.schema.json b/docs/assets/schemas/grammar.schema.json index 747c89f1..442beb36 100644 --- a/docs/assets/schemas/grammar.schema.json +++ b/docs/assets/schemas/grammar.schema.json @@ -8,6 +8,10 @@ "additionalProperties": false, "properties": { + "$schema": { + "type": "string" + }, + "name": { "description": "The name of the grammar", "type": "string",