const fs = require('node:fs'); const path = require('node:path'); const root = path.resolve(__dirname, '../..'); const document = fs.readFileSync(path.join(root, 'docs/104/IEC104_API接口汇总.md'), 'utf8'); const headers = []; function walk(folder) { for (const entry of fs.readdirSync(folder, { withFileTypes: true })) { const location = path.join(folder, entry.name); if (entry.isDirectory()) walk(location); else if (entry.name.endsWith('.h') && !entry.name.endsWith('_Internal.h')) headers.push(location); } } walk(path.join(root, '104')); walk(path.join(root, 'common')); const stripComments = source => source.replace(/\/\*[\s\S]*?\*\//g, '') .replace(/\/\/[^\n]*/g, '') .replace(/__attribute__\s*\(\(format\(printf,\s*2,\s*3\)\)\)/g, ''); const signaturePattern = /^[ \t]*((?:extern\s+)?(?:const\s+)?[A-Za-z_]\w*(?:[ \t]+[A-Za-z_]\w*)*[ \t*]+(IEC(?:104|60870)_[A-Za-z0-9_]+)\s*\([^;{}]*\)\s*;)/gm; const tokens = source => (source.replace(/^extern\s+/, '').match(/[A-Za-z_][A-Za-z0-9_]*|[0-9]+|[^\s]/g) || []).join(' '); const declarationOnly = new Set(['IEC60870_Clock_RTC_Read', 'IEC60870_Clock_RTC_Set']); const documented = new Map(); for (const block of document.matchAll(/```c\n([\s\S]*?)```/g)) { for (const match of stripComments(block[1]).matchAll(signaturePattern)) { if (/^typedef\b/.test(match[1])) continue; if (!documented.has(match[2])) documented.set(match[2], new Set()); documented.get(match[2]).add(tokens(match[1])); } } const missing = [], mismatched = []; let checked = 0; for (const header of headers) { for (const match of stripComments(fs.readFileSync(header, 'utf8')).matchAll(signaturePattern)) { if (/^typedef\b/.test(match[1])) continue; const name = match[2]; if (declarationOnly.has(name)) continue; ++checked; if (!documented.has(name)) missing.push({ header: path.relative(root, header), name }); else if (!documented.get(name).has(tokens(match[1]))) { mismatched.push({ header: path.relative(root, header), name, declaration: match[1], documented: [...documented.get(name)] }); } } } const commandHeader = stripComments(fs.readFileSync(path.join(root, '104/business/IEC104_Command.h'), 'utf8')); const commandReference = document.slice(document.indexOf('## 25. IEC104_Command.c')); const missingFields = []; let checkedCommandFields = 0; for (const structure of commandHeader.matchAll(/typedef\s+struct\s*\{([^{}]*)\}\s*(IEC104_Command\w+_t)\s*;/g)) { for (const declaration of structure[1].split(';').map(x => x.trim()).filter(Boolean)) { const names = declaration.split(',').map(x => /([A-Za-z_]\w*)\s*$/.exec(x)[1]); for (const name of names) { ++checkedCommandFields; if (!new RegExp(`\\b${name}\\b`).test(commandReference)) missingFields.push(`${structure[2]}.${name}`); } } } console.log(JSON.stringify({ checked, documented: documented.size, declarationOnly: [...declarationOnly], missing, mismatched, checkedCommandFields, missingFields }, null, 2)); process.exitCode = missing.length || mismatched.length || missingFields.length ? 1 : 0;