3 Commits

Author SHA1 Message Date
d24a522b32 Added php prettier plugin 2025-09-12 20:15:56 +08:00
41afb834ae Added sql prettier plugin 2025-09-12 00:52:19 +08:00
b745329e26 Added golang prettier plugin 2025-09-11 20:42:39 +08:00
29 changed files with 10404 additions and 302 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -12,13 +12,13 @@
"lint:fix": "eslint --fix"
},
"dependencies": {
"@codemirror/autocomplete": "^6.18.6",
"@codemirror/autocomplete": "^6.18.7",
"@codemirror/commands": "^6.8.1",
"@codemirror/lang-angular": "^0.1.4",
"@codemirror/lang-cpp": "^6.0.3",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-go": "^6.0.1",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-html": "^6.4.10",
"@codemirror/lang-java": "^6.0.2",
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.2",
@@ -33,7 +33,6 @@
"@codemirror/lang-sql": "^6.9.1",
"@codemirror/lang-vue": "^0.1.3",
"@codemirror/lang-wast": "^6.0.2",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.3",
"@codemirror/language-data": "^6.5.1",
@@ -41,7 +40,7 @@
"@codemirror/lint": "^6.8.5",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.1",
"@codemirror/view": "^6.38.2",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
"codemirror": "^6.0.2",
@@ -50,31 +49,37 @@
"colors-named-hex": "^1.0.2",
"franc-min": "^6.2.0",
"hsl-matcher": "^1.2.4",
"jsox": "^1.2.123",
"lezer": "^0.13.5",
"linguist-languages": "^9.0.0",
"node-sql-parser": "^5.3.12",
"php-parser": "^3.2.5",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"prettier": "^3.6.2",
"remarkable": "^2.0.1",
"sass": "^1.90.0",
"vue": "^3.5.19",
"vue-i18n": "^11.1.11",
"sass": "^1.92.1",
"sql-formatter": "^15.6.9",
"vue": "^3.5.21",
"vue-i18n": "^11.1.12",
"vue-pick-colors": "^1.8.0",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@eslint/js": "^9.34.0",
"@eslint/js": "^9.35.0",
"@lezer/generator": "^1.8.0",
"@types/node": "^24.3.0",
"@types/node": "^24.3.1",
"@types/remarkable": "^2.0.8",
"@vitejs/plugin-vue": "^6.0.1",
"@wailsio/runtime": "latest",
"eslint": "^9.34.0",
"eslint": "^9.35.0",
"eslint-plugin-vue": "^10.4.0",
"globals": "^16.3.0",
"globals": "^16.4.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.40.0",
"typescript-eslint": "^8.43.0",
"unplugin-vue-components": "^29.0.0",
"vite": "^7.1.3",
"vite": "^7.1.5",
"vite-plugin-node-polyfills": "^0.24.0",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.6"
}

BIN
frontend/public/go.wasm Normal file

Binary file not shown.

View File

@@ -0,0 +1,561 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
"use strict";
(() => {
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!globalThis.fs) {
let outputBuf = "";
globalThis.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substring(0, nl));
outputBuf = outputBuf.substring(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { callback(enosys()); },
close(fd, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { callback(enosys()); },
fstat(fd, callback) { callback(enosys()); },
fsync(fd, callback) { callback(null); },
ftruncate(fd, length, callback) { callback(enosys()); },
lchown(path, uid, gid, callback) { callback(enosys()); },
link(path, link, callback) { callback(enosys()); },
lstat(path, callback) { callback(enosys()); },
mkdir(path, perm, callback) { callback(enosys()); },
open(path, flags, mode, callback) { callback(enosys()); },
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
readdir(path, callback) { callback(enosys()); },
readlink(path, callback) { callback(enosys()); },
rename(from, to, callback) { callback(enosys()); },
rmdir(path, callback) { callback(enosys()); },
stat(path, callback) { callback(enosys()); },
symlink(path, link, callback) { callback(enosys()); },
truncate(path, length, callback) { callback(enosys()); },
unlink(path, callback) { callback(enosys()); },
utimes(path, atime, mtime, callback) { callback(enosys()); },
};
}
if (!globalThis.process) {
globalThis.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
getegid() { return -1; },
getgroups() { throw enosys(); },
pid: -1,
ppid: -1,
umask() { throw enosys(); },
cwd() { throw enosys(); },
chdir() { throw enosys(); },
}
}
if (!globalThis.crypto) {
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
}
if (!globalThis.performance) {
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
}
if (!globalThis.TextEncoder) {
throw new Error("globalThis.TextEncoder is not available, polyfill required");
}
if (!globalThis.TextDecoder) {
throw new Error("globalThis.TextDecoder is not available, polyfill required");
}
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
globalThis.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve;
});
this._pendingEvent = null;
this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const setInt64 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
}
const setInt32 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
}
const getInt64 = (addr) => {
const low = this.mem.getUint32(addr + 0, true);
const high = this.mem.getInt32(addr + 4, true);
return low + high * 4294967296;
}
const loadValue = (addr) => {
const f = this.mem.getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = this.mem.getUint32(addr, true);
return this._values[id];
}
const storeValue = (addr, v) => {
const nanHead = 0x7FF80000;
if (typeof v === "number" && v !== 0) {
if (isNaN(v)) {
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true);
return;
}
this.mem.setFloat64(addr, v, true);
return;
}
if (v === undefined) {
this.mem.setFloat64(addr, 0, true);
return;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = this._values.length;
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 0;
switch (typeof v) {
case "object":
if (v !== null) {
typeFlag = 1;
}
break;
case "string":
typeFlag = 2;
break;
case "symbol":
typeFlag = 3;
break;
case "function":
typeFlag = 4;
break;
}
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true);
}
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
_gotest: {
add: (a, b) => a + b,
},
gojs: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
sp >>>= 0;
const code = this.mem.getInt32(sp + 8, true);
this.exited = true;
delete this._inst;
delete this._values;
delete this._goRefCounts;
delete this._ids;
delete this._idPool;
this.exit(code);
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": (sp) => {
sp >>>= 0;
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = this.mem.getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
},
// func resetMemoryDataView()
"runtime.resetMemoryDataView": (sp) => {
sp >>>= 0;
this.mem = new DataView(this._inst.exports.mem.buffer);
},
// func nanotime1() int64
"runtime.nanotime1": (sp) => {
sp >>>= 0;
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
},
// func walltime() (sec int64, nsec int32)
"runtime.walltime": (sp) => {
sp >>>= 0;
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set(id, setTimeout(
() => {
this._resume();
while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
console.warn("scheduleTimeoutEvent: missed timeout event");
this._resume();
}
},
getInt64(sp + 8),
));
this.mem.setInt32(sp + 16, id, true);
},
// func clearTimeoutEvent(id int32)
"runtime.clearTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this.mem.getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id));
this._scheduledTimeouts.delete(id);
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
sp >>>= 0;
crypto.getRandomValues(loadSlice(sp + 8));
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (sp) => {
sp >>>= 0;
const id = this.mem.getUint32(sp + 8, true);
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
sp >>>= 0;
storeValue(sp + 24, loadString(sp + 8));
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
sp >>>= 0;
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (sp) => {
sp >>>= 0;
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (sp) => {
sp >>>= 0;
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
const result = Reflect.apply(m, v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, result);
this.mem.setUint8(sp + 64, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, err);
this.mem.setUint8(sp + 64, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.apply(v, undefined, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.construct(v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
sp >>>= 0;
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (sp) => {
sp >>>= 0;
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (sp) => {
sp >>>= 0;
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
sp >>>= 0;
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (sp) => {
sp >>>= 0;
const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
// func copyBytesToJS(dst ref, src []byte) (int, bool)
"syscall/js.copyBytesToJS": (sp) => {
sp >>>= 0;
const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16);
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
"debug": (value) => {
console.log(value);
},
}
};
}
async run(instance) {
if (!(instance instanceof WebAssembly.Instance)) {
throw new Error("Go.run: WebAssembly.Instance expected");
}
this._inst = instance;
this.mem = new DataView(this._inst.exports.mem.buffer);
this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
globalThis,
this,
];
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map([ // mapping from JS values to reference ids
[0, 1],
[null, 2],
[true, 3],
[false, 4],
[globalThis, 5],
[this, 6],
]);
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
const ptr = offset;
const bytes = encoder.encode(str + "\0");
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
offset += bytes.length;
if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
}
return ptr;
};
const argc = this.argv.length;
const argvPtrs = [];
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
argvPtrs.push(0);
const keys = Object.keys(this.env).sort();
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
argvPtrs.push(0);
const argv = offset;
argvPtrs.forEach((ptr) => {
this.mem.setUint32(offset, ptr, true);
this.mem.setUint32(offset + 4, 0, true);
offset += 8;
});
// The linker guarantees global data starts from at least wasmMinDataAddr.
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
const wasmMinDataAddr = 4096 + 8192;
if (offset >= wasmMinDataAddr) {
throw new Error("total length of command line and environment variables exceeds limit");
}
this._inst.exports.run(argc, argv);
if (this.exited) {
this._resolveExitPromise();
}
await this._exitPromise;
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
})();

View 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
)

View 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

View 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;

View 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
};

