From 762bc99584ede4d0b8099a743991e957aec86aa8 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sun, 9 Aug 2020 18:47:38 -0700 Subject: [PATCH] fix(typescript-estree): correct ChainExpression interaction with parentheses and non-nulls (#2380) --- .../no-non-null-asserted-optional-chain.ts | 148 ++++++++------ .../src/rules/prefer-optional-chain.ts | 6 + ...o-non-null-asserted-optional-chain.test.ts | 4 + packages/types/src/ts-estree.ts | 5 +- packages/typescript-estree/src/convert.ts | 38 ++-- packages/typescript-estree/src/node-utils.ts | 30 +-- .../src/ts-estree/estree-to-ts-node-types.ts | 3 +- .../tests/ast-alignment/fixtures-to-test.ts | 23 --- ...n-call-with-non-null-assertion.src.ts.shot | 182 +++++++---------- ...ptional-chain-call-with-parens.src.ts.shot | 33 +++- ...access-with-non-null-assertion.src.ts.shot | 186 +++++++----------- ...-chain-with-non-null-assertion.src.ts.shot | 91 ++++----- 12 files changed, 348 insertions(+), 401 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-non-null-asserted-optional-chain.ts b/packages/eslint-plugin/src/rules/no-non-null-asserted-optional-chain.ts index 30eca97523e..f5652b257c1 100644 --- a/packages/eslint-plugin/src/rules/no-non-null-asserted-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/no-non-null-asserted-optional-chain.ts @@ -7,8 +7,6 @@ import * as ts from 'typescript'; import * as semver from 'semver'; import * as util from '../util'; -type MessageIds = 'noNonNullOptionalChain' | 'suggestRemovingNonNull'; - const is3dot9 = semver.satisfies( ts.version, `>= 3.9.0 || >= 3.9.1-rc || >= 3.9.0-beta`, @@ -17,7 +15,7 @@ const is3dot9 = semver.satisfies( }, ); -export default util.createRule<[], MessageIds>({ +export default util.createRule({ name: 'no-non-null-asserted-optional-chain', meta: { type: 'problem', @@ -37,64 +35,22 @@ export default util.createRule<[], MessageIds>({ }, defaultOptions: [], create(context) { - const sourceCode = context.getSourceCode(); - - function isValidFor3dot9(node: TSESTree.ChainExpression): boolean { - if (!is3dot9) { - return false; - } - - // TS3.9 made a breaking change to how non-null works with optional chains. - // Pre-3.9, `x?.y!.z` means `(x?.y).z` - i.e. it essentially scrubbed the optionality from the chain - // Post-3.9, `x?.y!.z` means `x?.y!.z` - i.e. it just asserts that the property `y` is non-null, not the result of `x?.y`. - // This means that for > 3.9, x?.y!.z is valid! - // NOTE: these cases are still invalid: - // - x?.y.z! - // - (x?.y)!.z - - const parent = util.nullThrows( - node.parent, - util.NullThrowsReasons.MissingParent, - ); - const grandparent = util.nullThrows( - parent.parent, - util.NullThrowsReasons.MissingParent, - ); - - if ( - grandparent.type !== AST_NODE_TYPES.MemberExpression && - grandparent.type !== AST_NODE_TYPES.CallExpression - ) { - return false; - } - - const lastChildToken = util.nullThrows( - sourceCode.getLastToken(parent, util.isNotNonNullAssertionPunctuator), - util.NullThrowsReasons.MissingToken('last token', node.type), - ); - if (util.isClosingParenToken(lastChildToken)) { - return false; - } - - const tokenAfterNonNull = sourceCode.getTokenAfter(parent); - if ( - tokenAfterNonNull != null && - util.isClosingParenToken(tokenAfterNonNull) - ) { - return false; - } + // TS3.9 made a breaking change to how non-null works with optional chains. + // Pre-3.9, `x?.y!.z` means `(x?.y).z` - i.e. it essentially scrubbed the optionality from the chain + // Post-3.9, `x?.y!.z` means `x?.y!.z` - i.e. it just asserts that the property `y` is non-null, not the result of `x?.y`. + // This means that for > 3.9, x?.y!.z is valid! + // + // NOTE: these cases are still invalid for 3.9: + // - x?.y.z! + // - (x?.y)!.z - return true; - } - - return { + const baseSelectors = { + // non-nulling a wrapped chain will scrub all nulls introduced by the chain + // (x?.y)! + // (x?.())! 'TSNonNullExpression > ChainExpression'( node: TSESTree.ChainExpression, ): void { - if (isValidFor3dot9(node)) { - return; - } - // selector guarantees this assertion const parent = node.parent as TSESTree.TSNonNullExpression; context.report({ @@ -114,6 +70,84 @@ export default util.createRule<[], MessageIds>({ ], }); }, + + // non-nulling at the end of a chain will scrub all nulls introduced by the chain + // x?.y! + // x?.()! + 'ChainExpression > TSNonNullExpression'( + node: TSESTree.TSNonNullExpression, + ): void { + context.report({ + node, + messageId: 'noNonNullOptionalChain', + // use a suggestion instead of a fixer, because this can obviously break type checks + suggest: [ + { + messageId: 'suggestRemovingNonNull', + fix(fixer): TSESLint.RuleFix { + return fixer.removeRange([node.range[1] - 1, node.range[1]]); + }, + }, + ], + }); + }, + }; + + if (is3dot9) { + return baseSelectors; + } + + return { + ...baseSelectors, + [[ + // > :not(ChainExpression) because that case is handled by a previous selector + 'MemberExpression > TSNonNullExpression.object > :not(ChainExpression)', + 'CallExpression > TSNonNullExpression.callee > :not(ChainExpression)', + ].join(', ')](child: TSESTree.Node): void { + // selector guarantees this assertion + const node = child.parent as TSESTree.TSNonNullExpression; + + let current = child; + while (current) { + switch (current.type) { + case AST_NODE_TYPES.MemberExpression: + if (current.optional) { + // found an optional chain! stop traversing + break; + } + + current = current.object; + continue; + + case AST_NODE_TYPES.CallExpression: + if (current.optional) { + // found an optional chain! stop traversing + break; + } + + current = current.callee; + continue; + + default: + // something that's not a ChainElement, which means this is not an optional chain we want to check + return; + } + } + + context.report({ + node, + messageId: 'noNonNullOptionalChain', + // use a suggestion instead of a fixer, because this can obviously break type checks + suggest: [ + { + messageId: 'suggestRemovingNonNull', + fix(fixer): TSESLint.RuleFix { + return fixer.removeRange([node.range[1] - 1, node.range[1]]); + }, + }, + ], + }); + }, }; }, }); diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index c58e5d113d6..cd886a9d997 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -234,6 +234,12 @@ export default util.createRule({ } if (node.type === AST_NODE_TYPES.ChainExpression) { + /* istanbul ignore if */ if ( + node.expression.type === AST_NODE_TYPES.TSNonNullExpression + ) { + // this shouldn't happen + return ''; + } return getText(node.expression); } diff --git a/packages/eslint-plugin/tests/rules/no-non-null-asserted-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/no-non-null-asserted-optional-chain.test.ts index 6affcf2fef5..12406d1dc41 100644 --- a/packages/eslint-plugin/tests/rules/no-non-null-asserted-optional-chain.test.ts +++ b/packages/eslint-plugin/tests/rules/no-non-null-asserted-optional-chain.test.ts @@ -8,7 +8,11 @@ const ruleTester = new RuleTester({ ruleTester.run('no-non-null-asserted-optional-chain', rule, { valid: [ 'foo.bar!;', + 'foo.bar!.baz;', + 'foo.bar!.baz();', 'foo.bar()!;', + 'foo.bar()!();', + 'foo.bar()!.baz;', 'foo?.bar;', 'foo?.bar();', '(foo?.bar).baz!;', diff --git a/packages/types/src/ts-estree.ts b/packages/types/src/ts-estree.ts index 14cc90e5857..61e6bc097a5 100644 --- a/packages/types/src/ts-estree.ts +++ b/packages/types/src/ts-estree.ts @@ -308,7 +308,10 @@ export type Node = export type Accessibility = 'public' | 'protected' | 'private'; export type BindingPattern = ArrayPattern | ObjectPattern; export type BindingName = BindingPattern | Identifier; -export type ChainElement = CallExpression | MemberExpression; +export type ChainElement = + | CallExpression + | MemberExpression + | TSNonNullExpression; export type ClassElement = | ClassProperty | MethodDefinition diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 0b83a4cb965..a35c869c72e 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -14,7 +14,7 @@ import { getTextForTokenKind, getTSNodeAccessibility, hasModifier, - isChildOptionalChain, + isChildUnwrappableOptionalChain, isComma, isComputedProperty, isESTreeClassMember, @@ -406,24 +406,36 @@ export class Converter { tsNode: | ts.PropertyAccessExpression | ts.ElementAccessExpression - | ts.CallExpression, + | ts.CallExpression + | ts.NonNullExpression, ): TSESTree.ChainExpression | TSESTree.ChainElement { - let child = (node.type === AST_NODE_TYPES.MemberExpression - ? node.object - : node.callee) as TSESTree.Node; - const isChildOptional = isChildOptionalChain(tsNode, child); + const { child, isOptional } = ((): { + child: TSESTree.Node; + isOptional: boolean; + } => { + if (node.type === AST_NODE_TYPES.MemberExpression) { + return { child: node.object, isOptional: node.optional }; + } + if (node.type === AST_NODE_TYPES.CallExpression) { + return { child: node.callee, isOptional: node.optional }; + } + return { child: node.expression, isOptional: false }; + })(); + const isChildUnwrappable = isChildUnwrappableOptionalChain(tsNode, child); - if (!isChildOptional && !node.optional) { + if (!isChildUnwrappable && !isOptional) { return node; } - if (isChainExpression(child)) { + if (isChildUnwrappable && isChainExpression(child)) { // unwrap the chain expression child - child = child.expression; + const newChild = child.expression; if (node.type === AST_NODE_TYPES.MemberExpression) { - node.object = child; + node.object = newChild; + } else if (node.type === AST_NODE_TYPES.CallExpression) { + node.callee = newChild; } else { - node.callee = child; + node.expression = newChild; } } @@ -2209,10 +2221,12 @@ export class Converter { } case SyntaxKind.NonNullExpression: { - return this.createNode(node, { + const nnExpr = this.createNode(node, { type: AST_NODE_TYPES.TSNonNullExpression, expression: this.convertChild(node.expression), }); + + return this.convertChainExpression(nnExpr, node); } case SyntaxKind.TypeLiteral: { diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index c40a0ddd1f6..74c58666f28 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -1,7 +1,6 @@ import unescape from 'lodash/unescape'; import * as ts from 'typescript'; import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from './ts-estree'; -import { typescriptVersionIsAtLeast } from './version-check'; const SyntaxKind = ts.SyntaxKind; @@ -452,11 +451,12 @@ export function isChainExpression( /** * Returns true of the child of property access expression is an optional chain */ -export function isChildOptionalChain( +export function isChildUnwrappableOptionalChain( node: | ts.PropertyAccessExpression | ts.ElementAccessExpression - | ts.CallExpression, + | ts.CallExpression + | ts.NonNullExpression, child: TSESTree.Node, ): boolean { if ( @@ -467,30 +467,6 @@ export function isChildOptionalChain( return true; } - if (!typescriptVersionIsAtLeast['3.9']) { - return false; - } - - // TS3.9 made a breaking change to how non-null works with optional chains. - // Pre-3.9, `x?.y!.z` means `(x?.y).z` - i.e. it essentially scrubbed the optionality from the chain - // Post-3.9, `x?.y!.z` means `x?.y!.z` - i.e. it just asserts that the property `y` is non-null, not the result of `x?.y` - - if ( - child.type !== AST_NODE_TYPES.TSNonNullExpression || - !isChainExpression(child.expression) - ) { - return false; - } - - if ( - // make sure it's not (x.y)!.z - node.expression.kind === ts.SyntaxKind.NonNullExpression && - (node.expression as ts.NonNullExpression).expression.kind !== - ts.SyntaxKind.ParenthesizedExpression - ) { - return true; - } - return false; } diff --git a/packages/typescript-estree/src/ts-estree/estree-to-ts-node-types.ts b/packages/typescript-estree/src/ts-estree/estree-to-ts-node-types.ts index e62efb7bb8b..707477a162f 100644 --- a/packages/typescript-estree/src/ts-estree/estree-to-ts-node-types.ts +++ b/packages/typescript-estree/src/ts-estree/estree-to-ts-node-types.ts @@ -23,7 +23,8 @@ export interface EstreeToTsNodeTypes { [AST_NODE_TYPES.ChainExpression]: | ts.CallExpression | ts.PropertyAccessExpression - | ts.ElementAccessExpression; + | ts.ElementAccessExpression + | ts.NonNullExpression; [AST_NODE_TYPES.ClassBody]: ts.ClassDeclaration | ts.ClassExpression; [AST_NODE_TYPES.ClassDeclaration]: ts.ClassDeclaration; [AST_NODE_TYPES.ClassExpression]: ts.ClassExpression; diff --git a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts index 5f613a834f8..c4573651fa0 100644 --- a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts +++ b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts @@ -411,21 +411,6 @@ tester.addFixturePatternConfig('typescript/basics', { * https://github.com/babel/babel/issues/11939 */ 'catch-clause-with-annotation', - - /** - * Optional chaining - * Babel has updated to ESTree's representation, and we haven't yet - * TODO: remove this with the v4 release - */ - 'optional-chain-call-with-non-null-assertion', - 'optional-chain-call-with-parens', - 'optional-chain-call', - 'optional-chain-element-access-with-non-null-assertion', - 'optional-chain-element-access-with-parens', - 'optional-chain-element-access', - 'optional-chain-with-non-null-assertion', - 'optional-chain-with-parens', - 'optional-chain', ], ignoreSourceType: [ /** @@ -479,14 +464,6 @@ tester.addFixturePatternConfig('typescript/decorators/property-decorators', { tester.addFixturePatternConfig('typescript/expressions', { fileType: 'ts', - ignore: [ - /** - * Optional chaining - * Babel has updated to ESTree's representation, and we haven't yet - * TODO: remove this with the v4 release - */ - 'optional-call-expression-type-arguments', - ], }); tester.addFixturePatternConfig('typescript/errorRecovery', { diff --git a/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-call-with-non-null-assertion.src.ts.shot b/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-call-with-non-null-assertion.src.ts.shot index 119ab05be33..cc4eb5c81ff 100644 --- a/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-call-with-non-null-assertion.src.ts.shot +++ b/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-call-with-non-null-assertion.src.ts.shot @@ -13,11 +13,21 @@ Object { "arguments": Array [], "callee": Object { "expression": Object { - "expression": Object { - "computed": false, + "computed": false, + "loc": Object { + "end": Object { + "column": 10, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "object": Object { "loc": Object { "end": Object { - "column": 10, + "column": 5, "line": 2, }, "start": Object { @@ -25,64 +35,37 @@ Object { "line": 2, }, }, - "object": Object { - "loc": Object { - "end": Object { - "column": 5, - "line": 2, - }, - "start": Object { - "column": 2, - "line": 2, - }, + "name": "one", + "range": Array [ + 40, + 43, + ], + "type": "Identifier", + }, + "optional": true, + "property": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 2, }, - "name": "one", - "range": Array [ - 40, - 43, - ], - "type": "Identifier", - }, - "optional": true, - "property": Object { - "loc": Object { - "end": Object { - "column": 10, - "line": 2, - }, - "start": Object { - "column": 7, - "line": 2, - }, + "start": Object { + "column": 7, + "line": 2, }, - "name": "two", - "range": Array [ - 45, - 48, - ], - "type": "Identifier", }, + "name": "two", "range": Array [ - 40, + 45, 48, ], - "type": "MemberExpression", - }, - "loc": Object { - "end": Object { - "column": 10, - "line": 2, - }, - "start": Object { - "column": 2, - "line": 2, - }, + "type": "Identifier", }, "range": Array [ 40, 48, ], - "type": "ChainExpression", + "type": "MemberExpression", }, "loc": Object { "end": Object { @@ -167,11 +150,21 @@ Object { }, "object": Object { "expression": Object { - "expression": Object { - "computed": false, + "computed": false, + "loc": Object { + "end": Object { + "column": 10, + "line": 3, + }, + "start": Object { + "column": 2, + "line": 3, + }, + }, + "object": Object { "loc": Object { "end": Object { - "column": 10, + "column": 5, "line": 3, }, "start": Object { @@ -179,64 +172,37 @@ Object { "line": 3, }, }, - "object": Object { - "loc": Object { - "end": Object { - "column": 5, - "line": 3, - }, - "start": Object { - "column": 2, - "line": 3, - }, + "name": "one", + "range": Array [ + 55, + 58, + ], + "type": "Identifier", + }, + "optional": true, + "property": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 3, }, - "name": "one", - "range": Array [ - 55, - 58, - ], - "type": "Identifier", - }, - "optional": true, - "property": Object { - "loc": Object { - "end": Object { - "column": 10, - "line": 3, - }, - "start": Object { - "column": 7, - "line": 3, - }, + "start": Object { + "column": 7, + "line": 3, }, - "name": "two", - "range": Array [ - 60, - 63, - ], - "type": "Identifier", }, + "name": "two", "range": Array [ - 55, + 60, 63, ], - "type": "MemberExpression", - }, - "loc": Object { - "end": Object { - "column": 10, - "line": 3, - }, - "start": Object { - "column": 2, - "line": 3, - }, + "type": "Identifier", }, "range": Array [ 55, 63, ], - "type": "ChainExpression", + "type": "MemberExpression", }, "loc": Object { "end": Object { @@ -677,7 +643,7 @@ Object { }, "loc": Object { "end": Object { - "column": 11, + "column": 12, "line": 6, }, "start": Object { @@ -687,9 +653,9 @@ Object { }, "range": Array [ 117, - 125, + 126, ], - "type": "ChainExpression", + "type": "TSNonNullExpression", }, "loc": Object { "end": Object { @@ -705,7 +671,7 @@ Object { 117, 126, ], - "type": "TSNonNullExpression", + "type": "ChainExpression", }, "loc": Object { "end": Object { @@ -814,7 +780,7 @@ Object { }, "loc": Object { "end": Object { - "column": 11, + "column": 12, "line": 7, }, "start": Object { @@ -824,9 +790,9 @@ Object { }, "range": Array [ 134, - 142, + 143, ], - "type": "ChainExpression", + "type": "TSNonNullExpression", }, "loc": Object { "end": Object { @@ -842,7 +808,7 @@ Object { 134, 143, ], - "type": "TSNonNullExpression", + "type": "ChainExpression", }, "optional": false, "property": Object { diff --git a/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-call-with-parens.src.ts.shot b/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-call-with-parens.src.ts.shot index fb93938c467..d53caae0ce8 100644 --- a/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-call-with-parens.src.ts.shot +++ b/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-call-with-parens.src.ts.shot @@ -935,11 +935,29 @@ Object { "expression": Object { "arguments": Array [], "callee": Object { - "arguments": Array [], - "callee": Object { + "expression": Object { + "arguments": Array [], + "callee": Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 10, + }, + "start": Object { + "column": 3, + "line": 10, + }, + }, + "name": "one", + "range": Array [ + 184, + 187, + ], + "type": "Identifier", + }, "loc": Object { "end": Object { - "column": 6, + "column": 10, "line": 10, }, "start": Object { @@ -947,12 +965,12 @@ Object { "line": 10, }, }, - "name": "one", + "optional": true, "range": Array [ 184, - 187, + 191, ], - "type": "Identifier", + "type": "CallExpression", }, "loc": Object { "end": Object { @@ -964,12 +982,11 @@ Object { "line": 10, }, }, - "optional": true, "range": Array [ 184, 191, ], - "type": "CallExpression", + "type": "ChainExpression", }, "loc": Object { "end": Object { diff --git a/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-element-access-with-non-null-assertion.src.ts.shot b/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-element-access-with-non-null-assertion.src.ts.shot index bcbac3030b1..6ea1b41480b 100644 --- a/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-element-access-with-non-null-assertion.src.ts.shot +++ b/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-element-access-with-non-null-assertion.src.ts.shot @@ -23,11 +23,21 @@ Object { }, "object": Object { "expression": Object { - "expression": Object { - "computed": true, + "computed": true, + "loc": Object { + "end": Object { + "column": 14, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "object": Object { "loc": Object { "end": Object { - "column": 14, + "column": 5, "line": 2, }, "start": Object { @@ -35,65 +45,38 @@ Object { "line": 2, }, }, - "object": Object { - "loc": Object { - "end": Object { - "column": 5, - "line": 2, - }, - "start": Object { - "column": 2, - "line": 2, - }, - }, - "name": "one", - "range": Array [ - 40, - 43, - ], - "type": "Identifier", - }, - "optional": true, - "property": Object { - "loc": Object { - "end": Object { - "column": 13, - "line": 2, - }, - "start": Object { - "column": 8, - "line": 2, - }, - }, - "range": Array [ - 46, - 51, - ], - "raw": "'two'", - "type": "Literal", - "value": "two", - }, + "name": "one", "range": Array [ 40, - 52, + 43, ], - "type": "MemberExpression", + "type": "Identifier", }, - "loc": Object { - "end": Object { - "column": 14, - "line": 2, - }, - "start": Object { - "column": 2, - "line": 2, + "optional": true, + "property": Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 2, + }, + "start": Object { + "column": 8, + "line": 2, + }, }, + "range": Array [ + 46, + 51, + ], + "raw": "'two'", + "type": "Literal", + "value": "two", }, "range": Array [ 40, 52, ], - "type": "ChainExpression", + "type": "MemberExpression", }, "loc": Object { "end": Object { @@ -385,7 +368,7 @@ Object { }, "loc": Object { "end": Object { - "column": 15, + "column": 16, "line": 4, }, "start": Object { @@ -395,9 +378,9 @@ Object { }, "range": Array [ 89, - 101, + 102, ], - "type": "ChainExpression", + "type": "TSNonNullExpression", }, "loc": Object { "end": Object { @@ -413,7 +396,7 @@ Object { 89, 102, ], - "type": "TSNonNullExpression", + "type": "ChainExpression", }, "optional": false, "property": Object { @@ -472,11 +455,21 @@ Object { }, "object": Object { "expression": Object { - "expression": Object { - "computed": false, + "computed": false, + "loc": Object { + "end": Object { + "column": 10, + "line": 5, + }, + "start": Object { + "column": 2, + "line": 5, + }, + }, + "object": Object { "loc": Object { "end": Object { - "column": 10, + "column": 5, "line": 5, }, "start": Object { @@ -484,64 +477,37 @@ Object { "line": 5, }, }, - "object": Object { - "loc": Object { - "end": Object { - "column": 5, - "line": 5, - }, - "start": Object { - "column": 2, - "line": 5, - }, + "name": "one", + "range": Array [ + 113, + 116, + ], + "type": "Identifier", + }, + "optional": true, + "property": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 5, }, - "name": "one", - "range": Array [ - 113, - 116, - ], - "type": "Identifier", - }, - "optional": true, - "property": Object { - "loc": Object { - "end": Object { - "column": 10, - "line": 5, - }, - "start": Object { - "column": 7, - "line": 5, - }, + "start": Object { + "column": 7, + "line": 5, }, - "name": "two", - "range": Array [ - 118, - 121, - ], - "type": "Identifier", }, + "name": "two", "range": Array [ - 113, + 118, 121, ], - "type": "MemberExpression", - }, - "loc": Object { - "end": Object { - "column": 10, - "line": 5, - }, - "start": Object { - "column": 2, - "line": 5, - }, + "type": "Identifier", }, "range": Array [ 113, 121, ], - "type": "ChainExpression", + "type": "MemberExpression", }, "loc": Object { "end": Object { @@ -833,7 +799,7 @@ Object { }, "loc": Object { "end": Object { - "column": 11, + "column": 12, "line": 7, }, "start": Object { @@ -843,9 +809,9 @@ Object { }, "range": Array [ 160, - 168, + 169, ], - "type": "ChainExpression", + "type": "TSNonNullExpression", }, "loc": Object { "end": Object { @@ -861,7 +827,7 @@ Object { 160, 169, ], - "type": "TSNonNullExpression", + "type": "ChainExpression", }, "optional": false, "property": Object { diff --git a/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-with-non-null-assertion.src.ts.shot b/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-with-non-null-assertion.src.ts.shot index cb8e274c77e..df6699c1555 100644 --- a/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-with-non-null-assertion.src.ts.shot +++ b/packages/typescript-estree/tests/snapshots/typescript/basics/optional-chain-with-non-null-assertion.src.ts.shot @@ -23,11 +23,21 @@ Object { }, "object": Object { "expression": Object { - "expression": Object { - "computed": false, + "computed": false, + "loc": Object { + "end": Object { + "column": 10, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "object": Object { "loc": Object { "end": Object { - "column": 10, + "column": 5, "line": 2, }, "start": Object { @@ -35,64 +45,37 @@ Object { "line": 2, }, }, - "object": Object { - "loc": Object { - "end": Object { - "column": 5, - "line": 2, - }, - "start": Object { - "column": 2, - "line": 2, - }, + "name": "one", + "range": Array [ + 40, + 43, + ], + "type": "Identifier", + }, + "optional": true, + "property": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 2, }, - "name": "one", - "range": Array [ - 40, - 43, - ], - "type": "Identifier", - }, - "optional": true, - "property": Object { - "loc": Object { - "end": Object { - "column": 10, - "line": 2, - }, - "start": Object { - "column": 7, - "line": 2, - }, + "start": Object { + "column": 7, + "line": 2, }, - "name": "two", - "range": Array [ - 45, - 48, - ], - "type": "Identifier", }, + "name": "two", "range": Array [ - 40, + 45, 48, ], - "type": "MemberExpression", - }, - "loc": Object { - "end": Object { - "column": 10, - "line": 2, - }, - "start": Object { - "column": 2, - "line": 2, - }, + "type": "Identifier", }, "range": Array [ 40, 48, ], - "type": "ChainExpression", + "type": "MemberExpression", }, "loc": Object { "end": Object { @@ -382,7 +365,7 @@ Object { }, "loc": Object { "end": Object { - "column": 11, + "column": 12, "line": 4, }, "start": Object { @@ -392,9 +375,9 @@ Object { }, "range": Array [ 81, - 89, + 90, ], - "type": "ChainExpression", + "type": "TSNonNullExpression", }, "loc": Object { "end": Object { @@ -410,7 +393,7 @@ Object { 81, 90, ], - "type": "TSNonNullExpression", + "type": "ChainExpression", }, "optional": false, "property": Object {