Skip to content

Commit ee134ec

Browse files
committed
Refactor code processor: enhance replacement logic, improve error handling, and streamline whitespace management
1 parent ca43216 commit ee134ec

File tree

9 files changed

+429
-242
lines changed

9 files changed

+429
-242
lines changed

Makefile

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,42 @@ build: increment-version
2525
.PHONY: increment-version
2626
increment-version:
2727
@echo "Incrementing build version..."
28-
@current_version=$$(grep 'Version = ' internal/constants.go | sed 's/.*"\([^"]*\)".*/\1/'); \
29-
major=$$(echo $$current_version | cut -d. -f1); \
30-
minor=$$(echo $$current_version | cut -d. -f2); \
31-
patch=$$(echo $$current_version | cut -d. -f3); \
32-
new_patch=$$((patch + 1)); \
33-
new_version="$$major.$$minor.$$new_patch"; \
34-
echo "Updating version from $$current_version to $$new_version"; \
35-
sed -i 's/Version = "[^"]*"/Version = "'$$new_version'"/' internal/constants.go
28+
@go run - <<'EOF'
29+
package main
30+
31+
import (
32+
"fmt"
33+
"os"
34+
"regexp"
35+
"strconv"
36+
)
37+
38+
func main() {
39+
const file = "internal/constants.go"
40+
data, err := os.ReadFile(file)
41+
if err != nil {
42+
panic(err)
43+
}
44+
45+
re := regexp.MustCompile(`Version\s*=\s*"(\d+)\.(\d+)\.(\d+)"`)
46+
matches := re.FindSubmatch(data)
47+
if matches == nil {
48+
panic("could not find version in internal/constants.go")
49+
}
50+
51+
major, _ := strconv.Atoi(string(matches[1]))
52+
minor, _ := strconv.Atoi(string(matches[2]))
53+
patch, _ := strconv.Atoi(string(matches[3]))
54+
newVersion := fmt.Sprintf("%d.%d.%d", major, minor, patch+1)
55+
56+
fmt.Printf("Updating version from %d.%d.%d to %s\n", major, minor, patch, newVersion)
57+
58+
updated := re.ReplaceAll(data, []byte(fmt.Sprintf(`Version = "%s"`, newVersion)))
59+
if err := os.WriteFile(file, updated, 0o644); err != nil {
60+
panic(err)
61+
}
62+
}
63+
EOF
3664

3765
# Build without version increment
3866
.PHONY: build-no-version
@@ -61,9 +89,9 @@ endif
6189

6290
# Test the tool (create a simple test)
6391
.PHONY: test
64-
test: build
65-
@echo "Testing goahead build..."
66-
@echo "GoAhead built successfully. Use 'goahead -h' for usage help."
92+
test:
93+
@echo "Running unit and integration tests..."
94+
go test ./...
6795

6896
# Test as toolexec (requires installation)
6997
.PHONY: test-toolexec
@@ -100,12 +128,13 @@ help:
100128
@echo "Available targets:"
101129
@echo " build - Build for current platform"
102130
@echo " install - Install goahead locally"
103-
@echo " test - Test goahead in standalone mode"
131+
@echo " test - Run go test ./..."
104132
@echo " test-toolexec - Test goahead as toolexec"
105133
@echo " build-cross - Cross-compile for multiple platforms"
106134
@echo " clean - Clean build artifacts"
107135
@echo " setup - Setup project dependencies"
108136
@echo " help - Show this help"
109-
@echo "" @echo "Usage after installation:"
137+
@echo ""
138+
@echo "Usage after installation:"
110139
@echo " go install github.com/AeonDave/goahead@latest"
111-
@echo " go build -toolexec=\"goahead\" main.go"
140+
@echo " go build -toolexec=\"goahead\" ./..."

internal/code_processor.go

Lines changed: 96 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package internal
22