View 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
}

View File

@@ -0,0 +1,4 @@
import type { Plugin } from "prettier";
declare const plugin: Plugin;
export default plugin;

View File

@@ -0,0 +1,19 @@
export {
languages,
printers,
parsers,
options,
defaultOptions
} from './src/index.mjs';
import { languages, printers, parsers, options, defaultOptions } from './src/index.mjs';
const phpPlugin = {
languages,
printers,
parsers,
options,
defaultOptions
};
export default phpPlugin;

View File

@@ -0,0 +1,111 @@
import { printNumber, normalizeMagicMethodName } from "./util.mjs";
const ignoredProperties = new Set([
"loc",
"range",
"raw",
"comments",
"leadingComments",
"trailingComments",
"parenthesizedExpression",
"parent",
"prev",
"start",
"end",
"tokens",
"errors",
"extra",
]);
/**
* This function takes the existing ast node and a copy, by reference
* We use it for testing, so that we can compare pre-post versions of the AST,
* excluding things we don't care about (like node location, case that will be
* changed by the printer, etc.)
*/
function clean(node, newObj) {
if (node.kind === "string") {
// TODO if options are available in this method, replace with
// newObj.isDoubleQuote = !useSingleQuote(node, options);
delete newObj.isDoubleQuote;
}
if (["array", "list"].includes(node.kind)) {
// TODO if options are available in this method, assign instead of delete
delete newObj.shortForm;
}
if (node.kind === "inline") {
if (node.value.includes("___PSEUDO_INLINE_PLACEHOLDER___")) {
return null;
}
newObj.value = newObj.value.replace(/\n/g, "");
}
// continue ((2)); -> continue 2;
// continue 1; -> continue;
if ((node.kind === "continue" || node.kind === "break") && node.level) {
const { level } = newObj;
if (level.kind === "number") {
newObj.level = level.value === "1" ? null : level;
}
}
// if () {{ }} -> if () {}
if (node.kind === "block") {
if (node.children.length === 1 && node.children[0].kind === "block") {
while (newObj.children[0].kind === "block") {
newObj.children = newObj.children[0].children;
}
}
}
// Normalize numbers
if (node.kind === "number") {
newObj.value = printNumber(node.value);
}
const statements = ["foreach", "for", "if", "while", "do"];
if (statements.includes(node.kind)) {
if (node.body && node.body.kind !== "block") {
newObj.body = {
kind: "block",
children: [newObj.body],
};
} else {
newObj.body = newObj.body ? newObj.body : null;
}
if (node.alternate && node.alternate.kind !== "block") {
newObj.alternate = {
kind: "block",
children: [newObj.alternate],
};
} else {
newObj.alternate = newObj.alternate ? newObj.alternate : null;
}
}
if (node.kind === "usegroup" && typeof node.name === "string") {
newObj.name = newObj.name.replace(/^\\/, "");
}
if (node.kind === "useitem") {
newObj.name = newObj.name.replace(/^\\/, "");
}
if (node.kind === "method" && node.name.kind === "identifier") {
newObj.name.name = normalizeMagicMethodName(newObj.name.name);
}
if (node.kind === "noop") {
return null;
}
}
clean.ignoredProperties = ignoredProperties;
export default clean;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
import { doc } from "prettier";
import LINGUIST_LANGUAGES_PHP from "linguist-languages/data/PHP";
import LINGUIST_LANGUAGES_HTML_PHP from "linguist-languages/data/HTML_2b_PHP";
import parse from "./parser.mjs";
import print from "./printer.mjs";
import clean from "./clean.mjs";
import options from "./options.mjs";
import {
handleOwnLineComment,
handleEndOfLineComment,
handleRemainingComment,
getCommentChildNodes,
canAttachComment,
isBlockComment,
} from "./comments.mjs";
import { hasPragma, insertPragma } from "./pragma.mjs";
import { locStart, locEnd } from "./loc.mjs";
const { join, hardline } = doc.builders;
function createLanguage(linguistData, { extend, override }) {
const language = {};
for (const key in linguistData) {
const newKey = key === "languageId" ? "linguistLanguageId" : key;
language[newKey] = linguistData[key];
}
if (extend) {
for (const key in extend) {
language[key] = (language[key] || []).concat(extend[key]);
}
}
for (const key in override) {
language[key] = override[key];
}
return language;
}
const languages = [
createLanguage(LINGUIST_LANGUAGES_PHP, {
override: {
parsers: ["php"],
vscodeLanguageIds: ["php"],
},
}),
createLanguage(LINGUIST_LANGUAGES_HTML_PHP, {
override: {
parsers: ["php"],
vscodeLanguageIds: ["php"],
},
}),
];
const parsers = {
php: {
parse,
astFormat: "php",
locStart,
locEnd,
hasPragma,
},
};
const ignoredKeys = new Set([
"kind",
"loc",
"errors",
"extra",
"comments",
"leadingComments",
"enclosingNode",
"precedingNode",
"followingNode",
]);
function getVisitorKeys(node, nonTraversableKeys) {
return Object.keys(node).filter(
(key) => !nonTraversableKeys.has(key) && !ignoredKeys.has(key)
);
}
const printers = {
php: {
print,
getVisitorKeys,
insertPragma,
massageAstNode: clean,
getCommentChildNodes,
canAttachComment,
isBlockComment,
handleComments: {
ownLine: handleOwnLineComment,
endOfLine: handleEndOfLineComment,
remaining: handleRemainingComment,
},
willPrintOwnComments(path) {
const { node } = path;
return node && node.kind === "noop";
},
printComment(path) {
const comment = path.node;
switch (comment.kind) {
case "commentblock": {
// for now, don't touch single line block comments
if (!comment.value.includes("\n")) {
return comment.value;
}
const lines = comment.value.split("\n");
// if this is a block comment, handle indentation
if (
lines
.slice(1, lines.length - 1)
.every((line) => line.trim()[0] === "*")
) {
return join(
hardline,
lines.map(
(line, index) =>
(index > 0 ? " " : "") +
(index < lines.length - 1 ? line.trim() : line.trimLeft())
)
);
}
// otherwise we can't be sure about indentation, so just print as is
return comment.value;
}
case "commentline": {
return comment.value.trimRight();
}
/* c8 ignore next 2 */
default:
throw new Error(`Not a comment: ${JSON.stringify(comment)}`);
}
},
hasPrettierIgnore(path) {
const isSimpleIgnore = (comment) =>
comment.value.includes("prettier-ignore") &&
!comment.value.includes("prettier-ignore-start") &&
!comment.value.includes("prettier-ignore-end");
const { node, parent: parentNode } = path;
return (
(node &&
node.kind !== "classconstant" &&
node.comments &&
node.comments.length > 0 &&
node.comments.some(isSimpleIgnore)) ||
// For proper formatting, the classconstant ignore formatting should
// run on the "constant" child
(node &&
node.kind === "constant" &&
parentNode &&
parentNode.kind === "classconstant" &&
parentNode.comments &&
parentNode.comments.length > 0 &&
parentNode.comments.some(isSimpleIgnore))
);
},
},
};
const defaultOptions = {
tabWidth: 4,
};
export { languages, printers, parsers, options, defaultOptions };

View File

@@ -0,0 +1,4 @@
const loc = (prop) => (node) => node.loc?.[prop]?.offset;
export const locStart = loc("start");
export const locEnd = loc("end");

View File

