diff --git a/utils/doclint/api_parser.js b/utils/doclint/api_parser.js index 223d562542..105c56c658 100644 --- a/utils/doclint/api_parser.js +++ b/utils/doclint/api_parser.js @@ -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; }); } diff --git a/utils/doclint/documentation.js b/utils/doclint/documentation.js index f0527c4120..b93998bd33 100644 --- a/utils/doclint/documentation.js +++ b/utils/doclint/documentation.js @@ -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 */ diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index d9dde814b7..209ce04756 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -36,7 +36,6 @@ class TypesGenerator { * ignoreMissing?: Set, * doNotExportClassNames?: Set, * doNotGenerate?: Set, - * 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} */ - 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} */ - 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} */ - 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);