Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(325)

Side by Side Diff: pkg/polymer/lib/src/build/import_inliner.dart

Issue 513023002: Step one towards stable error messages with details: (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, 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 /// Transfomer that inlines polymer-element definitions from html imports. 5 /// Transfomer that inlines polymer-element definitions from html imports.
6 library polymer.src.build.import_inliner; 6 library polymer.src.build.import_inliner;
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:convert'; 9 import 'dart:convert';
10 10
11 import 'package:analyzer/analyzer.dart'; 11 import 'package:analyzer/analyzer.dart';
12 import 'package:analyzer/src/generated/ast.dart'; 12 import 'package:analyzer/src/generated/ast.dart';
13 import 'package:barback/barback.dart'; 13 import 'package:barback/barback.dart';
14 import 'package:code_transformers/assets.dart'; 14 import 'package:code_transformers/assets.dart';
15 import 'package:code_transformers/messages/build_logger.dart';
15 import 'package:path/path.dart' as path; 16 import 'package:path/path.dart' as path;
16 import 'package:html5lib/dom.dart' show 17 import 'package:html5lib/dom.dart' show
17 Document, DocumentFragment, Element, Node; 18 Document, DocumentFragment, Element, Node;
18 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; 19 import 'package:html5lib/dom_parsing.dart' show TreeVisitor;
19 import 'package:source_maps/refactor.dart' show TextEditTransaction; 20 import 'package:source_maps/refactor.dart' show TextEditTransaction;
20 import 'package:source_span/source_span.dart'; 21 import 'package:source_span/source_span.dart';
21 22
22 import 'common.dart'; 23 import 'common.dart';
23 import 'wrapped_logger.dart'; 24 import 'messages.dart';
24 25
25 // TODO(sigmund): move to web_components package (dartbug.com/18037). 26 // TODO(sigmund): move to web_components package (dartbug.com/18037).
26 class _HtmlInliner extends PolymerTransformer { 27 class _HtmlInliner extends PolymerTransformer {
27 final TransformOptions options; 28 final TransformOptions options;
28 final Transform transform; 29 final Transform transform;
29 final TransformLogger logger; 30 final BuildLogger logger;
30 final AssetId docId; 31 final AssetId docId;
31 final seen = new Set<AssetId>(); 32 final seen = new Set<AssetId>();
32 final scriptIds = <AssetId>[]; 33 final scriptIds = <AssetId>[];
33 final extractedFiles = new Set<AssetId>(); 34 final extractedFiles = new Set<AssetId>();
34 bool experimentalBootstrap = false; 35 bool experimentalBootstrap = false;
35 36
36 /// The number of extracted inline Dart scripts. Used as a counter to give 37 /// The number of extracted inline Dart scripts. Used as a counter to give
37 /// unique-ish filenames. 38 /// unique-ish filenames.
38 int inlineScriptCounter = 0; 39 int inlineScriptCounter = 0;
39 40
40 _HtmlInliner(TransformOptions options, Transform transform) 41 _HtmlInliner(TransformOptions options, Transform transform)
41 : options = options, 42 : options = options,
42 transform = transform, 43 transform = transform,
43 logger = options.releaseMode ? transform.logger : 44 logger = new BuildLogger(transform,
44 new WrappedLogger(transform, convertErrorsToWarnings: true), 45 convertErrorsToWarnings: !options.releaseMode),
45 docId = transform.primaryInput.id; 46 docId = transform.primaryInput.id;
46 47
47 Future apply() { 48 Future apply() {
48 seen.add(docId); 49 seen.add(docId);
49 50
50 Document document; 51 Document document;
51 bool changed = false; 52 bool changed = false;
52 53
53 return readPrimaryAsHtml(transform).then((doc) { 54 return readPrimaryAsHtml(transform, logger).then((doc) {
54 document = doc; 55 document = doc;
55 changed = new _UrlNormalizer(transform, docId, logger).visit(document) 56 changed = new _UrlNormalizer(transform, docId, logger).visit(document)
56 || changed; 57 || changed;
57 58
58 experimentalBootstrap = document.querySelectorAll('link').any((link) => 59 experimentalBootstrap = document.querySelectorAll('link').any((link) =>
59 link.attributes['rel'] == 'import' && 60 link.attributes['rel'] == 'import' &&
60 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); 61 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML);
61 changed = _extractScripts(document) || changed; 62 changed = _extractScripts(document) || changed;
62 return _visitImports(document); 63 return _visitImports(document);
63 }).then((importsFound) { 64 }).then((importsFound) {
64 changed = changed || importsFound; 65 changed = changed || importsFound;
65 return _removeScripts(document); 66 return _removeScripts(document);
66 }).then((scriptsRemoved) { 67 }).then((scriptsRemoved) {
67 changed = changed || scriptsRemoved; 68 changed = changed || scriptsRemoved;
68 69
69 var output = transform.primaryInput; 70 var output = transform.primaryInput;
70 if (changed) output = new Asset.fromString(docId, document.outerHtml); 71 if (changed) output = new Asset.fromString(docId, document.outerHtml);
71 transform.addOutput(output); 72 transform.addOutput(output);
72 73
73 // We produce a secondary asset with extra information for later phases. 74 // We produce a secondary asset with extra information for later phases.
74 transform.addOutput(new Asset.fromString( 75 transform.addOutput(new Asset.fromString(
75 docId.addExtension('._data'), 76 docId.addExtension('._data'),
76 JSON.encode({ 77 JSON.encode({
77 'experimental_bootstrap': experimentalBootstrap, 78 'experimental_bootstrap': experimentalBootstrap,
78 'script_ids': scriptIds, 79 'script_ids': scriptIds,
79 }, toEncodable: (id) => id.serialize()))); 80 }, toEncodable: (id) => id.serialize())));
80 81
81 // Write out the logs collected by our [WrappedLogger]. 82 // Write out the logs collected by our [BuildLogger].
82 if (options.injectBuildLogsInOutput && logger is WrappedLogger) { 83 if (options.injectBuildLogsInOutput) {
83 return (logger as WrappedLogger).writeOutput(); 84 return logger.writeOutput();
84 } 85 }
85 }); 86 });
86 } 87 }
87 88
88 /// Visits imports in [document] and add the imported documents to documents. 89 /// Visits imports in [document] and add the imported documents to documents.
89 /// Documents are added in the order they appear, transitive imports are added 90 /// Documents are added in the order they appear, transitive imports are added
90 /// first. 91 /// first.
91 /// 92 ///
92 /// Returns `true` if and only if the document was changed and should be 93 /// Returns `true` if and only if the document was changed and should be
93 /// written out. 94 /// written out.
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
147 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { 148 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) {
148 // Move the node into the body, where its contents will be placed. 149 // Move the node into the body, where its contents will be placed.
149 doc.body.insertBefore(node, insertionPoint); 150 doc.body.insertBefore(node, insertionPoint);
150 } 151 }
151 } 152 }
152 } 153 }
153 154
154 /// Loads an asset identified by [id], visits its imports and collects its 155 /// Loads an asset identified by [id], visits its imports and collects its
155 /// html imports. Then inlines it into the main document. 156 /// html imports. Then inlines it into the main document.
156 Future _inlineImport(AssetId id, Element link) { 157 Future _inlineImport(AssetId id, Element link) {
157 return readAsHtml(id, transform).catchError((error) { 158 return readAsHtml(id, transform, logger).catchError((error) {
158 logger.error( 159 logger.error(INLINE_IMPORT_FAIL.create({'error': error}),
159 "Failed to inline html import: $error", asset: id,
160 span: link.sourceSpan); 160 span: link.sourceSpan);
161 }).then((doc) { 161 }).then((doc) {
162 if (doc == null) return false; 162 if (doc == null) return false;
163 new _UrlNormalizer(transform, id, logger).visit(doc); 163 new _UrlNormalizer(transform, id, logger).visit(doc);
164 return _visitImports(doc).then((_) { 164 return _visitImports(doc).then((_) {
165 // _UrlNormalizer already ensures there is a library name. 165 // _UrlNormalizer already ensures there is a library name.
166 _extractScripts(doc, injectLibraryName: false); 166 _extractScripts(doc, injectLibraryName: false);
167 167
168 // TODO(jmesserly): figure out how this is working in vulcanizer. 168 // TODO(jmesserly): figure out how this is working in vulcanizer.
169 // Do they produce a <body> tag with a <head> and <body> inside? 169 // Do they produce a <body> tag with a <head> and <body> inside?
170 var imported = new DocumentFragment(); 170 var imported = new DocumentFragment();
171 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); 171 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes);
172 link.replaceWith(imported); 172 link.replaceWith(imported);
173 173
174 // Make sure to grab any logs from the inlined import. 174 // Make sure to grab any logs from the inlined import.
175 if (logger is WrappedLogger) { 175 return logger.addLogFilesFromAsset(id);
176 return (logger as WrappedLogger).addLogFilesFromAsset(id);
177 }
178 }); 176 });
179 }); 177 });
180 } 178 }
181 179
182 Future _inlineStylesheet(AssetId id, Element link) { 180 Future _inlineStylesheet(AssetId id, Element link) {
183 return transform.readInputAsString(id).catchError((error) { 181 return transform.readInputAsString(id).catchError((error) {
184 // TODO(jakemac): Move this warning to the linter once we can make it run 182 // TODO(jakemac): Move this warning to the linter once we can make it run
185 // always (see http://dartbug.com/17199). Then hide this error and replace 183 // always (see http://dartbug.com/17199). Then hide this error and replace
186 // with a comment pointing to the linter error (so we don't double warn). 184 // with a comment pointing to the linter error (so we don't double warn).
187 logger.warning( 185 logger.warning(INLINE_STYLE_FAIL.create({'error': error}),
188 "Failed to inline stylesheet: $error", asset: id,
189 span: link.sourceSpan); 186 span: link.sourceSpan);
190 }).then((css) { 187 }).then((css) {
191 if (css == null) return null; 188 if (css == null) return null;
192 css = new _UrlNormalizer(transform, id, logger).visitCss(css); 189 css = new _UrlNormalizer(transform, id, logger).visitCss(css);
193 var styleElement = new Element.tag('style')..text = css; 190 var styleElement = new Element.tag('style')..text = css;
194 // Copy over the extra attributes from the link tag to the style tag. 191 // Copy over the extra attributes from the link tag to the style tag.
195 // This adds support for no-shim, shim-shadowdom, etc. 192 // This adds support for no-shim, shim-shadowdom, etc.
196 link.attributes.forEach((key, value) { 193 link.attributes.forEach((key, value) {
197 if (!IGNORED_LINKED_STYLE_ATTRS.contains(key)) { 194 if (!IGNORED_LINKED_STYLE_ATTRS.contains(key)) {
198 styleElement.attributes[key] = value; 195 styleElement.attributes[key] = value;
(...skipping 19 matching lines...) Expand all
218 // We check for extractedFiles because 'hasInput' below is only true for 215 // We check for extractedFiles because 'hasInput' below is only true for
219 // assets that existed before this transformer runs (hasInput is false 216 // assets that existed before this transformer runs (hasInput is false
220 // for files created by [_extractScripts]). 217 // for files created by [_extractScripts]).
221 if (extractedFiles.contains(srcId)) { 218 if (extractedFiles.contains(srcId)) {
222 scriptIds.add(srcId); 219 scriptIds.add(srcId);
223 return true; 220 return true;
224 } 221 }
225 222
226 return transform.hasInput(srcId).then((exists) { 223 return transform.hasInput(srcId).then((exists) {
227 if (!exists) { 224 if (!exists) {
228 logger.warning('Script file at "$src" not found.', 225 logger.warning(SCRIPT_FILE_NOT_FOUND.create({'url': src}),
229 span: script.sourceSpan); 226 span: script.sourceSpan);
230 } else { 227 } else {
231 scriptIds.add(srcId); 228 scriptIds.add(srcId);
232 } 229 }
233 }); 230 });
234 } 231 }
235 }).then((_) => changed); 232 }).then((_) => changed);
236 } 233 }
237 234
238 /// Split inline scripts into their own files. We need to do this for dart2js 235 /// Split inline scripts into their own files. We need to do this for dart2js
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
335 /// Counter used to ensure that every library name we inject is unique. 332 /// Counter used to ensure that every library name we inject is unique.
336 int _count = 0; 333 int _count = 0;
337 334
338 /// Path to the top level folder relative to the transform primaryInput. 335 /// Path to the top level folder relative to the transform primaryInput.
339 /// This should just be some arbitrary # of ../'s. 336 /// This should just be some arbitrary # of ../'s.
340 final String topLevelPath; 337 final String topLevelPath;
341 338
342 /// Whether or not the normalizer has changed something in the tree. 339 /// Whether or not the normalizer has changed something in the tree.
343 bool changed = false; 340 bool changed = false;
344 341
345 final TransformLogger logger; 342 final BuildLogger logger;
346 343
347 _UrlNormalizer(transform, this.sourceId, this.logger) 344 _UrlNormalizer(transform, this.sourceId, this.logger)
348 : transform = transform, 345 : transform = transform,
349 topLevelPath = 346 topLevelPath =
350 '../' * (transform.primaryInput.id.path.split('/').length - 2); 347 '../' * (transform.primaryInput.id.path.split('/').length - 2);
351 348
352 visit(Node node) { 349 visit(Node node) {
353 super.visit(node); 350 super.visit(node);
354 return changed; 351 return changed;
355 } 352 }
356 353
357 visitElement(Element node) { 354 visitElement(Element node) {
358 // TODO(jakemac): Support custom elements that extend html elements which 355 // TODO(jakemac): Support custom elements that extend html elements which
359 // have url-like attributes. This probably means keeping a list of which 356 // have url-like attributes. This probably means keeping a list of which
360 // html elements support each url-like attribute. 357 // html elements support each url-like attribute.
361 if (!isCustomTagName(node.localName)) { 358 if (!isCustomTagName(node.localName)) {
362 node.attributes.forEach((name, value) { 359 node.attributes.forEach((name, value) {
363 if (_urlAttributes.contains(name)) { 360 if (_urlAttributes.contains(name)) {
364 if (!name.startsWith('_') && value.contains(_BINDING_REGEX)) { 361 if (!name.startsWith('_') && value.contains(_BINDING_REGEX)) {
365 logger.warning( 362 logger.warning(USE_UNDERSCORE_PREFIX.create({'name': name}),
366 'When using bindings with the "$name" attribute you may ' 363 span: node.sourceSpan, asset: sourceId);
367 'experience errors in certain browsers. Please use the '
368 '"_$name" attribute instead. For more information, see '
369 'http://goo.gl/5av8cU', span: node.sourceSpan, asset: sourceId);
370 } else if (name.startsWith('_') && !value.contains(_BINDING_REGEX)) { 364 } else if (name.startsWith('_') && !value.contains(_BINDING_REGEX)) {
371 logger.warning( 365 logger.warning(DONT_USE_UNDERSCORE_PREFIX.create(
372 'The "$name" attribute is only supported when using bindings. ' 366 {'name': name.substring(1)}),
373 'Please change to the "${name.substring(1)}" attribute.',
374 span: node.sourceSpan, asset: sourceId); 367 span: node.sourceSpan, asset: sourceId);
375 } 368 }
376 if (value != '' && !value.trim().startsWith(_BINDING_REGEX)) { 369 if (value != '' && !value.trim().startsWith(_BINDING_REGEX)) {
377 node.attributes[name] = _newUrl(value, node.sourceSpan); 370 node.attributes[name] = _newUrl(value, node.sourceSpan);
378 changed = changed || value != node.attributes[name]; 371 changed = changed || value != node.attributes[name];
379 } 372 }
380 } 373 }
381 }); 374 });
382 } 375 }
383 if (node.localName == 'style') { 376 if (node.localName == 'style') {
(...skipping 12 matching lines...) Expand all
396 389
397 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); 390 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true);
398 static final _QUOTE = new RegExp('["\']', multiLine: true); 391 static final _QUOTE = new RegExp('["\']', multiLine: true);
399 392
400 /// Visit the CSS text and replace any relative URLs so we can inline it. 393 /// Visit the CSS text and replace any relative URLs so we can inline it.
401 // Ported from: 394 // Ported from:
402 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149 395 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149
403 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. 396 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness.
404 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. 397 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure.
405 String visitCss(String cssText) { 398 String visitCss(String cssText) {
406 var url = spanUrlFor(sourceId, transform); 399 var url = spanUrlFor(sourceId, transform, logger);
407 var src = new SourceFile(cssText, url: url); 400 var src = new SourceFile(cssText, url: url);
408 return cssText.replaceAllMapped(_URL, (match) { 401 return cssText.replaceAllMapped(_URL, (match) {
409 // Extract the URL, without any surrounding quotes. 402 // Extract the URL, without any surrounding quotes.
410 var span = src.span(match.start, match.end); 403 var span = src.span(match.start, match.end);
411 var href = match[1].replaceAll(_QUOTE, ''); 404 var href = match[1].replaceAll(_QUOTE, '');
412 href = _newUrl(href, span); 405 href = _newUrl(href, span);
413 return 'url($href)'; 406 return 'url($href)';
414 }); 407 });
415 } 408 }
416 409
417 String visitInlineDart(String code) { 410 String visitInlineDart(String code) {
418 var unit = parseDirectives(code, suppressErrors: true); 411 var unit = parseDirectives(code, suppressErrors: true);
419 var file = new SourceFile(code, url: spanUrlFor(sourceId, transform)); 412 var file = new SourceFile(code,
413 url: spanUrlFor(sourceId, transform, logger));
420 var output = new TextEditTransaction(code, file); 414 var output = new TextEditTransaction(code, file);
421 var foundLibraryDirective = false; 415 var foundLibraryDirective = false;
422 for (Directive directive in unit.directives) { 416 for (Directive directive in unit.directives) {
423 if (directive is UriBasedDirective) { 417 if (directive is UriBasedDirective) {
424 var uri = directive.uri.stringValue; 418 var uri = directive.uri.stringValue;
425 var span = _getSpan(file, directive.uri); 419 var span = _getSpan(file, directive.uri);
426 420
427 var id = uriToAssetId(sourceId, uri, logger, span, 421 var id = uriToAssetId(sourceId, uri, logger, span,
428 errorOnAbsolute: false); 422 errorOnAbsolute: false);
429 if (id == null) continue; 423 if (id == null) continue;
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
486 if (newPath.startsWith('lib/')) { 480 if (newPath.startsWith('lib/')) {
487 return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}'; 481 return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}';
488 } 482 }
489 483
490 if (newPath.startsWith('asset/')) { 484 if (newPath.startsWith('asset/')) {
491 return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}'; 485 return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}';
492 } 486 }
493 487
494 if (primaryId.package != id.package) { 488 if (primaryId.package != id.package) {
495 // Techincally we shouldn't get there 489 // Techincally we shouldn't get there
496 logger.error("don't know how to include $id from $primaryId", span: span); 490 logger.error(INTERNAL_ERROR_DONT_KNOW_HOW_TO_IMPORT.create({
491 'target': id, 'source': primaryId, 'extra': ''}), span: span);
497 return href; 492 return href;
498 } 493 }
499 494
500 var builder = path.url; 495 var builder = path.url;
501 return builder.relative(builder.join('/', newPath), 496 return builder.relative(builder.join('/', newPath),
502 from: builder.join('/', builder.dirname(primaryId.path))); 497 from: builder.join('/', builder.dirname(primaryId.path)));
503 } 498 }
504 } 499 }
505 500
506 /// HTML attributes that expect a URL value. 501 /// HTML attributes that expect a URL value.
(...skipping 21 matching lines...) Expand all
528 /// style tag except these ones. 523 /// style tag except these ones.
529 const IGNORED_LINKED_STYLE_ATTRS = 524 const IGNORED_LINKED_STYLE_ATTRS =
530 const ['charset', 'href', 'href-lang', 'rel', 'rev']; 525 const ['charset', 'href', 'href-lang', 'rel', 'rev'];
531 526
532 /// Global RegExp objects. 527 /// Global RegExp objects.
533 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); 528 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]');
534 final _NUM_REGEX = new RegExp('[0-9]'); 529 final _NUM_REGEX = new RegExp('[0-9]');
535 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))'); 530 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))');
536 531
537 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); 532 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end);
OLDNEW
« no previous file with comments | « pkg/polymer/lib/src/build/generated/messages.html ('k') | pkg/polymer/lib/src/build/linter.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698