Skip to content

Commit c2f53fc

Browse files
committed
Add grammar error for invalid tagged template, more tests
1 parent c95daab commit c2f53fc

23 files changed

+562
-3
lines changed

src/compiler/checker.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -23182,7 +23182,7 @@ namespace ts {
2318223182
}
2318323183

2318423184
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
23185-
checkGrammarTypeArguments(node, node.typeArguments);
23185+
if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments);
2318623186
if (languageVersion < ScriptTarget.ES2015) {
2318723187
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
2318823188
}
@@ -32891,6 +32891,13 @@ namespace ts {
3289132891
checkGrammarForAtLeastOneTypeArgument(node, typeArguments);
3289232892
}
3289332893

32894+
function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean {
32895+
if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) {
32896+
return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain);
32897+
}
32898+
return false;
32899+
}
32900+
3289432901
function checkGrammarForOmittedArgument(args: NodeArray<Expression> | undefined): boolean {
3289532902
if (args) {
3289632903
for (const arg of args) {

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,10 @@
10351035
"category": "Error",
10361036
"code": 1356
10371037
},
1038+
"Tagged template expressions are not permitted in an optional chain.": {
1039+
"category": "Error",
1040+
"code": 1357
1041+
},
10381042

10391043
"Duplicate identifier '{0}'.": {
10401044
"category": "Error",

src/compiler/parser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4630,7 +4630,7 @@ namespace ts {
46304630
let isPropertyAccess = false;
46314631
if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) {
46324632
questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken);
4633-
isPropertyAccess = token() !== SyntaxKind.OpenBracketToken;
4633+
isPropertyAccess = tokenIsIdentifierOrKeyword(token());
46344634
}
46354635
else {
46364636
isPropertyAccess = parseOptional(SyntaxKind.DotToken);

src/compiler/transformers/esnext.ts

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ namespace ts {
2323
case SyntaxKind.PropertyAccessExpression:
2424
case SyntaxKind.ElementAccessExpression:
2525
case SyntaxKind.CallExpression:
26-
case SyntaxKind.TaggedTemplateExpression:
2726
if (node.flags & NodeFlags.OptionalChain) {
2827
const updated = visitOptionalExpression(node as OptionalChain, /*captureThisArg*/ false);
2928
Debug.assertNotNode(updated, isSyntheticReference);

src/testRunner/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"unittests/evaluation/awaiter.ts",
7676
"unittests/evaluation/forAwaitOf.ts",
7777
"unittests/evaluation/forOf.ts",
78+
"unittests/evaluation/optionalCall.ts",
7879
"unittests/evaluation/objectRest.ts",
7980
"unittests/services/cancellableLanguageServiceOperations.ts",
8081
"unittests/services/colorization.ts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
describe("unittests:: evaluation:: optionalCall", () => {
2+
it("f?.()", async () => {
3+
const result = evaluator.evaluateTypeScript(`
4+
function f(a) {
5+
output.push(a);
6+
output.push(this);
7+
}
8+
export const output: any[] = [];
9+
f?.(1);
10+
`);
11+
assert.strictEqual(result.output[0], 1);
12+
assert.isUndefined(result.output[1]);
13+
});
14+
it("o.f?.()", async () => {
15+
const result = evaluator.evaluateTypeScript(`
16+
export const o = {
17+
f(a) {
18+
output.push(a);
19+
output.push(this);
20+
}
21+
};
22+
export const output: any[] = [];
23+
o.f?.(1);
24+
`);
25+
assert.strictEqual(result.output[0], 1);
26+
assert.strictEqual(result.output[1], result.o);
27+
});
28+
it("o.x.f?.()", async () => {
29+
const result = evaluator.evaluateTypeScript(`
30+
export const o = {
31+
x: {
32+
f(a) {
33+
output.push(a);
34+
output.push(this);
35+
}
36+
}
37+
};
38+
export const output: any[] = [];
39+
o.x.f?.(1);
40+
`);
41+
assert.strictEqual(result.output[0], 1);
42+
assert.strictEqual(result.output[1], result.o.x);
43+
});
44+
it("o?.f()", async () => {
45+
const result = evaluator.evaluateTypeScript(`
46+
export const o = {
47+
f(a) {
48+
output.push(a);
49+
output.push(this);
50+
}
51+
};
52+
export const output: any[] = [];
53+
o?.f(1);
54+
`);
55+
assert.strictEqual(result.output[0], 1);
56+
assert.strictEqual(result.output[1], result.o);
57+
});
58+
it("o?.f?.()", async () => {
59+
const result = evaluator.evaluateTypeScript(`
60+
export const o = {
61+
f(a) {
62+
output.push(a);
63+
output.push(this);
64+
}
65+
};
66+
export const output: any[] = [];
67+
o?.f?.(1);
68+
`);
69+
assert.strictEqual(result.output[0], 1);
70+
assert.strictEqual(result.output[1], result.o);
71+
});
72+
it("o.x?.f()", async () => {
73+
const result = evaluator.evaluateTypeScript(`
74+
export const o = {
75+
x: {
76+
f(a) {
77+
output.push(a);
78+
output.push(this);
79+
}
80+
}
81+
};
82+
export const output: any[] = [];
83+
o.x?.f(1);
84+
`);
85+
assert.strictEqual(result.output[0], 1);
86+
assert.strictEqual(result.output[1], result.o.x);
87+
});
88+
it("o?.x.f()", async () => {
89+
const result = evaluator.evaluateTypeScript(`
90+
export const o = {
91+
x: {
92+
f(a) {
93+
output.push(a);
94+
output.push(this);
95+
}
96+
}
97+
};
98+
export const output: any[] = [];
99+
o?.x.f(1);
100+
`);
101+
assert.strictEqual(result.output[0], 1);
102+
assert.strictEqual(result.output[1], result.o.x);
103+
});
104+
it("o?.x?.f()", async () => {
105+
const result = evaluator.evaluateTypeScript(`
106+
export const o = {
107+
x: {
108+
f(a) {
109+
output.push(a);
110+
output.push(this);
111+
}
112+
}
113+
};
114+
export const output: any[] = [];
115+
o?.x?.f(1);
116+
`);
117+
assert.strictEqual(result.output[0], 1);
118+
assert.strictEqual(result.output[1], result.o.x);
119+
});
120+
it("o?.x?.f?.()", async () => {
121+
const result = evaluator.evaluateTypeScript(`
122+
export const o = {
123+
x: {
124+
f(a) {
125+
output.push(a);
126+
output.push(this);
127+
}
128+
}
129+
};
130+
export const output: any[] = [];
131+
o?.x?.f?.(1);
132+
`);
133+
assert.strictEqual(result.output[0], 1);
134+
assert.strictEqual(result.output[1], result.o.x);
135+
});
136+
it("f?.()?.()", async () => {
137+
const result = evaluator.evaluateTypeScript(`
138+
function g(a) {
139+
output.push(a);
140+
output.push(this);
141+
}
142+
function f(a) {
143+
output.push(a);
144+
return g;
145+
}
146+
export const output: any[] = [];
147+
f?.(1)?.(2)
148+
`);
149+
assert.strictEqual(result.output[0], 1);
150+
assert.strictEqual(result.output[1], 2);
151+
assert.isUndefined(result.output[2]);
152+
});
153+
it("f?.().f?.()", async () => {
154+
const result = evaluator.evaluateTypeScript(`
155+
export const o = {
156+
f(a) {
157+
output.push(a);
158+
output.push(this);
159+
}
160+
};
161+
function f(a) {
162+
output.push(a);
163+
return o;
164+
}
165+
export const output: any[] = [];
166+
f?.(1).f?.(2)
167+
`);
168+
assert.strictEqual(result.output[0], 1);
169+
assert.strictEqual(result.output[1], 2);
170+
assert.strictEqual(result.output[2], result.o);
171+
});
172+
it("f?.()?.f?.()", async () => {
173+
const result = evaluator.evaluateTypeScript(`
174+
export const o = {
175+
f(a) {
176+
output.push(a);
177+
output.push(this);
178+
}
179+
};
180+
function f(a) {
181+
output.push(a);
182+
return o;
183+
}
184+
export const output: any[] = [];
185+
f?.(1)?.f?.(2)
186+
`);
187+
assert.strictEqual(result.output[0], 1);
188+
assert.strictEqual(result.output[1], 2);
189+
assert.strictEqual(result.output[2], result.o);
190+
});
191+
});
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//// [callChain.2.ts]
2+
declare const o1: undefined | (() => number);
3+
o1?.();
4+
5+
declare const o2: undefined | { b: () => number };
6+
o2?.b();
7+
8+
declare const o3: { b: (() => { c: string }) | undefined };
9+
o3.b?.().c;
10+
11+
12+
//// [callChain.2.js]
13+
var _a, _b, _c, _d;
14+
(_a = o1) === null || _a === void 0 ? void 0 : _a();
15+
(_b = o2) === null || _b === void 0 ? void 0 : _b.b();
16+
(_d = (_c = o3).b) === null || _d === void 0 ? void 0 : _d.call(_c).c;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts ===
2+
declare const o1: undefined | (() => number);
3+
>o1 : Symbol(o1, Decl(callChain.2.ts, 0, 13))
4+
5+
o1?.();
6+
>o1 : Symbol(o1, Decl(callChain.2.ts, 0, 13))
7+
8+
declare const o2: undefined | { b: () => number };
9+
>o2 : Symbol(o2, Decl(callChain.2.ts, 3, 13))
10+
>b : Symbol(b, Decl(callChain.2.ts, 3, 31))
11+
12+
o2?.b();
13+
>o2?.b : Symbol(b, Decl(callChain.2.ts, 3, 31))
14+
>o2 : Symbol(o2, Decl(callChain.2.ts, 3, 13))
15+
>b : Symbol(b, Decl(callChain.2.ts, 3, 31))
16+
17+
declare const o3: { b: (() => { c: string }) | undefined };
18+
>o3 : Symbol(o3, Decl(callChain.2.ts, 6, 13))
19+
>b : Symbol(b, Decl(callChain.2.ts, 6, 19))
20+
>c : Symbol(c, Decl(callChain.2.ts, 6, 31))
21+
22+
o3.b?.().c;
23+
>o3.b?.().c : Symbol(c, Decl(callChain.2.ts, 6, 31))
24+
>o3.b : Symbol(b, Decl(callChain.2.ts, 6, 19))
25+
>o3 : Symbol(o3, Decl(callChain.2.ts, 6, 13))
26+
>b : Symbol(b, Decl(callChain.2.ts, 6, 19))
27+
>c : Symbol(c, Decl(callChain.2.ts, 6, 31))
28+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts ===
2+
declare const o1: undefined | (() => number);
3+
>o1 : () => number
4+
5+
o1?.();
6+
>o1?.() : number
7+
>o1 : () => number
8+
9+
declare const o2: undefined | { b: () => number };
10+
>o2 : { b: () => number; }
11+
>b : () => number
12+
13+
o2?.b();
14+
>o2?.b() : number
15+
>o2?.b : () => number
16+
>o2 : { b: () => number; }
17+
>b : () => number
18+
19+
declare const o3: { b: (() => { c: string }) | undefined };
20+
>o3 : { b: () => { c: string; }; }
21+
>b : () => { c: string; }
22+
>c : string
23+
24+
o3.b?.().c;
25+
>o3.b?.().c : string
26+
>o3.b?.() : { c: string; }
27+
>o3.b : () => { c: string; }
28+
>o3 : { b: () => { c: string; }; }
29+
>b : () => { c: string; }
30+
>c : string
31+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [elementAccessChain.2.ts]
2+
declare const o1: undefined | { b: string };
3+
o1?.["b"];
4+
5+
declare const o2: undefined | { b: { c: string } };
6+
o2?.["b"].c;
7+
o2?.b["c"];
8+
9+
declare const o3: { b: undefined | { c: string } };
10+
o3["b"]?.c;
11+
o3.b?.["c"];
12+
13+
14+
//// [elementAccessChain.2.js]
15+
var _a, _b, _c, _d, _e;
16+
(_a = o1) === null || _a === void 0 ? void 0 : _a["b"];
17+
(_b = o2) === null || _b === void 0 ? void 0 : _b["b"].c;
18+
(_c = o2) === null || _c === void 0 ? void 0 : _c.b["c"];
19+
(_d = o3["b"]) === null || _d === void 0 ? void 0 : _d.c;
20+
(_e = o3.b) === null || _e === void 0 ? void 0 : _e["c"];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.2.ts ===
2+
declare const o1: undefined | { b: string };
3+
>o1 : Symbol(o1, Decl(elementAccessChain.2.ts, 0, 13))
4+
>b : Symbol(b, Decl(elementAccessChain.2.ts, 0, 31))
5+
6+
o1?.["b"];
7+
>o1 : Symbol(o1, Decl(elementAccessChain.2.ts, 0, 13))
8+
>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 0, 31))
9+
10+
declare const o2: undefined | { b: { c: string } };
11+
>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13))
12+
>b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31))
13+
>c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36))
14+
15+
o2?.["b"].c;
16+
>o2?.["b"].c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36))
17+
>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13))
18+
>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31))
19+
>c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36))
20+
21+
o2?.b["c"];
22+
>o2?.b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31))
23+
>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13))
24+
>b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31))
25+
>"c" : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36))
26+
27+
declare const o3: { b: undefined | { c: string } };
28+
>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13))
29+
>b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19))
30+
>c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36))
31+
32+
o3["b"]?.c;
33+
>o3["b"]?.c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36))
34+
>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13))
35+
>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19))
36+
>c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36))
37+
38+
o3.b?.["c"];
39+
>o3.b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19))
40+
>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13))
41+
>b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19))
42+
>"c" : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36))
43+

0 commit comments

Comments
 (0)