Skip to content

Commit 0ec3e34

Browse files
author
Christoph Hegemann
authored
fix: resolve property assignments in object literals (#373)
Simpler alternative to #256 This follows the implementation I found in the TypeScript goToDefinition service https://github.com/microsoft/TypeScript/blob/88809467e8761e71483e2f4948ef411d8e447188/src/services/goToDefinition.ts#L307-L316 Leans on the TypeScript compilers getContextualType function to resolve object literals to the interfaces, or type definitions they are being checked against. This means we generate useful references in a lot more places, where we'd generate "dead" definitions before. Test plan Ports all tests from #256 and adds some basic new ones. Check the updated snapshot output to gauge the improvement or if there are any regressions.
1 parent f720536 commit 0ec3e34

31 files changed

+1131
-384
lines changed

.tool-versions

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
nodejs 20.8.1
22
pnpm 8.9.2
3+
yarn 1.22.17

package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,20 @@
4646
"@types/diff": "5.0.9",
4747
"@types/google-protobuf": "3.15.12",
4848
"@types/node": "20.10.5",
49-
"ts-node": "^10.7.0",
5049
"@types/progress": "2.0.7",
5150
"@typescript-eslint/eslint-plugin": "^7.18.0",
5251
"@typescript-eslint/parser": "^7.18.0",
5352
"diff": "^5.1.0",
5453
"eslint": "^8.57.0",
54+
"eslint-plugin-etc": "^2.0.3",
55+
"eslint-plugin-rxjs": "^5.0.3",
5556
"eslint-plugin-unicorn": "^55.0.0",
5657
"eslint-plugin-unused-imports": "^4.1.3",
5758
"pnpm": "8.12.1",
5859
"prettier": "3.3.2",
60+
"ts-node": "^10.7.0",
61+
"tsm": "^2.3.0",
5962
"typescript-eslint": "7.18.0",
60-
"uvu": "^0.5.6",
61-
"tsm": "^2.3.0"
63+
"uvu": "^0.5.6"
6264
}
6365
}

snapshots/input/pure-js/src/main.js

+4
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,7 @@ function var_function_scope() {
4747
}
4848
print_fib(k)
4949
}
50+
51+
function array_of_objects() {
52+
var a = [{ element: 0 }, { element: 1 }]
53+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Configuration } from './reusable-types'
2+
3+
function MyDecorator(value: Configuration) {
4+
return function (target: Function) {
5+
console.log(`MyDecorator is called with value: ${value}`)
6+
}
7+
}
8+
9+
@MyDecorator({ property: 42, property2: '42' })
10+
class MyClass {
11+
//...
12+
}

snapshots/input/syntax/src/infer-relationship.ts

-60
This file was deleted.

snapshots/input/syntax/src/inheritance.ts

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1+
import { Superinterface } from './reusable-types'
12
import { Overloader } from './overload'
23

