189 lines
6.1 KiB
Rust
189 lines
6.1 KiB
Rust
use crate::diagnostic::Diagnostic;
|
|
use crate::syntax::{
|
|
self, Declaration, InputKind, Name, Package, Pattern, StatementKind, TypeShape,
|
|
};
|
|
|
|
use super::Checked;
|
|
|
|
pub(super) fn check(package: &Package) -> Checked<()> {
|
|
for name in &package.name {
|
|
snake(name)?;
|
|
}
|
|
for declaration in &package.declarations {
|
|
match declaration {
|
|
Declaration::Api(binding) => {
|
|
snake(&binding.action)?;
|
|
for segment in &binding.target {
|
|
snake(segment)?;
|
|
}
|
|
}
|
|
Declaration::Rest(rest) => {
|
|
for segment in rest.model.iter().flatten() {
|
|
snake(segment)?;
|
|
}
|
|
}
|
|
Declaration::External(external) => {
|
|
for capability in &external.capabilities {
|
|
snake(capability)?;
|
|
}
|
|
}
|
|
Declaration::Type(ty) => {
|
|
camel(&ty.name)?;
|
|
if matches!(
|
|
ty.name.text.as_str(),
|
|
"Bool"
|
|
| "Int"
|
|
| "Decimal"
|
|
| "Float"
|
|
| "Text"
|
|
| "Id"
|
|
| "Bytes"
|
|
| "Secret"
|
|
| "Uuid"
|
|
| "DateTime"
|
|
| "Date"
|
|
| "Time"
|
|
| "Duration"
|
|
| "Json"
|
|
| "List"
|
|
| "Map"
|
|
| "MapEntry"
|
|
| "Stream"
|
|
| "AsyncStream"
|
|
| "Channel"
|
|
| "Task"
|
|
| "Group"
|
|
) {
|
|
return Err(Diagnostic::error(
|
|
"C002",
|
|
"type name is reserved by the language",
|
|
ty.name.span,
|
|
));
|
|
}
|
|
match &ty.shape {
|
|
TypeShape::Record(fields) => check_fields(fields)?,
|
|
TypeShape::Choice(variants) => {
|
|
for variant in variants {
|
|
camel(&variant.name)?;
|
|
check_fields(&variant.payload)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Declaration::Function(function) => {
|
|
for segment in function.name.text.split('.') {
|
|
snake(&Name {
|
|
text: segment.into(),
|
|
span: function.name.span,
|
|
})?;
|
|
}
|
|
for input in &function.inputs {
|
|
snake(&input.name)?;
|
|
match &input.kind {
|
|
InputKind::Value(Pattern::Variant { bindings, .. }) => {
|
|
for binding in bindings {
|
|
if binding.text != "_" {
|
|
snake(binding)?;
|
|
}
|
|
}
|
|
}
|
|
InputKind::Handler(signature) => {
|
|
check_fields(&signature.inputs)?;
|
|
check_fields(&signature.outputs)?;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
check_fields(&function.outputs)?;
|
|
for statement in &function.body {
|
|
if let StatementKind::Assign { target, .. } = &statement.kind {
|
|
for name in target {
|
|
snake(name)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Declaration::Database(binding) => snake(&binding.name)?,
|
|
Declaration::Schedule(schedule) => snake(&schedule.target)?,
|
|
Declaration::ModelIndex(index) => {
|
|
for field in &index.fields {
|
|
snake(field)?;
|
|
}
|
|
}
|
|
Declaration::Relation(relation) => {
|
|
snake(&relation.name)?;
|
|
for segment in &relation.field {
|
|
snake(segment)?;
|
|
}
|
|
}
|
|
Declaration::Seed(seed) => {
|
|
for row in &seed.rows {
|
|
for field in row {
|
|
snake(&field.name)?;
|
|
}
|
|
}
|
|
}
|
|
Declaration::Migration(migration) => {
|
|
snake(&migration.name)?;
|
|
for operation in &migration.operations {
|
|
match operation {
|
|
syntax::MigrationOperation::Drop(field) => snake(field)?,
|
|
syntax::MigrationOperation::Sql(_) => {}
|
|
}
|
|
}
|
|
}
|
|
Declaration::ModelSql(sql) => {
|
|
snake(&sql.name)?;
|
|
check_fields(&sql.inputs)?;
|
|
check_fields(&sql.outputs)?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn check_fields(fields: &[syntax::Field]) -> Checked<()> {
|
|
for field in fields {
|
|
snake(&field.name)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn snake(name: &Name) -> Checked<()> {
|
|
if name
|
|
.text
|
|
.starts_with(|character: char| character.is_ascii_lowercase())
|
|
&& name.text.split('_').all(|word| {
|
|
!word.is_empty()
|
|
&& word
|
|
.chars()
|
|
.all(|character| character.is_ascii_lowercase() || character.is_ascii_digit())
|
|
})
|
|
{
|
|
return Ok(());
|
|
}
|
|
Err(Diagnostic::error(
|
|
"C010",
|
|
"name must use ASCII lower_snake_case",
|
|
name.span,
|
|
))
|
|
}
|
|
|
|
fn camel(name: &Name) -> Checked<()> {
|
|
if name
|
|
.text
|
|
.starts_with(|character: char| character.is_ascii_uppercase())
|
|
&& name
|
|
.text
|
|
.chars()
|
|
.all(|character| character.is_ascii_alphanumeric())
|
|
{
|
|
return Ok(());
|
|
}
|
|
Err(Diagnostic::error(
|
|
"C010",
|
|
"type and variant names must use ASCII UpperCamelCase",
|
|
name.span,
|
|
))
|
|
}
|