Skip to content

Commit 4849663

Browse files
committed
Mix learned tags with plain comments and re-wrap trailing notes by marker
`codeql test run --learn` could already keep a trailing regular note when rewriting an expectation comment (`// $ Alert // note`), but only for `//`-comment languages: the note was carried verbatim *including* its `//` marker, so deleting the last expectation from a `#`-comment file would have produced an invalid `// note` line rather than `# note`, and a plain comment carrying no expectation at all was never a merge target. Generalise trailing-note handling so it is marker-aware and works for every line-comment language: - Rename `getTrailingComment` to `getTrailingNote` and return the note's *content*, with its own comment marker and surrounding whitespace stripped. It now also matches a plain comment that carries no expectation of its own (`// note`, `# note`), whose whole content is the note. - When re-rendering a comment, the note is re-wrapped with the inner `//` delimiter the framework recognises regardless of the outer marker, so a `#`-comment file renders `# $ Alert // note`. - When the last expectation is removed but a note survives, re-wrap the note in the file's own start marker (`# note`, not the invalid `// note`). - Broaden `isRewritableComment` to accept a plain comment carrying only a note, so an unexpected result on that line merges `Alert` into the comment (`// note` -> `// $ Alert // note`) instead of appending a second comment. `isRewritableComment` is only consulted on the `--learn` path, so ordinary `codeql test run` output is unchanged.
1 parent 00beeb9 commit 4849663

1 file changed

Lines changed: 58 additions & 32 deletions

File tree

shared/util/codeql/util/test/InlineExpectationsTest.qll

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,14 @@ module Make<InlineExpectationsTestSig Impl> {
374374
}
375375

