Skip to content

Commit beccdce

Browse files
committed
C#: Refactor definitions query, add queries for ide search
This enables jump-to-definition and find-references in the VS Code extension, for C# source archives.
1 parent 01eeebc commit beccdce

File tree

5 files changed

+225
-163
lines changed

5 files changed

+225
-163
lines changed

csharp/ql/src/codeql-suites/csharp-lgtm-full.qls

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
- qlpack: codeql-csharp
33
- apply: lgtm-selectors.yml
44
from: codeql-suite-helpers
5+
# These are only for IDE use.
6+
- exclude:
7+
tags contain:
8+
- ide-contextual-queries/local-definitions
9+
- ide-contextual-queries/local-references

csharp/ql/src/definitions.ql

Lines changed: 4 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -6,167 +6,8 @@
66
* @id cs/jump-to-definition
77
*/
88

9-
import csharp
9+
import definitions
1010

11-
/** An element with an associated definition. */
12-
abstract class Use extends @type_mention_parent {
13-
/**
14-
* Holds if this element is at the specified location.
15-
* The location spans column `startcolumn` of line `startline` to
16-
* column `endcolumn` of line `endline` in file `filepath`.
17-
* For more information, see
18-
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
19-
*/
20-
predicate hasLocationInfo(
21-
string filepath, int startline, int startcolumn, int endline, int endcolumn
22-
) {
23-
exists(Location l |
24-
l = this.(Element).getLocation() or
25-
l = this.(TypeMention).getLocation()
26-
|
27-
filepath = l.getFile().getAbsolutePath() and
28-
startline = l.getStartLine() and
29-
startcolumn = l.getStartColumn() and
30-
endline = l.getEndLine() and
31-
endcolumn = l.getEndColumn()
32-
)
33-
}
34-
35-
/** Gets the definition associated with this element. */
36-
abstract Declaration getDefinition();
37-
38-
/**
39-
* Gets the type of use.
40-
*
41-
* - `"M"`: call.
42-
* - `"V"`: variable use.
43-
* - `"T"`: type reference.
44-
*/
45-
abstract string getUseType();
46-
47-
/** Gets a textual representation of this element. */
48-
abstract string toString();
49-
}
50-
51-
/** A method call/access. */
52-
class MethodUse extends Use, QualifiableExpr {
53-
MethodUse() {
54-
this instanceof MethodCall or
55-
this instanceof MethodAccess
56-
}
57-
58-
/** Gets the qualifier of this method use, if any. */
59-
private Expr getFormatQualifier() {
60-
(
61-
if this.getQualifiedDeclaration().(Method).isExtensionMethod()
62-
then result = this.(MethodCall).getArgument(0)
63-
else result = this.getQualifier()
64-
) and
65-
not result.isImplicit()
66-
}
67-
68-
override predicate hasLocationInfo(
69-
string filepath, int startline, int startcolumn, int endline, int endcolumn
70-
) {
71-
Use.super.hasLocationInfo(filepath, _, _, _, _) and
72-
endline = startline and
73-
endcolumn = startcolumn + this.getQualifiedDeclaration().getName().length() - 1 and
74-
(
75-
exists(Location ql | ql = this.getFormatQualifier().getLocation() |
76-
startline = ql.getEndLine() and
77-
startcolumn = ql.getEndColumn() + 2
78-
)
79-
or
80-
not exists(this.getFormatQualifier()) and
81-
exists(Location l | l = this.getLocation() |
82-
startline = l.getStartLine() and
83-
startcolumn = l.getStartColumn()
84-
)
85-
)
86-
}
87-
88-
override Method getDefinition() { result = getQualifiedDeclaration().getSourceDeclaration() }
89-
90-
override string getUseType() { result = "M" }
91-
92-
override string toString() { result = this.(Expr).toString() }
93-
}
94-
95-
/** An access. */
96-
class AccessUse extends Access, Use {
97-
AccessUse() {
98-
not this.getTarget().(Parameter).getCallable() instanceof Accessor and
99-
not this = any(LocalVariableDeclAndInitExpr d).getLValue() and
100-
not this.isImplicit() and
101-
not this instanceof MethodAccess and // handled by `MethodUse`
102-
not this instanceof TypeAccess and // handled by `TypeMentionUse`
103-
not this.(FieldAccess).getParent() instanceof Field and // Enum initializer
104-
not this.(FieldAccess).getParent().getParent() instanceof Field and // Field initializer
105-
not this.(PropertyAccess).getParent().getParent() instanceof Property // Property initializer
106-
}
107-
108-
/** Gets the qualifier of this acccess, if any. */
109-
private Expr getFormatQualifier() {
110-
result = this.(QualifiableExpr).getQualifier() and
111-
not result.isImplicit()
112-
}
113-
114-
override predicate hasLocationInfo(
115-
string filepath, int startline, int startcolumn, int endline, int endcolumn
116-
) {
117-
exists(Location ql | ql = this.getFormatQualifier().getLocation() |
118-
startline = ql.getEndLine() and
119-
startcolumn = ql.getEndColumn() + 2 and
120-
Use.super.hasLocationInfo(filepath, _, _, endline, endcolumn)
121-
)
122-
or
123-
not exists(this.getFormatQualifier()) and
124-
Use.super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
125-
}
126-
127-
override Declaration getDefinition() { result = this.getTarget().getSourceDeclaration() }
128-
129-
override string getUseType() {
130-
if this instanceof Call or this instanceof LocalFunctionAccess
131-
then result = "M"
132-
else
133-
if this instanceof BaseAccess or this instanceof ThisAccess
134-
then result = "T"
135-
else result = "V"
136-
}
137-
138-
override string toString() { result = this.(Access).toString() }
139-
}
140-
141-
/** A type mention. */
142-
class TypeMentionUse extends Use, TypeMention {
143-
TypeMentionUse() {
144-
// In type mentions such as `T[]`, `T?`, `T*`, and `(S, T)`, we only want
145-
// uses for the nested type mentions
146-
forall(TypeMention child, Type t |
147-
child.getParent() = this and
148-
t = this.getType()
149-
|
150-
not t instanceof ArrayType and
151-
not t instanceof NullableType and
152-
not t instanceof PointerType and
153-
not t instanceof TupleType
154-
)
155-
}
156-
157-
override Type getDefinition() { result = this.getType().getSourceDeclaration() }
158-
159-
override string getUseType() {
160-
if this.getTarget() instanceof ObjectCreation
161-
then result = "M" // constructor call
162-
else result = "T"
163-
}
164-
165-
override string toString() { result = TypeMention.super.toString() }
166-
}
167-
168-
from Use use, Declaration definition
169-
where
170-
definition = use.getDefinition() and
171-
definition.fromSource()
172-
select use, definition, use.getUseType()
11+
from Use use, Declaration def, string kind
12+
where def = definitionOf(use, kind)
13+
select use, def, kind

