diff --git a/javascript/ql/lib/semmle/javascript/JSDoc.qll b/javascript/ql/lib/semmle/javascript/JSDoc.qll index 0f95023ce1f..85b7695cd70 100644 --- a/javascript/ql/lib/semmle/javascript/JSDoc.qll +++ b/javascript/ql/lib/semmle/javascript/JSDoc.qll @@ -415,7 +415,7 @@ class JSDocNamedTypeExpr extends JSDocTypeExpr { * - `foo.bar.Baz` has prefix `foo` and suffix `.bar.Baz`. * - `Baz` has prefix `Baz` and an empty suffix. */ - predicate hasNameParts(string prefix, string suffix) { + deprecated predicate hasNameParts(string prefix, string suffix) { not this = any(JSDocQualifiedTypeAccess a).getBase() and // restrict size of predicate exists(string regex, string name | regex = "([^.]+)(.*)" | name = this.getRawName() and @@ -423,46 +423,6 @@ class JSDocNamedTypeExpr extends JSDocTypeExpr { suffix = name.regexpCapture(regex, 2) ) } - - pragma[noinline] - pragma[nomagic] - private predicate hasNamePartsAndEnv(string prefix, string suffix, JSDoc::Environment env) { - // Force join ordering - this.hasNameParts(prefix, suffix) and - env.isContainerInScope(this.getContainer()) - } - - /** - * Gets the qualified name of this name by resolving its prefix, if any. - */ - cached - private string resolvedName() { - exists(string prefix, string suffix, JSDoc::Environment env | - this.hasNamePartsAndEnv(prefix, suffix, env) and - result = env.resolveAlias(prefix) + suffix - ) - } - - override predicate hasQualifiedName(string globalName) { - globalName = this.resolvedName() - or - not exists(this.resolvedName()) and - globalName = this.getRawName() - } - - override DataFlow::ClassNode getClass() { - exists(string name | - this.hasQualifiedName(name) and - result.hasQualifiedName(name) - ) - or - // Handle case where a local variable has a reference to the class, - // but the class doesn't have a globally qualified name. - exists(string alias, JSDoc::Environment env | - this.hasNamePartsAndEnv(alias, "", env) and - result.getAClassReference().flowsTo(env.getNodeFromAlias(alias)) - ) - } } /** @@ -491,12 +451,6 @@ class JSDocAppliedTypeExpr extends @jsdoc_applied_type_expr, JSDocTypeExpr { * For example, in `Array`, `string` is the only argument type. */ JSDocTypeExpr getAnArgument() { result = this.getArgument(_) } - - override predicate hasQualifiedName(string globalName) { - this.getHead().hasQualifiedName(globalName) - } - - override DataFlow::ClassNode getClass() { result = this.getHead().getClass() } } /** @@ -516,8 +470,6 @@ class JSDocNullableTypeExpr extends @jsdoc_nullable_type_expr, JSDocTypeExpr { predicate isPrefix() { jsdoc_prefix_qualifier(this) } override JSDocTypeExpr getAnUnderlyingType() { result = this.getTypeExpr().getAnUnderlyingType() } - - override DataFlow::ClassNode getClass() { result = this.getTypeExpr().getClass() } } /** @@ -537,8 +489,6 @@ class JSDocNonNullableTypeExpr extends @jsdoc_non_nullable_type_expr, JSDocTypeE predicate isPrefix() { jsdoc_prefix_qualifier(this) } override JSDocTypeExpr getAnUnderlyingType() { result = this.getTypeExpr().getAnUnderlyingType() } - - override DataFlow::ClassNode getClass() { result = this.getTypeExpr().getClass() } } /** @@ -643,8 +593,6 @@ class JSDocOptionalParameterTypeExpr extends @jsdoc_optional_type_expr, JSDocTyp override JSDocTypeExpr getAnUnderlyingType() { result = this.getUnderlyingType().getAnUnderlyingType() } - - override DataFlow::ClassNode getClass() { result = this.getUnderlyingType().getClass() } } /** @@ -679,7 +627,7 @@ module JSDoc { /** * A statement container which may declare JSDoc name aliases. */ - class Environment extends StmtContainer { + deprecated class Environment extends StmtContainer { /** * Gets the fully qualified name aliased by the given unqualified name * within this container. @@ -729,7 +677,7 @@ module JSDoc { } pragma[noinline] - private predicate isTypenamePrefix(string name) { + deprecated private predicate isTypenamePrefix(string name) { any(JSDocNamedTypeExpr expr).hasNameParts(name, _) } } diff --git a/javascript/ql/lib/semmle/javascript/TypeAnnotations.qll b/javascript/ql/lib/semmle/javascript/TypeAnnotations.qll index d10b60b92b5..318ad2f8873 100644 --- a/javascript/ql/lib/semmle/javascript/TypeAnnotations.qll +++ b/javascript/ql/lib/semmle/javascript/TypeAnnotations.qll @@ -4,6 +4,8 @@ import javascript private import internal.StmtContainers +private import internal.NameResolution +private import internal.UnderlyingTypes /** * 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 { TypeAnnotation getAnUnderlyingType() { result = this } /** + * DEPRECATED. Use `hasUnderlyingType` instead. + * * Holds if this is a reference to the type with qualified name `globalName` relative to the global scope. */ - predicate hasQualifiedName(string globalName) { none() } + deprecated predicate hasQualifiedName(string globalName) { + UnderlyingTypes::nodeHasUnderlyingType(this, globalName) + } /** + * DEPRECATED. Use `hasUnderlyingType` instead. + * * Holds if this is a reference to the type exported from `moduleName` under the name `exportedName`. */ - predicate hasQualifiedName(string moduleName, string exportedName) { none() } + deprecated predicate hasQualifiedName(string moduleName, string exportedName) { + UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, exportedName) + } + + /** + * Holds if this is a reference to the type with qualified name `globalName` relative to the global scope, + * or is declared as a subtype thereof, or is a union or intersection containing such a type. + */ + final predicate hasUnderlyingType(string globalName) { + UnderlyingTypes::nodeHasUnderlyingType(this, globalName) + } + + /** + * Holds if this is a reference to the type exported from `moduleName` under the name `exportedName`, + * or is declared as a subtype thereof, or is a union or intersection containing such a type. + */ + final predicate hasUnderlyingType(string moduleName, string exportedName) { + UnderlyingTypes::nodeHasUnderlyingType(this, moduleName, exportedName) + } /** Gets the statement in which this type appears. */ Stmt getEnclosingStmt() { none() } @@ -107,5 +133,5 @@ class TypeAnnotation extends @type_annotation, NodeInStmtContainer { * * This unfolds nullability modifiers and generic type applications. */ - DataFlow::ClassNode getClass() { none() } + final DataFlow::ClassNode getClass() { UnderlyingTypes::nodeHasUnderlyingClassType(this, result) } } diff --git a/javascript/ql/lib/semmle/javascript/TypeScript.qll b/javascript/ql/lib/semmle/javascript/TypeScript.qll index d8b6b63a366..f11ff776309 100644 --- a/javascript/ql/lib/semmle/javascript/TypeScript.qll +++ b/javascript/ql/lib/semmle/javascript/TypeScript.qll @@ -1,4 +1,5 @@ import javascript +private import semmle.javascript.internal.UnderlyingTypes /** * A statement that defines a namespace, that is, a namespace declaration or enum declaration. @@ -575,10 +576,6 @@ class TypeExpr extends ExprOrType, @typeexpr, TypeAnnotation { override Function getEnclosingFunction() { result = ExprOrType.super.getEnclosingFunction() } override TopLevel getTopLevel() { result = ExprOrType.super.getTopLevel() } - - override DataFlow::ClassNode getClass() { - result.getAstNode() = this.getType().(ClassType).getClass() - } } /** @@ -698,58 +695,9 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef { */ TypeName getTypeName() { ast_node_symbol(this, result) } - override predicate hasQualifiedName(string globalName) { - this.getTypeName().hasQualifiedName(globalName) - or - exists(LocalTypeAccess local | local = this | - not exists(local.getLocalTypeName()) and // Without a local type name, the type is looked up in the global scope. - globalName = local.getName() - ) - } - - override predicate hasQualifiedName(string moduleName, string exportedName) { - this.getTypeName().hasQualifiedName(moduleName, exportedName) - or - exists(ImportDeclaration imprt, ImportSpecifier spec | - moduleName = getImportName(imprt) and - spec = imprt.getASpecifier() - | - spec.getImportedName() = exportedName and - this = spec.getLocal().(TypeDecl).getLocalTypeName().getAnAccess() - or - (spec instanceof ImportNamespaceSpecifier or spec instanceof ImportDefaultSpecifier) and - this = - spec.getLocal().(LocalNamespaceDecl).getLocalNamespaceName().getAMemberAccess(exportedName) - ) - or - exists(ImportEqualsDeclaration imprt | - moduleName = getImportName(imprt.getImportedEntity()) and - this = - imprt - .getIdentifier() - .(LocalNamespaceDecl) - .getLocalNamespaceName() - .getAMemberAccess(exportedName) - ) - } - override string getAPrimaryQlClass() { result = "TypeAccess" } } -/** - * Gets a suitable name for the library imported by `imprt`. - * - * For relative imports, this is the snapshot-relative path to the imported module. - * For non-relative imports, it is the import path itself. - */ -private string getImportName(Import imprt) { - exists(string path | path = imprt.getImportedPath().getValue() | - if path.regexpMatch("[./].*") - then result = imprt.getImportedModule().getFile().getRelativePath() - else result = path - ) -} - /** An identifier that is used as part of a type, such as `Date`. */ class LocalTypeAccess extends @local_type_access, TypeAccess, Identifier, LexicalAccess { override predicate isStringy() { this.getName() = "String" } @@ -822,14 +770,6 @@ class GenericTypeExpr extends @generic_typeexpr, TypeExpr { /** Gets the number of type arguments. This is always at least one. */ int getNumTypeArgument() { result = count(this.getATypeArgument()) } - override predicate hasQualifiedName(string globalName) { - this.getTypeAccess().hasQualifiedName(globalName) - } - - override predicate hasQualifiedName(string moduleName, string exportedName) { - this.getTypeAccess().hasQualifiedName(moduleName, exportedName) - } - override string getAPrimaryQlClass() { result = "GenericTypeExpr" } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll b/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll index dd6e1a7d915..89b7fe04997 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Nest.qll @@ -237,7 +237,7 @@ module NestJS { CustomPipeClass() { exists(ClassDefinition cls | this = cls.flow() and - cls.getASuperInterface().hasQualifiedName("@nestjs/common", "PipeTransform") + cls.getASuperInterface().hasUnderlyingType("@nestjs/common", "PipeTransform") ) } diff --git a/javascript/ql/test/library-tests/JSDoc/NameResolution/test.expected b/javascript/ql/test/library-tests/JSDoc/NameResolution/test.expected index 97730513195..7c015994aaf 100644 --- a/javascript/ql/test/library-tests/JSDoc/NameResolution/test.expected +++ b/javascript/ql/test/library-tests/JSDoc/NameResolution/test.expected @@ -1,10 +1,10 @@ -| bar.js:5:14:5:14 | x | x | +| bar.js:5:14:5:14 | x | ns.very.long.namespace | | bar.js:5:14:5:18 | x.Foo | ns.very.long.namespace.Foo | -| bar.js:12:14:12:17 | iife | iife | +| bar.js:12:14:12:17 | iife | IIFE | | bar.js:12:14:12:21 | iife.Foo | IIFE.Foo | | closure.js:8:12:8:15 | goog | goog | | closure.js:8:12:8:19 | goog.net | goog.net | | closure.js:8:12:8:28 | goog.net.SomeType | goog.net.SomeType | -| closure.js:9:12:9:14 | net | net | +| closure.js:9:12:9:14 | net | goog.net | | closure.js:9:12:9:23 | net.SomeType | goog.net.SomeType | | closure.js:10:12:10:19 | SomeType | goog.net.SomeType | diff --git a/javascript/ql/test/library-tests/JSDoc/NameResolution/test.ql b/javascript/ql/test/library-tests/JSDoc/NameResolution/test.ql index 1b7ebfdd501..bb1de953169 100644 --- a/javascript/ql/test/library-tests/JSDoc/NameResolution/test.ql +++ b/javascript/ql/test/library-tests/JSDoc/NameResolution/test.ql @@ -1,3 +1,3 @@ import javascript -query string test_hasQualifiedName(JSDocNamedTypeExpr expr) { expr.hasQualifiedName(result) } +query string test_hasUnderlyingType(JSDocNamedTypeExpr expr) { expr.hasUnderlyingType(result) } diff --git a/javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.expected b/javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.expected index 8ac3eea2be5..06afe15ee18 100644 --- a/javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.expected +++ b/javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.expected @@ -2,13 +2,14 @@ test_isString | tst.js:2:12:2:17 | string | test_isNumber | tst.js:3:12:3:17 | number | -test_QualifiedName +test_hasUnderlyingType | VarType | tst.js:9:13:9:19 | VarType | | boolean | tst.js:5:14:5:20 | boolean | | foo | tst.js:4:12:4:14 | foo | | foo.bar | tst.js:4:12:4:18 | foo.bar | | foo.bar.baz | tst.js:4:12:4:22 | foo.bar.baz | | number | tst.js:3:12:3:17 | number | +| number | tst.js:3:12:3:18 | number? | | string | tst.js:2:12:2:17 | string | test_ParameterType | tst.js:7:12:7:12 | x | tst.js:2:12:2:17 | string | diff --git a/javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.ql b/javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.ql index 829435e3220..fd223ee5a53 100644 --- a/javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.ql +++ b/javascript/ql/test/library-tests/TypeAnnotations/JSDoc/JSDocTypeAnnotations.ql @@ -4,7 +4,7 @@ query TypeAnnotation test_isString() { result.isString() } query TypeAnnotation test_isNumber() { result.isNumber() } -query TypeAnnotation test_QualifiedName(string name) { result.hasQualifiedName(name) } +query TypeAnnotation test_hasUnderlyingType(string name) { result.hasUnderlyingType(name) } query TypeAnnotation test_ParameterType(Parameter p) { result = p.getTypeAnnotation() } diff --git a/javascript/ql/test/library-tests/TypeAnnotations/TSUnresolvedQualifiedName/QualifiedNames.ql b/javascript/ql/test/library-tests/TypeAnnotations/TSUnresolvedQualifiedName/QualifiedNames.ql index e9d66a4afe0..b4d324377be 100644 --- a/javascript/ql/test/library-tests/TypeAnnotations/TSUnresolvedQualifiedName/QualifiedNames.ql +++ b/javascript/ql/test/library-tests/TypeAnnotations/TSUnresolvedQualifiedName/QualifiedNames.ql @@ -1,5 +1,5 @@ import javascript from TypeAnnotation type, string mod, string name -where type.hasQualifiedName(mod, name) +where type.hasUnderlyingType(mod, name) select type, mod, name diff --git a/javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.expected b/javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.expected index 5ee97e2dfb5..3781aea96e2 100644 --- a/javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.expected +++ b/javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.expected @@ -1,13 +1,15 @@ -hasQualifiedNameModule -| default-import | default | tst.ts:11:9:11:21 | DefaultImport | +hasUnderlyingTypeModule +| default-import | | tst.ts:11:9:11:21 | DefaultImport | +| global | UnresolvedName | tst.ts:12:9:12:22 | UnresolvedName | +| import-assign | | tst.ts:10:9:10:11 | asn | | import-assign | Foo | tst.ts:10:9:10:15 | asn.Foo | | named-import | Name1 | tst.ts:7:9:7:13 | Name1 | | named-import | Name1 | tst.ts:13:9:13:13 | Name1 | | named-import | Name1 | tst.ts:13:9:13:21 | Name1 | | named-import | Name2 | tst.ts:8:9:8:13 | Name2 | +| namespace-import | | tst.ts:9:9:9:17 | namespace | | namespace-import | Foo | tst.ts:9:9:9:21 | namespace.Foo | -| tst.ts | ExportedClass | relative.ts:4:8:4:20 | ExportedClass | -hasQualifiedNameGlobal +hasUnderlyingTypeGlobal | UnresolvedName | tst.ts:12:9:12:22 | UnresolvedName | paramExample | tst.ts:7:5:7:6 | x1 | diff --git a/javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.ql b/javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.ql index 2b63e171f1e..199749ed3f6 100644 --- a/javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.ql +++ b/javascript/ql/test/library-tests/TypeScript/HasQualifiedNameFallback/Test.ql @@ -1,13 +1,13 @@ import javascript -query TypeAnnotation hasQualifiedNameModule(string moduleName, string member) { - result.hasQualifiedName(moduleName, member) +query TypeAnnotation hasUnderlyingTypeModule(string moduleName, string member) { + result.hasUnderlyingType(moduleName, member) } -query TypeAnnotation hasQualifiedNameGlobal(string globalName) { - result.hasQualifiedName(globalName) +query TypeAnnotation hasUnderlyingTypeGlobal(string globalName) { + result.hasUnderlyingType(globalName) } query Parameter paramExample() { - result.getTypeAnnotation().hasQualifiedName("named-import", "Name1") + result.getTypeAnnotation().hasUnderlyingType("named-import", "Name1") } diff --git a/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.expected b/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.expected index a9123b1ef55..e330bb897b7 100644 --- a/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.expected +++ b/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.expected @@ -6,5 +6,4 @@ underlyingTypeNode | foo | | file://:0:0:0:0 | use moduleImport("foo").getMember("exports") | | foo | | foo.ts:1:8:1:10 | use moduleImport("foo").getMember("exports").getMember("default") | -| foo | Bar | foo.ts:3:1:5:1 | use moduleImport("foo").getMember("exports").getMember("Bar").getInstance() | | foo | Bar | foo.ts:3:12:3:12 | use moduleImport("foo").getMember("exports").getMember("Bar").getInstance() |