All files / src/core/openapi jsdoc.ts

97.61% Statements 41/42
83.33% Branches 10/12
100% Functions 4/4
97.56% Lines 40/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81        25x 25x 25x   25x 25x   25x 2x     13x   13x 13x 12x   1x 1x 1x       13x 13x 1x   2x         13x 13x   13x   13x 13x 13x 1x 1x 1x 1x   1x             13x 13x 13x 1x     13x 13x 1x     13x   10x         1x 1x          
import * as fs from 'fs';
import type { RouteMetadata, ResponseMetadata } from './types.js';
 
export function extractMetadataFromFile(filePath: string): RouteMetadata {
  try {
    const content = fs.readFileSync(filePath, 'utf-8');
    const metadata: RouteMetadata = {};
 
    const jsdocRegex = /\/\*\*([\s\S]*?)\*\/\s*(?:export\s+default|export\s+(?:async\s+)?function|const\s+handler)/;
    const match = content.match(jsdocRegex);
 
    if (!match) {
      return metadata;
    }
 
    const jsdoc = match[1];
 
    const summaryMatch = jsdoc.match(/@summary\s+(.+)$/m);
    if (summaryMatch) {
      metadata.summary = summaryMatch[1].trim();
    } else {
      const descMatch = jsdoc.match(/\*\s+([^@\n].+)$/m);
      Eif (descMatch) {
        metadata.summary = descMatch[1].trim();
      }
    }
 
    const descriptionMatch = jsdoc.match(/@description\s+([\s\S]*?)(?=\s*@|\s*\*\/|$)/);
    if (descriptionMatch) {
      metadata.description = descriptionMatch[1]
        .split('\n')
        .map(line => line.replace(/^\s*\*\s?/, '').trim())
        .join(' ')
        .trim();
    }
 
    const tagMatches = jsdoc.matchAll(/@tag\s+(\w+)/g);
    metadata.tags = Array.from(tagMatches).map(m => m[1]);
 
    metadata.deprecated = /@deprecated/.test(jsdoc);
 
    const responseMatches = jsdoc.matchAll(/@response\s+(\d+)\s+(.+?)(?:\s+-\s*(\{[^}]+\}))?(?:\s+-\s*(.+))?$/gm);
    metadata.responses = {};
    for (const m of responseMatches) {
      const code = m[1];
      const description = m[2]?.trim();
      const type = m[3]?.replace(/[{}]/g, '').trim();
      const exampleStr = m[4]?.trim();
 
      metadata.responses[code] = {
        description,
        type,
        example: exampleStr ? tryParseJson(exampleStr) : undefined,
      };
    }
 
    const paramMatches = jsdoc.matchAll(/@param\s+(?:\{[^}]+\}\s+)?(\w+)\s+-\s*(.+)$/gm);
    metadata.paramDescriptions = {};
    for (const m of paramMatches) {
      metadata.paramDescriptions[m[1]] = m[2].trim();
    }
 
    const bodyDescMatch = jsdoc.match(/@bodyDescription\s+(.+)$/m);
    if (bodyDescMatch) {
      metadata.bodyDescription = bodyDescMatch[1].trim();
    }
 
    return metadata;
  } catch {
    return {};
  }
}
 
function tryParseJson(str: string): unknown {
  try {
    return JSON.parse(str);
  } catch {
    return str;
  }
}