Skip to content

Commit 0320f0f

Browse files
committed
add query for detecting suspisous method names in TypeScript
1 parent 9a8b622 commit 0320f0f

File tree

9 files changed

+217
-0
lines changed

9 files changed

+217
-0
lines changed

change-notes/1.23/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
|---------------------------------------------------------------------------|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1717
| Unused index variable (`js/unused-index-variable`) | correctness | Highlights loops that iterate over an array, but do not use the index variable to access array elements, indicating a possible typo or logic error. Results are shown on LGTM by default. |
1818
| Loop bound injection (`js/loop-bound-injection`) | security, external/cwe/cwe-834 | Highlights loops where a user-controlled object with an arbitrary .length value can trick the server to loop indefinitely. Results are not shown on LGTM by default. |
19+
| Suspicious method name (`js/suspicious-method-name`) | correctness, typescript, methods | Highlights suspiciously named methods where the developer likely meant to write a constructor or function. Results are shown on LGTM by default. |
1920

2021
## Changes to existing queries
2122

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
In TypeScript the keywords <code>constructor</code> and <code>new</code> are
8+
used to declare constructors in classes and interfaces respectively.
9+
However, by using the wrong keyword a programmer can accidentally declare e.g.
10+
a method called <code>constructor</code> inside an interface.
11+
Similarly the keyword <code>function</code> is used to declare functions in
12+
some contexts, however, using the keyword <code>function</code> inside a class
13+
or interface results in declaring a method called <code>function</code>.
14+
</p>
15+
16+
</overview>
17+
<recommendation>
18+
19+
<p>
20+
Declare classes as classes and not as interfaces.
21+
Use the keyword <code>constructor</code> to declare constructors in a class,
22+
use the keyword <code>new</code> to declare constructors inside interfaces,
23+
and don't use <code>function</code> when declaring an interface that is a
24+
function.
25+
</p>
26+
27+
</recommendation>
28+
<example>
29+
30+
<p>
31+
The below example declares an interface <code>Point</code> with 2 fields
32+
and a method called <code>constructor</code>. The interface does not declare
33+
a class <code>Point</code> with a constructor, which was likely what the
34+
developer meant to create.
35+
</p>
36+
<sample src="examples/SuspiciousMethodName.ts" />
37+
38+
<p>
39+
The below example is a fixed version of the above, where the interface is
40+
instead declared as a class, thereby describing the type the developer meant
41+
in the first place.
42+
</p>
43+
44+
<sample src="examples/SuspiciousMethodNameFixed.ts" />
45+
46+
</example>
47+
<references>
48+
49+
</references>
50+
</qhelp>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @name Suspicious method name
3+
* @description A method having the name "function", "new", or "constructor"
4+
* is usually caused by a programmer being confused about the TypeScript syntax.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @id js/suspicious-method-name
8+
* @precision high
9+
* @tags correctness
10+
* typescript
11+
* methods
12+
*/
13+
14+
import javascript
15+
16+
/**
17+
* Holds if the method name on the given container is likely to be a mistake.
18+
*/
19+
predicate isSuspisousMethodName(string name, ClassOrInterface container) {
20+
name = "function"
21+
or
22+
// "constructor" is only suspicious outside a class.
23+
name = "constructor" and not container instanceof ClassDefinition
24+
or
25+
// "new" is only suspicious inside a class.
26+
name = "new" and container instanceof ClassDefinition
27+
}
28+
29+
/**
30+
* Holds if the beginning of the location is before the end.
31+
*/
32+
predicate isRealLocation(Location l) {
33+
l.getEndLine() > l.getStartLine() or
34+
(l.getStartLine() = l.getEndLine() and l.getEndColumn() > l.getStartColumn())
35+
}
36+
37+
from MethodDeclaration member, ClassOrInterface container, string suffixMsg
38+
where
39+
container.getLocation().getFile().getFileType().isTypeScript() and
40+
container.getAMember() = member and
41+
isSuspisousMethodName(member.getName(), container) and
42+
43+
// Assume that a "new" method is intentional if the class has an explicit constructor.
44+
not (
45+
member.getName() = "new" and
46+
container instanceof ClassDefinition and
47+
exists(MemberDeclaration constructor |
48+
container.getAMember() = constructor and
49+
constructor.getName() = "constructor" and
50+
// Test that it is not an implicitly declared constructor.
51+
isRealLocation(constructor.getLocation())
52+
)
53+
) and
54+
55+
// Explicitly declared static methods are fine.
56+
not (
57+
container instanceof ClassDefinition and
58+
member.isStatic()
59+
) and
60+
61+
// Only looking for declared methods. Methods with a body are OK.
62+
not exists(member.getBody().getBody()) and
63+
64+
// The developer was not confused about "function" when there are other methods in the interface.
65+
not (
66+
member.getName() = "function" and
67+
exists(MethodDeclaration other | other = container.getMethod(_) |
68+
other.getName() != "function" and
69+
isRealLocation(other.getLocation())
70+
)
71+
) and
72+
73+
(
74+
member.getName() = "constructor" and suffixMsg = "Did you mean to write a class instead of an interface?"
75+
or
76+
member.getName() = "new" and suffixMsg = "Did you mean \"constructor\"?"
77+
or
78+
member.getName() = "function" and suffixMsg = "Did you mean to omit \"function\"?"
79+
)
80+
select member, "Declares a suspiciously named method \"" + member.getName() + "\". " + suffixMsg
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare class Point {
2+
x: number;
3+
y: number;
4+
constructor(x : number, y: number);
5+
}
6+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface Point {
2+
x: number;
3+
y: number;
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| tst.ts:7:3:7:24 | constru ... string; | Declares a suspiciously named method "constructor". Did you mean "new"? |
2+
| tst.ts:16:3:16:21 | function(): number; | Declares a suspiciously named method "function". Did you mean to omit "function"? |
3+
| tst.ts:37:3:37:21 | function(): number; | Declares a suspiciously named method "function". Did you mean to omit "function"? |
4+
| tst.ts:48:3:48:13 | new(): Quz; | Declares a suspiciously named method "new". Did you mean "constructor"? |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Declarations/SuspiciousMethodName.ql
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// OK: don't report anything in .js files.
2+
function getStuff(number) {
3+
return {
4+
"new": function() {
5+
6+
},
7+
"constructor": 123,
8+
"function": "this is a string!"
9+
}
10+
}
11+
12+
class Foobar {
13+
new() {
14+
return 123;
15+
}
16+
function() {
17+
return "string";
18+
}
19+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
var foo: MyInterface = 123 as any;
2+
3+
interface MyInterface {
4+
function (): number; // OK. Highly unlikely that it is an accident when there are other named methods in the interface.
5+
(): number; // OK: What was probaly meant above.
6+
new:() => void; // OK! This is a property, not a method, we ignore those.
7+
constructor(): string; // NOT OK! This a called "constructor"
8+
new(): Date; // OK! This a constructor signature.
9+
10+
myNumber: 123;
11+
}
12+
13+
var a : MyFunction = null as any;
14+
15+
interface MyFunction {
16+
function(): number; // NOT OK!
17+
}
18+
19+
20+
class Foo {
21+
new(): number { // OK! Highly unlikely that a developer confuses "constructor" and "new" when both are present.
22+
return 123;
23+
}
24+
constructor() { // OK! This is a constructor.
25+
26+
}
27+
myString = "foobar"
28+
29+
myMethod(): boolean {
30+
return Math.random() > 0.5;
31+
}
32+
}
33+
34+
var b : FunctionClass = new FunctionClass();
35+
36+
declare class FunctionClass {
37+
function(): number; // NOT OK:
38+
}
39+
40+
class Baz {
41+
new(): Baz { // OK! When there is a method body I assume the developer knows what they are doing.
42+
return null as any;
43+
}
44+
}
45+
46+
47+
declare class Quz {
48+
new(): Quz; // NOT OK! The developer likely meant to write constructor.
49+
}
50+
51+
var bla = new Foo();
52+
var blab = new Baz();

0 commit comments

Comments
 (0)