@@ -0,0 +1,250 @@
import { getPrecedence, shouldFlatten, isBitwiseOperator } from "./util.mjs";
function needsParens(path, options) {
const { parent } = path;
if (!parent) {
return false;
}
const { key, node } = path;
if (
[
// No need parens for top level children of this nodes
"program",
"expressionstatement",
"namespace",
"declare",
"block",
// No need parens
"include",
"print",
"return",
"echo",
].includes(parent.kind)
) {
return false;
}
switch (node.kind) {
case "pre":
case "post":
if (parent.kind === "unary") {
return (
node.kind === "pre" &&
((node.type === "+" && parent.type === "+") ||
(node.type === "-" && parent.type === "-"))
);
}
// else fallthrough
case "unary":
switch (parent.kind) {
case "unary":
return (
node.type === parent.type &&
(node.type === "+" || node.type === "-")
);
case "propertylookup":
case "nullsafepropertylookup":
case "staticlookup":
case "offsetlookup":
case "call":
return key === "what";
case "bin":
return parent.type === "**" && key === "left";
default:
return false;
}
case "bin": {
switch (parent.kind) {
case "assign":
case "retif":
return ["and", "xor", "or"].includes(node.type);
case "silent":
case "cast":
// TODO: bug https://github.com/glayzzle/php-parser/issues/172
return node.parenthesizedExpression;
case "pre":
case "post":
case "unary":
return true;
case "call":
case "propertylookup":
case "nullsafepropertylookup":
case "staticlookup":
case "offsetlookup":
return key === "what";
case "bin": {
const po = parent.type;
const pp = getPrecedence(po);
const no = node.type;
const np = getPrecedence(no);
if (pp > np) {
return true;
}
if (po === "||" && no === "&&") {
return true;
}
if (pp === np && key === "right") {
return true;
}
if (pp === np && !shouldFlatten(po, no)) {
return true;
}
if (pp < np && no === "%") {
return po === "+" || po === "-";
}
// Add parenthesis when working with bitwise operators
// It's not stricly needed but helps with code understanding
if (isBitwiseOperator(po)) {
return true;
}
return false;
}
default:
return false;
}
}
case "propertylookup":
case "nullsafepropertylookup":
case "staticlookup": {
switch (parent.kind) {
case "call":
return key === "what" && node.parenthesizedExpression;
default:
return false;
}
}
case "clone":
case "new": {
const requiresParens =
node.kind === "clone" ||
(node.kind === "new" && options.phpVersion < 8.4);
switch (parent.kind) {
case "propertylookup":
case "nullsafepropertylookup":
case "staticlookup":
case "offsetlookup":
case "call":
return key === "what" && requiresParens;
default:
return false;
}
}
case "yield": {
switch (parent.kind) {
case "propertylookup":
case "nullsafepropertylookup":
case "staticlookup":
case "offsetlookup":
case "call":
return key === "what";
case "retif":
return key === "test";
default:
return !!(node.key || node.value);
}
}
case "assign": {
if (
parent.kind === "for" &&
(parent.init.includes(node) || parent.increment.includes(node))
) {
return false;
} else if (parent.kind === "assign") {
return false;
} else if (parent.kind === "static") {
return false;
} else if (
["if", "do", "while", "foreach", "switch"].includes(parent.kind)
) {
return false;
} else if (parent.kind === "silent") {
return false;
} else if (parent.kind === "call") {
return false;
}
return true;
}
case "retif":
switch (parent.kind) {
case "cast":
return true;
case "unary":
case "bin":
case "retif":
if (key === "test" && !parent.trueExpr) {
return false;
}
return true;
case "propertylookup":
case "nullsafepropertylookup":
case "staticlookup":
case "offsetlookup":
case "call":
return key === "what";
default:
return false;
}
case "closure":
switch (parent.kind) {
case "call":
return key === "what";
// https://github.com/prettier/plugin-php/issues/1675
case "propertylookup":
case "nullsafepropertylookup":
return true;
default:
return false;
}
case "silence":
case "cast":
// TODO: bug https://github.com/glayzzle/php-parser/issues/172
return node.parenthesizedExpression;
// else fallthrough
case "string":
case "array":
switch (parent.kind) {
case "propertylookup":
case "nullsafepropertylookup":
case "staticlookup":
case "offsetlookup":
case "call":
if (
["string", "array"].includes(node.kind) &&
parent.kind === "offsetlookup"
) {
return false;
}
return key === "what";
default:
return false;
}
case "print":
case "include":
return parent.kind === "bin";
}
return false;
}
export default needsParens;

View File

@@ -0,0 +1,69 @@
const CATEGORY_PHP = "PHP";
// prettier-ignore
const SUPPORTED_PHP_VERSIONS = [
5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6,
7.0, 7.1, 7.2, 7.3, 7.4,
8.0, 8.1, 8.2, 8.3, 8.4,
];
export const LATEST_SUPPORTED_PHP_VERSION = Math.max(...SUPPORTED_PHP_VERSIONS);
/**
* Resolve the PHP version to a number based on the provided options.
*/
export function resolvePhpVersion(options) {
if (!options) {
return;
}
if (options.phpVersion === "auto" || options.phpVersion === "composer") {
options.phpVersion = LATEST_SUPPORTED_PHP_VERSION;
} else {
options.phpVersion = parseFloat(options.phpVersion);
}
}
export default {
phpVersion: {
since: "0.13.0",
category: CATEGORY_PHP,
type: "choice",
default: "auto",
description: "Minimum target PHP version.",
choices: [
...SUPPORTED_PHP_VERSIONS.map((v) => ({ value: v.toFixed(1) })),
{
value: "auto",
description: `Use latest PHP Version (${LATEST_SUPPORTED_PHP_VERSION})`,
},
],
},
trailingCommaPHP: {
since: "0.0.0",
category: CATEGORY_PHP,
type: "boolean",
default: true,
description: "Print trailing commas wherever possible when multi-line.",
},
braceStyle: {
since: "0.10.0",
category: CATEGORY_PHP,
type: "choice",
default: "per-cs",
description:
"Print one space or newline for code blocks (classes and functions).",
choices: [
{ value: "psr-2", description: "(deprecated) Use per-cs" },
{ value: "per-cs", description: "Use the PER Coding Style brace style." },
{ value: "1tbs", description: "Use 1tbs brace style." },
],
},
singleQuote: {
since: "0.0.0",
category: CATEGORY_PHP,
type: "boolean",
default: false,
description: "Use single quotes instead of double quotes.",
},
};

View File

@@ -0,0 +1,67 @@
import engine from "php-parser";
import { LATEST_SUPPORTED_PHP_VERSION } from "./options.mjs";
import { resolvePhpVersion } from "./options.mjs";
function parse(text, opts) {
const inMarkdown = opts && opts.parentParser === "markdown";
if (!text && inMarkdown) {
return "";
}
resolvePhpVersion(opts);
// Todo https://github.com/glayzzle/php-parser/issues/170
text = text.replace(/\?>\n<\?/g, "?>\n___PSEUDO_INLINE_PLACEHOLDER___<?");
// initialize a new parser instance
const parser = new engine({
parser: {
extractDoc: true,
version: `${LATEST_SUPPORTED_PHP_VERSION}`,
},
ast: {
withPositions: true,
withSource: true,
},
});
const hasOpenPHPTag = text.indexOf("<?php") !== -1;
const parseAsEval = inMarkdown && !hasOpenPHPTag;
let ast;
try {
ast = parseAsEval ? parser.parseEval(text) : parser.parseCode(text);
} catch (err) {
if (err instanceof SyntaxError && "lineNumber" in err) {
err.loc = {
start: {
line: err.lineNumber,
column: err.columnNumber,
},
};
delete err.lineNumber;
delete err.columnNumber;
}
throw err;
}
ast.extra = {
parseAsEval,
};
// https://github.com/glayzzle/php-parser/issues/155
// currently inline comments include the line break at the end, we need to
// strip those out and update the end location for each comment manually
ast.comments.forEach((comment) => {
if (comment.value[comment.value.length - 1] === "\n") {
comment.value = comment.value.slice(0, -1);
comment.loc.end.offset = comment.loc.end.offset - 1;
}
});
return ast;
}
export default parse;

View File

