Skip to content

Commit

Permalink
Merge pull request #161 from ysugimoto/feature/cmd-output-interface
Browse files Browse the repository at this point in the history
fix up output methods on runner
  • Loading branch information
ysugimoto authored Aug 24, 2023
2 parents 70cf476 + 8ef8658 commit f327cac
Showing 1 changed file with 72 additions and 67 deletions.
139 changes: 72 additions & 67 deletions cmd/falco/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"time"

"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/ysugimoto/falco/ast"
"github.com/ysugimoto/falco/config"
Expand Down Expand Up @@ -85,25 +86,30 @@ type Runner struct {
jsonMode bool
}

func NewRunner(c *config.Config, f Fetcher) (*Runner, error) {
r := &Runner{
level: LevelError,
overrides: make(map[string]linter.Severity),
lexers: make(map[string]*lexer.Lexer),
// Wrap writeln function in order to prevent to write when json mode turns on
func (r *Runner) message(c *color.Color, format string, args ...interface{}) {
// Suppress output when JSON mode turns on
// This is because JSON only should display JSON string
// so any other messages we must not output
if r.jsonMode {
return
}
writeln(c, format, args...)
}

if c.Json {
r.jsonMode = true
r.lintErrors = make(map[string][]*linter.LintError)
r.parseErrors = make(map[string]*parser.ParseError)
func NewRunner(c *config.Config, f Fetcher) (*Runner, error) {
r := &Runner{
level: LevelError,
overrides: make(map[string]linter.Severity),
lexers: make(map[string]*lexer.Lexer),
lintErrors: make(map[string][]*linter.LintError),
parseErrors: make(map[string]*parser.ParseError),
jsonMode: c.Json,
}

if c.Remote {
if !r.jsonMode {
writeln(cyan, "Remote option supplied. Fetching snippets from Fastly.")
}
r.message(cyan, "Remote option supplied. Fetching snippets from Fastly.")
// If remote flag is provided, fetch predefined data from Fastly.
// Currently, we only support Edge Dictionary.
//
// We communicate Fastly API with service id and api key,
// lookup fixed environment variable, FASTLY_SERVICE_ID and FASTLY_API_KEY
Expand All @@ -116,8 +122,8 @@ func NewRunner(c *config.Config, f Fetcher) (*Runner, error) {
// We allow each API call to take up to to 5 seconds
f := remote.NewFastlyApiFetcher(c.FastlyServiceID, c.FastlyApiKey, 5*time.Second)
snippets, err := NewSnippet(f).Fetch()
if err != nil && !r.jsonMode {
writeln(red, err.Error())
if err != nil {
r.message(red, err.Error())
}
// Stack to runner field, combime before run()
r.snippets = snippets
Expand All @@ -126,8 +132,8 @@ func NewRunner(c *config.Config, f Fetcher) (*Runner, error) {

if f != nil {
snippets, err := NewSnippet(f).Fetch()
if err != nil && !r.jsonMode {
writeln(red, err.Error())
if err != nil {
r.message(red, err.Error())
}
r.snippets = snippets
}
Expand Down Expand Up @@ -162,9 +168,7 @@ func NewRunner(c *config.Config, f Fetcher) (*Runner, error) {
case "IGNORE":
r.overrides[key] = linter.IGNORE
default:
if !r.jsonMode {
writeln(yellow, "Level for rule %s has invalid value %s, skipping.", key, value)
}
r.message(yellow, "Level for rule %s has invalid value %s, skipping.", key, value)
}
}

Expand Down Expand Up @@ -247,7 +251,16 @@ func (r *Runner) run(ctx *context.Context, main *resolver.VCL, mode RunMode) (*p
// Checking Fatal error, it means parse error occurs on included submodule
if lt.FatalError != nil {
if pe, ok := lt.FatalError.Error.(*parser.ParseError); ok {
r.printParseError(lt.FatalError.Lexer, pe)
var file string
if pe.Token.File != "" {
file = "in " + pe.Token.File + " "
}
// Nothing to print to stdout if JSON mode is enabled, exit early.
if r.jsonMode {
r.parseErrors[pe.Token.File] = pe
} else {
r.printParseError(lt.FatalError.Lexer, file, pe)
}
}
return nil, ErrParser
}
Expand All @@ -258,7 +271,17 @@ func (r *Runner) run(ctx *context.Context, main *resolver.VCL, mode RunMode) (*p
if !ok {
continue
}
r.printLinterError(r.lexers[main.Name], le)
// check severity with overrides
severity := le.Severity
if v, ok := r.overrides[string(le.Rule)]; ok {
severity = v
}

// Store all but ignored linter errors
if r.jsonMode && severity != linter.IGNORE {
r.lintErrors[le.Token.File] = append(r.lintErrors[le.Token.File], le)
}
r.printLinterError(r.lexers[main.Name], severity, le)
}
}

Expand All @@ -276,30 +299,26 @@ func (r *Runner) parseVCL(name, code string) (*ast.VCL, error) {
lx.NewLine()
pe, ok := errors.Cause(err).(*parser.ParseError)
if ok {
r.printParseError(lx, pe)
var file string
if pe.Token.File != "" {
file = "in " + pe.Token.File + " "
}
// Nothing to print to stdout if JSON mode is enabled, exit early.
if r.jsonMode {
r.parseErrors[pe.Token.File] = pe
}
r.printParseError(lx, file, pe)
}
return nil, ErrParser
}

lx.NewLine()
r.lexers[name] = lx
return vcl, nil
}

func (r *Runner) printParseError(lx *lexer.Lexer, err *parser.ParseError) {
var file string
if err.Token.File != "" {
file = "in " + err.Token.File + " "
if r.jsonMode {
r.parseErrors[err.Token.File] = err
}
}

// Nothing to print to stdout if JSON mode is enabled, exit early.
if r.jsonMode {
return
}

writeln(red, ":boom: %s\n%sat line %d, position %d", err.Message, file, err.Token.Line, err.Token.Position)
func (r *Runner) printParseError(lx *lexer.Lexer, file string, err *parser.ParseError) {
r.message(red, ":boom: %s\n%sat line %d, position %d", err.Message, file, err.Token.Line, err.Token.Position)

problemLine := err.Token.Line
for l := problemLine - 5; l <= problemLine; l++ {
Expand All @@ -309,20 +328,20 @@ func (r *Runner) printParseError(lx *lexer.Lexer, err *parser.ParseError) {
}
tabCount := strings.Count(line, "\t")
if l == problemLine {
writeln(yellow, " %d|%s", l, strings.ReplaceAll(line, "\t", " "))
r.message(yellow, " %d|%s", l, strings.ReplaceAll(line, "\t", " "))
lineLength := len(fmt.Sprint(l))
prefixSpaceSize := lineLength + err.Token.Position - tabCount + (tabCount * 4)
writeln(white, " %s%s",
r.message(white, " %s%s",
strings.Repeat(" ", prefixSpaceSize),
strings.Repeat("^", len([]rune(err.Token.Literal))+err.Token.Offset),
)
} else {
writeln(white, " %d|%s", l, strings.ReplaceAll(line, "\t", " "))
r.message(white, " %d|%s", l, strings.ReplaceAll(line, "\t", " "))
}
}
}

func (r *Runner) printLinterError(lx *lexer.Lexer, err *linter.LintError) {
func (r *Runner) printLinterError(lx *lexer.Lexer, severity linter.Severity, err *linter.LintError) {
var rule, file string

if err.Rule != "" {
Expand All @@ -335,41 +354,27 @@ func (r *Runner) printLinterError(lx *lexer.Lexer, err *linter.LintError) {
lx = r.lexers[err.Token.File]
}

// check severity with overrides
severity := err.Severity
if v, ok := r.overrides[string(err.Rule)]; ok {
severity = v
}

// Store all but ignored linter errors
if r.jsonMode && severity != linter.IGNORE {
r.lintErrors[err.Token.File] = append(r.lintErrors[err.Token.File], err)
}

switch severity {
case linter.ERROR:
r.errors++
if r.jsonMode {
return
}
writeln(red, ":fire:[ERROR] %s%s", err.Message, rule)
r.message(red, ":fire:[ERROR] %s%s", err.Message, rule)
case linter.WARNING:
r.warnings++
if r.jsonMode || r.level < LevelWarning {
if r.level < LevelWarning {
return
}
writeln(yellow, ":exclamation:[WARNING] %s%s", err.Message, rule)
r.message(yellow, ":exclamation:[WARNING] %s%s", err.Message, rule)
case linter.INFO:
r.infos++
if r.jsonMode || r.level < LevelInfo {
if r.level < LevelInfo {
return
}
writeln(cyan, ":speaker:[INFO] %s%s", err.Message, rule)
r.message(cyan, ":speaker:[INFO] %s%s", err.Message, rule)
case linter.IGNORE:
return
}

writeln(white, "%sat line %d, position %d", file, err.Token.Line, err.Token.Position)
r.message(white, "%sat line %d, position %d", file, err.Token.Line, err.Token.Position)

problemLine := err.Token.Line
for l := problemLine - 1; l <= problemLine+1; l++ {
Expand All @@ -379,22 +384,22 @@ func (r *Runner) printLinterError(lx *lexer.Lexer, err *linter.LintError) {
}
tabCount := strings.Count(line, "\t")
if l == problemLine {
writeln(yellow, " %d|%s", l, strings.ReplaceAll(line, "\t", " "))
r.message(yellow, " %d|%s", l, strings.ReplaceAll(line, "\t", " "))
lineLength := len(fmt.Sprint(l))
prefixSpaceSize := lineLength + err.Token.Position - tabCount + (tabCount * 4)
writeln(white, " %s%s",
r.message(white, " %s%s",
strings.Repeat(" ", prefixSpaceSize),
strings.Repeat("^", len([]rune(err.Token.Literal))+err.Token.Offset),
)
} else {
writeln(white, " %d|%s", l, strings.ReplaceAll(line, "\t", " "))
r.message(white, " %d|%s", l, strings.ReplaceAll(line, "\t", " "))
}
}

if err.Reference != "" {
writeln(white, "See reference documentation: %s", err.Reference)
r.message(white, "See reference documentation: %s", err.Reference)
}
writeln(white, "")
r.message(white, "")
}

func (r *Runner) Stats(rslv resolver.Resolver) (*StatsResult, error) {
Expand Down

0 comments on commit f327cac

Please sign in to comment.