Skip to content
Draft
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
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++ sources.
relativePath
.regexpMatch(".*\\.(c|cc|cpp|cxx|cp|c\\+\\+|h|hh|hpp|hxx|h\\+\\+|inl|tcc|ipp|tpp|cu|cuh)") 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 @@ module TestPostProcessing {

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 analyzed 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`.
*/
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 @@ module TestPostProcessing {
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