@@ -0,0 +1,92 @@
import { memoize } from "./util.mjs";
import parse from "./parser.mjs";
const reHasPragma = /@prettier|@format/;
const getPageLevelDocBlock = memoize((text) => {
const parsed = parse(text);
const [firstChild] = parsed.children;
const [firstDocBlock] = parsed.comments.filter(
(el) => el.kind === "commentblock"
);
if (
firstChild &&
firstDocBlock &&
firstDocBlock.loc.start.line < firstChild.loc.start.line
) {
return firstDocBlock;
}
});
function hasPragma(text) {
// fast path optimization - check if the pragma shows up in the file at all
if (!reHasPragma.test(text)) {
return false;
}
const pageLevelDocBlock = getPageLevelDocBlock(text);
if (pageLevelDocBlock) {
const { value } = pageLevelDocBlock;
return reHasPragma.test(value);
}
return false;
}
function injectPragma(docblock) {
let lines = docblock.split("\n");
if (lines.length === 1) {
// normalize to multiline for simplicity
const [, line] = /\/*\*\*(.*)\*\//.exec(lines[0]);
lines = ["/**", ` * ${line.trim()}`, " */"];
}
// find the first @pragma
// if there happens to be one on the opening line, just put it on the next line.
const pragmaIndex = lines.findIndex((line) => /@\S/.test(line)) || 1;
// not found => index == -1, which conveniently will splice 1 from the end.
lines.splice(pragmaIndex, 0, " * @format");
return lines.join("\n");
}
function insertPragma(text) {
const pageLevelDocBlock = getPageLevelDocBlock(text);
if (pageLevelDocBlock) {
const {
start: { offset: startOffset },
end: { offset: endOffset },
} = pageLevelDocBlock.loc;
const before = text.substring(0, startOffset);
const after = text.substring(endOffset);
return `${before}${injectPragma(pageLevelDocBlock.value, text)}${after}`;
}
const openTag = "<?php";
if (!text.startsWith(openTag)) {
// bail out
return text;
}
const splitAt = openTag.length;
const phpTag = text.substring(0, splitAt);
const after = text.substring(splitAt);
return `${phpTag}
/**
* @format
*/
${after}`;
}
export { hasPragma, insertPragma };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,743 @@
import { util as prettierUtil } from "prettier";
import { locStart } from "./loc.mjs";
const { hasNewline, skipEverythingButNewLine, skipNewline } = prettierUtil;
function printNumber(rawNumber) {
return (
rawNumber
.toLowerCase()
// Remove unnecessary plus and zeroes from scientific notation.
.replace(/^([+-]?[\d.]+e)(?:\+|(-))?0*(\d)/, "$1$2$3")
// Remove unnecessary scientific notation (1e0).
.replace(/^([+-]?[\d.]+)e[+-]?0+$/, "$1")
// Make sure numbers always start with a digit.
.replace(/^([+-])?\./, "$10.")
// Remove extraneous trailing decimal zeroes.
.replace(/(\.\d+?)0+(?=e|$)/, "$1")
// Remove unnecessary .e notation
.replace(/\.(?=e)/, "")
);
}
// http://php.net/manual/en/language.operators.precedence.php
const PRECEDENCE = new Map(
[
["or"],
["xor"],
["and"],
[
"=",
"+=",
"-=",
"*=",
"**=",
"/=",
".=",
"%=",
"&=",
"|=",
"^=",
"<<=",
">>=",
],
["??"],
["||"],
["&&"],
["|"],
["^"],
["&"],
["==", "===", "!=", "!==", "<>", "<=>"],
["<", ">", "<=", ">="],
[">>", "<<"],
["+", "-", "."],
["*", "/", "%"],
["!"],
["instanceof"],
["++", "--", "~"],
["**"],
].flatMap((operators, index) =>
operators.map((operator) => [operator, index])
)
);
function getPrecedence(operator) {
return PRECEDENCE.get(operator);
}
const equalityOperators = ["==", "!=", "===", "!==", "<>", "<=>"];
const multiplicativeOperators = ["*", "/", "%"];
const bitshiftOperators = [">>", "<<"];
function isBitwiseOperator(operator) {
return (
!!bitshiftOperators[operator] ||
operator === "|" ||
operator === "^" ||
operator === "&"
);
}
function shouldFlatten(parentOp, nodeOp) {
if (getPrecedence(nodeOp) !== getPrecedence(parentOp)) {
return false;
}
// ** is right-associative
// x ** y ** z --> x ** (y ** z)
if (parentOp === "**") {
return false;
}
// x == y == z --> (x == y) == z
if (
equalityOperators.includes(parentOp) &&
equalityOperators.includes(nodeOp)
) {
return false;
}
// x * y % z --> (x * y) % z
if (
(nodeOp === "%" && multiplicativeOperators.includes(parentOp)) ||
(parentOp === "%" && multiplicativeOperators.includes(nodeOp))
) {
return false;
}
// x * y / z --> (x * y) / z
// x / y * z --> (x / y) * z
if (
nodeOp !== parentOp &&
multiplicativeOperators.includes(nodeOp) &&
multiplicativeOperators.includes(parentOp)
) {
return false;
}
// x << y << z --> (x << y) << z
if (
bitshiftOperators.includes(parentOp) &&
bitshiftOperators.includes(nodeOp)
) {
return false;
}
return true;
}
function nodeHasStatement(node) {
return [
"block",
"program",
"namespace",
"class",
"enum",
"interface",
"trait",
"traituse",
"declare",
].includes(node.kind);
}
function getBodyFirstChild({ body }) {
if (!body) {
return null;
}
if (body.kind === "block") {
body = body.children;
}
return body[0];
}
function getNodeListProperty(node) {
const body = node.children || node.body || node.adaptations;
return Array.isArray(body) ? body : null;
}
function getLast(arr) {
if (arr.length > 0) {
return arr[arr.length - 1];
}
return null;
}
function getPenultimate(arr) {
if (arr.length > 1) {
return arr[arr.length - 2];
}
return null;
}
function isFirstChildrenInlineNode(path) {
const { node } = path;
if (node.kind === "program") {
const children = getNodeListProperty(node);
if (!children || children.length === 0) {
return false;
}
return children[0].kind === "inline";
}
if (node.kind === "switch") {
if (!node.body) {
return false;
}
const children = getNodeListProperty(node.body);
if (children.length === 0) {
return false;
}
const [firstCase] = children;
if (!firstCase.body) {
return false;
}
const firstCaseChildren = getNodeListProperty(firstCase.body);
if (firstCaseChildren.length === 0) {
return false;
}
return firstCaseChildren[0].kind === "inline";
}
const firstChild = getBodyFirstChild(node);
if (!firstChild) {
return false;
}
return firstChild.kind === "inline";
}
function isDocNode(node) {
return (
node.kind === "nowdoc" ||
(node.kind === "encapsed" && node.type === "heredoc")
);
}
/**
* Heredoc/Nowdoc nodes need a trailing linebreak if they
* appear as function arguments or array elements
*/
function docShouldHaveTrailingNewline(path, recurse = 0) {
const node = path.getNode(recurse);
const parent = path.getNode(recurse + 1);
const parentParent = path.getNode(recurse + 2);
if (!parent) {
return false;
}
if (
(parentParent &&
["call", "new", "echo"].includes(parentParent.kind) &&
!["call", "array"].includes(parent.kind)) ||
parent.kind === "parameter"
) {
const lastIndex = parentParent.arguments.length - 1;
const index = parentParent.arguments.indexOf(parent);
return index !== lastIndex;
}
if (parentParent && parentParent.kind === "for") {
const initIndex = parentParent.init.indexOf(parent);
if (initIndex !== -1) {
return initIndex !== parentParent.init.length - 1;
}
const testIndex = parentParent.test.indexOf(parent);
if (testIndex !== -1) {
return testIndex !== parentParent.test.length - 1;
}
const incrementIndex = parentParent.increment.indexOf(parent);
if (incrementIndex !== -1) {
return incrementIndex !== parentParent.increment.length - 1;
}
}
if (parent.kind === "bin") {
return (
parent.left === node || docShouldHaveTrailingNewline(path, recurse + 1)
);
}
if (parent.kind === "case" && parent.test === node) {
return true;
}
if (parent.kind === "staticvariable") {
const lastIndex = parentParent.variables.length - 1;
const index = parentParent.variables.indexOf(parent);
return index !== lastIndex;
}
if (parent.kind === "entry") {
if (parent.key === node) {
return true;
}
const lastIndex = parentParent.items.length - 1;
const index = parentParent.items.indexOf(parent);
return index !== lastIndex;
}
if (["call", "new"].includes(parent.kind)) {
const lastIndex = parent.arguments.length - 1;
const index = parent.arguments.indexOf(node);
return index !== lastIndex;
}
if (parent.kind === "echo") {
const lastIndex = parent.expressions.length - 1;
const index = parent.expressions.indexOf(node);
return index !== lastIndex;
}
if (parent.kind === "array") {
const lastIndex = parent.items.length - 1;
const index = parent.items.indexOf(node);
return index !== lastIndex;
}
if (parent.kind === "retif") {
return docShouldHaveTrailingNewline(path, recurse + 1);
}
return false;
}
function lineShouldEndWithSemicolon(path) {
const { node, parent: parentNode } = path;
if (!parentNode) {
return false;
}
// for single line control structures written in a shortform (ie without a block),
// we need to make sure the single body node gets a semicolon
if (
["for", "foreach", "while", "do", "if", "switch"].includes(
parentNode.kind
) &&
node.kind !== "block" &&
node.kind !== "if" &&
(parentNode.body === node || parentNode.alternate === node)
) {
return true;
}
if (!nodeHasStatement(parentNode)) {
return false;
}
if (node.kind === "echo" && node.shortForm) {
return false;
}
if (node.kind === "traituse") {
return !node.adaptations;
}
if (node.kind === "method" && node.isAbstract) {
return true;
}
if (node.kind === "method") {
const { parent } = path;
if (parent && parent.kind === "interface") {
return true;
}
}
return [
"expressionstatement",
"do",
"usegroup",
"classconstant",
"propertystatement",
"traitprecedence",
"traitalias",
"goto",
"constantstatement",
"enumcase",
"global",
"static",
"echo",
"unset",
"return",
"break",
"continue",
"throw",
].includes(node.kind);
}
function fileShouldEndWithHardline(path) {
const { node } = path;
const isProgramNode = node.kind === "program";
const lastNode = node.children && getLast(node.children);
if (!isProgramNode) {
return false;
}
if (lastNode && ["halt", "inline"].includes(lastNode.kind)) {
return false;
}
if (
lastNode &&
(lastNode.kind === "declare" || lastNode.kind === "namespace")
) {
const lastNestedNode =
lastNode.children.length > 0 && getLast(lastNode.children);
if (lastNestedNode && ["halt", "inline"].includes(lastNestedNode.kind)) {
return false;
}
}
return true;
}
function maybeStripLeadingSlashFromUse(name) {
const nameWithoutLeadingSlash = name.replace(/^\\/, "");
if (nameWithoutLeadingSlash.indexOf("\\") !== -1) {
return nameWithoutLeadingSlash;
}
return name;
}
function hasDanglingComments(node) {
return (
node.comments &&
node.comments.some((comment) => !comment.leading && !comment.trailing)
);
}
function isLookupNode(node) {
return (
node.kind === "propertylookup" ||
node.kind === "nullsafepropertylookup" ||
node.kind === "staticlookup" ||
node.kind === "offsetlookup"
);
}
function shouldPrintHardLineAfterStartInControlStructure(path) {
const { node } = path;
if (["try", "catch"].includes(node.kind)) {
return false;
}
return isFirstChildrenInlineNode(path);
}
function shouldPrintHardLineBeforeEndInControlStructure(path) {
const { node } = path;
if (["try", "catch"].includes(node.kind)) {
return true;
}
if (node.kind === "switch") {
const children = getNodeListProperty(node.body);
if (children.length === 0) {
return true;
}
const lastCase = getLast(children);
if (!lastCase.body) {
return true;
}
const childrenInCase = getNodeListProperty(lastCase.body);
if (childrenInCase.length === 0) {
return true;
}
return childrenInCase[0].kind !== "inline";
}
return !isFirstChildrenInlineNode(path);
}
function getAlignment(text) {
const lines = text.split("\n");
const lastLine = lines.pop();
return lastLine.length - lastLine.trimLeft().length + 1;
}
function isProgramLikeNode(node) {
return ["program", "declare", "namespace"].includes(node.kind);
}
function isReferenceLikeNode(node) {
return [
"name",
"parentreference",
"selfreference",
"staticreference",
].includes(node.kind);
}
// Return `logical` value for `bin` node containing `||` or `&&` type otherwise return kind of node.
// Require for grouping logical and binary nodes in right way.
function getNodeKindIncludingLogical(node) {
if (node.kind === "bin" && ["||", "&&"].includes(node.type)) {
return "logical";
}
return node.kind;
}
/**
* Check if string can safely be converted from double to single quotes and vice-versa, i.e.
*
* - no embedded variables ("foo $bar")
* - no linebreaks
* - no special characters like \n, \t, ...
* - no octal/hex/unicode characters
*
* See https://php.net/manual/en/language.types.string.php#language.types.string.syntax.double
*/
function useDoubleQuote(node, options) {
if (node.isDoubleQuote === options.singleQuote) {
// We have a double quote and the user passed singleQuote:true, or the other way around.
const rawValue = node.raw.slice(node.raw[0] === "b" ? 2 : 1, -1);
const isComplex = rawValue.match(
/\\([$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}|u{([0-9a-fA-F]+)})|\r?\n|'|"|\$/
);
return node.isDoubleQuote ? isComplex : !isComplex;
}
return node.isDoubleQuote;
}
function hasEmptyBody(path, name = "body") {
const { node } = path;
return (
node[name] &&
node[name].children &&
node[name].children.length === 0 &&
(!node[name].comments || node[name].comments.length === 0)
);
}
function isNextLineEmptyAfterNamespace(text, node) {
let idx = locStart(node);
idx = skipEverythingButNewLine(text, idx);
idx = skipNewline(text, idx);
return hasNewline(text, idx);
}
function shouldPrintHardlineBeforeTrailingComma(lastElem) {
if (
lastElem.kind === "nowdoc" ||
(lastElem.kind === "encapsed" && lastElem.type === "heredoc")
) {
return true;
}
if (
lastElem.kind === "entry" &&
(lastElem.value.kind === "nowdoc" ||
(lastElem.value.kind === "encapsed" && lastElem.value.type === "heredoc"))
) {
return true;
}
return false;
}
function getAncestorCounter(path, typeOrTypes) {
const types = [].concat(typeOrTypes);
let counter = -1;
let ancestorNode;
while ((ancestorNode = path.getParentNode(++counter))) {
if (types.indexOf(ancestorNode.kind) !== -1) {
return counter;
}
}
return -1;
}
function getAncestorNode(path, typeOrTypes) {
const counter = getAncestorCounter(path, typeOrTypes);
return counter === -1 ? null : path.getParentNode(counter);
}
const magicMethods = [
"__construct",
"__destruct",
"__call",
"__callStatic",
"__get",
"__set",
"__isset",
"__unset",
"__sleep",
"__wakeup",
"__toString",
"__invoke",
"__set_state",
"__clone",
"__debugInfo",
];
const magicMethodsMap = new Map(
magicMethods.map((name) => [name.toLowerCase(), name])
);
function normalizeMagicMethodName(name) {
const loweredName = name.toLowerCase();
if (magicMethodsMap.has(loweredName)) {
return magicMethodsMap.get(loweredName);
}
return name;
}
/**
* @param {string[]} kindsArray
* @returns {(node: Node | Comment) => Boolean}
*/
function createTypeCheckFunction(kindsArray) {
const kinds = new Set(kindsArray);
return (node) => kinds.has(node?.kind);
}
const isSingleWordType = createTypeCheckFunction([
"variadicplaceholder",
"namedargument",
"nullkeyword",
"identifier",
"parameter",
"variable",
"variadic",
"boolean",
"literal",
"number",
"string",
"clone",
"cast",
]);
const isArrayExpression = createTypeCheckFunction(["array"]);
const isCallLikeExpression = createTypeCheckFunction([
"nullsafepropertylookup",
"propertylookup",
"staticlookup",
"offsetlookup",
"call",
"new",
]);
const isArrowFuncExpression = createTypeCheckFunction(["arrowfunc"]);
function getChainParts(node, prev = []) {
const parts = prev;
if (isCallLikeExpression(node)) {
parts.push(node);
}
if (!node.what) {
return parts;
}
return getChainParts(node.what, parts);
}
function isSimpleCallArgument(node, depth = 2) {
if (depth <= 0) {
return false;
}
const isChildSimple = (child) => isSimpleCallArgument(child, depth - 1);
if (isSingleWordType(node)) {
return true;
}
if (isArrayExpression(node)) {
return node.items.every((x) => x === null || isChildSimple(x));
}
if (isCallLikeExpression(node)) {
const parts = getChainParts(node);
parts.unshift();
return (
parts.length <= depth &&
parts.every((node) =>
isLookupNode(node)
? isChildSimple(node.offset)
: node.arguments.every(isChildSimple)
)
);
}
if (isArrowFuncExpression(node)) {
return (
node.arguments.length <= depth && node.arguments.every(isChildSimple)
);
}
return false;
}
function memoize(fn) {
const cache = new Map();
return (key) => {
if (!cache.has(key)) {
cache.set(key, fn(key));
}
return cache.get(key);
};
}
export {
printNumber,
getPrecedence,
isBitwiseOperator,
shouldFlatten,
nodeHasStatement,
getLast,
getPenultimate,
getBodyFirstChild,
lineShouldEndWithSemicolon,
fileShouldEndWithHardline,
maybeStripLeadingSlashFromUse,
hasDanglingComments,
docShouldHaveTrailingNewline,
isLookupNode,
isFirstChildrenInlineNode,
shouldPrintHardLineAfterStartInControlStructure,
shouldPrintHardLineBeforeEndInControlStructure,
getAlignment,
isProgramLikeNode,
isReferenceLikeNode,
getNodeKindIncludingLogical,
useDoubleQuote,
hasEmptyBody,
isNextLineEmptyAfterNamespace,
shouldPrintHardlineBeforeTrailingComma,
isDocNode,
getAncestorNode,
normalizeMagicMethodName,
isSimpleCallArgument,
memoize,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
// SQL语言定义
export const languages = [
{
name: "SQL",
parsers: ["sql"],
extensions: [
".sql",
".ddl",
".dml",
".hql",
".psql",
".plsql",
".mysql",
".mssql",
".pgsql",
".sqlite",
".bigquery",
".snowflake",
".redshift",
".db2",
".n1ql",
".cql"
],
filenames: [
"*.sql",
"*.ddl",
"*.dml"
]
}
];

View File

@@ -0,0 +1,40 @@
import { AST, Option } from 'node-sql-parser';
import { Options, ParserOptions, Plugin } from 'prettier';
import {
FormatOptions,
FormatOptionsWithLanguage,
} from 'sql-formatter';
export type SqlBaseOptions = Option &
Partial<
| (FormatOptions & { dialect: string })
| (FormatOptionsWithLanguage & { dialect?: never })
> & {
formatter?: 'sql-formatter' | 'node-sql-parser' | 'sql-cst';
params?: string;
paramTypes?: string;
autoDetectDialect?: boolean;
};
export type SqlOptions = ParserOptions<AST> & SqlBaseOptions;
export type SqlFormatOptions = Options & SqlBaseOptions;
export declare const languages: Plugin["languages"];
export declare const parsers: {
sql: {
parse(text: string, options?: SqlOptions): AST | string;
astFormat: 'sql';
locStart(): number;
locEnd(): number;
};
};
export declare const printers: Plugin["printers"];
export declare const options: Plugin["options"];
export declare function detectDialect(sql: string): string;
declare const SqlPlugin: Plugin<AST | string>;
export default SqlPlugin;

View File

@@ -0,0 +1,459 @@
import { JSOX } from 'jsox';
import nodeSqlParser from 'node-sql-parser';
import {
format,
formatDialect,
} from 'sql-formatter';
import { detectDialect } from './detect.mjs';
import { languages } from './languages.js';
const parser = new nodeSqlParser.Parser();
const SQL_FORMATTER = 'sql-formatter';
const NODE_SQL_PARSER = 'node-sql-parser';
// Parsers
export const parsers = {
sql: {
parse(text, options = {}) {
const { formatter, type, database } = options;
return formatter === SQL_FORMATTER
? text
: parser.astify(text, { type, database });
},
astFormat: 'sql',
locStart: () => -1,
locEnd: () => -1,
},
};
// Printers
export const printers = {
sql: {
print(path, options = {}) {
const value = path.node;
const {
formatter = SQL_FORMATTER,
type,
database,
dialect,
language,
params,
paramTypes,
autoDetectDialect = true,
...formatOptions
} = options;
let formatted;
if (typeof value === 'string') {
// 准备sql-formatter选项
const sqlFormatterOptions = {
...formatOptions,
params:
params == null
? undefined
: JSOX.parse(params),
paramTypes:
paramTypes == null
? undefined
: JSOX.parse(paramTypes),
};
let finalLanguage = language;
let finalDialect = dialect;
if (autoDetectDialect && !language && !dialect) {
const detectedDialect = detectDialect(value);
finalLanguage = detectedDialect;
}
// 使用适当的格式化方法
if (finalDialect != null) {
// 使用formatDialect方法
formatted = formatDialect(value, {
...sqlFormatterOptions,
dialect: JSOX.parse(finalDialect),
});
} else {
// 使用format方法
formatted = format(value, {
...sqlFormatterOptions,
language: finalLanguage,
});
}
} else {
// 使用node-sql-parser进行格式化
formatted = parser.sqlify(value, { type, database });
}
return formatted + '\n';
},
},
};
// 插件选项
export const options = {
formatter: {
category: 'Config',
type: 'choice',
default: SQL_FORMATTER,
description: 'Choose which formatter to be used',
choices: [
{
value: SQL_FORMATTER,
description: 'use `sql-formatter` as formatter',
},
{
value: NODE_SQL_PARSER,
description: 'use `node-sql-parser` as formatter',
},
],
},
autoDetectDialect: {
category: 'Config',
type: 'boolean',
default: true,
description: 'Automatically detect SQL dialect if language/dialect is not specified',
},
dialect: {
category: 'Config',
type: 'string',
description: 'SQL dialect for `sql-formatter` formatDialect()',
},
language: {
category: 'Config',
type: 'choice',
default: 'sql',
description: 'SQL dialect for `sql-formatter` format()',
choices: [
{
value: 'sql',
description: 'Standard SQL: https://en.wikipedia.org/wiki/SQL:2011',
},
{
value: 'bigquery',
description:
'Google Standard SQL (Bigquery): https://cloud.google.com/bigquery',
},
{
value: 'db2',
description: 'IBM DB2: https://www.ibm.com/products/db2',
},
{
value: 'db2i',
description:
'IBM DB2i (experimental): https://www.ibm.com/docs/en/i/7.5?topic=overview-db2-i',
},
{
value: 'hive',
description: 'Apache Hive: https://hive.apache.org',
},
{
value: 'mariadb',
description: 'MariaDB: https://mariadb.com',
},
{
value: 'mysql',
description: 'MySQL: https://www.mysql.com',
},
{
value: 'n1ql',
description:
'Couchbase N1QL: https://www.couchbase.com/products/n1ql',
},
{
value: 'plsql',
description:
'Oracle PL/SQL: https://www.oracle.com/database/technologies/appdev/plsql.html',
},
{
value: 'postgresql',
description: 'PostgreSQL: https://www.postgresql.org',
},
{
value: 'redshift',
description:
'Amazon Redshift: https://docs.aws.amazon.com/redshift/latest/dg/cm_chap_SQLCommandRef.html',
},
{
value: 'singlestoredb',
description:
'SingleStoreDB: https://docs.singlestore.com/db/v7.8/en/introduction/singlestore-documentation.html',
},
{
value: 'snowflake',
description: 'Snowflake: https://docs.snowflake.com',
},
{
value: 'spark',
description: 'Spark: https://spark.apache.org',
},
{
value: 'sqlite',
description: 'SQLite: https://www.sqlite.org',
},
{
value: 'transactsql',
description:
'SQL Server Transact-SQL: https://docs.microsoft.com/en-us/sql/sql-server/',
},
{
value: 'tsql',
description:
'SQL Server Transact-SQL: https://docs.microsoft.com/en-us/sql/sql-server/',
},
{
value: 'trino',
description: 'Trino: https://trino.io',
},
],
},
keywordCase: {
category: 'Output',
type: 'choice',
default: 'preserve',
description:
'Converts reserved keywords to upper- or lowercase for `sql-formatter`',
choices: [
{
value: 'preserve',
description: 'preserves the original case of reserved keywords',
},
{
value: 'upper',
description: 'converts reserved keywords to uppercase',
},
{
value: 'lower',
description: 'converts reserved keywords to lowercase',
},
],
},
dataTypeCase: {
category: 'Output',
type: 'choice',
default: 'preserve',
description:
'Converts data types to upper- or lowercase for `sql-formatter`',
choices: [
{
value: 'preserve',
description: 'preserves the original case of data types',
},
{
value: 'upper',
description: 'converts data types to uppercase',
},
{
value: 'lower',
description: 'converts data types to lowercase',
},
],
},
functionCase: {
category: 'Output',
type: 'choice',
default: 'preserve',
description:
'Converts functions to upper- or lowercase for `sql-formatter`',
choices: [
{
value: 'preserve',
description: 'preserves the original case of functions',
},
{
value: 'upper',
description: 'converts functions to uppercase',
},
{
value: 'lower',
description: 'converts functions to lowercase',
},
],
},
identifierCase: {
category: 'Output',
type: 'choice',
default: 'preserve',
description:
'Converts identifiers to upper- or lowercase for `sql-formatter`. Only unquoted identifiers are converted. (experimental)',
choices: [
{
value: 'preserve',
description: 'preserves the original case of identifiers',
},
{
value: 'upper',
description: 'converts identifiers to uppercase',
},
{
value: 'lower',
description: 'converts identifiers to lowercase',
},
],
},
uppercase: {
category: 'Output',
type: 'boolean',
deprecated: '0.7.0',
description: 'Use `keywordCase` option instead',
},
indentStyle: {
category: 'Format',
type: 'choice',
default: 'standard',
description: `Switches between different indentation styles for \`sql-formatter\`.
Caveats of using \`"tabularLeft"\` and \`"tabularRight"\`:
- \`tabWidth\` option is ignored. Indentation will always be 10 spaces, regardless of what is specified by \`tabWidth\``,
choices: [
{
value: 'standard',
description:
'indents code by the amount specified by `tabWidth` option',
},
{
value: 'tabularLeft',
description:
'indents in tabular style with 10 spaces, aligning keywords to left',
},
{
value: 'tabularRight',
description:
'indents in tabular style with 10 spaces, aligning keywords to right',
},
],
},
logicalOperatorNewline: {
category: 'Format',
type: 'choice',
default: 'before',
description:
'Decides newline placement before or after logical operators (AND, OR, XOR)',
choices: [
{
value: 'before',
description: 'adds newline before the operator',
},
{
value: 'after',
description: 'adds newline after the operator',
},
],
},
expressionWidth: {
category: 'Format',
type: 'int',
default: 50,
description:
'Determines maximum length of parenthesized expressions for `sql-formatter`',
},
linesBetweenQueries: {
category: 'Format',
type: 'int',
default: 1,
description:
'Decides how many empty lines to leave between SQL statements for `sql-formatter`',
},
denseOperators: {
category: 'Format',
type: 'boolean',
default: false,
description:
'Decides whitespace around operators for `sql-formatter`. Does not apply to logical operators (AND, OR, XOR).',
},
newlineBeforeSemicolon: {
category: 'Format',
type: 'boolean',
default: false,
description:
'Whether to place query separator (`;`) on a separate line for `sql-formatter`',
},
params: {
category: 'Format',
type: 'string',
description:
'Specifies `JSOX` **stringified** parameter values to fill in for placeholders inside SQL for `sql-formatter`. This option is designed to be used through API (though nothing really prevents usage from command line).',
},
paramTypes: {
category: 'Config',
type: 'string',
description:
'Specifies `JSOX` **stringified** parameter types to support when parsing SQL prepared statements for `sql-formatter`.',
},
type: {
category: 'Config',
type: 'choice',
default: 'table',
description: 'Check the SQL with Authority List for `node-sql-parser`',
choices: [
{
value: 'table',
description: '`table` mode',
},
{
value: 'column',
description: '`column` mode',
},
],
},
database: {
category: 'Config',
type: 'choice',
default: 'mysql',
description: 'SQL dialect for `node-sql-parser`',
choices: [
{
value: 'bigquery',
description: 'BigQuery: https://cloud.google.com/bigquery',
},
{
value: 'db2',
description: 'IBM DB2: https://www.ibm.com/analytics/db2',
},
{
value: 'hive',
description: 'Hive: https://hive.apache.org',
},
{
value: 'mariadb',
description: 'MariaDB: https://mariadb.com',
},
{
value: 'mysql',
description: 'MySQL: https://www.mysql.com',
},
{
value: 'postgresql',
description: 'PostgreSQL: https://www.postgresql.org',
},
{
value: 'transactsql',
description:
'TransactSQL: https://docs.microsoft.com/en-us/sql/t-sql',
},
{
value: 'flinksql',
description:
'FlinkSQL: https://ci.apache.org/projects/flink/flink-docs-stable',
},
{
value: 'snowflake',
description: 'Snowflake (alpha): https://docs.snowflake.com',
},
],
},
};
const SqlPlugin = {
languages,
parsers,
printers,
options,
};
export { languages };
export default SqlPlugin;

View File

@@ -2,35 +2,33 @@
* 语言映射和解析器配置
*/
import { jsonLanguage } from "@codemirror/lang-json";
import { pythonLanguage } from "@codemirror/lang-python";
import { javascriptLanguage, typescriptLanguage } from "@codemirror/lang-javascript";
import { htmlLanguage } from "@codemirror/lang-html";
import { StandardSQL } from "@codemirror/lang-sql";
import { markdownLanguage } from "@codemirror/lang-markdown";
import { javaLanguage } from "@codemirror/lang-java";
import { phpLanguage } from "@codemirror/lang-php";
import { cssLanguage } from "@codemirror/lang-css";
import { cppLanguage } from "@codemirror/lang-cpp";
import { xmlLanguage } from "@codemirror/lang-xml";
import { rustLanguage } from "@codemirror/lang-rust";
import { yamlLanguage } from "@codemirror/lang-yaml";
import {jsonLanguage} from "@codemirror/lang-json";
import {pythonLanguage} from "@codemirror/lang-python";
import {javascriptLanguage, typescriptLanguage} from "@codemirror/lang-javascript";
import {htmlLanguage} from "@codemirror/lang-html";
import {StandardSQL} from "@codemirror/lang-sql";
import {markdownLanguage} from "@codemirror/lang-markdown";
import {javaLanguage} from "@codemirror/lang-java";
import {phpLanguage} from "@codemirror/lang-php";
import {cssLanguage} from "@codemirror/lang-css";
import {cppLanguage} from "@codemirror/lang-cpp";
import {xmlLanguage} from "@codemirror/lang-xml";
import {rustLanguage} from "@codemirror/lang-rust";
import {yamlLanguage} from "@codemirror/lang-yaml";
import { StreamLanguage } from "@codemirror/language";
import { ruby } from "@codemirror/legacy-modes/mode/ruby";
import { shell } from "@codemirror/legacy-modes/mode/shell";
import { go } from "@codemirror/legacy-modes/mode/go";
import { csharp } from "@codemirror/legacy-modes/mode/clike";
import { clojure } from "@codemirror/legacy-modes/mode/clojure";
import { erlang } from "@codemirror/legacy-modes/mode/erlang";
import { swift } from "@codemirror/legacy-modes/mode/swift";
import { kotlin } from "@codemirror/legacy-modes/mode/clike";
import { groovy } from "@codemirror/legacy-modes/mode/groovy";
import { powerShell } from "@codemirror/legacy-modes/mode/powershell";
import { scala } from "@codemirror/legacy-modes/mode/clike";
import { toml } from "@codemirror/legacy-modes/mode/toml";
import { elixir } from "codemirror-lang-elixir";
import { SupportedLanguage } from '../types';
import {StreamLanguage} from "@codemirror/language";
import {ruby} from "@codemirror/legacy-modes/mode/ruby";
import {shell} from "@codemirror/legacy-modes/mode/shell";
import {go} from "@codemirror/legacy-modes/mode/go";
import {csharp, kotlin, scala} from "@codemirror/legacy-modes/mode/clike";
import {clojure} from "@codemirror/legacy-modes/mode/clojure";
import {erlang} from "@codemirror/legacy-modes/mode/erlang";
import {swift} from "@codemirror/legacy-modes/mode/swift";
import {groovy} from "@codemirror/legacy-modes/mode/groovy";
import {powerShell} from "@codemirror/legacy-modes/mode/powershell";
import {toml} from "@codemirror/legacy-modes/mode/toml";
import {elixir} from "codemirror-lang-elixir";
import {SupportedLanguage} from '../types';
import typescriptPlugin from "prettier/plugins/typescript"
import babelPrettierPlugin from "prettier/plugins/babel"
@@ -38,96 +36,108 @@ import htmlPrettierPlugin from "prettier/plugins/html"
import cssPrettierPlugin from "prettier/plugins/postcss"
import markdownPrettierPlugin from "prettier/plugins/markdown"
import yamlPrettierPlugin from "prettier/plugins/yaml"
import goPrettierPlugin from "@/utils/prettier/plugins/go/go"
import sqlPrettierPlugin from "@/utils/prettier/plugins/sql/sql"
import phpPrettierPlugin from "@/utils/prettier/plugins/php"
import * as prettierPluginEstree from "prettier/plugins/estree";
/**
* 语言信息类
*/
export class LanguageInfo {
constructor(
public token: SupportedLanguage,
public name: string,
public parser: any,
public prettier?: {
parser: string;
plugins: any[];
constructor(
public token: SupportedLanguage,
public name: string,
public parser: any,
public prettier?: {
parser: string;
plugins: any[];
}) {
}
) {}
}
/**
* 支持的语言列表(与 Worker 中的 LANGUAGES 对应)
*/
export const LANGUAGES: LanguageInfo[] = [
new LanguageInfo("text", "Plain Text", null),
new LanguageInfo("json", "JSON", jsonLanguage.parser, {
parser: "json",
plugins: [babelPrettierPlugin, prettierPluginEstree]
}),
new LanguageInfo("py", "Python", pythonLanguage.parser),
new LanguageInfo("html", "HTML", htmlLanguage.parser, {
parser: "html",
plugins: [htmlPrettierPlugin]
}),
new LanguageInfo("sql", "SQL", StandardSQL.language.parser),
new LanguageInfo("md", "Markdown", markdownLanguage.parser, {
parser: "markdown",
plugins: [markdownPrettierPlugin]
}),
new LanguageInfo("java", "Java", javaLanguage.parser),
new LanguageInfo("php", "PHP", phpLanguage.configure({top:"Program"}).parser),
new LanguageInfo("css", "CSS", cssLanguage.parser, {
parser: "css",
plugins: [cssPrettierPlugin]
}),
new LanguageInfo("xml", "XML", xmlLanguage.parser),
new LanguageInfo("cpp", "C++", cppLanguage.parser),
new LanguageInfo("rs", "Rust", rustLanguage.parser),
new LanguageInfo("cs", "C#", StreamLanguage.define(csharp).parser),
new LanguageInfo("rb", "Ruby", StreamLanguage.define(ruby).parser),
new LanguageInfo("sh", "Shell", StreamLanguage.define(shell).parser),
new LanguageInfo("yaml", "YAML", yamlLanguage.parser, {
parser: "yaml",
plugins: [yamlPrettierPlugin]
}),
new LanguageInfo("toml", "TOML", StreamLanguage.define(toml).parser),
new LanguageInfo("go", "Go", StreamLanguage.define(go).parser),
new LanguageInfo("clj", "Clojure", StreamLanguage.define(clojure).parser),
new LanguageInfo("ex", "Elixir", elixir().language.parser),
new LanguageInfo("erl", "Erlang", StreamLanguage.define(erlang).parser),
new LanguageInfo("js", "JavaScript", javascriptLanguage.parser, {
parser: "babel",
plugins: [babelPrettierPlugin, prettierPluginEstree]
}),
new LanguageInfo("ts", "TypeScript", typescriptLanguage.parser, {
parser: "typescript",
plugins: [typescriptPlugin, prettierPluginEstree]
}),
new LanguageInfo("swift", "Swift", StreamLanguage.define(swift).parser),
new LanguageInfo("kt", "Kotlin", StreamLanguage.define(kotlin).parser),
new LanguageInfo("groovy", "Groovy", StreamLanguage.define(groovy).parser),
new LanguageInfo("ps1", "PowerShell", StreamLanguage.define(powerShell).parser),
new LanguageInfo("dart", "Dart", null), // 暂无解析器
new LanguageInfo("scala", "Scala", StreamLanguage.define(scala).parser),
new LanguageInfo("text", "Plain Text", null),
new LanguageInfo("json", "JSON", jsonLanguage.parser, {
parser: "json",
plugins: [babelPrettierPlugin, prettierPluginEstree]
}),
new LanguageInfo("py", "Python", pythonLanguage.parser),
new LanguageInfo("html", "HTML", htmlLanguage.parser, {
parser: "html",
plugins: [htmlPrettierPlugin]
}),
new LanguageInfo("sql", "SQL", StandardSQL.language.parser, {
parser: "sql",
plugins: [sqlPrettierPlugin]
}),
new LanguageInfo("md", "Markdown", markdownLanguage.parser, {
parser: "markdown",
plugins: [markdownPrettierPlugin]
}),
new LanguageInfo("java", "Java", javaLanguage.parser),
new LanguageInfo("php", "PHP", phpLanguage.configure({top: "Program"}).parser, {
parser: "php",
plugins: [phpPrettierPlugin]
}),
new LanguageInfo("css", "CSS", cssLanguage.parser, {
parser: "css",
plugins: [cssPrettierPlugin]
}),
new LanguageInfo("xml", "XML", xmlLanguage.parser),
new LanguageInfo("cpp", "C++", cppLanguage.parser),
new LanguageInfo("rs", "Rust", rustLanguage.parser),
new LanguageInfo("cs", "C#", StreamLanguage.define(csharp).parser),
new LanguageInfo("rb", "Ruby", StreamLanguage.define(ruby).parser),
new LanguageInfo("sh", "Shell", StreamLanguage.define(shell).parser),
new LanguageInfo("yaml", "YAML", yamlLanguage.parser, {
parser: "yaml",
plugins: [yamlPrettierPlugin]
}),
new LanguageInfo("toml", "TOML", StreamLanguage.define(toml).parser),
new LanguageInfo("go", "Go", StreamLanguage.define(go).parser, {
parser: "go",
plugins: [goPrettierPlugin]
}),
new LanguageInfo("clj", "Clojure", StreamLanguage.define(clojure).parser),
new LanguageInfo("ex", "Elixir", elixir().language.parser),
new LanguageInfo("erl", "Erlang", StreamLanguage.define(erlang).parser),
new LanguageInfo("js", "JavaScript", javascriptLanguage.parser, {
parser: "babel",
plugins: [babelPrettierPlugin, prettierPluginEstree]
}),
new LanguageInfo("ts", "TypeScript", typescriptLanguage.parser, {
parser: "typescript",
plugins: [typescriptPlugin, prettierPluginEstree]
}),
new LanguageInfo("swift", "Swift", StreamLanguage.define(swift).parser),
new LanguageInfo("kt", "Kotlin", StreamLanguage.define(kotlin).parser),
new LanguageInfo("groovy", "Groovy", StreamLanguage.define(groovy).parser),
new LanguageInfo("ps1", "PowerShell", StreamLanguage.define(powerShell).parser),
new LanguageInfo("dart", "Dart", null), // 暂无解析器
new LanguageInfo("scala", "Scala", StreamLanguage.define(scala).parser),
];
/**
* 语言映射表
*/
export const languageMapping = Object.fromEntries(
LANGUAGES.map(l => [l.token, l.parser])
LANGUAGES.map(l => [l.token, l.parser])
);
/**
* 根据 token 获取语言信息
*/
export function getLanguage(token: SupportedLanguage): LanguageInfo | undefined {
return LANGUAGES.find(lang => lang.token === token);
return LANGUAGES.find(lang => lang.token === token);
}
/**
* 获取所有语言的 token 列表
*/
export function getLanguageTokens(): SupportedLanguage[] {
return LANGUAGES.map(lang => lang.token);
return LANGUAGES.map(lang => lang.token);
}

View File

@@ -2,6 +2,7 @@ import {defineConfig, loadEnv} from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import * as path from 'path';
import { nodePolyfills } from 'vite-plugin-node-polyfills'
export default defineConfig(({mode}: { mode: string }): object => {
const env: Record<string, string> = loadEnv(mode, process.cwd());
@@ -15,6 +16,7 @@ export default defineConfig(({mode}: { mode: string }): object => {
},
plugins: [
vue(),
nodePolyfills(),
Components({
dts: true,
dirs: ['src/components'],

View File

@@ -11,10 +11,10 @@ import (
func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow, assets embed.FS, trayService *services.TrayService) {
// 创建系统托盘
systray := app.SystemTray.New()
// 设置提示
systray.SetTooltip("voidraft")
// 设置标签
systray.SetLabel("voidraft")
systray.Label()
// 设置图标
iconBytes, _ := assets.ReadFile("appicon.png")
systray.SetIcon(iconBytes)

View File

@@ -1 +1 @@
VERSION=1.3.6
VERSION=1.3.7