Skip to content

feat(api-reference): show method signatures, constructors and obsolete - #21

Merged
kdinev merged 11 commits into
mainfrom
ibarakov/api-method-signatures
Nov 12, 2026
Merged

kdinev merged 11 commits into
mainfrom
ibarakov/api-method-signatures

Conversation

@onlyexeption

@onlyexeption onlyexeption commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator
  • Methods and constructors now have full C# signatures and parameter descriptions (from reflection)
  • Obsolete members and types are flagged, with the replacement to use
  • Types and members hidden from IntelliSense are removed
  • Fixed gaps in doc text (<paramref>, <see langword>, <see cref>)
  • Agent rerun: no more wrong-overload build errors, much less DLL digging

@onlyexeption onlyexeption changed the title feat(api-reference): method signatures, constructors and obsolete not… feat(api-reference): show method signatures, constructors and obsolete Nov 5, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟡 scripts/build-api.ts — An override or new member that a subtype marks [Obsolete] or [EditorBrowsable(Never)] is listed again as a current member inherited from the base. knownProps, knownEvents and knownMethods are built only from the subtype's current lists. Its obsolete and hidden members are not in them, so the base's same-named or same-overload member is copied in with declaredOn. Fix: when walking the chain, seed the known sets with each level's obsolete names and overload keys, and track hidden ones too, so a re-declared member blocks its base version. Base obsolete entries should likewise be skipped when the overload is already in knownMethods. [also at: scripts/build-api.ts:415 - A subtype member lookup can now list a method as current and also as ⚠ OBSOLETE inherited, with the same signature.]

    Why this was flagged

    current (build-api.ts:310-315) removes a type's own obsolete and hidden members from properties/events/methods, and hidden ones are not recorded anywhere. enrichWithInheritance (build-api.ts:381-386) then fills the known sets only from those filtered lists. Take a subtype that overrides Foo() and marks the override [Obsolete], or re-declares a property with [EditorBrowsable(Never)] / [Obsolete]. The base's Foo() or property passes the !knownMethods.has(overloadKey(m)) / !knownProps.has(p.name) checks at 397-403 and is added as a current inherited member. The member lookup in ComponentTools.cs then shows it as usable, next to an "⚠ OBSOLETE" line for the same member. On the base branch there was no obsolete filtering, so the subtype's own entry blocked the base copy. The reverse case is also unguarded: the obsolete-inheritance loop at 405-410 does not check knownMethods, so a base obsolete method that the subtype overrides as current appears both as current and as obsolete.

    Verification: In scripts/build-api.ts, current returns false when flags?.obsolete !== undefined or flags?.hidden. enrichWithInheritance seeds knownProps, knownEvents and knownMethods from those filtered lists, so the base's same-named or same-overload member is pushed with declaredOn: base. On the base branch these members were already listed as current.

Comment thread scripts/build-api.ts Resolved
Comment thread scripts/build-api.ts Resolved
Comment thread scripts/build-api.ts Resolved
@onlyexeption

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment thread scripts/build-api.ts
Comment on lines +416 to +420
for (const o of (baseEntry.obsolete ?? []).filter(o => !o.declaredOn && o.kind !== 'constructor' &&
!knownObsolete.has(`${o.kind}|${o.signature ?? o.name}`))) {
if ((o.kind === 'property' && knownProps.has(o.name)) || (o.kind === 'event' && knownEvents.has(o.name))) continue;
(entry.obsolete ??= []).push({ ...o, declaredOn: base });
knownObsolete.add(`${o.kind}|${o.signature ?? o.name}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit (optional): A member lookup can list a method as current and also show an inherited "⚠ OBSOLETE" note for that same overload. The obsolete-inheritance loop skips a base's obsolete property or event when the subtype already lists that name. It has no such check for methods. A subtype that overrides an [Obsolete] base method without repeating the attribute therefore gets both entries. Fix: also skip a base's obsolete method when the subtype's knownMethods holds its overload, for example by keeping the overload key or doc-ID tail on the ObsoleteMember the way methods keep overloadKeys.

Why this was flagged

The trigger is a base type with an [Obsolete] virtual method and a subtype that overrides it without [Obsolete]. C# only warns about this (CS0672). Reflection does not carry the attribute onto the override, so the extractor gives the override no obsolete flag. build-api then lists it in the subtype's methods as current. In enrichWithInheritance (build-api.ts:416-420), properties are guarded by o.kind === 'property' && knownProps.has(o.name) and events by knownEvents. Methods are only checked against knownObsolete, so the base's ObsoleteMember is copied onto the subtype with declaredOn: base. ComponentTools.cs:286-291 then prints "- ⚠ OBSOLETE method void Foo(...) (inherited from Base)", and the Methods section still lists the same overload as current. The agent gets contradictory guidance. On the base branch there were no obsolete notes, so this mix is new.

Verification: nit. Trigger: an Infragistics base type has an [Obsolete] virtual method and a subtype overrides it without repeating [Obsolete]. C# only warns about this (CS0672).

Mechanism, verified:

  • The extractor reads attributes with m.GetCustomAttributesData() (Program.cs:553-555). That returns only attributes declared on the member itself, so the override gets no obsolete entry.
  • In build-api.ts the override passes current (line 566) and goes into the subtype's methods (line 589). It is not in hiddenOrObsolete/redeclared (lines 619-626), so block(entry) adds nothing for it.

Comment thread scripts/build-api.ts
return true;
};
// Constructors: public ones that take parameters. A parameterless one goes without saying, and XAML needs it anyway.
const isUsefulCtor = (m: RawMember) => m.fullName.includes('(') && !!meta(m)?.sig && !meta(m)!.sig!.startsWith('protected ');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) Agents may be shown a public constructor on an abstract type they can never instantiate, so following the new Constructors list fails with CS0144. isUsefulCtor only drops constructors whose signature starts with "protected ". MethodSignature (Program.cs:564) writes that prefix only when !m.IsPublic, and C# allows a public constructor on an abstract class. Fix: the extractor should treat a constructor of an abstract type (m.DeclaringType.IsAbstract) as protected, either by prefixing "protected " or by leaving it out of __members, so build-api drops it. ApiEntry has no abstract flag, so the page cannot otherwise warn.

Why this was flagged

The trigger is an Infragistics abstract class that declares a public constructor with parameters. The C# compiler accepts this and emits the constructor as public; analyzers such as CA1012 only warn. In Program.cs:195-203, IsUsable(m) is true for that constructor, and MethodSignature (Program.cs:564) gives it no "protected " modifier. In build-api.ts:571, isUsefulCtor accepts it because the sig has no "protected " prefix, so it is written to the page's constructors. ComponentTools.cs:145 then renders it under "### Constructors" as a plain Foo(int x) line. Nothing in ApiEntry (Models.cs) marks the type as abstract. An agent that follows the listing and writes new Foo(x) gets error CS0144. On the base branch no constructors were listed at all, so nothing invited this call. I could not count how many abstract Infragistics types have public constructors with parameters, because the DLLs are not in this checkout.

Verification: This happens when a shipped Infragistics abstract class declares a public constructor that takes parameters. scripts/type-extractor/Program.cs:564 adds the "protected " prefix only for non-public members and never looks at DeclaringType.IsAbstract. scripts/build-api.ts:571 filters only on that prefix, so the constructor reaches the page's constructors. Before this change there was no constructor list at all.

@kdinev
kdinev merged commit f678652 into main Nov 12, 2026
7 checks passed
@kdinev
kdinev deleted the ibarakov/api-method-signatures branch November 12, 2026 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants