Skip to content

Commit f3173ca

Browse files
author
Max Schaefer
committed
JavaScript: Add a few unit tests for API graphs.
1 parent 985399f commit f3173ca

70 files changed

Lines changed: 385 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| reexport/lib/utils.js:1:38:1:120 | /* use ... )))) */ | def (member util (member exports (module reexport))) has no outgoing edge labelled member id; it has no outgoing edges at all. |
2+
| typed/index.ts:14:36:14:106 | /* use ... )))) */ | use (module mongodb) has no outgoing edge labelled member Collection; it does have outgoing edges labelled member exports. |
3+
| typed/index.ts:22:39:22:119 | /* def ... )))) */ | use (module mongoose) has no outgoing edge labelled member Model; it does have outgoing edges labelled member exports. |
4+
| typed/index.ts:23:39:23:119 | /* def ... )))) */ | use (module mongoose) has no outgoing edge labelled member Query; it does have outgoing edges labelled member exports. |
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
* A test query that verifies assertions about the API graph embedded in source-code comments.
3+
*
4+
* An assertion is a comment of the form `def <path>` or `use <path>`, and asserts that
5+
* there is a def/use feature reachable from the root along the given path (described using
6+
* s-expression syntax), and its associated data-flow node must start on the same line as the
7+
* comment.
8+
*
9+
* We also support negative assertions of the form `!def <path>` or `!use <path>`, which assert
10+
* that there _isn't_ a node with the given path on the same line.
11+
*
12+
* The query only produces output for failed assertions, meaning that it should have no output
13+
* under normal circumstances.
14+
*
15+
* Note that this query file isn't itself meant to be run as a test; instead, the `.qlref`s
16+
* referring to it from inside the individual test directories should be run. However, when
17+
* all tests are run this test will also be run, hence we need to check in a (somewhat nonsensical)
18+
* `.expected` file for it as well.
19+
*/
20+
21+
import javascript
22+
23+
private DataFlow::Node getNode(API::Feature nd, string kind) {
24+
kind = "def" and
25+
result = nd.getARhs()
26+
or
27+
kind = "use" and
28+
result = nd.getAUse()
29+
}
30+
31+
private string getLoc(DataFlow::Node nd) {
32+
exists(string filepath, int startline |
33+
nd.hasLocationInfo(filepath, startline, _, _, _) and
34+
result = filepath + ":" + startline
35+
)
36+
}
37+
38+
/**
39+
* An assertion matching a data-flow node against an API-graph feature.
40+
*/
41+
class Assertion extends Comment {
42+
string polarity;
43+
string expectedKind;
44+
string expectedLoc;
45+
46+
Assertion() {
47+
exists(string txt, string rex |
48+
txt = this.getText().trim() and
49+
rex = "(!?)(def|use) .*"
50+
|
51+
polarity = txt.regexpCapture(rex, 1) and
52+
expectedKind = txt.regexpCapture(rex, 2) and
53+
expectedLoc = getFile().getAbsolutePath() + ":" + getLocation().getStartLine()
54+
)
55+
}
56+
57+
string getEdgeLabel(int i) { result = this.getText().regexpFind("(?<=\\()[^()]+", i, _).trim() }
58+
59+
int getPathLength() { result = max(int i | exists(getEdgeLabel(i))) + 1 }
60+
61+
API::Feature lookup(int i) {
62+
i = getPathLength() and
63+
result = API::root()
64+
or
65+
result = lookup(i + 1).getASuccessor(getEdgeLabel(i))
66+
}
67+
68+
predicate isNegative() { polarity = "!" }
69+
70+
predicate holds() { getLoc(getNode(lookup(0), expectedKind)) = expectedLoc }
71+
72+
string tryExplainFailure() {
73+
exists(int i, API::Feature nd, string prefix, string suffix |
74+
nd = lookup(i) and
75+
i > 0 and
76+
not exists(lookup([0 .. i - 1])) and
77+
prefix = nd + " has no outgoing edge labelled " + getEdgeLabel(i - 1) + ";" and
78+
if exists(nd.getASuccessor())
79+
then
80+
suffix =
81+
"it does have outgoing edges labelled " +
82+
concat(string lbl | exists(nd.getASuccessor(lbl)) | lbl, ", ") + "."
83+
else suffix = "it has no outgoing edges at all."
84+
|
85+
result = prefix + " " + suffix
86+
)
87+
or
88+
exists(API::Feature nd, string kind | nd = lookup(0) |
89+
exists(getNode(nd, kind)) and
90+
not exists(getNode(nd, expectedKind)) and
91+
result = "Expected " + expectedKind + " node, but found " + kind + " node."
92+
)
93+
or
94+
exists(DataFlow::Node nd | nd = getNode(lookup(0), expectedKind) |
95+
not getLoc(nd) = expectedLoc and
96+
result = "Node not found on this line (but there is one on line " + min(getLoc(nd)) + ")."
97+
)
98+
}
99+
100+
string explainFailure() {
101+
if isNegative()
102+
then (
103+
holds() and
104+
result = "Negative assertion failed."
105+
) else (
106+
not holds() and
107+
(
108+
result = tryExplainFailure()
109+
or
110+
not exists(tryExplainFailure()) and
111+
result = "Positive assertion failed for unknown reasons."
112+
)
113+
)
114+
}
115+
}
116+
117+
from Assertion a
118+
select a, a.explainFailure()

javascript/ql/test/library-tests/ApiGraphs/argprops/VerifyAssertions.expected

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
library-tests/ApiGraphs/VerifyAssertions.ql
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const assert = require("assert");
2+
3+
let o = {
4+
foo: 23 /* def (member foo (parameter 0 (member equal (member exports (module assert))))) */
5+
};
6+
assert.equal(o, o);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "argprops"
3+
}

javascript/ql/test/library-tests/ApiGraphs/async-await/VerifyAssertions.expected

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
library-tests/ApiGraphs/VerifyAssertions.ql
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const fs = require('fs-extra');
2+
3+
module.exports.foo = async function foo() {
4+
return await fs.copy('/tmp/myfile', '/tmp/mynewfile'); /* use (promised (return (member copy (member exports (module fs-extra))))) */ /* def (promised (return (member foo (member exports (module async-await))))) */
5+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "async-await",
3+
"dependencies": {
4+
"fs-extra": "*"
5+
}
6+
}

0 commit comments

Comments
 (0)