Skip to content

Commit

Permalink
docs: linkify params and options (#32823)
Browse files Browse the repository at this point in the history
References #32590.
  • Loading branch information
dgozman authored Sep 26, 2024
1 parent a9d5c39 commit a2bdb2f
Show file tree
Hide file tree
Showing 7 changed files with 1,938 additions and 1,121 deletions.
2,546 changes: 1,633 additions & 913 deletions packages/playwright-core/types/types.d.ts

Large diffs are not rendered by default.

405 changes: 247 additions & 158 deletions packages/playwright/types/test.d.ts

Large diffs are not rendered by default.

45 changes: 24 additions & 21 deletions packages/playwright/types/testReporter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,20 @@ export interface FullResult {
*
* Here is a typical order of reporter calls:
* - [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
* Suite}.
* once with a root suite that contains all other suites and tests. Learn more about
* [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
* called for each test run. It is given a {@link TestCase} that is executed, and a {@link TestResult} that is
* almost empty. Test result will be populated while the test runs (for example, with steps and stdio) and will
* get final `status` once the test finishes.
* called for each test run. It is given a [TestCase](https://playwright.dev/docs/api/class-testcase) that is
* executed, and a [TestResult](https://playwright.dev/docs/api/class-testresult) that is almost empty. Test
* 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)
* and
* [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.
* - [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.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
Expand All @@ -128,12 +130,13 @@ export interface FullResult {
* **Merged report API notes**
*
* 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
* produce final reports and all existing reporters should work without any changes. There some subtle differences
* though which might affect some custom reporters.
* - Projects from different shards are always kept as separate {@link 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
* [`merge-reports`](https://playwright.dev/docs/test-sharding#merge-reports-cli) CLI command, the same
* [Reporter](https://playwright.dev/docs/api/class-reporter) API is called to produce final reports and all existing
* reporters should work without any changes. There some subtle differences though which might affect some custom
* reporters.
* - Projects from different shards are always kept as separate
* [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).
*/
export interface Reporter {
Expand All @@ -151,8 +154,8 @@ export interface Reporter {
*/
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
* Suite}s.
* Called once before running tests. All tests have been already discovered and put into a hierarchy of
* [Suite](https://playwright.dev/docs/api/class-suite)s.
* @param config Resolved configuration.
* @param suite The root suite that contains all projects, files and test cases.
*/
Expand Down Expand Up @@ -321,16 +324,16 @@ export {};

/**
* `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.
* - File suite #1
* - {@link TestCase} #1
* - {@link TestCase} #2
* - [TestCase](https://playwright.dev/docs/api/class-testcase) #1
* - [TestCase](https://playwright.dev/docs/api/class-testcase) #2
* - Suite corresponding to a
* [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe)
* group
* - {@link TestCase} #1 in a group
* - {@link TestCase} #2 in a group
* - [TestCase](https://playwright.dev/docs/api/class-testcase) #1 in a group
* - [TestCase](https://playwright.dev/docs/api/class-testcase) #2 in a group
* - < more test cases ... >
* - File suite #2
* - < more file suites ... >
Expand Down Expand Up @@ -376,7 +379,7 @@ export interface 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>;

Expand Down Expand Up @@ -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 {
/**
Expand Down
23 changes: 11 additions & 12 deletions utils/doclint/documentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ const md = require('../markdown');
* @typedef {function({
* clazz?: Class,
* member?: Member,
* param?: string,
* option?: string,
* optionFullPath?: string,
* param?: { name: string, alias: string },
* option?: { name: string, alias: string },
* href?: string,
* }): string|undefined} Renderer
*/
Expand Down Expand Up @@ -742,7 +741,7 @@ function patchLinksInText(classOrMember, text, classesMap, membersMap, linkRende
return linkRenderer({ member, href }) || match;
}
if (p1 === 'param' || p1 === 'option') {
let /** @type {string } */ alias;
let /** @type {string } */ name;
let /** @type {Member} */ member;
if (p2.includes('.')) {
// fully-qualified name
Expand All @@ -751,7 +750,7 @@ function patchLinksInText(classOrMember, text, classesMap, membersMap, linkRende
if (!maybeMember)
throw new Error(`Undefined reference: ${match}\n=========\n${text}`);
member = maybeMember;
alias = rest.join('.');
name = rest.join('.');
} else {
// non-fully-qualified param/option reference from the same method.
if (!classOrMember || !(classOrMember instanceof Member)) {
Expand All @@ -762,23 +761,23 @@ function patchLinksInText(classOrMember, text, classesMap, membersMap, linkRende
if (!maybeMember)
throw new Error(`Undefined reference: ${match}\n=========\n${text}`);
member = maybeMember;
alias = p2;
name = p2;
}
if (p1 === 'param') {
const param = member.argsArray.find(a => a.name === alias);
const param = member.argsArray.find(a => a.name === name);
if (!param)
throw new Error(`Referenced parameter ${match} not found in the parent method ${member.name}\n=========\n${text}`);
alias = param.alias;
return linkRenderer({ member, param: alias, href }) || match;
return linkRenderer({ member, param: { name, alias: param.alias }, href }) || match;
} else {
// p1 === 'option'
const options = member.argsArray.find(a => a.name === 'options');
const parts = alias.split('.');
const option = options?.type?.properties?.find(a => a.name === parts[0]);
const parts = name.split('.');
const optionName = parts[0];
const option = options?.type?.properties?.find(a => a.name === optionName);
if (!option)
throw new Error(`Referenced option ${match} not found in the parent method ${member.name}\n=========\n${text}`);
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);
Expand Down
4 changes: 2 additions & 2 deletions utils/doclint/generateApiJson.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const PROJECT_DIR = path.join(__dirname, '..', '..');
documentation.setLinkRenderer(item => {
const { clazz, param, option } = item;
if (param)
return `\`${param}\``;
return `\`${param.alias}\``;
if (option)
return `\`${option}\``;
return `\`${option.alias}\``;
if (clazz)
return `\`${clazz.name}\``;
});
Expand Down
4 changes: 2 additions & 2 deletions utils/doclint/generateDotnetApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ documentation.setLinkRenderer(item => {
else if (item.member)
return `<see cref="I${toTitleCase(item.member.clazz.name)}.${toMemberName(item.member)}${asyncSuffix}"/>`;
else if (item.option)
return `<paramref name="${item.option}"/>`;
return `<paramref name="${item.option.name}"/>`;
else if (item.param)
return `<paramref name="${item.param}"/>`;
return `<paramref name="${item.param.name}"/>`;
else
throw new Error('Unknown link format.');
});
Expand Down
32 changes: 19 additions & 13 deletions utils/doclint/linkUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,25 @@
const toKebabCase = require('lodash/kebabCase.js')
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 memberName = toKebabCase(member.name);
let hash = null;
if (member.kind === 'property' || member.kind === 'method')
hash = `${className}-${memberName}`.toLowerCase();
else if (member.kind === 'event')
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})`;
};

Expand All @@ -41,26 +52,21 @@ function createClassMarkdownLink(languagePath, clazz) {
};

/**
* @param {string} language
* @param {string} language
* @param {OutputType} outputType
* @returns {Documentation.Renderer}
*/
function docsLinkRendererForLanguage(language, outputType) {
const languagePath = languageToRelativeDocsPath(language);
return ({ clazz, member, param, option }) => {
if (param)
return `\`${param}\``;
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 (clazz)
return createClassMarkdownLink(languagePath, clazz);
if (!member || !member.clazz)
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 + '.';
if (member.kind === 'method') {
const args = outputType === 'ReleaseNotesMd' ? '' : renderJSSignature(member.argsArray);
Expand Down

0 comments on commit a2bdb2f

Please sign in to comment.