fix(init): fix some schema issues

- Validate CamelCase name, TextMate scope
- Skip serialization of unused properties
- Disallow additional properties in schema
This commit is contained in:
ObserverOfTime 2024-10-01 11:02:41 +03:00 committed by Amaan Qureshi
parent 0683136ca0
commit 608506cb57
6 changed files with 62 additions and 26 deletions

View file

@ -159,7 +159,7 @@ impl TreeSitterJSON {
pub struct Grammar {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub upper_camel_name: Option<String>,
pub camelcase: Option<String>,
pub scope: String,
pub path: PathBuf,
#[serde(default, skip_serializing_if = "PathsJSON::is_empty")]
@ -192,7 +192,8 @@ pub struct Metadata {
pub authors: Option<Vec<Author>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<Links>,
#[serde(skip_serializing_if = "Option::is_none")]
// #[serde(skip_serializing_if = "Option::is_none")]
#[serde(skip)]
pub namespace: Option<String>,
}
@ -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,

View file

@ -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<Url>,
@ -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<bool> {
.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,

View file

@ -469,10 +469,20 @@ impl Init {
.interact_text()
};
let upper_camel_name = |name: &str| {
let camelcase_name = |name: &str| {
Input::<String>::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::<String>::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)?,

View file

@ -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

View file

@ -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"

View file

@ -8,6 +8,10 @@
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string"
},
"name": {
"description": "The name of the grammar",
"type": "string",