diff --git a/lib/modules/kss-splitter.js b/lib/modules/kss-splitter.js index 3845a92b..9155e710 100644 --- a/lib/modules/kss-splitter.js +++ b/lib/modules/kss-splitter.js @@ -1,61 +1,76 @@ 'use strict' -var kssBlocksParser = require('./kss-blocks-parser').kssBlocksParser; +var gonzales = require('gonzales-pe'), + gonzo = require('gonzales-ast'); module.exports = { /* Split string source into array of code and comment blocks */ - pureSplitter: function(source) { - var lines = source.split(/\n|$/g), - anInlineComment = /^ *\/\//, - multilineCommentStarted = /\/\*/, - multilineCommentFinished = /\*\//, - isMultilineCommentStarted, - blocks = [], - block, - type, - prevType; - - lines.forEach(function(line) { - - if (isMultilineCommentStarted) { - type = 'comment' - if (multilineCommentFinished.test(line)) { - isMultilineCommentStarted = false; + pureSplitter: function(source, syntax) { + + syntax = syntax || 'scss'; + var out = {}, + ast = gonzales.srcToAST({ + src: source, + syntax: syntax + }), + block, + blocks = [], + prevNode; + + gonzo.traverse(ast, [{ + // Visitor for SASS and SCSS syntaxes + test: function(name, nodes) { + return name !== 'stylesheet'; + }, + process: function(nodes) { + var startNewBlock = function() { + block && blocks.push(block); + block = { + type: '', + content: '' + }; + }, + type; + + // Calculate type of the current block + + // Multiline comment is comment + if (nodes[0] == 'commentML') { + type = 'comment'; } - } else { - isMultilineCommentStarted = multilineCommentStarted.test(line); - if (isMultilineCommentStarted) { - type = 'comment' - } else { - type = anInlineComment.test(line) ? 'comment' : 'code'; + // Singleline comment is comment + else if (nodes[0] === 'commentSL') { + type = 'comment'; } - } - - if (prevType != type) { - // Save old block if a new type appears - if (block) { - block.content = block.content.join('\n'); - blocks.push(block); + // Single breaklines between singleline comments are comments + else if (nodes[0] === 's' && nodes[1].split('\n').length <= 2 && prevNode[0] === 'commentSL') { + type = 'comment'; } - // Start a new block after changing the type - block = { - type: type, - content: [line] + else { + type = 'code'; } - } else { - block.content.push(line); - } - prevType = type; + // If type has been changed, start a new block + if (!block || block.type != type) { + startNewBlock(); + } - }); + // Extend current block content + block.type = type; + block.content += gonzales.astToSrc({ + ast: nodes, + syntax: syntax + }); + prevNode = nodes; - // Push the last block - block.content = block.content.join('\n'); - blocks.push(block); + } + }]); + // push last block + blocks.push(block); return blocks; + }, getBlocks: function(source) { @@ -86,11 +101,9 @@ module.exports = { } } else { // Not KSS comments are considered to be parts of code - pair.code.push('\n') pair.code.push(block.content) } } else { - pair.code.push('\n') pair.code.push(block.content) } diff --git a/test/kss-splitter.js b/test/kss-splitter.js index b4c33bd8..350d046b 100644 --- a/test/kss-splitter.js +++ b/test/kss-splitter.js @@ -7,68 +7,71 @@ var gulp = require('gulp'), describe('KSS splitter', function() { - it('Split 2 blocks of comments', function(){ - var str = multiline(function() { - /* + describe('Splitter module', function() { + it('Split 2 blocks of singleline comments', function(){ + var str = multiline(function() { + /* // Comment1 // Comment1 // Comment2 // Comment2 - */ - }), - result = [ - { type: 'comment', content: '// Comment1\n// Comment1' }, - { type: 'code', content: '' }, - { type: 'comment', content: '// Comment2\n// Comment2' } - ], - split = kssSplitter.pureSplitter(str); - - expect(split).eql(result); - + */ + }), + result = [ + { type: 'comment', content: '// Comment1\n// Comment1' }, + { type: 'code', content: '\n\n' }, + { type: 'comment', content: '// Comment2\n// Comment2' } + ], + split = kssSplitter.pureSplitter(str); + + expect(split).eql(result); + + }); }); - it('should parse single KSS block', function() { - var str = multiline(function() { - /* + describe('Singleline comment declarations', function() { + it('should parse single KSS block', function() { + var str = multiline(function() { + /* // Comment // Styleguide 1.0 .a { b: c } - */ - }), - result = [ - { - 'kss': '// Comment\n// Styleguide 1.0', - 'code': '\n\n.a { b: c }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - - it('should be agnostic to spaces in reference declaration', function(){ - var str = multiline(function() { - /* + */ + }), + result = [ + { + 'kss': '// Comment\n// Styleguide 1.0', + 'code': '\n\n.a { b: c }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should be agnostic to spaces in reference declaration', function(){ + var str = multiline(function() { + /* // Comment //Styleguide 1.0 .a { b: c } - */ - }), - result = [ - { - 'kss': '// Comment\n//Styleguide 1.0 ', - 'code': '\n\n.a { b: c }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - - it('should take multiline code', function(){ - var str = multiline(function() { - /* + */ + }), + result = [ + { + 'kss': '// Comment\n//Styleguide 1.0 ', + 'code': '\n\n.a { b: c }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should take multiline code', function(){ + var str = multiline(function() { + /* // Comment //Styleguide 1.0 @@ -76,21 +79,21 @@ describe('KSS splitter', function() { $a: b; .x { y: z } - */ - }), - result = [ - { - 'kss': '// Comment\n//Styleguide 1.0 ', - 'code': '\n\n.a { b: c }\n$a: b;\n\n.x { y: z }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - - it('should allow code blocks to have comments', function(){ - var str = multiline(function() { - /* + */ + }), + result = [ + { + 'kss': '// Comment\n//Styleguide 1.0 ', + 'code': '\n\n.a { b: c }\n$a: b;\n\n.x { y: z }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should allow code blocks to have not KSS comments', function(){ + var str = multiline(function() { + /* // Comment //Styleguide 1.0 @@ -99,21 +102,21 @@ $a: b; // Simple comment .x { y: z } - */ - }), - result = [ - { - 'kss': '// Comment\n//Styleguide 1.0 ', - 'code': '\n\n.a { b: c }\n\n// Simple comment\n\n.x { y: z }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - - it('should parse several blocks', function(){ - var str = multiline(function() { - /* + */ + }), + result = [ + { + 'kss': '// Comment\n//Styleguide 1.0 ', + 'code': '\n\n.a { b: c }\n\n// Simple comment\n\n.x { y: z }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should parse several blocks', function(){ + var str = multiline(function() { + /* // Comment1 // Styleguide 1.0 @@ -123,25 +126,67 @@ $a: b; // Styleguide 2.0 .x { y: z } - */ - }), - result = [ - { - 'kss': '// Comment1\n// Styleguide 1.0', - 'code': '\n\n.a { b: c }\n' - }, - { - 'kss': '// Comment2\n// Styleguide 2.0', - 'code': '\n\n.x { y: z }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); + */ + }), + result = [ + { + 'kss': '// Comment1\n// Styleguide 1.0', + 'code': '\n\n.a { b: c }\n\n' + }, + { + 'kss': '// Comment2\n// Styleguide 2.0', + 'code': '\n\n.x { y: z }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should parse several blocks 2', function() { + var str = multiline(function() { + /* +@import "test"; +// Comment +// Styleguide 1.0 - it('should allow blocks with no code', function(){ - var str = multiline(function() { - /* +.a { b: c } + +// Comment +// Styleguide 2 + +// Comment +// Styleguide 2.0 + + +.a { b: c } + + */ + }), + result = [ + { + kss: '', + code: '@import "test";\n' + }, + { + kss: '// Comment\n// Styleguide 1.0', + code: '\n\n.a { b: c }\n\n' + }, + { + kss: '// Comment\n// Styleguide 2', + code: '\n\n' + }, + { + kss: '// Comment\n// Styleguide 2.0', + code: '\n\n\n.a { b: c }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should allow blocks with no code', function(){ + var str = multiline(function() { + /* // Comment1 // Styleguide 1.0 @@ -154,29 +199,29 @@ $a: b; // Styleguide 3.0 .x { y: z } - */ - }), - result = [ - { - 'kss': '// Comment1\n// Styleguide 1.0', - 'code': '\n\n.a { b: c }\n' - }, - { - 'kss': '// Comment2\n// Styleguide 2.0', - 'code': '\n' - }, - { - 'kss': '// Comment3\n// Styleguide 3.0', - 'code': '\n\n.x { y: z }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - - it('should take any reference number', function(){ - var str = multiline(function() { - /* + */ + }), + result = [ + { + 'kss': '// Comment1\n// Styleguide 1.0', + 'code': '\n\n.a { b: c }\n\n' + }, + { + 'kss': '// Comment2\n// Styleguide 2.0', + 'code': '\n\n' + }, + { + 'kss': '// Comment3\n// Styleguide 3.0', + 'code': '\n\n.x { y: z }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should take any reference number', function(){ + var str = multiline(function() { + /* // Comment // Styleguide 1 @@ -187,192 +232,207 @@ $a: b; // Comment // Styleguide 5.1.2.6 - */ - }), - result = [ - { - 'kss': '// Comment\n// Styleguide 1', - 'code': '\n\n.a { b: c }\n' - }, - { - 'kss': '// Comment\n// Styleguide 1.1', - 'code': '\n' - }, - { - 'kss': '// Comment\n// Styleguide 5.1.2.6', - 'code': '' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - - it('should take code before first KSS block', function() { - var str = multiline(function() { - /* + */ + }), + result = [ + { + 'kss': '// Comment\n// Styleguide 1', + 'code': '\n\n.a { b: c }\n\n' + }, + { + 'kss': '// Comment\n// Styleguide 1.1', + 'code': '\n\n' + }, + { + 'kss': '// Comment\n// Styleguide 5.1.2.6', + 'code': '' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should take code before first KSS block', function() { + var str = multiline(function() { + /* .x { y: x } // Comment // Styleguide 1.0 .a { b: c } - */ - }), - result = [ - { - kss: '', - code: '\n.x { y: x }\n' - }, - { - 'kss': '// Comment\n// Styleguide 1.0', - 'code': '\n\n.a { b: c }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); + */ + }), + result = [ + { + kss: '', + code: '.x { y: x }\n\n' + }, + { + 'kss': '// Comment\n// Styleguide 1.0', + 'code': '\n\n.a { b: c }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); - it('should parse single KSS block in multiline comments', function() { - var str = '' + -'/* Comment\n' + -'Styleguide 1.0\n' + -'*/\n' + -'\n' + -'.a { b: c }', - result = [ - { - 'kss': '/* Comment\nStyleguide 1.0\n*/', - 'code': '\n\n.a { b: c }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); }); - it('should parse multiline KSS with no ending linebreak', function() { - var str = '' + -'/* Comment\n' + -'Styleguide 1.0 */' + -'\n' + -'.a { b: c }', - result = [ - { - 'kss': '/* Comment\nStyleguide 1.0 */', - 'code': '\n.a { b: c }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - it('should parse multiline KSS with string prefixes', function() { - var str = '' + -'/* Comment\n' + -' * Styleguide 1.0\n' + -'*/' + -'\n' + -'.a { b: c }', - result = [ - { - 'kss': '/* Comment\n * Styleguide 1.0\n*/', - 'code': '\n.a { b: c }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - it('should allow code blocks to have multiline comments', function(){ - var str = multiline(function() { - /* + describe('Multiline comment declarations', function() { + + it('should parse single KSS block in multiline comments', function() { + var str = '' + + '/* Comment\n' + + 'Styleguide 1.0\n' + + '*/\n' + + '\n' + + '.a { b: c }', + result = [ + { + 'kss': '/* Comment\nStyleguide 1.0\n*/', + 'code': '\n\n.a { b: c }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should parse multiline KSS with no ending linebreak', function() { + var str = '' + + '/* Comment\n' + + 'Styleguide 1.0 */' + + '\n' + + '.a { b: c }', + result = [ + { + 'kss': '/* Comment\nStyleguide 1.0 */', + 'code': '\n.a { b: c }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + it('should parse multiline KSS with string prefixes', function() { + var str = '' + + '/* Comment\n' + + ' * Styleguide 1.0\n' + + '*/' + + '\n' + + '.a { b: c }', + result = [ + { + 'kss': '/* Comment\n * Styleguide 1.0\n*/', + 'code': '\n.a { b: c }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should allow code blocks to have multiline comments', function(){ + var str = multiline(function() { + /* // Comment //Styleguide 1.0 .a { b: c } - */}) + -'\n\n/* Simple comment */\n\n' + -multiline(function() { - /* + */}) + + '\n\n/* Simple comment */\n\n' + + multiline(function() { + /* .x { y: z } - */ - }), - result = [ - { - 'kss': '// Comment\n//Styleguide 1.0 ', - 'code': '\n\n.a { b: c }\n\n/* Simple comment */\n\n.x { y: z }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); - }); - - it('should parse several KSS blocks with multiline comments', function(){ - var str = '/*\n' + -'Comment1\n' + -'Styleguide 1.0\n' + -'*/\n' + -'.a { b: c }\n' + -'\n' + -'/*\n' + -'Comment2\n' + -'Styleguide 2.0\n' + -'*/' - '', - result = [ - { - 'kss': '/*\nComment1\nStyleguide 1.0\n*/', - 'code': '\n.a { b: c }\n' - }, - { - 'kss': '/*\nComment2\nStyleguide 2.0\n*/', - 'code': '' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); + */ + }), + result = [ + { + 'kss': '// Comment\n//Styleguide 1.0 ', + 'code': '\n\n.a { b: c }\n\n/* Simple comment */\n\n.x { y: z }' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should parse several KSS blocks with multiline comments', function(){ + var str = '/*\n' + + 'Comment1\n' + + 'Styleguide 1.0\n' + + '*/\n' + + '.a { b: c }\n' + + '\n' + + '/*\n' + + 'Comment2\n' + + 'Styleguide 2.0\n' + + '*/' + '', + result = [ + { + 'kss': '/*\nComment1\nStyleguide 1.0\n*/', + 'code': '\n.a { b: c }\n\n' + }, + { + 'kss': '/*\nComment2\nStyleguide 2.0\n*/', + 'code': '' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); }); - /* TODO: Parser does not work with code after 2nd code block */ + describe('tricky CSS content', function() { - it('should return array of blocks', function() { - var str = multiline(function() { - /* -@import "test"; + it('should swallow content property with multiline comment', function() { + var str = multiline(function() { + /* // Comment // Styleguide 1.0 -.a { b: c } +a:before { content: "/* ..." } // Comment // Styleguide 2 -// Comment -// Styleguide 2.0 - - -.a { b: c } + */ + }), + result = [ + { + kss: '// Comment\n// Styleguide 1.0', + code: '\n\na:before { content: "/* ..." }\n\n' + }, + { + kss: '// Comment\n// Styleguide 2', + code: '' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); + + it('should swallow content property with singleline comment', function() { + var str = multiline(function() { + /* +a:before { content: "// Comment inside content" } - */ - }), - result = [ - { - kss: '', - code: '\n@import "test";' - }, - { - kss: '// Comment\n// Styleguide 1.0', - code: '\n\n.a { b: c }\n' - }, - { - kss: '// Comment\n// Styleguide 2', - code: '\n' - }, - { - kss: '// Comment\n// Styleguide 2.0', - code: '\n\n\n.a { b: c }' - } - ], - kssBlocks = kssSplitter.getBlocks(str); - expect(kssBlocks).eql(result); +// Comment +// Styleguide 2 + */ + }), + result = [ + { + kss: '', + code: 'a:before { content: "// Comment inside content" }\n\n' + }, + { + kss: '// Comment\n// Styleguide 2', + code: '' + } + ], + kssBlocks = kssSplitter.getBlocks(str); + expect(kssBlocks).eql(result); + }); }); });