✨ Added golang prettier plugin
This commit is contained in:
32
frontend/src/utils/prettier/plugins/go/build.bat
Normal file
32
frontend/src/utils/prettier/plugins/go/build.bat
Normal file
@@ -0,0 +1,32 @@
|
||||
@echo off
|
||||
rem Build script for Go Prettier Plugin WASM
|
||||
rem This script compiles the Go code to WebAssembly
|
||||
|
||||
echo 🔨 Building Go Prettier Plugin WASM...
|
||||
|
||||
rem Set WASM build environment
|
||||
set GOOS=js
|
||||
set GOARCH=wasm
|
||||
|
||||
rem Build the WASM file
|
||||
echo Compiling main.go to go.wasm...
|
||||
go build -o go.wasm main.go
|
||||
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
echo ✅ Build successful!
|
||||
|
||||
rem Show file size (Windows version)
|
||||
for %%A in (go.wasm) do echo 📊 WASM file size: %%~zA bytes
|
||||
|
||||
rem Copy to public directory for browser access
|
||||
if exist "..\..\..\..\..\public" (
|
||||
copy go.wasm ..\..\..\..\..\public\go.wasm > nul
|
||||
echo 📋 Copied to public directory
|
||||
)
|
||||
|
||||
echo 🎉 Go Prettier Plugin WASM is ready!
|
||||
) else (
|
||||
echo ❌ Build failed!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
30
frontend/src/utils/prettier/plugins/go/build.sh
Normal file
30
frontend/src/utils/prettier/plugins/go/build.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build script for Go Prettier Plugin WASM
|
||||
# This script compiles the Go code to WebAssembly
|
||||
|
||||
echo "🔨 Building Go Prettier Plugin WASM..."
|
||||
|
||||
# Set WASM build environment
|
||||
export GOOS=js
|
||||
export GOARCH=wasm
|
||||
|
||||
# Build the WASM file
|
||||
echo "Compiling main.go to go.wasm..."
|
||||
go build -o go.wasm main.go
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Build successful!"
|
||||
echo "📊 WASM file size: $(du -h go.wasm | cut -f1)"
|
||||
|
||||
# Copy to public directory for browser access
|
||||
if [ -d "../../../../../public" ]; then
|
||||
cp go.wasm ../../../../../public/go.wasm
|
||||
echo "📋 Copied to public directory"
|
||||
fi
|
||||
|
||||
echo "🎉 Go Prettier Plugin WASM is ready!"
|
||||
else
|
||||
echo "❌ Build failed!"
|
||||
exit 1
|
||||
fi
|
||||
10
frontend/src/utils/prettier/plugins/go/go.d.ts
vendored
Normal file
10
frontend/src/utils/prettier/plugins/go/go.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Parser, Plugin } from "prettier";
|
||||
|
||||
export declare const languages: Plugin["languages"];
|
||||
export declare const parsers: {
|
||||
go: Parser;
|
||||
};
|
||||
export declare const printers: Plugin["printers"];
|
||||
|
||||
declare const plugin: Plugin;
|
||||
export default plugin;
|
||||
116
frontend/src/utils/prettier/plugins/go/go.mjs
Normal file
116
frontend/src/utils/prettier/plugins/go/go.mjs
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Go Prettier Plugin for Vite + Vue3 Environment
|
||||
* WebAssembly-based Go code formatter for Prettier
|
||||
*/
|
||||
|
||||
let initializePromise = null;
|
||||
|
||||
// Load WASM file from public directory
|
||||
const loadWasm = async () => {
|
||||
try {
|
||||
const response = await fetch('/go.wasm');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load WASM file: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return await response.arrayBuffer();
|
||||
} catch (error) {
|
||||
console.error('WASM loading failed:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize Go runtime
|
||||
const initGoRuntime = async () => {
|
||||
if (globalThis.Go) return;
|
||||
|
||||
// Auto-load wasm_exec.js if not available
|
||||
try {
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = '/wasm_exec.js';
|
||||
document.head.appendChild(script);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
script.onload = resolve;
|
||||
script.onerror = () => reject(new Error('Failed to load wasm_exec.js'));
|
||||
setTimeout(() => reject(new Error('wasm_exec.js loading timeout')), 5000);
|
||||
});
|
||||
if (!globalThis.Go) {
|
||||
throw new Error('Go WASM runtime is not available after loading wasm_exec.js');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load wasm_exec.js:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const initialize = async () => {
|
||||
if (initializePromise) return initializePromise;
|
||||
|
||||
initializePromise = (async () => {
|
||||
await initGoRuntime();
|
||||
|
||||
const go = new globalThis.Go();
|
||||
const wasmBuffer = await loadWasm();
|
||||
const {instance} = await WebAssembly.instantiate(wasmBuffer, go.importObject);
|
||||
|
||||
// Run Go program
|
||||
go.run(instance).catch(err => {
|
||||
console.error('Go WASM program exit error:', err);
|
||||
});
|
||||
|
||||
// Wait for initialization to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if formatGo function is available
|
||||
if (typeof globalThis.formatGo !== 'function') {
|
||||
throw new Error('Go WASM module not properly initialized - formatGo function not available');
|
||||
}
|
||||
})();
|
||||
|
||||
return initializePromise;
|
||||
};
|
||||
|
||||
export const languages = [
|
||||
{
|
||||
name: "Go",
|
||||
parsers: ["go"],
|
||||
extensions: [".go"],
|
||||
vscodeLanguageIds: ["go"],
|
||||
},
|
||||
];
|
||||
|
||||
export const parsers = {
|
||||
go: {
|
||||
parse: (text) => text,
|
||||
astFormat: "go-format",
|
||||
locStart: (node) => 0,
|
||||
locEnd: (node) => node.length,
|
||||
},
|
||||
};
|
||||
|
||||
export const printers = {
|
||||
"go-format": {
|
||||
print: async (path) => {
|
||||
await initialize();
|
||||
const text = path.getValue();
|
||||
|
||||
if (typeof globalThis.formatGo !== 'function') {
|
||||
throw new Error('Go WASM module not properly initialized - formatGo function missing');
|
||||
}
|
||||
|
||||
try {
|
||||
return globalThis.formatGo(text);
|
||||
} catch (error) {
|
||||
throw new Error(`Go formatting failed: ${error.message}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Default export for Prettier plugin compatibility
|
||||
export default {
|
||||
languages,
|
||||
parsers,
|
||||
printers
|
||||
};
|
||||
255
frontend/src/utils/prettier/plugins/go/main.go
Normal file
255
frontend/src/utils/prettier/plugins/go/main.go
Normal file
@@ -0,0 +1,255 @@
|
||||
//go:build js && wasm
|
||||
|
||||
// Package main implements a WebAssembly module that provides Go code formatting
|
||||
// functionality for the Prettier plugin. This package exposes the formatGo function
|
||||
// to JavaScript, enabling web-based Go code formatting with better error tolerance.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// formatGoCode attempts to format Go source code with better error tolerance
|
||||
func formatGoCode(src string) (string, error) {
|
||||
// Trim input but preserve leading/trailing newlines structure
|
||||
trimmed := strings.TrimSpace(src)
|
||||
if trimmed == "" {
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// First try the standard format.Source for complete, valid code
|
||||
if formatted, err := format.Source([]byte(src)); err == nil {
|
||||
return string(formatted), nil
|
||||
}
|
||||
|
||||
// Create a new file set for parsing
|
||||
fset := token.NewFileSet()
|
||||
|
||||
// Strategy 1: Try as complete Go file
|
||||
if parsed, err := parser.ParseFile(fset, "", src, parser.ParseComments); err == nil {
|
||||
return formatASTNode(fset, parsed)
|
||||
}
|
||||
|
||||
// Strategy 2: Try wrapping as package-level declarations
|
||||
packageWrapped := fmt.Sprintf("package main\n\n%s", trimmed)
|
||||
if parsed, err := parser.ParseFile(fset, "", packageWrapped, parser.ParseComments); err == nil {
|
||||
if formatted, err := formatASTNode(fset, parsed); err == nil {
|
||||
return extractPackageContent(formatted), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Try wrapping in main function
|
||||
funcWrapped := fmt.Sprintf("package main\n\nfunc main() {\n%s\n}", indentLines(trimmed, "\t"))
|
||||
if parsed, err := parser.ParseFile(fset, "", funcWrapped, parser.ParseComments); err == nil {
|
||||
if formatted, err := formatASTNode(fset, parsed); err == nil {
|
||||
return extractFunctionBody(formatted), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 4: Try wrapping in anonymous function
|
||||
anonWrapped := fmt.Sprintf("package main\n\nvar _ = func() {\n%s\n}", indentLines(trimmed, "\t"))
|
||||
if parsed, err := parser.ParseFile(fset, "", anonWrapped, parser.ParseComments); err == nil {
|
||||
if formatted, err := formatASTNode(fset, parsed); err == nil {
|
||||
return extractFunctionBody(formatted), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 5: Try line-by-line formatting for complex cases
|
||||
return formatLineByLine(trimmed, fset)
|
||||
}
|
||||
|
||||
// formatASTNode formats an AST node using the standard formatter
|
||||
func formatASTNode(fset *token.FileSet, node interface{}) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, fset, node); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// extractPackageContent extracts content after package declaration
|
||||
func extractPackageContent(formatted string) string {
|
||||
lines := strings.Split(formatted, "\n")
|
||||
var contentLines []string
|
||||
skipNext := false
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "package ") {
|
||||
skipNext = true
|
||||
continue
|
||||
}
|
||||
if skipNext && strings.TrimSpace(line) == "" {
|
||||
skipNext = false
|
||||
continue
|
||||
}
|
||||
if !skipNext {
|
||||
contentLines = append(contentLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(contentLines, "\n")
|
||||
}
|
||||
|
||||
// extractFunctionBody extracts content from within a function body
|
||||
func extractFunctionBody(formatted string) string {
|
||||
lines := strings.Split(formatted, "\n")
|
||||
var bodyLines []string
|
||||
inFunction := false
|
||||
braceCount := 0
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "func ") && strings.Contains(line, "{") {
|
||||
inFunction = true
|
||||
braceCount = 1
|
||||
continue
|
||||
}
|
||||
|
||||
if inFunction {
|
||||
// Count braces to know when function ends
|
||||
braceCount += strings.Count(line, "{")
|
||||
braceCount -= strings.Count(line, "}")
|
||||
|
||||
if braceCount == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Remove one level of indentation and add the line
|
||||
if strings.HasPrefix(line, "\t") {
|
||||
bodyLines = append(bodyLines, line[1:])
|
||||
} else {
|
||||
bodyLines = append(bodyLines, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty lines from start and end
|
||||
for len(bodyLines) > 0 && strings.TrimSpace(bodyLines[0]) == "" {
|
||||
bodyLines = bodyLines[1:]
|
||||
}
|
||||
for len(bodyLines) > 0 && strings.TrimSpace(bodyLines[len(bodyLines)-1]) == "" {
|
||||
bodyLines = bodyLines[:len(bodyLines)-1]
|
||||
}
|
||||
|
||||
return strings.Join(bodyLines, "\n")
|
||||
}
|
||||
|
||||
// indentLines adds indentation to each non-empty line
|
||||
func indentLines(text, indent string) string {
|
||||
lines := strings.Split(text, "\n")
|
||||
var indentedLines []string
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
indentedLines = append(indentedLines, "")
|
||||
} else {
|
||||
indentedLines = append(indentedLines, indent+line)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(indentedLines, "\n")
|
||||
}
|
||||
|
||||
// formatLineByLine attempts to format each statement individually
|
||||
func formatLineByLine(src string, fset *token.FileSet) (string, error) {
|
||||
lines := strings.Split(src, "\n")
|
||||
var formattedLines []string
|
||||
|
||||
for _, line := range lines {
|
||||
trimmedLine := strings.TrimSpace(line)
|
||||
if trimmedLine == "" {
|
||||
formattedLines = append(formattedLines, "")
|
||||
continue
|
||||
}
|
||||
|
||||
// Try different wrapping strategies for individual lines
|
||||
attempts := []string{
|
||||
fmt.Sprintf("package main\n\nfunc main() {\n\t%s\n}", trimmedLine),
|
||||
fmt.Sprintf("package main\n\n%s", trimmedLine),
|
||||
fmt.Sprintf("package main\n\nvar _ = %s", trimmedLine),
|
||||
}
|
||||
|
||||
formatted := trimmedLine // fallback
|
||||
for _, attempt := range attempts {
|
||||
if parsed, err := parser.ParseFile(fset, "", attempt, parser.ParseComments); err == nil {
|
||||
if result, err := formatASTNode(fset, parsed); err == nil {
|
||||
if extracted := extractSingleStatement(result, trimmedLine); extracted != "" {
|
||||
formatted = extracted
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formattedLines = append(formattedLines, formatted)
|
||||
}
|
||||
|
||||
return strings.Join(formattedLines, "\n"), nil
|
||||
}
|
||||
|
||||
// extractSingleStatement tries to extract a single formatted statement
|
||||
func extractSingleStatement(formatted, original string) string {
|
||||
lines := strings.Split(formatted, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed != "" && !strings.HasPrefix(trimmed, "package ") &&
|
||||
!strings.HasPrefix(trimmed, "func ") && !strings.HasPrefix(trimmed, "var _ =") &&
|
||||
trimmed != "{" && trimmed != "}" {
|
||||
// Remove leading tabs/spaces to normalize indentation
|
||||
return strings.TrimLeft(line, " \t")
|
||||
}
|
||||
}
|
||||
|
||||
return original
|
||||
}
|
||||
|
||||
// formatGo is a JavaScript-callable function that formats Go source code.
|
||||
// It attempts multiple strategies to format code, including handling incomplete
|
||||
// or syntactically invalid code fragments.
|
||||
//
|
||||
// Parameters:
|
||||
// - this: The JavaScript 'this' context (unused)
|
||||
// - i: JavaScript arguments array where i[0] should contain the Go source code as a string
|
||||
//
|
||||
// Returns:
|
||||
// - js.Value: The formatted Go source code as a JavaScript string value
|
||||
// - If formatting fails completely, returns the original code unchanged
|
||||
// - If no arguments are provided, returns js.Null() and logs an error
|
||||
func formatGo(this js.Value, i []js.Value) interface{} {
|
||||
if len(i) == 0 {
|
||||
js.Global().Get("console").Call("error", "formatGo: missing code argument")
|
||||
return js.Null()
|
||||
}
|
||||
|
||||
code := i[0].String()
|
||||
if strings.TrimSpace(code) == "" {
|
||||
return js.ValueOf(code)
|
||||
}
|
||||
|
||||
formatted, err := formatGoCode(code)
|
||||
if err != nil {
|
||||
js.Global().Get("console").Call("warn", "Go formatting had issues:", err.Error())
|
||||
return js.ValueOf(code) // Return original code if all attempts fail
|
||||
}
|
||||
|
||||
return js.ValueOf(formatted)
|
||||
}
|
||||
|
||||
// main initializes the WebAssembly module and exposes the formatGo function
|
||||
// to the JavaScript global scope.
|
||||
func main() {
|
||||
// Create a channel to keep the Go program running
|
||||
c := make(chan struct{}, 0)
|
||||
|
||||
// Expose the formatGo function to the JavaScript global scope
|
||||
js.Global().Set("formatGo", js.FuncOf(formatGo))
|
||||
|
||||
// Block forever
|
||||
<-c
|
||||
}
|
||||
Reference in New Issue
Block a user