Skip to content

Commit cca48c0

Browse files
committed
JS: Use in TypeAnnotation.getClass and hasUnderlyingType predicates
1 parent b923eac commit cca48c0

File tree

12 files changed

+53
-137
lines changed

12 files changed

+53
-137
lines changed

javascript/ql/lib/semmle/javascript/JSDoc.qll

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -415,54 +415,14 @@ class JSDocNamedTypeExpr extends JSDocTypeExpr {
415415
* - `foo.bar.Baz` has prefix `foo` and suffix `.bar.Baz`.
416416
* - `Baz` has prefix `Baz` and an empty suffix.
417417
*/
418-
predicate hasNameParts(string prefix, string suffix) {
418+
deprecated predicate hasNameParts(string prefix, string suffix) {
419419
not this = any(JSDocQualifiedTypeAccess a).getBase() and // restrict size of predicate
420420
exists(string regex, string name | regex = "([^.]+)(.*)" |
421421
name = this.getRawName() and
422422
prefix = name.regexpCapture(regex, 1) and
423423
suffix = name.regexpCapture(regex, 2)
424424
)
425425
}
426-
427-
pragma[noinline]
428-
pragma[nomagic]
429-
private predicate hasNamePartsAndEnv(string prefix, string suffix, JSDoc::Environment env) {
430-
// Force join ordering
431-
this.hasNameParts(prefix, suffix) and
432-
env.isContainerInScope(this.getContainer())
433-
}
434-
435-
/**
436-
* Gets the qualified name of this name by resolving its prefix, if any.
437-
*/
438-
cached
439-
private string resolvedName() {
440-
exists(string prefix, string suffix, JSDoc::Environment env |
441-
this.hasNamePartsAndEnv(prefix, suffix, env) and
442-
result = env.resolveAlias(prefix) + suffix
443-
)
444-
}
445-
446-
override predicate hasQualifiedName(string globalName) {
447-
globalName = this.resolvedName()
448-
or
449-
not exists(this.resolvedName()) and
450-
globalName = this.getRawName()
451-
}
452-
453-
override DataFlow::ClassNode getClass() {
454-
exists(string name |
455-
this.hasQualifiedName(name) and
456-
result.hasQualifiedName(name)
457-
)
458-
or
459-
// Handle case where a local variable has a reference to the class,
460-
// but the class doesn't have a globally qualified name.
461-
exists(string alias, JSDoc::Environment env |
462-
this.hasNamePartsAndEnv(alias, "", env) and
463-
result.getAClassReference().flowsTo(env.getNodeFromAlias(alias))
464-
)
465-
}
466426
}
467427

468428
/**
@@ -491,12 +451,6 @@ class JSDocAppliedTypeExpr extends @jsdoc_applied_type_expr, JSDocTypeExpr {
491451
* For example, in `Array<string>`, `string` is the only argument type.
492452
*/
493453
JSDocTypeExpr getAnArgument() { result = this.getArgument(_) }
494-
495-
override predicate hasQualifiedName(string globalName) {
496-
this.getHead().hasQualifiedName(globalName)
497-
}
498-
499-
override DataFlow::ClassNode getClass() { result = this.getHead().getClass() }
500454
}
501455

