add query for detecting suspisous method names in TypeScript

This commit is contained in:
Erik Krogh Kristensen
2019-09-12 15:13:56 +01:00
parent 9a8b62250f
commit 0320f0f26b
9 changed files with 217 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
In TypeScript the keywords <code>constructor</code> and <code>new</code> are
used to declare constructors in classes and interfaces respectively.
However, by using the wrong keyword a programmer can accidentally declare e.g.
a method called <code>constructor</code> inside an interface.
Similarly the keyword <code>function</code> is used to declare functions in
some contexts, however, using the keyword <code>function</code> inside a class
or interface results in declaring a method called <code>function</code>.
</p>
</overview>
<recommendation>
<p>
Declare classes as classes and not as interfaces.
Use the keyword <code>constructor</code> to declare constructors in a class,
use the keyword <code>new</code> to declare constructors inside interfaces,
and don't use <code>function</code> when declaring an interface that is a
function.
</p>
</recommendation>
<example>
<p>
The below example declares an interface <code>Point</code> with 2 fields
and a method called <code>constructor</code>. The interface does not declare
a class <code>Point</code> with a constructor, which was likely what the
developer meant to create.
</p>
<sample src="examples/SuspiciousMethodName.ts" />
<p>
The below example is a fixed version of the above, where the interface is
instead declared as a class, thereby describing the type the developer meant
in the first place.
</p>
<sample src="examples/SuspiciousMethodNameFixed.ts" />
</example>
<references>
</references>
</qhelp>

View File

@@ -0,0 +1,80 @@
/**
* @name Suspicious method name
* @description A method having the name "function", "new", or "constructor"
* is usually caused by a programmer being confused about the TypeScript syntax.
* @kind problem
* @problem.severity warning
* @id js/suspicious-method-name
* @precision high
* @tags correctness
* typescript
* methods
*/
import javascript
/**
* Holds if the method name on the given container is likely to be a mistake.
*/
predicate isSuspisousMethodName(string name, ClassOrInterface container) {
name = "function"
or
// "constructor" is only suspicious outside a class.
name = "constructor" and not container instanceof ClassDefinition
or
// "new" is only suspicious inside a class.
name = "new" and container instanceof ClassDefinition
}
/**
* Holds if the beginning of the location is before the end.
*/
predicate isRealLocation(Location l) {
l.getEndLine() > l.getStartLine() or
(l.getStartLine() = l.getEndLine() and l.getEndColumn() > l.getStartColumn())
}
from MethodDeclaration member, ClassOrInterface container, string suffixMsg
where
container.getLocation().getFile().getFileType().isTypeScript() and
container.getAMember() = member and
isSuspisousMethodName(member.getName(), container) and
// Assume that a "new" method is intentional if the class has an explicit constructor.
not (
member.getName() = "new" and
container instanceof ClassDefinition and
exists(MemberDeclaration constructor |
container.getAMember() = constructor and
constructor.getName() = "constructor" and
// Test that it is not an implicitly declared constructor.
isRealLocation(constructor.getLocation())
)
) and
// Explicitly declared static methods are fine.
not (
container instanceof ClassDefinition and
member.isStatic()
) and
// Only looking for declared methods. Methods with a body are OK.
not exists(member.getBody().getBody()) and
// The developer was not confused about "function" when there are other methods in the interface.
not (
member.getName() = "function" and
exists(MethodDeclaration other | other = container.getMethod(_) |
other.getName() != "function" and
isRealLocation(other.getLocation())
)
) and
(
member.getName() = "constructor" and suffixMsg = "Did you mean to write a class instead of an interface?"
or
member.getName() = "new" and suffixMsg = "Did you mean \"constructor\"?"
or
member.getName() = "function" and suffixMsg = "Did you mean to omit \"function\"?"
)
select member, "Declares a suspiciously named method \"" + member.getName() + "\". " + suffixMsg

View File

@@ -0,0 +1,6 @@
declare class Point {
x: number;
y: number;
constructor(x : number, y: number);
}

View File

@@ -0,0 +1,4 @@
interface Point {
x: number;
y: number;
}