diff --git a/src/analyzers/solidity/diff.ts b/src/analyzers/solidity/diff.ts index c653dd9..4a247f1 100644 --- a/src/analyzers/solidity/diff.ts +++ b/src/analyzers/solidity/diff.ts @@ -18,6 +18,7 @@ export type ContractChange = export function diffContracts( baseline: Record, head: Record, + options?: { strict? : boolean}, ): ContractChange[] { const changes: ContractChange[] = []; diff --git a/src/analyzers/typescript/extract.ts b/src/analyzers/typescript/extract.ts index 43e6bf2..1169e60 100644 --- a/src/analyzers/typescript/extract.ts +++ b/src/analyzers/typescript/extract.ts @@ -1,5 +1,5 @@ import { readFile } from 'node:fs/promises'; -import { dirname, resolve } from 'node:path'; +import { dirname, isAbsolute, relative, resolve } from 'node:path'; import { Project, type SourceFile } from 'ts-morph'; import type { SdkExport } from '../../snapshot/schema.js'; import { serializeExport } from './serialize.js'; @@ -33,22 +33,31 @@ export async function loadSdk( return { packageName, packageVersion, - entryPath: resolvedEntry, - exports: extractExports(source), + entryPath: toRelative(resolvedEntry, cwd), + exports: extractExports(source, cwd), }; } -function extractExports(source: SourceFile): Record { +function extractExports(source: SourceFile, cwd: string): Record { const exports: Record = {}; for (const [name, declarations] of source.getExportedDeclarations()) { const decl = declarations[0]; if (!decl) continue; const serialized = serializeExport(decl); - if (serialized) exports[name] = serialized; + if (!serialized) continue; + exports[name] = { + ...serialized, + sourceFile: toRelative(decl.getSourceFile().getFilePath(), cwd), + line: decl.getStartLineNumber(), + }; } return exports; } +function toRelative(p: string, cwd: string): string { + return isAbsolute(p) ? relative(cwd, p) : p; +} + async function resolveEntry(entry: string, cwd: string): Promise { const abs = resolve(cwd, entry); if (entry.endsWith('.ts') || entry.endsWith('.tsx') || entry.endsWith('.d.ts')) { diff --git a/src/cli/commands/check.ts b/src/cli/commands/check.ts index 5529bbc..082b0b3 100644 --- a/src/cli/commands/check.ts +++ b/src/cli/commands/check.ts @@ -1,5 +1,5 @@ import { mkdir, writeFile } from 'node:fs/promises'; -import { dirname, resolve } from 'node:path'; +import { dirname, isAbsolute, relative, resolve } from 'node:path'; import { runDemos } from '../../analyzers/demos/run.js'; import type { DriftGuardConfig } from '../../config/schema.js'; import { findingsFromDemos } from '../../reporter/findings.js'; @@ -28,6 +28,11 @@ export async function runCheck( const head = await analyze(cwd, config); const report = buildReport(baseline, head, config); + // GitHub annotations require repo-relative paths; SARIF spec wants the same. + for (const f of report.findings) { + if (f.file && isAbsolute(f.file)) f.file = relative(cwd, f.file); + } + if (config.demos.enabled) { const demoResults = await runDemos(config.demos, cwd); const demoFindings = findingsFromDemos(demoResults, config.severity); diff --git a/src/reporter/findings.ts b/src/reporter/findings.ts index d431513..773bc72 100644 --- a/src/reporter/findings.ts +++ b/src/reporter/findings.ts @@ -91,32 +91,39 @@ export function findingsFromSdk( baseline: Snapshot, head: Snapshot, ): Finding[] { - const file = head.sdk?.entryPath ?? baseline.sdk?.entryPath; + const entryFile = head.sdk?.entryPath ?? baseline.sdk?.entryPath; const out: Finding[] = []; for (const change of changes) { + const exp = head.sdk?.exports[change.name] ?? baseline.sdk?.exports[change.name]; + const file = exp?.sourceFile ?? entryFile; + const line = exp?.line; switch (change.kind) { case 'export-added': push(out, 'sdk-added', 'sdk', config, { message: `Export ${change.name} added (${change.export.kind})`, file, + line, }); break; case 'export-removed': push(out, 'sdk-breaking', 'sdk', config, { message: `Export ${change.name} removed`, file, + line, }); break; case 'kind-changed': push(out, 'sdk-breaking', 'sdk', config, { message: `Export ${change.name} kind changed: ${change.before.kind} → ${change.after.kind}`, file, + line, }); break; case 'signature-changed': push(out, 'sdk-signature-changed', 'sdk', config, { message: `Export ${change.name} signature changed`, file, + line, before: change.before.signature, after: change.after.signature, }); diff --git a/src/snapshot/schema.ts b/src/snapshot/schema.ts index 7940f76..01a0ae1 100644 --- a/src/snapshot/schema.ts +++ b/src/snapshot/schema.ts @@ -47,6 +47,8 @@ const SdkExportSchema = z.object({ 'namespace', ]), signature: z.string(), + sourceFile: z.string().optional(), + line: z.number().int().nonnegative().optional(), }).strict(); const SdkSnapshotSchema = z.object({