diff --git a/ql/ql/src/codeql_ql/ast/Yaml.qll b/ql/ql/src/codeql_ql/ast/Yaml.qll new file mode 100644 index 000000000000..142820226a41 --- /dev/null +++ b/ql/ql/src/codeql_ql/ast/Yaml.qll @@ -0,0 +1,67 @@ +/** + * Provides classes for working with YAML data. + * + * YAML documents are represented as abstract syntax trees whose nodes + * are either YAML values or alias nodes referring to another YAML value. + */ + +private import codeql.yaml.Yaml as LibYaml + +private module YamlSig implements LibYaml::InputSig { + import codeql.Locations + + class LocatableBase extends @yaml_locatable { + Location getLocation() { yaml_locations(this, result) } + + string toString() { none() } + } + + class NodeBase extends LocatableBase, @yaml_node { + NodeBase getChildNode(int i) { yaml(result, _, this, i, _, _) } + + string getTag() { yaml(this, _, _, _, result, _) } + + string getAnchor() { yaml_anchors(this, result) } + + override string toString() { yaml(this, _, _, _, _, result) } + } + + class ScalarNodeBase extends NodeBase, @yaml_scalar_node { + int getStyle() { yaml_scalars(this, result, _) } + + string getValue() { yaml_scalars(this, _, result) } + } + + class CollectionNodeBase extends NodeBase, @yaml_collection_node { } + + class MappingNodeBase extends CollectionNodeBase, @yaml_mapping_node { } + + class SequenceNodeBase extends CollectionNodeBase, @yaml_sequence_node { } + + class AliasNodeBase extends NodeBase, @yaml_alias_node { + string getTarget() { yaml_aliases(this, result) } + } + + class ParseErrorBase extends LocatableBase, @yaml_error { + string getMessage() { yaml_errors(this, result) } + } +} + +import LibYaml::Make + +/** A `.qlref` YAML document. */ +class QlRefDocument extends YamlDocument { + QlRefDocument() { this.getFile().getExtension() = "qlref" } + + /** Holds if this `.qlref` file uses inline test expectations. */ + predicate usesInlineExpectations() { + exists(YamlMapping n, YamlScalar value | + n.getDocument() = this and + value.getValue().matches("%InlineExpectations%") + | + value = n.lookup("postprocess") + or + value = n.lookup("postprocess").(YamlSequence).getElement(_) + ) + } +} diff --git a/ql/ql/src/queries/style/QlRefInlineExpectations.ql b/ql/ql/src/queries/style/QlRefInlineExpectations.ql new file mode 100644 index 000000000000..870dd0f437c9 --- /dev/null +++ b/ql/ql/src/queries/style/QlRefInlineExpectations.ql @@ -0,0 +1,15 @@ +/** + * @name Query test without inline test expectations + * @description Using inline test expectations is a best practice for writing query tests. + * @kind problem + * @problem.severity warning + * @id ql/qlref-inline-expectations + * @precision high + */ + +import ql +import codeql_ql.ast.Yaml + +from QlRefDocument f +where not f.usesInlineExpectations() +select f, "Query test does not use inline test expectations." diff --git a/ql/ql/src/utils/test/InlineExpectationsTest.qll b/ql/ql/src/utils/test/InlineExpectationsTest.qll index 258115458edf..6d4014db766c 100644 --- a/ql/ql/src/utils/test/InlineExpectationsTest.qll +++ b/ql/ql/src/utils/test/InlineExpectationsTest.qll @@ -3,33 +3,6 @@ * See `shared/util/codeql/util/test/InlineExpectationsTest.qll` */ -private import ql as QL private import codeql.util.test.InlineExpectationsTest - -private module Impl implements InlineExpectationsTestSig { - private import codeql_ql.ast.internal.TreeSitter as TS - - private newtype TExpectationComment = MkExpectationComment(TS::QL::LineComment comment) - - /** - * Represents a line comment. - */ - class ExpectationComment extends TExpectationComment { - TS::QL::LineComment comment; - - ExpectationComment() { this = MkExpectationComment(comment) } - - /** Returns the contents of the given comment, _without_ the preceding comment marker (`//`). */ - string getContents() { result = comment.getValue().suffix(2) } - - /** Gets a textual representation of this element. */ - string toString() { result = comment.toString() } - - /** Gets the location of this comment. */ - Location getLocation() { result = comment.getLocation() } - } - - class Location = QL::Location; -} - +private import internal.InlineExpectationsTestImpl import Make diff --git a/ql/ql/src/utils/test/InlineExpectationsTestQuery.ql b/ql/ql/src/utils/test/InlineExpectationsTestQuery.ql new file mode 100644 index 000000000000..979839480e1d --- /dev/null +++ b/ql/ql/src/utils/test/InlineExpectationsTestQuery.ql @@ -0,0 +1,21 @@ +/** + * @kind test-postprocess + */ + +private import ql +private import codeql.util.test.InlineExpectationsTest as T +private import internal.InlineExpectationsTestImpl +import T::TestPostProcessing +import T::TestPostProcessing::Make + +private module Input implements T::TestPostProcessing::InputSig { + string getRelativeUrl(Location location) { + exists(File f, int startline, int startcolumn, int endline, int endcolumn | + location.hasLocationInfo(_, startline, startcolumn, endline, endcolumn) and + f = location.getFile() + | + result = + f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn + ) + } +} diff --git a/ql/ql/src/utils/test/internal/InlineExpectationsTestImpl.qll b/ql/ql/src/utils/test/internal/InlineExpectationsTestImpl.qll new file mode 100644 index 000000000000..647ddd5a87db --- /dev/null +++ b/ql/ql/src/utils/test/internal/InlineExpectationsTestImpl.qll @@ -0,0 +1,28 @@ +private import ql as QL +private import codeql.util.test.InlineExpectationsTest + +module Impl implements InlineExpectationsTestSig { + private import codeql_ql.ast.internal.TreeSitter as TS + + private newtype TExpectationComment = MkExpectationComment(TS::QL::LineComment comment) + + /** + * Represents a line comment. + */ + class ExpectationComment extends TExpectationComment { + TS::QL::LineComment comment; + + ExpectationComment() { this = MkExpectationComment(comment) } + + /** Returns the contents of the given comment, _without_ the preceding comment marker (`//`). */ + string getContents() { result = comment.getValue().suffix(2) } + + /** Gets a textual representation of this element. */ + string toString() { result = comment.toString() } + + /** Gets the location of this comment. */ + Location getLocation() { result = comment.getLocation() } + } + + class Location = QL::Location; +} diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/Foo.qll b/ql/ql/test/queries/style/QlRefInlineExpectations/Foo.qll new file mode 100644 index 000000000000..08d6fab7e6e3 --- /dev/null +++ b/ql/ql/test/queries/style/QlRefInlineExpectations/Foo.qll @@ -0,0 +1 @@ +import ql diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/QlRefInlineExpectations.expected b/ql/ql/test/queries/style/QlRefInlineExpectations/QlRefInlineExpectations.expected new file mode 100644 index 000000000000..7dd57a3ef860 --- /dev/null +++ b/ql/ql/test/queries/style/QlRefInlineExpectations/QlRefInlineExpectations.expected @@ -0,0 +1,2 @@ +| QlRefInlineExpectations.qlref:1:1:1:40 | queries ... ions.ql | Query test does not use inline test expectations. | +| Test3.qlref:1:1:1:39 | query: ... ists.ql | Query test does not use inline test expectations. | diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/QlRefInlineExpectations.qlref b/ql/ql/test/queries/style/QlRefInlineExpectations/QlRefInlineExpectations.qlref new file mode 100644 index 000000000000..d2c8c9d70691 --- /dev/null +++ b/ql/ql/test/queries/style/QlRefInlineExpectations/QlRefInlineExpectations.qlref @@ -0,0 +1 @@ +queries/style/QlRefInlineExpectations.ql \ No newline at end of file diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/Test1.expected b/ql/ql/test/queries/style/QlRefInlineExpectations/Test1.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/Test1.qlref b/ql/ql/test/queries/style/QlRefInlineExpectations/Test1.qlref new file mode 100644 index 000000000000..03dd6f50713b --- /dev/null +++ b/ql/ql/test/queries/style/QlRefInlineExpectations/Test1.qlref @@ -0,0 +1,2 @@ +query: queries/style/OmittableExists.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql \ No newline at end of file diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/Test2.expected b/ql/ql/test/queries/style/QlRefInlineExpectations/Test2.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/Test2.qlref b/ql/ql/test/queries/style/QlRefInlineExpectations/Test2.qlref new file mode 100644 index 000000000000..414c61f1e9cc --- /dev/null +++ b/ql/ql/test/queries/style/QlRefInlineExpectations/Test2.qlref @@ -0,0 +1,3 @@ +query: queries/style/OmittableExists.ql +postprocess: + - utils/test/InlineExpectationsTestQuery.ql \ No newline at end of file diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/Test3.expected b/ql/ql/test/queries/style/QlRefInlineExpectations/Test3.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ql/ql/test/queries/style/QlRefInlineExpectations/Test3.qlref b/ql/ql/test/queries/style/QlRefInlineExpectations/Test3.qlref new file mode 100644 index 000000000000..496512e7b452 --- /dev/null +++ b/ql/ql/test/queries/style/QlRefInlineExpectations/Test3.qlref @@ -0,0 +1 @@ +query: queries/style/OmittableExists.ql \ No newline at end of file diff --git a/ql/tools/pre-finalize.cmd b/ql/tools/pre-finalize.cmd index 516084941c93..49d18f3d1974 100644 --- a/ql/tools/pre-finalize.cmd +++ b/ql/tools/pre-finalize.cmd @@ -2,6 +2,7 @@ type NUL && "%CODEQL_DIST%\codeql" database index-files ^ --include=**/qlpack.yml ^ + --include-extension=.qlref ^ --size-limit=5m ^ --language yaml ^ -- ^ diff --git a/ql/tools/pre-finalize.sh b/ql/tools/pre-finalize.sh index 81fc6d582784..7003ad81c1b3 100755 --- a/ql/tools/pre-finalize.sh +++ b/ql/tools/pre-finalize.sh @@ -4,6 +4,7 @@ set -eu "$CODEQL_DIST/codeql" database index-files \ "--include=**/qlpack.yml" \ + --include-extension=.qlref \ --size-limit=5m \ --language yaml \ -- \ diff --git a/ql/tools/qltest.cmd b/ql/tools/qltest.cmd index d44290526450..9445c01d7e95 100644 --- a/ql/tools/qltest.cmd +++ b/ql/tools/qltest.cmd @@ -16,6 +16,7 @@ IF %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL% type NUL && "%CODEQL_DIST%\codeql.exe" database index-files ^ --prune=**/*.testproj ^ --include-extension=.yml ^ + --include-extension=.qlref ^ --size-limit=5m ^ --language=yaml ^ --working-dir=. ^ diff --git a/ql/tools/qltest.sh b/ql/tools/qltest.sh index d6b9e0ff5ee3..bfd72257de0e 100755 --- a/ql/tools/qltest.sh +++ b/ql/tools/qltest.sh @@ -15,6 +15,7 @@ set -eu exec "${CODEQL_DIST}/codeql" database index-files \ --prune="**/*.testproj" \ --include-extension=.yml \ + --include-extension=.qlref \ --size-limit=5m \ --language=yaml \ --working-dir=.\