Skip to content

Commit b0c81c4

Browse files
committed
More support for composite types
1 parent 16fa2ed commit b0c81c4

File tree

5 files changed

+114
-155
lines changed

5 files changed

+114
-155
lines changed

.vscode/launch.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
"program": "${workspaceFolder}/tsc/lib/main.js",
135135
"args": [
136136
"-p", "tsconfig.json",
137+
"--out", "${workspaceFolder}/tmp/protocol.lsif",
137138
"--outputFormat", "line"
138139
],
139140
"preLaunchTask": "npm: watch",

samples/typescript/index.ts

Lines changed: 25 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,37 @@
1-
import { foo, bar } from './sub/provide';
1+
const map: Map<string, string> = new Map();
2+
map.set('dirk', 'baeumer');
23

3-
console.log(foo, bar);
4+
interface A {
5+
x: {
6+
y: string;
7+
}
8+
}
49

10+
interface B {
11+
x: {
12+
y: number;
13+
}
14+
}
515

6-
// function foo(x: number): void {
7-
// }
16+
let z: A | B;
817

18+
let v = z.x.y;
19+
console.log(v);
920

10-
// interface I {
11-
// foo(): void;
12-
// }
21+
// import { mul } from './sub/provide';
1322

14-
// let i: I;
1523

16-
// interface I {
17-
// foo(): void;
24+
// const result: number = mul(10, 20);
25+
// if (result !== 200) {
26+
// throw new Error(`Multiplication failed`);
1827
// }
1928

20-
// interface II {
21-
// foo(): void;
22-
// }
23-
24-
// class B implements I, II {
25-
// foo(): void {
26-
// }
27-
// }
28-
29-
// let i: I;
30-
// i.foo();
31-
32-
// let b: B;
33-
// b.foo();
34-
35-
3629

37-
// export type BabelDescriptor = { initializer?: () => any; } & ( { foo: number } | { bar: number });
30+
// export class MyClass {
31+
// constructor() {
32+
// }
3833

39-
40-
// export type PropertyCreator = (
41-
// instance: any,
42-
// ) => void
43-
44-
// export interface Init {
45-
// initializer?: () => any;
46-
// }
47-
48-
// export type BabelDescriptor = PropertyCreator & Init
49-
50-
// import * as p from './sub/provide';
51-
// p.foo();
52-
53-
// process.env;
54-
55-
// interface A<T> {
56-
// x: T;
34+
// public mul(a: number, b: number): number {
35+
// return a * b;
36+
// }
5737
// }
58-
59-
// export function foo(): void {
60-
// }
61-
62-
// Object.is(1, 1);
63-
64-
// /**
65-
// * A longer comment that needs to be fetch
66-
// *
67-
// * jdjdj
68-
// * dkjdkj
69-
// */
70-
// interface I1 {
71-
// /**
72-
// * A longer comment that needs to be fetch
73-
// *
74-
// * jdjdj
75-
// * dkjdkj
76-
// */
77-
// get(): void;
78-
// }
79-
80-
// /**
81-
// * A longer comment that needs to be fetch
82-
// *
83-
// * jdjdj
84-
// * dkjdkj
85-
// */
86-
// interface I2 {
87-
// /**
88-
// * A longer comment that needs to be fetch
89-
// *
90-
// * jdjdj
91-
// * dkjdkj
92-
// */
93-
// get(): void;
94-
// }
95-
96-
// let i: I1 | I2;
97-
// i.get();
98-
99-
// let i2: I1;
100-
// i2.get();
101-
102-
103-
// import * as mobx from 'mobx';
104-
105-
// let x: mobx.ObservableMap;
106-
107-
// let s: Set<string> = new Set();
108-
// s.add('foo');
109-
110-
// /**
111-
// * A longer comment that needs to be fetch
112-
// *
113-
// * jdjdj
114-
// * dkjdkj
115-
// */
116-
// function foo(): number {
117-
// return 10;
118-
// }
119-
120-
// export default foo();
121-
122-
// /**
123-
// * A longer comment that needs to be fetch
124-
// *
125-
// * jdjdj
126-
// * dkjdkj
127-
// */
128-
// export const enum OutlineConfigKeys {
129-
// 'icons' = 'outline.icons',
130-
// 'problemsEnabled' = 'outline.problems.enabled',
131-
// 'problemsColors' = 'outline.problems.colors',
132-
// 'problemsBadges' = 'outline.problems.badges'
133-
// }

