Skip to content

Commit af112a0

Browse files
committed
QL: Add query detecting suspiciously missing parameters from the QLDoc of a predicate
1 parent 7a9a9d8 commit af112a0

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* @name Missing QLDoc for parameter
3+
* @description It is suspicious when a predicate has a parameter that is
4+
* unmentioned in the qldoc, and the qldoc contains a
5+
* code-fragment mentioning a non-parameter.
6+
* @kind problem
7+
* @problem.severity warning
8+
* @precision high
9+
* @id ql/missing-parameter-qldoc
10+
* @tags maintainability
11+
*/
12+
13+
import ql
14+
15+
/**
16+
* Gets a fragment enclosed in backticks (`) from a QLDoc of the predicate `p`.
17+
* Skips code-blocks that are triple-quoted.
18+
*/
19+
private string getACodeFragment(Predicate p) {
20+
result = p.getQLDoc().getContents().regexpFind("`([^`]*)`(?!`)", _, _)
21+
}
22+
23+
/** Gets a parameter name from `p`. */
24+
private string getAParameterName(Predicate p) { result = p.getParameter(_).getName() }
25+
26+
/**
27+
* Gets the name of a parameter of `p` that is mentioned in any way in the QLDoc of `p`.
28+
* Also includes names that are mentioned in non-code fragments.
29+
*/
30+
private string getADocumentedParameter(Predicate p) {
31+
result = p.getQLDoc().getContents().regexpFind("\\b\\w[\\w_]*\\b", _, _) and
32+
result.toLowerCase() = getAParameterName(p).toLowerCase()
33+
}
34+
35+
/**
36+
* Get something that looks like a parameter name from the QLDoc,
37+
* but which is not a parameter of `p`.
38+
*/
39+
private string getAMentionedNonParameter(Predicate p) {
40+
exists(string fragment | fragment = getACodeFragment(p) |
41+
result = fragment.substring(1, fragment.length() - 1)
42+
) and
43+
result.regexpMatch("^[a-z]\\w+$") and
44+
not result.toLowerCase() = getAParameterName(p).toLowerCase() and
45+
not result = ["true", "false", "NaN"] and // keywords
46+
not result.regexpMatch("\\d+") and // numbers
47+
// predicates get mentioned all the time, it's fine.
48+
not result =
49+
any(Predicate pred | pred.getLocation().getFile() = p.getLocation().getFile()).getName() and
50+
// classes get mentioned all the time, it's fine.
51+
not result =
52+
any(TypeExpr t | t.getLocation().getFile() = p.getLocation().getFile())
53+
.getResolvedType()
54+
.getName()
55+
}
56+
57+
/** Gets a parameter name from `p` that is not mentioned in the qldoc. */
58+
private string getAnUndocumentedParameter(Predicate p) {
59+
result = getAParameterName(p) and
60+
not result.toLowerCase() = getADocumentedParameter(p).toLowerCase() and
61+
not result = ["config", "conf", "cfg"] // DataFlow configurations are often undocumented, and that's fine.
62+
}
63+
64+
/** Holds if `p` has documented parameters, but `param` is undocumented */
65+
private predicate missingDocumentation(Predicate p, string param) {
66+
param = getAnUndocumentedParameter(p) and
67+
exists(getADocumentedParameter(p))
68+
}
69+
70+
/** Gets the one string containing the undocumented parameters from `p` */
71+
private string getUndocumentedParameters(Predicate p) {
72+
result = strictconcat(string param | missingDocumentation(p, param) | param, ", or ")
73+
}
74+
75+
/** Gets the parameter-like names mentioned in the QLDoc of `p` that are not parameters. */
76+
private string getMentionedNonParameters(Predicate p) {
77+
result = strictconcat(string param | param = getAMentionedNonParameter(p) | param, ", and ")
78+
}
79+
80+
from Predicate p
81+
select p,
82+
"The QLDoc has no documentation for " + getUndocumentedParameters(p) + ", but the QLDoc mentions "
83+
+ getMentionedNonParameters(p)

0 commit comments

Comments
 (0)