Skip to content

Commit b5872fe

Browse files
authored
Merge pull request #3873 from asger-semmle/js/type-qualified-name-fallback
Approved by erik-krogh
2 parents 2a70da4 + 961554e commit b5872fe

File tree

8 files changed

+113
-0
lines changed

8 files changed

+113
-0
lines changed

change-notes/1.26/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@
3333

3434

3535
## Changes to libraries
36+
* The predicate `TypeAnnotation.hasQualifiedName` now works in more cases when the imported library was not present during extraction.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @name Types with qualified name
3+
* @description The number of type annotations with a qualified name
4+
* @kind metric
5+
* @metricType project
6+
* @metricAggregate sum
7+
* @tags meta
8+
* @id js/meta/types-with-qualified-name
9+
*/
10+
11+
import javascript
12+
import meta.MetaMetrics
13+
14+
select projectRoot(), count(TypeAnnotation t | t.hasQualifiedName(_) or t.hasQualifiedName(_, _))

javascript/ql/src/semmle/javascript/TypeScript.qll

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,15 @@ class LocalNamespaceName extends @local_namespace_name, LexicalName {
509509
/** Gets a use of this namespace in an export. */
510510
ExportVarAccess getAnExportAccess() { namespacebind(result, this) }
511511

512+
/**
513+
* Gets an access to a type member of this namespace alias,
514+
* such as `http.ServerRequest` where `http` is a reference to this namespace.
515+
*/
516+
QualifiedTypeAccess getAMemberAccess(string member) {
517+
result.getIdentifier().getName() = member and
518+
result.getQualifier() = this.getAnAccess()
519+
}
520+
512521
/** Gets an identifier that refers to this namespace name. */
513522
Identifier getAnAccess() { namespacebind(result, this) }
514523

@@ -663,13 +672,54 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef {
663672

664673
override predicate hasQualifiedName(string globalName) {
665674
getTypeName().hasQualifiedName(globalName)
675+
or
676+
exists(LocalTypeAccess local | local = this |
677+
not exists(local.getLocalTypeName()) and // Without a local type name, the type is looked up in the global scope.
678+
globalName = local.getName()
679+
)
666680
}
667681

668682
override predicate hasQualifiedName(string moduleName, string exportedName) {
669683
getTypeName().hasQualifiedName(moduleName, exportedName)
684+
or
685+
exists(ImportDeclaration imprt, ImportSpecifier spec |
686+
moduleName = getImportName(imprt) and
687+
spec = imprt.getASpecifier()
688+
|
689+
spec.getImportedName() = exportedName and
690+
this = spec.getLocal().(TypeDecl).getLocalTypeName().getAnAccess()
691+
or
692+
spec instanceof ImportNamespaceSpecifier and
693+
this =
694+
spec.getLocal().(LocalNamespaceDecl).getLocalNamespaceName().getAMemberAccess(exportedName)
695+
)
696+
or
697+
exists(ImportEqualsDeclaration imprt |
698+
moduleName = getImportName(imprt.getImportedEntity()) and
699+
this =
700+
imprt
701+
.getIdentifier()
702+
.(LocalNamespaceDecl)
703+
.getLocalNamespaceName()
704+
.getAMemberAccess(exportedName)
705+
)
670706
}
671707
}
672708

709+
/**
710+
* Gets a suitable name for the library imported by `import`.
711+
*
712+
* For relative imports, this is the snapshot-relative path to the imported module.
713+
* For non-relative imports, it is the import path itself.
714+
*/
715+
private string getImportName(Import imprt) {
716+
exists(string path | path = imprt.getImportedPath().getValue() |
717+
if path.regexpMatch("[./].*")
718+
then result = imprt.getImportedModule().getFile().getRelativePath()
719+
else result = path
720+
)
721+
}
722+
673723
/** An identifier that is used as part of a type, such as `Date`. */
674724
class LocalTypeAccess extends @localtypeaccess, TypeAccess, Identifier, LexicalAccess {
675725
override predicate isStringy() { getName() = "String" }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
| tst.ts:4:8:4:21 | UnresolvedType | unresolved | UnresolvedType |
12
| tst.ts:5:8:5:19 | ResolvedType | resolved | ResolvedType |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
hasQualifiedNameModule
2+
| default-import | default | tst.ts:11:9:11:21 | DefaultImport |
3+
| import-assign | Foo | tst.ts:10:9:10:15 | asn.Foo |
4+
| library-tests/TypeScript/HasQualifiedNameFallback/tst.ts | ExportedClass | relative.ts:4:8:4:20 | ExportedClass |
5+
| named-import | Name1 | tst.ts:7:9:7:13 | Name1 |
6+
| named-import | Name1 | tst.ts:13:9:13:13 | Name1 |
7+
| named-import | Name1 | tst.ts:13:9:13:21 | Name1<number> |
8+
| named-import | Name2 | tst.ts:8:9:8:13 | Name2 |
9+
| namespace-import | Foo | tst.ts:9:9:9:21 | namespace.Foo |
10+
hasQualifiedNameGlobal
11+
| UnresolvedName | tst.ts:12:9:12:22 | UnresolvedName |
12+
paramExample
13+
| tst.ts:7:5:7:6 | x1 |
14+
| tst.ts:13:5:13:6 | x8 |
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import javascript
2+
3+
query TypeAnnotation hasQualifiedNameModule(string moduleName, string member) {
4+
result.hasQualifiedName(moduleName, member)
5+
}
6+
7+
query TypeAnnotation hasQualifiedNameGlobal(string globalName) {
8+
result.hasQualifiedName(globalName)
9+
}
10+
11+
query Parameter paramExample() {
12+
result.getTypeAnnotation().hasQualifiedName("named-import", "Name1")
13+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import * as foo from "./tst";
2+
import { ExportedClass } from "./tst";
3+
4+
var x: ExportedClass;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as namespace from "namespace-import";
2+
import { Name1, Name2 } from "named-import";
3+
import DefaultImport from "default-import";
4+
import asn = require("import-assign");
5+
6+
function foo(
7+
x1: Name1,
8+
x2: Name2,
9+
x3: namespace.Foo,
10+
x5: asn.Foo,
11+
x6: DefaultImport,
12+
x7: UnresolvedName,
13+
x8: Name1<number>
14+
) {}
15+
16+
export class ExportedClass {};

0 commit comments

Comments
 (0)