Skip to content

API: coarse-grained checker queries for whole-program extraction tools - #65351

Open
WinterYukky (WinterYukky) wants to merge 3 commits into
microsoft:mainfrom
WinterYukky:api/coarse-checker-queries
Open

WinterYukky (WinterYukky) wants to merge 3 commits into
microsoft:mainfrom
WinterYukky:api/coarse-checker-queries

Conversation

@WinterYukky

Copy link
Copy Markdown
Contributor

Fixes #65051

Summary

Tools that walk a whole program through the API ask the checker, for every type and every member, for its properties, declared type, declaring type and JSDoc as separate round trips, so their running time is bounded by the message count rather than by checker work. On aws-cdk-lib, jsii issues about 700k requests while the server spends about 6 s computing types, and most of the members it asks about are inspected and then discarded. This PR adds two methods that return the same information in one round trip. getMembersOfType takes a filter so that no type is materialised for a member the caller would drop. The handles returned are the same ones the existing methods return, and jsii's .jsii output is byte-identical between the two paths.

What changes

On the Go side, proto.go gains the two methods and the MemberFilter / TypeMember types, and session.go gains their handlers. Filter predicates are evaluated cheapest first, and an invalid excludeNamePattern is reported as a client error. owner is returned as a SymbolResponse, so on the client it is the same Symbol object the caller already holds.

On the client side, async/api.ts gains the two methods; sync/api.ts and proto.generated.ts are regenerated. Returned symbols and types go through the registry, so they are identical to the objects returned by the existing methods. No existing method or type changes.

Filter options

option meaning
visibility visibilities to include; ECMAScript private names count as private
kinds member kinds to include (property, accessor, method, parameterProperty)
excludeJsDocTags drop members whose JSDoc carries any of these tags
excludeNamePattern drop members whose name matches this RE2 pattern
includeInheritedFrom omitted: all inherited members; {}: own members only; { declaringTypes }: members declared by the given types
includeDeclaredType attach each member's declared type

With no filter, getMembersOfType returns the same symbols in the same order as getPropertiesOfType.

Tests

Go unit tests cover predicate combinations, kinds classification, the empty filter against getPropertiesOfType, invalid patterns, and getSymbolDocumentations against the individual methods element by element. A differential test runs the existing fine-grained queries and getMembersOfType over the same program (inheritance with overrides, overloads, accessors, all three visibilities plus ECMAScript private names, members excluded by JSDoc tag and by name pattern) and compares handles, declared types, documentation and ordering for every member. Client-side API tests are added as well. hereby test, test:api, lint and check:format pass.

Numbers

Same fork build, c7i.4xlarge, aws-cdk-lib benchmark, median of 3 runs, .jsii output byte-identical between the two paths:

