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 }