Restores source map support.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 11 Dec 2016 10:50:11 +0000 (11:50 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Fri, 16 Dec 2016 11:00:33 +0000 (12:00 +0100)
Re-built from ground up on top of the new tokenizing process
it features simpler rebasing while still supporting all features
of the previous solution.

37 files changed:
lib/clean.js
lib/properties/shorthand-compactor.js
lib/source-maps/track.js [deleted file]
lib/stringifier/helpers.js
lib/stringifier/source-maps.js
lib/tokenizer/tokenize.js
lib/urls/rewrite.js
lib/utils/apply-source-maps.js [new file with mode: 0644]
lib/utils/extract-import-url-and-media.js [new file with mode: 0644]
lib/utils/has-protocol.js [new file with mode: 0644]
lib/utils/input-source-map-tracker-2.js [deleted file]
lib/utils/input-source-map-tracker.js
lib/utils/is-absolute-resource.js [new file with mode: 0644]
lib/utils/is-allowed-resource.js [new file with mode: 0644]
lib/utils/is-data-uri-resource.js [new file with mode: 0644]
lib/utils/is-http-resource.js [new file with mode: 0644]
lib/utils/is-https-resource.js [new file with mode: 0644]
lib/utils/is-import.js [new file with mode: 0644]
lib/utils/is-remote-resource.js [new file with mode: 0644]
lib/utils/load-original-sources.js [new file with mode: 0644]
lib/utils/load-remote-resource.js [new file with mode: 0644]
lib/utils/match-data-uri.js [new file with mode: 0644]
lib/utils/read-sources.js
lib/utils/rebase-local-map.js [new file with mode: 0644]
lib/utils/rebase-remote-map.js [new file with mode: 0644]
lib/utils/rebase.js [new file with mode: 0644]
lib/utils/restore-import.js [new file with mode: 0644]
test/module-test.js
test/optimizer/extract-properties-test.js
test/optimizer/reorderable-test.js
test/properties/longhand-overriding-test.js
test/properties/optimizer-test.js
test/properties/override-compacting-test.js
test/properties/shorthand-compacting-test.js
test/protocol-imports-test.js
test/source-map-test.js
test/tokenizer/tokenize-test.js

index de987a1..091fa0d 100644 (file)
@@ -13,6 +13,7 @@ var Validator = require('./properties/validator');
 var override = require('./utils/override');
 var DEFAULT_TIMEOUT = 5000;
 
+var inputSourceMapTracker = require('./utils/input-source-map-tracker');
 var readSources = require('./utils/read-sources');
 var basicOptimize = require('./optimizer/basic');
 var advancedOptimize = require('./optimizer/advanced');
@@ -74,12 +75,19 @@ CleanCSS.prototype.minify = function (input, callback) {
       timeSpent: 0
     },
     errors: [],
-    warnings: [],
-    options: this.options,
+    inputSourceMapTracker: inputSourceMapTracker(),
     localOnly: !callback,
-    validator: new Validator(this.options.compatibility)
+    options: this.options,
+    source: null,
+    sourcesContent: {},
+    validator: new Validator(this.options.compatibility),
+    warnings: []
   };
 
