chore: remove support for "experimental" from documentation (#30880)

Also add support for "hidden" and make `generate_types/index` actually
pass tsc checks.
This commit is contained in:
Dmitry Gozman 2024-05-20 10:30:32 -07:00 committed by GitHub
parent 437b14a903
commit b67b9634c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 62 additions and 97 deletions

View File

@ -77,7 +77,10 @@ class ApiParser {
continue;
}
}
const clazz = new docs.Class(extractMetainfo(node), name, [], extendsName, extractComments(node));
const metainfo = extractMetainfo(node);
const clazz = new docs.Class(metainfo, name, [], extendsName, extractComments(node));
if (metainfo.hidden)
return;
this.classes.set(clazz.name, clazz);
}
@ -103,13 +106,14 @@ class ApiParser {
returnType = new docs.Type('void');
const comments = extractComments(spec);
const metainfo = extractMetainfo(spec);
let member;
if (match[1] === 'event')
member = docs.Member.createEvent(extractMetainfo(spec), name, returnType, comments);
member = docs.Member.createEvent(metainfo, name, returnType, comments);
if (match[1] === 'property')
member = docs.Member.createProperty(extractMetainfo(spec), name, returnType, comments, !optional);
member = docs.Member.createProperty(metainfo, name, returnType, comments, !optional);
if (['method', 'async method', 'optional method', 'optional async method'].includes(match[1])) {
member = docs.Member.createMethod(extractMetainfo(spec), name, [], returnType, comments);
member = docs.Member.createMethod(metainfo, name, [], returnType, comments);
if (match[1].includes('async'))
member.async = true;
if (match[1].includes('optional'))
@ -119,6 +123,11 @@ class ApiParser {
throw new Error('Unknown member: ' + spec.text);
const clazz = /** @type {docs.Class} */(this.classes.get(match[2]));
if (!clazz)
throw new Error(`Unknown class ${match[2]} for member: ` + spec.text);
if (metainfo.hidden)
return;
const existingMember = clazz.membersArray.find(m => m.name === name && m.kind === member.kind);
if (existingMember && isTypeOverride(existingMember, member)) {
for (const lang of member?.langs?.only || []) {
@ -157,6 +166,8 @@ class ApiParser {
throw new Error('Invalid member name ' + spec.text);
if (match[1] === 'param') {
const arg = this.parseProperty(spec);
if (!arg)
return;
arg.name = name;
const existingArg = method.argsArray.find(m => m.name === arg.name);
if (existingArg && isTypeOverride(existingArg, arg)) {
@ -171,13 +182,15 @@ class ApiParser {
}
} else {
// match[1] === 'option'
const p = this.parseProperty(spec);
if (!p)
return;
let options = method.argsArray.find(o => o.name === 'options');
if (!options) {
const type = new docs.Type('Object', []);
options = docs.Member.createProperty({ langs: {}, experimental: false, since: 'v1.0', deprecated: undefined, discouraged: undefined }, 'options', type, undefined, false);
options = docs.Member.createProperty({ langs: {}, since: 'v1.0', deprecated: undefined, discouraged: undefined }, 'options', type, undefined, false);
method.argsArray.push(options);
}
const p = this.parseProperty(spec);
p.required = false;
// @ts-ignore
options.type.properties.push(p);
@ -186,6 +199,7 @@ class ApiParser {
/**
* @param {MarkdownHeaderNode} spec
* @returns {docs.Member | null}
*/
parseProperty(spec) {
const param = childrenWithoutProperties(spec)[0];
@ -196,12 +210,15 @@ class ApiParser {
const name = text.substring(0, typeStart).replace(/\`/g, '').trim();
const comments = extractComments(spec);
const { type, optional } = this.parseType(/** @type {MarkdownLiNode} */(param));
return docs.Member.createProperty(extractMetainfo(spec), name, type, comments, !optional);
const metainfo = extractMetainfo(spec);
if (metainfo.hidden)
return null;
return docs.Member.createProperty(metainfo, name, type, comments, !optional);
}
/**
* @param {MarkdownLiNode} spec
* @return {{ type: docs.Type, optional: boolean, experimental: boolean }}
* @return {{ type: docs.Type, optional: boolean }}
*/
parseType(spec) {
const arg = parseVariable(spec.text);
@ -210,16 +227,16 @@ class ApiParser {
const { name, text } = parseVariable(/** @type {string} */(child.text));
const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]);
const childType = this.parseType(child);
properties.push(docs.Member.createProperty({ langs: {}, experimental: childType.experimental, since: 'v1.0', deprecated: undefined, discouraged: undefined }, name, childType.type, comments, !childType.optional));
properties.push(docs.Member.createProperty({ langs: {}, since: 'v1.0', deprecated: undefined, discouraged: undefined }, name, childType.type, comments, !childType.optional));
}
const type = docs.Type.parse(arg.type, properties);
return { type, optional: arg.optional, experimental: arg.experimental };
return { type, optional: arg.optional };
}
}
/**
* @param {string} line
* @returns {{ name: string, type: string, text: string, optional: boolean, experimental: boolean }}
* @returns {{ name: string, type: string, text: string, optional: boolean }}
*/
function parseVariable(line) {
let match = line.match(/^`([^`]+)` (.*)/);
@ -234,12 +251,9 @@ function parseVariable(line) {
const name = match[1];
let remainder = match[2];
let optional = false;
let experimental = false;
while ('?e'.includes(remainder[0])) {
while ('?'.includes(remainder[0])) {
if (remainder[0] === '?')
optional = true;
else if (remainder[0] === 'e')
experimental = true;
remainder = remainder.substring(1);
}
if (!remainder.startsWith('<'))
@ -252,7 +266,7 @@ function parseVariable(line) {
if (c === '>')
--depth;
if (depth === 0)
return { name, type: remainder.substring(1, i), text: remainder.substring(i + 2), optional, experimental };
return { name, type: remainder.substring(1, i), text: remainder.substring(i + 2), optional };
}
throw new Error('Should not be reached, line: ' + line);
}
@ -344,15 +358,15 @@ function parseApi(apiDir, paramsPath) {
/**
* @param {MarkdownHeaderNode} spec
* @returns {import('./documentation').Metainfo}
* @returns {import('./documentation').Metainfo & { hidden: boolean }}
*/
function extractMetainfo(spec) {
return {
langs: extractLangs(spec),
since: extractSince(spec),
experimental: extractExperimental(spec),
deprecated: extractAttribute(spec, 'deprecated'),
discouraged: extractAttribute(spec, 'discouraged'),
hidden: extractHidden(spec),
};
}
@ -402,9 +416,9 @@ function extractSince(spec) {
* @param {MarkdownHeaderNode} spec
* @returns {boolean}
*/
function extractExperimental(spec) {
function extractHidden(spec) {
for (const child of spec.children) {
if (child.type === 'li' && child.liType === 'bullet' && child.text === 'experimental')
if (child.type === 'li' && child.liType === 'bullet' && child.text === 'hidden')
return true;
}
return false;
@ -429,7 +443,7 @@ function extractSince(spec) {
*/
function childrenWithoutProperties(spec) {
return (spec.children || []).filter(c => {
const isProperty = c.type === 'li' && c.liType === 'bullet' && (c.text.startsWith('langs:') || c.text.startsWith('since:') || c.text.startsWith('deprecated:') || c.text.startsWith('discouraged:') || c.text === 'experimental');
const isProperty = c.type === 'li' && c.liType === 'bullet' && (c.text.startsWith('langs:') || c.text.startsWith('since:') || c.text.startsWith('deprecated:') || c.text.startsWith('discouraged:') || c.text === 'hidden');
return !isProperty;
});
}

View File

@ -57,7 +57,6 @@ const md = require('../markdown');
* since: string,
* deprecated?: string | undefined,
* discouraged?: string | undefined,
* experimental: boolean
* }} Metainfo
*/
@ -132,18 +131,6 @@ class Documentation {
this.index();
}
filterOutExperimental() {
const classesArray = [];
for (const clazz of this.classesArray) {
if (clazz.experimental)
continue;
clazz.filterOutExperimental();
classesArray.push(clazz);
}
this.classesArray = classesArray;
this.index();
}
index() {
for (const cls of this.classesArray) {
this.classes.set(cls.name, cls);
@ -231,7 +218,6 @@ class Documentation {
*/
constructor(metainfo, name, membersArray, extendsName = null, spec = undefined) {
this.langs = metainfo.langs;
this.experimental = metainfo.experimental;
this.since = metainfo.since;
this.deprecated = metainfo.deprecated;
this.discouraged = metainfo.discouraged;
@ -286,7 +272,7 @@ class Documentation {
}
clone() {
const cls = new Class({ langs: this.langs, experimental: this.experimental, since: this.since, deprecated: this.deprecated, discouraged: this.discouraged }, this.name, this.membersArray.map(m => m.clone()), this.extends, this.spec);
const cls = new Class({ langs: this.langs, since: this.since, deprecated: this.deprecated, discouraged: this.discouraged }, this.name, this.membersArray.map(m => m.clone()), this.extends, this.spec);
cls.comment = this.comment;
return cls;
}
@ -306,17 +292,6 @@ class Documentation {
this.membersArray = membersArray;
}
filterOutExperimental() {
const membersArray = [];
for (const member of this.membersArray) {
if (member.experimental)
continue;
member.filterOutExperimental();
membersArray.push(member);
}
this.membersArray = membersArray;
}
sortMembers() {
/**
* @param {Member} member
@ -362,7 +337,6 @@ class Member {
constructor(kind, metainfo, name, type, argsArray, spec = undefined, required = true) {
this.kind = kind;
this.langs = metainfo.langs;
this.experimental = metainfo.experimental;
this.since = metainfo.since;
this.deprecated = metainfo.deprecated;
this.discouraged = metainfo.discouraged;
@ -447,22 +421,8 @@ class Member {
}
}
filterOutExperimental() {
if (!this.type)
return;
this.type.filterOutExperimental();
const argsArray = [];
for (const arg of this.argsArray) {
if (arg.experimental || !arg.type)
continue;
arg.type.filterOutExperimental();
argsArray.push(arg);
}
this.argsArray = argsArray;
}
clone() {
const result = new Member(this.kind, { langs: this.langs, experimental: this.experimental, since: this.since, deprecated: this.deprecated, discouraged: this.discouraged }, this.name, this.type?.clone(), this.argsArray.map(arg => arg.clone()), this.spec, this.required);
const result = new Member(this.kind, { langs: this.langs, since: this.since, deprecated: this.deprecated, discouraged: this.discouraged }, this.name, this.type?.clone(), this.argsArray.map(arg => arg.clone()), this.spec, this.required);
result.alias = this.alias;
result.async = this.async;
result.paramOrOption = this.paramOrOption;
@ -671,19 +631,6 @@ class Type {
this.properties = properties;
}
filterOutExperimental() {
if (!this.properties)
return;
const properties = [];
for (const prop of this.properties) {
if (prop.experimental)
continue;
prop.filterOutExperimental();
properties.push(prop);
}
this.properties = properties;
}
/**
* @param {Type[]} result
*/

View File

@ -36,7 +36,6 @@ class TypesGenerator {
* ignoreMissing?: Set<string>,
* doNotExportClassNames?: Set<string>,
* doNotGenerate?: Set<string>,
* includeExperimental?: boolean,
* }} options
*/
constructor(options) {
@ -50,8 +49,6 @@ class TypesGenerator {
this.doNotExportClassNames = options.doNotExportClassNames || new Set();
this.doNotGenerate = options.doNotGenerate || new Set();
this.documentation.filterForLanguage('js');
if (!options.includeExperimental)
this.documentation.filterOutExperimental();
this.documentation.copyDocsFromSuperclasses([]);
this.injectDisposeAsync();
}
@ -65,7 +62,7 @@ class TypesGenerator {
continue;
if (!member.async)
continue;
newMember = new docs.Member('method', { langs: {}, since: '1.0', experimental: false }, '[Symbol.asyncDispose]', null, []);
newMember = new docs.Member('method', { langs: {}, since: '1.0' }, '[Symbol.asyncDispose]', null, []);
newMember.async = true;
break;
}
@ -98,12 +95,20 @@ class TypesGenerator {
}, (className, methodName, overloadIndex) => {
if (className === 'SuiteFunction' && methodName === '__call') {
const cls = this.documentation.classes.get('Test');
if (!cls)
throw new Error(`Unknown class "Test"`);
const method = cls.membersArray.find(m => m.alias === 'describe');
if (!method)
throw new Error(`Unknown method "Test.describe"`);
return this.memberJSDOC(method, ' ').trimLeft();
}
if (className === 'TestFunction' && methodName === '__call') {
const cls = this.documentation.classes.get('Test');
if (!cls)
throw new Error(`Unknown class "Test"`);
const method = cls.membersArray.find(m => m.alias === '(call)');
if (!method)
throw new Error(`Unknown method "Test.(call)"`);
return this.memberJSDOC(method, ' ').trimLeft();
}
@ -137,6 +142,8 @@ class TypesGenerator {
.filter(cls => !handledClasses.has(cls.name));
{
const playwright = this.documentation.classesArray.find(c => c.name === 'Playwright');
if (!playwright)
throw new Error(`Unknown class "Playwright"`);
playwright.membersArray = playwright.membersArray.filter(member => !['errors', 'devices'].includes(member.name));
playwright.index();
}
@ -327,19 +334,20 @@ class TypesGenerator {
hasOwnMethod(classDesc, member) {
if (this.handledMethods.has(`${classDesc.name}.${member.alias}#${member.overloadIndex}`))
return false;
while (classDesc = this.parentClass(classDesc)) {
if (classDesc.members.has(member.alias))
let parent = /** @type {docs.Class | undefined} */ (classDesc);
while (parent = this.parentClass(parent)) {
if (parent.members.has(member.alias))
return false;
}
return true;
}
/**
* @param {docs.Class} classDesc
* @param {docs.Class | undefined} classDesc
*/
parentClass(classDesc) {
if (!classDesc.extends)
return null;
if (!classDesc || !classDesc.extends)
return;
return this.documentation.classes.get(classDesc.extends);
}
@ -427,6 +435,8 @@ class TypesGenerator {
const name = namespace.map(n => n[0].toUpperCase() + n.substring(1)).join('');
const shouldExport = exported[name];
const properties = namespace[namespace.length - 1] === 'options' ? type.sortedProperties() : type.properties;
if (!properties)
throw new Error(`Object type must have properties`);
if (!this.objectDefinitions.some(o => o.name === name))
this.objectDefinitions.push({ name, properties });
if (shouldExport) {
@ -503,15 +513,13 @@ class TypesGenerator {
]);
/**
* @param {boolean} includeExperimental
* @returns {Promise<string>}
*/
async function generateCoreTypes(includeExperimental) {
async function generateCoreTypes() {
const documentation = coreDocumentation.clone();
const generator = new TypesGenerator({
documentation,
doNotGenerate: assertionClasses,
includeExperimental,
});
let types = await generator.generateTypes(path.join(__dirname, 'overrides.d.ts'));
const namedDevices = Object.keys(devices).map(name => ` ${JSON.stringify(name)}: DeviceDescriptor;`).join('\n');
@ -534,10 +542,9 @@ class TypesGenerator {
}
/**
* @param {boolean} includeExperimental
* @returns {Promise<string>}
*/
async function generateTestTypes(includeExperimental) {
async function generateTestTypes() {
const documentation = coreDocumentation.mergeWith(testDocumentation);
const generator = new TypesGenerator({
documentation,
@ -574,16 +581,14 @@ class TypesGenerator {
'TestFunction',
]),
doNotExportClassNames: assertionClasses,
includeExperimental,
});
return await generator.generateTypes(path.join(__dirname, 'overrides-test.d.ts'));
}
/**
* @param {boolean} includeExperimental
* @returns {Promise<string>}
*/
async function generateReporterTypes(includeExperimental) {
async function generateReporterTypes() {
const documentation = coreDocumentation.mergeWith(testDocumentation).mergeWith(reporterDocumentation);
const generator = new TypesGenerator({
documentation,
@ -601,7 +606,6 @@ class TypesGenerator {
'JSONReportTestResult',
'JSONReportTestStep',
]),
includeExperimental,
});
return await generator.generateTypes(path.join(__dirname, 'overrides-testReporter.d.ts'));
}
@ -629,9 +633,9 @@ class TypesGenerator {
if (!fs.existsSync(playwrightTypesDir))
fs.mkdirSync(playwrightTypesDir)
writeFile(path.join(coreTypesDir, 'protocol.d.ts'), fs.readFileSync(path.join(PROJECT_DIR, 'packages', 'playwright-core', 'src', 'server', 'chromium', 'protocol.d.ts'), 'utf8'), false);
writeFile(path.join(coreTypesDir, 'types.d.ts'), await generateCoreTypes(false), true);
writeFile(path.join(playwrightTypesDir, 'test.d.ts'), await generateTestTypes(false), true);
writeFile(path.join(playwrightTypesDir, 'testReporter.d.ts'), await generateReporterTypes(false), true);
writeFile(path.join(coreTypesDir, 'types.d.ts'), await generateCoreTypes(), true);
writeFile(path.join(playwrightTypesDir, 'test.d.ts'), await generateTestTypes(), true);
writeFile(path.join(playwrightTypesDir, 'testReporter.d.ts'), await generateReporterTypes(), true);
process.exit(0);
})().catch(e => {
console.error(e);