502456
/**
@@ -516,8 +470,6 @@ class JSDocNullableTypeExpr extends @jsdoc_nullable_type_expr, JSDocTypeExpr {
516470
predicate isPrefix() { jsdoc_prefix_qualifier(this) }
517471

518472
override JSDocTypeExpr getAnUnderlyingType() { result = this.getTypeExpr().getAnUnderlyingType() }
519-
520-
override DataFlow::ClassNode getClass() { result = this.getTypeExpr().getClass() }
521473
}
522474

523475
/**
@@ -537,8 +489,6 @@ class JSDocNonNullableTypeExpr extends @jsdoc_non_nullable_type_expr, JSDocTypeE
537489
predicate isPrefix() { jsdoc_prefix_qualifier(this) }
538490

539491
override JSDocTypeExpr getAnUnderlyingType() { result = this.getTypeExpr().getAnUnderlyingType() }
540-
541-
override DataFlow::ClassNode getClass() { result = this.getTypeExpr().getClass() }
542492
}
543493

544494
/**
@@ -643,8 +593,6 @@ class JSDocOptionalParameterTypeExpr extends @jsdoc_optional_type_expr, JSDocTyp
643593
override JSDocTypeExpr getAnUnderlyingType() {
644594
result = this.getUnderlyingType().getAnUnderlyingType()
645595
}
646-
647-
override DataFlow::ClassNode getClass() { result = this.getUnderlyingType().getClass() }
648596
}
649597

650598
/**
@@ -679,7 +627,7 @@ module JSDoc {
679627
/**
680628
* A statement container which may declare JSDoc name aliases.
681629
*/
682-
class Environment extends StmtContainer {
630+
deprecated class Environment extends StmtContainer {
683631
/**
684632
* Gets the fully qualified name aliased by the given unqualified name
685633
* within this container.
@@ -729,7 +677,7 @@ module JSDoc {
729677
}
730678

731679
pragma[noinline]
732-
private predicate isTypenamePrefix(string name) {
680+
deprecated private predicate isTypenamePrefix(string name) {
733681
any(JSDocNamedTypeExpr expr).hasNameParts(name, _)
734682
}
735683
}

javascript/ql/lib/semmle/javascript/TypeAnnotations.qll

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import javascript
66
private import internal.StmtContainers
7+
private import internal.NameResolution
8+
private import internal.UnderlyingTypes
79

810
/**
911
* A type annotation, either in the form of a TypeScript type or a JSDoc comment.
@@ -75,14 +77,38 @@ class TypeAnnotation extends @type_annotation, NodeInStmtContainer {
7577
TypeAnnotation getAnUnderlyingType() { result = this }
7678

7779
/**
80+
* DEPRECATED. Use `hasUnderlyingType` instead.
81+
*
7882
* Holds if this is a reference to the type with qualified name `globalName` relative to the global scope.
7983
*/
80-
predicate hasQualifiedName(string globalName) { none() }
84+
deprecated predicate hasQualifiedName(string globalName) {
85+
UnderlyingTypes::nodeHasUnderlyingType(this, globalName)
86+
}
8187

8288
/**
89+
* DEPRECATED. Use `hasUnderlyingType` instead.
90+
*
8391
* Holds if this is a reference to the type exported from `moduleName` under the name `exportedName`.
8492
*/
85-
predicate hasQualifiedName(string moduleName, string exportedName) { none() }
93+
deprecated predicate hasQualifiedName(string moduleName, string exportedName) {
94+
UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, exportedName)
95+
}
96+
97+
/**
98+
* Holds if this is a reference to the type with qualified name `globalName` relative to the global scope,
99+
* or is declared as a subtype thereof, or is a union or intersection containing such a type.
100+
*/
101+
final predicate hasUnderlyingType(string globalName) {
102+
UnderlyingTypes::nodeHasUnderlyingType(this, globalName)
103+
}
104+
105+
/**
106+
* Holds if this is a reference to the type exported from `moduleName` under the name `exportedName`,
107+
* or is declared as a subtype thereof, or is a union or intersection containing such a type.
108+
*/
109+
final predicate hasUnderlyingType(string moduleName, string exportedName) {
110+
UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, exportedName)
111+
}
86112

87113
/** Gets the statement in which this type appears. */
88114
Stmt getEnclosingStmt() { none() }
@@ -107,5 +133,5 @@ class TypeAnnotation extends @type_annotation, NodeInStmtContainer {
107133
*
108134
* This unfolds nullability modifiers and generic type applications.
109135
*/
110-
DataFlow::ClassNode getClass() { none() }
136+
final DataFlow::ClassNode getClass() { UnderlyingTypes::nodeHasUnderlyingClassType(this, result) }
111137
}

javascript/ql/lib/semmle/javascript/TypeScript.qll

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import javascript
2+
private import semmle.javascript.internal.UnderlyingTypes
23

