Skip to content

Commit 09c0d17

Browse files
Add regression test for #161 (#164)
1 parent 22285f9 commit 09c0d17

5 files changed

Lines changed: 51 additions & 20 deletions

File tree

source/lib/parser.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,47 @@ export const extractAssertions = (program: Program): Map<Assertion, Set<CallExpr
1414
const checker = program.getTypeChecker();
1515

1616
/**
17-
* Recursively loop over all the nodes and extract all the assertions out of the source files.
17+
* Checks if the given node is semantically valid and is an assertion.
1818
*/
19-
function walkNodes(node: Node) {
20-
if (isCallExpression(node)) {
21-
const expression = isPropertyAccessExpression(node.expression) ?
22-
node.expression.name :
23-
node.expression;
19+
function handleNode(node: CallExpression) {
20+
const expression = isPropertyAccessExpression(node.expression) ?
21+
node.expression.name :
22+
node.expression;
23+
24+
const maybeSymbol = checker.getSymbolAtLocation(expression);
25+
26+
if (!maybeSymbol) {
27+
// Bail out if a Symbol doesn't exist for this Node
28+
// This either means a symbol could not be resolved
29+
// for an identifier, or that the expression is
30+
// syntactically valid, but not semantically valid.
31+
return;
32+
}
33+
34+
const symbol = maybeSymbol.flags & SymbolFlags.Alias ?
35+
checker.getAliasedSymbol(maybeSymbol) :
36+
maybeSymbol;
2437

25-
const maybeAlias = checker.getSymbolAtLocation(expression);
26-
if (maybeAlias) {
27-
const symbol = maybeAlias.flags & SymbolFlags.Alias ?
28-
checker.getAliasedSymbol(maybeAlias) :
29-
maybeAlias;
38+
const identifier = symbol.getName();
3039

31-
const identifier = symbol.getName();
40+
// Check if the call type is a valid assertion
41+
if (assertionFnNames.has(identifier)) {
42+
const assertion = identifier as Assertion;
3243

33-
// Check if the call type is a valid assertion
34-
if (assertionFnNames.has(identifier)) {
35-
const assertion = identifier as Assertion;
44+
const nodes = assertions.get(assertion) ?? new Set<CallExpression>();
3645

37-
const nodes = assertions.get(assertion) ?? new Set<CallExpression>();
46+
nodes.add(node);
3847

39-
nodes.add(node);
48+
assertions.set(assertion, nodes);
49+
}
50+
}
4051

41-
assertions.set(assertion, nodes);
42-
}
43-
}
52+
/**
53+
* Recursively loop over all the nodes and extract all the assertions out of the source files.
54+
*/
55+
function walkNodes(node: Node) {
56+
if (isCallExpression(node)) {
57+
handleNode(node);
4458
}
4559

4660
forEachChild(node, walkNodes);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {expectType} from '../../../..';
2+
3+
// Identifier `bar` has no Symbol
4+
const anyCall = (foo: any) => foo.bar();
5+
6+
// Fails with `Cannot read properties of undefined (reading 'flags')` in 0.24.0
7+
expectType<any>(anyCall('foo'));
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "foo"
3+
}

source/test/test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,3 +466,9 @@ test('assertions should be identified if aliased', async t => {
466466

467467
verify(t, diagnostics, []);
468468
});
469+
470+
test('parsing undefined symbol should not fail', async t => {
471+
const diagnostics = await tsd({cwd: path.join(__dirname, 'fixtures/undefined-symbol')});
472+
473+
verify(t, diagnostics, []);
474+
});

0 commit comments

Comments
 (0)