Skip to content

Commit

Permalink
fix(scope-manager): support type predicates (#2493)
Browse files Browse the repository at this point in the history
Fixes #2462
  • Loading branch information
bradzacher committed Sep 5, 2020
1 parent a2686c0 commit a40f54c
Show file tree
Hide file tree
Showing 26 changed files with 1,292 additions and 0 deletions.
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';
};

0 comments on commit a40f54c

Please sign in to comment.