44 *
55 * This is useful for queries that find multiple related entities per finding (such as fields,
66 * parameters, or call sites) but where listing all of them would be too noisy. Instead, only the
7- * top `N` entities are reported (ordered by a configurable key ), and the message notes how many
7+ * top `N` entities are reported (ordered by `placeholderString()` ), and the message notes how many
88 * were omitted.
99 *
1010 * ## Usage
1111 *
12- * Implement the `LimitResultsConfigSig` signature and instantiate the `LimitResults` module:
12+ * Implement the `LimitResultsConfigSig` signature and instantiate the `LimitResults` module. Only
13+ * `problem` and `message` are required — `placeholderString` and `maxResults` have sensible
14+ * defaults:
1315 *
1416 * ```ql
1517 * module MyConfig implements LimitResultsConfigSig<MyFinding, MyEntity> {
1618 * predicate problem(MyFinding finding, MyEntity entity) {
1719 * entity = finding.getAnEntity()
1820 * }
1921 *
22+ * bindingset[remaining]
2023 * string message(MyFinding finding, MyEntity entity, string remaining) {
2124 * result = "Finding $@ has entity $@" + remaining + "."
2225 * }
23- *
24- * string orderBy(MyEntity entity) { result = entity.getName() }
25- *
26- * int maxResults() { result = 3 }
2726 * }
2827 *
2928 * module Results = LimitResults<MyFinding, MyEntity, MyConfig>;
29+ * ```
30+ *
31+ * The instantiated module exposes a `problems` query predicate that can be used directly as
32+ * the output of a query without any `from`/`where`/`select` boilerplate:
3033 *
31- * from MyFinding finding, MyEntity entity, string message
32- * where Results::hasLimitedResult(finding, entity, message)
33- * select finding, message, entity, entity.getName()
34+ * ```ql
35+ * module Results = LimitResults<MyFinding, MyEntity, MyConfig>;
36+ * // The query predicate Results::problems(...) is automatically part of the query output.
3437 * ```
3538 */
3639
@@ -39,16 +42,16 @@ private import qtil.parameterization.SignatureTypes
3942/**
4043 * A signature for configuring the `LimitResults` module.
4144 *
42- * Implement this signature in a module to define the relationship between findings and entities,
43- * the message format, the ordering of entities, and the maximum number of results to show per
44- * finding.
45+ * Only `problem` and `message` must be implemented. The predicates `placeholderString` and
46+ * `maxResults` have defaults and may be overridden.
4547 */
46- signature module LimitResultsConfigSig< FiniteType Finding, FiniteType Entity> {
48+ signature module LimitResultsConfigSig< FiniteStringableType Finding, FiniteStringableType Entity> {
4749 /**
4850 * The relationship between findings and their associated entities.
4951 *
5052 * Defines which entities are relevant to a given finding. All entities satisfying this predicate
51- * will be counted, but only the top `maxResults()` (ordered by `orderBy()`) will be reported.
53+ * will be counted, but only the top `maxResults()` (ordered by `placeholderString()`) will be
54+ * reported.
5255 */
5356 predicate problem ( Finding finding , Entity entity ) ;
5457
@@ -63,40 +66,59 @@ signature module LimitResultsConfigSig<FiniteType Finding, FiniteType Entity> {
6366 string message ( Finding finding , Entity entity , string remaining ) ;
6467
6568 /**
66- * The key to use when ordering entities within a finding (ascending).
69+ * The display string for an entity, also used as the ordering key (ascending).
6770 *
68- * Entities with smaller order keys are reported first. When the total count exceeds
71+ * Entities with smaller placeholder strings are reported first. When the total count exceeds
6972 * `maxResults()`, only the first `maxResults()` entities by this ordering are reported.
73+ *
74+ * Defaults to `entity.toString()`.
7075 */
71- string orderBy ( Entity entity ) ;
76+ default string placeholderString ( Entity entity ) { result = entity . toString ( ) }
7277
7378 /**
7479 * The maximum number of entities to report per finding.
7580 *
7681 * When the total number of entities for a finding exceeds this value, only the first
77- * `maxResults()` entities (by `orderBy ()`) are reported, and the message includes a
82+ * `maxResults()` entities (by `placeholderString ()`) are reported, and the message includes a
7883 * `" (and N more)"` suffix indicating the number of omitted entities.
84+ *
85+ * Defaults to `3`.
7986 */
80- int maxResults ( ) ;
87+ default int maxResults ( ) { result = 3 }
8188}
8289
8390/**
8491 * A module that limits the number of results reported per finding, appending an "and N more"
8592 * suffix when additional entities exist beyond the configured maximum.
8693 *
87- * Use `hasLimitedResult` as the body of a `where` clause in a `select` statement. Each result
88- * tuple corresponds to one of the top-ranked entities for a finding, together with the formatted
89- * message that includes the remaining count suffix when applicable .
94+ * The `problems` query predicate is the main entry point for use in a query. When this module is
95+ * instantiated as `module Results = LimitResults<...>`, the predicate `Results::problems` is
96+ * automatically part of the query output — no `from`/`where`/`select` boilerplate is needed .
9097 *
9198 * See `LimitResultsConfigSig` for configuration details.
9299 */
93- module LimitResults< FiniteType Finding, FiniteType Entity, LimitResultsConfigSig< Finding , Entity > Config> {
100+ module LimitResults< FiniteStringableType Finding, FiniteStringableType Entity, LimitResultsConfigSig< Finding , Entity > Config> {
101+ /**
102+ * A query predicate that reports findings alongside one of their top-ranked entities and a
103+ * formatted message. This is the primary way to use this module in a query.
104+ *
105+ * Each result tuple `(finding, msg, entity, entityStr)` corresponds to one of the top-ranked
106+ * entities for a finding. `entityStr` is `Config::placeholderString(entity)`, suitable for use
107+ * as the placeholder text in a `select` column alongside `entity`.
108+ *
109+ * At most `Config::maxResults()` entities are reported per finding.
110+ */
111+ query predicate problems ( Finding finding , string msg , Entity entity , string entityStr ) {
112+ hasLimitedResult ( finding , entity , msg ) and
113+ entityStr = Config:: placeholderString ( entity )
114+ }
115+
94116 /**
95117 * Holds for each finding and one of its top-ranked entities, providing the formatted message.
96118 *
97119 * At most `Config::maxResults()` entities are reported per finding. They are selected by ranking
98120 * all entities satisfying `Config::problem(finding, entity)` in ascending order of
99- * `Config::orderBy (entity)`, and taking those with rank <= `Config::maxResults()`.
121+ * `Config::placeholderString (entity)`, and taking those with rank <= `Config::maxResults()`.
100122 *
101123 * The `message` is produced by `Config::message(finding, entity, remaining)`, where `remaining`
102124 * is `" (and N more)"` if the total exceeds `Config::maxResults()`, or `""` otherwise.
@@ -105,7 +127,9 @@ module LimitResults<FiniteType Finding, FiniteType Entity, LimitResultsConfigSig
105127 exists ( int total , int ranked , string remaining |
106128 total = count ( Entity e | Config:: problem ( finding , e ) ) and
107129 entity =
108- rank [ ranked ] ( Entity e | Config:: problem ( finding , e ) | e order by Config:: orderBy ( e ) ) and
130+ rank [ ranked ] ( Entity e | Config:: problem ( finding , e ) |
131+ e order by Config:: placeholderString ( e )
132+ ) and
109133 ranked <= Config:: maxResults ( ) and
110134 (
111135 total > Config:: maxResults ( ) and
0 commit comments