OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE.md file. | |
4 | |
5 library fletchc.diagnostic; | |
6 | |
7 import 'messages.dart' show | |
8 DiagnosticKind, | |
9 getMessage; | |
10 | |
11 import 'hub/sentence_parser.dart' show | |
12 Preposition, | |
13 Target, | |
14 TargetKind, | |
15 Verb; | |
16 | |
17 export 'messages.dart' show | |
18 DiagnosticKind; | |
19 | |
20 /// Represents a parameter to a diagnostic, that is, a key in the `arguments` | |
21 /// map of [Diagnostic]. In a diagnostic message template (a [String]), a | |
22 /// parameter is represented by `"#{name}"`. | |
23 class DiagnosticParameter { | |
24 final DiagnosticParameterType type; | |
25 | |
26 final String name; | |
27 | |
28 const DiagnosticParameter(this.type, this.name); | |
29 | |
30 String toString() => '#{$name}'; | |
31 | |
32 static const DiagnosticParameter message = const DiagnosticParameter( | |
33 DiagnosticParameterType.string, 'message'); | |
34 | |
35 static const DiagnosticParameter verb = const DiagnosticParameter( | |
36 DiagnosticParameterType.verb, 'verb'); | |
37 | |
38 static const DiagnosticParameter sessionName = const DiagnosticParameter( | |
39 DiagnosticParameterType.sessionName, 'sessionName'); | |
40 | |
41 static const DiagnosticParameter target = const DiagnosticParameter( | |
42 DiagnosticParameterType.target, 'target'); | |
43 | |
44 static const DiagnosticParameter requiredTarget = const DiagnosticParameter( | |
45 DiagnosticParameterType.targetKind, 'requiredTarget'); | |
46 | |
47 static const DiagnosticParameter userInput = const DiagnosticParameter( | |
48 DiagnosticParameterType.string, 'userInput'); | |
49 | |
50 static const DiagnosticParameter additionalUserInput = | |
51 const DiagnosticParameter( | |
52 DiagnosticParameterType.string, 'additionalUserInput'); | |
53 | |
54 static const DiagnosticParameter address = const DiagnosticParameter( | |
55 DiagnosticParameterType.string, 'address'); | |
56 | |
57 static const DiagnosticParameter preposition = const DiagnosticParameter( | |
58 DiagnosticParameterType.preposition, 'preposition'); | |
59 | |
60 // TODO(ahe): This should probably be a more generalized location, for | |
61 // example, Spannable from dart2js. | |
62 static const DiagnosticParameter uri = const DiagnosticParameter( | |
63 DiagnosticParameterType.uri, 'uri'); | |
64 | |
65 static const DiagnosticParameter fixit = const DiagnosticParameter( | |
66 DiagnosticParameterType.string, 'fixit'); | |
67 } | |
68 | |
69 enum DiagnosticParameterType { | |
70 string, | |
71 verb, | |
72 sessionName, | |
73 target, | |
74 targetKind, | |
75 preposition, | |
76 uri, | |
77 } | |
78 | |
79 class Diagnostic { | |
80 final DiagnosticKind kind; | |
81 | |
82 final String template; | |
83 | |
84 final Map<DiagnosticParameter, dynamic> arguments; | |
85 | |
86 const Diagnostic(this.kind, this.template, this.arguments); | |
87 | |
88 String toString() => 'Diagnostic($kind, $template, $arguments)'; | |
89 | |
90 /// Convert [template] to a human-readable message. This entails replacing | |
91 /// all occurences of `"#{parameterName}"` with the corresponding value in | |
92 /// [arguments]. | |
93 String formatMessage() { | |
94 String formattedMessage = template; | |
95 Set<String> suppliedParameters = new Set<String>(); | |
96 arguments.forEach((DiagnosticParameter parameter, value) { | |
97 suppliedParameters.add('$parameter'); | |
98 String stringValue; | |
99 switch (parameter.type) { | |
100 case DiagnosticParameterType.string: | |
101 stringValue = value; | |
102 break; | |
103 | |
104 case DiagnosticParameterType.uri: | |
105 stringValue = '$value'; | |
106 break; | |
107 | |
108 case DiagnosticParameterType.verb: | |
109 Verb verb = value; | |
110 stringValue = verb.name; | |
111 break; | |
112 | |
113 case DiagnosticParameterType.sessionName: | |
114 stringValue = value; | |
115 break; | |
116 | |
117 case DiagnosticParameterType.target: | |
118 Target target = value; | |
119 // TODO(karlklose): Improve this conversion. | |
120 stringValue = '$target'; | |
121 break; | |
122 | |
123 case DiagnosticParameterType.targetKind: | |
124 TargetKind kind = value; | |
125 // TODO(karlklose): Improve this conversion. | |
126 stringValue = '$kind'; | |
127 break; | |
128 | |
129 case DiagnosticParameterType.preposition: | |
130 Preposition preposition = value; | |
131 // TODO(karlklose): Improve this conversion. | |
132 stringValue = | |
133 preposition.kind.toString().split('.').last.toLowerCase(); | |
134 break; | |
135 | |
136 default: | |
137 throwInternalError(""" | |
138 Unsupported parameter type '${parameter.type}' | |
139 found for parameter '$parameter' | |
140 when trying to format the following error message: | |
141 | |
142 $formattedMessage"""); | |
143 break; | |
144 } | |
145 formattedMessage = formattedMessage.replaceAll('$parameter', stringValue); | |
146 }); | |
147 | |
148 Set<String> usedParameters = new Set<String>(); | |
149 for (Match match in new RegExp("#{[^}]*}").allMatches(template)) { | |
150 String parameter = match.group(0); | |
151 usedParameters.add(parameter); | |
152 } | |
153 | |
154 Set<String> unusedParameters = | |
155 suppliedParameters.difference(usedParameters); | |
156 Set<String> missingParameters = | |
157 usedParameters.difference(suppliedParameters); | |
158 | |
159 if (missingParameters.isNotEmpty || unusedParameters.isNotEmpty) { | |
160 throw """ | |
161 Error when formatting diagnostic: | |
162 kind: $kind | |
163 template: $template | |
164 arguments: $arguments | |
165 missingParameters: ${missingParameters.join(', ')} | |
166 unusedParameters: ${unusedParameters.join(', ')} | |
167 formattedMessage: $formattedMessage"""; | |
168 } | |
169 | |
170 return formattedMessage; | |
171 } | |
172 } | |
173 | |
174 class InputError { | |
175 final DiagnosticKind kind; | |
176 | |
177 final Map<DiagnosticParameter, dynamic> arguments; | |
178 | |
179 const InputError(this.kind, [this.arguments]); | |
180 | |
181 Diagnostic asDiagnostic() { | |
182 return new Diagnostic(kind, getMessage(kind), arguments); | |
183 } | |
184 | |
185 String toString() => 'InputError($kind, $arguments)'; | |
186 } | |
187 | |
188 /// Throw an internal error that will be recorded as a compiler crash. | |
189 /// | |
190 /// In general, assume, no matter how unlikely, that [message] may be read by a | |
191 /// user (that is, a developer using Fletch). For this reason, try to: | |
192 /// | |
193 /// * Avoid phrases that can be interpreted as blaming the user (all error | |
194 /// messages should state what is wrong, in a way that doesn't assign blame). | |
195 /// | |
196 /// * Avoid being cute or funny (there's nothing more frustrating than being | |
197 /// affected by a bug and see a cute or funny message, especially if it | |
198 /// happens a lot). | |
199 /// | |
200 /// * Avoid phrases like "unreachable", "can't happen", "shouldn't happen", | |
201 /// "shouldn't be called", simply because it is wrong: it did happen. In most | |
202 /// cases a factual message would be "unimplemented", "unhandled case", | |
203 /// etc. Remember that the stacktrace will pinpoint the exact location of the | |
204 /// problem, so no need to repeat a method name. | |
205 void throwInternalError(String message) { | |
206 throw new InputError( | |
207 DiagnosticKind.internalError, | |
208 <DiagnosticParameter, dynamic>{DiagnosticParameter.message: message}); | |
209 } | |
210 | |
211 void throwFatalError( | |
212 DiagnosticKind kind, | |
213 {String message, | |
214 Verb verb, | |
215 String sessionName, | |
216 Target target, | |
217 TargetKind requiredTarget, | |
218 String address, | |
219 String userInput, | |
220 String additionalUserInput, | |
221 Preposition preposition, | |
222 Uri uri, | |
223 String fixit}) { | |
224 Map<DiagnosticParameter, dynamic> arguments = | |
225 <DiagnosticParameter, dynamic>{}; | |
226 if (message != null) { | |
227 arguments[DiagnosticParameter.message] = message; | |
228 } | |
229 if (verb != null) { | |
230 arguments[DiagnosticParameter.verb] = verb; | |
231 } | |
232 if (sessionName != null) { | |
233 arguments[DiagnosticParameter.sessionName] = sessionName; | |
234 } | |
235 if (target != null) { | |
236 arguments[DiagnosticParameter.target] = target; | |
237 } | |
238 if (address != null) { | |
239 arguments[DiagnosticParameter.address] = address; | |
240 } | |
241 if (userInput != null) { | |
242 arguments[DiagnosticParameter.userInput] = userInput; | |
243 } | |
244 if (additionalUserInput != null) { | |
245 arguments[DiagnosticParameter.additionalUserInput] = additionalUserInput; | |
246 } | |
247 if (preposition != null) { | |
248 arguments[DiagnosticParameter.preposition] = preposition; | |
249 } | |
250 if (uri != null) { | |
251 arguments[DiagnosticParameter.uri] = uri; | |
252 } | |
253 if (requiredTarget != null) { | |
254 arguments[DiagnosticParameter.requiredTarget] = requiredTarget; | |
255 } | |
256 if (fixit != null) { | |
257 arguments[DiagnosticParameter.fixit] = fixit; | |
258 } | |
259 throw new InputError(kind, arguments); | |
260 } | |
OLD | NEW |