JS: Add public API

This commit is contained in:
Asger F
2025-06-04 12:27:07 +02:00
parent 2a0c7c8801
commit b82e84930c
4 changed files with 202 additions and 0 deletions

View File

@@ -5,6 +5,8 @@
import javascript import javascript
private import internal.StmtContainers private import internal.StmtContainers
private import semmle.javascript.internal.CachedStages private import semmle.javascript.internal.CachedStages
private import semmle.javascript.internal.TypeResolution
private import semmle.javascript.internal.BindingInfo
/** /**
* A program element corresponding to JavaScript code, such as an expression * A program element corresponding to JavaScript code, such as an expression
@@ -472,5 +474,22 @@ module AST {
/** Gets the data flow node associated with this program element. */ /** Gets the data flow node associated with this program element. */
DataFlow::ValueNode flow() { result = DataFlow::valueNode(this) } DataFlow::ValueNode flow() { result = DataFlow::valueNode(this) }
/**
* Gets information about the results of name-resolution for this expression.
*
* This can be used to map an expression to the class it refers to, or
* associate it with a named value coming from an dependency.
*/
ExprNameBindingNode getNameBinding() { result = this }
/**
* Gets information about the type of this expression.
*
* This can be used to map an expression to the classes it may be an instance of
* (according to the type system), or to associate it with a named type coming
* from a dependency.
*/
TypeNameBindingNode getTypeBinding() { TypeResolution::valueHasType(this, result) }
} }
} }

View File

