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