chore: remove doclint type checking (#8549)

This commit is contained in:
jrandolf 2022-06-23 08:39:28 +02:00 committed by GitHub
parent 347101883f
commit 93d6c6deae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 0 additions and 2197 deletions

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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

View File

@ -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;