Skip to content

Commit e6d6cb9

Browse files
authored
Be more careful on invalid tokens (#1092)
1 parent e79155b commit e6d6cb9

File tree

5 files changed

+59
-26
lines changed

5 files changed

+59
-26
lines changed

src/diagnosticMessages.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export enum DiagnosticCode {
6565
An_accessor_cannot_have_type_parameters = 1094,
6666
A_set_accessor_cannot_have_a_return_type_annotation = 1095,
6767
Type_parameter_list_cannot_be_empty = 1098,
68+
Type_argument_list_cannot_be_empty = 1099,
6869
A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement = 1104,
6970
A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement = 1105,
7071
A_return_statement_can_only_be_used_within_a_function_body = 1108,
@@ -214,6 +215,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
214215
case 1094: return "An accessor cannot have type parameters.";
215216
case 1095: return "A 'set' accessor cannot have a return type annotation.";
216217
case 1098: return "Type parameter list cannot be empty.";
218+
case 1099: return "Type argument list cannot be empty.";
217219
case 1104: return "A 'continue' statement can only be used within an enclosing iteration statement.";
218220
case 1105: return "A 'break' statement can only be used within an enclosing iteration or switch statement.";
219221
case 1108: return "A 'return' statement can only be used within a function body.";

src/diagnosticMessages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"An accessor cannot have type parameters.": 1094,
6161
"A 'set' accessor cannot have a return type annotation.": 1095,
6262
"Type parameter list cannot be empty.": 1098,
63+
"Type argument list cannot be empty.": 1099,
6364
"A 'continue' statement can only be used within an enclosing iteration statement.": 1104,
6465
"A 'break' statement can only be used within an enclosing iteration or switch statement.": 1105,
6566
"A 'return' statement can only be used within a function body.": 1108,

src/diagnostics.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*//***/
66

77
import {
8-
Range
8+
Range,
9+
Source
910
} from "./ast";
1011

1112
import {
@@ -241,7 +242,7 @@ export abstract class DiagnosticEmitter {
241242
/** Diagnostic messages emitted so far. */
242243
diagnostics: DiagnosticMessage[];
243244
/** Diagnostic messages already seen, by range. */
244-
private seen: Map<Range,Set<DiagnosticCode>> = new Map();
245+
private seen: Map<Source,Map<i32,i32[]>> = new Map();
245246

246247
/** Initializes this diagnostic emitter. */
247248
protected constructor(diagnostics: DiagnosticMessage[] | null = null) {
@@ -260,17 +261,23 @@ export abstract class DiagnosticEmitter {
260261
): void {
261262
// It is possible that the same diagnostic is emitted twice, for example
262263
// when compiling generics with different types or when recompiling a loop
263-
// because our initial assumptions didn't hold. Deduplicate these.
264+
// because our initial assumptions didn't hold. It is even possible to get
265+
// multiple instances of the same range during parsing. Deduplicate these.
264266
if (range) {
265267
let seen = this.seen;
266-
if (seen.has(range)) {
267-
let codes = seen.get(range)!;
268-
if (codes.has(code)) return;
269-
codes.add(code);
268+
if (seen.has(range.source)) {
269+
let seenInSource = seen.get(range.source)!;
270+
if (seenInSource.has(range.start)) {
271+
let seenCodesAtPos = seenInSource.get(range.start)!;
272+
if (seenCodesAtPos.includes(code)) return;
273+
seenCodesAtPos.push(code);
274+
} else {
275+
seenInSource.set(range.start, [ code ]);
276+
}
270277
} else {
271-
let codes = new Set<DiagnosticCode>();
272-
codes.add(code);
273-
seen.set(range, codes);
278+
let seenInSource = new Map();
279+
seenInSource.set(range.start, [ code ]);
280+
seen.set(range.source, seenInSource);
274281
}
275282
}
276283
var message = DiagnosticMessage.create(code, category, arg0, arg1, arg2);

src/parser.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,8 +1043,9 @@ export class Parser extends DiagnosticEmitter {
10431043

10441044
// at '<': TypeParameter (',' TypeParameter)* '>'
10451045

1046-
var typeParameters: TypeParameterNode[] | null = null;
1046+
var typeParameters = new Array<TypeParameterNode>();
10471047
var seenOptional = false;
1048+
var start = tn.tokenPos;
10481049
while (!tn.skip(Token.GREATERTHAN)) {
10491050
let typeParameter = this.parseTypeParameter(tn);
10501051
if (!typeParameter) return null;
@@ -1057,8 +1058,7 @@ export class Parser extends DiagnosticEmitter {
10571058
);
10581059
typeParameter.defaultType = null;
10591060
}
1060-
if (!typeParameters) typeParameters = [ typeParameter ];
1061-
else typeParameters.push(typeParameter);
1061+
typeParameters.push(typeParameter);
10621062
if (!tn.skip(Token.COMMA)) {
10631063
if (tn.skip(Token.GREATERTHAN)) {
10641064
break;
@@ -1071,10 +1071,10 @@ export class Parser extends DiagnosticEmitter {
10711071
}
10721072
}
10731073
}
1074-
if (!(typeParameters && typeParameters.length)) {
1074+
if (!typeParameters.length) {
10751075
this.error(
10761076
DiagnosticCode.Type_parameter_list_cannot_be_empty,
1077-
tn.range()
1077+
tn.range(start, tn.pos)
10781078
); // recoverable
10791079
}
10801080
return typeParameters;
@@ -3618,7 +3618,8 @@ export class Parser extends DiagnosticEmitter {
36183618

36193619
var state = tn.mark();
36203620
if (!tn.skip(Token.LESSTHAN)) return null;
3621-
var typeArguments: TypeNode[] | null = null;
3621+
var start = tn.tokenPos;
3622+
var typeArguments = new Array<TypeNode>();
36223623
do {
36233624
if (tn.peek() === Token.GREATERTHAN) {
36243625
break;
@@ -3628,11 +3629,19 @@ export class Parser extends DiagnosticEmitter {
36283629
tn.reset(state);
36293630
return null;
36303631
}
3631-
if (!typeArguments) typeArguments = [ type ];
3632-
else typeArguments.push(type);
3632+
typeArguments.push(type);
36333633
} while (tn.skip(Token.COMMA));
3634-
if (tn.skip(Token.GREATERTHAN) && tn.skip(Token.OPENPAREN)) {
3635-
return typeArguments;
3634+
if (tn.skip(Token.GREATERTHAN)) {
3635+
let end = tn.pos;
3636+
if (tn.skip(Token.OPENPAREN)) {
3637+
if (!typeArguments.length) {
3638+
this.error(
3639+
DiagnosticCode.Type_argument_list_cannot_be_empty,
3640+
tn.range(start, end)
3641+
);
3642+
}
3643+
return typeArguments;
3644+
}
36363645
}
36373646
tn.reset(state);
36383647
return null;

src/tokenizer.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,11 @@ export class Tokenizer extends DiagnosticEmitter {
551551

552552
next(identifierHandling: IdentifierHandling = IdentifierHandling.DEFAULT): Token {
553553
this.nextToken = -1;
554-
return this.token = this.unsafeNext(identifierHandling);
554+
var token: Token;
555+
do token = this.unsafeNext(identifierHandling);
556+
while (token == Token.INVALID);
557+
this.token = token;
558+
return token;
555559
}
556560

557561
private unsafeNext(
@@ -962,11 +966,15 @@ export class Tokenizer extends DiagnosticEmitter {
962966
++this.pos;
963967
break;
964968
}
969+
let start = this.pos++;
970+
if ( // surrogate pair?
971+
(c & 0xFC00) == 0xD800 && this.pos < this.end &&
972+
((text.charCodeAt(this.pos)) & 0xFC00) == 0xDC00
973+
) ++this.pos;
965974
this.error(
966975
DiagnosticCode.Invalid_character,
967-
this.range(this.pos, this.pos + 1)
976+
this.range(start, this.pos)
968977
);
969-
++this.pos;
970978
return Token.INVALID;
971979
}
972980
}
@@ -984,7 +992,10 @@ export class Tokenizer extends DiagnosticEmitter {
984992
let posBefore = this.pos;
985993
let tokenBefore = this.token;
986994
let tokenPosBefore = this.tokenPos;
987-
this.nextToken = this.unsafeNext(identifierHandling, maxCompoundLength);
995+
let nextToken: Token;
996+
do nextToken = this.unsafeNext(identifierHandling, maxCompoundLength);
997+
while (nextToken == Token.INVALID);
998+
this.nextToken = nextToken;
988999
this.nextTokenPos = this.tokenPos;
9891000
if (checkOnNewLine) {
9901001
this.nextTokenOnNewLine = false;
@@ -1017,8 +1028,11 @@ export class Tokenizer extends DiagnosticEmitter {
10171028
break;
10181029
}
10191030
}
1020-
this.token = this.unsafeNext(identifierHandling, maxCompoundLength);
1021-
if (this.token == token) {
1031+
var nextToken: Token;
1032+
do nextToken = this.unsafeNext(identifierHandling, maxCompoundLength);
1033+
while (nextToken == Token.INVALID);
1034+
if (nextToken == token) {
1035+
this.token = token;
10221036
this.nextToken = -1;
10231037
return true;
10241038
} else {

0 commit comments

Comments
 (0)