Skip to content

Commit ee1bd4d

Browse files
[rust-api-parser] Update parser to have Stable Line IDs (Azure#10535)
* Enhance Rust API Parser with Stable Line IDs (#1) * formattign changes and "use name = summary" pattern * replaceCratePath utils * split if glob for "use" * checkpoint - do not show internal details such as generated::* * external module reexports being handled better * formatting changes and upgrade version * refactor common logic - re-export refs * processExternalReferences * linedid utils * checkpoint with a bug * new strategy to isolate the Use items - draft * simple Use item - id fix * template looks great * addExternalReferencesIfNotExists improvement * added a case where use item has not been processed yet, so we process it. * parent info missed * format * bug fix for identity * generic args fix * registerExternalItemReference * changelog * collapse rootModule into top-level * replaceSuperPrefix * line id utils added * update function name * stable lineIds updates * Update tools/apiview/parsers/rust-api-parser/src/process-items/processEnum.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * changelog * Update tools/apiview/parsers/rust-api-parser/CHANGELOG.md * Update tools/apiview/parsers/rust-api-parser/CHANGELOG.md * more descriptive unknown_names - the codepath that doesn't get hit * type --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 1b07f62 commit ee1bd4d

25 files changed

Lines changed: 203 additions & 96 deletions

tools/apiview/parsers/rust-api-parser/CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# 1.1.0
22

3-
Enhanced rendering of `use` items to more closely match the rustdoc (docs.rs) HTML view:
4-
- Improved handling of `use` items and module re-exports with better tracking to prevent duplication
5-
- Refactored module processing with enhanced sorting and organization of child items
6-
- Fixed bugs and improved utility functions for better code maintainability
3+
- Enhanced rendering of `use` items to more closely match the rustdoc (docs.rs) HTML view:
4+
- Improved handling of `use` items and module re-exports with better tracking to prevent duplication
5+
- Refactored module processing with enhanced sorting and organization of child items
6+
- Fixed bugs and improved utility functions for better code maintainability
7+
- Updated `ReviewLine#LineId`s to stable, meaningful identifiers rather than relying on the dynamic rustdoc ids.
78

89
# 1.0.1
910

tools/apiview/parsers/rust-api-parser/src/main.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CodeFile, TokenKind } from "./models/apiview-models";
44
import { Crate, FORMAT_VERSION, Id } from "../rustdoc-types/output/rustdoc-types";
55
import { externalReferencesLines } from "./process-items/utils/externalReexports";
66
import { sortExternalItems } from "./process-items/utils/sorting";
7+
import { updateReviewLinesWithStableLineIds } from "./utils/lineIdUtils";
78

89
let apiJson: Crate;
910
export const processedItems = new Set<number>();
@@ -67,8 +68,8 @@ function processExternalReferences(codeFile: CodeFile): void {
6768
*/
6869
function buildCodeFile(): CodeFile {
6970
const codeFile: CodeFile = {
70-
PackageName: apiJson.index[apiJson.root].name || "unknown",
71-
PackageVersion: apiJson["crate_version"] || "unknown",
71+
PackageName: apiJson.index[apiJson.root].name || "unknown_root_package_name",
72+
PackageVersion: apiJson["crate_version"] || "unknown_crate_version",
7273
ParserVersion: "1.1.0",
7374
Language: "Rust",
7475
ReviewLines: [],
@@ -77,6 +78,8 @@ function buildCodeFile(): CodeFile {
7778

7879
processRootItem(codeFile);
7980
processExternalReferences(codeFile);
81+
82+
updateReviewLinesWithStableLineIds(codeFile.ReviewLines);
8083
return codeFile;
8184
}
8285

tools/apiview/parsers/rust-api-parser/src/process-items/processAssocConst.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Item } from "../../rustdoc-types/output/rustdoc-types";
33
import { createDocsReviewLines } from "./utils/generateDocReviewLine";
44
import { typeToReviewTokens } from "./utils/typeToReviewTokens";
55
import { isAssocConstItem } from "./utils/typeGuards";
6+
import { lineIdMap } from "../utils/lineIdUtils";
67

78
/**
89
* Processes an associated constant item and returns ReviewLine objects.
@@ -30,7 +31,7 @@ export function processAssocConst(item: Item): ReviewLine[] | null {
3031
// Add name
3132
reviewLine.Tokens.push({
3233
Kind: TokenKind.MemberName,
33-
Value: item.name || "unknown",
34+
Value: item.name || "unknown_assoc_const",
3435
HasSuffixSpace: false,
3536
RenderClasses: ["interface"],
3637
});
@@ -62,5 +63,6 @@ export function processAssocConst(item: Item): ReviewLine[] | null {
6263
});
6364

6465
reviewLines.push(reviewLine);
66+
lineIdMap.set(item.id.toString(), `const_${item.name}`);
6567
return reviewLines;
6668
}

tools/apiview/parsers/rust-api-parser/src/process-items/processAssocType.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createDocsReviewLines } from "./utils/generateDocReviewLine";
44
import { typeToReviewTokens } from "./utils/typeToReviewTokens";
55
import { isAssocTypeItem } from "./utils/typeGuards";
66
import { createGenericBoundTokens, processGenerics } from "./utils/processGenerics";
7+
import { lineIdMap } from "../utils/lineIdUtils";
78

89
/**
910
* Processes an associated type item and returns ReviewLine objects.
@@ -32,7 +33,7 @@ export function processAssocType(item: Item): ReviewLine[] | null {
3233
// Add name
3334
reviewLine.Tokens.push({
3435
Kind: TokenKind.MemberName,
35-
Value: item.name || "unknown",
36+
Value: item.name || "unknown_assoc_type",
3637
HasSuffixSpace: false,
3738
RenderClasses: ["interface"],
3839
});
@@ -65,5 +66,6 @@ export function processAssocType(item: Item): ReviewLine[] | null {
6566
});
6667

6768
reviewLines.push(reviewLine);
69+
lineIdMap.set(item.id.toString(), `type_${item.name}`);
6870
return reviewLines;
6971
}

tools/apiview/parsers/rust-api-parser/src/process-items/processConstant.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Item } from "../../rustdoc-types/output/rustdoc-types";
33
import { createDocsReviewLines } from "./utils/generateDocReviewLine";
44
import { isConstantItem } from "./utils/typeGuards";
55
import { typeToReviewTokens } from "./utils/typeToReviewTokens";
6+
import { lineIdMap } from "../utils/lineIdUtils";
67

78
export function processConstant(item: Item) {
89
if (!isConstantItem(item)) return;
@@ -21,7 +22,7 @@ export function processConstant(item: Item) {
2122
});
2223
reviewLine.Tokens.push({
2324
Kind: TokenKind.MemberName,
24-
Value: item.name || "null",
25+
Value: item.name || "unknown_const",
2526
HasSuffixSpace: false,
2627
NavigateToId: item.id.toString(),
2728
NavigationDisplayName: item.name,
@@ -38,7 +39,7 @@ export function processConstant(item: Item) {
3839
});
3940
reviewLine.Tokens.push({
4041
Kind: TokenKind.Text,
41-
Value: item.inner.constant.const.expr || "null",
42+
Value: item.inner.constant.const.expr || "unknown_const_expr",
4243
HasSuffixSpace: false,
4344
});
4445
reviewLine.Tokens.push({
@@ -56,5 +57,6 @@ export function processConstant(item: Item) {
5657
});
5758
}
5859
reviewLines.push(reviewLine);
60+
lineIdMap.set(item.id.toString(), `const_${item.name}`);
5961
return reviewLines;
6062
}

tools/apiview/parsers/rust-api-parser/src/process-items/processEnum.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { createDocsReviewLines } from "./utils/generateDocReviewLine";
55
import { processGenerics } from "./utils/processGenerics";
66
import { isEnumItem } from "./utils/typeGuards";
77
import { getAPIJson } from "../main";
8+
import { lineIdMap } from "../utils/lineIdUtils";
89

910
export function processEnum(item: Item): ReviewLine[] {
1011
if (!isEnumItem(item)) return [];
1112
const apiJson = getAPIJson();
1213
const reviewLines: ReviewLine[] = item.docs ? createDocsReviewLines(item) : [];
1314

15+
lineIdMap.set(item.id.toString(), `enum_${item.name}`);
1416
// Process derives and impls
1517
let implResult: ImplProcessResult;
1618
if (item.inner.enum.impls) {
@@ -25,7 +27,6 @@ export function processEnum(item: Item): ReviewLine[] {
2527

2628
if (implResult.deriveTokens.length > 0) {
2729
const deriveTokensLine: ReviewLine = {
28-
LineId: item.id.toString() + "_derive",
2930
Tokens: implResult.deriveTokens,
3031
RelatedToLine: item.id.toString(),
3132
};
@@ -39,7 +40,7 @@ export function processEnum(item: Item): ReviewLine[] {
3940

4041
enumLine.Tokens.push({
4142
Kind: TokenKind.MemberName,
42-
Value: item.name || "null",
43+
Value: item.name || "unknown_enum",
4344
NavigateToId: item.id.toString(),
4445
NavigationDisplayName: item.name || undefined,
4546
RenderClasses: ["enum"],
@@ -67,12 +68,13 @@ export function processEnum(item: Item): ReviewLine[] {
6768
if (item.inner.enum.variants) {
6869
enumLine.Children = item.inner.enum.variants.map((variant: number) => {
6970
const variantItem = apiJson.index[variant];
71+
lineIdMap.set(variantItem.id.toString(), `variant_${variantItem.name}`);
7072
return {
7173
LineId: variantItem.id.toString(),
7274
Tokens: [
7375
{
7476
Kind: TokenKind.Text,
75-
Value: variantItem.name || "null",
77+
Value: variantItem.name || "unknown_variant",
7678
HasSuffixSpace: false,
7779
},
7880
{

tools/apiview/parsers/rust-api-parser/src/process-items/processExternType.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ReviewLine, TokenKind } from "../models/apiview-models";
22
import { Item } from "../../rustdoc-types/output/rustdoc-types";
33
import { createDocsReviewLines } from "./utils/generateDocReviewLine";
44
import { isExternTypeItem } from "./utils/typeGuards";
5+
import { lineIdMap } from "../utils/lineIdUtils";
56

67
/**
78
* Processes an extern type item and returns ReviewLine objects.
@@ -30,7 +31,7 @@ export function processExternType(item: Item): ReviewLine[] | null {
3031
// Add name
3132
reviewLine.Tokens.push({
3233
Kind: TokenKind.MemberName,
33-
Value: item.name || "unknown",
34+
Value: item.name || "unknown_extern_type",
3435
});
3536

3637
// Add semicolon
@@ -40,5 +41,6 @@ export function processExternType(item: Item): ReviewLine[] | null {
4041
});
4142

4243
reviewLines.push(reviewLine);
44+
lineIdMap.set(item.id.toString(), `extern_type_${item.name}`);
4345
return reviewLines;
4446
}

tools/apiview/parsers/rust-api-parser/src/process-items/processFunction.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createDocsReviewLines } from "./utils/generateDocReviewLine";
44
import { processGenerics } from "./utils/processGenerics";
55
import { isFunctionItem } from "./utils/typeGuards";
66
import { typeToReviewTokens } from "./utils/typeToReviewTokens";
7+
import { lineIdMap } from "../utils/lineIdUtils";
78

89
/**
910
* Processes the function header and adds modifiers and ABI information to the tokens
@@ -83,7 +84,7 @@ export function processFunction(item: Item): ReviewLine[] {
8384

8485
reviewLine.Tokens.push({
8586
Kind: TokenKind.MemberName,
86-
Value: item.name || "null",
87+
Value: item.name || "unknown_fn",
8788
HasSuffixSpace: false,
8889
RenderClasses: ["method"],
8990
NavigateToId: item.id.toString(),
@@ -175,5 +176,6 @@ export function processFunction(item: Item): ReviewLine[] {
175176
});
176177
}
177178
reviewLines.push(reviewLine);
179+
lineIdMap.set(item.id.toString(), `function_${item.name}`);
178180
return reviewLines;
179181
}

tools/apiview/parsers/rust-api-parser/src/process-items/processImpl.ts

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { typeToReviewTokens } from "./utils/typeToReviewTokens";
1313
import { processGenericArgs, processGenerics } from "./utils/processGenerics";
1414
import { getAPIJson } from "../main";
15+
import { lineIdMap } from "../utils/lineIdUtils";
1516

1617
// Interface for the result of processing implementations
1718
export interface ImplProcessResult {
@@ -60,7 +61,7 @@ export function processAutoTraitImpls(impls: number[]): ReviewToken[] {
6061
}
6162

6263
// Process manually implemented trait implementations
63-
function processOtherTraitImpls(impls: number[]): ReviewLine[] {
64+
function processOtherTraitImpls(impls: number[], prefixId: string): ReviewLine[] {
6465
const apiJson = getAPIJson();
6566
const traitImpls = getFilteredImpls(impls, isManualTraitImpl);
6667
return traitImpls.flatMap((implItem) => {
@@ -69,33 +70,35 @@ function processOtherTraitImpls(impls: number[]): ReviewLine[] {
6970
// Get the type that the trait is implemented for
7071
const parentName = typeToReviewTokens(implItem.inner.impl.for);
7172

72-
const implId = implItem.id.toString();
73+
const lineId = implItem.id.toString() + "_" + prefixId;
7374
// Process provided trait methods that are mentioned but not explicitly implemented
7475
const providedTraitMethods: ReviewLine[] = implItem.inner.impl.provided_trait_methods.map(
75-
(method) => ({
76-
LineId: implId + "_impl_" + method,
77-
Tokens: [
78-
{ Kind: TokenKind.Keyword, Value: "pub" },
79-
{ Kind: TokenKind.Keyword, Value: "fn" },
80-
{
81-
Kind: TokenKind.MemberName,
82-
Value: method,
83-
RenderClasses: ["method"],
84-
HasSuffixSpace: false,
85-
},
86-
{ Kind: TokenKind.Punctuation, Value: ";" },
87-
{
88-
Kind: TokenKind.Comment,
89-
Value: "// provided trait method",
90-
HasSuffixSpace: false,
91-
},
92-
],
93-
}),
76+
(method) => {
77+
return {
78+
Tokens: [
79+
{ Kind: TokenKind.Keyword, Value: "pub" },
80+
{ Kind: TokenKind.Keyword, Value: "fn" },
81+
{
82+
Kind: TokenKind.MemberName,
83+
Value: method,
84+
RenderClasses: ["method"],
85+
HasSuffixSpace: false,
86+
},
87+
{ Kind: TokenKind.Punctuation, Value: ";" },
88+
{
89+
Kind: TokenKind.Comment,
90+
Value: "// provided trait method",
91+
HasSuffixSpace: false,
92+
},
93+
],
94+
RelatedToLine: lineId,
95+
};
96+
},
9497
);
9598
const implGenerics = processGenerics(implItem.inner.impl.generics);
9699
// Create the main impl line with trait name and type
97100
const reviewLineForImpl: ReviewLine = {
98-
LineId: implId + "_impl",
101+
LineId: lineId,
99102
Tokens: [
100103
{
101104
Kind: TokenKind.Keyword,
@@ -108,7 +111,7 @@ function processOtherTraitImpls(impls: number[]): ReviewLine[] {
108111
Value: (implItem.inner.impl.is_negative ? "!" : "") + implItem.inner.impl.trait.name,
109112
HasPrefixSpace: true,
110113
HasSuffixSpace: false,
111-
NavigateToId: implId + "_impl",
114+
NavigateToId: lineId,
112115
// Create navigation display name by combining trait and type names
113116
NavigationDisplayName:
114117
implItem.inner.impl.trait.name + "_" + parentName.map((token) => token.Value).join(""),
@@ -132,8 +135,16 @@ function processOtherTraitImpls(impls: number[]): ReviewLine[] {
132135
],
133136
};
134137

138+
lineIdMap.set(
139+
lineId,
140+
prefixId +
141+
reviewLineForImpl.Tokens.map((token) => token.Value)
142+
.join("_")
143+
.replace(/[^a-zA-Z0-9]+/g, ""),
144+
);
145+
135146
const closingLine: ReviewLine = {
136-
RelatedToLine: implItem.id.toString() + "_impl",
147+
RelatedToLine: lineId,
137148
Tokens: [{ Kind: TokenKind.Punctuation, Value: "}" }],
138149
};
139150

@@ -166,6 +177,8 @@ export function processImpl(
166177
| (Item & { inner: { enum: Enum } })
167178
| (Item & { inner: { union: Union } }),
168179
): ImplProcessResult {
180+
const linedId = item.id.toString() + "_impl";
181+
lineIdMap.set(linedId, lineIdMap.get(item.id.toString()) + "_impl");
169182
// Get all implementations associated with this item
170183
const impls = getImplsFromItem(item);
171184

@@ -176,35 +189,35 @@ export function processImpl(
176189
// Process children first to check if they're empty
177190
const children = processImpls(impls).filter((item) => item != null);
178191

179-
// Only create an implBlock if there are children
180-
const implBlock: ReviewLine[] =
181-
children.length === 0
182-
? [] // Empty implBlock if no children
183-
: [
184-
// Create the main implementation line with type name
185-
{
186-
LineId: item.id.toString() + "_impl",
187-
Tokens: [
188-
{ Kind: TokenKind.Keyword, Value: "impl" },
189-
{
190-
Kind: TokenKind.MemberName,
191-
Value: item.name || "null",
192-
RenderClasses: ["interface"],
193-
NavigateToId: item.id.toString() + "_impl",
194-
NavigationDisplayName: item.name || undefined,
195-
},
196-
{ Kind: TokenKind.Punctuation, Value: "{" },
197-
],
198-
Children: children,
199-
},
192+
let implBlock: ReviewLine[] = [];
193+
if (children.length > 0) {
194+
// Only create an implBlock if there are children
195+
implBlock = [
196+
// Create the main implementation line with type name
197+
{
198+
LineId: linedId,
199+
Tokens: [
200+
{ Kind: TokenKind.Keyword, Value: "impl" },
200201
{
201-
RelatedToLine: item.id.toString() + "_impl",
202-
Tokens: [{ Kind: TokenKind.Punctuation, Value: "}" }],
202+
Kind: TokenKind.MemberName,
203+
Value: item.name || "unknown_impl",
204+
RenderClasses: ["interface"],
205+
NavigateToId: linedId,
206+
NavigationDisplayName: item.name || undefined,
203207
},
204-
];
208+
{ Kind: TokenKind.Punctuation, Value: "{" },
209+
],
210+
Children: children,
211+
},
212+
{
213+
RelatedToLine: linedId,
214+
Tokens: [{ Kind: TokenKind.Punctuation, Value: "}" }],
215+
},
216+
];
217+
}
205218

206219
// Process manual trait implementations (like impl Trait for Type { ... })
207-
const traitImpls = processOtherTraitImpls(impls);
220+
const traitImpls = processOtherTraitImpls(impls, item.name);
208221

209222
return { deriveTokens, implBlock, traitImpls };
210223
}

0 commit comments

Comments
 (0)