Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 9 additions & 0 deletions cpp/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// C/C++ databases can also contain XML (e.g. `.xml`, `.props`), whose block-comment
// syntax is not yet supported, so we only render for C/C++/Objective-C sources.
Comment thread
geoffw0 marked this conversation as resolved.
Outdated
relativePath
.regexpMatch(".*\\.(c|cc|cpp|cxx|cp|c\\+\\+|h|hh|hpp|hxx|h\\+\\+|inl|tcc|ipp|tpp|cu|cuh|m|mm)") and
result = "//"
}
}
8 changes: 8 additions & 0 deletions csharp/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// C# databases can also contain XML (e.g. `.csproj`, `.config`) and Razor markup, whose
// comment syntaxes are not yet supported, so we only render for C# sources.
relativePath.regexpMatch(".*\\.(cs|csx)") and
result = "//"
}
}
8 changes: 8 additions & 0 deletions go/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// Go databases can also contain XML, whose block-comment syntax is not yet supported, so
// we only render for Go sources.
relativePath.matches("%.go") and
result = "//"
}
}
8 changes: 8 additions & 0 deletions java/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// Java databases can also contain XML; those files use a different (block) comment
// syntax that is not yet supported, so we only render for Java and Kotlin sources.
(relativePath.matches("%.java") or relativePath.matches("%.kt")) and
result = "//"
}
}
8 changes: 8 additions & 0 deletions javascript/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// JavaScript databases can also contain HTML, whose (block) comment syntax is not yet
// supported, so we only render for the line-comment source files.
relativePath.regexpMatch(".*\\.(js|cjs|mjs|jsx|ts|cts|mts|tsx)") and
result = "//"
}
}
8 changes: 8 additions & 0 deletions python/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// Python databases can also contain XML, whose block-comment syntax is not yet supported,
// so we only render for Python sources.
relativePath.regexpMatch(".*\\.(py|pyi)") and
result = "#"
}
}
8 changes: 8 additions & 0 deletions ql/ql/src/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// The QL extractor can also extract YAML (e.g. `qlpack.yml`), whose `#` comment syntax
// differs, so we only render for QL sources and dbscheme files.
relativePath.regexpMatch(".*\\.(ql|qll|dbscheme)") and
result = "//"
}
}
8 changes: 8 additions & 0 deletions ruby/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// Ruby databases can also contain ERB, whose comment syntax is not yet supported, so we
// only render for plain Ruby sources.
relativePath.matches("%.rb") and
result = "#"
}
}
8 changes: 8 additions & 0 deletions rust/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// Rust databases can also contain YAML, whose `#` comment syntax differs, so we only
// render for Rust sources.
relativePath.matches("%.rs") and
result = "//"
}
}
122 changes: 122 additions & 0 deletions shared/util/codeql/util/test/InlineExpectationsTest.qll
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,28 @@

signature module InputSig<InlineExpectationsTestSig Input> {
string getRelativeUrl(Input::Location location);

/**
* Gets the marker that starts a line comment (for example `"//"` or `"#"`) in the source
* file with the given `relativePath`, provided that `codeql test run --learn` is able to
* render inline expectations for that file. Files for which this has no result are left
* untouched by `--learn`.
*
* This is keyed on the file rather than on the analysed language because a single database
* may contain source files in several languages with different comment syntaxes (for
* example Java together with XML). `relativePath` is the path reported by `getRelativeUrl`.
*/

Check warning

Code scanning / CodeQL

Misspelling Warning test

This comment contains the non-US spelling 'analysed', which should instead be 'analyzed'.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
bindingset[relativePath]
string getStartCommentMarker(string relativePath);

/**
* Gets the marker that ends a comment (for example `"-->"`) in the source file with the
* given `relativePath`. Defaults to the empty string, which is correct for languages whose
* inline expectations use line comments; block-comment languages can override it so that
* `--learn` renders a closing marker.
*/
bindingset[relativePath]
default string getEndCommentMarker(string relativePath) { result = "" }
}

module Make<InlineExpectationsTestSig Input, InputSig<Input> Input2> {
Expand Down Expand Up @@ -1011,5 +1033,105 @@
Test::testFailures(_, _) and
relation = "testFailures"
}

/**
* Gets the fully rendered inline expectation comment (including the comment markers) that
* `--learn` should insert for a new `tag` expectation in the file with the given
* `relativePath`, or has no result if that file's comment syntax is not supported.
*
* The leading space separates the comment from any existing content on the line.
*/
bindingset[relativePath, tag]
private string renderExpectationComment(string relativePath, string tag) {
exists(string startMarker, string endMarker, string endSuffix |
startMarker = Input2::getStartCommentMarker(relativePath) and
endMarker = Input2::getEndCommentMarker(relativePath) and
(
endMarker = "" and endSuffix = ""
or
endMarker != "" and endSuffix = " " + endMarker
) and
result = " " + startMarker + " $ " + tag + endSuffix
)
}

