Skip to content

Commit

Permalink
feat(parser): handle optional chaining in scope analysis (#1169)
Browse files Browse the repository at this point in the history
Co-authored-by: nizarius <petrpotapov@Petrs-MacBook-Pro.local>
Co-authored-by: Brad Zacher <brad.zacher@gmail.com>
  • Loading branch information
3 people committed Nov 11, 2019
1 parent 96d1cc3 commit 026ceb9
Show file tree
Hide file tree
Showing 5 changed files with 384 additions and 978 deletions.
Expand Up @@ -30,6 +30,71 @@ type Handler = (event: string) => any
`,
options: ['event'],
},
{
code: `
const a = foo?.bar?.name
`,
},
{
code: `
const a = foo?.bar?.name ?? "foobar"
`,
},
{
code: `
const a = foo()?.bar;
`,
},
{
code: `
const a = foo()?.bar ?? true;
`,
},
],
invalid: [
{
code: `
function onClick() {
console.log(event);
}
fdescribe("foo", function() {
});
`,
options: ['event'],
errors: [
{
message: "Unexpected use of 'event'.",
// the base rule doesn't use messageId
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
],
},
{
code: `
confirm("TEST");
`,
options: ['confirm'],
errors: [
{
message: "Unexpected use of 'confirm'.",
// the base rule doesn't use messageId
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
],
},
{
code: `
var a = confirm("TEST")?.a;
`,
options: ['confirm'],
errors: [
{
message: "Unexpected use of 'confirm'.",
// the base rule doesn't use messageId
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
],
},
],
invalid: [],
});
53 changes: 52 additions & 1 deletion packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
Expand Up @@ -68,6 +68,57 @@ function eachr(subject: Object | Array<Value>): typeof subject {
`
function eachr<Key, Value>(subject: Map<Key, Value>): typeof subject;
`,
`
var a = { b: 3 };
var c = a?.b;
`,
`
var a = { b: { c: 3 } };
var d = a?.["b"]?.c;
`,
`
var a = { b: 3 };
var c = { };
var d = (a || c)?.b;
`,
`
var a = { b: () => {} };
a?.b();
`,
],
invalid: [
{
code: 'a = 5;',
errors: [
{
messageId: 'undef',
data: {
name: 'a',
},
},
],
},
{
code: 'a?.b = 5;',
errors: [
{
messageId: 'undef',
data: {
name: 'a',
},
},
],
},
{
code: 'a()?.b = 5;',
errors: [
{
messageId: 'undef',
data: {
name: 'a',
},
},
],
},
],
invalid: [],
});
@@ -0,0 +1,85 @@
import rule from 'eslint/lib/rules/no-use-before-define';
import { RuleTester } from '../RuleTester';

const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: {},
},
parser: '@typescript-eslint/parser',
});

ruleTester.run('no-use-before-define', rule, {
valid: [
`
const updatedAt = data?.updatedAt;
`,
`
function f() {
return function t() {};
}
f()?.();
`,
`
var a = { b: 5 };
alert(a?.b);
`,
],
invalid: [
{
code: `
f();
function f() {}
`,
errors: [
{
message: "'f' was used before it was defined.",
// the base rule doesn't use messageId
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
],
},
{
code: `
alert(a);
var a = 10;
`,
errors: [
{
message: "'a' was used before it was defined.",
// the base rule doesn't use messageId
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
],
},
{
code: `
f()?.();
function f() {
return function t() {};
}
`,
errors: [
{
message: "'f' was used before it was defined.",
// the base rule doesn't use messageId
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
],
},
{
code: `
alert(a?.b);
var a = { b: 5 };
`,
errors: [
{
message: "'a' was used before it was defined.",
// the base rule doesn't use messageId
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
],
},
],
});
23 changes: 23 additions & 0 deletions packages/parser/src/analyze-scope.ts
Expand Up @@ -344,6 +344,29 @@ class Referencer extends TSESLintScope.Referencer<ScopeManager> {
node.arguments.forEach(this.visit, this);
}

/**
* Visit optional member expression.
* @param node The OptionalMemberExpression node to visit.
*/
OptionalMemberExpression(node: TSESTree.OptionalMemberExpression): void {
this.visit(node.object);
if (node.computed) {
this.visit(node.property);
}
}

/**
* Visit optional call expression.
* @param node The OptionalMemberExpression node to visit.
*/
OptionalCallExpression(node: TSESTree.OptionalCallExpression): void {
this.visitTypeParameters(node);

this.visit(node.callee);

node.arguments.forEach(this.visit, this);
}

/**
* Define the variable of this function declaration only once.
* Because to avoid confusion of `no-redeclare` rule by overloading.
Expand Down

0 comments on commit 026ceb9

Please sign in to comment.