33
import (
44
"bufio"
5+
"errors"
56
"fmt"
67
"os"
78
"regexp"
@@ -17,6 +18,11 @@ type CodeProcessor struct {
1718
var (
1819
assignmentPattern = regexp.MustCompile(`^\s*(var\s+\w+(\s+[\w.\[\]]+)?\s*=|[\w.,\s]+\s*:=|[\w.]+\s*=)\s*`)
1920
assignmentSplitPattern = regexp.MustCompile(`^(\s*(?:var\s+\w+(?:\s+[\w.\[\]]+)?\s*=|[\w.,\s]+\s*:=|[\w.]+\s*=)\s*)(.*)$`)
21+
stringLiteralPattern = regexp.MustCompile(`""`)
22+
numericZeroPattern = regexp.MustCompile(`\b0\b`)
23+
floatZeroPattern = regexp.MustCompile(`\b0\.0\b`)
24+
boolFalsePattern = regexp.MustCompile(`\bfalse\b`)
25+
errNoReplacement = errors.New("no replacement performed")
2026
)
2127

2228
func NewCodeProcessor(ctx *ProcessorContext, executor *FunctionExecutor) *CodeProcessor {
@@ -95,59 +101,80 @@ func (cp *CodeProcessor) processCodeLine(line, funcName, argsStr, filePath strin
95101
return line, false
96102
}
97103

98-
typeHint := "other"
99-
if userFunc, ok := cp.ctx.Functions[funcName]; ok {
100-
typeHint = mapOutputType(userFunc.OutputType)
104+
typeHint := cp.typeHintFor(funcName, result)
105+
formattedResult := formatResultForReplacement(result, typeHint)
106+
107+
leadingWhitespace, _ := splitLeadingWhitespace(line)
108+
newLine, replaced, buildErr := cp.buildReplacementLine(line, leadingWhitespace, funcName, argsStr, formattedResult, typeHint)
109+
if buildErr != nil {
110+
if errors.Is(buildErr, errNoReplacement) {
111+
_, _ = fmt.Fprintf(os.Stderr, "Warning: Could not replace function call for '%s' in line: %s\n", funcName, strings.TrimSpace(line))
112+
}
113+
return line, false
101114
}
102115

103-
inferred := inferResultKind(result)
104-
if typeHint == "other" {
105-
typeHint = inferred
116+
if replaced {
117+
_, _ = fmt.Fprintf(os.Stderr, "[goahead] Replaced in %s: %s(%s) -> %s\n", filePath, funcName, argsStr, result)
118+
if verbose {
119+
_, _ = fmt.Fprintf(os.Stderr, " Original: '%s'\n New: '%s'\n", strings.TrimSpace(line), strings.TrimSpace(newLine))
120+
}
106121
}
107122

108-
formattedResult := formatResultForReplacement(result, typeHint)
123+
return newLine, replaced
124+
}
109125

110-
trimmedLine := strings.TrimSpace(line)
111-
leadingWhitespace := ""
112-
if len(line) > len(trimmedLine) {
113-
leadingWhitespace = line[:len(line)-len(trimmedLine)]
126+
func splitLeadingWhitespace(line string) (string, string) {
127+
trimmed := strings.TrimSpace(line)
128+
if len(line) == len(trimmed) {
129+
return "", line
114130
}
131+
return line[:len(line)-len(trimmed)], trimmed
132+
}
115133

116-
isAssignment := assignmentPattern.MatchString(line)
134+
func (cp *CodeProcessor) buildReplacementLine(originalLine, leadingWhitespace, funcName, argsStr, formattedResult, typeHint string) (string, bool, error) {
135+
if assignmentPattern.MatchString(originalLine) {
136+
return cp.replaceInAssignment(originalLine, funcName, argsStr, formattedResult, typeHint)
137+
}
117138

118-
var newLine string
119-
if isAssignment {
120-
matches := assignmentSplitPattern.FindStringSubmatch(line)
139+
if replacedLine, ok := cp.replaceFunctionCall(originalLine, funcName, argsStr, formattedResult); ok {
140+
return replacedLine, true, nil
141+
}
121142

122-
if len(matches) >= 3 {
123-
varAssignPart := matches[1]
124-
expressionPart := matches[2]
125-
replacedExpression := cp.replaceFirstPlaceholder(expressionPart, formattedResult, typeHint)
126-
if replacedExpression != expressionPart {
127-
newLine = varAssignPart + replacedExpression
128-
} else {
129-
newLine = varAssignPart + formattedResult
130-
}
131-
} else {
132-
replacement := cp.replaceFunctionCall(line, funcName, argsStr, formattedResult)
133-
if replacement == line {
134-
_, _ = fmt.Fprintf(os.Stderr, "Warning: Could not replace function call for '%s' in line: %s\n", funcName, strings.TrimSpace(line))
135-
return line, false
136-
}
137-
newLine = replacement
143+
newLine := leadingWhitespace + formattedResult
144+
return newLine, newLine != originalLine, nil
145+
}
146+
147+
func (cp *CodeProcessor) replaceInAssignment(originalLine, funcName, argsStr, formattedResult, typeHint string) (string, bool, error) {
148+
matches := assignmentSplitPattern.FindStringSubmatch(originalLine)
149+
if len(matches) < 3 {
150+
replacedLine, ok := cp.replaceFunctionCall(originalLine, funcName, argsStr, formattedResult)
151+
if !ok {
152+
return "", false, errNoReplacement
138153
}
139-
} else {
140-
newLine = leadingWhitespace + formattedResult
154+
return replacedLine, true, nil
141155
}
142156

143-
replaced := newLine != line
157+
varAssignPart := matches[1]
158+
expressionPart := matches[2]
159+
160+
replacedExpression, replaced := cp.replaceFirstPlaceholder(expressionPart, formattedResult, typeHint)
144161
if replaced {
145-
_, _ = fmt.Fprintf(os.Stderr, "[goahead] Replaced in %s: %s(%s) -> %s\n", filePath, funcName, argsStr, result)
146-
if verbose {
147-
_, _ = fmt.Fprintf(os.Stderr, " Original: '%s'\n New: '%s'\n", strings.TrimSpace(line), strings.TrimSpace(newLine))
162+
newLine := varAssignPart + replacedExpression
163+
return newLine, newLine != originalLine, nil
164+
}
165+
166+
newLine := varAssignPart + formattedResult
167+
return newLine, newLine != originalLine, nil
168+
}
169+
170+
func (cp *CodeProcessor) typeHintFor(funcName, result string) string {
171+
if userFunc, ok := cp.ctx.Functions[funcName]; ok {
172+
hint := mapOutputType(userFunc.OutputType)
173+
if hint != "other" {
174+
return hint
148175
}
149176
}
150-
return newLine, replaced
177+
return inferResultKind(result)
151178
}
152179

153180
func (cp *CodeProcessor) writeFile(filePath string, lines []string) error {
@@ -183,98 +210,49 @@ func escapeString(s string) string {
183210
return `"` + strings.ReplaceAll(s, `"`, `\"`) + `"`
184211
}
185212

186-
func (cp *CodeProcessor) replaceFirstPlaceholder(expression, replacement, typeHint string) string {
213+
func (cp *CodeProcessor) replaceFirstPlaceholder(expression, replacement, typeHint string) (string, bool) {
187214
switch typeHint {
188215
case "string":
189-
re := regexp.MustCompile(`""`)
190-
if re.MatchString(expression) {
191-
replaced := false
192-
return re.ReplaceAllStringFunc(expression, func(match string) string {
193-
if !replaced {
194-
replaced = true
195-
return replacement
196-
}
197-
return match
198-
})
199-
}
200-
201-
case "int":
202-
re := regexp.MustCompile(`\b0\b`)
203-
if re.MatchString(expression) {
204-
replaced := false
205-
return re.ReplaceAllStringFunc(expression, func(match string) string {
206-
if !replaced {
207-
replaced = true
208-
return replacement
209-
}
210-
return match
211-
})
212-
}
213-
214-
case "uint":
215-
re := regexp.MustCompile(`\b0\b`)
216-
if re.MatchString(expression) {
217-
replaced := false
218-
return re.ReplaceAllStringFunc(expression, func(match string) string {
219-
if !replaced {
220-
replaced = true
221-
return replacement
222-
}
223-
return match
224-
})
225-
}
226-
216+
return replaceFirstMatch(stringLiteralPattern, expression, replacement)
217+
case "int", "uint":
218+
return replaceFirstMatch(numericZeroPattern, expression, replacement)
227219
case "float":
228-
re := regexp.MustCompile(`\b0\.0\b`)
229-
if re.MatchString(expression) {
230-
replaced := false
231-
return re.ReplaceAllStringFunc(expression, func(match string) string {
232-
if !replaced {
233-
replaced = true
234-
return replacement
235-
}
236-
return match
237-
})
238-
}
239-
reZero := regexp.MustCompile(`\b0\b`)
240-
if reZero.MatchString(expression) {
241-
replaced := false
242-
return reZero.ReplaceAllStringFunc(expression, func(match string) string {
243-
if !replaced {
244-
replaced = true
245-
return replacement
246-
}
247-
return match
248-
})
220+
if updated, ok := replaceFirstMatch(floatZeroPattern, expression, replacement); ok {
221+
return updated, true
249222
}
250-
223+
return replaceFirstMatch(numericZeroPattern, expression, replacement)
251224
case "bool":
252-
re := regexp.MustCompile(`\bfalse\b`)
253-
if re.MatchString(expression) {
254-
replaced := false
255-
return re.ReplaceAllStringFunc(expression, func(match string) string {
256-
if !replaced {
257-
replaced = true
258-
return replacement
259-
}
260-
return match
261-
})
262-
}
225+
return replaceFirstMatch(boolFalsePattern, expression, replacement)
226+
default:
227+
return expression, false
263228
}
264-
return expression
265229
}
266230

267-
func (cp *CodeProcessor) replaceFunctionCall(line, funcName, argsStr, replacement string) string {
268-
updated := line
231+
func replaceFirstMatch(re *regexp.Regexp, expression, replacement string) (string, bool) {
232+
replaced := false
233+
updated := re.ReplaceAllStringFunc(expression, func(match string) string {
234+
if replaced {
235+
return match
236+
}
237+
replaced = true
238+
return replacement
239+
})
240+
return updated, replaced
241+
}
242+
243+
func (cp *CodeProcessor) replaceFunctionCall(line, funcName, argsStr, replacement string) (string, bool) {
269244
if argsStr != "" {
270245
re := regexp.MustCompile(fmt.Sprintf(`%s\(\s*%s\s*\)`, regexp.QuoteMeta(funcName), regexp.QuoteMeta(argsStr)))
271-
updated = re.ReplaceAllString(updated, replacement)
246+
if re.MatchString(line) {
247+
return re.ReplaceAllString(line, replacement), true
248+
}
272249
}
273-
if updated == line {
274-
re := regexp.MustCompile(fmt.Sprintf(`%s\([^)]*\)`, regexp.QuoteMeta(funcName)))
275-
updated = re.ReplaceAllString(updated, replacement)
250+
251+
re := regexp.MustCompile(fmt.Sprintf(`%s\([^)]*\)`, regexp.QuoteMeta(funcName)))
252+
if re.MatchString(line) {
253+
return re.ReplaceAllString(line, replacement), true
276254
}
277-
return updated
255+
return line, false
278256
}
279257

280258
func mapOutputType(outputType string) string {

internal/constants.go

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package internal
22

33
const (
4-
Version = "1.0.2"
4+
Version = "1.1.0"
55
FunctionMarker = "//go:ahead functions"
66
CommentPattern = `^\s*//\s*:([^:]+)(?::(.*))?`
77
ExecutionTemplate = `package main
@@ -37,23 +37,4 @@ var (
3737
"/vendor/",
3838
"/pkg/mod/",
3939
}
40-
//SupportedTypes = []string{
41-
// "string",
42-
// "int",
43-
// "int8",
44-
// "int16",
45-
// "int32",
46-
// "int64",
47-
// "uint",
48-
// "uint8",
49-
// "uint16",
50-
// "uint32",
51-
// "uint64",
52-
// "bool",
53-
// "float32",
54-
// "float64",
55-
// "byte",
56-
// "rune",
57-
//}
58-
//ExcludedPaths = append(GoInstallPaths, SystemPaths...)
5940
)

0 commit comments

Comments
 (0)