Skip to content

Commit a480c6e

Browse files
author
Max Schaefer
committed
JavaScript: Implement LoC counting for functions in QL.
1 parent c09c35a commit a480c6e

File tree

1 file changed

+117
-3
lines changed

1 file changed

+117
-3
lines changed

javascript/ql/src/semmle/javascript/Functions.qll

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,15 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
119119
override StmtContainer getEnclosingContainer() { result = getEnclosingStmt().getContainer() }
120120

121121
/** Gets the number of lines in this function. */
122-
int getNumberOfLines() { numlines(this, result, _, _) }
122+
int getNumberOfLines() {
123+
exists(int sl, int el | getLocation().hasLocationInfo(_, sl, _, el, _) | result = el - sl + 1)
124+
}
123125

124126
/** Gets the number of lines containing code in this function. */
125-
int getNumberOfLinesOfCode() { numlines(this, _, result, _) }
127+
int getNumberOfLinesOfCode() { result = LinesOfCode::getNumCodeLines(this) }
126128

127129
/** Gets the number of lines containing comments in this function. */
128-
int getNumberOfLinesOfComments() { numlines(this, _, _, result) }
130+
int getNumberOfLinesOfComments() { result = LinesOfComments::getNumCommentLines(this) }
129131

130132
/** Gets the cyclomatic complexity of this function. */
131133
int getCyclomaticComplexity() {
@@ -341,6 +343,118 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
341343
CanonicalFunctionName getCanonicalName() { ast_node_symbol(this, result) }
342344
}
343345

346+
/**
347+
* Provides predicates for computing lines-of-code information for functions.
348+
*/
349+
private module LinesOfCode {
350+
/**
351+
* Holds if `tk` is interesting for the purposes of counting lines of code, that is, it might
352+
* contribute a line of code that isn't covered by any other token.
353+
*
354+
* A token is interesting if it is the first token on its line, or if it spans multiple lines.
355+
*/
356+
private predicate isInteresting(Token tk) {
357+
exists(int sl, int el | tk.getLocation().hasLocationInfo(_, sl, _, el, _) |
358+
not tk.getPreviousToken().getLocation().getEndLine() = sl
359+
or
360+
sl != el
361+
)
362+
}
363+
364+
/**
365+
* Gets the `i`th token in toplevel `tl`, but only if it is interesting.
366+
*/
367+
pragma[noinline]
368+
private Token getInterestingToken(TopLevel tl, int i) {
369+
result.getTopLevel() = tl and
370+
result.getIndex() = i and
371+
isInteresting(result)
372+
}
373+
374+
/**
375+
* Holds if `f` covers tokens between indices `start` and `end` (inclusive) in toplevel `tl`.
376+
*/
377+
predicate tokenRange(Function f, TopLevel tl, int start, int end) {
378+
tl = f.getTopLevel() and
379+
start = f.getFirstToken().getIndex() and
380+
end = f.getLastToken().getIndex()
381+
}
382+
383+
/**
384+
* Gets an interesting token belonging to `f`.
385+
*/
386+
private Token getAnInterestingToken(Function f) {
387+
result = f.getFirstToken()
388+
or
389+
exists(TopLevel tl, int start, int end | tokenRange(f, tl, start, end) |
390+
result = getInterestingToken(tl, [start .. end])
391+
)
392+
}
393+
394+
/**
395+
* Gets the line number of a line covered by `f` that contains at least one token.
396+
*/
397+
private int getACodeLine(Function f) {
398+
exists(Location loc | loc = getAnInterestingToken(f).getLocation() |
399+
result in [loc.getStartLine() .. loc.getEndLine()]
400+
)
401+
}
402+
403+
/**
404+
* Gets the number of lines of code of `f`.
405+
*
406+
* Note the special handling of empty locations; this is needed to correctly deal with
407+
* synthetic constructors.
408+
*/
409+
int getNumCodeLines(Function f) {
410+
if f.getLocation().isEmpty() then result = 0 else result = count(getACodeLine(f))
411+
}
412+
}
413+
414+
/**
415+
* Provides predicates for computing lines-of-comments information for functions.
416+
*/
417+
private module LinesOfComments {
418+
/**
419+
* Holds if `tk` is interesting for the purposes of counting comments, that is,
420+
* if it is preceded by a comment.
421+
*/
422+
private predicate isInteresting(Token tk) { exists(Comment c | tk = c.getNextToken()) }
423+
424+
/**
425+
* Gets the `i`th token in `tl`, if it is interesting.
426+
*/
427+
pragma[noinline]
428+
private Token getToken(TopLevel tl, int i) {
429+
result.getTopLevel() = tl and
430+
result.getIndex() = i and
431+
isInteresting(result)
432+
}
433+
434+
/**
435+
* Gets a comment inside function `f`.
436+
*/
437+
private Comment getAComment(Function f) {
438+
exists(TopLevel tl, int start, int end | LinesOfCode::tokenRange(f, tl, start, end) |
439+
result.getNextToken() = getToken(tl, [start + 1 .. end])
440+
)
441+
}
442+
443+
/**
444+
* Gets a line covered by `f` on which at least one comment appears.
445+
*/
446+
private int getACommentLine(Function f) {
447+
exists(Location loc | loc = getAComment(f).getLocation() |
448+
result in [loc.getStartLine() .. loc.getEndLine()]
449+
)
450+
}
451+
452+
/**
453+
* Gets the number of lines with at least one comment in `f`.
454+
*/
455+
int getNumCommentLines(Function f) { result = count(getACommentLine(f)) }
456+
}
457+
344458
/**
345459
* A method defined in a class or object expression.
346460
*/

0 commit comments

Comments
 (0)