+  if (typeof this.options.sourceMap == 'string') {
+    context.inputSourceMapTracker.track(undefined, this.options.sourceMap);
+  }
+
   return runner(context.localOnly)(function () {
     return readSources(input, context, function (tokens) {
       var stringify = context.options.sourceMap ?
index 5ee882a..5c2b2cf 100644 (file)
@@ -20,23 +20,28 @@ function mixedImportance(components) {
   return false;
 }
 
-function componentSourceMaps(components) {
-  var sourceMapping = [];
-
-  for (var name in components) {
-    var component = components[name];
-    var originalValue = component.all[component.position];
-    var mapping = originalValue[0][originalValue[0].length - 1];
-
-    if (Array.isArray(mapping))
-      Array.prototype.push.apply(sourceMapping, mapping);
+function joinMetadata(components, at) {
+  var metadata = [];
+  var component;
+  var originalValue;
+  var componentMetadata;
+  var name;
+
+  for (name in components) {
+    component = components[name];
+    originalValue = component.all[component.position];
+    componentMetadata = originalValue[at][originalValue[at].length - 1];
+
+    Array.prototype.push.apply(metadata, componentMetadata);
   }
 
-  return sourceMapping;
+  return metadata;
 }
 
 function replaceWithShorthand(properties, candidateComponents, name, validator) {
   var descriptor = compactable[name];
+  var nameMetadata;
+  var valueMetadata;
   var newValuePlaceholder = [
     Token.PROPERTY,
     [Token.PROPERTY_NAME, name],
@@ -70,10 +75,11 @@ function replaceWithShorthand(properties, candidateComponents, name, validator)
     candidateComponents[componentName].unused = true;
   }
 
-  // var sourceMapping = componentSourceMaps(candidateComponents);
-  // if (sourceMapping.length > 0)
-  //   newValuePlaceholder[0].push(sourceMapping);
-  // }
+  nameMetadata = joinMetadata(candidateComponents, 1);
+  newValuePlaceholder[1].push(nameMetadata);
+
+  valueMetadata = joinMetadata(candidateComponents, 2);
+  newValuePlaceholder[2].push(valueMetadata);
 
   newProperty.position = all.length;
   newProperty.all = all;
diff --git a/lib/source-maps/track.js b/lib/source-maps/track.js
deleted file mode 100644 (file)
index 735a58d..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-var escapePrefix = '__ESCAPED_';
-
-function trackPrefix(value, context, interestingContent) {
-  if (!interestingContent && value.indexOf('\n') == -1) {
-    if (value.indexOf(escapePrefix) === 0) {
-      return value;
-    } else {
-      context.column += value.length;
-      return;
-    }
-  }
-
-  var withoutContent = 0;
-  var split = value.split('\n');
-  var total = split.length;
-  var shift = 0;
-
-  while (true) {
-    if (withoutContent == total - 1)
-      break;
-
-    var part = split[withoutContent];
-    if (/\S/.test(part))
-      break;
-
-    shift += part.length + 1;
-    withoutContent++;
-  }
-
-  context.line += withoutContent;
-  context.column = withoutContent > 0 ? 0 : context.column;
-  context.column += /^(\s)*/.exec(split[withoutContent])[0].length;
-
-  return value.substring(shift).trimLeft();
-}
-
-function sourceFor(originalMetadata, contextMetadata, context) {
-  var source = originalMetadata.source || contextMetadata.source;
-
-  if (source && context.resolvePath)
-    return context.resolvePath(contextMetadata.source, source);
-
-  return source;
-}
-
-function snapshot(data, context, fallbacks) {
-  var metadata = {
-    line: context.line,
-    column: context.column,
-    source: context.source
-  };
-  var sourceContent = null;
-  var sourceMetadata = context.sourceMapTracker.isTracking(metadata.source) ?
-    context.sourceMapTracker.originalPositionFor(metadata, data, fallbacks || 0) :
-    {};
-
-  metadata.line = sourceMetadata.line || metadata.line;
-  metadata.column = sourceMetadata.column || metadata.column;
-  metadata.source = sourceMetadata.sourceResolved ?
-    sourceMetadata.source :
-    sourceFor(sourceMetadata, metadata, context);
-
-  if (context.sourceMapInlineSources) {
-    var sourceMapSourcesContent = context.sourceMapTracker.sourcesContentFor(context.source);
-    sourceContent = sourceMapSourcesContent && sourceMapSourcesContent[metadata.source] ?
-      sourceMapSourcesContent :
-      context.sourceReader.sourceAt(context.source);
-  }
-
-  return sourceContent ?
-    [metadata.line, metadata.column, metadata.source, sourceContent] :
-    [metadata.line, metadata.column, metadata.source];
-}
-
-function trackSuffix(data, context) {
-  var parts = data.split('\n');
-
-  for (var i = 0, l = parts.length; i < l; i++) {
-    var part = parts[i];
-    var cursor = 0;
-
-    if (i > 0) {
-      context.line++;
-      context.column = 0;
-    }
-
-    while (true) {
-      var next = part.indexOf(escapePrefix, cursor);
-
-      if (next == -1) {
-        context.column += part.substring(cursor).length;
-        break;
-      }
-
-      context.column += next - cursor;
-      cursor += next - cursor;
-
-      var escaped = part.substring(next, part.indexOf('__', next + 1) + 2);
-      var encodedValues = escaped.substring(escaped.indexOf('(') + 1, escaped.indexOf(')')).split(',');
-      context.line += ~~encodedValues[0];
-      context.column = (~~encodedValues[0] === 0 ? context.column : 0) + ~~encodedValues[1];
-      cursor += escaped.length;
-    }
-  }
-}
-
-function track(data, context, snapshotMetadata, fallbacks) {
-  var untracked = trackPrefix(data, context, snapshotMetadata);
-  var metadata = snapshotMetadata ?
-    snapshot(untracked, context, fallbacks) :
-    [];
-
-  if (untracked)
-    trackSuffix(untracked, context);
-
-  return metadata;
-}
-
-module.exports = track;
index 0cc788d..a020cbe 100644 (file)
@@ -127,7 +127,6 @@ function all(tokens, context) {
         break;
       case Token.AT_RULE_BLOCK:
         rules(token[1], context);
-        store(joinCharacter, context);
         store(Marker.OPEN_BRACE, context);
         body(token[2], context);
         store(Marker.CLOSE_BRACE, context);
@@ -136,6 +135,7 @@ function all(tokens, context) {
         rules(token[1], context);
         store(Marker.OPEN_BRACE, context);
         all(token[2], context);
+        store(joinCharacter, context);
         store(Marker.CLOSE_BRACE, context);
         break;
       case Token.COMMENT:
index 4b62a5b..14de180 100644 (file)
@@ -1,85 +1,74 @@
 var SourceMapGenerator = require('source-map').SourceMapGenerator;
 var all = require('./helpers').all;
 
+var isRemoteResource = require('../utils/is-remote-resource');
+
 var isWindows = process.platform == 'win32';
-var unknownSource = '$stdin';
+var UNKNOWN_SOURCE = '$stdin';
 
-function store(element, context) {
+function store(element, stringifyContext) {
   var fromString = typeof element == 'string';
   var value = fromString ? element : element[1];
+  var mappings = fromString ? null : element[2];
 
-  if (value.indexOf('_') > -1)
-    value = context.restore(value, prefixContentFrom(context.output));
-
-  track(value, fromString ? null : element, context);
-  context.output.push(value);
+  track(value, mappings, stringifyContext);
+  stringifyContext.output.push(value);
 }
 
-function prefixContentFrom(values) {
-  var content = [];
-
-  for (var i = values.length - 1; i >= 0; i--) {
-    var value = values[i];
-    content.unshift(value);
+function track(value, mappings, stringifyContext) {
+  var parts = value.split('\n');
 
-    if (value == '{' || value == ';')
-      break;
+  if (mappings) {
+    trackAllMappings(mappings, stringifyContext);
   }
 
-  return content.join('');
-}
-
-function track(value, element, context) {
-  if (element)
-    trackAllMappings(element, context);
-
-  var parts = value.split('\n');
-  context.line += parts.length - 1;
-  context.column = parts.length > 1 ? 0 : (context.column + parts.pop().length);
+  stringifyContext.line += parts.length - 1;
+  stringifyContext.column = parts.length > 1 ? 0 : (stringifyContext.column + parts.pop().length);
 }
 
-function trackAllMappings(element, context) {
-  var mapping = element[element.length - 1];
-
-  if (!Array.isArray(mapping))
-    return;
-
-  for (var i = 0, l = mapping.length; i < l; i++) {
-    trackMapping(mapping[i], context);
+function trackAllMappings(mappings, stringifyContext) {
+  for (var i = 0, l = mappings.length; i < l; i++) {
+    trackMapping(mappings[i], stringifyContext);
   }
 }
 
-function trackMapping(mapping, context) {
-  var source = mapping[2] || unknownSource;
+function trackMapping(mapping, stringifyContext) {
+  var line = mapping[0];
+  var column = mapping[1];
+  var originalSource = mapping[2];
+  var source = originalSource;
+  var storedSource = source || UNKNOWN_SOURCE;
 
-  if (isWindows)
+  if (isWindows && source && !isRemoteResource(source)) {
     source = source.replace(/\\/g, '/');
+  }
 
-  context.outputMap.addMapping({
+  stringifyContext.outputMap.addMapping({
     generated: {
-      line: context.line,
-      column: context.column
+      line: stringifyContext.line,
+      column: stringifyContext.column
     },
-    source: source,
+    source: storedSource,
     original: {
-      line: mapping[0],
-      column: mapping[1]
+      line: line,
+      column: column
     }
   });
 
-  if (mapping[3])
-    context.outputMap.setSourceContent(source, mapping[3][mapping[2]]);
+  if (stringifyContext.inlineSources && (originalSource in stringifyContext.sourcesContent)) {
+    stringifyContext.outputMap.setSourceContent(storedSource, stringifyContext.sourcesContent[originalSource]);
+  }
 }
 
 function stringify(tokens, context) {
   var stringifyContext = {
     column: 0,
-    inputMapTracker: context.inputMapTracker,
     keepBreaks: context.options.keepBreaks,
+    inlineSources: context.options.sourceMapInlineSources,
     line: 1,
     output: [],
     outputMap: new SourceMapGenerator(),
-    sourceMapInlineSources: context.options.sourceMapInlineSources,
+    sourcesContent: context.sourcesContent,
     spaceAfterClosingBrace: context.options.compatibility.properties.spaceAfterClosingBrace,
     store: store
   };
index ac8a4cb..9b4cf11 100644 (file)
@@ -55,6 +55,7 @@ function intoTokens(source, externalContext, internalContext, isNested) {
   var levels = [];
   var buffer = [];
   var buffers = [];
+  var serializedBuffer;
   var roundBracketLevel = 0;
   var isQuoted;
   var isSpace;
@@ -79,7 +80,7 @@ function intoTokens(source, externalContext, internalContext, isNested) {
     isCommentEnd = !wasCommentStart && level == Level.COMMENT && character == Marker.FORWARD_SLASH && source[position.index - 1] == Marker.STAR;
 
     metadata = buffer.length === 0 ?
-      metadataFrom(position, 0, externalContext) :
+      [position.line, position.column, position.source] :
       metadata;
 
     if (isEscaped) {
@@ -94,7 +95,7 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       buffers.push(buffer.slice(0, buffer.length - 2));
 
       buffer = buffer.slice(buffer.length - 2);
-      metadata = metadataFrom(position, 1, externalContext);
+      metadata = [position.line, position.column - 1, position.source];
 
       levels.push(level);
       level = Level.COMMENT;
@@ -105,7 +106,8 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       buffer.push(character);
     } else if (isCommentEnd) {
       // comment end, e.g. /* comment */<--
-      lastToken = [Token.COMMENT, buffer.join('').trim() + character, [metadata]];
+      serializedBuffer = buffer.join('').trim() + character;
+      lastToken = [Token.COMMENT, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]];
       newTokens.push(lastToken);
 
       level = levels.pop();
@@ -144,12 +146,14 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       roundBracketLevel--;
     } else if (character == Marker.SEMICOLON && level == Level.BLOCK) {
       // semicolon ending rule at block level, e.g. @import '...';<--
-      allTokens.push([Token.AT_RULE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      allTokens.push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
 
       buffer = [];
     } else if (character == Marker.COMMA && level == Level.BLOCK && ruleToken) {
       // comma separator at block level, e.g. a,div,<--
-      ruleToken[1].push([tokenScopeFrom(ruleToken[0]), buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      ruleToken[1].push([tokenScopeFrom(ruleToken[0]), serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext, ruleToken[1].length)]]);
 
       buffer = [];
     } else if (character == Marker.COMMA && level == Level.BLOCK && tokenTypeFrom(buffer) == Token.AT_RULE) {
@@ -159,12 +163,14 @@ function intoTokens(source, externalContext, internalContext, isNested) {
     } else if (character == Marker.COMMA && level == Level.BLOCK) {
       // comma separator at block level, e.g. a,<--
       ruleToken = [tokenTypeFrom(buffer), [], []];
-      ruleToken[1].push([tokenScopeFrom(ruleToken[0]), buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      ruleToken[1].push([tokenScopeFrom(ruleToken[0]), serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext, 0)]]);
 
       buffer = [];
     } else if (character == Marker.OPEN_BRACE && level == Level.BLOCK && ruleToken && ruleToken[0] == Token.BLOCK) {
       // open brace opening at-rule at block level, e.g. @media{<--
-      ruleToken[1].push([Token.BLOCK_SCOPE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      ruleToken[1].push([Token.BLOCK_SCOPE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
       allTokens.push(ruleToken);
 
       levels.push(level);
@@ -176,8 +182,9 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       ruleToken = null;
     } else if (character == Marker.OPEN_BRACE && level == Level.BLOCK && tokenTypeFrom(buffer) == Token.BLOCK) {
       // open brace opening at-rule at block level, e.g. @media{<--
+      serializedBuffer = buffer.join('').trim();
       ruleToken = ruleToken || [Token.BLOCK, [], []];
-      ruleToken[1].push([Token.BLOCK_SCOPE, buffer.join('').trim(), [metadata]]);
+      ruleToken[1].push([Token.BLOCK_SCOPE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
       allTokens.push(ruleToken);
 
       levels.push(level);
@@ -189,8 +196,9 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       ruleToken = null;
     } else if (character == Marker.OPEN_BRACE && level == Level.BLOCK) {
       // open brace opening rule at block level, e.g. div{<--
+      serializedBuffer = buffer.join('').trim();
       ruleToken = ruleToken || [tokenTypeFrom(buffer), [], []];
-      ruleToken[1].push([tokenScopeFrom(ruleToken[0]), buffer.join('').trim(), [metadata]]);
+      ruleToken[1].push([tokenScopeFrom(ruleToken[0]), serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext, ruleToken[1].length)]]);
       newTokens = ruleToken[2];
       allTokens.push(ruleToken);
 
@@ -209,19 +217,22 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       seekingValue = false;
     } else if (character == Marker.COLON && level == Level.RULE && !seekingValue) {
       // colon at rule level, e.g. a{color:<--
-      propertyToken = [Token.PROPERTY, [Token.PROPERTY_NAME, buffer.join('').trim(), [metadata]]];
+      serializedBuffer = buffer.join('').trim();
+      propertyToken = [Token.PROPERTY, [Token.PROPERTY_NAME, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]];
       newTokens.push(propertyToken);
 
       seekingValue = true;
       buffer = [];
     } else if (character == Marker.SEMICOLON && level == Level.RULE && propertyToken && ruleTokens.length > 0 && buffer.length > 0 && buffer[0] == Marker.AT) {
       // semicolon at rule level for at-rule, e.g. a{--color:{@apply(--other-color);<--
-      ruleToken[1].push([Token.AT_RULE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      ruleToken[1].push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
 
       buffer = [];
     } else if (character == Marker.SEMICOLON && level == Level.RULE && propertyToken && buffer.length > 0) {
       // semicolon at rule level, e.g. a{color:red;<--
-      propertyToken.push([Token.PROPERTY_VALUE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
 
       seekingValue = false;
       buffer = [];
@@ -230,7 +241,8 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       seekingValue = false;
     } else if (character == Marker.SEMICOLON && level == Level.RULE && buffer.length > 0 && buffer[0] == Marker.AT) {
       // semicolon for at-rule at rule level, e.g. a{@apply(--variable);<--
-      newTokens.push([Token.AT_RULE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('');
+      newTokens.push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
 
       seekingValue = false;
       buffer = [];
@@ -239,7 +251,8 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       // noop
     } else if (character == Marker.CLOSE_BRACE && level == Level.RULE && propertyToken && seekingValue && buffer.length > 0 && ruleTokens.length > 0) {
       // close brace at rule level, e.g. a{--color:{color:red}<--
-      propertyToken.push([Token.PROPERTY_VALUE, buffer.join(''), [metadata]]);
+      serializedBuffer = buffer.join('');
+      propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
       propertyToken = null;
       ruleToken = ruleTokens.pop();
       newTokens = ruleToken[2];
@@ -249,7 +262,8 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       buffer = [];
     } else if (character == Marker.CLOSE_BRACE && level == Level.RULE && propertyToken && buffer.length > 0 && buffer[0] == Marker.AT && ruleTokens.length > 0) {
       // close brace at rule level for at-rule, e.g. a{--color:{@apply(--other-color)}<--
-      ruleToken[1].push([Token.AT_RULE, buffer.join(''), [metadata]]);
+      serializedBuffer = buffer.join('');
+      ruleToken[1].push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
       propertyToken = null;
       ruleToken = ruleTokens.pop();
       newTokens = ruleToken[2];
@@ -267,7 +281,8 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       seekingValue = false;
     } else if (character == Marker.CLOSE_BRACE && level == Level.RULE && propertyToken && buffer.length > 0) {
       // close brace at rule level, e.g. a{color:red}<--
-      propertyToken.push([Token.PROPERTY_VALUE, buffer.join(''), [metadata]]);
+      serializedBuffer = buffer.join('');
+      propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
       propertyToken = null;
       ruleToken = ruleTokens.pop();
       newTokens = allTokens;
@@ -278,7 +293,8 @@ function intoTokens(source, externalContext, internalContext, isNested) {
     } else if (character == Marker.CLOSE_BRACE && level == Level.RULE && buffer.length > 0 && buffer[0] == Marker.AT) {
       // close brace after at-rule at rule level, e.g. a{@apply(--variable)}<--
       ruleToken = null;
-      newTokens.push([Token.AT_RULE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      newTokens.push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
       newTokens = allTokens;
 
       level = levels.pop();
@@ -305,7 +321,8 @@ function intoTokens(source, externalContext, internalContext, isNested) {
     } else if (character == Marker.CLOSE_ROUND_BRACKET && level == Level.RULE && seekingValue && roundBracketLevel == 1) {
       // round close bracket, e.g. a{color:hsla(0,0%,0%)<--
       buffer.push(character);
-      propertyToken.push([Token.PROPERTY_VALUE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
 
       roundBracketLevel--;
       buffer = [];
@@ -315,34 +332,38 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       roundBracketLevel--;
     } else if (character == Marker.FORWARD_SLASH && source[position.index + 1] != Marker.STAR && level == Level.RULE && seekingValue && buffer.length > 0) {
       // forward slash within a property, e.g. a{background:url(image.png) 0 0/<--
-      propertyToken.push([Token.PROPERTY_VALUE, buffer.join('').trim(), [metadata]]);
-      propertyToken.push([Token.PROPERTY_VALUE, character, [metadataFrom(position, 0, externalContext)]]);
+      serializedBuffer = buffer.join('').trim();
+      propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
+      propertyToken.push([Token.PROPERTY_VALUE, character, [[position.line, position.column, position.source]]]);
 
       buffer = [];
     } else if (character == Marker.FORWARD_SLASH && source[position.index + 1] != Marker.STAR && level == Level.RULE && seekingValue) {
       // forward slash within a property after space, e.g. a{background:url(image.png) 0 0 /<--
-      propertyToken.push([Token.PROPERTY_VALUE, character, [metadataFrom(position, 0, externalContext)]]);
+      propertyToken.push([Token.PROPERTY_VALUE, character, [[position.line, position.column, position.source]]]);
 
       buffer = [];
     } else if (character == Marker.COMMA && level == Level.RULE && seekingValue && buffer.length > 0) {
       // comma within a property, e.g. a{background:url(image.png),<--
-      propertyToken.push([Token.PROPERTY_VALUE, buffer.join('').trim(), [metadata]]);
-      propertyToken.push([Token.PROPERTY_VALUE, character, [metadataFrom(position, 0, externalContext)]]);
+      serializedBuffer = buffer.join('').trim();
+      propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
+      propertyToken.push([Token.PROPERTY_VALUE, character, [[position.line, position.column, position.source]]]);
 
       buffer = [];
     } else if (character == Marker.COMMA && level == Level.RULE && seekingValue) {
       // comma within a property after space, e.g. a{background:url(image.png) ,<--
-      propertyToken.push([Token.PROPERTY_VALUE, character, [metadataFrom(position, 0, externalContext)]]);
+      propertyToken.push([Token.PROPERTY_VALUE, character, [[position.line, position.column, position.source]]]);
 
       buffer = [];
     } else if ((isSpace || (isNewLineNix && !isNewLineWin)) && level == Level.RULE && seekingValue && propertyToken && buffer.length > 0) {
       // space or *nix newline within property, e.g. a{margin:0 <--
-      propertyToken.push([Token.PROPERTY_VALUE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
 
       buffer = [];
     } else if (isNewLineWin && level == Level.RULE && seekingValue && propertyToken && buffer.length > 1) {
       // win newline within property, e.g. a{margin:0\r\n<--
-      propertyToken.push([Token.PROPERTY_VALUE, buffer.join('').trim(), [metadata]]);
+      serializedBuffer = buffer.join('').trim();
+      propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
 
       buffer = [];
     } else if (isNewLineWin && level == Level.RULE && seekingValue) {
@@ -369,19 +390,19 @@ function intoTokens(source, externalContext, internalContext, isNested) {
   }
 
   if (seekingValue && buffer.length > 0) {
-    propertyToken.push([Token.PROPERTY_VALUE, buffer.join('').replace(TAIL_BROKEN_VALUE_PATTERN, ''), [metadata]]);
+    serializedBuffer = buffer.join('').replace(TAIL_BROKEN_VALUE_PATTERN, '');
+    propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
   }
 
   return allTokens;
 }
 
-function metadataFrom(position, columnDelta, externalContext) {
-  var metadata = [position.line, position.column - columnDelta, position.source];
-  metadata = externalContext.inputSourceMap ?
-    externalContext.inputSourceMapTracker.originalPositionFor(metadata) :
-    metadata;
+function originalMetadata(metadata, value, externalContext, selectorFallbacks) {
+  var source = metadata[2];
 
-  return metadata;
+  return externalContext.inputSourceMapTracker.isTracking(source) ?
+    externalContext.inputSourceMapTracker.originalPositionFor(metadata, value.length, selectorFallbacks) :
+    metadata;
 }
 
 function tokenTypeFrom(buffer) {
index daced3d..4fefbaf 100644 (file)
@@ -95,7 +95,7 @@ function hasRoundBrackets(url) {
   return ROUND_BRACKETS_PATTERN.test(url);
 }
 
-function rewriteUrl(originalUrl, rebaseConfig) {
+function rewriteUrl(originalUrl, rebaseConfig, pathOnly) {
   var strippedUrl = originalUrl
     .replace(URL_PREFIX_PATTERN, '')
     .replace(URL_SUFFIX_PATTERN, '')
@@ -109,7 +109,9 @@ function rewriteUrl(originalUrl, rebaseConfig) {
     strippedUrl[0] :
     quoteFor(unquotedUrl);
 
-  return URL_PREFIX + quote + rebase(unquotedUrl, rebaseConfig) + quote + URL_SUFFIX;
+  return pathOnly ?
+    rebase(unquotedUrl, rebaseConfig) :
+    URL_PREFIX + quote + rebase(unquotedUrl, rebaseConfig) + quote + URL_SUFFIX;
 }
 
 module.exports = rewriteUrl;
diff --git a/lib/utils/apply-source-maps.js b/lib/utils/apply-source-maps.js
new file mode 100644 (file)
index 0000000..e565d89
--- /dev/null
@@ -0,0 +1,239 @@
+var fs = require('fs');
+var path = require('path');
+
+var isAllowedResource = require('./is-allowed-resource');
+var isDataUriResource = require('./is-data-uri-resource');
+var isRemoteResource = require('./is-remote-resource');
+var loadRemoteResource = require('./load-remote-resource');
+var matchDataUri = require('./match-data-uri');
+var rebaseLocalMap = require('./rebase-local-map');
+var rebaseRemoteMap = require('./rebase-remote-map');
+
+var Token = require('../tokenizer/token');
+
+var MAP_MARKER_PATTERN = /^\/\*# sourceMappingURL=(\S+) \*\/$/;
+
+function applySourceMaps(tokens, context, callback) {
+  var applyContext = {
+    callback: callback,
+    index: 0,
+    inliner: context.options.inliner,
+    inputSourceMapTracker: context.inputSourceMapTracker,
+    localOnly: context.localOnly,
+    processedTokens: [],
+    processImportFrom: context.options.processImportFrom,
+    rebaseTo: context.options.rebaseTo,
+    sourceTokens: tokens,
+    warnings: context.warnings
+  };
+
+  return tokens.length > 0 ?
+    doApplySourceMaps(applyContext) :
+    callback(tokens);
+}
+
+function doApplySourceMaps(applyContext) {
+  var singleSourceTokens = [];
+  var lastSource = findTokenSource(applyContext.sourceTokens[0]);
+  var source;
+  var token;
+  var l;
+
+  for (l = applyContext.sourceTokens.length; applyContext.index < l; applyContext.index++) {
+    token = applyContext.sourceTokens[applyContext.index];
+    source = findTokenSource(token);
+
+    if (source != lastSource) {
+      singleSourceTokens = [];
+      lastSource = source;
+    }
+
+    singleSourceTokens.push(token);
+    applyContext.processedTokens.push(token);
+
+    if (token[0] == Token.COMMENT && MAP_MARKER_PATTERN.test(token[1])) {
+      return fetchAndApplySourceMap(token[1], source, singleSourceTokens, applyContext);
+    }
+  }
+
+  return applyContext.callback(applyContext.processedTokens);
+}
+
+function findTokenSource(token) {
+  var scope;
+  var metadata;
+
+  if (token[0] == Token.AT_RULE || token[0] == Token.COMMENT) {
+    metadata = token[2][0];
+  } else {
+    scope = token[1][0];
+    metadata = scope[2][0];
+  }
+
+  return metadata[2];
+}
+
+function fetchAndApplySourceMap(sourceMapComment, source, singleSourceTokens, applyContext) {
+  return extractInputSourceMapFrom(sourceMapComment, applyContext, function (inputSourceMap) {
+    if (inputSourceMap) {
+      applyContext.inputSourceMapTracker.track(source, inputSourceMap);
+      applySourceMapRecursively(singleSourceTokens, applyContext.inputSourceMapTracker);
+    }
+
+    applyContext.index++;
+    return doApplySourceMaps(applyContext);
+  });
+}
+
+function extractInputSourceMapFrom(sourceMapComment, applyContext, whenSourceMapReady) {
+  var uri = MAP_MARKER_PATTERN.exec(sourceMapComment)[1];
+  var rebasedToCurrentPath;
+  var sourceMap;
+  var rebasedMap;
+
+  if (isDataUriResource(uri)) {
+    sourceMap = extractInputSourceMapFromDataUri(uri);
+    return whenSourceMapReady(sourceMap);
+  } else if (isRemoteResource(uri)) {
+    return loadInputSourceMapFromRemoteUri(uri, applyContext, function (sourceMap) {
+      var parsedMap;
+
+      if (sourceMap) {
+        parsedMap = JSON.parse(sourceMap);
+        rebasedMap = rebaseRemoteMap(parsedMap, uri);
+        whenSourceMapReady(rebasedMap);
+      } else {
+        whenSourceMapReady(null);
+      }
+    });
+  } else {
+    // at this point `uri` is already rebased, see read-sources.js#rebaseSourceMapComment
+    // it is rebased to be consistent with rebasing other URIs
+    // however here we need to resolve it back to read it from disk
+    rebasedToCurrentPath = path.resolve(applyContext.rebaseTo, uri);
+    sourceMap = loadInputSourceMapFromLocalUri(rebasedToCurrentPath, applyContext);
+
+    if (sourceMap) {
+      rebasedMap = rebaseLocalMap(sourceMap, rebasedToCurrentPath, applyContext.rebaseTo);
+      return whenSourceMapReady(rebasedMap);
+    } else {
+      return whenSourceMapReady(null);
+    }
+  }
+}
+
+function extractInputSourceMapFromDataUri(uri) {
+  var dataUriMatch = matchDataUri(uri);
+  var charset = dataUriMatch[2] ? dataUriMatch[2].split(/[=;]/)[2] : 'us-ascii';
+  var encoding = dataUriMatch[3] ? dataUriMatch[3].split(';')[1] : 'utf8';
+  var data = encoding == 'utf8' ? global.unescape(dataUriMatch[4]) : dataUriMatch[4];
+
+  var buffer = new Buffer(data, encoding);
+  buffer.charset = charset;
+
+  return JSON.parse(buffer.toString());
+}
+
+function loadInputSourceMapFromRemoteUri(uri, applyContext, whenLoaded) {
+  var isAllowed = isAllowedResource(uri, true, applyContext.processImportFrom);
+
+  if (applyContext.localOnly) {
+    applyContext.warnings.push('Cannot fetch remote resource from "' + uri + '" as no callback given.');
+    return whenLoaded(null);
+  } else if (!isAllowed) {
+    applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
+    return whenLoaded(null);
+  }
+
+  loadRemoteResource(uri, applyContext.inliner, function (error, body) {
+    if (error) {
+      applyContext.warnings.push('Missing source map at "' + uri + '" - ' + error);
+      return whenLoaded(null);
+    }
+
+    whenLoaded(body);
+  });
+}
+
+function loadInputSourceMapFromLocalUri(uri, applyContext) {
+  var isAllowed = isAllowedResource(uri, true, applyContext.processImportFrom);
+  var sourceMap;
+
+  if (!fs.existsSync(uri) || !fs.statSync(uri).isFile()) {
+    applyContext.warnings.push('Ignoring local source map at "' + uri + '" as resource is missing.');
+    return null;
+  } else if (!isAllowed) {
+    applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
+    return null;
+  }
+
+  sourceMap = fs.readFileSync(uri, 'utf-8');
+  return JSON.parse(sourceMap);
+}
+
+function applySourceMapRecursively(tokens, inputSourceMapTracker) {
+  var token;
+  var i, l;
+
+  for (i = 0, l = tokens.length; i < l; i++) {
+    token = tokens[i];
+
+    switch (token[0]) {
+      case Token.AT_RULE:
+        applySourceMapTo(token, inputSourceMapTracker);
+        break;
+      case Token.AT_RULE_BLOCK:
+        applySourceMapRecursively(token[1], inputSourceMapTracker);
+        applySourceMapRecursively(token[2], inputSourceMapTracker);
+        break;
+      case Token.AT_RULE_BLOCK_SCOPE:
+        applySourceMapTo(token, inputSourceMapTracker);
+        break;
+      case Token.BLOCK:
+        applySourceMapRecursively(token[1], inputSourceMapTracker);
+        applySourceMapRecursively(token[2], inputSourceMapTracker);
+        break;
+      case Token.BLOCK_SCOPE:
+        applySourceMapTo(token, inputSourceMapTracker);
+        break;
+      case Token.COMMENT:
+        applySourceMapTo(token, inputSourceMapTracker);
+        break;
+      case Token.PROPERTY:
+        applySourceMapRecursively(token, inputSourceMapTracker);
+        break;
+      case Token.PROPERTY_BLOCK:
+        applySourceMapRecursively(token[1], inputSourceMapTracker);
+        break;
+      case Token.PROPERTY_NAME:
+        applySourceMapTo(token, inputSourceMapTracker);
+        break;
+      case Token.PROPERTY_VALUE:
+        applySourceMapTo(token, inputSourceMapTracker);
+        break;
+      case Token.RULE:
+        applySourceMapRecursively(token[1], inputSourceMapTracker);
+        applySourceMapRecursively(token[2], inputSourceMapTracker);
+        break;
+      case Token.RULE_SCOPE:
+        applySourceMapTo(token, inputSourceMapTracker);
+    }
+  }
+
+  return tokens;
+}
+
+function applySourceMapTo(token, inputSourceMapTracker) {
+  var value = token[1];
+  var metadata = token[2];
+  var newMetadata = [];
+  var i, l;
+
+  for (i = 0, l = metadata.length; i < l; i++) {
+    newMetadata.push(inputSourceMapTracker.originalPositionFor(metadata[i], value.length));
+  }
+
+  token[2] = newMetadata;
+}
+
+module.exports = applySourceMaps;
diff --git a/lib/utils/extract-import-url-and-media.js b/lib/utils/extract-import-url-and-media.js
new file mode 100644 (file)
index 0000000..e309c2f
--- /dev/null
@@ -0,0 +1,35 @@
+var split = require('../utils/split');
+
+var BRACE_PREFIX = /^\(/;
+var BRACE_SUFFIX = /\)$/;
+var IMPORT_PREFIX_PATTERN = /^@import/i;
+var QUOTE_PREFIX_PATTERN = /['"]\s*/;
+var QUOTE_SUFFIX_PATTERN = /\s*['"]/;
+var URL_PREFIX_PATTERN = /^url\(\s*/i;
+var URL_SUFFIX_PATTERN = /\s*\)/i;
+
+function extractImportUrlAndMedia(atRuleValue) {
+  var uri;
+  var mediaQuery;
+  var stripped;
+  var parts;
+
+  stripped = atRuleValue
+    .replace(IMPORT_PREFIX_PATTERN, '')
+    .trim()
+    .replace(URL_PREFIX_PATTERN, '(')
+    .replace(URL_SUFFIX_PATTERN, ')')
+    .replace(QUOTE_PREFIX_PATTERN, '')
+    .replace(QUOTE_SUFFIX_PATTERN, '');
+
+  parts = split(stripped, ' ');
+
+  uri = parts[0]
+    .replace(BRACE_PREFIX, '')
+    .replace(BRACE_SUFFIX, '');
+  mediaQuery = parts.slice(1).join(' ');
+
+  return [uri, mediaQuery];
+}
+
+module.exports = extractImportUrlAndMedia;
diff --git a/lib/utils/has-protocol.js b/lib/utils/has-protocol.js
new file mode 100644 (file)
index 0000000..fa1b61f
--- /dev/null
@@ -0,0 +1,7 @@
+var NO_PROTOCOL_RESOURCE_PATTERN = /^\/\//;
+
+function hasProtocol(uri) {
+  return !NO_PROTOCOL_RESOURCE_PATTERN.test(uri);
+}
+
+module.exports = hasProtocol;
diff --git a/lib/utils/input-source-map-tracker-2.js b/lib/utils/input-source-map-tracker-2.js
deleted file mode 100644 (file)
index ff37948..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-var SourceMapConsumer = require('source-map').SourceMapConsumer;
-
-function inputSourceMapTracker() {
-  var maps = {};
-
-  return {
-    originalPositionFor: originalPositionFor.bind(null, maps),
-    track: track.bind(null, maps)
-  };
-}
-
-function originalPositionFor(maps, metadata) {
-  var line = metadata[0];
-  var column = metadata[1];
-  var source = metadata[2];
-
-  return source in maps ?
-    toMetadata(maps[source].originalPositionFor({ line: line, column: column })) :
-    metadata;
-}
-
-function toMetadata(asHash) {
-  return [asHash.line, asHash.column, asHash.source];
-}
-
-function track(maps, source, data) {
-  maps[source] = new SourceMapConsumer(data);
-}
-
-module.exports = inputSourceMapTracker;
index bfd9106..ea2c034 100644 (file)
 var SourceMapConsumer = require('source-map').SourceMapConsumer;
 
-var fs = require('fs');
-var path = require('path');
-var http = require('http');
-var https = require('https');
-var url = require('url');
-
-var override = require('../utils/object.js').override;
-
-var MAP_MARKER = /\/\*# sourceMappingURL=(\S+) \*\//;
-var REMOTE_RESOURCE = /^(https?:)?\/\//;
-var DATA_URI = /^data:(\S*?)?(;charset=[^;]+)?(;[^,]+?)?,(.+)/;
-
-var unescape = global.unescape;
-
-function InputSourceMapStore(outerContext) {
-  this.options = outerContext.options;
-  this.errors = outerContext.errors;
-  this.warnings = outerContext.warnings;
-  this.sourceTracker = outerContext.sourceTracker;
-  this.timeout = this.options.inliner.timeout;
-  this.requestOptions = this.options.inliner.request;
-  this.localOnly = outerContext.localOnly;
-  this.relativeTo = outerContext.options.target || process.cwd();
-
-  this.maps = {};
-  this.sourcesContent = {};
-}
-
-function fromString(self, _, whenDone) {
-  self.trackLoaded(undefined, undefined, self.options.sourceMap);
-  return whenDone();
-}
-
-function fromSource(self, data, whenDone, context) {
-  var nextAt = 0;
-
-  function proceedToNext() {
-    context.cursor += nextAt + 1;
-    fromSource(self, data, whenDone, context);
-  }
-
-  while (context.cursor < data.length) {
-    var fragment = data.substring(context.cursor);
-
-    var markerStartMatch = self.sourceTracker.nextStart(fragment) || { index: -1 };
-    var markerEndMatch = self.sourceTracker.nextEnd(fragment) || { index: -1 };
-    var mapMatch = MAP_MARKER.exec(fragment) || { index: -1 };
-    var sourceMapFile = mapMatch[1];
-
-    nextAt = data.length;
-    if (markerStartMatch.index > -1)
-      nextAt = markerStartMatch.index;
-    if (markerEndMatch.index > -1 && markerEndMatch.index < nextAt)
-      nextAt = markerEndMatch.index;
-    if (mapMatch.index > -1 && mapMatch.index < nextAt)
-      nextAt = mapMatch.index;
-
-    if (nextAt == data.length)
-      break;
-
-    if (nextAt == markerStartMatch.index) {
-      context.files.push(markerStartMatch.filename);
-    } else if (nextAt == markerEndMatch.index) {
-      context.files.pop();
-    } else if (nextAt == mapMatch.index) {
-      var isRemote = /^https?:\/\//.test(sourceMapFile) || /^\/\//.test(sourceMapFile);
-      var isDataUri = DATA_URI.test(sourceMapFile);
-
-      if (isRemote) {
-        return fetchMapFile(self, sourceMapFile, context, proceedToNext);
-      } else {
-        var sourceFile = context.files[context.files.length - 1];
-        var sourceMapPath, sourceMapData;
-        var sourceDir = sourceFile ? path.dirname(sourceFile) : self.options.relativeTo;
-
-        if (isDataUri) {
-          // source map's path is the same as the source file it comes from
-          sourceMapPath = path.resolve(self.options.root, sourceFile || '');
-          sourceMapData = fromDataUri(sourceMapFile);
-        } else {
-          sourceMapPath = path.resolve(self.options.root, path.join(sourceDir || '', sourceMapFile));
-          sourceMapData = fs.readFileSync(sourceMapPath, 'utf-8');
-        }
-        self.trackLoaded(sourceFile || undefined, sourceMapPath, sourceMapData);
-      }
-    }
-
-    context.cursor += nextAt + 1;
-  }
-
-  return whenDone();
-}
-
-function fromDataUri(uriString) {
-  var match = DATA_URI.exec(uriString);
-  var charset = match[2] ? match[2].split(/[=;]/)[2] : 'us-ascii';
-  var encoding = match[3] ? match[3].split(';')[1] : 'utf8';
-  var data = encoding == 'utf8' ? unescape(match[4]) : match[4];
-
-  var buffer = new Buffer(data, encoding);
-  buffer.charset = charset;
-
-  return buffer.toString();
+function inputSourceMapTracker() {
+  var maps = {};
+
+  return {
+    all: all.bind(null, maps),
+    isTracking: isTracking.bind(null, maps),
+    originalPositionFor: originalPositionFor.bind(null, maps),
+    track: track.bind(null, maps)
+  };
 }
 
-function fetchMapFile(self, sourceUrl, context, done) {
-  fetch(self, sourceUrl, function (data) {
-    self.trackLoaded(context.files[context.files.length - 1] || undefined, sourceUrl, data);
-    done();
-  }, function (message) {
-    context.errors.push('Broken source map at "' + sourceUrl + '" - ' + message);
-    return done();
-  });
+function all(maps) {
+  return maps;
 }
 
-function fetch(self, path, onSuccess, onFailure) {
-  var protocol = path.indexOf('https') === 0 ? https : http;
-  var requestOptions = override(url.parse(path), self.requestOptions);
-  var errorHandled = false;
-
-  protocol
-    .get(requestOptions, function (res) {
-      if (res.statusCode < 200 || res.statusCode > 299)
-        return onFailure(res.statusCode);
-
-      var chunks = [];
-      res.on('data', function (chunk) {
-        chunks.push(chunk.toString());
-      });
-      res.on('end', function () {
-        onSuccess(chunks.join(''));
-      });
-    })
-    .on('error', function (res) {
-      if (errorHandled)
-        return;
-
-      onFailure(res.message);
-      errorHandled = true;
-    })
-    .on('timeout', function () {
-      if (errorHandled)
-        return;
-
-      onFailure('timeout');
-      errorHandled = true;
-    })
-    .setTimeout(self.timeout);
+function isTracking(maps, source) {
+  return source in maps;
 }
 
-function originalPositionIn(trackedSource, line, column, token, allowNFallbacks) {
-  var originalPosition;
-  var maxRange = token.length;
+function originalPositionFor(maps, metadata, range, selectorFallbacks) {
+  var line = metadata[0];
+  var column = metadata[1];
+  var source = metadata[2];
   var position = {
     line: line,
-    column: column + maxRange
+    column: column + range
   };
+  var originalPosition;
 
-  while (maxRange-- > 0) {
+  while (!originalPosition && position.column > column) {
     position.column--;
-    originalPosition = trackedSource.data.originalPositionFor(position);
-
-    if (originalPosition)
-      break;
+    originalPosition = maps[source].originalPositionFor(position);
   }
 
-  if (originalPosition.line === null && line > 1 && allowNFallbacks > 0)
-    return originalPositionIn(trackedSource, line - 1, column, token, allowNFallbacks - 1);
-
-  if (trackedSource.path && originalPosition.source) {
-    originalPosition.source = REMOTE_RESOURCE.test(trackedSource.path) ?
-      url.resolve(trackedSource.path, originalPosition.source) :
-      path.join(trackedSource.path, originalPosition.source);
-
-    originalPosition.sourceResolved = true;
+  if (originalPosition.line === null && line > 1 && selectorFallbacks > 0) {
+    return originalPositionFor(maps, [line - 1, column, source], range, selectorFallbacks - 1);
   }
 
-  return originalPosition;
+  return originalPosition.line !== null ?
+    toMetadata(originalPosition) :
+    metadata;
 }
 
-function trackContentSources(self, sourceFile) {
-  var consumer = self.maps[sourceFile].data;
-  var isRemote = REMOTE_RESOURCE.test(sourceFile);
-  var sourcesMapping = {};
-
-  consumer.sources.forEach(function (file, index) {
-    var uniquePath = isRemote ?
-      url.resolve(path.dirname(sourceFile), file) :
-      path.relative(self.relativeTo, path.resolve(path.dirname(sourceFile || '.'), file));
-
-    sourcesMapping[uniquePath] = consumer.sourcesContent && consumer.sourcesContent[index];
-  });
-  self.sourcesContent[sourceFile] = sourcesMapping;
+function toMetadata(asHash) {
+  return [asHash.line, asHash.column, asHash.source];
 }
 
-function _resolveSources(self, remaining, whenDone) {
-  function processNext() {
-    return _resolveSources(self, remaining, whenDone);
-  }
-
-  if (remaining.length === 0)
-    return whenDone();
-
-  var current = remaining.shift();
-  var sourceFile = current[0];
-  var originalFile = current[1];
-  var isRemote = REMOTE_RESOURCE.test(sourceFile);
-
-  if (isRemote && self.localOnly) {
-    self.warnings.push('No callback given to `#minify` method, cannot fetch a remote file from "' + originalFile + '"');
-    return processNext();
-  }
-
-  if (isRemote) {
-    fetch(self, originalFile, function (data) {
-      self.sourcesContent[sourceFile][originalFile] = data;
-      processNext();
-    }, function (message) {
-      self.warnings.push('Broken original source file at "' + originalFile + '" - ' + message);
-      processNext();
-    });
-  } else {
-    var fullPath = path.join(self.options.root, originalFile);
-    if (fs.existsSync(fullPath))
-      self.sourcesContent[sourceFile][originalFile] = fs.readFileSync(fullPath, 'utf-8');
-    else
-      self.warnings.push('Missing original source file at "' + fullPath + '".');
-    return processNext();
-  }
+function track(maps, source, data) {
+  maps[source] = new SourceMapConsumer(data);
 }
 
-InputSourceMapStore.prototype.track = function (data, whenDone) {
-  return typeof this.options.sourceMap == 'string' ?
-    fromString(this, data, whenDone) :
-    fromSource(this, data, whenDone, { files: [], cursor: 0, errors: this.errors });
-};
-
-InputSourceMapStore.prototype.trackLoaded = function (sourcePath, mapPath, mapData) {
-  var relativeTo = this.options.explicitTarget ? this.options.target : this.options.root;
-  var isRemote = REMOTE_RESOURCE.test(sourcePath);
-
-  if (mapPath) {
-    mapPath = isRemote ?
-      path.dirname(mapPath) :
-      path.dirname(path.relative(relativeTo, mapPath));
-  }
-
-  this.maps[sourcePath] = {
-    path: mapPath,
-    data: new SourceMapConsumer(mapData)
-  };
-
-  trackContentSources(this, sourcePath);
-};
-
-InputSourceMapStore.prototype.isTracking = function (source) {
-  return !!this.maps[source];
-};
-
-InputSourceMapStore.prototype.originalPositionFor = function (sourceInfo, token, allowNFallbacks) {
-  return originalPositionIn(this.maps[sourceInfo.source], sourceInfo.line, sourceInfo.column, token, allowNFallbacks);
-};
-
-InputSourceMapStore.prototype.sourcesContentFor = function (contextSource) {
-  return this.sourcesContent[contextSource];
-};
-
-InputSourceMapStore.prototype.resolveSources = function (whenDone) {
-  var toResolve = [];
-
-  for (var sourceFile in this.sourcesContent) {
-    var contents = this.sourcesContent[sourceFile];
-    for (var originalFile in contents) {
-      if (!contents[originalFile])
-        toResolve.push([sourceFile, originalFile]);
-    }
-  }
-
-  return _resolveSources(this, toResolve, whenDone);
-};
-
-module.exports = InputSourceMapStore;
+module.exports = inputSourceMapTracker;
diff --git a/lib/utils/is-absolute-resource.js b/lib/utils/is-absolute-resource.js
new file mode 100644 (file)
index 0000000..baa8023
--- /dev/null
@@ -0,0 +1,7 @@
+var isRemoteResource = require('./is-remote-resource');
+
+function isAbsoluteResource(uri) {
+  return !isRemoteResource(uri) && uri[0] == '/';
+}
+
+module.exports = isAbsoluteResource;
diff --git a/lib/utils/is-allowed-resource.js b/lib/utils/is-allowed-resource.js
new file mode 100644 (file)
index 0000000..d279719
--- /dev/null
@@ -0,0 +1,46 @@
+var url = require('url');
+
+var hasProtocol = require('./has-protocol');
+
+var HTTP_PROTOCOL = 'http:';
+
+function isAllowedResource(uri, isRemote, rules) {
+  var match;
+  var allowed = true;
+  var rule;
+  var i;
+
+  if (rules.length === 0) {
+    return false;
+  }
+
+  if (isRemote && !hasProtocol(uri)) {
+    uri = HTTP_PROTOCOL + uri;
+  }
+
+  match = isRemote ?
+    url.parse(uri).host :
+    uri;
+
+  for (i = 0; i < rules.length; i++) {
+    rule = rules[i];
+
+    if (rule == 'all') {
+      allowed = true;
+    } else if (isRemote && rule == 'local') {
+      allowed = false;
+    } else if (isRemote && rule == 'remote') {
+      allowed = true;
+    } else if (!isRemote && rule == 'remote') {
+      allowed = false;
+    } else if (!isRemote && rule == 'local') {
+      allowed = true;
+    } else if (rule[0] == '!' && rule.substring(1) === match) {
+      allowed = false;
+    }
+  }
+
+  return allowed;
+}
+
+module.exports = isAllowedResource;
diff --git a/lib/utils/is-data-uri-resource.js b/lib/utils/is-data-uri-resource.js
new file mode 100644 (file)
index 0000000..5855811
--- /dev/null
@@ -0,0 +1,7 @@
+var DATA_URI_PATTERN = /^data:(\S*?)?(;charset=[^;]+)?(;[^,]+?)?,(.+)/;
+
+function isDataUriResource(uri) {
+  return DATA_URI_PATTERN.test(uri);
+}
+
+module.exports = isDataUriResource;
diff --git a/lib/utils/is-http-resource.js b/lib/utils/is-http-resource.js
new file mode 100644 (file)
index 0000000..5179c2e
--- /dev/null
@@ -0,0 +1,7 @@
+var HTTP_RESOURCE_PATTERN = /^http:\/\//;
+
+function isHttpResource(uri) {
+  return HTTP_RESOURCE_PATTERN.test(uri);
+}
+
+module.exports = isHttpResource;
diff --git a/lib/utils/is-https-resource.js b/lib/utils/is-https-resource.js
new file mode 100644 (file)
index 0000000..c6938f5
--- /dev/null
@@ -0,0 +1,7 @@
+var HTTPS_RESOURCE_PATTERN = /^https:\/\//;
+
+function isHttpsResource(uri) {
+  return HTTPS_RESOURCE_PATTERN.test(uri);
+}
+
+module.exports = isHttpsResource;
diff --git a/lib/utils/is-import.js b/lib/utils/is-import.js
new file mode 100644 (file)
index 0000000..72abc32
--- /dev/null
@@ -0,0 +1,7 @@
+var IMPORT_PREFIX_PATTERN = /^@import/i;
+
+function isImport(value) {
+  return IMPORT_PREFIX_PATTERN.test(value);
+}
+
+module.exports = isImport;
diff --git a/lib/utils/is-remote-resource.js b/lib/utils/is-remote-resource.js
new file mode 100644 (file)
index 0000000..fb3b61f
--- /dev/null
@@ -0,0 +1,7 @@
+var REMOTE_RESOURCE_PATTERN = /^(\w+:\/\/|\/\/)/;
+
+function isRemoteResource(uri) {
+  return REMOTE_RESOURCE_PATTERN.test(uri);
+}
+
+module.exports = isRemoteResource;
diff --git a/lib/utils/load-original-sources.js b/lib/utils/load-original-sources.js
new file mode 100644 (file)
index 0000000..ba66199
--- /dev/null
@@ -0,0 +1,117 @@
+var fs = require('fs');
+var path = require('path');
+
+var isAllowedResource = require('./is-allowed-resource');
+var isRemoteResource = require('./is-remote-resource');
+var loadRemoteResource = require('./load-remote-resource');
+
+function loadOriginalSources(context, callback) {
+  var loadContext = {
+    callback: callback,
+    index: 0,
+    inliner: context.options.inliner,
+    localOnly: context.localOnly,
+    processImportFrom: context.options.processImportFrom,
+    rebaseTo: context.options.rebaseTo,
+    sourcesContent: context.sourcesContent,
+    uriToSource: uriToSourceMapping(context.inputSourceMapTracker.all()),
+    warnings: context.warnings
+  };
+
+  return doLoadOriginalSources(loadContext);
+}
+
+function uriToSourceMapping(allSourceMapConsumers) {
+  var mapping = {};
+  var consumer;
+  var uri;
+  var source;
+  var i, l;
+
+  for (source in allSourceMapConsumers) {
+    consumer = allSourceMapConsumers[source];
+
+    for (i = 0, l = consumer.sources.length; i < l; i++) {
+      uri = consumer.sources[i];
+      source = consumer.sourceContentFor(uri, true);
+
+      mapping[uri] = source;
+    }
+  }
+
+  return mapping;
+}
+
+function doLoadOriginalSources(loadContext) {
+  var uris = Object.keys(loadContext.uriToSource);
+  var uri;
+  var source;
+  var total;
+
+  for (total = uris.length; loadContext.index < total; loadContext.index++) {
+    uri = uris[loadContext.index];
+    source = loadContext.uriToSource[uri];
+
+    if (source) {
+      loadContext.sourcesContent[uri] = source;
+    } else {
+      return loadOriginalSource(uri, loadContext);
+    }
+  }
+
+  return loadContext.callback();
+}
+
+function loadOriginalSource(uri, loadContext) {
+  var content;
+
+  if (isRemoteResource(uri)) {
+    return loadOriginalSourceFromRemoteUri(uri, loadContext, function (content) {
+      loadContext.index++;
+      loadContext.sourcesContent[uri] = content;
+      return doLoadOriginalSources(loadContext);
+    });
+  } else {
+    content = loadOriginalSourceFromLocalUri(uri, loadContext);
+    loadContext.index++;
+    loadContext.sourcesContent[uri] = content;
+    return doLoadOriginalSources(loadContext);
+  }
+}
+
+function loadOriginalSourceFromRemoteUri(uri, loadContext, whenLoaded) {
+  var isAllowed = isAllowedResource(uri, true, loadContext.processImportFrom);
+
+  if (loadContext.localOnly) {
+    loadContext.warnings.push('Cannot fetch remote resource from "' + uri + '" as no callback given.');
+    return whenLoaded(null);
+  } else if (!isAllowed) {
+    loadContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
+    return whenLoaded(null);
+  }
+
+  loadRemoteResource(uri, loadContext.inliner, function (error, content) {
+    if (error) {
+      loadContext.warnings.push('Missing original source at "' + uri + '" - ' + error);
+    }
+
+    whenLoaded(content);
+  });
+}
+
+function loadOriginalSourceFromLocalUri(uri, loadContext) {
+  var isAllowed = isAllowedResource(uri, true, loadContext.processImportFrom);
+  var resolvedUri = path.resolve(loadContext.rebaseTo, uri);
+
+  if (!fs.existsSync(resolvedUri) || !fs.statSync(resolvedUri).isFile()) {
+    loadContext.warnings.push('Ignoring local source map at "' + resolvedUri + '" as resource is missing.');
+    return null;
+  } else if (!isAllowed) {
+    loadContext.warnings.push('Cannot fetch "' + resolvedUri + '" as resource is not allowed.');
+    return null;
+  }
+
+  return fs.readFileSync(resolvedUri, 'utf8');
+}
+
+module.exports = loadOriginalSources;
diff --git a/lib/utils/load-remote-resource.js b/lib/utils/load-remote-resource.js
new file mode 100644 (file)
index 0000000..9760574
--- /dev/null
@@ -0,0 +1,75 @@
+var http = require('http');
+var https = require('https');
+var url = require('url');
+
+var hasProtocol = require('./has-protocol');
+var isHttpResource = require('./is-http-resource');
+var isHttpsResource = require('./is-https-resource');
+var override = require('./override');
+
+var HTTP_PROTOCOL = 'http:';
+
+function loadRemoteResource(uri, inlinerOptions, callback) {
+  var proxyProtocol = inlinerOptions.request.protocol || inlinerOptions.request.hostname;
+  var errorHandled = false;
+  var requestOptions;
+  var fetch;
+
+  if (!hasProtocol(uri)) {
+    uri = 'http:' + uri;
+  }
+
+  requestOptions = override(
+    url.parse(uri),
+    inlinerOptions.request || {}
+  );
+
+  if (inlinerOptions.request.hostname !== undefined) {
+    // overwrite as we always expect a http proxy currently
+    requestOptions.protocol = inlinerOptions.request.protocol || HTTP_PROTOCOL;
+    requestOptions.path = requestOptions.href;
+  }
+
+  fetch = (proxyProtocol && !isHttpsResource(proxyProtocol)) || isHttpResource(uri) ?
+    http.get :
+    https.get;
+
+  fetch(requestOptions, function (res) {
+    var chunks = [];
+    var movedUri;
+
+    if (res.statusCode < 200 || res.statusCode > 399) {
+      return callback(res.statusCode, null);
+    } else if (res.statusCode > 299) {
+      movedUri = url.resolve(uri, res.headers.location);
+      return loadRemoteResource(movedUri, inlinerOptions, callback);
+    }
+
+    res.on('data', function (chunk) {
+      chunks.push(chunk.toString());
+    });
+    res.on('end', function () {
+      var body = chunks.join('');
+      callback(null, body);
+    });
+  })
+  .on('error', function (res) {
+    if (errorHandled) {
+      return;
+    }
+
+    errorHandled = true;
+    callback(res.message, null);
+  })
+  .on('timeout', function () {
+    if (errorHandled) {
+      return;
+    }
+
+    errorHandled = true;
+    callback('timeout', null);
+  })
+  .setTimeout(inlinerOptions.timeout);
+}
+
+module.exports = loadRemoteResource;
diff --git a/lib/utils/match-data-uri.js b/lib/utils/match-data-uri.js
new file mode 100644 (file)
index 0000000..d0d5a4c
--- /dev/null
@@ -0,0 +1,7 @@
+var DATA_URI_PATTERN = /^data:(\S*?)?(;charset=[^;]+)?(;[^,]+?)?,(.+)/;
+
+function matchDataUri(uri) {
+  return DATA_URI_PATTERN.exec(uri);
+}
+
+module.exports = matchDataUri;
index 24da9dd..418ed6e 100644 (file)
@@ -1,32 +1,35 @@
 var fs = require('fs');
-var http = require('http');
-var https = require('https');
 var path = require('path');
-var url = require('url');
+
+var applySourceMaps = require('./apply-source-maps');
+var extractImportUrlAndMedia = require('./extract-import-url-and-media');
+var isAbsoluteResource = require('./is-absolute-resource');
+var isAllowedResource = require('./is-allowed-resource');
+var isImport = require('./is-import');
+var isRemoteResource = require('./is-remote-resource');
+var loadOriginalSources = require('./load-original-sources');
+var loadRemoteResource = require('./load-remote-resource');
+var rebase = require('./rebase');
+var rebaseLocalMap = require('./rebase-local-map');
+var rebaseRemoteMap = require('./rebase-remote-map');
+var restoreImport = require('./restore-import');
 
 var tokenize = require('../tokenizer/tokenize');
 var Token = require('../tokenizer/token');
 
-var rewriteUrl = require('../urls/rewrite');
-
-var override = require('../utils/override');
-var split = require('../utils/split');
-
-var IMPORT_PREFIX_PATTERN = /^@import/i;
-var BRACE_PREFIX = /^\(/;
-var BRACE_SUFFIX = /\)$/;
-var QUOTE_PREFIX_PATTERN = /['"]\s*/;
-var QUOTE_SUFFIX_PATTERN = /\s*['"]/;
-var URL_PREFIX_PATTERN = /^url\(\s*/i;
-var URL_SUFFIX_PATTERN = /\s*\)/i;
-
-var HTTP_PROTOCOL = 'http:';
-var HTTP_RESOURCE_PATTERN = /^http:\/\//;
-var HTTPS_RESOURCE_PATTERN = /^https:\/\//;
-var NO_PROTOCOL_RESOURCE_PATTERN = /^\/\//;
-var REMOTE_RESOURCE_PATTERN = /^(https?:)?\/\//;
+var UNKNOWN_SOURCE = 'unknown-source';
 
 function readSources(input, context, callback) {
+  return doReadSources(input, context, function (tokens) {
+    return applySourceMaps(tokens, context, function () {
+      return context.options.sourceMapInlineSources ?
+        loadOriginalSources(context, function () { return callback(tokens); }) :
+        callback(tokens);
+    });
+  });
+}
+
+function doReadSources(input, context, callback) {
   if (typeof input == 'string') {
     return fromString(input, context, {}, callback);
   } else if (Buffer.isBuffer(input)) {
@@ -41,23 +44,32 @@ function readSources(input, context, callback) {
 function fromString(input, context, parentInlinerContext, callback) {
   var inputAsHash = {};
 
-  inputAsHash[false] = {
-    styles: input
+  inputAsHash[UNKNOWN_SOURCE] = {
+    styles: input,
+    sourceMap: (typeof context.options.sourceMap === 'string') ? context.options.sourceMap : null
   };
 
   return fromHash(inputAsHash, context, parentInlinerContext, callback);
 }
 
 function fromArray(input, context, parentInlinerContext, callback) {
+  var currentPath = path.resolve('');
   var inputAsHash = input.reduce(function (accumulator, uri) {
-    var absolutePath = uri[0] == '/' ?
+    var isRemoteUri = isRemoteResource(uri);
+    var absolutePath = uri[0] == '/' || isRemoteUri ?
       uri :
       path.resolve(uri);
+    var relativeToCurrentPath;
 
-    if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
-      context.errors.push('Ignoring "' + uri + '" as resource is missing.');
-    } else {
+    if (isRemoteUri) {
       accumulator[uri] = {
+        styles: restoreImport(uri, '') + ';'
+      };
+    } else if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
+      context.errors.push('Ignoring "' + absolutePath + '" as resource is missing.');
+    } else {
+      relativeToCurrentPath = path.relative(currentPath, absolutePath);
+      accumulator[relativeToCurrentPath] = {
         styles: fs.readFileSync(absolutePath, 'utf-8')
       };
     }
@@ -70,25 +82,28 @@ function fromArray(input, context, parentInlinerContext, callback) {
 
 function fromHash(input, context, parentInlinerContext, callback) {
   var tokens = [];
+  var newTokens = [];
   var sourcePath;
   var source;
   var rebaseFrom;
   var rebaseTo;
+  var parsedMap;
+  var rebasedMap;
   var rebaseConfig;
 
   for (sourcePath in input) {
     source = input[sourcePath];
 
-    if (isRemote(sourcePath)) {
+    if (sourcePath !== UNKNOWN_SOURCE && isRemoteResource(sourcePath)) {
       rebaseFrom = sourcePath;
       rebaseTo = sourcePath;
-    } else if (isAbsolute(sourcePath)) {
+    } else if (sourcePath !== UNKNOWN_SOURCE && isAbsoluteResource(sourcePath)) {
       rebaseFrom = path.dirname(sourcePath);
       rebaseTo = context.options.rebaseTo;
     } else {
-      rebaseFrom = sourcePath ?
+      rebaseFrom = sourcePath !== UNKNOWN_SOURCE ?
         path.dirname(path.resolve(sourcePath)) :
-        path.resolve(sourcePath);
+        path.resolve('');
       rebaseTo = context.options.rebaseTo;
     }
 
@@ -97,123 +112,45 @@ function fromHash(input, context, parentInlinerContext, callback) {
       toBase: rebaseTo
     };
 
-    tokens = tokens.concat(
-      rebase(
-        tokenize(source.styles, context),
-        context.options.rebase,
-        context.validator,
-        rebaseConfig
-      )
-    );
-
-    context.stats.originalSize += source.styles.length;
-  }
-
-  return context.options.processImport ?
-    inlineImports(tokens, context, parentInlinerContext, callback) :
-    callback(tokens);
-}
-
-function isAbsolute(uri) {
-  return uri && uri[0] == '/';
-}
-
-function rebase(tokens, rebaseAll, validator, rebaseConfig) {
-  return rebaseAll ?
-    rebaseEverything(tokens, validator, rebaseConfig) :
-    rebaseAtRules(tokens, validator, rebaseConfig);
-}
-
-function rebaseEverything(tokens, validator, rebaseConfig) {
-  var token;
-  var i, l;
-
-  for (i = 0, l = tokens.length; i < l; i++) {
-    token = tokens[i];
-
-    switch (token[0]) {
-      case Token.AT_RULE:
-        rebaseAtRule(token, validator, rebaseConfig);
-        break;
-      case Token.AT_RULE_BLOCK:
-        //
-        break;
-      case Token.BLOCK:
-        rebaseEverything(token[2], validator, rebaseConfig);
-        break;
-      case Token.PROPERTY:
-        //
-        break;
-      case Token.RULE:
-        rebaseProperties(token[2], validator, rebaseConfig);
-        break;
+    if (source.sourceMap) {
+      parsedMap = JSON.parse(source.sourceMap);
+      rebasedMap = isRemoteResource(sourcePath) ?
+        rebaseRemoteMap(parsedMap, sourcePath) :
+        rebaseLocalMap(parsedMap, sourcePath, context.options.rebaseTo);
+      context.inputSourceMapTracker.track(sourcePath, rebasedMap);
     }
-  }
-
-  return tokens;
-}
-
-function rebaseAtRules(tokens, validator, rebaseConfig) {
-  var token;
-  var i, l;
 
-  for (i = 0, l = tokens.length; i < l; i++) {
-    token = tokens[i];
+    context.source = sourcePath !== UNKNOWN_SOURCE ? sourcePath : undefined;
+    context.sourcesContent[context.source] = source.styles;
 
-    switch (token[0]) {
-      case Token.AT_RULE:
-        rebaseAtRule(token, validator, rebaseConfig);
-        break;
-    }
-  }
+    newTokens = tokenize(source.styles, context);
+    newTokens = rebase(newTokens, context.options.rebase, context.validator, rebaseConfig);
 
-  return tokens;
-}
+    tokens = tokens.concat(newTokens);
 
-function rebaseAtRule(token, validator, rebaseConfig) {
-  if (!IMPORT_PREFIX_PATTERN.test(token[1])) {
-    return;
+    context.stats.originalSize += source.styles.length;
   }
 
-  var uriAndMediaQuery = extractUrlAndMedia(token[1]);
-  var newUrl = rewriteUrl(uriAndMediaQuery[0], rebaseConfig);
-  var mediaQuery = uriAndMediaQuery[1];
-
-  token[1] = restoreImport(newUrl, mediaQuery);
-}
-
-function restoreImport(uri, mediaQuery) {
-  return ('@import ' + uri + ' ' + mediaQuery).trim();
-}
-
-function rebaseProperties(properties, validator, rebaseConfig) {
-  var property;
-  var value;
-  var i, l;
-  var j, m;
-
-  for (i = 0, l = properties.length; i < l; i++) {
-    property = properties[i];
-
-    for (j = 2 /* 0 is Token.PROPERTY, 1 is name */, m = property.length; j < m; j++) {
-      value = property[j][1];
-
-      if (validator.isValidUrl(value)) {
-        property[j][1] = rewriteUrl(value, rebaseConfig);
-      }
-    }
-  }
+  return context.options.processImport ?
+    inlineImports(tokens, context, parentInlinerContext, callback) :
+    callback(tokens);
 }
 
 function inlineImports(tokens, externalContext, parentInlinerContext, callback) {
   var inlinerContext = {
     afterContent: false,
     callback: callback,
+    errors: externalContext.errors,
     externalContext: externalContext,
     imported: parentInlinerContext.imported || [],
+    inlinerOptions: externalContext.options.inliner,
     isRemote: parentInlinerContext.isRemote || false,
+    localOnly: externalContext.localOnly,
     outputTokens: [],
-    sourceTokens: tokens
+    processImportFrom: externalContext.options.processImportFrom,
+    rebaseTo: externalContext.options.rebaseTo,
+    sourceTokens: tokens,
+    warnings: externalContext.warnings
   };
 
   return doInlineImports(inlinerContext);
@@ -226,7 +163,7 @@ function doInlineImports(inlinerContext) {
   for (i = 0, l = inlinerContext.sourceTokens.length; i < l; i++) {
     token = inlinerContext.sourceTokens[i];
 
-    if (token[0] == Token.AT_RULE && IMPORT_PREFIX_PATTERN.test(token[1])) {
+    if (token[0] == Token.AT_RULE && isImport(token[1])) {
       inlinerContext.sourceTokens.splice(0, i);
       return inlineStylesheet(token, inlinerContext);
     } else if (token[0] == Token.AT_RULE || token[0] == Token.COMMENT) {
@@ -242,220 +179,109 @@ function doInlineImports(inlinerContext) {
 }
 
 function inlineStylesheet(token, inlinerContext) {
-  var uriAndMediaQuery = extractUrlAndMedia(token[1]);
+  var uriAndMediaQuery = extractImportUrlAndMedia(token[1]);
   var uri = uriAndMediaQuery[0];
   var mediaQuery = uriAndMediaQuery[1];
+  var metadata = token[2];
 
-  return isRemote(uri) ?
-    inlineRemoteStylesheet(uri, mediaQuery, inlinerContext) :
-    inlineLocalStylesheet(uri, mediaQuery, inlinerContext);
-}
-
-function extractUrlAndMedia(atRuleValue) {
-  var uri;
-  var mediaQuery;
-  var stripped;
-  var parts;
-
-  stripped = atRuleValue
-    .replace(IMPORT_PREFIX_PATTERN, '')
-    .trim()
-    .replace(URL_PREFIX_PATTERN, '(')
-    .replace(URL_SUFFIX_PATTERN, ')')
-    .replace(QUOTE_PREFIX_PATTERN, '')
-    .replace(QUOTE_SUFFIX_PATTERN, '');
-
-  parts = split(stripped, ' ');
-
-  uri = parts[0]
-    .replace(BRACE_PREFIX, '')
-    .replace(BRACE_SUFFIX, '');
-  mediaQuery = parts.slice(1).join(' ');
-
-  return [uri, mediaQuery];
-}
-
-function isRemote(uri) {
-  return uri && REMOTE_RESOURCE_PATTERN.test(uri);
-}
-
-function allowedResource(uri, isRemote, rules) {
-  var match;
-  var allowed = true;
-  var rule;
-  var i;
-
-  if (rules.length === 0) {
-    return false;
-  }
-
-  if (isRemote && NO_PROTOCOL_RESOURCE_PATTERN.test(uri)) {
-    uri = 'http:' + uri;
-  }
-
-  match = isRemote ?
-    url.parse(uri).host :
-    uri;
-
-  for (i = 0; i < rules.length; i++) {
-    rule = rules[i];
-
-    if (rule == 'all') {
-      allowed = true;
-    } else if (isRemote && rule == 'local') {
-      allowed = false;
-    } else if (isRemote && rule == 'remote') {
-      allowed = true;
-    } else if (!isRemote && rule == 'remote') {
-      allowed = false;
-    } else if (!isRemote && rule == 'local') {
-      allowed = true;
-    } else if (rule[0] == '!' && rule.substring(1) === match) {
-      allowed = false;
-    }
-  }
-
-  return allowed;
+  return isRemoteResource(uri) ?
+    inlineRemoteStylesheet(uri, mediaQuery, metadata, inlinerContext) :
+    inlineLocalStylesheet(uri, mediaQuery, metadata, inlinerContext);
 }
 
-function inlineRemoteStylesheet(uri, mediaQuery, inlinerContext) {
-  var inliner = inlinerContext.externalContext.options.inliner;
-  var errorHandled = false;
-  var fetch;
-  var isAllowed = allowedResource(uri, true, inlinerContext.externalContext.options.processImportFrom);
-  var onError;
-  var options;
+function inlineRemoteStylesheet(uri, mediaQuery, metadata, inlinerContext) {
+  var isAllowed = isAllowedResource(uri, true, inlinerContext.processImportFrom);
   var originalUri = uri;
-  var proxyProtocol = inliner.request.protocol || inliner.request.hostname;
 
   if (inlinerContext.imported.indexOf(uri) > -1) {
-    inlinerContext.externalContext.warnings.push('Ignoring remote @import of "' + uri + '" as it has already been imported.');
+    inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as it has already been imported.');
     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
     return doInlineImports(inlinerContext);
-  } else if (inlinerContext.externalContext.localOnly && inlinerContext.afterContent) {
-    inlinerContext.externalContext.warnings.push('Ignoring remote @import of "' + uri + '" as no callback given and after other content.');
+  } else if (inlinerContext.localOnly && inlinerContext.afterContent) {
+    inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as no callback given and after other content.');
     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
     return doInlineImports(inlinerContext);
-  } else if (inlinerContext.externalContext.localOnly) {
-    inlinerContext.externalContext.warnings.push('Skipping remote @import of "' + uri + '" as no callback given.');
+  } else if (inlinerContext.localOnly) {
+    inlinerContext.warnings.push('Skipping remote @import of "' + uri + '" as no callback given.');
     inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
     return doInlineImports(inlinerContext);
   } else if (!isAllowed && inlinerContext.afterContent) {
-    inlinerContext.externalContext.warnings.push('Ignoring remote @import of "' + uri + '" as resource not allowed and after other content.');
+    inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as resource is not allowed and after other content.');
     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
     return doInlineImports(inlinerContext);
   } else if (!isAllowed) {
-    inlinerContext.externalContext.warnings.push('Skipping remote @import of "' + uri + '" as resource not allowed.');
+    inlinerContext.warnings.push('Skipping remote @import of "' + uri + '" as resource is not allowed.');
     inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
     return doInlineImports(inlinerContext);
   }
 
-  if (NO_PROTOCOL_RESOURCE_PATTERN.test(uri)) {
-    uri = 'http:' + uri;
-  }
-
-  fetch = (proxyProtocol && !HTTPS_RESOURCE_PATTERN.test(proxyProtocol)) || HTTP_RESOURCE_PATTERN.test(uri) ?
-    http.get :
-    https.get;
-
-  options = override(url.parse(uri), inliner.request);
-  if (inliner.request.hostname !== undefined) {
-    // overwrite as we always expect a http proxy currently
-    options.protocol = inliner.request.protocol || HTTP_PROTOCOL;
-    options.path = options.href;
-  }
-
-  onError = function(message) {
-    if (errorHandled)
-      return;
-
-    errorHandled = true;
-    inlinerContext.externalContext.errors.push('Broken @import declaration of "' + uri + '" - ' + message);
-
-    process.nextTick(function () {
-      inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
-      inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
-      doInlineImports(inlinerContext);
-    });
-  };
-
   inlinerContext.imported.push(uri);
 
-  fetch(options, function (res) {
-    var chunks = [];
-    var movedUri;
+  loadRemoteResource(uri, inlinerContext.inlinerOptions, function (error, importedStyles) {
+    var sourceHash = {};
 
-    if (res.statusCode < 200 || res.statusCode > 399) {
-      return onError('error ' + res.statusCode);
-    } else if (res.statusCode > 299) {
-      movedUri = url.resolve(uri, res.headers.location);
-      return inlineRemoteStylesheet(movedUri, mediaQuery, inlinerContext);
-    }
+    if (error) {
+      inlinerContext.errors.push('Broken @import declaration of "' + uri + '" - ' + error);
 
-    res.on('data', function (chunk) {
-      chunks.push(chunk.toString());
-    });
-    res.on('end', function () {
-      var importedStyles;
-      var sourceHash = {};
+      return process.nextTick(function () {
+        inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
+        inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
+        doInlineImports(inlinerContext);
+      });
+    }
 
-      importedStyles = chunks.join('');
-      sourceHash[originalUri] = {
-        styles: importedStyles
-      };
+    sourceHash[originalUri] = {
+      styles: importedStyles
+    };
 
-      inlinerContext.isRemote = true;
-      fromHash(sourceHash, inlinerContext.externalContext, inlinerContext, function (importedTokens) {
-        importedTokens = wrapInMedia(importedTokens, mediaQuery);
+    inlinerContext.isRemote = true;
+    fromHash(sourceHash, inlinerContext.externalContext, inlinerContext, function (importedTokens) {
+      importedTokens = wrapInMedia(importedTokens, mediaQuery, metadata);
 
-        inlinerContext.outputTokens = inlinerContext.outputTokens.concat(importedTokens);
-        inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
+      inlinerContext.outputTokens = inlinerContext.outputTokens.concat(importedTokens);
+      inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 
-        doInlineImports(inlinerContext);
-      });
+      doInlineImports(inlinerContext);
     });
-
-  })
-  .on('error', function (res) {
-    onError(res.message);
-  })
-  .on('timeout', function () {
-    onError('timeout');
-  })
-  .setTimeout(inliner.timeout);
+  });
 }
 
-function inlineLocalStylesheet(uri, mediaQuery, inlinerContext) {
-  var resolvedPath = uri[0] == '/' ?
-    path.resolve(path.resolve(''), uri.substring(1)) :
-    path.resolve(inlinerContext.externalContext.options.rebaseTo, uri);
+function inlineLocalStylesheet(uri, mediaQuery, metadata, inlinerContext) {
+  var currentPath = path.resolve('');
+  var relativeTo = uri[0] == '/' ?
+    currentPath :
+    path.resolve(inlinerContext.rebaseTo || '');
+  var absolutePath = uri[0] == '/' ?
+    path.resolve(relativeTo, uri.substring(1)) :
+    path.resolve(relativeTo, uri);
+  var relativeToCurrentPath = path.relative(currentPath, absolutePath);
   var importedStyles;
   var importedTokens;
-  var isAllowed = allowedResource(uri, false, inlinerContext.externalContext.options.processImportFrom);
+  var isAllowed = isAllowedResource(uri, false, inlinerContext.processImportFrom);
   var sourceHash = {};
 
-  if (inlinerContext.imported.indexOf(resolvedPath) > -1) {
-    inlinerContext.externalContext.warnings.push('Ignoring local @import of "' + uri + '" as it has already beeb imported.');
-  } else if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isFile()) {
-    inlinerContext.externalContext.errors.push('Ignoring local @import of "' + uri + '" as resource is missing.');
+  if (inlinerContext.imported.indexOf(absolutePath) > -1) {
+    inlinerContext.warnings.push('Ignoring local @import of "' + uri + '" as it has already been imported.');
+  } else if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
+    inlinerContext.errors.push('Ignoring local @import of "' + uri + '" as resource is missing.');
   } else if (!isAllowed && inlinerContext.afterContent) {
-    inlinerContext.externalContext.warnings.push('Ignoring local @import of "' + uri + '" as resource not allowed and after other content.');
+    inlinerContext.warnings.push('Ignoring local @import of "' + uri + '" as resource is not allowed and after other content.');
   } else if (inlinerContext.afterContent) {
-    inlinerContext.externalContext.warnings.push('Ignoring local @import of "' + uri + '" as after other content.');
+    inlinerContext.warnings.push('Ignoring local @import of "' + uri + '" as after other content.');
   } else if (!isAllowed) {
-    inlinerContext.externalContext.warnings.push('Skipping local @import of "' + uri + '" as resource not allowed.');
+    inlinerContext.warnings.push('Skipping local @import of "' + uri + '" as resource is not allowed.');
     inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
   } else {
-    importedStyles = fs.readFileSync(resolvedPath, 'utf-8');
-    inlinerContext.imported.push(resolvedPath);
+    importedStyles = fs.readFileSync(absolutePath, 'utf-8');
+    inlinerContext.imported.push(absolutePath);
 
-    sourceHash[resolvedPath] = {
+    sourceHash[relativeToCurrentPath] = {
       styles: importedStyles
     };
     importedTokens = fromHash(sourceHash, inlinerContext.externalContext, inlinerContext, function (tokens) { return tokens; });
-    importedTokens = wrapInMedia(importedTokens, mediaQuery);
+    importedTokens = wrapInMedia(importedTokens, mediaQuery, metadata);
 
     inlinerContext.outputTokens = inlinerContext.outputTokens.concat(importedTokens);
   }
@@ -465,9 +291,9 @@ function inlineLocalStylesheet(uri, mediaQuery, inlinerContext) {
   return doInlineImports(inlinerContext);
 }
 
-function wrapInMedia(tokens, mediaQuery) {
+function wrapInMedia(tokens, mediaQuery, metadata) {
   if (mediaQuery) {
-    return [[Token.BLOCK, [[Token.BLOCK_SCOPE, '@media ' + mediaQuery]], tokens]];
+    return [[Token.BLOCK, [[Token.BLOCK_SCOPE, '@media ' + mediaQuery, metadata]], tokens]];
   } else {
     return tokens;
   }
diff --git a/lib/utils/rebase-local-map.js b/lib/utils/rebase-local-map.js
new file mode 100644 (file)
index 0000000..3ca288d
--- /dev/null
@@ -0,0 +1,15 @@
+var path = require('path');
+
+function rebaseLocalMap(sourceMap, sourceUri, rebaseTo) {
+  var currentPath = path.resolve('');
+  var absolutePath = path.resolve(currentPath, sourceUri);
+  var sourceDirectory = path.dirname(absolutePath);
+
+  sourceMap.sources = sourceMap.sources.map(function(source) {
+    return path.relative(rebaseTo, path.resolve(sourceDirectory, source));
+  });
+
+  return sourceMap;
+}
+
+module.exports = rebaseLocalMap;
diff --git a/lib/utils/rebase-remote-map.js b/lib/utils/rebase-remote-map.js
new file mode 100644 (file)
index 0000000..7b6bb7a
--- /dev/null
@@ -0,0 +1,14 @@
+var path = require('path');
+var url = require('url');
+
+function rebaseRemoteMap(sourceMap, sourceUri) {
+  var sourceDirectory = path.dirname(sourceUri);
+
+  sourceMap.sources = sourceMap.sources.map(function(source) {
+    return url.resolve(sourceDirectory, source);
+  });
+
+  return sourceMap;
+}
+
+module.exports = rebaseRemoteMap;
diff --git a/lib/utils/rebase.js b/lib/utils/rebase.js
new file mode 100644 (file)
index 0000000..7848797
--- /dev/null
@@ -0,0 +1,101 @@
+var extractImportUrlAndMedia = require('./extract-import-url-and-media');
+var isImport = require('./is-import');
+var restoreImport = require('./restore-import');
+
+var rewriteUrl = require('../urls/rewrite');
+var Token = require('../tokenizer/token');
+
+var SOURCE_MAP_COMMENT_PATTERN = /^\/\*# sourceMappingURL=(\S+) \*\/$/;
+
+function rebase(tokens, rebaseAll, validator, rebaseConfig) {
+  return rebaseAll ?
+    rebaseEverything(tokens, validator, rebaseConfig) :
+    rebaseAtRules(tokens, validator, rebaseConfig);
+}
+
+function rebaseEverything(tokens, validator, rebaseConfig) {
+  var token;
+  var i, l;
+
+  for (i = 0, l = tokens.length; i < l; i++) {
+    token = tokens[i];
+
+    switch (token[0]) {
+      case Token.AT_RULE:
+        rebaseAtRule(token, validator, rebaseConfig);
+        break;
+      case Token.AT_RULE_BLOCK:
+        rebaseProperties(token[2], validator, rebaseConfig);
+        break;
+      case Token.BLOCK:
+        rebaseEverything(token[2], validator, rebaseConfig);
+        break;
+      case Token.COMMENT:
+        rebaseSourceMapComment(token, rebaseConfig);
+        break;
+      case Token.RULE:
+        rebaseProperties(token[2], validator, rebaseConfig);
+        break;
+    }
+  }
+
+  return tokens;
+}
+
+function rebaseAtRules(tokens, validator, rebaseConfig) {
+  var token;
+  var i, l;
+
+  for (i = 0, l = tokens.length; i < l; i++) {
+    token = tokens[i];
+
+    switch (token[0]) {
+      case Token.AT_RULE:
+        rebaseAtRule(token, validator, rebaseConfig);
+        break;
+    }
+  }
+
+  return tokens;
+}
+
+function rebaseAtRule(token, validator, rebaseConfig) {
+  if (!isImport(token[1])) {
+    return;
+  }
+
+  var uriAndMediaQuery = extractImportUrlAndMedia(token[1]);
+  var newUrl = rewriteUrl(uriAndMediaQuery[0], rebaseConfig);
+  var mediaQuery = uriAndMediaQuery[1];
+
+  token[1] = restoreImport(newUrl, mediaQuery);
+}
+
+function rebaseSourceMapComment(token, rebaseConfig) {
+  var matches = SOURCE_MAP_COMMENT_PATTERN.exec(token[1]);
+
+  if (matches && matches[1].indexOf('data:') === -1) {
+    token[1] = token[1].replace(matches[1], rewriteUrl(matches[1], rebaseConfig, true));
+  }
+}
+
+function rebaseProperties(properties, validator, rebaseConfig) {
+  var property;
+  var value;
+  var i, l;
+  var j, m;
+
+  for (i = 0, l = properties.length; i < l; i++) {
+    property = properties[i];
+
+    for (j = 2 /* 0 is Token.PROPERTY, 1 is name */, m = property.length; j < m; j++) {
+      value = property[j][1];
+
+      if (validator.isValidUrl(value)) {
+        property[j][1] = rewriteUrl(value, rebaseConfig);
+      }
+    }
+  }
+}
+
+module.exports = rebase;
diff --git a/lib/utils/restore-import.js b/lib/utils/restore-import.js
new file mode 100644 (file)
index 0000000..5bdbd92
--- /dev/null
@@ -0,0 +1,5 @@
+function restoreImport(uri, mediaQuery) {
+  return ('@import ' + uri + ' ' + mediaQuery).trim();
+}
+
+module.exports = restoreImport;
index b353784..b019427 100644 (file)
@@ -447,14 +447,22 @@ vows.describe('module tests').addBatch({
           assert.equal(minified.styles, '@import url(test/fixtures/partials/one.css);@import url(test/fixtures/partials/extra/three.css);@import url(test/fixtures/partials/extra/four.css);.two{color:#fff}');
         }
       },
-      'off and rebase off': {
+      'off - many files': {
         'topic': function () {
-          return new CleanCSS({ processImport: false, rebase: false }).minify(['./test/fixtures/partials/two.css']);
+          return new CleanCSS({ processImport: false }).minify(['./test/fixtures/partials/remote.css', './test/fixtures/partials-absolute/base.css']);
         },
         'should give right output': function (minified) {
-          assert.equal(minified.styles, '@import url(test/fixtures/partials/one.css);@import url(test/fixtures/partials/extra/three.css);@import url(test/fixtures/partials/extra/four.css);.two{color:#fff}');
+          assert.equal(minified.styles, '@import url(http://jakubpawlowicz.com/styles.css);@import url(test/fixtures/partials-absolute/extra/sub.css);.base{margin:0}');
         }
       },
+      'off - many files with content': {
+        'topic': function () {
+          return new CleanCSS({ processImport: false }).minify(['./test/fixtures/partials/two.css', './test/fixtures/partials-absolute/base.css']);
+        },
+        'should give right output': function (minified) {
+          assert.equal(minified.styles, '@import url(test/fixtures/partials/one.css);@import url(test/fixtures/partials/extra/three.css);@import url(test/fixtures/partials/extra/four.css);.two{color:#fff}.base{margin:0}');
+        }
+      }
     }
   },
   'accepts a list of source files as hash': {
index 209f929..75fa125 100644 (file)
@@ -1,13 +1,20 @@
 var vows = require('vows');
 var assert = require('assert');
 var tokenize = require('../../lib/tokenizer/tokenize');
+var inputSourceMapTracker = require('../../lib/utils/input-source-map-tracker');
 var extractProperties = require('../../lib/optimizer/extract-properties');
 
+function _tokenize(source) {
+  return tokenize(source, {
+    inputSourceMapTracker: inputSourceMapTracker()
+  });
+}
+
 vows.describe(extractProperties)
   .addBatch({
     'no properties': {
       'topic': function () {
-        return extractProperties(tokenize('a{}', {})[0]);
+        return extractProperties(_tokenize('a{}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, []);
@@ -15,7 +22,7 @@ vows.describe(extractProperties)
     },
     'no valid properties': {
       'topic': function () {
-        return extractProperties(tokenize('a{:red}', {})[0]);
+        return extractProperties(_tokenize('a{:red}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, []);
@@ -23,7 +30,7 @@ vows.describe(extractProperties)
     },
     'one property': {
       'topic': function () {
-        return extractProperties(tokenize('a{color:red}', {})[0]);
+        return extractProperties(_tokenize('a{color:red}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, [
@@ -41,7 +48,7 @@ vows.describe(extractProperties)
     },
     'one important property': {
       'topic': function () {
-        return extractProperties(tokenize('a{color:red!important}', {})[0]);
+        return extractProperties(_tokenize('a{color:red!important}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, [
@@ -59,7 +66,7 @@ vows.describe(extractProperties)
     },
     'one property - simple selector': {
       'topic': function () {
-        return extractProperties(tokenize('#one span{color:red}', {})[0]);
+        return extractProperties(_tokenize('#one span{color:red}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, [
@@ -77,7 +84,7 @@ vows.describe(extractProperties)
     },
     'one property - variable': {
       'topic': function () {
-        return extractProperties(tokenize('#one span{--color:red}', {})[0]);
+        return extractProperties(_tokenize('#one span{--color:red}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, []);
@@ -85,7 +92,7 @@ vows.describe(extractProperties)
     },
     'one property - block variable': {
       'topic': function () {
-        return extractProperties(tokenize('#one span{--color:{color:red;display:block};}', {})[0]);
+        return extractProperties(_tokenize('#one span{--color:{color:red;display:block};}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, []);
@@ -93,7 +100,7 @@ vows.describe(extractProperties)
     },
     'one property - complex selector': {
       'topic': function () {
-        return extractProperties(tokenize('.one{color:red}', {})[0]);
+        return extractProperties(_tokenize('.one{color:red}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, [
@@ -111,7 +118,7 @@ vows.describe(extractProperties)
     },
     'two properties': {
       'topic': function () {
-        return extractProperties(tokenize('a{color:red;display:block}', {})[0]);
+        return extractProperties(_tokenize('a{color:red;display:block}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, [
@@ -138,7 +145,7 @@ vows.describe(extractProperties)
     },
     'from @media': {
       'topic': function () {
-        return extractProperties(tokenize('@media{a{color:red;display:block}p{color:red}}', {})[0]);
+        return extractProperties(_tokenize('@media{a{color:red;display:block}p{color:red}}')[0]);
       },
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, [
@@ -177,7 +184,7 @@ vows.describe(extractProperties)
     'name root special cases': {
       'vendor prefix': {
         'topic': function () {
-          return extractProperties(tokenize('a{-moz-transform:none}', {})[0]);
+          return extractProperties(_tokenize('a{-moz-transform:none}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -195,7 +202,7 @@ vows.describe(extractProperties)
       },
       'list-style': {
         'topic': function () {
-          return extractProperties(tokenize('a{list-style:none}', {})[0]);
+          return extractProperties(_tokenize('a{list-style:none}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -213,7 +220,7 @@ vows.describe(extractProperties)
       },
       'border-radius': {
         'topic': function () {
-          return extractProperties(tokenize('a{border-top-left-radius:none}', {})[0]);
+          return extractProperties(_tokenize('a{border-top-left-radius:none}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -231,7 +238,7 @@ vows.describe(extractProperties)
       },
       'vendor prefixed border-radius': {
         'topic': function () {
-          return extractProperties(tokenize('a{-webkit-border-top-left-radius:none}', {})[0]);
+          return extractProperties(_tokenize('a{-webkit-border-top-left-radius:none}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -249,7 +256,7 @@ vows.describe(extractProperties)
       },
       'border-image-width': {
         'topic': function () {
-          return extractProperties(tokenize('a{border-image-width:2px}', {})[0]);
+          return extractProperties(_tokenize('a{border-image-width:2px}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -267,7 +274,7 @@ vows.describe(extractProperties)
       },
       'border-color': {
         'topic': function () {
-          return extractProperties(tokenize('a{border-color:red}', {})[0]);
+          return extractProperties(_tokenize('a{border-color:red}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -285,7 +292,7 @@ vows.describe(extractProperties)
       },
       'border-top-style': {
         'topic': function () {
-          return extractProperties(tokenize('a{border-top-style:none}', {})[0]);
+          return extractProperties(_tokenize('a{border-top-style:none}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -303,7 +310,7 @@ vows.describe(extractProperties)
       },
       'border-top': {
         'topic': function () {
-          return extractProperties(tokenize('a{border-top:none}', {})[0]);
+          return extractProperties(_tokenize('a{border-top:none}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -321,7 +328,7 @@ vows.describe(extractProperties)
       },
       'border-collapse': {
         'topic': function () {
-          return extractProperties(tokenize('a{border-collapse:collapse}', {})[0]);
+          return extractProperties(_tokenize('a{border-collapse:collapse}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
@@ -339,7 +346,7 @@ vows.describe(extractProperties)
       },
       'text-shadow': {
         'topic': function () {
-          return extractProperties(tokenize('a{text-shadow:none}', {})[0]);
+          return extractProperties(_tokenize('a{text-shadow:none}')[0]);
         },
         'has no properties': function (tokens) {
           assert.deepEqual(tokens, [
index 5a291ef..ff5ad9b 100644 (file)
@@ -2,12 +2,21 @@ var vows = require('vows');
 var assert = require('assert');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
+var inputSourceMapTracker = require('../../lib/utils/input-source-map-tracker');
 var extractProperties = require('../../lib/optimizer/extract-properties');
 var canReorder = require('../../lib/optimizer/reorderable').canReorder;
 var canReorderSingle = require('../../lib/optimizer/reorderable').canReorderSingle;
 
 function propertiesIn(source) {
-  return extractProperties(tokenize(source, { options: {} })[0]);
+  return extractProperties(
+    tokenize(
+      source,
+      {
+        inputSourceMapTracker: inputSourceMapTracker(),
+        options: {}
+      }
+    )[0]
+  );
 }
 
 vows.describe(canReorder)
index eb1f55c..a13ada4 100644 (file)
@@ -4,11 +4,13 @@ var assert = require('assert');
 var optimize = require('../../lib/properties/optimizer');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
+var inputSourceMapTracker = require('../../lib/utils/input-source-map-tracker');
 var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
 function _optimize(source) {
   var tokens = tokenize(source, {
+    inputSourceMapTracker: inputSourceMapTracker(),
     options: {},
     warnings: []
   });
index ed5d693..05425a7 100644 (file)
@@ -4,6 +4,7 @@ var assert = require('assert');
 var optimize = require('../../lib/properties/optimizer');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
+var inputSourceMapTracker = require('../../lib/utils/input-source-map-tracker');
 var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
@@ -12,6 +13,7 @@ function _optimize(source, mergeAdjacent, aggressiveMerging, compatibilityOption
   var validator = new Validator(compat);
 
   var tokens = tokenize(source, {
+    inputSourceMapTracker: inputSourceMapTracker(),
     options: {},
     warnings: []
   });
index d75d9d0..a43c790 100644 (file)
@@ -4,11 +4,13 @@ var assert = require('assert');
 var optimize = require('../../lib/properties/optimizer');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
+var inputSourceMapTracker = require('../../lib/utils/input-source-map-tracker');
 var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
 function _optimize(source, compat, aggressiveMerging) {
   var tokens = tokenize(source, {
+    inputSourceMapTracker: inputSourceMapTracker(),
     options: {},
     warnings: []
   });
index 172e20a..9980303 100644 (file)
@@ -4,11 +4,13 @@ var assert = require('assert');
 var optimize = require('../../lib/properties/optimizer');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
+var inputSourceMapTracker = require('../../lib/utils/input-source-map-tracker');
 var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
 function _optimize(source) {
   var tokens = tokenize(source, {
+    inputSourceMapTracker: inputSourceMapTracker(),
     options: {},
     warnings: []
   });
@@ -35,7 +37,16 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'background'],
+            ['property-name', 'background', [
+              [1, 2, undefined],
+              [1, 24, undefined],
+              [1, 56, undefined],
+              [1, 81, undefined],
+              [1, 105, undefined],
+              [1, 134, undefined],
+              [1, 155, undefined],
+              [1, 185, undefined]
+            ]],
             ['property-value', 'url(image.png)', [[1, 41, undefined]]],
             ['property-value', '#111', [[1, 19, undefined]]]
           ]
@@ -50,7 +61,16 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'background'],
+            ['property-name', 'background', [
+              [1, 2, undefined],
+              [1, 24, undefined],
+              [1, 56, undefined],
+              [1, 84, undefined],
+              [1, 108, undefined],
+              [1, 137, undefined],
+              [1, 158, undefined],
+              [1, 188, undefined]
+            ]],
             ['property-value', 'url(image.png)', [[1, 41, undefined]]],
             ['property-value', 'no-repeat', [[1, 74, undefined]]],
             ['property-value', '#111', [[1, 19, undefined]]]
@@ -66,7 +86,16 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'background'],
+            ['property-name', 'background', [
+              [1, 2, undefined],
+              [1, 34, undefined],
+              [1, 76, undefined],
+              [1, 111, undefined],
+              [1, 145, undefined],
+              [1, 184, undefined],
+              [1, 215, undefined],
+              [1, 255, undefined]
+            ]],
             ['property-value', 'url(image.png)', [[1, 51, undefined]]],
             ['property-value', '#111!important', [[1, 19, undefined]]]
           ]
@@ -81,7 +110,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'border-width'],
+            ['property-name', 'border-width', [
+              [1, 2, undefined],
+              [1, 23, undefined],
+              [1, 47, undefined],
+              [1, 69, undefined]
+            ]],
             ['property-value', '7px', [[1, 19, undefined]]],
             ['property-value', '4px', [[1, 88, undefined]]]
           ]
@@ -96,7 +130,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'border-color'],
+            ['property-name', 'border-color', [
+              [1, 2, undefined],
+              [1, 27, undefined],
+              [1, 55, undefined],
+              [1, 81, undefined]
+            ]],
             ['property-value', '#9fce00', [[1, 19, undefined]]]
           ]
         ]);
@@ -110,7 +149,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'border-color'],
+            ['property-name', 'border-color', [
+              [1, 2, undefined],
+              [1, 26, undefined],
+              [1, 51, undefined],
+              [1, 73, undefined]
+            ]],
             ['property-value', '#001', [[1, 68, undefined]]],
             ['property-value', '#002', [[1, 21, undefined]]],
             ['property-value', '#003', [[1, 46, undefined]]],
@@ -127,7 +171,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'border-radius'],
+            ['property-name', 'border-radius', [
+              [1, 2, undefined],
+              [1, 29, undefined],
+              [1, 60, undefined],
+              [1, 90, undefined]
+            ]],
             ['property-value', '7px', [[1, 25, undefined]]],
             ['property-value', '3px', [[1, 114, undefined]]],
             ['property-value', '6px', [[1, 56, undefined]]],
@@ -160,7 +209,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'border-radius'],
+            ['property-name', 'border-radius', [
+              [1, 2, undefined],
+              [1, 33, undefined],
+              [1, 65, undefined],
+              [1, 100, undefined]
+            ]],
             ['property-value', '7px', [[1, 25, undefined]]],
             ['property-value', '/'],
             ['property-value', '3px', [[1, 29, undefined]]]
@@ -176,7 +230,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'border-radius'],
+            ['property-name', 'border-radius', [
+              [1, 2, undefined],
+              [1, 33, undefined],
+              [1, 65, undefined],
+              [1, 100, undefined]
+            ]],
             ['property-value', '7px', [[1, 25, undefined]]],
             ['property-value', '6px', [[1, 57, undefined]]],
             ['property-value', '5px', [[1, 92, undefined]]],
@@ -214,7 +273,11 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'list-style'],
+            ['property-name', 'list-style', [
+              [1, 2, undefined],
+              [1, 25, undefined],
+              [1, 53, undefined]
+            ]],
             ['property-value', 'circle', [[1, 18, undefined]]],
             ['property-value', 'url(image.png)', [[1, 70, undefined]]]
           ]
@@ -229,7 +292,11 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'list-style'],
+            ['property-name', 'list-style', [
+              [1, 2, undefined],
+              [1, 34, undefined],
+              [1, 57, undefined]
+            ]],
             ['property-value', 'circle', [[1, 50, undefined]]],
             ['property-value', 'inside', [[1, 77, undefined]]],
             ['property-value', 'url(image.png)', [[1, 19, undefined]]]
@@ -245,7 +312,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'margin'],
+            ['property-name', 'margin', [
+              [1, 2, undefined],
+              [1, 18, undefined],
+              [1, 35, undefined],
+              [1, 53, undefined]
+            ]],
             ['property-value', '10px', [[1, 13, undefined]]],
             ['property-value', '5px', [[1, 31, undefined]]],
             ['property-value', '3px', [[1, 49, undefined]]],
@@ -262,7 +334,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'padding'],
+            ['property-name', 'padding', [
+              [1, 2, undefined],
+              [1, 19, undefined],
+              [1, 36, undefined],
+              [1, 55, undefined]
+            ]],
             ['property-value', '10px', [[1, 14, undefined]]],
             ['property-value', '2px', [[1, 69, undefined]]],
             ['property-value', '3px', [[1, 51, undefined]]],
@@ -279,7 +356,12 @@ vows.describe(optimize)
         assert.deepEqual(properties, [
           [
             'property',
-            ['property-name', 'padding'],
+            ['property-name', 'padding', [
+              [1, 2, undefined],
+              [1, 34, undefined],
+              [1, 67, undefined],
+              [1, 104, undefined]
+            ]],
             ['property-value', '10px', [[1, 14, undefined]]],
             ['property-value', '2px', [[1, 118, undefined]]],
             ['property-value', '3px', [[1, 82, undefined]]],
@@ -287,7 +369,12 @@ vows.describe(optimize)
           ],
           [
             'property',
-            ['property-name', 'margin'],
+            ['property-name', 'margin', [
+              [1, 19, undefined],
+              [1, 51, undefined],
+              [1, 86, undefined],
+              [1, 122, undefined]
+            ]],
             ['property-value', '3px', [[1, 30, undefined]]]
           ]
         ]);
@@ -311,7 +398,12 @@ vows.describe(optimize)
           ],
           [
             'property',
-            ['property-name', 'padding'],
+            ['property-name', 'padding', [
+              [1, 2, undefined],
+              [1, 19, undefined],
+              [1, 36, undefined],
+              [1, 65, undefined]
+            ]],
             ['property-value', '10px', [[1, 14, undefined]]],
             ['property-value', '2px', [[1, 79, undefined]]],
             ['property-value', '3px', [[1, 51, undefined]]],
index ad28bbf..6f347dc 100644 (file)
@@ -168,7 +168,7 @@ vows.describe('protocol imports').addBatch({
     },
     'should not raise errors': function (errors, minified) {
       assert.lengthOf(errors, 1);
-      assert.equal(errors[0], 'Broken @import declaration of "http://127.0.0.1/missing.css" - error 404');
+      assert.equal(errors[0], 'Broken @import declaration of "http://127.0.0.1/missing.css" - 404');
     },
     'should process @import': function (errors, minified) {
       assert.equal(minified.styles, '@import url(http://127.0.0.1/missing.css);p{font-size:13px}a{color:red}');
@@ -512,7 +512,7 @@ vows.describe('protocol imports').addBatch({
     },
     'should raise warnings': function (error, minified) {
       assert.lengthOf(minified.warnings, 1);
-      assert.equal(minified.warnings[0], 'Skipping remote @import of "http://127.0.0.1/skipped.css" as resource not allowed.');
+      assert.equal(minified.warnings[0], 'Skipping remote @import of "http://127.0.0.1/skipped.css" as resource is not allowed.');
     },
     'should keep imports': function (error, minified) {
       assert.equal(minified.styles, '@import url(http://127.0.0.1/skipped.css);.one{color:red}');
@@ -795,8 +795,8 @@ vows.describe('protocol imports').addBatch({
     },
     'should raise warnings': function (error, minified) {
       assert.lengthOf(minified.warnings, 2);
-      assert.equal(minified.warnings[0], 'Skipping remote @import of "http://127.0.0.1/remote.css" as resource not allowed.');
-      assert.equal(minified.warnings[1], 'Skipping remote @import of "http://assets.127.0.0.1/remote.css" as resource not allowed.');
+      assert.equal(minified.warnings[0], 'Skipping remote @import of "http://127.0.0.1/remote.css" as resource is not allowed.');
+      assert.equal(minified.warnings[1], 'Skipping remote @import of "http://assets.127.0.0.1/remote.css" as resource is not allowed.');
     },
     'should keeps imports': function (error, minified) {
       assert.equal(minified.styles, '@import url(http://127.0.0.1/remote.css);@import url(http://assets.127.0.0.1/remote.css);.one{color:red}');
@@ -864,9 +864,9 @@ vows.describe('protocol imports').addBatch({
     },
     'should raise a warning': function (error, minified) {
       assert.lengthOf(minified.warnings, 3);
-      assert.equal(minified.warnings[0], 'Skipping remote @import of "http://127.0.0.1/remote.css" as resource not allowed.');
-      assert.equal(minified.warnings[1], 'Skipping remote @import of "http://assets.127.0.0.1/remote.css" as resource not allowed.');
-      assert.equal(minified.warnings[2], 'Skipping local @import of "test/fixtures/partials/one.css" as resource not allowed.');
+      assert.equal(minified.warnings[0], 'Skipping remote @import of "http://127.0.0.1/remote.css" as resource is not allowed.');
+      assert.equal(minified.warnings[1], 'Skipping remote @import of "http://assets.127.0.0.1/remote.css" as resource is not allowed.');
+      assert.equal(minified.warnings[2], 'Skipping local @import of "test/fixtures/partials/one.css" as resource is not allowed.');
     },
     'should process first imports': function (error, minified) {
       assert.equal(minified.styles, '@import url(http://127.0.0.1/remote.css);@import url(http://assets.127.0.0.1/remote.css);@import url(test/fixtures/partials/one.css);');
@@ -882,7 +882,7 @@ vows.describe('protocol imports').addBatch({
     },
     'should raise a warning': function (error, minified) {
       assert.lengthOf(minified.warnings, 1);
-      assert.equal(minified.warnings[0], 'Skipping remote @import of "//127.0.0.1/remote.css" as resource not allowed.');
+      assert.equal(minified.warnings[0], 'Skipping remote @import of "//127.0.0.1/remote.css" as resource is not allowed.');
     },
     'should process first imports': function (error, minified) {
       assert.equal(minified.styles, '@import url(//127.0.0.1/remote.css);.one{color:red}');
index b1ba4a5..f648367 100644 (file)
@@ -1,20 +1,19 @@
 /* jshint unused: false */
 
-var vows = require('vows');
 var assert = require('assert');
-var CleanCSS = require('../index');
-
 var fs = require('fs');
+var http = require('http');
 var path = require('path');
-var inputMapPath = path.join('test', 'fixtures', 'source-maps', 'styles.css.map');
-var inputMap = fs.readFileSync(inputMapPath, 'utf-8');
 
-var nock = require('nock');
-var http = require('http');
 var enableDestroy = require('server-destroy');
-
+var nock = require('nock');
+var vows = require('vows');
 var port = 24682;
 
+var CleanCSS = require('../index');
+
+var inputMapPath = path.join('test', 'fixtures', 'source-maps', 'styles.css.map');
+var inputMap = fs.readFileSync(inputMapPath, 'utf-8');
 var lineBreak = require('os').EOL;
 var escape = global.escape;
 
@@ -91,7 +90,18 @@ vows.describe('source-map')
         return new CleanCSS({ sourceMap: true }).minify('/*! a */div[data-id=" abc "] { color:red; }');
       },
       'has 3 mappings': function (minified) {
-        assert.lengthOf(minified.sourceMap._mappings._array, 3);
+        assert.lengthOf(minified.sourceMap._mappings._array, 4);
+      },
+      'has comment mapping': function (minified) {
+        var mapping = {
+          generatedLine: 1,
+          generatedColumn: 0,
+          originalLine: 1,
+          originalColumn: 0,
+          source: '$stdin',
+          name: null
+        };
+        assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
       },
       'has selector mapping': function (minified) {
         var mapping = {
@@ -102,7 +112,7 @@ vows.describe('source-map')
           source: '$stdin',
           name: null
         };
-        assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
+        assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
       },
       'has name mapping': function (minified) {
         var mapping = {
@@ -113,7 +123,7 @@ vows.describe('source-map')
           source: '$stdin',
           name: null
         };
-        assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+        assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
       },
       'has value mapping': function (minified) {
         var mapping = {
@@ -124,7 +134,7 @@ vows.describe('source-map')
           source: '$stdin',
           name: null
         };
-        assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+        assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
       }
     },
     'module #2': {
@@ -479,7 +489,7 @@ vows.describe('source-map')
           generatedColumn: 0,
           originalLine: 1,
           originalColumn: 4,
-          source: 'test/fixtures/source-maps/styles.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
@@ -490,7 +500,7 @@ vows.describe('source-map')
           generatedColumn: 6,
           originalLine: 2,
           originalColumn: 2,
-          source: 'test/fixtures/source-maps/styles.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
@@ -501,15 +511,15 @@ vows.describe('source-map')
           generatedColumn: 12,
           originalLine: 2,
           originalColumn: 2,
-          source: 'test/fixtures/source-maps/styles.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
       }
     },
-    'input map from source with root': {
+    'input map from source with rebaseTo': {
       'topic': function () {
-        return new CleanCSS({ sourceMap: true, root: './test/fixtures' }).minify('div > a {\n  color: red;\n}/*# sourceMappingURL=source-maps/styles.css.map */');
+        return new CleanCSS({ sourceMap: true, rebaseTo: './test/fixtures' }).minify('div > a {\n  color: red;\n}/*# sourceMappingURL=' + inputMapPath + ' */');
       },
       'has 3 mappings': function (minified) {
         assert.lengthOf(minified.sourceMap._mappings._array, 3);
@@ -520,7 +530,7 @@ vows.describe('source-map')
           generatedColumn: 0,
           originalLine: 1,
           originalColumn: 4,
-          source: 'source-maps/styles.less',
+          source: path.join('source-maps', 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
@@ -531,7 +541,7 @@ vows.describe('source-map')
           generatedColumn: 6,
           originalLine: 2,
           originalColumn: 2,
-          source: 'source-maps/styles.less',
+          source: path.join('source-maps', 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
@@ -542,48 +552,7 @@ vows.describe('source-map')
           generatedColumn: 12,
           originalLine: 2,
           originalColumn: 2,
-          source: 'source-maps/styles.less',
-          name: null
-        };
-        assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
-      }
-    },
-    'input map from source with target': {
-      'topic': function () {
-        return new CleanCSS({ sourceMap: true, target: './test' }).minify('div > a {\n  color: red;\n}/*# sourceMappingURL=' + inputMapPath + ' */');
-      },
-      'has 3 mappings': function (minified) {
-        assert.lengthOf(minified.sourceMap._mappings._array, 3);
-      },
-      'has `div > a` mapping': function (minified) {
-        var mapping = {
-          generatedLine: 1,
-          generatedColumn: 0,
-          originalLine: 1,
-          originalColumn: 4,
-          source: 'fixtures/source-maps/styles.less',
-          name: null
-        };
-        assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
-      },
-      'has `color` mapping': function (minified) {
-        var mapping = {
-          generatedLine: 1,
-          generatedColumn: 6,
-          originalLine: 2,
-          originalColumn: 2,
-          source: 'fixtures/source-maps/styles.less',
-          name: null
-        };
-        assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
-      },
-      'has `red` mapping': function (minified) {
-        var mapping = {
-          generatedLine: 1,
-          generatedColumn: 12,
-          originalLine: 2,
-          originalColumn: 2,
-          source: 'fixtures/source-maps/styles.less',
+          source: path.join('source-maps', 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
@@ -598,7 +567,7 @@ vows.describe('source-map')
     'input map as inlined data URI with explicit charset us-ascii, not base64': inlineDataUriContext('data:application/json;charset=us-ascii,' + escape(inputMap)),
     'complex input map': {
       'topic': function () {
-        return new CleanCSS({ sourceMap: true, root: path.dirname(inputMapPath) }).minify('@import url(import.css);');
+        return new CleanCSS({ sourceMap: true }).minify('@import url(' + path.dirname(inputMapPath) + '/import.css);');
       },
       'has 6 mappings': function (minified) {
         assert.lengthOf(minified.sourceMap._mappings._array, 6);
@@ -609,7 +578,7 @@ vows.describe('source-map')
           generatedColumn: 0,
           originalLine: 1,
           originalColumn: 0,
-          source: 'some.less',
+          source: path.join(path.dirname(inputMapPath), 'some.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
@@ -620,7 +589,7 @@ vows.describe('source-map')
           generatedColumn: 4,
           originalLine: 2,
           originalColumn: 2,
-          source: 'some.less',
+          source: path.join(path.dirname(inputMapPath), 'some.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
@@ -631,7 +600,7 @@ vows.describe('source-map')
           generatedColumn: 10,
           originalLine: 2,
           originalColumn: 2,
-          source: 'some.less',
+          source: path.join(path.dirname(inputMapPath), 'some.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
@@ -642,7 +611,7 @@ vows.describe('source-map')
           generatedColumn: 14,
           originalLine: 1,
           originalColumn: 4,
-          source: 'styles.less',
+          source: path.join(path.dirname(inputMapPath), 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
@@ -653,7 +622,7 @@ vows.describe('source-map')
           generatedColumn: 20,
           originalLine: 2,
           originalColumn: 2,
-          source: 'styles.less',
+          source: path.join(path.dirname(inputMapPath), 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
@@ -664,20 +633,12 @@ vows.describe('source-map')
           generatedColumn: 26,
           originalLine: 2,
           originalColumn: 2,
-          source: 'styles.less',
+          source: path.join(path.dirname(inputMapPath), 'styles.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[5], mapping);
       }
     },
-    'complex input map referenced by path': {
-      'topic': function () {
-        return new CleanCSS({ sourceMap: true }).minify('@import url(test/fixtures/source-maps/import.css);');
-      },
-      'has 6 mappings': function (minified) {
-        assert.lengthOf(minified.sourceMap._mappings._array, 6);
-      }
-    },
     'complex but partial input map referenced by path': {
       'topic': function () {
         return new CleanCSS({ sourceMap: true }).minify('@import url(test/fixtures/source-maps/no-map-import.css);');
@@ -687,20 +648,20 @@ vows.describe('source-map')
       },
       'has 3 mappings to .less file': function (minified) {
         var fromLess = minified.sourceMap._mappings._array.filter(function (mapping) {
-          return mapping.source == 'test/fixtures/source-maps/styles.less';
+          return mapping.source == path.join('test', 'fixtures', 'source-maps', 'styles.less');
         });
         assert.lengthOf(fromLess, 3);
       },
       'has 3 mappings to .css file': function (minified) {
         var fromCSS = minified.sourceMap._mappings._array.filter(function (mapping) {
-          return mapping.source == 'test/fixtures/source-maps/no-map.css';
+          return mapping.source == path.join('test', 'fixtures', 'source-maps', 'no-map.css');
         });
         assert.lengthOf(fromCSS, 3);
       }
     },
-    'complex input map with an existing file as target': {
+    'complex input map with an existing file as rebaseTo': {
       'topic': function () {
-        return new CleanCSS({ sourceMap: true, target: path.join('test', 'fixtures', 'source-maps', 'styles.css') }).minify('@import url(test/fixtures/source-maps/styles.css);');
+        return new CleanCSS({ sourceMap: true, rebaseTo: path.join('test', 'fixtures', 'source-maps') }).minify('@import url(test/fixtures/source-maps/styles.css);');
       },
       'has 3 mappings': function (minified) {
         assert.lengthOf(minified.sourceMap._mappings._array, 3);
@@ -725,7 +686,7 @@ vows.describe('source-map')
           generatedColumn: 0,
           originalLine: 2,
           originalColumn: 8,
-          source: 'test/fixtures/source-maps/nested/once.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'nested/once.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
@@ -736,7 +697,7 @@ vows.describe('source-map')
           generatedColumn: 14,
           originalLine: 3,
           originalColumn: 4,
-          source: 'test/fixtures/source-maps/nested/once.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'nested/once.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
@@ -747,7 +708,7 @@ vows.describe('source-map')
           generatedColumn: 20,
           originalLine: 3,
           originalColumn: 4,
-          source: 'test/fixtures/source-maps/nested/once.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'nested/once.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
@@ -766,7 +727,7 @@ vows.describe('source-map')
           generatedColumn: 0,
           originalLine: 3,
           originalColumn: 4,
-          source: 'test/fixtures/source-maps/nested/twice.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'nested/twice.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
@@ -777,7 +738,7 @@ vows.describe('source-map')
           generatedColumn: 11,
           originalLine: 4,
           originalColumn: 6,
-          source: 'test/fixtures/source-maps/nested/twice.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'nested/twice.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
@@ -788,7 +749,7 @@ vows.describe('source-map')
           generatedColumn: 17,
           originalLine: 4,
           originalColumn: 6,
-          source: 'test/fixtures/source-maps/nested/twice.less',
+          source: path.join('test', 'fixtures', 'source-maps', 'nested/twice.less'),
           name: null
         };
         assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
@@ -935,9 +896,12 @@ vows.describe('source-map')
       'has mapping': function (errors, minified) {
         assert.isDefined(minified.sourceMap);
       },
-      'raises an error': function (errors, _) {
-        assert.lengthOf(errors, 1);
-        assert.equal(errors[0], 'Broken source map at "http://127.0.0.1/remote.css.map" - 404');
+      'raises no errors': function (errors, _) {
+        assert.isNull(errors);
+      },
+      'raises a warning': function (_, minified) {
+        assert.lengthOf(minified.warnings, 1);
+        assert.equal(minified.warnings[0], 'Missing source map at "http://127.0.0.1/remote.css.map" - 404');
       },
       teardown: function () {
         assert.isTrue(this.reqMocks.isDone());
@@ -971,9 +935,12 @@ vows.describe('source-map')
       'has mapping': function (errors, minified) {
         assert.isDefined(minified.sourceMap);
       },
-      'raises an error': function (errors, _) {
-        assert.lengthOf(errors, 1);
-        assert.include(errors[0], 'Broken source map at "http://127.0.0.1:' + port + '/remote.css.map"');
+      'raises no errors': function (errors, _) {
+        assert.isNull(errors);
+      },
+      'raises a warning': function (_, minified) {
+        assert.lengthOf(minified.warnings, 1);
+        assert.equal(minified.warnings[0], 'Missing source map at "http://127.0.0.1:24682/remote.css.map" - timeout');
       },
       teardown: function () {
         this.server.destroy();
@@ -1005,7 +972,7 @@ vows.describe('source-map')
       topic: function () {
         this.reqMocks = nock('https://127.0.0.1')
           .get('/remote.css')
-          .reply(200, '/*# sourceMappingURL=https://127.0.0.1/remote.css.map */')
+          .reply(200, 'div>a{color:blue}/*# sourceMappingURL=https://127.0.0.1/remote.css.map */')
           .get('/remote.css.map')
           .reply(200, inputMap);
 
@@ -1014,6 +981,9 @@ vows.describe('source-map')
       'has mapping': function (errors, minified) {
         assert.isDefined(minified.sourceMap);
       },
+      'maps to external source file': function (errors, minified) {
+        assert.equal(minified.sourceMap._mappings._array[0].source, 'https://127.0.0.1/styles.less');
+      },
       teardown: function () {
         assert.isTrue(this.reqMocks.isDone());
         nock.cleanAll();
@@ -1023,7 +993,7 @@ vows.describe('source-map')
       topic: function () {
         this.reqMocks = nock('http://127.0.0.1')
           .get('/remote.css')
-          .reply(200, '/*# sourceMappingURL=remote.css.map */')
+          .reply(200, 'div>a{color:blue}/*# sourceMappingURL=remote.css.map */')
           .get('/remote.css.map')
           .reply(200, inputMap);
 
@@ -1032,6 +1002,9 @@ vows.describe('source-map')
       'has mapping': function (errors, minified) {
         assert.isDefined(minified.sourceMap);
       },
+      'maps to external source file': function (errors, minified) {
+        assert.equal(minified.sourceMap._mappings._array[0].source, 'http://127.0.0.1/styles.less');
+      },
       teardown: function () {
         assert.isTrue(this.reqMocks.isDone());
         nock.cleanAll();
@@ -1041,7 +1014,7 @@ vows.describe('source-map')
       topic: function () {
         this.reqMocks = nock('http://127.0.0.1')
           .post('/remote.css')
-          .reply(200, '/*# sourceMappingURL=remote.css.map */')
+          .reply(200, 'div>a{color:blue}/*# sourceMappingURL=remote.css.map */')
           .post('/remote.css.map')
           .reply(200, inputMap);
 
@@ -1051,6 +1024,9 @@ vows.describe('source-map')
       'has mapping': function (errors, minified) {
         assert.isDefined(minified.sourceMap);
       },
+      'maps to external source file': function (errors, minified) {
+        assert.equal(minified.sourceMap._mappings._array[0].source, 'http://127.0.0.1/styles.less');
+      },
       teardown: function () {
         assert.isTrue(this.reqMocks.isDone());
         nock.cleanAll();
@@ -1087,7 +1063,7 @@ vows.describe('source-map')
         return new CleanCSS({ sourceMap: true, keepSpecialComments: 1 }).minify('div { color: #f00 !important; /*!1*/} /*!2*/ a{/*!3*/}');
       },
       'has right output': function (errors, minified) {
-        assert.equal(minified.styles, 'div{color:red!important/*!1*/}a{}');
+        assert.equal(minified.styles, 'div{color:red!important/*!1*/}');
       }
     }
   })
@@ -1122,7 +1098,7 @@ vows.describe('source-map')
             generatedColumn: 0,
             originalLine: 1,
             originalColumn: 0,
-            source: 'test/fixtures/source-maps/some.less',
+            source: path.join('test', 'fixtures', 'source-maps', 'some.less'),
             name: null
           };
           assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
@@ -1133,7 +1109,7 @@ vows.describe('source-map')
             generatedColumn: 4,
             originalLine: 2,
             originalColumn: 8,
-            source: 'test/fixtures/source-maps/nested/once.less',
+            source: path.join('test', 'fixtures', 'source-maps', 'nested/once.less'),
             name: null
           };
           assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
@@ -1144,7 +1120,7 @@ vows.describe('source-map')
             generatedColumn: 18,
             originalLine: 2,
             originalColumn: 2,
-            source: 'test/fixtures/source-maps/some.less',
+            source: path.join('test', 'fixtures', 'source-maps', 'some.less'),
             name: null
           };
           assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
@@ -1155,7 +1131,7 @@ vows.describe('source-map')
             generatedColumn: 24,
             originalLine: 2,
             originalColumn: 2,
-            source: 'test/fixtures/source-maps/some.less',
+            source: path.join('test', 'fixtures', 'source-maps', 'some.less'),
             name: null
           };
           assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
@@ -1166,7 +1142,7 @@ vows.describe('source-map')
             generatedColumn: 28,
             originalLine: 1,
             originalColumn: 4,
-            source: 'test/fixtures/source-maps/styles.less',
+            source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
             name: null
           };
           assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
@@ -1177,7 +1153,7 @@ vows.describe('source-map')
             generatedColumn: 34,
             originalLine: 2,
             originalColumn: 2,
-            source: 'test/fixtures/source-maps/styles.less',
+            source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
             name: null
           };
           assert.deepEqual(minified.sourceMap._mappings._array[5], mapping);
@@ -1188,7 +1164,7 @@ vows.describe('source-map')
             generatedColumn: 40,
             originalLine: 2,
             originalColumn: 2,
-            source: 'test/fixtures/source-maps/styles.less',
+            source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
             name: null
           };
           assert.deepEqual(minified.sourceMap._mappings._array[6], mapping);
@@ -1198,7 +1174,7 @@ vows.describe('source-map')
     'relative to path': {
       'complex but partial input map referenced by path': {
         'topic': function () {
-          return new CleanCSS({ sourceMap: true, target: './test' }).minify({
+          return new CleanCSS({ sourceMap: true, rebaseTo: './test' }).minify({
             'test/fixtures/source-maps/some.css': {
               styles: 'div {\n  color: red;\n}',
               sourceMap: '{"version":3,"sources":["some.less"],"names":[],"mappings":"AAAA;EACE,UAAA","file":"some.css"}'
@@ -1224,9 +1200,9 @@ vows.describe('source-map')
           });
 
           assert.deepEqual(sources, [
-            'fixtures/source-maps/some.less',
-            'fixtures/source-maps/nested/once.less',
-            'fixtures/source-maps/styles.less'
+            path.join('fixtures', 'source-maps', 'some.less'),
+            path.join('fixtures', 'source-maps', 'nested', 'once.less'),
+            path.join('fixtures', 'source-maps', 'styles.less')
           ]);
         }
       }
@@ -1298,8 +1274,8 @@ vows.describe('source-map')
       'from array - off': {
         'topic': function () {
           return new CleanCSS({ sourceMap: true }).minify([
-            'test/fixtures/partials/one.css',
-            'test/fixtures/partials/three.css'
+            path.join('test', 'fixtures', 'partials', 'one.css'),
+            path.join('test', 'fixtures', 'partials', 'three.css')
           ]);
         },
         'has 6 mappings': function (minified) {
@@ -1307,8 +1283,8 @@ vows.describe('source-map')
         },
         'has embedded sources': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
-            'test/fixtures/partials/one.css',
-            'test/fixtures/partials/three.css'
+            path.join('test', 'fixtures', 'partials', 'one.css'),
+            path.join('test', 'fixtures', 'partials', 'three.css')
           ]);
         },
         'has embedded sources content': function (minified) {
@@ -1318,8 +1294,8 @@ vows.describe('source-map')
       'from array - on': {
         'topic': function () {
           return new CleanCSS({ sourceMap: true, sourceMapInlineSources: true }).minify([
-            'test/fixtures/partials/one.css',
-            'test/fixtures/partials/three.css'
+            path.join('test', 'fixtures', 'partials', 'one.css'),
+            path.join('test', 'fixtures', 'partials', 'three.css')
           ]);
         },
         'has 6 mappings': function (minified) {
@@ -1327,14 +1303,14 @@ vows.describe('source-map')
         },
         'has embedded sources': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
-            'test/fixtures/partials/one.css',
-            'test/fixtures/partials/three.css'
+            path.join('test', 'fixtures', 'partials', 'one.css'),
+            path.join('test', 'fixtures', 'partials', 'three.css')
           ]);
         },
         'has embedded sources content': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
-            '.one { color:#f00; }' + lineBreak,
-            '.three {background-image: url(test/fixtures/partials/extra/down.gif);}' + lineBreak
+            '.one { color:#f00; }\n',
+            '.three {background-image: url(extra/down.gif);}\n'
           ]);
         }
       },
@@ -1358,7 +1334,7 @@ vows.describe('source-map')
         },
         'has embedded sources content': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
-            'div{background:url(http://127.0.0.1/image.png)}',
+            'div{background:url(image.png)}',
           ]);
         },
         'teardown': function () {
@@ -1502,9 +1478,9 @@ vows.describe('source-map')
         },
         'has embedded sources': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
-            'test/fixtures/source-maps/some.less',
-            'test/fixtures/source-maps/nested/once.less',
-            'test/fixtures/source-maps/styles.less'
+            path.join('test', 'fixtures', 'source-maps', 'some.less'),
+            path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
+            path.join('test', 'fixtures', 'source-maps', 'styles.less')
           ]);
         },
         'has embedded sources content': function (minified) {
@@ -1515,9 +1491,9 @@ vows.describe('source-map')
           ]);
         }
       },
-      'multiple relative to a target path': {
+      'multiple relative to rebaseTo path': {
         'topic': function () {
-          return new CleanCSS({ sourceMap: true, sourceMapInlineSources: true, target: path.join(process.cwd(), 'test') }).minify({
+          return new CleanCSS({ sourceMap: true, sourceMapInlineSources: true, rebaseTo: './test' }).minify({
             'test/fixtures/source-maps/some.css': {
               styles: 'div {\n  color: red;\n}',
               sourceMap: '{"version":3,"sources":["some.less"],"names":[],"mappings":"AAAA;EACE,UAAA","file":"some.css","sourcesContent":["div {\\n  color: red;\\n}\\n"]}'
@@ -1537,9 +1513,9 @@ vows.describe('source-map')
         },
         'has embedded sources': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
-            'fixtures/source-maps/some.less',
-            'fixtures/source-maps/nested/once.less',
-            'fixtures/source-maps/styles.less'
+            path.join('fixtures', 'source-maps', 'some.less'),
+            path.join('fixtures', 'source-maps', 'nested', 'once.less'),
+            path.join('fixtures', 'source-maps', 'styles.less')
           ]);
         },
         'has embedded sources content': function (minified) {
@@ -1572,16 +1548,16 @@ vows.describe('source-map')
         },
         'has embedded sources': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
-            'test/fixtures/source-maps/some.less',
-            'test/fixtures/source-maps/nested/once.less',
-            'test/fixtures/source-maps/styles.less'
+            path.join('test', 'fixtures', 'source-maps', 'some.less'),
+            path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
+            path.join('test', 'fixtures', 'source-maps', 'styles.less')
           ]);
         },
         'has embedded sources content': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
             'div {\n  color: red;\n}\n',
             'section {\n  > div a {\n    color:red;\n  }\n}\n',
-            'div > a {' + lineBreak + '  color: blue;' + lineBreak + '}' + lineBreak
+            'div > a {\n  color: blue;\n}\n'
           ]);
         }
       },
@@ -1607,9 +1583,9 @@ vows.describe('source-map')
         },
         'has embedded sources': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
-            'test/fixtures/source-maps/some.less',
-            'test/fixtures/source-maps/nested/once.less',
-            'test/fixtures/source-maps/styles.less'
+            path.join('test', 'fixtures', 'source-maps', 'some.less'),
+            path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
+            path.join('test', 'fixtures', 'source-maps', 'styles.less')
           ]);
         },
         'has embedded sources content': function (minified) {
@@ -1639,20 +1615,20 @@ vows.describe('source-map')
             }
           }, this.callback);
         },
-        'has 7 mappings': function (minified) {
+        'has 7 mappings': function (errors, minified) {
           assert.lengthOf(minified.sourceMap._mappings._array, 7);
         },
-        'has embedded sources': function (minified) {
+        'has embedded sources': function (errors, minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
             'http://127.0.0.1/some.less',
-            'test/fixtures/source-maps/nested/once.less',
+            path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
             'http://127.0.0.1/styles.less'
           ]);
         },
-        'has embedded sources content': function (minified) {
+        'has embedded sources content': function (errors, minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
             'div {\n  color: red;\n}\n',
-            'section {' + lineBreak + '  > div a {' + lineBreak + '    color:red;' + lineBreak + '  }' + lineBreak + '}' + lineBreak,
+            'section {\n  > div a {\n    color:red;\n  }\n}\n',
             'div > a {\n  color: blue;\n}\n'
           ]);
         },
@@ -1688,19 +1664,19 @@ vows.describe('source-map')
           assert.lengthOf(minified.sourceMap._mappings._array, 7);
         },
         'should warn about some.less': function (minified) {
-          assert.deepEqual(minified.warnings, ['Broken original source file at "http://127.0.0.1/some.less" - 404']);
+          assert.deepEqual(minified.warnings, ['Missing original source at "http://127.0.0.1/some.less" - 404']);
         },
         'has embedded sources': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
             'http://127.0.0.1/some.less',
-            'test/fixtures/source-maps/nested/once.less',
+            path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
             'http://127.0.0.1/styles.less'
           ]);
         },
         'has embedded sources content': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
             null,
-            'section {' + lineBreak + '  > div a {' + lineBreak + '    color:red;' + lineBreak + '  }' + lineBreak + '}' + lineBreak,
+            'section {\n  > div a {\n    color:red;\n  }\n}\n',
             'div > a {\n  color: blue;\n}\n'
           ]);
         },
@@ -1731,21 +1707,21 @@ vows.describe('source-map')
         },
         'should warn about some.less and styles.less': function (minified) {
           assert.deepEqual(minified.warnings, [
-            'No callback given to `#minify` method, cannot fetch a remote file from "http://127.0.0.1/some.less"',
-            'No callback given to `#minify` method, cannot fetch a remote file from "http://127.0.0.1/styles.less"'
+            'Cannot fetch remote resource from "http://127.0.0.1/some.less" as no callback given.',
+            'Cannot fetch remote resource from "http://127.0.0.1/styles.less" as no callback given.'
           ]);
         },
         'has embedded sources': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
             'http://127.0.0.1/some.less',
-            'test/fixtures/source-maps/nested/once.less',
+            path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
             'http://127.0.0.1/styles.less'
           ]);
         },
         'has embedded sources content': function (minified) {
           assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
             null,
-            'section {' + lineBreak + '  > div a {' + lineBreak + '    color:red;' + lineBreak + '  }' + lineBreak + '}' + lineBreak,
+            'section {\n  > div a {\n    color:red;\n  }\n}\n',
             null
           ]);
         }
index 14c175b..379a464 100644 (file)
@@ -1,7 +1,7 @@
 var vows = require('vows');
 var assert = require('assert');
 var tokenize = require('../../lib/tokenizer/tokenize');
-var inputSourceMapTracker = require('../../lib/utils/input-source-map-tracker-2');
+var inputSourceMapTracker = require('../../lib/utils/input-source-map-tracker');
 
 var fs = require('fs');
 var path = require('path');
@@ -20,6 +20,7 @@ function tokenizerContext(group, specs) {
   function toTokens(source) {
     return function () {
       return tokenize(source, {
+        inputSourceMapTracker: inputSourceMapTracker(),
         options: {},
         warnings: []
       });
@@ -3426,8 +3427,9 @@ vows.describe(tokenize)
         var warnings = [];
 
         tokenize('a{display:block', {
-          source: 'one.css',
+          inputSourceMapTracker: inputSourceMapTracker(),
           options: {},
+          source: 'one.css',
           warnings: warnings
         });
 
@@ -3442,8 +3444,9 @@ vows.describe(tokenize)
     'sources - rule with properties': {
       'topic': function () {
         return tokenize('a{color:red}', {
-          source: 'one.css',
+          inputSourceMapTracker: inputSourceMapTracker(),
           options: {},
+          source: 'one.css',
           warnings: []
         });
       },
@@ -3487,14 +3490,11 @@ vows.describe(tokenize)
   .addBatch({
     'input source maps - simple': {
       'topic': function () {
-        var sourceMapTracker = inputSourceMapTracker({
-          errors: {}
-        });
+        var sourceMapTracker = inputSourceMapTracker();
         sourceMapTracker.track('styles.css', inputMap);
 
         return tokenize('div > a {\n  color: red;\n}', {
           source: 'styles.css',
-          inputSourceMap: true,
           inputSourceMapTracker: sourceMapTracker,
           options: {},
           warnings: []
@@ -3509,7 +3509,7 @@ vows.describe(tokenize)
                 'rule-scope',
                 'div > a',
                 [
-                  [1, 0, 'styles.less']
+                  [1, 4, 'styles.less']
                 ]
               ]
             ],
@@ -3538,14 +3538,11 @@ vows.describe(tokenize)
     },
     'with fallback for properties': {
       'topic': function () {
-        var sourceMapTracker = inputSourceMapTracker({
-          errors: {}
-        });
+        var sourceMapTracker = inputSourceMapTracker();
         sourceMapTracker.track('styles.css', inputMap);
 
         return tokenize('div > a {\n  color: red red;\n}', {
           source: 'styles.css',
-          inputSourceMap: true,
           inputSourceMapTracker: sourceMapTracker,
           options: {},
           warnings: []
@@ -3560,7 +3557,7 @@ vows.describe(tokenize)
                 'rule-scope',
                 'div > a',
                 [
-                  [1, 0, 'styles.less']
+                  [1, 4, 'styles.less']
                 ]
               ]
             ],