@doccov/sdk 0.3.7 → 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 +39 -6
- package/dist/index.js +331 -28
- package/package.json +1 -1
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
|
|
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:
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
1184
|
-
|
|
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
|
|
1190
|
-
impactByFile.set(
|
|
1191
|
-
}
|
|
1192
|
-
impact.references.push(
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
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
|
-
|
|
1213
|
-
|
|
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
|
|
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
|
|
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
|
|
@@ -4122,7 +4425,7 @@ function generateAssertionFix(drift, exportEntry) {
|
|
|
4122
4425
|
const oldValue = oldValueMatch?.[1];
|
|
4123
4426
|
if (!oldValue)
|
|
4124
4427
|
return null;
|
|
4125
|
-
const updatedExample = oldExample.replace(new RegExp(`//\\s*=>\\s*${
|
|
4428
|
+
const updatedExample = oldExample.replace(new RegExp(`//\\s*=>\\s*${escapeRegex2(oldValue)}`, "g"), `// => ${newValue}`);
|
|
4126
4429
|
const updatedExamples = [...examples];
|
|
4127
4430
|
updatedExamples[exampleIndex] = updatedExample;
|
|
4128
4431
|
return {
|
|
@@ -4240,7 +4543,7 @@ function stringifySchema(schema) {
|
|
|
4240
4543
|
}
|
|
4241
4544
|
return "unknown";
|
|
4242
4545
|
}
|
|
4243
|
-
function
|
|
4546
|
+
function escapeRegex2(str) {
|
|
4244
4547
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4245
4548
|
}
|
|
4246
4549
|
function categorizeDrifts(drifts) {
|