...

Text file src/github.com/playwright-community/playwright-go/patches/main.patch

Documentation: github.com/playwright-community/playwright-go/patches

     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