Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5ae286e

Browse files
authored
fix(typescript-estree): correct parenthesized optional chain AST (typescript-eslint#1141)
- Also fixes the package not working in the browser: - ts.sys undefined in the browser - process is undefined in the browser - Whitelist 3.7.1-rc
1 parent 1508670 commit 5ae286e

File tree

11 files changed

+9915
-874
lines changed

11 files changed

+9915
-874
lines changed

packages/parser/tests/lib/__snapshots__/typescript.ts.snap

Lines changed: 1542 additions & 145 deletions
Large diffs are not rendered by default.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function processOptionalCallParens(one?: any) {
2+
(one?.fn());
3+
(one?.two).fn();
4+
(one.two?.fn());
5+
(one.two?.three).fn();
6+
(one.two?.three?.fn());
7+
8+
(one?.());
9+
(one?.())();
10+
(one?.())?.();
11+
12+
(one?.()).two;
13+
}

packages/shared-fixtures/fixtures/typescript/basics/optional-chain-call.src.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ function processOptionalCall(one?: any) {
66
one.two?.three?.fn();
77

88
one?.();
9+
one?.()();
910
one?.()?.();
1011

1112
one?.().two;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function processOptionalElementParens(one?: any) {
2+
(one?.[2]);
3+
(one?.[2])[3];
4+
(one[2]?.[3]);
5+
(one[2]?.[3])[4];
6+
(one[2]?.[3]?.[4]);
7+
(one[2]?.[3]?.[4])[5];
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function processOptionalParens(one?: any) {
2+
(one?.two);
3+
(one?.two).three;
4+
(one.two?.three);
5+
(one.two?.three).four;
6+
(one.two?.three?.four);
7+
(one.two?.three?.four).five;
8+
}

packages/typescript-estree/src/convert.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,16 +1691,19 @@ export class Converter {
16911691
}
16921692

16931693
case SyntaxKind.PropertyAccessExpression: {
1694-
const isLocallyOptional = node.questionDotToken !== undefined;
16951694
const object = this.convertChild(node.expression);
16961695
const property = this.convertChild(node.name);
16971696
const computed = false;
1698-
if (
1699-
isLocallyOptional ||
1700-
// the optional expression should propogate up the member expression tree
1701-
object.type === AST_NODE_TYPES.OptionalMemberExpression ||
1702-
object.type === AST_NODE_TYPES.OptionalCallExpression
1703-
) {
1697+
1698+
const isLocallyOptional = node.questionDotToken !== undefined;
1699+
// the optional expression should propogate up the member expression tree
1700+
const isChildOptional =
1701+
(object.type === AST_NODE_TYPES.OptionalMemberExpression ||
1702+
object.type === AST_NODE_TYPES.OptionalCallExpression) &&
1703+
// (x?.y).z is semantically different, and as such .z is no longer optional
1704+
node.expression.kind !== ts.SyntaxKind.ParenthesizedExpression;
1705+
1706+
if (isLocallyOptional || isChildOptional) {
17041707
return this.createNode<TSESTree.OptionalMemberExpression>(node, {
17051708
type: AST_NODE_TYPES.OptionalMemberExpression,
17061709
object,
@@ -1720,16 +1723,19 @@ export class Converter {
17201723
}
17211724

17221725
case SyntaxKind.ElementAccessExpression: {
1723-
const isLocallyOptional = node.questionDotToken !== undefined;
17241726
const object = this.convertChild(node.expression);
17251727
const property = this.convertChild(node.argumentExpression);
17261728
const computed = true;
1727-
if (
1728-
isLocallyOptional ||
1729-
// the optional expression should propogate up the member expression tree
1730-
object.type === AST_NODE_TYPES.OptionalMemberExpression ||
1731-
object.type === AST_NODE_TYPES.OptionalCallExpression
1732-
) {
1729+
1730+
const isLocallyOptional = node.questionDotToken !== undefined;
1731+
// the optional expression should propogate up the member expression tree
1732+
const isChildOptional =
1733+
(object.type === AST_NODE_TYPES.OptionalMemberExpression ||
1734+
object.type === AST_NODE_TYPES.OptionalCallExpression) &&
1735+
// (x?.y).z is semantically different, and as such .z is no longer optional
1736+
node.expression.kind !== ts.SyntaxKind.ParenthesizedExpression;
1737+
1738+
if (isLocallyOptional || isChildOptional) {
17331739
return this.createNode<TSESTree.OptionalMemberExpression>(node, {
17341740
type: AST_NODE_TYPES.OptionalMemberExpression,
17351741
object,
@@ -1749,16 +1755,19 @@ export class Converter {
17491755
}
17501756

17511757
case SyntaxKind.CallExpression: {
1752-
const isLocallyOptional = node.questionDotToken !== undefined;
17531758
const callee = this.convertChild(node.expression);
17541759
const args = node.arguments.map(el => this.convertChild(el));
17551760
let result;
1756-
if (
1757-
isLocallyOptional ||
1758-
// the optional expression should propogate up the member expression tree
1759-
callee.type === AST_NODE_TYPES.OptionalMemberExpression ||
1760-
callee.type === AST_NODE_TYPES.OptionalCallExpression
1761-
) {
1761+
1762+
const isLocallyOptional = node.questionDotToken !== undefined;
1763+
// the optional expression should propogate up the member expression tree
1764+
const isChildOptional =
1765+
(callee.type === AST_NODE_TYPES.OptionalMemberExpression ||
1766+
callee.type === AST_NODE_TYPES.OptionalCallExpression) &&
1767+
// (x?.y).z() is semantically different, and as such .z() is no longer optional
1768+
node.expression.kind !== ts.SyntaxKind.ParenthesizedExpression;
1769+
1770+
if (isLocallyOptional || isChildOptional) {
17621771
result = this.createNode<TSESTree.OptionalCallExpression>(node, {
17631772
type: AST_NODE_TYPES.OptionalCallExpression,
17641773
callee,

packages/typescript-estree/src/create-program/shared.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ const DEFAULT_COMPILER_OPTIONS: ts.CompilerOptions = {
2020

2121
// This narrows the type so we can be sure we're passing canonical names in the correct places
2222
type CanonicalPath = string & { __brand: unknown };
23-
const getCanonicalFileName = ts.sys.useCaseSensitiveFileNames
23+
// typescript doesn't provide a ts.sys implementation for browser environments
24+
const useCaseSensitiveFileNames =
25+
ts.sys !== undefined ? ts.sys.useCaseSensitiveFileNames : true;
26+
const getCanonicalFileName = useCaseSensitiveFileNames
2427
? (filePath: string): CanonicalPath =>
2528
path.normalize(filePath) as CanonicalPath
2629
: (filePath: string): CanonicalPath =>

packages/typescript-estree/src/parser.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@ import { TSESTree } from './ts-estree';
1616
* This needs to be kept in sync with the top-level README.md in the
1717
* typescript-eslint monorepo
1818
*/
19-
const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.8.0 || >3.7.0-dev.0';
19+
const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.8.0';
20+
/*
21+
* The semver package will ignore prerelease ranges, and we don't want to explicitly document every one
22+
* List them all separately here, so we can automatically create the full string
23+
*/
24+
const SUPPORTED_PRERELEASE_RANGES = ['>3.7.0-dev.0', '3.7.1-rc'];
2025
const ACTIVE_TYPESCRIPT_VERSION = ts.version;
2126
const isRunningSupportedTypeScriptVersion = semver.satisfies(
2227
ACTIVE_TYPESCRIPT_VERSION,
23-
SUPPORTED_TYPESCRIPT_VERSIONS,
28+
[SUPPORTED_TYPESCRIPT_VERSIONS]
29+
.concat(SUPPORTED_PRERELEASE_RANGES)
30+
.join(' || '),
2431
);
2532

2633
let extra: Extra;
@@ -224,22 +231,21 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void {
224231
}
225232

226233
function warnAboutTSVersion(): void {
227-
if (
228-
!isRunningSupportedTypeScriptVersion &&
229-
!warnedAboutTSVersion &&
230-
process.stdout.isTTY
231-
) {
232-
const border = '=============';
233-
const versionWarning = [
234-
border,
235-
'WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.',
236-
'You may find that it works just fine, or you may not.',
237-
`SUPPORTED TYPESCRIPT VERSIONS: ${SUPPORTED_TYPESCRIPT_VERSIONS}`,
238-
`YOUR TYPESCRIPT VERSION: ${ACTIVE_TYPESCRIPT_VERSION}`,
239-
'Please only submit bug reports when using the officially supported version.',
240-
border,
241-
];
242-
extra.log(versionWarning.join('\n\n'));
234+
if (!isRunningSupportedTypeScriptVersion && !warnedAboutTSVersion) {
235+
const isTTY = typeof process === undefined ? false : process.stdout.isTTY;
236+
if (isTTY) {
237+
const border = '=============';
238+
const versionWarning = [
239+
border,
240+
'WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.',
241+
'You may find that it works just fine, or you may not.',
242+
`SUPPORTED TYPESCRIPT VERSIONS: ${SUPPORTED_TYPESCRIPT_VERSIONS}`,
243+
`YOUR TYPESCRIPT VERSION: ${ACTIVE_TYPESCRIPT_VERSION}`,
244+
'Please only submit bug reports when using the officially supported version.',
245+
border,
246+
];
247+
extra.log(versionWarning.join('\n\n'));
248+
}
243249
warnedAboutTSVersion = true;
244250
}
245251
}

packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,8 +583,11 @@ tester.addFixturePatternConfig('typescript/basics', {
583583
*/
584584
// optional chaining
585585
'optional-chain',
586+
'optional-chain-with-parens',
586587
'optional-chain-call',
588+
'optional-chain-call-with-parens',
587589
'optional-chain-element-access',
590+
'optional-chain-element-access-with-parens',
588591
'async-function-expression',
589592
'class-with-accessibility-modifiers',
590593
'class-with-mixin',

packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,8 +1922,14 @@ exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" e
19221922

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

1925+
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"`;
1926+
19251927
exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/optional-chain-element-access.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;
19261928

1929+
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"`;
1930+
1931+
exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/optional-chain-with-parens.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;
1932+
19271933
exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/parenthesized-use-strict.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;
19281934

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

0 commit comments

Comments
 (0)