Files
codeql/javascript/ql/src/Expressions/MisspelledIdentifier.ql
2018-08-02 17:53:23 +01:00

123 lines
4.2 KiB
Plaintext

/**
* @name Misspelled identifier
* @description Misspelled identifiers make code harder to read and understand.
* @kind problem
* @problem.severity recommendation
* @id js/misspelled-identifier
* @tags maintainability
* readability
* @precision high
*/
import Misspelling
/**
* An identifier part.
*/
class IdentifierPart extends string {
IdentifierPart() {
idPart(_, this, _)
}
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
*/
predicate hasLocationInfo(string filepath, int startline, int startcolumn,
int endline, int endcolumn) {
exists (Identifier id, int start, Location l, int len | occursIn(id, start, len) and l = id.getLocation() |
filepath = l.getFile().getAbsolutePath() and
startline = l.getStartLine() and startcolumn = l.getStartColumn() + start and
// identifiers cannot span more than one line
endline = startline and endcolumn = startcolumn + len - 1
)
}
/**
* Holds if this identifier part occurs at offset `start` inside identifier `id`,
* and has length `len`.
*/
predicate occursIn(Identifier id, int start, int len) {
idPart(id, this, start) and len = this.length()
}
}
/**
* An identifier part that corresponds to a typo in `normalized_typos`.
*/
class WrongIdentifierPart extends IdentifierPart {
WrongIdentifierPart() {
normalized_typos(this, _, _, _, _, _)
}
/**
* Gets an identifier part that corresponds to a correction of this typo.
*/
string getASuggestion() {
exists (IdentifierPart right | normalized_typos(this, right, _, _, _, _) |
result = "'" + right + "'"
)
}
/**
* Gets a pretty-printed string representation of all corrections of
* this typo that appear as identifier parts in the code.
*/
string ppSuggestions() {
exists (string cat |
// first, concatenate with commas
cat = concat(getASuggestion(), ", ") and
// then, replace last comma with "or"
result = cat.regexpReplaceAll(", ([^,]++)$", " or $1")
)
}
override predicate occursIn(Identifier id, int start, int len) {
super.occursIn(id, start, len) and
// throw out cases where the wrong word appears as a prefix or suffix of a right word,
// and thus the result is most likely a false positive caused by our word segmentation algorithm
exists (string lowerid | lowerid = id.getName().toLowerCase() |
not exists (string right, int rightlen |
this.prefixOf(right, rightlen) and lowerid.substring(start, start+rightlen) = right or
this.suffixOf(right, rightlen) and lowerid.substring(start+len-rightlen, start+len) = right
)
) and
// also throw out cases flagged by another query
not misspelledVariableName(id, _)
}
/**
* Holds if this identifier part is a (proper) prefix of `right`, which is
* a correct spelling with length `rightlen`.
*/
predicate prefixOf(string right, int rightlen) {
exists (string c, int wronglen |
normalized_typos(this, _, c, _, _, _) and normalized_typos(_, right, _, _, c, _) and
wronglen = this.length() and rightlen = right.length() and
wronglen < rightlen and right.prefix(wronglen) = this
)
}
/**
* Holds if this identifier part is a (proper) suffix of `right`, which is
* a correct spelling with length `rightlen`.
*/
predicate suffixOf(string right, int rightlen) {
exists (string c, int wronglen |
normalized_typos(this, _, _, c, _, _) and normalized_typos(_, right, _, _, _, c) and
wronglen = this.length() and rightlen = right.length() and
wronglen < rightlen and right.suffix(rightlen-wronglen) = this
)
}
}
from WrongIdentifierPart wrong
where // make sure we have at least one occurrence of a correction
exists(wrong.getASuggestion()) and
// make sure we have at least one unambiguous occurrence of the wrong word
wrong.occursIn(_, _, _)
select wrong, "'" + wrong + "' may be a typo for " + wrong.ppSuggestions() + "."