├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── api-extractor.json ├── bin └── tiny-sass ├── changelog.md ├── cli.ts ├── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── readme.md ├── rollup.config.js ├── scripts ├── build.js ├── release.sh ├── test-production.js └── test-source.ts ├── src ├── __tests__ │ ├── __snapshots__ │ │ ├── compile.spec.ts.snap │ │ ├── render.spec.ts.snap │ │ └── traverse.spec.ts.snap │ ├── compile.spec.ts │ ├── render.spec.ts │ ├── scss │ │ └── basic.scss │ └── traverse.spec.ts ├── codegen.ts ├── compile.ts ├── css-module │ ├── import │ │ └── importModule.ts │ ├── index.ts │ ├── module.md │ ├── use │ │ └── loader.ts │ └── util.ts ├── enviroment │ └── Enviroment.ts ├── genCodeVisitor.ts ├── global.ts ├── index.ts ├── interpret │ ├── index.ts │ ├── loadPlugin.ts │ ├── processExpression.ts │ └── transformStatement.ts ├── parse │ ├── ast.ts │ ├── errors.ts │ ├── index.ts │ ├── input_stream.ts │ ├── lexical.ts │ ├── parse.ts │ └── util.ts ├── render.ts ├── transform-middleware │ ├── index.ts │ ├── transformExtend.ts │ ├── transformMedia.ts │ └── transformNest.ts ├── transform.ts ├── traverse.ts ├── tree │ ├── Media.ts │ ├── declaration.ts │ ├── index.ts │ ├── keyframes.ts │ ├── rule.ts │ ├── selector.ts │ ├── text.ts │ ├── tree.ts │ └── util.ts └── type │ ├── codegen.ts │ ├── index.ts │ └── options.ts ├── test-dist ├── ast │ ├── atrule │ │ └── keyframes.json │ ├── comment.json │ ├── extend.json │ ├── flow-control │ │ ├── each │ │ │ └── each.json │ │ ├── else-if.json │ │ ├── else.json │ │ └── if.json │ ├── function │ │ ├── function-with-if.json │ │ └── function.json │ ├── mixin │ │ ├── basic.json │ │ ├── mixin-keyframes-content.json │ │ ├── optional-params.json │ │ ├── params.json │ │ └── var-key-parent-selector.json │ ├── module │ │ ├── circular │ │ │ └── circular-reference.json │ │ ├── forward │ │ │ └── bootstrap-forward.json │ │ ├── import.json │ │ ├── index.json │ │ ├── use-import │ │ │ └── use-import.json │ │ └── use │ │ │ ├── index.json │ │ │ └── use.json │ ├── nest │ │ ├── at-rules-and-bubbling.json │ │ └── var-nested.json │ ├── operator.json │ ├── plugin │ │ └── plugin.json │ ├── repeat.json │ ├── selector │ │ └── complicated-selector.json │ ├── var-nested.json │ └── var-simple.json ├── code-gen-ast │ ├── atrule │ │ └── keyframes.json │ ├── comment.json │ ├── extend.json │ ├── flow-control │ │ ├── each │ │ │ └── each.json │ │ ├── else-if.json │ │ ├── else.json │ │ └── if.json │ ├── function │ │ ├── function-with-if.json │ │ └── function.json │ ├── mixin │ │ ├── basic.json │ │ ├── mixin-keyframes-content.json │ │ ├── optional-params.json │ │ ├── params.json │ │ └── var-key-parent-selector.json │ ├── module │ │ ├── forward │ │ │ └── bootstrap-forward.json │ │ ├── import.json │ │ ├── index.json │ │ ├── use-import │ │ │ └── use-import.json │ │ └── use │ │ │ ├── index.json │ │ │ └── use.json │ ├── nest │ │ ├── at-rules-and-bubbling.json │ │ └── var-nested.json │ ├── operator.json │ ├── plugin │ │ └── plugin.json │ ├── repeat.json │ ├── selector │ │ └── complicated-selector.json │ ├── var-nested.json │ └── var-simple.json ├── css │ ├── atrule │ │ └── keyframes.css │ ├── comment.css │ ├── extend.css │ ├── flow-control │ │ ├── each │ │ │ └── each.css │ │ ├── else-if.css │ │ ├── else.css │ │ └── if.css │ ├── function │ │ ├── function-with-if.css │ │ └── function.css │ ├── mixin │ │ ├── basic.css │ │ ├── mixin-keyframes-content.css │ │ ├── optional-params.css │ │ ├── params.css │ │ └── var-key-parent-selector.css │ ├── module │ │ ├── forward │ │ │ └── bootstrap-forward.css │ │ ├── import.css │ │ ├── use-import │ │ │ └── use-import.css │ │ └── use │ │ │ └── use.css │ ├── nest │ │ ├── at-rules-and-bubbling.css │ │ └── var-nested.css │ ├── operator.css │ ├── plugin │ │ └── plugin.css │ ├── repeat.css │ ├── selector │ │ └── complicated-selector.css │ ├── var-nested.css │ └── var-simple.css └── source-map │ ├── atrule │ └── keyframes.css.map │ ├── comment.css.map │ ├── extend.css.map │ ├── flow-control │ ├── each │ │ └── each.css.map │ ├── else-if.css.map │ ├── else.css.map │ └── if.css.map │ ├── function │ ├── function-with-if.css.map │ └── function.css.map │ ├── mixin │ ├── basic.css.map │ ├── mixin-keyframes-content.css.map │ ├── optional-params.css.map │ ├── params.css.map │ └── var-key-parent-selector.css.map │ ├── module │ ├── forward │ │ └── bootstrap-forward.css.map │ ├── import.css.map │ ├── index.css.map │ ├── use-import │ │ └── use-import.css.map │ └── use │ │ ├── index.css.map │ │ └── use.css.map │ ├── nest │ ├── at-rules-and-bubbling.css.map │ └── var-nested.css.map │ ├── operator.css.map │ ├── plugin │ └── plugin.css.map │ ├── repeat.css.map │ ├── selector │ └── complicated-selector.css.map │ └── var-simple.css.map ├── test-sass-dist ├── atrule │ ├── keyframes.css │ └── keyframes.css.map ├── comment.css ├── comment.css.map ├── extend.css ├── extend.css.map ├── flow-control │ ├── each │ │ ├── each.css │ │ └── each.css.map │ ├── else-if.css │ ├── else-if.css.map │ ├── else.css │ ├── else.css.map │ ├── if.css │ └── if.css.map ├── function │ ├── function-with-if.css │ ├── function-with-if.css.map │ ├── function.css │ └── function.css.map ├── mixin │ ├── basic.css │ ├── basic.css.map │ ├── mixin-keyframes-content.css │ ├── mixin-keyframes-content.css.map │ ├── optional-params.css │ ├── optional-params.css.map │ ├── params.css │ ├── params.css.map │ ├── var-key-parent-selector.css │ └── var-key-parent-selector.css.map ├── module │ ├── circular │ │ └── circular-reference.css │ ├── forward │ │ ├── bootstrap-forward.css │ │ └── bootstrap-forward.css.map │ ├── import.css │ ├── import.css.map │ ├── index.css │ ├── index.css.map │ ├── use-import │ │ ├── use-import.css │ │ └── use-import.css.map │ └── use │ │ ├── use.css │ │ └── use.css.map ├── nest │ ├── at-rules-and-bubbling.css │ ├── at-rules-and-bubbling.css.map │ ├── var-nested.css │ └── var-nested.css.map ├── operator.css ├── operator.css.map ├── plugin │ ├── plugin.css │ └── plugin.css.map ├── repeat.css ├── repeat.css.map ├── selector │ ├── complicated-selector.css │ └── complicated-selector.css.map ├── var-nested.css ├── var-nested.css.map ├── var-simple.css └── var-simple.css.map ├── test ├── atrule │ └── keyframes.scss ├── comment.scss ├── extend.scss ├── flow-control │ ├── each │ │ └── each.scss │ ├── else-if.scss │ ├── else.scss │ └── if.scss ├── function │ ├── function-with-if.scss │ └── function.scss ├── mixin │ ├── basic.scss │ ├── mixin-keyframes-content.scss │ ├── optional-params.scss │ ├── params.scss │ └── var-key-parent-selector.scss ├── module │ ├── _base-2.scss │ ├── _base.scss │ ├── circular │ │ ├── _c1.scss │ │ ├── _c2.scss │ │ └── circular-reference.scss │ ├── forward │ │ ├── _color.scss │ │ ├── _forward.scss │ │ ├── _var.scss │ │ └── bootstrap-forward.scss │ ├── import.scss │ ├── use-import │ │ ├── _base-2.scss │ │ ├── _base.scss │ │ └── use-import.scss │ └── use │ │ ├── _base-2.scss │ │ ├── _base-3.scss │ │ ├── _base.scss │ │ └── use.scss ├── nest │ ├── at-rules-and-bubbling.scss │ └── var-nested.scss ├── operator.scss ├── plugin │ ├── my-plugin.js │ └── plugin.scss ├── repeat.scss ├── selector │ └── complicated-selector.scss └── var-simple.scss ├── todos.md ├── transform.md ├── traversal.md └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | temp 4 | js-module-test -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "tiny-sass", 10 | "type": "node", 11 | "request": "launch", 12 | "stopOnEntry": false, 13 | "cwd": "${workspaceFolder}", 14 | "runtimeArgs": [ 15 | "-r", 16 | "ts-node/register" 17 | ], 18 | "args": [ 19 | "${workspaceFolder}/scripts/test-source.ts" 20 | ], 21 | "preLaunchTask": null, 22 | "runtimeExecutable": null, 23 | "env": { 24 | "NODE_ENV": "development", 25 | "TS_NODE_FILES": "true" 26 | }, 27 | "console": "integratedTerminal", 28 | "sourceMaps": true 29 | }, 30 | { 31 | "name": "Debug Jest Tests", 32 | "type": "node", 33 | "request": "launch", 34 | // "program": "${workspaceFolder}/scripts/jest.js", 35 | "runtimeArgs": [ 36 | "--inspect-brk", 37 | "${workspaceRoot}/node_modules/.bin/jest", 38 | "--runInBand", 39 | "--coverage", 40 | "false" 41 | ], 42 | "console": "integratedTerminal", 43 | "internalConsoleOptions": "neverOpen" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 wizardpisces 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "mainEntryPointFilePath": "/dist/types/cli.d.ts", 4 | "dtsRollup": { 5 | "enabled": true, 6 | "publicTrimmedFilePath": "/dist/.d.ts" 7 | }, 8 | "apiReport": { 9 | "enabled": true, 10 | "reportFolder": "/temp/" 11 | }, 12 | "docModel": { 13 | "enabled": true 14 | }, 15 | "tsdocMetadata": { 16 | "enabled": false 17 | }, 18 | "messages": { 19 | "compilerMessageReporting": { 20 | "default": { 21 | "logLevel": "warning" 22 | } 23 | }, 24 | "extractorMessageReporting": { 25 | "default": { 26 | "logLevel": "warning", 27 | "addToApiReportFile": true 28 | }, 29 | "ae-missing-release-tag": { 30 | "logLevel": "none" 31 | } 32 | }, 33 | "tsdocMessageReporting": { 34 | "default": { 35 | "logLevel": "warning" 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /bin/tiny-sass: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const run = require('../dist/tiny-sass-compiler.cjs.prod.js'); 4 | 5 | const [node_env, bin_path, ...args] = process.argv; 6 | 7 | run(args[0], args[1],{generateAstFile:true}) -------------------------------------------------------------------------------- /cli.ts: -------------------------------------------------------------------------------- 1 | import { 2 | parse, 3 | transform, 4 | generate, 5 | requireCSS 6 | // compile = parse + transform + generate 7 | } from './src' 8 | import { RootNode } from './src/parse/ast' 9 | import fs from 'fs'; 10 | import path from 'path'; 11 | import mkdirp from 'mkdirp'; 12 | import { CodegenResult } from './src/type'; 13 | 14 | export interface RunOptions { 15 | genOtherInfo: boolean 16 | sourceMap: boolean 17 | } 18 | 19 | function run( 20 | sourceDir: string, 21 | outputDir: string = './', 22 | options: RunOptions = { 23 | genOtherInfo: false, 24 | sourceMap: false 25 | } 26 | ): void { 27 | 28 | let sourceDirLength = path.basename(sourceDir).length; 29 | 30 | if (!fs.existsSync(outputDir)) { 31 | mkdirp.sync(outputDir) 32 | } 33 | 34 | function render(filePath) { 35 | 36 | if (path.extname(filePath) !== '.scss') return; 37 | 38 | let cssRequired = requireCSS(filePath), 39 | basename = path.basename(filePath, '.scss'), 40 | sourceDirname = path.dirname(filePath), 41 | 42 | normalDistPath = path.join(outputDir, sourceDirname.substr(sourceDirLength)), 43 | astDistPath = path.join(outputDir, 'ast', sourceDirname.substr(sourceDirLength)), 44 | codeGenAstDistPath = path.join(outputDir, 'code-gen-ast', sourceDirname.substr(sourceDirLength)), 45 | sourceMapDistPath = path.join(outputDir, 'source-map', sourceDirname.substr(sourceDirLength)), 46 | cssDistPath = path.join(outputDir, 'css', sourceDirname.substr(sourceDirLength)), 47 | parsedAst: RootNode; 48 | 49 | if (!options.genOtherInfo) { 50 | if (!fs.existsSync(normalDistPath)) { 51 | mkdirp.sync(normalDistPath) 52 | } 53 | cssDistPath = normalDistPath 54 | } else { 55 | if (!fs.existsSync(cssDistPath)) { 56 | mkdirp.sync(cssDistPath) 57 | } 58 | if (!fs.existsSync(codeGenAstDistPath)) { 59 | mkdirp.sync(codeGenAstDistPath) 60 | } 61 | if (!fs.existsSync(astDistPath)) { 62 | mkdirp.sync(astDistPath) 63 | } 64 | if (!fs.existsSync(sourceMapDistPath)) { 65 | mkdirp.sync(sourceMapDistPath) 66 | } 67 | } 68 | 69 | try { 70 | parsedAst = parse(cssRequired.source, cssRequired) 71 | } catch (e) { 72 | console.error(`\nParser Error:\n filePath: ${filePath}\n`, e) 73 | return; 74 | } 75 | 76 | function write_parsed_ast(cb) { 77 | fs.writeFile(path.join(astDistPath, basename + '.json'), JSON.stringify(parsedAst, null, 2), function (err) { 78 | if (err) { 79 | console.error(err) 80 | return console.error(`parse failed ${basename}`); 81 | } 82 | cb() 83 | }) 84 | } 85 | 86 | function write_compiled() { 87 | let compiled: CodegenResult; 88 | 89 | try { 90 | transform(parsedAst, { 91 | ...cssRequired 92 | }) 93 | compiled = generate(parsedAst, { 94 | sourceMap: options.sourceMap 95 | }) 96 | } catch (e) { 97 | console.log('Error source code path: ', filePath) 98 | console.log(e) 99 | return; 100 | } 101 | 102 | const { ast, map, code } = compiled; 103 | 104 | function writeResultCode(cb) { 105 | fs.writeFile(path.join(cssDistPath, basename + '.css'), (code), function (err) { 106 | if (err) { 107 | return console.error(`write css failed ${basename}`); 108 | } 109 | cb() 110 | }) 111 | } 112 | 113 | function writeCodegenAst(cb) { 114 | fs.writeFile(path.join(codeGenAstDistPath, basename + '.json'), JSON.stringify(ast, null, 2), function (err) { 115 | if (err) { 116 | return console.error(`write transformed ast failed ${basename},\nError:${err}`); 117 | } 118 | cb() 119 | }) 120 | } 121 | 122 | function writeSourceMap() { 123 | fs.writeFile(path.join(sourceMapDistPath, basename + '.css.map'), JSON.stringify(map, null, 2), function (err) { 124 | if (err) { 125 | return console.error(`write source map failed ${basename}`); 126 | } 127 | console.log(`compile success ${basename}`) 128 | }) 129 | } 130 | 131 | writeResultCode(() => { 132 | if (!options.genOtherInfo) { 133 | return; 134 | } 135 | writeCodegenAst(() => { 136 | writeSourceMap() 137 | }) 138 | }) 139 | } 140 | 141 | if (!options.genOtherInfo) { 142 | write_compiled() 143 | } else { 144 | write_parsed_ast(() => { 145 | write_compiled() 146 | }) 147 | } 148 | } 149 | 150 | function renderDir(sourceDir) { 151 | fs.readdirSync(sourceDir).forEach((filename) => { 152 | /** 153 | * do not compile partial files as entry point 154 | * https://sass-lang.com/documentation/at-rules/use 155 | */ 156 | if (filename.startsWith('_')) { 157 | return; 158 | } 159 | 160 | // if (filename!== 'flow-control' && filename !== 'each' && filename !=='each.scss') return; 161 | // if (filename !== 'mixin' && filename !== 'mixin-keyframes-content.scss') return; 162 | // if (filename!== 'var-simple.scss') return; 163 | // if (filename!='nest' && filename !== 'at-rules-and-bubbling.scss') return; 164 | // if (filename!== 'function' && !filename.startsWith('function')) return; 165 | // if (filename !== 'selector' && filename != 'complicated-selector.scss') return; 166 | // if (filename !== 'module' && filename != 'use' && filename!=='use.scss') return; 167 | 168 | let filePath = path.join(sourceDir, filename), 169 | stat = fs.lstatSync(filePath) 170 | if (stat.isFile()) { 171 | return render(filePath) 172 | } else if (stat.isDirectory()) { 173 | renderDir(filePath) 174 | } else { 175 | console.error('Unknown file type') 176 | } 177 | }) 178 | } 179 | 180 | renderDir(sourceDir) 181 | 182 | } 183 | 184 | export * from './src' 185 | 186 | export { 187 | run 188 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const isNode = 4 | typeof process !== 'undefined' && 5 | Object.prototype.toString.call(process) === '[object process]'; 6 | 7 | if (isNode) { 8 | module.exports = require('./dist/tiny-sass-compiler.cjs.prod.js') 9 | } else { 10 | module.exports = require('./dist/tiny-sass-compiler.esm-browser.prod.js') 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | globals: { 4 | __DEV__: true, 5 | __VERSION__: require('./package.json').version, 6 | }, 7 | watchPathIgnorePatterns: ['/node_modules/', '/test*/', '/.git/'], 8 | moduleFileExtensions: ['ts', 'js', 'json'], 9 | rootDir: __dirname, 10 | testMatch: ['/src/**/__tests__/**/*spec.[jt]s?(x)'], 11 | testPathIgnorePatterns: ['/node_modules/'], 12 | transform: { 13 | "^.+\\.(t|j)sx?$": "ts-jest" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-sass-compiler", 3 | "version": "0.12.2", 4 | "description": "simple scss compiler", 5 | "main": "index.js", 6 | "module": "dist/tiny-sass-compiler.esm-browser.prod.js", 7 | "types": "dist/tiny-sass-compiler.d.ts", 8 | "unpkg": "dist/tinySassCompiler.global.js", 9 | "jsdelivr": "dist/tinySassCompiler.global.js", 10 | "files": [ 11 | "dist", 12 | "bin", 13 | "index.js" 14 | ], 15 | "bin": { 16 | "tiny-sass": "bin/tiny-sass" 17 | }, 18 | "scripts": { 19 | "test-source": "node -v && node -r ts-node/register scripts/test-source.ts", 20 | "test": "node scripts/test-production.js", 21 | "jest": "jest", 22 | "build": "node scripts/build.js", 23 | "release": "sh scripts/release.sh", 24 | "sass-test": "sass ./test/:./test-sass-dist/" 25 | }, 26 | "engines": { 27 | "node": ">=10.x" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/wizardpisces/tiny-sass-compiler.git" 32 | }, 33 | "keywords": [ 34 | "scss", 35 | "css", 36 | "compiler", 37 | "ast" 38 | ], 39 | "author": "wizardpisces@gmail.com", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/wizardpisces/tiny-sass-compiler/issues" 43 | }, 44 | "homepage": "https://github.com/wizardpisces/tiny-sass-compiler#readme", 45 | "devDependencies": { 46 | "@microsoft/api-extractor": "^7.9.11", 47 | "@rollup/plugin-commonjs": "^15.0.0", 48 | "@rollup/plugin-json": "^4.1.0", 49 | "@rollup/plugin-node-resolve": "^9.0.0", 50 | "@rollup/plugin-replace": "^2.3.3", 51 | "@types/jest": "^26.0.0", 52 | "brotli": "^1.3.2", 53 | "jest": "^29.6.1", 54 | "rollup": "^2.26.11", 55 | "rollup-plugin-terser": "^7.0.1", 56 | "rollup-plugin-typescript2": "^0.27.2", 57 | "sass": "^1.26.10", 58 | "ts-jest": "^26.1.0", 59 | "ts-node": "^9.0.0", 60 | "tslib": "^2.0.0", 61 | "typescript": "^3.8.3" 62 | }, 63 | "dependencies": { 64 | "mkdirp": "^1.0.4", 65 | "source-map": "^0.6.1" 66 | } 67 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Another SASS compiler written from scratch, runnable both in ***node*** and ***browser*** environment 4 | 5 | [Demo](https://wizardpisces.github.io/sass) 6 | 7 | ## Target 8 | This project(**Not Production Ready**) is for people who want to understand how to write a compiler; Basic Steps: 9 | 10 | 1. SourceCode (SASS Scanning) 11 | 2. TokenStream (Parsing) 12 | 3. [AST or Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (Analysis) 13 | 4. [IR or Intermediate_representation](https://en.wikipedia.org/wiki/Intermediate_representation) 14 | 5. HighLevelLanguage (CSS Code and SourceMap Generation) 15 | 16 | ## Features: 17 | 18 | 1. Variables 19 | 2. Nesting 20 | 3. Extend/Inheritance 21 | 4. Operators 22 | 5. Mixins 23 | 6. Modules ([@import](https://sass-lang.com/documentation/at-rules/import) and [@use](https://sass-lang.com/documentation/at-rules/use)(which is more efficient than @import)) 24 | 25 | ## Installation 26 | 27 | ```bash 28 | npm install --save tiny-sass-compiler 29 | ``` 30 | 31 | ### Usage in node 32 | 33 | ```ts 34 | import sass from "tiny-sass-compiler"; 35 | 36 | //render API 37 | sass.render({filename:'./default.scss'},(err,result)=>{ 38 | console.log(result.code) 39 | }) 40 | // or renderSync 41 | const result = sass.renderSync({filename:'./default.scss'}) 42 | console.log(result.code) 43 | ``` 44 | 45 | ### Usage in browser 46 | 47 | ```ts 48 | import {compile} from 'tiny-sass-compiler/dist/tiny-sass-compiler.esm-browser.prod.js' 49 | const result = compile(` 50 | $font-stack: Helvetica, sans-serif; 51 | $primary-color: #333; 52 | 53 | body .test{ 54 | font: 100% $font-stack; 55 | color: $primary-color; 56 | }`) 57 | 58 | console.log(result.code) 59 | ``` 60 | 61 | ## Terminal Setup 62 | 63 | ```bash 64 | npm install -g tiny-sass-compiler 65 | ``` 66 | 67 | ### Command Line Interface 68 | 69 | *Support **.scss** extension for now* 70 | 71 | ### Usage 72 | 73 | 74 | `tiny-sass [output]` 75 | 76 | The `input` and `output` must be a directory 77 | 78 | Example 79 | 80 | ```bash 81 | tiny-sass src/ dist/ 82 | ``` 83 | 84 | *will generate intermediate AST file in dist/ast and css file in dist/css* 85 | 86 | ## Test 87 | 88 | ### Snapshot Test 89 | **Development** 90 | ```bash 91 | npm run test-source 92 | ``` 93 | **Production** 94 | ```bash 95 | npm run test 96 | ``` 97 | *will generate intermediate AST file in test-dist/ast and css file in test-dist/css* 98 | 99 | ### Example: 100 | 101 | #### input: 102 | 103 | ```scss 104 | $font-stack: Helvetica, sans-serif; 105 | $primary-color: #333; 106 | 107 | body .test{ 108 | font: 100% $font-stack; 109 | color: $primary-color; 110 | } 111 | ``` 112 | #### output: 113 | 114 | CSS 115 | ```css 116 | body .test { 117 | font: 100% Helvetica, sans-serif; 118 | color: #333; 119 | } 120 | ``` 121 | 122 | ### Jest test 123 | 124 | ```bash 125 | npm run jest 126 | ``` 127 | 128 | *Interested in more intermediate status? View files in ./test-dist/ which contains ast after parse+transform and dist code after codegen* 129 | 130 | ## Other Readme 131 | 132 | * [AST Descriptor Syntax](https://github.com/wizardpisces/tiny-sass-compiler/blob/master/src/parse/ast.ts) 133 | * [AST travesal Plugin](https://github.com/wizardpisces/tiny-sass-compiler/blob/master/traversal.md) 134 | * [AST Interpret Transform Plugin](https://github.com/wizardpisces/tiny-sass-compiler/blob/master/transform.md) 135 | 136 | ## Reference 137 | 138 | * [csstree](https://github.com/csstree/csstree) 139 | * [astexplorer](https://astexplorer.net/#/gist/244e2fb4da940df52bf0f4b94277db44/e79aff44611020b22cfd9708f3a99ce09b7d67a8) 140 | * [vue-next/compiler-core](https://github.com/vuejs/vue-next/tree/master/packages/compiler-core) 141 | * [lisperator](http://lisperator.net/pltut/) 142 | * [less](https://less.bootcss.com/features/#plugin-at-rules) 143 | * [hast](https://github.com/syntax-tree/hast) 144 | * [unist](https://github.com/syntax-tree/unist) 145 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import ts from 'rollup-plugin-typescript2' 3 | import replace from '@rollup/plugin-replace' 4 | import json from '@rollup/plugin-json' 5 | import nodeResolve from '@rollup/plugin-node-resolve' 6 | const name = 'tiny-sass-compiler'; 7 | const resolve = p => path.resolve(__dirname, p) 8 | const pkg = require(resolve(`package.json`)) 9 | const masterVersion = pkg.version 10 | const packageOptions = pkg.buildOptions || {} 11 | 12 | export default packageConfigs 13 | 14 | const outputConfigs = { 15 | cjs: { 16 | file: resolve(`dist/${name}.cjs.js`), 17 | format: `cjs` 18 | }, 19 | 'esm-bundler': { 20 | file: resolve(`dist/${name}.esm-bundler.js`), 21 | format: `es` 22 | }, 23 | 24 | global: { 25 | file: resolve(`dist/${name}.global.js`), 26 | format: `iife` 27 | }, 28 | 29 | // browser distribution 30 | 'esm-browser': { 31 | file: resolve(`dist/${name}.esm-browser.js`), 32 | format: `es` 33 | }, 34 | // 'cjs-browser': { 35 | // file: resolve(`dist/${name}.cjs-browser.js`), 36 | // format: `cjs` 37 | // } 38 | } 39 | 40 | const packageFormats = Object.keys(outputConfigs); 41 | const packageConfigs = [] 42 | 43 | if (process.env.NODE_ENV === 'production') { 44 | packageFormats.forEach(format => { 45 | packageConfigs.push(createProductionConfig(format)) 46 | packageConfigs.push(createMinifiedConfig(format)) 47 | }) 48 | } 49 | 50 | function createConfig(format, output, plugins = []) { 51 | if (!output) { 52 | console.log(require('chalk').yellow(`invalid format: "${format}"`)) 53 | process.exit(1) 54 | } 55 | 56 | const isBrowserESMBuild = /browser/.test(format) 57 | const isGlobalBuild = /global/.test(format) 58 | const isBrowserBuild = isBrowserESMBuild | isGlobalBuild 59 | 60 | output.sourcemap = false 61 | output.externalLiveBindings = false 62 | 63 | if(isGlobalBuild){ 64 | output.name = 'tinySassCompiler' 65 | } 66 | 67 | const shouldEmitDeclarations = process.env.TYPES != null 68 | // const shouldEmitDeclarations =false 69 | 70 | 71 | const tsPlugin = ts({ 72 | useTsconfigDeclarationDir: true, 73 | check: process.env.NODE_ENV === 'production', 74 | tsconfig: resolve('tsconfig.json'), 75 | /** 76 | * caution: 77 | * 78 | * clean set to true to avoid cache not track dependency which is not 100 % right 79 | * in circumstance when change enum order, another dependent file keep the old order if not modified 80 | */ 81 | // clean: true, 82 | cacheRoot: resolve('node_modules/.rts2_cache'), 83 | tsconfigOverride: { 84 | compilerOptions: { 85 | sourceMap: output.sourcemap, 86 | declaration: shouldEmitDeclarations, 87 | // declarationMap: shouldEmitDeclarations, 88 | module: 'ESNext' // when build for production, do not compile module, leave it to rollup 89 | }, 90 | exclude: ['**/__tests__', 'test-dts'], 91 | include: ['src'] 92 | } 93 | }) 94 | 95 | const nodePlugins = [ 96 | nodeResolve({ 97 | preferBuiltins: true 98 | }), 99 | require('@rollup/plugin-commonjs')({ 100 | sourceMap: false 101 | }) 102 | ] 103 | 104 | const external = isBrowserBuild ? ['fs', 'path'] : ['fs', 'path', 'util']; // node build externalize system package 105 | const entryFile = isBrowserBuild ? resolve('src/index.ts') : resolve('cli.ts') 106 | 107 | return { 108 | input: entryFile, 109 | external, 110 | plugins: [ 111 | json({ 112 | namedExports: false 113 | }), 114 | tsPlugin, 115 | createReplacePlugin(isBrowserBuild), 116 | ...nodePlugins, 117 | ...plugins 118 | ], 119 | output, 120 | onwarn: (msg, warn) => { 121 | if (!/Circular/.test(msg)) { 122 | warn(msg) 123 | } 124 | }, 125 | treeshake: { 126 | moduleSideEffects: false 127 | } 128 | } 129 | } 130 | 131 | function createReplacePlugin(isBrowserBuild) { 132 | const replacements = { 133 | __COMMIT__: `"${process.env.COMMIT}"`, 134 | __VERSION__: `"${masterVersion}"`, 135 | __BROWSER__: isBrowserBuild 136 | } 137 | 138 | return replace(replacements) 139 | } 140 | 141 | function createProductionConfig(format) { 142 | return createConfig(format, { 143 | file: outputConfigs[format].file.replace(/\.js$/, '.prod.js'), 144 | format: outputConfigs[format].format 145 | }) 146 | } 147 | 148 | function createMinifiedConfig(format) { 149 | const { 150 | terser 151 | } = require('rollup-plugin-terser') 152 | return createConfig( 153 | format, { 154 | file: outputConfigs[format].file.replace(/\.js$/, '.prod.js'), 155 | format: outputConfigs[format].format 156 | }, 157 | [ 158 | terser({ 159 | module: /^esm/.test(format), 160 | compress: { 161 | ecma: 2015, 162 | pure_getters: true 163 | } 164 | }) 165 | ] 166 | ) 167 | } -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | /* 2 | Produces production builds and stitches together d.ts files. 3 | */ 4 | 5 | const fs = require('fs-extra') 6 | const path = require('path') 7 | const chalk = require('chalk') 8 | const execa = require('execa') 9 | const { 10 | gzipSync 11 | } = require('zlib') 12 | const { 13 | compress 14 | } = require('brotli') 15 | 16 | const args = require('minimist')(process.argv.slice(2)) 17 | const sourceMap = true; 18 | 19 | const commit = execa.sync('git', ['rev-parse', 'HEAD']).stdout.slice(0, 7) 20 | 21 | const resolve = p => path.resolve(__dirname,'../', p) 22 | const pkgDir = resolve('./') 23 | const pkg = require(`${pkgDir}/package.json`) 24 | 25 | run() 26 | 27 | async function run() { 28 | await fs.remove(`${pkgDir}/dist`) 29 | await build() 30 | checkSize() 31 | } 32 | 33 | async function build() { 34 | 35 | 36 | const env = 'production' 37 | try{ 38 | 39 | await execa( 40 | 'rollup', 41 | [ 42 | '-c', 43 | '--environment', 44 | [ 45 | `COMMIT:${commit}`, 46 | `NODE_ENV:${env}`, 47 | `TYPES:true`, 48 | sourceMap ? `SOURCE_MAP:true` : `` 49 | ] 50 | .filter(Boolean) 51 | .join(',') 52 | ], { 53 | stdio: 'inherit' 54 | } 55 | ) 56 | }catch(e){ 57 | console.log('error rollup',__dirname,e) 58 | } 59 | 60 | if (pkg.types) { 61 | console.log( 62 | chalk.bold(chalk.yellow(`Rolling up type definitions for tiny-sass-compiler...`)) 63 | ) 64 | 65 | // build types 66 | const { 67 | Extractor, 68 | ExtractorConfig 69 | } = require('@microsoft/api-extractor') 70 | 71 | const extractorConfigPath = path.resolve(pkgDir, `api-extractor.json`) 72 | const extractorConfig = ExtractorConfig.loadFileAndPrepare( 73 | extractorConfigPath 74 | ) 75 | const extractorResult = Extractor.invoke(extractorConfig, { 76 | localBuild: true, 77 | showVerboseMessages: true 78 | }) 79 | 80 | if (extractorResult.succeeded) { 81 | // concat additional d.ts to rolled-up dts 82 | const typesDir = path.resolve(pkgDir, 'types') 83 | if (await fs.exists(typesDir)) { 84 | const dtsPath = path.resolve(pkgDir, pkg.types) 85 | const existing = await fs.readFile(dtsPath, 'utf-8') 86 | const typeFiles = await fs.readdir(typesDir) 87 | const toAdd = await Promise.all( 88 | typeFiles.map(file => { 89 | return fs.readFile(path.resolve(typesDir, file), 'utf-8') 90 | }) 91 | ) 92 | await fs.writeFile(dtsPath, existing + '\n' + toAdd.join('\n')) 93 | } 94 | console.log( 95 | chalk.bold(chalk.green(`API Extractor completed successfully.`)) 96 | ) 97 | } else { 98 | console.error( 99 | `API Extractor completed with ${extractorResult.errorCount} errors` + 100 | ` and ${extractorResult.warningCount} warnings` 101 | ) 102 | process.exitCode = 1 103 | } 104 | 105 | await fs.remove(`${pkgDir}/dist/types`) 106 | } 107 | } 108 | 109 | function checkSize() { 110 | const target = pkg.name 111 | const pkgDir = path.resolve(__dirname,`./${target}`) 112 | checkFileSize(resolve(`dist/${target}.cjs.prod.js`)) 113 | } 114 | 115 | function checkFileSize(filePath) { 116 | if (!fs.existsSync(filePath)) { 117 | return 118 | } 119 | const file = fs.readFileSync(filePath) 120 | const minSize = (file.length / 1024).toFixed(2) + 'kb' 121 | const gzipped = gzipSync(file) 122 | const gzippedSize = (gzipped.length / 1024).toFixed(2) + 'kb' 123 | const compressed = compress(file) 124 | const compressedSize = (compressed.length / 1024).toFixed(2) + 'kb' 125 | console.log( 126 | `${chalk.gray( 127 | chalk.bold(path.basename(filePath)) 128 | )} min:${minSize} / gzip:${gzippedSize} / brotli:${compressedSize}` 129 | ) 130 | } 131 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | echo "Run test... " 5 | 6 | npm run jest 7 | npm run build 8 | npm run test 9 | 10 | echo "Enter commit message: " 11 | read MESSAGE 12 | 13 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r 14 | echo # (optional) move to a new line 15 | if [[ $REPLY =~ ^[Yy]$ ]] 16 | then 17 | echo "Releasing $VERSION ..." 18 | # commit 19 | git add -A 20 | git commit -m "[build] $VERSION,[message] $MESSAGE" 21 | npm version $VERSION --message "[release] $VERSION" 22 | 23 | # publish 24 | git push origin refs/tags/v$VERSION 25 | git push 26 | npm publish 27 | fi -------------------------------------------------------------------------------- /scripts/test-production.js: -------------------------------------------------------------------------------- 1 | const { 2 | run 3 | } = require('../index.js') 4 | /** 5 | * Todos: 6 | * add test cases besides input output snapshots 7 | */ 8 | run('./test', './test-dist', { 9 | genOtherInfo: true, 10 | sourceMap: true 11 | }); -------------------------------------------------------------------------------- /scripts/test-source.ts: -------------------------------------------------------------------------------- 1 | import { run } from '../cli' 2 | 3 | /** 4 | * Todos: 5 | * add test cases besides input output snapshots 6 | */ 7 | run('./test', './test-dist', { 8 | genOtherInfo: true, 9 | sourceMap: true 10 | }); -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/compile.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`compiler: integration tests Source Map 1`] = ` 4 | "body .test{ 5 | font:100% Helvetica, sans-serif; 6 | color:#333; 7 | } 8 | .message,.success,.message-shared{ 9 | border:1px solid #ccc; 10 | padding:10px; 11 | color:#333; 12 | } 13 | .error{ 14 | border:1px solid #ccc; 15 | padding:10px; 16 | color:#333; 17 | } 18 | .success{ 19 | border-color:green; 20 | } 21 | .error{ 22 | color:red; 23 | } 24 | " 25 | `; 26 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/render.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`render: tests render 1`] = ` 4 | "body .test{ 5 | font:100% Helvetica, sans-serif; 6 | color:#333; 7 | } 8 | " 9 | `; 10 | 11 | exports[`render: tests renderSync 1`] = ` 12 | "body .test{ 13 | font:100% Helvetica, sans-serif; 14 | color:#333; 15 | } 16 | " 17 | `; 18 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/traverse.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`plugin: PluginFn traversePluginFn 1`] = ` 4 | "body .test{ 5 | color:#333; 6 | } 7 | " 8 | `; 9 | 10 | exports[`plugin: PluginFn traverseVisitorPlugin 1`] = ` 11 | "body .test{ 12 | color:#333; 13 | } 14 | " 15 | `; 16 | 17 | exports[`plugin: PluginFn traverseVisitorPluginWithObject 1`] = ` 18 | "body .test{ 19 | color:#333; 20 | } 21 | " 22 | `; 23 | -------------------------------------------------------------------------------- /src/__tests__/compile.spec.ts: -------------------------------------------------------------------------------- 1 | import {compile} from '../index' 2 | import { SourceMapConsumer, RawSourceMap } from 'source-map' 3 | 4 | describe('compiler: integration tests', () => { 5 | const source = ` 6 | $stack: Helvetica, sans-serif; 7 | $primary: #333; 8 | 9 | body .test{ 10 | font: 100% $stack; 11 | color: $primary; 12 | } 13 | 14 | .message-shared { 15 | border: 1px solid #ccc; 16 | padding: 10px; 17 | color: $primary; 18 | } 19 | 20 | %message-shared { 21 | border: 1px solid #ccc; 22 | padding: 10px; 23 | color: $primary; 24 | } 25 | 26 | .message { 27 | @extend .message-shared; 28 | } 29 | 30 | .success { 31 | @extend .message-shared; 32 | border-color: green; 33 | } 34 | 35 | .error{ 36 | color:red; 37 | @extend %message-shared 38 | } 39 | `.trim() 40 | interface Pos { 41 | line: number 42 | column: number 43 | name?: string 44 | } 45 | 46 | function getPositionInCode( 47 | code: string, 48 | token: string, 49 | expectName: string | boolean = false 50 | ): Pos { 51 | const generatedOffset = code.indexOf(token) 52 | let line = 1 53 | let lastNewLinePos = -1 54 | for (let i = 0; i < generatedOffset; i++) { 55 | if (code.charCodeAt(i) === 10 /* newline char code */) { 56 | line++ 57 | lastNewLinePos = i 58 | } 59 | } 60 | const res: Pos = { 61 | line, 62 | column: 63 | lastNewLinePos === -1 64 | ? generatedOffset 65 | : generatedOffset - lastNewLinePos - 1 66 | } 67 | if (expectName) { 68 | res.name = typeof expectName === 'string' ? expectName : token 69 | } 70 | return res 71 | } 72 | test('Source Map', () => { 73 | const { code, map } = compile(source, { 74 | sourceMap: true, 75 | filename: `foo.scss`, 76 | source 77 | }) 78 | 79 | expect(code).toMatchSnapshot() 80 | 81 | expect(map!.sources).toEqual([`foo.scss`]) 82 | expect(map!.sourcesContent).toEqual([source]) 83 | 84 | const consumer = new SourceMapConsumer(map as RawSourceMap) 85 | 86 | // selector 87 | expect( 88 | consumer.originalPositionFor(getPositionInCode(code, `body .test`)) 89 | ).toMatchObject(getPositionInCode(source, `body .test`)) 90 | 91 | // property 92 | expect( 93 | consumer.originalPositionFor(getPositionInCode(code, `font`)) 94 | ).toMatchObject(getPositionInCode(source, `font`)) 95 | 96 | expect( 97 | consumer.originalPositionFor(getPositionInCode(code, `color`)) 98 | ).toMatchObject(getPositionInCode(source, `color`)) 99 | 100 | /** 101 | * variable (one property line may contain multiple varialble which is multiple to one line situation) 102 | * in reality scss filename and line map could resolve most problem, so column info could be discarded in sourceMap 103 | */ 104 | 105 | // expect( 106 | // consumer.originalPositionFor(getPositionInCode(code, `#333`)) 107 | // ).toMatchObject(getPositionInCode(source, `$primary`)) 108 | 109 | // @extend 110 | expect( 111 | consumer.originalPositionFor(getPositionInCode(code, `.message,.success,.message-shared`)) 112 | ).toMatchObject(getPositionInCode(source, `.message-shared`)) 113 | 114 | expect( 115 | consumer.originalPositionFor(getPositionInCode(code, `border-color`)) 116 | ).toMatchObject(getPositionInCode(source, `border-color`)) 117 | 118 | // Todos @import @mixin @if ... 119 | }) 120 | }) -------------------------------------------------------------------------------- /src/__tests__/render.spec.ts: -------------------------------------------------------------------------------- 1 | import { render, renderSync } from '../render' 2 | 3 | describe('render: tests', () => { 4 | test('render', () => { 5 | render({ filename:__dirname+'/scss/basic.scss'},(err,res)=>{ 6 | if(res){ 7 | expect(res.code).toMatchSnapshot() 8 | } 9 | }) 10 | }) 11 | test('renderSync', () => { 12 | const res = renderSync({ filename: __dirname+'/scss/basic.scss'}) 13 | expect(res.code).toMatchSnapshot() 14 | }) 15 | }) -------------------------------------------------------------------------------- /src/__tests__/scss/basic.scss: -------------------------------------------------------------------------------- 1 | $stack: Helvetica, 2 | sans-serif; 3 | $primary: #333; 4 | 5 | body .test { 6 | font: 100% $stack; 7 | color: $primary; 8 | } -------------------------------------------------------------------------------- /src/__tests__/traverse.spec.ts: -------------------------------------------------------------------------------- 1 | import { parse, transform, generate } from '../index' 2 | import { NodeTypes, CodegenNode } from '../parse/ast' 3 | import { traverse, TraverseContext, PluginVisitor } from '../traverse' 4 | 5 | describe('plugin: PluginFn', () => { 6 | const source = ` 7 | $stack: Helvetica, sans-serif; 8 | $primary: #333; 9 | 10 | body .test{ 11 | font: 100% $stack; 12 | color: $primary; 13 | } 14 | 15 | `.trim() 16 | let result = 17 | `body .test{ 18 | color:#333; 19 | } 20 | ` 21 | beforeEach(() => { 22 | traverse.resetPlugin(); 23 | }); 24 | 25 | function traversePluginFn(context: TraverseContext) { 26 | let { currentNode } = context; 27 | if ((currentNode as CodegenNode).type === NodeTypes.DECLARATION && (currentNode as CodegenNode).left.value === 'font') { 28 | context.removeNode() 29 | } 30 | } 31 | 32 | function traverseVisitorPlugin(): PluginVisitor { 33 | return { 34 | visitor: { 35 | [NodeTypes.DECLARATION](context: TraverseContext) { 36 | let { currentNode } = context; 37 | if ((currentNode as CodegenNode).left.value === 'font') { 38 | context.removeNode() 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | function traverseVisitorPluginWithObject(): PluginVisitor { 46 | return { 47 | visitor: { 48 | [NodeTypes.DECLARATION]: { 49 | enter(context: TraverseContext) { 50 | let { currentNode } = context; 51 | if ((currentNode as CodegenNode).left.value === 'font') { 52 | context.removeNode() 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | test('traverseVisitorPlugin', () => { 61 | let parsedAst = parse(source, { 62 | filename: `default.scss`, 63 | source 64 | }) 65 | transform(parsedAst, { 66 | filename: `default.scss`, 67 | }) 68 | traverse.registerPlugin(traverseVisitorPlugin()) 69 | 70 | let { code } = generate(parsedAst) 71 | expect(code).toEqual(result) 72 | expect(code).toMatchSnapshot() 73 | }) 74 | 75 | test('traverseVisitorPluginWithObject', () => { 76 | let parsedAst = parse(source, { 77 | filename: `default.scss`, 78 | source 79 | }) 80 | transform(parsedAst, { 81 | filename: `default.scss`, 82 | }) 83 | traverse.registerPlugin(traverseVisitorPluginWithObject()) 84 | 85 | let { code } = generate(parsedAst) 86 | expect(code).toEqual(result) 87 | expect(code).toMatchSnapshot() 88 | }) 89 | 90 | test('traversePluginFn', () => { 91 | 92 | let parsedAst = parse(source, { 93 | filename: `default.scss`, 94 | source 95 | }) 96 | transform(parsedAst, { 97 | filename: `default.scss`, 98 | }) 99 | traverse.registerPlugin(traversePluginFn) 100 | 101 | let { code } = generate(parsedAst) 102 | expect(code).toEqual(result) 103 | expect(code).toMatchSnapshot() 104 | 105 | // walk(parsedAst,testPlugin) 106 | 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RootNode, 3 | Position, 4 | SourceLocation, 5 | } from './parse/ast'; 6 | import { SourceMapGenerator } from 'source-map' 7 | import { advancePositionWithMutation } from './parse/util' 8 | import { traverse } from './traverse' 9 | // todos complete CodegenNode type 10 | import { isBrowser } from './global' 11 | import { CodegenContext, CodegenResult, CodegenOptions } from './type' 12 | import { genCodeVisitor } from './genCodeVisitor'; 13 | 14 | function createCodegenContext( 15 | ast: RootNode, 16 | { 17 | sourceMap = false 18 | }: CodegenOptions 19 | ): CodegenContext { 20 | const context: CodegenContext = { 21 | code: '', 22 | sourceMap, 23 | column: 1,// source-map column is 0 based 24 | line: 1, 25 | offset: 0, 26 | indentLevel: 0, 27 | // source: ast.source, 28 | push(code: string, sourceLoc: SourceLocation) { 29 | function isValidSourceLocation(sourceLoc: SourceLocation) { 30 | return sourceLoc && sourceLoc.end.offset > 0; 31 | } 32 | context.code += code 33 | if (!isBrowser() && context.map) { 34 | if (isValidSourceLocation(sourceLoc)) { 35 | addMapping(sourceLoc.start, sourceLoc.filename) 36 | } 37 | 38 | advancePositionWithMutation(context, code) 39 | 40 | // end info is not needed for now ,which could compress source-map size 41 | // if(sourceLoc) { 42 | // addMapping(sourceLoc.sourceLoc.end) 43 | // } 44 | } 45 | }, 46 | indent() { 47 | newline(++context.indentLevel) 48 | }, 49 | deindent(withoutNewLine = false) { 50 | if (withoutNewLine) { 51 | --context.indentLevel 52 | } else { 53 | newline(--context.indentLevel) 54 | } 55 | }, 56 | newline() { 57 | newline(context.indentLevel) 58 | } 59 | } 60 | 61 | function newline(n: number) { 62 | context.push('\n' + ` `.repeat(n)) 63 | } 64 | 65 | function addMapping(loc: Position, filename: string, name?: string) { 66 | context.map!.addMapping({ 67 | name, 68 | source: filename, 69 | original: { 70 | line: loc.line, 71 | column: loc.column - 1 72 | }, 73 | generated: { 74 | line: context.line, 75 | column: context.column - 1 76 | } 77 | }) 78 | } 79 | 80 | if (!isBrowser() && sourceMap) { 81 | context.map = new SourceMapGenerator() 82 | Object.keys(ast.fileSourceMap).forEach(filename => context.map!.setSourceContent(filename, ast.fileSourceMap[filename])) 83 | } 84 | 85 | return context 86 | } 87 | 88 | export function generate( 89 | ast: RootNode, 90 | options: CodegenOptions = {} 91 | ): CodegenResult { 92 | 93 | // run plugins automatically before codegen 94 | traverse.applyPlugins(ast); 95 | 96 | const context = createCodegenContext(ast, options); 97 | 98 | // reset Plugin to prevent user registered plugins from being executed multiple times 99 | traverse.resetPlugin().registerPlugin(genCodeVisitor(context)).applyPlugins(ast); 100 | 101 | return { 102 | ast, 103 | code: context.code, 104 | // SourceMapGenerator does have toJSON() method but it's not in the types 105 | map: context.map ? (context.map as any).toJSON() : undefined 106 | }; 107 | } -------------------------------------------------------------------------------- /src/compile.ts: -------------------------------------------------------------------------------- 1 | import { CompilerOptions } from './type/options' 2 | import baseParse from './parse' 3 | import { generate } from './codegen' 4 | import { CodegenResult } from './type' 5 | import { transform } from './transform' 6 | 7 | export default function baseCompile( 8 | scss: string, 9 | options: CompilerOptions = { 10 | filename: 'default.scss', 11 | source: '' 12 | } 13 | ): CodegenResult { 14 | let ast = baseParse(scss, options) 15 | 16 | transform(ast, { 17 | ...options 18 | }) 19 | 20 | return generate(ast, options) 21 | } -------------------------------------------------------------------------------- /src/css-module/import/importModule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * will be deprecated, replaced by @use and merge @import to @use 3 | */ 4 | import fs from 'fs' 5 | import parse from '../../parse' 6 | import { TransformContext, ParserOptions } from '../../type' 7 | import { RootNode, Statement, TextNode, NodeTypes, ImportStatement } from '../../parse/ast' 8 | import { resolveSourceFilePath } from '../util' 9 | 10 | // prevent same file be parsed multiple times 11 | const parseCache = new Object(null) 12 | // will be deprecated by @use 13 | export function importModule(context: TransformContext, root: RootNode) { 14 | let statementList: RootNode['children'] = []; 15 | 16 | /** 17 | * Todos 18 | * only support @import in the head lines for now 19 | * 20 | * depth/left first walk import module, push child module children before parent module child 21 | */ 22 | function walkNode(root: RootNode) { 23 | 24 | function requireModule(module: ImportStatement, parent: RootNode) { 25 | module.params.forEach((param: TextNode) => { 26 | let filename = param.value, 27 | filePath = resolveSourceFilePath(filename, context.filename), 28 | source = fs.readFileSync(filePath, 'utf8'), 29 | parseOptions: ParserOptions = { 30 | filename: filePath, 31 | source 32 | }; 33 | 34 | 35 | let childRootNode: RootNode = parseCache[filePath] || parse(source, parseOptions) 36 | 37 | parseCache[filePath] = childRootNode 38 | 39 | walkNode(childRootNode) 40 | 41 | // collect source map file in module 42 | Object.assign(parent.fileSourceMap, childRootNode.fileSourceMap) 43 | }) 44 | } 45 | 46 | root.children.forEach((statement) => { 47 | if (statement.type === NodeTypes.IMPORT) { 48 | requireModule(statement, root) 49 | } else { 50 | statementList.push(statement as Statement) 51 | } 52 | }) 53 | } 54 | 55 | walkNode(root) 56 | 57 | root.children = statementList; 58 | } -------------------------------------------------------------------------------- /src/css-module/index.ts: -------------------------------------------------------------------------------- 1 | export { compatibleLoadModule } from './use/loader' -------------------------------------------------------------------------------- /src/css-module/module.md: -------------------------------------------------------------------------------- 1 | ## Todos 2 | 3 | * source has been parsed 2 times in main file , how to design more suitable API? 4 | 5 | ## Reference 6 | * https://sass-lang.com/documentation/at-rules/use 7 | * https://github.com/nodejs/node/blob/master/doc/api/modules.md 8 | -------------------------------------------------------------------------------- /src/css-module/util.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | 4 | export const EXTNAME_GLOBAL = '.scss' 5 | export function resolveSourceFilePath(filename: string, parentPath = './') { 6 | let extname = path.extname(filename), 7 | basename = path.basename(filename), 8 | dirname = path.dirname(filename), 9 | parentPathDir = path.dirname(parentPath), 10 | filePath = path.join(parentPathDir, dirname, '_' + basename + (extname ? '' : EXTNAME_GLOBAL)); 11 | 12 | if(!fs.existsSync(filePath)){ 13 | filePath = path.join(parentPathDir, dirname, basename + (extname ? '' : EXTNAME_GLOBAL)); 14 | } 15 | return filePath 16 | } 17 | 18 | export function createModuleError(msg){ 19 | throw Error(`[ tiny-sass-compiler -> module ]: ${msg}`) 20 | } -------------------------------------------------------------------------------- /src/enviroment/Enviroment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * use Kind to differentiate same name but different kind variable 3 | * @function vs @mixin 4 | * */ 5 | 6 | import { NodeTypes } from '../parse/ast'; 7 | 8 | export type NamespacedId = { 9 | name: string 10 | namespace: string | string[] | undefined 11 | } 12 | 13 | export type Kind = NodeTypes.VARIABLE | NodeTypes.VAR_KEY // VAR_KEY only involes get data so merge into NodeTypes.VARIABLE for now 14 | | NodeTypes.FUNCTION | NodeTypes.RETURN 15 | | NodeTypes.MIXIN | NodeTypes.CONTENT 16 | 17 | export class Variable { 18 | private _value: string | number | Function | Environment 19 | kind: Kind = NodeTypes.VARIABLE // set default as VARIABLE 20 | constructor(kind: Kind, val: any) { 21 | this.kind = kind; 22 | this._value = val; 23 | } 24 | get value() { 25 | return this._value; 26 | } 27 | set value(val: any) { 28 | this._value = val; 29 | } 30 | } 31 | 32 | export class Environment { 33 | vars: { 34 | [kind: string]: { 35 | [name: string]: Variable 36 | } 37 | } 38 | /** 39 | * to resolve @use namespaced variable ,@include ,callExpression etc 40 | * etc: 41 | * @use './vars.scss' 42 | * body{ 43 | * color: vars.$color-primary 44 | * } 45 | */ 46 | envMap: { 47 | [namespace: string]: Environment 48 | } = {} 49 | 50 | parent: Environment | null 51 | 52 | lookUpModuleChain: Environment[] = [] 53 | 54 | constructor(parent: Environment | null) { 55 | /** 56 | * this line could also be written in : this.vars = {} 57 | * history : 58 | * used for convinient scoped vars query, 59 | * which is migrated to parent search then get data by two vars 60 | */ 61 | this.vars = Object.create(parent ? parent.vars : null); 62 | /** 63 | * link child envMap to the same top envMap to reduce @use module namespaced env query time 64 | */ 65 | this.envMap = parent && parent.envMap || {} 66 | /** 67 | * link parent for convinient scope search 68 | */ 69 | this.parent = parent; 70 | 71 | /** 72 | * added first in first 73 | */ 74 | this.lookUpModuleChain = [this] 75 | } 76 | 77 | /** 78 | * lookup steps: 79 | * 1. search this module 80 | * 2. search up this -> parent module 81 | * 3. search through @forward modules (loopUpChain) 82 | * 4. repeat step 1 83 | */ 84 | 85 | lookup(name: string, kind: Kind): Environment | undefined { 86 | 87 | let envList: Environment[] = [this,...this.lookUpModuleChain]; 88 | 89 | function searchScope(scope: Environment | null) { 90 | while (scope) { 91 | if (scope.vars[kind] && scope.vars[kind][name]) 92 | return scope; 93 | scope = scope.parent; 94 | } 95 | return undefined 96 | } 97 | 98 | return envList.find((moduleEnv: Environment) => { 99 | return searchScope(moduleEnv) !== undefined 100 | }) 101 | } 102 | 103 | public addLookUpModuleChain(env: Environment) { 104 | /** 105 | * later added module will be higher lookup (refer to lookup method) priority 106 | */ 107 | this.lookUpModuleChain.unshift(env) 108 | } 109 | 110 | public extend() { 111 | return new Environment(this); 112 | } 113 | /** 114 | * support for length one namespace for now 115 | */ 116 | public setEnvByName(name: string, env: Environment) { 117 | this.envMap[name] = env 118 | } 119 | 120 | public getEnvByNamespace(namespace: NamespacedId['namespace']): Environment { 121 | let env: Environment = this; 122 | 123 | if (Array.isArray(namespace) && namespace.length > 0) { 124 | let len = namespace.length, 125 | i = 0; 126 | 127 | while (len--) { 128 | env = env.envMap[namespace[i++]] 129 | } 130 | 131 | } else if (typeof namespace === 'string') { 132 | env = env.envMap[namespace] 133 | } 134 | 135 | return env 136 | } 137 | 138 | public get(name: string | NamespacedId, kind: Kind = NodeTypes.VARIABLE) { 139 | let result: any, 140 | env: Environment = this; 141 | 142 | if (typeof name === 'object') { 143 | env = env.getEnvByNamespace(name.namespace) 144 | if (!env) { 145 | throw new Error(`[Environment]: Undefined Environment name:${name.name},namespace:${JSON.stringify(name.namespace)}`); 146 | } 147 | name = name.name 148 | } 149 | 150 | result = env.lookup(name, kind); 151 | if (result) { 152 | return result.vars[kind][name].value 153 | } 154 | 155 | return undefined; 156 | } 157 | 158 | public def(name: string, value: any = '', kind: Kind = NodeTypes.VARIABLE) { 159 | 160 | if (kind === NodeTypes.VARIABLE && typeof value === 'function') { // outside register function plugin 161 | kind = NodeTypes.FUNCTION 162 | } 163 | 164 | if (!this.vars[kind]) { 165 | this.vars[kind] = {} 166 | } 167 | return this.vars[kind][name] = new Variable(kind, value); 168 | } 169 | 170 | public add(...args: Parameters) { 171 | this.def.apply(this, args) 172 | } 173 | }; -------------------------------------------------------------------------------- /src/genCodeVisitor.ts: -------------------------------------------------------------------------------- 1 | import { Plugin, TraverseContext } from './traverse'; 2 | import { NodeTypes, TextNode, SelectorNode, DeclarationStatement, MediaPrelude, MediaQuery, KeyframesPrelude, Atrule } from './parse/ast'; 3 | import { CodegenContext } from './type'; 4 | import { isEmptyNode, isKeyframesName } from './parse/util'; 5 | 6 | export function genCodeVisitor(context: CodegenContext): Plugin { 7 | return { 8 | visitor: { 9 | [NodeTypes.EMPTY]() { 10 | 11 | }, 12 | 13 | [NodeTypes.TEXT]: { 14 | enter(ctx: TraverseContext) { 15 | let node = ctx.currentNode 16 | context.push((node).value, node.loc) 17 | } 18 | }, 19 | [NodeTypes.SELECTOR]: { 20 | enter(ctx: TraverseContext) { 21 | let node = ctx.currentNode; 22 | try { 23 | context.push(node.value.value as string, node.loc) 24 | } catch (e) { 25 | console.log('*********** genSelector **************', e) 26 | } 27 | context.push('{'); 28 | context.indent(); 29 | } 30 | }, 31 | [NodeTypes.DECLARATION]: { 32 | enter(ctx: TraverseContext) { 33 | let node = ctx.currentNode; 34 | if (ctx.childIndex && !isEmptyNode(node)) { 35 | context.newline() 36 | } 37 | context.push(node.left.value, node.left.loc) 38 | context.push(':') 39 | context.push((node.right as TextNode).value, node.right.loc) 40 | context.push(';'); 41 | 42 | } 43 | }, 44 | [NodeTypes.RULE]: { 45 | enter(ctx: TraverseContext) { 46 | }, 47 | leave(ctx: TraverseContext) { 48 | context.deindent(); 49 | context.push('}'); 50 | 51 | if(ctx.parent!.type === NodeTypes.Atrule){ 52 | if(ctx.childIndex !== ctx.parent!.block.children.length -1){ 53 | context.newline() 54 | } 55 | }else{ 56 | context.newline() 57 | } 58 | }, 59 | }, 60 | [NodeTypes.KeyframesPrelude]: { 61 | enter(ctx: TraverseContext) { 62 | let node = ctx.currentNode; 63 | context.push(node.children.map((child) => (child as TextNode).value).join(' '), node.loc) 64 | context.push('{'); 65 | context.indent(); 66 | } 67 | }, 68 | [NodeTypes.MediaPrelude]: { 69 | enter(ctx: TraverseContext) { 70 | let node = ctx.currentNode 71 | let prelude: string = node.children.map((mediaQuery: MediaQuery) => { 72 | return mediaQuery.children.map(child => { 73 | if (child.type === NodeTypes.MediaFeature) { 74 | return `(${child.name}:${child.value.value})` 75 | } else if (child.type === NodeTypes.TEXT) { // csstree name Identifier eg: screen , and etc 76 | return child.value 77 | } 78 | }).join(' '); 79 | }).join(','); 80 | 81 | context.push(prelude) 82 | context.push('{'); 83 | context.indent(); 84 | } 85 | }, 86 | [NodeTypes.Atrule]: { 87 | enter(ctx: TraverseContext) { 88 | let node = ctx.currentNode;; 89 | if (node.name === 'media') { 90 | context.push('@media ') 91 | } else if (isKeyframesName(node.name)) { 92 | context.push(node.name + ' ') 93 | } 94 | }, 95 | leave(ctx: TraverseContext) { 96 | context.deindent(); 97 | context.push('}'); 98 | context.newline() 99 | }, 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /src/global.ts: -------------------------------------------------------------------------------- 1 | // Global compile-time function and also ts-node runtime function 2 | function isBrowser() { 3 | // @ts-ignore 4 | return '__BROWSER__' === '1'; 5 | } 6 | export { 7 | isBrowser 8 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as parse } from './parse' 2 | export { generate } from './codegen' 3 | export * from './traverse' 4 | export { transform } from './transform' 5 | export { default as compile } from './compile' 6 | export * from './render' 7 | -------------------------------------------------------------------------------- /src/interpret/index.ts: -------------------------------------------------------------------------------- 1 | export { transformStatement } from './transformStatement' -------------------------------------------------------------------------------- /src/interpret/loadPlugin.ts: -------------------------------------------------------------------------------- 1 | import { PluginStatement, createEmptyNode, EmptyNode } from '../parse/ast'; 2 | import path from 'path' 3 | import { TransformContext } from '@/type'; 4 | 5 | const EXTNAME_GLOBAL = '.js' 6 | export function loadPlugin(node: PluginStatement, context: TransformContext): EmptyNode { 7 | const { filename = './', env } = context; 8 | let filenameRelative = node.value.value, 9 | extname = path.extname(filenameRelative), 10 | basename = path.basename(filenameRelative), 11 | dirname = path.dirname(filenameRelative), 12 | 13 | /** 14 | * path.join generate: test/plugin/my-plugin which cannot be required by node 15 | * path.resolve generate: /Users/.../test/plugin/my-plugin which can be required by node 16 | */ 17 | resolvedFilePath = path.resolve(path.dirname(filename), dirname, basename + (extname ? '' : EXTNAME_GLOBAL)); 18 | 19 | require(resolvedFilePath).install(env) 20 | 21 | return createEmptyNode() 22 | } -------------------------------------------------------------------------------- /src/parse/errors.ts: -------------------------------------------------------------------------------- 1 | import { SourceLocation} from './ast' 2 | 3 | export interface CompilerError extends SyntaxError { 4 | code: ErrorCodes 5 | loc?: SourceLocation 6 | } 7 | export interface CoreCompilerError extends CompilerError { 8 | code: ErrorCodes 9 | } 10 | 11 | export function defaultOnError(error: CompilerError) { 12 | throw error 13 | } 14 | 15 | type messagesType = { [code: number]: string | Function } 16 | 17 | export function createCompilerError( 18 | code: T, 19 | loc?: SourceLocation, 20 | additionalMessage?: string, 21 | messages?: messagesType, 22 | ): T extends ErrorCodes ? CoreCompilerError : CompilerError { 23 | let msg = (messages || errorMessages)[code]; 24 | if(typeof msg === 'function'){ 25 | msg =( msg as Function)(additionalMessage); 26 | }else{ 27 | msg +='\n' + (additionalMessage || ``) 28 | } 29 | const error = new SyntaxError(String(msg)) as CompilerError 30 | error.code = code 31 | error.loc = loc 32 | return error as any 33 | } 34 | 35 | 36 | export const enum ErrorCodes { 37 | //parse error 38 | UNKNONWN_TOKEN_TYPE, 39 | UNKNOWN_CHAR, 40 | INVALID_LOC_POSITION, 41 | EXPECT_TEXT_NODE_AFTER_OPERATOR_NODE, 42 | EXPECTED_X, 43 | UNKNOWN_EXPRESSION_TYPE, 44 | UNKNOWN_STATEMENT_TYPE, 45 | UNDEFINED_VARIABLE, 46 | UNKNOWN_EVALUATE_BINARY_TYPE, 47 | EXPECT_BINARY_COMPUTE_TO_BE_NUMBER, 48 | UNKNOWN_KEYWORD, 49 | 50 | } 51 | 52 | export const errorMessages: messagesType = { 53 | // parse errors 54 | [ErrorCodes.UNKNONWN_TOKEN_TYPE]: 'Unknown token type.', 55 | [ErrorCodes.UNKNOWN_CHAR]: 'Unknown char.', 56 | [ErrorCodes.INVALID_LOC_POSITION]: 'Incorrect sourceLocation. start loc should be smaller than end loc.', 57 | [ErrorCodes.EXPECT_TEXT_NODE_AFTER_OPERATOR_NODE]: 'Expect text node after operator node.', 58 | [ErrorCodes.EXPECTED_X]: (str: string) =>`Expected "${str}"`, 59 | [ErrorCodes.UNKNOWN_EXPRESSION_TYPE]: 'Unknown expression type.', 60 | [ErrorCodes.UNKNOWN_STATEMENT_TYPE]: 'Unknown statement type.', 61 | [ErrorCodes.UNDEFINED_VARIABLE]: 'Undefined variable.', 62 | [ErrorCodes.UNKNOWN_EVALUATE_BINARY_TYPE]: 'Unknown evaluate binary type.', 63 | [ErrorCodes.EXPECT_BINARY_COMPUTE_TO_BE_NUMBER]: 'Expect binary compute to be number.', 64 | [ErrorCodes.UNKNOWN_KEYWORD]: 'Unknown keyword.', 65 | } 66 | -------------------------------------------------------------------------------- /src/parse/index.ts: -------------------------------------------------------------------------------- 1 | import input_stream from './input_stream' 2 | import lexical from './lexical' 3 | import parse from './parse' 4 | import { RootNode } from './ast' 5 | import { ParserOptions } from '@/type' 6 | // import runtime_schema_check from './runtime_ast_check' 7 | 8 | export default function baseParse( 9 | scss: string, 10 | options: ParserOptions 11 | ): RootNode { 12 | return parse(lexical(input_stream(scss,options.filename)), options) 13 | } -------------------------------------------------------------------------------- /src/parse/input_stream.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defaultOnError, 3 | ErrorCodes, 4 | createCompilerError 5 | } from './errors'; 6 | import { 7 | SourceLocation, 8 | Position 9 | } from './ast' 10 | 11 | export type InputStream = { 12 | next: () => string, 13 | peek: () => string, 14 | setCoordination: (position: Position) => void, 15 | getCoordination: () => Position, 16 | eof: () => boolean, 17 | croak: (msg: string) => void, 18 | emitError: (code: ErrorCodes, loc?: SourceLocation, additionalMessage?: string) => void 19 | 20 | } 21 | 22 | function input_stream(input: string, filename: string): InputStream { 23 | 24 | let offset = 0, line = 1, column = 1; 25 | return { 26 | next, 27 | peek, 28 | setCoordination, 29 | getCoordination, 30 | eof, 31 | croak, 32 | emitError 33 | }; 34 | 35 | function next() { 36 | let ch = input.charAt(offset++); 37 | 38 | if (ch == "\n") line++, column = 1; else column++; 39 | 40 | return ch; 41 | } 42 | 43 | function setCoordination(coordination: Position) { 44 | offset = coordination.offset; 45 | line = coordination.line; 46 | column = coordination.column; 47 | } 48 | 49 | function getCoordination() { 50 | return { 51 | offset, 52 | line, 53 | column 54 | } 55 | } 56 | 57 | function peek() { 58 | return input.charAt(offset); 59 | } 60 | 61 | function eof() { 62 | return peek() === ""; 63 | } 64 | 65 | function croak(msg) { 66 | throw new Error(msg + " (" + line + ":" + column + ")"); 67 | } 68 | 69 | function emitError( 70 | code: ErrorCodes, 71 | loc: SourceLocation = { 72 | start: getCoordination(), 73 | end: getCoordination(), 74 | filename 75 | }, 76 | additionalMessage: string = '' 77 | ): void { 78 | defaultOnError( 79 | createCompilerError(code, loc, additionalMessage) 80 | ) 81 | } 82 | } 83 | 84 | export default input_stream; -------------------------------------------------------------------------------- /src/parse/util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NodeTypes, 3 | TextNode, 4 | puncType, 5 | arithmeticOperator, 6 | Node, 7 | Position, 8 | SimpleExpressionNode, 9 | createTextNode 10 | } from './ast'; 11 | 12 | export const isArray = Array.isArray 13 | 14 | export const debug = (function () { 15 | let isDebug = false, 16 | count = 0; 17 | return () => { 18 | if (count++ > 20 && isDebug) { 19 | return true; 20 | } 21 | return false; 22 | } 23 | })() 24 | 25 | 26 | export function is_calculate_op_char(ch: arithmeticOperator | string) { 27 | let op_chars = ' + - * / % ' 28 | return op_chars.indexOf(' ' + ch + ' ') >= 0; 29 | } 30 | 31 | export function is_punc(ch: string | puncType): boolean { 32 | return ",;(){}#".indexOf(ch) >= 0; // support expr { #{var}:var } 33 | } 34 | 35 | export const PRECEDENCE = { 36 | "=": 1, 37 | "||": 2, 38 | "&&": 3, 39 | "<": 7, ">": 7, "<=": 7, ">=": 7, "==": 7, "!=": 7, 40 | "+": 10, "-": 10, 41 | "*": 20, "/": 20, "%": 20, 42 | }; 43 | 44 | export function is_operator(op: string) { 45 | return Object.keys(PRECEDENCE).includes(op) 46 | } 47 | 48 | export function isKeyframesName(name: string): boolean { 49 | return name.indexOf('keyframes') > -1 50 | } 51 | /** 52 | * fill whitespace between tokens which is removed in lexical analyze 53 | * 54 | */ 55 | 56 | export function fillWhitespace(tokens: SimpleExpressionNode[]) { 57 | if (tokens.length <= 1) return tokens; 58 | 59 | let list: SimpleExpressionNode[] = [], 60 | whitespaceToken: TextNode = createTextNode(' '), 61 | curIndex = 0, 62 | curToken, 63 | nextToken; 64 | 65 | while (curIndex < tokens.length - 1) { 66 | curToken = tokens[curIndex]; 67 | nextToken = tokens[curIndex + 1]; 68 | list.push(curToken) 69 | if (nextToken.loc.start.offset > curToken.loc.end.offset) { 70 | // one whitespace is enough to demonstrate semantics 71 | list.push(whitespaceToken) 72 | } 73 | curIndex++; 74 | } 75 | 76 | list.push(tokens[curIndex]) 77 | 78 | return list; 79 | } 80 | 81 | /** 82 | * todos: optimize deep_clone 83 | */ 84 | export function deepClone(obj: object) { 85 | return JSON.parse(JSON.stringify(obj)) 86 | } 87 | 88 | export function isEmptyNode(node: Node): boolean { 89 | return node.type === NodeTypes.EMPTY 90 | } 91 | 92 | export function isMediaNode(node: Node): boolean { 93 | return node.type === NodeTypes.Atrule && node.name === 'media'; 94 | } 95 | 96 | // function createPromiseCallback() { 97 | // var resolve, reject; 98 | // var promise = new Promise(function (_resolve, _reject) { 99 | // resolve = _resolve; 100 | // reject = _reject; 101 | // }); 102 | // var cb = function (err, res) { 103 | // if (err) { return reject(err) } 104 | // resolve(res || ''); 105 | // }; 106 | // return { promise: promise, cb: cb } 107 | // } 108 | 109 | // advance by mutation without cloning (for performance reasons), since this 110 | // gets called a lot in the parser 111 | export function advancePositionWithMutation( 112 | pos: Position, 113 | source: string, 114 | numberOfCharacters: number = source.length 115 | ): Position { 116 | let linesCount = 0 117 | let lastNewLinePos = -1 118 | for (let i = 0; i < numberOfCharacters; i++) { 119 | if (source.charCodeAt(i) === 10 /* newline char code */) { 120 | linesCount++ 121 | lastNewLinePos = i 122 | } 123 | } 124 | 125 | pos.offset += numberOfCharacters 126 | pos.line += linesCount 127 | pos.column = 128 | lastNewLinePos === -1 129 | ? pos.column + numberOfCharacters 130 | : numberOfCharacters - lastNewLinePos 131 | 132 | return pos 133 | } 134 | 135 | export function range(n: number) { 136 | let list: number[] = [], 137 | i = 0; 138 | while (n--) { 139 | list.push(i++) 140 | } 141 | return list 142 | } 143 | -------------------------------------------------------------------------------- /src/render.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import baseCompile from './compile' 3 | import { CompilerOptions, CodegenResult } from './type' 4 | 5 | export type RenderOptions = { 6 | filename: string 7 | } 8 | 9 | export type RenderCallback = (err: null | Error, result: CodegenResult | null) => void 10 | 11 | export function requireCSS(scssPath: string): CompilerOptions { 12 | return { 13 | source: fs.readFileSync(scssPath, 'utf8'), 14 | filename: scssPath 15 | } 16 | } 17 | 18 | export function render(options: RenderOptions, cb: RenderCallback) { 19 | try { 20 | let cssRequired = requireCSS(options.filename), 21 | result = baseCompile(cssRequired.source, cssRequired) 22 | cb(null, result) 23 | } catch (e) { 24 | cb(e, null) 25 | } 26 | } 27 | 28 | export function renderSync(options:RenderOptions){ 29 | let cssRequired = requireCSS(options.filename), 30 | result = baseCompile(cssRequired.source, cssRequired) 31 | return result 32 | } -------------------------------------------------------------------------------- /src/transform-middleware/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import transformNest from './transformNest' 3 | import transformExtend from './transformExtend' 4 | 5 | export const transformMiddleware = (ast) => [transformNest, transformExtend].reduce((ast, middleware) => { 6 | return middleware.call(null, ast) 7 | }, ast) -------------------------------------------------------------------------------- /src/transform-middleware/transformExtend.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {ast => ast} ast 4 | * compile @extend 5 | */ 6 | import { 7 | NodeTypes, 8 | RootNode, 9 | createEmptyNode, 10 | createTextNode, 11 | createSelectorNode 12 | } from '../parse/ast'; 13 | import { isEmptyNode } from '../parse/util'; 14 | 15 | export default function transformExtend(ast: RootNode) { 16 | /** 17 | * transform 18 | * 19 | * .basic{} 20 | * .primary{ 21 | * @extend .basic; 22 | * } 23 | * 24 | * to 25 | * 26 | * {'.basic':['primary']} 27 | */ 28 | let extendSelectorPair = {} 29 | 30 | function is_placeholder(exp) { 31 | return exp.type === NodeTypes.PLACEHOLDER; 32 | } 33 | 34 | function rm_empty_child(exp) { 35 | return exp.type === NodeTypes.RULE && (is_placeholder(exp.selector.value) || exp.children.length === 0) ? createEmptyNode() : exp 36 | } 37 | 38 | function collect_extend(exp) { 39 | function collect(rule) { 40 | rule.children = rule.children.map(exp => { 41 | if (exp.type === NodeTypes.EXTEND) { 42 | extendSelectorPair[exp.param.value] = (extendSelectorPair[exp.param.value] || []).concat(rule.selector.value.value) 43 | return createEmptyNode(); 44 | } 45 | return exp; 46 | }).filter(exp => !isEmptyNode(exp)) 47 | 48 | return rule; 49 | } 50 | return exp.type === NodeTypes.RULE ? collect(exp) : exp; 51 | } 52 | 53 | function transform_extend(exp) { 54 | function transform(rule) { 55 | if (is_placeholder(rule.selector.value)) { 56 | rule.selector = createSelectorNode( 57 | createTextNode( 58 | extendSelectorPair[rule.selector.value.value].join(','), 59 | rule.selector.loc 60 | ) 61 | ) 62 | 63 | } else { 64 | rule.selector = createSelectorNode( 65 | createTextNode( 66 | extendSelectorPair[rule.selector.value.value].concat(rule.selector.value.value).join(','), 67 | rule.selector.loc 68 | ) 69 | ) 70 | } 71 | return rule; 72 | } 73 | return exp.type === NodeTypes.RULE && extendSelectorPair[exp.selector.value.value] ? transform(exp) : exp; 74 | } 75 | 76 | function toplevel(ast) { 77 | 78 | ast.children = ast.children.map(exp => collect_extend(exp)) 79 | ast.children = ast.children.map(exp => transform_extend(exp)) 80 | ast.children = ast.children.map(exp => rm_empty_child(exp)).filter(exp => !isEmptyNode(exp)) 81 | return ast; 82 | } 83 | 84 | return toplevel(ast); 85 | } -------------------------------------------------------------------------------- /src/transform-middleware/transformMedia.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * buble @media 5 | * 6 | */ 7 | import { 8 | RootNode, 9 | NodeTypes, 10 | Node, 11 | SelectorNode, 12 | MediaStatement, 13 | RuleStatement, 14 | } from '../parse/ast'; 15 | 16 | import { 17 | isSameMediaPrelude, 18 | mergeMediaWithSamePrelude, 19 | } from '../tree/Media' 20 | 21 | import { 22 | Rule, 23 | Media 24 | } from '../tree' 25 | import { isMediaNode } from '../parse/util'; 26 | 27 | export function broadcastMedia(ast: RootNode) { 28 | 29 | function traverseChildren(children: Node[], parentSelectorMeta: SelectorNode['meta']) { 30 | children.forEach((child, index) => { 31 | if (child.type === NodeTypes.RULE) { 32 | child = new Rule(child as RuleStatement).addMeta(parentSelectorMeta).toJSON(); 33 | traverseChildren(child.children, child.selector.meta.slice()) 34 | } else if (child.type === NodeTypes.Atrule && child.name === 'media') { 35 | let newRule: RuleStatement = new Rule(child as MediaStatement).addMeta(parentSelectorMeta).toJSON() 36 | children.splice(index, 1, newRule) // replace media with newRule 37 | traverseChildren(newRule.children, newRule.selector.meta.slice()) 38 | } 39 | }) 40 | } 41 | 42 | traverseChildren(ast.children, []) 43 | } 44 | 45 | /** 46 | * 47 | * @param ast 48 | * this phase ast contain only one level children 49 | */ 50 | export function bubbleAndMergeMedia(ast: RootNode) { 51 | 52 | function bubbleMedia(ast:RootNode){ 53 | ast.children.forEach((child, index) => { 54 | if (child.type === NodeTypes.RULE && child.selector.meta.length) { // contains media data 55 | let newMedia:MediaStatement = new Media(child).toJSON() 56 | ast.children.splice(index,1,newMedia) 57 | } 58 | }) 59 | } 60 | 61 | function mergeMedia(ast:RootNode){ 62 | let children: RootNode['children'] = [] 63 | let prevMedia: MediaStatement | null = null; 64 | 65 | // merge consecutive media with the same prelude which breaked down from previous media broadcast and flatten 66 | ast.children.forEach(child => { 67 | if (isMediaNode(child)) { 68 | if (prevMedia) { 69 | if (isSameMediaPrelude(prevMedia.prelude, child.prelude)) { 70 | prevMedia = mergeMediaWithSamePrelude(prevMedia, child as MediaStatement) 71 | } else { 72 | children.push(prevMedia) 73 | prevMedia = child as MediaStatement 74 | } 75 | } else { 76 | prevMedia = child as MediaStatement 77 | } 78 | } else { 79 | // collect previous merged media and reset prevMedia 80 | prevMedia && children.push(prevMedia) && (prevMedia = null) 81 | children.push(child) 82 | } 83 | 84 | }) 85 | 86 | // resolve tailing media 87 | prevMedia && children.push(prevMedia) 88 | 89 | ast.children = children; 90 | } 91 | 92 | bubbleMedia(ast) 93 | mergeMedia(ast) 94 | 95 | } -------------------------------------------------------------------------------- /src/transform-middleware/transformNest.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * ast => ast 5 | * transform nested sass ast to flattened css ast 6 | * 7 | */ 8 | import { 9 | NodeTypes, 10 | RuleStatement, 11 | RootNode, 12 | SelectorNode, 13 | TextNode, 14 | createEmptyNode 15 | } from '../parse/ast'; 16 | import { isEmptyNode } from '../parse/util'; 17 | import { 18 | broadcastMedia, 19 | bubbleAndMergeMedia 20 | } from './transformMedia' 21 | 22 | export default function tranformNest(ast: RootNode) { 23 | 24 | // DFS search 25 | function flatten_nested_rule(ruleNode: RuleStatement, arr: RuleStatement[] = []): RuleStatement[] { 26 | 27 | function flatten(ruleNode: RuleStatement, parentSelector: string = '') { 28 | 29 | /** 30 | * https://sass-lang.com/documentation/style-rules/parent-selector 31 | * support parent selector with loosening restriction 32 | */ 33 | 34 | let containParentSelector = false; 35 | let selector: SelectorNode = ruleNode.selector as SelectorNode, 36 | selectorValue: TextNode = selector.value as TextNode; // after transform selector value will be TextNode 37 | 38 | if (selectorValue.value.indexOf('&') >= 0) { // & symbol stands for parentSelector 39 | containParentSelector = true; 40 | selectorValue.value = selectorValue.value.replace(/&/, parentSelector) 41 | } 42 | 43 | if (!containParentSelector) { 44 | selectorValue.value = (parentSelector + ' ' + selectorValue.value).trim() 45 | } 46 | 47 | /** 48 | * collect ruleNode reference before children traverse to keep flattened RuleStatement in order 49 | */ 50 | arr.push(ruleNode); 51 | 52 | ruleNode.children.forEach((exp, index) => { 53 | if (exp.type === NodeTypes.RULE) { 54 | // 动态替换掉即将被转换了的模块 55 | ruleNode.children.splice(index, 1, createEmptyNode()) 56 | flatten(exp, selectorValue.value) 57 | } 58 | }); 59 | 60 | /** 61 | * filter out empty node after all children traversed 62 | */ 63 | 64 | ruleNode.children = ruleNode.children.filter(node => !isEmptyNode(node)); 65 | ruleNode.children.length === 0 && arr.splice(arr.indexOf(ruleNode), 1) 66 | 67 | return arr; 68 | } 69 | 70 | return flatten(ruleNode) 71 | } 72 | 73 | function flattenNested(ast:RootNode){ 74 | let children: RootNode["children"] = []; 75 | 76 | ast.children.forEach(exp => { 77 | if (exp.type === NodeTypes.RULE) { 78 | children = children.concat(...flatten_nested_rule(exp)) 79 | } else if(exp.type === NodeTypes.BODY){ 80 | children = children.concat(exp.children as RootNode["children"]) 81 | } else { 82 | children.push(exp) 83 | } 84 | }); 85 | 86 | ast.children = children 87 | } 88 | 89 | function toplevel(ast: RootNode) { 90 | // propagate media before flatten selector 91 | broadcastMedia(ast); 92 | 93 | flattenNested(ast) 94 | 95 | // extract after flatten selector and media 96 | bubbleAndMergeMedia(ast) 97 | 98 | return ast; 99 | } 100 | 101 | return toplevel(ast) 102 | } -------------------------------------------------------------------------------- /src/transform.ts: -------------------------------------------------------------------------------- 1 | import { RootNode, NodeTypes } from './parse/ast' 2 | import { TransformOptions, TransformContext } from './type' 3 | import { defaultOnError } from './parse/errors' 4 | import { 5 | Environment 6 | } from './enviroment/Enviroment' 7 | import { transformMiddleware } from './transform-middleware/index' 8 | import { isBrowser } from './global' 9 | import { compatibleLoadModule } from './css-module' 10 | import { interpret } from './css-module/use/loader' 11 | import { transformStatement } from './interpret' 12 | // - NodeTransform: 13 | // Transforms that operate directly on a childNode. NodeTransforms may mutate, 14 | // replace or remove the node being processed. 15 | 16 | export function createTransformContext( 17 | root: RootNode, 18 | { 19 | nodeTransforms = [transformStatement], 20 | onError = defaultOnError, 21 | filename = './default.scss' 22 | }: TransformOptions 23 | ): TransformContext { 24 | const context: TransformContext = { 25 | onError, 26 | nodeTransforms, 27 | root, 28 | filename, 29 | env: new Environment(null), 30 | } 31 | 32 | return context 33 | } 34 | 35 | export function transform(root: RootNode, options: TransformOptions) { 36 | 37 | const context = createTransformContext(root, options) 38 | 39 | let hasModule = root.children.some(node => node.type === NodeTypes.IMPORT || node.type === NodeTypes.USE || node.type === NodeTypes.FORWARD) 40 | if (!isBrowser() && hasModule) { 41 | /** 42 | * resolve module 43 | * @use ,@import 44 | * load-module <--- depend on ----> interpret-module 45 | */ 46 | compatibleLoadModule(context, null, root) 47 | } else { 48 | interpret(root, context) 49 | } 50 | 51 | // transformMiddleware will be slowly replaced by transform plugins if possible, where self designed plugin comes up 52 | transformMiddleware(root) 53 | } 54 | -------------------------------------------------------------------------------- /src/tree/Media.ts: -------------------------------------------------------------------------------- 1 | import { RuleStatement, MediaStatement, createMediaStatement, createMediaFromRule, NodeTypes, MediaQuery } from "../parse/ast"; 2 | import { Tree } from './tree'; 3 | 4 | type params = Parameters 5 | 6 | export default class Media extends Tree{ 7 | mediaStatement: MediaStatement 8 | constructor(mediaPrelude: params[0] | RuleStatement | RuleStatement[] | MediaStatement, body?: params[1]) { 9 | super() 10 | if (mediaPrelude.length || (mediaPrelude as RuleStatement).type === NodeTypes.RULE) { // create from rules 11 | // mediaPrelude.type === NodeTypes.RULE 12 | this.mediaStatement = createMediaFromRule(mediaPrelude as RuleStatement[]) 13 | } else if (body) { 14 | this.mediaStatement = createMediaStatement(mediaPrelude as params[0], body as params[1]) 15 | } else { 16 | this.mediaStatement = mediaPrelude as MediaStatement 17 | } 18 | } 19 | 20 | toJSON() { 21 | return this.mediaStatement 22 | } 23 | 24 | accept(visitor) { 25 | 26 | } 27 | } 28 | 29 | export function mergeMediaWithSamePrelude(...mediaList: MediaStatement[]): MediaStatement { 30 | 31 | mediaList[0].block.children = mediaList.reduce( 32 | (children: MediaStatement['block']['children'], media: MediaStatement) => 33 | children.concat(media.block['children']) 34 | , []) 35 | 36 | return createMediaStatement(mediaList[0].prelude, mediaList[0].block) 37 | } 38 | 39 | export function isSameMediaPrelude(prelude1: MediaStatement['prelude'], prelude2: MediaStatement['prelude']): Boolean { 40 | if (prelude1.children.length !== prelude2.children.length) { 41 | return false 42 | } 43 | 44 | return prelude1.children.every((mediaQuery: MediaQuery, index: number) => { 45 | return mediaQuery === prelude2.children[index] 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/tree/declaration.ts: -------------------------------------------------------------------------------- 1 | import { DeclarationStatement } from "../parse/ast"; 2 | import { Tree } from './tree'; 3 | 4 | // type params = Parameters 5 | 6 | export default class Declaration extends Tree{ 7 | declarationStatement: DeclarationStatement 8 | constructor(node: DeclarationStatement) { 9 | super() 10 | this.declarationStatement = node; 11 | } 12 | 13 | toJSON() { 14 | return this.declarationStatement 15 | } 16 | } -------------------------------------------------------------------------------- /src/tree/index.ts: -------------------------------------------------------------------------------- 1 | import Rule from './rule' 2 | import Media from './media' 3 | import Selector from './selector' 4 | import Text from './text' 5 | import Declaration from './declaration' 6 | import KeyframesTree from './keyframes' 7 | 8 | export { 9 | Rule, 10 | Media, 11 | Selector, 12 | Text, 13 | Declaration, 14 | KeyframesTree 15 | } -------------------------------------------------------------------------------- /src/tree/keyframes.ts: -------------------------------------------------------------------------------- 1 | import { Keyframes } from "../parse/ast"; 2 | import { Tree } from './tree'; 3 | 4 | // type params = Parameters 5 | 6 | export default class KeyframesTree extends Tree{ 7 | keyframes: Keyframes 8 | constructor(node: Keyframes) { 9 | super() 10 | this.keyframes = node; 11 | } 12 | 13 | toJSON() { 14 | return this.keyframes 15 | } 16 | } -------------------------------------------------------------------------------- /src/tree/rule.ts: -------------------------------------------------------------------------------- 1 | import { RuleStatement, MediaStatement, createRuleFromMedia, createRuleStatement, SelectorNode } from "../parse/ast"; 2 | import { isMediaNode } from '../parse/util'; 3 | import { Tree } from './tree'; 4 | 5 | type params = Parameters 6 | 7 | export default class Rule extends Tree{ 8 | ruleStatement: RuleStatement 9 | constructor(selector: params[0] | MediaStatement | RuleStatement,children?: params[1]){ 10 | super() 11 | if (isMediaNode(selector)){ 12 | this.ruleStatement = createRuleFromMedia(selector as MediaStatement) 13 | } else if(children){ 14 | this.ruleStatement = createRuleStatement(selector as params[0], children) 15 | }else{ 16 | this.ruleStatement = selector as RuleStatement 17 | } 18 | } 19 | 20 | addMeta(meta: SelectorNode['meta']){ 21 | this.ruleStatement.selector.meta = joinMeta(this.ruleStatement.selector.meta,meta) 22 | return this; 23 | } 24 | 25 | toJSON(){ 26 | return this.ruleStatement 27 | } 28 | } 29 | 30 | function joinMeta(meta1: SelectorNode['meta'], meta2: SelectorNode['meta']) { 31 | return meta1.concat(meta2) 32 | } -------------------------------------------------------------------------------- /src/tree/selector.ts: -------------------------------------------------------------------------------- 1 | import { SelectorNode } from "../parse/ast"; 2 | import { Tree } from './tree'; 3 | 4 | // type params = Parameters 5 | 6 | export default class Selector extends Tree{ 7 | selectorNode: SelectorNode 8 | constructor(selectorNode: SelectorNode) { 9 | super() 10 | this.selectorNode = selectorNode; 11 | } 12 | 13 | toJSON() { 14 | return this.selectorNode 15 | } 16 | } -------------------------------------------------------------------------------- /src/tree/text.ts: -------------------------------------------------------------------------------- 1 | import { TextNode } from "../parse/ast"; 2 | import { Tree } from './tree'; 3 | 4 | // type params = Parameters 5 | 6 | export default class Text extends Tree{ 7 | textNode: TextNode 8 | constructor(text: TextNode) { 9 | super() 10 | this.textNode = text; 11 | } 12 | 13 | toJSON() { 14 | return this.textNode 15 | } 16 | } -------------------------------------------------------------------------------- /src/tree/tree.ts: -------------------------------------------------------------------------------- 1 | import { CodegenContext } from '@/type' 2 | 3 | export class Tree { 4 | constructor(){ 5 | } 6 | 7 | @deprecate() 8 | genCSS(context: CodegenContext){ 9 | 10 | } 11 | toJSON(){ 12 | 13 | } 14 | } 15 | 16 | /** 17 | * decorator will run at compile time so it should only prompt once 18 | * it would be too noise if used with runtime action 19 | */ 20 | export function deprecate() { 21 | return function (target, name, descriptor) { 22 | console.warn(` 23 | [Tree]: ${name} will be deprecated!!! Code generation process has been replaced by genCodeVisitor which reuse traverse over node!!! 24 | `) 25 | } 26 | } -------------------------------------------------------------------------------- /src/tree/util.ts: -------------------------------------------------------------------------------- 1 | import { RuleStatement } from '@/parse/ast'; 2 | import { CodegenContext } from '@/type'; 3 | import { Rule } from '.'; 4 | 5 | export function genChildrenIterator(children: RuleStatement[], context: CodegenContext) { 6 | const { push, deindent, indent, newline } = context; 7 | push('{'); 8 | indent(); 9 | 10 | children.forEach((child: RuleStatement, index: number) => { 11 | new Rule(child).genCSS(context) 12 | if (index !== children.length - 1) { 13 | newline() 14 | } 15 | }) 16 | deindent(); 17 | push('}'); 18 | } -------------------------------------------------------------------------------- /src/type/codegen.ts: -------------------------------------------------------------------------------- 1 | import { SourceMapGenerator, RawSourceMap } from 'source-map' 2 | import {RootNode,SourceLocation} from '../parse/ast' 3 | import { CodegenOptions } from './options' 4 | 5 | export interface CodegenResult { 6 | code: string 7 | ast: RootNode 8 | map?: RawSourceMap 9 | } 10 | 11 | export interface CodegenContext extends Required { 12 | // source: string 13 | code: string 14 | indentLevel: number 15 | line: number 16 | column: number 17 | offset: number 18 | map?: SourceMapGenerator 19 | push(code: string, sourceLoc?: SourceLocation): void 20 | indent(): void 21 | deindent(withoutNewLine?: boolean): void 22 | newline(): void 23 | } 24 | -------------------------------------------------------------------------------- /src/type/index.ts: -------------------------------------------------------------------------------- 1 | export * from './codegen' 2 | export * from './options' -------------------------------------------------------------------------------- /src/type/options.ts: -------------------------------------------------------------------------------- 1 | import { CompilerError} from '../parse/errors' 2 | import { RootNode, Statement, CodegenNode } from '../parse/ast' 3 | import { Environment } from '../enviroment/Enviroment' 4 | 5 | export interface TransformContext extends Required { 6 | root: RootNode 7 | env: Environment, 8 | } 9 | 10 | export type NodeTransform = ( 11 | node: Statement | CodegenNode, 12 | context: TransformContext 13 | ) => Statement 14 | 15 | 16 | export interface TransformOptions { 17 | /** 18 | * An array of node trasnforms to be applied to every AST node. 19 | */ 20 | nodeTransforms?: NodeTransform[] 21 | 22 | // mainly used to resolve @import,@use module 23 | filename?: string 24 | 25 | onError?: (error: CompilerError) => void 26 | } 27 | 28 | export interface CodegenOptions { 29 | /** 30 | * Generate source map? 31 | * @default false 32 | */ 33 | sourceMap?: boolean 34 | } 35 | 36 | export interface ParserOptions { 37 | /** 38 | * Filename for source map generation. 39 | */ 40 | filename: string 41 | source: string 42 | } 43 | 44 | export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions -------------------------------------------------------------------------------- /test-dist/ast/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [], 4 | "fileSourceMap": { 5 | "test/comment.scss": "/*\ntest multiline comment\n*/\n\n// test single line comment" 6 | }, 7 | "loc": { 8 | "filename": "", 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 1, 17 | "offset": 0 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /test-dist/ast/module/circular/circular-reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "USE", 6 | "params": [ 7 | { 8 | "type": "TEXT", 9 | "value": "./c1", 10 | "loc": { 11 | "start": { 12 | "offset": 5, 13 | "line": 1, 14 | "column": 6 15 | }, 16 | "end": { 17 | "offset": 11, 18 | "line": 1, 19 | "column": 12 20 | }, 21 | "filename": "test/module/circular/circular-reference.scss" 22 | } 23 | } 24 | ], 25 | "loc": { 26 | "filename": "", 27 | "start": { 28 | "line": 1, 29 | "column": 1, 30 | "offset": 0 31 | }, 32 | "end": { 33 | "line": 1, 34 | "column": 1, 35 | "offset": 0 36 | } 37 | } 38 | } 39 | ], 40 | "fileSourceMap": { 41 | "test/module/circular/circular-reference.scss": "@use './c1';" 42 | }, 43 | "loc": { 44 | "filename": "", 45 | "start": { 46 | "line": 1, 47 | "column": 1, 48 | "offset": 0 49 | }, 50 | "end": { 51 | "line": 1, 52 | "column": 1, 53 | "offset": 0 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /test-dist/ast/module/forward/bootstrap-forward.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "USE", 6 | "params": [ 7 | { 8 | "type": "TEXT", 9 | "value": "./forward.scss", 10 | "loc": { 11 | "start": { 12 | "offset": 5, 13 | "line": 1, 14 | "column": 6 15 | }, 16 | "end": { 17 | "offset": 21, 18 | "line": 1, 19 | "column": 22 20 | }, 21 | "filename": "test/module/forward/bootstrap-forward.scss" 22 | } 23 | } 24 | ], 25 | "loc": { 26 | "filename": "", 27 | "start": { 28 | "line": 1, 29 | "column": 1, 30 | "offset": 0 31 | }, 32 | "end": { 33 | "line": 1, 34 | "column": 1, 35 | "offset": 0 36 | } 37 | } 38 | }, 39 | { 40 | "type": "RULE", 41 | "selector": { 42 | "type": "SELECTOR", 43 | "meta": [], 44 | "loc": { 45 | "start": { 46 | "offset": 24, 47 | "line": 3, 48 | "column": 1 49 | }, 50 | "end": { 51 | "offset": 37, 52 | "line": 3, 53 | "column": 14 54 | }, 55 | "filename": "test/module/forward/bootstrap-forward.scss" 56 | }, 57 | "value": { 58 | "type": "TEXT", 59 | "value": ".forward-test", 60 | "loc": { 61 | "start": { 62 | "offset": 24, 63 | "line": 3, 64 | "column": 1 65 | }, 66 | "end": { 67 | "offset": 37, 68 | "line": 3, 69 | "column": 14 70 | }, 71 | "filename": "test/module/forward/bootstrap-forward.scss" 72 | } 73 | } 74 | }, 75 | "children": [ 76 | { 77 | "type": "DECLARATION", 78 | "left": { 79 | "type": "TEXT", 80 | "value": "color", 81 | "loc": { 82 | "start": { 83 | "offset": 43, 84 | "line": 4, 85 | "column": 5 86 | }, 87 | "end": { 88 | "offset": 48, 89 | "line": 4, 90 | "column": 10 91 | }, 92 | "filename": "test/module/forward/bootstrap-forward.scss" 93 | } 94 | }, 95 | "right": { 96 | "type": "LIST", 97 | "value": [ 98 | { 99 | "type": "TEXT", 100 | "value": "forward.$color-primary", 101 | "loc": { 102 | "start": { 103 | "offset": 50, 104 | "line": 4, 105 | "column": 12 106 | }, 107 | "end": { 108 | "offset": 72, 109 | "line": 4, 110 | "column": 34 111 | }, 112 | "filename": "test/module/forward/bootstrap-forward.scss" 113 | } 114 | } 115 | ], 116 | "loc": { 117 | "start": { 118 | "offset": 50, 119 | "line": 4, 120 | "column": 12 121 | }, 122 | "end": { 123 | "offset": 72, 124 | "line": 4, 125 | "column": 34 126 | }, 127 | "filename": "test/module/forward/bootstrap-forward.scss" 128 | } 129 | }, 130 | "loc": { 131 | "start": { 132 | "offset": 43, 133 | "line": 4, 134 | "column": 5 135 | }, 136 | "end": { 137 | "offset": 72, 138 | "line": 4, 139 | "column": 34 140 | }, 141 | "filename": "test/module/forward/bootstrap-forward.scss" 142 | } 143 | }, 144 | { 145 | "type": "DECLARATION", 146 | "left": { 147 | "type": "TEXT", 148 | "value": "background", 149 | "loc": { 150 | "start": { 151 | "offset": 78, 152 | "line": 5, 153 | "column": 5 154 | }, 155 | "end": { 156 | "offset": 88, 157 | "line": 5, 158 | "column": 15 159 | }, 160 | "filename": "test/module/forward/bootstrap-forward.scss" 161 | } 162 | }, 163 | "right": { 164 | "type": "LIST", 165 | "value": [ 166 | { 167 | "type": "TEXT", 168 | "value": "forward.$white", 169 | "loc": { 170 | "start": { 171 | "offset": 90, 172 | "line": 5, 173 | "column": 17 174 | }, 175 | "end": { 176 | "offset": 104, 177 | "line": 5, 178 | "column": 31 179 | }, 180 | "filename": "test/module/forward/bootstrap-forward.scss" 181 | } 182 | } 183 | ], 184 | "loc": { 185 | "start": { 186 | "offset": 90, 187 | "line": 5, 188 | "column": 17 189 | }, 190 | "end": { 191 | "offset": 104, 192 | "line": 5, 193 | "column": 31 194 | }, 195 | "filename": "test/module/forward/bootstrap-forward.scss" 196 | } 197 | }, 198 | "loc": { 199 | "start": { 200 | "offset": 78, 201 | "line": 5, 202 | "column": 5 203 | }, 204 | "end": { 205 | "offset": 104, 206 | "line": 5, 207 | "column": 31 208 | }, 209 | "filename": "test/module/forward/bootstrap-forward.scss" 210 | } 211 | } 212 | ], 213 | "loc": { 214 | "filename": "", 215 | "start": { 216 | "line": 1, 217 | "column": 1, 218 | "offset": 0 219 | }, 220 | "end": { 221 | "line": 1, 222 | "column": 1, 223 | "offset": 0 224 | } 225 | } 226 | } 227 | ], 228 | "fileSourceMap": { 229 | "test/module/forward/bootstrap-forward.scss": "@use './forward.scss';\n\n.forward-test{\n color: forward.$color-primary;\n background: forward.$white;\n}" 230 | }, 231 | "loc": { 232 | "filename": "", 233 | "start": { 234 | "line": 1, 235 | "column": 1, 236 | "offset": 0 237 | }, 238 | "end": { 239 | "line": 1, 240 | "column": 1, 241 | "offset": 0 242 | } 243 | } 244 | } -------------------------------------------------------------------------------- /test-dist/ast/module/use/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "USE", 6 | "params": [ 7 | { 8 | "type": "TEXT", 9 | "value": "./base.scss", 10 | "loc": { 11 | "start": { 12 | "offset": 20, 13 | "line": 2, 14 | "column": 6 15 | }, 16 | "end": { 17 | "offset": 33, 18 | "line": 2, 19 | "column": 19 20 | }, 21 | "filename": "index.scss" 22 | } 23 | } 24 | ], 25 | "loc": { 26 | "filename": "", 27 | "start": { 28 | "line": 1, 29 | "column": 1, 30 | "offset": 0 31 | }, 32 | "end": { 33 | "line": 1, 34 | "column": 1, 35 | "offset": 0 36 | } 37 | } 38 | }, 39 | { 40 | "type": "USE", 41 | "params": [ 42 | { 43 | "type": "TEXT", 44 | "value": "./base-2.scss", 45 | "loc": { 46 | "start": { 47 | "offset": 40, 48 | "line": 3, 49 | "column": 6 50 | }, 51 | "end": { 52 | "offset": 55, 53 | "line": 3, 54 | "column": 21 55 | }, 56 | "filename": "index.scss" 57 | } 58 | } 59 | ], 60 | "loc": { 61 | "filename": "", 62 | "start": { 63 | "line": 1, 64 | "column": 1, 65 | "offset": 0 66 | }, 67 | "end": { 68 | "line": 1, 69 | "column": 1, 70 | "offset": 0 71 | } 72 | } 73 | }, 74 | { 75 | "type": "RULE", 76 | "selector": { 77 | "type": "SELECTOR", 78 | "meta": [], 79 | "loc": { 80 | "start": { 81 | "offset": 58, 82 | "line": 5, 83 | "column": 1 84 | }, 85 | "end": { 86 | "offset": 66, 87 | "line": 5, 88 | "column": 9 89 | }, 90 | "filename": "index.scss" 91 | }, 92 | "value": { 93 | "type": "TEXT", 94 | "value": ".inverse", 95 | "loc": { 96 | "start": { 97 | "offset": 58, 98 | "line": 5, 99 | "column": 1 100 | }, 101 | "end": { 102 | "offset": 66, 103 | "line": 5, 104 | "column": 9 105 | }, 106 | "filename": "index.scss" 107 | } 108 | } 109 | }, 110 | "children": [ 111 | { 112 | "type": "DECLARATION", 113 | "left": { 114 | "type": "TEXT", 115 | "value": "background-color", 116 | "loc": { 117 | "start": { 118 | "offset": 71, 119 | "line": 6, 120 | "column": 3 121 | }, 122 | "end": { 123 | "offset": 87, 124 | "line": 6, 125 | "column": 19 126 | }, 127 | "filename": "index.scss" 128 | } 129 | }, 130 | "right": { 131 | "type": "LIST", 132 | "value": [ 133 | { 134 | "type": "VARIABLE", 135 | "value": "$primary-color", 136 | "loc": { 137 | "start": { 138 | "offset": 89, 139 | "line": 6, 140 | "column": 21 141 | }, 142 | "end": { 143 | "offset": 103, 144 | "line": 6, 145 | "column": 35 146 | }, 147 | "filename": "index.scss" 148 | } 149 | } 150 | ], 151 | "loc": { 152 | "start": { 153 | "offset": 89, 154 | "line": 6, 155 | "column": 21 156 | }, 157 | "end": { 158 | "offset": 103, 159 | "line": 6, 160 | "column": 35 161 | }, 162 | "filename": "index.scss" 163 | } 164 | }, 165 | "loc": { 166 | "start": { 167 | "offset": 71, 168 | "line": 6, 169 | "column": 3 170 | }, 171 | "end": { 172 | "offset": 103, 173 | "line": 6, 174 | "column": 35 175 | }, 176 | "filename": "index.scss" 177 | } 178 | }, 179 | { 180 | "type": "DECLARATION", 181 | "left": { 182 | "type": "TEXT", 183 | "value": "color", 184 | "loc": { 185 | "start": { 186 | "offset": 107, 187 | "line": 7, 188 | "column": 3 189 | }, 190 | "end": { 191 | "offset": 112, 192 | "line": 7, 193 | "column": 8 194 | }, 195 | "filename": "index.scss" 196 | } 197 | }, 198 | "right": { 199 | "type": "LIST", 200 | "value": [ 201 | { 202 | "type": "TEXT", 203 | "value": "white", 204 | "loc": { 205 | "start": { 206 | "offset": 114, 207 | "line": 7, 208 | "column": 10 209 | }, 210 | "end": { 211 | "offset": 119, 212 | "line": 7, 213 | "column": 15 214 | }, 215 | "filename": "index.scss" 216 | } 217 | } 218 | ], 219 | "loc": { 220 | "start": { 221 | "offset": 114, 222 | "line": 7, 223 | "column": 10 224 | }, 225 | "end": { 226 | "offset": 119, 227 | "line": 7, 228 | "column": 15 229 | }, 230 | "filename": "index.scss" 231 | } 232 | }, 233 | "loc": { 234 | "start": { 235 | "offset": 107, 236 | "line": 7, 237 | "column": 3 238 | }, 239 | "end": { 240 | "offset": 119, 241 | "line": 7, 242 | "column": 15 243 | }, 244 | "filename": "index.scss" 245 | } 246 | } 247 | ], 248 | "loc": { 249 | "filename": "", 250 | "start": { 251 | "line": 1, 252 | "column": 1, 253 | "offset": 0 254 | }, 255 | "end": { 256 | "line": 1, 257 | "column": 1, 258 | "offset": 0 259 | } 260 | } 261 | } 262 | ], 263 | "fileSourceMap": { 264 | "index.scss": "// styles.scss\n@use \"./base.scss\";\n@use \"./base-2.scss\";\n\n.inverse {\n background-color: $primary-color;\n color: white;\n}" 265 | }, 266 | "loc": { 267 | "filename": "", 268 | "start": { 269 | "line": 1, 270 | "column": 1, 271 | "offset": 0 272 | }, 273 | "end": { 274 | "line": 1, 275 | "column": 1, 276 | "offset": 0 277 | } 278 | } 279 | } -------------------------------------------------------------------------------- /test-dist/ast/plugin/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "PLUGIN", 6 | "value": { 7 | "type": "TEXT", 8 | "value": "my-plugin", 9 | "loc": { 10 | "start": { 11 | "offset": 21, 12 | "line": 2, 13 | "column": 9 14 | }, 15 | "end": { 16 | "offset": 32, 17 | "line": 2, 18 | "column": 20 19 | }, 20 | "filename": "test/plugin/plugin.scss" 21 | } 22 | }, 23 | "loc": { 24 | "filename": "", 25 | "start": { 26 | "line": 1, 27 | "column": 1, 28 | "offset": 0 29 | }, 30 | "end": { 31 | "line": 1, 32 | "column": 1, 33 | "offset": 0 34 | } 35 | } 36 | }, 37 | { 38 | "type": "RULE", 39 | "selector": { 40 | "type": "SELECTOR", 41 | "meta": [], 42 | "loc": { 43 | "start": { 44 | "offset": 35, 45 | "line": 4, 46 | "column": 1 47 | }, 48 | "end": { 49 | "offset": 46, 50 | "line": 4, 51 | "column": 12 52 | }, 53 | "filename": "test/plugin/plugin.scss" 54 | }, 55 | "value": { 56 | "type": "TEXT", 57 | "value": ".show-me-pi", 58 | "loc": { 59 | "start": { 60 | "offset": 35, 61 | "line": 4, 62 | "column": 1 63 | }, 64 | "end": { 65 | "offset": 46, 66 | "line": 4, 67 | "column": 12 68 | }, 69 | "filename": "test/plugin/plugin.scss" 70 | } 71 | } 72 | }, 73 | "children": [ 74 | { 75 | "type": "DECLARATION", 76 | "left": { 77 | "type": "TEXT", 78 | "value": "value", 79 | "loc": { 80 | "start": { 81 | "offset": 53, 82 | "line": 5, 83 | "column": 5 84 | }, 85 | "end": { 86 | "offset": 58, 87 | "line": 5, 88 | "column": 10 89 | }, 90 | "filename": "test/plugin/plugin.scss" 91 | } 92 | }, 93 | "right": { 94 | "type": "LIST", 95 | "value": [ 96 | { 97 | "type": "CALL", 98 | "id": { 99 | "loc": { 100 | "start": { 101 | "offset": 60, 102 | "line": 5, 103 | "column": 12 104 | }, 105 | "end": { 106 | "offset": 62, 107 | "line": 5, 108 | "column": 14 109 | }, 110 | "filename": "test/plugin/plugin.scss" 111 | }, 112 | "namespace": [], 113 | "name": "pi", 114 | "type": "IDENTIFIER" 115 | }, 116 | "args": [], 117 | "loc": { 118 | "start": { 119 | "offset": 60, 120 | "line": 5, 121 | "column": 12 122 | }, 123 | "end": { 124 | "offset": 62, 125 | "line": 5, 126 | "column": 14 127 | }, 128 | "filename": "test/plugin/plugin.scss" 129 | } 130 | } 131 | ], 132 | "loc": { 133 | "start": { 134 | "offset": 60, 135 | "line": 5, 136 | "column": 12 137 | }, 138 | "end": { 139 | "offset": 62, 140 | "line": 5, 141 | "column": 14 142 | }, 143 | "filename": "test/plugin/plugin.scss" 144 | } 145 | }, 146 | "loc": { 147 | "start": { 148 | "offset": 53, 149 | "line": 5, 150 | "column": 5 151 | }, 152 | "end": { 153 | "offset": 62, 154 | "line": 5, 155 | "column": 14 156 | }, 157 | "filename": "test/plugin/plugin.scss" 158 | } 159 | } 160 | ], 161 | "loc": { 162 | "filename": "", 163 | "start": { 164 | "line": 1, 165 | "column": 1, 166 | "offset": 0 167 | }, 168 | "end": { 169 | "line": 1, 170 | "column": 1, 171 | "offset": 0 172 | } 173 | } 174 | } 175 | ], 176 | "fileSourceMap": { 177 | "test/plugin/plugin.scss": "// less code\n@plugin \"my-plugin\";\n\n.show-me-pi {\n value: pi();\n}" 178 | }, 179 | "loc": { 180 | "filename": "", 181 | "start": { 182 | "line": 1, 183 | "column": 1, 184 | "offset": 0 185 | }, 186 | "end": { 187 | "line": 1, 188 | "column": 1, 189 | "offset": 0 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [], 4 | "fileSourceMap": { 5 | "test/comment.scss": "/*\ntest multiline comment\n*/\n\n// test single line comment" 6 | }, 7 | "loc": { 8 | "filename": "", 9 | "start": { 10 | "line": 1, 11 | "column": 1, 12 | "offset": 0 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 1, 17 | "offset": 0 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/function/function-with-if.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "RULE", 6 | "selector": { 7 | "type": "SELECTOR", 8 | "meta": [], 9 | "loc": { 10 | "start": { 11 | "offset": 111, 12 | "line": 11, 13 | "column": 1 14 | }, 15 | "end": { 16 | "offset": 119, 17 | "line": 11, 18 | "column": 9 19 | }, 20 | "filename": "test/function/function-with-if.scss" 21 | }, 22 | "value": { 23 | "type": "TEXT", 24 | "value": ".sidebar", 25 | "loc": { 26 | "start": { 27 | "offset": 111, 28 | "line": 11, 29 | "column": 1 30 | }, 31 | "end": { 32 | "offset": 119, 33 | "line": 11, 34 | "column": 9 35 | }, 36 | "filename": "test/function/function-with-if.scss" 37 | } 38 | } 39 | }, 40 | "children": [ 41 | { 42 | "type": "DECLARATION", 43 | "left": { 44 | "type": "TEXT", 45 | "value": "margin-left", 46 | "loc": { 47 | "start": { 48 | "offset": 126, 49 | "line": 12, 50 | "column": 5 51 | }, 52 | "end": { 53 | "offset": 137, 54 | "line": 12, 55 | "column": 16 56 | }, 57 | "filename": "test/function/function-with-if.scss" 58 | } 59 | }, 60 | "right": { 61 | "type": "TEXT", 62 | "loc": { 63 | "start": { 64 | "offset": 139, 65 | "line": 12, 66 | "column": 18 67 | }, 68 | "end": { 69 | "offset": 155, 70 | "line": 12, 71 | "column": 34 72 | }, 73 | "filename": "test/function/function-with-if.scss" 74 | }, 75 | "value": "10px" 76 | }, 77 | "loc": { 78 | "start": { 79 | "offset": 126, 80 | "line": 12, 81 | "column": 5 82 | }, 83 | "end": { 84 | "offset": 155, 85 | "line": 12, 86 | "column": 34 87 | }, 88 | "filename": "test/function/function-with-if.scss" 89 | } 90 | } 91 | ], 92 | "loc": { 93 | "filename": "", 94 | "start": { 95 | "line": 1, 96 | "column": 1, 97 | "offset": 0 98 | }, 99 | "end": { 100 | "line": 1, 101 | "column": 1, 102 | "offset": 0 103 | } 104 | } 105 | } 106 | ], 107 | "fileSourceMap": { 108 | "test/function/function-with-if.scss": "@function min2($a, $b) {\n @if $a > $b {\n @return $b;\n }\n\n @else {\n @return $a;\n }\n}\n\n.sidebar {\n margin-left: min2(5, 6) * 2px;\n}" 109 | }, 110 | "loc": { 111 | "filename": "", 112 | "start": { 113 | "line": 1, 114 | "column": 1, 115 | "offset": 0 116 | }, 117 | "end": { 118 | "line": 1, 119 | "column": 1, 120 | "offset": 0 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/function/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "RULE", 6 | "selector": { 7 | "type": "SELECTOR", 8 | "meta": [], 9 | "loc": { 10 | "start": { 11 | "offset": 50, 12 | "line": 6, 13 | "column": 1 14 | }, 15 | "end": { 16 | "offset": 58, 17 | "line": 6, 18 | "column": 9 19 | }, 20 | "filename": "test/function/function.scss" 21 | }, 22 | "value": { 23 | "type": "TEXT", 24 | "value": ".sidebar", 25 | "loc": { 26 | "start": { 27 | "offset": 50, 28 | "line": 6, 29 | "column": 1 30 | }, 31 | "end": { 32 | "offset": 58, 33 | "line": 6, 34 | "column": 9 35 | }, 36 | "filename": "test/function/function.scss" 37 | } 38 | } 39 | }, 40 | "children": [ 41 | { 42 | "type": "DECLARATION", 43 | "left": { 44 | "type": "TEXT", 45 | "value": "float", 46 | "loc": { 47 | "start": { 48 | "offset": 65, 49 | "line": 7, 50 | "column": 5 51 | }, 52 | "end": { 53 | "offset": 70, 54 | "line": 7, 55 | "column": 10 56 | }, 57 | "filename": "test/function/function.scss" 58 | } 59 | }, 60 | "right": { 61 | "type": "TEXT", 62 | "loc": { 63 | "start": { 64 | "offset": 72, 65 | "line": 7, 66 | "column": 12 67 | }, 68 | "end": { 69 | "offset": 76, 70 | "line": 7, 71 | "column": 16 72 | }, 73 | "filename": "test/function/function.scss" 74 | }, 75 | "value": "left" 76 | }, 77 | "loc": { 78 | "start": { 79 | "offset": 65, 80 | "line": 7, 81 | "column": 5 82 | }, 83 | "end": { 84 | "offset": 76, 85 | "line": 7, 86 | "column": 16 87 | }, 88 | "filename": "test/function/function.scss" 89 | } 90 | }, 91 | { 92 | "type": "DECLARATION", 93 | "left": { 94 | "type": "TEXT", 95 | "value": "margin-left", 96 | "loc": { 97 | "start": { 98 | "offset": 82, 99 | "line": 8, 100 | "column": 5 101 | }, 102 | "end": { 103 | "offset": 93, 104 | "line": 8, 105 | "column": 16 106 | }, 107 | "filename": "test/function/function.scss" 108 | } 109 | }, 110 | "right": { 111 | "type": "TEXT", 112 | "loc": { 113 | "start": { 114 | "offset": 95, 115 | "line": 8, 116 | "column": 18 117 | }, 118 | "end": { 119 | "offset": 111, 120 | "line": 8, 121 | "column": 34 122 | }, 123 | "filename": "test/function/function.scss" 124 | }, 125 | "value": "14px" 126 | }, 127 | "loc": { 128 | "start": { 129 | "offset": 82, 130 | "line": 8, 131 | "column": 5 132 | }, 133 | "end": { 134 | "offset": 111, 135 | "line": 8, 136 | "column": 34 137 | }, 138 | "filename": "test/function/function.scss" 139 | } 140 | } 141 | ], 142 | "loc": { 143 | "filename": "", 144 | "start": { 145 | "line": 1, 146 | "column": 1, 147 | "offset": 0 148 | }, 149 | "end": { 150 | "line": 1, 151 | "column": 1, 152 | "offset": 0 153 | } 154 | } 155 | } 156 | ], 157 | "fileSourceMap": { 158 | "test/function/function.scss": "@function plus($a, $b) {\n @return $a + $b;\n}\n\n\n.sidebar {\n float: left;\n margin-left: plus(4, 3) * 2px;\n}\n" 159 | }, 160 | "loc": { 161 | "filename": "", 162 | "start": { 163 | "line": 1, 164 | "column": 1, 165 | "offset": 0 166 | }, 167 | "end": { 168 | "line": 1, 169 | "column": 1, 170 | "offset": 0 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/mixin/var-key-parent-selector.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "RULE", 6 | "selector": { 7 | "type": "SELECTOR", 8 | "meta": [], 9 | "loc": { 10 | "start": { 11 | "offset": 140, 12 | "line": 9, 13 | "column": 1 14 | }, 15 | "end": { 16 | "offset": 148, 17 | "line": 9, 18 | "column": 9 19 | }, 20 | "filename": "test/mixin/var-key-parent-selector.scss" 21 | }, 22 | "value": { 23 | "type": "TEXT", 24 | "value": ".sidebar", 25 | "loc": { 26 | "start": { 27 | "offset": 140, 28 | "line": 9, 29 | "column": 1 30 | }, 31 | "end": { 32 | "offset": 148, 33 | "line": 9, 34 | "column": 9 35 | }, 36 | "filename": "test/mixin/var-key-parent-selector.scss" 37 | } 38 | } 39 | }, 40 | "children": [ 41 | { 42 | "type": "DECLARATION", 43 | "left": { 44 | "type": "TEXT", 45 | "value": "float", 46 | "loc": { 47 | "start": { 48 | "offset": 50, 49 | "line": 2, 50 | "column": 3 51 | }, 52 | "end": { 53 | "offset": 61, 54 | "line": 2, 55 | "column": 14 56 | }, 57 | "filename": "test/mixin/var-key-parent-selector.scss" 58 | } 59 | }, 60 | "right": { 61 | "type": "TEXT", 62 | "loc": { 63 | "start": { 64 | "offset": 64, 65 | "line": 2, 66 | "column": 17 67 | }, 68 | "end": { 69 | "offset": 74, 70 | "line": 2, 71 | "column": 27 72 | }, 73 | "filename": "test/mixin/var-key-parent-selector.scss" 74 | }, 75 | "value": "left" 76 | }, 77 | "loc": { 78 | "start": { 79 | "offset": 50, 80 | "line": 2, 81 | "column": 3 82 | }, 83 | "end": { 84 | "offset": 74, 85 | "line": 2, 86 | "column": 27 87 | }, 88 | "filename": "test/mixin/var-key-parent-selector.scss" 89 | } 90 | } 91 | ], 92 | "loc": { 93 | "filename": "", 94 | "start": { 95 | "line": 1, 96 | "column": 1, 97 | "offset": 0 98 | }, 99 | "end": { 100 | "line": 1, 101 | "column": 1, 102 | "offset": 0 103 | } 104 | } 105 | }, 106 | { 107 | "type": "RULE", 108 | "selector": { 109 | "type": "SELECTOR", 110 | "meta": [], 111 | "loc": { 112 | "start": { 113 | "offset": 80, 114 | "line": 4, 115 | "column": 4 116 | }, 117 | "end": { 118 | "offset": 98, 119 | "line": 4, 120 | "column": 22 121 | }, 122 | "filename": "test/mixin/var-key-parent-selector.scss" 123 | }, 124 | "value": { 125 | "type": "TEXT", 126 | "value": "[dir=rtl] .sidebar.second", 127 | "loc": { 128 | "start": { 129 | "offset": 80, 130 | "line": 4, 131 | "column": 4 132 | }, 133 | "end": { 134 | "offset": 98, 135 | "line": 4, 136 | "column": 22 137 | }, 138 | "filename": "test/mixin/var-key-parent-selector.scss" 139 | } 140 | } 141 | }, 142 | "children": [ 143 | { 144 | "type": "DECLARATION", 145 | "left": { 146 | "type": "TEXT", 147 | "value": "float", 148 | "loc": { 149 | "start": { 150 | "offset": 105, 151 | "line": 5, 152 | "column": 5 153 | }, 154 | "end": { 155 | "offset": 117, 156 | "line": 5, 157 | "column": 17 158 | }, 159 | "filename": "test/mixin/var-key-parent-selector.scss" 160 | } 161 | }, 162 | "right": { 163 | "type": "TEXT", 164 | "loc": { 165 | "start": { 166 | "offset": 121, 167 | "line": 5, 168 | "column": 21 169 | }, 170 | "end": { 171 | "offset": 131, 172 | "line": 5, 173 | "column": 31 174 | }, 175 | "filename": "test/mixin/var-key-parent-selector.scss" 176 | }, 177 | "value": "right" 178 | }, 179 | "loc": { 180 | "start": { 181 | "offset": 105, 182 | "line": 5, 183 | "column": 5 184 | }, 185 | "end": { 186 | "offset": 131, 187 | "line": 5, 188 | "column": 31 189 | }, 190 | "filename": "test/mixin/var-key-parent-selector.scss" 191 | } 192 | } 193 | ], 194 | "loc": { 195 | "filename": "", 196 | "start": { 197 | "line": 1, 198 | "column": 1, 199 | "offset": 0 200 | }, 201 | "end": { 202 | "line": 1, 203 | "column": 1, 204 | "offset": 0 205 | } 206 | } 207 | } 208 | ], 209 | "fileSourceMap": { 210 | "test/mixin/var-key-parent-selector.scss": "@mixin rtl($property, $ltr-value, $rtl-value) {\n #{$property}: $ltr-value;\n\n [dir=rtl] &.second {\n #{ $property }: $rtl-value;\n }\n}\n\n.sidebar {\n @include rtl(float, left, right);\n}" 211 | }, 212 | "loc": { 213 | "filename": "", 214 | "start": { 215 | "line": 1, 216 | "column": 1, 217 | "offset": 0 218 | }, 219 | "end": { 220 | "line": 1, 221 | "column": 1, 222 | "offset": 0 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/module/forward/bootstrap-forward.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "RULE", 6 | "selector": { 7 | "type": "SELECTOR", 8 | "meta": [], 9 | "loc": { 10 | "start": { 11 | "offset": 24, 12 | "line": 3, 13 | "column": 1 14 | }, 15 | "end": { 16 | "offset": 37, 17 | "line": 3, 18 | "column": 14 19 | }, 20 | "filename": "test/module/forward/bootstrap-forward.scss" 21 | }, 22 | "value": { 23 | "type": "TEXT", 24 | "value": ".forward-test", 25 | "loc": { 26 | "start": { 27 | "offset": 24, 28 | "line": 3, 29 | "column": 1 30 | }, 31 | "end": { 32 | "offset": 37, 33 | "line": 3, 34 | "column": 14 35 | }, 36 | "filename": "test/module/forward/bootstrap-forward.scss" 37 | } 38 | } 39 | }, 40 | "children": [ 41 | { 42 | "type": "DECLARATION", 43 | "left": { 44 | "type": "TEXT", 45 | "value": "color", 46 | "loc": { 47 | "start": { 48 | "offset": 43, 49 | "line": 4, 50 | "column": 5 51 | }, 52 | "end": { 53 | "offset": 48, 54 | "line": 4, 55 | "column": 10 56 | }, 57 | "filename": "test/module/forward/bootstrap-forward.scss" 58 | } 59 | }, 60 | "right": { 61 | "type": "TEXT", 62 | "loc": { 63 | "start": { 64 | "offset": 50, 65 | "line": 4, 66 | "column": 12 67 | }, 68 | "end": { 69 | "offset": 72, 70 | "line": 4, 71 | "column": 34 72 | }, 73 | "filename": "test/module/forward/bootstrap-forward.scss" 74 | }, 75 | "value": "red" 76 | }, 77 | "loc": { 78 | "start": { 79 | "offset": 43, 80 | "line": 4, 81 | "column": 5 82 | }, 83 | "end": { 84 | "offset": 72, 85 | "line": 4, 86 | "column": 34 87 | }, 88 | "filename": "test/module/forward/bootstrap-forward.scss" 89 | } 90 | }, 91 | { 92 | "type": "DECLARATION", 93 | "left": { 94 | "type": "TEXT", 95 | "value": "background", 96 | "loc": { 97 | "start": { 98 | "offset": 78, 99 | "line": 5, 100 | "column": 5 101 | }, 102 | "end": { 103 | "offset": 88, 104 | "line": 5, 105 | "column": 15 106 | }, 107 | "filename": "test/module/forward/bootstrap-forward.scss" 108 | } 109 | }, 110 | "right": { 111 | "type": "TEXT", 112 | "loc": { 113 | "start": { 114 | "offset": 90, 115 | "line": 5, 116 | "column": 17 117 | }, 118 | "end": { 119 | "offset": 104, 120 | "line": 5, 121 | "column": 31 122 | }, 123 | "filename": "test/module/forward/bootstrap-forward.scss" 124 | }, 125 | "value": "#fff" 126 | }, 127 | "loc": { 128 | "start": { 129 | "offset": 78, 130 | "line": 5, 131 | "column": 5 132 | }, 133 | "end": { 134 | "offset": 104, 135 | "line": 5, 136 | "column": 31 137 | }, 138 | "filename": "test/module/forward/bootstrap-forward.scss" 139 | } 140 | } 141 | ], 142 | "loc": { 143 | "filename": "", 144 | "start": { 145 | "line": 1, 146 | "column": 1, 147 | "offset": 0 148 | }, 149 | "end": { 150 | "line": 1, 151 | "column": 1, 152 | "offset": 0 153 | } 154 | } 155 | } 156 | ], 157 | "fileSourceMap": { 158 | "test/module/forward/bootstrap-forward.scss": "@use './forward.scss';\n\n.forward-test{\n color: forward.$color-primary;\n background: forward.$white;\n}" 159 | }, 160 | "loc": { 161 | "filename": "", 162 | "start": { 163 | "line": 1, 164 | "column": 1, 165 | "offset": 0 166 | }, 167 | "end": { 168 | "line": 1, 169 | "column": 1, 170 | "offset": 0 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/plugin/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "RULE", 6 | "selector": { 7 | "type": "SELECTOR", 8 | "meta": [], 9 | "loc": { 10 | "start": { 11 | "offset": 35, 12 | "line": 4, 13 | "column": 1 14 | }, 15 | "end": { 16 | "offset": 46, 17 | "line": 4, 18 | "column": 12 19 | }, 20 | "filename": "test/plugin/plugin.scss" 21 | }, 22 | "value": { 23 | "type": "TEXT", 24 | "value": ".show-me-pi", 25 | "loc": { 26 | "start": { 27 | "offset": 35, 28 | "line": 4, 29 | "column": 1 30 | }, 31 | "end": { 32 | "offset": 46, 33 | "line": 4, 34 | "column": 12 35 | }, 36 | "filename": "test/plugin/plugin.scss" 37 | } 38 | } 39 | }, 40 | "children": [ 41 | { 42 | "type": "DECLARATION", 43 | "left": { 44 | "type": "TEXT", 45 | "value": "value", 46 | "loc": { 47 | "start": { 48 | "offset": 53, 49 | "line": 5, 50 | "column": 5 51 | }, 52 | "end": { 53 | "offset": 58, 54 | "line": 5, 55 | "column": 10 56 | }, 57 | "filename": "test/plugin/plugin.scss" 58 | } 59 | }, 60 | "right": { 61 | "type": "TEXT", 62 | "loc": { 63 | "start": { 64 | "offset": 60, 65 | "line": 5, 66 | "column": 12 67 | }, 68 | "end": { 69 | "offset": 62, 70 | "line": 5, 71 | "column": 14 72 | }, 73 | "filename": "test/plugin/plugin.scss" 74 | }, 75 | "value": "3.141592653589793" 76 | }, 77 | "loc": { 78 | "start": { 79 | "offset": 53, 80 | "line": 5, 81 | "column": 5 82 | }, 83 | "end": { 84 | "offset": 62, 85 | "line": 5, 86 | "column": 14 87 | }, 88 | "filename": "test/plugin/plugin.scss" 89 | } 90 | } 91 | ], 92 | "loc": { 93 | "filename": "", 94 | "start": { 95 | "line": 1, 96 | "column": 1, 97 | "offset": 0 98 | }, 99 | "end": { 100 | "line": 1, 101 | "column": 1, 102 | "offset": 0 103 | } 104 | } 105 | } 106 | ], 107 | "fileSourceMap": { 108 | "test/plugin/plugin.scss": "// less code\n@plugin \"my-plugin\";\n\n.show-me-pi {\n value: pi();\n}" 109 | }, 110 | "loc": { 111 | "filename": "", 112 | "start": { 113 | "line": 1, 114 | "column": 1, 115 | "offset": 0 116 | }, 117 | "end": { 118 | "line": 1, 119 | "column": 1, 120 | "offset": 0 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/repeat.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "RULE", 6 | "selector": { 7 | "type": "SELECTOR", 8 | "meta": [], 9 | "loc": { 10 | "start": { 11 | "offset": 0, 12 | "line": 1, 13 | "column": 1 14 | }, 15 | "end": { 16 | "offset": 8, 17 | "line": 1, 18 | "column": 9 19 | }, 20 | "filename": "test/repeat.scss" 21 | }, 22 | "value": { 23 | "type": "TEXT", 24 | "value": ".repeat1", 25 | "loc": { 26 | "start": { 27 | "offset": 0, 28 | "line": 1, 29 | "column": 1 30 | }, 31 | "end": { 32 | "offset": 8, 33 | "line": 1, 34 | "column": 9 35 | }, 36 | "filename": "test/repeat.scss" 37 | } 38 | } 39 | }, 40 | "children": [ 41 | { 42 | "type": "DECLARATION", 43 | "left": { 44 | "type": "TEXT", 45 | "value": "color", 46 | "loc": { 47 | "start": { 48 | "offset": 15, 49 | "line": 2, 50 | "column": 5 51 | }, 52 | "end": { 53 | "offset": 20, 54 | "line": 2, 55 | "column": 10 56 | }, 57 | "filename": "test/repeat.scss" 58 | } 59 | }, 60 | "right": { 61 | "type": "TEXT", 62 | "loc": { 63 | "start": { 64 | "offset": 22, 65 | "line": 2, 66 | "column": 12 67 | }, 68 | "end": { 69 | "offset": 25, 70 | "line": 2, 71 | "column": 15 72 | }, 73 | "filename": "test/repeat.scss" 74 | }, 75 | "value": "red" 76 | }, 77 | "loc": { 78 | "start": { 79 | "offset": 15, 80 | "line": 2, 81 | "column": 5 82 | }, 83 | "end": { 84 | "offset": 25, 85 | "line": 2, 86 | "column": 15 87 | }, 88 | "filename": "test/repeat.scss" 89 | } 90 | } 91 | ], 92 | "loc": { 93 | "filename": "", 94 | "start": { 95 | "line": 1, 96 | "column": 1, 97 | "offset": 0 98 | }, 99 | "end": { 100 | "line": 1, 101 | "column": 1, 102 | "offset": 0 103 | } 104 | } 105 | }, 106 | { 107 | "type": "RULE", 108 | "selector": { 109 | "type": "SELECTOR", 110 | "meta": [], 111 | "loc": { 112 | "start": { 113 | "offset": 30, 114 | "line": 5, 115 | "column": 1 116 | }, 117 | "end": { 118 | "offset": 38, 119 | "line": 5, 120 | "column": 9 121 | }, 122 | "filename": "test/repeat.scss" 123 | }, 124 | "value": { 125 | "type": "TEXT", 126 | "value": ".repeat1", 127 | "loc": { 128 | "start": { 129 | "offset": 30, 130 | "line": 5, 131 | "column": 1 132 | }, 133 | "end": { 134 | "offset": 38, 135 | "line": 5, 136 | "column": 9 137 | }, 138 | "filename": "test/repeat.scss" 139 | } 140 | } 141 | }, 142 | "children": [ 143 | { 144 | "type": "DECLARATION", 145 | "left": { 146 | "type": "TEXT", 147 | "value": "color", 148 | "loc": { 149 | "start": { 150 | "offset": 45, 151 | "line": 6, 152 | "column": 5 153 | }, 154 | "end": { 155 | "offset": 50, 156 | "line": 6, 157 | "column": 10 158 | }, 159 | "filename": "test/repeat.scss" 160 | } 161 | }, 162 | "right": { 163 | "type": "TEXT", 164 | "loc": { 165 | "start": { 166 | "offset": 52, 167 | "line": 6, 168 | "column": 12 169 | }, 170 | "end": { 171 | "offset": 57, 172 | "line": 6, 173 | "column": 17 174 | }, 175 | "filename": "test/repeat.scss" 176 | }, 177 | "value": "green" 178 | }, 179 | "loc": { 180 | "start": { 181 | "offset": 45, 182 | "line": 6, 183 | "column": 5 184 | }, 185 | "end": { 186 | "offset": 57, 187 | "line": 6, 188 | "column": 17 189 | }, 190 | "filename": "test/repeat.scss" 191 | } 192 | }, 193 | { 194 | "type": "DECLARATION", 195 | "left": { 196 | "type": "TEXT", 197 | "value": "size", 198 | "loc": { 199 | "start": { 200 | "offset": 63, 201 | "line": 7, 202 | "column": 5 203 | }, 204 | "end": { 205 | "offset": 67, 206 | "line": 7, 207 | "column": 9 208 | }, 209 | "filename": "test/repeat.scss" 210 | } 211 | }, 212 | "right": { 213 | "type": "TEXT", 214 | "loc": { 215 | "start": { 216 | "offset": 69, 217 | "line": 7, 218 | "column": 11 219 | }, 220 | "end": { 221 | "offset": 73, 222 | "line": 7, 223 | "column": 15 224 | }, 225 | "filename": "test/repeat.scss" 226 | }, 227 | "value": "14px" 228 | }, 229 | "loc": { 230 | "start": { 231 | "offset": 63, 232 | "line": 7, 233 | "column": 5 234 | }, 235 | "end": { 236 | "offset": 73, 237 | "line": 7, 238 | "column": 15 239 | }, 240 | "filename": "test/repeat.scss" 241 | } 242 | } 243 | ], 244 | "loc": { 245 | "filename": "", 246 | "start": { 247 | "line": 1, 248 | "column": 1, 249 | "offset": 0 250 | }, 251 | "end": { 252 | "line": 1, 253 | "column": 1, 254 | "offset": 0 255 | } 256 | } 257 | } 258 | ], 259 | "fileSourceMap": { 260 | "test/repeat.scss": ".repeat1 {\n color: red;\n}\n\n.repeat1 {\n color: green;\n size: 14px;\n}\n" 261 | }, 262 | "loc": { 263 | "filename": "", 264 | "start": { 265 | "line": 1, 266 | "column": 1, 267 | "offset": 0 268 | }, 269 | "end": { 270 | "line": 1, 271 | "column": 1, 272 | "offset": 0 273 | } 274 | } 275 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/selector/complicated-selector.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "RULE", 6 | "selector": { 7 | "type": "SELECTOR", 8 | "meta": [], 9 | "loc": { 10 | "start": { 11 | "offset": 0, 12 | "line": 1, 13 | "column": 1 14 | }, 15 | "end": { 16 | "offset": 7, 17 | "line": 1, 18 | "column": 8 19 | }, 20 | "filename": "test/selector/complicated-selector.scss" 21 | }, 22 | "value": { 23 | "type": "TEXT", 24 | "value": ".button", 25 | "loc": { 26 | "start": { 27 | "offset": 0, 28 | "line": 1, 29 | "column": 1 30 | }, 31 | "end": { 32 | "offset": 7, 33 | "line": 1, 34 | "column": 8 35 | }, 36 | "filename": "test/selector/complicated-selector.scss" 37 | } 38 | } 39 | }, 40 | "children": [ 41 | { 42 | "type": "DECLARATION", 43 | "left": { 44 | "type": "TEXT", 45 | "value": "border", 46 | "loc": { 47 | "start": { 48 | "offset": 14, 49 | "line": 2, 50 | "column": 5 51 | }, 52 | "end": { 53 | "offset": 20, 54 | "line": 2, 55 | "column": 11 56 | }, 57 | "filename": "test/selector/complicated-selector.scss" 58 | } 59 | }, 60 | "right": { 61 | "type": "TEXT", 62 | "loc": { 63 | "start": { 64 | "offset": 22, 65 | "line": 2, 66 | "column": 13 67 | }, 68 | "end": { 69 | "offset": 37, 70 | "line": 2, 71 | "column": 28 72 | }, 73 | "filename": "test/selector/complicated-selector.scss" 74 | }, 75 | "value": "1px solid black" 76 | }, 77 | "loc": { 78 | "start": { 79 | "offset": 14, 80 | "line": 2, 81 | "column": 5 82 | }, 83 | "end": { 84 | "offset": 37, 85 | "line": 2, 86 | "column": 28 87 | }, 88 | "filename": "test/selector/complicated-selector.scss" 89 | } 90 | } 91 | ], 92 | "loc": { 93 | "filename": "", 94 | "start": { 95 | "line": 1, 96 | "column": 1, 97 | "offset": 0 98 | }, 99 | "end": { 100 | "line": 1, 101 | "column": 1, 102 | "offset": 0 103 | } 104 | } 105 | }, 106 | { 107 | "type": "RULE", 108 | "selector": { 109 | "type": "SELECTOR", 110 | "meta": [], 111 | "loc": { 112 | "start": { 113 | "offset": 44, 114 | "line": 3, 115 | "column": 6 116 | }, 117 | "end": { 118 | "offset": 66, 119 | "line": 3, 120 | "column": 28 121 | }, 122 | "filename": "test/selector/complicated-selector.scss" 123 | }, 124 | "value": { 125 | "type": "TEXT", 126 | "loc": { 127 | "start": { 128 | "offset": 44, 129 | "line": 3, 130 | "column": 6 131 | }, 132 | "end": { 133 | "offset": 66, 134 | "line": 3, 135 | "column": 28 136 | }, 137 | "filename": "test/selector/complicated-selector.scss" 138 | }, 139 | "value": ".button :not([disabled]):hover" 140 | } 141 | }, 142 | "children": [ 143 | { 144 | "type": "DECLARATION", 145 | "left": { 146 | "type": "TEXT", 147 | "value": "border-width", 148 | "loc": { 149 | "start": { 150 | "offset": 77, 151 | "line": 4, 152 | "column": 9 153 | }, 154 | "end": { 155 | "offset": 89, 156 | "line": 4, 157 | "column": 21 158 | }, 159 | "filename": "test/selector/complicated-selector.scss" 160 | } 161 | }, 162 | "right": { 163 | "type": "TEXT", 164 | "loc": { 165 | "start": { 166 | "offset": 91, 167 | "line": 4, 168 | "column": 23 169 | }, 170 | "end": { 171 | "offset": 94, 172 | "line": 4, 173 | "column": 26 174 | }, 175 | "filename": "test/selector/complicated-selector.scss" 176 | }, 177 | "value": "2px" 178 | }, 179 | "loc": { 180 | "start": { 181 | "offset": 77, 182 | "line": 4, 183 | "column": 9 184 | }, 185 | "end": { 186 | "offset": 94, 187 | "line": 4, 188 | "column": 26 189 | }, 190 | "filename": "test/selector/complicated-selector.scss" 191 | } 192 | } 193 | ], 194 | "loc": { 195 | "filename": "", 196 | "start": { 197 | "line": 1, 198 | "column": 1, 199 | "offset": 0 200 | }, 201 | "end": { 202 | "line": 1, 203 | "column": 1, 204 | "offset": 0 205 | } 206 | } 207 | } 208 | ], 209 | "fileSourceMap": { 210 | "test/selector/complicated-selector.scss": ".button {\n border: 1px solid black;\n &:not([disabled]):hover {\n border-width: 2px;\n }\n}" 211 | }, 212 | "loc": { 213 | "filename": "", 214 | "start": { 215 | "line": 1, 216 | "column": 1, 217 | "offset": 0 218 | }, 219 | "end": { 220 | "line": 1, 221 | "column": 1, 222 | "offset": 0 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /test-dist/code-gen-ast/var-simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "RootNode", 3 | "children": [ 4 | { 5 | "type": "RULE", 6 | "selector": { 7 | "type": "SELECTOR", 8 | "meta": [], 9 | "loc": { 10 | "start": { 11 | "offset": 63, 12 | "line": 4, 13 | "column": 1 14 | }, 15 | "end": { 16 | "offset": 73, 17 | "line": 4, 18 | "column": 11 19 | }, 20 | "filename": "test/var-simple.scss" 21 | }, 22 | "value": { 23 | "type": "TEXT", 24 | "value": "body .test", 25 | "loc": { 26 | "start": { 27 | "offset": 63, 28 | "line": 4, 29 | "column": 1 30 | }, 31 | "end": { 32 | "offset": 73, 33 | "line": 4, 34 | "column": 11 35 | }, 36 | "filename": "test/var-simple.scss" 37 | } 38 | } 39 | }, 40 | "children": [ 41 | { 42 | "type": "DECLARATION", 43 | "left": { 44 | "type": "TEXT", 45 | "value": "font", 46 | "loc": { 47 | "start": { 48 | "offset": 77, 49 | "line": 5, 50 | "column": 3 51 | }, 52 | "end": { 53 | "offset": 81, 54 | "line": 5, 55 | "column": 7 56 | }, 57 | "filename": "test/var-simple.scss" 58 | } 59 | }, 60 | "right": { 61 | "type": "TEXT", 62 | "loc": { 63 | "start": { 64 | "offset": 83, 65 | "line": 5, 66 | "column": 9 67 | }, 68 | "end": { 69 | "offset": 99, 70 | "line": 5, 71 | "column": 25 72 | }, 73 | "filename": "test/var-simple.scss" 74 | }, 75 | "value": "100% Helvetica, sans-serif" 76 | }, 77 | "loc": { 78 | "start": { 79 | "offset": 77, 80 | "line": 5, 81 | "column": 3 82 | }, 83 | "end": { 84 | "offset": 99, 85 | "line": 5, 86 | "column": 25 87 | }, 88 | "filename": "test/var-simple.scss" 89 | } 90 | }, 91 | { 92 | "type": "DECLARATION", 93 | "left": { 94 | "type": "TEXT", 95 | "value": "color", 96 | "loc": { 97 | "start": { 98 | "offset": 103, 99 | "line": 6, 100 | "column": 3 101 | }, 102 | "end": { 103 | "offset": 108, 104 | "line": 6, 105 | "column": 8 106 | }, 107 | "filename": "test/var-simple.scss" 108 | } 109 | }, 110 | "right": { 111 | "type": "TEXT", 112 | "loc": { 113 | "start": { 114 | "offset": 110, 115 | "line": 6, 116 | "column": 10 117 | }, 118 | "end": { 119 | "offset": 124, 120 | "line": 6, 121 | "column": 24 122 | }, 123 | "filename": "test/var-simple.scss" 124 | }, 125 | "value": "#333" 126 | }, 127 | "loc": { 128 | "start": { 129 | "offset": 103, 130 | "line": 6, 131 | "column": 3 132 | }, 133 | "end": { 134 | "offset": 124, 135 | "line": 6, 136 | "column": 24 137 | }, 138 | "filename": "test/var-simple.scss" 139 | } 140 | } 141 | ], 142 | "loc": { 143 | "filename": "", 144 | "start": { 145 | "line": 1, 146 | "column": 1, 147 | "offset": 0 148 | }, 149 | "end": { 150 | "line": 1, 151 | "column": 1, 152 | "offset": 0 153 | } 154 | } 155 | } 156 | ], 157 | "fileSourceMap": { 158 | "test/var-simple.scss": "$font-stack: Helvetica, sans-serif;\n$primary-color: #333;\n\nbody .test{\n font: 100% $font-stack;\n color: $primary-color;\n}\n" 159 | }, 160 | "loc": { 161 | "filename": "", 162 | "start": { 163 | "line": 1, 164 | "column": 1, 165 | "offset": 0 166 | }, 167 | "end": { 168 | "line": 1, 169 | "column": 1, 170 | "offset": 0 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /test-dist/css/atrule/keyframes.css: -------------------------------------------------------------------------------- 1 | @keyframes slide-in{ 2 | from{ 3 | margin-left:100%; 4 | width:300%; 5 | } 6 | 70%{ 7 | margin-left:90%; 8 | width:150%; 9 | } 10 | to{ 11 | margin-left:0%; 12 | width:100%; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test-dist/css/comment.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardpisces/tiny-sass-compiler/ddcd827b2dd16f504b05f0411c938e8bb5a5e5cf/test-dist/css/comment.css -------------------------------------------------------------------------------- /test-dist/css/extend.css: -------------------------------------------------------------------------------- 1 | .message,.success,.message-shared{ 2 | border:1px solid #ccc; 3 | padding:10px; 4 | color:#333; 5 | } 6 | .error{ 7 | border:1px solid #ccc; 8 | padding:10px; 9 | color:#333; 10 | } 11 | .success{ 12 | border-color:green; 13 | } 14 | .error{ 15 | color:red; 16 | } 17 | -------------------------------------------------------------------------------- /test-dist/css/flow-control/each/each.css: -------------------------------------------------------------------------------- 1 | .icon-#asfssd-40px{ 2 | font-size:40px; 3 | } 4 | .icon-#asfssd-40px .ok{ 5 | font-size:40px; 6 | border:40px solid #111111; 7 | } 8 | .icon-#asfssd-50px{ 9 | font-size:50px; 10 | } 11 | .icon-#asfssd-50px .ok{ 12 | font-size:50px; 13 | border:50px solid #111111; 14 | } 15 | -------------------------------------------------------------------------------- /test-dist/css/flow-control/else-if.css: -------------------------------------------------------------------------------- 1 | .next{ 2 | height:0; 3 | width:0; 4 | border-color:transparent; 5 | border-style:solid; 6 | border-width:2.5px; 7 | border-left-color:black; 8 | } 9 | -------------------------------------------------------------------------------- /test-dist/css/flow-control/else.css: -------------------------------------------------------------------------------- 1 | .banner{ 2 | background-color:#f2ece4; 3 | color:#036; 4 | } 5 | body.dark .banner{ 6 | background-color:#6b717f; 7 | color:#d2e1dd; 8 | } 9 | -------------------------------------------------------------------------------- /test-dist/css/flow-control/if.css: -------------------------------------------------------------------------------- 1 | .square-av{ 2 | width:100px; 3 | height:100px; 4 | } 5 | .circle-av{ 6 | width:100px; 7 | height:100px; 8 | border-radius:50px; 9 | } 10 | -------------------------------------------------------------------------------- /test-dist/css/function/function-with-if.css: -------------------------------------------------------------------------------- 1 | .sidebar{ 2 | margin-left:10px; 3 | } 4 | -------------------------------------------------------------------------------- /test-dist/css/function/function.css: -------------------------------------------------------------------------------- 1 | .sidebar{ 2 | float:left; 3 | margin-left:14px; 4 | } 5 | -------------------------------------------------------------------------------- /test-dist/css/mixin/basic.css: -------------------------------------------------------------------------------- 1 | nav ul{ 2 | margin:0; 3 | padding:0; 4 | list-style:none; 5 | } 6 | nav ul li{ 7 | display:inline-block; 8 | } 9 | -------------------------------------------------------------------------------- /test-dist/css/mixin/mixin-keyframes-content.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes move-the-object{ 2 | 0%{ 3 | transform:translateX(0); 4 | } 5 | 100%{ 6 | transform:translateX(200px); 7 | } 8 | } 9 | @-moz-keyframes move-the-object{ 10 | 0%{ 11 | transform:translateX(0); 12 | } 13 | 100%{ 14 | transform:translateX(200px); 15 | } 16 | } 17 | @-o-keyframes move-the-object{ 18 | 0%{ 19 | transform:translateX(0); 20 | } 21 | 100%{ 22 | transform:translateX(200px); 23 | } 24 | } 25 | @keyframes move-the-object{ 26 | 0%{ 27 | transform:translateX(0); 28 | } 29 | 100%{ 30 | transform:translateX(200px); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-dist/css/mixin/optional-params.css: -------------------------------------------------------------------------------- 1 | .mail-icon{ 2 | text-indent:-99999em; 3 | overflow:hidden; 4 | text-align:left; 5 | } 6 | .mail-icon background{ 7 | image:url("/images/mail.svg"); 8 | repeat:no-repeat; 9 | position:Helvetica, sans-serif default2; 10 | } 11 | -------------------------------------------------------------------------------- /test-dist/css/mixin/params.css: -------------------------------------------------------------------------------- 1 | .box{ 2 | -webkit-transform:rotate(30deg); 3 | -ms-transform:rotate(30deg); 4 | transform:rotate(30deg) skew(30deg,20deg); 5 | left:1px; 6 | } 7 | -------------------------------------------------------------------------------- /test-dist/css/mixin/var-key-parent-selector.css: -------------------------------------------------------------------------------- 1 | .sidebar{ 2 | float:left; 3 | } 4 | [dir=rtl] .sidebar.second{ 5 | float:right; 6 | } 7 | -------------------------------------------------------------------------------- /test-dist/css/module/forward/bootstrap-forward.css: -------------------------------------------------------------------------------- 1 | .forward-test{ 2 | color:red; 3 | background:#fff; 4 | } 5 | -------------------------------------------------------------------------------- /test-dist/css/module/import.css: -------------------------------------------------------------------------------- 1 | .base-2{ 2 | background:red; 3 | } 4 | body{ 5 | font:100% Helvetica, sans-serif; 6 | color:#333; 7 | } 8 | .base-2{ 9 | background:red; 10 | } 11 | .inverse{ 12 | background-color:#333; 13 | color:white; 14 | } 15 | -------------------------------------------------------------------------------- /test-dist/css/module/use-import/use-import.css: -------------------------------------------------------------------------------- 1 | .base-2{ 2 | background:red; 3 | } 4 | .base{ 5 | color:yellow; 6 | } 7 | .base{ 8 | color:yellow; 9 | } 10 | .use{ 11 | background-color:yellow; 12 | color:3; 13 | width:1212.1212; 14 | } 15 | -------------------------------------------------------------------------------- /test-dist/css/module/use/use.css: -------------------------------------------------------------------------------- 1 | .base-3{ 2 | background:red; 3 | } 4 | .base-2{ 5 | background:yellow; 6 | } 7 | .base{ 8 | color:yellow; 9 | } 10 | .use{ 11 | background-color:yellow; 12 | color:3; 13 | font:1212.1212; 14 | margin:0; 15 | padding:0; 16 | } 17 | .use-more-than-one-namespace{ 18 | color:yellow; 19 | } 20 | -------------------------------------------------------------------------------- /test-dist/css/nest/at-rules-and-bubbling.css: -------------------------------------------------------------------------------- 1 | .component{ 2 | width:300px; 3 | } 4 | @media (min-width:1280px){ 5 | .component .selector{ 6 | width:800px; 7 | } 8 | } 9 | @media (min-width:768px){ 10 | .component{ 11 | width:600px; 12 | } 13 | } 14 | @media (min-resolution:192dpi) and (min-width:768px){ 15 | .component .selector2{ 16 | background-image:url(/img/retina2x.png); 17 | } 18 | } 19 | @media (min-width:768px){ 20 | .component .selector3{ 21 | color:red; 22 | } 23 | } 24 | @media (min-width:960px){ 25 | .hide-extra-small{ 26 | display:none; 27 | } 28 | .selector1{ 29 | color:red; 30 | } 31 | .selector1 .selector1-1{ 32 | color:green; 33 | } 34 | } 35 | .class-a{ 36 | width:1px; 37 | } 38 | -------------------------------------------------------------------------------- /test-dist/css/nest/var-nested.css: -------------------------------------------------------------------------------- 1 | .main2{ 2 | margin:3px; 3 | right:20px; 4 | } 5 | .main{ 6 | top:20px; 7 | } 8 | .main .child1 .child2{ 9 | background:green; 10 | } 11 | .main .child1 .child3{ 12 | background:green; 13 | } 14 | -------------------------------------------------------------------------------- /test-dist/css/operator.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | width:9px; 3 | } 4 | article[role="main"]{ 5 | float:left; 6 | width:62.5%; 7 | } 8 | aside[role="complementary"]{ 9 | float:right; 10 | width:31.25%; 11 | } 12 | -------------------------------------------------------------------------------- /test-dist/css/plugin/plugin.css: -------------------------------------------------------------------------------- 1 | .show-me-pi{ 2 | value:3.141592653589793; 3 | } 4 | -------------------------------------------------------------------------------- /test-dist/css/repeat.css: -------------------------------------------------------------------------------- 1 | .repeat1{ 2 | color:red; 3 | } 4 | .repeat1{ 5 | color:green; 6 | size:14px; 7 | } 8 | -------------------------------------------------------------------------------- /test-dist/css/selector/complicated-selector.css: -------------------------------------------------------------------------------- 1 | .button{ 2 | border:1px solid black; 3 | } 4 | .button :not([disabled]):hover{ 5 | border-width:2px; 6 | } 7 | -------------------------------------------------------------------------------- /test-dist/css/var-nested.css: -------------------------------------------------------------------------------- 1 | .main2{ 2 | margin:3px; 3 | right:20px; 4 | } 5 | .main{ 6 | top:20px; 7 | } 8 | .main .child1{ 9 | margin:2px; 10 | } 11 | .main .child1 .child2{ 12 | background:green; 13 | } 14 | -------------------------------------------------------------------------------- /test-dist/css/var-simple.css: -------------------------------------------------------------------------------- 1 | body .test{ 2 | font:100% Helvetica, sans-serif; 3 | color:#333; 4 | } 5 | -------------------------------------------------------------------------------- /test-dist/source-map/atrule/keyframes.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/atrule/keyframes.scss" 5 | ], 6 | "names": [], 7 | "mappings": "WAAW;EACP;IACI,YAAa;IACb,MAAO;;EAGX;IACI,YAAa;IACb,MAAO;;EAGX;IACI,YAAa;IACb,MAAO", 8 | "sourcesContent": [ 9 | "@keyframes slide-in {\n from {\n margin-left: 100%;\n width: 300%;\n }\n\n 70% {\n margin-left: 90%;\n width: 150%;\n }\n\n to {\n margin-left: 0%;\n width: 100%;\n }\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/comment.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [], 4 | "names": [], 5 | "mappings": "", 6 | "sourcesContent": [] 7 | } -------------------------------------------------------------------------------- /test-dist/source-map/extend.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/extend.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAAA;EACI,OAAQ;EACR,QAAS;EACT,MAAO;;AAGX;EACE,OAAQ;EACR,QAAS;EACT,MAAO;;AAOT;EAEI,aAAc;;AAElB;EACI,MAAM", 8 | "sourcesContent": [ 9 | ".message-shared {\n border: 1px solid #ccc;\n padding: 10px;\n color: #333;\n}\n\n%message-shared {\n border: 1px solid #ccc;\n padding: 10px;\n color: #333;\n}\n\n.message {\n @extend .message-shared;\n}\n\n.success {\n @extend .message-shared;\n border-color: green;\n}\n.error{\n color:red;\n @extend %message-shared\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/flow-control/each/each.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/flow-control/each/each.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAGE;EACE,UAAW;;AACX;EACI,UAAW;EACX,OAAQ;;AAJd;EACE,UAAW;;AACX;EACI,UAAW;EACX,OAAQ", 8 | "sourcesContent": [ 9 | "$sizes: 40px, 50px;\n\n@each $size in $sizes {\n .icon-#asfssd-#{$size} {\n font-size: $size;\n .ok{\n font-size: $size;\n border: $size solid #111111;\n }\n }\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/flow-control/else-if.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/flow-control/else-if.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAqBA;EApBE,OAAQ;EACR,MAAO;EAEP,aAAc;EACd,aAAc;EACd,aAAc;EAKZ,kBAAmB", 8 | "sourcesContent": [ 9 | "@mixin triangle($size, $color, $direction) {\n height: 0;\n width: 0;\n\n border-color: transparent;\n border-style: solid;\n border-width: $size / 2;\n\n @if $direction == up {\n border-bottom-color: $color;\n } @else if $direction == right {\n border-left-color: $color;\n } @else if $direction == down {\n border-top-color: $color;\n } @else if $direction == left {\n border-right-color: $color;\n } @else {\n @error \"Unknown direction #{$direction}.\";\n }\n}\n\n.next {\n @include triangle(5px, black, right);\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/flow-control/else.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/flow-control/else.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAeA;EARI,iBAAkB;EAClB,MAAO;;AAST;EAPE,iBAAkB;EAClB,MAAO", 8 | "sourcesContent": [ 9 | "$light-background: #f2ece4;\n$light-text: #036;\n$dark-background: #6b717f;\n$dark-text: #d2e1dd;\n\n@mixin theme-colors($light-theme: true) {\n @if $light-theme {\n background-color: $light-background;\n color: $light-text;\n } @else {\n background-color: $dark-background;\n color: $dark-text;\n }\n}\n\n.banner {\n @include theme-colors($light-theme: true);\n body.dark & {\n @include theme-colors($light-theme: false);\n }\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/flow-control/if.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/flow-control/if.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AASA;EARE,MAAO;EACP,OAAQ;;AAQV;EATE,MAAO;EACP,OAAQ;EAGN,cAAe", 8 | "sourcesContent": [ 9 | "@mixin avatar($size, $circle: false) {\n width: $size;\n height: $size;\n\n @if $circle {\n border-radius: $size / 2;\n }\n}\n\n.square-av { @include avatar(100px, $circle: false); }\n.circle-av { @include avatar(100px, $circle: true); }" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/function/function-with-if.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/function/function-with-if.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAUA;EACI,YAAa", 8 | "sourcesContent": [ 9 | "@function min2($a, $b) {\n @if $a > $b {\n @return $b;\n }\n\n @else {\n @return $a;\n }\n}\n\n.sidebar {\n margin-left: min2(5, 6) * 2px;\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/function/function.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/function/function.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAKA;EACI,MAAO;EACP,YAAa", 8 | "sourcesContent": [ 9 | "@function plus($a, $b) {\n @return $a + $b;\n}\n\n\n.sidebar {\n float: left;\n margin-left: plus(4, 3) * 2px;\n}\n" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/mixin/basic.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/mixin/basic.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAcA;EAbE,OAAQ;EACR,QAAS;EACT,WAAY;;AAMZ;EACE,QAAS", 8 | "sourcesContent": [ 9 | "@mixin reset-list {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n@mixin horizontal-list {\n @include reset-list;\n\n li {\n display: inline-block;\n }\n}\n\nnav ul {\n @include horizontal-list;\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/mixin/mixin-keyframes-content.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/mixin/mixin-keyframes-content.scss" 5 | ], 6 | "names": [], 7 | "mappings": "mBACuB;EAkBnB;IACI,UAAW;;EAGf;IACI,UAAW;;;gBAnBC;EAchB;IACI,UAAW;;EAGf;IACI,UAAW;;;cAfD;EAUd;IACI,UAAW;;EAGf;IACI,UAAW;;;WAXJ;EAMX;IACI,UAAW;;EAGf;IACI,UAAW", 8 | "sourcesContent": [ 9 | "@mixin keyframes($animationName) {\n @-webkit-keyframes #{$animationName} {\n @content;\n }\n\n @-moz-keyframes #{$animationName} {\n @content;\n }\n\n @-o-keyframes #{$animationName} {\n @content;\n }\n\n @keyframes #{$animationName} {\n @content;\n }\n}\n\n@include keyframes(move-the-object) {\n 0% {\n transform: translateX(0);\n }\n\n 100% {\n transform: translateX(200px);\n }\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/mixin/optional-params.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/mixin/optional-params.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAaA;EAXE,YAAa;EACb,SAAU;EACV,WAAY;;AAEZ;EACE,MAAO;EACP,OAAQ;EACR,SAAU", 8 | "sourcesContent": [ 9 | "$font: Helvetica, sans-serif;\n@mixin replace-text($image,$x:default1, $y:default2) {\n text-indent: -99999em;\n overflow: hidden;\n text-align: left;\n\n background {\n image: $image;\n repeat: no-repeat;\n position: $x $y;\n }\n}\n\n.mail-icon {\n @include replace-text(url(\"/images/mail.svg\"),$font);\n}\n" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/mixin/params.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/mixin/params.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAMA;EAJE,kBAAmB;EACnB,cAAe;EACf,UAAW;EAIX,KAAK", 8 | "sourcesContent": [ 9 | "$property2 : skew(30deg, 20deg);\n@mixin transform($property,$property2) {\n -webkit-transform: $property;\n -ms-transform: $property;\n transform: $property $property2;\n}\n.box { \n @include transform(rotate(30deg),$property2); \n left:1px;\n}\n" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/mixin/var-key-parent-selector.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/mixin/var-key-parent-selector.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAQA;EAPE,MAAc;;AAEb;EACC,MAAgB", 8 | "sourcesContent": [ 9 | "@mixin rtl($property, $ltr-value, $rtl-value) {\n #{$property}: $ltr-value;\n\n [dir=rtl] &.second {\n #{ $property }: $rtl-value;\n }\n}\n\n.sidebar {\n @include rtl(float, left, right);\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/module/forward/bootstrap-forward.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/module/forward/bootstrap-forward.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAEA;EACI,MAAO;EACP,WAAY", 8 | "sourcesContent": [ 9 | "@use './forward.scss';\n\n.forward-test{\n color: forward.$color-primary;\n background: forward.$white;\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/module/import.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/module/_base-2.scss", 5 | "test/module/_base.scss", 6 | "test/module/import.scss" 7 | ], 8 | "names": [], 9 | "mappings": "AAIA;EACI,WAAY;;ACJhB;EAGE,KAAM;EACN,MAAO;;ADDT;EACI,WAAY;;AEDhB;EACE,iBAAkB;EAClB,MAAO", 10 | "sourcesContent": [ 11 | "$font-stack: Helvetica,\nsans-serif;\n$primary-color: #333;\n\n.base-2 {\n background: red;\n}", 12 | "@import \"./base-2.scss\";\n// _base.scss\n\nbody {\n font: 100% $font-stack;\n color: $primary-color;\n}", 13 | "// styles.scss\n@import \"./base.scss\";\n@import \"./base-2.scss\";\n\n.inverse {\n background-color: $primary-color;\n color: white;\n}" 14 | ] 15 | } -------------------------------------------------------------------------------- /test-dist/source-map/module/index.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "./base-2.scss", 5 | "./base.scss", 6 | "index.scss" 7 | ], 8 | "names": [], 9 | "mappings": "AAIA;EACI,WAAY;;ACJhB;EAGE,KAAM;EACN,MAAO;;ADDT;EACI,WAAY;;AEDhB;EACE,iBAAkB;EAClB,MAAO", 10 | "sourcesContent": [ 11 | "$font-stack: Helvetica,\nsans-serif;\n$primary-color: #333;\n\n.base-2 {\n background: red;\n}", 12 | "@import \"./base-2.scss\";\n// _base.scss\n\nbody {\n font: 100% $font-stack;\n color: $primary-color;\n}", 13 | "// styles.scss\n@import \"./base.scss\";\n@import \"./base-2.scss\";\n\n.inverse {\n background-color: $primary-color;\n color: white;\n}" 14 | ] 15 | } -------------------------------------------------------------------------------- /test-dist/source-map/module/use-import/use-import.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/module/use-import/_base-2.scss", 5 | "test/module/use-import/_base.scss", 6 | "test/module/use-import/use-import.scss" 7 | ], 8 | "names": [], 9 | "mappings": "AAKA;EACI,WAAY;;ACFhB;EACE,MAAO;;AADT;EACE,MAAO;;ACAT;EACI,iBAAkB;EAClB,MAAO;EACP,MAAO", 10 | "sourcesContent": [ 11 | null, 12 | "@use \"./base-2.scss\";\n// _base.scss\n$primary-color:base-2.$primary-color;\n\n.base {\n color: base-2.$primary-color;\n}", 13 | "// styles.scss\n@use \"./base.scss\";\n@use \"./base-2.scss\";\n@import \"./base.scss\";\n\n.use {\n background-color: base.$primary-color;\n color: base-2.plus(1, 2);\n width: 1212.1212;\n}" 14 | ] 15 | } -------------------------------------------------------------------------------- /test-dist/source-map/module/use/index.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "./base-2.scss", 5 | "./base.scss", 6 | "index.scss" 7 | ], 8 | "names": [], 9 | "mappings": "AAIA;EACI,WAAY;;ACJhB;EAGE,KAAM;EACN,MAAO;;ADDT;EACI,WAAY;;AEDhB;EACE,iBAAkB;EAClB,MAAO", 10 | "sourcesContent": [ 11 | "$font-stack: Helvetica,\nsans-serif;\n$primary-color: #333;\n\n.base-2 {\n background: red;\n}", 12 | "@use \"./base-2.scss\";\n// _base.scss\n\nbody {\n font: 100% $font-stack;\n color: $primary-color;\n}", 13 | "// styles.scss\n@use \"./base.scss\";\n@use \"./base-2.scss\";\n\n.inverse {\n background-color: $primary-color;\n color: white;\n}" 14 | ] 15 | } -------------------------------------------------------------------------------- /test-dist/source-map/module/use/use.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/module/use/_base-3.scss", 5 | "test/module/use/_base-2.scss", 6 | "test/module/use/_base.scss", 7 | "test/module/use/use.scss" 8 | ], 9 | "names": [], 10 | "mappings": "AAKA;EACI,WAAY;;ACFhB;EACI,WAAY;;ACDhB;EACE,MAAO;;ACDT;EACE,iBAAkB;EAClB,MAAO;EACP,KAAM;EDEN,OAAQ;EACR,QAAS;;ACCX;EACE,MAAO", 11 | "sourcesContent": [ 12 | null, 13 | null, 14 | null, 15 | "// styles.scss\n@use \"./base.scss\";\n@use \"./base-3.scss\";\n\n.use {\n background-color: base.$primary-color;\n color: base-3.plus(1,2);\n font: 1212.1212;\n @include base.base-mixin;\n}\n\n.use-more-than-one-namespace{\n color: base.$primary-color;\n}" 16 | ] 17 | } -------------------------------------------------------------------------------- /test-dist/source-map/nest/at-rules-and-bubbling.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/nest/at-rules-and-bubbling.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAAA;EACI,MAAO;;;;IAGC,MAAO;;;;;IAIX,MAAO;;;;;IAGC,iBAAkB;;;;EAG1B;IACI,MAAM;;;;EAQd;IACI,QAAS;;EAEb;IACI,MAAM;;EACN;IACI,MAAM;;;AAKlB;EACI,MAAO", 8 | "sourcesContent": [ 9 | ".component {\n width: 300px;\n .selector{\n @media (min-width: 1280px) {\n width: 800px;\n }\n }\n @media (min-width: 768px) {\n width: 600px;\n .selector2{\n @media (min-resolution: 192dpi) {\n background-image: url(/img/retina2x.png);\n }\n }\n .selector3{\n color:red;\n }\n }\n}\n\n$layout-breakpoint-small: 960px;\n\n@media (min-width: $layout-breakpoint-small) {\n .hide-extra-small {\n display: none;\n }\n .selector1{\n color:red;\n .selector1-1{\n color:green;\n }\n }\n}\n\n.class-a{\n width: 1px;\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/nest/var-nested.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/nest/var-nested.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAGA;EAEI,OAAQ;EACR,MAAO;;AAEX;EACI,IAAO;;AAEH;EACI,WAAW;;AAEf;EACI,WAAW", 8 | "sourcesContent": [ 9 | "$top : 20px;\n$margin: 2px;\n$right: $top;\n.main2{\n $margin: 3px;\n margin: $margin;\n right: $right;\n}\n.main {\n top : $top; \n .child1{\n .child2{\n background:green;\n }\n .child3{\n background:green;\n }\n }\n}\n" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/operator.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/operator.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AACA;EACE,MAAO;;AAGT;EACE,MAAO;EACP,MAAO;;AAGT;EACE,MAAO;EACP,MAAO", 8 | "sourcesContent": [ 9 | "$var : 600px;\n.container {\n width: 1px + 8px;\n}\n\narticle[role=\"main\"] {\n float: left;\n width: $var / 960px * 100%;\n}\n\naside[role=\"complementary\"] {\n float: right;\n width: 300px / 960px * 100%;\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/plugin/plugin.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/plugin/plugin.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAGA;EACI,MAAO", 8 | "sourcesContent": [ 9 | "// less code\n@plugin \"my-plugin\";\n\n.show-me-pi {\n value: pi();\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/repeat.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/repeat.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAAA;EACI,MAAO;;AAGX;EACI,MAAO;EACP,KAAM", 8 | "sourcesContent": [ 9 | ".repeat1 {\n color: red;\n}\n\n.repeat1 {\n color: green;\n size: 14px;\n}\n" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/selector/complicated-selector.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/selector/complicated-selector.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAAA;EACI,OAAQ;;AACP;EACG,aAAc", 8 | "sourcesContent": [ 9 | ".button {\n border: 1px solid black;\n &:not([disabled]):hover {\n border-width: 2px;\n }\n}" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-dist/source-map/var-simple.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "test/var-simple.scss" 5 | ], 6 | "names": [], 7 | "mappings": "AAGA;EACE,KAAM;EACN,MAAO", 8 | "sourcesContent": [ 9 | "$font-stack: Helvetica, sans-serif;\n$primary-color: #333;\n\nbody .test{\n font: 100% $font-stack;\n color: $primary-color;\n}\n" 10 | ] 11 | } -------------------------------------------------------------------------------- /test-sass-dist/atrule/keyframes.css: -------------------------------------------------------------------------------- 1 | @keyframes slide-in { 2 | from { 3 | margin-left: 100%; 4 | width: 300%; 5 | } 6 | 70% { 7 | margin-left: 90%; 8 | width: 150%; 9 | } 10 | to { 11 | margin-left: 0%; 12 | width: 100%; 13 | } 14 | } 15 | 16 | /*# sourceMappingURL=keyframes.css.map */ 17 | -------------------------------------------------------------------------------- /test-sass-dist/atrule/keyframes.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/atrule/keyframes.scss"],"names":[],"mappings":"AAAA;EACI;IACI;IACA;;EAGJ;IACI;IACA;;EAGJ;IACI;IACA","file":"keyframes.css"} -------------------------------------------------------------------------------- /test-sass-dist/comment.css: -------------------------------------------------------------------------------- 1 | /* 2 | test multiline comment 3 | */ 4 | 5 | /*# sourceMappingURL=comment.css.map */ 6 | -------------------------------------------------------------------------------- /test-sass-dist/comment.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../test/comment.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA","file":"comment.css"} -------------------------------------------------------------------------------- /test-sass-dist/extend.css: -------------------------------------------------------------------------------- 1 | .message-shared, .success, .message { 2 | border: 1px solid #ccc; 3 | padding: 10px; 4 | color: #333; 5 | } 6 | 7 | .error { 8 | border: 1px solid #ccc; 9 | padding: 10px; 10 | color: #333; 11 | } 12 | 13 | .success { 14 | border-color: green; 15 | } 16 | 17 | .error { 18 | color: red; 19 | } 20 | 21 | /*# sourceMappingURL=extend.css.map */ 22 | -------------------------------------------------------------------------------- /test-sass-dist/extend.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../test/extend.scss"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;;;AAGJ;EACE;EACA;EACA;;;AAOF;EAEI;;;AAEJ;EACI","file":"extend.css"} -------------------------------------------------------------------------------- /test-sass-dist/flow-control/each/each.css: -------------------------------------------------------------------------------- 1 | .icon-#asfssd-40px { 2 | font-size: 40px; 3 | } 4 | .icon-#asfssd-40px .ok { 5 | font-size: 40px; 6 | border: 40px solid #111111; 7 | } 8 | 9 | .icon-#asfssd-50px { 10 | font-size: 50px; 11 | } 12 | .icon-#asfssd-50px .ok { 13 | font-size: 50px; 14 | border: 50px solid #111111; 15 | } 16 | 17 | /*# sourceMappingURL=each.css.map */ 18 | -------------------------------------------------------------------------------- /test-sass-dist/flow-control/each/each.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../../test/flow-control/each/each.scss"],"names":[],"mappings":"AAGE;EACE,WAJI;;AAKJ;EACI,WANA;EAOA;;;AAJN;EACE,WAJI;;AAKJ;EACI,WANA;EAOA","file":"each.css"} -------------------------------------------------------------------------------- /test-sass-dist/flow-control/else-if.css: -------------------------------------------------------------------------------- 1 | .next { 2 | height: 0; 3 | width: 0; 4 | border-color: transparent; 5 | border-style: solid; 6 | border-width: 2.5px; 7 | border-left-color: black; 8 | } 9 | 10 | /*# sourceMappingURL=else-if.css.map */ 11 | -------------------------------------------------------------------------------- /test-sass-dist/flow-control/else-if.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/flow-control/else-if.scss"],"names":[],"mappings":"AAqBA;EApBE;EACA;EAEA;EACA;EACA;EAKE,mBAWqB","file":"else-if.css"} -------------------------------------------------------------------------------- /test-sass-dist/flow-control/else.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | background-color: #f2ece4; 3 | color: #036; 4 | } 5 | body.dark .banner { 6 | background-color: #6b717f; 7 | color: #d2e1dd; 8 | } 9 | 10 | /*# sourceMappingURL=else.css.map */ 11 | -------------------------------------------------------------------------------- /test-sass-dist/flow-control/else.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/flow-control/else.scss"],"names":[],"mappings":"AAeA;EARI,kBAPe;EAQf,OAPS;;AAgBX;EAPE,kBARc;EASd,OARQ","file":"else.css"} -------------------------------------------------------------------------------- /test-sass-dist/flow-control/if.css: -------------------------------------------------------------------------------- 1 | .square-av { 2 | width: 100px; 3 | height: 100px; 4 | } 5 | 6 | .circle-av { 7 | width: 100px; 8 | height: 100px; 9 | border-radius: 50px; 10 | } 11 | 12 | /*# sourceMappingURL=if.css.map */ 13 | -------------------------------------------------------------------------------- /test-sass-dist/flow-control/if.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/flow-control/if.scss"],"names":[],"mappings":"AASA;EARE,OAQ2B;EAP3B,QAO2B;;;AAC7B;EATE,OAS2B;EAR3B,QAQ2B;EALzB","file":"if.css"} -------------------------------------------------------------------------------- /test-sass-dist/function/function-with-if.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | margin-left: 10px; 3 | } 4 | 5 | /*# sourceMappingURL=function-with-if.css.map */ 6 | -------------------------------------------------------------------------------- /test-sass-dist/function/function-with-if.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/function/function-with-if.scss"],"names":[],"mappings":"AAUA;EACI","file":"function-with-if.css"} -------------------------------------------------------------------------------- /test-sass-dist/function/function.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | float: left; 3 | margin-left: 14px; 4 | } 5 | 6 | /*# sourceMappingURL=function.css.map */ 7 | -------------------------------------------------------------------------------- /test-sass-dist/function/function.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/function/function.scss"],"names":[],"mappings":"AAKA;EACI;EACA","file":"function.css"} -------------------------------------------------------------------------------- /test-sass-dist/mixin/basic.css: -------------------------------------------------------------------------------- 1 | nav ul { 2 | margin: 0; 3 | padding: 0; 4 | list-style: none; 5 | } 6 | nav ul li { 7 | display: inline-block; 8 | } 9 | 10 | /*# sourceMappingURL=basic.css.map */ 11 | -------------------------------------------------------------------------------- /test-sass-dist/mixin/basic.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/mixin/basic.scss"],"names":[],"mappings":"AAcA;EAbE;EACA;EACA;;AAMA;EACE","file":"basic.css"} -------------------------------------------------------------------------------- /test-sass-dist/mixin/mixin-keyframes-content.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes move-the-object { 2 | 0% { 3 | transform: translateX(0); 4 | } 5 | 100% { 6 | transform: translateX(200px); 7 | } 8 | } 9 | @-moz-keyframes move-the-object { 10 | 0% { 11 | transform: translateX(0); 12 | } 13 | 100% { 14 | transform: translateX(200px); 15 | } 16 | } 17 | @-o-keyframes move-the-object { 18 | 0% { 19 | transform: translateX(0); 20 | } 21 | 100% { 22 | transform: translateX(200px); 23 | } 24 | } 25 | @keyframes move-the-object { 26 | 0% { 27 | transform: translateX(0); 28 | } 29 | 100% { 30 | transform: translateX(200px); 31 | } 32 | } 33 | 34 | /*# sourceMappingURL=mixin-keyframes-content.css.map */ 35 | -------------------------------------------------------------------------------- /test-sass-dist/mixin/mixin-keyframes-content.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/mixin/mixin-keyframes-content.scss"],"names":[],"mappings":"AACI;EAkBA;IACI;;EAGJ;IACI;;;AAnBJ;EAcA;IACI;;EAGJ;IACI;;;AAfJ;EAUA;IACI;;EAGJ;IACI;;;AAXJ;EAMA;IACI;;EAGJ;IACI","file":"mixin-keyframes-content.css"} -------------------------------------------------------------------------------- /test-sass-dist/mixin/optional-params.css: -------------------------------------------------------------------------------- 1 | .mail-icon { 2 | text-indent: -99999em; 3 | overflow: hidden; 4 | text-align: left; 5 | } 6 | .mail-icon background { 7 | image: url("/images/mail.svg"); 8 | repeat: no-repeat; 9 | position: Helvetica, sans-serif default2; 10 | } 11 | 12 | /*# sourceMappingURL=optional-params.css.map */ 13 | -------------------------------------------------------------------------------- /test-sass-dist/mixin/optional-params.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/mixin/optional-params.scss"],"names":[],"mappings":"AAaA;EAXE;EACA;EACA;;AAEA;EACE,OAOoB;EANpB;EACA","file":"optional-params.css"} -------------------------------------------------------------------------------- /test-sass-dist/mixin/params.css: -------------------------------------------------------------------------------- 1 | .box { 2 | -webkit-transform: rotate(30deg); 3 | -ms-transform: rotate(30deg); 4 | transform: rotate(30deg) skew(30deg, 20deg); 5 | left: 1px; 6 | } 7 | 8 | /*# sourceMappingURL=params.css.map */ 9 | -------------------------------------------------------------------------------- /test-sass-dist/mixin/params.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/mixin/params.scss"],"names":[],"mappings":"AAMA;EAJE,mBAKmB;EAJnB,eAImB;EAHnB;EAIA","file":"params.css"} -------------------------------------------------------------------------------- /test-sass-dist/mixin/var-key-parent-selector.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | float: left; 3 | } 4 | [dir=rtl] .sidebar.second { 5 | float: right; 6 | } 7 | 8 | /*# sourceMappingURL=var-key-parent-selector.css.map */ 9 | -------------------------------------------------------------------------------- /test-sass-dist/mixin/var-key-parent-selector.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/mixin/var-key-parent-selector.scss"],"names":[],"mappings":"AAQA;EAPE,OAQoB;;AANnB;EACC,OAKwB","file":"var-key-parent-selector.css"} -------------------------------------------------------------------------------- /test-sass-dist/module/circular/circular-reference.css: -------------------------------------------------------------------------------- 1 | /* Error: Module loop: this module is already being loaded. 2 | * ,--> test/module/circular/_c2.scss 3 | * 1 | @use './c1'; 4 | * | ^^^^^^^^^^^ new load 5 | * ' 6 | * ,--> test/module/circular/circular-reference.scss 7 | * 1 | @use './c1'; 8 | * | =========== original load 9 | * ' 10 | * test/module/circular/_c2.scss 1:1 @use 11 | * test/module/circular/_c1.scss 1:1 @use 12 | * test/module/circular/circular-reference.scss 1:1 root stylesheet */ 13 | 14 | body::before { 15 | font-family: "Source Code Pro", "SF Mono", Monaco, Inconsolata, "Fira Mono", 16 | "Droid Sans Mono", monospace, monospace; 17 | white-space: pre; 18 | display: block; 19 | padding: 1em; 20 | margin-bottom: 1em; 21 | border-bottom: 2px solid black; 22 | content: "Error: Module loop: this module is already being loaded.\a \250c \2500 \2500 > test/module/circular/_c2.scss\a 1 \2502 @use './c1';\a \2502 ^^^^^^^^^^^ new load\a \2575 \a \250c \2500 \2500 > test/module/circular/circular-reference.scss\a 1 \2502 @use './c1';\a \2502 \2501 \2501 \2501 \2501 \2501 \2501 \2501 \2501 \2501 \2501 \2501 original load\a \2575 \a test/module/circular/_c2.scss 1:1 @use\a test/module/circular/_c1.scss 1:1 @use\a test/module/circular/circular-reference.scss 1:1 root stylesheet"; 23 | } 24 | -------------------------------------------------------------------------------- /test-sass-dist/module/forward/bootstrap-forward.css: -------------------------------------------------------------------------------- 1 | .forward-test { 2 | color: red; 3 | background: #fff; 4 | } 5 | 6 | /*# sourceMappingURL=bootstrap-forward.css.map */ 7 | -------------------------------------------------------------------------------- /test-sass-dist/module/forward/bootstrap-forward.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../../test/module/forward/bootstrap-forward.scss","../../../test/module/forward/_var.scss","../../../test/module/forward/_color.scss"],"names":[],"mappings":"AAEA;EACI,OCHY;EDIZ,YEJI","file":"bootstrap-forward.css"} -------------------------------------------------------------------------------- /test-sass-dist/module/import.css: -------------------------------------------------------------------------------- 1 | .base-2 { 2 | background: red; 3 | } 4 | 5 | body { 6 | font: 100% Helvetica, sans-serif; 7 | color: #333; 8 | } 9 | 10 | .base-2 { 11 | background: red; 12 | } 13 | 14 | .inverse { 15 | background-color: #333; 16 | color: white; 17 | } 18 | 19 | /*# sourceMappingURL=import.css.map */ 20 | -------------------------------------------------------------------------------- /test-sass-dist/module/import.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/module/_base-2.scss","../../test/module/_base.scss","../../test/module/import.scss"],"names":[],"mappings":"AAIA;EACI;;;ACFJ;EACE;EACA,ODHc;;;AAEhB;EACI;;;AEDJ;EACE,kBFHc;EEId","file":"import.css"} -------------------------------------------------------------------------------- /test-sass-dist/module/index.css: -------------------------------------------------------------------------------- 1 | .base-2 { 2 | background: red; 3 | } 4 | 5 | body { 6 | font: 100% Helvetica, sans-serif; 7 | color: #333; 8 | } 9 | 10 | .inverse { 11 | background-color: #333; 12 | color: white; 13 | } 14 | 15 | /*# sourceMappingURL=index.css.map */ 16 | -------------------------------------------------------------------------------- /test-sass-dist/module/index.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/module/_base-2.scss","../../test/module/_base.scss","../../test/module/index.scss"],"names":[],"mappings":"AAIA;EACI;;;ACFJ;EACE;EACA,ODHc;;;AEChB;EACE,kBFFc;EEGd","file":"index.css"} -------------------------------------------------------------------------------- /test-sass-dist/module/use-import/use-import.css: -------------------------------------------------------------------------------- 1 | .base-2 { 2 | background: red; 3 | } 4 | 5 | .base { 6 | color: yellow; 7 | } 8 | 9 | .base-2 { 10 | background: red; 11 | } 12 | 13 | .base { 14 | color: yellow; 15 | } 16 | 17 | .use { 18 | background-color: yellow; 19 | color: 3; 20 | width: 1212.1212; 21 | } 22 | 23 | /*# sourceMappingURL=use-import.css.map */ 24 | -------------------------------------------------------------------------------- /test-sass-dist/module/use-import/use-import.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../../test/module/use-import/_base-2.scss","../../../test/module/use-import/_base.scss","../../../test/module/use-import/use-import.scss"],"names":[],"mappings":"AAKA;EACI;;;ACFJ;EACE,ODJc;;;AAIhB;EACI;;;ACFJ;EACE,ODJc;;;AEIhB;EACI,kBFLY;EEMZ;EACA","file":"use-import.css"} -------------------------------------------------------------------------------- /test-sass-dist/module/use/use.css: -------------------------------------------------------------------------------- 1 | .base-3 { 2 | background: red; 3 | } 4 | 5 | .base-2 { 6 | background: yellow; 7 | } 8 | 9 | .base { 10 | color: yellow; 11 | } 12 | 13 | .use { 14 | background-color: yellow; 15 | color: 3; 16 | font: 1212.1212; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | .use-more-than-one-namespace { 22 | color: yellow; 23 | } 24 | 25 | /*# sourceMappingURL=use.css.map */ 26 | -------------------------------------------------------------------------------- /test-sass-dist/module/use/use.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../../test/module/use/_base-3.scss","../../../test/module/use/_base-2.scss","../../../test/module/use/_base.scss","../../../test/module/use/use.scss"],"names":[],"mappings":"AAKA;EACI;;;ACFJ;EACI,YDJY;;;AEGhB;EACE,OFJc;;;AGGhB;EACE,kBHJc;EGKd;EACA;EDEA;EACA;;;ACCF;EACE,OHXc","file":"use.css"} -------------------------------------------------------------------------------- /test-sass-dist/nest/at-rules-and-bubbling.css: -------------------------------------------------------------------------------- 1 | .component { 2 | width: 300px; 3 | } 4 | @media (min-width: 1280px) { 5 | .component .selector { 6 | width: 800px; 7 | } 8 | } 9 | @media (min-width: 768px) { 10 | .component { 11 | width: 600px; 12 | } 13 | } 14 | @media (min-width: 768px) and (min-resolution: 192dpi) { 15 | .component .selector2 { 16 | background-image: url(/img/retina2x.png); 17 | } 18 | } 19 | @media (min-width: 768px) { 20 | .component .selector3 { 21 | color: red; 22 | } 23 | } 24 | 25 | @media (min-width: 960px) { 26 | .hide-extra-small { 27 | display: none; 28 | } 29 | 30 | .selector1 { 31 | color: red; 32 | } 33 | .selector1 .selector1-1 { 34 | color: green; 35 | } 36 | } 37 | .class-a { 38 | width: 1px; 39 | } 40 | 41 | /*# sourceMappingURL=at-rules-and-bubbling.css.map */ 42 | -------------------------------------------------------------------------------- /test-sass-dist/nest/at-rules-and-bubbling.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/nest/at-rules-and-bubbling.scss"],"names":[],"mappings":"AAAA;EACI;;AAEI;EADJ;IAEQ;;;AAGR;EAPJ;IAQQ;;;AAEI;EADJ;IAEQ;;;AAJZ;EAOI;IACI;;;;AAOZ;EACI;IACI;;;EAEJ;IACI;;EACA;IACI;;;AAKZ;EACI","file":"at-rules-and-bubbling.css"} -------------------------------------------------------------------------------- /test-sass-dist/nest/var-nested.css: -------------------------------------------------------------------------------- 1 | .main2 { 2 | margin: 3px; 3 | right: 20px; 4 | } 5 | 6 | .main { 7 | top: 20px; 8 | } 9 | .main .child1 .child2 { 10 | background: green; 11 | } 12 | .main .child1 .child3 { 13 | background: green; 14 | } 15 | 16 | /*# sourceMappingURL=var-nested.css.map */ 17 | -------------------------------------------------------------------------------- /test-sass-dist/nest/var-nested.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/nest/var-nested.scss"],"names":[],"mappings":"AAGA;EAEI,QADS;EAET,OANG;;;AAQP;EACI,KATG;;AAWC;EACI;;AAEJ;EACI","file":"var-nested.css"} -------------------------------------------------------------------------------- /test-sass-dist/operator.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 9px; 3 | } 4 | 5 | article[role=main] { 6 | float: left; 7 | width: 62.5%; 8 | } 9 | 10 | aside[role=complementary] { 11 | float: right; 12 | width: 31.25%; 13 | } 14 | 15 | /*# sourceMappingURL=operator.css.map */ 16 | -------------------------------------------------------------------------------- /test-sass-dist/operator.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../test/operator.scss"],"names":[],"mappings":"AACA;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA","file":"operator.css"} -------------------------------------------------------------------------------- /test-sass-dist/plugin/plugin.css: -------------------------------------------------------------------------------- 1 | @plugin "my-plugin"; 2 | .show-me-pi { 3 | value: pi(); 4 | } 5 | 6 | /*# sourceMappingURL=plugin.css.map */ 7 | -------------------------------------------------------------------------------- /test-sass-dist/plugin/plugin.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/plugin/plugin.scss"],"names":[],"mappings":"AACA;AAEA;EACI","file":"plugin.css"} -------------------------------------------------------------------------------- /test-sass-dist/repeat.css: -------------------------------------------------------------------------------- 1 | .repeat1 { 2 | color: red; 3 | } 4 | 5 | .repeat1 { 6 | color: green; 7 | size: 14px; 8 | } 9 | 10 | /*# sourceMappingURL=repeat.css.map */ 11 | -------------------------------------------------------------------------------- /test-sass-dist/repeat.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../test/repeat.scss"],"names":[],"mappings":"AAAA;EACI;;;AAGJ;EACI;EACA","file":"repeat.css"} -------------------------------------------------------------------------------- /test-sass-dist/selector/complicated-selector.css: -------------------------------------------------------------------------------- 1 | .button { 2 | border: 1px solid black; 3 | } 4 | .button:not([disabled]):hover { 5 | border-width: 2px; 6 | } 7 | 8 | /*# sourceMappingURL=complicated-selector.css.map */ 9 | -------------------------------------------------------------------------------- /test-sass-dist/selector/complicated-selector.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../test/selector/complicated-selector.scss"],"names":[],"mappings":"AAAA;EACI;;AACA;EACI","file":"complicated-selector.css"} -------------------------------------------------------------------------------- /test-sass-dist/var-nested.css: -------------------------------------------------------------------------------- 1 | .main2 { 2 | margin: 3px; 3 | right: 20px; 4 | } 5 | 6 | .main { 7 | top: 20px; 8 | } 9 | .main .child1 { 10 | margin: 2px; 11 | } 12 | .main .child1 .child2 { 13 | background: green; 14 | } 15 | 16 | /*# sourceMappingURL=var-nested.css.map */ 17 | -------------------------------------------------------------------------------- /test-sass-dist/var-nested.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../test/var-nested.scss"],"names":[],"mappings":"AAGA;EAEI,QADS;EAET,OANG;;;AAQP;EACI,KATG;;AAUH;EACI,QAVC;;AAWD;EACI","file":"var-nested.css"} -------------------------------------------------------------------------------- /test-sass-dist/var-simple.css: -------------------------------------------------------------------------------- 1 | body .test { 2 | font: 100% Helvetica, sans-serif; 3 | color: #333; 4 | } 5 | 6 | /*# sourceMappingURL=var-simple.css.map */ 7 | -------------------------------------------------------------------------------- /test-sass-dist/var-simple.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../test/var-simple.scss"],"names":[],"mappings":"AAGA;EACE;EACA,OAJc","file":"var-simple.css"} -------------------------------------------------------------------------------- /test/atrule/keyframes.scss: -------------------------------------------------------------------------------- 1 | @keyframes slide-in { 2 | from { 3 | margin-left: 100%; 4 | width: 300%; 5 | } 6 | 7 | 70% { 8 | margin-left: 90%; 9 | width: 150%; 10 | } 11 | 12 | to { 13 | margin-left: 0%; 14 | width: 100%; 15 | } 16 | } -------------------------------------------------------------------------------- /test/comment.scss: -------------------------------------------------------------------------------- 1 | /* 2 | test multiline comment 3 | */ 4 | 5 | // test single line comment -------------------------------------------------------------------------------- /test/extend.scss: -------------------------------------------------------------------------------- 1 | .message-shared { 2 | border: 1px solid #ccc; 3 | padding: 10px; 4 | color: #333; 5 | } 6 | 7 | %message-shared { 8 | border: 1px solid #ccc; 9 | padding: 10px; 10 | color: #333; 11 | } 12 | 13 | .message { 14 | @extend .message-shared; 15 | } 16 | 17 | .success { 18 | @extend .message-shared; 19 | border-color: green; 20 | } 21 | .error{ 22 | color:red; 23 | @extend %message-shared 24 | } -------------------------------------------------------------------------------- /test/flow-control/each/each.scss: -------------------------------------------------------------------------------- 1 | $sizes: 40px, 50px; 2 | 3 | @each $size in $sizes { 4 | .icon-#asfssd-#{$size} { 5 | font-size: $size; 6 | .ok{ 7 | font-size: $size; 8 | border: $size solid #111111; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /test/flow-control/else-if.scss: -------------------------------------------------------------------------------- 1 | @mixin triangle($size, $color, $direction) { 2 | height: 0; 3 | width: 0; 4 | 5 | border-color: transparent; 6 | border-style: solid; 7 | border-width: $size / 2; 8 | 9 | @if $direction == up { 10 | border-bottom-color: $color; 11 | } @else if $direction == right { 12 | border-left-color: $color; 13 | } @else if $direction == down { 14 | border-top-color: $color; 15 | } @else if $direction == left { 16 | border-right-color: $color; 17 | } @else { 18 | @error "Unknown direction #{$direction}."; 19 | } 20 | } 21 | 22 | .next { 23 | @include triangle(5px, black, right); 24 | } -------------------------------------------------------------------------------- /test/flow-control/else.scss: -------------------------------------------------------------------------------- 1 | $light-background: #f2ece4; 2 | $light-text: #036; 3 | $dark-background: #6b717f; 4 | $dark-text: #d2e1dd; 5 | 6 | @mixin theme-colors($light-theme: true) { 7 | @if $light-theme { 8 | background-color: $light-background; 9 | color: $light-text; 10 | } @else { 11 | background-color: $dark-background; 12 | color: $dark-text; 13 | } 14 | } 15 | 16 | .banner { 17 | @include theme-colors($light-theme: true); 18 | body.dark & { 19 | @include theme-colors($light-theme: false); 20 | } 21 | } -------------------------------------------------------------------------------- /test/flow-control/if.scss: -------------------------------------------------------------------------------- 1 | @mixin avatar($size, $circle: false) { 2 | width: $size; 3 | height: $size; 4 | 5 | @if $circle { 6 | border-radius: $size / 2; 7 | } 8 | } 9 | 10 | .square-av { @include avatar(100px, $circle: false); } 11 | .circle-av { @include avatar(100px, $circle: true); } -------------------------------------------------------------------------------- /test/function/function-with-if.scss: -------------------------------------------------------------------------------- 1 | @function min2($a, $b) { 2 | @if $a > $b { 3 | @return $b; 4 | } 5 | 6 | @else { 7 | @return $a; 8 | } 9 | } 10 | 11 | .sidebar { 12 | margin-left: min2(5, 6) * 2px; 13 | } -------------------------------------------------------------------------------- /test/function/function.scss: -------------------------------------------------------------------------------- 1 | @function plus($a, $b) { 2 | @return $a + $b; 3 | } 4 | 5 | 6 | .sidebar { 7 | float: left; 8 | margin-left: plus(4, 3) * 2px; 9 | } 10 | -------------------------------------------------------------------------------- /test/mixin/basic.scss: -------------------------------------------------------------------------------- 1 | @mixin reset-list { 2 | margin: 0; 3 | padding: 0; 4 | list-style: none; 5 | } 6 | 7 | @mixin horizontal-list { 8 | @include reset-list; 9 | 10 | li { 11 | display: inline-block; 12 | } 13 | } 14 | 15 | nav ul { 16 | @include horizontal-list; 17 | } -------------------------------------------------------------------------------- /test/mixin/mixin-keyframes-content.scss: -------------------------------------------------------------------------------- 1 | @mixin keyframes($animationName) { 2 | @-webkit-keyframes #{$animationName} { 3 | @content; 4 | } 5 | 6 | @-moz-keyframes #{$animationName} { 7 | @content; 8 | } 9 | 10 | @-o-keyframes #{$animationName} { 11 | @content; 12 | } 13 | 14 | @keyframes #{$animationName} { 15 | @content; 16 | } 17 | } 18 | 19 | @include keyframes(move-the-object) { 20 | 0% { 21 | transform: translateX(0); 22 | } 23 | 24 | 100% { 25 | transform: translateX(200px); 26 | } 27 | } -------------------------------------------------------------------------------- /test/mixin/optional-params.scss: -------------------------------------------------------------------------------- 1 | $font: Helvetica, sans-serif; 2 | @mixin replace-text($image,$x:default1, $y:default2) { 3 | text-indent: -99999em; 4 | overflow: hidden; 5 | text-align: left; 6 | 7 | background { 8 | image: $image; 9 | repeat: no-repeat; 10 | position: $x $y; 11 | } 12 | } 13 | 14 | .mail-icon { 15 | @include replace-text(url("/images/mail.svg"),$font); 16 | } 17 | -------------------------------------------------------------------------------- /test/mixin/params.scss: -------------------------------------------------------------------------------- 1 | $property2 : skew(30deg, 20deg); 2 | @mixin transform($property,$property2) { 3 | -webkit-transform: $property; 4 | -ms-transform: $property; 5 | transform: $property $property2; 6 | } 7 | .box { 8 | @include transform(rotate(30deg),$property2); 9 | left:1px; 10 | } 11 | -------------------------------------------------------------------------------- /test/mixin/var-key-parent-selector.scss: -------------------------------------------------------------------------------- 1 | @mixin rtl($property, $ltr-value, $rtl-value) { 2 | #{$property}: $ltr-value; 3 | 4 | [dir=rtl] &.second { 5 | #{ $property }: $rtl-value; 6 | } 7 | } 8 | 9 | .sidebar { 10 | @include rtl(float, left, right); 11 | } -------------------------------------------------------------------------------- /test/module/_base-2.scss: -------------------------------------------------------------------------------- 1 | $font-stack: Helvetica, 2 | sans-serif; 3 | $primary-color: #333; 4 | 5 | .base-2 { 6 | background: red; 7 | } -------------------------------------------------------------------------------- /test/module/_base.scss: -------------------------------------------------------------------------------- 1 | @import "./base-2.scss"; 2 | // _base.scss 3 | 4 | body { 5 | font: 100% $font-stack; 6 | color: $primary-color; 7 | } -------------------------------------------------------------------------------- /test/module/circular/_c1.scss: -------------------------------------------------------------------------------- 1 | @use './c2'; -------------------------------------------------------------------------------- /test/module/circular/_c2.scss: -------------------------------------------------------------------------------- 1 | @use './c1'; -------------------------------------------------------------------------------- /test/module/circular/circular-reference.scss: -------------------------------------------------------------------------------- 1 | @use './c1'; -------------------------------------------------------------------------------- /test/module/forward/_color.scss: -------------------------------------------------------------------------------- 1 | $white: #fff; -------------------------------------------------------------------------------- /test/module/forward/_forward.scss: -------------------------------------------------------------------------------- 1 | @forward './var.scss'; 2 | @forward './color.scss'; -------------------------------------------------------------------------------- /test/module/forward/_var.scss: -------------------------------------------------------------------------------- 1 | $color-primary: red; -------------------------------------------------------------------------------- /test/module/forward/bootstrap-forward.scss: -------------------------------------------------------------------------------- 1 | @use './forward.scss'; 2 | 3 | .forward-test{ 4 | color: forward.$color-primary; 5 | background: forward.$white; 6 | } -------------------------------------------------------------------------------- /test/module/import.scss: -------------------------------------------------------------------------------- 1 | // styles.scss 2 | @import "./base.scss"; 3 | @import "./base-2.scss"; 4 | 5 | .inverse { 6 | background-color: $primary-color; 7 | color: white; 8 | } -------------------------------------------------------------------------------- /test/module/use-import/_base-2.scss: -------------------------------------------------------------------------------- 1 | // @use './base'; 2 | $primary-color: yellow; 3 | @function plus($a, $b) { 4 | @return $a + $b; 5 | } 6 | .base-2 { 7 | background: red; 8 | } -------------------------------------------------------------------------------- /test/module/use-import/_base.scss: -------------------------------------------------------------------------------- 1 | @use "./base-2.scss"; 2 | // _base.scss 3 | $primary-color:base-2.$primary-color; 4 | 5 | .base { 6 | color: base-2.$primary-color; 7 | } -------------------------------------------------------------------------------- /test/module/use-import/use-import.scss: -------------------------------------------------------------------------------- 1 | // styles.scss 2 | @use "./base.scss"; 3 | @use "./base-2.scss"; 4 | @import "./base.scss"; 5 | 6 | .use { 7 | background-color: base.$primary-color; 8 | color: base-2.plus(1, 2); 9 | width: 1212.1212; 10 | } -------------------------------------------------------------------------------- /test/module/use/_base-2.scss: -------------------------------------------------------------------------------- 1 | @use './base-3'; 2 | 3 | $primary-color: base-3.$primary-color; 4 | 5 | .base-2 { 6 | background: base-3.$primary-color; 7 | } -------------------------------------------------------------------------------- /test/module/use/_base-3.scss: -------------------------------------------------------------------------------- 1 | // @use './base'; 2 | $primary-color: yellow; 3 | @function plus($a, $b) { 4 | @return $a + $b; 5 | } 6 | .base-3 { 7 | background: red; 8 | } -------------------------------------------------------------------------------- /test/module/use/_base.scss: -------------------------------------------------------------------------------- 1 | @use "./base-2.scss"; 2 | // _base.scss 3 | $primary-color:base-2.$primary-color; 4 | 5 | .base { 6 | color: base-2.$primary-color; 7 | } 8 | 9 | @mixin base-mixin { 10 | margin: 0; 11 | padding: 0; 12 | } 13 | -------------------------------------------------------------------------------- /test/module/use/use.scss: -------------------------------------------------------------------------------- 1 | // styles.scss 2 | @use "./base.scss"; 3 | @use "./base-3.scss"; 4 | 5 | .use { 6 | background-color: base.$primary-color; 7 | color: base-3.plus(1,2); 8 | font: 1212.1212; 9 | @include base.base-mixin; 10 | } 11 | 12 | .use-more-than-one-namespace{ 13 | color: base.$primary-color; 14 | } -------------------------------------------------------------------------------- /test/nest/at-rules-and-bubbling.scss: -------------------------------------------------------------------------------- 1 | .component { 2 | width: 300px; 3 | .selector{ 4 | @media (min-width: 1280px) { 5 | width: 800px; 6 | } 7 | } 8 | @media (min-width: 768px) { 9 | width: 600px; 10 | .selector2{ 11 | @media (min-resolution: 192dpi) { 12 | background-image: url(/img/retina2x.png); 13 | } 14 | } 15 | .selector3{ 16 | color:red; 17 | } 18 | } 19 | } 20 | 21 | $layout-breakpoint-small: 960px; 22 | 23 | @media (min-width: $layout-breakpoint-small) { 24 | .hide-extra-small { 25 | display: none; 26 | } 27 | .selector1{ 28 | color:red; 29 | .selector1-1{ 30 | color:green; 31 | } 32 | } 33 | } 34 | 35 | .class-a{ 36 | width: 1px; 37 | } -------------------------------------------------------------------------------- /test/nest/var-nested.scss: -------------------------------------------------------------------------------- 1 | $top : 20px; 2 | $margin: 2px; 3 | $right: $top; 4 | .main2{ 5 | $margin: 3px; 6 | margin: $margin; 7 | right: $right; 8 | } 9 | .main { 10 | top : $top; 11 | .child1{ 12 | .child2{ 13 | background:green; 14 | } 15 | .child3{ 16 | background:green; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/operator.scss: -------------------------------------------------------------------------------- 1 | $var : 600px; 2 | .container { 3 | width: 1px + 8px; 4 | } 5 | 6 | article[role="main"] { 7 | float: left; 8 | width: $var / 960px * 100%; 9 | } 10 | 11 | aside[role="complementary"] { 12 | float: right; 13 | width: 300px / 960px * 100%; 14 | } -------------------------------------------------------------------------------- /test/plugin/my-plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | install: function (functions) { 3 | functions.add('pi', function () { 4 | return Math.PI; 5 | }); 6 | } 7 | }; -------------------------------------------------------------------------------- /test/plugin/plugin.scss: -------------------------------------------------------------------------------- 1 | // less code 2 | @plugin "my-plugin"; 3 | 4 | .show-me-pi { 5 | value: pi(); 6 | } -------------------------------------------------------------------------------- /test/repeat.scss: -------------------------------------------------------------------------------- 1 | .repeat1 { 2 | color: red; 3 | } 4 | 5 | .repeat1 { 6 | color: green; 7 | size: 14px; 8 | } 9 | -------------------------------------------------------------------------------- /test/selector/complicated-selector.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | border: 1px solid black; 3 | &:not([disabled]):hover { 4 | border-width: 2px; 5 | } 6 | } -------------------------------------------------------------------------------- /test/var-simple.scss: -------------------------------------------------------------------------------- 1 | $font-stack: Helvetica, sans-serif; 2 | $primary-color: #333; 3 | 4 | body .test{ 5 | font: 100% $font-stack; 6 | color: $primary-color; 7 | } 8 | -------------------------------------------------------------------------------- /todos.md: -------------------------------------------------------------------------------- 1 | 2 | ## Todos: 3 | 4 | ### feat 5 | 6 | * add @forward 7 | * try to add more detailed selector parsing reference [css-selector-parser](https://github.com/mdevils/css-selector-parser) 8 | * use [fiber](https://www.npmjs.com/package/fibers)/[sass-fiber](https://sass-lang.com/documentation/js-api#fiber) to optimize render/renderSync ? 9 | * [incremental parsing](https://tree-sitter.github.io/tree-sitter/)? 10 | * add '( | )' check to binary precedence, only support whitespace gap operator (eg: 1 + 2 but not 1+2) 11 | * write selector lexical analyze in detail (complete selector parse) 12 | * support length(n) namespace for @use ? Is it necessary? 13 | * add static check for acss in vscode 14 | * add browser style[type='text/acss'] and link[rel='RootNode/acss'] support 15 | * add changeLog generation (reference vite) 16 | * parse errors 17 | 1. correctly report syntax error when there is no bracket 18 | * contribute rollup/webpack/vite etc tiny-sass-compiler loader or plugin 19 | * update scripts/release.sh (reference vue-next) 20 | 21 | ### other advance 22 | 23 | * simple webkit(based on canvas) + simple js(based on estree) 24 | * use go to write the program -> then compile to Native -> auto load platform specific binary (reference [esbuild](https://github.com/evanw/esbuild) and [es-module-lexer](https://github.com/guybedford/es-module-lexer)) 25 | * complete test cases (doing) 26 | * make API more flexible (reference [esprima](https://www.npmjs.com/package/esprima) / [ast-types](https://www.npmjs.com/package/ast-types) / [escodegen](https://www.npmjs.com/package/escodegen)) 27 | -------------------------------------------------------------------------------- /transform.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | Using a @plugin at-rule is similar to using an @import for your .scss files. 4 | ```less 5 | @plugin "my-plugin"; // automatically appends .js if no extension 6 | ``` 7 | 8 | ```ts 9 | module.exports = { 10 | install: function(functions:Enviroment) { 11 | functions.add('pi', function() { 12 | return Math.PI; 13 | }); 14 | } 15 | }; 16 | ``` 17 | 18 | [Enviroment Definition](https://github.com/wizardpisces/tiny-sass-compiler/blob/master/src/enviroment/Enviroment.ts) 19 | 20 | ## Reference 21 | * https://less.bootcss.com/features/#plugin-at-rules -------------------------------------------------------------------------------- /traversal.md: -------------------------------------------------------------------------------- 1 | # AST traversal 2 | 3 | Handler on node entrance, i.e. before any nested node is processed. 4 | 5 | ***ast traversal api applies only on dist code(css) related ast*** 6 | 7 | In fact tiny-sass-compiler's source code generation depends on the same traverse as plugin 8 | 9 | Detail usage can refer to 10 | ``` 11 | /src/__tests__/plugin.spec.ts 12 | ``` 13 | or 14 | source code generator: 15 | ``` 16 | /src/genCodeVisitor.ts 17 | ``` 18 | 19 | ### Todos 20 | refer to 21 | * [babel-types](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#babel-types) 22 | * [babel-template](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#babel-template) 23 | 24 | ## reference 25 | * https://github.com/csstree/csstree/blob/master/docs/traversal.md 26 | * https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-visitors -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "sourceMap": true, 5 | "target": "esnext", 6 | "module": "commonjs", //support: npm run test-source 7 | "moduleResolution": "node", 8 | "allowJs": true, 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noImplicitAny": false, 12 | "resolveJsonModule": true, 13 | "esModuleInterop": true, 14 | "removeComments": false, 15 | "declaration": true, 16 | "declarationDir": "dist/types", //support api-extractor.json bundle ts config mainEntryPointFilePath 17 | "importHelpers": true, 18 | "allowSyntheticDefaultImports": true, 19 | "experimentalDecorators": true, 20 | "jsx": "preserve", 21 | "lib": [ 22 | "esnext", 23 | "dom" 24 | ], 25 | "types": [ 26 | "jest", 27 | "node" 28 | ], 29 | "rootDir": ".", 30 | "paths": { 31 | "@/*": [ 32 | "src/*" 33 | ], 34 | "~": [ 35 | "/*" 36 | ] 37 | } 38 | }, 39 | "include": [ 40 | "src/**/*" 41 | ] 42 | } --------------------------------------------------------------------------------