3-
export interface Superinterface {
4-
property: string
5-
interfaceMethod(): string
6-
}
74
export interface IntermediateSuperinterface extends Superinterface {
85
intermediateInterfaceMethod(): string
96
}
@@ -43,10 +40,3 @@ export const objectLiteralImplementation: Superinterface = {
4340
throw new Error('Function not implemented.')
4441
},
4542
}
46-
export function consumesInterface(superInterface: Superinterface): void {}
47-
export function infersInterface(): void {
48-
consumesInterface({
49-
interfaceMethod: (): string => 'inferred',
50-
property: 'inferred',
51-
})
52-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Option } from './reusable-types'
2+
3+
interface Foobar {
4+
foobar: number
5+
}
6+
7+
export function hasArrowFunctionParameter(
8+
something: number,
9+
fn: (foobar: Foobar) => Foobar
10+
): Foobar {
11+
return fn({ foobar: 42 + something })
12+
}
13+
14+
export function consumesArrowFunction(): number {
15+
return (
16+
hasArrowFunctionParameter(1, ({ foobar }) => ({ foobar: foobar + 1 }))
17+
.foobar +
18+
hasArrowFunctionParameter(2, foobar => ({ foobar: foobar.foobar + 2 }))
19+
.foobar
20+
)
21+
}
22+
23+
export function genericArrow(): Foobar[] {
24+
return [1].map<Foobar>(n => ({ foobar: n + 1 }))
25+
}
26+
27+
export function genericArrowOption(): Option<Foobar>[] {
28+
return [1].map<Option<Foobar>>(n => ({ value: { foobar: n + 1 } }))
29+
}
30+
31+
export function genericArrow2(): Foobar[] {
32+
// navigation to `foobar` below does not work with tsserver or scip-java
33+
// because `map` is missing an explicit `map<Foobar>` annotation.
34+
return [1].map(n => ({ foobar: n + 1 }))
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {
2+
Configuration,
3+
GenericClass,
4+
GenericInterface,
5+
Option,
6+
Superinterface,
7+
} from './reusable-types'
8+
9+
export function consumesInterface(superInterface: Superinterface): void {}
10+
export function consumesArray(superInterface: Superinterface[]): void {}
11+
export function consumesGenericInterface<T>(
12+
genercInterface: GenericInterface<T>
13+
): void {}
14+
15+
export function infersInterface(): void {
16+
consumesInterface({
17+
interfaceMethod: (): string => 'inferred',
18+
property: 'inferred',
19+
})
20+
consumesArray([
21+
{
22+
interfaceMethod: (): string => 'inferred',
23+
property: 'inferred',
24+
},
25+
])
26+
consumesGenericInterface<number>({
27+
interfaceMethod: (): string => 'inferred',
28+
property: 123,
29+
})
30+
consumesGenericInterface<Option<Configuration>[]>({
31+
interfaceMethod: (): string => 'inferred',
32+
property: [{ value: { property: 42, property2: '42' } }],
33+
})
34+
}
35+
export function returnStatementInsideArgumentExpression(): Configuration[] {
36+
if (1 == 1) {
37+
return [1].map<Configuration>((number: number): Configuration => {
38+
const incremented = number + 1
39+
return {
40+
property: incremented,
41+
property2: incremented.toString(),
42+
}
43+
})
44+
} else {
45+
return [1].map<Configuration>(number => {
46+
const incremented = number + 1
47+
return {
48+
property: incremented,
49+
property2: incremented.toString(),
50+
}
51+
})
52+
}
53+
}
54+
55+
export function createGenericClass(): GenericClass<Configuration> {
56+
return new GenericClass<Configuration>([{ property: 1, property2: '2' }])
57+
}
58+
59+
export function handleGenericClass() {
60+
return createGenericClass().map(({ property, property2 }) => ({
61+
property: property + 1,
62+
property2: property2 + '1',
63+
}))
64+
}
65+
66+
export function handleShorthand() {
67+
const property = '42'
68+
const interfaceMethod = (): string => 'inferred'
69+
consumesInterface({
70+
interfaceMethod,
71+
property,
72+
})
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Option } from './reusable-types'
2+
3+
interface Address {
4+
street: string
5+
people: Person[]
6+
}
7+
interface Person {
8+
name: string
9+
address?: Address
10+
}
11+
12+
export function handleNestedObjectLiterals(): Person {
13+
return {
14+
name: 'John',
15+
address: {
16+
street: 'Oxford Street',
17+
people: [
18+
{
19+
name: 'Susan',
20+
},
21+
],
22+
},
23+
}
24+
}
25+
26+
export function handleNestedTypeVariables(): Option<Person> {
27+
return {
28+
value: {
29+
name: 'John',
30+
address: {
31+
street: 'Oxford Street',
32+
people: [
33+
{
34+
name: 'Susan',
35+
},
36+
],
37+
},
38+
},
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Configuration } from './reusable-types'
2+
3+
function random(): number {
4+
return Math.random()
5+
}
6+
7+
export function handleArrayLiteral(): Configuration[] {
8+
return [
9+
{
10+
property: 41,
11+
property2: '41',
12+
},
13+
]
14+
}
15+
16+
export function returnStatement(): Configuration {
17+
if (random() > 0) {
18+
return {
19+
property: 41,
20+
property2: '41',
21+
}
22+
}
23+
for (let i = 0; i < 9; i++) {
24+
if (random() > i) {
25+
return {
26+
property: 41,
27+
property2: '41',
28+
}
29+
}
30+
}
31+
for (const i of [1, 2, 3]) {
32+
if (random() > i) {
33+
return {
34+
property: 41,
35+
property2: '41',
36+
}
37+
}
38+
}
39+
for (const i in { '1': 2 }) {
40+
if (random() > Number.parseInt(i)) {
41+
return {
42+
property: 41,
43+
property2: '41',
44+
}
45+
}
46+
}
47+
while (random() < 0) {
48+
return {
49+
property: 41,
50+
property2: '41',
51+
}
52+
}
53+
do {
54+
if (random() > 0) {
55+
return {
56+
property: 41,
57+
property2: '41',
58+
}
59+
}
60+
} while (random() < 0)
61+
62+
return {
63+
property: 42,
64+
property2: '41',
65+
}
66+
}
67+
68+
export function constDeclaration(): number[] {
69+
var configuration1: Configuration = {
70+
property: 1,
71+
property2: '1',
72+
}
73+
configuration1 = {
74+
property: 2,
75+
property2: '2',
76+
}
77+
let configuration2: Configuration = {
78+
property: 3,
79+
property2: '3',
80+
}
81+
configuration2.property = configuration1.property
82+
const configuration3: Configuration = {
83+
property: 4,
84+
property2: '4',
85+
}
86+
return [
87+
configuration1.property,
88+
configuration2.property,
89+
configuration3.property,
90+
]
91+
}

snapshots/input/syntax/src/property-assignment.ts

+5
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ export function shorthandPropertyAssignment() {
55
const a = 'a'
66
return { a }
77
}
8+
type A = { a: string; b: number }
9+
export function typedPropertyAssignment(): A {
10+
// prettier-ignore
11+
return { a: 'a', "b": 10 }
12+
}

0 commit comments

Comments
 (0)