samples/typescript/sub/provide.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
const _foo: string = '';
2-
const bar: string = '';
3-
4-
export { _foo as foo, bar };
1+
/**
2+
* Multiplies two numbers
3+
*
4+
* @param a the multiplicator
5+
* @param b the multiplicand
6+
*/
7+
export function mul(a: number, b: number): number {
8+
return a * b;
9+
}

tsc/src/lsif.ts

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,17 @@ abstract class SymbolDataResolver {
11031103
return sourceFiles[0];
11041104
}
11051105

1106+
public getIdentifierInformation(sourceFile: ts.SourceFile, symbol: ts.Symbol, declaration: ts.Node): [ts.Node, string] | [undefined, undefined] {
1107+
if (tss.isNamedDeclaration(declaration)) {
1108+
let name = declaration.name;
1109+
return [name, name.getText()];
1110+
}
1111+
if (tss.isValueModule(symbol) && ts.isSourceFile(declaration)) {
1112+
return [declaration, ''];
1113+
}
1114+
return [undefined, undefined];
1115+
}
1116+
11061117
public abstract resolve(sourceFile: ts.SourceFile | undefined, id: SymbolId, symbol: ts.Symbol, location?: ts.Node, scope?: ts.Node): SymbolData;
11071118
}
11081119

@@ -1193,26 +1204,22 @@ class UnionOrIntersectionResolver extends SymbolDataResolver {
11931204
}
11941205

