Skip to content

Commit a98fadd

Browse files
committed
feat: integrate go bindings generation using wit-bindgen-go and update documentation
Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
1 parent f76364b commit a98fadd

File tree

6 files changed

+252
-20
lines changed

6 files changed

+252
-20
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,4 @@ dist/
7979
!/syntaxes/*.tmLanguage.json
8080
types/
8181
out/
82+
tmp/

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,23 @@ To install from source, follow these steps:
237237
* Run npm commands to install:
238238
`npm ci && npm run install-extension`
239239

240+
## Testing
241+
242+
The extension includes comprehensive tests:
243+
244+
```bash
245+
# Run all tests (lint, format, build, grammar, unit tests)
246+
npm test
247+
248+
# Run unit tests only
249+
npm run test-unit
250+
251+
# Run tests in watch mode
252+
npm run test-unit-watch
253+
```
254+
255+
For Go WASM integration tests, see [docs/GO_TESTING.md](docs/GO_TESTING.md).
256+
240257
## Publishing (for maintainers)
241258

242259
This extension is automatically published to both Visual Studio Marketplace and Open VSX Registry through GitHub Actions when a release is created.

tests/bindings-generation.test.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { describe, it, expect, beforeAll } from "vitest";
2+
import { readFileSync } from "fs";
3+
import path from "path";
4+
import { fileURLToPath } from "url";
5+
6+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
7+
8+
// Import the WASM module directly
9+
import init, { WitBindgen } from "../wit-bindgen-wasm/pkg/wit_bindgen_wasm.js";
10+
11+
const TEST_WIT = `package example:test;
12+
13+
world test-world {
14+
export greet: func(name: string) -> string;
15+
}
16+
`;
17+
18+
describe("Bindings Generation for All Languages", () => {
19+
let witBindgen: WitBindgen;
20+
21+
beforeAll(async () => {
22+
// Initialize WASM module using the built file
23+
const wasmPath = path.join(__dirname, "../wit-bindgen-wasm/pkg/wit_bindgen_wasm_bg.wasm");
24+
const wasmBuffer = readFileSync(wasmPath);
25+
await init(wasmBuffer);
26+
27+
// Create WitBindgen instance
28+
witBindgen = new WitBindgen();
29+
});
30+
31+
const supportedLanguages = [
32+
{ lang: "rust", extension: ".rs", expectedContent: "fn greet", minLength: 100 },
33+
{ lang: "c", extension: ".h", expectedContent: "uint8_t", minLength: 100 },
34+
{ lang: "cpp", extension: ".h", expectedContent: "namespace", minLength: 100 },
35+
{ lang: "go", extension: ".go", expectedContent: "package", minLength: 100 },
36+
{ lang: "csharp", extension: ".cs", expectedContent: "namespace", minLength: 100 },
37+
{ lang: "moonbit", extension: ".mbt", expectedContent: "Generated by", minLength: 30 },
38+
{ lang: "markdown", extension: ".md", expectedContent: "World test-world", minLength: 100 },
39+
];
40+
41+
supportedLanguages.forEach(({ lang, extension, expectedContent, minLength }) => {
42+
it(`should generate actual code stubs for ${lang}`, () => {
43+
const resultJson = witBindgen.generateBindings(TEST_WIT, lang, undefined);
44+
const result = JSON.parse(resultJson);
45+
46+
// Verify files were generated
47+
expect(Object.keys(result).length).toBeGreaterThan(0);
48+
49+
// Verify at least one file has the expected extension
50+
const hasExpectedFile = Object.keys(result).some((filename) => filename.endsWith(extension));
51+
expect(hasExpectedFile).toBe(true);
52+
53+
// Find the main file with the expected extension
54+
const mainFile = Object.entries(result).find(([filename]) => filename.endsWith(extension));
55+
expect(mainFile).toBeDefined();
56+
57+
if (mainFile) {
58+
const [filename, content] = mainFile as [string, string];
59+
60+
// Convert from latin1 encoding
61+
const buffer = Buffer.from(content, "latin1");
62+
const text = buffer.toString("utf8");
63+
64+
// Verify the file contains actual code, not just error messages
65+
expect(text.length).toBeGreaterThan(minLength);
66+
67+
// Should not contain deprecation/unsupported messages
68+
expect(text).not.toContain("no longer supported");
69+
expect(text).not.toContain("deprecated");
70+
expect(text).not.toContain("has been removed");
71+
expect(text).not.toContain("migrate to");
72+
73+
// Should contain expected language-specific content
74+
expect(text).toContain(expectedContent);
75+
76+
console.log(`✅ ${lang}: Generated ${filename} (${text.length} bytes)`);
77+
}
78+
});
79+
80+
it(`should not generate only README files for ${lang}`, () => {
81+
const resultJson = witBindgen.generateBindings(TEST_WIT, lang, undefined);
82+
const result = JSON.parse(resultJson);
83+
84+
// Verify that not all files are README files
85+
const allFilenames = Object.keys(result);
86+
const onlyReadmes = allFilenames.every(
87+
(f) => f.toLowerCase().includes("readme") || f.toLowerCase().includes("read-me")
88+
);
89+
90+
expect(onlyReadmes).toBe(false);
91+
92+
// Verify at least one non-README code file exists
93+
const hasCodeFile = allFilenames.some(
94+
(f) => !f.toLowerCase().includes("readme") && (f.endsWith(extension) || f.includes("."))
95+
);
96+
97+
expect(hasCodeFile).toBe(true);
98+
});
99+
});
100+
101+
it("should generate different output for each language", () => {
102+
const results: Record<string, Record<string, string>> = {};
103+
104+
for (const { lang } of supportedLanguages) {
105+
const resultJson = witBindgen.generateBindings(TEST_WIT, lang, undefined);
106+
results[lang] = JSON.parse(resultJson);
107+
}
108+
109+
// Compare each pair to ensure they're different
110+
const languages = supportedLanguages.map((l) => l.lang);
111+
for (let i = 0; i < languages.length; i++) {
112+
for (let j = i + 1; j < languages.length; j++) {
113+
const lang1 = languages[i];
114+
const lang2 = languages[j];
115+
116+
// Get file lists
117+
const files1 = Object.keys(results[lang1]);
118+
const files2 = Object.keys(results[lang2]);
119+
120+
// Languages should generate different sets of files
121+
const sameFiles = JSON.stringify(files1.sort()) === JSON.stringify(files2.sort());
122+
123+
if (!sameFiles) {
124+
// Different file lists is good
125+
expect(sameFiles).toBe(false);
126+
} else {
127+
// If same file lists, content should be different
128+
const firstFile1 = files1[0];
129+
const content1 = results[lang1][firstFile1];
130+
const content2 = results[lang2][firstFile1];
131+
132+
expect(content1).not.toEqual(content2);
133+
}
134+
}
135+
}
136+
});
137+
138+
it("should handle complex WIT definitions for all languages", () => {
139+
const complexWit = `package example:complex;
140+
141+
interface logger {
142+
enum level {
143+
debug,
144+
info,
145+
warn,
146+
error,
147+
}
148+
149+
record entry {
150+
level: level,
151+
message: string,
152+
}
153+
154+
log: func(entry: entry);
155+
}
156+
157+
world app {
158+
import logger;
159+
export run: func() -> result<_, string>;
160+
}
161+
`;
162+
163+
for (const { lang, extension } of supportedLanguages) {
164+
const resultJson = witBindgen.generateBindings(complexWit, lang, undefined);
165+
const result = JSON.parse(resultJson);
166+
167+
expect(Object.keys(result).length).toBeGreaterThan(0);
168+
169+
const hasExpectedFile = Object.keys(result).some((filename) => filename.endsWith(extension));
170+
expect(hasExpectedFile).toBe(true);
171+
172+
console.log(`✅ ${lang}: Successfully handled complex WIT (${Object.keys(result).length} files)`);
173+
}
174+
});
175+
});

wit-bindgen-wasm/Cargo.lock

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

wit-bindgen-wasm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ wit-bindgen-core = "0.52"
2222
wit-bindgen-c = "0.52"
2323
wit-bindgen-cpp = "0.52"
2424
wit-bindgen-rust = "0.52"
25+
wit-bindgen-go = "0.52"
2526
wit-bindgen-csharp = "0.52"
2627
wit-bindgen-moonbit = "0.52"
2728
wit-bindgen-markdown = "0.52"

wit-bindgen-wasm/src/lib.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use wit_bindgen_core::WorldGenerator;
1212
use wit_bindgen_rust as rust;
1313
use wit_bindgen_c as c;
1414
use wit_bindgen_cpp as cpp;
15+
use wit_bindgen_go as go;
1516
use wit_bindgen_csharp as csharp;
1617
use wit_bindgen_moonbit as moonbit;
1718
use wit_bindgen_markdown as markdown;
@@ -232,7 +233,7 @@ impl WitBindgen {
232233
let mut error_files = HashMap::new();
233234
error_files.insert(
234235
"error.txt".to_string(),
235-
format!("// Unsupported language: {}\n// Supported languages: rust, c, cpp, csharp, go, moonbit, markdown", language)
236+
format!("// Unsupported language: {}\n// Supported languages: rust, c, cpp, csharp, moonbit, markdown\n// Note: Go bindings moved to go.bytecodealliance.org/cmd/wit-bindgen-go", language)
236237
);
237238
error_files
238239
},
@@ -416,25 +417,47 @@ impl WitBindgen {
416417
Ok(result)
417418
}
418419

419-
/// Generate Go bindings - Note: wit-bindgen Go support has moved
420-
fn generate_go_bindings(&self, _content: &str, _world_name: Option<String>) -> HashMap<String, String> {
421-
let mut files = HashMap::new();
422-
files.insert(
423-
"README.md".to_string(),
424-
"# Go Bindings Generation\n\n\
425-
The wit-bindgen Go generators have been moved to a separate project.\n\n\
426-
## New Go Implementation\n\n\
427-
Please use the new Go WIT bindings generator:\n\n\
428-
```bash\n\
429-
# Install the new Go generator\n\
430-
go install go.bytecodealliance.org/cmd/wit-bindgen-go@latest\n\n\
431-
# Generate Go bindings\n\
432-
wit-bindgen-go generate <path-to-wit-pkg>\n\
433-
```\n\n\
434-
For more information, visit: https://github.com/bytecodealliance/go-modules\n\n\
435-
Note: This requires `wasm-tools` to be installed.".to_string()
436-
);
437-
files
420+
/// Generate Go bindings using wit-bindgen-go library
421+
fn generate_go_bindings(&self, content: &str, world_name: Option<String>) -> HashMap<String, String> {
422+
match self.generate_go_with_wit_bindgen(content, world_name.as_deref()) {
423+
Ok(files) => files,
424+
Err(e) => {
425+
console_error(&format!("wit-bindgen-go failed: {}", e));
426+
let mut error_files = HashMap::new();
427+
error_files.insert(
428+
"error.txt".to_string(),
429+
format!("Go binding generation failed: {}", e)
430+
);
431+
error_files
432+
}
433+
}
434+
}
435+
436+
/// Generate Go bindings using wit-bindgen-go library
437+
fn generate_go_with_wit_bindgen(&self, content: &str, world_name: Option<&str>) -> Result<HashMap<String, String>, anyhow::Error> {
438+
let inline_path = Path::new("inline.wit");
439+
let mut resolve = Resolve::default();
440+
let package_id = resolve.push_str(inline_path, content)
441+
.with_context(|| "Failed to parse WIT content for Go binding generation")?;
442+
443+
let world_id = if let Some(world_name) = world_name {
444+
resolve.select_world(&[package_id], Some(world_name))?
445+
} else {
446+
resolve.select_world(&[package_id], None)?
447+
};
448+
449+
let opts = go::Opts::default();
450+
let mut generator = opts.build();
451+
let mut files = Files::default();
452+
453+
generator.generate(&resolve, world_id, &mut files)?;
454+
455+
let mut result = HashMap::new();
456+
for (filename, content) in files.iter() {
457+
result.insert(filename.to_string(), bytes_to_latin1_string(content));
458+
}
459+
460+
Ok(result)
438461
}
439462

440463
/// Generate MoonBit bindings using wit-bindgen-moonbit library

0 commit comments

Comments
 (0)