| 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 |