blob: 777caf6e0afc186751e7ec986e2059cd123fa758 [file]
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
const { normalizePath } = require('./lib');
function makeFinding(entry, severity, rule, line, message) {
return {
severity,
rule,
path: entry.source_path,
line,
message,
owner: entry.owner || '@apache/doris-website-maintainers',
};
}
function isLegacyVersion(version) {
if (!version || version === 'current') {
return false;
}
const match = String(version).match(/^(\d+)(?:\.(\d+))?/);
if (!match) {
return false;
}
const major = Number(match[1]);
const minor = Number(match[2] || 0);
return major < 2 || (major === 2 && minor <= 1);
}
function severityForEntry(entry) {
if (isLegacyVersion(entry.version) || entry.blocking_level === 'report_only') {
return 'info';
}
return 'warning';
}
function normalizeHeading(text) {
return String(text || '')
.replace(/\s*\{#[^}]+}\s*$/, '')
.replace(/<[^>]+>/g, '')
.replace(/[::]+$/g, '')
.replace(/\s*\/\s*/g, '/')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function parseMarkdownSections(content) {
const lines = String(content || '').split(/\r?\n/);
const headings = [];
let inFence = false;
let fenceMarker = null;
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
const fence = line.match(/^\s*(```|~~~)/);
if (fence) {
if (!inFence) {
inFence = true;
fenceMarker = fence[1];
} else if (fence[1] === fenceMarker) {
inFence = false;
fenceMarker = null;
}
continue;
}
if (inFence) {
continue;
}
const heading = line.match(/^(#{1,6})\s+(.+?)\s*$/);
if (!heading) {
continue;
}
headings.push({
level: heading[1].length,
title: heading[2].trim(),
normalizedTitle: normalizeHeading(heading[2]),
line: index + 1,
lineIndex: index,
});
}
return headings.map((heading, index) => {
const nextHeading = headings.slice(index + 1).find((candidate) => candidate.level <= heading.level);
const endLineIndex = nextHeading ? nextHeading.lineIndex : lines.length;
return {
...heading,
content: lines.slice(heading.lineIndex + 1, endLineIndex).join('\n'),
};
});
}
function findSection(sections, aliases) {
const normalizedAliases = new Set(aliases.map(normalizeHeading));
return sections.find((section) => normalizedAliases.has(section.normalizedTitle)) || null;
}
function extractFencedCodeBlocks(content) {
const lines = String(content || '').split(/\r?\n/);
const blocks = [];
let open = null;
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
if (!open) {
const start = line.match(/^\s*```([^\s`]*)?.*$/);
if (start) {
open = {
lang: (start[1] || '').trim().toLowerCase(),
startLine: index + 1,
contentLines: [],
};
}
continue;
}
if (/^\s*```\s*$/.test(line)) {
blocks.push({
lang: open.lang,
startLine: open.startLine,
content: open.contentLines.join('\n'),
});
open = null;
continue;
}
open.contentLines.push(line);
}
return blocks;
}
function textOutsideFencedCode(content) {
const lines = String(content || '').split(/\r?\n/);
const kept = [];
let inFence = false;
for (const line of lines) {
if (/^\s*```/.test(line)) {
inFence = !inFence;
continue;
}
if (!inFence) {
kept.push(line);
}
}
return kept.join('\n');
}
function normalizeTableCell(cell) {
return String(cell || '')
.replace(/`/g, '')
.replace(/\*\*/g, '')
.trim()
.toLowerCase();
}
function tableCells(line) {
return line
.trim()
.replace(/^\|/, '')
.replace(/\|$/, '')
.split('|')
.map(normalizeTableCell);
}
function isTableSeparator(line) {
const cells = tableCells(line);
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
}
function hasMarkdownTableWithColumns(content, columnAliasGroups) {
const lines = textOutsideFencedCode(content).split(/\r?\n/);
for (let index = 0; index < lines.length - 1; index += 1) {
if (!lines[index].includes('|') || !lines[index + 1].includes('|') || !isTableSeparator(lines[index + 1])) {
continue;
}
const headers = new Set(tableCells(lines[index]).filter(Boolean));
const hasColumns = columnAliasGroups.every((aliases) =>
aliases.map(normalizeTableCell).some((alias) => headers.has(alias)),
);
if (hasColumns) {
return true;
}
}
return false;
}
function filterFindings(findings, changedFiles) {
if (!changedFiles || changedFiles.length === 0) {
return findings;
}
const changed = new Set(changedFiles.map((filePath) => normalizePath(filePath)));
return findings.filter((finding) => {
const findingPath = normalizePath(finding.path || '');
const related = finding.related_paths || [];
return (
changed.has(findingPath) ||
changed.has(findingPath.replace(/^\.\//, '')) ||
related.some((relatedPath) => changed.has(normalizePath(relatedPath)))
);
});
}
function readMarkdownEntry(rootDir, entry) {
const absPath = path.join(rootDir, entry.source_path);
if (!fs.existsSync(absPath)) {
return null;
}
const raw = fs.readFileSync(absPath, 'utf8');
const parsed = matter(raw);
return {
raw,
data: parsed.data || {},
content: parsed.content || '',
};
}
module.exports = {
extractFencedCodeBlocks,
filterFindings,
findSection,
hasMarkdownTableWithColumns,
isLegacyVersion,
makeFinding,
normalizeHeading,
parseMarkdownSections,
readMarkdownEntry,
severityForEntry,
textOutsideFencedCode,
};