Skip to content

JS: QL-side type/name resolution for TypeScript and JSDoc #19078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 41 commits into
base: main
Choose a base branch
from

Conversation

asgerf
Copy link
Contributor

@asgerf asgerf commented Mar 20, 2025

Prepares for disabling TypeScript type extraction by recovering the relevant type information on the QL side.

Actually disabling type extraction will happen in a separate PR so it's easier to validate in isolation and rollback if needed.

The code is divided into three main components:

  • Name resolution: resolve a type name or variable to its definition; looking through imports and (re-)exports.
  • Type resolution: determine the type of an expression. Here we use a TypeExpr as a placeholder for an actual type which is good enough for our needs. Does not support generics.
  • Underlying types: Determine the 'underlying types' of a type. The concept of "underlying types" is meant to provide library models a simple way of reasoning about named types without having to deal with unions, intersections, and subtyping relationship. For example (Request & { x: T }) | null has Request as an underlying type.

One of the complexities of TypeScript is the fact that there are three "declaration spaces" in which variables can exist: value, type, and namespace. For example, for a declaration like class C {}, the value C refers to the class itself (i.e. its constructor), whereas the type C describes an instance of the class (not the class itself). In practice, it seems that values and namespaces can be merged without problems so I decided to simplify things by doing that.

Effects on call graph

  • Disabling type extraction on main would lose us 40k call edges on the default benchmark suite.
  • The new QL-based implementation recovers 98% of those edges, only 746 edges were not recovered.
  • On top of that, it discovers 30k new call edges that were missed in the original extractor-based implementation.
    • One of reasons for this is better support for JSDoc type annotations in .js files. The new solution has unified support for the two kinds of type annotations, which was not possible previously since we didn't want to run the TypeScript compiler on .js files.
    • Another reason is better tolerance for missing dependencies, as something like KnownClass | UnresolvedClass will always propagate information about KnownClass even if UnresolvedClass could not be resolved.

@github-actions github-actions bot added the JS label Mar 20, 2025
@asgerf asgerf force-pushed the js/name-resolution branch from cee75ae to fa3e5ed Compare March 20, 2025 15:31
}

/** Helps track flow from a particular set of source nodes. */
module Track<nodeSig/1 isSource> {

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.
@asgerf asgerf force-pushed the js/name-resolution branch 2 times, most recently from fe9b23d to c07cc6e Compare March 27, 2025 20:20
@asgerf asgerf force-pushed the js/name-resolution branch from 2d928f2 to 3b395af Compare April 3, 2025 09:22
@asgerf asgerf force-pushed the js/name-resolution branch from 3b395af to d92247c Compare April 11, 2025 11:37
@asgerf asgerf force-pushed the js/name-resolution branch from 87454f7 to f289592 Compare May 2, 2025 11:43
@asgerf asgerf force-pushed the js/name-resolution branch 4 times, most recently from 45b09df to ae0aeb9 Compare May 19, 2025 10:00
asgerf added 18 commits May 20, 2025 13:20
Overload resolution has little impact on data flow analysis, because there we care about the concrete implementation of the function, which is the same for all overloads. It can affect the return type, which in turn can affect the call graph we generate, but we'll just have to accept this as overload resolution is too hard without negative recursion.
…nt declarations

This test enforced the opinion that ambient declarations should have no impact on data flow, which is no longer the case. For now I'm just updating the test output.
@asgerf asgerf force-pushed the js/name-resolution branch from aab67e7 to 2b208d6 Compare May 20, 2025 13:57
@asgerf asgerf force-pushed the js/name-resolution branch from 2b208d6 to d644f80 Compare May 20, 2025 14:21
@asgerf asgerf added the no-change-note-required This PR does not need a change note label May 22, 2025
@asgerf asgerf marked this pull request as ready for review May 22, 2025 09:54
@asgerf asgerf requested a review from a team as a code owner May 22, 2025 09:54
Copy link
Contributor

@Napalys Napalys left a comment

Choose a reason for hiding this comment

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

Extraordinary... 👏

then result = a
else (
(if a = "" or b = "" then result = a + b else result = a + "." + b) and
result.length() < 100
Copy link
Contributor

Choose a reason for hiding this comment

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

Why a 100 limit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's to avoid infinite recursion on pathological examples. In practice these access paths aren't very long.

Co-authored-by: Napalys Klicius <napalys@github.com>
Copy link
Contributor

@erik-krogh erik-krogh left a comment

Choose a reason for hiding this comment

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

I still haven't looked closely at the three big commits, but here are some small comments.

Comment on lines +53 to +55
or
method instanceof GetterMethodDeclaration and
memberType = method.getBody().getReturnTypeAnnotation()
Copy link
Contributor

Choose a reason for hiding this comment

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

This change to have some special handling of getter methods seems fine, but it also seems disconnected from the rest of the commit? (And from the commit-message "Hide shadowed inherited members").

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, I seem to have omitted this in the commit message. I was fixing some issues found in a DCA run and it seems two independent fixes ended up in the same commit. I'm not sure it makes sense to go back and change the commit history though.

@@ -333,7 +333,13 @@ module SourceNode {
astNode instanceof TaggedTemplateExpr or
astNode instanceof Templating::PipeRefExpr or
astNode instanceof Templating::TemplateVarRefExpr or
astNode instanceof StringLiteral
astNode instanceof StringLiteral or
astNode instanceof TypeAssertion
Copy link
Contributor

Choose a reason for hiding this comment

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

What about a SatisfiesExpr?

Also I don't see a test of TypeAssertion as part of this commit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a test and a case for SatisfiesExpr 👍

Comment on lines +186 to +190
exists(MethodDeclaration member |
not member instanceof ConstructorDeclaration and
node = member.getBody() and
result = member.getNameExpr()
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we have a case for FunctionDeclStmt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS no-change-note-required This PR does not need a change note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy