static parseParam(typeText = null, paramName = null, paramDesc = null) {
    const result = {};

    if (typeText) {
      // check nullable
      if (typeText[0] === '?') {
        result.nullable = true;
      } else if (typeText[0] === '!') {
        result.nullable = false;
      } else {
        result.nullable = null;
      }
      typeText = typeText.replace(/^[?!]/, '');

      // check record and union
      if (typeText[0] === '{') {
        result.types = [typeText];
      } else if (typeText[0] === '(') {
        typeText = typeText.replace(/^[(]/, '').replace(/[)]$/, '');
        result.types = typeText.split('|');
      } else if (typeText.includes('|')) {
        if (typeText.match(/<.*?\|.*?>/)) {
          // union in generics. e.g. `Array<string|number>`
          // hack: in this case, process this type in DocBuilder#_buildTypeDocLinkHTML
          result.types = [typeText];
        } else if (typeText.match(/^\.\.\.\(.*?\)/)) {
          // union with spread. e.g. `...(string|number)`
          // hack: in this case, process this type in DocBuilder#_buildTypeDocLinkHTML
          result.types = [typeText];
        } else {
          result.types = typeText.split('|');
        }
      } else {
        result.types = [typeText];
      }

      if (typeText.indexOf('...') === 0) {
        result.spread = true;
      } else {
        result.spread = false;
      }
    } else {
      result.types = [''];
    }

    if (result.types.some(t => !t)) {
      throw new Error(`Empty Type found name=${paramName} desc=${paramDesc}`);
    }

    if (paramName) {
      // check optional
      if (paramName[0] === '[') {
        result.optional = true;
        paramName = paramName.replace(/^[[]/, '').replace(/[\]]$/, '');
      } else {
        result.optional = false;
      }

      // check default value
      const pair = paramName.split('=');
      if (pair.length === 2) {
        result.defaultValue = pair[1];
        try {
          const raw = JSON.parse(pair[1]);
          result.defaultRaw = raw;
        } catch (e) {
          result.defaultRaw = pair[1];
        }
      }

      result.name = pair[0].trim();
    }

    result.description = paramDesc;

    return result;
  }
parse param text and build formatted result.
@param {string} typeText - param type text.
@param {string} [paramName] - param name.
@param {string} [paramDesc] - param description.
@returns {ParsedParam} formatted result.

@example
let value = '{number} param - this is number param';
let {typeText, paramName, paramDesc} = ParamParser.parseParamValue(value);
let result = ParamParser.parseParam(typeText, paramName, paramDesc);