@doccov/sdk 0.3.6 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -116,7 +116,11 @@ interface ExportReference {
116
116
  /**
117
117
  * Change type for an impacted reference
118
118
  */
119
- type DocsChangeType = "signature-changed" | "removed" | "deprecated";
119
+ type DocsChangeType = "signature-changed" | "removed" | "deprecated" | "method-removed" | "method-changed" | "method-deprecated";
120
+ /**
121
+ * Member-level change type
122
+ */
123
+ type MemberChangeType = "added" | "removed" | "signature-changed";
120
124
  /**
121
125
  * An impacted reference in a documentation file
122
126
  */
@@ -131,6 +135,14 @@ interface DocsImpactReference {
131
135
  suggestion?: string;
132
136
  /** Context around the reference */
133
137
  context?: string;
138
+ /** Member/method name if this is a member-level change */
139
+ memberName?: string;
140
+ /** Type of member change (added, removed, signature-changed) */
141
+ memberChangeType?: MemberChangeType;
142
+ /** Suggested replacement for removed/changed members */
143
+ replacementSuggestion?: string;
144
+ /** True if this is just a class instantiation (new ClassName()) */
145
+ isInstantiation?: boolean;
134
146
  }
135
147
  /**
136
148
  * Documentation file impact summary
@@ -198,14 +210,32 @@ declare function findExportReferences(files: MarkdownDocFile[], exportNames: str
198
210
  */
199
211
  declare function blockReferencesExport(block: MarkdownCodeBlock, exportName: string): boolean;
200
212
  import { SpecDiff } from "@openpkg-ts/spec";
213
+ type MemberChangeType2 = "added" | "removed" | "signature-changed";
214
+ interface MemberChange {
215
+ /** The class this member belongs to */
216
+ className: string;
217
+ /** The member name (e.g., "evaluateChainhook") */
218
+ memberName: string;
219
+ /** Kind of member */
220
+ memberKind: "method" | "property" | "accessor" | "constructor";
221
+ /** Type of change */
222
+ changeType: MemberChangeType2;
223
+ /** Old signature string (for signature changes) */
224
+ oldSignature?: string;
225
+ /** New signature string (for signature changes) */
226
+ newSignature?: string;
227
+ /** Suggested replacement (e.g., "Use replayChainhook instead") */
228
+ suggestion?: string;
229
+ }
201
230
  /**
202
231
  * Analyze docs impact from a spec diff
203
232
  *
204
233
  * @param diff - The spec diff result
205
234
  * @param markdownFiles - Parsed markdown files
206
235
  * @param newExportNames - All names in the new spec (for missing docs detection)
236
+ * @param memberChanges - Optional member-level changes for granular detection
207
237
  */
208
- declare function analyzeDocsImpact(diff: SpecDiff, markdownFiles: MarkdownDocFile[], newExportNames?: string[]): DocsImpactResult;
238
+ declare function analyzeDocsImpact(diff: SpecDiff, markdownFiles: MarkdownDocFile[], newExportNames?: string[], memberChanges?: MemberChange[]): DocsImpactResult;
209
239
  /**
210
240
  * Find references to deprecated exports
211
241
  */
@@ -225,13 +255,15 @@ declare function getDocumentedExports(markdownFiles: MarkdownDocFile[], exportNa
225
255
  * Get all exports that lack documentation
226
256
  */
227
257
  declare function getUndocumentedExports(markdownFiles: MarkdownDocFile[], exportNames: string[]): string[];
228
- import { OpenPkg as OpenPkg2, SpecDiff as SpecDiff2 } from "@openpkg-ts/spec";
258
+ import { OpenPkg as OpenPkg3, SpecDiff as SpecDiff2 } from "@openpkg-ts/spec";
229
259
  /**
230
260
  * Extended spec diff result with docs impact
231
261
  */
232
262
  interface SpecDiffWithDocs extends SpecDiff2 {
233
263
  /** Docs impact analysis (only present if markdown files provided) */
234
264
  docsImpact?: DocsImpactResult;
265
+ /** Member-level changes for classes (methods added/removed/changed) */
266
+ memberChanges?: MemberChange[];
235
267
  }
236
268
  /**
237
269
  * Options for diffSpecWithDocs
@@ -263,7 +295,7 @@ interface DiffWithDocsOptions {
263
295
  * }
264
296
  * ```
265
297
  */
266
- declare function diffSpecWithDocs(oldSpec: OpenPkg2, newSpec: OpenPkg2, options?: DiffWithDocsOptions): SpecDiffWithDocs;
298
+ declare function diffSpecWithDocs(oldSpec: OpenPkg3, newSpec: OpenPkg3, options?: DiffWithDocsOptions): SpecDiffWithDocs;
267
299
  /**
268
300
  * Check if a diff has any docs impact
269
301
  */
@@ -276,6 +308,7 @@ declare function getDocsImpactSummary(diff: SpecDiffWithDocs): {
276
308
  impactedReferenceCount: number;
277
309
  missingDocsCount: number;
278
310
  totalIssues: number;
311
+ memberChangesCount: number;
279
312
  };
280
313
  interface DocCovOptions {
281
314
  includePrivate?: boolean;
@@ -466,7 +499,7 @@ declare class DocCov {
466
499
  declare function analyze(code: string, options?: AnalyzeOptions): Promise<OpenPkgSpec>;
467
500
  declare function analyzeFile(filePath: string, options?: AnalyzeOptions): Promise<OpenPkgSpec>;
468
501
  /** @deprecated Use DocCov instead */
469
- declare const OpenPkg3: typeof DocCov;
502
+ declare const OpenPkg4: typeof DocCov;
470
503
  /**
471
504
  * Project detection types for I/O-agnostic project analysis.
472
505
  * Used by both CLI (NodeFileSystem) and API (SandboxFileSystem).
@@ -723,4 +756,4 @@ declare function readPackageJson(fs: FileSystem, dir: string): Promise<PackageJs
723
756
  * ```
724
757
  */
725
758
  declare function analyzeProject2(fs: FileSystem, options?: AnalyzeProjectOptions): Promise<ProjectInfo>;
726
- export { serializeJSDoc, safeParseJson, runExamplesWithPackage, runExamples, runExample, readPackageJson, parseMarkdownFiles, parseMarkdownFile, parseJSDocToPatch, parseAssertions, mergeFixes, isFixableDrift, isExecutableLang, hasNonAssertionComments, hasDocsImpact, hasDocsForExport, getUndocumentedExports, getRunCommand, getPrimaryBuildScript, getInstallCommand, getDocumentedExports, getDocsImpactSummary, generateFixesForExport, generateFix, formatPackageList, findRemovedReferences, findPackageByName, findJSDocLocation, findExportReferences, findDeprecatedReferences, extractPackageSpec, extractImports, extractFunctionCalls, diffSpecWithDocs, detectPackageManager, detectMonorepo, detectExampleRuntimeErrors, detectExampleAssertionFailures, detectEntryPoint, detectBuildInfo, createSourceFile, categorizeDrifts, blockReferencesExport, applyPatchToJSDoc, applyEdits, analyzeProject2 as analyzeProject, analyzeFile, analyzeDocsImpact, analyze, WorkspacePackage, SpecDiffWithDocs, SandboxFileSystem, RunExamplesWithPackageResult, RunExamplesWithPackageOptions, RunExampleOptions, ProjectInfo, PackageManagerInfo, PackageManager, PackageJson, PackageExports, OpenPkgSpec, OpenPkgOptions, OpenPkg3 as OpenPkg, NodeFileSystem, MonorepoType, MonorepoInfo, MarkdownDocFile, MarkdownCodeBlock, JSDocTag, JSDocReturn, JSDocPatch, JSDocParam, JSDocEdit, FixType, FixSuggestion, FilterOptions, FileSystem, ExportReference, ExampleRunResult, EntryPointSource, EntryPointInfo, DocsImpactResult, DocsImpactReference, DocsImpact, DocsChangeType, DocCovOptions, DocCov, DiffWithDocsOptions, Diagnostic, BuildInfo, ApplyEditsResult, AnalyzeProjectOptions, AnalyzeOptions, AnalysisResult };
759
+ export { serializeJSDoc, safeParseJson, runExamplesWithPackage, runExamples, runExample, readPackageJson, parseMarkdownFiles, parseMarkdownFile, parseJSDocToPatch, parseAssertions, mergeFixes, isFixableDrift, isExecutableLang, hasNonAssertionComments, hasDocsImpact, hasDocsForExport, getUndocumentedExports, getRunCommand, getPrimaryBuildScript, getInstallCommand, getDocumentedExports, getDocsImpactSummary, generateFixesForExport, generateFix, formatPackageList, findRemovedReferences, findPackageByName, findJSDocLocation, findExportReferences, findDeprecatedReferences, extractPackageSpec, extractImports, extractFunctionCalls, diffSpecWithDocs, detectPackageManager, detectMonorepo, detectExampleRuntimeErrors, detectExampleAssertionFailures, detectEntryPoint, detectBuildInfo, createSourceFile, categorizeDrifts, blockReferencesExport, applyPatchToJSDoc, applyEdits, analyzeProject2 as analyzeProject, analyzeFile, analyzeDocsImpact, analyze, WorkspacePackage, SpecDiffWithDocs, SandboxFileSystem, RunExamplesWithPackageResult, RunExamplesWithPackageOptions, RunExampleOptions, ProjectInfo, PackageManagerInfo, PackageManager, PackageJson, PackageExports, OpenPkgSpec, OpenPkgOptions, OpenPkg4 as OpenPkg, NodeFileSystem, MonorepoType, MonorepoInfo, MarkdownDocFile, MarkdownCodeBlock, JSDocTag, JSDocReturn, JSDocPatch, JSDocParam, JSDocEdit, FixType, FixSuggestion, FilterOptions, FileSystem, ExportReference, ExampleRunResult, EntryPointSource, EntryPointInfo, DocsImpactResult, DocsImpactReference, DocsImpact, DocsChangeType, DocCovOptions, DocCov, DiffWithDocsOptions, Diagnostic, BuildInfo, ApplyEditsResult, AnalyzeProjectOptions, AnalyzeOptions, AnalysisResult };
package/dist/index.js CHANGED
@@ -1107,6 +1107,39 @@ function extractFunctionCalls(code) {
1107
1107
  }
1108
1108
  return Array.from(calls);
1109
1109
  }
1110
+ function extractMethodCalls(code) {
1111
+ const calls = [];
1112
+ const lines = code.split(`
1113
+ `);
1114
+ const methodCallRegex = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\.\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
1115
+ const skipObjects = new Set(["console", "Math", "JSON", "Object", "Array", "String", "Number"]);
1116
+ for (let lineIndex = 0;lineIndex < lines.length; lineIndex++) {
1117
+ const line = lines[lineIndex];
1118
+ let match;
1119
+ methodCallRegex.lastIndex = 0;
1120
+ while ((match = methodCallRegex.exec(line)) !== null) {
1121
+ const objectName = match[1];
1122
+ const methodName = match[2];
1123
+ if (skipObjects.has(objectName)) {
1124
+ continue;
1125
+ }
1126
+ calls.push({
1127
+ objectName,
1128
+ methodName,
1129
+ line: lineIndex,
1130
+ context: line.trim()
1131
+ });
1132
+ }
1133
+ }
1134
+ return calls;
1135
+ }
1136
+ function hasInstantiation(code, className) {
1137
+ const regex = new RegExp(`\\bnew\\s+${escapeRegex(className)}\\s*\\(`, "g");
1138
+ return regex.test(code);
1139
+ }
1140
+ function escapeRegex(str) {
1141
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1142
+ }
1110
1143
  function findExportReferences(files, exportNames) {
1111
1144
  const references = [];
1112
1145
  const exportSet = new Set(exportNames);
@@ -1174,27 +1207,99 @@ function getChangeType(exportName, diff) {
1174
1207
  }
1175
1208
  return null;
1176
1209
  }
1177
- function analyzeDocsImpact(diff, markdownFiles, newExportNames = []) {
1210
+ function mapMemberChangeType(memberChangeType) {
1211
+ switch (memberChangeType) {
1212
+ case "removed":
1213
+ return "method-removed";
1214
+ case "signature-changed":
1215
+ return "method-changed";
1216
+ default:
1217
+ return "signature-changed";
1218
+ }
1219
+ }
1220
+ function analyzeDocsImpact(diff, markdownFiles, newExportNames = [], memberChanges) {
1178
1221
  const changedExports = [
1179
1222
  ...diff.breaking
1180
1223
  ];
1181
- const references = findExportReferences(markdownFiles, changedExports);
1224
+ const memberChangesByMethod = new Map;
1225
+ if (memberChanges) {
1226
+ for (const mc of memberChanges) {
1227
+ memberChangesByMethod.set(mc.memberName, mc);
1228
+ }
1229
+ }
1230
+ const classesWithMemberChanges = new Set(memberChanges?.map((mc) => mc.className) ?? []);
1182
1231
  const impactByFile = new Map;
1183
- for (const ref of references) {
1184
- const changeType = getChangeType(ref.exportName, diff);
1185
- if (!changeType)
1186
- continue;
1187
- let impact = impactByFile.get(ref.file);
1232
+ const addReference = (file, ref) => {
1233
+ let impact = impactByFile.get(file);
1188
1234
  if (!impact) {
1189
- impact = { file: ref.file, references: [] };
1190
- impactByFile.set(ref.file, impact);
1191
- }
1192
- impact.references.push({
1193
- exportName: ref.exportName,
1194
- line: ref.line,
1195
- changeType,
1196
- context: ref.context
1197
- });
1235
+ impact = { file, references: [] };
1236
+ impactByFile.set(file, impact);
1237
+ }
1238
+ impact.references.push(ref);
1239
+ };
1240
+ for (const mdFile of markdownFiles) {
1241
+ for (const block of mdFile.codeBlocks) {
1242
+ const reportedRefs = new Set;
1243
+ if (memberChanges && memberChanges.length > 0) {
1244
+ const methodCalls = extractMethodCalls(block.code);
1245
+ for (const call of methodCalls) {
1246
+ const memberChange = memberChangesByMethod.get(call.methodName);
1247
+ if (memberChange) {
1248
+ const refKey = `${mdFile.path}:${block.lineStart + call.line}:${call.methodName}`;
1249
+ if (!reportedRefs.has(refKey)) {
1250
+ reportedRefs.add(refKey);
1251
+ addReference(mdFile.path, {
1252
+ exportName: memberChange.className,
1253
+ memberName: call.methodName,
1254
+ memberChangeType: memberChange.changeType,
1255
+ changeType: mapMemberChangeType(memberChange.changeType),
1256
+ replacementSuggestion: memberChange.suggestion,
1257
+ line: block.lineStart + call.line,
1258
+ context: call.context,
1259
+ isInstantiation: false
1260
+ });
1261
+ }
1262
+ }
1263
+ }
1264
+ for (const className of classesWithMemberChanges) {
1265
+ if (hasInstantiation(block.code, className)) {
1266
+ const refKey = `${mdFile.path}:${block.lineStart}:new ${className}`;
1267
+ if (!reportedRefs.has(refKey)) {
1268
+ const hasMethodRefs = Array.from(reportedRefs).some((key) => key.startsWith(`${mdFile.path}:`) && memberChanges.some((mc) => mc.className === className && key.includes(mc.memberName)));
1269
+ if (!hasMethodRefs) {
1270
+ reportedRefs.add(refKey);
1271
+ addReference(mdFile.path, {
1272
+ exportName: className,
1273
+ line: block.lineStart,
1274
+ changeType: "signature-changed",
1275
+ context: `new ${className}(...)`,
1276
+ isInstantiation: true
1277
+ });
1278
+ }
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+ const exportsWithoutMemberChanges = changedExports.filter((name) => !classesWithMemberChanges.has(name));
1284
+ if (exportsWithoutMemberChanges.length > 0) {
1285
+ const refs = findExportReferences([{ ...mdFile, codeBlocks: [block] }], exportsWithoutMemberChanges);
1286
+ for (const ref of refs) {
1287
+ const changeType = getChangeType(ref.exportName, diff);
1288
+ if (!changeType)
1289
+ continue;
1290
+ const refKey = `${ref.file}:${ref.line}:${ref.exportName}`;
1291
+ if (!reportedRefs.has(refKey)) {
1292
+ reportedRefs.add(refKey);
1293
+ addReference(ref.file, {
1294
+ exportName: ref.exportName,
1295
+ line: ref.line,
1296
+ changeType,
1297
+ context: ref.context
1298
+ });
1299
+ }
1300
+ }
1301
+ }
1302
+ }
1198
1303
  }
1199
1304
  const documentedExports = new Set;
1200
1305
  for (const file of markdownFiles) {
@@ -1208,10 +1313,11 @@ function analyzeDocsImpact(diff, markdownFiles, newExportNames = []) {
1208
1313
  }
1209
1314
  const missingDocs = diff.nonBreaking.filter((name) => !documentedExports.has(name));
1210
1315
  const totalCodeBlocks = markdownFiles.reduce((sum, f) => sum + f.codeBlocks.length, 0);
1211
- const allReferences = findExportReferences(markdownFiles, [
1212
- ...changedExports,
1213
- ...diff.nonBreaking
1214
- ]);
1316
+ const allReferences = findExportReferences(markdownFiles, [...changedExports, ...diff.nonBreaking]);
1317
+ let impactedReferences = 0;
1318
+ for (const impact of impactByFile.values()) {
1319
+ impactedReferences += impact.references.length;
1320
+ }
1215
1321
  return {
1216
1322
  impactedFiles: Array.from(impactByFile.values()),
1217
1323
  missingDocs,
@@ -1219,7 +1325,7 @@ function analyzeDocsImpact(diff, markdownFiles, newExportNames = []) {
1219
1325
  filesScanned: markdownFiles.length,
1220
1326
  codeBlocksFound: totalCodeBlocks,
1221
1327
  referencesFound: allReferences.length,
1222
- impactedReferences: references.length
1328
+ impactedReferences
1223
1329
  }
1224
1330
  };
1225
1331
  }
@@ -1246,18 +1352,213 @@ function getUndocumentedExports(markdownFiles, exportNames) {
1246
1352
  const documented = new Set(getDocumentedExports(markdownFiles, exportNames));
1247
1353
  return exportNames.filter((name) => !documented.has(name));
1248
1354
  }
1355
+ // src/markdown/member-diff.ts
1356
+ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
1357
+ const changes = [];
1358
+ const oldExportMap = toExportMap(oldSpec.exports ?? []);
1359
+ const newExportMap = toExportMap(newSpec.exports ?? []);
1360
+ for (const className of changedClassNames) {
1361
+ const oldExport = oldExportMap.get(className);
1362
+ const newExport = newExportMap.get(className);
1363
+ if (!oldExport?.members && !newExport?.members) {
1364
+ continue;
1365
+ }
1366
+ const oldMembers = toMemberMap(oldExport?.members ?? []);
1367
+ const newMembers = toMemberMap(newExport?.members ?? []);
1368
+ const addedMemberNames = [];
1369
+ for (const [memberName, newMember] of newMembers) {
1370
+ if (!oldMembers.has(memberName)) {
1371
+ addedMemberNames.push(memberName);
1372
+ changes.push({
1373
+ className,
1374
+ memberName,
1375
+ memberKind: getMemberKind(newMember),
1376
+ changeType: "added",
1377
+ newSignature: formatSignature(newMember)
1378
+ });
1379
+ }
1380
+ }
1381
+ for (const [memberName, oldMember] of oldMembers) {
1382
+ if (!newMembers.has(memberName)) {
1383
+ const suggestion = findSimilarMember(memberName, newMembers, addedMemberNames);
1384
+ changes.push({
1385
+ className,
1386
+ memberName,
1387
+ memberKind: getMemberKind(oldMember),
1388
+ changeType: "removed",
1389
+ oldSignature: formatSignature(oldMember),
1390
+ suggestion
1391
+ });
1392
+ }
1393
+ }
1394
+ for (const [memberName, oldMember] of oldMembers) {
1395
+ const newMember = newMembers.get(memberName);
1396
+ if (newMember && hasSignatureChanged(oldMember, newMember)) {
1397
+ changes.push({
1398
+ className,
1399
+ memberName,
1400
+ memberKind: getMemberKind(newMember),
1401
+ changeType: "signature-changed",
1402
+ oldSignature: formatSignature(oldMember),
1403
+ newSignature: formatSignature(newMember)
1404
+ });
1405
+ }
1406
+ }
1407
+ }
1408
+ return changes;
1409
+ }
1410
+ function toExportMap(exports) {
1411
+ const map = new Map;
1412
+ for (const exp of exports) {
1413
+ if (exp?.name) {
1414
+ map.set(exp.name, exp);
1415
+ }
1416
+ }
1417
+ return map;
1418
+ }
1419
+ function toMemberMap(members) {
1420
+ const map = new Map;
1421
+ for (const member of members) {
1422
+ if (member?.name) {
1423
+ map.set(member.name, member);
1424
+ }
1425
+ }
1426
+ return map;
1427
+ }
1428
+ function getMemberKind(member) {
1429
+ switch (member.kind) {
1430
+ case "method":
1431
+ return "method";
1432
+ case "property":
1433
+ return "property";
1434
+ case "accessor":
1435
+ return "accessor";
1436
+ case "constructor":
1437
+ return "constructor";
1438
+ default:
1439
+ return "method";
1440
+ }
1441
+ }
1442
+ function formatSignature(member) {
1443
+ if (!member.signatures?.length) {
1444
+ return member.name ?? "";
1445
+ }
1446
+ const sig = member.signatures[0];
1447
+ const params = sig.parameters?.map((p) => {
1448
+ const optional = p.required === false ? "?" : "";
1449
+ return `${p.name}${optional}`;
1450
+ }) ?? [];
1451
+ return `${member.name}(${params.join(", ")})`;
1452
+ }
1453
+ function hasSignatureChanged(oldMember, newMember) {
1454
+ const oldSigs = oldMember.signatures ?? [];
1455
+ const newSigs = newMember.signatures ?? [];
1456
+ if (oldSigs.length !== newSigs.length) {
1457
+ return true;
1458
+ }
1459
+ for (let i = 0;i < oldSigs.length; i++) {
1460
+ if (!signaturesEqual(oldSigs[i], newSigs[i])) {
1461
+ return true;
1462
+ }
1463
+ }
1464
+ return false;
1465
+ }
1466
+ function signaturesEqual(a, b) {
1467
+ const aParams = a.parameters ?? [];
1468
+ const bParams = b.parameters ?? [];
1469
+ if (aParams.length !== bParams.length) {
1470
+ return false;
1471
+ }
1472
+ for (let i = 0;i < aParams.length; i++) {
1473
+ const ap = aParams[i];
1474
+ const bp = bParams[i];
1475
+ if (ap.name !== bp.name)
1476
+ return false;
1477
+ if (ap.required !== bp.required)
1478
+ return false;
1479
+ if (JSON.stringify(ap.schema) !== JSON.stringify(bp.schema))
1480
+ return false;
1481
+ }
1482
+ if (JSON.stringify(a.returns) !== JSON.stringify(b.returns)) {
1483
+ return false;
1484
+ }
1485
+ return true;
1486
+ }
1487
+ function findSimilarMember(removedName, newMembers, addedMembers) {
1488
+ const candidates = addedMembers.length > 0 ? addedMembers : Array.from(newMembers.keys());
1489
+ let bestMatch;
1490
+ let bestScore = 0;
1491
+ for (const name of candidates) {
1492
+ if (name === removedName)
1493
+ continue;
1494
+ const removedWords = splitCamelCase(removedName);
1495
+ const newWords = splitCamelCase(name);
1496
+ let matchingWords = 0;
1497
+ let suffixMatch = false;
1498
+ if (removedWords.length > 0 && newWords.length > 0) {
1499
+ const removedSuffix = removedWords[removedWords.length - 1];
1500
+ const newSuffix = newWords[newWords.length - 1];
1501
+ if (removedSuffix === newSuffix) {
1502
+ suffixMatch = true;
1503
+ matchingWords += 2;
1504
+ }
1505
+ }
1506
+ for (const word of removedWords) {
1507
+ if (newWords.includes(word)) {
1508
+ matchingWords++;
1509
+ }
1510
+ }
1511
+ const wordScore = matchingWords / Math.max(removedWords.length, newWords.length);
1512
+ const editDistance = levenshteinDistance(removedName.toLowerCase(), name.toLowerCase());
1513
+ const maxLen = Math.max(removedName.length, name.length);
1514
+ const levenScore = 1 - editDistance / maxLen;
1515
+ const totalScore = suffixMatch ? wordScore * 1.5 + levenScore : wordScore + levenScore * 0.5;
1516
+ if (totalScore > bestScore && totalScore >= 0.5) {
1517
+ bestScore = totalScore;
1518
+ bestMatch = name;
1519
+ }
1520
+ }
1521
+ return bestMatch ? `Use ${bestMatch} instead` : undefined;
1522
+ }
1523
+ function levenshteinDistance(a, b) {
1524
+ const matrix = [];
1525
+ for (let i = 0;i <= b.length; i++) {
1526
+ matrix[i] = [i];
1527
+ }
1528
+ for (let j = 0;j <= a.length; j++) {
1529
+ matrix[0][j] = j;
1530
+ }
1531
+ for (let i = 1;i <= b.length; i++) {
1532
+ for (let j = 1;j <= a.length; j++) {
1533
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
1534
+ matrix[i][j] = matrix[i - 1][j - 1];
1535
+ } else {
1536
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
1537
+ }
1538
+ }
1539
+ }
1540
+ return matrix[b.length][a.length];
1541
+ }
1542
+ function splitCamelCase(str) {
1543
+ return str.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(" ");
1544
+ }
1249
1545
  // src/markdown/diff-with-docs.ts
1250
1546
  import { diffSpec } from "@openpkg-ts/spec";
1251
1547
  function diffSpecWithDocs(oldSpec, newSpec, options = {}) {
1252
1548
  const baseDiff = diffSpec(oldSpec, newSpec);
1549
+ const memberChanges = diffMemberChanges(oldSpec, newSpec, baseDiff.breaking);
1253
1550
  if (!options.markdownFiles?.length) {
1254
- return baseDiff;
1551
+ return {
1552
+ ...baseDiff,
1553
+ memberChanges: memberChanges.length > 0 ? memberChanges : undefined
1554
+ };
1255
1555
  }
1256
1556
  const newExportNames = newSpec.exports?.map((e) => e.name) ?? [];
1257
- const docsImpact = analyzeDocsImpact(baseDiff, options.markdownFiles, newExportNames);
1557
+ const docsImpact = analyzeDocsImpact(baseDiff, options.markdownFiles, newExportNames, memberChanges);
1258
1558
  return {
1259
1559
  ...baseDiff,
1260
- docsImpact
1560
+ docsImpact,
1561
+ memberChanges: memberChanges.length > 0 ? memberChanges : undefined
1261
1562
  };
1262
1563
  }
1263
1564
  function hasDocsImpact(diff) {
@@ -1271,7 +1572,8 @@ function getDocsImpactSummary(diff) {
1271
1572
  impactedFileCount: 0,
1272
1573
  impactedReferenceCount: 0,
1273
1574
  missingDocsCount: 0,
1274
- totalIssues: 0
1575
+ totalIssues: 0,
1576
+ memberChangesCount: diff.memberChanges?.length ?? 0
1275
1577
  };
1276
1578
  }
1277
1579
  const impactedFileCount = diff.docsImpact.impactedFiles.length;
@@ -1281,7 +1583,8 @@ function getDocsImpactSummary(diff) {
1281
1583
  impactedFileCount,
1282
1584
  impactedReferenceCount,
1283
1585
  missingDocsCount,
1284
- totalIssues: impactedReferenceCount + missingDocsCount
1586
+ totalIssues: impactedReferenceCount + missingDocsCount,
1587
+ memberChangesCount: diff.memberChanges?.length ?? 0
1285
1588
  };
1286
1589
  }
1287
1590
  // src/analysis/run-analysis.ts
@@ -1299,101 +1602,6 @@ var resolvedTypeScriptModule = (() => {
1299
1602
  })();
1300
1603
  var ts2 = resolvedTypeScriptModule;
1301
1604
 
1302
- // src/analysis/context.ts
1303
- import * as path2 from "node:path";
1304
-
1305
- // src/options.ts
1306
- var DEFAULT_OPTIONS = {
1307
- includePrivate: false,
1308
- followImports: true
1309
- };
1310
- function normalizeDocCovOptions(options = {}) {
1311
- return {
1312
- ...DEFAULT_OPTIONS,
1313
- ...options
1314
- };
1315
- }
1316
- var normalizeOpenPkgOptions = normalizeDocCovOptions;
1317
-
1318
- // src/analysis/program.ts
1319
- import * as path from "node:path";
1320
- var DEFAULT_COMPILER_OPTIONS = {
1321
- target: ts2.ScriptTarget.Latest,
1322
- module: ts2.ModuleKind.CommonJS,
1323
- lib: ["lib.es2021.d.ts"],
1324
- declaration: true,
1325
- moduleResolution: ts2.ModuleResolutionKind.NodeJs
1326
- };
1327
- function createProgram({
1328
- entryFile,
1329
- baseDir = path.dirname(entryFile),
1330
- content
1331
- }) {
1332
- const configPath = ts2.findConfigFile(baseDir, ts2.sys.fileExists, "tsconfig.json");
1333
- let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
1334
- if (configPath) {
1335
- const configFile = ts2.readConfigFile(configPath, ts2.sys.readFile);
1336
- const parsedConfig = ts2.parseJsonConfigFileContent(configFile.config, ts2.sys, path.dirname(configPath));
1337
- compilerOptions = { ...compilerOptions, ...parsedConfig.options };
1338
- }
1339
- const allowJsVal = compilerOptions.allowJs;
1340
- if (typeof allowJsVal === "boolean" && allowJsVal) {
1341
- compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
1342
- }
1343
- const compilerHost = ts2.createCompilerHost(compilerOptions, true);
1344
- let inMemorySource;
1345
- if (content !== undefined) {
1346
- inMemorySource = ts2.createSourceFile(entryFile, content, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TS);
1347
- const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
1348
- compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
1349
- if (fileName === entryFile) {
1350
- return inMemorySource;
1351
- }
1352
- return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
1353
- };
1354
- }
1355
- const program = ts2.createProgram([entryFile], compilerOptions, compilerHost);
1356
- const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
1357
- return {
1358
- program,
1359
- compilerHost,
1360
- compilerOptions,
1361
- sourceFile,
1362
- configPath
1363
- };
1364
- }
1365
-
1366
- // src/analysis/context.ts
1367
- function createAnalysisContext({
1368
- entryFile,
1369
- packageDir,
1370
- content,
1371
- options
1372
- }) {
1373
- const baseDir = packageDir ?? path2.dirname(entryFile);
1374
- const normalizedOptions = normalizeOpenPkgOptions(options);
1375
- const programResult = createProgram({ entryFile, baseDir, content });
1376
- if (!programResult.sourceFile) {
1377
- throw new Error(`Could not load ${entryFile}`);
1378
- }
1379
- return {
1380
- entryFile,
1381
- baseDir,
1382
- program: programResult.program,
1383
- checker: programResult.program.getTypeChecker(),
1384
- sourceFile: programResult.sourceFile,
1385
- compilerOptions: programResult.compilerOptions,
1386
- compilerHost: programResult.compilerHost,
1387
- options: normalizedOptions,
1388
- configPath: programResult.configPath
1389
- };
1390
- }
1391
-
1392
- // src/analysis/spec-builder.ts
1393
- import * as fs from "node:fs";
1394
- import * as path3 from "node:path";
1395
- import { SCHEMA_URL } from "@openpkg-ts/spec";
1396
-
1397
1605
  // src/utils/type-utils.ts
1398
1606
  function getTypeId(type, typeChecker) {
1399
1607
  const internalId = type.id;
@@ -1515,6 +1723,30 @@ function collectReferencedTypesFromNode(node, typeChecker, referencedTypes) {
1515
1723
  });
1516
1724
  }
1517
1725
  function isBuiltInType(name) {
1726
+ if (name.length === 1 && /^[A-Z]$/.test(name)) {
1727
+ return true;
1728
+ }
1729
+ if (/^T[A-Z][a-zA-Z]*$/.test(name)) {
1730
+ return true;
1731
+ }
1732
+ const libraryInternals = [
1733
+ "UnionStatic",
1734
+ "IntersectStatic",
1735
+ "ObjectStatic",
1736
+ "ArrayStatic",
1737
+ "StaticDecode",
1738
+ "StaticEncode",
1739
+ "ZodType",
1740
+ "ZodObject",
1741
+ "ZodString",
1742
+ "ZodNumber",
1743
+ "ZodArray",
1744
+ "ZodUnion",
1745
+ "ZodIntersection"
1746
+ ];
1747
+ if (libraryInternals.includes(name)) {
1748
+ return true;
1749
+ }
1518
1750
  const builtIns = [
1519
1751
  "string",
1520
1752
  "number",
@@ -1523,6 +1755,8 @@ function isBuiltInType(name) {
1523
1755
  "symbol",
1524
1756
  "undefined",
1525
1757
  "null",
1758
+ "true",
1759
+ "false",
1526
1760
  "any",
1527
1761
  "unknown",
1528
1762
  "never",
@@ -1566,11 +1800,125 @@ function isBuiltInType(name) {
1566
1800
  "Proxy",
1567
1801
  "Intl",
1568
1802
  "globalThis",
1803
+ "Record",
1804
+ "Partial",
1805
+ "Required",
1806
+ "Readonly",
1807
+ "Pick",
1808
+ "Omit",
1809
+ "Exclude",
1810
+ "Extract",
1811
+ "NonNullable",
1812
+ "ReturnType",
1813
+ "Parameters",
1814
+ "InstanceType",
1815
+ "ConstructorParameters",
1816
+ "Awaited",
1817
+ "ThisType",
1818
+ "Uppercase",
1819
+ "Lowercase",
1820
+ "Capitalize",
1821
+ "Uncapitalize",
1569
1822
  "__type"
1570
1823
  ];
1571
1824
  return builtIns.includes(name);
1572
1825
  }
1573
1826
 
1827
+ // src/analysis/context.ts
1828
+ import * as path2 from "node:path";
1829
+
1830
+ // src/options.ts
1831
+ var DEFAULT_OPTIONS = {
1832
+ includePrivate: false,
1833
+ followImports: true
1834
+ };
1835
+ function normalizeDocCovOptions(options = {}) {
1836
+ return {
1837
+ ...DEFAULT_OPTIONS,
1838
+ ...options
1839
+ };
1840
+ }
1841
+ var normalizeOpenPkgOptions = normalizeDocCovOptions;
1842
+
1843
+ // src/analysis/program.ts
1844
+ import * as path from "node:path";
1845
+ var DEFAULT_COMPILER_OPTIONS = {
1846
+ target: ts2.ScriptTarget.Latest,
1847
+ module: ts2.ModuleKind.CommonJS,
1848
+ lib: ["lib.es2021.d.ts"],
1849
+ declaration: true,
1850
+ moduleResolution: ts2.ModuleResolutionKind.NodeJs
1851
+ };
1852
+ function createProgram({
1853
+ entryFile,
1854
+ baseDir = path.dirname(entryFile),
1855
+ content
1856
+ }) {
1857
+ const configPath = ts2.findConfigFile(baseDir, ts2.sys.fileExists, "tsconfig.json");
1858
+ let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
1859
+ if (configPath) {
1860
+ const configFile = ts2.readConfigFile(configPath, ts2.sys.readFile);
1861
+ const parsedConfig = ts2.parseJsonConfigFileContent(configFile.config, ts2.sys, path.dirname(configPath));
1862
+ compilerOptions = { ...compilerOptions, ...parsedConfig.options };
1863
+ }
1864
+ const allowJsVal = compilerOptions.allowJs;
1865
+ if (typeof allowJsVal === "boolean" && allowJsVal) {
1866
+ compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
1867
+ }
1868
+ const compilerHost = ts2.createCompilerHost(compilerOptions, true);
1869
+ let inMemorySource;
1870
+ if (content !== undefined) {
1871
+ inMemorySource = ts2.createSourceFile(entryFile, content, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TS);
1872
+ const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
1873
+ compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
1874
+ if (fileName === entryFile) {
1875
+ return inMemorySource;
1876
+ }
1877
+ return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
1878
+ };
1879
+ }
1880
+ const program = ts2.createProgram([entryFile], compilerOptions, compilerHost);
1881
+ const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
1882
+ return {
1883
+ program,
1884
+ compilerHost,
1885
+ compilerOptions,
1886
+ sourceFile,
1887
+ configPath
1888
+ };
1889
+ }
1890
+
1891
+ // src/analysis/context.ts
1892
+ function createAnalysisContext({
1893
+ entryFile,
1894
+ packageDir,
1895
+ content,
1896
+ options
1897
+ }) {
1898
+ const baseDir = packageDir ?? path2.dirname(entryFile);
1899
+ const normalizedOptions = normalizeOpenPkgOptions(options);
1900
+ const programResult = createProgram({ entryFile, baseDir, content });
1901
+ if (!programResult.sourceFile) {
1902
+ throw new Error(`Could not load ${entryFile}`);
1903
+ }
1904
+ return {
1905
+ entryFile,
1906
+ baseDir,
1907
+ program: programResult.program,
1908
+ checker: programResult.program.getTypeChecker(),
1909
+ sourceFile: programResult.sourceFile,
1910
+ compilerOptions: programResult.compilerOptions,
1911
+ compilerHost: programResult.compilerHost,
1912
+ options: normalizedOptions,
1913
+ configPath: programResult.configPath
1914
+ };
1915
+ }
1916
+
1917
+ // src/analysis/spec-builder.ts
1918
+ import * as fs from "node:fs";
1919
+ import * as path3 from "node:path";
1920
+ import { SCHEMA_URL } from "@openpkg-ts/spec";
1921
+
1574
1922
  // src/utils/parameter-utils.ts
1575
1923
  var BUILTIN_TYPE_SCHEMAS = {
1576
1924
  Date: { type: "string", format: "date-time" },
@@ -3705,10 +4053,10 @@ function collectDanglingRefs(spec) {
3705
4053
  const referencedTypes = new Set;
3706
4054
  collectAllRefs(spec.exports, referencedTypes);
3707
4055
  collectAllRefs(spec.types, referencedTypes);
3708
- return Array.from(referencedTypes).filter((ref) => !definedTypes.has(ref));
4056
+ return Array.from(referencedTypes).filter((ref) => !definedTypes.has(ref) && !isBuiltInType(ref));
3709
4057
  }
3710
4058
  function collectExternalTypes(spec) {
3711
- return (spec.types ?? []).filter((t) => t.kind === "external").map((t) => t.id);
4059
+ return (spec.types ?? []).filter((t) => t.kind === "external").map((t) => t.id).filter((id) => !isBuiltInType(id));
3712
4060
  }
3713
4061
  function hasExternalImports(sourceFile) {
3714
4062
  let found = false;
@@ -4077,7 +4425,7 @@ function generateAssertionFix(drift, exportEntry) {
4077
4425
  const oldValue = oldValueMatch?.[1];
4078
4426
  if (!oldValue)
4079
4427
  return null;
4080
- const updatedExample = oldExample.replace(new RegExp(`//\\s*=>\\s*${escapeRegex(oldValue)}`, "g"), `// => ${newValue}`);
4428
+ const updatedExample = oldExample.replace(new RegExp(`//\\s*=>\\s*${escapeRegex2(oldValue)}`, "g"), `// => ${newValue}`);
4081
4429
  const updatedExamples = [...examples];
4082
4430
  updatedExamples[exampleIndex] = updatedExample;
4083
4431
  return {
@@ -4195,7 +4543,7 @@ function stringifySchema(schema) {
4195
4543
  }
4196
4544
  return "unknown";
4197
4545
  }
4198
- function escapeRegex(str) {
4546
+ function escapeRegex2(str) {
4199
4547
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4200
4548
  }
4201
4549
  function categorizeDrifts(drifts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccov/sdk",
3
- "version": "0.3.6",
3
+ "version": "0.5.6",
4
4
  "description": "DocCov SDK - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",