Skip to content

Commit b3bb125

Browse files
authored
Infer symbol relationship for object literals property in return statement (#248)
* Infer symnbol relationship for object literals property in return statement Previously, scip-typescript didn't infer correct relationships between the object literel property and interface property in return statements like below: ```ts interface Configuration { property: number } export function returnStatement(): Configuration { return { property: 42, } } ``` This commit fixes this specific issue. This commit also adds a test case for a situation where scip-typescript doesn't infer the relationship today but tsserver does. We should fix that in the future. * Handle if/while/for/do statements
1 parent 9c0d816 commit b3bb125

File tree

4 files changed

+234
-10
lines changed

4 files changed

+234
-10
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
interface Configuration {
2+
property: number
3+
}
4+
5+
function random(): number {
6+
return Math.random()
7+
}
8+
export function returnStatement(): Configuration {
9+
if (random() > 0) {
10+
return {
11+
property: 41,
12+
}
13+
}
14+
for (let i = 0; i < 9; i++) {
15+
if (random() > i) {
16+
return {
17+
property: 41,
18+
}
19+
}
20+
}
21+
for (const i of [1, 2, 3]) {
22+
if (random() > i) {
23+
return {
24+
property: 41,
25+
}
26+
}
27+
}
28+
for (const i in { '1': 2 }) {
29+
if (random() > Number.parseInt(i)) {
30+
return {
31+
property: 41,
32+
}
33+
}
34+
}
35+
while (random() < 0) {
36+
return {
37+
property: 41,
38+
}
39+
}
40+
do {
41+
if (random() > 0) {
42+
return {
43+
property: 41,
44+
}
45+
}
46+
} while (random() < 0)
47+
48+
return {
49+
property: 42,
50+
}
51+
}
52+
53+
export function returnStatementInsideArgumentExpression(): Configuration[] {
54+
return [1].map<Configuration>(number => {
55+
const incremented = number + 1
56+
return {
57+
property: incremented,
58+
}
59+
})
60+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
interface Configuration {
2+
// definition syntax 1.0.0 src/`infer-relationship.ts`/
3+
//documentation ```ts\nmodule "infer-relationship.ts"\n```
4+
// ^^^^^^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/Configuration#
5+
// documentation ```ts\ninterface Configuration\n```
6+
property: number
7+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/Configuration#property.
8+
// documentation ```ts\n(property) property: number\n```
9+
}
10+
11+
function random(): number {
12+
// ^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/random().
13+
// documentation ```ts\nfunction random(): number\n```
14+
return Math.random()
15+
// ^^^^ reference typescript 4.8.4 lib/`lib.es5.d.ts`/Math#
16+
// ^^^^ reference typescript 4.8.4 lib/`lib.es5.d.ts`/Math.
17+
// ^^^^ reference typescript 4.8.4 lib/`lib.es2015.core.d.ts`/Math#
18+
// ^^^^ reference typescript 4.8.4 lib/`lib.es2015.symbol.wellknown.d.ts`/Math#
19+
// ^^^^^^ reference typescript 4.8.4 lib/`lib.es5.d.ts`/Math#random().
20+
}
21+
export function returnStatement(): Configuration {
22+
// ^^^^^^^^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/returnStatement().
23+
// documentation ```ts\nfunction returnStatement(): Configuration\n```
24+
// ^^^^^^^^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/Configuration#
25+
if (random() > 0) {
26+
// ^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/random().
27+
return {
28+
property: 41,
29+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/property0:
30+
// documentation ```ts\n(property) property: number\n```
31+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`infer-relationship.ts`/Configuration#property.
32+
}
33+
}
34+
for (let i = 0; i < 9; i++) {
35+
// ^ definition local 2
36+
// documentation ```ts\nvar i: number\n```
37+
// ^ reference local 2
38+
// ^ reference local 2
39+
if (random() > i) {
40+
// ^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/random().
41+
// ^ reference local 2
42+
return {
43+
property: 41,
44+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/property1:
45+
// documentation ```ts\n(property) property: number\n```
46+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`infer-relationship.ts`/Configuration#property.
47+
}
48+
}
49+
}
50+
for (const i of [1, 2, 3]) {
51+
// ^ definition local 5
52+
// documentation ```ts\nvar i: number\n```
53+
if (random() > i) {
54+
// ^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/random().
55+
// ^ reference local 5
56+
return {
57+
property: 41,
58+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/property2:
59+
// documentation ```ts\n(property) property: number\n```
60+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`infer-relationship.ts`/Configuration#property.
61+
}
62+
}
63+
}
64+
for (const i in { '1': 2 }) {
65+
// ^ definition local 8
66+
// documentation ```ts\nvar i: string\n```
67+
// ^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/`'1'0`:
68+
// documentation ```ts\n(property) '1': number\n```
69+
if (random() > Number.parseInt(i)) {
70+
// ^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/random().
71+
// ^^^^^^ reference typescript 4.8.4 lib/`lib.es5.d.ts`/Number#
72+
// ^^^^^^ reference typescript 4.8.4 lib/`lib.es5.d.ts`/Number.
73+
// ^^^^^^ reference typescript 4.8.4 lib/`lib.es5.d.ts`/Number#
74+
// ^^^^^^ reference typescript 4.8.4 lib/`lib.es2020.number.d.ts`/Number#
75+
// ^^^^^^^^ reference typescript 4.8.4 lib/`lib.es2015.core.d.ts`/NumberConstructor#parseInt().
76+
// ^ reference local 8
77+
return {
78+
property: 41,
79+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/property3:
80+
// documentation ```ts\n(property) property: number\n```
81+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`infer-relationship.ts`/Configuration#property.
82+
}
83+
}
84+
}
85+
while (random() < 0) {
86+
// ^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/random().
87+
return {
88+
property: 41,
89+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/property4:
90+
// documentation ```ts\n(property) property: number\n```
91+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`infer-relationship.ts`/Configuration#property.
92+
}
93+
}
94+
do {
95+
if (random() > 0) {
96+
// ^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/random().
97+
return {
98+
property: 41,
99+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/property5:
100+
// documentation ```ts\n(property) property: number\n```
101+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`infer-relationship.ts`/Configuration#property.
102+
}
103+
}
104+
} while (random() < 0)
105+
// ^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/random().
106+
107+
return {
108+
property: 42,
109+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/property6:
110+
// documentation ```ts\n(property) property: number\n```
111+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`infer-relationship.ts`/Configuration#property.
112+
}
113+
}
114+
115+
export function returnStatementInsideArgumentExpression(): Configuration[] {
116+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/returnStatementInsideArgumentExpression().
117+
// documentation ```ts\nfunction returnStatementInsideArgumentExpression(): Configuration[]\n```
118+
// ^^^^^^^^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/Configuration#
119+
return [1].map<Configuration>(number => {
120+
// ^^^ reference typescript 4.8.4 lib/`lib.es5.d.ts`/Array#map().
121+
// ^^^^^^^^^^^^^ reference syntax 1.0.0 src/`infer-relationship.ts`/Configuration#
122+
// ^^^^^^ definition local 12
123+
// documentation ```ts\n(parameter) number: number\n```
124+
const incremented = number + 1
125+
// ^^^^^^^^^^^ definition local 15
126+
// documentation ```ts\nvar incremented: number\n```
127+
// ^^^^^^ reference local 12
128+
return {
129+
property: incremented,
130+
// ^^^^^^^^ definition syntax 1.0.0 src/`infer-relationship.ts`/property7:
131+
// documentation ```ts\n(property) property: number\n```
132+
// ^^^^^^^^^^^ reference local 15
133+
}
134+
})
135+
}
136+

snapshots/output/syntax/src/interface.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
property: 'a',
2727
// ^^^^^^^^ definition syntax 1.0.0 src/`interface.ts`/property0:
2828
// documentation ```ts\n(property) property: string\n```
29+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`interface.ts`/Interface#property.
2930
methodSignature(param: string): string {
3031
// ^^^^^^^^^^^^^^^ definition local 4
3132
// documentation ```ts\n(method) methodSignature(param: string): string\n```
33+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`interface.ts`/Interface#methodSignature().
3234
// ^^^^^ definition local 5
3335
// documentation ```ts\n(parameter) param: string\n```
3436
return param
@@ -37,6 +39,7 @@
3739
methodSignature2: (param: string): string => {
3840
// ^^^^^^^^^^^^^^^^ definition syntax 1.0.0 src/`interface.ts`/methodSignature20:
3941
// documentation ```ts\n(property) methodSignature2: (param: string) => string\n```
42+
// relationship implementation reference scip-typescript npm syntax 1.0.0 src/`interface.ts`/Interface#methodSignature2.
4043
// ^^^^^ definition local 7
4144
// documentation ```ts\n(parameter) param: string\n```
4245
return param

src/FileIndexer.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class FileIndexer {
3838
}
3939
public index(): void {
4040
// Uncomment below if you want to skip certain files for local development.
41-
// if (!this.sourceFile.fileName.includes('ClassWithPrivate')) {
41+
// if (!this.sourceFile.fileName.includes('infer-relationship')) {
4242
// return
4343
// }
4444
this.emitSourceFileOccurrence()
@@ -561,7 +561,10 @@ export class FileIndexer {
561561
onAncestor(declaration)
562562
}
563563
if (ts.isObjectLiteralExpression(declaration)) {
564-
const tpe = this.inferredTypeOfObjectLiteral(declaration)
564+
const tpe = this.inferredTypeOfObjectLiteral(
565+
declaration.parent,
566+
declaration
567+
)
565568
for (const symbolDeclaration of tpe.symbol?.declarations || []) {
566569
loop(symbolDeclaration)
567570
}
@@ -592,19 +595,41 @@ export class FileIndexer {
592595
// `SomeInterface`. The object literal could satisfy many types, but in this
593596
// particular location must only satisfy `SomeInterface`.
594597
private inferredTypeOfObjectLiteral(
595-
node: ts.ObjectLiteralExpression
598+
node: ts.Node,
599+
literal: ts.ObjectLiteralExpression
596600
): ts.Type {
597-
if (ts.isVariableDeclaration(node.parent)) {
601+
if (
602+
ts.isIfStatement(node) ||
603+
ts.isForStatement(node) ||
604+
ts.isForInStatement(node) ||
605+
ts.isForOfStatement(node) ||
606+
ts.isWhileStatement(node) ||
607+
ts.isDoStatement(node) ||
608+
ts.isReturnStatement(node) ||
609+
ts.isBlock(node)
610+
) {
611+
return this.inferredTypeOfObjectLiteral(node.parent, literal)
612+
}
613+
614+
if (ts.isVariableDeclaration(node)) {
598615
// Example, return `SomeInterface` from `const x: SomeInterface = {y: 42}`.
599-
return this.checker.getTypeAtLocation(node.parent.name)
616+
return this.checker.getTypeAtLocation(node.name)
617+
}
618+
619+
if (ts.isFunctionLike(node)) {
620+
const functionType = this.checker.getTypeAtLocation(node)
621+
const callSignatures = functionType.getCallSignatures()
622+
if (callSignatures.length > 0) {
623+
return callSignatures[0].getReturnType()
624+
}
600625
}
601626

602-
if (ts.isCallOrNewExpression(node.parent)) {
627+
if (ts.isCallOrNewExpression(node)) {
603628
// Example: return the type of the second parameter of `someMethod` from
604629
// the expression `someMethod(someParameter, {y: 42})`.
605-
const signature = this.checker.getResolvedSignature(node.parent)
606-
for (const [index, argument] of (node.parent.arguments || []).entries()) {
607-
if (argument === node) {
630+
const signature = this.checker.getResolvedSignature(node)
631+
for (const [index, argument] of (node.arguments || []).entries()) {
632+
if (argument === literal) {
608633
const parameterSymbol = signature?.getParameters()[index]
609634
if (parameterSymbol) {
610635
return this.checker.getTypeOfSymbolAtLocation(parameterSymbol, node)
@@ -613,7 +638,7 @@ export class FileIndexer {
613638
}
614639
}
615640

616-
return this.checker.getTypeAtLocation(node)
641+
return this.checker.getTypeAtLocation(literal)
617642
}
618643
}
619644

0 commit comments

Comments
 (0)