UNPKG

6.98 kBPlain TextView Raw
1// tslint:disable ban-types
2import { tsUtils } from '@neo-one/ts-utils';
3import ts from 'typescript';
4
5import { CompilerDiagnostic } from './CompilerDiagnostic';
6import { DiagnosticCode } from './DiagnosticCode';
7import { Globals, LibAliases, LibAliasesWithReset, Libs } from './symbols';
8
9export interface DiagnosticOptions {
10 readonly error?: boolean;
11 readonly warning?: boolean;
12}
13
14export class Context {
15 public constructor(
16 public readonly program: ts.Program,
17 public readonly typeChecker: ts.TypeChecker,
18 public readonly languageService: ts.LanguageService,
19 public readonly globals: Globals,
20 public readonly libs: Libs,
21 public readonly libAliases: LibAliasesWithReset,
22 private readonly mutableDiagnostics: ts.Diagnostic[] = ts.getPreEmitDiagnostics(program),
23 ) {}
24
25 public get diagnostics(): ReadonlyArray<ts.Diagnostic> {
26 return this.mutableDiagnostics;
27 }
28
29 public update(
30 program: ts.Program,
31 typeChecker: ts.TypeChecker,
32 languageService: ts.LanguageService,
33 globals: Globals,
34 libs: Libs,
35 libAliases: LibAliasesWithReset,
36 ): Context {
37 return new Context(program, typeChecker, languageService, globals, libs, libAliases, [...this.mutableDiagnostics]);
38 }
39
40 public addDiagnostics(diagnostics: ReadonlyArray<ts.Diagnostic>): void {
41 this.mutableDiagnostics.push(...diagnostics);
42 }
43
44 public reportError(node: ts.Node, message: string, code: DiagnosticCode): void {
45 this.mutableDiagnostics.push(new CompilerDiagnostic(node, message, code, ts.DiagnosticCategory.Error));
46 }
47
48 public reportWarning(node: ts.Node, message: string, code: DiagnosticCode): void {
49 this.mutableDiagnostics.push(new CompilerDiagnostic(node, message, code, ts.DiagnosticCategory.Warning));
50 }
51
52 public reportUnsupported(node: ts.Node): void {
53 this.reportError(node, 'Unsupported syntax', DiagnosticCode.UNSUPPORTED_SYNTAX);
54 }
55
56 public reportTypeError(node: ts.Node): void {
57 this.reportError(
58 node,
59 'Could not infer type. Please add an explicit type annotation.',
60 DiagnosticCode.UNKNOWN_TYPE,
61 );
62 }
63
64 public reportTypeWarning(node: ts.Node): void {
65 this.reportWarning(
66 node,
67 'Could not infer type. Deoptimized implementation will be used. Add an explicit type annotation ' +
68 'to optimize the output.',
69 DiagnosticCode.UNKNOWN_TYPE,
70 );
71 }
72
73 public getType(
74 node: ts.Node,
75 { warning = true, error = false }: DiagnosticOptions = { warning: true, error: false },
76 ): ts.Type | undefined {
77 const type = this.getNotAnyType(tsUtils.type_.getType(this.typeChecker, node));
78
79 if (type === undefined) {
80 if (error) {
81 this.reportTypeError(node);
82 } else if (warning) {
83 this.reportTypeWarning(node);
84 }
85 }
86
87 if (type !== undefined) {
88 const constraintType = tsUtils.type_.getConstraint(type);
89 if (constraintType !== undefined) {
90 return constraintType;
91 }
92 }
93
94 return type;
95 }
96
97 public getTypeOfSymbol(
98 symbol: ts.Symbol | undefined,
99 node: ts.Node,
100 { warning = true, error = false }: DiagnosticOptions = { warning: true, error: false },
101 ): ts.Type | undefined {
102 if (symbol === undefined) {
103 return undefined;
104 }
105
106 const type = this.getNotAnyType(tsUtils.type_.getTypeAtLocation(this.typeChecker, symbol, node));
107 if (type === undefined) {
108 if (error) {
109 this.reportTypeError(node);
110 } else if (warning) {
111 this.reportTypeWarning(node);
112 }
113 }
114
115 return type;
116 }
117
118 public getSymbol(
119 node: ts.Node,
120 { warning = true, error = false }: DiagnosticOptions = { warning: true, error: false },
121 ): ts.Symbol | undefined {
122 let symbol = tsUtils.node.getSymbol(this.typeChecker, node);
123 if (symbol === undefined) {
124 const noWarnOrError = { warning: false, error: false };
125 const type = this.getType(node, noWarnOrError);
126 symbol = this.getSymbolForType(node, type, noWarnOrError);
127 }
128
129 if (symbol === undefined) {
130 const message = 'Could not determine source symbol.';
131 if (error) {
132 this.reportError(node, message, DiagnosticCode.UNKNOWN_SYMBOL);
133 } else if (warning) {
134 this.reportWarning(node, message, DiagnosticCode.UNKNOWN_SYMBOL);
135 }
136
137 return undefined;
138 }
139
140 const aliased = tsUtils.symbol.getAliasedSymbol(this.typeChecker, symbol);
141 if (aliased !== undefined) {
142 return aliased;
143 }
144
145 return symbol;
146 }
147
148 public getSymbolForType(
149 node: ts.Node,
150 type: ts.Type | undefined,
151 { warning = true, error = false }: DiagnosticOptions = { warning: true, error: false },
152 ): ts.Symbol | undefined {
153 if (type === undefined) {
154 return undefined;
155 }
156
157 const symbol = tsUtils.type_.getSymbol(type);
158 if (symbol === undefined) {
159 if (!tsUtils.type_.isSymbolic(type)) {
160 return undefined;
161 }
162
163 const message = `Could not determine source symbol for type: ${tsUtils.type_.getText(
164 this.typeChecker,
165 type,
166 node,
167 )}.`;
168 if (error) {
169 this.reportError(node, message, DiagnosticCode.UNKNOWN_SYMBOL);
170 } else if (warning) {
171 this.reportWarning(node, message, DiagnosticCode.UNKNOWN_SYMBOL);
172 }
173
174 return undefined;
175 }
176
177 const aliased = tsUtils.symbol.getAliasedSymbol(this.typeChecker, symbol);
178 if (aliased !== undefined) {
179 return aliased;
180 }
181
182 return symbol;
183 }
184
185 public isOnlyGlobal(node: ts.Node, type: ts.Type | undefined, name: keyof Globals): boolean {
186 return this.isGlobalSymbol(node, this.getSymbolForType(node, type), name);
187 }
188
189 public isGlobal(node: ts.Node, type: ts.Type | undefined, name: keyof Globals): boolean {
190 return this.isGlobalSymbol(node, this.getSymbolForType(node, type), name);
191 }
192
193 public hasGlobal(node: ts.Node, type: ts.Type | undefined, name: keyof Globals): boolean {
194 return (
195 type !== undefined &&
196 tsUtils.type_.hasType(type, (testType) => this.isGlobalSymbol(node, this.getSymbolForType(node, testType), name))
197 );
198 }
199
200 public isGlobalSymbol(_node: ts.Node, symbol: ts.Symbol | undefined, name: keyof Globals): boolean {
201 return symbol === this.globals[name];
202 }
203
204 public isOnlyLib(node: ts.Node, type: ts.Type | undefined, name: keyof Libs): boolean {
205 return this.isLibSymbol(node, this.getSymbolForType(node, type), name);
206 }
207
208 public isLibSymbol(_node: ts.Node, symbol: ts.Symbol | undefined, name: keyof Libs): boolean {
209 return symbol === this.libs[name];
210 }
211
212 public isLibAlias(identifier: ts.Identifier | undefined, name: keyof LibAliases): boolean {
213 if (identifier === undefined) {
214 return false;
215 }
216
217 return this.libAliases[name].has(identifier);
218 }
219
220 private getNotAnyType(type: ts.Type | undefined): ts.Type | undefined {
221 // tslint:disable-next-line no-bitwise
222 if (type === undefined || tsUtils.type_.isAny(type)) {
223 return undefined;
224 }
225
226 return type;
227 }
228}