-
Notifications
You must be signed in to change notification settings - Fork 0
Add ranked "and N more" reporting module #27
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
Open
Copilot
wants to merge
5
commits into
main
Choose a base branch
from
copilot/add-ranked-and-n-more-reporting-module
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
290295d
Initial plan
Copilot 49c6b01
Add ranked 'and n more' reporting module (LimitResults)
Copilot ba7bd9d
Address PR feedback: add query predicate, default maxResults/placehol…
Copilot d58a5a0
Address PR review: add orderBy/andMoreText defaults, convert to query…
Copilot 8477200
Format
MichaelRFairhurst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| /** | ||
| * A module for limiting the number of results reported per finding to at most `N`, while appending | ||
| * an "and M more" suffix to the message when there are additional results beyond the limit. | ||
| * | ||
| * This is useful for queries that find multiple related entities per finding (such as fields, | ||
| * parameters, or call sites) but where listing all of them would be too noisy. Instead, only the | ||
| * top `N` entities are reported (ordered by `orderBy()`), and the message notes how many were | ||
| * omitted. | ||
| * | ||
| * ## Usage | ||
| * | ||
| * Implement the `LimitResultsConfigSig` signature and instantiate the `LimitResults` module. Only | ||
| * `problem` and `message` are required — `placeholderString`, `orderBy`, `maxResults`, and | ||
| * `andMoreText` have sensible defaults. The instantiated module's `problems` query predicate is | ||
| * automatically part of the query output without any `from`/`where`/`select` boilerplate: | ||
| * | ||
| * ```ql | ||
| * module MyConfig implements LimitResultsConfigSig<MyFinding, MyEntity> { | ||
| * predicate problem(MyFinding finding, MyEntity entity) { | ||
| * entity = finding.getAnEntity() | ||
| * } | ||
| * | ||
| * bindingset[remaining] | ||
| * string message(MyFinding finding, MyEntity entity, string remaining) { | ||
| * result = "Finding " + finding.getName() + " has entity $@" + remaining + "." | ||
| * } | ||
| * } | ||
| * | ||
| * module Results = LimitResults<MyFinding, MyEntity, MyConfig>; | ||
| * ``` | ||
| */ | ||
|
|
||
| private import qtil.parameterization.SignatureTypes | ||
|
|
||
| /** | ||
| * A signature for configuring the `LimitResults` module. | ||
| * | ||
| * Only `problem` and `message` must be implemented. The predicates `placeholderString`, | ||
| * `orderBy`, `maxResults`, and `andMoreText` have defaults and may be overridden. | ||
| */ | ||
| signature module LimitResultsConfigSig<FiniteStringableType Finding, FiniteStringableType Entity> { | ||
| /** | ||
| * The relationship between findings and their associated entities. | ||
| * | ||
| * Defines which entities are relevant to a given finding. All entities satisfying this predicate | ||
| * will be counted, but only the top `maxResults()` (ordered by `orderBy()`) will be reported. | ||
| */ | ||
| predicate problem(Finding finding, Entity entity); | ||
|
|
||
| /** | ||
| * Builds the message for the finding, incorporating the "and N more" remaining string. | ||
| * | ||
| * The `remaining` parameter is either an empty string (when all entities are shown) or a string | ||
| * like `" (and 2 more)"` when some entities are omitted. The message should embed `remaining` | ||
| * appropriately, for example: `result = "Foo is broken" + remaining + "."`. | ||
| */ | ||
| bindingset[remaining] | ||
| string message(Finding finding, Entity entity, string remaining); | ||
|
|
||
| /** | ||
| * The display string for an entity. | ||
| * | ||
| * Used as the `entityStr` column in the `problems` query predicate output. Also used as the | ||
| * default ordering key — see `orderBy`. | ||
| * | ||
| * Defaults to `entity.toString()`. | ||
| */ | ||
| default string placeholderString(Entity entity) { result = entity.toString() } | ||
|
|
||
| /** | ||
| * The key to use when ordering entities within a finding (ascending). | ||
| * | ||
| * Entities with smaller order keys are reported first. When the total count exceeds | ||
| * `maxResults()`, only the first `maxResults()` entities by this ordering are reported. | ||
| * | ||
| * Defaults to `placeholderString(entity)`. | ||
| */ | ||
| default string orderBy(Entity entity) { result = placeholderString(entity) } | ||
|
|
||
| /** | ||
| * The maximum number of entities to report per finding. | ||
| * | ||
| * When the total number of entities for a finding exceeds this value, only the first | ||
| * `maxResults()` entities (by `orderBy()`) are reported, and the message includes the | ||
| * `andMoreText()` suffix indicating the number of omitted entities. | ||
| * | ||
| * Defaults to `3`. | ||
| */ | ||
| default int maxResults() { result = 3 } | ||
|
|
||
| /** | ||
| * The suffix appended to the message when `n` entities are omitted. | ||
| * | ||
| * The parameter `n` is the number of omitted entities (i.e. `total - maxResults()`). Override | ||
| * this to customise the "and N more" text, for example to use a different locale. | ||
| * | ||
| * Defaults to `" (and N more)"`. | ||
| */ | ||
| bindingset[n] | ||
| default string andMoreText(int n) { result = " (and " + n + " more)" } | ||
| } | ||
|
|
||
| /** | ||
| * A module that limits the number of results reported per finding, appending an "and N more" | ||
| * suffix when additional entities exist beyond the configured maximum. | ||
| * | ||
| * The `problems` query predicate is the main entry point for use in a query. When this module is | ||
| * instantiated as `module Results = LimitResults<...>`, the predicate `Results::problems` is | ||
| * automatically part of the query output — no `from`/`where`/`select` boilerplate is needed. | ||
| * | ||
| * See `LimitResultsConfigSig` for configuration details. | ||
| */ | ||
| module LimitResults<FiniteStringableType Finding, FiniteStringableType Entity, LimitResultsConfigSig<Finding, Entity> Config> { | ||
| /** | ||
| * A query predicate that reports findings alongside one of their top-ranked entities and a | ||
| * formatted message. This is the primary way to use this module in a query. | ||
| * | ||
| * Each result tuple `(finding, msg, entity, entityStr)` corresponds to one of the top-ranked | ||
| * entities for a finding. `entityStr` is `Config::placeholderString(entity)`, suitable for use | ||
| * as the placeholder text in a `select` column alongside `entity`. | ||
| * | ||
| * At most `Config::maxResults()` entities are reported per finding. | ||
| */ | ||
| query predicate problems(Finding finding, string msg, Entity entity, string entityStr) { | ||
| hasLimitedResult(finding, entity, msg) and | ||
| entityStr = Config::placeholderString(entity) | ||
| } | ||
|
|
||
| /** | ||
| * Holds for each finding and one of its top-ranked entities, providing the formatted message. | ||
| * | ||
| * At most `Config::maxResults()` entities are reported per finding. They are selected by ranking | ||
| * all entities satisfying `Config::problem(finding, entity)` in ascending order of | ||
| * `Config::orderBy(entity)`, and taking those with rank <= `Config::maxResults()`. | ||
| * | ||
| * The `message` is produced by `Config::message(finding, entity, remaining)`, where `remaining` | ||
| * is `Config::andMoreText(n)` (with `n = total - maxResults()`) if the total exceeds | ||
| * `Config::maxResults()`, or `""` otherwise. | ||
| */ | ||
| predicate hasLimitedResult(Finding finding, Entity entity, string message) { | ||
| exists(int total, int ranked, string remaining | | ||
| total = count(Entity e | Config::problem(finding, e)) and | ||
| entity = | ||
| rank[ranked](Entity e | Config::problem(finding, e) | | ||
| e order by Config::orderBy(e) | ||
| ) and | ||
| ranked <= Config::maxResults() and | ||
| ( | ||
| total > Config::maxResults() and | ||
| remaining = Config::andMoreText(total - Config::maxResults()) | ||
| or | ||
| total <= Config::maxResults() and remaining = "" | ||
| ) and | ||
| message = Config::message(finding, entity, remaining) | ||
| ) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /** | ||
| * Test data for LimitResultsTest.ql. | ||
| * | ||
| * Models a simple set of "bugs", each associated with a set of "fields". | ||
| * | ||
| * - BugA: 1 field (X) - fewer than maxResults | ||
| * - BugB: 3 fields (A, B, C) - exactly maxResults | ||
| * - BugC: 5 fields (A, B, C, D, E) - more than maxResults | ||
| */ | ||
| newtype TBug = | ||
| TBugA() or | ||
| TBugB() or | ||
| TBugC() | ||
|
|
||
| class Bug extends TBug { | ||
| string getName() { | ||
| this = TBugA() and result = "BugA" | ||
| or | ||
| this = TBugB() and result = "BugB" | ||
| or | ||
| this = TBugC() and result = "BugC" | ||
| } | ||
|
|
||
| string toString() { result = getName() } | ||
| } | ||
|
|
||
| newtype TBugField = | ||
| TBugFieldPair(string bugName, string fieldName) { | ||
| bugName = "BugA" and fieldName = "X" | ||
| or | ||
| bugName = "BugB" and fieldName = ["A", "B", "C"] | ||
| or | ||
| bugName = "BugC" and fieldName = ["A", "B", "C", "D", "E"] | ||
| } | ||
|
|
||
| class BugField extends TBugField { | ||
| string getBugName() { this = TBugFieldPair(result, _) } | ||
|
|
||
| string getFieldName() { this = TBugFieldPair(_, result) } | ||
|
|
||
| string toString() { result = getBugName() + "." + getFieldName() } | ||
|
|
||
| Bug getBug() { result.getName() = getBugName() } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| | BugA | BugA has field X | BugA.X | BugA.X | | ||
| | BugB | BugB has field A | BugB.A | BugB.A | | ||
| | BugB | BugB has field B | BugB.B | BugB.B | | ||
| | BugB | BugB has field C | BugB.C | BugB.C | | ||
| | BugC | BugC has field A (and 2 more) | BugC.A | BugC.A | | ||
| | BugC | BugC has field B (and 2 more) | BugC.B | BugC.B | | ||
| | BugC | BugC has field C (and 2 more) | BugC.C | BugC.C | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import qtil.results.LimitResults | ||
| import Bugs | ||
|
|
||
| module TestConfig implements LimitResultsConfigSig<Bug, BugField> { | ||
| predicate problem(Bug bug, BugField field) { field.getBug() = bug } | ||
|
|
||
| bindingset[remaining] | ||
| string message(Bug bug, BugField field, string remaining) { | ||
| result = bug.getName() + " has field " + field.getFieldName() + remaining | ||
| } | ||
| } | ||
|
|
||
| module Results = LimitResults<Bug, BugField, TestConfig>; | ||
|
|
||
| query predicate problems(Bug bug, string msg, BugField field, string fieldStr) { | ||
| Results::problems(bug, msg, field, fieldStr) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a default
string andMoreText(int total)predicateThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in d58a5a0 — added
bindingset[n] default string andMoreText(int n) { result = " (and " + n + " more)" }to the signature. ThehasLimitedResultpredicate now callsConfig::andMoreText(total - Config::maxResults())for the suffix.