11951206
public resolve(sourceFile: ts.SourceFile, id: SymbolId, symbol: ts.Symbol, location?: ts.Node, scope?: ts.Node): SymbolData {
1196-
if (location === undefined) {
1197-
throw new Error(`Union or intersection resolver requires a location`);
1198-
}
1199-
let type = this.typeChecker.getTypeOfSymbolAtLocation(symbol, location);
1200-
if (!type.isUnionOrIntersection()) {
1201-
throw new Error(`Type is not a union or intersection type`);
1202-
}
1203-
if (type.types.length > 0) {
1204-
let datas: SymbolData[] = [];
1205-
for (let typeElem of type.types) {
1206-
let symbol = typeElem.symbol;
1207-
// This happens for base types like undefined, number, ....
1208-
if (symbol !== undefined) {
1209-
datas.push(this.resolverContext.getOrCreateSymbolData(symbol));
1210-
}
1207+
const composites = tss.getCompositeSymbols(this.typeChecker, symbol, location);
1208+
if (composites !== undefined) {
1209+
const datas: SymbolData[] = [];
1210+
for (let symbol of composites) {
1211+
datas.push(this.resolverContext.getOrCreateSymbolData(symbol));
12111212
}
12121213
return new UnionOrIntersectionSymbolData(this.symbolDataContext, id, sourceFile, datas);
12131214
} else {
12141215
return new StandardSymbolData(this.symbolDataContext, id, undefined);
12151216
}
1217+
// We have something like x: { prop: number} | { prop: string };
1218+
throw new Error(`Union or intersection resolver requires a location`);
1219+
}
1220+
1221+
public getIdentifierInformation(sourceFile: ts.SourceFile, symbol: ts.Symbol, declaration: ts.Node): [ts.Node, string] | [undefined, undefined] {
1222+
return [declaration, declaration.getText()];
12161223
}
12171224
}
12181225

@@ -2051,7 +2058,7 @@ class Visitor implements ResolverContext {
20512058
let hover: lsp.Hover | undefined;
20522059
for (let declaration of declarations) {
20532060
const sourceFile = declaration.getSourceFile();
2054-
const [identifierNode, identifierText] = this.getIdentifierInformation(sourceFile, symbol, declaration);
2061+
const [identifierNode, identifierText] = resolver.getIdentifierInformation(sourceFile, symbol, declaration);
20552062
if (identifierNode !== undefined && identifierText !== undefined) {
20562063
const documentData = this.getOrCreateDocumentData(sourceFile);
20572064
const range = ts.isSourceFile(declaration) ? { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } : Converter.rangeFromNode(sourceFile, identifierNode);
@@ -2100,17 +2107,6 @@ class Visitor implements ResolverContext {
21002107
return result;
21012108
}
21022109

2103-
private getIdentifierInformation(sourceFile: ts.SourceFile, symbol: ts.Symbol, declaration: ts.Node): [ts.Node, string] | [undefined, undefined] {
2104-
if (tss.isNamedDeclaration(declaration)) {
2105-
let name = declaration.name;
2106-
return [name, name.getText()];
2107-
}
2108-
if (tss.isValueModule(symbol) && ts.isSourceFile(declaration)) {
2109-
return [declaration, ''];
2110-
}
2111-
return [undefined, undefined];
2112-
}
2113-
21142110
private resolveEmittingNode(symbol: ts.Symbol, isExported: boolean): ts.Node | undefined {
21152111
// The symbol has a export path so we can't bind this to a node
21162112
// Note that we even treat private class members like this. Reason being
@@ -2146,11 +2142,16 @@ class Visitor implements ResolverContext {
21462142

21472143
private getResolver(symbol: ts.Symbol, location?: ts.Node): SymbolDataResolver {
21482144
if (location !== undefined && tss.isTransient(symbol)) {
2149-
let type = this.typeChecker.getTypeOfSymbolAtLocation(symbol, location);
2150-
if (type.isUnionOrIntersection()) {
2145+
if (tss.isComposite(this.typeChecker, symbol, location)) {
21512146
return this.symbolDataResolvers.unionOrIntersection;
21522147
} else {
2153-
return this.symbolDataResolvers.transient;
2148+
// Problem: Symbols that come from the lib*.d.ts files are marked transient
2149+
// as well. Check if the symbol has some other meaningful flags
2150+
if ((symbol.getFlags() & ~ts.SymbolFlags.Transient) !== 0) {
2151+
return this.symbolDataResolvers.standard;
2152+
} else {
2153+
return this.symbolDataResolvers.transient;
2154+
}
21542155
}
21552156
}
21562157
if (tss.isTypeAlias(symbol)) {

tsc/src/typescripts.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export function flattenDiagnosticMessageText(messageText: string | ts.Diagnostic
114114

115115
interface InternalSymbol extends ts.Symbol {
116116
parent?: ts.Symbol;
117+
containingType?: ts.UnionOrIntersectionType;
117118
__symbol__data__key__: string | undefined;
118119
}
119120

@@ -239,6 +240,53 @@ export function isTypeAlias(symbol: ts.Symbol): boolean {
239240
return symbol !== undefined && (symbol.getFlags() & ts.SymbolFlags.TypeAlias) !== 0;
240241
}
241242

243+
export function isComposite(typeChecker: ts.TypeChecker, symbol: ts.Symbol, location?: ts.Node): boolean {
244+
const containingType = (symbol as InternalSymbol).containingType;
245+
if (containingType !== undefined && containingType.isUnionOrIntersection()) {
246+
return true;
247+
}
248+
249+
if (location !== undefined) {
250+
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location);
251+
if (type.isUnionOrIntersection()) {
252+
return true;
253+
}
254+
}
255+
256+
return false;
257+
}
258+
259+
export function getCompositeSymbols(typeChecker: ts.TypeChecker, symbol: ts.Symbol, location?: ts.Node): ts.Symbol[] | undefined {
260+
// We have something like x: { prop: number} | { prop: string };
261+
const containingType = (symbol as InternalSymbol).containingType;
262+
if (containingType !== undefined) {
263+
let result: ts.Symbol[] = [];
264+
for (let typeElem of containingType.types) {
265+
const symbolElem = typeElem.getProperty(symbol.getName());
266+
if (symbolElem !== undefined) {
267+
result.push(symbolElem);
268+
}
269+
}
270+
return result.length > 0 ? result : undefined;
271+
}
272+
if (location !== undefined) {
273+
const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location);
274+
// we have something like x: A | B;
275+
if (type.isUnionOrIntersection()) {
276+
let result: ts.Symbol[] = [];
277+
for (let typeElem of type.types) {
278+
const symbolElem = typeElem.symbol;
279+
// This happens for base types like undefined, number, ....
280+
if (symbolElem !== undefined) {
281+
result.push(symbolElem);
282+
}
283+
}
284+
return result;
285+
}
286+
}
287+
return undefined;
288+
}
289+
242290
export function isPrivate(symbol: ts.Symbol): boolean {
243291
let declarations = symbol.getDeclarations();
244292
if (declarations) {

0 commit comments

Comments
 (0)