UNPKG

9.25 kBPlain TextView Raw
1// tslint:disable ban-types
2import { tsUtils } from '@neo-one/ts-utils';
3import _ from 'lodash';
4import ts, { DiagnosticCategory } from 'typescript';
5import { format } from 'util';
6import { AnalysisService } from './analysis';
7import { Builtins, createBuiltins } from './compile/builtins';
8import { CompilerDiagnostic } from './CompilerDiagnostic';
9import { DiagnosticCode } from './DiagnosticCode';
10import { DiagnosticMessage } from './DiagnosticMessage';
11import { createMemoized, nodeKey, symbolKey, typeKey } from './utils';
12
13export interface DiagnosticOptions {
14 readonly error?: boolean;
15 readonly warning?: boolean;
16}
17
18export const DEFAULT_DIAGNOSTIC_OPTIONS = {
19 error: false,
20 warning: true,
21};
22
23const getErrorKey = (diagnostic: ts.Diagnostic) =>
24 `${diagnostic.file}:${diagnostic.start}:${diagnostic.length}:${diagnostic.code}`;
25const getFullKey = (diagnostic: ts.Diagnostic) =>
26 `${diagnostic.file}:${diagnostic.start}:${diagnostic.length}:${diagnostic.category}:${diagnostic.code}:${
27 diagnostic.messageText
28 }`;
29
30export class Context {
31 public readonly builtins: Builtins;
32 public readonly analysis: AnalysisService;
33 private readonly memoized = createMemoized();
34
35 public constructor(
36 public readonly program: ts.Program,
37 public readonly typeChecker: ts.TypeChecker,
38 public readonly languageService: ts.LanguageService,
39 public readonly smartContractDir: string,
40 private readonly mutableDiagnostics: ts.Diagnostic[] = ts.getPreEmitDiagnostics(program),
41 ) {
42 this.builtins = createBuiltins(this);
43 this.analysis = new AnalysisService(this);
44 }
45
46 public get diagnostics(): ReadonlyArray<ts.Diagnostic> {
47 const errorDiagnostics = new Set<string>();
48 // tslint:disable-next-line no-loop-statement
49 for (const diagnostic of this.mutableDiagnostics) {
50 if (diagnostic.category === DiagnosticCategory.Error) {
51 errorDiagnostics.add(getErrorKey(diagnostic));
52 }
53 }
54
55 const diagnostics = this.mutableDiagnostics.filter(
56 (diagnostic) =>
57 diagnostic.category === DiagnosticCategory.Error || !errorDiagnostics.has(getErrorKey(diagnostic)),
58 );
59
60 return _.uniqBy(diagnostics, getFullKey);
61 }
62
63 public update(
64 program: ts.Program,
65 typeChecker: ts.TypeChecker,
66 languageService: ts.LanguageService,
67 smartContractDir: string,
68 ): Context {
69 return new Context(program, typeChecker, languageService, smartContractDir, [...this.mutableDiagnostics]);
70 }
71
72 public reportError(
73 node: ts.Node,
74 code: DiagnosticCode,
75 message: DiagnosticMessage,
76 // tslint:disable-next-line no-any readonly-array
77 ...args: any[]
78 ): void {
79 this.mutableDiagnostics.push(
80 new CompilerDiagnostic(node, this.getDiagnosticMessage(message, ...args), code, ts.DiagnosticCategory.Error),
81 );
82 }
83
84 // tslint:disable-next-line no-any readonly-array
85 public reportWarning(node: ts.Node, code: DiagnosticCode, message: DiagnosticMessage, ...args: any[]): void {
86 this.mutableDiagnostics.push(
87 new CompilerDiagnostic(node, this.getDiagnosticMessage(message, ...args), code, ts.DiagnosticCategory.Warning),
88 );
89 }
90
91 public reportUnsupported(node: ts.Node): void {
92 this.reportError(node, DiagnosticCode.GenericUnsupportedSyntax, DiagnosticMessage.GenericUnsupportedSyntax);
93 }
94
95 public reportUnsupportedEfficiency(node: ts.Node): void {
96 this.reportError(node, DiagnosticCode.GenericUnsupportedSyntax, DiagnosticMessage.EfficiencyUnsupportedSyntax);
97 }
98
99 public reportTypeError(node: ts.Node): void {
100 this.reportError(node, DiagnosticCode.UnknownType, DiagnosticMessage.CouldNotInferType);
101 }
102
103 public reportTypeWarning(node: ts.Node): void {
104 this.reportWarning(node, DiagnosticCode.UnknownType, DiagnosticMessage.CouldNotInferTypeDeopt);
105 }
106
107 public getType(
108 node: ts.Node,
109 {
110 warning = DEFAULT_DIAGNOSTIC_OPTIONS.warning,
111 error = DEFAULT_DIAGNOSTIC_OPTIONS.error,
112 }: DiagnosticOptions = DEFAULT_DIAGNOSTIC_OPTIONS,
113 ): ts.Type | undefined {
114 return this.memoized('type', nodeKey(node), () => {
115 const type = this.getNotAnyTypeBase(tsUtils.type_.getType(this.typeChecker, node));
116
117 if (type === undefined) {
118 if (error) {
119 this.reportTypeError(node);
120 } else if (warning) {
121 this.reportTypeWarning(node);
122 }
123 }
124
125 if (type !== undefined) {
126 const constraintType = tsUtils.type_.getConstraint(type);
127 if (constraintType !== undefined) {
128 return constraintType;
129 }
130 }
131
132 return type;
133 });
134 }
135
136 public getTypeOfSymbol(
137 symbol: ts.Symbol | undefined,
138 node: ts.Node,
139 {
140 warning = DEFAULT_DIAGNOSTIC_OPTIONS.warning,
141 error = DEFAULT_DIAGNOSTIC_OPTIONS.error,
142 }: DiagnosticOptions = DEFAULT_DIAGNOSTIC_OPTIONS,
143 ): ts.Type | undefined {
144 if (symbol === undefined) {
145 return undefined;
146 }
147
148 return this.memoized('type-of-symbol', `${symbolKey(symbol)}:${nodeKey(node)}`, () => {
149 const type = this.getNotAnyTypeBase(tsUtils.type_.getTypeAtLocation(this.typeChecker, symbol, node));
150 if (type === undefined) {
151 if (error) {
152 this.reportTypeError(node);
153 } else if (warning) {
154 this.reportTypeWarning(node);
155 }
156 }
157
158 if (type !== undefined) {
159 const constraintType = tsUtils.type_.getConstraint(type);
160 if (constraintType !== undefined) {
161 return constraintType;
162 }
163 }
164
165 return type;
166 });
167 }
168
169 public getSymbol(
170 node: ts.Node,
171 {
172 warning = DEFAULT_DIAGNOSTIC_OPTIONS.warning,
173 error = DEFAULT_DIAGNOSTIC_OPTIONS.error,
174 }: DiagnosticOptions = DEFAULT_DIAGNOSTIC_OPTIONS,
175 ): ts.Symbol | undefined {
176 return this.memoized('symbol', nodeKey(node), () => {
177 const symbol = tsUtils.node.getSymbol(this.typeChecker, node);
178 if (symbol === undefined) {
179 if (error) {
180 this.reportSymbolError(node);
181 } else if (warning) {
182 this.reportSymbolWarning(node);
183 }
184
185 return undefined;
186 }
187
188 const aliased = tsUtils.symbol.getAliasedSymbol(this.typeChecker, symbol);
189 if (aliased !== undefined) {
190 return aliased;
191 }
192
193 return symbol;
194 });
195 }
196
197 public getTypeSymbol(
198 node: ts.Node,
199 {
200 warning = DEFAULT_DIAGNOSTIC_OPTIONS.warning,
201 error = DEFAULT_DIAGNOSTIC_OPTIONS.error,
202 }: DiagnosticOptions = DEFAULT_DIAGNOSTIC_OPTIONS,
203 ): ts.Symbol | undefined {
204 return this.memoized('type-symbol', nodeKey(node), () => {
205 const noWarnOrError = { warning: false, error: false };
206 const type = this.getType(node, noWarnOrError);
207 const symbol = this.getSymbolForType(node, type, noWarnOrError);
208 if (symbol === undefined) {
209 if (error) {
210 this.reportSymbolError(node);
211 } else if (warning) {
212 this.reportSymbolWarning(node);
213 }
214
215 return undefined;
216 }
217
218 return symbol;
219 });
220 }
221
222 public getSymbolForType(
223 _node: ts.Node,
224 type: ts.Type | undefined,
225 _options: DiagnosticOptions = DEFAULT_DIAGNOSTIC_OPTIONS,
226 ): ts.Symbol | undefined {
227 if (type === undefined) {
228 return undefined;
229 }
230
231 return this.memoized('symbol-for-type', typeKey(type), () => {
232 let symbol = tsUtils.type_.getSymbol(type);
233 if (symbol === undefined) {
234 symbol = tsUtils.type_.getAliasSymbol(type);
235 }
236
237 if (symbol === undefined) {
238 return undefined;
239 }
240
241 const aliased = tsUtils.symbol.getAliasedSymbol(this.typeChecker, symbol);
242 if (aliased !== undefined) {
243 return aliased;
244 }
245
246 return symbol;
247 });
248 }
249
250 public getNotAnyType(
251 node: ts.Node,
252 typeIn: ts.Type | undefined,
253 {
254 warning = DEFAULT_DIAGNOSTIC_OPTIONS.warning,
255 error = DEFAULT_DIAGNOSTIC_OPTIONS.error,
256 }: DiagnosticOptions = DEFAULT_DIAGNOSTIC_OPTIONS,
257 ): ts.Type | undefined {
258 const type = this.getNotAnyTypeBase(typeIn);
259 if (type === undefined) {
260 if (error) {
261 this.reportTypeError(node);
262 } else if (warning) {
263 this.reportTypeWarning(node);
264 }
265 }
266
267 return type;
268 }
269
270 private getNotAnyTypeBase(type: ts.Type | undefined): ts.Type | undefined {
271 // tslint:disable-next-line no-bitwise
272 if (type === undefined || tsUtils.type_.isAny(type)) {
273 return undefined;
274 }
275
276 return type;
277 }
278
279 private reportSymbolError(node: ts.Node): void {
280 this.reportError(node, DiagnosticCode.UnknownSymbol, DiagnosticMessage.CouldNotInferSymbol);
281 }
282
283 private reportSymbolWarning(node: ts.Node): void {
284 this.reportError(node, DiagnosticCode.UnknownSymbol, DiagnosticMessage.CouldNotInferSymbolDeopt);
285 }
286
287 // tslint:disable-next-line no-any readonly-array
288 private getDiagnosticMessage(message: DiagnosticMessage, ...args: any[]): string {
289 const match = message.match(/%[dfijoOs]/g);
290 const expectedLength = (match === null ? [] : match).length;
291 if (expectedLength !== args.length) {
292 throw new Error(
293 `The provided arguments length (${
294 args.length
295 }) does not match the required arguments length (${expectedLength})`,
296 );
297 }
298
299 return format(message, ...args);
300 }
301}