Skip to content

Commit b5008d8

Browse files
committed
TS: only transfer offsets as part of the AST
1 parent 201f64e commit b5008d8

File tree

2 files changed

+50
-25
lines changed

2 files changed

+50
-25
lines changed

javascript/extractor/lib/typescript/src/ast_extractor.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface AugmentedSourceFile extends ts.SourceFile {
99
parseDiagnostics?: any[];
1010
$tokens?: Token[];
1111
$symbol?: number;
12+
$lineStarts?: ReadonlyArray<number>;
1213
}
1314

1415
export interface AugmentedNode extends ts.Node {
@@ -21,9 +22,7 @@ export interface AugmentedNode extends ts.Node {
2122
$overloadIndex?: number;
2223
}
2324

24-
export interface AugmentedPos extends ts.LineAndCharacter {
25-
$offset?: number;
26-
}
25+
export type AugmentedPos = number;
2726

2827
export interface Token {
2928
kind: ts.SyntaxKind;
@@ -66,18 +65,18 @@ function forEachNode(ast: ts.Node, callback: (node: ts.Node) => void) {
6665
}
6766

6867
export function augmentAst(ast: AugmentedSourceFile, code: string, project: Project | null) {
68+
ast.$lineStarts = ast.getLineStarts();
69+
6970
/**
70-
* Converts a numeric offset to a position object with line and column information.
71+
* Converts a numeric offset to the value expected by the Java counterpart of the extractor.
7172
*/
7273
function augmentPos(pos: number, shouldSkipWhitespace?: boolean): AugmentedPos {
7374
// skip over leading spaces/comments
7475
if (shouldSkipWhitespace) {
7576
skipWhiteSpace.lastIndex = pos;
7677
pos += skipWhiteSpace.exec(code)[0].length;
7778
}
78-
let posObject: AugmentedPos = ast.getLineAndCharacterOfPosition(pos);
79-
posObject.$offset = pos;
80-
return posObject;
79+
return pos;
8180
}
8281

8382
// Find the position of all tokens where the scanner requires parse-tree information.

javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ public class TypeScriptASTConverter {
162162
private final JsonObject syntaxKinds;
163163
private final Map<Integer, String> nodeFlagMap = new LinkedHashMap<>();
164164
private final Map<Integer, String> syntaxKindMap = new LinkedHashMap<>();
165+
private int[] lineStarts;
165166

166167
private int syntaxKindExtends;
167168

@@ -199,6 +200,8 @@ private void makeEnumIdMap(JsonObject enumObject, Map<Integer, String> idToName)
199200
* into a parser {@link Result}.
200201
*/
201202
public Result convertAST(JsonObject ast, String source) {
203+
this.lineStarts = toIntArray(ast.getAsJsonArray("$lineStarts"));
204+
202205
List<ParseError> errors = new ArrayList<ParseError>();
203206

204207
// process parse diagnostics (i.e., syntax errors) reported by the TypeScript compiler
@@ -207,11 +210,8 @@ public Result convertAST(JsonObject ast, String source) {
207210
for (JsonElement elt : parseDiagnostics) {
208211
JsonObject parseDiagnostic = elt.getAsJsonObject();
209212
String message = parseDiagnostic.get("messageText").getAsString();
210-
JsonObject pos = parseDiagnostic.get("$pos").getAsJsonObject();
211-
int line = pos.get("line").getAsInt() + 1;
212-
int column = pos.get("character").getAsInt();
213-
int offset = pos.get("$offset").getAsInt();
214-
errors.add(new ParseError(message, line, column, offset));
213+
Position pos = getPosition(parseDiagnostic.get("$pos"));
214+
errors.add(new ParseError(message, pos.getLine(), pos.getColumn(), pos.getOffset()));
215215
}
216216
return new Result(source, null, new ArrayList<>(), new ArrayList<>(), errors);
217217
}
@@ -231,14 +231,44 @@ public Result convertAST(JsonObject ast, String source) {
231231
return new Result(source, converted, tokens, comments, errors);
232232
}
233233

234+
/**
235+
* Converts a JSON array to an int array.
236+
* The array is assumed to only contain integers.
237+
*/
238+
private static int[] toIntArray(JsonArray array) {
239+
int[] result = new int[array.size()];
240+
for (int i = 0; i < array.size(); ++i) {
241+
result[i] = array.get(i).getAsInt();
242+
}
243+
return result;
244+
}
245+
246+
private int getLineFromPos(int pos) {
247+
int low = 0, high = this.lineStarts.length - 1;
248+
while (low < high) {
249+
int mid = high - ((high - low) >> 1); // Get middle, rounding up.
250+
int startOfLine = lineStarts[mid];
251+
if (startOfLine <= pos) {
252+
low = mid;
253+
} else {
254+
high = mid - 1;
255+
}
256+
}
257+
return low;
258+
}
259+
260+
private int getColumnFromLinePos(int line, int pos) {
261+
return pos - lineStarts[line];
262+
}
263+
234264
/**
235265
* Extract tokens and comments from the given TypeScript AST.
236266
*/
237267
private void extractTokensAndComments(JsonObject ast, List<Token> tokens, List<Comment> comments) {
238268
for (JsonElement elt : ast.get("$tokens").getAsJsonArray()) {
239269
JsonObject token = elt.getAsJsonObject();
240270
String text = token.get("text").getAsString();
241-
Position start = getPosition(token.get("tokenPos").getAsJsonObject(), true);
271+
Position start = getPosition(token.get("tokenPos"));
242272
Position end = advance(start, text);
243273
SourceLocation loc = new SourceLocation(text, start, end);
244274
String kind = getKind(token);
@@ -886,7 +916,7 @@ private Node convertClass(JsonObject node, String kind, SourceLocation loc) thro
886916
} else {
887917
superInterfaces = convertSuperInterfaceClause(supers);
888918
}
889-
afterHead = heritageClause.get("$end").getAsJsonObject().get("$offset").getAsInt();
919+
afterHead = heritageClause.get("$end").getAsInt();
890920
}
891921
if (superInterfaces == null) {
892922
superInterfaces = new ArrayList<>();
@@ -2191,8 +2221,8 @@ private Position advance(Position pos, String skipped) {
21912221
* Get the source location of the given AST node.
21922222
*/
21932223
private SourceLocation getSourceLocation(JsonObject node) {
2194-
Position start = getPosition(node.get("$pos").getAsJsonObject(), true);
2195-
Position end = getPosition(node.get("$end").getAsJsonObject(), false);
2224+
Position start = getPosition(node.get("$pos"));
2225+
Position end = getPosition(node.get("$end"));
21962226
int startOffset = start.getOffset();
21972227
int endOffset = end.getOffset();
21982228
if (startOffset > endOffset)
@@ -2207,15 +2237,11 @@ private SourceLocation getSourceLocation(JsonObject node) {
22072237
* For start positions, we need to skip over whitespace, which is included in
22082238
* the positions reported by the TypeScript compiler.
22092239
*/
2210-
private Position getPosition(JsonObject pos, boolean isStart) {
2211-
int line = pos.get("line").getAsInt() + 1;
2212-
int column = pos.get("character").getAsInt();
2213-
int offset = pos.get("$offset").getAsInt();
2214-
if (isStart) {
2215-
while (offset < source.length() && Character.isWhitespace(source.charAt(offset)))
2216-
++offset;
2217-
}
2218-
return new Position(line, column, offset);
2240+
private Position getPosition(JsonElement elm) {
2241+
int offset = elm.getAsInt();
2242+
int line = getLineFromPos(offset);
2243+
int column = getColumnFromLinePos(line, offset);
2244+
return new Position(line + 1, column, offset);
22192245
}
22202246

22212247
private Iterable<JsonElement> getModifiers(JsonObject node) {

0 commit comments

Comments
 (0)