34
/**
45
* A statement that defines a namespace, that is, a namespace declaration or enum declaration.
@@ -575,10 +576,6 @@ class TypeExpr extends ExprOrType, @typeexpr, TypeAnnotation {
575576
override Function getEnclosingFunction() { result = ExprOrType.super.getEnclosingFunction() }
576577

577578
override TopLevel getTopLevel() { result = ExprOrType.super.getTopLevel() }
578-
579-
override DataFlow::ClassNode getClass() {
580-
result.getAstNode() = this.getType().(ClassType).getClass()
581-
}
582579
}
583580

584581
/**
@@ -698,58 +695,9 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef {
698695
*/
699696
TypeName getTypeName() { ast_node_symbol(this, result) }
700697

701-
override predicate hasQualifiedName(string globalName) {
702-
this.getTypeName().hasQualifiedName(globalName)
703-
or
704-
exists(LocalTypeAccess local | local = this |
705-
not exists(local.getLocalTypeName()) and // Without a local type name, the type is looked up in the global scope.
706-
globalName = local.getName()
707-
)
708-
}
709-
710-
override predicate hasQualifiedName(string moduleName, string exportedName) {
711-
this.getTypeName().hasQualifiedName(moduleName, exportedName)
712-
or
713-
exists(ImportDeclaration imprt, ImportSpecifier spec |
714-
moduleName = getImportName(imprt) and
715-
spec = imprt.getASpecifier()
716-
|
717-
spec.getImportedName() = exportedName and
718-
this = spec.getLocal().(TypeDecl).getLocalTypeName().getAnAccess()
719-
or
720-
(spec instanceof ImportNamespaceSpecifier or spec instanceof ImportDefaultSpecifier) and
721-
this =
722-
spec.getLocal().(LocalNamespaceDecl).getLocalNamespaceName().getAMemberAccess(exportedName)
723-
)
724-
or
725-
exists(ImportEqualsDeclaration imprt |
726-
moduleName = getImportName(imprt.getImportedEntity()) and
727-
this =
728-
imprt
729-
.getIdentifier()
730-
.(LocalNamespaceDecl)
731-
.getLocalNamespaceName()
732-
.getAMemberAccess(exportedName)
733-
)
734-
}
735-
736698
override string getAPrimaryQlClass() { result = "TypeAccess" }
737699
}
738700

