blob: 04b067b94d2d17c41e4472b6171e10944489fdbd [file] [edit]
// 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{}
}