Refactors source map stringifier in OO style.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sat, 29 Nov 2014 23:47:12 +0000 (23:47 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 8 Dec 2014 09:39:15 +0000 (09:39 +0000)
lib/selectors/source-map-stringifier.js

index 0e197f9..e8b9ba7 100644 (file)
@@ -5,53 +5,62 @@ var SourceMapGenerator = require('source-map').SourceMapGenerator;
 
 var lineBreak = require('os').EOL;
 
-function SourceMapStringifier(options, restoreCallback, inputMapTracker) {
+function Rebuilder(options, restoreCallback, inputMapTracker) {
+  this.column = 0;
+  this.line = 1;
+  this.output = [];
   this.keepBreaks = options.keepBreaks;
-  this.restoreCallback = restoreCallback;
-  this.outputMap = new SourceMapGenerator();
+  this.relativeTo = options.relativeTo;
+  this.restore = restoreCallback;
   this.inputMapTracker = inputMapTracker;
+  this.outputMap = new SourceMapGenerator();
 
   if (options.root) {
-    this.resolvePath = rootPathResolver(options);
+    this.rebaseTo = path.resolve(options.root);
+    this.resolvePath = this.rootPathResolver;
   } else if (options.target) {
-    this.resolvePath = targetRelativePathResolver(options);
+    this.rebaseTo = path.dirname(path.resolve(process.cwd(), options.target));
+    this.resolvePath = this.relativePathResolver;
   }
 }
 
-function rootPathResolver(options) {
-  var rootPath = path.resolve(options.root);
-  return function (sourcePath) {
-    return sourcePath.replace(rootPath, '');
-  };
-}
+Rebuilder.prototype.rootPathResolver = function (sourcePath) {
+  return sourcePath.replace(this.rebaseTo, '');
+};
 
-function targetRelativePathResolver(options) {
-  var relativeTo = path.dirname(path.resolve(process.cwd(), options.target));
-  return function (sourcePath, sourceRelativeTo) {
-    if (sourceRelativeTo)
-      sourcePath = path.resolve(path.dirname(sourceRelativeTo), sourcePath);
+Rebuilder.prototype.relativePathResolver = function (sourcePath, sourceRelativeTo) {
+  if (sourceRelativeTo)
+    sourcePath = path.resolve(path.dirname(sourceRelativeTo), sourcePath);
 
-    return path.normalize(sourcePath) === path.resolve(sourcePath) ?
-      path.relative(relativeTo, sourcePath) :
-      path.relative(relativeTo, path.join(options.relativeTo, sourcePath));
-  };
-}
+  return path.normalize(sourcePath) === path.resolve(sourcePath) ?
+    path.relative(this.rebaseTo, sourcePath) :
+    path.relative(this.rebaseTo, path.join(this.relativeTo, sourcePath));
+};
 
-function valueRebuilder(list, store, separator) {
+Rebuilder.prototype.rebuildValue = function (list, separator) {
   for (var i = 0, l = list.length; i < l; i++) {
-    store(list[i]);
-    store(i < l - 1 ? separator : '');
+    this.store(list[i]);
+    this.store(i < l - 1 ? separator : '');
   }
-}
+};
+
+Rebuilder.prototype.store = function (token) {
+  var value = typeof token == 'string' ?
+    token :
+    token.value.indexOf('_') > -1 ? this.restore(token.value) : token.value;
+
+  this.track(value, token.metadata);
+  this.output.push(value);
+};
 
-function rebuild(tokens, store, keepBreaks, isFlatBlock) {
-  var joinCharacter = isFlatBlock ? ';' : (keepBreaks ? lineBreak : '');
+Rebuilder.prototype.rebuildList = function (tokens, isFlatBlock) {
+  var joinCharacter = isFlatBlock ? ';' : (this.keepBreaks ? lineBreak : '');
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
     if (token.kind === 'text' || token.kind == 'at-rule') {
-      store(token);
+      this.store(token);
       continue;
     }
 
@@ -61,86 +70,74 @@ function rebuild(tokens, store, keepBreaks, isFlatBlock) {
 
     if (token.kind == 'block') {
       if (token.body.length > 0) {
-        valueRebuilder([{ value: token.value, metadata: token.metadata }], store, '');
-        store('{');
+        this.rebuildValue([{ value: token.value, metadata: token.metadata }], '');
+        this.store('{');
         if (token.isFlatBlock)
-          valueRebuilder(token.body, store, ';');
+          this.rebuildValue(token.body, ';');
         else
-          rebuild(token.body, store, keepBreaks, false);
-        store('}');
+          this.rebuildList(token.body, false);
+        this.store('}');
       }
     } else {
-      valueRebuilder(token.value, store, ',');
-      store('{');
-      valueRebuilder(token.body, store, ';');
-      store('}');
+      this.rebuildValue(token.value, ',');
+      this.store('{');
+      this.rebuildValue(token.body, ';');
+      this.store('}');
     }
 
-    store(joinCharacter);
+    this.store(joinCharacter);
   }
-}
+};
+
+Rebuilder.prototype.track = function (value, metadata) {
+  if (metadata)
+    this.trackMetadata(metadata);
 
-function track(context, value, metadata) {
-  if (metadata) {
-    var original = context.inputMapTracker.isTracking() ?
-      context.inputMapTracker.originalPositionFor(metadata) :
-      {};
-    var source = original.source || metadata.source;
-
-    if (source) {
-      if (metadata.source && (/^https?:\/\//.test(metadata.source) || /^\/\//.test(metadata.source)) && source != metadata.source)
-        source = url.resolve(metadata.source, source);
-      else if (context.resolvePath)
-        source = context.resolvePath(source, metadata.source);
+  var parts = value.split('\n');
+  this.line += parts.length - 1;
+  this.column = parts.length > 1 ? 0 : (this.column + parts.pop().length);
+};
+
+Rebuilder.prototype.trackMetadata = function (metadata) {
+  var original = this.inputMapTracker.isTracking() ?
+    this.inputMapTracker.originalPositionFor(metadata) :
+    {};
+
+  this.outputMap.addMapping({
+    generated: {
+      line: this.line,
+      column: this.column,
+    },
+    source: this.stylingSourceFor(original, metadata) || '__stdin__.css',
+    original: {
+      line: original.line || metadata.line,
+      column: original.column || metadata.column
     }
+  });
+};
 
-    context.outputMap.addMapping({
-      generated: {
-        line: context.line,
-        column: context.column,
-      },
-      source: source || '__stdin__.css',
-      original: {
-        line: original.line || metadata.line,
-        column: original.column || metadata.column
-      }
-    });
-  }
+Rebuilder.prototype.stylingSourceFor = function (original, metadata) {
+  var source = original.source || metadata.source;
 
-  var parts = value.split('\n');
-  context.line += parts.length - 1;
-  context.column = parts.length > 1 ? 0 : (context.column + parts.pop().length);
-}
+  if (source && metadata.source && (/^https?:\/\//.test(metadata.source) || /^\/\//.test(metadata.source)) && source != metadata.source)
+    return url.resolve(metadata.source, source);
+  else if (source && this.resolvePath)
+    return this.resolvePath(source, metadata.source);
+  else
+    return source;
+};
 
-SourceMapStringifier.prototype.toString = function (tokens) {
-  var self = this;
-  var output = [];
-  var context = {
-    column: 0,
-    line: 1,
-    inputMapTracker: this.inputMapTracker,
-    outputMap: this.outputMap,
-    resolvePath: this.resolvePath
-  };
 
-  function store(token) {
-    if (typeof token == 'string') {
-      track(context, token);
-      output.push(token);
-    } else {
-      var val = token.value.indexOf('_') > -1 ?
-        self.restoreCallback(token.value) :
-        token.value;
-      track(context, val, token.metadata);
-      output.push(val);
-    }
-  }
+function SourceMapStringifier(options, restoreCallback, inputMapTracker) {
+  this.rebuilder = new Rebuilder(options, restoreCallback, inputMapTracker);
+}
 
-  rebuild(tokens, store, this.keepBreaks, false);
+SourceMapStringifier.prototype.toString = function (tokens) {
+  this.rebuilder.rebuildList(tokens);
 
   return {
-    sourceMap: this.outputMap,
-    styles: output.join('').trim()
+    sourceMap: this.rebuilder.outputMap,
+    styles: this.rebuilder.output.join('').trim()
   };
 };