path wall requests transport wait bytes sent
fine-grained (today's methods) 34.5 s 698k 8.6 s 131 MB
getSymbolDocumentations + getMembersOfType 29.4 s 304k 5.4 s 52 MB

A current aws-cdk-lib checkout built from source (2 runs): 43.2 / 43.1 s → 37.8 / 38.2 s, requests 783k → 340k, bytes sent 168 MB → 65 MB, server time unchanged.

Copilot AI balanced review requested due to automatic review settings November 6, 2026 01:53
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Nov 6, 2026
@typescript-automation typescript-automation Bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Nov 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The implementation drops valid declarationless members even without a filter, with additional unresolved errors in member classification, visibility, pattern handling, and optional-field typing.

6 open issues
What changed in this PR

Adds coarse-grained checker queries for whole-program extraction tools, addressing #65051 by reducing per-member API round trips.

Changes:

  • Adds bulk symbol documentation and filtered member queries to the Go API.
  • Exposes both methods through async, sync, and generator clients, using the existing object registry.
  • Adds predicate, differential, identity, and client-parity tests.
File Description
tsc/​internal/​api/​session.go Implements request handlers, member filtering, and metadata collection.
tsc/​internal/​api/​session_members_test.go Tests filters and bulk documentation equivalence.
tsc/​internal/​api/​session_members_differential_test.go Compares member results with fine-grained queries.
tsc/​internal/​api/​proto.go Defines protocol methods, filters, and response types.
packages/​typescript/​test/​sync/​api.test.ts Tests synchronous queries and object identity.
packages/​typescript/​test/​sync/​api-generators.test.ts Adds generator-batching parity checks.
packages/​typescript/​test/​async/​api.test.ts Tests asynchronous queries and object identity.
packages/​typescript/​src/​api/​sync/​api.ts Adds generated sync and generator interfaces.
packages/​typescript/​src/​api/​proto.generated.ts Adds generated protocol declarations.
packages/​typescript/​src/​api/​async/​api.ts Implements public async methods and result conversion.

Review effort: Balanced

Comment thread tsc/internal/api/session.go Outdated
Comment on lines +5072 to +5073
if len(decls) == 0 {
continue
Comment on lines +2305 to +2317
visibility?: readonly ("public" | "protected" | "private")[];
/** Member kinds to include; empty means all. */
kinds?: readonly ("property" | "accessor" | "method" | "parameterProperty")[];
/** Drop members whose JSDoc carries any of these tags. */
excludeJsDocTags?: readonly string[];
/** Drop members whose (wire-form) name matches this RE2 pattern. */
excludeNamePattern?: string;
/**
* Which declaring types to take members from: `undefined` means all, `{}` means the
* queried type only, `{ declaringTypes }` adds members declared on those types.
*/
includeInheritedFrom?: { all?: boolean; declaringTypes?: readonly Symbol[]; };
includeDeclaredType?: boolean;
Comment thread tsc/internal/api/session.go Outdated
excludedTags[tag] = struct{}{}
}
var excludeName *regexp.Regexp
if filter.ExcludeNamePattern != "" {
Comment thread tsc/internal/api/session.go Outdated
}
decl := decls[0]
if len(visibilities) > 0 {
if _, ok := visibilities[memberVisibility(decl)]; !ok {
Comment thread tsc/internal/api/session.go Outdated
}
member := &MemberResponse{
Symbol: setup.newSymbolResponse(prop),
IsMethod: prop.Flags()&ast.SymbolFlagsMethod != 0,
Comment thread tsc/internal/api/session.go Outdated
switch {
case decl.Kind == ast.KindParameter:
return "parameterProperty"
case ast.IsAccessor(decl):
@jakebailey Jake Bailey (jakebailey) added the Domain: Performance Reports of unusually slow behavior label Nov 6, 2026
…cker queries for traversal tools

Tools that walk a whole program (binding generators, documentation
generators, API extractors) ask the checker, for every member of every
type, for its documentation and its type, and skip most members after
looking at them. Over the synchronous API that is several round trips per
member, and the server materializes a type for members the caller then
discards.

getSymbolDocumentations returns the JSDoc tags and documentation comment of
many symbols in one request, element for element equal to getJsDocTags and
getDocumentationComment.

getMembersOfType returns the properties of a type that pass a filter, each
with its documentation, the symbol of the type declaring it and, on request,
its declared type. The filter is a set of neutral predicates (visibility,
member kind, excluded JSDoc tags, an excluded name pattern, which declaring
types to take members from) that the caller combines to describe the members
it will render; a tool's own policy stays in the tool. Members come back in
getPropertiesOfType order with the same symbol and type handles.

The Go tests include a differential fixture: a class hierarchy with
overrides, overloads, accessors, every visibility including an ECMAScript
private name, a parameter property and excluded members, checked member by
member against what the fine-grained path (getPropertiesOfType +
getTypeOfSymbolAtLocation + getJsDocTags + getDocumentationComment) reports.
The client tests cover the two methods through the API and in the
generator batching parity suite.

Refs: microsoft#65051
@remote-swe
remote-swe Bot force-pushed the api/coarse-checker-queries branch from bad7efe to 3fcd4c6 Compare November 6, 2026 02:28
…er than their first declaration

Review of the first version found six gaps, all in how a member's metadata
was derived from its first declaration:

- Members without a declaration (tuple elements, a tuple's `length`) were
  dropped even with no filter, so the result diverged from
  getPropertiesOfType. They are kept, classified from their symbol flags,
  have no owner, and their declared type comes from getTypeOfSymbol, which
  needs no location; the declared type of every member is now obtained that
  way.
- Visibility is read with GetDeclarationModifierFlagsFromSymbol, so a
  getter/setter pair is classified by the getter whatever the declaration
  order, and a synthetic union or intersection member by its constituents.
  ECMAScript private names still count as private.
- isMethod is derived from the same classification as the `method` kind, so
  the synthetic member of `{ m(): number } | { m(): string }`, which carries
  no method flag, is a method and gets no declared type.
- Auto-accessors (`accessor x = 0`) are accessors, like explicit getters and
  setters.
- excludeNamePattern distinguishes absent from empty: a pattern that is
  passed is always compiled, and the empty pattern matches every member.
- MemberFilterInput accepts an explicit `undefined` for every optional field,
  nested ones included, under exactOptionalPropertyTypes.

Each gap has a test that failed before the change.

Refs: microsoft#65351

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Domain: Performance Reports of unusually slow behavior For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

API: coarse-grained checker queries for tools that extract a whole API surface (getSymbolDocumentations, getMembersOfType)

3 participants