chore: remove doclint type checking (#8549)
This commit is contained in:
parent
347101883f
commit
93d6c6deae
@ -1,161 +0,0 @@
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class Documentation {
|
||||
/**
|
||||
* @param {!Array<!Documentation.Class>} classesArray
|
||||
*/
|
||||
constructor(classesArray) {
|
||||
this.classesArray = classesArray;
|
||||
/** @type {!Map<string, !Documentation.Class>} */
|
||||
this.classes = new Map();
|
||||
for (const cls of classesArray) {
|
||||
this.classes.set(cls.name, cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Documentation.Class = class {
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {!Array<!Documentation.Member>} membersArray
|
||||
* @param {?string=} extendsName
|
||||
* @param {string=} comment
|
||||
*/
|
||||
constructor(name, membersArray, extendsName = null, comment = '') {
|
||||
this.name = name;
|
||||
this.membersArray = membersArray;
|
||||
/** @type {!Map<string, !Documentation.Member>} */
|
||||
this.members = new Map();
|
||||
/** @type {!Map<string, !Documentation.Member>} */
|
||||
this.properties = new Map();
|
||||
/** @type {!Array<!Documentation.Member>} */
|
||||
this.propertiesArray = [];
|
||||
/** @type {!Map<string, !Documentation.Member>} */
|
||||
this.methods = new Map();
|
||||
/** @type {!Array<!Documentation.Member>} */
|
||||
this.methodsArray = [];
|
||||
/** @type {!Map<string, !Documentation.Member>} */
|
||||
this.events = new Map();
|
||||
/** @type {!Array<!Documentation.Member>} */
|
||||
this.eventsArray = [];
|
||||
this.comment = comment;
|
||||
this.extends = extendsName;
|
||||
for (const member of membersArray) {
|
||||
this.members.set(member.name, member);
|
||||
if (member.kind === 'method') {
|
||||
this.methods.set(member.name, member);
|
||||
this.methodsArray.push(member);
|
||||
} else if (member.kind === 'property') {
|
||||
this.properties.set(member.name, member);
|
||||
this.propertiesArray.push(member);
|
||||
} else if (member.kind === 'event') {
|
||||
this.events.set(member.name, member);
|
||||
this.eventsArray.push(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Documentation.Member = class {
|
||||
/**
|
||||
* @param {string} kind
|
||||
* @param {string} name
|
||||
* @param {?Documentation.Type} type
|
||||
* @param {!Array<!Documentation.Member>} argsArray
|
||||
*/
|
||||
constructor(
|
||||
kind,
|
||||
name,
|
||||
type,
|
||||
argsArray,
|
||||
comment = '',
|
||||
returnComment = '',
|
||||
required = true
|
||||
) {
|
||||
this.kind = kind;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.comment = comment;
|
||||
this.returnComment = returnComment;
|
||||
this.argsArray = argsArray;
|
||||
this.required = required;
|
||||
/** @type {!Map<string, !Documentation.Member>} */
|
||||
this.args = new Map();
|
||||
for (const arg of argsArray) {
|
||||
this.args.set(arg.name, arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {!Array<!Documentation.Member>} argsArray
|
||||
* @param {?Documentation.Type} returnType
|
||||
* @returns {!Documentation.Member}
|
||||
*/
|
||||
static createMethod(name, argsArray, returnType, returnComment, comment) {
|
||||
return new Documentation.Member(
|
||||
'method',
|
||||
name,
|
||||
returnType,
|
||||
argsArray,
|
||||
comment,
|
||||
returnComment
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {!Documentation.Type} type
|
||||
* @param {string=} comment
|
||||
* @param {boolean=} required
|
||||
* @returns {!Documentation.Member}
|
||||
*/
|
||||
static createProperty(name, type, comment, required) {
|
||||
return new Documentation.Member(
|
||||
'property',
|
||||
name,
|
||||
type,
|
||||
[],
|
||||
comment,
|
||||
undefined,
|
||||
required
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {?Documentation.Type=} type
|
||||
* @param {string=} comment
|
||||
* @returns {!Documentation.Member}
|
||||
*/
|
||||
static createEvent(name, type = null, comment) {
|
||||
return new Documentation.Member('event', name, type, [], comment);
|
||||
}
|
||||
};
|
||||
|
||||
Documentation.Type = class {
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {!Array<!Documentation.Member>=} properties
|
||||
*/
|
||||
constructor(name, properties = []) {
|
||||
this.name = name;
|
||||
this.properties = properties;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Documentation;
|
@ -1,312 +0,0 @@
|
||||
const ts = require('typescript');
|
||||
const path = require('path');
|
||||
const Documentation = require('./Documentation.js');
|
||||
module.exports = checkSources;
|
||||
|
||||
/**
|
||||
* @param {!Array<!import('../Source')>} sources
|
||||
*/
|
||||
function checkSources(sources) {
|
||||
// special treatment for Events.js
|
||||
const classEvents = new Map();
|
||||
const eventsSource = sources.find((source) => source.name() === 'Events.js');
|
||||
if (eventsSource) {
|
||||
const { Events } = require(eventsSource.filePath());
|
||||
for (const [className, events] of Object.entries(Events)) {
|
||||
classEvents.set(
|
||||
className,
|
||||
Array.from(Object.values(events))
|
||||
.filter((e) => typeof e === 'string')
|
||||
.map((e) => Documentation.Member.createEvent(e))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const excludeClasses = new Set([]);
|
||||
const program = ts.createProgram({
|
||||
options: {
|
||||
allowJs: true,
|
||||
target: ts.ScriptTarget.ES2017,
|
||||
},
|
||||
rootNames: sources.map((source) => source.filePath()),
|
||||
});
|
||||
const checker = program.getTypeChecker();
|
||||
const sourceFiles = program.getSourceFiles();
|
||||
/** @type {!Array<!Documentation.Class>} */
|
||||
const classes = [];
|
||||
/** @type {!Map<string, string>} */
|
||||
const inheritance = new Map();
|
||||
|
||||
const sourceFilesNoNodeModules = sourceFiles.filter(
|
||||
(x) => !x.fileName.includes('node_modules')
|
||||
);
|
||||
const sourceFileNamesSet = new Set(
|
||||
sourceFilesNoNodeModules.map((x) => x.fileName)
|
||||
);
|
||||
sourceFilesNoNodeModules.map((x) => {
|
||||
if (x.fileName.includes('/lib/')) {
|
||||
const potentialTSSource = x.fileName
|
||||
.replace('lib', 'src')
|
||||
.replace('.js', '.ts');
|
||||
if (sourceFileNamesSet.has(potentialTSSource)) {
|
||||
/* Not going to visit this file because we have the TypeScript src code
|
||||
* which we'll use instead.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
visit(x);
|
||||
});
|
||||
|
||||
const errors = [];
|
||||
const documentation = new Documentation(
|
||||
recreateClassesWithInheritance(classes, inheritance)
|
||||
);
|
||||
|
||||
return { errors, documentation };
|
||||
|
||||
/**
|
||||
* @param {!Array<!Documentation.Class>} classes
|
||||
* @param {!Map<string, string>} inheritance
|
||||
* @returns {!Array<!Documentation.Class>}
|
||||
*/
|
||||
function recreateClassesWithInheritance(classes, inheritance) {
|
||||
const classesByName = new Map(classes.map((cls) => [cls.name, cls]));
|
||||
return classes.map((cls) => {
|
||||
const membersMap = new Map();
|
||||
for (let wp = cls; wp; wp = classesByName.get(inheritance.get(wp.name))) {
|
||||
for (const member of wp.membersArray) {
|
||||
// Member was overridden.
|
||||
const memberId = member.kind + ':' + member.name;
|
||||
if (membersMap.has(memberId)) {
|
||||
continue;
|
||||
}
|
||||
membersMap.set(memberId, member);
|
||||
}
|
||||
}
|
||||
return new Documentation.Class(cls.name, Array.from(membersMap.values()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ts.Node} node
|
||||
*/
|
||||
function visit(node) {
|
||||
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
||||
const symbol = node.name
|
||||
? checker.getSymbolAtLocation(node.name)
|
||||
: node.symbol;
|
||||
let className = symbol.getName();
|
||||
|
||||
if (className === '__class') {
|
||||
let parent = node;
|
||||
while (parent.parent) {
|
||||
parent = parent.parent;
|
||||
}
|
||||
className = path.basename(parent.fileName, '.js');
|
||||
}
|
||||
if (className && !excludeClasses.has(className)) {
|
||||
classes.push(serializeClass(className, symbol, node));
|
||||
const parentClassName = parentClass(node);
|
||||
if (parentClassName) {
|
||||
inheritance.set(className, parentClassName);
|
||||
}
|
||||
excludeClasses.add(className);
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
|
||||
function parentClass(classNode) {
|
||||
for (const herigateClause of classNode.heritageClauses || []) {
|
||||
for (const heritageType of herigateClause.types) {
|
||||
const parentClassName = heritageType.expression.escapedText;
|
||||
return parentClassName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function serializeSymbol(symbol, circular = []) {
|
||||
const type = checker.getTypeOfSymbolAtLocation(
|
||||
symbol,
|
||||
symbol.valueDeclaration
|
||||
);
|
||||
const name = symbol.getName();
|
||||
if (symbol.valueDeclaration && symbol.valueDeclaration.dotDotDotToken) {
|
||||
try {
|
||||
const innerType = serializeType(type.typeArguments[0], circular);
|
||||
innerType.name = '...' + innerType.name;
|
||||
return Documentation.Member.createProperty('...' + name, innerType);
|
||||
} catch (error) {
|
||||
/**
|
||||
* DocLint struggles with the paramArgs type on CDPSession.send because
|
||||
* it uses a complex type from the devtools-protocol method. Doclint
|
||||
* isn't going to be here for much longer so we'll just silence this
|
||||
* warning than try to add support which would warrant a huge rewrite.
|
||||
*/
|
||||
if (name !== 'paramArgs') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Documentation.Member.createProperty(
|
||||
name,
|
||||
serializeType(type, circular)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ts.ObjectType} type
|
||||
*/
|
||||
function isRegularObject(type) {
|
||||
if (type.isIntersection()) {
|
||||
return true;
|
||||
}
|
||||
if (!type.objectFlags) {
|
||||
return false;
|
||||
}
|
||||
if (!('aliasSymbol' in type)) {
|
||||
return false;
|
||||
}
|
||||
if (type.getConstructSignatures().length) {
|
||||
return false;
|
||||
}
|
||||
if (type.getCallSignatures().length) {
|
||||
return false;
|
||||
}
|
||||
if (type.isLiteral()) {
|
||||
return false;
|
||||
}
|
||||
if (type.isUnion()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ts.Type} type
|
||||
* @returns {!Documentation.Type}
|
||||
*/
|
||||
function serializeType(type, circular = []) {
|
||||
let typeName = checker.typeToString(type);
|
||||
if (
|
||||
typeName === 'any' ||
|
||||
typeName === '{ [x: string]: string; }' ||
|
||||
typeName === '{}'
|
||||
) {
|
||||
typeName = 'Object';
|
||||
}
|
||||
const nextCircular = [typeName].concat(circular);
|
||||
|
||||
if (isRegularObject(type)) {
|
||||
let properties = undefined;
|
||||
if (!circular.includes(typeName)) {
|
||||
properties = type
|
||||
.getProperties()
|
||||
.map((property) => serializeSymbol(property, nextCircular));
|
||||
}
|
||||
return new Documentation.Type('Object', properties);
|
||||
}
|
||||
if (type.isUnion() && typeName.includes('|')) {
|
||||
const types = type.types.map((type) => serializeType(type, circular));
|
||||
const name = types.map((type) => type.name).join('|');
|
||||
const properties = [].concat(...types.map((type) => type.properties));
|
||||
return new Documentation.Type(
|
||||
name.replace(/false\|true/g, 'boolean'),
|
||||
properties
|
||||
);
|
||||
}
|
||||
if (type.typeArguments) {
|
||||
const properties = [];
|
||||
const innerTypeNames = [];
|
||||
for (const typeArgument of type.typeArguments) {
|
||||
const innerType = serializeType(typeArgument, nextCircular);
|
||||
if (innerType.properties) {
|
||||
properties.push(...innerType.properties);
|
||||
}
|
||||
innerTypeNames.push(innerType.name);
|
||||
}
|
||||
if (
|
||||
innerTypeNames.length === 0 ||
|
||||
(innerTypeNames.length === 1 && innerTypeNames[0] === 'void')
|
||||
) {
|
||||
return new Documentation.Type(type.symbol.name);
|
||||
}
|
||||
return new Documentation.Type(
|
||||
`${type.symbol.name}<${innerTypeNames.join(', ')}>`,
|
||||
properties
|
||||
);
|
||||
}
|
||||
return new Documentation.Type(typeName, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!ts.Symbol} symbol
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function symbolHasPrivateModifier(symbol) {
|
||||
const modifiers =
|
||||
(symbol.valueDeclaration && symbol.valueDeclaration.modifiers) || [];
|
||||
return modifiers.some(
|
||||
(modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} className
|
||||
* @param {!ts.Symbol} symbol
|
||||
* @returns {}
|
||||
*/
|
||||
function serializeClass(className, symbol, node) {
|
||||
/** @type {!Array<!Documentation.Member>} */
|
||||
const members = classEvents.get(className) || [];
|
||||
|
||||
for (const [name, member] of symbol.members || []) {
|
||||
/* Before TypeScript we denoted private methods with an underscore
|
||||
* but in TypeScript we use the private keyword
|
||||
* hence we check for either here.
|
||||
*/
|
||||
if (name.startsWith('_') || symbolHasPrivateModifier(member)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const memberType = checker.getTypeOfSymbolAtLocation(
|
||||
member,
|
||||
member.valueDeclaration
|
||||
);
|
||||
const signature = memberType.getCallSignatures()[0];
|
||||
if (signature) {
|
||||
members.push(serializeSignature(name, signature));
|
||||
} else {
|
||||
members.push(serializeProperty(name, memberType));
|
||||
}
|
||||
}
|
||||
|
||||
return new Documentation.Class(className, members);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {!ts.Signature} signature
|
||||
*/
|
||||
function serializeSignature(name, signature) {
|
||||
const parameters = signature.parameters.map((s) => serializeSymbol(s));
|
||||
const returnType = serializeType(signature.getReturnType());
|
||||
return Documentation.Member.createMethod(
|
||||
name,
|
||||
parameters,
|
||||
returnType.name !== 'void' ? returnType : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {!ts.Type} type
|
||||
*/
|
||||
function serializeProperty(name, type) {
|
||||
return Documentation.Member.createProperty(name, serializeType(type));
|
||||
}
|
||||
}
|
@ -1,417 +0,0 @@
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const Documentation = require('./Documentation.js');
|
||||
const { Parser, HtmlRenderer } = require('commonmark');
|
||||
|
||||
class MDOutline {
|
||||
/**
|
||||
* @param {!Page} page
|
||||
* @param {string} text
|
||||
* @returns {!MDOutline}
|
||||
*/
|
||||
static async create(page, text) {
|
||||
// Render markdown as HTML.
|
||||
const reader = new Parser();
|
||||
const parsed = reader.parse(text);
|
||||
const writer = new HtmlRenderer();
|
||||
const html = writer.render(parsed);
|
||||
|
||||
page.on('console', (msg) => {
|
||||
console.log(msg.text());
|
||||
});
|
||||
// Extract headings.
|
||||
await page.setContent(html);
|
||||
const { classes, errors } = await page.evaluate(() => {
|
||||
const classes = [];
|
||||
const errors = [];
|
||||
const headers = document.body.querySelectorAll('h3');
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const fragment = extractSiblingsIntoFragment(
|
||||
headers[i],
|
||||
headers[i + 1]
|
||||
);
|
||||
classes.push(parseClass(fragment));
|
||||
}
|
||||
return { classes, errors };
|
||||
|
||||
/**
|
||||
* @param {HTMLLIElement} element
|
||||
*/
|
||||
function parseProperty(element) {
|
||||
const clone = element.cloneNode(true);
|
||||
const ul = clone.querySelector(':scope > ul');
|
||||
const str = parseComment(
|
||||
extractSiblingsIntoFragment(clone.firstChild, ul)
|
||||
);
|
||||
const name = str
|
||||
.substring(0, str.indexOf('<'))
|
||||
.replace(/\`/g, '')
|
||||
.trim();
|
||||
const type = findType(str);
|
||||
const properties = [];
|
||||
const comment = str
|
||||
.substring(str.indexOf('<') + type.length + 2)
|
||||
.trim();
|
||||
// Strings have enum values instead of properties
|
||||
if (!type.includes('string')) {
|
||||
for (const childElement of element.querySelectorAll(
|
||||
':scope > ul > li'
|
||||
)) {
|
||||
const property = parseProperty(childElement);
|
||||
property.required = property.comment.includes('***required***');
|
||||
properties.push(property);
|
||||
}
|
||||
}
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
comment,
|
||||
properties,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function findType(str) {
|
||||
const start = str.indexOf('<') + 1;
|
||||
let count = 1;
|
||||
for (let i = start; i < str.length; i++) {
|
||||
if (str[i] === '<') {
|
||||
count++;
|
||||
}
|
||||
if (str[i] === '>') {
|
||||
count--;
|
||||
}
|
||||
if (!count) {
|
||||
return str.substring(start, i);
|
||||
}
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DocumentFragment} content
|
||||
*/
|
||||
function parseClass(content) {
|
||||
const members = [];
|
||||
const headers = content.querySelectorAll('h4');
|
||||
const name = content.firstChild.textContent;
|
||||
let extendsName = null;
|
||||
let commentStart = content.firstChild.nextSibling;
|
||||
const extendsElement = content.querySelector('ul');
|
||||
if (
|
||||
extendsElement &&
|
||||
extendsElement.textContent.trim().startsWith('extends:')
|
||||
) {
|
||||
commentStart = extendsElement.nextSibling;
|
||||
extendsName = extendsElement.querySelector('a').textContent;
|
||||
}
|
||||
const comment = parseComment(
|
||||
extractSiblingsIntoFragment(commentStart, headers[0])
|
||||
);
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const fragment = extractSiblingsIntoFragment(
|
||||
headers[i],
|
||||
headers[i + 1]
|
||||
);
|
||||
members.push(parseMember(fragment));
|
||||
}
|
||||
return {
|
||||
name,
|
||||
comment,
|
||||
extendsName,
|
||||
members,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} content
|
||||
*/
|
||||
function parseComment(content) {
|
||||
for (const code of content.querySelectorAll('pre > code')) {
|
||||
code.replaceWith(
|
||||
'```' +
|
||||
code.className.substring('language-'.length) +
|
||||
'\n' +
|
||||
code.textContent +
|
||||
'```'
|
||||
);
|
||||
}
|
||||
for (const code of content.querySelectorAll('code')) {
|
||||
code.replaceWith('`' + code.textContent + '`');
|
||||
}
|
||||
for (const strong of content.querySelectorAll('strong')) {
|
||||
strong.replaceWith('**' + parseComment(strong) + '**');
|
||||
}
|
||||
return content.textContent.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {DocumentFragment} content
|
||||
*/
|
||||
function parseMember(content) {
|
||||
const name = content.firstChild.textContent;
|
||||
const args = [];
|
||||
let returnType = null;
|
||||
|
||||
const paramRegex = /^\w+\.[\w$]+\((.*)\)$/;
|
||||
const matches = paramRegex.exec(name) || ['', ''];
|
||||
const parameters = matches[1];
|
||||
const optionalStartIndex = parameters.indexOf('[');
|
||||
const optinalParamsStr =
|
||||
optionalStartIndex !== -1
|
||||
? parameters.substring(optionalStartIndex).replace(/[\[\]]/g, '')
|
||||
: '';
|
||||
const optionalparams = new Set(
|
||||
optinalParamsStr
|
||||
.split(',')
|
||||
.filter((x) => x)
|
||||
.map((x) => x.trim())
|
||||
);
|
||||
const ul = content.querySelector('ul');
|
||||
for (const element of content.querySelectorAll('h4 + ul > li')) {
|
||||
if (
|
||||
element.matches('li') &&
|
||||
element.textContent.trim().startsWith('<')
|
||||
) {
|
||||
returnType = parseProperty(element);
|
||||
} else if (
|
||||
element.matches('li') &&
|
||||
element.firstChild.matches &&
|
||||
element.firstChild.matches('code')
|
||||
) {
|
||||
const property = parseProperty(element);
|
||||
property.required = !optionalparams.has(property.name);
|
||||
args.push(property);
|
||||
} else if (
|
||||
element.matches('li') &&
|
||||
element.firstChild.nodeType === Element.TEXT_NODE &&
|
||||
element.firstChild.textContent.toLowerCase().startsWith('return')
|
||||
) {
|
||||
returnType = parseProperty(element);
|
||||
const expectedText = 'returns: ';
|
||||
let actualText = element.firstChild.textContent;
|
||||
let angleIndex = actualText.indexOf('<');
|
||||
let spaceIndex = actualText.indexOf(' ');
|
||||
angleIndex = angleIndex === -1 ? actualText.length : angleIndex;
|
||||
spaceIndex = spaceIndex === -1 ? actualText.length : spaceIndex + 1;
|
||||
actualText = actualText.substring(
|
||||
0,
|
||||
Math.min(angleIndex, spaceIndex)
|
||||
);
|
||||
if (actualText !== expectedText) {
|
||||
errors.push(
|
||||
`${name} has mistyped 'return' type declaration: expected exactly '${expectedText}', found '${actualText}'.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const comment = parseComment(
|
||||
extractSiblingsIntoFragment(ul ? ul.nextSibling : content)
|
||||
);
|
||||
return {
|
||||
name,
|
||||
args,
|
||||
returnType,
|
||||
comment,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Node} fromInclusive
|
||||
* @param {!Node} toExclusive
|
||||
* @returns {!DocumentFragment}
|
||||
*/
|
||||
function extractSiblingsIntoFragment(fromInclusive, toExclusive) {
|
||||
const fragment = document.createDocumentFragment();
|
||||
let node = fromInclusive;
|
||||
while (node && node !== toExclusive) {
|
||||
const next = node.nextSibling;
|
||||
fragment.appendChild(node);
|
||||
node = next;
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
});
|
||||
return new MDOutline(classes, errors);
|
||||
}
|
||||
|
||||
constructor(classes, errors) {
|
||||
this.classes = [];
|
||||
this.errors = errors;
|
||||
const classHeading = /^class: (\w+)$/;
|
||||
const constructorRegex = /^new (\w+)\((.*)\)$/;
|
||||
const methodRegex = /^(\w+)\.([\w$]+)\((.*)\)$/;
|
||||
const propertyRegex = /^(\w+)\.(\w+)$/;
|
||||
const eventRegex = /^event: '(\w+)'$/;
|
||||
let currentClassName = null;
|
||||
let currentClassMembers = [];
|
||||
let currentClassComment = '';
|
||||
let currentClassExtends = null;
|
||||
for (const cls of classes) {
|
||||
const match = cls.name.match(classHeading);
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
currentClassName = match[1];
|
||||
currentClassComment = cls.comment;
|
||||
currentClassExtends = cls.extendsName;
|
||||
for (const member of cls.members) {
|
||||
if (constructorRegex.test(member.name)) {
|
||||
const match = member.name.match(constructorRegex);
|
||||
handleMethod.call(this, member, match[1], 'constructor', match[2]);
|
||||
} else if (methodRegex.test(member.name)) {
|
||||
const match = member.name.match(methodRegex);
|
||||
handleMethod.call(this, member, match[1], match[2], match[3]);
|
||||
} else if (propertyRegex.test(member.name)) {
|
||||
const match = member.name.match(propertyRegex);
|
||||
handleProperty.call(this, member, match[1], match[2]);
|
||||
} else if (eventRegex.test(member.name)) {
|
||||
const match = member.name.match(eventRegex);
|
||||
handleEvent.call(this, member, match[1]);
|
||||
}
|
||||
}
|
||||
flushClassIfNeeded.call(this);
|
||||
}
|
||||
|
||||
function handleMethod(member, className, methodName, parameters) {
|
||||
if (
|
||||
!currentClassName ||
|
||||
!className ||
|
||||
!methodName ||
|
||||
className.toLowerCase() !== currentClassName.toLowerCase()
|
||||
) {
|
||||
this.errors.push(`Failed to process header as method: ${member.name}`);
|
||||
return;
|
||||
}
|
||||
parameters = parameters.trim().replace(/[\[\]]/g, '');
|
||||
if (parameters !== member.args.map((arg) => arg.name).join(', ')) {
|
||||
this.errors.push(
|
||||
`Heading arguments for "${
|
||||
member.name
|
||||
}" do not match described ones, i.e. "${parameters}" != "${member.args
|
||||
.map((a) => a.name)
|
||||
.join(', ')}"`
|
||||
);
|
||||
}
|
||||
const args = member.args.map(createPropertyFromJSON);
|
||||
let returnType = null;
|
||||
let returnComment = '';
|
||||
if (member.returnType) {
|
||||
const returnProperty = createPropertyFromJSON(member.returnType);
|
||||
returnType = returnProperty.type;
|
||||
returnComment = returnProperty.comment;
|
||||
}
|
||||
const method = Documentation.Member.createMethod(
|
||||
methodName,
|
||||
args,
|
||||
returnType,
|
||||
returnComment,
|
||||
member.comment
|
||||
);
|
||||
currentClassMembers.push(method);
|
||||
}
|
||||
|
||||
function createPropertyFromJSON(payload) {
|
||||
const type = new Documentation.Type(
|
||||
payload.type,
|
||||
payload.properties.map(createPropertyFromJSON)
|
||||
);
|
||||
const required = payload.required;
|
||||
return Documentation.Member.createProperty(
|
||||
payload.name,
|
||||
type,
|
||||
payload.comment,
|
||||
required
|
||||
);
|
||||
}
|
||||
|
||||
function handleProperty(member, className, propertyName) {
|
||||
if (
|
||||
!currentClassName ||
|
||||
!className ||
|
||||
!propertyName ||
|
||||
className.toLowerCase() !== currentClassName.toLowerCase()
|
||||
) {
|
||||
this.errors.push(
|
||||
`Failed to process header as property: ${member.name}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const type = member.returnType ? member.returnType.type : null;
|
||||
const properties = member.returnType ? member.returnType.properties : [];
|
||||
currentClassMembers.push(
|
||||
createPropertyFromJSON({
|
||||
type,
|
||||
name: propertyName,
|
||||
properties,
|
||||
comment: member.comment,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
member.returnType && createPropertyFromJSON(member.returnType).type,
|
||||
member.comment
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function flushClassIfNeeded() {
|
||||
if (currentClassName === null) {
|
||||
return;
|
||||
}
|
||||
this.classes.push(
|
||||
new Documentation.Class(
|
||||
currentClassName,
|
||||
currentClassMembers,
|
||||
currentClassExtends,
|
||||
currentClassComment
|
||||
)
|
||||
);
|
||||
currentClassName = null;
|
||||
currentClassMembers = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Page} page
|
||||
* @param {!Array<!Source>} sources
|
||||
* @returns {!Promise<{documentation: !Documentation, errors: !Array<string>}>}
|
||||
*/
|
||||
module.exports = async function (page, sources) {
|
||||
const classes = [];
|
||||
const errors = [];
|
||||
for (const source of sources) {
|
||||
const outline = await MDOutline.create(page, source.text());
|
||||
classes.push(...outline.classes);
|
||||
errors.push(...outline.errors);
|
||||
}
|
||||
const documentation = new Documentation(classes);
|
||||
return { documentation, errors };
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -15,8 +15,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line import/extensions
|
||||
const puppeteer = require('../..');
|
||||
const path = require('path');
|
||||
const Source = require('./Source.js');
|
||||
|
||||
@ -72,38 +70,6 @@ async function run() {
|
||||
))
|
||||
);
|
||||
|
||||
const browser = await puppeteer.launch();
|
||||
const page = await browser.newPage();
|
||||
const checkPublicAPI = require('./check_public_api/index.js');
|
||||
const tsSources = [
|
||||
/* Source.readdir doesn't deal with nested directories well.
|
||||
* Rather than invest time here when we're going to remove this Doc tooling soon
|
||||
* we'll just list the directories manually.
|
||||
*/
|
||||
...(await Source.readdir(path.join(PROJECT_DIR, 'src'), 'ts')),
|
||||
...(await Source.readdir(path.join(PROJECT_DIR, 'src', 'common'), 'ts')),
|
||||
...(await Source.readdir(path.join(PROJECT_DIR, 'src', 'node'), 'ts')),
|
||||
];
|
||||
|
||||
const tsSourcesNoDefinitions = tsSources.filter(
|
||||
(source) => !source.filePath().endsWith('.d.ts')
|
||||
);
|
||||
|
||||
const jsSources = [
|
||||
...(await Source.readdir(path.join(PROJECT_DIR, 'lib'))),
|
||||
...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'cjs'))),
|
||||
...(await Source.readdir(
|
||||
path.join(PROJECT_DIR, 'lib', 'cjs', 'puppeteer', 'common')
|
||||
)),
|
||||
...(await Source.readdir(
|
||||
path.join(PROJECT_DIR, 'lib', 'cjs', 'puppeteer', 'node')
|
||||
)),
|
||||
];
|
||||
const allSrcCode = [...jsSources, ...tsSourcesNoDefinitions];
|
||||
messages.push(...(await checkPublicAPI(page, mdSources, allSrcCode)));
|
||||
|
||||
await browser.close();
|
||||
|
||||
for (const source of mdSources) {
|
||||
if (!source.hasUpdatedText()) {
|
||||
continue;
|
||||
|
Loading…
Reference in New Issue
Block a user