376376
/**
377-
* Holds if `location` is the location of an inline expectation comment that
377+
* Holds if `location` is the location of a comment that
378378
* `codeql test run --learn` may rewrite as a whole.
379379
*
380-
* A comment is rewritable when it carries at least one parseable expectation, none of its
381-
* expectations is unparseable, and none mixes (in a single comma-separated group) a tag this
382-
* test understands with a tag it ignores (for example a query ID that does not match the
380+
* A comment is rewritable when it either carries at least one parseable expectation, or is a
381+
* plain comment carrying only a trailing note (see `getTrailingNote`) that a freshly learned
382+
* tag can be merged into (for example `// note` -> `// $ Alert // note`); and in addition none
383+
* of its expectations is unparseable, and none mixes (in a single comma-separated group) a tag
384+
* this test understands with a tag it ignores (for example a query ID that does not match the
383385
* current query). The last condition keeps the rewrite from having to take apart a group such
384386
* as `Alert,Source[other-query]` whose parts this test treats differently; such comments are
385387
* left untouched.
@@ -391,7 +393,14 @@ module Make<InlineExpectationsTestSig Impl> {
391393
*/
392394
predicate isRewritableComment(Impl::Location location) {
393395
exists(Impl::ExpectationComment comment | comment.getLocation() = location |
394-
getAnExpectation(comment, _, _, _, _) and
396+
(
397+
getAnExpectation(comment, _, _, _, _)
398+
or
399+
// A plain comment carrying only a note (no expectation of its own) is rewritable too, so
400+
// that a freshly learned tag can be merged into it rather than appended as a second
401+
// comment (for example `// note` -> `// $ Alert // note`).
402+
getTrailingNote(location, _) and not getAnExpectation(comment, _, _, _, _)
403+
) and
395404
not exists(InvalidTestExpectation invalid | invalid.getLocation() = location) and
396405
not exists(string tags, string owned, string ignored |
397406
getAnExpectation(comment, _, _, tags, _) and
@@ -423,22 +432,32 @@ module Make<InlineExpectationsTestSig Impl> {
423432
}
424433

425434
/**
426-
* Holds if the expectation comment at `location` ends with a trailing regular (non-interpreted)
427-
* comment `text`, that is a `//` sequence after the expectations whose remainder the framework
428-
* treats as an ordinary comment (see `expectationCommentPattern`). `text` includes the trailing
429-
* comment's own `//` marker, for example `// note`.
435+
* Holds if the comment at `location` carries a trailing regular (non-interpreted) note `note`,
436+
* with the note's own comment marker and surrounding whitespace stripped. This is either:
430437
*
431-
* `codeql test run --learn` keeps this trailing comment when it rewrites or deletes the
432-
* expectation comment, so an explanatory note written next to an expectation is not lost. Only
433-
* `//` starts such a trailing comment, mirroring `expectationCommentPattern`'s
434-
* `(?:[^/]|/[^/])*` expectation region, which ends only at `//`; a `#` never does, so this is in
435-
* practice a feature of `//`-comment languages.
438+
* - the text after a `//` that follows the expectations in an expectation comment (for example
439+
* `note` in `// $ Alert // note` or `# $ Alert // note`), which the framework treats as an
440+
* ordinary comment (see `expectationCommentPattern`); or
441+
* - the whole content of a plain comment that carries no expectation at all (for example `note`
442+
* in `// note` or `# note`), into which `--learn` may merge a freshly learned tag.
443+
*
444+
* `codeql test run --learn` keeps this note when it rewrites, deletes, or merges into the
445+
* comment, re-wrapping it with the appropriate markers (`<marker> $ ... // note` when
446+
* expectations remain, or `<marker> note` when none do), so an explanatory note written next to
447+
* code is never lost. Only `//` delimits such a note within an expectation comment, mirroring
448+
* `expectationCommentPattern`'s `(?:[^/]|/[^/])*` expectation region, which ends only at `//`; a
449+
* `#` never does, so `# $ Alert # note` reads `note` as a tag rather than a note.
436450
*/
437-
predicate getTrailingComment(Impl::Location location, string text) {
438-
exists(Impl::ExpectationComment comment |
439-
comment.getLocation() = location and
440-
text = comment.getContents().regexpCapture("\\s*\\$ (?:[^/]|/[^/])*(//.*)", 1)
441-
)
451+
predicate getTrailingNote(Impl::Location location, string note) {
452+
exists(Impl::ExpectationComment comment | comment.getLocation() = location |
453+
note = comment.getContents().regexpCapture("\\s*\\$ (?:[^/]|/[^/])*//(.*)", 1).trim()
454+
or
455+
// A plain comment with no expectation of its own: its whole content is the note.
456+
not getAnExpectation(comment, _, _, _, _) and
457+
not exists(InvalidTestExpectation invalid | invalid.getLocation() = location) and
458+
note = comment.getContents().trim()
459+
) and
460+
note != ""
442461
}
443462

444463
query predicate testFailures(FailureLocatable element, string message) {
@@ -1307,14 +1326,16 @@ module TestPostProcessing {
13071326
or
13081327
endMarker != "" and endSuffix = " " + endMarker
13091328
) and
1310-
// Preserve a trailing regular comment (e.g. `// $ Alert // note`) that sits after the
1311-
// expectations, so rewriting the expectations never drops an explanatory note.
1329+
// Preserve a trailing regular note (e.g. the `note` in `// $ Alert // note`) that sits
1330+
// after the expectations, so rewriting the expectations never drops an explanatory note.
1331+
// The inner delimiter is always `//`, which the framework recognises regardless of the
1332+
// outer comment marker (so a `#`-comment file renders `# $ Alert // note`).
13121333
(
1313-
exists(string trailing |
1314-
Test::getTrailingComment(commentLoc, trailing) and trailingSuffix = " " + trailing
1334+
exists(string note |
1335+
Test::getTrailingNote(commentLoc, note) and trailingSuffix = " // " + note
13151336
)
13161337
or
1317-
not Test::getTrailingComment(commentLoc, _) and trailingSuffix = ""
1338+
not Test::getTrailingNote(commentLoc, _) and trailingSuffix = ""
13181339
) and
13191340
body =
13201341
concat(string column |
@@ -1352,10 +1373,10 @@ module TestPostProcessing {
13521373
* `SPURIOUS:`, and `MISSING:` columns. Expectations this test ignores (for example a tag
13531374
* annotated with a different query's ID) are preserved verbatim, so the comment keeps any
13541375
* expectation belonging to a different query that shares the same source file; see
1355-
* `isRewritableComment` and `Test::getAForeignExpectation`. A trailing regular comment after
1356-
* the expectations (for example the ` // note` in `// $ Alert // note`) is likewise preserved;
1357-
* see `Test::getTrailingComment`. Only `//` starts such a trailing comment, so this is in
1358-
* practice a feature of `//`-comment languages.
1376+
* `isRewritableComment` and `Test::getAForeignExpectation`. A trailing regular note after the
1377+
* expectations (for example the `note` in `// $ Alert // note` or `# $ Alert // note`) is
1378+
* likewise preserved, and a freshly learned tag can be merged into a plain comment that carries
1379+
* only such a note (`// note` -> `// $ Alert // note`); see `Test::getTrailingNote`.
13591380
*/
13601381
query predicate learnEdits(
13611382
string file, int line, string operation, int startColumn, int endColumn, string text
@@ -1413,12 +1434,17 @@ module TestPostProcessing {
14131434
text = renderLearnedComment(relativePath, commentLoc)
14141435
or
14151436
not exists(string column, string t | desiredExpectation(commentLoc, column, t)) and
1416-
// No expectation survives, so drop the `$ ...` comment. If it carried a trailing regular
1417-
// comment, keep that in place of the whole comment; otherwise delete to the end of line.
1437+
// No expectation survives, so drop the `$ ...` part of the comment. If it carried a
1438+
// trailing regular note, keep that note re-wrapped in the file's own comment marker (so a
1439+
// `#`-comment file yields `# note`, not an invalid `// note`); otherwise delete to the
1440+
// end of line.
14181441
(
1419-
Test::getTrailingComment(commentLoc, text)
1442+
exists(string note |
1443+
Test::getTrailingNote(commentLoc, note) and
1444+
text = Input2::getStartCommentMarker(relativePath) + " " + note
1445+
)
14201446
or
1421-
not Test::getTrailingComment(commentLoc, _) and text = ""
1447+
not Test::getTrailingNote(commentLoc, _) and text = ""
14221448
)
14231449
)
14241450
)

0 commit comments

Comments
 (0)