Skip to content

feat: ✨ add project level manual scan [ROAD-1239] #203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ dist: build
env:
- GO111MODULE=on
- CGO_ENABLED=0
- LS_PROTOCOL_VERSION=4
- LS_PROTOCOL_VERSION=5
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,32 @@ Right now the language server supports the following actions:
}
```

### Commands
- NavigateToRangeCommand
- command: "snyk.navigateToRange"
- args: path, Range
- WorkspaceScanCommand
- command: "snyk.workspace.scan"
- args: empty
- WorkspaceFolderScanCommand
- command: "snyk.workspaceFolder.scan"
- args: path
- OpenBrowserCommand
- command: "snyk.openBrowser"
- args: URL
- LoginCommand
- command: "snyk.login"
- args: empty
- CopyAuthLinkCommand
- command: "snyk.copyAuthLink"
- args: empty
- LogoutCommand
- command: "snyk.logout"
- args: empty
- TrustWorkspaceFoldersCommand
- command: "snyk.trustWorkspaceFolders"
- args: empty

## Installation

### Download
Expand Down
2 changes: 1 addition & 1 deletion application/server/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func Test_WorkspaceDidChangeConfiguration_Push(t *testing.T) {
assert.Equal(t, "token", config.CurrentConfig().Token())
}

func callBackMock(_ context.Context, request *jrpc2.Request) (interface{}, error) {
func callBackMock(_ context.Context, request *jrpc2.Request) (any, error) {
jsonRPCRecorder.Record(*request)
if request.Method() == "workspace/configuration" {
return []lsp.Settings{sampleSettings}, nil
Expand Down
21 changes: 20 additions & 1 deletion application/server/execute_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
)

func ExecuteCommandHandler(srv *jrpc2.Server) jrpc2.Handler {
return handler.New(func(ctx context.Context, params sglsp.ExecuteCommandParams) (interface{}, error) {
return handler.New(func(ctx context.Context, params sglsp.ExecuteCommandParams) (any, error) {
// The context provided by the JSON-RPC server is cancelled once a new message is being processed,
// so we don't want to propagate it to functions that start background operations
bgCtx := context.Background()
Expand All @@ -50,11 +50,30 @@ func ExecuteCommandHandler(srv *jrpc2.Server) jrpc2.Handler {
log.Warn().Str("method", method).Msg("received NavigateToRangeCommand without range")
}
navigateToLocation(srv, args)

case snyk.WorkspaceScanCommand:
w := workspace.Get()
w.ClearIssues(bgCtx)
w.ScanWorkspace(bgCtx)
handleUntrustedFolders(bgCtx, srv)

case snyk.WorkspaceFolderScanCommand:
w := workspace.Get()
if len(args) != 1 {
log.Warn().Str("method", method).Msg("received WorkspaceFolderScanCommand without path")
return nil, nil
}
path := args[0].(string)
f := w.GetFolderContaining(path)
if f == nil {
log.Warn().Str("method", method).Msg("received WorkspaceFolderScanCommand with path not in workspace")
log.Warn().Interface("folders", w.Folders())
return nil, nil
}
f.ClearScannedStatus()
f.ClearDiagnosticsFromPathRecursively(path)
f.ScanFolder(bgCtx)
handleUntrustedFolders(bgCtx, srv)
case snyk.OpenBrowserCommand:
command.OpenBrowser(params.Arguments[0].(string))
case snyk.TrustWorkspaceFoldersCommand:
Expand Down
49 changes: 49 additions & 0 deletions application/server/execute_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package server

import (
"context"
"testing"
"time"

Expand Down Expand Up @@ -48,6 +49,54 @@ func Test_executeWorkspaceScanCommand_shouldStartWorkspaceScanOnCommandReceipt(t
}, 2*time.Second, time.Millisecond)
}

func Test_executeWorkspaceFolderScanCommand_shouldStartFolderScanOnCommandReceipt(t *testing.T) {
loc := setupServer(t)

scanner := &snyk.TestScanner{}
workspace.Get().AddFolder(workspace.NewFolder("dummy", "dummy", scanner, di.HoverService()))

params := lsp.ExecuteCommandParams{Command: snyk.WorkspaceFolderScanCommand, Arguments: []any{"dummy"}}
_, err := loc.Client.Call(ctx, "workspace/executeCommand", params)
if err != nil {
t.Fatal(err)
}
assert.Eventually(t, func() bool {
return scanner.Calls() > 0
}, 2*time.Second, time.Millisecond)
}

func Test_executeWorkspaceFolderScanCommand_shouldNotClearOtherFoldersDiagnostics(t *testing.T) {
loc := setupServer(t)

scannerForFolder := snyk.NewTestScanner()
scannerForDontClear := snyk.NewTestScanner()
folder := workspace.NewFolder("dummy", "dummy", scannerForFolder, di.HoverService())
dontClear := workspace.NewFolder("dontclear", "dontclear", scannerForDontClear, di.HoverService())

dontClearIssuePath := "dontclear/file.txt"
scannerForDontClear.AddTestIssue(snyk.Issue{AffectedFilePath: dontClearIssuePath})
scannerForFolder.AddTestIssue(snyk.Issue{AffectedFilePath: "dummy/file.txt"})

workspace.Get().AddFolder(folder)
workspace.Get().AddFolder(dontClear)

// prepare pre-existent diagnostics for folder
folder.ScanFolder(context.Background())
dontClear.ScanFolder(context.Background())

params := lsp.ExecuteCommandParams{Command: snyk.WorkspaceFolderScanCommand, Arguments: []any{"dummy"}}
_, err := loc.Client.Call(ctx, "workspace/executeCommand", params)
if err != nil {
t.Fatal(err)
}
assert.Eventually(t, func() bool {
// must be two scans for dummy as initialization + scan after issuing command
return scannerForFolder.Calls() == 2 && scannerForDontClear.Calls() == 1
}, 2*time.Second, time.Millisecond)

assert.Equal(t, 1, len(dontClear.AllIssuesFor(dontClearIssuePath)))
}

func Test_executeWorkspaceScanCommand_shouldAskForTrust(t *testing.T) {
loc := setupServer(t)

Expand Down
6 changes: 3 additions & 3 deletions application/server/lsp/message_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ type Diagnostic struct {
*
* @since 3.16.0
*/
Data interface{} `json:"data,omitempty"`
Data any `json:"data,omitempty"`
}

type DiagnosticTag int
Expand Down Expand Up @@ -345,7 +345,7 @@ type ProgressParams struct {
/**
* The progress data.
*/
Value interface{} `json:"value,omitempty"`
Value any `json:"value,omitempty"`
}

type WorkDoneProgressKind struct {
Expand Down Expand Up @@ -619,7 +619,7 @@ type CodeAction struct {
*
* @since 3.16.0
*/
Data interface{} `json:"data,omitempty"`
Data any `json:"data,omitempty"`
}

type CodeActionTriggerKind float64
Expand Down
6 changes: 3 additions & 3 deletions application/server/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ import (
"github.com/snyk/snyk-ls/internal/progress"
)

func notifier(srv *jrpc2.Server, method string, params interface{}) {
func notifier(srv *jrpc2.Server, method string, params any) {
log.Debug().Str("method", "notifier").Msgf("Notifying")
err := srv.Notify(context.Background(), method, params)
logError(err, "notifier")
}

type Server interface {
Notify(ctx context.Context, method string, params interface{}) error
Callback(ctx context.Context, method string, params interface{}) (*jrpc2.Response, error)
Notify(ctx context.Context, method string, params any) error
Callback(ctx context.Context, method string, params any) (*jrpc2.Response, error)
}

var progressStopChan = make(chan bool, 1000)
Expand Down
4 changes: 2 additions & 2 deletions application/server/notification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ type ServerImplMock struct{}

var notified = concurrency.AtomicBool{}

func (b *ServerImplMock) Callback(_ context.Context, _ string, _ interface{}) (*jrpc2.Response, error) { // todo: check if better way exists, mocking? go mock / testify
func (b *ServerImplMock) Callback(_ context.Context, _ string, _ any) (*jrpc2.Response, error) { // todo: check if better way exists, mocking? go mock / testify
notified.Set(true)
return nil, nil
}
func (b *ServerImplMock) Notify(_ context.Context, _ string, _ interface{}) error {
func (b *ServerImplMock) Notify(_ context.Context, _ string, _ any) error {
notified.Set(true)
return nil
}
Expand Down
25 changes: 13 additions & 12 deletions application/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func initHandlers(srv *jrpc2.Server, handlers *handler.Map) {
// WorkspaceWillDeleteFilesHandler handles the workspace/willDeleteFiles message that's raised by the client
// when files are deleted
func WorkspaceWillDeleteFilesHandler() jrpc2.Handler {
return handler.New(func(ctx context.Context, params lsp.DeleteFilesParams) (interface{}, error) {
return handler.New(func(ctx context.Context, params lsp.DeleteFilesParams) (any, error) {
ws := workspace.Get()
for _, file := range params.Files {
path := uri.PathFromUri(file.Uri)
Expand All @@ -114,7 +114,7 @@ func WorkspaceWillDeleteFilesHandler() jrpc2.Handler {
})
}

func navigateToLocation(srv *jrpc2.Server, args []interface{}) {
func navigateToLocation(srv *jrpc2.Server, args []any) {
method := "navigateToLocation"
// convert to correct type
var myRange snyk.Range
Expand Down Expand Up @@ -164,7 +164,7 @@ func CodeActionHandler() jrpc2.Handler {
}

func WorkspaceDidChangeWorkspaceFoldersHandler(srv *jrpc2.Server) jrpc2.Handler {
return handler.New(func(ctx context.Context, params lsp.DidChangeWorkspaceFoldersParams) (interface{}, error) {
return handler.New(func(ctx context.Context, params lsp.DidChangeWorkspaceFoldersParams) (any, error) {
// The context provided by the JSON-RPC server is cancelled once a new message is being processed,
// so we don't want to propagate it to functions that start background operations
bgCtx := context.Background()
Expand All @@ -178,7 +178,7 @@ func WorkspaceDidChangeWorkspaceFoldersHandler(srv *jrpc2.Server) jrpc2.Handler
}

func InitializeHandler(srv *jrpc2.Server) handler.Func {
return handler.New(func(ctx context.Context, params lsp.InitializeParams) (interface{}, error) {
return handler.New(func(ctx context.Context, params lsp.InitializeParams) (any, error) {
method := "InitializeHandler"
log.Info().Str("method", method).Interface("params", params).Msg("RECEIVING")
InitializeSettings(params.InitializationOptions)
Expand Down Expand Up @@ -242,6 +242,7 @@ func InitializeHandler(srv *jrpc2.Server) handler.Func {
Commands: []string{
snyk.NavigateToRangeCommand,
snyk.WorkspaceScanCommand,
snyk.WorkspaceFolderScanCommand,
snyk.OpenBrowserCommand,
snyk.LoginCommand,
snyk.CopyAuthLinkCommand,
Expand All @@ -255,7 +256,7 @@ func InitializeHandler(srv *jrpc2.Server) handler.Func {
})
}
func InitializedHandler(srv *jrpc2.Server) handler.Func {
return handler.New(func(ctx context.Context, params lsp.InitializedParams) (interface{}, error) {
return handler.New(func(ctx context.Context, params lsp.InitializedParams) (any, error) {
log.Debug().Str("method", "InitializedHandler").Msgf("initializing CLI now")
err := di.CliInitializer().Init()
if err != nil {
Expand Down Expand Up @@ -328,7 +329,7 @@ func monitorClientProcess(pid int) time.Duration {
}

func Shutdown() jrpc2.Handler {
return handler.New(func(ctx context.Context) (interface{}, error) {
return handler.New(func(ctx context.Context) (any, error) {
log.Info().Str("method", "Shutdown").Msg("RECEIVING")
defer log.Info().Str("method", "Shutdown").Msg("SENDING")
di.ErrorReporter().FlushErrorReporting()
Expand All @@ -344,7 +345,7 @@ func Shutdown() jrpc2.Handler {
}

func Exit(srv *jrpc2.Server) jrpc2.Handler {
return handler.New(func(_ context.Context) (interface{}, error) {
return handler.New(func(_ context.Context) (any, error) {
log.Info().Str("method", "Exit").Msg("RECEIVING")
log.Info().Msg("Stopping server...")
(*srv).Stop()
Expand All @@ -361,7 +362,7 @@ func logError(err error, method string) {
}

func TextDocumentDidOpenHandler() jrpc2.Handler {
return handler.New(func(_ context.Context, params sglsp.DidOpenTextDocumentParams) (interface{}, error) {
return handler.New(func(_ context.Context, params sglsp.DidOpenTextDocumentParams) (any, error) {
method := "TextDocumentDidOpenHandler"
filePath := uri.PathFromUri(params.TextDocument.URI)
log.Info().Str("method", method).Str("documentURI", filePath).Msg("RECEIVING")
Expand All @@ -376,7 +377,7 @@ func TextDocumentDidOpenHandler() jrpc2.Handler {
}

func TextDocumentDidSaveHandler() jrpc2.Handler {
return handler.New(func(_ context.Context, params sglsp.DidSaveTextDocumentParams) (interface{}, error) {
return handler.New(func(_ context.Context, params sglsp.DidSaveTextDocumentParams) (any, error) {
// The context provided by the JSON-RPC server is cancelled once a new message is being processed,
// so we don't want to propagate it to functions that start background operations
bgCtx := context.Background()
Expand Down Expand Up @@ -408,22 +409,22 @@ func TextDocumentHover() jrpc2.Handler {
}

func WindowWorkDoneProgressCancelHandler() jrpc2.Handler {
return handler.New(func(_ context.Context, params lsp.WorkdoneProgressCancelParams) (interface{}, error) {
return handler.New(func(_ context.Context, params lsp.WorkdoneProgressCancelParams) (any, error) {
log.Info().Str("method", "WindowWorkDoneProgressCancelHandler").Interface("params", params).Msg("RECEIVING")
CancelProgress(params.Token)
return nil, nil
})
}

func NoOpHandler() jrpc2.Handler {
return handler.New(func(_ context.Context, params sglsp.DidCloseTextDocumentParams) (interface{}, error) {
return handler.New(func(_ context.Context, params sglsp.DidCloseTextDocumentParams) (any, error) {
log.Info().Str("method", "NoOpHandler").Interface("params", params).Msg("RECEIVING")
return nil, nil
})
}

func registerNotifier(srv *jrpc2.Server) {
callbackFunction := func(params interface{}) {
callbackFunction := func(params any) {
switch params := params.(type) {
case lsp.AuthenticationParams:
notifier(srv, "$/snyk.hasAuthenticated", params)
Expand Down
26 changes: 24 additions & 2 deletions application/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func cleanupChannels() {
di.HoverService().ClearAllHovers()
}

type onCallbackFn = func(ctx context.Context, request *jrpc2.Request) (interface{}, error)
type onCallbackFn = func(ctx context.Context, request *jrpc2.Request) (any, error)

func startServer(callBackFn onCallbackFn) server.Local {
var srv *jrpc2.Server
Expand All @@ -119,7 +119,7 @@ func startServer(callBackFn onCallbackFn) server.Local {
OnNotify: func(request *jrpc2.Request) {
jsonRPCRecorder.Record(*request)
},
OnCallback: func(ctx context.Context, request *jrpc2.Request) (interface{}, error) {
OnCallback: func(ctx context.Context, request *jrpc2.Request) (any, error) {
if callBackFn != nil {
return callBackFn(ctx, request)
}
Expand Down Expand Up @@ -199,6 +199,28 @@ func Test_initialize_shouldSupportDocumentOpening(t *testing.T) {
assert.Equal(t, result.Capabilities.TextDocumentSync.Options.OpenClose, true)
}

func Test_initialize_shouldSupportAllCommands(t *testing.T) {
loc := setupServer(t)

rsp, err := loc.Client.Call(ctx, "initialize", nil)
if err != nil {
t.Fatal(err)
}
var result lsp.InitializeResult
if err := rsp.UnmarshalResult(&result); err != nil {
t.Fatal(err)
}

assert.Contains(t, result.Capabilities.ExecuteCommandProvider.Commands, snyk.NavigateToRangeCommand)
assert.Contains(t, result.Capabilities.ExecuteCommandProvider.Commands, snyk.WorkspaceScanCommand)
assert.Contains(t, result.Capabilities.ExecuteCommandProvider.Commands, snyk.WorkspaceFolderScanCommand)
assert.Contains(t, result.Capabilities.ExecuteCommandProvider.Commands, snyk.OpenBrowserCommand)
assert.Contains(t, result.Capabilities.ExecuteCommandProvider.Commands, snyk.LoginCommand)
assert.Contains(t, result.Capabilities.ExecuteCommandProvider.Commands, snyk.CopyAuthLinkCommand)
assert.Contains(t, result.Capabilities.ExecuteCommandProvider.Commands, snyk.LogoutCommand)
assert.Contains(t, result.Capabilities.ExecuteCommandProvider.Commands, snyk.TrustWorkspaceFoldersCommand)
}

func Test_initialize_shouldSupportDocumentSaving(t *testing.T) {
loc := setupServer(t)

Expand Down
4 changes: 2 additions & 2 deletions application/server/trust_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func Test_handleUntrustedFolders_shouldNotTriggerTrustRequestWhenAlreadyRequesti
}

func Test_handleUntrustedFolders_shouldTriggerTrustRequestAndScanAfterConfirmation(t *testing.T) {
loc := setupCustomServer(t, func(_ context.Context, _ *jrpc2.Request) (interface{}, error) {
loc := setupCustomServer(t, func(_ context.Context, _ *jrpc2.Request) (any, error) {
return lsp.MessageActionItem{
Title: doTrust,
}, nil
Expand All @@ -80,7 +80,7 @@ func Test_handleUntrustedFolders_shouldTriggerTrustRequestAndScanAfterConfirmati
}

func Test_handleUntrustedFolders_shouldTriggerTrustRequestAndNotScanAfterNegativeConfirmation(t *testing.T) {
loc := setupCustomServer(t, func(_ context.Context, _ *jrpc2.Request) (interface{}, error) {
loc := setupCustomServer(t, func(_ context.Context, _ *jrpc2.Request) (any, error) {
return lsp.MessageActionItem{
Title: dontTrust,
}, nil
Expand Down
2 changes: 1 addition & 1 deletion domain/ide/hover/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
sglsp "github.com/sourcegraph/go-lsp"
)

type Context interface{}
type Context any

type Hover[T Context] struct {
Id string
Expand Down
Loading