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

View file

@ -103,8 +103,8 @@ First, add fixtures that will load the extension:
```ts ```ts
// fixtures.ts // fixtures.ts
import { test as base, expect, chromium, type BrowserContext } from "@playwright/test"; import { test as base, expect, chromium, type BrowserContext } from '@playwright/test';
import path from "path"; import path from 'path';
export const test = base.extend<{ export const test = base.extend<{
context: BrowserContext; context: BrowserContext;
@ -185,16 +185,16 @@ def extension_id(context) -> Generator[str, None, None]:
Then use these fixtures in a test: Then use these fixtures in a test:
```ts ```ts
import { test, expect } from "./fixtures"; import { test, expect } from './fixtures';
test("example test", async ({ page }) => { test('example test', async ({ page }) => {
await page.goto("https://example.com"); await page.goto('https://example.com');
await expect(page.locator("body")).toHaveText("Changed by my-extension"); 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 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 * }} Metainfo
*/ */
/**
* @typedef {{
* csharpOptionOverloadsShortNotation?: boolean,
* }} LanguageOptions
*/
class Documentation { class Documentation {
/** /**
* @param {!Array<!Documentation.Class>} classesArray * @param {!Array<!Documentation.Class>} classesArray
@ -104,13 +110,14 @@ class Documentation {
/** /**
* @param {string} lang * @param {string} lang
* @param {LanguageOptions=} options
*/ */
filterForLanguage(lang) { filterForLanguage(lang, options = {}) {
const classesArray = []; const classesArray = [];
for (const clazz of this.classesArray) { for (const clazz of this.classesArray) {
if (clazz.langs.only && !clazz.langs.only.includes(lang)) if (clazz.langs.only && !clazz.langs.only.includes(lang))
continue; continue;
clazz.filterForLanguage(lang); clazz.filterForLanguage(lang, options);
classesArray.push(clazz); classesArray.push(clazz);
} }
this.classesArray = classesArray; this.classesArray = classesArray;
@ -259,13 +266,14 @@ Documentation.Class = class {
/** /**
* @param {string} lang * @param {string} lang
* @param {LanguageOptions=} options
*/ */
filterForLanguage(lang) { filterForLanguage(lang, options = {}) {
const membersArray = []; const membersArray = [];
for (const member of this.membersArray) { for (const member of this.membersArray) {
if (member.langs.only && !member.langs.only.includes(lang)) if (member.langs.only && !member.langs.only.includes(lang))
continue; continue;
member.filterForLanguage(lang); member.filterForLanguage(lang, options);
membersArray.push(member); membersArray.push(member);
} }
this.membersArray = membersArray; this.membersArray = membersArray;
@ -406,31 +414,41 @@ Documentation.Member = class {
} }
} }
/** /**
* @param {string} lang * @param {string} lang
* @param {LanguageOptions=} options
*/ */
filterForLanguage(lang) { filterForLanguage(lang, options = {}) {
if (!this.type) if (!this.type)
return; return;
if (this.langs.aliases && this.langs.aliases[lang]) if (this.langs.aliases && this.langs.aliases[lang])
this.alias = this.langs.aliases[lang]; this.alias = this.langs.aliases[lang];
if (this.langs.types && this.langs.types[lang]) if (this.langs.types && this.langs.types[lang])
this.type = this.langs.types[lang]; this.type = this.langs.types[lang];
this.type.filterForLanguage(lang); this.type.filterForLanguage(lang, options);
const argsArray = []; const argsArray = [];
for (const arg of this.argsArray) { for (const arg of this.argsArray) {
if (arg.langs.only && !arg.langs.only.includes(lang)) if (arg.langs.only && !arg.langs.only.includes(lang))
continue; continue;
const overriddenArg = (arg.langs.overrides && arg.langs.overrides[lang]) || arg; const overriddenArg = (arg.langs.overrides && arg.langs.overrides[lang]) || arg;
overriddenArg.filterForLanguage(lang); overriddenArg.filterForLanguage(lang, options);
// @ts-ignore // @ts-ignore
if (overriddenArg.name === 'options' && !overriddenArg.type.properties.length) if (overriddenArg.name === 'options' && !overriddenArg.type.properties.length)
continue; continue;
// @ts-ignore // @ts-ignore
overriddenArg.type.filterForLanguage(lang); overriddenArg.type.filterForLanguage(lang, options);
argsArray.push(overriddenArg); argsArray.push(overriddenArg);
} }
this.argsArray = argsArray; 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() { filterOutExperimental() {
@ -642,15 +660,16 @@ Documentation.Type = class {
/** /**
* @param {string} lang * @param {string} lang
* @param {LanguageOptions=} options
*/ */
filterForLanguage(lang) { filterForLanguage(lang, options = {}) {
if (!this.properties) if (!this.properties)
return; return;
const properties = []; const properties = [];
for (const prop of this.properties) { for (const prop of this.properties) {
if (prop.langs.only && !prop.langs.only.includes(lang)) if (prop.langs.only && !prop.langs.only.includes(lang))
continue; continue;
prop.filterForLanguage(lang); prop.filterForLanguage(lang, options);
properties.push(prop); properties.push(prop);
} }
this.properties = properties; this.properties = properties;
@ -839,4 +858,73 @@ function generateSourceCodeComment(spec) {
return md.render(comments, 120); 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; module.exports = Documentation;

View file

@ -287,7 +287,7 @@ function renderConstructors(name, type, out) {
function renderMember(member, parent, options, out) { function renderMember(member, parent, options, out) {
const name = toMemberName(member); const name = toMemberName(member);
if (member.kind === 'method') { if (member.kind === 'method') {
renderMethod(member, parent, name, { mode: 'options', trimRunAndPrefix: options.trimRunAndPrefix }, out); renderMethod(member, parent, name, { trimRunAndPrefix: options.trimRunAndPrefix }, out);
return; return;
} }
@ -348,12 +348,6 @@ function getPropertyOverloads(type, member, name, parent) {
if (member.type.expression === '[string]|[float]') if (member.type.expression === '[string]|[float]')
jsonName = `${member.name}String`; jsonName = `${member.name}String`;
overloads.push({ type, name, jsonName }); 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; return overloads;
} }
@ -463,7 +457,6 @@ function generateEnumNameIfApplicable(type) {
* @param {Documentation.Class | Documentation.Type} parent * @param {Documentation.Class | Documentation.Type} parent
* @param {string} name * @param {string} name
* @param {{ * @param {{
* mode: 'options'|'named'|'base',
* nodocs?: boolean, * nodocs?: boolean,
* abstract?: boolean, * abstract?: boolean,
* public?: boolean, * public?: boolean,
@ -556,16 +549,12 @@ function renderMethod(member, parent, name, options, out) {
return; return;
if (arg.name === 'options') { if (arg.name === 'options') {
if (options.mode === 'options' || options.mode === 'base') { const optionsType = rewriteSuggestedOptionsName(member.clazz.name + name.replace('<T>', '') + 'Options');
const optionsType = rewriteSuggestedOptionsName(member.clazz.name + name.replace('<T>', '') + 'Options'); if (!optionTypes.has(optionsType) || arg.type.properties.length > optionTypes.get(optionsType).properties.length)
if (!optionTypes.has(optionsType) || arg.type.properties.length > optionTypes.get(optionsType).properties.length) optionTypes.set(optionsType, arg.type);
optionTypes.set(optionsType, arg.type); args.push(`${optionsType}? options = default`);
args.push(`${optionsType}? options = default`); argTypeMap.set(`${optionsType}? options = default`, 'options');
argTypeMap.set(`${optionsType}? options = default`, 'options'); addParamsDoc('options', ['Call options']);
addParamsDoc('options', ['Call options']);
} else {
arg.type.properties.forEach(processArg);
}
return; 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 .sort((a, b) => b.alias === 'options' ? -1 : 0) // move options to the back to the arguments list
.forEach(processArg); .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 (!explodedArgs.length) {
if (!options.nodocs) { if (!options.nodocs) {
out.push(...XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth)); out.push(...XmlDoc.renderXmlDoc(member.spec, maxDocumentationColumnWidth));
@ -670,7 +628,7 @@ function renderMethod(member, parent, name, options, out) {
} }
if (member.deprecated) if (member.deprecated)
out.push(`[System.Obsolete]`); 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 { } else {
let containsOptionalExplodedArgs = false; let containsOptionalExplodedArgs = false;
explodedArgs.forEach((explodedArg, argIndex) => { explodedArgs.forEach((explodedArg, argIndex) => {
@ -692,7 +650,7 @@ function renderMethod(member, parent, name, options, out) {
overloadedArgs.push(arg); 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) if (argIndex < explodedArgs.length - 1)
out.push(''); // output a special blank line out.push(''); // output a special blank line
}); });
@ -712,7 +670,7 @@ function renderMethod(member, parent, name, options, out) {
if (!options.nodocs) if (!options.nodocs)
printArgDoc(argType, paramDocs.get(argType), out); 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 {string} name
* @param {boolean} convert * @param {boolean} convert
@ -895,7 +845,7 @@ function toAsync(name, convert) {
} }
/** /**
* @param {string} suggestedName * @param {string} suggestedName
* @returns {string} * @returns {string}
*/ */
function rewriteSuggestedOptionsName(suggestedName) { function rewriteSuggestedOptionsName(suggestedName) {