├── .travis.yml ├── .gitignore ├── README.md ├── package.json ├── LICENSE ├── bin └── less2sass ├── test ├── fixtures │ ├── test.less │ └── test.scss └── index.js └── index.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "node" 6 | - "iojs" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # === Node === 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | pids 12 | logs 13 | results 14 | 15 | npm-debug.log 16 | node_modules 17 | 18 | # === Mac === 19 | .DS_Store 20 | .AppleDouble 21 | .LSOverride 22 | 23 | # Icon must ends with two \r. 24 | Icon 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear on external disk 33 | .Spotlight-V100 34 | .Trashes 35 | 36 | # === Vim === 37 | [._]*.s[a-w][a-z] 38 | [._]s[a-w][a-z] 39 | *.un~ 40 | Session.vim 41 | .netrwhist 42 | *~ 43 | 44 | # === Sublime === 45 | # workspace files are user-specific 46 | *.sublime-workspace 47 | 48 | # project files should be checked into the repository, unless a significant 49 | # proportion of contributors will probably not be using SublimeText 50 | # *.sublime-project 51 | 52 | # === Webstorm === 53 | .idea 54 | 55 | # Users Environment Variables 56 | .lock-wscript 57 | 58 | lib/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | less2sass 2 | ========= 3 | 4 | [![Build Status](https://travis-ci.org/ekryski/less2sass.svg?branch=master)](https://travis-ci.org/ekryski/less2sass) 5 | 6 | A little script to convert less to sass files 7 | 8 | > **Note: Due to the nature of less and sass it does not do a completely perfect conversion. You will have to do some manual work :-(** 9 | 10 | ## Installing 11 | 12 | `npm install -g less2sass` 13 | 14 | ## Running 15 | You can run less2sass on a single file or on entire directory. It will recurse through the directory and convert any less files to scss, preserving the directory structure. 16 | 17 | `less2sass ` 18 | 19 | ## Caveats 20 | 21 | * This does not really convert colour functions, it makes a best attempt but most colour functions will need to be ported over manually 22 | * It does not convert to proper `.sass` yet. Only to `.scss` 23 | * It may be buggy so you have to check your code but hopefully this script will save you some time. If you come across a bug, please create an issue. Better yet, a pull request! 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "less2sass", 3 | "description": "A quick less to sass converter", 4 | "version": "1.0.3", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Eric Kryski", 8 | "email": "e.kryski@gmail.com" 9 | }, 10 | "homepage": "http://erickryski.com", 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:ekryski/less2sass.git" 14 | }, 15 | "engines": { 16 | "node": "=0.10.x", 17 | "npm": "=1.2.x" 18 | }, 19 | "bin": { 20 | "less2sass": "bin/less2sass" 21 | }, 22 | "main": "index.js", 23 | "devDependencies": { 24 | "mocha": "^2.3.4" 25 | }, 26 | "scripts": { 27 | "publish": "git push origin && git push origin --tags", 28 | "release:patch": "npm version patch && npm publish", 29 | "release:minor": "npm version minor && npm publish", 30 | "release:major": "npm version major && npm publish", 31 | "mocha": "mocha --reporter spec", 32 | "test": "npm run mocha" 33 | }, 34 | "dependencies": { 35 | "commander": "^2.9.0", 36 | "mkdirp": "^0.5.1", 37 | "win-fork": "^1.1.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Eric Kryski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bin/less2sass: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander'); 4 | var path = require('path'); 5 | var fs = require('fs'); 6 | var less2sass = require('..'); 7 | var mkdir = require('mkdirp').sync; 8 | 9 | /** 10 | * Helpers. 11 | */ 12 | 13 | function log(type, msg, color){ 14 | color = color || '36'; 15 | var w = 10; 16 | var len = Math.max(0, w - type.length); 17 | var pad = Array(len + 1).join(' '); 18 | console.log(' \033[' + color + 'm%s\033[m : \033[90m%s\033[m', pad + type, msg); 19 | } 20 | 21 | function info(type, msg){ 22 | log(type, msg); 23 | } 24 | 25 | function warn(type, msg){ 26 | log(type, msg, '33'); 27 | } 28 | 29 | function error(type, msg){ 30 | log(type, msg, '31'); 31 | } 32 | 33 | function write(path, str) { 34 | if (fs.existsSync(path)) { 35 | warn('exists', path); 36 | } else { 37 | log('create', path, '32'); 38 | fs.writeFileSync(path, str); 39 | } 40 | } 41 | 42 | function isLessFile(filename) { 43 | return /\.less$/.test(filename); 44 | } 45 | 46 | function convert(filePath) { 47 | info('processing', filePath); 48 | 49 | var buffer = fs.readFileSync(filePath); 50 | var output = less2sass.convert(buffer.toString()); 51 | var newPath = filePath.replace(/\.less$/, '.scss'); 52 | 53 | write(newPath, output); 54 | } 55 | 56 | /** 57 | * Usage. 58 | */ 59 | 60 | program 61 | .version(require('../package.json').version) 62 | .usage(''); 63 | 64 | 65 | /** 66 | * Examples. 67 | */ 68 | 69 | program.on('--help', function () { 70 | console.log(' Commands:'); 71 | console.log(); 72 | console.log(' less2sass converts the less file to scss.\n'); 73 | console.log(); 74 | }); 75 | 76 | /** 77 | * Parse. 78 | */ 79 | 80 | program.parse(process.argv); 81 | 82 | // args void of file path 83 | 84 | var args = process.argv.slice(3); 85 | 86 | // command 87 | if (program.args[0] === undefined) { 88 | error('fail', 'You must provide a file path'); 89 | 90 | program.help(); 91 | } 92 | 93 | function read(filePath) { 94 | var fullPath = path.resolve(process.cwd(), filePath); 95 | var stats = fs.statSync(fullPath); 96 | info('scanning', fullPath); 97 | 98 | if (isLessFile(fullPath)) { 99 | convert(fullPath); 100 | } 101 | else if (stats.isDirectory()){ 102 | var files = fs.readdirSync(fullPath); 103 | 104 | for (var i = 0; i < files.length; i++) { 105 | read(path.join(fullPath, files[i])); 106 | } 107 | } 108 | } 109 | 110 | read(program.args[0]); 111 | -------------------------------------------------------------------------------- /test/fixtures/test.less: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | @import 'app/app.less'; 3 | 4 | 5 | // variables 6 | @black: #000; 7 | @san-serif-stack: helvetica, arial; 8 | @standard-fonts: ~'@{san-serif-stack}, sans-serif'; 9 | 10 | // mixins 11 | .box-sizing (@sizing:border-box) { 12 | -ms-box-sizing: @sizing; 13 | -moz-box-sizing: @sizing; 14 | -webkit-box-sizing: @sizing; 15 | box-sizing: @sizing; 16 | } 17 | 18 | .drop-shadow(@x-axis: 0, @y-axis: 1px, @blur: 2px, @alpha: 0.1) { 19 | -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 20 | -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 21 | box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 22 | } 23 | 24 | .drop-shadow-2( 25 | @x-axis: 0, 26 | @y-axis: 1px, 27 | @blur: 2px, 28 | @alpha: 0.1 29 | ) { 30 | -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 31 | -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 32 | box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 33 | } 34 | 35 | .drop-shadow-3( 36 | @x-axis: 0, 37 | @y-axis: 1px, 38 | @blur: 2px, 39 | @alpha: 0.1) { 40 | -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 41 | -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 42 | box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 43 | } 44 | 45 | #drop { 46 | .shadow-3( 47 | @x-axis: 0, 48 | @y-axis: 1px, 49 | @blur: 2px, 50 | @alpha: 0.1) { 51 | -webkit-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 52 | -moz-box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 53 | box-shadow: @x-axis @y-axis @blur rgba(0, 0, 0, @alpha); 54 | } 55 | } 56 | 57 | // styles 58 | 59 | @keyframes grow-font-size { 60 | 0% { 61 | font-size: .5em; 62 | } 63 | 100% { 64 | font-size: 1em; 65 | } 66 | } 67 | 68 | #header { 69 | height: 35px; 70 | position: relative; 71 | background: lighten(@black, 40%); 72 | .drop-shadow(0, 2px, 4px, 0.4); 73 | .drop-shadow-2( 74 | 0, 75 | 2px, 76 | 4px, 77 | 0.4); 78 | .drop-shadow-3( 79 | 0, 80 | 2px, 81 | 4px, 82 | 0.4 83 | ); 84 | .box-sizing; 85 | 86 | .btn.btn-large { 87 | padding: 6px 12px; 88 | float: left; 89 | background: spin(@black, 10%); 90 | font-weight: 300; 91 | animation: grow-font-size 1s; 92 | .box-sizing(); 93 | 94 | &:hover { 95 | background: lighten(@black, 10%); 96 | filter: ~"progid:DXImageTransform.Microsoft.gradient(startColorstr=@{black}, endColorstr=@{black})"; 97 | } 98 | } 99 | } 100 | 101 | @media (min-width: 768px) and (max-width: 979px) { 102 | .btn.btn-large { 103 | padding: 3px 6px; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/fixtures/test.scss: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | @import 'app/app.scss'; 3 | 4 | 5 | // variables 6 | $black: #000; 7 | $san-serif-stack: helvetica, arial; 8 | $standard-fonts: '#{$san-serif-stack}, sans-serif'; 9 | 10 | // mixins 11 | @mixin box-sizing($sizing:border-box) { 12 | -ms-box-sizing: $sizing; 13 | -moz-box-sizing: $sizing; 14 | -webkit-box-sizing: $sizing; 15 | box-sizing: $sizing; 16 | } 17 | 18 | @mixin drop-shadow($x-axis: 0, $y-axis: 1px, $blur: 2px, $alpha: 0.1) { 19 | -webkit-box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 20 | -moz-box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 21 | box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 22 | } 23 | 24 | @mixin drop-shadow-2( 25 | $x-axis: 0, 26 | $y-axis: 1px, 27 | $blur: 2px, 28 | $alpha: 0.1 29 | ) { 30 | -webkit-box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 31 | -moz-box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 32 | box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 33 | } 34 | 35 | @mixin drop-shadow-3( 36 | $x-axis: 0, 37 | $y-axis: 1px, 38 | $blur: 2px, 39 | $alpha: 0.1) { 40 | -webkit-box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 41 | -moz-box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 42 | box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 43 | } 44 | 45 | #drop { 46 | @mixin shadow-3( 47 | $x-axis: 0, 48 | $y-axis: 1px, 49 | $blur: 2px, 50 | $alpha: 0.1) { 51 | -webkit-box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 52 | -moz-box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 53 | box-shadow: $x-axis $y-axis $blur rgba(0, 0, 0, $alpha); 54 | } 55 | } 56 | 57 | // styles 58 | 59 | @keyframes grow-font-size { 60 | 0% { 61 | font-size: .5em; 62 | } 63 | 100% { 64 | font-size: 1em; 65 | } 66 | } 67 | 68 | #header { 69 | height: 35px; 70 | position: relative; 71 | background: lighten($black, 40%); 72 | @include drop-shadow(0, 2px, 4px, 0.4); 73 | @include drop-shadow-2( 74 | 0, 75 | 2px, 76 | 4px, 77 | 0.4); 78 | @include drop-shadow-3( 79 | 0, 80 | 2px, 81 | 4px, 82 | 0.4 83 | ); 84 | @include box-sizing; 85 | 86 | .btn.btn-large { 87 | padding: 6px 12px; 88 | float: left; 89 | background: adjust-hue($black, 10%); 90 | font-weight: 300; 91 | animation: grow-font-size 1s; 92 | @include box-sizing(); 93 | 94 | &:hover { 95 | background: lighten($black, 10%); 96 | filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$black}, endColorstr=#{$black})"; 97 | } 98 | } 99 | } 100 | 101 | @media (min-width: 768px) and (max-width: 979px) { 102 | .btn.btn-large { 103 | padding: 3px 6px; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // http://stackoverflow.com/questions/14970224/anyone-know-of-a-good-way-to-convert-from-less-to-sass 2 | 3 | function Less2Sass(){ 4 | 5 | } 6 | 7 | Less2Sass.prototype.convert = function(file) { 8 | 9 | this.file = file; 10 | 11 | this.convertInterpolatedVariables() 12 | .convertVariables() 13 | .convertTildaStrings() 14 | .convertMixins() 15 | .includeMixins() 16 | .convertExtend() 17 | .convertColourHelpers() 18 | .convertFileExtensions() 19 | .convertFunctionUnit(); 20 | 21 | return this.file; 22 | }; 23 | 24 | Less2Sass.prototype.includeMixins = function() { 25 | var includeRegex = /^(\s*)\.([a-zA-Z][\w\-]*\(?[^;{}]*\)?;{1}$)/gm; 26 | 27 | this.file = this.file.replace(includeRegex, '$1@include $2'); 28 | 29 | return this; 30 | }; 31 | 32 | 33 | Less2Sass.prototype.convertMixins = function() { 34 | // Simple form: no semicolons. 35 | const mixinRegexNoSemicolon = /^(\s*?)\.([\w\-]*?)\s*\(([\s\S][^\;]+?)?\)\s*\{$/gm; 36 | this.file = this.file.replace(mixinRegexNoSemicolon, '$1@mixin $2($3) {'); 37 | // With semicolons. 38 | const mixinRegexWithSemicolon = /^(\s*?)\.([\w\-]*?)\s*\(([\s\S][^\,]+?)?\)\s*\{$/gm; 39 | this.file = this.file.replace(mixinRegexWithSemicolon, function (match, g1, g2, g3) { 40 | return g1 + '@mixin ' + g2 + '(' + g3.replace(/;/g, ',') + ') {'; 41 | }); 42 | return this; 43 | }; 44 | 45 | Less2Sass.prototype.convertFunctionUnit = function() { 46 | // Two-args. 47 | const unitTwoArgRegex = /unit\((\S+),(\S+)\)/g; 48 | this.file = this.file.replace(unitTwoArgRegex, '0$2 + $1'); 49 | // One-arg. 50 | const unitOneArgRegex = /unit\(([^,]+)\)/g; 51 | this.file = this.file.replace(unitOneArgRegex, 'unit-less($1)'); 52 | 53 | return this; 54 | }; 55 | 56 | Less2Sass.prototype.convertExtend = function() { 57 | // http://lesscss.org/features/#extend-feature 58 | // &:extend syntax. 59 | const andExtendRegex = /&:extend\((.[\w]*)\);/g; 60 | this.file = this.file.replace(andExtendRegex, '@extend $1;'); 61 | 62 | return this; 63 | }; 64 | 65 | Less2Sass.prototype.convertColourHelpers = function() { 66 | var helperRegex = /spin\(/g; 67 | 68 | this.file = this.file.replace(helperRegex, 'adjust-hue('); 69 | 70 | // TODO (EK): Flag other colour helpers for manual conversion that SASS does not have 71 | 72 | return this; 73 | }; 74 | 75 | Less2Sass.prototype.convertTildaStrings = function() { 76 | var tildaRegex = /~("|')/g; 77 | 78 | this.file = this.file.replace(tildaRegex, '$1'); 79 | 80 | return this; 81 | }; 82 | 83 | Less2Sass.prototype.convertInterpolatedVariables = function() { 84 | var interpolationRegex = /@\{(?!(\s|\())/g; 85 | 86 | this.file = this.file.replace(interpolationRegex, '#{$'); 87 | 88 | return this; 89 | }; 90 | 91 | Less2Sass.prototype.convertVariables = function() { 92 | // Matches any @ that doesn't have 'media ' or 'import ' after it. 93 | var atRegex = /@(?!(media|import|mixin|font-face|keyframes)(\s|\())/g; 94 | 95 | this.file = this.file.replace(atRegex, '$'); 96 | 97 | return this; 98 | }; 99 | 100 | Less2Sass.prototype.convertFileExtensions = function() { 101 | var extensionRegex = /\.less/g; 102 | 103 | this.file = this.file.replace(extensionRegex, '.scss'); 104 | 105 | return this; 106 | }; 107 | 108 | module.exports = new Less2Sass(); 109 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var less2sass = require('..'); 2 | var assert = require('assert'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | describe('less2sass', function() { 7 | 8 | describe("whole file", function() { 9 | var fixture; 10 | var expected; 11 | 12 | before(function() { 13 | fixture = fs.readFileSync(path.resolve(__dirname, 'fixtures/test.less')).toString(); 14 | expected = fs.readFileSync(path.resolve(__dirname, 'fixtures/test.scss')).toString(); 15 | }); 16 | 17 | it('converts multiple variables in the same line', function() { 18 | const result = less2sass.convert(fixture); 19 | assert.equal(result, expected); 20 | }); 21 | }); 22 | 23 | describe("variables", function() { 24 | it('converts interpolated variables to #{$', function() { 25 | const result = less2sass.convert('@san-serif-stack: helvetica, arial; @standard-fonts: ~\'@{san-serif-stack}, sans-serif\';'); 26 | assert.equal(result, '$san-serif-stack: helvetica, arial; $standard-fonts: \'#{$san-serif-stack}, sans-serif\';'); 27 | }); 28 | 29 | it('converts @ for variables to $', function() { 30 | const result = less2sass.convert('@var1: #000;'); 31 | assert.equal(result, '$var1: #000;'); 32 | }); 33 | 34 | it('converts multiple variables in the same line', function() { 35 | const result = less2sass.convert('@var1: #000; @var2: #fff;'); 36 | assert.equal(result, '$var1: #000; $var2: #fff;'); 37 | }); 38 | 39 | it('still converts variables have the word "media" in them', function() { 40 | const result = less2sass.convert('@mediaType: screen;'); 41 | assert.equal(result, '$mediaType: screen;'); 42 | }); 43 | 44 | it('still converts variables have the word "import" in them', function() { 45 | const result = less2sass.convert('@importType: screen;'); 46 | assert.equal(result, '$importType: screen;'); 47 | }); 48 | 49 | it('still converts variables have the word "import" in them', function() { 50 | const result = less2sass.convert('@mixinType: screen;'); 51 | assert.equal(result, '$mixinType: screen;'); 52 | }); 53 | 54 | it('does not convert @ to $ for media queries', function() { 55 | const result = less2sass.convert('@media(min-width:768px) {}'); 56 | assert.equal(result, '@media(min-width:768px) {}'); 57 | }); 58 | 59 | it('does not convert @ to $ for @mixin statements', function() { 60 | const result = less2sass.convert('@mixin screen() {}'); 61 | assert.equal(result, '@mixin screen() {}'); 62 | }); 63 | 64 | it('does not convert @ to $ for @import statements', function() { 65 | const result = less2sass.convert('@import "common"'); 66 | assert.equal(result, '@import "common"'); 67 | }); 68 | 69 | it('does not convert @ to $ for @font-face statements', function() { 70 | const result = less2sass.convert('@font-face {}'); 71 | assert.equal(result, '@font-face {}'); 72 | }); 73 | 74 | it('does not convert @ to $ for @keyframes statements', function() { 75 | const result = less2sass.convert('@keyframes {}'); 76 | assert.equal(result, '@keyframes {}'); 77 | }); 78 | }); 79 | 80 | describe("~ strings", function() { 81 | it('converts ~\'\' to \'\'', function() { 82 | const result = less2sass.convert('~\'san-serif\''); 83 | assert.equal(result, '\'san-serif\''); 84 | }); 85 | 86 | it('converts ~"" to ""', function() { 87 | const result = less2sass.convert('~"san-serif"'); 88 | assert.equal(result, '"san-serif"'); 89 | }); 90 | }); 91 | 92 | describe("colour helpers", function() { 93 | it('converts spin function to adjust-hue', function() { 94 | const result = less2sass.convert('spin(#aaaaaa, 10)'); 95 | assert.equal(result, 'adjust-hue(#aaaaaa, 10)'); 96 | }); 97 | }); 98 | describe("extend", function() { 99 | it('converts &:extend syntax', function() { 100 | const result = less2sass.convert('.bar {\n &:extend(.foo2);\n}'); 101 | assert.equal(result, '.bar {\n @extend .foo2;\n}'); 102 | }); 103 | }); 104 | 105 | describe("mixins", function() { 106 | it('converts mixin declarations to use the @mixins syntax', function() { 107 | const result = less2sass.convert('.drop-shadow(@x-axis: 0, @y-axis: 1px, @blur: 2px, @alpha: 0.1) {\n}'); 108 | assert.equal(result, '@mixin drop-shadow($x-axis: 0, $y-axis: 1px, $blur: 2px, $alpha: 0.1) {\n}'); 109 | }); 110 | 111 | it('converts mixin declarations with new lines in param list and retains the new lines', function() { 112 | const result = less2sass.convert('.drop-shadow(\n @x-axis: 0,\n @y-axis: 1px,\n @blur: 2px,\n @alpha: 0.1) {\n}'); 113 | assert.equal(result, '@mixin drop-shadow(\n $x-axis: 0,\n $y-axis: 1px,\n $blur: 2px,\n $alpha: 0.1) {\n}'); 114 | }); 115 | 116 | it('converts mixin declarations with semicolons in param list and treat them as params', function() { 117 | // http://lesscss.org/features/#mixins-parametric-feature-mixins-with-multiple-parameters 118 | // Semicolons separated params. 119 | const result = less2sass.convert('.button-variant(@color; @background; @border) {\n /**/\n}'); 120 | assert.equal(result, '@mixin button-variant($color, $background, $border) {\n /**/\n}'); 121 | }); 122 | 123 | it('do not converts mixin declarations with semicolons and colons mixed in param list', function() { 124 | // http://lesscss.org/features/#mixins-parametric-feature-mixins-with-multiple-parameters 125 | const result = less2sass.convert('.name(1, 2, 3; something, else) {\n}'); 126 | // TODO Flag the case as ambiguous? 127 | assert.equal(result, '.name(1, 2, 3; something, else) {\n}'); 128 | }); 129 | 130 | it('converts mixin declarations with mixin call inlined', function() { 131 | const result = less2sass.convert('.setTapColor(@c:rgba(0,0,0,0)) {\n -webkit-tap-highlight-color: @c;\n}'); 132 | assert.equal(result, '@mixin setTapColor($c:rgba(0,0,0,0)) {\n -webkit-tap-highlight-color: $c;\n}'); 133 | }); 134 | 135 | it('converts mixin declarations with zero param', function() { 136 | const result = less2sass.convert('.foo() {\n display: block;\n}'); 137 | assert.equal(result, '@mixin foo() {\n display: block;\n}'); 138 | }); 139 | 140 | it('converts mixin call without argments to use the @include syntax', function() { 141 | const result = less2sass.convert('.box-sizing();'); 142 | assert.equal(result, '@include box-sizing();'); 143 | }); 144 | 145 | it('converts mixin call in shorthand form to use the @include syntax', function() { 146 | const result = less2sass.convert('.box-sizing;'); 147 | assert.equal(result, '@include box-sizing;'); 148 | }); 149 | 150 | it('converts mixin call with arguments to use the @include syntax', function() { 151 | const result = less2sass.convert('.drop-shadow(0, 2px, 4px, 0.4);'); 152 | assert.equal(result, '@include drop-shadow(0, 2px, 4px, 0.4);'); 153 | }); 154 | 155 | it('does not convert .foo .bar', function() { 156 | const result = less2sass.convert('.foo .bar {}'); 157 | assert.equal(result, '.foo .bar {}'); 158 | }); 159 | 160 | it('does not convert .5em (or similar)', function() { 161 | const result = less2sass.convert('font-size: .5em;'); 162 | assert.equal(result, 'font-size: .5em;'); 163 | }); 164 | }); 165 | 166 | describe('imports', function() { 167 | it('convert imports with the .less extension to .scss', function() { 168 | const result = less2sass.convert('@import \'app/app.less\';'); 169 | assert.equal(result, '@import \'app/app.scss\';'); 170 | }); 171 | }); 172 | 173 | describe('functions', function() { 174 | describe('unit', function() { 175 | // http://lesscss.org/functions/#misc-functions-unit 176 | it('convert one param call of less unit to sass unit-less', function() { 177 | // http://sass-lang.com/documentation/Sass/Script/Functions.html#unitless-instance_method 178 | const result = less2sass.convert('unit(5em)'); 179 | assert.equal(result, 'unit-less(5em)'); 180 | }); 181 | it('convert two param call of less unit without unit in first param to dimension + unit', function() { 182 | const result = less2sass.convert('unit(42,px)'); 183 | assert.equal(result, '0px + 42'); 184 | }); 185 | it('convert two param call of less unit with unit in first param to unit conversion', function() { 186 | // https://www.sitepoint.com/understanding-sass-units/ 187 | const result = less2sass.convert('unit(5in,px)'); 188 | assert.equal(result, '0px + 5in'); 189 | }); 190 | it('manage variable in first param', function() { 191 | const result = less2sass.convert('unit($size,px)'); 192 | assert.equal(result, '0px + $size'); 193 | }); 194 | }); 195 | }); 196 | 197 | describe("control flow", function() { 198 | it.skip('strips out { and }', function() { 199 | // TODO 200 | assert.equal(false, true); 201 | }); 202 | 203 | it.skip('strips out ;', function() { 204 | // TODO 205 | assert.equal(false, true); 206 | }); 207 | }); 208 | 209 | }); 210 | --------------------------------------------------------------------------------