🐛 Fixed docker、shell prettier plugin issue
This commit is contained in:
26
frontend/package-lock.json
generated
26
frontend/package-lock.json
generated
@@ -41,7 +41,6 @@
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@lezer/lr": "^1.4.2",
|
||||
"@prettier/plugin-xml": "^3.4.2",
|
||||
"@reteps/dockerfmt": "^0.3.6",
|
||||
"@toml-tools/lexer": "^1.0.0",
|
||||
"@toml-tools/parser": "^1.0.0",
|
||||
"codemirror": "^6.0.2",
|
||||
@@ -60,7 +59,6 @@
|
||||
"prettier": "^3.6.2",
|
||||
"remarkable": "^2.0.1",
|
||||
"sass": "^1.92.1",
|
||||
"sh-syntax": "^0.5.8",
|
||||
"vue": "^3.5.21",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-pick-colors": "^1.8.0",
|
||||
@@ -1877,15 +1875,6 @@
|
||||
"prettier": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@reteps/dockerfmt": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmmirror.com/@reteps/dockerfmt/-/dockerfmt-0.3.6.tgz",
|
||||
"integrity": "sha512-Tb5wIMvBf/nLejTQ61krK644/CEMB/cpiaIFXqGApfGqO3GwcR3qnI0DbmkFVCl2OyEp8LnLX3EkucoL0+tbFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^v12.20.0 || ^14.13.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.29",
|
||||
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz",
|
||||
@@ -6227,21 +6216,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sh-syntax": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmmirror.com/sh-syntax/-/sh-syntax-0.5.8.tgz",
|
||||
"integrity": "sha512-JfVoxf4FxQI5qpsPbkHhZo+n6N9YMJobyl4oGEUBb/31oQYlgTjkXQD8PBiafS2UbWoxrTO0Z5PJUBXEPAG1Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/sh-syntax"
|
||||
}
|
||||
},
|
||||
"node_modules/sha.js": {
|
||||
"version": "2.4.12",
|
||||
"resolved": "https://registry.npmmirror.com/sha.js/-/sha.js-2.4.12.tgz",
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@lezer/lr": "^1.4.2",
|
||||
"@prettier/plugin-xml": "^3.4.2",
|
||||
"@reteps/dockerfmt": "^0.3.6",
|
||||
"@toml-tools/lexer": "^1.0.0",
|
||||
"@toml-tools/parser": "^1.0.0",
|
||||
"codemirror": "^6.0.2",
|
||||
@@ -64,7 +63,6 @@
|
||||
"prettier": "^3.6.2",
|
||||
"remarkable": "^2.0.1",
|
||||
"sass": "^1.92.1",
|
||||
"sh-syntax": "^0.5.8",
|
||||
"vue": "^3.5.21",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-pick-colors": "^1.8.0",
|
||||
|
||||
23
frontend/src/common/prettier/plugins/docker/docker_fmt.d.ts
vendored
Normal file
23
frontend/src/common/prettier/plugins/docker/docker_fmt.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// Format options interface for Dockerfile
|
||||
export interface FormatOptions {
|
||||
indent?: number;
|
||||
trailingNewline?: boolean;
|
||||
spaceRedirects?: boolean;
|
||||
}
|
||||
|
||||
// Initialize the WASM module
|
||||
declare function init(wasmUrl?: string): Promise<void>;
|
||||
|
||||
// Format Dockerfile content
|
||||
export declare function format(text: string, options?: FormatOptions): string;
|
||||
|
||||
// Format Dockerfile contents (alias for compatibility)
|
||||
export declare function formatDockerfileContents(
|
||||
fileContents: string,
|
||||
options?: FormatOptions
|
||||
): string;
|
||||
|
||||
// Placeholder for Node.js compatibility (not implemented in browser)
|
||||
export declare function formatDockerfile(): never;
|
||||
|
||||
export default init;
|
||||
86
frontend/src/common/prettier/plugins/docker/docker_fmt.js
Normal file
86
frontend/src/common/prettier/plugins/docker/docker_fmt.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import './wasm_exec.js'
|
||||
// Format options for Dockerfile
|
||||
export const FormatOptions = {
|
||||
indentSize: 4,
|
||||
trailingNewline: true,
|
||||
spaceRedirects: false,
|
||||
}
|
||||
|
||||
let wasmInstance = null;
|
||||
let isInitialized = false;
|
||||
|
||||
// Initialize the WASM module
|
||||
export default async function init(wasmUrl) {
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load WASM file
|
||||
const wasmPath = wasmUrl || new URL('./docker_fmt.wasm', import.meta.url).href;
|
||||
const wasmResponse = await fetch(wasmPath);
|
||||
const wasmBytes = await wasmResponse.arrayBuffer();
|
||||
|
||||
// Initialize Go runtime
|
||||
const go = new Go();
|
||||
const result = await WebAssembly.instantiate(wasmBytes, go.importObject);
|
||||
|
||||
wasmInstance = result.instance;
|
||||
|
||||
// Run the Go program (don't await, as it needs to stay alive)
|
||||
go.run(wasmInstance);
|
||||
|
||||
// Wait a bit for the Go program to initialize and register the function
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
isInitialized = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Dockerfile WASM module:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Format Dockerfile content
|
||||
export function format(text, options = {}) {
|
||||
if (!isInitialized) {
|
||||
throw new Error('WASM module not initialized. Call init() first.');
|
||||
}
|
||||
|
||||
if (typeof globalThis.dockerFormat !== 'function') {
|
||||
throw new Error('dockerFormat function not available. WASM module may not be properly initialized.');
|
||||
}
|
||||
|
||||
const config = {
|
||||
indentSize: options.indentSize || options.indent || 4,
|
||||
trailingNewline: options.trailingNewline !== undefined ? options.trailingNewline : true,
|
||||
spaceRedirects: options.spaceRedirects !== undefined ? options.spaceRedirects : false
|
||||
};
|
||||
|
||||
try {
|
||||
// Call the dockerFormat function registered by Go
|
||||
const result = globalThis.dockerFormat(text, config);
|
||||
|
||||
// Check if there was an error
|
||||
if (result && Array.isArray(result) && result[0] === true) {
|
||||
throw new Error(result[1] || 'Unknown formatting error');
|
||||
}
|
||||
|
||||
// Return the formatted result
|
||||
return result && Array.isArray(result) ? result[1] : result;
|
||||
} catch (error) {
|
||||
console.warn('Dockerfile formatting error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Format Dockerfile contents (alias for compatibility)
|
||||
export function formatDockerfileContents(fileContents, options) {
|
||||
return format(fileContents, options);
|
||||
}
|
||||
|
||||
// Placeholder for Node.js compatibility (not implemented in browser)
|
||||
export function formatDockerfile() {
|
||||
throw new Error(
|
||||
'`formatDockerfile` is not implemented in the browser. Use `format` or `formatDockerfileContents` instead.',
|
||||
);
|
||||
}
|
||||
BIN
frontend/src/common/prettier/plugins/docker/docker_fmt.wasm
Normal file
BIN
frontend/src/common/prettier/plugins/docker/docker_fmt.wasm
Normal file
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
import initAsync from './docker_fmt.js'
|
||||
import wasm_url from './docker_fmt.wasm?url'
|
||||
|
||||
export default function init() {
|
||||
return initAsync(wasm_url)
|
||||
}
|
||||
|
||||
export * from './docker_fmt.js'
|
||||
55
frontend/src/common/prettier/plugins/docker/format.go
Normal file
55
frontend/src/common/prettier/plugins/docker/format.go
Normal file
@@ -0,0 +1,55 @@
|
||||
//go:build js || wasm
|
||||
|
||||
// tinygo build -o docker_fmt.wasm -target wasm --no-debug
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/reteps/dockerfmt/lib"
|
||||
)
|
||||
|
||||
func Format(this js.Value, args []js.Value) any {
|
||||
if len(args) < 1 {
|
||||
return []any{true, "missing input argument"}
|
||||
}
|
||||
|
||||
input := args[0].String()
|
||||
|
||||
// Default configuration
|
||||
indentSize := uint(4)
|
||||
newlineFlag := true
|
||||
spaceRedirects := false
|
||||
|
||||
// Parse optional configuration if provided
|
||||
if len(args) > 1 && !args[1].IsNull() && !args[1].IsUndefined() {
|
||||
config := args[1]
|
||||
if !config.Get("indentSize").IsUndefined() {
|
||||
indentSize = uint(config.Get("indentSize").Int())
|
||||
}
|
||||
if !config.Get("trailingNewline").IsUndefined() {
|
||||
newlineFlag = config.Get("trailingNewline").Bool()
|
||||
}
|
||||
if !config.Get("spaceRedirects").IsUndefined() {
|
||||
spaceRedirects = config.Get("spaceRedirects").Bool()
|
||||
}
|
||||
}
|
||||
|
||||
originalLines := strings.SplitAfter(input, "\n")
|
||||
c := &lib.Config{
|
||||
IndentSize: indentSize,
|
||||
TrailingNewline: newlineFlag,
|
||||
SpaceRedirects: spaceRedirects,
|
||||
}
|
||||
|
||||
result := lib.FormatFileLines(originalLines, c)
|
||||
|
||||
return []any{false, result}
|
||||
}
|
||||
|
||||
func main() {
|
||||
done := make(chan bool)
|
||||
js.Global().Set("dockerFormat", js.FuncOf(Format))
|
||||
<-done
|
||||
}
|
||||
16
frontend/src/common/prettier/plugins/docker/go.mod
Normal file
16
frontend/src/common/prettier/plugins/docker/go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module docker_fmt
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require github.com/reteps/dockerfmt v0.3.7
|
||||
|
||||
require (
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/moby/buildkit v0.20.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
google.golang.org/protobuf v1.35.2 // indirect
|
||||
mvdan.cc/sh/v3 v3.11.0 // indirect
|
||||
)
|
||||
65
frontend/src/common/prettier/plugins/docker/go.sum
Normal file
65
frontend/src/common/prettier/plugins/docker/go.sum
Normal file
@@ -0,0 +1,65 @@
|
||||
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
|
||||
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/moby/buildkit v0.20.2 h1:qIeR47eQ1tzI1rwz0on3Xx2enRw/1CKjFhoONVcTlMA=
|
||||
github.com/moby/buildkit v0.20.2/go.mod h1:DhaF82FjwOElTftl0JUAJpH/SUIUx4UvcFncLeOtlDI=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/reteps/dockerfmt v0.3.7 h1:GChhICBoy6oiTuoLTLFtGnfyBi2qY9dvHBhrcWrN8Zk=
|
||||
github.com/reteps/dockerfmt v0.3.7/go.mod h1:5lpbp1KzLWaRhL7qB6IEutHoQK3ZcT2Lb5MWPmFts74=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
|
||||
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
|
||||
111
frontend/src/common/prettier/plugins/docker/index.ts
Normal file
111
frontend/src/common/prettier/plugins/docker/index.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { Plugin, SupportLanguage, Parser, Printer, SupportOption } from 'prettier'
|
||||
import dockerfileInit, { format } from './docker_fmt_vite.js'
|
||||
|
||||
// Language configuration for Dockerfile
|
||||
const languages: SupportLanguage[] = [
|
||||
{
|
||||
name: 'dockerfile',
|
||||
parsers: ['dockerfile'],
|
||||
extensions: ['.docker', '.Dockerfile'],
|
||||
filenames: ['Dockerfile', 'dockerfile'],
|
||||
linguistLanguageId: 99,
|
||||
vscodeLanguageIds: ['dockerfile'],
|
||||
},
|
||||
]
|
||||
|
||||
// Parser configuration
|
||||
const parsers: Record<string, Parser<any>> = {
|
||||
dockerfile: {
|
||||
parse: (text: string) => {
|
||||
// For Dockerfile, we don't need complex parsing, just return the text
|
||||
// The formatting will be handled by the print function
|
||||
return { type: 'dockerfile', value: text }
|
||||
},
|
||||
astFormat: 'dockerfile',
|
||||
locStart: () => 0,
|
||||
locEnd: () => 0,
|
||||
},
|
||||
}
|
||||
|
||||
// Printer configuration
|
||||
const printers: Record<string, Printer<any>> = {
|
||||
dockerfile: {
|
||||
// @ts-expect-error -- Support async printer like shell plugin
|
||||
async print(path: any, options: any) {
|
||||
await ensureInitialized()
|
||||
const text = path.getValue().value || path.getValue()
|
||||
|
||||
try {
|
||||
const formatted = format(text, {
|
||||
indent: options.tabWidth || 2,
|
||||
trailingNewline: true,
|
||||
spaceRedirects: options.spaceRedirects !== false,
|
||||
})
|
||||
return formatted
|
||||
} catch (error) {
|
||||
console.warn('Dockerfile formatting error:', error)
|
||||
return text
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// WASM initialization
|
||||
let isInitialized = false
|
||||
let initPromise: Promise<void> | null = null
|
||||
|
||||
async function ensureInitialized(): Promise<void> {
|
||||
if (isInitialized) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
if (!initPromise) {
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
await dockerfileInit()
|
||||
isInitialized = true
|
||||
} catch (error) {
|
||||
console.warn('Failed to initialize Dockerfile WASM module:', error)
|
||||
initPromise = null
|
||||
throw error
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
return initPromise
|
||||
}
|
||||
|
||||
// Configuration mapping function
|
||||
function mapOptionsToConfig(options: any) {
|
||||
return {
|
||||
indent: options.tabWidth || 2,
|
||||
trailingNewline: options.insertFinalNewline !== false,
|
||||
spaceRedirects: options.spaceRedirects !== false,
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin options
|
||||
const options: Record<string, SupportOption> = {
|
||||
spaceRedirects: {
|
||||
type: 'boolean',
|
||||
category: 'Format',
|
||||
default: true,
|
||||
description: 'Add spaces around redirect operators',
|
||||
},
|
||||
}
|
||||
|
||||
// Plugin definition
|
||||
const plugin: Plugin = {
|
||||
languages,
|
||||
parsers,
|
||||
printers,
|
||||
options,
|
||||
defaultOptions: {
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
spaceRedirects: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default plugin
|
||||
export { languages, parsers, printers, options }
|
||||
553
frontend/src/common/prettier/plugins/docker/wasm_exec.js
Normal file
553
frontend/src/common/prettier/plugins/docker/wasm_exec.js
Normal file
@@ -0,0 +1,553 @@
|
||||
// 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.
|
||||
//
|
||||
// This file has been modified for use by the TinyGo compiler.
|
||||
|
||||
(() => {
|
||||
// Map multiple JavaScript environments to a single common API,
|
||||
// preferring web standards over Node.js API.
|
||||
//
|
||||
// Environments considered:
|
||||
// - Browsers
|
||||
// - Node.js
|
||||
// - Electron
|
||||
// - Parcel
|
||||
|
||||
if (typeof global !== "undefined") {
|
||||
// global already exists
|
||||
} else if (typeof window !== "undefined") {
|
||||
window.global = window;
|
||||
} else if (typeof self !== "undefined") {
|
||||
self.global = self;
|
||||
} else {
|
||||
throw new Error("cannot export Go (neither global, window nor self is defined)");
|
||||
}
|
||||
|
||||
if (!global.require && typeof require !== "undefined") {
|
||||
global.require = require;
|
||||
}
|
||||
|
||||
if (!global.fs && global.require) {
|
||||
global.fs = require("node:fs");
|
||||
}
|
||||
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!global.fs) {
|
||||
let outputBuf = "";
|
||||
global.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.substr(0, nl));
|
||||
outputBuf = outputBuf.substr(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 (!global.process) {
|
||||
global.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 (!global.crypto) {
|
||||
const nodeCrypto = require("node:crypto");
|
||||
global.crypto = {
|
||||
getRandomValues(b) {
|
||||
nodeCrypto.randomFillSync(b);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.performance) {
|
||||
global.performance = {
|
||||
now() {
|
||||
const [sec, nsec] = process.hrtime();
|
||||
return sec * 1000 + nsec / 1000000;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.TextEncoder) {
|
||||
global.TextEncoder = require("node:util").TextEncoder;
|
||||
}
|
||||
|
||||
if (!global.TextDecoder) {
|
||||
global.TextDecoder = require("node:util").TextDecoder;
|
||||
}
|
||||
|
||||
// End of polyfills for common API.
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
let reinterpretBuf = new DataView(new ArrayBuffer(8));
|
||||
var logLine = [];
|
||||
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
|
||||
|
||||
global.Go = class {
|
||||
constructor() {
|
||||
this._callbackTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const mem = () => {
|
||||
// The buffer may change when requesting more memory.
|
||||
return new DataView(this._inst.exports.memory.buffer);
|
||||
}
|
||||
|
||||
const unboxValue = (v_ref) => {
|
||||
reinterpretBuf.setBigInt64(0, v_ref, true);
|
||||
const f = reinterpretBuf.getFloat64(0, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = v_ref & 0xffffffffn;
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
|
||||
const loadValue = (addr) => {
|
||||
let v_ref = mem().getBigUint64(addr, true);
|
||||
return unboxValue(v_ref);
|
||||
}
|
||||
|
||||
const boxValue = (v) => {
|
||||
const nanHead = 0x7FF80000n;
|
||||
|
||||
if (typeof v === "number") {
|
||||
if (isNaN(v)) {
|
||||
return nanHead << 32n;
|
||||
}
|
||||
if (v === 0) {
|
||||
return (nanHead << 32n) | 1n;
|
||||
}
|
||||
reinterpretBuf.setFloat64(0, v, true);
|
||||
return reinterpretBuf.getBigInt64(0, true);
|
||||
}
|
||||
|
||||
switch (v) {
|
||||
case undefined:
|
||||
return 0n;
|
||||
case null:
|
||||
return (nanHead << 32n) | 2n;
|
||||
case true:
|
||||
return (nanHead << 32n) | 3n;
|
||||
case false:
|
||||
return (nanHead << 32n) | 4n;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = BigInt(this._values.length);
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 1n;
|
||||
switch (typeof v) {
|
||||
case "string":
|
||||
typeFlag = 2n;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 3n;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 4n;
|
||||
break;
|
||||
}
|
||||
return id | ((nanHead | typeFlag) << 32n);
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
let v_ref = boxValue(v);
|
||||
mem().setBigUint64(addr, v_ref, true);
|
||||
}
|
||||
|
||||
const loadSlice = (array, len, cap) => {
|
||||
return new Uint8Array(this._inst.exports.memory.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (array, len, cap) => {
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (ptr, len) => {
|
||||
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
wasi_snapshot_preview1: {
|
||||
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
|
||||
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
|
||||
let nwritten = 0;
|
||||
if (fd == 1) {
|
||||
for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) {
|
||||
let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32
|
||||
let ptr = mem().getUint32(iov_ptr + 0, true);
|
||||
let len = mem().getUint32(iov_ptr + 4, true);
|
||||
nwritten += len;
|
||||
for (let i=0; i<len; i++) {
|
||||
let c = mem().getUint8(ptr+i);
|
||||
if (c == 13) { // CR
|
||||
// ignore
|
||||
} else if (c == 10) { // LF
|
||||
// write line
|
||||
let line = decoder.decode(new Uint8Array(logLine));
|
||||
logLine = [];
|
||||
console.log(line);
|
||||
} else {
|
||||
logLine.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('invalid file descriptor:', fd);
|
||||
}
|
||||
mem().setUint32(nwritten_ptr, nwritten, true);
|
||||
return 0;
|
||||
},
|
||||
fd_close: () => 0, // dummy
|
||||
fd_fdstat_get: () => 0, // dummy
|
||||
fd_seek: () => 0, // dummy
|
||||
proc_exit: (code) => {
|
||||
this.exited = true;
|
||||
this.exitCode = code;
|
||||
this._resolveExitPromise();
|
||||
throw wasmExit;
|
||||
},
|
||||
random_get: (bufPtr, bufLen) => {
|
||||
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
gojs: {
|
||||
// func ticks() int64
|
||||
"runtime.ticks": () => {
|
||||
return BigInt((timeOrigin + performance.now()) * 1e6);
|
||||
},
|
||||
|
||||
// func sleepTicks(timeout int64)
|
||||
"runtime.sleepTicks": (timeout) => {
|
||||
// Do not sleep, only reactivate scheduler after the given timeout.
|
||||
setTimeout(() => {
|
||||
if (this.exited) return;
|
||||
try {
|
||||
this._inst.exports.go_scheduler();
|
||||
} catch (e) {
|
||||
if (e !== wasmExit) throw e;
|
||||
}
|
||||
}, Number(timeout)/1e6);
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
"syscall/js.finalizeRef": (v_ref) => {
|
||||
// Note: TinyGo does not support finalizers so this is only called
|
||||
// for one specific case, by js.go:jsString. and can/might leak memory.
|
||||
const id = v_ref & 0xffffffffn;
|
||||
if (this._goRefCounts?.[id] !== undefined) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
console.error("syscall/js.finalizeRef: unknown id", id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (value_ptr, value_len) => {
|
||||
value_ptr >>>= 0;
|
||||
const s = loadString(value_ptr, value_len);
|
||||
return boxValue(s);
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (v_ref, p_ptr, p_len) => {
|
||||
let prop = loadString(p_ptr, p_len);
|
||||
let v = unboxValue(v_ref);
|
||||
let result = Reflect.get(v, prop);
|
||||
return boxValue(result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => {
|
||||
const v = unboxValue(v_ref);
|
||||
const p = loadString(p_ptr, p_len);
|
||||
const x = unboxValue(x_ref);
|
||||
Reflect.set(v, p, x);
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
"syscall/js.valueDelete": (v_ref, p_ptr, p_len) => {
|
||||
const v = unboxValue(v_ref);
|
||||
const p = loadString(p_ptr, p_len);
|
||||
Reflect.deleteProperty(v, p);
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (v_ref, i) => {
|
||||
return boxValue(Reflect.get(unboxValue(v_ref), i));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (v_ref, i, x_ref) => {
|
||||
Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => {
|
||||
const v = unboxValue(v_ref);
|
||||
const name = loadString(m_ptr, m_len);
|
||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
||||
try {
|
||||
const m = Reflect.get(v, name);
|
||||
storeValue(ret_addr, Reflect.apply(m, v, args));
|
||||
mem().setUint8(ret_addr + 8, 1);
|
||||
} catch (err) {
|
||||
storeValue(ret_addr, err);
|
||||
mem().setUint8(ret_addr + 8, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
|
||||
try {
|
||||
const v = unboxValue(v_ref);
|
||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
||||
storeValue(ret_addr, Reflect.apply(v, undefined, args));
|
||||
mem().setUint8(ret_addr + 8, 1);
|
||||
} catch (err) {
|
||||
storeValue(ret_addr, err);
|
||||
mem().setUint8(ret_addr + 8, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
|
||||
const v = unboxValue(v_ref);
|
||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
||||
try {
|
||||
storeValue(ret_addr, Reflect.construct(v, args));
|
||||
mem().setUint8(ret_addr + 8, 1);
|
||||
} catch (err) {
|
||||
storeValue(ret_addr, err);
|
||||
mem().setUint8(ret_addr+ 8, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (v_ref) => {
|
||||
return unboxValue(v_ref).length;
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (ret_addr, v_ref) => {
|
||||
const s = String(unboxValue(v_ref));
|
||||
const str = encoder.encode(s);
|
||||
storeValue(ret_addr, str);
|
||||
mem().setInt32(ret_addr + 8, str.length, true);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => {
|
||||
const str = unboxValue(v_ref);
|
||||
loadSlice(slice_ptr, slice_len, slice_cap).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (v_ref, t_ref) => {
|
||||
return unboxValue(v_ref) instanceof unboxValue(t_ref);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => {
|
||||
let num_bytes_copied_addr = ret_addr;
|
||||
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
|
||||
|
||||
const dst = loadSlice(dest_addr, dest_len);
|
||||
const src = unboxValue(src_ref);
|
||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
|
||||
mem().setUint8(returned_status_addr, 1); // Return "ok" status
|
||||
},
|
||||
|
||||
// copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
// Originally copied from upstream Go project, then modified:
|
||||
// https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416
|
||||
"syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => {
|
||||
let num_bytes_copied_addr = ret_addr;
|
||||
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
|
||||
|
||||
const dst = unboxValue(dst_ref);
|
||||
const src = loadSlice(src_addr, src_len);
|
||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
|
||||
mem().setUint8(returned_status_addr, 1); // Return "ok" status
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Go 1.20 uses 'env'. Go 1.21 uses 'gojs'.
|
||||
// For compatibility, we use both as long as Go 1.20 is supported.
|
||||
this.importObject.env = this.importObject.gojs;
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
this._inst = instance;
|
||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
global,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = []; // 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
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
this.exitCode = 0;
|
||||
|
||||
if (this._inst.exports._start) {
|
||||
let exitPromise = new Promise((resolve, reject) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
|
||||
// Run program, but catch the wasmExit exception that's thrown
|
||||
// to return back here.
|
||||
try {
|
||||
this._inst.exports._start();
|
||||
} catch (e) {
|
||||
if (e !== wasmExit) throw e;
|
||||
}
|
||||
|
||||
await exitPromise;
|
||||
return this.exitCode;
|
||||
} else {
|
||||
this._inst.exports._initialize();
|
||||
}
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
try {
|
||||
this._inst.exports.resume();
|
||||
} catch (e) {
|
||||
if (e !== wasmExit) throw e;
|
||||
}
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
global.require &&
|
||||
// global.require.main === module &&
|
||||
global.process &&
|
||||
global.process.versions &&
|
||||
!global.process.versions.electron
|
||||
) {
|
||||
if (process.argv.length != 3) {
|
||||
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
|
||||
let exitCode = await go.run(result.instance);
|
||||
process.exit(exitCode);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -1,471 +1,207 @@
|
||||
import type { Parser, ParserOptions, Plugin, Printer } from 'prettier'
|
||||
import {
|
||||
type File,
|
||||
LangVariant,
|
||||
type Node,
|
||||
type ParseError,
|
||||
type ShOptions,
|
||||
type ShPrintOptions,
|
||||
getProcessor,
|
||||
} from 'sh-syntax'
|
||||
/**
|
||||
* Prettier Plugin for Shell formatting using shfmt WebAssembly
|
||||
*
|
||||
* This plugin provides support for formatting Shell files using the shfmt WASM implementation.
|
||||
*/
|
||||
import type { Plugin, Parser, Printer } from 'prettier';
|
||||
|
||||
import { languages } from './languages'
|
||||
// Import the shell WASM module
|
||||
import shfmtInit, { format, type Config } from './shfmt_vite.js';
|
||||
|
||||
// 创建处理器实例
|
||||
let processorInstance: any = null
|
||||
const parserName = 'sh';
|
||||
|
||||
const getProcessorInstance = async () => {
|
||||
if (!processorInstance) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const initWasm = await import('sh-syntax/main.wasm?init')
|
||||
processorInstance = getProcessor(initWasm.default)
|
||||
} catch {
|
||||
processorInstance = getProcessor(() =>
|
||||
fetch(new URL('sh-syntax/main.wasm', import.meta.url))
|
||||
)
|
||||
}
|
||||
// Language configuration
|
||||
const languages = [
|
||||
{
|
||||
name: 'Shell',
|
||||
aliases: ['sh', 'bash', 'shell'],
|
||||
parsers: [parserName],
|
||||
extensions: ['.sh', '.bash', '.zsh', '.fish', '.ksh'],
|
||||
aceMode: 'sh',
|
||||
tmScope: 'source.shell',
|
||||
linguistLanguageId: 302,
|
||||
vscodeLanguageIds: ['shellscript']
|
||||
}
|
||||
return processorInstance
|
||||
}
|
||||
];
|
||||
|
||||
export interface DockerfilePrintOptions extends ParserOptions<string> {
|
||||
indent?: number
|
||||
spaceRedirects?: boolean
|
||||
}
|
||||
|
||||
export interface ShParserOptions
|
||||
extends Partial<ParserOptions<Node>>,
|
||||
ShOptions {
|
||||
filepath?: string
|
||||
}
|
||||
|
||||
export type { ShPrintOptions }
|
||||
|
||||
export interface ShPrinterOptions extends ShPrintOptions {
|
||||
filepath?: string
|
||||
tabWidth: number
|
||||
}
|
||||
|
||||
export class ShSyntaxParseError<
|
||||
E extends Error = ParseError | SyntaxError,
|
||||
> extends SyntaxError {
|
||||
declare cause: E
|
||||
|
||||
declare loc: { start: { column: number; line: number } } | undefined
|
||||
|
||||
constructor(err: E) {
|
||||
const error = err as ParseError | SyntaxError
|
||||
super(('Text' in error && error.Text) || error.message)
|
||||
this.cause = err
|
||||
// `error instanceof ParseError` won't not work because the error is thrown wrapped by `synckit`
|
||||
if ('Pos' in error && error.Pos != null && typeof error.Pos === 'object') {
|
||||
this.loc = { start: { column: error.Pos.Col, line: error.Pos.Line } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasPragma(text: string) {
|
||||
/**
|
||||
* We don't want to parse every file twice but Prettier's interface isn't
|
||||
* conducive to caching/memoizing an upstream Parser, so we're going with some
|
||||
* minor Regex hackery.
|
||||
*
|
||||
* Only read empty lines, comments, and shebangs at the start of the file. We
|
||||
* do not support Bash's pseudo-block comments.
|
||||
*/
|
||||
|
||||
// No, we don't support unofficial block comments.
|
||||
const commentLineRegex = /^\s*(#(?<comment>.*))?$/gm
|
||||
let lastIndex = -1
|
||||
|
||||
/**
|
||||
* Only read leading comments, skip shebangs, and check for the pragma. We
|
||||
* don't want to have to parse every file twice.
|
||||
*/
|
||||
for (;;) {
|
||||
const match = commentLineRegex.exec(text)
|
||||
|
||||
// Found "real" content, EoF, or stuck in a loop.
|
||||
if (match == null || match.index !== lastIndex + 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
lastIndex = commentLineRegex.lastIndex
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- incorrect typing
|
||||
const comment = match.groups?.comment?.trim()
|
||||
|
||||
// Empty lines and shebangs have no captures
|
||||
if (comment == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (comment.startsWith('@prettier') || comment.startsWith('@format')) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dockerfileParser: Parser<string> = {
|
||||
astFormat: 'dockerfile',
|
||||
hasPragma,
|
||||
parse: text => text,
|
||||
// Parser configuration
|
||||
const shParser: Parser<string> = {
|
||||
astFormat: parserName,
|
||||
parse: (text: string) => text,
|
||||
locStart: () => 0,
|
||||
locEnd: node => node.length,
|
||||
}
|
||||
locEnd: (node: string) => node.length,
|
||||
};
|
||||
|
||||
let formatDockerfileContents_:
|
||||
| any
|
||||
| undefined
|
||||
// Lazy initialize shfmt WASM module
|
||||
let initPromise: Promise<void> | null = null;
|
||||
let isInitialized = false;
|
||||
|
||||
const getFormatDockerfileContents = async () => {
|
||||
if (!formatDockerfileContents_) {
|
||||
try {
|
||||
// @ts-ignore - 忽略模块解析错误
|
||||
const dockerfmt = await import('@reteps/dockerfmt')
|
||||
formatDockerfileContents_ = dockerfmt.formatDockerfileContents
|
||||
} catch (error) {
|
||||
console.warn('Failed to load @reteps/dockerfmt:', error)
|
||||
formatDockerfileContents_ = null
|
||||
}
|
||||
function initShfmt(): Promise<void> {
|
||||
if (isInitialized) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return formatDockerfileContents_
|
||||
|
||||
if (!initPromise) {
|
||||
initPromise = (async () => {
|
||||
try {
|
||||
await shfmtInit();
|
||||
isInitialized = true;
|
||||
} catch (error) {
|
||||
console.warn('Failed to initialize shfmt WASM module:', error);
|
||||
initPromise = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
const dockerPrinter: Printer<string> = {
|
||||
// @ts-expect-error -- https://github.com/prettier/prettier/issues/15080#issuecomment-1630987744
|
||||
async print(
|
||||
path,
|
||||
{
|
||||
filepath,
|
||||
|
||||
// parser options
|
||||
keepComments = true,
|
||||
variant,
|
||||
stopAt,
|
||||
recoverErrors,
|
||||
|
||||
// printer options
|
||||
useTabs,
|
||||
tabWidth,
|
||||
indent = useTabs ? 0 : (tabWidth ?? 2),
|
||||
binaryNextLine = true,
|
||||
switchCaseIndent = true,
|
||||
spaceRedirects,
|
||||
// eslint-disable-next-line sonarjs/deprecation
|
||||
keepPadding,
|
||||
minify,
|
||||
singleLine,
|
||||
functionNextLine,
|
||||
}: ShPrintOptions,
|
||||
) {
|
||||
const formatDockerfileContents = await getFormatDockerfileContents()
|
||||
// Printer configuration
|
||||
const shPrinter: Printer<string> = {
|
||||
// @ts-expect-error -- Support async printer like shell plugin
|
||||
async print(path, options) {
|
||||
try {
|
||||
if (formatDockerfileContents) {
|
||||
return await formatDockerfileContents(path.node, {
|
||||
indent,
|
||||
spaceRedirects: spaceRedirects ?? false,
|
||||
trailingNewline: true,
|
||||
})
|
||||
}
|
||||
throw new Error('dockerfmt not available')
|
||||
} catch {
|
||||
/*
|
||||
* `dockerfmt` is buggy now and could throw unexpectedly, so we fallback to
|
||||
* the `sh` printer automatically in this case.
|
||||
*
|
||||
* @see {https://github.com/reteps/dockerfmt/issues/21}
|
||||
* @see {https://github.com/reteps/dockerfmt/issues/25}
|
||||
*/
|
||||
const processor = await getProcessorInstance()
|
||||
return processor(path.node, {
|
||||
print: true,
|
||||
filepath,
|
||||
keepComments,
|
||||
variant,
|
||||
stopAt,
|
||||
recoverErrors,
|
||||
useTabs,
|
||||
tabWidth,
|
||||
indent,
|
||||
binaryNextLine,
|
||||
switchCaseIndent,
|
||||
spaceRedirects: spaceRedirects ?? true,
|
||||
keepPadding,
|
||||
minify,
|
||||
singleLine,
|
||||
functionNextLine,
|
||||
})
|
||||
// Wait for initialization to complete
|
||||
await initShfmt();
|
||||
|
||||
const text = (path as any).getValue ? (path as any).getValue() : path.node;
|
||||
const config = getShfmtConfig(options);
|
||||
|
||||
// Format using shfmt (synchronous call)
|
||||
const formatted = format(text, config);
|
||||
|
||||
return formatted.trim();
|
||||
} catch (error) {
|
||||
console.warn('Shell formatting failed:', error);
|
||||
// Return original text if formatting fails
|
||||
return (path as any).getValue ? (path as any).getValue() : path.node;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Helper function to create shfmt config from Prettier options
|
||||
function getShfmtConfig(options: any): Config {
|
||||
const config: Config = {};
|
||||
|
||||
// Map Prettier options to shfmt config
|
||||
if (options.useTabs !== undefined) {
|
||||
config.useTabs = options.useTabs;
|
||||
}
|
||||
|
||||
if (options.tabWidth !== undefined) {
|
||||
config.tabWidth = options.tabWidth;
|
||||
}
|
||||
|
||||
if (options.printWidth !== undefined) {
|
||||
config.printWidth = options.printWidth;
|
||||
}
|
||||
|
||||
// Shell-specific options
|
||||
if (options.shVariant !== undefined) {
|
||||
config.variant = options.shVariant;
|
||||
}
|
||||
|
||||
if (options.shKeepComments !== undefined) {
|
||||
config.keepComments = options.shKeepComments;
|
||||
}
|
||||
|
||||
if (options.shBinaryNextLine !== undefined) {
|
||||
config.binaryNextLine = options.shBinaryNextLine;
|
||||
}
|
||||
|
||||
if (options.shSwitchCaseIndent !== undefined) {
|
||||
config.switchCaseIndent = options.shSwitchCaseIndent;
|
||||
}
|
||||
|
||||
if (options.shSpaceRedirects !== undefined) {
|
||||
config.spaceRedirects = options.shSpaceRedirects;
|
||||
}
|
||||
|
||||
if (options.shKeepPadding !== undefined) {
|
||||
config.keepPadding = options.shKeepPadding;
|
||||
}
|
||||
|
||||
if (options.shFunctionNextLine !== undefined) {
|
||||
config.functionNextLine = options.shFunctionNextLine;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
const shParser: Parser<Node> = {
|
||||
astFormat: 'sh',
|
||||
hasPragma,
|
||||
locStart: node => node.Pos.Offset,
|
||||
locEnd: node => node.End.Offset,
|
||||
async parse(
|
||||
text,
|
||||
{
|
||||
filepath = '',
|
||||
keepComments = true,
|
||||
/**
|
||||
* The following `@link` doesn't work as expected, see
|
||||
* {@link https://github.com/microsoft/tsdoc/issues/9}
|
||||
*/
|
||||
/** TODO: support {@link LangVariant.LangAuto} */ // eslint-disable-line sonarjs/todo-tag
|
||||
variant,
|
||||
stopAt,
|
||||
recoverErrors,
|
||||
}: ShParserOptions,
|
||||
) {
|
||||
const processor = await getProcessorInstance()
|
||||
return processor(text, {
|
||||
filepath,
|
||||
keepComments,
|
||||
variant,
|
||||
stopAt,
|
||||
recoverErrors,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const shPrinter: Printer<Node | string> = {
|
||||
// @ts-expect-error -- https://github.com/prettier/prettier/issues/15080#issuecomment-1630987744
|
||||
async print(
|
||||
path,
|
||||
{
|
||||
originalText,
|
||||
filepath,
|
||||
|
||||
// parser options
|
||||
keepComments = true,
|
||||
variant,
|
||||
stopAt,
|
||||
recoverErrors,
|
||||
|
||||
// printer options
|
||||
useTabs,
|
||||
tabWidth,
|
||||
indent = useTabs ? 0 : tabWidth,
|
||||
binaryNextLine = true,
|
||||
switchCaseIndent = true,
|
||||
spaceRedirects = true,
|
||||
// eslint-disable-next-line sonarjs/deprecation
|
||||
keepPadding,
|
||||
minify,
|
||||
singleLine,
|
||||
functionNextLine,
|
||||
}: ShPrintOptions,
|
||||
) {
|
||||
const processor = await getProcessorInstance()
|
||||
return processor(path.node as File, {
|
||||
originalText,
|
||||
filepath,
|
||||
keepComments,
|
||||
variant,
|
||||
stopAt,
|
||||
recoverErrors,
|
||||
useTabs,
|
||||
tabWidth,
|
||||
indent,
|
||||
binaryNextLine,
|
||||
switchCaseIndent,
|
||||
spaceRedirects,
|
||||
keepPadding,
|
||||
minify,
|
||||
singleLine,
|
||||
functionNextLine,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export const parsers = {
|
||||
dockerfile: dockerfileParser,
|
||||
sh: shParser,
|
||||
}
|
||||
|
||||
export const printers = {
|
||||
dockerfile: dockerPrinter,
|
||||
sh: shPrinter,
|
||||
}
|
||||
|
||||
export const options: Plugin['options'] = {
|
||||
keepComments: {
|
||||
// since: '0.1.0',
|
||||
category: 'Output',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'KeepComments makes the parser parse comments and attach them to nodes, as opposed to discarding them.',
|
||||
},
|
||||
variant: {
|
||||
// since: '0.1.0',
|
||||
category: 'Config',
|
||||
type: 'choice',
|
||||
// Plugin options
|
||||
const options = {
|
||||
shVariant: {
|
||||
since: '0.0.1',
|
||||
category: 'Format' as const,
|
||||
type: 'choice' as const,
|
||||
default: 'bash',
|
||||
description: 'Shell variant to use for formatting',
|
||||
choices: [
|
||||
{
|
||||
value: LangVariant.LangBash,
|
||||
description: [
|
||||
'LangBash corresponds to the GNU Bash language, as described in its manual at https://www.gnu.org/software/bash/manual/bash.html.',
|
||||
'',
|
||||
'We currently follow Bash version 5.2.',
|
||||
'',
|
||||
'Its string representation is "bash".',
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
value: LangVariant.LangPOSIX,
|
||||
description: [
|
||||
'LangPOSIX corresponds to the POSIX Shell language, as described at https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html.',
|
||||
'',
|
||||
'Its string representation is "posix" or "sh".',
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
value: LangVariant.LangMirBSDKorn,
|
||||
description: [
|
||||
'LangMirBSDKorn corresponds to the MirBSD Korn Shell, also known as mksh, as described at http://www.mirbsd.org/htman/i386/man1/mksh.htm.',
|
||||
'Note that it shares some features with Bash, due to the shared ancestry that is ksh.',
|
||||
'',
|
||||
'We currently follow mksh version 59.',
|
||||
'',
|
||||
'Its string representation is "mksh".',
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
value: LangVariant.LangBats,
|
||||
description: [
|
||||
'LangBats corresponds to the Bash Automated Testing System language, as described at https://github.com/bats-core/bats-core.',
|
||||
"Note that it's just a small extension of the Bash language.",
|
||||
'',
|
||||
'Its string representation is "bats".',
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
value: LangVariant.LangAuto,
|
||||
description: [
|
||||
"LangAuto corresponds to automatic language detection, commonly used by end-user applications like shfmt, which can guess a file's language variant given its filename or shebang.",
|
||||
'',
|
||||
'At this time, [Variant] does not support LangAuto.',
|
||||
].join('\n'),
|
||||
},
|
||||
],
|
||||
description:
|
||||
'Variant changes the shell language variant that the parser will accept.',
|
||||
{ value: 'bash', description: 'Bash shell' },
|
||||
{ value: 'posix', description: 'POSIX shell' },
|
||||
{ value: 'mksh', description: 'MirBSD Korn shell' },
|
||||
{ value: 'bats', description: 'Bats testing framework' }
|
||||
]
|
||||
},
|
||||
stopAt: {
|
||||
// since: '0.1.0',
|
||||
category: 'Config',
|
||||
type: 'path',
|
||||
description: [
|
||||
'StopAt configures the lexer to stop at an arbitrary word, treating it as if it were the end of the input. It can contain any characters except whitespace, and cannot be over four bytes in size.',
|
||||
'This can be useful to embed shell code within another language, as one can use a special word to mark the delimiters between the two.',
|
||||
'As a word, it will only apply when following whitespace or a separating token. For example, StopAt("$$") will act on the inputs "foo $$" and "foo;$$", but not on "foo \'$$\'".',
|
||||
'The match is done by prefix, so the example above will also act on "foo $$bar".',
|
||||
].join('\n'),
|
||||
},
|
||||
recoverErrors: {
|
||||
// since: '0.17.0',
|
||||
category: 'Config',
|
||||
type: 'path',
|
||||
description: [
|
||||
'RecoverErrors allows the parser to skip up to a maximum number of errors in the given input on a best-effort basis.',
|
||||
'This can be useful to tab-complete an interactive shell prompt, or when providing diagnostics on slightly incomplete shell source.',
|
||||
'',
|
||||
'Currently, this only helps with mandatory tokens from the shell grammar which are not present in the input. They result in position fields or nodes whose position report [Pos.IsRecovered] as true.',
|
||||
'',
|
||||
'For example, given the input `(foo |`, the result will contain two recovered positions; first, the pipe requires a statement to follow, and as [Stmt.Pos] reports, the entire node is recovered.',
|
||||
'Second, the subshell needs to be closed, so [Subshell.Rparen] is recovered.',
|
||||
].join('\n'),
|
||||
},
|
||||
indent: {
|
||||
// since: '0.1.0',
|
||||
category: 'Format',
|
||||
type: 'int',
|
||||
description:
|
||||
'Indent sets the number of spaces used for indentation. If set to 0, tabs will be used instead.',
|
||||
},
|
||||
binaryNextLine: {
|
||||
// since: '0.1.0',
|
||||
category: 'Output',
|
||||
type: 'boolean',
|
||||
shKeepComments: {
|
||||
since: '0.0.1',
|
||||
category: 'Format' as const,
|
||||
type: 'boolean' as const,
|
||||
default: true,
|
||||
description:
|
||||
'BinaryNextLine will make binary operators appear on the next line when a binary command, such as a pipe, spans multiple lines. A backslash will be used.',
|
||||
description: 'Keep comments in formatted output'
|
||||
},
|
||||
switchCaseIndent: {
|
||||
// since: '0.1.0',
|
||||
category: 'Format',
|
||||
type: 'boolean',
|
||||
shBinaryNextLine: {
|
||||
since: '0.0.1',
|
||||
category: 'Format' as const,
|
||||
type: 'boolean' as const,
|
||||
default: true,
|
||||
description:
|
||||
'SwitchCaseIndent will make switch cases be indented. As such, switch case bodies will be two levels deeper than the switch itself.',
|
||||
description: 'Place binary operators on next line'
|
||||
},
|
||||
spaceRedirects: {
|
||||
// since: '0.1.0',
|
||||
category: 'Format',
|
||||
type: 'boolean',
|
||||
shSwitchCaseIndent: {
|
||||
since: '0.0.1',
|
||||
category: 'Format' as const,
|
||||
type: 'boolean' as const,
|
||||
default: true,
|
||||
description:
|
||||
"SpaceRedirects will put a space after most redirection operators. The exceptions are '>&', '<&', '>(', and '<('.",
|
||||
description: 'Indent switch case statements'
|
||||
},
|
||||
keepPadding: {
|
||||
// since: '0.1.0',
|
||||
category: 'Format',
|
||||
type: 'boolean',
|
||||
shSpaceRedirects: {
|
||||
since: '0.0.1',
|
||||
category: 'Format' as const,
|
||||
type: 'boolean' as const,
|
||||
default: true,
|
||||
description: 'Add spaces around redirects'
|
||||
},
|
||||
shKeepPadding: {
|
||||
since: '0.0.1',
|
||||
category: 'Format' as const,
|
||||
type: 'boolean' as const,
|
||||
default: false,
|
||||
description: [
|
||||
'KeepPadding will keep most nodes and tokens in the same column that they were in the original source.',
|
||||
'This allows the user to decide how to align and pad their code with spaces.',
|
||||
'',
|
||||
'Note that this feature is best-effort and will only keep the alignment stable, so it may need some human help the first time it is run.',
|
||||
].join('\n'),
|
||||
deprecated: [
|
||||
'This formatting option is flawed and buggy, and often does not result in what the user wants when the code gets complex enough.',
|
||||
'The next major version, v4, will remove this feature entirely.',
|
||||
'See: https://github.com/mvdan/sh/issues/658',
|
||||
].join('\n'),
|
||||
description: 'Keep padding in column alignment'
|
||||
},
|
||||
minify: {
|
||||
// since: '0.1.0',
|
||||
category: 'Output',
|
||||
type: 'boolean',
|
||||
shFunctionNextLine: {
|
||||
since: '0.0.1',
|
||||
category: 'Format' as const,
|
||||
type: 'boolean' as const,
|
||||
default: false,
|
||||
description: [
|
||||
'Minify will print programs in a way to save the most bytes possible.',
|
||||
'For example, indentation and comments are skipped, and extra whitespace is avoided when possible.',
|
||||
].join('\n'),
|
||||
},
|
||||
singleLine: {
|
||||
// since: '0.17.0',
|
||||
category: 'Format',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: [
|
||||
'SingleLine will attempt to print programs in one line. For example, lists of commands or nested blocks do not use newlines in this mode.',
|
||||
'Note that some newlines must still appear, such as those following comments or around here-documents.',
|
||||
'',
|
||||
"Print's trailing newline when given a [*File] is not affected by this option.",
|
||||
].join('\n'),
|
||||
},
|
||||
functionNextLine: {
|
||||
// since: '0.1.0',
|
||||
category: 'Format',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
"FunctionNextLine will place a function's opening braces on the next line.",
|
||||
},
|
||||
}
|
||||
description: 'Place function opening brace on next line'
|
||||
}
|
||||
};
|
||||
|
||||
const shellPlugin: Plugin = {
|
||||
// Plugin definition
|
||||
const shPlugin: Plugin = {
|
||||
languages,
|
||||
parsers,
|
||||
printers,
|
||||
parsers: {
|
||||
[parserName]: shParser,
|
||||
},
|
||||
printers: {
|
||||
[parserName]: shPrinter,
|
||||
},
|
||||
options,
|
||||
}
|
||||
};
|
||||
|
||||
export default shellPlugin
|
||||
export { languages }
|
||||
// Export plugin without auto-initialization
|
||||
export default shPlugin;
|
||||
export { languages, initShfmt as initialize };
|
||||
export const parsers = shPlugin.parsers;
|
||||
export const printers = shPlugin.printers;
|
||||
@@ -1,60 +0,0 @@
|
||||
export const languages = [
|
||||
{
|
||||
name: "Shell",
|
||||
parsers: ["sh"],
|
||||
extensions: [
|
||||
".sh",
|
||||
".bash",
|
||||
".zsh",
|
||||
".fish",
|
||||
".ksh",
|
||||
".csh",
|
||||
".tcsh",
|
||||
".ash",
|
||||
".dash"
|
||||
],
|
||||
filenames: [
|
||||
"*.sh",
|
||||
"*.bash",
|
||||
".bashrc",
|
||||
".bash_profile",
|
||||
".bash_login",
|
||||
".bash_logout",
|
||||
".zshrc",
|
||||
".profile"
|
||||
],
|
||||
interpreters: [
|
||||
"bash",
|
||||
"sh",
|
||||
"zsh",
|
||||
"fish",
|
||||
"ksh",
|
||||
"csh",
|
||||
"tcsh",
|
||||
"ash",
|
||||
"dash"
|
||||
],
|
||||
tmScope: "source.shell",
|
||||
aceMode: "sh",
|
||||
codemirrorMode: "shell",
|
||||
linguistLanguageId: 302,
|
||||
vscodeLanguageIds: ["shellscript"]
|
||||
},
|
||||
{
|
||||
name: "Dockerfile",
|
||||
parsers: ["dockerfile"],
|
||||
extensions: [".dockerfile"],
|
||||
filenames: [
|
||||
"Dockerfile",
|
||||
"*.dockerfile",
|
||||
"Containerfile",
|
||||
"*.containerfile"
|
||||
],
|
||||
tmScope: "source.dockerfile",
|
||||
aceMode: "dockerfile",
|
||||
codemirrorMode: "dockerfile",
|
||||
linguistLanguageId: 99,
|
||||
vscodeLanguageIds: ["dockerfile"]
|
||||
}
|
||||
];
|
||||
|
||||
51
frontend/src/common/prettier/plugins/shell/shfmt.d.ts
vendored
Normal file
51
frontend/src/common/prettier/plugins/shell/shfmt.d.ts
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* TypeScript definitions for Shell formatter WASM module
|
||||
*/
|
||||
|
||||
// Language variants enum
|
||||
export declare const LangVariant: {
|
||||
readonly LangBash: 0;
|
||||
readonly LangPOSIX: 1;
|
||||
readonly LangMirBSDKorn: 2;
|
||||
readonly LangBats: 3;
|
||||
readonly LangAuto: 4;
|
||||
};
|
||||
|
||||
// Configuration interface
|
||||
export interface Config {
|
||||
useTabs?: boolean;
|
||||
tabWidth?: number;
|
||||
printWidth?: number;
|
||||
variant?: number;
|
||||
keepComments?: boolean;
|
||||
binaryNextLine?: boolean;
|
||||
switchCaseIndent?: boolean;
|
||||
spaceRedirects?: boolean;
|
||||
keepPadding?: boolean;
|
||||
functionNextLine?: boolean;
|
||||
}
|
||||
|
||||
// Parse error class
|
||||
export declare class ParseError extends Error {
|
||||
Filename?: string;
|
||||
Incomplete?: boolean;
|
||||
Text: string;
|
||||
Pos?: any;
|
||||
|
||||
constructor(params: {
|
||||
Filename?: string;
|
||||
Incomplete?: boolean;
|
||||
Text: string;
|
||||
Pos?: any;
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize the WASM module
|
||||
declare function init(wasmUrl?: string): Promise<void>;
|
||||
export default init;
|
||||
|
||||
// Format shell code
|
||||
export declare function format(text: string, config?: Config): string;
|
||||
|
||||
// Parse shell code (returns AST)
|
||||
export declare function parse(text: string, config?: Config): any;
|
||||
245
frontend/src/common/prettier/plugins/shell/shfmt.js
Normal file
245
frontend/src/common/prettier/plugins/shell/shfmt.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Shell formatter WASM module wrapper
|
||||
* Based on the existing src implementation but adapted for browser use
|
||||
*/
|
||||
|
||||
// Import WASM execution environment
|
||||
import './wasm_exec.cjs';
|
||||
|
||||
// Language variants enum
|
||||
export const LangVariant = {
|
||||
LangBash: 0,
|
||||
LangPOSIX: 1,
|
||||
LangMirBSDKorn: 2,
|
||||
LangBats: 3,
|
||||
LangAuto: 4,
|
||||
};
|
||||
|
||||
// Parse error class
|
||||
export class ParseError extends Error {
|
||||
constructor({ Filename, Incomplete, Text, Pos }) {
|
||||
super(Text);
|
||||
this.Filename = Filename;
|
||||
this.Incomplete = Incomplete;
|
||||
this.Text = Text;
|
||||
this.Pos = Pos;
|
||||
}
|
||||
}
|
||||
|
||||
let encoder;
|
||||
let decoder;
|
||||
let wasmInstance = null;
|
||||
let isInitialized = false;
|
||||
|
||||
// Initialize the WASM module
|
||||
export default async function init(wasmUrl) {
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
encoder = new TextEncoder();
|
||||
decoder = new TextDecoder();
|
||||
|
||||
try {
|
||||
// Load WASM file
|
||||
const wasmPath = wasmUrl || new URL('./shfmt.wasm', import.meta.url).href;
|
||||
const wasmResponse = await fetch(wasmPath);
|
||||
const wasmBytes = await wasmResponse.arrayBuffer();
|
||||
|
||||
// Initialize Go runtime
|
||||
const go = new ShellGo();
|
||||
const result = await WebAssembly.instantiate(wasmBytes, go.importObject);
|
||||
|
||||
wasmInstance = result.instance;
|
||||
|
||||
// Run the Go program
|
||||
go.run(wasmInstance);
|
||||
|
||||
isInitialized = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize shfmt WASM module:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Format shell code
|
||||
export function format(text, config = {}) {
|
||||
if (!isInitialized || !wasmInstance) {
|
||||
throw new Error('WASM module not initialized. Call init() first.');
|
||||
}
|
||||
|
||||
const {
|
||||
variant = LangVariant.LangBash,
|
||||
keepComments = true,
|
||||
useTabs = false,
|
||||
tabWidth = 2,
|
||||
binaryNextLine = true,
|
||||
switchCaseIndent = true,
|
||||
spaceRedirects = true,
|
||||
keepPadding = false,
|
||||
functionNextLine = false
|
||||
} = config;
|
||||
|
||||
const indent = useTabs ? 0 : tabWidth;
|
||||
|
||||
try {
|
||||
const { memory, wasmAlloc, wasmFree, process } = wasmInstance.exports;
|
||||
|
||||
// Encode input text
|
||||
const encodedText = encoder.encode(text);
|
||||
const encodedFilePath = encoder.encode('input.sh');
|
||||
const encodedStopAt = encoder.encode('');
|
||||
|
||||
// Allocate memory for inputs
|
||||
const filePathPointer = wasmAlloc(encodedFilePath.byteLength);
|
||||
new Uint8Array(memory.buffer).set(encodedFilePath, filePathPointer);
|
||||
|
||||
const textPointer = wasmAlloc(encodedText.byteLength);
|
||||
new Uint8Array(memory.buffer).set(encodedText, textPointer);
|
||||
|
||||
const stopAtPointer = wasmAlloc(encodedStopAt.byteLength);
|
||||
new Uint8Array(memory.buffer).set(encodedStopAt, stopAtPointer);
|
||||
|
||||
// Call the process function
|
||||
const resultPointer = process(
|
||||
filePathPointer, encodedFilePath.byteLength, encodedFilePath.byteLength,
|
||||
textPointer, encodedText.byteLength, encodedText.byteLength,
|
||||
true, // print mode
|
||||
keepComments,
|
||||
variant,
|
||||
stopAtPointer, encodedStopAt.byteLength, encodedStopAt.byteLength,
|
||||
0, // recoverErrors
|
||||
indent,
|
||||
binaryNextLine,
|
||||
switchCaseIndent,
|
||||
spaceRedirects,
|
||||
keepPadding,
|
||||
false, // minify
|
||||
false, // singleLine
|
||||
functionNextLine
|
||||
);
|
||||
|
||||
// Free allocated memory
|
||||
wasmFree(filePathPointer);
|
||||
wasmFree(textPointer);
|
||||
wasmFree(stopAtPointer);
|
||||
|
||||
// Read result
|
||||
const result = new Uint8Array(memory.buffer).subarray(resultPointer);
|
||||
const end = result.indexOf(0);
|
||||
const resultString = decoder.decode(result.subarray(0, end));
|
||||
|
||||
// Parse result
|
||||
if (!resultString.startsWith('{"') || !resultString.endsWith('}')) {
|
||||
throw new ParseError({
|
||||
Filename: 'input.sh',
|
||||
Incomplete: true,
|
||||
Text: resultString,
|
||||
});
|
||||
}
|
||||
|
||||
const { file, text: processedText, parseError, message } = JSON.parse(resultString);
|
||||
|
||||
if (parseError || message) {
|
||||
throw parseError == null
|
||||
? new SyntaxError(message)
|
||||
: new ParseError(parseError);
|
||||
}
|
||||
|
||||
return processedText || text;
|
||||
} catch (error) {
|
||||
console.warn('Shell formatting error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse shell code (returns AST)
|
||||
export function parse(text, config = {}) {
|
||||
if (!isInitialized || !wasmInstance) {
|
||||
throw new Error('WASM module not initialized. Call init() first.');
|
||||
}
|
||||
|
||||
const {
|
||||
variant = LangVariant.LangBash,
|
||||
keepComments = true,
|
||||
useTabs = false,
|
||||
tabWidth = 2,
|
||||
binaryNextLine = true,
|
||||
switchCaseIndent = true,
|
||||
spaceRedirects = true,
|
||||
keepPadding = false,
|
||||
functionNextLine = false
|
||||
} = config;
|
||||
|
||||
const indent = useTabs ? 0 : tabWidth;
|
||||
|
||||
try {
|
||||
const { memory, wasmAlloc, wasmFree, process } = wasmInstance.exports;
|
||||
|
||||
// Encode input text
|
||||
const encodedText = encoder.encode(text);
|
||||
const encodedFilePath = encoder.encode('input.sh');
|
||||
const encodedStopAt = encoder.encode('');
|
||||
|
||||
// Allocate memory for inputs
|
||||
const filePathPointer = wasmAlloc(encodedFilePath.byteLength);
|
||||
new Uint8Array(memory.buffer).set(encodedFilePath, filePathPointer);
|
||||
|
||||
const textPointer = wasmAlloc(encodedText.byteLength);
|
||||
new Uint8Array(memory.buffer).set(encodedText, textPointer);
|
||||
|
||||
const stopAtPointer = wasmAlloc(encodedStopAt.byteLength);
|
||||
new Uint8Array(memory.buffer).set(encodedStopAt, stopAtPointer);
|
||||
|
||||
// Call the process function
|
||||
const resultPointer = process(
|
||||
filePathPointer, encodedFilePath.byteLength, encodedFilePath.byteLength,
|
||||
textPointer, encodedText.byteLength, encodedText.byteLength,
|
||||
false, // parse mode
|
||||
keepComments,
|
||||
variant,
|
||||
stopAtPointer, encodedStopAt.byteLength, encodedStopAt.byteLength,
|
||||
0, // recoverErrors
|
||||
indent,
|
||||
binaryNextLine,
|
||||
switchCaseIndent,
|
||||
spaceRedirects,
|
||||
keepPadding,
|
||||
false, // minify
|
||||
false, // singleLine
|
||||
functionNextLine
|
||||
);
|
||||
|
||||
// Free allocated memory
|
||||
wasmFree(filePathPointer);
|
||||
wasmFree(textPointer);
|
||||
wasmFree(stopAtPointer);
|
||||
|
||||
// Read result
|
||||
const result = new Uint8Array(memory.buffer).subarray(resultPointer);
|
||||
const end = result.indexOf(0);
|
||||
const resultString = decoder.decode(result.subarray(0, end));
|
||||
|
||||
// Parse result
|
||||
if (!resultString.startsWith('{"') || !resultString.endsWith('}')) {
|
||||
throw new ParseError({
|
||||
Filename: 'input.sh',
|
||||
Incomplete: true,
|
||||
Text: resultString,
|
||||
});
|
||||
}
|
||||
|
||||
const { file, text: processedText, parseError, message } = JSON.parse(resultString);
|
||||
|
||||
if (parseError || message) {
|
||||
throw parseError == null
|
||||
? new SyntaxError(message)
|
||||
: new ParseError(parseError);
|
||||
}
|
||||
|
||||
return file;
|
||||
} catch (error) {
|
||||
console.warn('Shell parsing error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
BIN
frontend/src/common/prettier/plugins/shell/shfmt.wasm
Normal file
BIN
frontend/src/common/prettier/plugins/shell/shfmt.wasm
Normal file
Binary file not shown.
8
frontend/src/common/prettier/plugins/shell/shfmt_vite.js
Normal file
8
frontend/src/common/prettier/plugins/shell/shfmt_vite.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import initAsync from './shfmt.js'
|
||||
import wasm_url from './shfmt.wasm?url'
|
||||
|
||||
export default function init() {
|
||||
return initAsync(wasm_url)
|
||||
}
|
||||
|
||||
export * from './shfmt.js'
|
||||
10
frontend/src/common/prettier/plugins/shell/src/go.mod
Normal file
10
frontend/src/common/prettier/plugins/shell/src/go.mod
Normal file
@@ -0,0 +1,10 @@
|
||||
module shell_fmt
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/mailru/easyjson v0.9.1
|
||||
mvdan.cc/sh/v3 v3.12.0
|
||||
)
|
||||
|
||||
require github.com/josharian/intern v1.0.0 // indirect
|
||||
16
frontend/src/common/prettier/plugins/shell/src/go.sum
Normal file
16
frontend/src/common/prettier/plugins/shell/src/go.sum
Normal file
@@ -0,0 +1,16 @@
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
|
||||
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
|
||||
95
frontend/src/common/prettier/plugins/shell/src/main.go
Normal file
95
frontend/src/common/prettier/plugins/shell/src/main.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// tinygo build -o shfmt.wasm -target=wasm --no-debug
|
||||
package src
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
var (
|
||||
parser *syntax.Parser
|
||||
printer *syntax.Printer
|
||||
)
|
||||
|
||||
type ParserOptions struct {
|
||||
KeepComments bool
|
||||
Variant syntax.LangVariant
|
||||
StopAt string
|
||||
RecoverErrors int
|
||||
}
|
||||
|
||||
type PrinterOptions struct {
|
||||
Indent uint
|
||||
BinaryNextLine bool
|
||||
SwitchCaseIndent bool
|
||||
SpaceRedirects bool
|
||||
KeepPadding bool
|
||||
Minify bool
|
||||
SingleLine bool
|
||||
FunctionNextLine bool
|
||||
}
|
||||
|
||||
type SyntaxOptions struct {
|
||||
ParserOptions
|
||||
PrinterOptions
|
||||
}
|
||||
|
||||
// `Parse` converts shell script text into a structured syntax tree.
|
||||
// It assembles parser options based on the provided configuration—such as whether to keep comments,
|
||||
// the shell syntax variant to use, an optional stopping point, and the desired error recovery level.
|
||||
// The supplied file path is used for contextual error reporting.
|
||||
// It returns a syntax.File representing the parsed script, or an error if parsing fails.
|
||||
func Parse(text string, filepath string, parserOptions ParserOptions) (*syntax.File, error) {
|
||||
var options []syntax.ParserOption
|
||||
|
||||
options = append(options, syntax.KeepComments(parserOptions.KeepComments), syntax.Variant(parserOptions.Variant))
|
||||
|
||||
if parserOptions.StopAt != "" {
|
||||
options = append(options, syntax.StopAt(parserOptions.StopAt))
|
||||
}
|
||||
|
||||
if parserOptions.RecoverErrors != 0 {
|
||||
options = append(options, syntax.RecoverErrors(parserOptions.RecoverErrors))
|
||||
}
|
||||
|
||||
parser = syntax.NewParser(options...)
|
||||
|
||||
return parser.Parse(bytes.NewReader([]byte(text)), filepath)
|
||||
}
|
||||
|
||||
// `Print` returns the formatted shell script defined in originalText.
|
||||
// It first parses the input using the parser options in syntaxOptions and then prints the resulting
|
||||
// syntax tree using printer options—including indentation, single-line formatting, and others.
|
||||
// The filepath parameter is used for context in error messages. On success, Print returns the formatted
|
||||
// script as a string, or an error if parsing or printing fails.
|
||||
func Print(originalText string, filepath string, syntaxOptions SyntaxOptions) (string, error) {
|
||||
file, err := Parse(originalText, filepath, syntaxOptions.ParserOptions)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
printer = syntax.NewPrinter(
|
||||
syntax.Indent(syntaxOptions.Indent),
|
||||
syntax.BinaryNextLine(syntaxOptions.BinaryNextLine),
|
||||
syntax.SwitchCaseIndent(syntaxOptions.SwitchCaseIndent),
|
||||
syntax.SpaceRedirects(syntaxOptions.SpaceRedirects),
|
||||
syntax.KeepPadding(syntaxOptions.KeepPadding),
|
||||
syntax.Minify(syntaxOptions.Minify),
|
||||
syntax.SingleLine(syntaxOptions.SingleLine),
|
||||
syntax.FunctionNextLine(syntaxOptions.FunctionNextLine),
|
||||
)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := io.Writer(&buf)
|
||||
|
||||
err = printer.Print(writer, file)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), err
|
||||
}
|
||||
219
frontend/src/common/prettier/plugins/shell/src/structs.go
Normal file
219
frontend/src/common/prettier/plugins/shell/src/structs.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
type Pos struct {
|
||||
Offset uint
|
||||
Line uint
|
||||
Col uint
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Pos Pos
|
||||
End Pos
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
Hash Pos
|
||||
Text string
|
||||
Pos Pos
|
||||
End Pos
|
||||
}
|
||||
|
||||
type Word struct {
|
||||
Parts []Node
|
||||
Lit string
|
||||
Pos Pos
|
||||
End Pos
|
||||
}
|
||||
|
||||
type Lit struct {
|
||||
ValuePos Pos
|
||||
ValueEnd Pos
|
||||
Value string
|
||||
Pos Pos
|
||||
End Pos
|
||||
}
|
||||
|
||||
type Redirect struct {
|
||||
OpPos Pos
|
||||
Op string
|
||||
N *Lit
|
||||
Word *Word
|
||||
Hdoc *Word
|
||||
Pos Pos
|
||||
End Pos
|
||||
}
|
||||
|
||||
type Stmt struct {
|
||||
Comments []Comment
|
||||
Cmd *Node
|
||||
Position Pos
|
||||
Semicolon Pos
|
||||
Negated bool
|
||||
Background bool
|
||||
Coprocess bool
|
||||
Redirs []Redirect
|
||||
Pos Pos
|
||||
End Pos
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Name string
|
||||
Stmt []Stmt
|
||||
Last []Comment
|
||||
Pos Pos
|
||||
End Pos
|
||||
}
|
||||
|
||||
type ParseError struct {
|
||||
syntax.ParseError
|
||||
Pos Pos
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
File `json:"file"`
|
||||
Text string `json:"text"`
|
||||
*ParseError `json:"parseError"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func MapParseError(err error) (*ParseError, string) {
|
||||
if err == nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
parseError, ok := err.(syntax.ParseError)
|
||||
|
||||
if ok {
|
||||
return &ParseError{
|
||||
ParseError: parseError,
|
||||
Pos: mapPos(parseError.Pos),
|
||||
}, parseError.Error()
|
||||
}
|
||||
|
||||
return nil, err.Error()
|
||||
}
|
||||
|
||||
func mapPos(pos syntax.Pos) Pos {
|
||||
return Pos{
|
||||
Offset: pos.Offset(),
|
||||
Line: pos.Line(),
|
||||
Col: pos.Col(),
|
||||
}
|
||||
}
|
||||
|
||||
func mapNode(node syntax.Node) *Node {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &Node{
|
||||
Pos: mapPos(node.Pos()),
|
||||
End: mapPos(node.End()),
|
||||
}
|
||||
}
|
||||
|
||||
// `mapComments` transforms a slice of syntax.Comment into a slice of Comment by converting each comment's hash, text, start, and end positions using mapPos. It preserves the order of the comments and returns an empty slice if the input is nil or empty.
|
||||
func mapComments(comments []syntax.Comment) []Comment {
|
||||
commentsSize := len(comments)
|
||||
commentList := make([]Comment, commentsSize)
|
||||
for i := range commentsSize {
|
||||
curr := comments[i]
|
||||
commentList[i] = Comment{
|
||||
Hash: mapPos(curr.Hash),
|
||||
Text: curr.Text,
|
||||
Pos: mapPos(curr.Pos()),
|
||||
End: mapPos(curr.End()),
|
||||
}
|
||||
}
|
||||
return commentList
|
||||
}
|
||||
|
||||
// `mapWord` converts a *syntax.Word into a custom *Word structure. It maps each part of the syntax.Word using mapNode,
|
||||
// extracts the literal via Lit(), and maps the start and end positions using mapPos. If the input word is nil, it returns nil.
|
||||
func mapWord(word *syntax.Word) *Word {
|
||||
if word == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
size := len(word.Parts)
|
||||
parts := make([]Node, size)
|
||||
|
||||
for i := range size {
|
||||
parts[i] = *mapNode(word.Parts[i])
|
||||
}
|
||||
|
||||
return &Word{
|
||||
Parts: parts,
|
||||
Lit: word.Lit(),
|
||||
Pos: mapPos(word.Pos()),
|
||||
End: mapPos(word.End()),
|
||||
}
|
||||
}
|
||||
|
||||
// `mapRedirects` converts a slice of syntax.Redirect pointers into a slice of custom Redirect structures.
|
||||
// It maps each redirect’s operator position, associated literal (if present), word, heredoc, and overall positional data using helper functions.
|
||||
// If the literal component (N) is non-nil, it is transformed into a Lit structure that encapsulates both its value and positional information.
|
||||
func mapRedirects(redirects []*syntax.Redirect) []Redirect {
|
||||
redirsSize := len(redirects)
|
||||
redirs := make([]Redirect, redirsSize)
|
||||
for i := range redirsSize {
|
||||
curr := redirects[i]
|
||||
var N *Lit
|
||||
if curr.N != nil {
|
||||
ValuePos := mapPos(curr.N.Pos())
|
||||
ValueEnd := mapPos(curr.N.End())
|
||||
N = &Lit{
|
||||
ValuePos: ValuePos,
|
||||
ValueEnd: ValueEnd,
|
||||
Value: curr.N.Value,
|
||||
Pos: ValuePos,
|
||||
End: ValueEnd,
|
||||
}
|
||||
}
|
||||
redirs[i] = Redirect{
|
||||
OpPos: mapPos(curr.OpPos),
|
||||
Op: curr.Op.String(),
|
||||
N: N,
|
||||
Word: mapWord(curr.Word),
|
||||
Hdoc: mapWord(curr.Hdoc),
|
||||
Pos: mapPos(curr.Pos()),
|
||||
End: mapPos(curr.End()),
|
||||
}
|
||||
}
|
||||
return redirs
|
||||
}
|
||||
|
||||
// `mapStmts` converts a slice of *syntax.Stmt into a slice of Stmt by mapping each statement's components—including comments, command node, positional information, semicolon, redirections, and execution flags (negated, background, coprocess).
|
||||
func mapStmts(stmts []*syntax.Stmt) []Stmt {
|
||||
stmtsSize := len(stmts)
|
||||
stmtList := make([]Stmt, stmtsSize)
|
||||
for i := range stmtsSize {
|
||||
curr := stmts[i]
|
||||
stmtList[i] = Stmt{
|
||||
Comments: mapComments(curr.Comments),
|
||||
Cmd: mapNode(curr.Cmd),
|
||||
Position: mapPos(curr.Position),
|
||||
Semicolon: mapPos(curr.Semicolon),
|
||||
Negated: curr.Negated,
|
||||
Background: curr.Background,
|
||||
Coprocess: curr.Coprocess,
|
||||
Redirs: mapRedirects(curr.Redirs),
|
||||
Pos: mapPos(curr.Pos()),
|
||||
End: mapPos(curr.End()),
|
||||
}
|
||||
}
|
||||
return stmtList
|
||||
}
|
||||
|
||||
func MapFile(file syntax.File) File {
|
||||
return File{
|
||||
Name: file.Name,
|
||||
Stmt: mapStmts(file.Stmts),
|
||||
Last: mapComments(file.Last),
|
||||
Pos: mapPos(file.Pos()),
|
||||
End: mapPos(file.End()),
|
||||
}
|
||||
}
|
||||
1166
frontend/src/common/prettier/plugins/shell/src/structs_easyjson.go
Normal file
1166
frontend/src/common/prettier/plugins/shell/src/structs_easyjson.go
Normal file
File diff suppressed because it is too large
Load Diff
533
frontend/src/common/prettier/plugins/shell/wasm_exec.cjs
Normal file
533
frontend/src/common/prettier/plugins/shell/wasm_exec.cjs
Normal file
@@ -0,0 +1,533 @@
|
||||
// modified based on https://github.com/tinygo-org/tinygo/blob/3e60eeb368f25f237a512e7553fd6d70f36dc74c/targets/wasm_exec.js
|
||||
|
||||
// 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.
|
||||
//
|
||||
// This file has been modified for use by the TinyGo compiler.
|
||||
|
||||
(() => {
|
||||
// Map multiple JavaScript environments to a single common API,
|
||||
// preferring web standards over Node.js API.
|
||||
//
|
||||
// Environments considered:
|
||||
// - Browsers
|
||||
// - Node.js
|
||||
// - Electron
|
||||
// - Parcel
|
||||
|
||||
if (typeof global !== "undefined") {
|
||||
// global already exists
|
||||
} else if (typeof window !== "undefined") {
|
||||
window.global = window;
|
||||
} else if (typeof self !== "undefined") {
|
||||
self.global = self;
|
||||
} else {
|
||||
throw new Error("cannot export Go (neither global, window nor self is defined)");
|
||||
}
|
||||
|
||||
if (!global.require && typeof require !== "undefined") {
|
||||
global.require = require;
|
||||
}
|
||||
|
||||
if (!global.fs && global.require) {
|
||||
global.fs = require("fs");
|
||||
}
|
||||
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!global.fs) {
|
||||
let outputBuf = "";
|
||||
global.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.substr(0, nl));
|
||||
outputBuf = outputBuf.substr(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 (!global.process) {
|
||||
global.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 (!global.crypto) {
|
||||
const nodeCrypto = require("crypto");
|
||||
global.crypto = {
|
||||
getRandomValues(b) {
|
||||
nodeCrypto.randomFillSync(b);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.performance) {
|
||||
global.performance = {
|
||||
now() {
|
||||
const [sec, nsec] = process.hrtime();
|
||||
return sec * 1000 + nsec / 1000000;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.TextEncoder) {
|
||||
global.TextEncoder = require("util").TextEncoder;
|
||||
}
|
||||
|
||||
if (!global.TextDecoder) {
|
||||
global.TextDecoder = require("util").TextDecoder;
|
||||
}
|
||||
|
||||
// End of polyfills for common API.
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
let reinterpretBuf = new DataView(new ArrayBuffer(8));
|
||||
var logLine = [];
|
||||
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
|
||||
|
||||
global.ShellGo = class {
|
||||
constructor() {
|
||||
this._callbackTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const mem = () => {
|
||||
// The buffer may change when requesting more memory.
|
||||
return new DataView(this._inst.exports.memory.buffer);
|
||||
}
|
||||
|
||||
const unboxValue = (v_ref) => {
|
||||
reinterpretBuf.setBigInt64(0, v_ref, true);
|
||||
const f = reinterpretBuf.getFloat64(0, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = v_ref & 0xffffffffn;
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
|
||||
const loadValue = (addr) => {
|
||||
let v_ref = mem().getBigUint64(addr, true);
|
||||
return unboxValue(v_ref);
|
||||
}
|
||||
|
||||
const boxValue = (v) => {
|
||||
const nanHead = 0x7FF80000n;
|
||||
|
||||
if (typeof v === "number") {
|
||||
if (isNaN(v)) {
|
||||
return nanHead << 32n;
|
||||
}
|
||||
if (v === 0) {
|
||||
return (nanHead << 32n) | 1n;
|
||||
}
|
||||
reinterpretBuf.setFloat64(0, v, true);
|
||||
return reinterpretBuf.getBigInt64(0, true);
|
||||
}
|
||||
|
||||
switch (v) {
|
||||
case undefined:
|
||||
return 0n;
|
||||
case null:
|
||||
return (nanHead << 32n) | 2n;
|
||||
case true:
|
||||
return (nanHead << 32n) | 3n;
|
||||
case false:
|
||||
return (nanHead << 32n) | 4n;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = BigInt(this._values.length);
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 1n;
|
||||
switch (typeof v) {
|
||||
case "string":
|
||||
typeFlag = 2n;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 3n;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 4n;
|
||||
break;
|
||||
}
|
||||
return id | ((nanHead | typeFlag) << 32n);
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
let v_ref = boxValue(v);
|
||||
mem().setBigUint64(addr, v_ref, true);
|
||||
}
|
||||
|
||||
const loadSlice = (array, len, cap) => {
|
||||
return new Uint8Array(this._inst.exports.memory.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (array, len, cap) => {
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (ptr, len) => {
|
||||
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
wasi_snapshot_preview1: {
|
||||
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
|
||||
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
|
||||
let nwritten = 0;
|
||||
if (fd == 1) {
|
||||
for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) {
|
||||
let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32
|
||||
let ptr = mem().getUint32(iov_ptr + 0, true);
|
||||
let len = mem().getUint32(iov_ptr + 4, true);
|
||||
nwritten += len;
|
||||
for (let i=0; i<len; i++) {
|
||||
let c = mem().getUint8(ptr+i);
|
||||
if (c == 13) { // CR
|
||||
// ignore
|
||||
} else if (c == 10) { // LF
|
||||
// write line
|
||||
let line = decoder.decode(new Uint8Array(logLine));
|
||||
logLine = [];
|
||||
console.log(line);
|
||||
} else {
|
||||
logLine.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('invalid file descriptor:', fd);
|
||||
}
|
||||
mem().setUint32(nwritten_ptr, nwritten, true);
|
||||
return 0;
|
||||
},
|
||||
fd_close: () => 0, // dummy
|
||||
fd_fdstat_get: () => 0, // dummy
|
||||
fd_seek: () => 0, // dummy
|
||||
proc_exit: (code) => {
|
||||
this.exited = true;
|
||||
this.exitCode = code;
|
||||
this._resolveExitPromise();
|
||||
throw wasmExit;
|
||||
},
|
||||
random_get: (bufPtr, bufLen) => {
|
||||
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
gojs: {
|
||||
// func ticks() float64
|
||||
"runtime.ticks": () => {
|
||||
return timeOrigin + performance.now();
|
||||
},
|
||||
|
||||
// func sleepTicks(timeout float64)
|
||||
"runtime.sleepTicks": (timeout) => {
|
||||
// Do not sleep, only reactivate scheduler after the given timeout.
|
||||
setTimeout(() => {
|
||||
if (this.exited) return;
|
||||
try {
|
||||
this._inst.exports.go_scheduler();
|
||||
} catch (e) {
|
||||
if (e !== wasmExit) throw e;
|
||||
}
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
"syscall/js.finalizeRef": (v_ref) => {
|
||||
// Note: TinyGo does not support finalizers so this is only called
|
||||
// for one specific case, by js.go:jsString. and can/might leak memory.
|
||||
const id = v_ref & 0xffffffffn;
|
||||
if (this._goRefCounts?.[id] !== undefined) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
console.error("syscall/js.finalizeRef: unknown id", id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (value_ptr, value_len) => {
|
||||
value_ptr >>>= 0;
|
||||
const s = loadString(value_ptr, value_len);
|
||||
return boxValue(s);
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (v_ref, p_ptr, p_len) => {
|
||||
let prop = loadString(p_ptr, p_len);
|
||||
let v = unboxValue(v_ref);
|
||||
let result = Reflect.get(v, prop);
|
||||
return boxValue(result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => {
|
||||
const v = unboxValue(v_ref);
|
||||
const p = loadString(p_ptr, p_len);
|
||||
const x = unboxValue(x_ref);
|
||||
Reflect.set(v, p, x);
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
"syscall/js.valueDelete": (v_ref, p_ptr, p_len) => {
|
||||
const v = unboxValue(v_ref);
|
||||
const p = loadString(p_ptr, p_len);
|
||||
Reflect.deleteProperty(v, p);
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (v_ref, i) => {
|
||||
return boxValue(Reflect.get(unboxValue(v_ref), i));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (v_ref, i, x_ref) => {
|
||||
Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => {
|
||||
const v = unboxValue(v_ref);
|
||||
const name = loadString(m_ptr, m_len);
|
||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
||||
try {
|
||||
const m = Reflect.get(v, name);
|
||||
storeValue(ret_addr, Reflect.apply(m, v, args));
|
||||
mem().setUint8(ret_addr + 8, 1);
|
||||
} catch (err) {
|
||||
storeValue(ret_addr, err);
|
||||
mem().setUint8(ret_addr + 8, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
|
||||
try {
|
||||
const v = unboxValue(v_ref);
|
||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
||||
storeValue(ret_addr, Reflect.apply(v, undefined, args));
|
||||
mem().setUint8(ret_addr + 8, 1);
|
||||
} catch (err) {
|
||||
storeValue(ret_addr, err);
|
||||
mem().setUint8(ret_addr + 8, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
|
||||
const v = unboxValue(v_ref);
|
||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
||||
try {
|
||||
storeValue(ret_addr, Reflect.construct(v, args));
|
||||
mem().setUint8(ret_addr + 8, 1);
|
||||
} catch (err) {
|
||||
storeValue(ret_addr, err);
|
||||
mem().setUint8(ret_addr+ 8, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (v_ref) => {
|
||||
return unboxValue(v_ref).length;
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (ret_addr, v_ref) => {
|
||||
const s = String(unboxValue(v_ref));
|
||||
const str = encoder.encode(s);
|
||||
storeValue(ret_addr, str);
|
||||
mem().setInt32(ret_addr + 8, str.length, true);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => {
|
||||
const str = unboxValue(v_ref);
|
||||
loadSlice(slice_ptr, slice_len, slice_cap).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (v_ref, t_ref) => {
|
||||
return unboxValue(v_ref) instanceof unboxValue(t_ref);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => {
|
||||
let num_bytes_copied_addr = ret_addr;
|
||||
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
|
||||
|
||||
const dst = loadSlice(dest_addr, dest_len);
|
||||
const src = unboxValue(src_ref);
|
||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
|
||||
mem().setUint8(returned_status_addr, 1); // Return "ok" status
|
||||
},
|
||||
|
||||
// copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
// Originally copied from upstream Go project, then modified:
|
||||
// https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416
|
||||
"syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => {
|
||||
let num_bytes_copied_addr = ret_addr;
|
||||
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
|
||||
|
||||
const dst = unboxValue(dst_ref);
|
||||
const src = loadSlice(src_addr, src_len);
|
||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
|
||||
mem().setUint8(returned_status_addr, 1); // Return "ok" status
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Go 1.20 uses 'env'. Go 1.21 uses 'gojs'.
|
||||
// For compatibility, we use both as long as Go 1.20 is supported.
|
||||
this.importObject.env = this.importObject.gojs;
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
this._inst = instance;
|
||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
global,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = []; // 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
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
this.exitCode = 0;
|
||||
|
||||
if (this._inst.exports._start) {
|
||||
let exitPromise = new Promise((resolve, reject) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
|
||||
// Run program, but catch the wasmExit exception that's thrown
|
||||
// to return back here.
|
||||
try {
|
||||
this._inst.exports._start();
|
||||
} catch (e) {
|
||||
if (e !== wasmExit) throw e;
|
||||
}
|
||||
|
||||
await exitPromise;
|
||||
return this.exitCode;
|
||||
} else {
|
||||
this._inst.exports._initialize();
|
||||
}
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
try {
|
||||
this._inst.exports.resume();
|
||||
} catch (e) {
|
||||
if (e !== wasmExit) throw e;
|
||||
}
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,7 @@
|
||||
import _fs from 'node:fs'
|
||||
|
||||
declare global {
|
||||
namespace globalThis {
|
||||
var fs: typeof _fs
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ const options = {
|
||||
since: '0.0.1',
|
||||
category: 'Format' as const,
|
||||
type: 'boolean' as const,
|
||||
default: false,
|
||||
default: true,
|
||||
description: 'When set, changes reserved keywords to ALL CAPS'
|
||||
},
|
||||
sqlLinesBetweenQueries: {
|
||||
|
||||
@@ -43,7 +43,8 @@ import sqlPrettierPlugin from "@/common/prettier/plugins/sql"
|
||||
import phpPrettierPlugin from "@/common/prettier/plugins/php"
|
||||
import javaPrettierPlugin from "@/common/prettier/plugins/java"
|
||||
import xmlPrettierPlugin from "@prettier/plugin-xml"
|
||||
import * as shellPrettierPlugin from "@/common/prettier/plugins/shell";
|
||||
import shellPrettierPlugin from "@/common/prettier/plugins/shell";
|
||||
import dockerfilePrettierPlugin from "@/common/prettier/plugins/docker";
|
||||
import rustPrettierPlugin from "@/common/prettier/plugins/rust";
|
||||
import tomlPrettierPlugin from "@/common/prettier/plugins/toml";
|
||||
import clojurePrettierPlugin from "@cospaia/prettier-plugin-clojure";
|
||||
@@ -174,8 +175,8 @@ export const LANGUAGES: LanguageInfo[] = [
|
||||
}),
|
||||
new LanguageInfo("scala", "Scala", StreamLanguage.define(scala).parser, ["scala"]),
|
||||
new LanguageInfo("dockerfile", "Dockerfile", StreamLanguage.define(dockerFile).parser, ["dockerfile"], {
|
||||
parser: "sh",
|
||||
plugins: [shellPrettierPlugin]
|
||||
parser: "dockerfile",
|
||||
plugins: [dockerfilePrettierPlugin]
|
||||
}),
|
||||
new LanguageInfo("lua", "Lua", StreamLanguage.define(lua).parser, ["lua"], {
|
||||
parser: "lua",
|
||||
|
||||
@@ -16,7 +16,18 @@ export default defineConfig(({mode}: { mode: string }): object => {
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
nodePolyfills(),
|
||||
nodePolyfills({
|
||||
include: [],
|
||||
exclude: [],
|
||||
// Whether to polyfill specific globals.
|
||||
globals: {
|
||||
Buffer: true, // can also be 'build', 'dev', or false
|
||||
global: true,
|
||||
process: true,
|
||||
},
|
||||
// Whether to polyfill `node:` protocol imports.
|
||||
protocolImports: true,
|
||||
}),
|
||||
Components({
|
||||
dts: true,
|
||||
dirs: ['src/components'],
|
||||
@@ -40,18 +51,10 @@ export default defineConfig(({mode}: { mode: string }): object => {
|
||||
watch: null,
|
||||
reportCompressedSize: false, // 跳过压缩大小报告
|
||||
rollupOptions: {
|
||||
maxParallelFileOps: 2,
|
||||
treeshake: {
|
||||
moduleSideEffects: false,
|
||||
propertyReadSideEffects: false,
|
||||
tryCatchDeoptimization: false
|
||||
},
|
||||
output: {
|
||||
format: 'es',
|
||||
chunkFileNames: 'js/[name]-[hash].js',
|
||||
entryFileNames: 'js/[name]-[hash].js',
|
||||
assetFileNames: '[ext]/[name]-[hash].[ext]',
|
||||
compact: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
VERSION=1.3.7
|
||||
VERSION=1.4.0
|
||||
|
||||
Reference in New Issue
Block a user