github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/common/herrors/error_locator.go (about) 1 // Copyright 2022 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 // Package herrors contains common Hugo errors and error related utilities. 15 package herrors 16 17 import ( 18 "io" 19 "path/filepath" 20 "strings" 21 22 "github.com/gohugoio/hugo/common/text" 23 ) 24 25 // LineMatcher contains the elements used to match an error to a line 26 type LineMatcher struct { 27 Position text.Position 28 Error error 29 30 LineNumber int 31 Offset int 32 Line string 33 } 34 35 // LineMatcherFn is used to match a line with an error. 36 // It returns the column number or 0 if the line was found, but column could not be determinde. Returns -1 if no line match. 37 type LineMatcherFn func(m LineMatcher) int 38 39 // SimpleLineMatcher simply matches by line number. 40 var SimpleLineMatcher = func(m LineMatcher) int { 41 if m.Position.LineNumber == m.LineNumber { 42 // We found the line, but don't know the column. 43 return 0 44 } 45 return -1 46 } 47 48 // NopLineMatcher is a matcher that always returns 1. 49 // This will effectively give line 1, column 1. 50 var NopLineMatcher = func(m LineMatcher) int { 51 return 1 52 } 53 54 // OffsetMatcher is a line matcher that matches by offset. 55 var OffsetMatcher = func(m LineMatcher) int { 56 if m.Offset+len(m.Line) >= m.Position.Offset { 57 // We found the line, but return 0 to signal that we want to determine 58 // the column from the error. 59 return 0 60 } 61 return -1 62 } 63 64 // ErrorContext contains contextual information about an error. This will 65 // typically be the lines surrounding some problem in a file. 66 type ErrorContext struct { 67 68 // If a match will contain the matched line and up to 2 lines before and after. 69 // Will be empty if no match. 70 Lines []string 71 72 // The position of the error in the Lines above. 0 based. 73 LinesPos int 74 75 // The position of the content in the file. Note that this may be different from the error's position set 76 // in FileError. 77 Position text.Position 78 79 // The lexer to use for syntax highlighting. 80 // https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages 81 ChromaLexer string 82 } 83 84 func chromaLexerFromType(fileType string) string { 85 switch fileType { 86 case "html", "htm": 87 return "go-html-template" 88 } 89 return fileType 90 } 91 92 func extNoDelimiter(filename string) string { 93 return strings.TrimPrefix(filepath.Ext(filename), ".") 94 } 95 96 func chromaLexerFromFilename(filename string) string { 97 if strings.Contains(filename, "layouts") { 98 return "go-html-template" 99 } 100 101 ext := extNoDelimiter(filename) 102 return chromaLexerFromType(ext) 103 } 104 105 func locateErrorInString(src string, matcher LineMatcherFn) *ErrorContext { 106 return locateError(strings.NewReader(src), &fileError{}, matcher) 107 } 108 109 func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext { 110 if le == nil { 111 panic("must provide an error") 112 } 113 114 ectx := &ErrorContext{LinesPos: -1, Position: text.Position{Offset: -1}} 115 116 b, err := io.ReadAll(r) 117 if err != nil { 118 return ectx 119 } 120 121 lines := strings.Split(string(b), "\n") 122 123 lineNo := 0 124 posBytes := 0 125 126 for li, line := range lines { 127 lineNo = li + 1 128 m := LineMatcher{ 129 Position: le.Position(), 130 Error: le, 131 LineNumber: lineNo, 132 Offset: posBytes, 133 Line: line, 134 } 135 v := matches(m) 136 if ectx.LinesPos == -1 && v != -1 { 137 ectx.Position.LineNumber = lineNo 138 ectx.Position.ColumnNumber = v 139 break 140 } 141 142 posBytes += len(line) 143 } 144 145 if ectx.Position.LineNumber > 0 { 146 low := ectx.Position.LineNumber - 3 147 if low < 0 { 148 low = 0 149 } 150 151 if ectx.Position.LineNumber > 2 { 152 ectx.LinesPos = 2 153 } else { 154 ectx.LinesPos = ectx.Position.LineNumber - 1 155 } 156 157 high := ectx.Position.LineNumber + 2 158 if high > len(lines) { 159 high = len(lines) 160 } 161 162 ectx.Lines = lines[low:high] 163 164 } 165 166 return ectx 167 }