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