Skip to content

Commit 8aa88d5

Browse files
fix(doc): check and update optional types in the api (#1206)
This adds a new check to doclint for whether a member is correctly marked as optional. part of #6
1 parent f4e9b50 commit 8aa88d5

File tree

6 files changed

+84
-34
lines changed

6 files changed

+84
-34
lines changed

docs/api.md

+25-25
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ This object can be used to launch or connect to Chromium, returning instances of
7171
#### playwright.devices
7272
- returns: <[Object]>
7373

74-
Returns a list of devices to be used with [`browser.newContext(options)`](#browsernewcontextoptions) or [`browser.newPage(options)`](#browsernewpageoptions). Actual list of devices can be found in [src/deviceDescriptors.ts](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts).
74+
Returns a list of devices to be used with [`browser.newContext([options])`](#browsernewcontextoptions) or [`browser.newPage([options])`](#browsernewpageoptions). Actual list of devices can be found in [src/deviceDescriptors.ts](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts).
7575

7676
```js
7777
const { webkit, devices } = require('playwright');
@@ -145,14 +145,14 @@ const { firefox } = require('playwright'); // Or 'chromium' or 'webkit'.
145145
})();
146146
```
147147

148-
See [ChromiumBrowser], [FirefoxBrowser] and [WebKitBrowser] for browser-specific features. Note that [browserType.connect(options)](#browsertypeconnectoptions) and [browserType.launch(options)](#browsertypelaunchoptions) always return a specific browser instance, based on the browser being connected to or launched.
148+
See [ChromiumBrowser], [FirefoxBrowser] and [WebKitBrowser] for browser-specific features. Note that [browserType.connect(options)](#browsertypeconnectoptions) and [browserType.launch([options])](#browsertypelaunchoptions) always return a specific browser instance, based on the browser being connected to or launched.
149149

150150
<!-- GEN:toc -->
151151
- [event: 'disconnected'](#event-disconnected)
152152
- [browser.close()](#browserclose)
153153
- [browser.contexts()](#browsercontexts)
154154
- [browser.isConnected()](#browserisconnected)
155-
- [browser.newContext(options)](#browsernewcontextoptions)
155+
- [browser.newContext([options])](#browsernewcontextoptions)
156156
- [browser.newPage([options])](#browsernewpageoptions)
157157
<!-- GEN:stop -->
158158

@@ -182,7 +182,7 @@ a single instance of [BrowserContext].
182182

183183
Indicates that the browser is connected.
184184

185-
#### browser.newContext(options)
185+
#### browser.newContext([options])
186186
- `options` <[Object]>
187187
- `ignoreHTTPSErrors` <?[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
188188
- `bypassCSP` <?[boolean]> Toggles bypassing page's Content-Security-Policy.
@@ -604,8 +604,8 @@ page.removeListener('request', logRequest);
604604
- [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args)
605605
- [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args)
606606
- [page.exposeFunction(name, playwrightFunction)](#pageexposefunctionname-playwrightfunction)
607-
- [page.fill(selector, value, options)](#pagefillselector-value-options)
608-
- [page.focus(selector, options)](#pagefocusselector-options)
607+
- [page.fill(selector, value[, options])](#pagefillselector-value-options)
608+
- [page.focus(selector[, options])](#pagefocusselector-options)
609609
- [page.frames()](#pageframes)
610610
- [page.goBack([options])](#pagegobackoptions)
611611
- [page.goForward([options])](#pagegoforwardoptions)
@@ -620,7 +620,7 @@ page.removeListener('request', logRequest);
620620
- [page.reload([options])](#pagereloadoptions)
621621
- [page.route(url, handler)](#pagerouteurl-handler)
622622
- [page.screenshot([options])](#pagescreenshotoptions)
623-
- [page.select(selector, value, options)](#pageselectselector-value-options)
623+
- [page.select(selector, value[, options])](#pageselectselector-value-options)
624624
- [page.setCacheEnabled([enabled])](#pagesetcacheenabledenabled)
625625
- [page.setContent(html[, options])](#pagesetcontenthtml-options)
626626
- [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout)
@@ -1135,7 +1135,7 @@ const fs = require('fs');
11351135
})();
11361136
```
11371137

1138-
#### page.fill(selector, value, options)
1138+
#### page.fill(selector, value[, options])
11391139
- `selector` <[string]> A selector to query page for.
11401140
- `value` <[string]> Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element.
11411141
- `options` <[Object]>
@@ -1150,7 +1150,7 @@ If there's no text `<input>`, `<textarea>` or `[contenteditable]` element matchi
11501150
11511151
Shortcut for [page.mainFrame().fill()](#framefillselector-value)
11521152

1153-
#### page.focus(selector, options)
1153+
#### page.focus(selector[, options])
11541154
- `selector` <[string]> A selector of an element to focus. If there are multiple elements satisfying the selector, the first will be focused.
11551155
- `options` <[Object]>
11561156
- `waitFor` <[boolean]> Whether to wait for the element to be present in the dom. Defaults to `true`.
@@ -1216,7 +1216,7 @@ Navigate to the next page in history.
12161216
12171217
> **NOTE** Headless mode doesn't support navigation to a PDF document. See the [upstream issue](https://bugs.chromium.org/p/chromium/issues/detail?id=761295).
12181218
1219-
Shortcut for [page.mainFrame().goto(url, options)](#framegotourl-options)
1219+
Shortcut for [page.mainFrame().goto(url[, options])](#framegotourl-options)
12201220

12211221
#### page.hover(selector[, options])
12221222
- `selector` <[string]> A selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered.
@@ -1381,7 +1381,7 @@ await browser.close();
13811381

13821382
> **NOTE** Screenshots take at least 1/6 second on Chromium OS X and Chromium Windows. See https://crbug.com/741689 for discussion.
13831383
1384-
#### page.select(selector, value, options)
1384+
#### page.select(selector, value[, options])
13851385
- `selector` <[string]> A selector to query frame for.
13861386
- `value` <[string]|[ElementHandle]|[Object]|[Array]<[string]>|[Array]<[ElementHandle]>|[Array]<[Object]>> Options to select. If the `<select>` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match.
13871387
- `value` <[string]> Matches by `option.value`.
@@ -1412,7 +1412,7 @@ page.select('select#colors', { value: 'blue' }, { index: 2 }, 'red');
14121412
Shortcut for [page.mainFrame().select()](#frameselectselector-values)
14131413

14141414
#### page.setCacheEnabled([enabled])
1415-
- `enabled` <[boolean]> sets the `enabled` state of the cache.
1415+
- `enabled` <[boolean]> sets the `enabled` state of the cache. Defaults to `true`.
14161416
- returns: <[Promise]>
14171417

14181418
Toggles ignoring cache for each request based on the enabled state. By default, caching is enabled.
@@ -1468,7 +1468,7 @@ The extra HTTP headers will be sent with every request the page initiates.
14681468
- `height` <[number]> page height in pixels. **required**
14691469
- returns: <[Promise]>
14701470

1471-
In the case of multiple pages in a single browser, each page can have its own viewport size. However, [browser.newContext(options)](#browsernewcontextoptions) allows to set viewport size (and more) for all pages in the context at once.
1471+
In the case of multiple pages in a single browser, each page can have its own viewport size. However, [browser.newContext([options])](#browsernewcontextoptions) allows to set viewport size (and more) for all pages in the context at once.
14721472

14731473
`page.setViewportSize` will resize the page. A lot of websites don't expect phones to change size, so you should set the viewport size before navigating to the page.
14741474

@@ -1800,15 +1800,15 @@ An example of getting text from an iframe element:
18001800
- [frame.dblclick(selector[, options])](#framedblclickselector-options)
18011801
- [frame.evaluate(pageFunction[, ...args])](#frameevaluatepagefunction-args)
18021802
- [frame.evaluateHandle(pageFunction[, ...args])](#frameevaluatehandlepagefunction-args)
1803-
- [frame.fill(selector, value, options)](#framefillselector-value-options)
1804-
- [frame.focus(selector, options)](#framefocusselector-options)
1803+
- [frame.fill(selector, value[, options])](#framefillselector-value-options)
1804+
- [frame.focus(selector[, options])](#framefocusselector-options)
18051805
- [frame.frameElement()](#frameframeelement)
18061806
- [frame.goto(url[, options])](#framegotourl-options)
18071807
- [frame.hover(selector[, options])](#framehoverselector-options)
18081808
- [frame.isDetached()](#frameisdetached)
18091809
- [frame.name()](#framename)
18101810
- [frame.parentFrame()](#frameparentframe)
1811-
- [frame.select(selector, value, options)](#frameselectselector-value-options)
1811+
- [frame.select(selector, value[, options])](#frameselectselector-value-options)
18121812
- [frame.setContent(html[, options])](#framesetcontenthtml-options)
18131813
- [frame.title()](#frametitle)
18141814
- [frame.tripleclick(selector[, options])](#frametripleclickselector-options)
@@ -2025,7 +2025,7 @@ console.log(await resultHandle.jsonValue());
20252025
await resultHandle.dispose();
20262026
```
20272027

2028-
#### frame.fill(selector, value, options)
2028+
#### frame.fill(selector, value[, options])
20292029
- `selector` <[string]> A selector to query page for.
20302030
- `value` <[string]> Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element.
20312031
- `options` <[Object]>
@@ -2036,7 +2036,7 @@ await resultHandle.dispose();
20362036
This method focuses the element and triggers an `input` event after filling.
20372037
If there's no text `<input>`, `<textarea>` or `[contenteditable]` element matching `selector`, the method throws an error.
20382038

2039-
#### frame.focus(selector, options)
2039+
#### frame.focus(selector[, options])
20402040
- `selector` <[string]> A selector of an element to focus. If there are multiple elements satisfying the selector, the first will be focused.
20412041
- `options` <[Object]>
20422042
- `waitFor` <[boolean]> Whether to wait for the element to be present in the dom. Defaults to `true`.
@@ -2116,7 +2116,7 @@ If the name is empty, returns the id attribute instead.
21162116
#### frame.parentFrame()
21172117
- returns: <?[Frame]> Parent frame, if any. Detached frames and main frames return `null`.
21182118

2119-
#### frame.select(selector, value, options)
2119+
#### frame.select(selector, value[, options])
21202120
- `selector` <[string]> A selector to query frame for.
21212121
- `value` <[string]|[ElementHandle]|[Object]|[Array]<[string]>|[Array]<[ElementHandle]>|[Array]<[Object]>> Options to select. If the `<select>` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match.
21222122
- `value` <[string]> Matches by `option.value`.
@@ -3488,7 +3488,7 @@ This methods attaches Playwright to an existing browser instance.
34883488
#### browserType.devices
34893489
- returns: <[Object]>
34903490

3491-
Returns a list of devices to be used with [`browser.newContext(options)`](#browsernewcontextoptions) and [`browser.newPage(options)`](#browsernewpageoptions). Actual list of devices can be found in [src/deviceDescriptors.ts](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts).
3491+
Returns a list of devices to be used with [`browser.newContext([options])`](#browsernewcontextoptions) and [`browser.newPage([options])`](#browsernewpageoptions). Actual list of devices can be found in [src/deviceDescriptors.ts](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts).
34923492

34933493
```js
34943494
const { webkit } = require('playwright');
@@ -3640,23 +3640,23 @@ await browser.stopTracing();
36403640

36413641
<!-- GEN:toc -->
36423642
- [chromiumBrowser.createBrowserSession()](#chromiumbrowsercreatebrowsersession)
3643-
- [chromiumBrowser.startTracing(page, [options])](#chromiumbrowserstarttracingpage-options)
3643+
- [chromiumBrowser.startTracing([page, options])](#chromiumbrowserstarttracingpage-options)
36443644
- [chromiumBrowser.stopTracing()](#chromiumbrowserstoptracing)
36453645
<!-- GEN:stop -->
36463646
<!-- GEN:toc-extends-Browser -->
36473647
- [event: 'disconnected'](#event-disconnected)
36483648
- [browser.close()](#browserclose)
36493649
- [browser.contexts()](#browsercontexts)
36503650
- [browser.isConnected()](#browserisconnected)
3651-
- [browser.newContext(options)](#browsernewcontextoptions)
3651+
- [browser.newContext([options])](#browsernewcontextoptions)
36523652
- [browser.newPage([options])](#browsernewpageoptions)
36533653
<!-- GEN:stop -->
36543654

36553655
#### chromiumBrowser.createBrowserSession()
36563656
- returns: <[Promise]<[ChromiumSession]>> Promise that resolves to the newly created browser
36573657
session.
36583658

3659-
#### chromiumBrowser.startTracing(page, [options])
3659+
#### chromiumBrowser.startTracing([page, options])
36603660
- `page` <[Page]> Optional, if specified, tracing includes screenshots of the given page.
36613661
- `options` <[Object]>
36623662
- `path` <[string]> A path to write the trace file to.
@@ -3839,7 +3839,7 @@ Firefox browser instance does not expose Firefox-specific features.
38393839
- [browser.close()](#browserclose)
38403840
- [browser.contexts()](#browsercontexts)
38413841
- [browser.isConnected()](#browserisconnected)
3842-
- [browser.newContext(options)](#browsernewcontextoptions)
3842+
- [browser.newContext([options])](#browsernewcontextoptions)
38433843
- [browser.newPage([options])](#browsernewpageoptions)
38443844
<!-- GEN:stop -->
38453845

@@ -3854,7 +3854,7 @@ WebKit browser instance does not expose WebKit-specific features.
38543854
- [browser.close()](#browserclose)
38553855
- [browser.contexts()](#browsercontexts)
38563856
- [browser.isConnected()](#browserisconnected)
3857-
- [browser.newContext(options)](#browsernewcontextoptions)
3857+
- [browser.newContext([options])](#browsernewcontextoptions)
38583858
- [browser.newPage([options])](#browsernewpageoptions)
38593859
<!-- GEN:stop -->
38603860

src/chromium/crBrowser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
150150
return await this._connection.createBrowserSession();
151151
}
152152

153-
async startTracing(page: Page | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
153+
async startTracing(page?: Page, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
154154
assert(!this._tracingRecording, 'Cannot start recording trace while already recording trace.');
155155
this._tracingClient = page ? (page._delegate as CRPage)._client : this._client;
156156

utils/doclint/check_public_api/JSBuilder.js

+49-6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function checkSources(sources, externalDependencies) {
4040
options: {
4141
allowJs: true,
4242
target: ts.ScriptTarget.ESNext,
43+
strict: true
4344
},
4445
rootNames: sources.map(source => source.filePath())
4546
});
@@ -152,15 +153,39 @@ function checkSources(sources, externalDependencies) {
152153
return parents;
153154
}
154155

155-
function serializeSymbol(symbol, circular = []) {
156+
/**
157+
* @param {ts.Symbol} symbol
158+
* @param {string[]=} circular
159+
* @param {boolean=} parentRequired
160+
*/
161+
function serializeSymbol(symbol, circular = [], parentRequired = true) {
156162
const type = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);
157163
const name = symbol.getName();
158164
if (symbol.valueDeclaration && symbol.valueDeclaration.dotDotDotToken) {
159165
const innerType = serializeType(type.typeArguments ? type.typeArguments[0] : type, circular);
160166
innerType.name = '...' + innerType.name;
161-
return Documentation.Member.createProperty('...' + name, innerType);
167+
const required = false;
168+
return Documentation.Member.createProperty('...' + name, innerType, undefined, required);
162169
}
163-
return Documentation.Member.createProperty(name, serializeType(type, circular));
170+
171+
const required = parentRequired && !typeHasUndefined(type);
172+
return Documentation.Member.createProperty(name, serializeType(type, circular), undefined, required);
173+
}
174+
175+
/**
176+
* @param {!ts.Type} type
177+
*/
178+
function typeHasUndefined(type) {
179+
if (!type.isUnion())
180+
return type.flags & ts.TypeFlags.Undefined;
181+
return type.types.some(typeHasUndefined);
182+
}
183+
184+
/**
185+
* @param {!ts.Type} type
186+
*/
187+
function isNotNullish(type) {
188+
return !((type.flags & ts.TypeFlags.Undefined) || (type.flags & ts.TypeFlags.Null));
164189
}
165190

166191
/**
@@ -208,7 +233,9 @@ function checkSources(sources, externalDependencies) {
208233
return new Documentation.Type('Object', properties);
209234
}
210235
if (type.isUnion() && (typeName.includes('|') || type.types.every(type => type.isStringLiteral() || type.intrinsicName === 'number'))) {
211-
const types = type.types.map(type => serializeType(type, circular));
236+
const types = type.types
237+
.filter(isNotNullish)
238+
.map(type => serializeType(type, circular));
212239
const name = types.map(type => type.name).join('|');
213240
const properties = [].concat(...types.map(type => type.properties));
214241
return new Documentation.Type(name.replace(/false\|true/g, 'boolean'), properties);
@@ -253,7 +280,7 @@ function checkSources(sources, externalDependencies) {
253280
continue;
254281
}
255282
const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
256-
const signature = memberType.getCallSignatures()[0];
283+
const signature = signatureForType(memberType);
257284
if (signature)
258285
members.push(serializeSignature(name, signature));
259286
else
@@ -263,12 +290,28 @@ function checkSources(sources, externalDependencies) {
263290
return new Documentation.Class(className, members);
264291
}
265292

293+
/**
294+
* @param {ts.Type} type
295+
*/
296+
function signatureForType(type) {
297+
const signatures = type.getCallSignatures();
298+
if (signatures.length)
299+
return signatures[0];
300+
if (type.isUnion()) {
301+
const innerTypes = type.types.filter(isNotNullish);
302+
if (innerTypes.length === 1)
303+
return signatureForType(innerTypes[0]);
304+
}
305+
return null;
306+
}
307+
266308
/**
267309
* @param {string} name
268310
* @param {!ts.Signature} signature
269311
*/
270312
function serializeSignature(name, signature) {
271-
const parameters = signature.parameters.map(s => serializeSymbol(s));
313+
const minArgumentCount = signature.minArgumentCount || 0;
314+
const parameters = signature.parameters.map((s, index) => serializeSymbol(s, [], index < minArgumentCount));
272315
const returnType = serializeType(signature.getReturnType());
273316
return Documentation.Member.createMethod(name, parameters, returnType.name !== 'void' ? returnType : null);
274317
}

utils/doclint/check_public_api/MDBuilder.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class MDOutline {
154154
returnType = parseProperty(element);
155155
} else if (element.matches('li') && element.firstChild.matches && element.firstChild.matches('code')) {
156156
const property = parseProperty(element);
157-
property.required = !optionalparams.has(property.name);
157+
property.required = !optionalparams.has(property.name) && !property.name.startsWith('...');
158158
args.push(property);
159159
} else if (element.matches('li') && element.firstChild.nodeType === Element.TEXT_NODE && element.firstChild.textContent.toLowerCase().startsWith('return')) {
160160
returnType = parseProperty(element);

utils/doclint/check_public_api/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ function compareDocumentations(actual, expected) {
143143
for (const arg of argsDiff.extra)
144144
text.push(`- Non-existing argument found: ${arg}`);
145145
errors.push(text.join('\n'));
146+
} else {
147+
for (const name of actualMethod.args.keys()) {
148+
const actual = actualMethod.args.get(name);
149+
const expected = expectedMethod.args.get(name);
150+
if (actual.required !== expected.required)
151+
errors.push(`${className}.${methodName}(): ${name} should be ${expected.required ? 'required' : 'optional'}`);
152+
}
146153
}
147154

148155
for (const arg of argsDiff.equal)

utils/doclint/check_public_api/test/js-builder-common/api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class A {
44
constructor(delegate) {
55
}
66

7-
get getter() {
7+
get getter() : any {
88
return null;
99
}
1010

0 commit comments

Comments
 (0)