739-
/**
740-
* Gets a suitable name for the library imported by `imprt`.
741-
*
742-
* For relative imports, this is the snapshot-relative path to the imported module.
743-
* For non-relative imports, it is the import path itself.
744-
*/
745-
private string getImportName(Import imprt) {
746-
exists(string path | path = imprt.getImportedPathString() |
747-
if path.regexpMatch("[./].*")
748-
then result = imprt.getImportedModule().getFile().getRelativePath()
749-
else result = path
750-
)
751-
}
752-
753701
/** An identifier that is used as part of a type, such as `Date`. */
754702
class LocalTypeAccess extends @local_type_access, TypeAccess, Identifier, LexicalAccess {
755703
override predicate isStringy() { this.getName() = "String" }
@@ -822,14 +770,6 @@ class GenericTypeExpr extends @generic_typeexpr, TypeExpr {
822770
/** Gets the number of type arguments. This is always at least one. */
823771
int getNumTypeArgument() { result = count(this.getATypeArgument()) }
824772

825-
override predicate hasQualifiedName(string globalName) {
826-
this.getTypeAccess().hasQualifiedName(globalName)
827-
}
828-
829-
override predicate hasQualifiedName(string moduleName, string exportedName) {
830-
this.getTypeAccess().hasQualifiedName(moduleName, exportedName)
831-
}
832-
833773
override string getAPrimaryQlClass() { result = "GenericTypeExpr" }
834774
}
835775

javascript/ql/lib/semmle/javascript/frameworks/Nest.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ module NestJS {
237237
CustomPipeClass() {
238238
exists(ClassDefinition cls |
239239
this = cls.flow() and
240-
cls.getASuperInterface().hasQualifiedName("@nestjs/common", "PipeTransform")
240+
cls.getASuperInterface().hasUnderlyingType("@nestjs/common", "PipeTransform")
241241
)
242242
}
243243

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
| bar.js:5:14:5:14 | x | x |
1+
| bar.js:5:14:5:14 | x | ns.very.long.namespace |
22
| bar.js:5:14:5:18 | x.Foo | ns.very.long.namespace.Foo |
3-
| bar.js:12:14:12:17 | iife | iife |
3+
| bar.js:12:14:12:17 | iife | IIFE |
44
| bar.js:12:14:12:21 | iife.Foo | IIFE.Foo |
55
| closure.js:8:12:8:15 | goog | goog |
66
| closure.js:8:12:8:19 | goog.net | goog.net |
77
| closure.js:8:12:8:28 | goog.net.SomeType | goog.net.SomeType |
8-
| closure.js:9:12:9:14 | net | net |
8+
| closure.js:9:12:9:14 | net | goog.net |
99
| closure.js:9:12:9:23 | net.SomeType | goog.net.SomeType |
1010
| closure.js:10:12:10:19 | SomeType | goog.net.SomeType |
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import javascript
22

3-
query string test_hasQualifiedName(JSDocNamedTypeExpr expr) { expr.hasQualifiedName(result) }
3+
query string test_hasUnderlyingType(JSDocNamedTypeExpr expr) { expr.hasUnderlyingType(result) }

javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.expected

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ test_isString
22
| tst.js:2:12:2:17 | string |
33
test_isNumber
44
| tst.js:3:12:3:17 | number |
5-
test_QualifiedName
5+
test_hasUnderlyingType
66
| VarType | tst.js:9:13:9:19 | VarType |
77
| boolean | tst.js:5:14:5:20 | boolean |
88
| foo | tst.js:4:12:4:14 | foo |
99
| foo.bar | tst.js:4:12:4:18 | foo.bar |
1010
| foo.bar.baz | tst.js:4:12:4:22 | foo.bar.baz |
1111
| number | tst.js:3:12:3:17 | number |
12+
| number | tst.js:3:12:3:18 | number? |
1213
| string | tst.js:2:12:2:17 | string |
1314
test_ParameterType
1415
| tst.js:7:12:7:12 | x | tst.js:2:12:2:17 | string |

javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ query TypeAnnotation test_isString() { result.isString() }
44

55
query TypeAnnotation test_isNumber() { result.isNumber() }
66

7-
query TypeAnnotation test_QualifiedName(string name) { result.hasQualifiedName(name) }
7+
query TypeAnnotation test_hasUnderlyingType(string name) { result.hasUnderlyingType(name) }
88

99
query TypeAnnotation test_ParameterType(Parameter p) { result = p.getTypeAnnotation() }
1010

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import javascript
22

33
from TypeAnnotation type, string mod, string name
4-
where type.hasQualifiedName(mod, name)
4+
where type.hasUnderlyingType(mod, name)
55
select type, mod, name

javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.expected

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
hasQualifiedNameModule
2-
| default-import | default | tst.ts:11:9:11:21 | DefaultImport |
1+
hasUnderlyingTypeModule
2+
| default-import | | tst.ts:11:9:11:21 | DefaultImport |
3+
| global | UnresolvedName | tst.ts:12:9:12:22 | UnresolvedName |
4+
| import-assign | | tst.ts:10:9:10:11 | asn |
35
| import-assign | Foo | tst.ts:10:9:10:15 | asn.Foo |
46
| named-import | Name1 | tst.ts:7:9:7:13 | Name1 |
57
| named-import | Name1 | tst.ts:13:9:13:13 | Name1 |
68
| named-import | Name1 | tst.ts:13:9:13:21 | Name1<number> |
79
| named-import | Name2 | tst.ts:8:9:8:13 | Name2 |
10+
| namespace-import | | tst.ts:9:9:9:17 | namespace |
811
| namespace-import | Foo | tst.ts:9:9:9:21 | namespace.Foo |
9-
| tst.ts | ExportedClass | relative.ts:4:8:4:20 | ExportedClass |
10-
hasQualifiedNameGlobal
12+
hasUnderlyingTypeGlobal
1113
| UnresolvedName | tst.ts:12:9:12:22 | UnresolvedName |
1214
paramExample
1315
| tst.ts:7:5:7:6 | x1 |

0 commit comments

Comments
 (0)