1 |
|
2 | import { tsUtils } from '@neo-one/ts-utils';
|
3 | import ts from 'typescript';
|
4 |
|
5 | import { CompilerDiagnostic } from './CompilerDiagnostic';
|
6 | import { DiagnosticCode } from './DiagnosticCode';
|
7 | import { Globals, LibAliases, LibAliasesWithReset, Libs } from './symbols';
|
8 |
|
9 | export interface DiagnosticOptions {
|
10 | readonly error?: boolean;
|
11 | readonly warning?: boolean;
|
12 | }
|
13 |
|
14 | export 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 |
|
222 | if (type === undefined || tsUtils.type_.isAny(type)) {
|
223 | return undefined;
|
224 | }
|
225 |
|
226 | return type;
|
227 | }
|
228 | }
|