github.com/wata727/tflint@v0.12.2-0.20191013070026-96dd0d36f385/formatter/pretty.go (about)

     1  package formatter
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  
     9  	"github.com/fatih/color"
    10  	hcl "github.com/hashicorp/hcl/v2"
    11  	"github.com/hashicorp/hcl/v2/hclparse"
    12  	"github.com/wata727/tflint/tflint"
    13  )
    14  
    15  var colorBold = color.New(color.Bold).SprintfFunc()
    16  var colorHighlight = color.New(color.Bold).Add(color.Underline).SprintFunc()
    17  var colorError = color.New(color.FgRed).SprintFunc()
    18  var colorWarning = color.New(color.FgYellow).SprintFunc()
    19  var colorNotice = color.New(color.FgHiWhite).SprintFunc()
    20  
    21  func (f *Formatter) prettyPrint(issues tflint.Issues, err *tflint.Error, sources map[string][]byte) {
    22  	if len(issues) > 0 {
    23  		fmt.Fprintf(f.Stdout, "%d issue(s) found:\n\n", len(issues))
    24  
    25  		for _, issue := range issues.Sort() {
    26  			f.printIssueWithSource(issue, sources)
    27  		}
    28  	}
    29  
    30  	if err != nil {
    31  		f.printErrors(err, sources)
    32  	}
    33  }
    34  
    35  func (f *Formatter) printIssueWithSource(issue *tflint.Issue, sources map[string][]byte) {
    36  	fmt.Fprintf(
    37  		f.Stdout,
    38  		"%s: %s (%s)\n\n",
    39  		colorSeverity(issue.Rule.Severity()), colorBold(issue.Message), issue.Rule.Name(),
    40  	)
    41  	fmt.Fprintf(f.Stdout, "  on %s line %d:\n", issue.Range.Filename, issue.Range.Start.Line)
    42  
    43  	src := sources[issue.Range.Filename]
    44  
    45  	if src == nil {
    46  		fmt.Fprintf(f.Stdout, "   (source code not available)\n")
    47  	} else {
    48  		sc := hcl.NewRangeScanner(src, issue.Range.Filename, bufio.ScanLines)
    49  
    50  		for sc.Scan() {
    51  			lineRange := sc.Range()
    52  			if !lineRange.Overlaps(issue.Range) {
    53  				continue
    54  			}
    55  
    56  			beforeRange, highlightedRange, afterRange := lineRange.PartitionAround(issue.Range)
    57  			if highlightedRange.Empty() {
    58  				fmt.Fprintf(f.Stdout, "%4d: %s\n", lineRange.Start.Line, sc.Bytes())
    59  			} else {
    60  				before := beforeRange.SliceBytes(src)
    61  				highlighted := highlightedRange.SliceBytes(src)
    62  				after := afterRange.SliceBytes(src)
    63  				fmt.Fprintf(
    64  					f.Stdout,
    65  					"%4d: %s%s%s\n",
    66  					lineRange.Start.Line,
    67  					before,
    68  					colorHighlight(string(highlighted)),
    69  					after,
    70  				)
    71  			}
    72  		}
    73  	}
    74  
    75  	if len(issue.Callers) > 0 {
    76  		fmt.Fprint(f.Stdout, "\nCallers:\n")
    77  		for _, caller := range issue.Callers {
    78  			fmt.Fprintf(f.Stdout, "   %s\n", caller)
    79  		}
    80  	}
    81  
    82  	if issue.Rule.Link() != "" {
    83  		fmt.Fprintf(f.Stdout, "\nReference: %s\n", issue.Rule.Link())
    84  	}
    85  
    86  	fmt.Fprint(f.Stdout, "\n")
    87  }
    88  
    89  func (f *Formatter) printErrors(err *tflint.Error, sources map[string][]byte) {
    90  	if diags, ok := err.Cause.(hcl.Diagnostics); ok {
    91  		fmt.Fprintf(f.Stderr, "%s. %d error(s) occurred:\n\n", err.Message, len(diags.Errs()))
    92  
    93  		writer := hcl.NewDiagnosticTextWriter(f.Stderr, parseSources(sources), 0, !f.NoColor)
    94  		writer.WriteDiagnostics(diags)
    95  	} else {
    96  		fmt.Fprintf(f.Stderr, "%s. An error occurred:\n\n", err.Message)
    97  		fmt.Fprintf(f.Stderr, "%s: %s\n\n", colorError("Error"), err.Cause.Error())
    98  	}
    99  }
   100  
   101  func parseSources(sources map[string][]byte) map[string]*hcl.File {
   102  	ret := map[string]*hcl.File{}
   103  	parser := hclparse.NewParser()
   104  
   105  	var file *hcl.File
   106  	var diags hcl.Diagnostics
   107  	for filename, src := range sources {
   108  		if strings.HasSuffix(filename, ".json") {
   109  			file, diags = parser.ParseJSON(src, filename)
   110  		} else {
   111  			file, diags = parser.ParseHCL(src, filename)
   112  		}
   113  
   114  		if diags.HasErrors() {
   115  			log.Printf("[WARN] Failed to parse %s. This file is not available in output. Reason: %s", filename, diags.Error())
   116  		}
   117  		ret[filename] = file
   118  	}
   119  
   120  	return ret
   121  }
   122  
   123  func colorSeverity(severity string) string {
   124  	switch severity {
   125  	case tflint.ERROR:
   126  		return colorError(severity)
   127  	case tflint.WARNING:
   128  		return colorWarning(severity)
   129  	case tflint.NOTICE:
   130  		return colorNotice(severity)
   131  	default:
   132  		panic("Unreachable")
   133  	}
   134  }