| // Copyright 2020-2025 Buf Technologies, Inc. |
| // |
| // 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 |
| // |
| // http://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. |
| |
| package bufprotocompile |
| |
| import ( |
| "errors" |
| "fmt" |
| "io/fs" |
| |
| "buf.build/go/standard/xslices" |
| "github.com/bufbuild/buf/private/bufpkg/bufanalysis" |
| "github.com/bufbuild/buf/private/pkg/normalpath" |
| "github.com/bufbuild/protocompile/reporter" |
| ) |
| |
| // FileAnnotationForErrorWithPos returns a new FileAnnotation for the ErrorWithPos. |
| // |
| // This special-cases fs.PathErrors if there is FileInfo information. We know that if there |
| // is FileInfo information, and there is a fs.PathError, this can only happen due to import |
| // errors. Therefore, we print out a special message saying that the error is for an import. |
| func FileAnnotationForErrorWithPos( |
| errorWithPos reporter.ErrorWithPos, |
| options ...FileAnnotationOption, |
| ) (bufanalysis.FileAnnotation, error) { |
| fileAnnotationOptions := newFileAnnotationOptions() |
| for _, option := range options { |
| option(fileAnnotationOptions) |
| } |
| |
| var fileInfo bufanalysis.FileInfo |
| var startLine int |
| var startColumn int |
| var endLine int |
| var endColumn int |
| sourcePos := errorWithPos.GetPosition() |
| if sourcePos.Filename != "" { |
| path, err := normalpath.NormalizeAndValidate(sourcePos.Filename) |
| if err != nil { |
| return nil, err |
| } |
| externalPath := path |
| if fileAnnotationOptions.externalPathResolver != nil { |
| externalPath = fileAnnotationOptions.externalPathResolver(path) |
| } |
| fileInfo = newFileInfo(path, externalPath) |
| } |
| if sourcePos.Line > 0 { |
| startLine = sourcePos.Line |
| endLine = sourcePos.Line |
| } |
| if sourcePos.Col > 0 { |
| startColumn = sourcePos.Col |
| endColumn = sourcePos.Col |
| } |
| |
| typeString := "COMPILE" |
| message := "Compile error." |
| // this should never happen |
| // maybe we should error |
| if err := errorWithPos.Unwrap(); err != nil { |
| var pathError *fs.PathError |
| if fileInfo != nil && errors.As(err, &pathError) { |
| // Special-case path errors as being for imports, as we know that they are for |
| // an import if we had a fileInfo. |
| message = fmt.Sprintf("import %q: %s", pathError.Path, pathError.Err) |
| } else { |
| message = err.Error() |
| } |
| } |
| |
| return bufanalysis.NewFileAnnotation( |
| fileInfo, |
| startLine, |
| startColumn, |
| endLine, |
| endColumn, |
| typeString, |
| message, |
| "", // pluginName |
| "", // policyName |
| ), nil |
| } |
| |
| // FileAnnotationSetForErrorsWithPos returns new FileAnnotations for the ErrorsWithPos. |
| // |
| // This special-cases fs.PathErrors if there is FileInfo information. We know that if there |
| // is FileInfo information, and there is a fs.PathError, this can only happen due to import |
| // errors. Therefore, we print out a special message saying that the error is for an import. |
| func FileAnnotationSetForErrorsWithPos( |
| errorsWithPos []reporter.ErrorWithPos, |
| options ...FileAnnotationOption, |
| ) (bufanalysis.FileAnnotationSet, error) { |
| fileAnnotations, err := xslices.MapError( |
| errorsWithPos, |
| func(errorWithPos reporter.ErrorWithPos) (bufanalysis.FileAnnotation, error) { |
| return FileAnnotationForErrorWithPos(errorWithPos, options...) |
| }, |
| ) |
| if err != nil { |
| return nil, err |
| } |
| return bufanalysis.NewFileAnnotationSet(fileAnnotations...), nil |
| } |
| |
| // FileAnnotationOption is an option when creating a FileAnnotation. |
| type FileAnnotationOption func(*fileAnnotationOptions) |
| |
| // WithExternalPathResolver returns a new FileAnnotationOption that will map the given |
| // path to an external path. |
| func WithExternalPathResolver(externalPathResolver func(path string) string) FileAnnotationOption { |
| return func(fileAnnotationOptions *fileAnnotationOptions) { |
| fileAnnotationOptions.externalPathResolver = externalPathResolver |
| } |
| } |
| |
| // *** PRIVATE *** |
| |
| type fileInfo struct { |
| path string |
| externalPath string |
| } |
| |
| func newFileInfo(path string, externalPath string) *fileInfo { |
| return &fileInfo{ |
| path: path, |
| externalPath: externalPath, |
| } |
| } |
| |
| func (f *fileInfo) Path() string { |
| return f.path |
| } |
| |
| func (f *fileInfo) ExternalPath() string { |
| return f.externalPath |
| } |
| |
| type fileAnnotationOptions struct { |
| externalPathResolver func(path string) string |
| } |
| |
| func newFileAnnotationOptions() *fileAnnotationOptions { |
| return &fileAnnotationOptions{} |
| } |