csharp/ql/src/definitions.qll

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/**
2+
* Provides classes and predicates related to jump-to-definition links
3+
* in the code viewer.
4+
*/
5+
6+
import csharp
7+
8+
/** An element with an associated definition. */
9+
abstract class Use extends @type_mention_parent {
10+
/**
11+
* Holds if this element is at the specified location.
12+
* The location spans column `startcolumn` of line `startline` to
13+
* column `endcolumn` of line `endline` in file `filepath`.
14+
* For more information, see
15+
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
16+
*/
17+
predicate hasLocationInfo(
18+
string filepath, int startline, int startcolumn, int endline, int endcolumn
19+
) {
20+
exists(Location l |
21+
l = this.(Element).getLocation() or
22+
l = this.(TypeMention).getLocation()
23+
|
24+
filepath = l.getFile().getAbsolutePath() and
25+
startline = l.getStartLine() and
26+
startcolumn = l.getStartColumn() and
27+
endline = l.getEndLine() and
28+
endcolumn = l.getEndColumn()
29+
)
30+
}
31+
32+
/** Gets the definition associated with this element. */
33+
abstract Declaration getDefinition();
34+
35+
/**
36+
* Gets the type of use.
37+
*
38+
* - `"M"`: call.
39+
* - `"V"`: variable use.
40+
* - `"T"`: type reference.
41+
*/
42+
abstract string getUseType();
43+
44+
/** Gets a textual representation of this element. */
45+
abstract string toString();
46+
}
47+
48+
/** A method call/access. */
49+
class MethodUse extends Use, QualifiableExpr {
50+
MethodUse() {
51+
this instanceof MethodCall or
52+
this instanceof MethodAccess
53+
}
54+
55+
/** Gets the qualifier of this method use, if any. */
56+
private Expr getFormatQualifier() {
57+
(
58+
if this.getQualifiedDeclaration().(Method).isExtensionMethod()
59+
then result = this.(MethodCall).getArgument(0)
60+
else result = this.getQualifier()
61+
) and
62+
not result.isImplicit()
63+
}
64+
65+
override predicate hasLocationInfo(
66+
string filepath, int startline, int startcolumn, int endline, int endcolumn
67+
) {
68+
Use.super.hasLocationInfo(filepath, _, _, _, _) and
69+
endline = startline and
70+
endcolumn = startcolumn + this.getQualifiedDeclaration().getName().length() - 1 and
71+
(
72+
exists(Location ql | ql = this.getFormatQualifier().getLocation() |
73+
startline = ql.getEndLine() and
74+
startcolumn = ql.getEndColumn() + 2
75+
)
76+
or
77+
not exists(this.getFormatQualifier()) and
78+
exists(Location l | l = this.getLocation() |
79+
startline = l.getStartLine() and
80+
startcolumn = l.getStartColumn()
81+
)
82+
)
83+
}
84+
85+
override Method getDefinition() { result = getQualifiedDeclaration().getSourceDeclaration() }
86+
87+
override string getUseType() { result = "M" }
88+
89+
override string toString() { result = this.(Expr).toString() }
90+
}
91+
92+
/** An access. */
93+
class AccessUse extends Access, Use {
94+
AccessUse() {
95+
not this.getTarget().(Parameter).getCallable() instanceof Accessor and
96+
not this = any(LocalVariableDeclAndInitExpr d).getLValue() and
97+
not this.isImplicit() and
98+
not this instanceof MethodAccess and // handled by `MethodUse`
99+
not this instanceof TypeAccess and // handled by `TypeMentionUse`
100+
not this.(FieldAccess).getParent() instanceof Field and // Enum initializer
101+
not this.(FieldAccess).getParent().getParent() instanceof Field and // Field initializer
102+
not this.(PropertyAccess).getParent().getParent() instanceof Property // Property initializer
103+
}
104+
105+
/** Gets the qualifier of this acccess, if any. */
106+
private Expr getFormatQualifier() {
107+
result = this.(QualifiableExpr).getQualifier() and
108+
not result.isImplicit()
109+
}
110+
111+
override predicate hasLocationInfo(
112+
string filepath, int startline, int startcolumn, int endline, int endcolumn
113+
) {
114+
exists(Location ql | ql = this.getFormatQualifier().getLocation() |
115+
startline = ql.getEndLine() and
116+
startcolumn = ql.getEndColumn() + 2 and
117+
Use.super.hasLocationInfo(filepath, _, _, endline, endcolumn)
118+
)
119+
or
120+
not exists(this.getFormatQualifier()) and
121+
Use.super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
122+
}
123+
124+
override Declaration getDefinition() { result = this.getTarget().getSourceDeclaration() }
125+
126+
override string getUseType() {
127+
if this instanceof Call or this instanceof LocalFunctionAccess
128+
then result = "M"
129+
else
130+
if this instanceof BaseAccess or this instanceof ThisAccess
131+
then result = "T"
132+
else result = "V"
133+
}
134+
135+
override string toString() { result = this.(Access).toString() }
136+
}
137+
138+
/** A type mention. */
139+
class TypeMentionUse extends Use, TypeMention {
140+
TypeMentionUse() {
141+
// In type mentions such as `T[]`, `T?`, `T*`, and `(S, T)`, we only want
142+
// uses for the nested type mentions
143+
forall(TypeMention child, Type t |
144+
child.getParent() = this and
145+
t = this.getType()
146+
|
147+
not t instanceof ArrayType and
148+
not t instanceof NullableType and
149+
not t instanceof PointerType and
150+
not t instanceof TupleType
151+
)
152+
}
153+
154+
override Type getDefinition() { result = this.getType().getSourceDeclaration() }
155+
156+
override string getUseType() {
157+
if this.getTarget() instanceof ObjectCreation
158+
then result = "M" // constructor call
159+
else result = "T"
160+
}
161+
162+
override string toString() { result = TypeMention.super.toString() }
163+
}
164+
165+
/**
166+
* Gets an element, of kind `kind`, that element `e` uses, if any.
167+
*/
168+
cached
169+
Declaration definitionOf(Use use, string kind) {
170+
result = use.getDefinition() and
171+
result.fromSource() and
172+
kind = use.getUseType()
173+
}
174+
175+
/**
176+
* Returns an appropriately encoded version of a filename `name`
177+
* passed by the VS Code extension in order to coincide with the
178+
* output of `.getFile()` on locatable entities.
179+
*/
180+
cached
181+
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }

csharp/ql/src/localDefinitions.ql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @name Jump-to-definition links
3+
* @description Generates use-definition pairs that provide the data
4+
* for jump-to-definition in the code viewer.
5+
* @kind definitions
6+
* @id cs/ide-jump-to-definition
7+
* @tags ide-contextual-queries/local-definitions
8+
*/
9+
10+
import definitions
11+
12+
external string selectedSourceFile();
13+
14+
from Use e, Declaration def, string kind, string filepath
15+
where
16+
def = definitionOf(e, kind) and
17+
e.hasLocationInfo(filepath, _, _, _, _) and
18+
filepath = getEncodedFile(selectedSourceFile()).getAbsolutePath()
19+
select e, def, kind

csharp/ql/src/localReferences.ql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @name Find-references links
3+
* @description Generates use-definition pairs that provide the data
4+
* for find-references in the code viewer.
5+
* @kind definitions
6+
* @id cs/ide-find-references
7+
* @tags ide-contextual-queries/local-references
8+
*/
9+
10+
import definitions
11+
12+
external string selectedSourceFile();
13+
14+
from Use e, Declaration def, string kind
15+
where def = definitionOf(e, kind) and def.getFile() = getEncodedFile(selectedSourceFile())
16+
select e, def, kind

0 commit comments

Comments
 (0)