import path from 'node:path' import type { Block, Spec } from 'comment-parser' const organizationName = 'webdriverio' // Usually your GitHub org/user name. const projectName = 'webdriverio' // Usually your repo name. const repoUrl = `https://github.com/${organizationName}/${projectName}` interface TagType extends Spec { string: string } interface Example { file: string; format: string; code: string; } function normalizeSpec(spec: Spec): TagType { const ensureType = (currentType: string | undefined) => currentType || 'any' const trimDescription = (desc: string) => desc && desc.endsWith('\n') ? desc.replace(/(\n)+$/, '') : desc const string = spec.source[0].source.split(`@${spec.tag}`)[1].trim() spec.type = ensureType(spec.type) spec.description = trimDescription(spec.description) return { ...spec, string, } } export default function (docfile: { filename: string; javadoc: Block[]; }) { const javadoc = docfile.javadoc[0] let type = '' const name = path.basename(path.basename(docfile.filename, '.js'), '.ts') const scope = docfile.filename.split('/').slice(-2, -1)[0] let description = '' const paramStr: string[] = [] const propertyTags: TagType[] = [] const paramTags: TagType[] = [] const returnTags: TagType[] = [] const throwsTags: TagType[] = [] const fires: string[] = [] const listens: string[] = [] const exampleReferences: string[] = [] let tagDeprecated = '' let tagSee = '' let tagVersion = '' let tagAuthor = '' let tagCustomType = '' let tagMobileElement = false let tagSkipUsage = false let returns for (const tag of javadoc.tags) { const newTag = normalizeSpec(tag) const { tag: tagName, type: tagType, description, string, source } = newTag const typeOnToken = source[0].tokens.type switch (tagName) { case 'param': { if (tagType.includes('=')) { newTag.optional = true newTag.type = newTag.type.replace('=', '') } if (!tagType.includes('`')) { newTag.type = `\`${newTag.type}\`` } paramTags.push(newTag) paramStr.push(newTag.name) break } case 'rowInfo': { paramTags.push(newTag) break } case 'property': { propertyTags.push(newTag) break } case 'return': case 'returns': { returns = { type: newTag.type, name: newTag.tag, description: string.split(typeOnToken)[1] } returnTags.push(newTag) break } case 'throws': { newTag.name = newTag.tag newTag.description = string.split(typeOnToken)[1] throwsTags.push(newTag) break } case 'fires': { fires.push(description) break } case 'listens': { listens.push(description) break } case 'namespace': { type = 'namespace' break } case 'method': { type = 'method' break } case 'class': { type = 'class' break } case 'function': { type = 'function' break } case 'event': { type = 'event' break } case 'see': { tagSee = source[0].source.split('@see')[1].trim() break } case 'version': { tagVersion = source[0].source.split('@version')[1].trim() break } case 'deprecated': { tagDeprecated = source[0].source.split('@deprecated')[1].trim() break } case 'mobileElement': { tagMobileElement = true break } case 'skipUsage': { tagSkipUsage = true break } case 'author': { tagAuthor = description break } case 'type': { tagCustomType = source[0].source.split('@type')[1].trim() break } case 'example': { exampleReferences.push(source[0].source.split('@example')[1].trim()) break } case 'alias': case 'for': case 'uses': case 'docs': { break } default: console.log('unknown tag ', tag.tag, ' in ', docfile.filename) break } } description = javadoc.description .replace(/\nh1/, '#') .replace(/\nh2/, '##') .replace(/\nh3/, '###') .replace(/\nh4/, '####') .replace(/\nh5/, '#####') .replace(/\nh6/, '######') .replace(/^h1/, '#') .replace(/^h2/, '##') .replace(/^h3/, '###') .replace(/^h4/, '####') .replace(/^h5/, '#####') .replace(/^h6/, '######') const files: Example[] = [] let exampleCodeLine: string[] = [] const example = description.match(/((.|\n)*)<\/example>/g) let exampleFilename = '' let currentLine = 0 if (example && example[0]) { console.log('parse example section for', docfile.filename) const exampleLines = example[0].replace(/<(\/)*example>/g, '').split(/\n/g) exampleLines.forEach(function(line: string) { ++currentLine const checkForFilenameExpression = line.match(/\s\s\s\s(:(\S)*\.(\S)*)/g) if ((checkForFilenameExpression && checkForFilenameExpression.length) || (currentLine === exampleLines.length)) { if (exampleCodeLine.length) { /** * remove filename expression in first line */ exampleFilename = exampleCodeLine.shift()!.trim().slice(1) const code = exampleCodeLine .join('\n') /** * allow to have `@` in code, e.g. `@keyframes slidein { ... }` * without it would be interpreted as a jsdoc tag */ .replace('\\@', '@') /** * add example */ if (exampleFilename !== '' && code !== '') { files.push({ file: exampleFilename, format: exampleFilename.split(/\./).pop() || 'js', code: code }) } /** * reset loop conditions */ exampleCodeLine = [] } /** * if this is the last line of code dont proceed */ if (currentLine === exampleLines.length) { return } } exampleCodeLine.push(line.slice(4)) }) /** * remove example section from description */ description = description.slice(0, description.indexOf('')) } /** * format param strings, from * ``` * browser.waitUntil(condition, options, options.timeout, options.reverse, options.timeoutMsg, options.interval) * ``` * to * ``` * browser.waitUntil(condition, { timeout, reverse, timeoutMsg, interval }) * ``` */ const parsedParamStr = paramStr .filter((param, i) => !paramStr[i + 1] || paramStr[i + 1].split('.')[0] !== param) .filter((param) => !param.includes('.')) const paramOptions = paramStr.filter((param) => param.includes('.')).map((param) => param.split('.')[1]) if (paramOptions.length) { parsedParamStr.push(`{ ${paramOptions.join(', ')} }`) } const commandDescription = { command: name, paramString: parsedParamStr.join(', '), paramTags: paramTags, propertyTags: propertyTags, returnTags: returnTags, throwsTags: throwsTags, fires: fires, listens: listens, returns: returns, author: tagAuthor, version: tagVersion, see: tagSee, deprecated: tagDeprecated, tagType: tagCustomType, type: type ? type : false, isMethod: type === 'method', isFunction: type === 'function', isClass: type === 'class', isNamespace: type === 'namespace', isEvent: type === 'event', hasType: tagCustomType !== '', description: description, examples: files, exampleReferences, customEditUrl: `${repoUrl}/edit/main/packages/webdriverio/src/commands/${scope}/${name}.ts`, hasDocusaurusHeader: true, originalId: `api/${scope}/${name}`, isElementScope: scope === 'element' || tagMobileElement, isSkipUsage: tagSkipUsage, isNetworkScope : scope === 'network', isMockScope : scope === 'mock', isDialogScope : scope === 'dialog', isClockScope : scope === 'clock', } return commandDescription }