Skip to content

Commit 24a5301

Browse files
author
Esben Sparre Andreasen
authored
Merge pull request #2056 from erik-krogh/suspiciousMethodName
JS: add query for detecting suspicious method names in TypeScript
2 parents ff5a98b + 3a55880 commit 24a5301

File tree

9 files changed

+211
-0
lines changed

9 files changed

+211
-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-declaration`) | 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: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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> for
8+
member declarations are used to declare constructors in classes and interfaces
9+
respectively.
10+
However, a member declaration with the name <code>new</code> in an interface
11+
or <code>constructor</code> in a class, will declare an ordinary method named
12+
<code>new</code> or <code>constructor</code> rather than a constructor.
13+
Similarly, the keyword <code>function</code> is used to declare functions in
14+
some contexts. However, using the name <code>function</code> for a class
15+
or interface member declaration declares a method named <code>function</code>.
16+
</p>
17+
18+
</overview>
19+
<recommendation>
20+
21+
<p>
22+
Declare classes as classes and not as interfaces.
23+
Use the keyword <code>constructor</code> to declare constructors in a class,
24+
use the keyword <code>new</code> to declare constructors inside interfaces,
25+
and don't use <code>function</code> when declaring a call signature in an
26+
interface.
27+
</p>
28+
29+
</recommendation>
30+
<example>
31+
32+
<p>
33+
The below example declares an interface <code>Point</code> with 2 fields
34+
and a method called <code>constructor</code>. The interface does not declare
35+
a class <code>Point</code> with a constructor, which was likely what the
36+
developer meant to create.
37+
</p>
38+
<sample src="examples/SuspiciousMethodNameDeclaration.ts" />
39+
40+
<p>
41+
The below example is a fixed version of the above, where the interface is
42+
instead declared as a class, thereby describing the type the developer meant
43+
in the first place.
44+
</p>
45+
46+
<sample src="examples/SuspiciousMethodNameDeclarationFixed.ts" />
47+
48+
</example>
49+
<references>
50+
51+
<li>TypeScript specification: <a href="https://github.com/microsoft/TypeScript/blob/master/doc/spec.md#3.8.9">Constructor Type Literals</a>.</li>
52+
<li>TypeScript specification: <a href="https://github.com/microsoft/TypeScript/blob/master/doc/spec.md#8.3.1">Constructor Parameters</a>.</li>
53+
54+
</references>
55+
</qhelp>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* @name Suspicious method name declaration
3+
* @description A method declaration with a name that is a special keyword in another
4+
* context is suspicious.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @id js/suspicious-method-name-declaration
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 isSuspiciousMethodName(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+
from MethodDeclaration member, ClassOrInterface container, string name, string msg
30+
where
31+
container.getLocation().getFile().getFileType().isTypeScript() and
32+
container.getMember(name) = member and
33+
isSuspiciousMethodName(name, container) and
34+
35+
// Cases to ignore.
36+
not (
37+
// Assume that a "new" method is intentional if the class has an explicit constructor.
38+
name = "new" and
39+
container instanceof ClassDefinition and
40+
exists(ConstructorDeclaration constructor |
41+
container.getMember("constructor") = constructor and
42+
not constructor.isSynthetic()
43+
)
44+
or
45+
// Explicitly declared static methods are fine.
46+
container instanceof ClassDefinition and
47+
member.isStatic()
48+
or
49+
// Only looking for declared methods. Methods with a body are OK.
50+
exists(member.getBody().getBody())
51+
or
52+
// The developer was not confused about "function" when there are other methods in the interface.
53+
name = "function" and
54+
exists(MethodDeclaration other | other = container.getAMethod() |
55+
other.getName() != "function" and
56+
not other.(ConstructorDeclaration).isSynthetic()
57+
)
58+
)
59+
60+
and
61+
62+
(
63+
name = "constructor" and msg = "The member name 'constructor' does not declare a constructor in interfaces, but it does in classes."
64+
or
65+
name = "new" and msg = "The member name 'new' does not declare a constructor, but 'constructor' does in class declarations."
66+
or
67+
name = "function" and msg = "The member name 'function' does not declare a function, it declares a method named 'function'."
68+
)
69+
select member, msg
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; | The member name 'constructor' does not declare a constructor in interfaces, but it does in classes. |
2+
| tst.ts:16:3:16:21 | function(): number; | The member name 'function' does not declare a function, it declares a method named 'function'. |
3+
| tst.ts:37:3:37:21 | function(): number; | The member name 'function' does not declare a function, it declares a method named 'function'. |
4+
| tst.ts:48:3:48:13 | new(): Quz; | The member name 'new' does not declare a constructor, but 'constructor' does in class declarations. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Declarations/SuspiciousMethodNameDeclaration.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 probably 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)