diff --git a/lib/modules/parser.js b/lib/modules/parser.js index d28ad58f..c04b9f78 100644 --- a/lib/modules/parser.js +++ b/lib/modules/parser.js @@ -75,6 +75,45 @@ module.exports.parseVariables = function(string, syntax) { return out; }; +// Parse Style variables to object +module.exports.findVariables = function(string, syntax) { + syntax = syntax || 'scss'; + + var out = [], + ast = gonzales.srcToAST({ + src: string, + syntax: syntax + }); + + gonzo.traverse(ast, [{ + // Visitor for SASS and SCSS syntaxes + test: function(name, nodes) { + return name === 'value'; + }, + process: function(nodes) { + nodes.forEach(function(element) { + if (element[0] === 'variable' && element[1][0] === 'ident') { + out.push(element[1][1]); + } + }); + } + }, { + // Visitor for LESS syntax + test: function(name, nodes) { + return name === 'value'; + }, + process: function(nodes) { + nodes.forEach(function(element) { + if (element[0] === 'atkeyword' && element[1][0] === 'ident') { + out.push(element[1][1]); + } + }); + } + }]); + + return out; +}; + // Modifies string so that variables passed in object are updated module.exports.setVariables = function(string, syntax, variables) { var sorted = [], lines = [], diff --git a/lib/styleguide.js b/lib/styleguide.js index 891bb830..f70ffb3e 100644 --- a/lib/styleguide.js +++ b/lib/styleguide.js @@ -122,8 +122,17 @@ module.exports = function(opt) { // If settings file is found, generate settings object if (opt.styleVariables) { - var syntax = path.extname(opt.styleVariables).substring(1); + var syntax = path.extname(opt.styleVariables).substring(1), + variables; + // Parse variables from the defined file styleguide.config.settings = parser.parseVariables(fs.readFileSync(opt.styleVariables, 'utf-8'), syntax); + // Go trough every styleguide style block and find used variables + styleguide.sections.forEach(function(section) { + if (section.css) { + section.variables = parser.findVariables(section.css, syntax); + } + return section; + }); } // Create JSON containing KSS data @@ -142,8 +151,8 @@ module.exports = function(opt) { .pipe(pushAllFiles()) } - opt.onCompileError = onCompileError; // Preprocess all CSS files and combile then to a single file + opt.onCompileError = onCompileError; preprocess.getStream(Object.keys(filesBuffer), opt) .pipe(through.obj(function(file, enc, cb) { diff --git a/package.json b/package.json index 26a05512..af57c337 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "yargs": "^1.3.2" }, "devDependencies": { + "async": "^0.9.0", "chai": "^1.9.2", "conventional-changelog": "git://github.com/sc5/conventional-changelog.git#features/sc-styleguide", "exec-sync": "^0.1.6", @@ -78,6 +79,7 @@ "karma-sinon-chai": "^0.2.0", "main-bower-files": "^2.1.0", "mocha": "^2.0.1", + "mocha-shared": "^0.2.0", "multiline": "^1.0.1", "sinon": "^1.11.1", "sinon-chai": "^2.6.0", diff --git a/test/markdown.js b/test/markdown.js index d225bcf0..67753844 100644 --- a/test/markdown.js +++ b/test/markdown.js @@ -8,7 +8,6 @@ describe('Markdown', function() { it('getRenderer if formed correctly', function() { var renderer = markdown.getRenderer(); - console.log(renderer); expect(renderer).to.be.an('object'); expect(renderer.heading).to.be.an('function'); expect(renderer.paragraph).to.be.an('function'); diff --git a/test/parser.js b/test/parser.js index 21118e14..f17dcd3c 100644 --- a/test/parser.js +++ b/test/parser.js @@ -5,6 +5,78 @@ var gulp = require('gulp'), parser = require('../lib/modules/parser'); describe('Parser', function() { + describe('variable finding', function() { + describe('SCSS syntax', function() { + it('should return all used variables', function() { + var str = multiline(function() { + /* + color: $mycolor1; + .testStyle { + border: 1px solid $mycolor2; + } + .testStyle2 { + background-color: $mycolor3; + } + */ + }), + result = [ + 'mycolor1', 'mycolor2', 'mycolor3' + ] + expect(parser.findVariables(str)).eql(result); + }); + + it('should not return new variable definitions', function() { + var str = multiline(function() { + /* + $mycolor: #00ff00; + .testStyle { + color: $mycolor2; + } + */ + }), + result = [ + 'mycolor2' + ] + expect(parser.findVariables(str)).eql(result); + }); + }); + + describe('LESS syntax', function() { + it('should return all used variables', function() { + var str = multiline(function() { + /* + color: @mycolor1; + .testStyle { + border: 1px solid @mycolor2; + } + .testStyle2 { + background-color: @mycolor3; + } + */ + }), + result = [ + 'mycolor1', 'mycolor2', 'mycolor3' + ] + expect(parser.findVariables(str, 'less')).eql(result); + }); + + it('should not return new variable definitions', function() { + var str = multiline(function() { + /* + @mycolor: #00ff00; + .testStyle { + color: @mycolor2; + } + */ + }), + result = [ + 'mycolor2' + ] + expect(parser.findVariables(str, 'less')).eql(result); + }); + }); + }); + describe('variable parser', function() { describe('SCSS syntax', function() { it('should parse basic variables', function() { diff --git a/test/projects/less-project/source/styles/style.less b/test/projects/less-project/source/styles/style.less index 1e2bae52..aa8d041c 100644 --- a/test/projects/less-project/source/styles/style.less +++ b/test/projects/less-project/source/styles/style.less @@ -27,3 +27,22 @@ // Styleguide 3.0 .test-css {color: purple;} + +// Section with multiple variables +// +// Markup: +//
Section markup
+// +// Styleguide 4.0 + +.section1 { + color: @color-red; +} + +.section2 { + background-color: @color-green; +} + +.section3 { + border: 1px solid @color-blue; +} diff --git a/test/projects/scss-project/source/styles/style.scss b/test/projects/scss-project/source/styles/style.scss index d06d1e37..bbd1220c 100644 --- a/test/projects/scss-project/source/styles/style.scss +++ b/test/projects/scss-project/source/styles/style.scss @@ -26,4 +26,23 @@ // // Styleguide 3.0 -.test-css {color: purple;} \ No newline at end of file +.test-css {color: purple;} + +// Section with multiple variables +// +// Markup: +//
Section markup
+// +// Styleguide 4.0 + +.section1 { + color: $color-red; +} + +.section2 { + background-color: $color-green; +} + +.section3 { + border: 1px solid $color-blue; +} diff --git a/test/structure.js b/test/structure.js index b122f553..b39398ed 100644 --- a/test/structure.js +++ b/test/structure.js @@ -31,7 +31,7 @@ beforeEach(function() { function styleguideStream(source, config) { return gulp.src(source || defaultSource) - .pipe(styleguide(defaultConfig)) + .pipe(styleguide(config || defaultConfig)) } // This collector collects all files from the stream to the array passed as parameter @@ -154,112 +154,162 @@ describe('overview.html', function() { }); }); -['SCSS', 'LESS'].forEach(function(type) { - var source, - config; +function sharedStyleguideCss() { + it('should exist', function() { + expect(this.styleguideFile).to.be.an('object'); + }); + + it('should be processed correctly', function() { + expect(this.styleguideFile.contents.toString()).to.contain('.section {\n color: #ff0000;'); + }); +}; + +function sharedStyleguideJSON() { + it('should exist', function() { + expect(this.jsonData).to.be.an('object'); + }); + + it('should contain correct title', function() { + expect(this.jsonData.config.title).to.eql('Test Styleguide'); + }); + + it('should contain correct appRoot', function() { + expect(this.jsonData.config.appRoot).to.eql('/my-styleguide-book'); + }); + + it('should contain extra heads in correct format', function() { + expect(this.jsonData.config.extraHead).to.eql(defaultConfig.extraHead[0] + '\n' + defaultConfig.extraHead[1]); + }); + + it('should contain all common classes', function() { + expect(this.jsonData.config.commonClass).to.eql(['custom-class-1', 'custom-class-2']); + }); + + it('should contain all style variables from defined file', function() { + var sassData = { + 'color-red': { value: '#ff0000', index: 0 }, + 'color-green': { value: '#00ff00', index: 1 }, + 'color-blue': { value: '#0000ff', index: 2 } + }; + expect(this.jsonData.config.settings).to.eql(sassData); + }); + + it('should not reveal outputPath', function() { + expect(this.jsonData.config.outputPath).to.not.exist; + }); + + it('should have all the modifiers', function() { + expect(this.jsonData.sections[1].modifiers.length).to.eql(3) + }); + + // Markup + + it('should print markup if defined', function() { + expect(this.jsonData.sections[0].markup).to.not.be.empty; + }); + + it('should not print empty markup', function() { + expect(this.jsonData.sections[2].markup).to.not.exist; + }); + + // Related CSS + + it('should not print empty CSS', function() { + expect(this.jsonData.sections[1].css).to.not.exist; + }); + + it('should have section CSS', function() { + expect(this.jsonData.sections[2].css).to.eql('.test-css {color: purple;}'); + }); + + // Related variables + + it('should contain all related variables', function() { + var relatedVariables = ['color-red', 'color-green', 'color-blue']; + expect(this.jsonData.sections[3].variables).to.eql(relatedVariables) + }); +} + +describe('styleguide.css for SCSS project', function() { + beforeEach(function(done) { + var files = [], + _this = this, + source, + config; - beforeEach(function() { config = defaultConfig; - if (type === 'SCSS') { - config.styleVariables = './test/projects/scss-project/source/styles/_styleguide_variables.scss'; - source = './test/projects/scss-project/source/**/*.scss' - } else if (type === 'LESS') { - config.styleVariables = './test/projects/less-project/source/styles/_styleguide_variables.less'; - source = './test/projects/less-project/source/**/*.less' - } + config.styleVariables = './test/projects/scss-project/source/styles/_styleguide_variables.scss'; + source = './test/projects/scss-project/source/**/*.scss'; + styleguideStream(source, config).pipe( + through.obj({objectMode: true}, collector(files), function(callback) { + _this.styleguideFile = findFile(files, 'styleguide.css'); + done(); + }) + ); }); - describe('styleguide.css for ' + type + ' project', function() { - var styleguideFile; - this.timeout(5000); - - before(function(done) { - var files = []; - styleguideStream().pipe( - through.obj({objectMode: true}, collector(files), function(callback) { - styleguideFile = findFile(files, 'styleguide.css'); - done(); - }) - ); - }); - - it('should exist', function() { - expect(styleguideFile).to.be.an('object'); - }); - - it('should be processed correctly', function() { - expect(styleguideFile.contents.toString()).to.contain('.section {\n color: #ff0000; }'); - }); - }); - - describe('styleguide.json for ' + type + ' project', function() { - var jsonData; - this.timeout(5000); - - before(function(done) { - var files = []; - - styleguideStream(source).pipe( - through.obj({objectMode: true}, collector(files), function(callback) { - if (jsonData = findFile(files, 'styleguide.json')) { - jsonData = JSON.parse(jsonData.contents); - } - done(); - }) - ); - }); - - it('should exist', function() { - expect(jsonData).to.be.an('object'); - }); - - it('should contain correct title', function() { - expect(jsonData.config.title).to.eql('Test Styleguide'); - }); - - it('should contain correct appRoot', function() { - expect(jsonData.config.appRoot).to.eql('/my-styleguide-book'); - }); - - it('should contain extra heads in correct format', function() { - expect(jsonData.config.extraHead).to.eql(defaultConfig.extraHead[0] + '\n' + defaultConfig.extraHead[1]); - }); - - it('should contain all common classes', function() { - expect(jsonData.config.commonClass).to.eql(['custom-class-1', 'custom-class-2']); - }); - - it('should contain all ' + type + ' variables from defined file', function() { - var sassData = { - 'color-red': { value: '#ff0000', index: 0 }, - 'color-green': { value: '#00ff00', index: 1 }, - 'color-blue': { value: '#0000ff', index: 2 } - }; - expect(jsonData.config.settings).to.eql(sassData); - }); - - it('should not reveal outputPath', function() { - expect(jsonData.config.outputPath).to.not.exist; - }); - - it('should print markup if defined', function() { - expect(jsonData.sections[0].markup).to.not.be.empty; - }); - - it('should not print empty markup', function() { - expect(jsonData.sections[2].markup).to.not.exist; - }); - - it('should have all the modifiers', function() { - expect(jsonData.sections[1].modifiers.length).to.eql(3) - }); - - it('should have section CSS', function() { - expect(jsonData.sections[2].css).to.eql('.test-css {color: purple;}'); - }); - - it('should not print empty markup', function() { - expect(jsonData.sections[1].css).to.not.exist; - }); + sharedStyleguideCss(); +}); + +describe('styleguide.css for LESS project', function() { + beforeEach(function(done) { + var files = [], + _this = this, + source, + config; + + config = defaultConfig; + config.styleVariables = './test/projects/less-project/source/styles/_styleguide_variables.less'; + source = './test/projects/less-project/source/**/*.less'; + styleguideStream(source, config).pipe( + through.obj({objectMode: true}, collector(files), function(callback) { + _this.styleguideFile = findFile(files, 'styleguide.css'); + done(); + }) + ); }); + + sharedStyleguideCss(); +}); + +describe('styleguide.json for SCSS project', function() { + beforeEach(function(done) { + var files = [], + _this = this, + source, + config; + + config = defaultConfig; + config.styleVariables = './test/projects/scss-project/source/styles/_styleguide_variables.scss'; + source = './test/projects/scss-project/source/**/*.scss'; + styleguideStream(source, config).pipe( + through.obj({objectMode: true}, collector(files), function(callback) { + _this.jsonData = JSON.parse(findFile(files, 'styleguide.json').contents); + done(); + }) + ); + }); + + sharedStyleguideJSON(); +}); + +describe('styleguide.json for LESS project', function() { + beforeEach(function(done) { + var files = [], + _this = this, + source, + config; + + config = defaultConfig; + config.styleVariables = './test/projects/less-project/source/styles/_styleguide_variables.less'; + source = './test/projects/less-project/source/**/*.less'; + styleguideStream(source, config).pipe( + through.obj({objectMode: true}, collector(files), function(callback) { + _this.jsonData = JSON.parse(findFile(files, 'styleguide.json').contents); + done(); + }) + ); + }); + + sharedStyleguideJSON(); });