docs: linkify params and options (#32823)

References https://github.com/microsoft/playwright/issues/32590.
This commit is contained in:
Dmitry Gozman 2024-09-26 05:13:00 -07:00 коммит произвёл GitHub
Родитель a9d5c39d40
Коммит a2bdb2fd79
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 1932 добавлений и 1115 удалений

2534
packages/playwright-core/types/types.d.ts поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

405
packages/playwright/types/test.d.ts поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

45
packages/playwright/types/testReporter.d.ts поставляемый
Просмотреть файл

@ -94,18 +94,20 @@ export interface FullResult {
* *
* Here is a typical order of reporter calls: * Here is a typical order of reporter calls:
* - [reporter.onBegin(config, suite)](https://playwright.dev/docs/api/class-reporter#reporter-on-begin) is called * - [reporter.onBegin(config, suite)](https://playwright.dev/docs/api/class-reporter#reporter-on-begin) is called
* once with a root suite that contains all other suites and tests. Learn more about [suites hierarchy]{@link * once with a root suite that contains all other suites and tests. Learn more about
* Suite}. * [suites hierarchy][Suite](https://playwright.dev/docs/api/class-suite).
* - [reporter.onTestBegin(test, result)](https://playwright.dev/docs/api/class-reporter#reporter-on-test-begin) is * - [reporter.onTestBegin(test, result)](https://playwright.dev/docs/api/class-reporter#reporter-on-test-begin) is
* called for each test run. It is given a {@link TestCase} that is executed, and a {@link TestResult} that is * called for each test run. It is given a [TestCase](https://playwright.dev/docs/api/class-testcase) that is
* almost empty. Test result will be populated while the test runs (for example, with steps and stdio) and will * executed, and a [TestResult](https://playwright.dev/docs/api/class-testresult) that is almost empty. Test
* get final `status` once the test finishes. * result will be populated while the test runs (for example, with steps and stdio) and will get final `status`
* once the test finishes.
* - [reporter.onStepBegin(test, result, step)](https://playwright.dev/docs/api/class-reporter#reporter-on-step-begin) * - [reporter.onStepBegin(test, result, step)](https://playwright.dev/docs/api/class-reporter#reporter-on-step-begin)
* and * and
* [reporter.onStepEnd(test, result, step)](https://playwright.dev/docs/api/class-reporter#reporter-on-step-end) * [reporter.onStepEnd(test, result, step)](https://playwright.dev/docs/api/class-reporter#reporter-on-step-end)
* are called for each executed step inside the test. When steps are executed, test run has not finished yet. * are called for each executed step inside the test. When steps are executed, test run has not finished yet.
* - [reporter.onTestEnd(test, result)](https://playwright.dev/docs/api/class-reporter#reporter-on-test-end) is * - [reporter.onTestEnd(test, result)](https://playwright.dev/docs/api/class-reporter#reporter-on-test-end) is
* called when test run has finished. By this time, {@link TestResult} is complete and you can use * called when test run has finished. By this time, [TestResult](https://playwright.dev/docs/api/class-testresult)
* is complete and you can use
* [testResult.status](https://playwright.dev/docs/api/class-testresult#test-result-status), * [testResult.status](https://playwright.dev/docs/api/class-testresult#test-result-status),
* [testResult.error](https://playwright.dev/docs/api/class-testresult#test-result-error) and more. * [testResult.error](https://playwright.dev/docs/api/class-testresult#test-result-error) and more.
* - [reporter.onEnd(result)](https://playwright.dev/docs/api/class-reporter#reporter-on-end) is called once after * - [reporter.onEnd(result)](https://playwright.dev/docs/api/class-reporter#reporter-on-end) is called once after
@ -128,12 +130,13 @@ export interface FullResult {
* **Merged report API notes** * **Merged report API notes**
* *
* When merging multiple [`blob`](https://playwright.dev/docs/test-reporters#blob-reporter) reports via * When merging multiple [`blob`](https://playwright.dev/docs/test-reporters#blob-reporter) reports via
* [`merge-reports`](https://playwright.dev/docs/test-sharding#merge-reports-cli) CLI command, the same {@link Reporter} API is called to * [`merge-reports`](https://playwright.dev/docs/test-sharding#merge-reports-cli) CLI command, the same
* produce final reports and all existing reporters should work without any changes. There some subtle differences * [Reporter](https://playwright.dev/docs/api/class-reporter) API is called to produce final reports and all existing
* though which might affect some custom reporters. * reporters should work without any changes. There some subtle differences though which might affect some custom
* - Projects from different shards are always kept as separate {@link TestProject} objects. E.g. if project * reporters.
* 'Desktop Chrome' was sharded across 5 machines then there will be 5 instances of projects with the same name in * - Projects from different shards are always kept as separate
* the config passed to * [TestProject](https://playwright.dev/docs/api/class-testproject) objects. E.g. if project 'Desktop Chrome' was
* sharded across 5 machines then there will be 5 instances of projects with the same name in the config passed to
* [reporter.onBegin(config, suite)](https://playwright.dev/docs/api/class-reporter#reporter-on-begin). * [reporter.onBegin(config, suite)](https://playwright.dev/docs/api/class-reporter#reporter-on-begin).
*/ */
export interface Reporter { export interface Reporter {
@ -151,8 +154,8 @@ export interface Reporter {
*/ */
onEnd?(result: FullResult): Promise<{ status?: FullResult['status'] } | undefined | void> | void; onEnd?(result: FullResult): Promise<{ status?: FullResult['status'] } | undefined | void> | void;
/** /**
* Called once before running tests. All tests have been already discovered and put into a hierarchy of {@link * Called once before running tests. All tests have been already discovered and put into a hierarchy of
* Suite}s. * [Suite](https://playwright.dev/docs/api/class-suite)s.
* @param config Resolved configuration. * @param config Resolved configuration.
* @param suite The root suite that contains all projects, files and test cases. * @param suite The root suite that contains all projects, files and test cases.
*/ */
@ -321,16 +324,16 @@ export {};
/** /**
* `Suite` is a group of tests. All tests in Playwright Test form the following hierarchy: * `Suite` is a group of tests. All tests in Playwright Test form the following hierarchy:
* - Root suite has a child suite for each {@link FullProject}. * - Root suite has a child suite for each [FullProject](https://playwright.dev/docs/api/class-fullproject).
* - Project suite #1. Has a child suite for each test file in the project. * - Project suite #1. Has a child suite for each test file in the project.
* - File suite #1 * - File suite #1
* - {@link TestCase} #1 * - [TestCase](https://playwright.dev/docs/api/class-testcase) #1
* - {@link TestCase} #2 * - [TestCase](https://playwright.dev/docs/api/class-testcase) #2
* - Suite corresponding to a * - Suite corresponding to a
* [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe)
* group * group
* - {@link TestCase} #1 in a group * - [TestCase](https://playwright.dev/docs/api/class-testcase) #1 in a group
* - {@link TestCase} #2 in a group * - [TestCase](https://playwright.dev/docs/api/class-testcase) #2 in a group
* - < more test cases ... > * - < more test cases ... >
* - File suite #2 * - File suite #2
* - < more file suites ... > * - < more file suites ... >
@ -376,7 +379,7 @@ export interface Suite {
parent?: Suite; parent?: Suite;
/** /**
* Child suites. See {@link Suite} for the hierarchy of suites. * Child suites. See [Suite](https://playwright.dev/docs/api/class-suite) for the hierarchy of suites.
*/ */
suites: Array<Suite>; suites: Array<Suite>;
@ -578,7 +581,7 @@ export interface TestError {
} }
/** /**
* A result of a single {@link TestCase} run. * A result of a single [TestCase](https://playwright.dev/docs/api/class-testcase) run.
*/ */
export interface TestResult { export interface TestResult {
/** /**

Просмотреть файл

@ -45,9 +45,8 @@ const md = require('../markdown');
* @typedef {function({ * @typedef {function({
* clazz?: Class, * clazz?: Class,
* member?: Member, * member?: Member,
* param?: string, * param?: { name: string, alias: string },
* option?: string, * option?: { name: string, alias: string },
* optionFullPath?: string,
* href?: string, * href?: string,
* }): string|undefined} Renderer * }): string|undefined} Renderer
*/ */
@ -742,7 +741,7 @@ function patchLinksInText(classOrMember, text, classesMap, membersMap, linkRende
return linkRenderer({ member, href }) || match; return linkRenderer({ member, href }) || match;
} }
if (p1 === 'param' || p1 === 'option') { if (p1 === 'param' || p1 === 'option') {
let /** @type {string } */ alias; let /** @type {string } */ name;
let /** @type {Member} */ member; let /** @type {Member} */ member;
if (p2.includes('.')) { if (p2.includes('.')) {
// fully-qualified name // fully-qualified name
@ -751,7 +750,7 @@ function patchLinksInText(classOrMember, text, classesMap, membersMap, linkRende
if (!maybeMember) if (!maybeMember)
throw new Error(`Undefined reference: ${match}\n=========\n${text}`); throw new Error(`Undefined reference: ${match}\n=========\n${text}`);
member = maybeMember; member = maybeMember;
alias = rest.join('.'); name = rest.join('.');
} else { } else {
// non-fully-qualified param/option reference from the same method. // non-fully-qualified param/option reference from the same method.
if (!classOrMember || !(classOrMember instanceof Member)) { if (!classOrMember || !(classOrMember instanceof Member)) {
@ -762,23 +761,23 @@ function patchLinksInText(classOrMember, text, classesMap, membersMap, linkRende
if (!maybeMember) if (!maybeMember)
throw new Error(`Undefined reference: ${match}\n=========\n${text}`); throw new Error(`Undefined reference: ${match}\n=========\n${text}`);
member = maybeMember; member = maybeMember;
alias = p2; name = p2;
} }
if (p1 === 'param') { if (p1 === 'param') {
const param = member.argsArray.find(a => a.name === alias); const param = member.argsArray.find(a => a.name === name);
if (!param) if (!param)
throw new Error(`Referenced parameter ${match} not found in the parent method ${member.name}\n=========\n${text}`); throw new Error(`Referenced parameter ${match} not found in the parent method ${member.name}\n=========\n${text}`);
alias = param.alias; return linkRenderer({ member, param: { name, alias: param.alias }, href }) || match;
return linkRenderer({ member, param: alias, href }) || match;
} else { } else {
// p1 === 'option' // p1 === 'option'
const options = member.argsArray.find(a => a.name === 'options'); const options = member.argsArray.find(a => a.name === 'options');
const parts = alias.split('.'); const parts = name.split('.');
const option = options?.type?.properties?.find(a => a.name === parts[0]); const optionName = parts[0];
const option = options?.type?.properties?.find(a => a.name === optionName);
if (!option) if (!option)
throw new Error(`Referenced option ${match} not found in the parent method ${member.name}\n=========\n${text}`); throw new Error(`Referenced option ${match} not found in the parent method ${member.name}\n=========\n${text}`);
parts[0] = option.alias; parts[0] = option.alias;
return linkRenderer({ member, option: parts[0], optionFullPath: parts.join('.'), href }) || match; return linkRenderer({ member, option: { name: optionName, alias: parts.join('.') }, href }) || match;
} }
} }
throw new Error(`Undefined link prefix, expected event|method|property|param|option, got: ` + match); throw new Error(`Undefined link prefix, expected event|method|property|param|option, got: ` + match);

Просмотреть файл

@ -25,9 +25,9 @@ const PROJECT_DIR = path.join(__dirname, '..', '..');
documentation.setLinkRenderer(item => { documentation.setLinkRenderer(item => {
const { clazz, param, option } = item; const { clazz, param, option } = item;
if (param) if (param)
return `\`${param}\``; return `\`${param.alias}\``;
if (option) if (option)
return `\`${option}\``; return `\`${option.alias}\``;
if (clazz) if (clazz)
return `\`${clazz.name}\``; return `\`${clazz.name}\``;
}); });

Просмотреть файл

@ -61,9 +61,9 @@ documentation.setLinkRenderer(item => {
else if (item.member) else if (item.member)
return `<see cref="I${toTitleCase(item.member.clazz.name)}.${toMemberName(item.member)}${asyncSuffix}"/>`; return `<see cref="I${toTitleCase(item.member.clazz.name)}.${toMemberName(item.member)}${asyncSuffix}"/>`;
else if (item.option) else if (item.option)
return `<paramref name="${item.option}"/>`; return `<paramref name="${item.option.name}"/>`;
else if (item.param) else if (item.param)
return `<paramref name="${item.param}"/>`; return `<paramref name="${item.param.name}"/>`;
else else
throw new Error('Unknown link format.'); throw new Error('Unknown link format.');
}); });

Просмотреть файл

@ -20,7 +20,16 @@
const toKebabCase = require('lodash/kebabCase.js') const toKebabCase = require('lodash/kebabCase.js')
const Documentation = require('./documentation'); const Documentation = require('./documentation');
function createMarkdownLink(languagePath, member, text) { /**
* @param {string} languagePath
* @param {Documentation.Member} member
* @param {string} text
* @param {string=} paramOrOption
* @returns {string}
*/
function createMarkdownLink(languagePath, member, text, paramOrOption) {
if (!member.clazz)
throw new Error('Member without a class!');
const className = toKebabCase(member.clazz.name); const className = toKebabCase(member.clazz.name);
const memberName = toKebabCase(member.name); const memberName = toKebabCase(member.name);
let hash = null; let hash = null;
@ -28,6 +37,8 @@ function createMarkdownLink(languagePath, member, text) {
hash = `${className}-${memberName}`.toLowerCase(); hash = `${className}-${memberName}`.toLowerCase();
else if (member.kind === 'event') else if (member.kind === 'event')
hash = `${className}-event-${memberName}`.toLowerCase(); hash = `${className}-event-${memberName}`.toLowerCase();
if (paramOrOption)
hash += '-option-' + toKebabCase(paramOrOption).toLowerCase();
return `[${text}](https://playwright.dev${languagePath}/docs/api/class-${member.clazz.name.toLowerCase()}#${hash})`; return `[${text}](https://playwright.dev${languagePath}/docs/api/class-${member.clazz.name.toLowerCase()}#${hash})`;
}; };
@ -41,26 +52,21 @@ function createClassMarkdownLink(languagePath, clazz) {
}; };
/** /**
* @param {string} language * @param {string} language
* @param {OutputType} outputType * @param {OutputType} outputType
* @returns {Documentation.Renderer} * @returns {Documentation.Renderer}
*/ */
function docsLinkRendererForLanguage(language, outputType) { function docsLinkRendererForLanguage(language, outputType) {
const languagePath = languageToRelativeDocsPath(language); const languagePath = languageToRelativeDocsPath(language);
return ({ clazz, member, param, option }) => { return ({ clazz, member, param, option }) => {
if (param) if (clazz)
return `\`${param}\``; return createClassMarkdownLink(languagePath, clazz);
if (option)
return `\`${option}\``;
if (clazz) {
if (outputType === 'Types')
return `{@link ${clazz.name}}`;
if (outputType === 'ReleaseNotesMd')
return createClassMarkdownLink(languagePath, clazz);
throw new Error(`Unexpected output type ${outputType}`);
}
if (!member || !member.clazz) if (!member || !member.clazz)
throw new Error('Internal error'); throw new Error('Internal error');
if (param)
return createMarkdownLink(languagePath, member, `\`${param.alias}\``, param.name);
if (option)
return createMarkdownLink(languagePath, member, `\`${option.alias}\``, option.name);
const className = member.clazz.varName === 'playwrightAssertions' ? '' : member.clazz.varName + '.'; const className = member.clazz.varName === 'playwrightAssertions' ? '' : member.clazz.varName + '.';
if (member.kind === 'method') { if (member.kind === 'method') {
const args = outputType === 'ReleaseNotesMd' ? '' : renderJSSignature(member.argsArray); const args = outputType === 'ReleaseNotesMd' ? '' : renderJSSignature(member.argsArray);