| OLD | NEW |
| 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 /// Common methods used by transfomers. | 5 /// Common methods used by transfomers. |
| 6 library polymer.src.build.common; | 6 library polymer.src.build.common; |
| 7 | 7 |
| 8 import 'dart:async'; | 8 import 'dart:async'; |
| 9 | 9 |
| 10 import 'package:analyzer/src/generated/ast.dart'; | 10 import 'package:analyzer/src/generated/ast.dart'; |
| 11 import 'package:analyzer/src/generated/error.dart'; | 11 import 'package:analyzer/src/generated/error.dart'; |
| 12 import 'package:analyzer/src/generated/parser.dart'; | 12 import 'package:analyzer/src/generated/parser.dart'; |
| 13 import 'package:analyzer/src/generated/scanner.dart'; | 13 import 'package:analyzer/src/generated/scanner.dart'; |
| 14 import 'package:barback/barback.dart'; | 14 import 'package:barback/barback.dart'; |
| 15 import 'package:code_transformers/messages/build_logger.dart'; |
| 15 import 'package:html5lib/dom.dart' show Document; | 16 import 'package:html5lib/dom.dart' show Document; |
| 16 import 'package:html5lib/parser.dart' show HtmlParser; | 17 import 'package:html5lib/parser.dart' show HtmlParser; |
| 18 import 'package:observe/transformer.dart' show ObservableTransformer; |
| 17 import 'package:path/path.dart' as path; | 19 import 'package:path/path.dart' as path; |
| 18 import 'package:observe/transformer.dart' show ObservableTransformer; | 20 |
| 21 import 'constants.dart'; |
| 22 import 'messages.dart'; |
| 23 |
| 24 export 'constants.dart'; |
| 19 | 25 |
| 20 const _ignoredErrors = const [ | 26 const _ignoredErrors = const [ |
| 21 'unexpected-dash-after-double-dash-in-comment', | 27 'unexpected-dash-after-double-dash-in-comment', |
| 22 'unexpected-char-in-comment', | 28 'unexpected-char-in-comment', |
| 23 ]; | 29 ]; |
| 24 | 30 |
| 25 /// Parses an HTML file [contents] and returns a DOM-like tree. Adds emitted | 31 /// Parses an HTML file [contents] and returns a DOM-like tree. Adds emitted |
| 26 /// error/warning to [logger]. | 32 /// error/warning to [logger]. |
| 27 Document _parseHtml(String contents, String sourcePath, TransformLogger logger, | 33 Document _parseHtml(String contents, String sourcePath, BuildLogger logger, |
| 28 {bool checkDocType: true, bool showWarnings: true}) { | 34 {bool checkDocType: true, bool showWarnings: true}) { |
| 29 // TODO(jmesserly): make HTTP encoding configurable | 35 // TODO(jmesserly): make HTTP encoding configurable |
| 30 var parser = new HtmlParser(contents, encoding: 'utf8', | 36 var parser = new HtmlParser(contents, encoding: 'utf8', |
| 31 generateSpans: true, sourceUrl: sourcePath); | 37 generateSpans: true, sourceUrl: sourcePath); |
| 32 var document = parser.parse(); | 38 var document = parser.parse(); |
| 33 | 39 |
| 34 // Note: errors aren't fatal in HTML (unless strict mode is on). | 40 // Note: errors aren't fatal in HTML (unless strict mode is on). |
| 35 // So just print them as warnings. | 41 // So just print them as warnings. |
| 36 if (showWarnings) { | 42 if (showWarnings) { |
| 37 for (var e in parser.errors) { | 43 for (var e in parser.errors) { |
| 38 if (_ignoredErrors.contains(e.errorCode)) continue; | 44 if (_ignoredErrors.contains(e.errorCode)) continue; |
| 39 if (checkDocType || e.errorCode != 'expected-doctype-but-got-start-tag') { | 45 if (checkDocType || e.errorCode != 'expected-doctype-but-got-start-tag') { |
| 40 logger.warning(e.message, span: e.span); | 46 logger.warning(HTML5_WARNING.create({'message': e.message}), |
| 47 span: e.span); |
| 41 } | 48 } |
| 42 } | 49 } |
| 43 } | 50 } |
| 44 return document; | 51 return document; |
| 45 } | 52 } |
| 46 | 53 |
| 47 /// Additional options used by polymer transformers | 54 /// Additional options used by polymer transformers |
| 48 class TransformOptions { | 55 class TransformOptions { |
| 49 /// List of entrypoints paths. The paths are relative to the package root and | 56 /// List of entrypoints paths. The paths are relative to the package root and |
| 50 /// are represented using posix style, which matches the representation used | 57 /// are represented using posix style, which matches the representation used |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 119 // Then check the global default setting. | 126 // Then check the global default setting. |
| 120 var globalDefault = inlineStylesheets['default']; | 127 var globalDefault = inlineStylesheets['default']; |
| 121 return (globalDefault != null) ? globalDefault : true; | 128 return (globalDefault != null) ? globalDefault : true; |
| 122 } | 129 } |
| 123 } | 130 } |
| 124 | 131 |
| 125 /// Mixin for polymer transformers. | 132 /// Mixin for polymer transformers. |
| 126 abstract class PolymerTransformer { | 133 abstract class PolymerTransformer { |
| 127 TransformOptions get options; | 134 TransformOptions get options; |
| 128 | 135 |
| 129 Future<Document> readPrimaryAsHtml(Transform transform) { | 136 Future<Document> readPrimaryAsHtml(Transform transform, BuildLogger logger) { |
| 130 var asset = transform.primaryInput; | 137 var asset = transform.primaryInput; |
| 131 var id = asset.id; | 138 var id = asset.id; |
| 132 return asset.readAsString().then((content) { | 139 return asset.readAsString().then((content) { |
| 133 return _parseHtml(content, id.path, transform.logger, | 140 return _parseHtml(content, id.path, logger, |
| 134 checkDocType: options.isHtmlEntryPoint(id)); | 141 checkDocType: options.isHtmlEntryPoint(id)); |
| 135 }); | 142 }); |
| 136 } | 143 } |
| 137 | 144 |
| 138 Future<Document> readAsHtml(AssetId id, Transform transform, | 145 Future<Document> readAsHtml(AssetId id, Transform transform, |
| 146 BuildLogger logger, |
| 139 {bool showWarnings: true}) { | 147 {bool showWarnings: true}) { |
| 140 var primaryId = transform.primaryInput.id; | 148 var primaryId = transform.primaryInput.id; |
| 141 bool samePackage = id.package == primaryId.package; | 149 bool samePackage = id.package == primaryId.package; |
| 142 var url = spanUrlFor(id, transform); | 150 var url = spanUrlFor(id, transform, logger); |
| 143 return transform.readInputAsString(id).then((content) { | 151 return transform.readInputAsString(id).then((content) { |
| 144 return _parseHtml(content, url, transform.logger, | 152 return _parseHtml(content, url, logger, |
| 145 checkDocType: samePackage && options.isHtmlEntryPoint(id), | 153 checkDocType: samePackage && options.isHtmlEntryPoint(id), |
| 146 showWarnings: showWarnings); | 154 showWarnings: showWarnings); |
| 147 }); | 155 }); |
| 148 } | 156 } |
| 149 | 157 |
| 150 Future<bool> assetExists(AssetId id, Transform transform) => | 158 Future<bool> assetExists(AssetId id, Transform transform) => |
| 151 transform.getInput(id).then((_) => true).catchError((_) => false); | 159 transform.getInput(id).then((_) => true).catchError((_) => false); |
| 152 | 160 |
| 153 String toString() => 'polymer ($runtimeType)'; | 161 String toString() => 'polymer ($runtimeType)'; |
| 154 } | 162 } |
| 155 | 163 |
| 156 /// Gets the appropriate URL to use in a span to produce messages (e.g. | 164 /// Gets the appropriate URL to use in a span to produce messages (e.g. |
| 157 /// warnings) for users. This will attempt to format the URL in the most useful | 165 /// warnings) for users. This will attempt to format the URL in the most useful |
| 158 /// way: | 166 /// way: |
| 159 /// | 167 /// |
| 160 /// - If the asset is within the primary package, then use the [id.path], | 168 /// - If the asset is within the primary package, then use the [id.path], |
| 161 /// the user will know it is a file from their own code. | 169 /// the user will know it is a file from their own code. |
| 162 /// - If the asset is from another package, then use [assetUrlFor], this will | 170 /// - If the asset is from another package, then use [assetUrlFor], this will |
| 163 /// likely be a "package:" url to the file in the other package, which is | 171 /// likely be a "package:" url to the file in the other package, which is |
| 164 /// enough for users to identify where the error is. | 172 /// enough for users to identify where the error is. |
| 165 String spanUrlFor(AssetId id, Transform transform) { | 173 String spanUrlFor(AssetId id, Transform transform, logger) { |
| 166 var primaryId = transform.primaryInput.id; | 174 var primaryId = transform.primaryInput.id; |
| 167 bool samePackage = id.package == primaryId.package; | 175 bool samePackage = id.package == primaryId.package; |
| 168 return samePackage ? id.path | 176 return samePackage ? id.path |
| 169 : assetUrlFor(id, primaryId, transform.logger, allowAssetUrl: true); | 177 : assetUrlFor(id, primaryId, logger, allowAssetUrl: true); |
| 170 } | 178 } |
| 171 | 179 |
| 172 /// Transformer phases which should be applied to the Polymer package. | 180 /// Transformer phases which should be applied to the Polymer package. |
| 173 List<List<Transformer>> get phasesForPolymer => | 181 List<List<Transformer>> get phasesForPolymer => |
| 174 [[new ObservableTransformer(['lib/src/instance.dart'])]]; | 182 [[new ObservableTransformer(['lib/src/instance.dart'])]]; |
| 175 | 183 |
| 176 /// Generate the import url for a file described by [id], referenced by a file | 184 /// Generate the import url for a file described by [id], referenced by a file |
| 177 /// with [sourceId]. | 185 /// with [sourceId]. |
| 178 // TODO(sigmund): this should also be in barback (dartbug.com/12610) | 186 // TODO(sigmund): this should also be in barback (dartbug.com/12610) |
| 179 String assetUrlFor(AssetId id, AssetId sourceId, TransformLogger logger, | 187 String assetUrlFor(AssetId id, AssetId sourceId, BuildLogger logger, |
| 180 {bool allowAssetUrl: false}) { | 188 {bool allowAssetUrl: false}) { |
| 181 // use package: and asset: urls if possible | 189 // use package: and asset: urls if possible |
| 182 if (id.path.startsWith('lib/')) { | 190 if (id.path.startsWith('lib/')) { |
| 183 return 'package:${id.package}/${id.path.substring(4)}'; | 191 return 'package:${id.package}/${id.path.substring(4)}'; |
| 184 } | 192 } |
| 185 | 193 |
| 186 if (id.path.startsWith('asset/')) { | 194 if (id.path.startsWith('asset/')) { |
| 187 if (!allowAssetUrl) { | 195 if (!allowAssetUrl) { |
| 188 logger.error("asset urls not allowed. " | 196 logger.error(INTERNAL_ERROR_DONT_KNOW_HOW_TO_IMPORT.create({ |
| 189 "Don't know how to refer to $id from $sourceId"); | 197 'target': id, |
| 198 'source': sourceId, |
| 199 'extra': ' (asset urls not allowed.)'})); |
| 190 return null; | 200 return null; |
| 191 } | 201 } |
| 192 return 'asset:${id.package}/${id.path.substring(6)}'; | 202 return 'asset:${id.package}/${id.path.substring(6)}'; |
| 193 } | 203 } |
| 194 | 204 |
| 195 // Use relative urls only if it's possible. | 205 // Use relative urls only if it's possible. |
| 196 if (id.package != sourceId.package) { | 206 if (id.package != sourceId.package) { |
| 197 logger.error("don't know how to refer to $id from $sourceId"); | 207 logger.error("don't know how to refer to $id from $sourceId"); |
| 198 return null; | 208 return null; |
| 199 } | 209 } |
| 200 | 210 |
| 201 var builder = path.url; | 211 var builder = path.url; |
| 202 return builder.relative(builder.join('/', id.path), | 212 return builder.relative(builder.join('/', id.path), |
| 203 from: builder.join('/', builder.dirname(sourceId.path))); | 213 from: builder.join('/', builder.dirname(sourceId.path))); |
| 204 } | 214 } |
| 205 | 215 |
| 206 | 216 |
| 207 /// Convert system paths to asset paths (asset paths are posix style). | 217 /// Convert system paths to asset paths (asset paths are posix style). |
| 208 String systemToAssetPath(String assetPath) { | 218 String systemToAssetPath(String assetPath) { |
| 209 if (path.Style.platform != path.Style.windows) return assetPath; | 219 if (path.Style.platform != path.Style.windows) return assetPath; |
| 210 return path.posix.joinAll(path.split(assetPath)); | 220 return path.posix.joinAll(path.split(assetPath)); |
| 211 } | 221 } |
| 212 | 222 |
| 213 /// These names have meaning in SVG or MathML, so they aren't allowed as custom | |
| 214 /// tags. See [isCustomTagName]. | |
| 215 const invalidTagNames = const { | |
| 216 'annotation-xml': '', | |
| 217 'color-profile': '', | |
| 218 'font-face': '', | |
| 219 'font-face-src': '', | |
| 220 'font-face-uri': '', | |
| 221 'font-face-format': '', | |
| 222 'font-face-name': '', | |
| 223 'missing-glyph': '', | |
| 224 }; | |
| 225 | |
| 226 /// Returns true if this is a valid custom element name. See: | 223 /// Returns true if this is a valid custom element name. See: |
| 227 /// <http://w3c.github.io/webcomponents/spec/custom/#dfn-custom-element-type> | 224 /// <http://w3c.github.io/webcomponents/spec/custom/#dfn-custom-element-type> |
| 228 bool isCustomTagName(String name) { | 225 bool isCustomTagName(String name) { |
| 229 if (name == null || !name.contains('-')) return false; | 226 if (name == null || !name.contains('-')) return false; |
| 230 return !invalidTagNames.containsKey(name); | 227 return !invalidTagNames.containsKey(name); |
| 231 } | 228 } |
| 232 | 229 |
| 233 /// Regex to split names in the 'attributes' attribute, which supports 'a b c', | 230 /// Regex to split names in the 'attributes' attribute, which supports 'a b c', |
| 234 /// 'a,b,c', or even 'a b,c'. This is the same as in `lib/src/declaration.dart`. | 231 /// 'a,b,c', or even 'a b,c'. This is the same as in `lib/src/declaration.dart`. |
| 235 final ATTRIBUTES_REGEX = new RegExp(r'\s|,'); | 232 final ATTRIBUTES_REGEX = new RegExp(r'\s|,'); |
| 236 | |
| 237 const POLYMER_EXPERIMENTAL_HTML = 'packages/polymer/polymer_experimental.html'; | |
| 238 | |
| 239 const String LOG_EXTENSION = '._buildLogs'; | |
| OLD | NEW |