fix(ci): honor Periphery ignores in shared intersection (#110839)

This commit is contained in:
Peter Steinberger
2026-07-18 18:46:52 +01:00
committed by GitHub
parent 917bec3987
commit 7e27101f2c
3 changed files with 88 additions and 5 deletions

View File

@@ -23,6 +23,10 @@ export function parseRepoLocation(location: string): {
file: string;
line: string;
};
export function filterIgnoredFindings(
findings: PeripheryFinding[],
repoRoot?: string,
): PeripheryFinding[];
export function escapeCommandData(value: unknown): string;
export function escapeCommandProperty(value: unknown): string;
export function formatAnnotation(finding: PeripheryFinding): string;

View File

@@ -5,6 +5,8 @@ import path from "node:path";
import { isDirectRunUrl } from "./lib/direct-run.mjs";
const SHARED_LOCATION_PREFIX = "../shared/OpenClawKit/Sources/";
const SHARED_SOURCE_ROOT = "apps/shared/OpenClawKit/Sources";
const BARE_PERIPHERY_IGNORE_COMMENT = /\/\/\/?\s*periphery:ignore(?![:\w])/;
function requireValue(args, index, option) {
const value = args[index + 1];
@@ -107,6 +109,39 @@ export function parseRepoLocation(location) {
};
}
export function filterIgnoredFindings(findings, repoRoot = process.cwd()) {
const sourceRoot = path.resolve(repoRoot, SHARED_SOURCE_ROOT);
const sourceLines = new Map();
return findings.filter((finding) => {
const location = parseRepoLocation(finding.location);
const sourceFile = path.resolve(repoRoot, location.file);
const relativeSource = path.relative(sourceRoot, sourceFile);
if (
!relativeSource ||
path.isAbsolute(relativeSource) ||
relativeSource === ".." ||
relativeSource.startsWith(`..${path.sep}`)
) {
throw new Error(`invalid shared Periphery source path: ${location.file}`);
}
let lines = sourceLines.get(sourceFile);
if (!lines) {
lines = fs.readFileSync(sourceFile, "utf8").split(/\r?\n/);
sourceLines.set(sourceFile, lines);
}
const declarationIndex = Number(location.line) - 1;
if (declarationIndex < 0 || declarationIndex >= lines.length) {
throw new Error(`invalid shared Periphery source line: ${finding.location}`);
}
return ![lines[declarationIndex - 1], lines[declarationIndex]].some(
(line) => typeof line === "string" && BARE_PERIPHERY_IGNORE_COMMENT.test(line),
);
});
}
export function escapeCommandData(value) {
return String(value ?? "")
.replaceAll("%", "%25")
@@ -166,9 +201,11 @@ export function run(args, env = process.env) {
const options = parseArgs(args);
readStatus(options.iosStatus, "iOS");
readStatus(options.macosStatus, "macOS");
const findings = intersectFindings(
readFindings(options.iosResults, "iOS"),
readFindings(options.macosResults, "macOS"),
const findings = filterIgnoredFindings(
intersectFindings(
readFindings(options.iosResults, "iOS"),
readFindings(options.macosResults, "macOS"),
),
);
fs.mkdirSync(path.dirname(options.output), { recursive: true });

View File

@@ -1,9 +1,12 @@
import { readFileSync } from "node:fs";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { compileFunction } from "node:vm";
import { describe, expect, it } from "vitest";
import { parse } from "yaml";
import {
buildSummary,
filterIgnoredFindings,
formatAnnotation,
intersectFindings,
parseRepoLocation,
@@ -11,6 +14,7 @@ import {
} from "../../scripts/periphery-intersection.mjs";
const WORKFLOW_PATH = ".github/workflows/shared-openclawkit-periphery.yml";
const FINDING_SOURCE = "../shared/OpenClawKit/Sources/OpenClawKit/Example.swift";
type WorkflowStep = {
id?: string;
@@ -35,12 +39,24 @@ function finding(overrides: Record<string, unknown> = {}) {
return {
ids: ["s:11OpenClawKit7ExampleV"],
kind: "struct",
location: "../shared/OpenClawKit/Sources/OpenClawKit/Example.swift:12:8",
location: `${FINDING_SOURCE}:12:8`,
name: "Example",
...overrides,
};
}
function withSharedSource(source: string, test: (repoRoot: string) => void) {
const repoRoot = mkdtempSync(join(tmpdir(), "openclaw-periphery-intersection-"));
const sourceFile = join(repoRoot, "apps/shared/OpenClawKit/Sources/OpenClawKit/Example.swift");
mkdirSync(dirname(sourceFile), { recursive: true });
writeFileSync(sourceFile, source);
try {
test(repoRoot);
} finally {
rmSync(repoRoot, { force: true, recursive: true });
}
}
describe("Periphery intersection", () => {
it("matches exact Swift USRs instead of declaration names", () => {
const sameNameDifferentUsr = finding({ ids: ["s:11OpenClawKit7ExampleV_other"] });
@@ -62,6 +78,32 @@ describe("Periphery intersection", () => {
expect(intersectFindings([later, finding()], [finding(), later])).toEqual([finding(), later]);
});
it("honors bare Periphery ignore comments on or above declarations", () => {
withSharedSource(
[
"// periphery:ignore - exported package surface",
"public struct Example {}",
"",
'public init(value: String = "value") {} // periphery:ignore - exported initializer',
].join("\n"),
(repoRoot) => {
const declaration = finding({ location: `${FINDING_SOURCE}:2:8` });
const inline = finding({ location: `${FINDING_SOURCE}:4:8` });
expect(filterIgnoredFindings([declaration, inline], repoRoot)).toEqual([]);
},
);
});
it("does not treat scoped Periphery commands as bare ignores", () => {
withSharedSource(
["// periphery:ignore:parameters value", "public struct Example {}"].join("\n"),
(repoRoot) => {
const command = finding({ location: `${FINDING_SOURCE}:2:8` });
expect(filterIgnoredFindings([command], repoRoot)).toEqual([command]);
},
);
});
it("fails closed when a finding has no USR", () => {
expect(() => validateFindings([finding({ ids: [] })], "iOS")).toThrow(
"iOS finding 0 has no usable Swift USR",