[MM-17889] Implement validation of plugin API version comments (#11941)

Этот коммит содержится в:
Paulo Bittencourt
2019-09-17 02:03:28 -04:00
коммит произвёл Ben Schumacher
родитель 04653ec924
Коммит 5b79fc4110
42 изменённых файлов: 8875 добавлений и 5 удалений

126
plugin/checker/main.go Обычный файл
Просмотреть файл

@@ -0,0 +1,126 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"bytes"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"go/ast"
"golang.org/x/tools/go/packages"
"github.com/pkg/errors"
)
const pluginPackagePath = "github.com/mattermost/mattermost-server/plugin"
func main() {
if err := runCheck(pluginPackagePath); err != nil {
fmt.Fprintln(os.Stderr, "#", pluginPackagePath)
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func runCheck(pkgPath string) error {
pkg, err := getPackage(pkgPath)
if err != nil {
return err
}
apiInterface := findAPIInterface(pkg.Syntax)
if apiInterface == nil {
return errors.Errorf("could not find API interface in package %s", pkgPath)
}
invalidMethods := findInvalidMethods(apiInterface.Methods.List)
if len(invalidMethods) > 0 {
return errors.New(renderErrorMessage(pkg, invalidMethods))
}
return nil
}
func getPackage(pkgPath string) (*packages.Package, error) {
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax,
}
pkgs, err := packages.Load(cfg, pkgPath)
if err != nil {
return nil, err
}
if len(pkgs) == 0 {
return nil, errors.Errorf("could not find package %s", pkgPath)
}
return pkgs[0], nil
}
func findAPIInterface(files []*ast.File) *ast.InterfaceType {
for _, f := range files {
var iface *ast.InterfaceType
ast.Inspect(f, func(n ast.Node) bool {
if t, ok := n.(*ast.TypeSpec); ok {
if i, ok := t.Type.(*ast.InterfaceType); ok && t.Name.Name == "API" {
iface = i
return false
}
}
return true
})
if iface != nil {
return iface
}
}
return nil
}
func findInvalidMethods(methods []*ast.Field) []*ast.Field {
var invalid []*ast.Field
for _, m := range methods {
if !hasValidMinimumVersionComment(m.Doc.Text()) {
invalid = append(invalid, m)
}
}
return invalid
}
var versionRequirementRE = regexp.MustCompile(`^Minimum server version: \d+\.\d+(\.\d+)?$`)
func hasValidMinimumVersionComment(s string) bool {
lines := strings.Split(strings.TrimSpace(s), "\n")
if len(lines) > 0 {
lastLine := lines[len(lines)-1]
return versionRequirementRE.MatchString(lastLine)
}
return false
}
func renderErrorMessage(pkg *packages.Package, methods []*ast.Field) string {
cwd, _ := os.Getwd()
out := &bytes.Buffer{}
for _, m := range methods {
pos := pkg.Fset.Position(m.Pos())
filename, err := filepath.Rel(cwd, pos.Filename)
if err != nil {
// If deriving a relative path fails for some reason,
// we prefer to still print the absolute path to the file.
filename = pos.Filename
}
fmt.Fprintf(out,
"%s:%d:%d: missing a minimum server version comment\n",
filename,
pos.Line,
pos.Column,
)
}
return out.String()
}

49
plugin/checker/main_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,49 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRunCheck(t *testing.T) {
testCases := []struct {
name, pkgPath, err string
}{
{
name: "valid comments",
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/valid",
err: "",
},
{
name: "invalid comments",
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/invalid",
err: "test/invalid/invalid.go:15:2: missing a minimum server version comment\n",
},
{
name: "missing API interface",
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/missing",
err: "could not find API interface in package github.com/mattermost/mattermost-server/plugin/checker/test/missing",
},
{
name: "non-existent package path",
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist",
err: "could not find API interface in package github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := runCheck(tc.pkgPath)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
} else {
assert.NoError(t, err)
}
})
}
}

16
plugin/checker/test/invalid/invalid.go Обычный файл
Просмотреть файл

@@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package invalid
type API interface {
// ValidMethod is a fake method for testing the
// plugin comment checker with a valid comment.
//
// Minimum server version: 1.2.3
ValidMethod()
// InvalidMethod is a fake method for testing the
// plugin comment checker with an invalid comment.
InvalidMethod()
}

8
plugin/checker/test/missing/missing.go Обычный файл
Просмотреть файл

@@ -0,0 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package missing
// SomeType is a fake interface for testing the plugin comment checker.
type SomeType interface {
}

12
plugin/checker/test/valid/valid.go Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package valid
type API interface {
// ValidMethod is a fake method for testing the
// plugin comment checker with a valid comment.
//
// Minimum server version: 1.2.3
ValidMethod()
}