Skip to content

Commit 026ceb9

Browse files
Nizariusnizariusbradzacher
committed
feat(parser): handle optional chaining in scope analysis (#1169)
Co-authored-by: nizarius <petrpotapov@Petrs-MacBook-Pro.local> Co-authored-by: Brad Zacher <brad.zacher@gmail.com>
1 parent 96d1cc3 commit 026ceb9

File tree

5 files changed

+384
-978
lines changed

5 files changed

+384
-978
lines changed

packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts

+66-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,71 @@ type Handler = (event: string) => any
3030
`,
3131
options: ['event'],
3232
},
33+
{
34+
code: `
35+
const a = foo?.bar?.name
36+
`,
37+
},
38+
{
39+
code: `
40+
const a = foo?.bar?.name ?? "foobar"
41+
`,
42+
},
43+
{
44+
code: `
45+
const a = foo()?.bar;
46+
`,
47+
},
48+
{
49+
code: `
50+
const a = foo()?.bar ?? true;
51+
`,
52+
},
53+
],
54+
invalid: [
55+
{
56+
code: `
57+
function onClick() {
58+
console.log(event);
59+
}
60+
61+
fdescribe("foo", function() {
62+
});
63+
`,
64+
options: ['event'],
65+
errors: [
66+
{
67+
message: "Unexpected use of 'event'.",
68+
// the base rule doesn't use messageId
69+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
70+
} as any,
71+
],
72+
},
73+
{
74+
code: `
75+
confirm("TEST");
76+
`,
77+
options: ['confirm'],
78+
errors: [
79+
{
80+
message: "Unexpected use of 'confirm'.",
81+
// the base rule doesn't use messageId
82+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
83+
} as any,
84+
],
85+
},
86+
{
87+
code: `
88+
var a = confirm("TEST")?.a;
89+
`,
90+
options: ['confirm'],
91+
errors: [
92+
{
93+
message: "Unexpected use of 'confirm'.",
94+
// the base rule doesn't use messageId
95+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
96+
} as any,
97+
],
98+
},
3399
],
34-
invalid: [],
35100
});

packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts

+52-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,57 @@ function eachr(subject: Object | Array<Value>): typeof subject {
6868
`
6969
function eachr<Key, Value>(subject: Map<Key, Value>): typeof subject;
7070
`,
71+
`
72+
var a = { b: 3 };
73+
var c = a?.b;
74+
`,
75+
`
76+
var a = { b: { c: 3 } };
77+
var d = a?.["b"]?.c;
78+
`,
79+
`
80+
var a = { b: 3 };
81+
var c = { };
82+
var d = (a || c)?.b;
83+
`,
84+
`
85+
var a = { b: () => {} };
86+
a?.b();
87+
`,
88+
],
89+
invalid: [
90+
{
91+
code: 'a = 5;',
92+
errors: [
93+
{
94+
messageId: 'undef',
95+
data: {
96+
name: 'a',
97+
},
98+
},
99+
],
100+
},
101+
{
102+
code: 'a?.b = 5;',
103+
errors: [
104+
{
105+
messageId: 'undef',
106+
data: {
107+
name: 'a',
108+
},
109+
},
110+
],
111+
},
112+
{
113+
code: 'a()?.b = 5;',
114+
errors: [
115+
{
116+
messageId: 'undef',
117+
data: {
118+
name: 'a',
119+
},
120+
},
121+
],
122+
},
71123
],
72-
invalid: [],
73124
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import rule from 'eslint/lib/rules/no-use-before-define';
2+
import { RuleTester } from '../RuleTester';
3+
4+
const ruleTester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 6,
7+
sourceType: 'module',
8+
ecmaFeatures: {},
9+
},
10+
parser: '@typescript-eslint/parser',
11+
});
12+
13+
ruleTester.run('no-use-before-define', rule, {
14+
valid: [
15+
`
16+
const updatedAt = data?.updatedAt;
17+
`,
18+
`
19+
function f() {
20+
return function t() {};
21+
}
22+
f()?.();
23+
`,
24+
`
25+
var a = { b: 5 };
26+
alert(a?.b);
27+
`,
28+
],
29+
invalid: [
30+
{
31+
code: `
32+
f();
33+
function f() {}
34+
`,
35+
errors: [
36+
{
37+
message: "'f' was used before it was defined.",
38+
// the base rule doesn't use messageId
39+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
40+
} as any,
41+
],
42+
},
43+
{
44+
code: `
45+
alert(a);
46+
var a = 10;
47+
`,
48+
errors: [
49+
{
50+
message: "'a' was used before it was defined.",
51+
// the base rule doesn't use messageId
52+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
53+
} as any,
54+
],
55+
},
56+
{
57+
code: `
58+
f()?.();
59+
function f() {
60+
return function t() {};
61+
}
62+
`,
63+
errors: [
64+
{
65+
message: "'f' was used before it was defined.",
66+
// the base rule doesn't use messageId
67+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
68+
} as any,
69+
],
70+
},
71+
{
72+
code: `
73+
alert(a?.b);
74+
var a = { b: 5 };
75+
`,
76+
errors: [
77+
{
78+
message: "'a' was used before it was defined.",
79+
// the base rule doesn't use messageId
80+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
81+
} as any,
82+
],
83+
},
84+
],
85+
});

packages/parser/src/analyze-scope.ts

+23
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,29 @@ class Referencer extends TSESLintScope.Referencer<ScopeManager> {
344344
node.arguments.forEach(this.visit, this);
345345
}
346346

347+
/**
348+
* Visit optional member expression.
349+
* @param node The OptionalMemberExpression node to visit.
350+
*/
351+
OptionalMemberExpression(node: TSESTree.OptionalMemberExpression): void {
352+
this.visit(node.object);
353+
if (node.computed) {
354+
this.visit(node.property);
355+
}
356+
}
357+
358+
/**
359+
* Visit optional call expression.
360+
* @param node The OptionalMemberExpression node to visit.
361+
*/
362+
OptionalCallExpression(node: TSESTree.OptionalCallExpression): void {
363+
this.visitTypeParameters(node);
364+
365+
this.visit(node.callee);
366+
367+
node.arguments.forEach(this.visit, this);
368+
}
369+
347370
/**
348371
* Define the variable of this function declaration only once.
349372
* Because to avoid confusion of `no-redeclare` rule by overloading.

0 commit comments

Comments
 (0)