@@ -6,11 +6,20 @@ import javascript
private import internal.StmtContainers private import internal.StmtContainers
private import internal.NameResolution private import internal.NameResolution
private import internal.UnderlyingTypes private import internal.UnderlyingTypes
private import internal.BindingInfo
/** /**
* A type annotation, either in the form of a TypeScript type or a JSDoc comment. * A type annotation, either in the form of a TypeScript type or a JSDoc comment.
*/ */
class TypeAnnotation extends @type_annotation, NodeInStmtContainer { class TypeAnnotation extends @type_annotation, NodeInStmtContainer {
/**
* Gets information about the results of name-resolution for this type.
*
* This can be used to map a type name to the class/interface it refers to, or
* associate it with a named type coming from an dependency.
*/
TypeNameBindingNode getTypeBinding() { result = this }
/** Holds if this is the `any` type. */ /** Holds if this is the `any` type. */
predicate isAny() { none() } predicate isAny() { none() }

View File

@@ -0,0 +1,159 @@
/**
* Provides a limited public interface to name/type resolution information.
*/
private import javascript
private import semmle.javascript.internal.NameResolution
private import semmle.javascript.internal.TypeResolution
private import semmle.javascript.internal.UnderlyingTypes
/**
* Interface for accessing name-resolution info about type names.
*/
class TypeNameBindingNode extends NameResolution::Node {
/**
* Holds if type refers to, or is an alias for, the given type name relative to the global scope.
*
* For example:
* ```ts
* var x: Document; // hasQualifiedName("Document")
* var x: Electron; // hasQualifiedName("Electron")
* var x: Electron.BrowserWindow; // hasQualifiedName("Electron.BrowserWindow")
* ```
*/
predicate hasQualifiedName(string qualifiedName) {
NameResolution::nodeRefersToModule(this, "global", qualifiedName)
}
/**
* Holds if this refers a value exported by the given module, with the given
* qualified name. If the `qualifiedName` is empty, this refers to the module itself.
*
* For example, the type annotations below have the following name bindings:
* ```ts
* import { Request } from "express";
*
* var x: Request; // hasUnderlyingType("express", "Request")
* var x: Request | null; // no result (see hasUnderlyingType)
* var x: Request & { prop: string }; // no result (see hasUnderlyingType)
*
* interface CustomSubtype extends Request {}
*
* var x: CustomSubtype; // no result (see hasUnderlyingType)
*
* var x: typeof import("express"); // hasUnderlyingType("express", "")
* ```
*/
predicate hasQualifiedName(string moduleName, string qualifiedName) {
NameResolution::nodeRefersToModule(this, moduleName, qualifiedName)
}
/**
* Holds if this type refers to the given type exported from the given module, after
* unfolding unions and intersections, and following subtype relations.
*
* For example:
* ```ts
* import { Request } from "express";
*
* var x: Request; // hasUnderlyingType("express", "Request")
* var x: Request | null; // hasUnderlyingType("express", "Request")
* var x: Request & { prop: string }; // hasUnderlyingType("express", "Request")
*
* interface CustomSubtype extends Request {}
*
* var x: CustomSubtype; // hasUnderlyingType("express", "Request")
* ```
*/
predicate hasUnderlyingType(string moduleName, string qualifiedName) {
UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, qualifiedName)
}
/**
* Holds if this type refers to the given type from the global scope, after
* unfolding unions and intersections, and following subtype relations.
*
* For example:
* ```ts
* var x: Document; // hasUnderlyingType("Document")
* var x: Document | null; // hasUnderlyingType("Document")
* var x: Document & { prop: string }; // hasUnderlyingType("Document")
*
* interface CustomSubtype extends Document {}
*
* var x: CustomSubtype; // hasUnderlyingType("Document")
* ```
*/
predicate hasUnderlyingType(string qualifiedName) {
UnderlyingTypes::nodeHasUnderlyingType(this, qualifiedName)
}
/**
* Gets the declaration of the type being referenced by this name.
*
* For example:
* ```ts
* class Foo {}
*
* type T = Foo;
* var x: T; // getTypeDefinition() maps T to the class Foo above
* ```
*
* Note that this has no result for function-style classes referenced from
* a JSDoc comment.
*/
TypeDefinition getTypeDefinition() { TypeResolution::trackType(result) = this }
/**
* Gets a class that this type refers to, after unfolding unions and intersections (but not subtyping).
*
* For example, the type of `x` maps to the class `C` in each example below:
* ```ts
* class C {}
*
* var x: C;
* var x: C | null;
* var x: C & { prop: string };
* ```
*/
DataFlow::ClassNode getAnUnderlyingClass() {
UnderlyingTypes::nodeHasUnderlyingClassType(this, result)
}
}
/**
* Interface for accessing name-resolution info about expressions.
*/
class ExprNameBindingNode extends NameResolution::Node {
/**
* Holds if this refers a value exported by the given module, with the given
* qualified name. If the `qualifiedName` is empty, this refers to the module itself.
*
* For example, the type annotations below have the following name bindings:
* ```ts
* import * as f from "foo";
*
* var x = f; // hasQualifiedName(f, "")
* var x = f.x.y; // hasQualifiedName(f, "x.y")
* ```
*/
predicate hasQualifiedName(string moduleName, string qualifiedName) {
NameResolution::nodeRefersToModule(this, moduleName, qualifiedName)
}
/**
* Gets the class, or function acting as a class, referenced by this name.
*
* ```ts
* class Foo {}
* const T = Foo;
* var x = T; // getClassNode() maps T to the class Foo above
*
* function Bar() {}
* Bar.prototype.blah = function() {};
* const S = Bar;
* var x = S; // getClassNode() maps S to the function Bar above
* ```
*/
DataFlow::ClassNode getClassNode() { NameResolution::nodeRefersToClass(this, result) }
}

View File

@@ -519,4 +519,19 @@ module NameResolution {
cls.flowsTo(AccessPath::getAnAssignmentTo(name)) and cls.flowsTo(AccessPath::getAnAssignmentTo(name)) and
not cls.getTopLevel().isExterns() // don't propagate externs classes not cls.getTopLevel().isExterns() // don't propagate externs classes
} }
/**
* Holds if `node` refers to the given class.
*/
pragma[nomagic]
predicate nodeRefersToClass(Node node, DataFlow::ClassNode cls) {
exists(string name |
classHasGlobalName(cls, name) and
nodeRefersToModule(node, "global", name)
)
or
trackClassValue(cls.getAstNode()) = node
or
trackFunctionValue(cls.getAstNode()) = node
}
} }