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(scope-manager): support type predicates #2493

Merged
merged 1 commit into from Sep 5, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 29 additions & 0 deletions packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
Expand Up @@ -111,6 +111,35 @@ function eachr<Key, Value>(subject: Map<Key, Value>): typeof subject;
var a = { b: () => {} };
a?.b();
`,
// https://github.com/typescript-eslint/typescript-eslint/issues/2462
`
export default class Column {
isColumnString(column: unknown): column is string {
return typeof this.column === 'string';
}
}
`,
`
type T = string;
function predicate(arg: any): arg is T {
return typeof arg === 'string';
}
`,
`
function predicate(arg: any): asserts arg {
if (arg == null) {
throw 'oops';
}
}
`,
`
type T = string;
function predicate(arg: any): asserts arg is T {
if (typeof arg !== 'string') {
throw 'oops';
}
}
`,
],
invalid: [
{
Expand Down
7 changes: 7 additions & 0 deletions packages/scope-manager/src/referencer/TypeVisitor.ts
Expand Up @@ -199,6 +199,13 @@ class TypeVisitor extends Visitor {
this.visit(node.default);
}

protected TSTypePredicate(node: TSESTree.TSTypePredicate): void {
if (node.parameterName.type !== AST_NODE_TYPES.TSThisType) {
this.#referencer.currentScope().referenceValue(node.parameterName);
}
this.visit(node.typeAnnotation);
}

// a type query `typeof foo` is a special case that references a _non-type_ variable,
protected TSTypeQuery(node: TSESTree.TSTypeQuery): void {
if (node.exprName.type === AST_NODE_TYPES.Identifier) {
Expand Down
@@ -0,0 +1 @@
const foo = (arg: any): asserts arg => {};
@@ -0,0 +1,84 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`functions arrow type-predicate-asserts1 1`] = `
ScopeManager {
variables: Array [
Variable$1 {
defs: Array [
VariableDefinition$1 {
name: Identifier<"foo">,
node: VariableDeclarator$1,
},
],
name: "foo",
references: Array [
Reference$1 {
identifier: Identifier<"foo">,
init: true,
isRead: false,
isTypeReference: false,
isValueReference: true,
isWrite: true,
resolved: Variable$1,
writeExpr: ArrowFunctionExpression$2,
},
],
isValueVariable: true,
isTypeVariable: false,
},
Variable$2 {
defs: Array [
ParameterDefinition$2 {
name: Identifier<"arg">,
node: ArrowFunctionExpression$2,
},
],
name: "arg",
references: Array [
Reference$2 {
identifier: Identifier<"arg">,
isRead: true,
isTypeReference: false,
isValueReference: true,
isWrite: false,
resolved: Variable$2,
},
],
isValueVariable: true,
isTypeVariable: false,
},
],
scopes: Array [
GlobalScope$1 {
block: Program$3,
isStrict: false,
references: Array [
Reference$1,
],
set: Map {
"foo" => Variable$1,
},
type: "global",
upper: null,
variables: Array [
Variable$1,
],
},
FunctionScope$2 {
block: ArrowFunctionExpression$2,
isStrict: false,
references: Array [
Reference$2,
],
set: Map {
"arg" => Variable$2,
},
type: "function",
upper: GlobalScope$1,
variables: Array [
Variable$2,
],
},
],
}
`;
@@ -0,0 +1,2 @@
type T = string;
const foo = (arg: any): asserts arg is T => {};
@@ -0,0 +1,108 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`functions arrow type-predicate-asserts2 1`] = `
ScopeManager {
variables: Array [
Variable$1 {
defs: Array [
TypeDefinition$1 {
name: Identifier<"T">,
node: TSTypeAliasDeclaration$1,
},
],
name: "T",
references: Array [
Reference$3 {
identifier: Identifier<"T">,
isRead: true,
isTypeReference: true,
isValueReference: false,
isWrite: false,
resolved: Variable$1,
},
],
isValueVariable: false,
isTypeVariable: true,
},
Variable$2 {
defs: Array [
VariableDefinition$2 {
name: Identifier<"foo">,
node: VariableDeclarator$2,
},
],
name: "foo",
references: Array [
Reference$1 {
identifier: Identifier<"foo">,
init: true,
isRead: false,
isTypeReference: false,
isValueReference: true,
isWrite: true,
resolved: Variable$2,
writeExpr: ArrowFunctionExpression$3,
},
],
isValueVariable: true,
isTypeVariable: false,
},
Variable$3 {
defs: Array [
ParameterDefinition$3 {
name: Identifier<"arg">,
node: ArrowFunctionExpression$3,
},
],
name: "arg",
references: Array [
Reference$2 {
identifier: Identifier<"arg">,
isRead: true,
isTypeReference: false,
isValueReference: true,
isWrite: false,
resolved: Variable$3,
},
],
isValueVariable: true,
isTypeVariable: false,
},
],
scopes: Array [
GlobalScope$1 {
block: Program$4,
isStrict: false,
references: Array [
Reference$1,
],
set: Map {
"T" => Variable$1,
"foo" => Variable$2,
},
type: "global",
upper: null,
variables: Array [
Variable$1,
Variable$2,
],
},
FunctionScope$2 {
block: ArrowFunctionExpression$3,
isStrict: false,
references: Array [
Reference$2,
Reference$3,
],
set: Map {
"arg" => Variable$3,
},
type: "function",
upper: GlobalScope$1,
variables: Array [
Variable$3,
],
},
],
}
`;
@@ -0,0 +1,3 @@
const foo = (arg: any): arg is string => {
return typeof arg === 'string';
};
@@ -0,0 +1,93 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`functions arrow type-predicate1 1`] = `
ScopeManager {
variables: Array [
Variable$1 {
defs: Array [
VariableDefinition$1 {
name: Identifier<"foo">,
node: VariableDeclarator$1,
},
],
name: "foo",
references: Array [
Reference$1 {
identifier: Identifier<"foo">,
init: true,
isRead: false,
isTypeReference: false,
isValueReference: true,
isWrite: true,
resolved: Variable$1,
writeExpr: ArrowFunctionExpression$2,
},
],
isValueVariable: true,
isTypeVariable: false,
},
Variable$2 {
defs: Array [
ParameterDefinition$2 {
name: Identifier<"arg">,
node: ArrowFunctionExpression$2,
},
],
name: "arg",
references: Array [
Reference$2 {
identifier: Identifier<"arg">,
isRead: true,
isTypeReference: false,
isValueReference: true,
isWrite: false,
resolved: Variable$2,
},
Reference$3 {
identifier: Identifier<"arg">,
isRead: true,
isTypeReference: false,
isValueReference: true,
isWrite: false,
resolved: Variable$2,
},
],
isValueVariable: true,
isTypeVariable: false,
},
],
scopes: Array [
GlobalScope$1 {
block: Program$3,
isStrict: false,
references: Array [
Reference$1,
],
set: Map {
"foo" => Variable$1,
},
type: "global",
upper: null,
variables: Array [
Variable$1,
],
},
FunctionScope$2 {
block: ArrowFunctionExpression$2,
isStrict: false,
references: Array [
Reference$2,
Reference$3,
],
set: Map {
"arg" => Variable$2,
},
type: "function",
upper: GlobalScope$1,
variables: Array [
Variable$2,
],
},
],
}
`;
@@ -0,0 +1,4 @@
type T = string;
const foo = (arg: any): arg is T => {
return typeof arg === 'string';
};