class-content-parser.ts•9.82 kB
/**
 * Class Content Parser
 *
 * Responsible for parsing markdown documentation content and extracting
 * structured class information including constants, properties, methods, and inheritance.
 *
 * Single Responsibility: Converting markdown content to structured data
 */
import { Logger } from '../../utils/logger.js';
export interface SFCCMethod {
  name: string;
  signature: string;
  description: string;
  parameters?: string[];
  returnType?: string;
  deprecated?: boolean;
  deprecationMessage?: string;
}
export interface SFCCProperty {
  name: string;
  type: string;
  description: string;
  modifiers?: string[];
  deprecated?: boolean;
  deprecationMessage?: string;
}
export interface SFCCConstant {
  name: string;
  type: string;
  value?: string;
  description: string;
  deprecated?: boolean;
  deprecationMessage?: string;
}
export interface SFCCClassDetails {
  className: string;
  packageName: string;
  description: string;
  constants: SFCCConstant[];
  properties: SFCCProperty[];
  methods: SFCCMethod[];
  inheritance?: string[];
  constructorInfo?: string;
}
export class ClassContentParser {
  private logger: Logger;
  constructor() {
    this.logger = Logger.getChildLogger('ClassContentParser');
  }
  /**
   * Parse markdown content and extract structured class information
   */
  parseClassContent(content: string): SFCCClassDetails {
    const lines = content.split('\n');
    let currentSection = '';
    let className = '';
    let packageName = '';
    let description = '';
    const constants: SFCCConstant[] = [];
    const properties: SFCCProperty[] = [];
    const methods: SFCCMethod[] = [];
    const inheritance: string[] = [];
    let constructorInfo = '';
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i].trim();
      // Extract package name
      if (line.startsWith('## Package:')) {
        packageName = line.replace('## Package:', '').trim();
      }
      // Extract class name
      if (line.startsWith('# ') && !line.startsWith('## ')) {
        className = line.replace('# ', '').replace('Class ', '').trim();
      }
      // Track current section
      if (line.startsWith('## ')) {
        currentSection = line.replace('## ', '').trim();
      }
      // Extract description
      if (currentSection === 'Description' && line && !line.startsWith('#')) {
        description += `${line} `;
      }
      // Extract inheritance hierarchy
      if (currentSection === 'Inheritance Hierarchy' && line.includes('-')) {
        const hierarchyItem = line.replace(/^[\s-]*/, '').trim();
        if (hierarchyItem) {
          inheritance.push(hierarchyItem);
        }
      }
      // Extract constants
      if (currentSection === 'Constants' && line.startsWith('### ')) {
        const constant = this.parseConstant(line, lines, i);
        if (constant) {
          constants.push(constant);
        }
      }
      // Extract properties
      if (currentSection === 'Properties' && line.startsWith('### ')) {
        const property = this.parseProperty(line, lines, i);
        if (property) {
          properties.push(property);
        }
      }
      // Extract methods
      if ((currentSection === 'Method Summary' || currentSection === 'Method Details') && line.startsWith('### ')) {
        const method = this.parseMethod(line, lines, i);
        if (method) {
          methods.push(method);
        }
      }
      // Extract constructor info
      if (currentSection === 'Constructor Summary' && line && !line.startsWith('#')) {
        constructorInfo += `${line} `;
      }
    }
    return {
      className: className.trim(),
      packageName: packageName.trim(),
      description: description.trim(),
      constants,
      properties,
      methods,
      inheritance: inheritance.length > 0 ? inheritance : undefined,
      constructorInfo: constructorInfo.trim() || undefined,
    };
  }
  /**
   * Parse a constant definition from markdown content
   */
  private parseConstant(headerLine: string, lines: string[], startIndex: number): SFCCConstant | null {
    const constName = headerLine.replace('### ', '').trim();
    let constType = '';
    let constValue = '';
    let constDesc = '';
    let deprecated = false;
    let deprecationMessage = '';
    // Look for type, value and description in following lines
    for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
      const nextLine = lines[j].trim();
      if (nextLine.startsWith('**Type:**')) {
        const typeMatch = nextLine.match(/\*\*Type:\*\*\s*(.+)/);
        if (typeMatch) {
          const typeInfo = typeMatch[1];
          // Extract type and value if present (e.g., "String = 'COMPLETED'" or "Number = 8")
          const valueMatch = typeInfo.match(/^(\w+)\s*=\s*(.+)$/);
          if (valueMatch) {
            constType = valueMatch[1];
            constValue = valueMatch[2];
          } else {
            constType = typeInfo.trim();
          }
        }
      } else if (nextLine.startsWith('**Deprecated:**')) {
        const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
        deprecated = true;
        deprecationMessage = deprecationInfo.message;
      } else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#')) {
        constDesc += `${nextLine} `;
      }
    }
    return {
      name: constName,
      type: constType,
      value: constValue || undefined,
      description: constDesc.trim(),
      deprecated: deprecated || undefined,
      deprecationMessage: deprecationMessage || undefined,
    };
  }
  /**
   * Parse a property definition from markdown content
   */
  private parseProperty(headerLine: string, lines: string[], startIndex: number): SFCCProperty | null {
    const propName = headerLine.replace('### ', '').trim();
    let propType = '';
    let propDesc = '';
    const modifiers: string[] = [];
    let deprecated = false;
    let deprecationMessage = '';
    // Look for type and description in following lines
    for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
      const nextLine = lines[j].trim();
      if (nextLine.startsWith('**Type:**')) {
        const typeMatch = nextLine.match(/\*\*Type:\*\*\s*(.+)/);
        if (typeMatch) {
          const typeInfo = typeMatch[1];
          propType = typeInfo.split(' ')[0];
          if (typeInfo.includes('(Read Only)')) {
            modifiers.push('Read Only');
          }
          if (typeInfo.includes('(Static)')) {
            modifiers.push('Static');
          }
        }
      } else if (nextLine.startsWith('**Deprecated:**')) {
        const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
        deprecated = true;
        deprecationMessage = deprecationInfo.message;
      } else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#')) {
        propDesc += `${nextLine} `;
      }
    }
    return {
      name: propName,
      type: propType,
      description: propDesc.trim(),
      modifiers: modifiers.length > 0 ? modifiers : undefined,
      deprecated: deprecated || undefined,
      deprecationMessage: deprecationMessage || undefined,
    };
  }
  /**
   * Parse a method definition from markdown content
   */
  private parseMethod(headerLine: string, lines: string[], startIndex: number): SFCCMethod | null {
    const methodName = headerLine.replace('### ', '').trim();
    let signature = '';
    let methodDesc = '';
    let deprecated = false;
    let deprecationMessage = '';
    // Look for signature and description in following lines
    for (let j = startIndex + 1; j < lines.length && !lines[j].startsWith('#'); j++) {
      const nextLine = lines[j].trim();
      if (nextLine.startsWith('**Signature:**')) {
        const sigMatch = nextLine.match(/\*\*Signature:\*\*\s*`(.+)`/);
        if (sigMatch) {
          signature = sigMatch[1];
        }
      } else if (nextLine.startsWith('**Description:**')) {
        methodDesc = nextLine.replace('**Description:**', '').trim();
      } else if (nextLine.startsWith('**Deprecated:**')) {
        const deprecationInfo = this.parseDeprecationInfo(nextLine, lines, j);
        deprecated = true;
        deprecationMessage = deprecationInfo.message;
      } else if (nextLine && !nextLine.startsWith('**') && !nextLine.startsWith('#') && !nextLine.startsWith('---')) {
        if (!methodDesc && !nextLine.includes('Signature:')) {
          methodDesc += `${nextLine} `;
        }
      }
    }
    return {
      name: methodName,
      signature: signature || methodName,
      description: methodDesc.trim(),
      deprecated: deprecated || undefined,
      deprecationMessage: deprecationMessage || undefined,
    };
  }
  /**
   * Parse deprecation information from markdown content
   */
  private parseDeprecationInfo(deprecationLine: string, lines: string[], startIndex: number): { message: string } {
    let deprecationMessage = '';
    // Check if there's a message on the same line
    const sameLineMessage = deprecationLine.replace('**Deprecated:**', '').trim();
    if (sameLineMessage) {
      deprecationMessage = sameLineMessage;
    } else {
      // Look for the deprecation message on subsequent lines until next ** marker
      const depLines: string[] = [];
      for (let k = startIndex + 1; k < lines.length && !lines[k].startsWith('#'); k++) {
        const depLine = lines[k].trim();
        if (depLine.startsWith('**') && !depLine.startsWith('**Deprecated:**')) {
          break; // Stop at next ** marker
        }
        if (depLine && !depLine.startsWith('---')) {
          depLines.push(depLine);
        }
      }
      deprecationMessage = depLines.join(' ').trim();
    }
    return { message: deprecationMessage };
  }
}