diff --git a/ql/src/codeql_ruby/ast/Expr.qll b/ql/src/codeql_ruby/ast/Expr.qll index 61dcb2d49d8..e288e83b80c 100644 --- a/ql/src/codeql_ruby/ast/Expr.qll +++ b/ql/src/codeql_ruby/ast/Expr.qll @@ -142,6 +142,15 @@ class ExprSequence extends Expr { final predicate isEmpty() { this.getNumberOfExpressions() = 0 } } +/** + * A sequence of expressions representing the body of a method, class, module, + * or do-block. That is, any body that may also include rescue/ensure/else + * statements. + */ +class BodyStatement extends ExprSequence { + override BodyStatement::Range range; +} + /** * A scope resolution, typically used to access constants defined in a class or * module. diff --git a/ql/src/codeql_ruby/ast/Method.qll b/ql/src/codeql_ruby/ast/Method.qll index 205ebef4ca3..2e24be8b84c 100644 --- a/ql/src/codeql_ruby/ast/Method.qll +++ b/ql/src/codeql_ruby/ast/Method.qll @@ -18,7 +18,7 @@ class Callable extends Expr, CfgScope { } /** A method. */ -class Method extends Callable, @method { +class Method extends Callable, BodyStatement, @method { final override Method::Range range; final override string getAPrimaryQlClass() { result = "Method" } @@ -42,7 +42,7 @@ class Method extends Callable, @method { } /** A singleton method. */ -class SingletonMethod extends Callable, @singleton_method { +class SingletonMethod extends Callable, BodyStatement, @singleton_method { final override SingletonMethod::Range range; final override string getAPrimaryQlClass() { result = "SingletonMethod" } @@ -73,7 +73,7 @@ class Block extends AstNode, Callable { } /** A block enclosed within `do` and `end`. */ -class DoBlock extends Block, @do_block { +class DoBlock extends Block, BodyStatement, @do_block { final override DoBlock::Range range; final override string getAPrimaryQlClass() { result = "DoBlock" } diff --git a/ql/src/codeql_ruby/ast/Module.qll b/ql/src/codeql_ruby/ast/Module.qll index cb0f8300b0c..b2ba9d32879 100644 --- a/ql/src/codeql_ruby/ast/Module.qll +++ b/ql/src/codeql_ruby/ast/Module.qll @@ -1,6 +1,31 @@ private import codeql_ruby.AST private import internal.Module +/** + * The base class for classes, singleton classes, and modules. + */ +class ModuleBase extends BodyStatement { + override ModuleBase::Range range; + + /** Gets a method defined in this module/class. */ + Method getAMethod() { result = this.getAnExpr() } + + /** Gets the method named `name` in this module/class, if any. */ + Method getMethod(string name) { result = this.getAMethod() and result.getName() = name } + + /** Gets a class defined in this module/class. */ + Class getAClass() { result = this.getAnExpr() } + + /** Gets the class named `name` in this module/class, if any. */ + Class getClass(string name) { result = this.getAClass() and result.getName() = name } + + /** Gets a module defined in this module/class. */ + Module getAModule() { result = this.getAnExpr() } + + /** Gets the module named `name` in this module/class, if any. */ + Module getModule(string name) { result = this.getAModule() and result.getName() = name } +} + /** * A class definition. * @@ -11,7 +36,7 @@ private import internal.Module * end * ``` */ -class Class extends ExprSequence { +class Class extends ModuleBase { final override Class::Range range; final override string getAPrimaryQlClass() { result = "Class" } @@ -54,17 +79,6 @@ class Class extends ExprSequence { */ final ScopeResolution getNameScopeResolution() { result = range.getNameScopeResolution() } - /** - * Gets a method defined in this class. - * ```rb - * class Foo - * def bar - * end - * end - * ``` - */ - final Method getAMethod() { result = this.getAnExpr() } - /** * Gets the `Expr` used as the superclass in the class definition, if any. * @@ -91,7 +105,7 @@ class Class extends ExprSequence { * end * ``` */ -class SingletonClass extends ExprSequence, @singleton_class { +class SingletonClass extends ModuleBase, @singleton_class { final override SingletonClass::Range range; final override string getAPrimaryQlClass() { result = "Class" } @@ -108,17 +122,6 @@ class SingletonClass extends ExprSequence, @singleton_class { * ``` */ final Expr getValue() { result = range.getValue() } - - /** - * Gets a method defined in this singleton class. - * ```rb - * class << foo - * def bar - * end - * end - * ``` - */ - final Method getAMethod() { result = this.getAnExpr() } } /** @@ -146,7 +149,7 @@ class SingletonClass extends ExprSequence, @singleton_class { * end * ``` */ -class Module extends ExprSequence, @module { +class Module extends ModuleBase, @module { final override Module::Range range; final override string getAPrimaryQlClass() { result = "Module" } @@ -188,15 +191,4 @@ class Module extends ExprSequence, @module { * ``` */ final ScopeResolution getNameScopeResolution() { result = range.getNameScopeResolution() } - - /** - * Gets a method defined in this module. - * ```rb - * module Foo - * def bar - * end - * end - * ``` - */ - final Method getAMethod() { result = this.getAnExpr() } } diff --git a/ql/src/codeql_ruby/ast/internal/Expr.qll b/ql/src/codeql_ruby/ast/internal/Expr.qll index 2709e3d4a6e..3483cea9882 100644 --- a/ql/src/codeql_ruby/ast/internal/Expr.qll +++ b/ql/src/codeql_ruby/ast/internal/Expr.qll @@ -168,6 +168,10 @@ module ExprSequence { } } +module BodyStatement { + abstract class Range extends ExprSequence::Range { } +} + module ThenExpr { class Range extends ExprSequence::Range, @then { final override Generated::Then generated; diff --git a/ql/src/codeql_ruby/ast/internal/Method.qll b/ql/src/codeql_ruby/ast/internal/Method.qll index 9013519a95c..b6c1109a841 100644 --- a/ql/src/codeql_ruby/ast/internal/Method.qll +++ b/ql/src/codeql_ruby/ast/internal/Method.qll @@ -9,7 +9,7 @@ module Callable { } module Method { - class Range extends Callable::Range, @method { + class Range extends Callable::Range, BodyStatement::Range, @method { final override Generated::Method generated; override Parameter getParameter(int n) { result = generated.getParameters().getChild(n) } @@ -21,11 +21,13 @@ module Method { } final predicate isSetter() { generated.getName() instanceof Generated::Setter } + + final override Expr getExpr(int i) { result = generated.getChild(i) } } } module SingletonMethod { - class Range extends Callable::Range, @singleton_method { + class Range extends Callable::Range, BodyStatement::Range, @singleton_method { final override Generated::SingletonMethod generated; override Parameter getParameter(int n) { result = generated.getParameters().getChild(n) } @@ -35,6 +37,8 @@ module SingletonMethod { result = generated.getName().(SymbolLiteral).getValueText() or result = generated.getName().(Generated::Setter).getName().getValue() + "=" } + + final override Expr getExpr(int i) { result = generated.getChild(i) } } } @@ -51,10 +55,12 @@ module Block { } module DoBlock { - class Range extends Block::Range, @do_block { + class Range extends Block::Range, BodyStatement::Range, @do_block { final override Generated::DoBlock generated; final override Parameter getParameter(int n) { result = generated.getParameters().getChild(n) } + + final override Expr getExpr(int i) { result = generated.getChild(i) } } } diff --git a/ql/src/codeql_ruby/ast/internal/Module.qll b/ql/src/codeql_ruby/ast/internal/Module.qll index 40b9993fe47..60477728d4b 100644 --- a/ql/src/codeql_ruby/ast/internal/Module.qll +++ b/ql/src/codeql_ruby/ast/internal/Module.qll @@ -2,8 +2,12 @@ private import codeql_ruby.AST private import codeql_ruby.ast.internal.Expr private import codeql_ruby.ast.internal.TreeSitter +module ModuleBase { + abstract class Range extends BodyStatement::Range { } +} + module Class { - class Range extends ExprSequence::Range, @class { + class Range extends ModuleBase::Range, @class { final override Generated::Class generated; final override Expr getExpr(int i) { result = generated.getChild(i) } @@ -20,18 +24,17 @@ module Class { } module SingletonClass { - class Range extends ExprSequence::Range, @singleton_class { + class Range extends ModuleBase::Range, @singleton_class { final override Generated::SingletonClass generated; final override Expr getExpr(int i) { result = generated.getChild(i) } final Expr getValue() { result = generated.getValue() } - } } module Module { - class Range extends ExprSequence::Range, @module { + class Range extends ModuleBase::Range, @module { final override Generated::Module generated; final override Expr getExpr(int n) { result = generated.getChild(n) } diff --git a/ql/test/library-tests/ast/classes/classes.expected b/ql/test/library-tests/ast/classes/classes.expected deleted file mode 100644 index c833e0928d7..00000000000 --- a/ql/test/library-tests/ast/classes/classes.expected +++ /dev/null @@ -1,18 +0,0 @@ -classes -| classes.rb:3:1:4:3 | Foo | Class | Foo | -| classes.rb:7:1:8:3 | Bar | Class | Bar | -| classes.rb:11:1:12:3 | Baz | Class | Baz | -| classes.rb:16:1:17:3 | MyClass | Class | MyClass | -| classes.rb:20:1:31:3 | Wibble | Class | Wibble | -classesWithScopeResolutionNames -| classes.rb:16:1:17:3 | MyClass | classes.rb:16:7:16:23 | ...::MyClass | -exprsInClasses -| classes.rb:20:1:31:3 | Wibble | 0 | classes.rb:21:3:23:5 | method_a | Method | -| classes.rb:20:1:31:3 | Wibble | 1 | classes.rb:25:3:27:5 | method_b | Method | -| classes.rb:20:1:31:3 | Wibble | 2 | classes.rb:29:3:29:20 | call to some_method_call | Call | -| classes.rb:20:1:31:3 | Wibble | 3 | classes.rb:30:3:30:19 | ... = ... | AssignExpr | -methodsInClasses -| classes.rb:20:1:31:3 | Wibble | classes.rb:21:3:23:5 | method_a | -| classes.rb:20:1:31:3 | Wibble | classes.rb:25:3:27:5 | method_b | -classesWithASuperclass -| classes.rb:11:1:12:3 | Baz | classes.rb:11:13:11:32 | call to superclass_for | diff --git a/ql/test/library-tests/ast/classes/singleton_classes.expected b/ql/test/library-tests/ast/classes/singleton_classes.expected deleted file mode 100644 index 93769be4d40..00000000000 --- a/ql/test/library-tests/ast/classes/singleton_classes.expected +++ /dev/null @@ -1,10 +0,0 @@ -singletonClasses -| classes.rb:35:1:46:3 | class << ... | Class | classes.rb:35:10:35:10 | call to x | -exprsInSingletonClasses -| classes.rb:35:1:46:3 | class << ... | 0 | classes.rb:36:3:38:5 | length | Method | -| classes.rb:35:1:46:3 | class << ... | 1 | classes.rb:40:3:42:5 | wibble | Method | -| classes.rb:35:1:46:3 | class << ... | 2 | classes.rb:44:3:44:21 | call to another_method_call | Call | -| classes.rb:35:1:46:3 | class << ... | 3 | classes.rb:45:3:45:20 | ... = ... | AssignExpr | -methodsInSingletonClasses -| classes.rb:35:1:46:3 | class << ... | classes.rb:36:3:38:5 | length | -| classes.rb:35:1:46:3 | class << ... | classes.rb:40:3:42:5 | wibble | diff --git a/ql/test/library-tests/ast/modules/classes.expected b/ql/test/library-tests/ast/modules/classes.expected new file mode 100644 index 00000000000..11091f6135e --- /dev/null +++ b/ql/test/library-tests/ast/modules/classes.expected @@ -0,0 +1,29 @@ +classes +| classes.rb:3:1:4:3 | Foo | Class | Foo | +| classes.rb:7:1:8:3 | Bar | Class | Bar | +| classes.rb:11:1:12:3 | Baz | Class | Baz | +| classes.rb:16:1:17:3 | MyClass | Class | MyClass | +| classes.rb:20:1:37:3 | Wibble | Class | Wibble | +| classes.rb:32:3:33:5 | ClassInWibble | Class | ClassInWibble | +| modules.rb:6:5:7:7 | ClassInFooBar | Class | ClassInFooBar | +| modules.rb:19:3:20:5 | ClassInFoo | Class | ClassInFoo | +| modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | Class | ClassInAnotherDefinitionOfFoo | +| modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | Class | ClassInAnotherDefinitionOfFooBar | +classesWithScopeResolutionNames +| classes.rb:16:1:17:3 | MyClass | classes.rb:16:7:16:23 | ...::MyClass | +exprsInClasses +| classes.rb:20:1:37:3 | Wibble | 0 | classes.rb:21:3:23:5 | method_a | Method | +| classes.rb:20:1:37:3 | Wibble | 1 | classes.rb:25:3:27:5 | method_b | Method | +| classes.rb:20:1:37:3 | Wibble | 2 | classes.rb:29:3:29:20 | call to some_method_call | Call | +| classes.rb:20:1:37:3 | Wibble | 3 | classes.rb:30:3:30:19 | ... = ... | AssignExpr | +| classes.rb:20:1:37:3 | Wibble | 4 | classes.rb:32:3:33:5 | ClassInWibble | Class | +| classes.rb:20:1:37:3 | Wibble | 5 | classes.rb:35:3:36:5 | ModuleInWibble | Module | +methodsInClasses +| classes.rb:20:1:37:3 | Wibble | classes.rb:21:3:23:5 | method_a | method_a | +| classes.rb:20:1:37:3 | Wibble | classes.rb:25:3:27:5 | method_b | method_b | +classesInClasses +| classes.rb:20:1:37:3 | Wibble | classes.rb:32:3:33:5 | ClassInWibble | ClassInWibble | +modulesInClasses +| classes.rb:20:1:37:3 | Wibble | classes.rb:35:3:36:5 | ModuleInWibble | ModuleInWibble | +classesWithASuperclass +| classes.rb:11:1:12:3 | Baz | classes.rb:11:13:11:32 | call to superclass_for | diff --git a/ql/test/library-tests/ast/classes/classes.ql b/ql/test/library-tests/ast/modules/classes.ql similarity index 63% rename from ql/test/library-tests/ast/classes/classes.ql rename to ql/test/library-tests/ast/modules/classes.ql index 7fe592e8e0a..f88d785581d 100644 --- a/ql/test/library-tests/ast/classes/classes.ql +++ b/ql/test/library-tests/ast/modules/classes.ql @@ -12,6 +12,10 @@ query predicate exprsInClasses(Class c, int i, Expr e, string eClass) { e = c.getExpr(i) and eClass = e.getAPrimaryQlClass() } -query predicate methodsInClasses(Class c, Method m) { m = c.getAMethod() } +query predicate methodsInClasses(Class c, Method m, string name) { m = c.getMethod(name) } + +query predicate classesInClasses(Class c, Class child, string name) { child = c.getClass(name) } + +query predicate modulesInClasses(Class c, Module m, string name) { m = c.getModule(name) } query predicate classesWithASuperclass(Class c, Expr scExpr) { scExpr = c.getSuperclassExpr() } diff --git a/ql/test/library-tests/ast/classes/classes.rb b/ql/test/library-tests/ast/modules/classes.rb similarity index 87% rename from ql/test/library-tests/ast/classes/classes.rb rename to ql/test/library-tests/ast/modules/classes.rb index 477194ec774..a6bc21f799c 100644 --- a/ql/test/library-tests/ast/classes/classes.rb +++ b/ql/test/library-tests/ast/modules/classes.rb @@ -16,7 +16,7 @@ module MyModule; end class MyModule::MyClass end -# a class with some methods and some other arbitrary expressions +# a class with various expressions class Wibble def method_a puts 'a' @@ -28,6 +28,12 @@ class Wibble some_method_call() $global_var = 123 + + class ClassInWibble + end + + module ModuleInWibble + end end # a singleton class with some methods and some other arbitrary expressions diff --git a/ql/test/library-tests/ast/modules/module_base.expected b/ql/test/library-tests/ast/modules/module_base.expected new file mode 100644 index 00000000000..1755384327e --- /dev/null +++ b/ql/test/library-tests/ast/modules/module_base.expected @@ -0,0 +1,40 @@ +moduleBases +| classes.rb:3:1:4:3 | Foo | Class | +| classes.rb:7:1:8:3 | Bar | Class | +| classes.rb:11:1:12:3 | Baz | Class | +| classes.rb:15:1:15:20 | MyModule | Module | +| classes.rb:16:1:17:3 | MyClass | Class | +| classes.rb:20:1:37:3 | Wibble | Class | +| classes.rb:32:3:33:5 | ClassInWibble | Class | +| classes.rb:35:3:36:5 | ModuleInWibble | Module | +| classes.rb:41:1:52:3 | class << ... | Class | +| modules.rb:1:1:2:3 | Empty | Module | +| modules.rb:4:1:24:3 | Foo | Module | +| modules.rb:5:3:14:5 | Bar | Module | +| modules.rb:6:5:7:7 | ClassInFooBar | Class | +| modules.rb:19:3:20:5 | ClassInFoo | Class | +| modules.rb:26:1:35:3 | Foo | Module | +| modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | Class | +| modules.rb:37:1:46:3 | Bar | Module | +| modules.rb:48:1:57:3 | Bar | Module | +| modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | Class | +moduleBaseClasses +| classes.rb:20:1:37:3 | Wibble | classes.rb:32:3:33:5 | ClassInWibble | +| modules.rb:4:1:24:3 | Foo | modules.rb:19:3:20:5 | ClassInFoo | +| modules.rb:5:3:14:5 | Bar | modules.rb:6:5:7:7 | ClassInFooBar | +| modules.rb:26:1:35:3 | Foo | modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | +| modules.rb:48:1:57:3 | Bar | modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | +moduleBaseMethods +| classes.rb:20:1:37:3 | Wibble | classes.rb:21:3:23:5 | method_a | +| classes.rb:20:1:37:3 | Wibble | classes.rb:25:3:27:5 | method_b | +| classes.rb:41:1:52:3 | class << ... | classes.rb:42:3:44:5 | length | +| classes.rb:41:1:52:3 | class << ... | classes.rb:46:3:48:5 | wibble | +| modules.rb:4:1:24:3 | Foo | modules.rb:16:3:17:5 | method_in_foo | +| modules.rb:5:3:14:5 | Bar | modules.rb:9:5:10:7 | method_in_foo_bar | +| modules.rb:26:1:35:3 | Foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo | +| modules.rb:37:1:46:3 | Bar | modules.rb:38:3:39:5 | method_a | +| modules.rb:37:1:46:3 | Bar | modules.rb:41:3:42:5 | method_b | +| modules.rb:48:1:57:3 | Bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar | +moduleBaseModules +| classes.rb:20:1:37:3 | Wibble | classes.rb:35:3:36:5 | ModuleInWibble | +| modules.rb:4:1:24:3 | Foo | modules.rb:5:3:14:5 | Bar | diff --git a/ql/test/library-tests/ast/modules/module_base.ql b/ql/test/library-tests/ast/modules/module_base.ql new file mode 100644 index 00000000000..e4e72ed1c69 --- /dev/null +++ b/ql/test/library-tests/ast/modules/module_base.ql @@ -0,0 +1,9 @@ +import ruby + +query predicate moduleBases(ModuleBase mb, string pClass) { pClass = mb.getAPrimaryQlClass() } + +query predicate moduleBaseClasses(ModuleBase mb, Class c) { c = mb.getAClass() } + +query predicate moduleBaseMethods(ModuleBase mb, Method m) { m = mb.getAMethod() } + +query predicate moduleBaseModules(ModuleBase mb, Module m) { m = mb.getAModule() } diff --git a/ql/test/library-tests/ast/modules/modules.expected b/ql/test/library-tests/ast/modules/modules.expected index 8711dcace8d..9d650a05cbb 100644 --- a/ql/test/library-tests/ast/modules/modules.expected +++ b/ql/test/library-tests/ast/modules/modules.expected @@ -1,4 +1,6 @@ modules +| classes.rb:15:1:15:20 | MyModule | Module | MyModule | +| classes.rb:35:3:36:5 | ModuleInWibble | Module | ModuleInWibble | | modules.rb:1:1:2:3 | Empty | Module | Empty | | modules.rb:4:1:24:3 | Foo | Module | Foo | | modules.rb:5:3:14:5 | Bar | Module | Bar | @@ -30,9 +32,16 @@ exprsInModules | modules.rb:48:1:57:3 | Bar | 2 | modules.rb:55:3:55:30 | call to puts | Call | | modules.rb:48:1:57:3 | Bar | 3 | modules.rb:56:3:56:17 | ... = ... | AssignExpr | methodsInModules -| modules.rb:4:1:24:3 | Foo | modules.rb:16:3:17:5 | method_in_foo | -| modules.rb:5:3:14:5 | Bar | modules.rb:9:5:10:7 | method_in_foo_bar | -| modules.rb:26:1:35:3 | Foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo | -| modules.rb:37:1:46:3 | Bar | modules.rb:38:3:39:5 | method_a | -| modules.rb:37:1:46:3 | Bar | modules.rb:41:3:42:5 | method_b | -| modules.rb:48:1:57:3 | Bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar | +| modules.rb:4:1:24:3 | Foo | modules.rb:16:3:17:5 | method_in_foo | method_in_foo | +| modules.rb:5:3:14:5 | Bar | modules.rb:9:5:10:7 | method_in_foo_bar | method_in_foo_bar | +| modules.rb:26:1:35:3 | Foo | modules.rb:27:3:28:5 | method_in_another_definition_of_foo | method_in_another_definition_of_foo | +| modules.rb:37:1:46:3 | Bar | modules.rb:38:3:39:5 | method_a | method_a | +| modules.rb:37:1:46:3 | Bar | modules.rb:41:3:42:5 | method_b | method_b | +| modules.rb:48:1:57:3 | Bar | modules.rb:52:3:53:5 | method_in_another_definition_of_foo_bar | method_in_another_definition_of_foo_bar | +classesInModules +| modules.rb:4:1:24:3 | Foo | modules.rb:19:3:20:5 | ClassInFoo | ClassInFoo | +| modules.rb:5:3:14:5 | Bar | modules.rb:6:5:7:7 | ClassInFooBar | ClassInFooBar | +| modules.rb:26:1:35:3 | Foo | modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | ClassInAnotherDefinitionOfFoo | +| modules.rb:48:1:57:3 | Bar | modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | ClassInAnotherDefinitionOfFooBar | +modulesInModules +| modules.rb:4:1:24:3 | Foo | modules.rb:5:3:14:5 | Bar | Bar | diff --git a/ql/test/library-tests/ast/modules/modules.ql b/ql/test/library-tests/ast/modules/modules.ql index 9fce008790f..5fb00a77868 100644 --- a/ql/test/library-tests/ast/modules/modules.ql +++ b/ql/test/library-tests/ast/modules/modules.ql @@ -12,4 +12,14 @@ query predicate exprsInModules(Module m, int i, Expr e, string eClass) { e = m.getExpr(i) and eClass = e.getAPrimaryQlClass() } -query predicate methodsInModules(Module mod, Method method) { method = mod.getAMethod() } +query predicate methodsInModules(Module mod, Method method, string name) { + method = mod.getMethod(name) +} + +query predicate classesInModules(Module mod, Class klass, string name) { + klass = mod.getClass(name) +} + +query predicate modulesInModules(Module mod, Module child, string name) { + child = mod.getModule(name) +} diff --git a/ql/test/library-tests/ast/modules/singleton_classes.expected b/ql/test/library-tests/ast/modules/singleton_classes.expected new file mode 100644 index 00000000000..9d810846f60 --- /dev/null +++ b/ql/test/library-tests/ast/modules/singleton_classes.expected @@ -0,0 +1,10 @@ +singletonClasses +| classes.rb:41:1:52:3 | class << ... | Class | classes.rb:41:10:41:10 | call to x | +exprsInSingletonClasses +| classes.rb:41:1:52:3 | class << ... | 0 | classes.rb:42:3:44:5 | length | Method | +| classes.rb:41:1:52:3 | class << ... | 1 | classes.rb:46:3:48:5 | wibble | Method | +| classes.rb:41:1:52:3 | class << ... | 2 | classes.rb:50:3:50:21 | call to another_method_call | Call | +| classes.rb:41:1:52:3 | class << ... | 3 | classes.rb:51:3:51:20 | ... = ... | AssignExpr | +methodsInSingletonClasses +| classes.rb:41:1:52:3 | class << ... | classes.rb:42:3:44:5 | length | +| classes.rb:41:1:52:3 | class << ... | classes.rb:46:3:48:5 | wibble | diff --git a/ql/test/library-tests/ast/classes/singleton_classes.ql b/ql/test/library-tests/ast/modules/singleton_classes.ql similarity index 100% rename from ql/test/library-tests/ast/classes/singleton_classes.ql rename to ql/test/library-tests/ast/modules/singleton_classes.ql