| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /// Common methods used by transfomers. | |
| 6 library polymer.src.build.common; | |
| 7 | |
| 8 import 'dart:async'; | |
| 9 | |
| 10 import 'package:analyzer/src/generated/ast.dart'; | |
| 11 import 'package:analyzer/src/generated/error.dart'; | |
| 12 import 'package:analyzer/src/generated/parser.dart'; | |
| 13 import 'package:analyzer/src/generated/scanner.dart'; | |
| 14 import 'package:barback/barback.dart'; | |
| 15 import 'package:code_transformers/messages/build_logger.dart'; | |
| 16 import 'package:html/dom.dart' show Document; | |
| 17 import 'package:html/parser.dart' show HtmlParser; | |
| 18 import 'package:observe/transformer.dart' show ObservableTransformer; | |
| 19 import 'package:path/path.dart' as path; | |
| 20 | |
| 21 import 'constants.dart'; | |
| 22 import 'messages.dart'; | |
| 23 | |
| 24 export 'constants.dart'; | |
| 25 | |
| 26 const _ignoredErrors = const [ | |
| 27 'unexpected-dash-after-double-dash-in-comment', | |
| 28 'unexpected-char-in-comment', | |
| 29 ]; | |
| 30 | |
| 31 /// Parses an HTML file [contents] and returns a DOM-like tree. Adds emitted | |
| 32 /// error/warning to [logger]. | |
| 33 Document _parseHtml(String contents, String sourcePath, BuildLogger logger, | |
| 34 {bool checkDocType: true, bool showWarnings: true}) { | |
| 35 // TODO(jmesserly): make HTTP encoding configurable | |
| 36 var parser = new HtmlParser(contents, | |
| 37 encoding: 'utf8', generateSpans: true, sourceUrl: sourcePath); | |
| 38 var document = parser.parse(); | |
| 39 | |
| 40 // Note: errors aren't fatal in HTML (unless strict mode is on). | |
| 41 // So just print them as warnings. | |
| 42 if (showWarnings) { | |
| 43 for (var e in parser.errors) { | |
| 44 if (_ignoredErrors.contains(e.errorCode)) continue; | |
| 45 if (checkDocType || e.errorCode != 'expected-doctype-but-got-start-tag') { | |
| 46 logger.warning(HTML5_WARNING.create({'message': e.message}), | |
| 47 span: e.span); | |
| 48 } | |
| 49 } | |
| 50 } | |
| 51 return document; | |
| 52 } | |
| 53 | |
| 54 /// Additional options used by polymer transformers | |
| 55 class TransformOptions { | |
| 56 /// List of entrypoints paths. The paths are relative to the package root and | |
| 57 /// are represented using posix style, which matches the representation used | |
| 58 /// in asset ids in barback. If null, anything under 'web/' or 'test/' is | |
| 59 /// considered an entry point. | |
| 60 final List<String> entryPoints; | |
| 61 | |
| 62 /// Map of stylesheet paths that should or should not be inlined. The paths | |
| 63 /// are relative to the package root and are represented using posix style, | |
| 64 /// which matches the representation used in asset ids in barback. | |
| 65 /// | |
| 66 /// There is an additional special key 'default' for the global default. | |
| 67 final Map<String, bool> inlineStylesheets; | |
| 68 | |
| 69 /// True to enable Content Security Policy. | |
| 70 /// This means the HTML page will not have inlined .js code. | |
| 71 final bool contentSecurityPolicy; | |
| 72 | |
| 73 /// True to include the compiled JavaScript directly from the HTML page. | |
| 74 /// If enabled this will remove "packages/browser/dart.js" and replace | |
| 75 /// `type="application/dart"` scripts with equivalent *.dart.js files. | |
| 76 final bool directlyIncludeJS; | |
| 77 | |
| 78 /// Run transformers to create a releasable app. For example, include the | |
| 79 /// minified versions of the polyfills rather than the debug versions. | |
| 80 final bool releaseMode; | |
| 81 | |
| 82 /// This will make a physical element appear on the page showing build logs. | |
| 83 /// It will only appear when ![releaseMode] even if this is true. | |
| 84 final bool injectBuildLogsInOutput; | |
| 85 | |
| 86 /// Rules to determine whether to run liner on an html file. | |
| 87 // TODO(jmesserly): instead of this flag, we should only run linter on | |
| 88 // reachable (entry point+imported) html if deploying. See dartbug.com/17199. | |
| 89 final LintOptions lint; | |
| 90 | |
| 91 /// This will automatically inject the polyfills from the `web_components` | |
| 92 /// package in all entry points, if it is not already included. | |
| 93 final bool injectWebComponentsJs; | |
| 94 | |
| 95 TransformOptions({entryPoints, this.inlineStylesheets, | |
| 96 this.contentSecurityPolicy: false, this.directlyIncludeJS: true, | |
| 97 this.releaseMode: true, this.lint: const LintOptions(), | |
| 98 this.injectBuildLogsInOutput: false, this.injectWebComponentsJs: true}) | |
| 99 : entryPoints = entryPoints == null | |
| 100 ? null | |
| 101 : entryPoints.map(systemToAssetPath).toList(); | |
| 102 | |
| 103 /// Whether an asset with [id] is an entry point HTML file. | |
| 104 bool isHtmlEntryPoint(AssetId id) { | |
| 105 if (id.extension != '.html') return false; | |
| 106 | |
| 107 // Note: [id.path] is a relative path from the root of a package. | |
| 108 if (entryPoints == null) { | |
| 109 return id.path.startsWith('web/') || id.path.startsWith('test/'); | |
| 110 } | |
| 111 | |
| 112 return entryPoints.contains(id.path); | |
| 113 } | |
| 114 | |
| 115 // Whether a stylesheet with [id] should be inlined, the default is true. | |
| 116 bool shouldInlineStylesheet(AssetId id) { | |
| 117 // Note: [id.path] is a relative path from the root of a package. | |
| 118 // Default is to inline everything | |
| 119 if (inlineStylesheets == null) return true; | |
| 120 // First check for the full asset path overrides. | |
| 121 var override = inlineStylesheets[id.toString()]; | |
| 122 if (override != null) return override; | |
| 123 // Then check just the path overrides (if the package was not specified). | |
| 124 override = inlineStylesheets[id.path]; | |
| 125 if (override != null) return override; | |
| 126 // Then check the global default setting. | |
| 127 var globalDefault = inlineStylesheets['default']; | |
| 128 return (globalDefault != null) ? globalDefault : true; | |
| 129 } | |
| 130 | |
| 131 // Whether a stylesheet with [id] has an overriden inlining setting. | |
| 132 bool stylesheetInliningIsOverridden(AssetId id) { | |
| 133 return inlineStylesheets != null && | |
| 134 (inlineStylesheets.containsKey(id.toString()) || | |
| 135 inlineStylesheets.containsKey(id.path)); | |
| 136 } | |
| 137 } | |
| 138 | |
| 139 class LintOptions { | |
| 140 /// Whether lint is enabled. | |
| 141 final bool enabled; | |
| 142 | |
| 143 /// Patterns explicitly included/excluded from linting (if any). | |
| 144 final List<RegExp> patterns; | |
| 145 | |
| 146 /// When [patterns] is not null, whether they denote inclusion or exclusion. | |
| 147 final bool isInclude; | |
| 148 | |
| 149 const LintOptions() | |
| 150 : enabled = true, | |
| 151 patterns = null, | |
| 152 isInclude = true; | |
| 153 | |
| 154 const LintOptions.disabled() | |
| 155 : enabled = false, | |
| 156 patterns = null, | |
| 157 isInclude = true; | |
| 158 | |
| 159 LintOptions.include(List<String> patterns) | |
| 160 : enabled = true, | |
| 161 isInclude = true, | |
| 162 patterns = patterns.map((s) => new RegExp(s)).toList(); | |
| 163 | |
| 164 LintOptions.exclude(List<String> patterns) | |
| 165 : enabled = true, | |
| 166 isInclude = false, | |
| 167 patterns = patterns.map((s) => new RegExp(s)).toList(); | |
| 168 | |
| 169 bool shouldLint(String fileName) { | |
| 170 if (!enabled) return false; | |
| 171 if (patterns == null) return isInclude; | |
| 172 for (var pattern in patterns) { | |
| 173 if (pattern.hasMatch(fileName)) return isInclude; | |
| 174 } | |
| 175 return !isInclude; | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 /// Mixin for polymer transformers. | |
| 180 abstract class PolymerTransformer { | |
| 181 TransformOptions get options; | |
| 182 | |
| 183 Future<Document> readPrimaryAsHtml(Transform transform, BuildLogger logger) { | |
| 184 var asset = transform.primaryInput; | |
| 185 var id = asset.id; | |
| 186 return asset.readAsString().then((content) { | |
| 187 return _parseHtml(content, id.path, logger, | |
| 188 checkDocType: options.isHtmlEntryPoint(id)); | |
| 189 }); | |
| 190 } | |
| 191 | |
| 192 Future<Document> readAsHtml( | |
| 193 AssetId id, Transform transform, BuildLogger logger, | |
| 194 {bool showWarnings: true}) { | |
| 195 var primaryId = transform.primaryInput.id; | |
| 196 bool samePackage = id.package == primaryId.package; | |
| 197 var url = spanUrlFor(id, transform, logger); | |
| 198 return transform.readInputAsString(id).then((content) { | |
| 199 return _parseHtml(content, url, logger, | |
| 200 checkDocType: samePackage && options.isHtmlEntryPoint(id), | |
| 201 showWarnings: showWarnings); | |
| 202 }); | |
| 203 } | |
| 204 | |
| 205 Future<bool> assetExists(AssetId id, Transform transform) => | |
| 206 transform.getInput(id).then((_) => true).catchError((_) => false); | |
| 207 | |
| 208 String toString() => 'polymer ($runtimeType)'; | |
| 209 } | |
| 210 | |
| 211 /// Gets the appropriate URL to use in a span to produce messages (e.g. | |
| 212 /// warnings) for users. This will attempt to format the URL in the most useful | |
| 213 /// way: | |
| 214 /// | |
| 215 /// - If the asset is within the primary package, then use the [id.path], | |
| 216 /// the user will know it is a file from their own code. | |
| 217 /// - If the asset is from another package, then use [assetUrlFor], this will | |
| 218 /// likely be a "package:" url to the file in the other package, which is | |
| 219 /// enough for users to identify where the error is. | |
| 220 String spanUrlFor(AssetId id, Transform transform, logger) { | |
| 221 var primaryId = transform.primaryInput.id; | |
| 222 bool samePackage = id.package == primaryId.package; | |
| 223 return samePackage | |
| 224 ? id.path | |
| 225 : assetUrlFor(id, primaryId, logger, allowAssetUrl: true); | |
| 226 } | |
| 227 | |
| 228 /// Transformer phases which should be applied to the Polymer package. | |
| 229 List<List<Transformer>> get phasesForPolymer => | |
| 230 [[new ObservableTransformer(files: ['lib/src/instance.dart'])]]; | |
| 231 | |
| 232 /// Generate the import url for a file described by [id], referenced by a file | |
| 233 /// with [sourceId]. | |
| 234 // TODO(sigmund): this should also be in barback (dartbug.com/12610) | |
| 235 String assetUrlFor(AssetId id, AssetId sourceId, BuildLogger logger, | |
| 236 {bool allowAssetUrl: false}) { | |
| 237 // use package: and asset: urls if possible | |
| 238 if (id.path.startsWith('lib/')) { | |
| 239 return 'package:${id.package}/${id.path.substring(4)}'; | |
| 240 } | |
| 241 | |
| 242 if (id.path.startsWith('asset/')) { | |
| 243 if (!allowAssetUrl) { | |
| 244 logger.error(INTERNAL_ERROR_DONT_KNOW_HOW_TO_IMPORT.create({ | |
| 245 'target': id, | |
| 246 'source': sourceId, | |
| 247 'extra': ' (asset urls not allowed.)' | |
| 248 })); | |
| 249 return null; | |
| 250 } | |
| 251 return 'asset:${id.package}/${id.path.substring(6)}'; | |
| 252 } | |
| 253 | |
| 254 // Use relative urls only if it's possible. | |
| 255 if (id.package != sourceId.package) { | |
| 256 logger.error("don't know how to refer to $id from $sourceId"); | |
| 257 return null; | |
| 258 } | |
| 259 | |
| 260 var builder = path.url; | |
| 261 return builder.relative(builder.join('/', id.path), | |
| 262 from: builder.join('/', builder.dirname(sourceId.path))); | |
| 263 } | |
| 264 | |
| 265 /// Convert system paths to asset paths (asset paths are posix style). | |
| 266 String systemToAssetPath(String assetPath) { | |
| 267 if (path.Style.platform != path.Style.windows) return assetPath; | |
| 268 return path.posix.joinAll(path.split(assetPath)); | |
| 269 } | |
| 270 | |
| 271 /// Returns true if this is a valid custom element name. See: | |
| 272 /// <http://w3c.github.io/webcomponents/spec/custom/#dfn-custom-element-type> | |
| 273 bool isCustomTagName(String name) { | |
| 274 if (name == null || !name.contains('-')) return false; | |
| 275 return !invalidTagNames.containsKey(name); | |
| 276 } | |
| 277 | |
| 278 /// Regex to split names in the 'attributes' attribute, which supports 'a b c', | |
| 279 /// 'a,b,c', or even 'a b,c'. This is the same as in `lib/src/declaration.dart`. | |
| 280 final ATTRIBUTES_REGEX = new RegExp(r'\s|,'); | |
| OLD | NEW |