blob: c5624fa594a0e8f3800bd9f08f7de6accd87020b [file]
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { buildManifest } = require('./manifest');
const { ensureDirForFile, getChangedFiles, parseArgs } = require('./lib');
const {
filterFindings,
findSection,
makeFinding,
parseMarkdownSections,
readMarkdownEntry,
severityForEntry,
} = require('./doc-quality-utils');
const REQUIRED_FEATURE_SECTIONS = [
{
label: 'Overview',
aliases: ['Overview', 'Overview paragraph', '概览', '概述', '简介'],
},
{
label: 'Quick Start',
aliases: ['Quick Start', 'Quick start', 'Basic usage', 'Quick Start / Basic usage', 'Quick Start/Basic usage', '快速开始', '基本使用', '基本用法'],
},
{
label: 'Parameters/Options',
aliases: ['Parameters', 'Options', 'Parameters / Options', 'Parameters/Options', 'Parameter reference', 'Options reference', '参数', '选项', '参数配置'],
},
{
label: 'Examples',
aliases: ['Examples', 'Example', '示例'],
},
{
label: 'Error handling',
aliases: ['Error handling', 'Caveats', 'Error handling and caveats', 'Error handling/Caveats', 'Known issues', 'Limitations', '错误处理', '注意事项', '限制'],
},
{
label: 'Best practices',
aliases: ['Best practices', 'Best Practices', '最佳实践'],
},
];
function frontMatterDocType(data) {
const raw = String(data?.doc_type || data?.docType || '').trim().toLowerCase();
return raw.replace(/-/g, '_');
}
function isFeatureEntry(entry, markdown) {
const explicitType = frontMatterDocType(markdown.data);
return explicitType === 'feature' || entry.doc_type === 'feature';
}
function lintFeatureDocEntry(entry, markdown) {
const severity = severityForEntry(entry);
const sections = parseMarkdownSections(markdown.content);
const findings = [];
for (const requiredSection of REQUIRED_FEATURE_SECTIONS) {
if (findSection(sections, requiredSection.aliases)) {
continue;
}
findings.push(
makeFinding(
entry,
severity,
'feature-doc-section-required',
1,
`Feature docs should include a ${requiredSection.label} section.`,
),
);
}
return findings;
}
function lintFeatureDocs(options = {}) {
const rootDir = options.rootDir || process.cwd();
const manifest = options.manifest || buildManifest({ rootDir });
const findings = [];
for (const entry of manifest.entries) {
const markdown = readMarkdownEntry(rootDir, entry);
if (!markdown || !isFeatureEntry(entry, markdown)) {
continue;
}
findings.push(...lintFeatureDocEntry(entry, markdown));
}
return findings;
}
function runCli() {
const args = parseArgs(process.argv.slice(2));
const rootDir = args.root ? path.resolve(args.root) : process.cwd();
const changedFiles = args.changed ? getChangedFiles(rootDir) : args.files ? args.files.split(',') : null;
const manifest = buildManifest({ rootDir });
const findings = filterFindings(lintFeatureDocs({ rootDir, manifest }), changedFiles);
const output = JSON.stringify({ schema_version: 1, findings }, null, 2);
if (args.output) {
const outputPath = path.resolve(rootDir, args.output);
ensureDirForFile(outputPath);
fs.writeFileSync(outputPath, `${output}\n`, 'utf8');
} else {
process.stdout.write(`${output}\n`);
}
if (args['fail-on-findings'] && findings.length > 0) {
process.exitCode = 1;
}
}
if (require.main === module) {
runCli();
}
module.exports = {
REQUIRED_FEATURE_SECTIONS,
lintFeatureDocs,
};