Skip to content

Commit 70ae728

Browse files
Add unique-command-use.ql
1 parent 396fdb8 commit 70ae728

File tree

1 file changed

+93
-0
lines changed

1 file changed

+93
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @name A VS Code command should not be used in multiple locations
3+
* @kind problem
4+
* @problem.severity warning
5+
* @id vscode-codeql/unique-command-use
6+
* @description Using each VS Code command from only one location makes
7+
* our telemtry more useful because we can differentiate more user
8+
* interactions and know which features of the UI users are using.
9+
*/
10+
11+
import javascript
12+
13+
abstract class CommandUsage extends Locatable {
14+
abstract string getCommandName();
15+
16+
predicate isUsedFromOtherPlace() {
17+
exists(CommandUsage e | e != this and e.getCommandName() = this.getCommandName())
18+
}
19+
20+
predicate isFirstUsage() {
21+
forall(CommandUsage e | e.getCommandName() = this.getCommandName() |
22+
e.getLocationOrdinal() >= this.getLocationOrdinal()
23+
)
24+
}
25+
26+
string getLocationOrdinal() {
27+
result =
28+
this.getFile().getRelativePath() + ":" + this.getLocation().getStartLine() + ":" +
29+
this.getLocation().getStartColumn()
30+
}
31+
}
32+
33+
class CommandUsageCallExpr extends CommandUsage, CallExpr {
34+
CommandUsageCallExpr() {
35+
this.getCalleeName() = "executeCommand" and
36+
this.getArgument(0).(StringLiteral).getValue().matches("%codeQL%") and
37+
not this.getFile().getRelativePath().matches("extensions/ql-vscode/test/%")
38+
}
39+
40+
override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() }
41+
}
42+
43+
class CommandUsagePackageJsonMenuItem extends CommandUsage, JsonObject {
44+
CommandUsagePackageJsonMenuItem() {
45+
this.getFile().getBaseName() = "package.json" and
46+
exists(this.getPropValue("command")) and
47+
exists(JsonObject topObject, string menuName |
48+
not exists(topObject.getParent()) and
49+
topObject
50+
.getPropValue("contributes")
51+
.getPropValue("menus")
52+
.getPropValue(menuName)
53+
.getElementValue(_) = this and
54+
menuName != "commandPalette"
55+
)
56+
}
57+
58+
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
59+
}
60+
61+
predicate isDisabledInCommandPalette(string commandName) {
62+
exists(JsonObject topObject, JsonObject commandPaletteObject |
63+
not exists(topObject.getParent()) and
64+
topObject
65+
.getPropValue("contributes")
66+
.getPropValue("menus")
67+
.getPropValue("commandPalette")
68+
.getElementValue(_) = commandPaletteObject and
69+
commandPaletteObject.getPropValue("command").getStringValue() = commandName and
70+
commandPaletteObject.getPropValue("when").getStringValue() = "false"
71+
)
72+
}
73+
74+
class CommandUsagePackageJsonCommandPalette extends CommandUsage, JsonObject {
75+
CommandUsagePackageJsonCommandPalette() {
76+
this.getFile().getBaseName() = "package.json" and
77+
exists(this.getPropValue("command")) and
78+
exists(JsonObject topObject |
79+
not exists(topObject.getParent()) and
80+
topObject.getPropValue("contributes").getPropValue("commands").getElementValue(_) = this
81+
) and
82+
not isDisabledInCommandPalette(this.getPropValue("command").getStringValue())
83+
}
84+
85+
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
86+
}
87+
88+
from CommandUsage e
89+
where
90+
e.isUsedFromOtherPlace() and
91+
not e.isFirstUsage()
92+
select e, "The " + e.getCommandName() + " command is used from another location"
93+

0 commit comments

Comments
 (0)