Fixes #964 - allows for configurable line breaks.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 2 Aug 2018 12:31:54 +0000 (14:31 +0200)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 2 Aug 2018 12:32:10 +0000 (14:32 +0200)
Use `{ format: { breakWith: 'lf' } }` option to configure what line
break looks like, allows `'crlf'` or `'lf'`, defaults to current system
one so former on Windows and latter on Unix.

History.md
README.md
lib/options/format.js
lib/writer/helpers.js
lib/writer/simple.js
lib/writer/source-maps.js
test/integration-test.js
test/options/format-test.js

index e2bc541..3d82a7e 100644 (file)
@@ -5,6 +5,7 @@
 * Fixed issue [#861](https://github.com/jakubpawlowicz/clean-css/issues/861) - new `transition` property optimizer.
 * Fixed issue [#895](https://github.com/jakubpawlowicz/clean-css/issues/895) - ignoring specific styles.
 * Fixed issue [#947](https://github.com/jakubpawlowicz/clean-css/issues/947) - selector based filtering.
+* Fixed issue [#964](https://github.com/jakubpawlowicz/clean-css/issues/964) - adds configurable line breaks.
 * Fixed issue [#986](https://github.com/jakubpawlowicz/clean-css/issues/986) - level 2 optimizations and CSS 4 colors.
 * Fixed issue [#1000](https://github.com/jakubpawlowicz/clean-css/issues/1000) - carriage return handling in tokenizer.
 * Fixed issue [#1038](https://github.com/jakubpawlowicz/clean-css/issues/1038) - `font-variation-settings` quoting.
index 9d44ca6..45b2e59 100644 (file)
--- a/README.md
+++ b/README.md
@@ -122,6 +122,7 @@ clean-css 4.2 will introduce the following changes / features:
 * new `transition` property optimizer;
 * preserves any CSS content between `/* clean-css ignore:start */` and `/* clean-css ignore:end */` comments;
 * allows filtering based on selector in `transform` callback, see [example](#how-to-apply-arbitrary-transformations-to-css-properties);
+* adds configurable line breaks via `format: { breakWith: 'lf' }` option;
 
 ## Constructor options
 
@@ -264,6 +265,7 @@ new CleanCSS({
       beforeBlockEnds: false, // controls if a line break comes before a block ends; defaults to `false`
       betweenSelectors: false // controls if a line break comes between selectors; defaults to `false`
     },
+    breakWith: '\n', // controls the new line character, can be `'\r\n'` or `'\n'` (aliased as `'windows'` and `'unix'` or `'crlf'` and `'lf'`); defaults to system one, so former on Windows and latter on Unix
     indentBy: 0, // controls number of characters to indent with; defaults to `0`
     indentWith: 'space', // controls a character to indent with, can be `'space'` or `'tab'`; defaults to `'space'`
     spaces: { // controls where to insert spaces
index e641689..3474862 100644 (file)
@@ -1,3 +1,5 @@
+var systemLineBreak = require('os').EOL;
+
 var override = require('../utils/override');
 
 var Breaks = {
@@ -12,6 +14,12 @@ var Breaks = {
   BetweenSelectors: 'betweenSelectors'
 };
 
+var BreakWith = {
+  CarriageReturnLineFeed: '\r\n',
+  LineFeed: '\n',
+  System: systemLineBreak
+};
+
 var IndentWith = {
   Space: ' ',
   Tab: '\t'
@@ -25,6 +33,7 @@ var Spaces = {
 
 var DEFAULTS = {
   breaks: breaks(false),
+  breakWith: BreakWith.System,
   indentBy: 0,
   indentWith: IndentWith.Space,
   spaces: spaces(false),
@@ -76,6 +85,10 @@ function formatFrom(source) {
     return false;
   }
 
+  if (typeof source == 'object' && 'breakWith' in source) {
+    source = override(source, { breakWith: mapBreakWith(source.breakWith) });
+  }
+
   if (typeof source == 'object' && 'indentBy' in source) {
     source = override(source, { indentBy: parseInt(source.indentBy) });
   }
@@ -168,6 +181,21 @@ function normalizeValue(value) {
   }
 }
 
+function mapBreakWith(value) {
+  switch (value) {
+    case 'windows':
+    case 'crlf':
+    case BreakWith.CarriageReturnLineFeed:
+      return BreakWith.CarriageReturnLineFeed;
+    case 'unix':
+    case 'lf':
+    case BreakWith.LineFeed:
+      return BreakWith.LineFeed;
+    default:
+      return systemLineBreak;
+  }
+}
+
 function mapIndentWith(value) {
   switch (value) {
     case 'space':
index 3ee2642..1172740 100644 (file)
@@ -1,4 +1,3 @@
-var lineBreak = require('os').EOL;
 var emptyCharacter = '';
 
 var Breaks = require('../options/format').Breaks;
@@ -153,7 +152,7 @@ function openBrace(context, where, needsPrefixSpace) {
     context.indentWith = context.format.indentWith.repeat(context.indentBy);
     return (needsPrefixSpace && allowsSpace(context, Spaces.BeforeBlockBegins) ? Marker.SPACE : emptyCharacter) +
       Marker.OPEN_CURLY_BRACKET +
-      (allowsBreak(context, where) ? lineBreak : emptyCharacter) +
+      (allowsBreak(context, where) ? context.format.breakWith : emptyCharacter) +
       context.indentWith;
   } else {
     return Marker.OPEN_CURLY_BRACKET;
@@ -164,10 +163,10 @@ function closeBrace(context, where, beforeBlockEnd, isLast) {
   if (context.format) {
     context.indentBy -= context.format.indentBy;
     context.indentWith = context.format.indentWith.repeat(context.indentBy);
-    return (allowsBreak(context, Breaks.AfterProperty) || beforeBlockEnd && allowsBreak(context, Breaks.BeforeBlockEnds) ? lineBreak : emptyCharacter) +
+    return (allowsBreak(context, Breaks.AfterProperty) || beforeBlockEnd && allowsBreak(context, Breaks.BeforeBlockEnds) ? context.format.breakWith : emptyCharacter) +
       context.indentWith +
       Marker.CLOSE_CURLY_BRACKET +
-      (isLast ? emptyCharacter : (allowsBreak(context, where) ? lineBreak : emptyCharacter) + context.indentWith);
+      (isLast ? emptyCharacter : (allowsBreak(context, where) ? context.format.breakWith : emptyCharacter) + context.indentWith);
   } else {
     return Marker.CLOSE_CURLY_BRACKET;
   }
@@ -181,13 +180,13 @@ function colon(context) {
 
 function semicolon(context, where, isLast) {
   return context.format ?
-    Marker.SEMICOLON + (isLast || !allowsBreak(context, where) ? emptyCharacter : lineBreak + context.indentWith) :
+    Marker.SEMICOLON + (isLast || !allowsBreak(context, where) ? emptyCharacter : context.format.breakWith + context.indentWith) :
     Marker.SEMICOLON;
 }
 
 function comma(context) {
   return context.format ?
-    Marker.COMMA + (allowsBreak(context, Breaks.BetweenSelectors) ? lineBreak : emptyCharacter) + context.indentWith :
+    Marker.COMMA + (allowsBreak(context, Breaks.BetweenSelectors) ? context.format.breakWith : emptyCharacter) + context.indentWith :
     Marker.COMMA;
 }
 
@@ -220,7 +219,7 @@ function all(context, tokens) {
         break;
       case Token.COMMENT:
         store(context, token);
-        store(context, allowsBreak(context, Breaks.AfterComment) ? lineBreak : emptyCharacter);
+        store(context, allowsBreak(context, Breaks.AfterComment) ? context.format.breakWith : emptyCharacter);
         break;
       case Token.RAW:
         store(context, token);
index 21e7f88..20fde2a 100644 (file)
@@ -1,7 +1,5 @@
 var all = require('./helpers').all;
 
-var lineBreak = require('os').EOL;
-
 function store(serializeContext, token) {
   var value = typeof token == 'string' ?
     token :
@@ -15,8 +13,8 @@ function store(serializeContext, token) {
 
 function wrap(serializeContext, value) {
   if (serializeContext.column + value.length > serializeContext.format.wrapAt) {
-    track(serializeContext, lineBreak);
-    serializeContext.output.push(lineBreak);
+    track(serializeContext, serializeContext.format.breakWith);
+    serializeContext.output.push(serializeContext.format.breakWith);
   }
 }
 
index 4729eb0..6856579 100644 (file)
@@ -1,7 +1,6 @@
 var SourceMapGenerator = require('source-map').SourceMapGenerator;
 var all = require('./helpers').all;
 
-var lineBreak = require('os').EOL;
 var isRemoteResource = require('../utils/is-remote-resource');
 
 var isWindows = process.platform == 'win32';
@@ -23,8 +22,8 @@ function store(serializeContext, element) {
 
 function wrap(serializeContext, value) {
   if (serializeContext.column + value.length > serializeContext.format.wrapAt) {
-    track(serializeContext, lineBreak, false);
-    serializeContext.output.push(lineBreak);
+    track(serializeContext, serializeContext.format.breakWith, false);
+    serializeContext.output.push(serializeContext.format.breakWith);
   }
 }
 
index bd7b12f..7a97c72 100644 (file)
@@ -308,6 +308,22 @@ vows.describe('integration tests')
       ]
     }, { format: 'keep-breaks', level: { 1: { specialComments: 0 } } })
   )
+  .addBatch(
+    optimizerContext('CRLF line breaks', {
+      'uses specified one': [
+        '.block{color:red;display:block}',
+        '.block{color:red;\r\ndisplay:block\r\n}'
+      ]
+    }, { format: { breaks: { afterProperty: true }, breakWith: 'crlf' } })
+  )
+  .addBatch(
+    optimizerContext('LF line breaks', {
+      'uses specified one': [
+        '.block{color:red;display:block}',
+        '.block{color:red;\ndisplay:block\n}'
+      ]
+    }, { format: { breaks: { afterProperty: true }, breakWith: 'lf' } })
+  )
   .addBatch(
     optimizerContext('selectors', {
       'not expand + in selectors mixed with calc methods': [
index 87a789d..15149e2 100644 (file)
@@ -1,4 +1,5 @@
 var assert = require('assert');
+var systemLineBreak = require('os').EOL;
 
 var vows = require('vows');
 
@@ -39,6 +40,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: false,
             betweenSelectors: false
           },
+          breakWith: systemLineBreak,
           indentBy: 0,
           indentWith: ' ',
           spaces: {
@@ -53,7 +55,7 @@ vows.describe(formatFrom)
     },
     'hash': {
       'topic': function () {
-        return formatFrom({ breaks: { afterProperty: true }, indentBy: 1 });
+        return formatFrom({ breaks: { afterProperty: true }, breakWith: '\r\n', indentBy: 1 });
       },
       'is merged with default': function (formatOptions) {
         assert.deepEqual(formatOptions, {
@@ -68,6 +70,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: false,
             betweenSelectors: false
           },
+          breakWith: '\r\n',
           indentBy: 1,
           indentWith: ' ',
           spaces: {
@@ -97,6 +100,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: false,
             betweenSelectors: false
           },
+          breakWith: systemLineBreak,
           indentBy: 2,
           indentWith: ' ',
           spaces: {
@@ -126,6 +130,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: false,
             betweenSelectors: false
           },
+          breakWith: systemLineBreak,
           indentBy: 0,
           indentWith: '\t',
           spaces: {
@@ -155,6 +160,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: false,
             betweenSelectors: false
           },
+          breakWith: systemLineBreak,
           indentBy: 0,
           indentWith: '\t',
           spaces: {
@@ -184,6 +190,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: false,
             betweenSelectors: false
           },
+          breakWith: systemLineBreak,
           indentBy: 3,
           indentWith: ' ',
           spaces: {
@@ -213,6 +220,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: false,
             betweenSelectors: false
           },
+          breakWith: systemLineBreak,
           indentBy: 0,
           indentWith: '\t',
           spaces: {
@@ -242,6 +250,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: true,
             betweenSelectors: true
           },
+          breakWith: systemLineBreak,
           indentBy: 2,
           indentWith: ' ',
           spaces: {
@@ -271,6 +280,7 @@ vows.describe(formatFrom)
             beforeBlockEnds: true,
             betweenSelectors: false
           },
+          breakWith: systemLineBreak,
           indentBy: 0,
           indentWith: ' ',
           spaces: {