1diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md
2index 660de17f9e668adebd9ce310ba82879cd14d32e9..6a14a8d872d4943637386fbb658e08bdd508030f 100644
3--- a/docs/src/api/class-browsercontext.md
4+++ b/docs/src/api/class-browsercontext.md
5@@ -215,6 +215,7 @@ await context.AddCookiesAsync(new[] { cookie1, cookie2 });
6 ```
7
8 ### param: BrowserContext.addCookies.cookies
9+* langs: go
10 - `cookies` <[Array]<[Object]>>
11 - `name` <[string]>
12 - `value` <[string]>
13@@ -287,7 +288,17 @@ Script to be evaluated in all pages in the browser context.
14 * langs: csharp, java
15 - `script` <[string]|[path]>
16
17-Script to be evaluated in all pages in the browser context.
18+### param: BrowserContext.addInitScript.script
19+* langs: go
20+- `script` <[string]>
21+
22+Optional Script source to be evaluated in all pages in the browser context.
23+
24+### param: BrowserContext.addInitScript.path
25+* langs: go
26+- `path` <[string]>
27+
28+Optional Script path to be evaluated in all pages in the browser context.
29
30 ### param: BrowserContext.addInitScript.arg
31 * langs: js
32@@ -1021,7 +1032,7 @@ it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/We
33 handler function to route the request.
34
35 ### param: BrowserContext.route.handler
36-* langs: csharp, java
37+* langs: csharp, java,go
38 - `handler` <[function]\([Route]\)>
39
40 handler function to route the request.
41@@ -1186,7 +1197,7 @@ A glob pattern, regex pattern or predicate receiving [URL] used to register a ro
42 [`method: BrowserContext.route`].
43
44 ### param: BrowserContext.unroute.handler
45-* langs: js, python
46+* langs: js, python, go
47 - `handler` <[function]\([Route], [Request]\)>
48
49 Optional handler function used to register a routing with [`method: BrowserContext.route`].
50diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md
51index 34b59c1a05eae03d9bde75e4a2a34fff180b8b2d..67c810045ea3c4b3a1bd9b87029703c85cc9e3b0 100644
52--- a/docs/src/api/class-frame.md
53+++ b/docs/src/api/class-frame.md
54@@ -1449,7 +1449,7 @@ await page.MainFrame.WaitForFunctionAsync("selector => !!document.querySelector(
55
56 Optional argument to pass to [`param: expression`].
57
58-### option: Frame.waitForFunction.polling = %%-js-python-wait-for-function-polling-%%
59+### option: Frame.waitForFunction.polling = %%-js-python-go-wait-for-function-polling-%%
60
61 ### option: Frame.waitForFunction.polling = %%-csharp-java-wait-for-function-polling-%%
62
63diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md
64index 5979b8d5528b771fd01e1d70d5f9a247b6d183c5..4b0bceb89242f328435020d6d782133a001ea328 100644
65--- a/docs/src/api/class-page.md
66+++ b/docs/src/api/class-page.md
67@@ -522,6 +522,18 @@ Script to be evaluated in all pages in the browser context.
68
69 Optional argument to pass to [`param: script`] (only supported when passing a function).
70
71+### param: Page.addInitScript.script
72+* langs: go
73+- `script` <[string]>
74+
75+Optional Script source to be evaluated in all pages in the browser context.
76+
77+### param: Page.addInitScript.path
78+* langs: go
79+- `path` <[string]>
80+
81+Optional Script path to be evaluated in all pages in the browser context.
82+
83 ## async method: Page.addScriptTag
84 - returns: <[ElementHandle]>
85
86@@ -969,7 +981,7 @@ Changes the CSS media type of the page. The only allowed values are `'screen'`,
87 Passing `null` disables CSS media emulation.
88
89 ### option: Page.emulateMedia.media
90-* langs: csharp
91+* langs: csharp, go
92 - `media` <[Media]<"screen"|"print"|"null">>
93
94 Changes the CSS media type of the page. The only allowed values are `'Screen'`, `'Print'` and `'Null'`.
95@@ -983,7 +995,7 @@ Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`
96 `null` disables color scheme emulation.
97
98 ### option: Page.emulateMedia.colorScheme
99-* langs: csharp
100+* langs: csharp, go
101 - `colorScheme` <[ColorScheme]<"light"|"dark"|"no-preference"|"null">>
102
103 Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. Passing
104@@ -996,7 +1008,7 @@ Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`
105 Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. Passing `null` disables reduced motion emulation.
106
107 ### option: Page.emulateMedia.reducedMotion
108-* langs: csharp
109+* langs: csharp, go
110 - `reducedMotion` <[ReducedMotion]<"reduce"|"no-preference"|"null">>
111
112 Emulates `'prefers-reduced-motion'` media feature, supported values are `'reduce'`, `'no-preference'`. Passing `null` disables reduced motion emulation.
113@@ -2284,7 +2296,7 @@ Paper format. If set, takes priority over [`option: width`] or [`option: height`
114 Paper width, accepts values labeled with units.
115
116 ### option: Page.pdf.width
117-* langs: csharp, java
118+* langs: csharp, java, go
119 - `width` <[string]>
120
121 Paper width, accepts values labeled with units.
122@@ -2296,7 +2308,7 @@ Paper width, accepts values labeled with units.
123 Paper height, accepts values labeled with units.
124
125 ### option: Page.pdf.height
126-* langs: csharp, java
127+* langs: csharp, java, go
128 - `height` <[string]>
129
130 Paper height, accepts values labeled with units.
131@@ -2312,7 +2324,7 @@ Paper height, accepts values labeled with units.
132 Paper margins, defaults to none.
133
134 ### option: Page.pdf.margin
135-* langs: csharp, java
136+* langs: csharp, java, go
137 - `margin` <[Object]>
138 - `top` <[string]> Top margin, accepts values labeled with units. Defaults to `0`.
139 - `right` <[string]> Right margin, accepts values labeled with units. Defaults to `0`.
140@@ -2627,7 +2639,7 @@ A glob pattern, regex pattern or predicate receiving [URL] to match while routin
141 When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
142 it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
143 ### param: Page.route.handler
144-* langs: js, python
145+* langs: js, python, go
146 - `handler` <[function]\([Route], [Request]\)>
147
148 handler function to route the request.
149@@ -3018,7 +3030,7 @@ the [`param: url`].
150 A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
151
152 ### param: Page.unroute.handler
153-* langs: js, python
154+* langs: js, python, go
155 - `handler` <[function]\([Route], [Request]\)>
156
157 Optional handler function to route the request.
158@@ -3268,7 +3280,7 @@ Shortcut for main frame's [`method: Frame.waitForFunction`].
159
160 Optional argument to pass to [`param: expression`].
161
162-### option: Page.waitForFunction.polling = %%-js-python-wait-for-function-polling-%%
163+### option: Page.waitForFunction.polling = %%-js-python-go-wait-for-function-polling-%%
164
165 ### option: Page.waitForFunction.polling = %%-csharp-java-wait-for-function-polling-%%
166
167diff --git a/docs/src/api/class-route.md b/docs/src/api/class-route.md
168index 61dd9e3ad4e9b83a8132bc61c026d2c18b35d979..fc440fe38af9dfbd4f6d3c7ef651890657c16008 100644
169--- a/docs/src/api/class-route.md
170+++ b/docs/src/api/class-route.md
171@@ -103,10 +103,17 @@ If set changes the request URL. New URL must have same protocol as original one.
172 If set changes the request method (e.g. GET or POST)
173
174 ### option: Route.continue.postData
175+* langs: js
176 - `postData` <[string]|[Buffer]>
177
178 If set changes the post data of request
179
180+### option: Route.continue.postData
181+* langs: go
182+- `postData` <[any]>
183+
184+If set changes the post data of request
185+
186 ### option: Route.continue.headers
187 - `headers` <[Object]<[string], [string]>>
188
189@@ -202,6 +209,12 @@ If set, equals to setting `Content-Type` response header.
190
191 Response body.
192
193+### option: Route.fulfill.body
194+* langs: go
195+- `body` <[any]>
196+
197+Response body.
198+
199 ### option: Route.fulfill.body
200 * langs: csharp, java
201 - `body` <[string]>
202diff --git a/docs/src/api/params.md b/docs/src/api/params.md
203index f214cf943ae2c95c9377a093f0132e237b9bea2e..bb8269a16898d456ffd3601264469a5bda7518ac 100644
204--- a/docs/src/api/params.md
205+++ b/docs/src/api/params.md
206@@ -146,8 +146,8 @@ Defaults to `'visible'`. Can be either:
207 * `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or `visibility:hidden`.
208 This is opposite to the `'visible'` option.
209
210-## js-python-wait-for-function-polling
211-* langs: js, python
212+## js-python-go-wait-for-function-polling
213+* langs: js, python, go
214 - `polling` <[float]|"raf">
215
216 If [`option: polling`] is `'raf'`, then [`param: expression`] is constantly executed in `requestAnimationFrame`
217@@ -168,14 +168,14 @@ If `true`, Playwright does not pass its own configurations args and only uses th
218 array is given, then filters out the given default arguments. Dangerous option; use with care. Defaults to `false`.
219
220 ## csharp-java-browser-option-ignoredefaultargs
221-* langs: csharp, java
222+* langs: csharp, java, go
223 - `ignoreDefaultArgs` <[Array]<[string]>>
224
225 If `true`, Playwright does not pass its own configurations args and only uses the ones from [`option: args`].
226 Dangerous option; use with care.
227
228 ## csharp-java-browser-option-ignorealldefaultargs
229-* langs: csharp, java
230+* langs: csharp, java, go
231 - `ignoreAllDefaultArgs` <[boolean]>
232
233 If `true`, Playwright does not pass its own configurations args and only uses the ones from [`option: args`].
234@@ -194,7 +194,7 @@ Dangerous option; use with care. Defaults to `false`.
235 Network proxy settings.
236
237 ## csharp-java-browser-option-env
238-* langs: csharp, java
239+* langs: csharp, java, go
240 - `env` <[Object]<[string], [string]>>
241
242 Specify environment variables that will be visible to the browser. Defaults to `process.env`.
243@@ -206,7 +206,7 @@ Specify environment variables that will be visible to the browser. Defaults to `
244 Specify environment variables that will be visible to the browser. Defaults to `process.env`.
245
246 ## js-python-context-option-storage-state
247-* langs: js, python
248+* langs: js, python, go
249 - `storageState` <[path]|[Object]>
250 - `cookies` <[Array]<[Object]>> cookies to set for context
251 - `name` <[string]>
252@@ -234,7 +234,7 @@ Populates context with given storage state. This option can be used to initializ
253 obtained via [`method: BrowserContext.storageState`].
254
255 ## csharp-java-context-option-storage-state-path
256-* langs: csharp, java
257+* langs: csharp, java, go
258 - `storageStatePath` <[path]>
259
260 Populates context with given storage state. This option can be used to initialize context with logged-in information
261@@ -407,7 +407,7 @@ Function to be evaluated in the worker context.
262 Function to be evaluated in the worker context.
263
264 ## python-context-option-viewport
265-* langs: python
266+* langs: python, go
267 - `viewport` <[null]|[Object]>
268 - `width` <[int]> page width in pixels.
269 - `height` <[int]> page height in pixels.
270@@ -415,7 +415,7 @@ Function to be evaluated in the worker context.
271 Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `no_viewport` disables the fixed viewport.
272
273 ## python-context-option-no-viewport
274-* langs: python
275+* langs: python, go
276 - `noViewport` <[boolean]>
277
278 Does not enforce fixed viewport, allows resizing window in the headed mode.
279@@ -557,7 +557,7 @@ call [`method: BrowserContext.close`] for the HAR to be saved.
280 Optional setting to control whether to omit request content from the HAR. Defaults to `false`.
281
282 ## context-option-recordvideo
283-* langs: js
284+* langs: js, go
285 - `recordVideo` <[Object]>
286 - `dir` <[path]> Path to the directory to put videos into.
287 - `size` <[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`
288@@ -831,7 +831,7 @@ Firefox user preferences. Learn more about the Firefox user preferences at
289 [`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
290
291 ## csharp-java-browser-option-firefoxuserprefs
292-* langs: csharp, java
293+* langs: csharp, java, go
294 - `firefoxUserPrefs` <[Object]<[string], [any]>>
295
296 Firefox user preferences. Learn more about the Firefox user preferences at
297@@ -884,12 +884,14 @@ Slows down Playwright operations by the specified amount of milliseconds. Useful
298 - %%-browser-option-tracesdir-%%
299
300 ## locator-option-has-text
301+* langs: js, python, csharp, go
302 - `hasText` <[string]|[RegExp]>
303
304 Matches elements containing specified text somewhere inside, possibly in a child or a descendant element.
305 For example, `"Playwright"` matches `<article><div>Playwright</div></article>`.
306
307 ## locator-option-has
308+* langs: js, python, csharp, go
309 - `has` <[Locator]>
310
311 Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
312@@ -934,6 +936,7 @@ saved to the disk.
313 Specify screenshot type, defaults to `png`.
314
315 ## screenshot-option-mask
316+* langs: js, python, csharp
317 - `mask` <[Array]<[Locator]>>
318
319 Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with
320diff --git a/utils/doclint/generateGoApi.js b/utils/doclint/generateGoApi.js
321new file mode 100644
322index 0000000000000000000000000000000000000000..1c30b40e9fe05745829b34acd907bcdb06f53d8e
323--- /dev/null
324+++ b/utils/doclint/generateGoApi.js
325@@ -0,0 +1,765 @@
326+/**
327+ * Copyright (c) Microsoft Corporation.
328+ *
329+ * Licensed under the Apache License, Version 2.0 (the "License");
330+ * you may not use this file except in compliance with the License.
331+ * You may obtain a copy of the License at
332+ *
333+ * http://www.apache.org/licenses/LICENSE-2.0
334+ *
335+ * Unless required by applicable law or agreed to in writing, software
336+ * distributed under the License is distributed on an "AS IS" BASIS,
337+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
338+ * See the License for the specific language governing permissions and
339+ * limitations under the License.
340+ */
341+
342+// @ts-check
343+
344+const path = require('path');
345+const Documentation = require('./documentation');
346+const XmlDoc = require('./xmlDocumentation')
347+const PROJECT_DIR = path.join(__dirname, '..', '..');
348+const fs = require('fs');
349+const { parseApi } = require('./api_parser');
350+const { Type } = require('./documentation');
351+const { EOL } = require('os');
352+
353+
354+const maxDocumentationColumnWidth = 80;
355+
356+/** @type {Map<string, Documentation.Type>} */
357+const additionalTypes = new Map(); // this will hold types that we discover, because of .NET specifics, like results
358+/** @type {Map<string, string>} */
359+const documentedResults = new Map(); // will hold documentation for new types
360+/** @type {Map<string, string[]>} */
361+const enumTypes = new Map();
362+
363+let documentation;
364+/** @type {Map<string, string>} */
365+let classNameMap;
366+
367+{
368+ const typesDir = process.argv[2] || path.join(__dirname, 'generate_types', 'go');
369+ if (!fs.existsSync(typesDir))
370+ fs.mkdirSync(typesDir, { recursive: true });
371+
372+ const structsFile = path.join(typesDir, "generated-structs.go");
373+ const enumsFile = path.join(typesDir, "generated-enums.go");
374+
375+ for (const file of [structsFile, enumsFile])
376+ fs.writeFileSync(file, "package playwright\n")
377+
378+ documentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'api'));
379+ documentation.filterForLanguage('go');
380+ documentation.setLinkRenderer(item => {
381+ if (item.clazz)
382+ return translateMemberName("interface", item.clazz.name, null);
383+ else if (item.option || item.param)
384+ return `\`${item.option || item.param}\``
385+ else if (item.member)
386+ return `${translateMemberName("interface", item.member.clazz.name, null)}.${translateMemberName(item.member.kind, item.member.name, item.member)}()`;
387+ });
388+ // we have some "predefined" types, like the mixed state enum, that we can map in advance
389+ enumTypes.set("MixedState", ["On", "Off", "Mixed"]);
390+
391+ // map the name to a C# friendly one (we prepend an I to denote an interface)
392+ classNameMap = new Map(documentation.classesArray.map(x => [x.name, translateMemberName('interface', x.name, null)]));
393+
394+ // map some types that we know of
395+ classNameMap.set('Error', 'Exception');
396+ classNameMap.set('TimeoutError', 'TimeoutException');
397+ classNameMap.set('EvaluationArgument', 'interface{}');
398+ classNameMap.set('boolean', '*bool');
399+ classNameMap.set('Serializable', 'T');
400+ classNameMap.set('any', 'interface{}');
401+ classNameMap.set('Buffer', '[]byte'); // TODO(mxschmitt): use bytes.Buffer
402+ classNameMap.set('path', '*string');
403+ classNameMap.set('URL', 'string');
404+ classNameMap.set('RegExp', 'Regex');
405+
406+ // this are types that we don't explicility render even if we get the specs
407+ const ignoredTypes = ['TimeoutException'];
408+
409+ /**
410+ * @param {string} file
411+ * @param {string[]} data
412+ */
413+ let appendFile = (file, data) => {
414+ let content = data.join(`${EOL}\t`);
415+ fs.appendFileSync(file, content);
416+ }
417+
418+ for (const element of documentation.classesArray) {
419+ const name = classNameMap.get(element.name);
420+ if (ignoredTypes.includes(name))
421+ continue;
422+
423+ const out = [];
424+ console.log(`Generating ${name}`);
425+
426+ if (element.spec)
427+ out.push(...XmlDoc.renderXmlDoc(element.spec, maxDocumentationColumnWidth));
428+ else {
429+ let ownDocumentation = documentedResults.get(name);
430+ if (ownDocumentation) {
431+ out.push(`// ${ownDocumentation}`);
432+ }
433+ }
434+
435+ if (element.extends === 'IEventEmitter')
436+ element.extends = null;
437+
438+ out.push(`type ${name} interface {`);
439+ if (element.extends)
440+ out.push(element.extends)
441+
442+ for (const member of element.membersArray) {
443+ renderMember(member, element, out);
444+ }
445+
446+ // we want to separate the items with a space and this is nicer, than holding
447+ // an index in each iterator down the line
448+ const lastLine = out.pop();
449+ if (lastLine !== '')
450+ out.push(lastLine);
451+
452+ out.push('}');
453+ }
454+
455+ additionalTypes.forEach((type, name) => {
456+ if (name.startsWith("Android") || !type.properties?.length)
457+ return
458+ const out = []
459+ let ownDocumentation = documentedResults.get(name);
460+ if (ownDocumentation)
461+ out.push(`// ${ownDocumentation}`)
462+ out.push(`type ${name} struct {`)
463+ // TODO: consider how this could be merged with the `translateType` check
464+ if (type.union
465+ && type.union[0].name === 'null'
466+ && type.union.length == 2) {
467+ type = type.union[1];
468+ }
469+
470+ if (type.name === 'Array') {
471+ throw new Error('Array at this stage is unexpected.');
472+ } else if (type.properties) {
473+ for (const member of type.properties) {
474+ let fakeType = new Type(name, null);
475+ renderMember(member, fakeType, out);
476+ }
477+ } else if (type.union) {
478+ console.log("enum", type)
479+ } else {
480+ console.log(type);
481+ throw new Error(`Not sure what to do in this case.`);
482+ }
483+ out.push("}\n")
484+ appendFile(structsFile, out);
485+ });
486+
487+ enumTypes.forEach((values, name) => {
488+ const out = []
489+ const fcall = `get${name}`
490+ out.push(`func ${fcall}(in string) *${name} {
491+ v := ${name}(in)
492+ return &v
493+ }
494+ `)
495+
496+ out.push(`type ${name} string`)
497+ out.push(" var (")
498+ values.forEach((v, i) => {
499+ // strip out the quotes
500+ v = v.replace(/[\"]/g, ``)
501+ let escapedEnumValue = v.replace(/[-]/g, ' ')
502+ .split(' ')
503+ .map(word => word[0].toUpperCase() + word.substring(1)).join('');
504+
505+ if (i === 0)
506+ out.push(`${name}${escapedEnumValue} *${name} = ${fcall}("${v}")`)
507+ else
508+ out.push(`${name}${escapedEnumValue} = ${fcall}("${v}")`)
509+ });
510+ out.push(")\n")
511+
512+ appendFile(enumsFile, out);
513+ });
514+}
515+
516+/**
517+ * @param {string} memberKind
518+ * @param {string} name
519+ * @param {Documentation.Member} member
520+ */
521+function translateMemberName(memberKind, name, member = null) {
522+ if (!name) return name;
523+
524+ // we strip it for special chars, like @ because we might get called back with it in some special cases
525+ // like, when generating classes inside methods for params
526+ name = name.replace(/[@-]/g, '');
527+
528+ if (memberKind === 'argument') {
529+ if (['params', 'event'].includes(name)) { // just in case we want to add others
530+ return `@${name}`;
531+ } else {
532+ return name;
533+ }
534+ }
535+
536+ // check if there's an alias in the docs, in which case
537+ // we return that, otherwise, we apply our dotnet magic to it
538+ if (member) {
539+ if (member.alias !== name) {
540+ return member.alias;
541+ }
542+ }
543+
544+ // we sanitize some common abbreviations to ensure consistency
545+ name = name.replace(/(HTTP[S]?)/g, (m, g) => {
546+ return g[0].toUpperCase() + g.substring(1).toLowerCase();
547+ });
548+
549+ if (name === "url")
550+ return "URL"
551+
552+ let assumedName = name.charAt(0).toUpperCase() + name.substring(1);
553+
554+ switch (memberKind) {
555+ case "interface":
556+ // apply name mapping if the map exists
557+ let mappedName = classNameMap ? classNameMap.get(assumedName) : null;
558+ if (mappedName)
559+ return mappedName;
560+ return `${assumedName}`;
561+ case "method":
562+ if (member)
563+ return `${assumedName}`;
564+ return assumedName;
565+ case "event":
566+ return `${assumedName}`;
567+ case "enum":
568+ return `${assumedName}`;
569+ default:
570+ return `${assumedName}`;
571+ }
572+}
573+
574+/**
575+ *
576+ * @param {Documentation.Member} member
577+ * @param {Documentation.Class|Documentation.Type} parent
578+ * @param {string[]} out
579+ */
580+function renderMember(member, parent, out) {
581+ let output = line => {
582+ if (typeof (line) === 'string')
583+ out.push(`\t${line}`);
584+ else
585+ out.push(...line.map(x => `\t${x}`));
586+ }
587+
588+ let name = translateMemberName(member.kind, member.name, member);
589+ if (member.kind === 'method') {
590+ renderMethod(member, parent, output, name);
591+ } else {
592+ let type = translateType(member.type, parent, t => generateNameDefault(member, name, t, parent));
593+ if (member.kind === 'event') {
594+ if (!member.type)
595+ throw new Error(`No Event Type for ${name} in ${parent.name}`);
596+ if (member.spec)
597+ output(XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth));
598+ } else if (member.kind === 'property') {
599+ if (member.spec) {
600+ const text = member.spec
601+ if (text.length > 0) {
602+ text.forEach(line => {
603+ let words = line.text.split(' ');
604+ let lines = [];
605+ let lineText = "";
606+ for (let i = 0; i < words.length; i++) {
607+ lineText += ' ' + words[i];
608+ if (lineText.length >= 80) {
609+ lines.push(lineText);
610+ lineText = '';
611+ }
612+ }
613+ lines.push(lineText);
614+ lines.filter(line => !["", " "].includes(line)).forEach(line => output(`//${line}`));
615+ }
616+ )
617+ }
618+ }
619+ let propertyOrigin = member.name;
620+ if (member.type.expression === '[string]|[float]')
621+ propertyOrigin = `${member.name}String`;
622+ if (parent && member && member.name === 'children') { // this is a special hack for Accessibility
623+ console.warn(`children property found in ${parent.name}, assuming array.`);
624+ type = `[]${parent.name}`;
625+ }
626+ if (parent.name.endsWith("Result")) {
627+ type = type.replace("*", "")
628+ }
629+ output(`${name} ${type} \`json:"${propertyOrigin}"\``);
630+ } else {
631+ throw new Error(`Problem rendering a member: ${type} - ${name} (${member.kind})`);
632+ }
633+ }
634+}
635+
636+/**
637+ *
638+ * @param {Documentation.Member} member
639+ * @param {string} name
640+ * @param {Documentation.Type} t
641+ * @param {*} parent
642+ */
643+function generateNameDefault(member, name, t, parent) {
644+ if (!t.properties
645+ && !t.templates
646+ && !t.union
647+ && t.expression === '[Object]')
648+ return 'interface{}';
649+
650+ // we'd get this call for enums, primarily
651+ let enumName = generateEnumNameIfApplicable(member, name, t, parent);
652+ if (!enumName && member) {
653+ if (member.kind === 'method' || member.kind === 'property') {
654+ // this should be easy to name... let's call it the same as the argument (eternal optimist)
655+ let probableName = `${parent.name}${translateMemberName(``, name, null)}`;
656+ let probableType = additionalTypes.get(probableName);
657+ if (probableType) {
658+ // compare it with what?
659+ if (probableType.expression != t.expression) {
660+ throw new Error(`Non-matching types with the same name. Panic.`);
661+ }
662+ } else {
663+ additionalTypes.set(probableName, t);
664+ }
665+
666+ return probableName;
667+ }
668+
669+ if (member.kind === 'event') {
670+ return `${name}Payload`;
671+ }
672+ }
673+
674+ return enumName || t.name;
675+}
676+
677+function generateEnumNameIfApplicable(member, name, type, parent) {
678+ if (!type.union)
679+ return null;
680+
681+ const potentialValues = type.union.filter(u => u.name.startsWith('"'));
682+ if ((potentialValues.length !== type.union.length)
683+ && !(type.union[0].name === 'null' && potentialValues.length === type.union.length - 1))
684+ return null; // this isn't an enum, so we don't care, we let the caller generate the name
685+
686+ if (type && type.name)
687+ return type.name;
688+
689+ // our enum naming policy leaves a few bits to be desired, but it'll do for now
690+ // however, with the recent changes, this almost never gets called anymore
691+ return translateMemberName('enum', name, type);
692+}
693+
694+/**
695+ *
696+ * @param {string} v
697+ * @returns {string}
698+ */
699+function makeFirstCharUpperCase(v) {
700+ return v[0].toUpperCase() + v.slice(1)
701+}
702+
703+/**
704+ * Rendering a method is so _special_, with so many weird edge cases, that it
705+ * makes sense to put it separate from the other logic.
706+ * @param {Documentation.Member} member
707+ * @param {Documentation.Class|Documentation.Type} parent
708+ * @param {Function} output
709+ */
710+function renderMethod(member, parent, output, name) {
711+ const typeResolve = (type) => translateType(type, parent, (t) => {
712+ let newName = `${parent.name}${translateMemberName(member.kind, member.name, null)}Result`;
713+ documentedResults.set(newName, `Result of calling <see cref="${translateMemberName("interface", parent.name)}.${translateMemberName(member.kind, member.name, member)}" />.`);
714+ return newName;
715+ });
716+
717+ /** @type {Map<string, string[]>} */
718+ const paramDocs = new Map();
719+ /**
720+ * @param {string} paramName
721+ * @param {string[]} docs
722+ */
723+ const addParamsDoc = (paramName, docs) => {
724+ if (paramName.startsWith('@'))
725+ paramName = paramName.substring(1);
726+ if (paramDocs.get(paramName))
727+ throw new Error(`Parameter ${paramName} already exists in the docs.`);
728+ paramDocs.set(paramName, docs);
729+ };
730+
731+ /** @type {string} */
732+ let type = null;
733+ // need to check the original one
734+ if (member.type.name === 'Object' || member.type.name === 'Array') {
735+ let innerType = member.type;
736+ let isArray = false;
737+ if (innerType.name === 'Array') {
738+ // we want to influence the name, but also change the object type
739+ innerType = member.type.templates[0];
740+ isArray = true;
741+ }
742+
743+ if (innerType.expression === '[Object]<[string], [string]>') {
744+ // do nothing, because this is handled down the road
745+ } else if (!isArray && !innerType.properties) {
746+ type = `dynamic`;
747+ } else {
748+ type = classNameMap.get(innerType.name);
749+ if (!type) {
750+ type = typeResolve(innerType);
751+ }
752+
753+ if (isArray)
754+ type = `IReadOnlyCollection<${type}>`;
755+ }
756+ }
757+
758+ type = type || typeResolve(member.type);
759+
760+ const optionsStructName = `${parent.name}${makeFirstCharUpperCase(member.name)}Options`
761+ let optionsStructMembers = member.argsArray.find(a => a.name === "options")?.type.properties || []
762+ if (!optionsStructMembers.length)
763+ optionsStructMembers = member.argsArray.filter(a => (!a.required || a.langs.only?.includes("go")))
764+
765+ if (optionsStructMembers.length > 0) {
766+ let fakeType = new Type("Object", optionsStructMembers);
767+ additionalTypes.set(optionsStructName, fakeType)
768+ }
769+ // TODO: this is something that will probably go into the docs
770+ // translate simple getters into read-only properties, and simple
771+ // set-only methods to settable properties
772+ if (member.args.size == 0
773+ && type !== 'void'
774+ && !name.startsWith('Get')) {
775+ if (!member.async) {
776+ if (member.spec)
777+ output(XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth));
778+ output(`${type} ${name} { get; }`);
779+ return;
780+ }
781+ name = `Get${name}`;
782+ } else if (member.args.size == 1
783+ && type === 'void'
784+ && name.startsWith('Set')
785+ && !member.async) {
786+ name = name.substring(3); // remove the 'Set'
787+ if (member.spec)
788+ output(XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth));
789+ output(`${translateType(member.argsArray[0].type, parent)} ${name} { set; }`);
790+ return;
791+ }
792+
793+ // HACK: special case for generics handling!
794+ if (type === 'T') {
795+ name = `${name}<T>`;
796+ }
797+
798+ // adjust the return type for async methods
799+ // if (member.async) {
800+ // if (type === 'void')
801+ // type = `Task`;
802+ // else
803+ // type = `Task<${type}>`;
804+ // }
805+
806+ // render args
807+ let args = [];
808+ /**
809+ *
810+ * @param {string} innerArgType
811+ * @param {string} innerArgName
812+ * @param {Documentation.Member} argument
813+ */
814+ const pushArg = (innerArgType, innerArgName, argument) => {
815+ let isNullable = ['int', 'bool', 'decimal', 'float'].includes(innerArgType);
816+ const requiredPrefix = argument.required ? "" : isNullable ? "?" : "";
817+ const requiredSuffix = argument.required ? "" : " = default";
818+ args.push(`${innerArgType}${requiredPrefix} ${innerArgName}${requiredSuffix}`);
819+ };
820+
821+ let parseArg = (/** @type {Documentation.Member} */ arg) => {
822+ if (arg.name === "options") {
823+ arg.type.properties.forEach(prop => {
824+ parseArg(prop);
825+ });
826+ return;
827+ }
828+
829+ if (arg.type.expression === '[string]|[path]') {
830+ let argName = translateMemberName('argument', arg.name, null);
831+ pushArg("string", argName, arg);
832+ pushArg("string", `${argName}Path`, arg);
833+ if (arg.spec) {
834+ addParamsDoc(argName, XmlDoc.renderTextOnly(arg.spec, maxDocumentationColumnWidth));
835+ addParamsDoc(`${argName}Path`, [`Instead of specifying <paramref name="${argName}"/>, gives the file name to load from.`]);
836+ }
837+ return;
838+ } else if (arg.type.expression === '[boolean]|[Array]<[string]>') {
839+ // HACK: this hurts my brain too
840+ // we split this into two args, one boolean, with the logical name
841+ let argName = translateMemberName('argument', arg.name, null);
842+ let leftArgType = translateType(arg.type.union[0], parent, (t) => { throw new Error('Not supported'); });
843+ let rightArgType = translateType(arg.type.union[1], parent, (t) => { throw new Error('Not supported'); });
844+
845+ pushArg(leftArgType, argName, arg);
846+ pushArg(rightArgType, `${argName}Values`, arg);
847+
848+ addParamsDoc(argName, XmlDoc.renderTextOnly(arg.spec, maxDocumentationColumnWidth));
849+ addParamsDoc(`${argName}Values`, [`The values to take into account when <paramref name="${argName}"/> is <code>true</code>.`]);
850+
851+ return;
852+ }
853+
854+ const argName = translateMemberName('argument', arg.alias || arg.name, null);
855+ const argType = translateType(arg.type, parent, (t) => generateNameDefault(member, argName, t, parent));
856+
857+ if (argType === null && arg.type.union) {
858+ // we might have to split this into multiple arguments
859+ let translatedArguments = arg.type.union.map(t => translateType(t, parent, (x) => generateNameDefault(member, argName, x, parent)));
860+ if (translatedArguments.includes(null))
861+ throw new Error('Unexpected null in translated argument types. Aborting.');
862+
863+ let argDocumentation = XmlDoc.renderTextOnly(arg.spec, maxDocumentationColumnWidth);
864+ for (const newArg of translatedArguments) {
865+ const sanitizedArgName = newArg.match(/(?<=^[\s"']*)(\w+)/g, '')?.[0] || newArg;
866+ const newArgName = `${argName}${sanitizedArgName[0].toUpperCase() + sanitizedArgName.substring(1)}`;
867+ pushArg(newArg, newArgName, arg);
868+ addParamsDoc(newArgName, argDocumentation);
869+ }
870+ return;
871+ }
872+
873+ addParamsDoc(argName, XmlDoc.renderTextOnly(arg.spec, maxDocumentationColumnWidth));
874+
875+ if (argName === 'timeout' && argType === 'decimal') {
876+ args.push(`int timeout = 0`); // a special argument, we ignore our convention
877+ return;
878+ }
879+
880+ pushArg(argType, argName, arg);
881+ };
882+
883+ member.args.forEach(parseArg);
884+
885+ output(XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth));
886+ paramDocs.forEach((val, ind) => {
887+ if (val && val.length === 1)
888+ output(`/// <param name="${ind}">${val}</param>`);
889+ else {
890+ output(`/// <param name="${ind}">`);
891+ output(val.map(l => `/// ${l}`));
892+ output(`/// </param>`);
893+ }
894+ });
895+ output(`${name}(${args.join(', ')}) ${type}`);
896+}
897+
898+/**
899+ *
900+ * @callback generateNameCallback
901+ * @param {Documentation.Type} t
902+ * @returns {string}
903+ */
904+
905+/**
906+ * @param {Documentation.Type} type
907+ * @param {Documentation.Class|Documentation.Type} parent
908+ * @param {generateNameCallback} generateNameCallback
909+*/
910+function translateType(type, parent, generateNameCallback = t => t.name) {
911+ if (type.name === "int")
912+ return "*int"
913+ if (type.name === "string")
914+ return "*string"
915+ if (type.name === "float")
916+ return "*float64"
917+ if (type.name === "Serializable")
918+ return "interface{}"
919+ if (type.name === "Logger")
920+ return "interface{}"
921+ // a few special cases we can fix automatically
922+ if (type.expression === '[null]|[Error]')
923+ return 'void';
924+ else if (type.expression === '[boolean]|"mixed"')
925+ return 'MixedState';
926+
927+ let isNullableEnum = false;
928+ if (type.union) {
929+ if (type.union[0].name === 'null') {
930+ // for dotnet, this is a nullable type
931+ // if the other side is a primitive type
932+ if (type.union.length > 2) {
933+ if (type.union.filter(x => x.name.startsWith('"')).length == type.union.length - 1)
934+ isNullableEnum = true;
935+ else
936+ return `interface{}`
937+ // throw new Error(`Union (${parent.name}) with null is too long.`);
938+ } else {
939+ const innerTypeName = translateType(type.union[1], parent, generateNameCallback);
940+ // if type is primitive, or an enum, then it's nullable
941+ if (innerTypeName === 'bool'
942+ || innerTypeName === 'int') {
943+ return `${innerTypeName}?`;
944+ }
945+
946+ // if it's not a value type, it'll be nullable by default, so we can ignore it
947+ return `${innerTypeName}`;
948+ }
949+ }
950+
951+ if (type.union.filter(u => u.name.startsWith(`"`)).length == type.union.length
952+ || isNullableEnum) {
953+ // this is an enum
954+ let enumName = generateNameCallback(type);
955+ if (!enumName)
956+ throw new Error(`This was supposed to be an enum, but it failed generating a name, ${type.name} ${parent ? parent.name : ""}.`);
957+
958+ // make sure we map the enum, or invalidate the name, in case it doesn't match well
959+ const potentialEnum = enumTypes.get(enumName);
960+ let enumValues = type.union.filter(x => x.name !== 'null').map(x => x.name);
961+ if (potentialEnum) {
962+ // compare values
963+ if (potentialEnum.join(',') !== enumValues.join(',')) {
964+ // for now, we'll merge the two enums, if they have the same name, and we'll go from there
965+ potentialEnum.concat(enumValues.filter(x => !potentialEnum.includes(x))); // merge & de-dupe
966+ // TODO: think about doing global type annotation, where we can add comments, such as this?
967+ enumTypes.set(enumName, potentialEnum);
968+ }
969+ } else {
970+ enumTypes.set(enumName, enumValues);
971+ }
972+ if (isNullableEnum)
973+ return `*${enumName}?`;
974+ return `*${enumName}`;
975+ }
976+
977+ if (type.expression === '[string]|[Buffer]')
978+ return `[]byte`; // TODO: make sure we implement extension methods for this!
979+ else if (type.expression === '[string]|[float]'
980+ || type.expression === '[string]|[float]|[boolean]') {
981+ console.warn(`${type.name} should be a 'string', but was a ${type.expression}`);
982+ return `string`;
983+ } else if (type.union.length == 2 && type.union[1].name === 'Array' && type.union[1].templates[0].name === type.union[0].name)
984+ return `[]${type.union[0].name}`; // an example of this is [string]|[Array]<[string]>
985+ else if (type.union[0].name === 'path')
986+ // we don't support path, but we know it's usually an object on the other end, and we expect
987+ // the dotnet folks to use [NameOfTheObject].LoadFromPath(); method which we can provide separately
988+ return translateType(type.union[1], parent, generateNameCallback);
989+ else if (type.expression === '[float]|"raf"')
990+ return `interface{}`; // hardcoded because there's no other way to denote this
991+ else if (type.expression === '[string]|[RegExp]')
992+ return "interface{}"
993+ if (type.expression === "[string]|[RegExp]|[function]([URL]):[boolean]")
994+ return "interface{}"
995+ return null;
996+ }
997+
998+ const removePointer = i => i.replace(/^\*(.*)/g, "$1")
999+ if (type.name === 'Array') {
1000+ if (type.templates.length != 1)
1001+ throw new Error(`Array (${type.name} from ${parent.name}) has more than 1 dimension. Panic.`);
1002+
1003+ let innerType = translateType(type.templates[0], parent, generateNameCallback);
1004+ return `[]${removePointer(innerType)}`;
1005+ }
1006+
1007+ if (type.name === 'Object') {
1008+ // take care of some common cases
1009+ // TODO: this can be genericized
1010+ if (type.templates && type.templates.length == 2) {
1011+ // get the inner types of both templates, and if they're strings, it's a keyvaluepair string, string,
1012+ let keyType = translateType(type.templates[0], parent, generateNameCallback);
1013+ let valueType = translateType(type.templates[1], parent, generateNameCallback);
1014+ return `map[${removePointer(keyType)}]${removePointer(valueType)}`;
1015+ }
1016+
1017+ if ((type.name === 'Object')
1018+ && !type.properties
1019+ && !type.union) {
1020+ return 'interface{}';
1021+ }
1022+ // this is an additional type that we need to generate
1023+ let objectName = generateNameCallback(type);
1024+ if (objectName === 'Object') {
1025+ throw new Error('Object unexpected');
1026+ } else if (type.name === 'Object') {
1027+ registerAdditionalType(objectName, type);
1028+ }
1029+ return `*${objectName}`;
1030+ }
1031+
1032+ if (type.name === 'Map') {
1033+ if (type.templates && type.templates.length == 2) {
1034+ // we map to a dictionary
1035+ let keyType = translateType(type.templates[0], parent, generateNameCallback);
1036+ let valueType = translateType(type.templates[1], parent, generateNameCallback);
1037+ return `Dictionary<${keyType}, ${valueType}>`;
1038+ } else {
1039+ throw 'Map has invalid number of templates.';
1040+ }
1041+ }
1042+
1043+ if (type.name === 'function') {
1044+ if (type.expression === '[function]' || !type.args)
1045+ return 'interface{}'; // super simple mapping
1046+
1047+ let argsList = '';
1048+ if (type.args) {
1049+ let translatedCallbackArguments = type.args.map(t => translateType(t, parent, generateNameCallback));
1050+ if (translatedCallbackArguments.includes(null))
1051+ throw new Error('There was an argument we could not parse. Aborting.');
1052+
1053+ argsList = translatedCallbackArguments.join(', ');
1054+ }
1055+
1056+ if (!type.returnType) {
1057+ // this is an Action
1058+ return `func(${argsList})`;
1059+ } else {
1060+ let returnType = translateType(type.returnType, parent, generateNameCallback);
1061+ if (returnType == null)
1062+ throw new Error('Unexpected null as return type.');
1063+
1064+ return `Func<${argsList}, ${returnType}>`;
1065+ }
1066+ }
1067+
1068+ // there's a chance this is a name we've already seen before, so check
1069+ // this is also where we map known types, like boolean -> bool, etc.
1070+ let name = classNameMap.get(type.name) || type.name;
1071+ return `${name}`;
1072+}
1073+
1074+/**
1075+ *
1076+ * @param {string} typeName
1077+ * @param {Documentation.Type} type
1078+ */
1079+function registerAdditionalType(typeName, type) {
1080+ if (['object', 'string', 'int'].includes(typeName))
1081+ return;
1082+
1083+ let potentialType = additionalTypes.get(typeName);
1084+ if (potentialType) {
1085+ console.log(`Type ${typeName} already exists, so skipping...`);
1086+ return;
1087+ }
1088+
1089+ additionalTypes.set(typeName, type);
1090+}
View as plain text