chore: multiply overloaded options in csharp (#18818)

This way we'll get the same treatment in docs generator as well as
dotnet api generator.

This also adds non-suffixed aliases for string options, e.g. `Name` in
addition to `NameString` and `NameRegex`.

Fixes #18407.
This commit is contained in:
Dmitry Gozman 2022-11-15 15:46:54 -08:00 committed by GitHub
parent 210a57ea3b
commit 0387d96cd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 134 additions and 80 deletions

View file

@ -112,10 +112,18 @@ If set changes the request method (e.g. GET or POST)
### option: Route.continue.postData
* since: v1.8
* langs: js, python, java
- `postData` <[string]|[Buffer]>
If set changes the post data of request
### option: Route.continue.postData
* since: v1.8
* langs: csharp
- `postData` <[Buffer]>
If set changes the post data of request
### option: Route.continue.headers
* since: v1.8
- `headers` <[Object]<[string], [string]>>
@ -378,10 +386,18 @@ If set changes the request method (e.g. GET or POST)
### option: Route.fallback.postData
* since: v1.23
* langs: js, python, java
- `postData` <[string]|[Buffer]>
If set changes the post data of request
### option: Route.fallback.postData
* since: v1.23
* langs: csharp
- `postData` <[Buffer]>
If set changes the post data of request
### option: Route.fallback.headers
* since: v1.23
- `headers` <[Object]<[string], [string]>>

View file

@ -103,8 +103,8 @@ First, add fixtures that will load the extension:
```ts
// fixtures.ts
import { test as base, expect, chromium, type BrowserContext } from "@playwright/test";
import path from "path";
import { test as base, expect, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';
export const test = base.extend<{
context: BrowserContext;
@ -185,16 +185,16 @@ def extension_id(context) -> Generator[str, None, None]:
Then use these fixtures in a test:
```ts
import { test, expect } from "./fixtures";
import { test, expect } from './fixtures';
test("example test", async ({ page }) => {
await page.goto("https://example.com");
await expect(page.locator("body")).toHaveText("Changed by my-extension");
test('example test', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.locator('body')).toHaveText('Changed by my-extension');
});
test("popup page", async ({ page, extensionId }) => {
test('popup page', async ({ page, extensionId }) => {
await page.goto(`chrome-extension://${extensionId}/popup.html`);
await expect(page.locator("body")).toHaveText("my-extension popup");
await expect(page.locator('body')).toHaveText('my-extension popup');
});
```

View file

@ -59,6 +59,12 @@ const md = require('../markdown');
* }} Metainfo
*/
/**
* @typedef {{
* csharpOptionOverloadsShortNotation?: boolean,
* }} LanguageOptions
*/
class Documentation {
/**
* @param {!Array<!Documentation.Class>} classesArray
@ -104,13 +110,14 @@ class Documentation {
/**
* @param {string} lang
* @param {LanguageOptions=} options
*/
filterForLanguage(lang) {
filterForLanguage(lang, options = {}) {
const classesArray = [];
for (const clazz of this.classesArray) {
if (clazz.langs.only && !clazz.langs.only.includes(lang))
continue;
clazz.filterForLanguage(lang);
clazz.filterForLanguage(lang, options);
classesArray.push(clazz);
}
this.classesArray = classesArray;
@ -259,13 +266,14 @@ Documentation.Class = class {
/**
* @param {string} lang
* @param {LanguageOptions=} options
*/
filterForLanguage(lang) {
filterForLanguage(lang, options = {}) {
const membersArray = [];
for (const member of this.membersArray) {
if (member.langs.only && !member.langs.only.includes(lang))
continue;
member.filterForLanguage(lang);
member.filterForLanguage(lang, options);
membersArray.push(member);
}
this.membersArray = membersArray;
@ -408,29 +416,39 @@ Documentation.Member = class {
/**
* @param {string} lang
* @param {LanguageOptions=} options
*/
filterForLanguage(lang) {
filterForLanguage(lang, options = {}) {
if (!this.type)
return;
if (this.langs.aliases && this.langs.aliases[lang])
this.alias = this.langs.aliases[lang];
if (this.langs.types && this.langs.types[lang])
this.type = this.langs.types[lang];
this.type.filterForLanguage(lang);
this.type.filterForLanguage(lang, options);
const argsArray = [];
for (const arg of this.argsArray) {
if (arg.langs.only && !arg.langs.only.includes(lang))
continue;
const overriddenArg = (arg.langs.overrides && arg.langs.overrides[lang]) || arg;
overriddenArg.filterForLanguage(lang);
overriddenArg.filterForLanguage(lang, options);
// @ts-ignore
if (overriddenArg.name === 'options' && !overriddenArg.type.properties.length)
continue;
// @ts-ignore
overriddenArg.type.filterForLanguage(lang);
overriddenArg.type.filterForLanguage(lang, options);
argsArray.push(overriddenArg);
}
this.argsArray = argsArray;
const optionsArg = this.argsArray.find(arg => arg.name === 'options');
if (lang === 'csharp' && optionsArg) {
try {
patchCSharpOptionOverloads(optionsArg, options);
} catch (e) {
throw new Error(`Error processing csharp options in ${this.clazz?.name}.${this.name}: ` + e.message);
}
}
}
filterOutExperimental() {
@ -642,15 +660,16 @@ Documentation.Type = class {
/**
* @param {string} lang
* @param {LanguageOptions=} options
*/
filterForLanguage(lang) {
filterForLanguage(lang, options = {}) {
if (!this.properties)
return;
const properties = [];
for (const prop of this.properties) {
if (prop.langs.only && !prop.langs.only.includes(lang))
continue;
prop.filterForLanguage(lang);
prop.filterForLanguage(lang, options);
properties.push(prop);
}
this.properties = properties;
@ -839,4 +858,73 @@ function generateSourceCodeComment(spec) {
return md.render(comments, 120);
}
/**
* @param {Documentation.Member} optionsArg
* @param {LanguageOptions=} options
*/
function patchCSharpOptionOverloads(optionsArg, options = {}) {
const props = optionsArg.type?.properties;
if (!props)
return;
const propsToDelete = new Set();
const propsToAdd = [];
for (const prop of props) {
const union = prop.type?.union;
if (!union)
continue;
const isEnum = union[0].name.startsWith('"');
const isNullable = union.length === 2 && union.some(type => type.name === 'null');
if (isEnum || isNullable)
continue;
const shortNotation = [];
propsToDelete.add(prop);
for (const type of union) {
const suffix = csharpOptionOverloadSuffix(prop.name, type.name);
if (options.csharpOptionOverloadsShortNotation) {
if (type.name === 'string')
shortNotation.push(prop.alias);
else
shortNotation.push(prop.alias + suffix);
continue;
}
const newProp = prop.clone();
newProp.name = prop.name + suffix;
newProp.alias = prop.alias + suffix;
newProp.type = type;
propsToAdd.push(newProp);
if (type.name === 'string') {
const stringProp = prop.clone();
stringProp.type = type;
propsToAdd.push(stringProp);
}
}
if (options.csharpOptionOverloadsShortNotation) {
const newProp = prop.clone();
newProp.alias = newProp.name = shortNotation.join('|');
propsToAdd.push(newProp);
}
}
for (const prop of propsToDelete)
props.splice(props.indexOf(prop), 1);
props.push(...propsToAdd);
}
/**
* @param {string} option
* @param {string} type
*/
function csharpOptionOverloadSuffix(option, type) {
switch (type) {
case 'string': return 'String';
case 'RegExp': return 'Regex';
case 'function': return 'Func';
case 'Buffer': return 'Byte';
case 'Serializable': return 'Object';
}
throw new Error(`CSharp option "${option}" has unsupported type overload "${type}"`);
}
module.exports = Documentation;

View file

@ -287,7 +287,7 @@ function renderConstructors(name, type, out) {
function renderMember(member, parent, options, out) {
const name = toMemberName(member);
if (member.kind === 'method') {
renderMethod(member, parent, name, { mode: 'options', trimRunAndPrefix: options.trimRunAndPrefix }, out);
renderMethod(member, parent, name, { trimRunAndPrefix: options.trimRunAndPrefix }, out);
return;
}
@ -348,12 +348,6 @@ function getPropertyOverloads(type, member, name, parent) {
if (member.type.expression === '[string]|[float]')
jsonName = `${member.name}String`;
overloads.push({ type, name, jsonName });
} else {
for (const overload of member.type.union) {
const t = translateType(overload, parent, t => generateNameDefault(member, name, t, parent));
const suffix = toOverloadSuffix(t);
overloads.push({ type: t, name: name + suffix, jsonName: member.name + suffix });
}
}
return overloads;
}
@ -463,7 +457,6 @@ function generateEnumNameIfApplicable(type) {
* @param {Documentation.Class | Documentation.Type} parent
* @param {string} name
* @param {{
* mode: 'options'|'named'|'base',
* nodocs?: boolean,
* abstract?: boolean,
* public?: boolean,
@ -556,16 +549,12 @@ function renderMethod(member, parent, name, options, out) {
return;
if (arg.name === 'options') {
if (options.mode === 'options' || options.mode === 'base') {
const optionsType = rewriteSuggestedOptionsName(member.clazz.name + name.replace('<T>', '') + 'Options');
if (!optionTypes.has(optionsType) || arg.type.properties.length > optionTypes.get(optionsType).properties.length)
optionTypes.set(optionsType, arg.type);
args.push(`${optionsType}? options = default`);
argTypeMap.set(`${optionsType}? options = default`, 'options');
addParamsDoc('options', ['Call options']);
} else {
arg.type.properties.forEach(processArg);
}
return;
}
@ -632,37 +621,6 @@ function renderMethod(member, parent, name, options, out) {
.sort((a, b) => b.alias === 'options' ? -1 : 0) // move options to the back to the arguments list
.forEach(processArg);
let body = ';';
if (options.mode === 'base') {
// Generate options -> named transition.
const tokens = [];
for (const arg of member.argsArray) {
if (arg.name === 'action' && options.trimRunAndPrefix)
continue;
if (arg.name !== 'options') {
tokens.push(toArgumentName(arg.name));
continue;
}
for (const opt of arg.type.properties) {
// TODO: use translate type here?
if (opt.type.union && !opt.type.union[0].name.startsWith('"') && opt.type.union[0].name !== 'null' && opt.type.expression !== '[string]|[Buffer]') {
// Explode overloads.
for (const t of opt.type.union) {
const suffix = toOverloadSuffix(translateType(t, parent));
tokens.push(`${opt.name}${suffix}: options.${toMemberName(opt)}${suffix}`);
}
} else {
tokens.push(`${opt.alias || opt.name}: options.${toMemberName(opt)}`);
}
}
}
body = `
{
options ??= new ${member.clazz.name}${name}Options();
return ${toAsync(name, member.async)}(${tokens.join(', ')});
}`;
}
if (!explodedArgs.length) {
if (!options.nodocs) {
out.push(...XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth));
@ -670,7 +628,7 @@ function renderMethod(member, parent, name, options, out) {
}
if (member.deprecated)
out.push(`[System.Obsolete]`);
out.push(`${modifiers}${type} ${toAsync(name, member.async)}(${args.join(', ')})${body}`);
out.push(`${modifiers}${type} ${toAsync(name, member.async)}(${args.join(', ')});`);
} else {
let containsOptionalExplodedArgs = false;
explodedArgs.forEach((explodedArg, argIndex) => {
@ -692,7 +650,7 @@ function renderMethod(member, parent, name, options, out) {
overloadedArgs.push(arg);
}
}
out.push(`${modifiers}${type} ${toAsync(name, member.async)}(${overloadedArgs.join(', ')})${body}`);
out.push(`${modifiers}${type} ${toAsync(name, member.async)}(${overloadedArgs.join(', ')});`);
if (argIndex < explodedArgs.length - 1)
out.push(''); // output a special blank line
});
@ -712,7 +670,7 @@ function renderMethod(member, parent, name, options, out) {
if (!options.nodocs)
printArgDoc(argType, paramDocs.get(argType), out);
});
out.push(`${type} ${name}(${filteredArgs.join(', ')})${body}`);
out.push(`${type} ${name}(${filteredArgs.join(', ')});`);
}
}
}
@ -874,14 +832,6 @@ function printArgDoc(name, value, out) {
}
}
/**
* @param {string} typeName
* @return {string}
*/
function toOverloadSuffix(typeName) {
return toTitleCase(typeName.replace(/[<].*[>]/, '').replace(/[^a-zA-Z]/g, ''));
}
/**
* @param {string} name
* @param {boolean} convert