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

Draft
wants to merge 57 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
eb05996
Move getAChildContainer one scope up
asgerf Apr 24, 2025
2ce01bf
Add Folder::Resolve as a generalisation of Folder::Append
asgerf Apr 24, 2025
ec9d15b
JS: Make shared Folder module visible
asgerf Apr 24, 2025
8c0b0c4
JS: Ensure json files are extracted properly in tests
asgerf Apr 24, 2025
359525b
JS: Extract more tsconfig.json patterns
asgerf Apr 29, 2025
565cb43
JS: Add test
asgerf Apr 24, 2025
17aa522
JS: Add some helpers
asgerf Apr 24, 2025
ef32a03
JS: Extract from methods from PathString into a non-abstract base class
asgerf Apr 28, 2025
59e1cbc
JS: Add tsconfig class
asgerf Apr 24, 2025
bb91df8
JS: Add helper for doing path resolution with JS rules
asgerf Apr 24, 2025
f542956
JS: Add internal extension of PackageJson class
asgerf Apr 24, 2025
ed4864e
JS: Add two more helpers to FilePath class
asgerf Apr 28, 2025
6725cb5
JS: Implement import resolution
asgerf Apr 29, 2025
e4420f6
JS: Move babel-root-import test
asgerf Apr 28, 2025
d724874
JS: Implement babel-plugin-root-import as a PathMapping
asgerf Apr 28, 2025
a195d07
JS: Resolve Angular2 templateUrl with ResolveExpr instead of PathExpr
asgerf Apr 28, 2025
c293f03
JS: Remove a dependency on getImportedPath()
asgerf Apr 28, 2025
fe055ad
JS: Use PackageJsonEx instead of resolveMainModule
asgerf Apr 28, 2025
ed2a832
JS: Deprecate PathExpr and related classes
asgerf Apr 29, 2025
be5de9c
JS: Update test output
asgerf Apr 29, 2025
5de2c93
JS: Rename getTargetFile to getImportedFile and remove its deprecated…
asgerf Apr 29, 2025
70a5ec5
JS: Add package.json files in tests relying on node_modules
asgerf Apr 29, 2025
b0f73f1
JS: Update test output now that we import .d.ts files more liberally
asgerf Apr 29, 2025
f3e0cfd
Apply suggestions from code review
asgerf May 2, 2025
5c9218f
JS: Add comment about 'path' heuristic
asgerf May 2, 2025
1f308ee
JS: Explain use of monotonicAggregates
asgerf May 2, 2025
52eed26
Add meta query
asgerf Mar 11, 2025
03b3b93
Disable noisy meta query
asgerf Mar 19, 2025
db0bbd8
JS: Exclude externs from CallGraph meta-query
asgerf Apr 11, 2025
0b040fe
JS: Add ImportSpecifier.getImportDeclaration()
asgerf Mar 11, 2025
479297d
JS: Do not ignore variables from ambient declarations
asgerf Apr 11, 2025
3c9dca4
JS: Make Closure concepts based on AST instead
asgerf Apr 11, 2025
23b09be
JS: Avoid accidental recursion with API graphs
asgerf Apr 11, 2025
060e6b1
JS: Add helper for getting local type names
asgerf Apr 11, 2025
57811cc
JS: Resolve JSDocLocalTypeAccess to a variable in scope
asgerf Apr 11, 2025
4df4454
JS: Add test
asgerf Apr 11, 2025
5df7bc7
Create NameResolution.qll
asgerf Apr 11, 2025
0a96f2e
Create UnderlyingTypes.qll
asgerf Apr 11, 2025
1dd4d25
Create TypeResolution.qll
asgerf Apr 11, 2025
bf70508
JS: Use underlying types in DataFlow::Node
asgerf Apr 11, 2025
46884e8
JS: Use in TypeAnnotation.getClass and hasUnderlyingType predicates
asgerf Apr 11, 2025
d1af83b
JS: Update jQuery model
asgerf Apr 11, 2025
58d4856
JS: Use sanitizing primitive types in ViewComponentInput
asgerf Apr 11, 2025
7d7634d
JS: Use sanitizing primitive type in Nest model
asgerf Apr 11, 2025
5236d93
JS: Use hasUnderlyingStringOrAnyType in Nest model (TODO: refactor)
asgerf Apr 11, 2025
4bd9611
JS: Use in MissingAwait
asgerf Apr 11, 2025
f37988b
JS: Remove some dependencies on type extraction
asgerf Apr 11, 2025
68091af
JS: Some test updates
asgerf Apr 11, 2025
7d213e4
JS: Also check contextual type
asgerf Apr 14, 2025
adfc001
JS: Use type resolution for CG augmentation
asgerf Apr 22, 2025
5df9263
JS: Add call graph test with types
asgerf Apr 30, 2025
176d123
JS: Hide shadowed inherited members
asgerf Apr 30, 2025
1ec6b98
JS: Also propagate through promise types
asgerf Apr 30, 2025
70a8ba2
JS: Add failing test for assigning a non-SourceNode to a type annotat…
asgerf Apr 30, 2025
7f7fc58
JS: Mark type-annotated nodes as SourceNode
asgerf Apr 30, 2025
f289592
JS: Update to avoid deprecations after import resolution change
asgerf May 2, 2025
fbebf72
JS: Remove redundant casts
asgerf May 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ private void setupFilters() {
patterns.add("**/*.view.json"); // SAP UI5
patterns.add("**/manifest.json");
patterns.add("**/package.json");
patterns.add("**/tsconfig*.json");
patterns.add("**/*tsconfig*.json");
patterns.add("**/codeql-javascript-*.json");

// include any explicitly specified extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public Context(Label parent, int childIndex) {
private final boolean tolerateParseErrors;

public JSONExtractor(ExtractorConfig config) {
this.tolerateParseErrors = config.isTolerateParseErrors();
this.tolerateParseErrors = true;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,14 @@ public void setupMatchers(ArgsParser ap) {
// only extract HTML and JS by default
addIncludesFor(includes, FileType.HTML);
addIncludesFor(includes, FileType.JS);
includes.add("**/.babelrc*.json");


// extract TypeScript if `--typescript` or `--typescript-full` was specified
if (getTypeScriptMode(ap) != TypeScriptMode.NONE) addIncludesFor(includes, FileType.TYPESCRIPT);
if (getTypeScriptMode(ap) != TypeScriptMode.NONE) {
addIncludesFor(includes, FileType.TYPESCRIPT);
includes.add("**/*tsconfig*.json");
}

// add explicit include patterns
for (String pattern : ap.getZeroOrMore(P_INCLUDE))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,15 +426,13 @@ public Void visit(Identifier nd, Void v) {
// cases where we turn on the 'declKind' flags
@Override
public Void visit(FunctionDeclaration nd, Void v) {
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
// strict mode functions are block-scoped, non-strict mode ones aren't
if (blockscope == isStrict) visit(nd.getId(), DeclKind.var);
return null;
}

@Override
public Void visit(ClassDeclaration nd, Void c) {
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
if (blockscope) visit(nd.getClassDef().getId(), DeclKind.varAndType);
return null;
}
Expand Down Expand Up @@ -483,7 +481,6 @@ public Void visit(EnhancedForStatement nd, Void v) {

@Override
public Void visit(VariableDeclaration nd, Void v) {
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
// in block scoping mode, only process 'let'; in non-block scoping
// mode, only process non-'let'
if (blockscope == nd.isBlockScoped(ecmaVersion)) visit(nd.getDeclarations());
Expand Down Expand Up @@ -518,8 +515,7 @@ public Void visit(ClassBody nd, Void c) {
@Override
public Void visit(NamespaceDeclaration nd, Void c) {
if (blockscope) return null;
boolean isAmbientOutsideDtsFile = nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile();
boolean hasVariable = nd.isInstantiated() && !isAmbientOutsideDtsFile;
boolean hasVariable = nd.isInstantiated();
visit(nd.getName(), hasVariable ? DeclKind.varAndNamespace : DeclKind.namespace);
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion javascript/ql/examples/snippets/importfrom.ql
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
import javascript

from ImportDeclaration id
where id.getImportedPath().getValue() = "react"
where id.getImportedPathString() = "react"
select id
2 changes: 1 addition & 1 deletion javascript/ql/lib/definitions.qll
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private predicate importLookup(AstNode path, Module target, string kind) {
kind = "I" and
(
exists(Import i |
path = i.getImportedPath() and
path = i.getImportedPathExpr() and
target = i.getImportedModule()
)
or
Expand Down
28 changes: 18 additions & 10 deletions javascript/ql/lib/semmle/javascript/AMD.qll
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,14 @@ class AmdModuleDefinition extends CallExpr instanceof AmdModuleDefinition::Range
result = this.getArgument(1)
}

/** DEPRECATED. Use `getDependencyExpr` instead. */
deprecated PathExpr getDependency(int i) { result = this.getDependencyExpr(i) }

/** DEPRECATED. Use `getADependencyExpr` instead. */
deprecated PathExpr getADependency() { result = this.getADependencyExpr() }

/** Gets the `i`th dependency of this module definition. */
PathExpr getDependency(int i) {
Expr getDependencyExpr(int i) {
exists(Expr expr |
expr = this.getDependencies().getElement(i) and
not isPseudoDependency(expr.getStringValue()) and
Expand All @@ -71,8 +77,8 @@ class AmdModuleDefinition extends CallExpr instanceof AmdModuleDefinition::Range
}

/** Gets a dependency of this module definition. */
PathExpr getADependency() {
result = this.getDependency(_) or
Expr getADependencyExpr() {
result = this.getDependencyExpr(_) or
result = this.getARequireCall().getAnArgument()
}

Expand Down Expand Up @@ -233,7 +239,7 @@ private class AmdDependencyPath extends PathExprCandidate {
}

/** A constant path element appearing in an AMD dependency expression. */
private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString {
deprecated private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString {
ConstantAmdDependencyPathElement() { this = any(AmdDependencyPath amd).getAPart() }

override string getValue() { result = this.getStringValue() }
Expand Down Expand Up @@ -261,11 +267,13 @@ private predicate amdModuleTopLevel(AmdModuleDefinition def, TopLevel tl) {
* An AMD dependency, viewed as an import.
*/
private class AmdDependencyImport extends Import {
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependency() }
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependencyExpr() }

override Module getEnclosingModule() { this = result.(AmdModule).getDefine().getADependency() }
override Module getEnclosingModule() {
this = result.(AmdModule).getDefine().getADependencyExpr()
}

override PathExpr getImportedPath() { result = this }
override Expr getImportedPathExpr() { result = this }

/**
* Gets a file that looks like it might be the target of this import.
Expand All @@ -274,7 +282,7 @@ private class AmdDependencyImport extends Import {
* adding well-known JavaScript file extensions like `.js`.
*/
private File guessTarget() {
exists(PathString imported, string abspath, string dirname, string basename |
exists(FilePath imported, string abspath, string dirname, string basename |
this.targetCandidate(result, abspath, imported, dirname, basename)
|
abspath.regexpMatch(".*/\\Q" + imported + "\\E")
Expand All @@ -296,9 +304,9 @@ private class AmdDependencyImport extends Import {
* `dirname` and `basename` to the dirname and basename (respectively) of `imported`.
*/
private predicate targetCandidate(
File f, string abspath, PathString imported, string dirname, string basename
File f, string abspath, FilePath imported, string dirname, string basename
) {
imported = this.getImportedPath().getValue() and
imported = this.getImportedPathString() and
f.getStem() = imported.getStem() and
f.getAbsolutePath() = abspath and
dirname = imported.getDirName() and
Expand Down
12 changes: 5 additions & 7 deletions javascript/ql/lib/semmle/javascript/ApiGraphs.qll
Original file line number Diff line number Diff line change
Expand Up @@ -649,11 +649,13 @@ module API {
/** Gets a node corresponding to an import of module `m` without taking into account types from models. */
Node getAModuleImportRaw(string m) {
result = Impl::MkModuleImport(m) or
result = Impl::MkModuleImport(m).(Node).getMember("default")
result = Impl::MkModuleImport(m).(Node).getMember("default") or
result = Impl::MkTypeUse(m, "")
}

/** Gets a node whose type has the given qualified name, not including types from models. */
Node getANodeOfTypeRaw(string moduleName, string exportedName) {
exportedName != "" and
result = Impl::MkTypeUse(moduleName, exportedName).(Node).getInstance()
or
exportedName = "" and
Expand Down Expand Up @@ -749,18 +751,14 @@ module API {
MkModuleImport(string m) {
imports(_, m)
or
any(TypeAnnotation n).hasQualifiedName(m, _)
or
any(Type t).hasUnderlyingType(m, _)
any(TypeAnnotation n).hasUnderlyingType(m, _)
} or
MkClassInstance(DataFlow::ClassNode cls) { needsDefNode(cls) } or
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
/** A use of a TypeScript type. */
MkTypeUse(string moduleName, string exportName) {
any(TypeAnnotation n).hasQualifiedName(moduleName, exportName)
or
any(Type t).hasUnderlyingType(moduleName, exportName)
any(TypeAnnotation n).hasUnderlyingType(moduleName, exportName)
} or
MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) {
trackUseNode(src, true, bound, "").flowsTo(nd.getCalleeNode())
Expand Down
Loading
Loading