Skip to content

Commit 0e761c1

Browse files
authored
Support go (#16)
* Enable go via workspace detection
1 parent 026ba5b commit 0e761c1

22 files changed

+2033
-1011
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"request": "launch",
1111
"runtimeExecutable": "${execPath}",
1212
"args": [
13-
"--disable-extensions",
13+
// "--disable-extensions",
1414
"--extensionDevelopmentPath=${workspaceFolder}"
1515
],
1616
"outFiles": [

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"cSpell.words": [
55
"esbuild",
66
"socketsecurity"
7-
]
7+
],
8+
"typescript.tsdk": "node_modules/typescript/lib"
89
}

package-lock.json

Lines changed: 497 additions & 641 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@
3838
"workspaceContains:**/{*[rR][eE][qQ][uU][iI][rR][eE][mM][eE][nN][tT][sS].[tT][xX][tT],[rR][eE][qQ][uU][iI][rR][eE][mM][eE][nN][tT][sS]/*.[tT][xX][tT],[rR][eE][qQ][uU][iI][rR][eE][mM][eE][nN][tT][sS]-*.[tT][xX][tT],[rR][eE][qQ][uU][iI][rR][eE][mM][eE][nN][tT][sS].[fF][rR][oO][zZ][eE][nN]}",
3939
"workspaceContains:**/[pP][yY][pP][rR][oO][jJ][eE][cC][tT].[tT][oO][mM][lL]",
4040
"workspaceContains:**/[pP][iI][pP][fF][iI][lL][eE]",
41+
"workspaceContains:**/[gG][oO].[mM][oO][dD]",
42+
"workspaceContains:**/[gG][oO].[sS][uU][mM]",
4143
"onLanguage:python",
42-
"onLanguage:javascript"
44+
"onLanguage:javascript",
45+
"onLanguage:go"
4346
],
4447
"type": "commonjs",
4548
"main": "./out/main.js",
@@ -103,6 +106,14 @@
103106
"examples": [
104107
"/usr/bin/python"
105108
]
109+
},
110+
"socket-security.goExecutable": {
111+
"order": 6,
112+
"type": "string",
113+
"description": "Path to a Go executable to use for Socket dependency analysis.",
114+
"examples": [
115+
"/usr/bin/go"
116+
]
106117
}
107118
}
108119
}
@@ -113,7 +124,7 @@
113124
"publisher": "SocketSecurity",
114125
"scripts": {
115126
"vscode:prepublish": "npm run esbuild -- --minify",
116-
"esbuild-base": "esbuild --bundle --external:vscode --outdir=out/ --platform=node --sourcemap",
127+
"esbuild-base": "esbuild --bundle --external:vscode --loader:.wasm=binary --loader:.go=file --outdir=out/ --platform=node --sourcemap",
117128
"esbuild": "npm run esbuild-base -- --format=cjs main=src/extension.ts",
118129
"test-compile": "tsc -p ./",
119130
"lint": "eslint \"src/**/*.ts\"",
@@ -123,6 +134,7 @@
123134
"dependencies": {
124135
"@babel/parser": "^7.20.7",
125136
"@socketsecurity/config": "^2.0.0",
137+
"@vscode/vsce": "^2.20.1",
126138
"acorn-walk": "^8.2.0",
127139
"antlr4": "^4.13.0",
128140
"ast-types": "^0.14.2",
@@ -147,8 +159,7 @@
147159
"esbuild": "^0.16.7",
148160
"eslint": "^8.26.0",
149161
"toml-eslint-parser": "^0.6.0",
150-
"typescript": "^4.8.4",
151-
"vsce": "^2.15.0"
162+
"typescript": "^4.8.4"
152163
},
153164
"repository": {
154165
"url": "https://github.com/SocketDev/vscode-socket-security"

src/data/go/builtins.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const goBuiltins = [
2+
'archive/tar',
3+
'archive/zip',
4+
'bufio',
5+
'bytes',
6+
'compress/bzip2',
7+
'compress/flate',
8+
'compress/gzip',
9+
'compress/lzw',
10+
'compress/zlib',
11+
'container/heap',
12+
'container/list',
13+
'container/ring',
14+
'context',
15+
'crypto',
16+
'database/sql',
17+
'debug/buildinfo',
18+
'debug/dwarf',
19+
'debug/elf',
20+
'debug/gosym',
21+
'debug/macho',
22+
'debug/pe',
23+
'debug/plan9obj',
24+
'embed',
25+
'encoding',
26+
'errors',
27+
'expvar',
28+
'flag',
29+
'fmt',
30+
'go/ast',
31+
'go/build',
32+
'go/constant',
33+
'go/doc',
34+
'go/format',
35+
'go/importer',
36+
'go/internal/gccgoimporter',
37+
'go/internal/gcimporter',
38+
'go/internal/srcimporter',
39+
'go/internal/typeparams',
40+
'go/parser',
41+
'go/printer',
42+
'go/scanner',
43+
'go/token',
44+
'go/types',
45+
'hash',
46+
'html',
47+
'image',
48+
'index/suffixarray',
49+
'internal',
50+
'io',
51+
'log',
52+
'math',
53+
'mime',
54+
'net',
55+
'os',
56+
'path',
57+
'plugin',
58+
'reflect',
59+
'regexp',
60+
'runtime',
61+
'sort',
62+
'strconv',
63+
'strings',
64+
'sync',
65+
'syscall',
66+
'testing',
67+
'text/scanner',
68+
'text/tabwriter',
69+
'text/template',
70+
'time',
71+
'unicode',
72+
'unsafe'
73+
]
74+
75+
export function isGoBuiltin (pkg: string) {
76+
return goBuiltins.some(builtin => pkg === builtin || pkg.startsWith(builtin + '/'))
77+
}

src/data/go/executable.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as vscode from 'vscode'
2+
import { EXTENSION_PREFIX } from '../../util';
3+
4+
async function getGoExtension() {
5+
const go = vscode.extensions.getExtension('golang.go');
6+
if (go && !go.isActive) await go.activate();
7+
return go?.exports;
8+
}
9+
10+
export async function initGo(): Promise<vscode.Disposable> {
11+
// in the future, do any needed init work with the golang.go extension instance here
12+
return new vscode.Disposable(() => {});
13+
}
14+
15+
const warned = new Set<string>();
16+
17+
export async function getGoExecutable(fileName?: string): Promise<string | void> {
18+
// no executable in virtual workspace
19+
if (vscode.workspace.workspaceFolders?.every(f => f.uri.scheme !== 'file')) return
20+
const workspaceConfig = vscode.workspace.getConfiguration(EXTENSION_PREFIX);
21+
const pathOverride = workspaceConfig.get<string>('goExecutable');
22+
if (pathOverride) {
23+
return Promise.resolve(vscode.workspace.fs.stat(vscode.Uri.file(pathOverride))).then(
24+
st => {
25+
if (st.type & vscode.FileType.File) return pathOverride;
26+
throw new Error('not a file')
27+
}
28+
).catch(err => {
29+
vscode.window.showErrorMessage(`Failed to find Go binary at '${pathOverride}'. Please update ${EXTENSION_PREFIX}.goExecutable.`)
30+
})
31+
}
32+
const ext = await getGoExtension();
33+
const cmd = await ext?.settings.getExecutionCommand(
34+
'go',
35+
fileName && vscode.Uri.file(fileName)
36+
)
37+
if (cmd) return cmd.binPath
38+
const workspaceID = vscode.workspace.name ||
39+
vscode.workspace.workspaceFolders?.map(f => f.uri.fsPath).join(',') ||
40+
vscode.window.activeTextEditor?.document.uri.fsPath;
41+
if (workspaceID) {
42+
if (warned.has(workspaceID)) return;
43+
warned.add(workspaceID);
44+
}
45+
const installGo = 'Install Go extension';
46+
const configGo = 'Configure Go extension';
47+
const setPath = `Configure Socket`;
48+
vscode.window.showErrorMessage(
49+
`Socket failed to find a Go installation; please ${ext ? 'install the Go toolchain with' : 'install'} the Go extension or set ${EXTENSION_PREFIX}.goExecutable.`,
50+
ext ? configGo : installGo,
51+
setPath
52+
).then(async res => {
53+
if (res === installGo) {
54+
vscode.env.openExternal(vscode.Uri.parse('vscode:extension/golang.go'));
55+
} else if (res === configGo) {
56+
vscode.commands.executeCommand('go.tools.install');
57+
} else if (res === setPath) {
58+
await workspaceConfig.update('goExecutable', '', vscode.ConfigurationTarget.Global)
59+
vscode.commands.executeCommand('workbench.action.openSettingsJson');
60+
}
61+
})
62+
}

src/data/go/find-imports.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"bufio"
6+
"os"
7+
"go/parser"
8+
"go/token"
9+
"strconv"
10+
)
11+
12+
// Eventually maybe migrate to WASI with tinygo
13+
14+
15+
type VSPosition struct {
16+
Line int `json:"line"`
17+
Character int `json:"character"`
18+
}
19+
20+
type VSRange struct {
21+
Start VSPosition `json:"start"`
22+
End VSPosition `json:"end"`
23+
}
24+
25+
type GoImport struct {
26+
Name string `json:"name"`
27+
Range VSRange `json:"range"`
28+
}
29+
30+
func toVSPos(src token.Position) VSPosition {
31+
// TODO: breaks on unicode but probably rare enough to not matter
32+
return VSPosition { src.Line - 1, src.Column - 1, }
33+
}
34+
35+
func main() {
36+
reader := bufio.NewReader(os.Stdin)
37+
fset := token.NewFileSet()
38+
file, err := parser.ParseFile(fset, "src.go", reader, parser.ImportsOnly)
39+
40+
if err != nil {
41+
os.Stderr.Write([]byte(err.Error()))
42+
os.Exit(1)
43+
}
44+
45+
results := make([]GoImport, 0)
46+
47+
for _, imp := range file.Imports {
48+
start := fset.Position(imp.Pos())
49+
end := fset.Position(imp.End())
50+
name, nameErr := strconv.Unquote(imp.Path.Value)
51+
52+
if nameErr != nil {
53+
os.Stderr.Write([]byte(nameErr.Error()))
54+
os.Exit(1)
55+
}
56+
57+
results = append(results, GoImport {
58+
name,
59+
VSRange { toVSPos(start), toVSPos(end), },
60+
})
61+
}
62+
63+
result, encErr := json.Marshal(results)
64+
if encErr != nil {
65+
os.Stderr.Write([]byte(err.Error()))
66+
os.Exit(1)
67+
}
68+
69+
os.Stdout.Write(result)
70+
}

src/data/go/import-finder.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import importFinder from './find-imports.go'
2+
3+
let cachedBin: Promise<string> | null = null
4+
let lastBinPath: string | null = null
5+
6+
export async function generateNativeGoImportBinary (goBin: string) {
7+
const [
8+
childProcess,
9+
path,
10+
fs,
11+
os
12+
] = await Promise.all([
13+
import('node:child_process'),
14+
import('node:path'),
15+
import('node:fs/promises'),
16+
import('node:os')
17+
])
18+
if (cachedBin && lastBinPath === goBin) {
19+
const bin = await cachedBin.catch(() => null)
20+
if (bin) {
21+
const valid = await fs.lstat(bin).then(f => {
22+
return f.isFile()
23+
}, err => {
24+
if (err && (typeof err as { code?: unknown }).code === 'ENOENT') {
25+
return false
26+
}
27+
throw err
28+
})
29+
if (valid) return bin
30+
}
31+
}
32+
lastBinPath = goBin
33+
cachedBin = (async () => {
34+
const outBin = path.join(await fs.mkdtemp(path.join(os.tmpdir(), 'socket-')), 'go-import-parser')
35+
const build = childProcess.spawn(goBin, ['build', '-o', outBin, importFinder], {
36+
cwd: __dirname
37+
})
38+
39+
const exitCode = await new Promise<number | null>((resolve, reject) => {
40+
build.once('exit', resolve)
41+
build.once('error', reject)
42+
setTimeout(() => reject(new Error('timeout')), 3000)
43+
})
44+
45+
if (exitCode) {
46+
throw new Error(`failed to build with code ${exitCode}`)
47+
}
48+
49+
return outBin
50+
})()
51+
52+
return cachedBin
53+
54+
}
55+

0 commit comments

Comments
 (0)