import path from 'node:path'
import type { Block, Spec } from 'comment-parser'
const organizationName = 'webdriverio'
const projectName = 'webdriverio'
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(/<example>((.|\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('<example>'))
}
* 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
}