/**
* Holds if `codeql test run --learn` should edit the source file `file` so that its inline
* expectations match the current query results. Each row asks the test runner to change
* `line`, where `operation` is either:
*
* - `"append"`: add `text` (a fully rendered comment) at the end of the line; `startColumn`
* and `endColumn` are both 0.
* - `"replace"`: replace the 1-based inclusive column range `[startColumn, endColumn]` with
* `text` (the empty string deletes the range).
*
* Only a deliberately reliable subset is emitted so far, restricted to the plain `Alert`
* tag with no value or query-id annotation:
*
* - an actual result with no matching expectation gets a new `// $ Alert` comment appended
* (an *unexpected result*), and
* - a plain `// $ Alert` comment whose result is now missing, and which is the only
* expectation on that comment, is removed (a *missing result*).
*
* Cases that need sub-comment column information (promoting `MISSING:`/clearing `SPURIOUS:`,
* or editing one tag among several on a line) are intentionally left for a follow-up.
*/
query predicate learnEdits(
string file, int line, string operation, int startColumn, int endColumn, string text
) {
// Unexpected result: append a new `// $ Alert` comment on the alert's line. The comment
// must go on the result's *end* line, because an expectation matches a result when the
// expectation's start line equals the result's end line (see `onSameLine`). For most
// languages a result spans a single line, but some (e.g. Rust) include leading trivia in
// the location, so the start and end lines differ.
exists(Test::ActualTestResult actualResult, string relativePath, int el, string comment |
actualResult.getTag() = "Alert" and
actualResult.getValue() = "" and
not actualResult.isOptional() and
not exists(
Test::getAMatchingExpectation(actualResult.getLocation(), actualResult.toString(),
actualResult.getTag(), actualResult.getValue(), false)
) and
parseLocationString(actualResult.getLocation().getRelativeUrl(), relativePath, _, _, el, _) and
comment = renderExpectationComment(relativePath, "Alert") and
file = relativePath and
line = el and
operation = "append" and
startColumn = 0 and
endColumn = 0 and
text = comment
)
or
// Missing result: remove a plain `// $ Alert` comment that is the comment's sole
// expectation and no longer matches any actual result.
exists(Test::GoodTestExpectation expectation, string relativePath, int sl, int sc |
expectation.getTag() = "Alert" and
expectation.getValue() = "" and
not expectation = Test::getAMatchingExpectation(_, _, _, _, _) and
// the comment carries exactly this one expectation, so removing it wholesale is safe
count(Test::FailureLocatable other |
other.getLocation() = expectation.getLocation() and
(
other instanceof Test::GoodTestExpectation or
other instanceof Test::FalsePositiveTestExpectation or
other instanceof Test::FalseNegativeTestExpectation
)
) = 1 and
not exists(Test::InvalidTestExpectation invalid |
invalid.getLocation() = expectation.getLocation()
) and
parseLocationString(expectation.getLocation().getRelativeUrl(), relativePath, sl, sc, _, _) and
file = relativePath and
line = sl and
operation = "replace" and
startColumn = sc and
// A trailing inline expectation comment always runs to the end of its line, so delete from
// the comment marker to the end of the line. `endColumn = 0` is the engine's "to end of
// line" convention; using it avoids depending on how each extractor reports a line
// comment's end column (e.g. Swift reports it as ending at column 1 of the next line).
endColumn = 0 and
text = ""
)
}
}
}
7 changes: 7 additions & 0 deletions swift/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,11 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// The Swift extractor only ingests Swift sources (no XML/YAML/HTML in its dbscheme), so a
// constant marker is safe; revisit if Swift ever gains extraction of another file type.
exists(relativePath) and result = "//"
}
}
10 changes: 10 additions & 0 deletions unified/ql/lib/utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,14 @@ private module Input implements T::TestPostProcessing::InputSig<Impl> {
f.getRelativePath() + ":" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}

bindingset[relativePath]
string getStartCommentMarker(string relativePath) {
// The unified extractor is a new tree-sitter-based extractor that currently ingests only
// Swift sources (see `file_types` in its `codeql-extractor.yml`), which use `//`. Gating on
// the extension keeps this correct if it later gains a language with a different comment
// syntax.
relativePath.regexpMatch(".*\\.(swift|swiftinterface)") and
result = "//"
}
}
Loading