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

View File

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

View File

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