From e66d30dc00faf0c9d53c02f47a511bb304054f91 Mon Sep 17 00:00:00 2001 From: Pranaya Tomar Date: Fri, 5 Apr 2024 10:46:39 +0200 Subject: [PATCH] feat(psl): Recommend type in error message if schema validation fails because of case (#4137) * feat(psl): error for ignore case validation * feat(psl): fix formatting * feat(psl): ignore Source & Generator when recommending type in error * feat(psl): enclose recommended type in quotes Closes #15174 --------- Co-authored-by: Sophie <29753584+Druue@users.noreply.github.com> --- psl/diagnostics/src/error.rs | 7 +++ psl/parser-database/src/names.rs | 2 +- psl/parser-database/src/types.rs | 79 +++++++++++++++++++++++++------- psl/psl/tests/base/basic.rs | 64 ++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 18 deletions(-) diff --git a/psl/diagnostics/src/error.rs b/psl/diagnostics/src/error.rs index c6a16dffdba..6a11d461a13 100644 --- a/psl/diagnostics/src/error.rs +++ b/psl/diagnostics/src/error.rs @@ -293,6 +293,13 @@ impl DatamodelError { Self::new(msg, span) } + pub fn new_type_for_case_not_found_error(type_name: &str, suggestion: &str, span: Span) -> DatamodelError { + let msg = format!( + "Type \"{type_name}\" is neither a built-in type, nor refers to another model, custom type, or enum. Did you mean \"{suggestion}\"?" + ); + Self::new(msg, span) + } + pub fn new_scalar_type_not_found_error(type_name: &str, span: Span) -> DatamodelError { Self::new(format!("Type \"{type_name}\" is not a built-in type."), span) } diff --git a/psl/parser-database/src/names.rs b/psl/parser-database/src/names.rs index 9ed71f98742..3208c1c3bdb 100644 --- a/psl/parser-database/src/names.rs +++ b/psl/parser-database/src/names.rs @@ -159,7 +159,7 @@ fn duplicate_top_error(existing: &ast::Top, duplicate: &ast::Top) -> DatamodelEr } fn assert_is_not_a_reserved_scalar_type(ident: &ast::Identifier, ctx: &mut Context<'_>) { - if ScalarType::try_from_str(&ident.name).is_some() { + if ScalarType::try_from_str(&ident.name, false).is_some() { ctx.push_error(DatamodelError::new_reserved_scalar_type_error(&ident.name, ident.span)); } } diff --git a/psl/parser-database/src/types.rs b/psl/parser-database/src/types.rs index 1668243247b..c5f2d222ce1 100644 --- a/psl/parser-database/src/types.rs +++ b/psl/parser-database/src/types.rs @@ -648,10 +648,41 @@ fn visit_model<'db>(model_id: ast::ModelId, ast_model: &'db ast::Model, ctx: &mu native_type: None, }); } - Err(supported) => ctx.push_error(DatamodelError::new_type_not_found_error( - supported, - ast_field.field_type.span(), - )), + Err(supported) => { + let top_names: Vec<_> = ctx + .ast + .iter_tops() + .filter_map(|(_, top)| match top { + ast::Top::Source(_) | ast::Top::Generator(_) => None, + _ => Some(&top.identifier().name), + }) + .collect(); + + match top_names.iter().find(|&name| name.to_lowercase() == supported) { + Some(ignore_case_match) => { + ctx.push_error(DatamodelError::new_type_for_case_not_found_error( + supported, + ignore_case_match.as_str(), + ast_field.field_type.span(), + )); + } + None => match ScalarType::try_from_str(supported, true) { + Some(ignore_case_match) => { + ctx.push_error(DatamodelError::new_type_for_case_not_found_error( + supported, + ignore_case_match.as_str(), + ast_field.field_type.span(), + )); + } + None => { + ctx.push_error(DatamodelError::new_type_not_found_error( + supported, + ast_field.field_type.span(), + )); + } + }, + } + } } } } @@ -699,7 +730,7 @@ fn field_type<'db>(field: &'db ast::Field, ctx: &mut Context<'db>) -> Result Option { - match s { - "Int" => Some(ScalarType::Int), - "BigInt" => Some(ScalarType::BigInt), - "Float" => Some(ScalarType::Float), - "Boolean" => Some(ScalarType::Boolean), - "String" => Some(ScalarType::String), - "DateTime" => Some(ScalarType::DateTime), - "Json" => Some(ScalarType::Json), - "Bytes" => Some(ScalarType::Bytes), - "Decimal" => Some(ScalarType::Decimal), - _ => None, + pub(crate) fn try_from_str(s: &str, ignore_case: bool) -> Option { + match ignore_case { + true => match s.to_lowercase().as_str() { + "int" => Some(ScalarType::Int), + "bigint" => Some(ScalarType::BigInt), + "float" => Some(ScalarType::Float), + "boolean" => Some(ScalarType::Boolean), + "string" => Some(ScalarType::String), + "datetime" => Some(ScalarType::DateTime), + "json" => Some(ScalarType::Json), + "bytes" => Some(ScalarType::Bytes), + "decimal" => Some(ScalarType::Decimal), + _ => None, + }, + _ => match s { + "Int" => Some(ScalarType::Int), + "BigInt" => Some(ScalarType::BigInt), + "Float" => Some(ScalarType::Float), + "Boolean" => Some(ScalarType::Boolean), + "String" => Some(ScalarType::String), + "DateTime" => Some(ScalarType::DateTime), + "Json" => Some(ScalarType::Json), + "Bytes" => Some(ScalarType::Bytes), + "Decimal" => Some(ScalarType::Decimal), + _ => None, + }, } } } diff --git a/psl/psl/tests/base/basic.rs b/psl/psl/tests/base/basic.rs index ca806fb6f51..a8c47884c21 100644 --- a/psl/psl/tests/base/basic.rs +++ b/psl/psl/tests/base/basic.rs @@ -239,3 +239,67 @@ fn type_aliases_must_error() { expectation.assert_eq(&error); } + +#[test] +fn must_return_good_error_message_for_type_match() { + let dml = indoc! {r#" + model User { + firstName String + } + model B { + a datetime + b footime + c user + d DB + e JS + } + + datasource db { + provider = "postgresql" + url = env("TEST_DATABASE_URL") + extensions = [citext, pg_trgm] + } + + generator js { + provider = "prisma-client-js" + previewFeatures = ["postgresqlExtensions"] + } + "#}; + + let error = parse_unwrap_err(dml); + + let expected = expect![[r#" + error: Type "datetime" is neither a built-in type, nor refers to another model, custom type, or enum. Did you mean "DateTime"? + --> schema.prisma:5 +  |  +  4 | model B { +  5 |  a datetime +  |  + error: Type "footime" is neither a built-in type, nor refers to another model, custom type, or enum. + --> schema.prisma:6 +  |  +  5 |  a datetime +  6 |  b footime +  |  + error: Type "user" is neither a built-in type, nor refers to another model, custom type, or enum. Did you mean "User"? + --> schema.prisma:7 +  |  +  6 |  b footime +  7 |  c user +  |  + error: Type "DB" is neither a built-in type, nor refers to another model, custom type, or enum. + --> schema.prisma:8 +  |  +  7 |  c user +  8 |  d DB +  |  + error: Type "JS" is neither a built-in type, nor refers to another model, custom type, or enum. + --> schema.prisma:9 +  |  +  8 |  d DB +  9 |  e JS +  |  + "#]]; + + expected.assert_eq(&error); +}