github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/linter/fixer/fixer.go (about) 1 package fixer 2 3 import ( 4 "bytes" 5 "io/ioutil" 6 "strings" 7 8 "github.com/yoheimuta/go-protoparser/v4/lexer" 9 "github.com/yoheimuta/go-protoparser/v4/parser/meta" 10 11 "github.com/yoheimuta/go-protoparser/v4/parser" 12 13 "github.com/yoheimuta/protolint/internal/osutil" 14 ) 15 16 // TextEdit represents the replacement of the code between Pos and End with the new text. 17 type TextEdit struct { 18 Pos int 19 End int // Inclusive. If the target is abc, pos and end are 1 and 3, respectively. 20 NewText []byte 21 } 22 23 // Fixer provides the ways to operate the proto content. 24 type Fixer interface { 25 // NOTE: This method is insufficient to process unexpected multi-line contents. 26 ReplaceText(line int, old, new string) 27 ReplaceAll(proc func(lines []string) []string) 28 29 SearchAndReplace(startPos meta.Position, lex func(lex *lexer.Lexer) TextEdit) error 30 ReplaceContent(proc func(content []byte) []byte) 31 32 Lines() []string 33 } 34 35 // Fixing adds the way to modify the proto file to Fixer. 36 type Fixing interface { 37 Fixer 38 Finally() error 39 } 40 41 // NewFixing creates a fixing, depending on fixMode. 42 func NewFixing(fixMode bool, proto *parser.Proto) (Fixing, error) { 43 if fixMode { 44 return NewBaseFixing(proto.Meta.Filename) 45 } 46 return NopFixing{}, nil 47 } 48 49 // BaseFixing implements Fixing. 50 type BaseFixing struct { 51 content []byte 52 lineEnding string 53 fileName string 54 textEdits []TextEdit 55 } 56 57 // NewBaseFixing creates a BaseFixing. 58 func NewBaseFixing(protoFileName string) (*BaseFixing, error) { 59 content, err := ioutil.ReadFile(protoFileName) 60 if err != nil { 61 return nil, err 62 } 63 64 // Regardless of the actual dominant line ending, the fixer will go with LF 65 // because the parser recognizes only LF as a line ending. 66 // 67 // It will work for most cases like used LF, CRLF, and a mix of LF and CRLF. 68 // See also https://github.com/yoheimuta/protolint/issues/280. 69 lineEnding := "\n" 70 71 return &BaseFixing{ 72 content: content, 73 lineEnding: lineEnding, 74 fileName: protoFileName, 75 }, nil 76 } 77 78 // ReplaceText replaces the text at the line. 79 func (f *BaseFixing) ReplaceText(line int, old, new string) { 80 lines := strings.Split(string(f.content), f.lineEnding) 81 lines[line-1] = strings.Replace(lines[line-1], old, new, 1) 82 f.content = []byte(strings.Join(lines, f.lineEnding)) 83 } 84 85 // ReplaceAll replaces the lines. 86 func (f *BaseFixing) ReplaceAll(proc func(lines []string) []string) { 87 lines := strings.Split(string(f.content), f.lineEnding) 88 lines = proc(lines) 89 f.content = []byte(strings.Join(lines, f.lineEnding)) 90 } 91 92 // SearchAndReplace locates text edits and replaces with them. 93 func (f *BaseFixing) SearchAndReplace(startPos meta.Position, lex func(lex *lexer.Lexer) TextEdit) error { 94 r := bytes.NewReader(f.content) 95 _, err := r.Seek(int64(startPos.Offset), 0) 96 if err != nil { 97 return err 98 } 99 100 l := lexer.NewLexer(r) 101 t := lex(l) 102 t.Pos += startPos.Offset 103 t.End += startPos.Offset 104 f.textEdits = append(f.textEdits, t) 105 return nil 106 } 107 108 // ReplaceContent replaces entire content. 109 func (f *BaseFixing) ReplaceContent(proc func(content []byte) []byte) { 110 f.content = proc(f.content) 111 } 112 113 // Lines returns the line format of f.content. 114 func (f *BaseFixing) Lines() []string { 115 return strings.Split(string(f.content), f.lineEnding) 116 } 117 118 // Finally writes the fixed content to the file. 119 func (f *BaseFixing) Finally() error { 120 diff := 0 121 for _, t := range f.textEdits { 122 t.Pos += diff 123 t.End += diff 124 f.content = append(f.content[:t.Pos], append(t.NewText, f.content[t.End+1:]...)...) 125 diff += len(t.NewText) - (t.End - t.Pos + 1) 126 } 127 return osutil.WriteExistingFile(f.fileName, f.content) 128 } 129 130 // Replace records a textedit to replace the old with the next later. 131 func (f *BaseFixing) Replace(t TextEdit) { 132 f.textEdits = append(f.textEdits, t) 133 } 134 135 // Content returns f.content. 136 func (f *BaseFixing) Content() []byte { 137 return f.content 138 } 139 140 // LineEnding is a detected line ending. 141 func (f *BaseFixing) LineEnding() string { 142 return f.lineEnding 143 } 144 145 // NopFixing does nothing. 146 type NopFixing struct{} 147 148 // ReplaceText noop 149 func (f NopFixing) ReplaceText(line int, old, new string) {} 150 151 // ReplaceAll noop 152 func (f NopFixing) ReplaceAll(proc func(lines []string) []string) {} 153 154 // SearchAndReplace noop 155 func (f NopFixing) SearchAndReplace(startPos meta.Position, lex func(lexer *lexer.Lexer) TextEdit) error { 156 return nil 157 } 158 159 // ReplaceContent noop. 160 func (f NopFixing) ReplaceContent(proc func(content []byte) []byte) {} 161 162 // Lines noop. 163 func (f NopFixing) Lines() []string { return []string{} } 164 165 // Finally noop 166 func (f NopFixing) Finally() error { return nil }