[doclint] lint events
This patch: - adds event linting to the doclint - improves `api.md` to add events and more information about classes reported by events. References #14.
This commit is contained in:
parent
ac75455983
commit
6eac22dd87
85
docs/api.md
85
docs/api.md
@ -13,6 +13,17 @@
|
||||
* [browser.stdout](#browserstdout)
|
||||
* [browser.version()](#browserversion)
|
||||
- [class: Page](#class-page)
|
||||
* [event: 'consolemessage'](#event-consolemessage)
|
||||
* [event: 'dialog'](#event-dialog)
|
||||
* [event: 'frameattached'](#event-frameattached)
|
||||
* [event: 'framedetached'](#event-framedetached)
|
||||
* [event: 'framenavigated'](#event-framenavigated)
|
||||
* [event: 'load'](#event-load)
|
||||
* [event: 'pageerror'](#event-pageerror)
|
||||
* [event: 'request'](#event-request)
|
||||
* [event: 'requestfailed'](#event-requestfailed)
|
||||
* [event: 'requestfinished'](#event-requestfinished)
|
||||
* [event: 'response'](#event-response)
|
||||
* [page.addScriptTag(url)](#pageaddscripttagurl)
|
||||
* [page.click(selector)](#pageclickselector)
|
||||
* [page.close()](#pageclose)
|
||||
@ -179,6 +190,59 @@ browser.newPage().then(async page =>
|
||||
});
|
||||
```
|
||||
|
||||
#### event: 'consolemessage'
|
||||
- <[string]>
|
||||
|
||||
Emitted when a page calls one of console API methods, e.g. `console.log`.
|
||||
|
||||
#### event: 'dialog'
|
||||
- <[Dialog]>
|
||||
|
||||
Emitted when a javascript dialog, such as `alert`, `prompt`, `confirm` or `beforeunload`, gets opened on the page. Puppeteer can take action to the dialog via dialog's [accept](#dialogacceptprompttext) or [dismiss](#dialogdismiss) methods.
|
||||
|
||||
#### event: 'frameattached'
|
||||
- <[Frame]>
|
||||
|
||||
Emitted when a frame gets attached.
|
||||
|
||||
#### event: 'framedetached'
|
||||
- <[Frame]>
|
||||
|
||||
Emitted when a frame gets detached.
|
||||
|
||||
#### event: 'framenavigated'
|
||||
- <[Frame]>
|
||||
|
||||
Emitted when a frame committed navigation.
|
||||
|
||||
#### event: 'load'
|
||||
|
||||
Emitted when a page's `load` event was dispatched.
|
||||
|
||||
#### event: 'pageerror'
|
||||
- <[string]>
|
||||
|
||||
Emitted when an unhandled exception happens on the page. The only argument of the event holds the exception message.
|
||||
|
||||
#### event: 'request'
|
||||
- <[Request]>
|
||||
|
||||
Emitted when a page issues a request. The [request] object is a read-only object. In order to intercept and mutate requests, see [page.setRequestInterceptor](#pagesetrequestinterceptorinterceptor)
|
||||
|
||||
#### event: 'requestfailed'
|
||||
- <[Request]>
|
||||
|
||||
Emitted when a request is failed.
|
||||
|
||||
#### event: 'requestfinished'
|
||||
- <[Request]>
|
||||
|
||||
Emitted when a request is successfully finished.
|
||||
|
||||
#### event: 'response'
|
||||
- <[Response]>
|
||||
|
||||
Emitted when a [response] is received.
|
||||
|
||||
#### page.addScriptTag(url)
|
||||
- `url` <[string]> Url of a script to be added
|
||||
@ -373,6 +437,9 @@ This is a shortcut for [page.mainFrame().url()](#frameurl)
|
||||
Shortcut for [page.mainFrame().waitFor(selector)](#framewaitforselector).
|
||||
|
||||
### class: Dialog
|
||||
|
||||
[Dialog] objects are dispatched by page via the ['dialog'](#event-dialog) event.
|
||||
|
||||
#### dialog.accept([promptText])
|
||||
- `promptText` <[string]> A text to enter in prompt. Does not cause any effects if the dialog's `type` is not prompt.
|
||||
- returns: <[Promise]> Promise which resolves when the dialog has being accepted.
|
||||
@ -389,6 +456,14 @@ Shortcut for [page.mainFrame().waitFor(selector)](#framewaitforselector).
|
||||
Dialog's type, could be one of the `alert`, `beforeunload`, `confirm` and `prompt`.
|
||||
|
||||
### class: Frame
|
||||
|
||||
At every point of time, page exposes its current frame tree via the [page.mainFrame()](#pagemainframe) and [frame.childFrames()](#framechildframes) methods.
|
||||
|
||||
[Frame] object's lifecycle is controlled by three events, dispatched on the page object:
|
||||
- ['frameattached'](#event-frameattached) - fired when the frame gets attached to the page. Frame could be attached to the page only once.
|
||||
- ['framenavigated'](#event-framenavigated) - fired when the frame commits navigation to a different URL.
|
||||
- ['framedetached'](#event-framedetached) - fired when the frame gets detached from the page. Frame could be detached from the page only once.
|
||||
|
||||
#### frame.childFrames()
|
||||
- returns: <[Array]<[Frame]>>
|
||||
|
||||
@ -446,6 +521,15 @@ immediately.
|
||||
|
||||
### class: Request
|
||||
|
||||
Whenever the page sends a request, the following events are emitted by puppeteer's page:
|
||||
- ['request'](#event-request) emitted when the request is issued by the page.
|
||||
- ['response'](#event-response) emitted when/if the response is received for the request.
|
||||
- ['requestfinished'](#event-requestfinished) emitted when the response body is downloaded and the request is complete.
|
||||
|
||||
If request fails at some point, then instead of 'requestfinished' event (and possibly instead of 'response' event), the ['requestfailed'](#event-requestfailed) event is emitted.
|
||||
|
||||
If request gets a 'redirect' response, the request is successfully finished with the 'requestfinished' event, and a new request is issued to a redirected url.
|
||||
|
||||
[Request] class represents requests which are sent by page. [Request] implements [Body] mixin, which in case of HTTP POST requests allows clients to call `request.json()` or `request.text()` to get different representations of request's body.
|
||||
|
||||
#### request.headers
|
||||
@ -616,3 +700,4 @@ If there's already a header with name `name`, the header gets overwritten.
|
||||
[Request]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-request "Request"
|
||||
[Browser]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-browser "Browser"
|
||||
[Body]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-body "Body"
|
||||
[Dialog]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-dialog "Dialog"
|
||||
|
@ -66,6 +66,14 @@ class Documentation {
|
||||
errors.push(`Non-existing property found: ${className}.${propertyName}`);
|
||||
for (let propertyName of propertyDiff.missing)
|
||||
errors.push(`Property not found: ${className}.${propertyName}`);
|
||||
|
||||
const actualEvents = Array.from(actualClass.events.keys()).sort();
|
||||
const expectedEvents = Array.from(expectedClass.events.keys()).sort();
|
||||
const eventsDiff = diff(actualEvents, expectedEvents);
|
||||
for (let eventName of eventsDiff.extra)
|
||||
errors.push(`Non-existing event found in class ${className}: '${eventName}'`);
|
||||
for (let eventName of eventsDiff.missing)
|
||||
errors.push(`Event not found in class ${className}: '${eventName}'`);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
@ -110,12 +118,15 @@ Documentation.Class = class {
|
||||
this.members = new Map();
|
||||
this.properties = new Map();
|
||||
this.methods = new Map();
|
||||
this.events = new Map();
|
||||
for (let member of membersArray) {
|
||||
this.members.set(member.name, member);
|
||||
if (member.type === 'method')
|
||||
this.methods.set(member.name, member);
|
||||
else if (member.type === 'property')
|
||||
this.properties.set(member.name, member);
|
||||
else if (member.type === 'event')
|
||||
this.events.set(member.name, member);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -156,6 +167,14 @@ Documentation.Member = class {
|
||||
static createProperty(name) {
|
||||
return new Documentation.Member('property', name, [], false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @return {!Documentation.Member}
|
||||
*/
|
||||
static createEvent(name) {
|
||||
return new Documentation.Member('event', name, [], false, false);
|
||||
}
|
||||
};
|
||||
|
||||
Documentation.Argument = class {
|
||||
|
@ -7,6 +7,8 @@ const Documentation = require('./Documentation');
|
||||
class JSOutline {
|
||||
constructor(text) {
|
||||
this.classes = [];
|
||||
this.errors = [];
|
||||
this._eventsByClassName = new Map();
|
||||
this._currentClassName = null;
|
||||
this._currentClassMembers = [];
|
||||
|
||||
@ -17,9 +19,12 @@ class JSOutline {
|
||||
this._onClassDeclaration(node);
|
||||
else if (node.type === 'MethodDefinition')
|
||||
this._onMethodDefinition(node);
|
||||
else if (node.type === 'AssignmentExpression')
|
||||
this._onAssignmentExpression(node);
|
||||
});
|
||||
walker.walk(ast);
|
||||
this._flushClassIfNeeded();
|
||||
this._recreateClassesWithEvents();
|
||||
}
|
||||
|
||||
_onClassDeclaration(node) {
|
||||
@ -68,6 +73,24 @@ class JSOutline {
|
||||
return ESTreeWalker.SkipSubtree;
|
||||
}
|
||||
|
||||
_onAssignmentExpression(node) {
|
||||
if (node.left.type !== 'MemberExpression' || node.right.type !== 'ObjectExpression')
|
||||
return;
|
||||
if (node.left.object.type !== 'Identifier' || node.left.property.type !== 'Identifier' || node.left.property.name !== 'Events')
|
||||
return;
|
||||
const className = node.left.object.name;
|
||||
let events = this._eventsByClassName.get(className);
|
||||
if (!events) {
|
||||
events = [];
|
||||
this._eventsByClassName.set(className, events);
|
||||
}
|
||||
for (let property of node.right.properties) {
|
||||
if (property.type !== 'Property' || property.key.type !== 'Identifier' || property.value.type !== 'Literal')
|
||||
continue;
|
||||
events.push(Documentation.Member.createEvent(property.value.value));
|
||||
}
|
||||
}
|
||||
|
||||
_flushClassIfNeeded() {
|
||||
if (this._currentClassName === null)
|
||||
return;
|
||||
@ -77,6 +100,14 @@ class JSOutline {
|
||||
this._currentClassMembers = [];
|
||||
}
|
||||
|
||||
_recreateClassesWithEvents() {
|
||||
this.classes = this.classes.map(cls => {
|
||||
let events = this._eventsByClassName.get(cls.name) || [];
|
||||
let members = cls.membersArray.concat(events);
|
||||
return new Documentation.Class(cls.name, members);
|
||||
});
|
||||
}
|
||||
|
||||
_extractText(node) {
|
||||
if (!node)
|
||||
return null;
|
||||
|
@ -55,6 +55,7 @@ class MDOutline {
|
||||
const constructorRegex = /^new (\w+)\((.*)\)$/;
|
||||
const methodRegex = /^(\w+)\.(\w+)\((.*)\)$/;
|
||||
const propertyRegex = /^(\w+)\.(\w+)$/;
|
||||
const eventRegex = /^event: '(\w+)'$/;
|
||||
let currentClassName = null;
|
||||
let currentClassMembers = [];
|
||||
for (const cls of classes) {
|
||||
@ -72,6 +73,9 @@ class MDOutline {
|
||||
} else if (propertyRegex.test(member.name)) {
|
||||
let match = member.name.match(propertyRegex);
|
||||
handleProperty.call(this, member, match[1], match[2]);
|
||||
} else if (eventRegex.test(member.name)) {
|
||||
let match = member.name.match(eventRegex);
|
||||
handleEvent.call(this, member, match[1]);
|
||||
}
|
||||
}
|
||||
flushClassIfNeeded.call(this);
|
||||
@ -98,6 +102,14 @@ class MDOutline {
|
||||
currentClassMembers.push(Documentation.Member.createProperty(propertyName));
|
||||
}
|
||||
|
||||
function handleEvent(member, eventName) {
|
||||
if (!currentClassName || !eventName) {
|
||||
this.errors.push(`Failed to process header as event: ${member.name}`);
|
||||
return;
|
||||
}
|
||||
currentClassMembers.push(Documentation.Member.createEvent(eventName));
|
||||
}
|
||||
|
||||
function flushClassIfNeeded() {
|
||||
if (currentClassName === null)
|
||||
return;
|
||||
|
@ -61,20 +61,44 @@ async function lint(page, docsFolderPath, jsFolderPath) {
|
||||
*/
|
||||
function lintMarkdown(doc) {
|
||||
const errors = [];
|
||||
// Methods should be sorted alphabetically.
|
||||
for (let cls of doc.classesArray) {
|
||||
for (let i = 0; i < cls.membersArray.length - 1; ++i) {
|
||||
// Constructor always goes first.
|
||||
if (cls.membersArray[i].name === 'constructor') {
|
||||
if (i > 0)
|
||||
let members = cls.membersArray;
|
||||
|
||||
// Events should go first.
|
||||
let eventIndex = 0;
|
||||
for (; eventIndex < members.length && members[eventIndex].type === 'event'; ++eventIndex);
|
||||
for (; eventIndex < members.length && members[eventIndex].type !== 'event'; ++eventIndex);
|
||||
if (eventIndex < members.length)
|
||||
errors.push(`Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order`);
|
||||
|
||||
// Constructor should be right after events and before all other members.
|
||||
let constructorIndex = members.findIndex(member => member.type === 'method' && member.name === 'constructor');
|
||||
if (constructorIndex > 0 && members[constructorIndex - 1].type !== 'event')
|
||||
errors.push(`Constructor of ${cls.name} should go before other methods`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Events should be sorted alphabetically.
|
||||
for (let i = 0; i < members.length - 1; ++i) {
|
||||
let member1 = cls.membersArray[i];
|
||||
let member2 = cls.membersArray[i + 1];
|
||||
if (member1.type !== 'event' || member2.type !== 'event')
|
||||
continue;
|
||||
if (member1.name > member2.name)
|
||||
errors.push(`Event '${member1.name}' in class ${cls.name} breaks alphabetic ordering of events`);
|
||||
}
|
||||
|
||||
// All other members should be sorted alphabetically.
|
||||
for (let i = 0; i < members.length - 1; ++i) {
|
||||
let member1 = cls.membersArray[i];
|
||||
let member2 = cls.membersArray[i + 1];
|
||||
if (member1.type === 'event' || member2.type === 'event')
|
||||
continue;
|
||||
if (member1.type === 'method' && member1.name === 'constructor')
|
||||
continue;
|
||||
if (member1.name > member2.name) {
|
||||
let memberName = `${cls.name}.${member1.name}` + (member1.type === 'method' ? '()' : '');
|
||||
errors.push(`${memberName} breaks alphabetic member sorting inside class ${cls.name}`);
|
||||
let memberName = `${cls.name}.${member1.name}`;
|
||||
if (member1.type === 'method')
|
||||
memberName += '()';
|
||||
errors.push(`${memberName} breaks alphabetic ordering of class members.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
### class: Foo
|
||||
|
||||
#### event: 'c'
|
||||
|
||||
#### event: 'a'
|
||||
|
||||
#### foo.aaa()
|
||||
|
||||
#### new Foo()
|
||||
#### event: 'b'
|
||||
|
||||
#### foo.ddd
|
||||
|
||||
#### new Foo()
|
||||
|
||||
#### foo.ccc()
|
||||
|
||||
#### foo.bbb()
|
||||
|
@ -9,3 +9,9 @@ class Foo {
|
||||
|
||||
ccc() {}
|
||||
}
|
||||
|
||||
Foo.Events = {
|
||||
a: 'a',
|
||||
b: 'b',
|
||||
c: 'c'
|
||||
}
|
||||
|
5
utils/doclint/test/09-event-errors/doc.md
Normal file
5
utils/doclint/test/09-event-errors/doc.md
Normal file
@ -0,0 +1,5 @@
|
||||
### class: Foo
|
||||
|
||||
#### event: 'start'
|
||||
|
||||
#### event: 'stop'
|
7
utils/doclint/test/09-event-errors/foo.js
Normal file
7
utils/doclint/test/09-event-errors/foo.js
Normal file
@ -0,0 +1,7 @@
|
||||
class Foo {
|
||||
}
|
||||
|
||||
Foo.Events = {
|
||||
Start: 'start',
|
||||
Finish: 'finish',
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
[MarkDown] Events should go first. Event 'b' in class Foo breaks order
|
||||
[MarkDown] Constructor of Foo should go before other methods
|
||||
[MarkDown] Foo.ddd breaks alphabetic member sorting inside class Foo
|
||||
[MarkDown] Foo.ccc() breaks alphabetic member sorting inside class Foo
|
||||
[MarkDown] Event 'c' in class Foo breaks alphabetic ordering of events
|
||||
[MarkDown] Foo.ddd breaks alphabetic ordering of class members.
|
||||
[MarkDown] Foo.ccc() breaks alphabetic ordering of class members.
|
2
utils/doclint/test/golden/09-event-errors.txt
Normal file
2
utils/doclint/test/golden/09-event-errors.txt
Normal file
@ -0,0 +1,2 @@
|
||||
[MarkDown] Non-existing event found in class Foo: 'stop'
|
||||
[MarkDown] Event not found in class Foo: 'finish'
|
@ -39,6 +39,7 @@ describe('doclint', function() {
|
||||
it('06-duplicates', SX(test));
|
||||
it('07-sorting', SX(test));
|
||||
it('08-return', SX(test));
|
||||
it('09-event-errors', SX(test));
|
||||
});
|
||||
|
||||
async function test() {
|
||||
|
Loading…
Reference in New Issue
Block a user