vet/pkg/code/scanner.go
Omkar Phansopkar 08b5f612ac
Implemented code scan command for building sqlite storage with code analysis data (#326)
* Implemented code scan command for building sqlite storage with code analysis data

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* Added E2E test for code scan command

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>

* refactor: Migrate pkg/command to internal/command since we use pkg as a independent concern

---------

Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
Co-authored-by: abhisek <abhisek.datta@gmail.com>
2025-02-03 11:23:51 +05:30

137 lines
3.5 KiB
Go

package code
import (
"context"
"fmt"
"regexp"
"github.com/safedep/code/core"
"github.com/safedep/code/fs"
"github.com/safedep/code/parser"
"github.com/safedep/code/plugin"
"github.com/safedep/code/plugin/depsusage"
"github.com/safedep/vet/ent"
"github.com/safedep/vet/pkg/storage"
)
// User define configuration for the scanner
type ScannerConfig struct {
// First party application code directories
AppDirectories []string
// 3rd party imported code directories (e.g. Python virtual env, `node_modules` etc.)
ImportDirectories []string
// Regular expressions to exclude files or directories
// from traversal
ExcludePatterns []*regexp.Regexp
// Languages to scan
Languages []core.Language
// Define callbacks if required
Callbacks *ScannerCallbackRegistry
// Plugin specific configuration
SkipDependencyUsagePlugin bool
}
type ScannerCallbackRegistry struct {
// On start of scan
OnScanStart func() error
// On end of scan
OnScanEnd func() error
}
// Scanner defines the contract for implementing a code scanner. The purpose
// of code scanner is to scan configured directories for code files,
// parse them, process them with plugins, persist the plugin results. It
// should also expose the necessary callbacks for interactive applications
// to show progress to user.
type Scanner interface {
Scan(context.Context) error
}
type scanner struct {
config ScannerConfig
storage storage.Storage[*ent.Client]
writer writerRepository
}
func NewScanner(config ScannerConfig, storage storage.Storage[*ent.Client]) (Scanner, error) {
client, err := storage.Client()
if err != nil {
return nil, fmt.Errorf("failed to get ent client: %w", err)
}
writer, err := newWriterRepository(client)
if err != nil {
return nil, fmt.Errorf("failed to create writer repository: %w", err)
}
return &scanner{
config: config,
storage: storage,
writer: writer,
}, nil
}
func (s *scanner) Scan(ctx context.Context) error {
err := s.config.Callbacks.OnScanStart()
if err != nil {
return fmt.Errorf("failed to execute OnScanStart callback: %w", err)
}
fileSystem, err := fs.NewLocalFileSystem(fs.LocalFileSystemConfig{
AppDirectories: s.config.AppDirectories,
ImportDirectories: s.config.ImportDirectories,
ExcludePatterns: s.config.ExcludePatterns,
})
if err != nil {
return fmt.Errorf("failed to create file system: %w", err)
}
walker, err := fs.NewSourceWalker(fs.SourceWalkerConfig{}, s.config.Languages)
if err != nil {
return fmt.Errorf("failed to create source walker: %w", err)
}
treeWalker, err := parser.NewWalkingParser(walker, s.config.Languages)
if err != nil {
return fmt.Errorf("failed to create tree walker: %w", err)
}
// Configure plugins
plugins := []core.Plugin{}
if !s.config.SkipDependencyUsagePlugin {
var usageCallback depsusage.DependencyUsageCallback = func(ctx context.Context, evidence *depsusage.UsageEvidence) error {
_, err := s.writer.SaveDependencyUsage(ctx, evidence)
if err != nil {
return fmt.Errorf("failed to save dependency usage: %w", err)
}
return nil
}
plugins = append(plugins, depsusage.NewDependencyUsagePlugin(usageCallback))
}
// Execute plugins
pluginExecutor, err := plugin.NewTreeWalkPluginExecutor(treeWalker, plugins)
if err != nil {
return fmt.Errorf("failed to create plugin executor: %w", err)
}
err = pluginExecutor.Execute(ctx, fileSystem)
if err != nil {
return fmt.Errorf("failed to execute plugin: %w", err)
}
err = s.config.Callbacks.OnScanEnd()
if err != nil {
return fmt.Errorf("failed to execute OnScanEnd callback: %w", err)
}
return nil
}