diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index c7d911eac63f..0bd79dd20291 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -594,6 +594,9 @@ module API { exportedName = "" and result = getAModuleImportRaw(moduleName) } + + /** Gets a sink node that represents instances of `cls`. */ + Node getClassInstance(DataFlow::ClassNode cls) { result = Impl::MkClassInstance(cls) } } /** diff --git a/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll new file mode 100644 index 000000000000..5d4a067874c7 --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll @@ -0,0 +1,459 @@ +/** + * Provides predicates for generating names for classes and functions that are part + * of the public API of a library. + * + * When possible, we try to use the qualified name by which a class/function can be accessed + * from client code. + * + * However, there are cases where classes and functions can be exposed to client + * code without being accessible as a qualified name. For example; + * ```js + * // 'Foo' is internal, but clients can reach its methods via `getFoo().m()` + * class Foo { + * m() {} + * } + * export function getFoo() { + * return new Foo(); + * } + * + * // Clients can reach m() via getObj().m() + * export function getObj() { + * return { + * m() {} + * } + * } + * ``` + * + * In these cases, we try to make up human-readable names for the endpoints. + * We make an effort to make these unambiguous in practice, though this is not always guaranteed. + */ + +private import javascript + +/** Concatenates two access paths. */ +bindingset[x, y] +private string join(string x, string y) { + if x = "" or y = "" then result = x + y else result = x + "." + y +} + +private predicate isPackageExport(API::Node node) { node = API::moduleExport(_) } + +private predicate memberEdge(API::Node pred, API::Node succ) { succ = pred.getAMember() } + +/** Gets the shortest distance from a packaeg export to `nd` in the API graph. */ +private int distanceFromPackageExport(API::Node nd) = + shortestDistances(isPackageExport/1, memberEdge/2)(_, nd, result) + +private predicate isExported(API::Node node) { + isPackageExport(node) + or + exists(API::Node pred | + isExported(pred) and + memberEdge(pred, node) + ) +} + +/** + * Holds if `node` is a default export that can be reinterpreted as a namespace export, + * because the enclosing module has no named exports. + */ +private predicate defaultExportCanBeInterpretedAsNamespaceExport(API::Node node) { + exists(ES2015Module mod | + node.asSink() = mod.getAnExportedValue("default") and + not mod.hasBothNamedAndDefaultExports() + ) +} + +private predicate isPrivateAssignment(DataFlow::Node node) { + exists(MemberDeclaration decl | + node = decl.getInit().flow() and + decl.isPrivate() + ) + or + exists(DataFlow::PropWrite write | + write.isPrivateField() and + node = write.getRhs() + ) +} + +private predicate isPrivateLike(API::Node node) { isPrivateAssignment(node.asSink()) } + +private API::Node getASuccessor(API::Node node, string name, int badness) { + isExported(node) and + exists(string member | + result = node.getMember(member) and + not isPrivateLike(node) and + if member = "default" + then + if defaultExportCanBeInterpretedAsNamespaceExport(node) + then ( + badness = 5 and name = "" + ) else ( + badness = 10 and name = "default" + ) + else ( + name = member and badness = 0 + ) + ) +} + +private API::Node getAPredecessor(API::Node node, string name, int badness) { + node = getASuccessor(result, name, badness) +} + +/** + * Gets the predecessor of `node` to use when constructing a qualified name for it, + * and binds `name` and `badness` corresponding to the label on that edge. + */ +private API::Node getPreferredPredecessor(API::Node node, string name, int badness) { + // For root nodes, we prefer not having a predecessor, as we use the package name. + not isPackageExport(node) and + // Rank predecessors by name-badness, export-distance, and name. + // Since min() can only return a single value, we need a separate min() call per column. + badness = min(int b | exists(getAPredecessor(node, _, b)) | b) and + result = + min(API::Node pred, string name1 | + pred = getAPredecessor(node, name1, badness) + | + pred order by distanceFromPackageExport(pred), name1 + ) and + name = min(string n | result = getAPredecessor(node, n, badness) | n) +} + +/** + * Holds if values escpin + */ +private predicate sinkHasNameCandidate(API::Node sink, string package, string name, int badness) { + sink = API::moduleExport(package) and + name = "" and + badness = 0 + or + exists(API::Node baseNode, string baseName, int baseBadness, string step, int stepBadness | + sinkHasNameCandidate(baseNode, package, baseName, baseBadness) and + baseNode = getPreferredPredecessor(sink, step, stepBadness) and + badness = (baseBadness + stepBadness).minimum(20) and + name = join(baseName, step) + ) +} + +/** + * Holds if `(package, name)` is the primary name to associate with `node`. + * + * `badness` is bound to the associated badness of the name. + */ +private predicate sinkHasPrimaryName(API::Node sink, string package, string name, int badness) { + badness = min(int b | sinkHasNameCandidate(sink, _, _, b) | b) and + package = min(string p | sinkHasNameCandidate(sink, p, _, badness) | p) and + name = min(string n | sinkHasNameCandidate(sink, package, n, badness) | n order by n.length(), n) +} + +/** + * Holds if `(package, name)` is the primary name to associate with `node`. + */ +private predicate sinkHasPrimaryName(API::Node sink, string package, string name) { + sinkHasPrimaryName(sink, package, name, _) +} + +/** + * Holds if `(package, name)` is an alias for `node`. + * + * This means it is a valid name for it, but was not chosen as the primary name. + */ +predicate sinkHasAlias(API::Node sink, string package, string name) { + not sinkHasPrimaryName(sink, package, name) and + ( + exists(string baseName, string step | + sinkHasPrimaryName(getAPredecessor(sink, step, _), package, baseName) and + name = join(baseName, step) + ) + or + sink = API::moduleExport(package) and + name = "" + ) +} + +/** Gets a sink node reachable from `node`. */ +bindingset[node] +private API::Node getASinkNode(DataFlow::SourceNode node) { result.getAValueReachingSink() = node } + +bindingset[qualifiedName] +private int getBadnessOfClassName(string qualifiedName) { + if qualifiedName.matches("%.constructor") + then result = 10 + else + if qualifiedName = "" + then result = 5 + else result = 0 +} + +/** Holds if `(package, name)` is a potential name for `cls`, with the given `badness`. */ +private predicate classObjectHasNameCandidate( + DataFlow::ClassNode cls, string package, string name, int badness +) { + // There can be multiple API nodes associated with `cls`. + // For example: + /// + // class C {} + // module.exports.A = C; // first sink + // module.exports.B = C; // second sink + // + exists(int baseBadness | + sinkHasPrimaryName(getASinkNode(cls), package, name, baseBadness) and + badness = baseBadness + getBadnessOfClassName(name) + ) +} + +private predicate classObjectHasPrimaryName( + DataFlow::ClassNode cls, string package, string name, int badness +) { + badness = min(int b | classObjectHasNameCandidate(cls, _, _, b) | b) and + package = min(string p | classObjectHasNameCandidate(cls, p, _, badness) | p) and + name = min(string n | classObjectHasNameCandidate(cls, package, n, badness) | n) +} + +/** Holds if `(package, name)` is the primary name for the class object of `cls`. */ +predicate classObjectHasPrimaryName(DataFlow::ClassNode cls, string package, string name) { + classObjectHasPrimaryName(cls, package, name, _) +} + +/** Holds if `(package, name)` is an alias for the class object of `cls`. */ +predicate classObjectHasAlias(DataFlow::ClassNode cls, string package, string name) { + not classObjectHasPrimaryName(cls, package, name) and + exists(int badness | + classObjectHasNameCandidate(cls, package, name, badness) and + badness < 100 + ) +} + +/** Holds if an instance of `cls` can be exposed to client code. */ +private predicate hasEscapingInstance(DataFlow::ClassNode cls) { + cls.getAnInstanceReference().flowsTo(any(API::Node n).asSink()) +} + +/** + * Holds if `(package, name)` is a potential name to use for instances of `cls`, with the given `badness`. + */ +private predicate classInstanceHasNameCandidate( + DataFlow::ClassNode cls, string package, string name, int badness +) { + exists(string baseName | + classObjectHasPrimaryName(cls, package, baseName, badness) and + name = join(baseName, "prototype") + ) + or + // In case the class itself is unaccessible, but an instance is exposed via an access path, + // consider using that access path. For example: + // + // class InternalClass {} + // module.exports.foo = new InternalClass(); + // + exists(int baseBadness | + sinkHasPrimaryName(getASinkNode(cls.getAnInstanceReference()), package, name, baseBadness) and + badness = baseBadness + 30 // add penalty, as we prefer to base this on the class name + ) + or + // If neither the class nor its instances are accessible via an access path, but instances of the + // class can still escape via more complex access patterns, resort to a synthesized name. + // For example: + // + // class InternalClass {} + // function foo() { + // return new InternalClass(); + // } + // + hasEscapingInstance(cls) and + exists(string baseName | + InternalModuleNaming::fallbackModuleName(cls.getTopLevel(), package, baseName, badness - 100) and + name = join(baseName, cls.getName()) + ".prototype" + ) +} + +private predicate classInstanceHasPrimaryName( + DataFlow::ClassNode cls, string package, string name, int badness +) { + badness = min(int b | classInstanceHasNameCandidate(cls, _, _, b) | b) and + package = min(string p | classInstanceHasNameCandidate(cls, p, _, badness) | p) and + name = + min(string n | + classInstanceHasNameCandidate(cls, package, n, badness) + | + n order by n.length(), n + ) +} + +/** Holds if `(package, name)` is the primary name to use for instances of `cls`. */ +predicate classInstanceHasPrimaryName(DataFlow::ClassNode cls, string package, string name) { + classInstanceHasPrimaryName(cls, package, name, _) +} + +/** Holds if `(package, name)` is an alias referring to some instance of `cls`. */ +predicate classInstanceHasAlias(DataFlow::ClassNode cls, string package, string name) { + not classInstanceHasPrimaryName(cls, package, name) and + exists(int badness | + classInstanceHasNameCandidate(cls, package, name, badness) and + badness < 100 // Badness 100 is when we start to synthesize names. Do not suggest these as aliases. + ) +} + +private predicate functionHasNameCandidate( + DataFlow::FunctionNode function, string package, string name, int badness +) { + sinkHasPrimaryName(getASinkNode(function), package, name, badness) + or + exists(DataFlow::ClassNode cls | + function = cls.getConstructor() and + classObjectHasPrimaryName(cls, package, name, badness) + or + exists(string baseName, string memberName | + function = cls.getInstanceMethod(memberName) and + classInstanceHasPrimaryName(cls, package, baseName, badness) and + name = join(baseName, memberName) + or + function = cls.getStaticMethod(memberName) and + classObjectHasPrimaryName(cls, package, baseName, badness) and + name = join(baseName, memberName) + ) + ) +} + +private predicate functionHasPrimaryName( + DataFlow::FunctionNode function, string package, string name, int badness +) { + badness = min(int b | functionHasNameCandidate(function, _, _, b) | b) and + package = min(string p | functionHasNameCandidate(function, p, _, badness) | p) and + name = + min(string n | + functionHasNameCandidate(function, package, n, badness) + | + n order by n.length(), n + ) +} + +/** + * Holds if `(package, name)` is the primary name for the given `function`. + */ +predicate functionHasPrimaryName(DataFlow::FunctionNode function, string package, string name) { + functionHasPrimaryName(function, package, name, _) +} + +/** + * Holds if `(package, name)` is an alias for the given `function`. + */ +predicate functionHasAlias(DataFlow::FunctionNode function, string package, string name) { + not functionHasPrimaryName(function, package, name) and + exists(int badness | + functionHasNameCandidate(function, package, name, badness) and + badness < 100 + ) +} + +/** + * Converts a `(package, name)` pair to a string of form `(package).name`. + */ +bindingset[package, name] +string renderName(string package, string name) { result = join("(" + package + ")", name) } + +/** + * Contains predicates for naming individual modules (i.e. files) inside of a package. + * + * These names are not necessarily part of a package's public API, and so we only used them + * as a fallback when a publicly-accessible access path cannot be found. + */ +private module InternalModuleNaming { + /** Gets the path to `folder` relative to its enclosing non-private `package.json` file. */ + private string getPackageRelativePathFromFolder(Folder folder) { + exists(PackageJson json | + json.getFile() = folder.getFile("package.json") and + not json.isPrivate() and + result = json.getPackageName() + ) + or + not exists(folder.getFile("package.json")) and + result = + getPackageRelativePathFromFolder(folder.getParentContainer()) + "/" + folder.getBaseName() + } + + private string getPackageRelativePath(Module mod) { + exists(PackageJson json, string relativePath | + not json.isPrivate() and + json.getExportedModule(relativePath) = mod and + if relativePath = "." + then result = json.getPackageName() + else result = json.getPackageName() + "/" + relativePath.regexpReplaceAll("^\\./", "") + ) + or + not mod = any(PackageJson json | not json.isPrivate()).getExportedModule(_) and + not mod.isAmbient() and + exists(string folderPath | + folderPath = getPackageRelativePathFromFolder(mod.getFile().getParentContainer()) and + if mod.getName() = "index" + then result = folderPath + else result = folderPath + "/" + mod.getName() + ) + } + + /** Holds if `(package, name)` should be used to refer to code inside `mod`. */ + predicate fallbackModuleName(Module mod, string package, string name, int badness) { + sinkHasPrimaryName(getASinkNode(mod.getDefaultOrBulkExport()), package, name, badness) + or + badness = 50 and + package = getPackageRelativePath(mod) and + name = "" + } +} + +/** + * Contains query predicates for emitting debugging information about endpoint naming. + */ +module Debug { + /** Holds if `node` has multiple preferred predecessors. */ + query predicate ambiguousPreferredPredecessor(API::Node node) { + strictcount(API::Node pred, string name, int badness | + pred = getPreferredPredecessor(node, name, badness) + ) > 1 + } + + /** Holds if the given `node` has multiple primary names. */ + query string ambiguousSinkName(API::Node node) { + strictcount(string package, string name | sinkHasPrimaryName(node, package, name)) > 1 and + result = + concat(string package, string name | + sinkHasPrimaryName(node, package, name) + | + renderName(package, name), ", " + ) + } + + /** Holds if the given `node` has multiple primary names. */ + query string ambiguousClassObjectName(DataFlow::ClassNode node) { + strictcount(string package, string name | classObjectHasPrimaryName(node, package, name)) > 1 and + result = + concat(string package, string name | + classObjectHasPrimaryName(node, package, name) + | + renderName(package, name), ", " + ) + } + + /** Holds if the given `node` has multiple primary names. */ + query string ambiguousClassInstanceName(DataFlow::ClassNode node) { + strictcount(string package, string name | classInstanceHasPrimaryName(node, package, name)) > 1 and + result = + concat(string package, string name | + classInstanceHasPrimaryName(node, package, name) + | + renderName(package, name), ", " + ) + } + + /** Holds if the given `node` has multiple primary names. */ + query string ambiguousFunctionName(DataFlow::FunctionNode node) { + strictcount(string package, string name | functionHasPrimaryName(node, package, name)) > 1 and + result = + concat(string package, string name | + functionHasPrimaryName(node, package, name) + | + renderName(package, name), ", " + ) + } +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected new file mode 100644 index 000000000000..e0cc251f9039 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.expected @@ -0,0 +1,7 @@ +testFailures +ambiguousPreferredPredecessor +ambiguousSinkName +ambiguousClassObjectName +ambiguousClassInstanceName +ambiguousFunctionName +failures diff --git a/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.ql b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.ql new file mode 100644 index 000000000000..102ebb721a86 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/EndpointNaming.ql @@ -0,0 +1,41 @@ +import javascript +import semmle.javascript.RestrictedLocations +import semmle.javascript.Lines +import semmle.javascript.endpoints.EndpointNaming as EndpointNaming +import testUtilities.InlineExpectationsTest +import EndpointNaming::Debug + +module TestConfig implements TestSig { + string getARelevantTag() { + result = "instance" + or + result = "class" + or + result = "method" + } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(string package, string name | + element = "" and + value = EndpointNaming::renderName(package, name) + | + exists(DataFlow::ClassNode cls | location = cls.getAstNode().getLocation() | + tag = "class" and + EndpointNaming::classObjectHasPrimaryName(cls, package, name) + or + tag = "instance" and + EndpointNaming::classInstanceHasPrimaryName(cls, package, name) + ) + or + element = "" and + exists(DataFlow::FunctionNode function | + not function.getFunction() = any(ConstructorDeclaration decl | decl.isSynthetic()).getBody() and + location = function.getFunction().getLocation() and + tag = "method" and + EndpointNaming::functionHasPrimaryName(function, package, name) + ) + ) + } +} + +import MakeTest diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack1/main.js b/javascript/ql/test/library-tests/EndpointNaming/pack1/main.js new file mode 100644 index 000000000000..ead8000ff147 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack1/main.js @@ -0,0 +1,13 @@ +export class PublicClass {} // $ class=(pack1).PublicClass instance=(pack1).PublicClass.prototype + +class PrivateClass {} + +export const ExportedConst = class ExportedConstClass {} // $ class=(pack1).ExportedConst instance=(pack1).ExportedConst.prototype + +class ClassWithEscapingInstance {} // $ instance=(pack1).ClassWithEscapingInstance.prototype + +export function getEscapingInstance() { + return new ClassWithEscapingInstance(); +} // $ method=(pack1).getEscapingInstance + +export function publicFunction() {} // $ method=(pack1).publicFunction diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack1/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack1/package.json new file mode 100644 index 000000000000..da2dbb94dd1b --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack1/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack1", + "main": "./main.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack10/foo.js b/javascript/ql/test/library-tests/EndpointNaming/pack10/foo.js new file mode 100644 index 000000000000..3495843defeb --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack10/foo.js @@ -0,0 +1 @@ +export default class FooClass {} // $ class=(pack10).Foo instance=(pack10).Foo.prototype diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack10/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack10/index.js new file mode 100644 index 000000000000..59bad5c3ecb5 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack10/index.js @@ -0,0 +1,3 @@ +import { default as Foo } from "./foo"; + +export { Foo } diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack10/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack10/package.json new file mode 100644 index 000000000000..977ffaf282b8 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack10/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack10", + "main": "./index.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack2/lib.js b/javascript/ql/test/library-tests/EndpointNaming/pack2/lib.js new file mode 100644 index 000000000000..ed996a3350e6 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack2/lib.js @@ -0,0 +1,6 @@ +class AmbiguousClass { + instanceMethod(foo) {} // $ method=(pack2).lib.LibClass.prototype.instanceMethod +} // $ class=(pack2).lib.LibClass instance=(pack2).lib.LibClass.prototype + +export default AmbiguousClass; +export { AmbiguousClass as LibClass } diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack2/main.js b/javascript/ql/test/library-tests/EndpointNaming/pack2/main.js new file mode 100644 index 000000000000..e40015de73fd --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack2/main.js @@ -0,0 +1,9 @@ +class AmbiguousClass { + instanceMethod() {} // $ method=(pack2).MainClass.prototype.instanceMethod +} // $ class=(pack2).MainClass instance=(pack2).MainClass.prototype + +export default AmbiguousClass; +export { AmbiguousClass as MainClass } + +import * as lib from "./lib"; +export { lib } diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack2/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack2/package.json new file mode 100644 index 000000000000..b359913f6395 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack2/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack2", + "main": "./main.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack3/lib.js b/javascript/ql/test/library-tests/EndpointNaming/pack3/lib.js new file mode 100644 index 000000000000..9ef8c57437db --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack3/lib.js @@ -0,0 +1 @@ +export default function(x,y,z) {} // $ method=(pack3).libFunction diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack3/main.js b/javascript/ql/test/library-tests/EndpointNaming/pack3/main.js new file mode 100644 index 000000000000..3f49675b4926 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack3/main.js @@ -0,0 +1,7 @@ +function ambiguousFunction(x, y, z) {} // $ method=(pack3).namedFunction + +export default ambiguousFunction; +export { ambiguousFunction as namedFunction }; + +import libFunction from "./lib"; +export { libFunction }; diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack3/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack3/package.json new file mode 100644 index 000000000000..0ca9a6083320 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack3/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack3", + "main": "./main.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack4/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack4/index.js new file mode 100644 index 000000000000..15143e30bf61 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack4/index.js @@ -0,0 +1 @@ +export default class C {} // $ class=(pack4) instance=(pack4).prototype diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack4/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack4/package.json new file mode 100644 index 000000000000..fb63d98f8abd --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack4/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack4", + "main": "./index.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack5/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack5/package.json new file mode 100644 index 000000000000..d6f924e72cad --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack5/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack5", + "main": "./dist/index.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack5/src/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack5/src/index.js new file mode 100644 index 000000000000..d96538407862 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack5/src/index.js @@ -0,0 +1 @@ +export default class C {} // $ class=(pack5) instance=(pack5).prototype diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack6/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack6/index.js new file mode 100644 index 000000000000..e15b5319858b --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack6/index.js @@ -0,0 +1,6 @@ +class C { + instanceMethod() {} // $ method=(pack6).instanceMethod + static staticMethod() {} // not accessible +} // $ instance=(pack6) + +export default new C(); diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack6/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack6/package.json new file mode 100644 index 000000000000..c4daea408e33 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack6/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack6", + "main": "./index.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack7/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack7/index.js new file mode 100644 index 000000000000..b56b32095d48 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack7/index.js @@ -0,0 +1,6 @@ +export class D {} // $ class=(pack7).D instance=(pack7).D.prototype + +// In this case we are forced to include ".default" to avoid ambiguity with class D above. +export default { + D: class {} // $ class=(pack7).default.D instance=(pack7).default.D.prototype +}; diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack7/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack7/package.json new file mode 100644 index 000000000000..a43ae4f59034 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack7/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack7", + "main": "./index.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack8/foo.js b/javascript/ql/test/library-tests/EndpointNaming/pack8/foo.js new file mode 100644 index 000000000000..b141c3c81e71 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack8/foo.js @@ -0,0 +1,5 @@ +class Foo {} // $ class=(pack8).Foo instance=(pack8).Foo.prototype + +module.exports = Foo; +module.exports.default = Foo; +module.exports.Foo = Foo; diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack8/index.js b/javascript/ql/test/library-tests/EndpointNaming/pack8/index.js new file mode 100644 index 000000000000..3cf0920f4bf0 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack8/index.js @@ -0,0 +1,5 @@ +class Main {} // $ class=(pack8) instance=(pack8).prototype + +Main.Foo = require('./foo'); + +module.exports = Main; diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack8/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack8/package.json new file mode 100644 index 000000000000..09d57a3348e2 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack8/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack8", + "main": "./index.js" +} diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack9/foo.js b/javascript/ql/test/library-tests/EndpointNaming/pack9/foo.js new file mode 100644 index 000000000000..55d14ad5b523 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack9/foo.js @@ -0,0 +1 @@ +export class Foo {} // $ instance=(pack9/foo).Foo.prototype diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack9/index.ts b/javascript/ql/test/library-tests/EndpointNaming/pack9/index.ts new file mode 100644 index 000000000000..65c783aa4991 --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack9/index.ts @@ -0,0 +1,9 @@ +// Only the type is exposed. For the time being we do not consider type-only declarations or .d.ts files +// when naming classes. +export type { Foo } from "./foo"; + +import * as foo from "./foo"; + +export function expose() { + return new foo.Foo(); // expose an instance of Foo but not the class +} // $ method=(pack9).expose diff --git a/javascript/ql/test/library-tests/EndpointNaming/pack9/package.json b/javascript/ql/test/library-tests/EndpointNaming/pack9/package.json new file mode 100644 index 000000000000..4e69ff9e365b --- /dev/null +++ b/javascript/ql/test/library-tests/EndpointNaming/pack9/package.json @@ -0,0 +1,4 @@ +{ + "name": "pack9", + "main": "./index.js" +}