Merge pull request #224 from SeaQL/rust-keywords

Escape rust keywords with `r#` raw identifier
This commit is contained in:
Chris Tsang 2021-10-07 16:35:50 +08:00 committed by Chris Tsang
parent a9f9599a4d
commit cab0f6ba6e
12 changed files with 406 additions and 18 deletions

View File

@ -1,3 +1,4 @@
use crate::util::escape_rust_keyword;
use heck::{CamelCase, SnakeCase};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
@ -14,11 +15,11 @@ pub struct Column {
impl Column {
pub fn get_name_snake_case(&self) -> Ident {
format_ident!("{}", self.name.to_snake_case())
format_ident!("{}", escape_rust_keyword(self.name.to_snake_case()))
}
pub fn get_name_camel_case(&self) -> Ident {
format_ident!("{}", self.name.to_camel_case())
format_ident!("{}", escape_rust_keyword(self.name.to_camel_case()))
}
pub fn get_rs_type(&self) -> TokenStream {

View File

@ -597,18 +597,85 @@ mod tests {
name: "id".to_owned(),
}],
},
Entity {
table_name: "rust_keyword".to_owned(),
columns: vec![
Column {
name: "id".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: true,
not_null: true,
unique: false,
},
Column {
name: "testing".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "rust".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "keywords".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "type".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "typeof".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "crate".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
Column {
name: "self".to_owned(),
col_type: ColumnType::Integer(Some(11)),
auto_increment: false,
not_null: true,
unique: false,
},
],
relations: vec![],
conjunct_relations: vec![],
primary_keys: vec![PrimaryKey {
name: "id".to_owned(),
}],
},
]
}
#[test]
fn test_gen_expanded_code_blocks() -> io::Result<()> {
let entities = setup();
const ENTITY_FILES: [&str; 5] = [
const ENTITY_FILES: [&str; 6] = [
include_str!("../../tests/expanded/cake.rs"),
include_str!("../../tests/expanded/cake_filling.rs"),
include_str!("../../tests/expanded/filling.rs"),
include_str!("../../tests/expanded/fruit.rs"),
include_str!("../../tests/expanded/vendor.rs"),
include_str!("../../tests/expanded/rust_keyword.rs"),
];
assert_eq!(entities.len(), ENTITY_FILES.len());
@ -642,12 +709,13 @@ mod tests {
#[test]
fn test_gen_compact_code_blocks() -> io::Result<()> {
let entities = setup();
const ENTITY_FILES: [&str; 5] = [
const ENTITY_FILES: [&str; 6] = [
include_str!("../../tests/compact/cake.rs"),
include_str!("../../tests/compact/cake_filling.rs"),
include_str!("../../tests/compact/filling.rs"),
include_str!("../../tests/compact/fruit.rs"),
include_str!("../../tests/compact/vendor.rs"),
include_str!("../../tests/compact/rust_keyword.rs"),
];
assert_eq!(entities.len(), ENTITY_FILES.len());

View File

@ -1,5 +1,6 @@
mod entity;
mod error;
mod util;
pub use entity::*;
pub use error::*;

View File

@ -0,0 +1,23 @@
pub(crate) fn escape_rust_keyword<T>(string: T) -> String
where
T: ToString,
{
let string = string.to_string();
if RUST_KEYWORDS.iter().any(|s| s.eq(&string)) {
format!("r#{}", string)
} else if RUST_SPECIAL_KEYWORDS.iter().any(|s| s.eq(&string)) {
format!("{}_", string)
} else {
string
}
}
pub(crate) const RUST_KEYWORDS: [&str; 49] = [
"as", "async", "await", "break", "const", "continue", "dyn", "else", "enum", "extern", "false",
"fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
"return", "static", "struct", "super", "trait", "true", "type", "union", "unsafe", "use",
"where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv",
"try", "typeof", "unsized", "virtual", "yield",
];
pub(crate) const RUST_SPECIAL_KEYWORDS: [&str; 3] = ["crate", "Self", "self"];

View File

@ -0,0 +1,30 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "rust_keyword")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub testing: i32,
pub rust: i32,
pub keywords: i32,
pub r#type: i32,
pub r#typeof: i32,
pub crate_: i32,
pub self_: i32,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
_ => panic!("No RelationDef"),
}
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,79 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
use sea_orm::entity::prelude::*;
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
pub struct Entity;
impl EntityName for Entity {
fn table_name(&self) -> &str {
"rust_keyword"
}
}
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model {
pub id: i32,
pub testing: i32,
pub rust: i32,
pub keywords: i32,
pub r#type: i32,
pub r#typeof: i32,
pub crate_: i32,
pub self_: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
Id,
Testing,
Rust,
Keywords,
Type,
Typeof,
Crate,
Self_,
}
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey {
Id,
}
impl PrimaryKeyTrait for PrimaryKey {
type ValueType = i32;
fn auto_increment() -> bool {
true
}
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl ColumnTrait for Column {
type EntityName = Entity;
fn def(&self) -> ColumnDef {
match self {
Self::Id => ColumnType::Integer.def(),
Self::Testing => ColumnType::Integer.def(),
Self::Rust => ColumnType::Integer.def(),
Self::Keywords => ColumnType::Integer.def(),
Self::Type => ColumnType::Integer.def(),
Self::Typeof => ColumnType::Integer.def(),
Self::Crate => ColumnType::Integer.def(),
Self::Self_ => ColumnType::Integer.def(),
}
}
}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
_ => panic!("No RelationDef"),
}
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,4 +1,4 @@
use crate::util::field_not_ignored;
use crate::util::{escape_rust_keyword, field_not_ignored, trim_starting_raw_identifier};
use heck::CamelCase;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, quote_spanned};
@ -29,10 +29,10 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result<Token
.clone()
.into_iter()
.map(|field| {
let mut ident = format_ident!(
"{}",
field.ident.as_ref().unwrap().to_string().to_camel_case()
);
let ident = field.ident.as_ref().unwrap().to_string();
let ident = trim_starting_raw_identifier(ident).to_camel_case();
let ident = escape_rust_keyword(ident);
let mut ident = format_ident!("{}", &ident);
for attr in field.attrs.iter() {
if let Some(ident) = attr.path.get_ident() {
if ident != "sea_orm" {

View File

@ -1,3 +1,5 @@
use crate::util::{escape_rust_keyword, trim_starting_raw_identifier};
use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{
@ -5,8 +7,6 @@ use syn::{
Lit, Meta,
};
use convert_case::{Case, Casing};
pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Result<TokenStream> {
// if #[sea_orm(table_name = "foo", schema_name = "bar")] specified, create Entity struct
let mut table_name = None;
@ -60,8 +60,10 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
if let Fields::Named(fields) = item_struct.fields {
for field in fields.named {
if let Some(ident) = &field.ident {
let mut field_name =
Ident::new(&ident.to_string().to_case(Case::Pascal), Span::call_site());
let mut field_name = Ident::new(
&trim_starting_raw_identifier(&ident).to_case(Case::Pascal),
Span::call_site(),
);
let mut nullable = false;
let mut default_value = None;
@ -168,6 +170,8 @@ pub fn expand_derive_entity_model(data: Data, attrs: Vec<Attribute>) -> syn::Res
field_name = enum_name;
}
field_name = Ident::new(&escape_rust_keyword(field_name), Span::call_site());
if ignore {
continue;
} else {

View File

@ -1,4 +1,7 @@
use crate::{attributes::derive_attr, util::field_not_ignored};
use crate::{
attributes::derive_attr,
util::{escape_rust_keyword, field_not_ignored, trim_starting_raw_identifier},
};
use heck::CamelCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned};
@ -43,10 +46,10 @@ impl DeriveModel {
let column_idents = fields
.iter()
.map(|field| {
let mut ident = format_ident!(
"{}",
field.ident.as_ref().unwrap().to_string().to_camel_case()
);
let ident = field.ident.as_ref().unwrap().to_string();
let ident = trim_starting_raw_identifier(ident).to_camel_case();
let ident = escape_rust_keyword(ident);
let mut ident = format_ident!("{}", &ident);
for attr in field.attrs.iter() {
if let Some(ident) = attr.path.get_ident() {
if ident != "sea_orm" {

View File

@ -24,3 +24,39 @@ pub(crate) fn field_not_ignored(field: &Field) -> bool {
}
true
}
pub(crate) fn trim_starting_raw_identifier<T>(string: T) -> String
where
T: ToString,
{
string
.to_string()
.trim_start_matches(RAW_IDENTIFIER)
.to_string()
}
pub(crate) fn escape_rust_keyword<T>(string: T) -> String
where
T: ToString,
{
let string = string.to_string();
if RUST_KEYWORDS.iter().any(|s| s.eq(&string)) {
format!("r#{}", string)
} else if RUST_SPECIAL_KEYWORDS.iter().any(|s| s.eq(&string)) {
format!("{}_", string)
} else {
string
}
}
pub(crate) const RAW_IDENTIFIER: &str = "r#";
pub(crate) const RUST_KEYWORDS: [&str; 49] = [
"as", "async", "await", "break", "const", "continue", "dyn", "else", "enum", "extern", "false",
"fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
"return", "static", "struct", "super", "trait", "true", "type", "union", "unsafe", "use",
"where", "while", "abstract", "become", "box", "do", "final", "macro", "override", "priv",
"try", "typeof", "unsized", "virtual", "yield",
];
pub(crate) const RUST_SPECIAL_KEYWORDS: [&str; 3] = ["crate", "Self", "self"];

View File

@ -7,6 +7,7 @@ pub mod cake_filling_price;
pub mod entity_linked;
pub mod filling;
pub mod fruit;
pub mod rust_keyword;
pub mod vendor;
pub use cake::Entity as Cake;
@ -15,4 +16,5 @@ pub use cake_filling::Entity as CakeFilling;
pub use cake_filling_price::Entity as CakeFillingPrice;
pub use filling::Entity as Filling;
pub use fruit::Entity as Fruit;
pub use rust_keyword::Entity as RustKeyword;
pub use vendor::Entity as Vendor;

View File

@ -0,0 +1,141 @@
use crate as sea_orm;
use crate::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "rust_keyword")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub testing: i32,
pub rust: i32,
pub keywords: i32,
pub r#raw_identifier: i32,
pub r#as: i32,
pub r#async: i32,
pub r#await: i32,
pub r#break: i32,
pub r#const: i32,
pub r#continue: i32,
pub crate_: i32,
pub r#dyn: i32,
pub r#else: i32,
pub r#enum: i32,
pub r#extern: i32,
pub r#false: i32,
pub r#fn: i32,
pub r#for: i32,
pub r#if: i32,
pub r#impl: i32,
pub r#in: i32,
pub r#let: i32,
pub r#loop: i32,
pub r#match: i32,
pub r#mod: i32,
pub r#move: i32,
pub r#mut: i32,
pub r#pub: i32,
pub r#ref: i32,
pub r#return: i32,
pub self_: i32,
pub r#static: i32,
pub r#struct: i32,
pub r#trait: i32,
pub r#true: i32,
pub r#type: i32,
pub r#union: i32,
pub r#unsafe: i32,
pub r#use: i32,
pub r#where: i32,
pub r#while: i32,
pub r#abstract: i32,
pub r#become: i32,
pub r#box: i32,
pub r#do: i32,
pub r#final: i32,
pub r#macro: i32,
pub r#override: i32,
pub r#priv: i32,
pub r#try: i32,
pub r#typeof: i32,
pub r#unsized: i32,
pub r#virtual: i32,
pub r#yield: i32,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
_ => panic!("No RelationDef"),
}
}
}
impl ActiveModelBehavior for ActiveModel {}
#[cfg(test)]
mod tests {
use crate::tests_cfg::rust_keyword::*;
use sea_query::Iden;
#[test]
fn test_columns() {
assert_eq!(Column::Id.to_string().as_str(), "id");
assert_eq!(Column::Testing.to_string().as_str(), "testing");
assert_eq!(Column::Rust.to_string().as_str(), "rust");
assert_eq!(Column::Keywords.to_string().as_str(), "keywords");
assert_eq!(Column::RawIdentifier.to_string().as_str(), "raw_identifier");
assert_eq!(Column::As.to_string().as_str(), "as");
assert_eq!(Column::Async.to_string().as_str(), "async");
assert_eq!(Column::Await.to_string().as_str(), "await");
assert_eq!(Column::Break.to_string().as_str(), "break");
assert_eq!(Column::Const.to_string().as_str(), "const");
assert_eq!(Column::Continue.to_string().as_str(), "continue");
assert_eq!(Column::Dyn.to_string().as_str(), "dyn");
assert_eq!(Column::Crate.to_string().as_str(), "crate");
assert_eq!(Column::Else.to_string().as_str(), "else");
assert_eq!(Column::Enum.to_string().as_str(), "enum");
assert_eq!(Column::Extern.to_string().as_str(), "extern");
assert_eq!(Column::False.to_string().as_str(), "false");
assert_eq!(Column::Fn.to_string().as_str(), "fn");
assert_eq!(Column::For.to_string().as_str(), "for");
assert_eq!(Column::If.to_string().as_str(), "if");
assert_eq!(Column::Impl.to_string().as_str(), "impl");
assert_eq!(Column::In.to_string().as_str(), "in");
assert_eq!(Column::Let.to_string().as_str(), "let");
assert_eq!(Column::Loop.to_string().as_str(), "loop");
assert_eq!(Column::Match.to_string().as_str(), "match");
assert_eq!(Column::Mod.to_string().as_str(), "mod");
assert_eq!(Column::Move.to_string().as_str(), "move");
assert_eq!(Column::Mut.to_string().as_str(), "mut");
assert_eq!(Column::Pub.to_string().as_str(), "pub");
assert_eq!(Column::Ref.to_string().as_str(), "ref");
assert_eq!(Column::Return.to_string().as_str(), "return");
assert_eq!(Column::Self_.to_string().as_str(), "self");
assert_eq!(Column::Static.to_string().as_str(), "static");
assert_eq!(Column::Struct.to_string().as_str(), "struct");
assert_eq!(Column::Trait.to_string().as_str(), "trait");
assert_eq!(Column::True.to_string().as_str(), "true");
assert_eq!(Column::Type.to_string().as_str(), "type");
assert_eq!(Column::Union.to_string().as_str(), "union");
assert_eq!(Column::Unsafe.to_string().as_str(), "unsafe");
assert_eq!(Column::Use.to_string().as_str(), "use");
assert_eq!(Column::Where.to_string().as_str(), "where");
assert_eq!(Column::While.to_string().as_str(), "while");
assert_eq!(Column::Abstract.to_string().as_str(), "abstract");
assert_eq!(Column::Become.to_string().as_str(), "become");
assert_eq!(Column::Box.to_string().as_str(), "box");
assert_eq!(Column::Do.to_string().as_str(), "do");
assert_eq!(Column::Final.to_string().as_str(), "final");
assert_eq!(Column::Macro.to_string().as_str(), "macro");
assert_eq!(Column::Override.to_string().as_str(), "override");
assert_eq!(Column::Priv.to_string().as_str(), "priv");
assert_eq!(Column::Try.to_string().as_str(), "try");
assert_eq!(Column::Typeof.to_string().as_str(), "typeof");
assert_eq!(Column::Unsized.to_string().as_str(), "unsized");
assert_eq!(Column::Virtual.to_string().as_str(), "virtual");
assert_eq!(Column::Yield.to_string().as_str(), "yield");
}
}