Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QL4QL: Add query suggesting use of inline test expectations #18767

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions ql/ql/src/codeql_ql/ast/Yaml.qll
Original file line number Diff line number Diff line change
@@ -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<YamlSig>

/** 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(_)
)
}
}
15 changes: 15 additions & 0 deletions ql/ql/src/queries/style/QlRefInlineExpectations.ql
Original file line number Diff line number Diff line change
@@ -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."
29 changes: 1 addition & 28 deletions ql/ql/src/utils/test/InlineExpectationsTest.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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<Impl>
21 changes: 21 additions & 0 deletions ql/ql/src/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
@@ -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<Impl, Input>

private module Input implements T::TestPostProcessing::InputSig<Impl> {
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
)
}
}
28 changes: 28 additions & 0 deletions ql/ql/src/utils/test/internal/InlineExpectationsTestImpl.qll
Original file line number Diff line number Diff line change
@@ -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.
*/
Comment on lines +9 to +11

Check warning

Code scanning / CodeQL

Class QLDoc style. Warning test

The QLDoc for a class should start with 'A', 'An', or 'The'.
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;
}
1 change: 1 addition & 0 deletions ql/ql/test/queries/style/QlRefInlineExpectations/Foo.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import ql
Original file line number Diff line number Diff line change
@@ -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. |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
queries/style/QlRefInlineExpectations.ql

Check warning

Code scanning / CodeQL

Query test without inline test expectations Warning test

Query test does not use inline test expectations.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
query: queries/style/OmittableExists.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query: queries/style/OmittableExists.ql
postprocess:
- utils/test/InlineExpectationsTestQuery.ql
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
query: queries/style/OmittableExists.ql

Check warning

Code scanning / CodeQL

Query test without inline test expectations Warning test

Query test does not use inline test expectations.
1 change: 1 addition & 0 deletions ql/tools/pre-finalize.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

type NUL && "%CODEQL_DIST%\codeql" database index-files ^
--include=**/qlpack.yml ^
--include-extension=.qlref ^
--size-limit=5m ^
--language yaml ^
-- ^
Expand Down
1 change: 1 addition & 0 deletions ql/tools/pre-finalize.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -eu

"$CODEQL_DIST/codeql" database index-files \
"--include=**/qlpack.yml" \
--include-extension=.qlref \
--size-limit=5m \
--language yaml \
-- \
Expand Down
1 change: 1 addition & 0 deletions ql/tools/qltest.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -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=. ^
Expand Down
1 change: 1 addition & 0 deletions ql/tools/qltest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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=.\
Expand Down
Loading