UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

767 lines 34.8 kB
export class DocumentationAnalyzer { projectRoot; essentialFiles = [ { file: 'README.md', weight: 25, required: true }, { file: 'CHANGELOG.md', weight: 10, required: false }, { file: 'LICENSE', weight: 5, required: false }, { file: 'CONTRIBUTING.md', weight: 8, required: false }, { file: 'API.md', weight: 15, required: false }, { file: 'docs/README.md', weight: 10, required: false }, { file: 'docs/api/', weight: 12, required: false }, { file: 'docs/examples/', weight: 8, required: false }, { file: 'docs/guides/', weight: 7, required: false } ]; readmeSections = [ 'Installation', 'Usage', 'API', 'Examples', 'Contributing', 'License', 'Getting Started', 'Configuration', 'Troubleshooting' ]; constructor(projectRoot) { this.projectRoot = projectRoot; } async analyze() { const missingDocs = []; const suggestions = []; const issues = []; const strengths = []; const fileAnalysis = []; // Comprehensive documentation analysis const readmeQuality = await this.analyzeReadmeQuality(); const apiDocumentation = await this.analyzeApiDocumentation(); const { coverage, qualityScore } = await this.analyzeCoverageAndQuality(missingDocs, suggestions, issues, strengths); const readabilityScore = await this.analyzeReadability(); const fileAnalysisResults = await this.analyzeFileDocumentation(); fileAnalysis.push(...fileAnalysisResults); // Calculate overall scores const apiDocumentationScore = apiDocumentation.apiCoveragePercentage; const score = this.calculateOverallScore(coverage, qualityScore, readabilityScore, apiDocumentationScore, readmeQuality.score); // Generate comprehensive recommendations const recommendations = this.generateRecommendations(readmeQuality, apiDocumentation, issues, coverage); return { score, coverage, qualityScore, readabilityScore, apiDocumentationScore, readmeQuality, apiDocumentation, missingDocs, suggestions, issues, strengths, recommendations, fileAnalysis }; } async analyzeReadmeQuality() { const fs = await import('fs-extra'); const path = await import('path'); const readmePath = path.join(this.projectRoot, 'README.md'); const result = { exists: false, score: 0, wordCount: 0, sectionCount: 0, codeExampleCount: 0, linkCount: 0, missingEssentialSections: [], readabilityMetrics: { averageWordsPerSentence: 0, averageSentencesPerParagraph: 0, complexWordCount: 0, readingLevel: 'elementary', readingTimeMinutes: 0 }, structureScore: 0, contentQuality: 0 }; if (!(await fs.pathExists(readmePath))) { return result; } result.exists = true; try { const content = await fs.readFile(readmePath, 'utf-8'); // Basic metrics result.wordCount = content.split(/\s+/).filter(word => word.length > 0).length; result.sectionCount = (content.match(/^#{1,6}\s+/gm) || []).length; result.codeExampleCount = (content.match(/```[\s\S]*?```/g) || []).length; result.linkCount = (content.match(/\[([^\]]+)\]\(([^)]+)\)/g) || []).length; // Check for essential sections const requiredSections = ['installation', 'usage', 'getting started', 'setup']; const recommendedSections = ['api', 'examples', 'contributing', 'license', 'configuration']; const lowercaseContent = content.toLowerCase(); const foundRequired = requiredSections.filter(section => lowercaseContent.includes(section) || lowercaseContent.includes(`# ${section}`) || lowercaseContent.includes(`## ${section}`)); const foundRecommended = recommendedSections.filter(section => lowercaseContent.includes(section) || lowercaseContent.includes(`# ${section}`) || lowercaseContent.includes(`## ${section}`)); result.missingEssentialSections = requiredSections.filter(section => !foundRequired.includes(section)); // Calculate readability metrics result.readabilityMetrics = this.calculateReadabilityMetrics(content); // Structure score (0-100) result.structureScore = Math.min(100, ((result.sectionCount > 0 ? 20 : 0) + (foundRequired.length * 15) + (foundRecommended.length * 5) + (result.codeExampleCount > 0 ? 15 : 0) + (result.linkCount > 0 ? 10 : 0) + (content.includes('Table of Contents') || content.includes('TOC') ? 10 : 0))); // Content quality score (0-100) result.contentQuality = Math.min(100, ((result.wordCount > 100 ? 20 : Math.floor(result.wordCount / 5)) + (result.codeExampleCount * 10) + (result.readabilityMetrics.readingLevel === 'high_school' || result.readabilityMetrics.readingLevel === 'college' ? 20 : 10) + (content.includes('badge') || content.includes('shield') ? 10 : 0) + (result.linkCount > 2 ? 15 : result.linkCount * 5) + (content.includes('screenshot') || content.includes('demo') ? 10 : 0))); // Overall README score result.score = Math.round((result.structureScore + result.contentQuality) / 2); } catch (error) { // README exists but can't be read } return result; } async analyzeApiDocumentation() { const fs = await import('fs-extra'); const path = await import('path'); const glob = await import('glob'); const result = { totalFunctions: 0, documentedFunctions: 0, totalClasses: 0, documentedClasses: 0, totalInterfaces: 0, documentedInterfaces: 0, totalTypes: 0, documentedTypes: 0, apiCoveragePercentage: 0, qualityScore: 0, missingParameterDocs: [], missingReturnDocs: [], missingExampleDocs: [], outdatedDocs: [] }; try { const files = await glob.glob('**/*.{ts,js,tsx,jsx}', { cwd: this.projectRoot, ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**', '**/*.test.*', '**/*.spec.*'] }); for (const file of files) { const filePath = path.join(this.projectRoot, file); try { const content = await fs.readFile(filePath, 'utf-8'); await this.analyzeFileApiDocumentation(content, file, result); } catch (error) { // Skip files that can't be read } } // Calculate coverage percentage const totalApiElements = result.totalFunctions + result.totalClasses + result.totalInterfaces + result.totalTypes; const documentedApiElements = result.documentedFunctions + result.documentedClasses + result.documentedInterfaces + result.documentedTypes; result.apiCoveragePercentage = totalApiElements > 0 ? Math.round((documentedApiElements / totalApiElements) * 100) : 100; // Calculate quality score based on completeness and detail result.qualityScore = this.calculateApiQualityScore(result); } catch (error) { // Skip if analysis fails } return result; } async analyzeFileApiDocumentation(content, file, result) { // Function patterns const functionPatterns = [ /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)/g, /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g, /(\w+)\s*:\s*\([^)]*\)\s*=>/g, /(?:public|private|protected|static)?\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/g ]; // Class and interface patterns const classPattern = /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/g; const interfacePattern = /(?:export\s+)?interface\s+(\w+)/g; const typePattern = /(?:export\s+)?type\s+(\w+)\s*=/g; // JSDoc pattern const jsdocPattern = /\/\*\*[\s\S]*?\*\//g; // Count functions let match; const functions = []; for (const pattern of functionPatterns) { while ((match = pattern.exec(content)) !== null) { if (match[1] && !functions.includes(match[1])) { functions.push(match[1]); result.totalFunctions++; } } } // Count classes while ((match = classPattern.exec(content)) !== null) { result.totalClasses++; } // Count interfaces while ((match = interfacePattern.exec(content)) !== null) { result.totalInterfaces++; } // Count types while ((match = typePattern.exec(content)) !== null) { result.totalTypes++; } // Analyze JSDoc comments const jsdocComments = content.match(jsdocPattern) || []; for (const jsdoc of jsdocComments) { // Check what this JSDoc documents const afterJsdoc = content.substring(content.indexOf(jsdoc) + jsdoc.length); if (/^\s*(?:export\s+)?(?:async\s+)?function/.test(afterJsdoc) || /^\s*(?:export\s+)?const\s+\w+\s*=\s*(?:async\s+)?\(/.test(afterJsdoc) || /^\s*(?:public|private|protected|static)?\s*(?:async\s+)?\w+\s*\(/.test(afterJsdoc)) { result.documentedFunctions++; // Check documentation quality if (!jsdoc.includes('@param')) { const functionMatch = afterJsdoc.match(/(?:function\s+)?(\w+)/) || afterJsdoc.match(/const\s+(\w+)/); if (functionMatch && afterJsdoc.includes('(') && afterJsdoc.includes(')')) { result.missingParameterDocs.push(`${file}: ${functionMatch[1]}`); } } if (!jsdoc.includes('@returns') && !jsdoc.includes('@return')) { const functionMatch = afterJsdoc.match(/(?:function\s+)?(\w+)/) || afterJsdoc.match(/const\s+(\w+)/); if (functionMatch) { result.missingReturnDocs.push(`${file}: ${functionMatch[1]}`); } } if (!jsdoc.includes('@example')) { const functionMatch = afterJsdoc.match(/(?:function\s+)?(\w+)/) || afterJsdoc.match(/const\s+(\w+)/); if (functionMatch) { result.missingExampleDocs.push(`${file}: ${functionMatch[1]}`); } } } else if (/^\s*(?:export\s+)?(?:abstract\s+)?class/.test(afterJsdoc)) { result.documentedClasses++; } else if (/^\s*(?:export\s+)?interface/.test(afterJsdoc)) { result.documentedInterfaces++; } else if (/^\s*(?:export\s+)?type/.test(afterJsdoc)) { result.documentedTypes++; } } } async analyzeCoverageAndQuality(missingDocs, suggestions, issues, strengths) { const fs = await import('fs-extra'); const path = await import('path'); let totalWeight = 0; let foundWeight = 0; // Check essential files for (const essential of this.essentialFiles) { totalWeight += essential.weight; const filePath = path.join(this.projectRoot, essential.file); if (await fs.pathExists(filePath)) { foundWeight += essential.weight; try { const stats = await fs.stat(filePath); if (stats.size < 100) { issues.push({ type: 'incomplete', severity: 'medium', file: essential.file, message: `${essential.file} exists but appears incomplete (${stats.size} bytes)`, suggestion: `Add substantial content to ${essential.file}` }); } else { strengths.push(`${essential.file} exists and has content`); } } catch (error) { // Skip if can't read file stats } } else { missingDocs.push(essential.file); const severity = essential.required ? 'critical' : 'medium'; issues.push({ type: 'missing', severity, file: essential.file, message: `Missing ${essential.file}`, suggestion: `Create ${essential.file} with appropriate content` }); } } // Check package.json documentation await this.checkPackageJsonDocumentation(issues, suggestions, strengths); // Check docs directory structure await this.checkDocsDirectoryStructure(issues, suggestions, strengths); const coverage = totalWeight > 0 ? Math.round((foundWeight / totalWeight) * 100) : 0; const qualityScore = this.calculateQualityScore(issues, strengths); return { coverage, qualityScore }; } async checkPackageJsonDocumentation(issues, suggestions, strengths) { const fs = await import('fs-extra'); const path = await import('path'); const packageJsonPath = path.join(this.projectRoot, 'package.json'); if (await fs.pathExists(packageJsonPath)) { try { const packageJson = await fs.readJson(packageJsonPath); if (!packageJson.description || packageJson.description.length < 10) { issues.push({ type: 'missing', severity: 'medium', file: 'package.json', message: 'Missing or insufficient package description', suggestion: 'Add a meaningful description (at least 10 characters)' }); } else { strengths.push('Package has descriptive description'); } if (!packageJson.repository) { suggestions.push('Add repository URL to package.json for better discoverability'); } else { strengths.push('Repository information is provided'); } if (!packageJson.keywords || packageJson.keywords.length === 0) { suggestions.push('Add keywords to package.json for better discoverability'); } else { strengths.push(`Package has ${packageJson.keywords.length} keywords`); } if (!packageJson.author && !packageJson.contributors) { suggestions.push('Add author or contributors information to package.json'); } else { strengths.push('Author/contributor information is provided'); } if (packageJson.homepage) { strengths.push('Homepage URL is provided'); } if (packageJson.bugs) { strengths.push('Bug reporting information is provided'); } } catch (error) { issues.push({ type: 'poor_quality', severity: 'low', file: 'package.json', message: 'Cannot parse package.json', suggestion: 'Verify package.json syntax is valid' }); } } } async checkDocsDirectoryStructure(issues, suggestions, strengths) { const fs = await import('fs-extra'); const path = await import('path'); const docsDir = path.join(this.projectRoot, 'docs'); if (await fs.pathExists(docsDir)) { strengths.push('Documentation directory exists'); try { const entries = await fs.readdir(docsDir, { withFileTypes: true }); const files = entries.filter(entry => entry.isFile()).map(entry => entry.name); const dirs = entries.filter(entry => entry.isDirectory()).map(entry => entry.name); if (files.length === 0 && dirs.length === 0) { issues.push({ type: 'incomplete', severity: 'medium', file: 'docs/', message: 'Documentation directory is empty', suggestion: 'Add documentation files or subdirectories' }); } else { strengths.push(`Documentation directory contains ${files.length} files and ${dirs.length} subdirectories`); } // Check for common documentation patterns if (files.some(f => f.toLowerCase().includes('api'))) { strengths.push('API documentation detected'); } if (dirs.includes('examples') || files.some(f => f.toLowerCase().includes('example'))) { strengths.push('Examples documentation detected'); } if (dirs.includes('guides') || files.some(f => f.toLowerCase().includes('guide'))) { strengths.push('User guides detected'); } } catch (error) { // Skip if can't read docs directory } } else { suggestions.push('Create a docs/ directory for comprehensive documentation'); } } async analyzeReadability() { const fs = await import('fs-extra'); const path = await import('path'); let totalScore = 0; let fileCount = 0; // Analyze markdown files const markdownFiles = ['README.md', 'CHANGELOG.md', 'CONTRIBUTING.md']; for (const file of markdownFiles) { const filePath = path.join(this.projectRoot, file); if (await fs.pathExists(filePath)) { try { const content = await fs.readFile(filePath, 'utf-8'); const metrics = this.calculateReadabilityMetrics(content); // Score based on reading level and structure let fileScore = 50; // Base score switch (metrics.readingLevel) { case 'high_school': case 'college': fileScore += 30; break; case 'middle': fileScore += 20; break; default: fileScore += 10; } // Bonus for good sentence length if (metrics.averageWordsPerSentence >= 10 && metrics.averageWordsPerSentence <= 20) { fileScore += 10; } // Bonus for good paragraph structure if (metrics.averageSentencesPerParagraph >= 2 && metrics.averageSentencesPerParagraph <= 5) { fileScore += 10; } totalScore += Math.min(100, fileScore); fileCount++; } catch (error) { // Skip if can't read file } } } return fileCount > 0 ? Math.round(totalScore / fileCount) : 70; // Default decent score } calculateReadabilityMetrics(content) { // Remove code blocks and other non-prose content const prose = content .replace(/```[\s\S]*?```/g, '') .replace(/`[^`]+`/g, '') .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') .replace(/#{1,6}\s+/g, '') .replace(/\*\*([^*]+)\*\*/g, '$1') .replace(/\*([^*]+)\*/g, '$1'); const sentences = prose.split(/[.!?]+/).filter(s => s.trim().length > 0); const paragraphs = prose.split(/\n\s*\n/).filter(p => p.trim().length > 0); const words = prose.split(/\s+/).filter(w => w.length > 0); const averageWordsPerSentence = sentences.length > 0 ? words.length / sentences.length : 0; const averageSentencesPerParagraph = paragraphs.length > 0 ? sentences.length / paragraphs.length : 0; // Count complex words (3+ syllables, simplified) const complexWordCount = words.filter(word => { const syllables = word.toLowerCase().replace(/[^aeiou]/g, '').length; return syllables >= 3; }).length; // Determine reading level based on average sentence length and complex words let readingLevel = 'elementary'; const complexWordRatio = words.length > 0 ? complexWordCount / words.length : 0; if (averageWordsPerSentence > 20 || complexWordRatio > 0.3) { readingLevel = 'graduate'; } else if (averageWordsPerSentence > 15 || complexWordRatio > 0.2) { readingLevel = 'college'; } else if (averageWordsPerSentence > 12 || complexWordRatio > 0.15) { readingLevel = 'high_school'; } else if (averageWordsPerSentence > 8 || complexWordRatio > 0.1) { readingLevel = 'middle'; } // Estimate reading time (average 200 words per minute) const readingTimeMinutes = Math.max(1, Math.ceil(words.length / 200)); return { averageWordsPerSentence: Math.round(averageWordsPerSentence * 10) / 10, averageSentencesPerParagraph: Math.round(averageSentencesPerParagraph * 10) / 10, complexWordCount, readingLevel, readingTimeMinutes }; } async analyzeFileDocumentation() { const fs = await import('fs-extra'); const path = await import('path'); const glob = await import('glob'); const results = []; try { const files = await glob.glob('**/*.{ts,js,tsx,jsx,md}', { cwd: this.projectRoot, ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'] }); for (const file of files.slice(0, 20)) { // Limit for performance const filePath = path.join(this.projectRoot, file); try { const content = await fs.readFile(filePath, 'utf-8'); const analysis = this.analyzeIndividualFile(content, file); results.push(analysis); } catch (error) { // Skip files that can't be read } } } catch (error) { // Skip if glob fails } return results; } analyzeIndividualFile(content, file) { const isMarkdown = file.endsWith('.md'); const isSource = /\.(ts|js|tsx|jsx)$/.test(file); let score = 50; // Base score const strengths = []; const weaknesses = []; // File header check const hasFileHeader = content.startsWith('/**') || content.startsWith('/*') || (isMarkdown && content.startsWith('#')); if (hasFileHeader) { score += 10; strengths.push('Has file header/description'); } else { weaknesses.push('Missing file header or description'); } // Examples check const hasExamples = content.includes('example') || content.includes('Example') || content.includes('```') || content.includes('@example'); if (hasExamples) { score += 15; strengths.push('Contains examples or code samples'); } let functionDocumentationRatio = 0; let classDocumentationRatio = 0; if (isSource) { // Count functions and documentation const functions = (content.match(/(?:function|=>|\([^)]*\)\s*{)/g) || []).length; const classes = (content.match(/class\s+\w+/g) || []).length; const jsdocs = (content.match(/\/\*\*[\s\S]*?\*\//g) || []).length; if (functions > 0) { functionDocumentationRatio = Math.min(100, (jsdocs / functions) * 100); score += functionDocumentationRatio * 0.3; // Up to 30 points if (functionDocumentationRatio > 80) { strengths.push('Well-documented functions'); } else if (functionDocumentationRatio < 30) { weaknesses.push('Poor function documentation'); } } if (classes > 0) { classDocumentationRatio = Math.min(100, (jsdocs / classes) * 100); score += classDocumentationRatio * 0.2; // Up to 20 points if (classDocumentationRatio > 80) { strengths.push('Well-documented classes'); } else if (classDocumentationRatio < 50) { weaknesses.push('Poor class documentation'); } } // Check for TypeScript types if (file.endsWith('.ts') || file.endsWith('.tsx')) { if (content.includes('interface') || content.includes('type ')) { score += 10; strengths.push('Uses TypeScript types/interfaces'); } } } if (isMarkdown) { // Markdown-specific analysis const headings = (content.match(/^#{1,6}\s+/gm) || []).length; const links = (content.match(/\[([^\]]+)\]\([^)]+\)/g) || []).length; const codeBlocks = (content.match(/```[\s\S]*?```/g) || []).length; if (headings > 0) { score += Math.min(15, headings * 3); strengths.push(`Well-structured with ${headings} headings`); } if (links > 0) { score += Math.min(10, links * 2); strengths.push(`Contains ${links} helpful links`); } if (codeBlocks > 0) { score += Math.min(15, codeBlocks * 5); strengths.push(`Includes ${codeBlocks} code examples`); } } // Length analysis if (content.length < 200) { score -= 10; weaknesses.push('File is very short'); } else if (content.length > 1000) { score += 5; strengths.push('Substantial content'); } return { file, type: isMarkdown ? 'markdown' : isSource ? 'source' : 'other', score: Math.max(0, Math.min(100, Math.round(score))), issues: weaknesses.length, strengths, weaknesses, functionDocumentationRatio: Math.round(functionDocumentationRatio), classDocumentationRatio: Math.round(classDocumentationRatio), hasFileHeader, hasExamples }; } calculateApiQualityScore(apiDoc) { let score = apiDoc.apiCoveragePercentage; // Bonus for complete documentation if (apiDoc.missingParameterDocs.length === 0) score += 10; if (apiDoc.missingReturnDocs.length === 0) score += 10; if (apiDoc.missingExampleDocs.length === 0) score += 15; // Penalty for many missing elements score -= Math.min(20, apiDoc.missingParameterDocs.length * 2); score -= Math.min(15, apiDoc.missingReturnDocs.length * 2); score -= Math.min(10, apiDoc.missingExampleDocs.length); return Math.max(0, Math.min(100, Math.round(score))); } calculateQualityScore(issues, strengths) { let score = 70; // Base quality score // Deduct for issues for (const issue of issues) { switch (issue.severity) { case 'critical': score -= 15; break; case 'high': score -= 10; break; case 'medium': score -= 5; break; case 'low': score -= 2; break; } } // Add for strengths score += Math.min(30, strengths.length * 3); return Math.max(0, Math.min(100, score)); } calculateOverallScore(coverage, qualityScore, readabilityScore, apiDocumentationScore, readmeScore) { // Weighted average const weights = { coverage: 0.25, quality: 0.20, readability: 0.15, api: 0.25, readme: 0.15 }; return Math.round(coverage * weights.coverage + qualityScore * weights.quality + readabilityScore * weights.readability + apiDocumentationScore * weights.api + readmeScore * weights.readme); } generateRecommendations(readmeQuality, apiDocumentation, issues, coverage) { const recommendations = []; // Critical recommendations if (!readmeQuality.exists) { recommendations.push({ priority: 'critical', category: 'content', title: 'Create comprehensive README', description: 'README.md is missing - this is essential for any project', effort: 'medium', impact: 'high', actionItems: [ 'Create README.md file', 'Add project description and overview', 'Include installation instructions', 'Add usage examples', 'Document basic API or CLI usage' ] }); } // API documentation recommendations if (apiDocumentation.apiCoveragePercentage < 50) { recommendations.push({ priority: 'high', category: 'api', title: 'Improve API documentation coverage', description: `Only ${apiDocumentation.apiCoveragePercentage}% of API elements are documented`, effort: 'high', impact: 'high', actionItems: [ 'Add JSDoc comments to public functions', 'Document function parameters with @param', 'Document return values with @returns', 'Add usage examples with @example', 'Document class constructors and methods' ] }); } // README quality recommendations if (readmeQuality.exists && readmeQuality.score < 60) { recommendations.push({ priority: 'medium', category: 'content', title: 'Enhance README quality', description: 'README exists but lacks essential sections or content', effort: 'medium', impact: 'medium', actionItems: [ ...readmeQuality.missingEssentialSections.map(section => `Add ${section} section`), 'Include more code examples', 'Add links to detailed documentation', 'Consider adding badges or status indicators' ] }); } // Structure recommendations if (coverage < 70) { recommendations.push({ priority: 'medium', category: 'structure', title: 'Create comprehensive documentation structure', description: 'Missing key documentation files and directories', effort: 'medium', impact: 'medium', actionItems: [ 'Create docs/ directory', 'Add API documentation files', 'Create user guides and tutorials', 'Add CHANGELOG.md for version history', 'Consider adding CONTRIBUTING.md' ] }); } // Example recommendations if (apiDocumentation.missingExampleDocs.length > 5) { recommendations.push({ priority: 'medium', category: 'examples', title: 'Add more code examples', description: 'Many functions lack usage examples', effort: 'low', impact: 'medium', actionItems: [ 'Add @example tags to JSDoc comments', 'Create examples/ directory with sample code', 'Include common use cases in README', 'Add interactive examples or demos' ] }); } // Maintenance recommendations const criticalIssues = issues.filter(issue => issue.severity === 'critical').length; if (criticalIssues > 0) { recommendations.push({ priority: 'high', category: 'maintenance', title: 'Address critical documentation issues', description: `${criticalIssues} critical documentation issues need immediate attention`, effort: 'low', impact: 'high', actionItems: issues .filter(issue => issue.severity === 'critical') .map(issue => issue.suggestion) .slice(0, 5) }); } return recommendations.sort((a, b) => { const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 }; return priorityOrder[b.priority] - priorityOrder[a.priority]; }); } } //# sourceMappingURL=DocumentationAnalyzer.js.map