diff --git a/checker/BUILD b/checker/BUILD index b7b6dba3e..30bd1ee78 100644 --- a/checker/BUILD +++ b/checker/BUILD @@ -27,7 +27,6 @@ cc_library( hdrs = ["type_check_issue.h"], deps = [ "//common:source", - "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/strings:string_view", ], @@ -45,6 +44,7 @@ cc_test( cc_library( name = "validation_result", + srcs = ["validation_result.cc"], hdrs = ["validation_result.h"], deps = [ ":type_check_issue", @@ -53,6 +53,7 @@ cc_library( "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", "@com_google_absl//absl/types:span", ], ) @@ -64,6 +65,7 @@ cc_test( ":type_check_issue", ":validation_result", "//base/ast_internal:ast_impl", + "//common:source", "//internal:testing", "@com_google_absl//absl/status", "@com_google_absl//absl/status:status_matchers", diff --git a/checker/type_check_issue.cc b/checker/type_check_issue.cc index 1f32ee54e..b1d3caa11 100644 --- a/checker/type_check_issue.cc +++ b/checker/type_check_issue.cc @@ -16,7 +16,6 @@ #include -#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "common/source.h" @@ -42,15 +41,19 @@ absl::string_view SeverityString(TypeCheckIssue::Severity severity) { } // namespace -std::string TypeCheckIssue::ToDisplayString(const Source& source) const { +std::string TypeCheckIssue::ToDisplayString(const Source* source) const { int column = location_.column; // convert to 1-based if it's in range. int display_column = column >= 0 ? column + 1 : column; - return absl::StrCat( - absl::StrFormat("%s: %s:%d:%d: %s", SeverityString(severity_), - source.description(), location_.line, display_column, - message_), - source.DisplayErrorLocation(location_)); + if (source) { + return absl::StrFormat("%s: %s:%d:%d: %s%s", SeverityString(severity_), + source->description(), location_.line, + display_column, message_, + source->DisplayErrorLocation(location_)); + } + + return absl::StrFormat("%s: :%d:%d: %s", SeverityString(severity_), + location_.line, display_column, message_); } } // namespace cel diff --git a/checker/type_check_issue.h b/checker/type_check_issue.h index d58f39658..9f6f57a3d 100644 --- a/checker/type_check_issue.h +++ b/checker/type_check_issue.h @@ -48,7 +48,11 @@ class TypeCheckIssue { } // Format the issue highlighting the source position. - std::string ToDisplayString(const Source& source) const; + std::string ToDisplayString(const Source* source) const; + + std::string ToDisplayString(const Source& source) const { + return ToDisplayString(&source); + } absl::string_view message() const { return message_; } Severity severity() const { return severity_; } diff --git a/checker/validation_result.cc b/checker/validation_result.cc new file mode 100644 index 000000000..88d52932a --- /dev/null +++ b/checker/validation_result.cc @@ -0,0 +1,32 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "checker/validation_result.h" + +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "checker/type_check_issue.h" + +namespace cel { + +std::string ValidationResult::FormatError() const { + return absl::StrJoin( + issues_, "\n", [this](std::string* out, const TypeCheckIssue& issue) { + absl::StrAppend(out, issue.ToDisplayString(source_.get())); + }); +} + +} // namespace cel diff --git a/checker/validation_result.h b/checker/validation_result.h index c5ed50b35..846c171ae 100644 --- a/checker/validation_result.h +++ b/checker/validation_result.h @@ -16,6 +16,7 @@ #define THIRD_PARTY_CEL_CPP_CHECKER_VALIDATION_RESULT_H_ #include +#include #include #include @@ -68,6 +69,22 @@ class ValidationResult { return std::move(source_); } + // Returns a string representation of the issues in the result suitable for + // display. + // + // The result is empty if no issues are present. + // + // The result is formatted similarly to CEL-Java and CEL-Go, but we do not + // give strong guarantees on the format or stability. + // + // Example: + // + // ERROR: :1:3: Issue1 + // | source.cel + // | ..^ + // INFORMATION: :-1:-1: Issue2 + std::string FormatError() const; + private: absl::Nullable> ast_; std::vector issues_; diff --git a/checker/validation_result_test.cc b/checker/validation_result_test.cc index d3d7cb3c4..3866b350d 100644 --- a/checker/validation_result_test.cc +++ b/checker/validation_result_test.cc @@ -15,11 +15,13 @@ #include "checker/validation_result.h" #include +#include #include "absl/status/status.h" #include "absl/status/status_matchers.h" #include "base/ast_internal/ast_impl.h" #include "checker/type_check_issue.h" +#include "common/source.h" #include "internal/testing.h" namespace cel { @@ -65,5 +67,24 @@ TEST(ValidationResultTest, GetIssues) { EXPECT_THAT(result.GetIssues()[1].severity(), Severity::kInformation); } +TEST(ValidationResultTest, FormatError) { + ValidationResult result( + {TypeCheckIssue::CreateError({1, 2}, "Issue1"), + TypeCheckIssue(Severity::kInformation, {-1, -1}, "Issue2")}); + EXPECT_FALSE(result.IsValid()); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr source, + NewSource("source.cel", "")); + result.SetSource(std::move(source)); + + ASSERT_THAT(result.GetIssues(), SizeIs(2)); + + EXPECT_THAT(result.FormatError(), + "ERROR: :1:3: Issue1\n" + " | source.cel\n" + " | ..^\n" + "INFORMATION: :-1:-1: Issue2"); +} + } // namespace } // namespace cel