OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dart 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 file. | |
4 | |
5 library analyzer.src.task.html; | |
6 | |
7 import 'dart:collection'; | |
8 | |
9 import 'package:analyzer/src/generated/engine.dart' hide AnalysisTask; | |
10 import 'package:analyzer/src/generated/error.dart'; | |
11 import 'package:analyzer/src/generated/source.dart'; | |
12 import 'package:analyzer/src/task/dart.dart'; | |
13 import 'package:analyzer/src/task/general.dart'; | |
14 import 'package:analyzer/task/dart.dart'; | |
15 import 'package:analyzer/task/general.dart'; | |
16 import 'package:analyzer/task/html.dart'; | |
17 import 'package:analyzer/task/model.dart'; | |
18 import 'package:html/dom.dart'; | |
19 import 'package:html/parser.dart'; | |
20 import 'package:source_span/source_span.dart'; | |
21 import 'package:analyzer/src/context/cache.dart'; | |
22 import 'package:analyzer/src/generated/java_engine.dart'; | |
23 import 'package:analyzer/src/generated/scanner.dart'; | |
24 | |
25 /** | |
26 * The Dart scripts that are embedded in an HTML file. | |
27 */ | |
28 final ListResultDescriptor<DartScript> DART_SCRIPTS = | |
29 new ListResultDescriptor<DartScript>('DART_SCRIPTS', DartScript.EMPTY_LIST); | |
30 | |
31 /** | |
32 * The errors found while parsing an HTML file. | |
33 */ | |
34 final ListResultDescriptor<AnalysisError> HTML_DOCUMENT_ERRORS = | |
35 new ListResultDescriptor<AnalysisError>( | |
36 'HTML_DOCUMENT_ERRORS', AnalysisError.NO_ERRORS); | |
37 | |
38 /** | |
39 * A Dart script that is embedded in an HTML file. | |
40 */ | |
41 class DartScript implements Source { | |
42 /** | |
43 * An empty list of scripts. | |
44 */ | |
45 static final List<DartScript> EMPTY_LIST = <DartScript>[]; | |
46 | |
47 /** | |
48 * The source containing this script. | |
49 */ | |
50 final Source source; | |
51 | |
52 /** | |
53 * The fragments that comprise this content of the script. | |
54 */ | |
55 final List<ScriptFragment> fragments; | |
56 | |
57 /** | |
58 * Initialize a newly created script in the given [source] that is composed of | |
59 * given [fragments]. | |
60 */ | |
61 DartScript(this.source, this.fragments); | |
62 | |
63 @override | |
64 TimestampedData<String> get contents => | |
65 new TimestampedData(modificationStamp, fragments[0].content); | |
66 | |
67 @override | |
68 String get encoding => source.encoding; | |
69 | |
70 @override | |
71 String get fullName => source.fullName; | |
72 | |
73 @override | |
74 bool get isInSystemLibrary => source.isInSystemLibrary; | |
75 | |
76 @override | |
77 int get modificationStamp => source.modificationStamp; | |
78 | |
79 @override | |
80 String get shortName => source.shortName; | |
81 | |
82 @override | |
83 Uri get uri => throw new StateError('uri not supported for scripts'); | |
84 | |
85 @override | |
86 UriKind get uriKind => | |
87 throw new StateError('uriKind not supported for scripts'); | |
88 | |
89 @override | |
90 bool exists() => source.exists(); | |
91 | |
92 @override | |
93 Uri resolveRelativeUri(Uri relativeUri) => | |
94 throw new StateError('resolveRelativeUri not supported for scripts'); | |
95 } | |
96 | |
97 /** | |
98 * A task that looks for Dart scripts in an HTML file and computes both the Dart | |
99 * libraries that are referenced by those scripts and the embedded Dart scripts. | |
100 */ | |
101 class DartScriptsTask extends SourceBasedAnalysisTask { | |
102 /** | |
103 * The name of the [HTML_DOCUMENT] input. | |
104 */ | |
105 static const String DOCUMENT_INPUT = 'DOCUMENT'; | |
106 | |
107 /** | |
108 * The task descriptor describing this kind of task. | |
109 */ | |
110 static final TaskDescriptor DESCRIPTOR = new TaskDescriptor('DartScriptsTask', | |
111 createTask, buildInputs, <ResultDescriptor>[ | |
112 DART_SCRIPTS, | |
113 REFERENCED_LIBRARIES | |
114 ]); | |
115 | |
116 DartScriptsTask(InternalAnalysisContext context, AnalysisTarget target) | |
117 : super(context, target); | |
118 | |
119 @override | |
120 TaskDescriptor get descriptor => DESCRIPTOR; | |
121 | |
122 @override | |
123 void internalPerform() { | |
124 // | |
125 // Prepare inputs. | |
126 // | |
127 Source source = target.source; | |
128 Document document = getRequiredInput(DOCUMENT_INPUT); | |
129 // | |
130 // Process the script tags. | |
131 // | |
132 List<Source> libraries = <Source>[]; | |
133 List<DartScript> inlineScripts = <DartScript>[]; | |
134 List<Element> scripts = document.getElementsByTagName('script'); | |
135 for (Element script in scripts) { | |
136 LinkedHashMap<dynamic, String> attributes = script.attributes; | |
137 if (attributes['type'] == 'application/dart') { | |
138 String src = attributes['src']; | |
139 if (src == null) { | |
140 if (script.hasContent()) { | |
141 List<ScriptFragment> fragments = <ScriptFragment>[]; | |
142 for (Node node in script.nodes) { | |
143 if (node.nodeType == Node.TEXT_NODE) { | |
144 FileLocation start = node.sourceSpan.start; | |
145 fragments.add(new ScriptFragment(start.offset, start.line, | |
146 start.column, (node as Text).data)); | |
147 } | |
148 } | |
149 inlineScripts.add(new DartScript(source, fragments)); | |
150 } | |
151 } else if (AnalysisEngine.isDartFileName(src)) { | |
152 Source source = context.sourceFactory.resolveUri(target.source, src); | |
153 if (source != null) { | |
154 libraries.add(source); | |
155 } | |
156 } | |
157 } | |
158 } | |
159 // | |
160 // Record outputs. | |
161 // | |
162 outputs[REFERENCED_LIBRARIES] = | |
163 libraries.isEmpty ? Source.EMPTY_LIST : libraries; | |
164 outputs[DART_SCRIPTS] = | |
165 inlineScripts.isEmpty ? DartScript.EMPTY_LIST : inlineScripts; | |
166 } | |
167 | |
168 /** | |
169 * Return a map from the names of the inputs of this kind of task to the task | |
170 * input descriptors describing those inputs for a task with the | |
171 * given [target]. | |
172 */ | |
173 static Map<String, TaskInput> buildInputs(Source target) { | |
174 return <String, TaskInput>{DOCUMENT_INPUT: HTML_DOCUMENT.of(target)}; | |
175 } | |
176 | |
177 /** | |
178 * Create a [DartScriptsTask] based on the given [target] in the given | |
179 * [context]. | |
180 */ | |
181 static DartScriptsTask createTask( | |
182 AnalysisContext context, AnalysisTarget target) { | |
183 return new DartScriptsTask(context, target); | |
184 } | |
185 } | |
186 | |
187 /** | |
188 * A task that merges all of the errors for a single source into a single list | |
189 * of errors. | |
190 */ | |
191 class HtmlErrorsTask extends SourceBasedAnalysisTask { | |
192 /** | |
193 * The name of the input that is a list of errors from each of the embedded | |
194 * Dart scripts. | |
195 */ | |
196 static const String DART_ERRORS_INPUT = 'DART_ERRORS'; | |
197 | |
198 /** | |
199 * The name of the [HTML_DOCUMENT_ERRORS] input. | |
200 */ | |
201 static const String DOCUMENT_ERRORS_INPUT = 'DOCUMENT_ERRORS'; | |
202 | |
203 /** | |
204 * The task descriptor describing this kind of task. | |
205 */ | |
206 static final TaskDescriptor DESCRIPTOR = new TaskDescriptor('HtmlErrorsTask', | |
207 createTask, buildInputs, <ResultDescriptor>[HTML_ERRORS]); | |
208 | |
209 HtmlErrorsTask(InternalAnalysisContext context, AnalysisTarget target) | |
210 : super(context, target); | |
211 | |
212 @override | |
213 TaskDescriptor get descriptor => DESCRIPTOR; | |
214 | |
215 @override | |
216 void internalPerform() { | |
217 // | |
218 // Prepare inputs. | |
219 // | |
220 List<List<AnalysisError>> dartErrors = getRequiredInput(DART_ERRORS_INPUT); | |
221 List<AnalysisError> documentErrors = | |
222 getRequiredInput(DOCUMENT_ERRORS_INPUT); | |
223 // | |
224 // Compute the error list. | |
225 // | |
226 List<AnalysisError> errors = <AnalysisError>[]; | |
227 errors.addAll(documentErrors); | |
228 for (List<AnalysisError> scriptErrors in dartErrors) { | |
229 errors.addAll(scriptErrors); | |
230 } | |
231 // | |
232 // Record outputs. | |
233 // | |
234 outputs[HTML_ERRORS] = removeDuplicateErrors(errors); | |
235 } | |
236 | |
237 /** | |
238 * Return a map from the names of the inputs of this kind of task to the task | |
239 * input descriptors describing those inputs for a task with the | |
240 * given [target]. | |
241 */ | |
242 static Map<String, TaskInput> buildInputs(Source target) { | |
243 return <String, TaskInput>{ | |
244 DOCUMENT_ERRORS_INPUT: HTML_DOCUMENT_ERRORS.of(target), | |
245 DART_ERRORS_INPUT: DART_SCRIPTS.of(target).toListOf(DART_ERRORS) | |
246 }; | |
247 } | |
248 | |
249 /** | |
250 * Create an [HtmlErrorsTask] based on the given [target] in the given | |
251 * [context]. | |
252 */ | |
253 static HtmlErrorsTask createTask( | |
254 AnalysisContext context, AnalysisTarget target) { | |
255 return new HtmlErrorsTask(context, target); | |
256 } | |
257 } | |
258 | |
259 /** | |
260 * A task that scans the content of a file, producing a set of Dart tokens. | |
261 */ | |
262 class ParseHtmlTask extends SourceBasedAnalysisTask { | |
263 /** | |
264 * The name of the input whose value is the content of the file. | |
265 */ | |
266 static const String CONTENT_INPUT_NAME = 'CONTENT_INPUT_NAME'; | |
267 | |
268 /** | |
269 * The task descriptor describing this kind of task. | |
270 */ | |
271 static final TaskDescriptor DESCRIPTOR = new TaskDescriptor('ParseHtmlTask', | |
272 createTask, buildInputs, <ResultDescriptor>[ | |
273 HTML_DOCUMENT, | |
274 HTML_DOCUMENT_ERRORS | |
275 ]); | |
276 | |
277 /** | |
278 * Initialize a newly created task to access the content of the source | |
279 * associated with the given [target] in the given [context]. | |
280 */ | |
281 ParseHtmlTask(InternalAnalysisContext context, AnalysisTarget target) | |
282 : super(context, target); | |
283 | |
284 @override | |
285 TaskDescriptor get descriptor => DESCRIPTOR; | |
286 | |
287 @override | |
288 void internalPerform() { | |
289 String content = getRequiredInput(CONTENT_INPUT_NAME); | |
290 | |
291 if (context.getModificationStamp(target.source) < 0) { | |
292 String message = 'Content could not be read'; | |
293 if (context is InternalAnalysisContext) { | |
294 CacheEntry entry = (context as InternalAnalysisContext).getCacheEntry(ta
rget); | |
295 CaughtException exception = entry.exception; | |
296 if (exception != null) { | |
297 message = exception.toString(); | |
298 } | |
299 } | |
300 | |
301 outputs[HTML_DOCUMENT] = new Document(); | |
302 outputs[HTML_DOCUMENT_ERRORS] = <AnalysisError>[new AnalysisError( | |
303 target.source, 0, 0, ScannerErrorCode.UNABLE_GET_CONTENT, [message])]; | |
304 } else { | |
305 HtmlParser parser = new HtmlParser(content, generateSpans: true); | |
306 parser.compatMode = 'quirks'; | |
307 Document document = parser.parse(); | |
308 List<ParseError> parseErrors = parser.errors; | |
309 List<AnalysisError> errors = <AnalysisError>[]; | |
310 for (ParseError parseError in parseErrors) { | |
311 SourceSpan span = parseError.span; | |
312 errors.add(new AnalysisError(target.source, span.start.offset, | |
313 span.length, HtmlErrorCode.PARSE_ERROR, [parseError.message])); | |
314 } | |
315 | |
316 outputs[HTML_DOCUMENT] = document; | |
317 outputs[HTML_DOCUMENT_ERRORS] = errors; | |
318 } | |
319 } | |
320 | |
321 /** | |
322 * Return a map from the names of the inputs of this kind of task to the task | |
323 * input descriptors describing those inputs for a task with the given | |
324 * [source]. | |
325 */ | |
326 static Map<String, TaskInput> buildInputs(Source source) { | |
327 return <String, TaskInput>{CONTENT_INPUT_NAME: CONTENT.of(source)}; | |
328 } | |
329 | |
330 /** | |
331 * Create a [ParseHtmlTask] based on the given [target] in the given [context]
. | |
332 */ | |
333 static ParseHtmlTask createTask( | |
334 AnalysisContext context, AnalysisTarget target) { | |
335 return new ParseHtmlTask(context, target); | |
336 } | |
337 } | |
338 | |
339 /** | |
340 * A fragment of a [DartScript]. | |
341 */ | |
342 class ScriptFragment { | |
343 /** | |
344 * The offset of the first character of the fragment, relative to the start of | |
345 * the containing source. | |
346 */ | |
347 final int offset; | |
348 | |
349 /** | |
350 * The line number of the line containing the first character of the fragment. | |
351 */ | |
352 final int line; | |
353 | |
354 /** | |
355 * The column number of the line containing the first character of the | |
356 * fragment. | |
357 */ | |
358 final int column; | |
359 | |
360 /** | |
361 * The content of the fragment. | |
362 */ | |
363 final String content; | |
364 | |
365 /** | |
366 * Initialize a newly created script fragment to have the given [offset] and | |
367 * [content]. | |
368 */ | |
369 ScriptFragment(this.offset, this.line, this.column, this.content); | |
370 } | |
OLD | NEW |