Files
codeql/javascript/ql/src/LanguageFeatures/TemplateSyntaxInStringLiteral.ql
Anders Schack-Mulligen ca45fb5a60 JavaScript: Autoformat.
2019-09-06 09:04:51 +02:00

104 lines
3.3 KiB
Plaintext

/**
* @name Template syntax in string literal
* @description A string literal appears to use template syntax but is not quoted with backticks.
* @kind problem
* @problem.severity warning
* @id js/template-syntax-in-string-literal
* @precision high
* @tags correctness
*/
import javascript
/** A toplevel that contains at least one template literal. */
class CandidateTopLevel extends TopLevel {
CandidateTopLevel() { exists(TemplateLiteral template | template.getTopLevel() = this) }
}
/** A string literal in a toplevel that contains at least one template literal. */
class CandidateStringLiteral extends StringLiteral {
CandidateTopLevel tl;
string v;
CandidateStringLiteral() {
tl = this.getTopLevel() and
v = getStringValue()
}
/**
* Extracts a `${...}` part from this string literal using an inexact regular expression.
*
* Breakdown of the sequence matched by the expression:
* - any prefix and then `${`
* - any amount of whitespace and simple unary operators
* - name of the variable
* - optionally: a character terminating the identifier token, followed by anything
* - `}`, followed by anything
*/
string getAReferencedVariable() {
result = v.regexpCapture(".*\\$\\{[\\s+\\-!]*([\\w\\p{L}$]+)([^\\w\\p{L}$].*)?\\}.*", 1)
}
/**
* Gets an ancestor node of this string literal in the AST that can be reached without
* stepping over scope elements.
*/
ASTNode getIntermediate() {
result = this
or
exists(ASTNode mid | mid = getIntermediate() |
not mid instanceof ScopeElement and
result = mid.getParent()
)
}
/**
* Holds if this string literal is in the given `scope`.
*/
predicate isInScope(Scope scope) {
scope instanceof GlobalScope or
getIntermediate().(ScopeElement).getScope() = scope.getAnInnerScope*()
}
}
/**
* Holds if `obj` has a property for each template variable in `lit` and they occur as arguments
* to the same call.
*
* This recognises a typical pattern in which template arguments are passed along with a string,
* for example:
*
* ```
* output.emit('<a href="${url}">${name}$</a>',
* { url: url, name: name } );
* ```
*/
predicate providesTemplateVariablesFor(ObjectExpr obj, CandidateStringLiteral lit) {
exists(CallExpr call | call.getAnArgument() = obj and call.getAnArgument() = lit) and
forex(string name | lit.getAReferencedVariable() = name | hasProperty(obj, name))
}
/** Holds if `object` has a property with the given `name`. */
predicate hasProperty(ObjectExpr object, string name) { name = object.getAProperty().getName() }
/**
* Gets a declaration of variable `v` in `tl`, where `v` has the given `name` and
* belongs to `scope`.
*/
VarDecl getDeclIn(Variable v, Scope s, string name, CandidateTopLevel tl) {
v.getName() = name and
v.getADeclaration() = result and
v.getScope() = s and
result.getTopLevel() = tl
}
from CandidateStringLiteral lit, Variable v, Scope s, string name, VarDecl decl
where
decl = getDeclIn(v, s, name, lit.getTopLevel()) and
lit.getAReferencedVariable() = name and
lit.isInScope(s) and
not exists(ObjectExpr obj | providesTemplateVariablesFor(obj, lit)) and
not lit.getStringValue() = "${" + name + "}"
select lit, "This string is not a template literal, but appears to reference the variable $@.",
decl, v.getName()