Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(typescript-estree): correct parenthesized optional chain AST #1141

Merged
merged 4 commits into from Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1,687 changes: 1,542 additions & 145 deletions packages/parser/tests/lib/__snapshots__/typescript.ts.snap

Large diffs are not rendered by default.

@@ -0,0 +1,13 @@
function processOptionalCallParens(one?: any) {
(one?.fn());
(one?.two).fn();
(one.two?.fn());
(one.two?.three).fn();
(one.two?.three?.fn());

(one?.());
(one?.())();
(one?.())?.();

(one?.()).two;
}
Expand Up @@ -6,6 +6,7 @@ function processOptionalCall(one?: any) {
one.two?.three?.fn();

one?.();
one?.()();
one?.()?.();

one?.().two;
Expand Down
@@ -0,0 +1,8 @@
function processOptionalElementParens(one?: any) {
(one?.[2]);
(one?.[2])[3];
(one[2]?.[3]);
(one[2]?.[3])[4];
(one[2]?.[3]?.[4]);
(one[2]?.[3]?.[4])[5];
}
@@ -0,0 +1,8 @@
function processOptionalParens(one?: any) {
(one?.two);
(one?.two).three;
(one.two?.three);
(one.two?.three).four;
(one.two?.three?.four);
(one.two?.three?.four).five;
}
51 changes: 30 additions & 21 deletions packages/typescript-estree/src/convert.ts
Expand Up @@ -1690,16 +1690,19 @@ export class Converter {
}

case SyntaxKind.PropertyAccessExpression: {
const isLocallyOptional = node.questionDotToken !== undefined;
const object = this.convertChild(node.expression);
const property = this.convertChild(node.name);
const computed = false;
if (
isLocallyOptional ||
// the optional expression should propogate up the member expression tree
object.type === AST_NODE_TYPES.OptionalMemberExpression ||
object.type === AST_NODE_TYPES.OptionalCallExpression
) {

const isLocallyOptional = node.questionDotToken !== undefined;
// the optional expression should propogate up the member expression tree
const isChildOptional =
(object.type === AST_NODE_TYPES.OptionalMemberExpression ||
object.type === AST_NODE_TYPES.OptionalCallExpression) &&
// (x?.y).z is semantically different, and as such .z is no longer optional
node.expression.kind !== ts.SyntaxKind.ParenthesizedExpression;

if (isLocallyOptional || isChildOptional) {
return this.createNode<TSESTree.OptionalMemberExpression>(node, {
type: AST_NODE_TYPES.OptionalMemberExpression,
object,
Expand All @@ -1719,16 +1722,19 @@ export class Converter {
}

case SyntaxKind.ElementAccessExpression: {
const isLocallyOptional = node.questionDotToken !== undefined;
const object = this.convertChild(node.expression);
const property = this.convertChild(node.argumentExpression);
const computed = true;
if (
isLocallyOptional ||
// the optional expression should propogate up the member expression tree
object.type === AST_NODE_TYPES.OptionalMemberExpression ||
object.type === AST_NODE_TYPES.OptionalCallExpression
) {

const isLocallyOptional = node.questionDotToken !== undefined;
// the optional expression should propogate up the member expression tree
const isChildOptional =
(object.type === AST_NODE_TYPES.OptionalMemberExpression ||
object.type === AST_NODE_TYPES.OptionalCallExpression) &&
// (x?.y).z is semantically different, and as such .z is no longer optional
node.expression.kind !== ts.SyntaxKind.ParenthesizedExpression;

if (isLocallyOptional || isChildOptional) {
return this.createNode<TSESTree.OptionalMemberExpression>(node, {
type: AST_NODE_TYPES.OptionalMemberExpression,
object,
Expand All @@ -1748,16 +1754,19 @@ export class Converter {
}

case SyntaxKind.CallExpression: {
const isLocallyOptional = node.questionDotToken !== undefined;
const callee = this.convertChild(node.expression);
const args = node.arguments.map(el => this.convertChild(el));
let result;
if (
isLocallyOptional ||
// the optional expression should propogate up the member expression tree
callee.type === AST_NODE_TYPES.OptionalMemberExpression ||
callee.type === AST_NODE_TYPES.OptionalCallExpression
) {

const isLocallyOptional = node.questionDotToken !== undefined;
// the optional expression should propogate up the member expression tree
const isChildOptional =
(callee.type === AST_NODE_TYPES.OptionalMemberExpression ||
callee.type === AST_NODE_TYPES.OptionalCallExpression) &&
// (x?.y).z() is semantically different, and as such .z() is no longer optional
node.expression.kind !== ts.SyntaxKind.ParenthesizedExpression;

if (isLocallyOptional || isChildOptional) {
result = this.createNode<TSESTree.OptionalCallExpression>(node, {
type: AST_NODE_TYPES.OptionalCallExpression,
callee,
Expand Down
5 changes: 4 additions & 1 deletion packages/typescript-estree/src/create-program/shared.ts
Expand Up @@ -20,7 +20,10 @@ const DEFAULT_COMPILER_OPTIONS: ts.CompilerOptions = {

// This narrows the type so we can be sure we're passing canonical names in the correct places
type CanonicalPath = string & { __brand: unknown };
const getCanonicalFileName = ts.sys.useCaseSensitiveFileNames
// typescript doesn't provide a ts.sys implementation for browser environments
const useCaseSensitiveFileNames =
ts.sys !== undefined ? ts.sys.useCaseSensitiveFileNames : true;
const getCanonicalFileName = useCaseSensitiveFileNames
? (filePath: string): CanonicalPath =>
path.normalize(filePath) as CanonicalPath
: (filePath: string): CanonicalPath =>
Expand Down
Expand Up @@ -583,8 +583,11 @@ tester.addFixturePatternConfig('typescript/basics', {
*/
// optional chaining
'optional-chain',
'optional-chain-with-parens',
'optional-chain-call',
'optional-chain-call-with-parens',
'optional-chain-element-access',
'optional-chain-element-access-with-parens',
'async-function-expression',
'class-with-accessibility-modifiers',
'class-with-mixin',
Expand Down
Expand Up @@ -1918,8 +1918,14 @@ exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" e

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/optional-chain-call.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/optional-chain-call-with-parens.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/optional-chain-element-access.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/optional-chain-element-access-with-parens.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/optional-chain-with-parens.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/parenthesized-use-strict.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/readonly-arrays.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;
Expand Down