Skip to content

Commit

Permalink
Add grammar error for invalid tagged template, more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Sep 7, 2019
1 parent c95daab commit c2f53fc
Show file tree
Hide file tree
Showing 23 changed files with 562 additions and 3 deletions.
9 changes: 8 additions & 1 deletion src/compiler/checker.ts
Expand Up @@ -23182,7 +23182,7 @@ namespace ts {
}

function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
checkGrammarTypeArguments(node, node.typeArguments);
if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments);
if (languageVersion < ScriptTarget.ES2015) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
}
Expand Down Expand Up @@ -32891,6 +32891,13 @@ namespace ts {
checkGrammarForAtLeastOneTypeArgument(node, typeArguments);
}

function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean {
if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) {
return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain);
}
return false;
}

function checkGrammarForOmittedArgument(args: NodeArray<Expression> | undefined): boolean {
if (args) {
for (const arg of args) {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Expand Up @@ -1035,6 +1035,10 @@
"category": "Error",
"code": 1356
},
"Tagged template expressions are not permitted in an optional chain.": {
"category": "Error",
"code": 1357
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/parser.ts
Expand Up @@ -4630,7 +4630,7 @@ namespace ts {
let isPropertyAccess = false;
if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) {
questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken);
isPropertyAccess = token() !== SyntaxKind.OpenBracketToken;
isPropertyAccess = tokenIsIdentifierOrKeyword(token());
}
else {
isPropertyAccess = parseOptional(SyntaxKind.DotToken);
Expand Down
1 change: 0 additions & 1 deletion src/compiler/transformers/esnext.ts
Expand Up @@ -23,7 +23,6 @@ namespace ts {
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.CallExpression:
case SyntaxKind.TaggedTemplateExpression:
if (node.flags & NodeFlags.OptionalChain) {
const updated = visitOptionalExpression(node as OptionalChain, /*captureThisArg*/ false);
Debug.assertNotNode(updated, isSyntheticReference);
Expand Down
1 change: 1 addition & 0 deletions src/testRunner/tsconfig.json
Expand Up @@ -75,6 +75,7 @@
"unittests/evaluation/awaiter.ts",
"unittests/evaluation/forAwaitOf.ts",
"unittests/evaluation/forOf.ts",
"unittests/evaluation/optionalCall.ts",
"unittests/evaluation/objectRest.ts",
"unittests/services/cancellableLanguageServiceOperations.ts",
"unittests/services/colorization.ts",
Expand Down
191 changes: 191 additions & 0 deletions src/testRunner/unittests/evaluation/optionalCall.ts
@@ -0,0 +1,191 @@
describe("unittests:: evaluation:: optionalCall", () => {
it("f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
function f(a) {
output.push(a);
output.push(this);
}
export const output: any[] = [];
f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.isUndefined(result.output[1]);
});
it("o.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
export const output: any[] = [];
o.f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o.x.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o.x.f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.f()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
export const output: any[] = [];
o?.f(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o?.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
export const output: any[] = [];
o?.f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o.x?.f()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o.x?.f(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x.f()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o?.x.f(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x?.f()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o?.x?.f(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x?.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o?.x?.f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("f?.()?.()", async () => {
const result = evaluator.evaluateTypeScript(`
function g(a) {
output.push(a);
output.push(this);
}
function f(a) {
output.push(a);
return g;
}
export const output: any[] = [];
f?.(1)?.(2)
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], 2);
assert.isUndefined(result.output[2]);
});
it("f?.().f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
function f(a) {
output.push(a);
return o;
}
export const output: any[] = [];
f?.(1).f?.(2)
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], 2);
assert.strictEqual(result.output[2], result.o);
});
it("f?.()?.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
function f(a) {
output.push(a);
return o;
}
export const output: any[] = [];
f?.(1)?.f?.(2)
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], 2);
assert.strictEqual(result.output[2], result.o);
});
});
16 changes: 16 additions & 0 deletions tests/baselines/reference/callChain.2.js
@@ -0,0 +1,16 @@
//// [callChain.2.ts]
declare const o1: undefined | (() => number);
o1?.();

declare const o2: undefined | { b: () => number };
o2?.b();

declare const o3: { b: (() => { c: string }) | undefined };
o3.b?.().c;


//// [callChain.2.js]
var _a, _b, _c, _d;
(_a = o1) === null || _a === void 0 ? void 0 : _a();
(_b = o2) === null || _b === void 0 ? void 0 : _b.b();
(_d = (_c = o3).b) === null || _d === void 0 ? void 0 : _d.call(_c).c;
28 changes: 28 additions & 0 deletions tests/baselines/reference/callChain.2.symbols
@@ -0,0 +1,28 @@
=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts ===
declare const o1: undefined | (() => number);
>o1 : Symbol(o1, Decl(callChain.2.ts, 0, 13))

o1?.();
>o1 : Symbol(o1, Decl(callChain.2.ts, 0, 13))

declare const o2: undefined | { b: () => number };
>o2 : Symbol(o2, Decl(callChain.2.ts, 3, 13))
>b : Symbol(b, Decl(callChain.2.ts, 3, 31))

o2?.b();
>o2?.b : Symbol(b, Decl(callChain.2.ts, 3, 31))
>o2 : Symbol(o2, Decl(callChain.2.ts, 3, 13))
>b : Symbol(b, Decl(callChain.2.ts, 3, 31))

declare const o3: { b: (() => { c: string }) | undefined };
>o3 : Symbol(o3, Decl(callChain.2.ts, 6, 13))
>b : Symbol(b, Decl(callChain.2.ts, 6, 19))
>c : Symbol(c, Decl(callChain.2.ts, 6, 31))

o3.b?.().c;
>o3.b?.().c : Symbol(c, Decl(callChain.2.ts, 6, 31))
>o3.b : Symbol(b, Decl(callChain.2.ts, 6, 19))
>o3 : Symbol(o3, Decl(callChain.2.ts, 6, 13))
>b : Symbol(b, Decl(callChain.2.ts, 6, 19))
>c : Symbol(c, Decl(callChain.2.ts, 6, 31))

31 changes: 31 additions & 0 deletions tests/baselines/reference/callChain.2.types
@@ -0,0 +1,31 @@
=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.2.ts ===
declare const o1: undefined | (() => number);
>o1 : () => number

o1?.();
>o1?.() : number
>o1 : () => number

declare const o2: undefined | { b: () => number };
>o2 : { b: () => number; }
>b : () => number

o2?.b();
>o2?.b() : number
>o2?.b : () => number
>o2 : { b: () => number; }
>b : () => number

declare const o3: { b: (() => { c: string }) | undefined };
>o3 : { b: () => { c: string; }; }
>b : () => { c: string; }
>c : string

o3.b?.().c;
>o3.b?.().c : string
>o3.b?.() : { c: string; }
>o3.b : () => { c: string; }
>o3 : { b: () => { c: string; }; }
>b : () => { c: string; }
>c : string

20 changes: 20 additions & 0 deletions tests/baselines/reference/elementAccessChain.2.js
@@ -0,0 +1,20 @@
//// [elementAccessChain.2.ts]
declare const o1: undefined | { b: string };
o1?.["b"];

declare const o2: undefined | { b: { c: string } };
o2?.["b"].c;
o2?.b["c"];

declare const o3: { b: undefined | { c: string } };
o3["b"]?.c;
o3.b?.["c"];


//// [elementAccessChain.2.js]
var _a, _b, _c, _d, _e;
(_a = o1) === null || _a === void 0 ? void 0 : _a["b"];
(_b = o2) === null || _b === void 0 ? void 0 : _b["b"].c;
(_c = o2) === null || _c === void 0 ? void 0 : _c.b["c"];
(_d = o3["b"]) === null || _d === void 0 ? void 0 : _d.c;
(_e = o3.b) === null || _e === void 0 ? void 0 : _e["c"];
43 changes: 43 additions & 0 deletions tests/baselines/reference/elementAccessChain.2.symbols
@@ -0,0 +1,43 @@
=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.2.ts ===
declare const o1: undefined | { b: string };
>o1 : Symbol(o1, Decl(elementAccessChain.2.ts, 0, 13))
>b : Symbol(b, Decl(elementAccessChain.2.ts, 0, 31))

o1?.["b"];
>o1 : Symbol(o1, Decl(elementAccessChain.2.ts, 0, 13))
>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 0, 31))

declare const o2: undefined | { b: { c: string } };
>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13))
>b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31))
>c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36))

o2?.["b"].c;
>o2?.["b"].c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36))
>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13))
>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31))
>c : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36))

o2?.b["c"];
>o2?.b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31))
>o2 : Symbol(o2, Decl(elementAccessChain.2.ts, 3, 13))
>b : Symbol(b, Decl(elementAccessChain.2.ts, 3, 31))
>"c" : Symbol(c, Decl(elementAccessChain.2.ts, 3, 36))

declare const o3: { b: undefined | { c: string } };
>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13))
>b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19))
>c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36))

o3["b"]?.c;
>o3["b"]?.c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36))
>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13))
>"b" : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19))
>c : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36))

o3.b?.["c"];
>o3.b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19))
>o3 : Symbol(o3, Decl(elementAccessChain.2.ts, 7, 13))
>b : Symbol(b, Decl(elementAccessChain.2.ts, 7, 19))
>"c" : Symbol(c, Decl(elementAccessChain.2.ts, 7, 36))

0 comments on commit c2f53fc

Please sign in to comment.