├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── document.js ├── get-lang.js ├── get-syntax.js ├── index.js ├── load-syntax.js ├── normal-opts.js ├── package.json ├── parse-style.js ├── parse.js ├── parser.js ├── patch-postcss.js ├── processor.js ├── stringify.js ├── syntax.js └── test ├── custom.js ├── document.js ├── fixtures ├── glamorous.jsx └── link.js ├── glamorous.js ├── html.js ├── index.js ├── languages.js ├── load-syntax.js ├── markdown.js ├── parse-style.js ├── safe.js ├── sugarss.js └── vue.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | 64 | 65 | # End of https://www.gitignore.io/api/node 66 | 67 | /packages/**/package-lock.json 68 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pid 3 | *.seed 4 | .editorconfig 5 | .eslintrc* 6 | .git 7 | .gitignore 8 | .grunt 9 | .lock-wscript 10 | .node_repl_history 11 | .nyc_output 12 | .stylelintrc* 13 | .travis.yml 14 | .vscode 15 | appveyor.yml 16 | coverage 17 | gulpfile.js 18 | lib-cov 19 | logs 20 | node_modules 21 | npm-debug.log* 22 | pids 23 | test 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | language: node_js 4 | 5 | node_js: 6 | - stable 7 | 8 | before_install: 9 | - curl -s https://raw.githubusercontent.com/gucong3000/postcss-syntaxes/HEAD/deps.js | node 10 | 11 | after_script: 12 | - codecov 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 刘祺 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PostCSS Syntax 2 | ==== 3 | 4 | [![NPM version](https://img.shields.io/npm/v/postcss-syntax.svg?style=flat-square)](https://www.npmjs.com/package/postcss-syntax) 5 | [![Travis](https://img.shields.io/travis/gucong3000/postcss-syntax.svg)](https://travis-ci.org/gucong3000/postcss-syntax) 6 | [![Travis](https://img.shields.io/travis/gucong3000/postcss-syntaxes.svg?label=integration)](https://travis-ci.org/gucong3000/postcss-syntaxes) 7 | [![Codecov](https://img.shields.io/codecov/c/github/gucong3000/postcss-syntax.svg)](https://codecov.io/gh/gucong3000/postcss-syntax) 8 | [![David](https://img.shields.io/david/dev/gucong3000/postcss-syntax.svg)](https://david-dm.org/gucong3000/postcss-syntax?type=dev) 9 | 10 | 13 | 14 | postcss-syntax can automatically switch the required [PostCSS](https://github.com/postcss/postcss) syntax by file extension/source 15 | 16 | ## Getting Started 17 | 18 | First thing's first, install the module: 19 | 20 | ``` 21 | npm install postcss-syntax --save-dev 22 | ``` 23 | 24 | If you want support SCSS/SASS/LESS/SugarSS syntax, you need to install these module: 25 | 26 | - SCSS: [postcss-scss](https://github.com/postcss/postcss-scss) 27 | - SASS: [postcss-sass](https://github.com/aleshaoleg/postcss-sass) 28 | - LESS: [postcss-less](https://github.com/shellscape/postcss-less) 29 | - SugarSS: [sugarss](https://github.com/postcss/sugarss) 30 | 31 | If you want support HTML (and HTML-like)/Markdown/CSS-in-JS file format, you need to install these module: 32 | 33 | - CSS-in-JS: [postcss-jsx](https://github.com/gucong3000/postcss-jsx) 34 | - HTML (and HTML-like): [postcss-html](https://github.com/gucong3000/postcss-html) 35 | - Markdown: [postcss-markdown](https://github.com/gucong3000/postcss-markdown) 36 | 37 | ## Use Cases 38 | 39 | ```js 40 | const postcss = require('postcss'); 41 | const syntax = require('postcss-syntax')({ 42 | rules: [ 43 | { 44 | test: /\.(?:[sx]?html?|[sx]ht|vue|ux|php)$/i, 45 | extract: 'html', 46 | }, 47 | { 48 | test: /\.(?:markdown|md)$/i, 49 | extract: 'markdown', 50 | }, 51 | { 52 | test: /\.(?:[cm]?[jt]sx?|es\d*|pac)$/i, 53 | extract: 'jsx', 54 | }, 55 | { 56 | // custom language for file extension 57 | test: /\.postcss$/i, 58 | lang: 'scss' 59 | }, 60 | { 61 | // custom language for file extension 62 | test: /\.customcss$/i, 63 | lang: 'custom' 64 | }, 65 | ], 66 | 67 | // custom parser for CSS (using `postcss-safe-parser`) 68 | css: 'postcss-safe-parser', 69 | // custom parser for SASS (PostCSS-compatible syntax.) 70 | sass: require('postcss-sass'), 71 | // custom parser for SCSS (by module name) 72 | scss: 'postcss-scss', 73 | // custom parser for LESS (by module path) 74 | less: './node_modules/postcss-less', 75 | // custom parser for SugarSS 76 | sugarss: require('sugarss'), 77 | // custom parser for custom language 78 | custom: require('postcss-custom-syntax'), 79 | 80 | }); 81 | postcss(plugins).process(source, { syntax: syntax }).then(function (result) { 82 | // An alias for the result.css property. Use it with syntaxes that generate non-CSS output. 83 | result.content 84 | }); 85 | ``` 86 | -------------------------------------------------------------------------------- /document.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const PostCssRoot = require("postcss/lib/root"); 3 | class Document extends PostCssRoot { 4 | toString (stringifier) { 5 | return super.toString(stringifier || { 6 | stringify: require("./stringify"), 7 | }); 8 | } 9 | 10 | each (callback) { 11 | const result = this.nodes.map(node => node.each(callback)); 12 | return result.every(result => result !== false) && result.pop(); 13 | } 14 | 15 | append () { 16 | this.last.append.apply( 17 | this.last, 18 | Array.from(arguments) 19 | ); 20 | return this; 21 | } 22 | 23 | prepend () { 24 | this.first.prepend.apply( 25 | this.first, 26 | Array.from(arguments) 27 | ); 28 | return this; 29 | } 30 | 31 | insertBefore (exist, add) { 32 | exist.prepend(add); 33 | return this; 34 | } 35 | 36 | insertAfter (exist, add) { 37 | exist.append(add); 38 | return this; 39 | } 40 | } 41 | module.exports = Document; 42 | -------------------------------------------------------------------------------- /get-lang.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const languages = { 4 | sass: /^sass$/i, 5 | // https://github.com/Microsoft/vscode/blob/master/extensions/scss/package.json 6 | scss: /^scss$/i, 7 | // https://github.com/Microsoft/vscode/blob/master/extensions/less/package.json 8 | less: /^less$/i, 9 | // https://github.com/MhMadHamster/vscode-postcss-language/blob/master/package.json 10 | sugarss: /^s(?:ugar)?ss$/i, 11 | // https://github.com/d4rkr00t/language-stylus/blob/master/package.json 12 | stylus: /^styl(?:us)?$/i, 13 | // WXSS(WeiXin Style Sheets) See: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html 14 | // acss(AntFinancial Style Sheet) See: https://docs.alipay.com/mini/framework/acss 15 | // `*.pcss`, `*.postcss` 16 | // https://github.com/Microsoft/vscode/blob/master/extensions/css/package.json 17 | // https://github.com/rcsole/postcss-syntax/blob/master/package.json 18 | css: /^(?:wx|\w*c)ss$/i, 19 | }; 20 | 21 | const extracts = { 22 | // https://github.com/Microsoft/vscode/blob/master/extensions/javascript/package.json 23 | // https://github.com/Microsoft/vscode/blob/master/extensions/typescript-basics/package.json 24 | // https://github.com/michaelgmcd/vscode-language-babel/blob/master/package.json 25 | jsx: /^(?:[cm]?[jt]sx?|es\d*|pac|babel|flow)$/i, 26 | // *.*html? HTML https://github.com/Microsoft/vscode/blob/master/extensions/html/package.json 27 | // *.xslt? XSLT https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx 28 | // *.vue VUE https://vue-loader.vuejs.org/spec.html 29 | // *.wpy WePY https://github.com/Tencent/wepy/blob/master/docs/md/doc.md#wpy文件说明 30 | // *.ux quickapp https://doc.quickapp.cn/framework/source-file.html 31 | // *.php* PHP https://github.com/Microsoft/vscode/blob/master/extensions/php/package.json 32 | // *.twig Twig https://github.com/mblode/vscode-twig-language/blob/master/package.json 33 | // *.liquid Liquid https://github.com/GingerBear/vscode-liquid/blob/master/package.json 34 | // *.svelte Svelte https://github.com/UnwrittenFun/svelte-vscode/blob/master/package.json 35 | html: /^(?:\w*html?|xht|xslt?|mdoc|jsp|aspx?|volt|ejs|vue|wpy|ux|php\d*|ctp|twig|liquid|svelte)$/i, 36 | // https://github.com/Microsoft/vscode/blob/master/extensions/markdown-basics/package.json 37 | markdown: /^(?:m(?:ark)?d(?:ow)?n|mk?d)$/i, 38 | // https://github.com/Microsoft/vscode/blob/master/extensions/xml/package.json 39 | xml: /^(?:xml|xsd|ascx|atom|axml|bpmn|config|cpt|csl|csproj|csproj|user|dita|ditamap|dtd|dtml|fsproj|fxml|iml|isml|jmx|launch|menu|mxml|nuspec|opml|owl|proj|props|pt|publishsettings|pubxml|pubxml|user|rdf|rng|rss|shproj|storyboard|svg|targets|tld|tmx|vbproj|vbproj|user|vcxproj|vcxproj|filters|wsdl|wxi|wxl|wxs|xaml|xbl|xib|xlf|xliff|xpdl|xul|xoml)$/i, 40 | }; 41 | 42 | function sourceType (source) { 43 | source = source && source.trim(); 44 | if (!source) { 45 | return; 46 | } 47 | let extract; 48 | if ( 49 | // start with strict mode 50 | // start with import code 51 | // start with require code 52 | /^(?:(?:\/\/[^\r\n]*\r?\n|\/\*.*?\*\/)\s*)*(?:(?:("|')use strict\1|import(?:\s+[^;]+\s+from)?\s+("|')[^'"]+?\2|export\s+[^;]+\s+[^;]+)\s*(;|\r?\n|$)|(?:(?:var|let|const)\s+[^;]+\s*=\s*)?(?:require|import)\(.+\))/.test(source) || 53 | // https://en.wikipedia.org/wiki/Shebang_(Unix) 54 | (/^#!([^\r\n]+)/.test(source) && /(?:^|\s+|\/)(?:ts-)?node(?:\.\w+)?(?:\s+|$)$/.test(RegExp.$1)) 55 | ) { 56 | extract = "jsx"; 57 | } else if ( 58 | /^(?:<\?.*?\?>\s*)*<(?:!DOCTYPE\s+)?html(\s+[^<>]*)?>/i.test(source) || 59 | /^<\?php(?:\s+[\s\S]*)?(?:\?>|$)/.test(source) 60 | ) { 61 | extract = "html"; 62 | } else if (/^<\?xml(\s+[^<>]*)?\?>/i.test(source)) { 63 | // https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx 64 | if (/]*>/.test(source) || /<\/xsl:\w+>/i.test(source)) { 65 | extract = "html"; 66 | } else { 67 | extract = "xml"; 68 | } 69 | } else if (/^(?:#+\s+\S+|\S+[^\r\n]*\r?\n=+(\r?\n|$))/.test(source)) { 70 | extract = "markdown"; 71 | } else if (/<(\w+)(?:\s+[^<>]*)?>[\s\S]*?<\/\1>/.test(source)) { 72 | extract = "html"; 73 | } else { 74 | return; 75 | } 76 | return { 77 | extract, 78 | }; 79 | } 80 | 81 | function extType (extName, languages) { 82 | for (const langName in languages) { 83 | if (languages[langName].test(extName)) { 84 | return langName; 85 | } 86 | } 87 | } 88 | 89 | function fileType (file) { 90 | if (file && /\.(\w+)(?:[?#].*?)?$/.test(file)) { 91 | const extName = RegExp.$1; 92 | const extract = extType(extName, extracts); 93 | if (extract) { 94 | return { 95 | extract, 96 | }; 97 | } 98 | const lang = extType(extName, languages); 99 | if (lang) { 100 | return { 101 | lang, 102 | }; 103 | } 104 | } 105 | } 106 | 107 | function getLang (file, source) { 108 | return fileType(file) || sourceType(source); 109 | } 110 | 111 | module.exports = getLang; 112 | -------------------------------------------------------------------------------- /get-syntax.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const path = require("path"); 3 | const reSyntaxCSS = /^(?:post)?css$/i; 4 | 5 | function cssSyntax () { 6 | return { 7 | stringify: require("postcss/lib/stringify"), 8 | parse: require("postcss/lib/parse"), 9 | }; 10 | } 11 | 12 | function normalize (syntax) { 13 | if (!syntax.parse) { 14 | syntax = { 15 | parse: syntax, 16 | }; 17 | } 18 | return syntax; 19 | } 20 | 21 | function requireSyntax (syntax) { 22 | if (reSyntaxCSS.test(syntax)) { 23 | return cssSyntax(); 24 | } else if (/^sugarss$/i.test(syntax)) { 25 | syntax = "sugarss"; 26 | } else if (path.isAbsolute(syntax) || syntax[0] === ".") { 27 | syntax = path.resolve(syntax); 28 | } else { 29 | syntax = syntax.toLowerCase().replace(/^(?:postcss-)?(\w+)/i, "postcss-$1"); 30 | } 31 | return normalize(require(syntax)); 32 | } 33 | 34 | function getSyntax (lang, opts) { 35 | let syntax; 36 | lang = lang || "css"; 37 | if (opts.syntax.config[lang]) { 38 | syntax = opts.syntax.config[lang]; 39 | if (typeof syntax === "string") { 40 | if (syntax !== lang && opts.syntax.config[syntax]) { 41 | return getSyntax(syntax, opts); 42 | } 43 | syntax = requireSyntax(syntax); 44 | } else { 45 | syntax = normalize(syntax); 46 | } 47 | } else if (reSyntaxCSS.test(lang)) { 48 | syntax = cssSyntax(); 49 | } else { 50 | return requireSyntax(lang); 51 | } 52 | if (!syntax.stringify) { 53 | if (reSyntaxCSS.test(lang)) { 54 | syntax.stringify = require("postcss/lib/stringify"); 55 | } else { 56 | syntax.stringify = getSyntax(null, opts).stringify; 57 | } 58 | } 59 | opts.syntax.config[lang] = syntax; 60 | return syntax; 61 | } 62 | 63 | module.exports = getSyntax; 64 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const stringify = require("./stringify"); 3 | const parse = require("./parse"); 4 | 5 | const defaultConfig = { 6 | postcss: "css", 7 | stylus: "css", 8 | babel: "jsx", 9 | xml: "html", 10 | }; 11 | 12 | function initSyntax (syntax) { 13 | syntax.stringify = stringify.bind(syntax); 14 | syntax.parse = parse.bind(syntax); 15 | return syntax; 16 | } 17 | 18 | function syntax (config) { 19 | return initSyntax({ 20 | config: Object.assign({}, defaultConfig, config), 21 | }); 22 | } 23 | 24 | initSyntax(syntax); 25 | syntax.config = defaultConfig; 26 | module.exports = syntax; 27 | -------------------------------------------------------------------------------- /load-syntax.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const getSyntax = require("./get-syntax"); 3 | const cache = {}; 4 | 5 | function loadSyntax (opts, id) { 6 | const cssSyntax = getSyntax("css", opts); 7 | const modulePath = id + "/template-" + (cssSyntax.parse.name === "safeParse" ? "safe-" : "") + "parse"; 8 | let syntax = cache[modulePath]; 9 | if (!syntax) { 10 | syntax = { 11 | parse: require(modulePath), 12 | }; 13 | try { 14 | syntax.stringify = require(id + "/template-stringify"); 15 | } catch (ex) { 16 | syntax.stringify = cssSyntax.stringify; 17 | } 18 | cache[modulePath] = syntax; 19 | } 20 | return syntax; 21 | } 22 | 23 | module.exports = loadSyntax; 24 | -------------------------------------------------------------------------------- /normal-opts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function normalOpts (opts, syntax) { 4 | if (!opts) { 5 | opts = {}; 6 | } 7 | opts.syntax = syntax; 8 | return opts; 9 | } 10 | 11 | module.exports = normalOpts; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-syntax", 3 | "version": "0.36.2", 4 | "description": "Automatically switch PostCSS syntax by file extensions", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/gucong3000/postcss-syntax.git" 8 | }, 9 | "keywords": [ 10 | "postcss", 11 | "syntax", 12 | "switch", 13 | "extension" 14 | ], 15 | "author": "gucong", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/gucong3000/postcss-syntax/issues" 19 | }, 20 | "homepage": "https://github.com/gucong3000/postcss-syntax#readme", 21 | "nyc": { 22 | "reporter": [ 23 | "lcov", 24 | "text" 25 | ], 26 | "all": true, 27 | "cache": true, 28 | "check-coverage": true 29 | }, 30 | "scripts": { 31 | "mocha": "mocha --require ./test/fixtures/link --no-timeouts", 32 | "test": "nyc npm run mocha", 33 | "debug": "npm run mocha -- --inspect-brk" 34 | }, 35 | "extensions": [ 36 | ".css", 37 | ".pcss", 38 | ".postcss", 39 | ".acss", 40 | ".wxss", 41 | ".sass", 42 | ".scss", 43 | ".less", 44 | ".sss", 45 | ".js", 46 | ".es6", 47 | ".mjs", 48 | ".cjs", 49 | ".pac", 50 | ".jsx", 51 | ".ts", 52 | ".tsx", 53 | ".babel", 54 | ".flow", 55 | ".html", 56 | ".htm", 57 | ".shtml", 58 | ".xhtml", 59 | ".mdoc", 60 | ".jsp", 61 | ".asp", 62 | ".aspx", 63 | ".jshtm", 64 | ".volt", 65 | ".ejs", 66 | ".rhtml", 67 | ".xsl", 68 | ".xslt", 69 | ".vue", 70 | ".wpy", 71 | ".ux", 72 | ".php", 73 | ".php4", 74 | ".php5", 75 | ".phtml", 76 | ".ctp", 77 | ".twig", 78 | ".liquid", 79 | ".md", 80 | ".mdown", 81 | ".markdown", 82 | ".markdn", 83 | ".svelte" 84 | ], 85 | "peerDependencies": { 86 | "postcss": ">=5.0.0" 87 | }, 88 | "devDependencies": { 89 | "chai": "^4.2.0", 90 | "codecov": "^3.1.0", 91 | "mocha": "^5.2.0", 92 | "nyc": "^13.1.0", 93 | "postcss": "^7.0.7", 94 | "postcss-html": ">=0.36.0", 95 | "postcss-jsx": ">=0.36.0", 96 | "postcss-less": "^3.1.0", 97 | "postcss-markdown": ">=0.36.0", 98 | "postcss-safe-parser": "^4.0.1", 99 | "postcss-scss": "^2.0.0", 100 | "proxyquire": "^2.1.0", 101 | "sugarss": "^2.0.0" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /parse-style.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const reNewLine = /(?:\r?\n|\r)/gm; 3 | const Input = require("postcss/lib/input"); 4 | const Document = require("./document"); 5 | const getSyntax = require("./get-syntax"); 6 | const patch = require("./patch-postcss"); 7 | 8 | class LocalFixer { 9 | constructor (lines, style) { 10 | let line = 0; 11 | let column = style.startIndex; 12 | lines.some((lineEndIndex, lineNumber) => { 13 | if (lineEndIndex >= style.startIndex) { 14 | line = lineNumber--; 15 | if (lineNumber in lines) { 16 | column = style.startIndex - lines[lineNumber] - 1; 17 | } 18 | return true; 19 | } 20 | }); 21 | 22 | this.line = line; 23 | this.column = column; 24 | this.style = style; 25 | } 26 | object (object) { 27 | if (object) { 28 | if (object.line === 1) { 29 | object.column += this.column; 30 | } 31 | object.line += this.line; 32 | } 33 | } 34 | node (node) { 35 | this.object(node.source.start); 36 | this.object(node.source.end); 37 | } 38 | root (root) { 39 | this.node(root); 40 | root.walk(node => { 41 | this.node(node); 42 | }); 43 | } 44 | error (error) { 45 | if (error && error.name === "CssSyntaxError") { 46 | this.object(error); 47 | this.object(error.input); 48 | error.message = error.message.replace(/:\d+:\d+:/, ":" + error.line + ":" + error.column + ":"); 49 | } 50 | return error; 51 | } 52 | parse (opts) { 53 | const style = this.style; 54 | const syntax = style.syntax || getSyntax(style.lang, opts); 55 | let root = style.root; 56 | try { 57 | root = syntax.parse(style.content, Object.assign({}, opts, { 58 | map: false, 59 | }, style.opts)); 60 | } catch (error) { 61 | if (style.ignoreErrors) { 62 | return; 63 | } else if (!style.skipConvert) { 64 | this.error(error); 65 | } 66 | throw error; 67 | } 68 | if (!style.skipConvert) { 69 | this.root(root); 70 | } 71 | 72 | root.source.inline = Boolean(style.inline); 73 | root.source.lang = style.lang; 74 | root.source.syntax = syntax; 75 | return root; 76 | } 77 | } 78 | 79 | function docFixer (source, opts) { 80 | let match; 81 | const lines = []; 82 | reNewLine.lastIndex = 0; 83 | while ((match = reNewLine.exec(source))) { 84 | lines.push(match.index); 85 | } 86 | lines.push(source.length); 87 | return function parseStyle (style) { 88 | return new LocalFixer(lines, style).parse(opts); 89 | }; 90 | } 91 | 92 | function parseStyle (source, opts, styles) { 93 | patch(Document); 94 | 95 | const document = new Document(); 96 | 97 | let index = 0; 98 | if (styles.length) { 99 | const parseStyle = docFixer(source, opts); 100 | styles.sort((a, b) => ( 101 | a.startIndex - b.startIndex 102 | )).forEach(style => { 103 | const root = parseStyle(style); 104 | if (root) { 105 | root.raws.beforeStart = source.slice(index, style.startIndex); 106 | if (style.endIndex) { 107 | index = style.endIndex; 108 | } else { 109 | index = style.startIndex + (style.content || root.source.input.css).length; 110 | } 111 | root.document = document; 112 | document.nodes.push(root); 113 | } 114 | }); 115 | } 116 | document.raws.afterEnd = index ? source.slice(index) : source; 117 | document.source = { 118 | input: new Input(source, opts), 119 | start: { 120 | line: 1, 121 | column: 1, 122 | }, 123 | opts, 124 | }; 125 | return document; 126 | } 127 | module.exports = parseStyle; 128 | -------------------------------------------------------------------------------- /parse.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const parser = require("./parser"); 4 | const processor = require("./processor"); 5 | const getLang = require("./get-lang"); 6 | const normalOpts = require("./normal-opts"); 7 | 8 | function getSyntax (opts, source) { 9 | const rules = opts.syntax && opts.syntax.config && opts.syntax.config.rules; 10 | const file = opts.from || ""; 11 | return (rules && rules.find( 12 | rule => rule.test.test ? rule.test.test(file) : rule.test(file, source) 13 | )) || getLang(file, source) || { 14 | lang: "css", 15 | }; 16 | } 17 | 18 | function parse (source, opts) { 19 | source = source.toString(); 20 | opts = normalOpts(opts, this); 21 | const syntax = getSyntax(opts, source); 22 | const syntaxOpts = Object.assign({}, opts, syntax.opts); 23 | let root; 24 | if (syntax.extract) { 25 | root = processor(source, syntax.extract, syntaxOpts); 26 | root.source.lang = syntax.extract; 27 | } else { 28 | root = parser(source, syntax.lang, syntaxOpts); 29 | root.source.lang = syntax.lang; 30 | } 31 | return root; 32 | } 33 | 34 | module.exports = parse; 35 | -------------------------------------------------------------------------------- /parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const getSyntax = require("./get-syntax"); 4 | const patch = require("./patch-postcss"); 5 | 6 | function parser (source, lang, opts) { 7 | patch(); 8 | 9 | const syntax = getSyntax(lang, opts); 10 | const root = syntax.parse(source, opts); 11 | 12 | root.source.syntax = syntax; 13 | root.source.lang = lang; 14 | 15 | return root; 16 | } 17 | 18 | module.exports = parser; 19 | -------------------------------------------------------------------------------- /patch-postcss.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const path = require("path"); 3 | const patched = {}; 4 | 5 | function isPromise (obj) { 6 | return typeof obj === "object" && typeof obj.then === "function"; 7 | } 8 | 9 | function runDocument (plugin) { 10 | const result = this.result; 11 | result.lastPlugin = plugin; 12 | const promise = result.root.nodes.map(root => { 13 | try { 14 | return plugin(root, result); 15 | } catch (error) { 16 | this.handleError(error, plugin); 17 | throw error; 18 | } 19 | }); 20 | if (promise.some(isPromise)) { 21 | return Promise.all(promise); 22 | } 23 | } 24 | 25 | function patchDocument (Document, LazyResult) { 26 | LazyResult = LazyResult.prototype; 27 | const runRoot = LazyResult.run; 28 | 29 | LazyResult.run = function run () { 30 | return (this.result.root instanceof Document ? runDocument : runRoot).apply(this, arguments); 31 | }; 32 | } 33 | 34 | function patchNode (Node) { 35 | Node = Node.prototype; 36 | const NodeToString = Node.toString; 37 | Node.toString = function toString (stringifier) { 38 | return NodeToString.call(this, stringifier || this.root().source.syntax); 39 | }; 40 | } 41 | 42 | function patch (Document) { 43 | let fn; 44 | let file; 45 | if (Document) { 46 | patch(); 47 | fn = patchDocument.bind(this, Document); 48 | file = "lazy-result"; 49 | } else { 50 | fn = patchNode; 51 | file = "node"; 52 | } 53 | findPostcss().map(dir => ( 54 | [dir + "lib", file].join(path.sep) 55 | )).filter(file => ( 56 | !patched[file] 57 | )).forEach(file => { 58 | try { 59 | fn(require(file)); 60 | } catch (ex) { 61 | // 62 | } 63 | patched[file] = true; 64 | }); 65 | } 66 | 67 | function findPostcss () { 68 | const result = {}; 69 | for (const file in require.cache) { 70 | if (/^(.+?(\\|\/))postcss(\2)/.test(file)) { 71 | result[RegExp.lastMatch] = true; 72 | } 73 | } 74 | return Object.keys(result); 75 | } 76 | 77 | module.exports = patch; 78 | -------------------------------------------------------------------------------- /processor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const parseStyle = require("./parse-style"); 4 | 5 | function getSyntax (config, syntax) { 6 | if (typeof syntax !== "string") { 7 | return syntax; 8 | } 9 | let syntaxConfig = config[syntax]; 10 | 11 | if (syntaxConfig) { 12 | syntaxConfig = getSyntax(config, syntaxConfig); 13 | } else { 14 | syntaxConfig = { 15 | extract: require(syntax.toLowerCase().replace(/^(postcss-)?/i, "postcss-") + "/extract"), 16 | }; 17 | config[syntax] = syntaxConfig; 18 | } 19 | 20 | return syntaxConfig; 21 | } 22 | 23 | function processor (source, lang, opts) { 24 | const syntax = getSyntax(opts.syntax.config, lang); 25 | const styles = (syntax.extract || syntax)(source, opts) || []; 26 | return parseStyle(source, opts, styles); 27 | } 28 | 29 | module.exports = processor; 30 | -------------------------------------------------------------------------------- /stringify.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function stringify (document) { 4 | let stringify; 5 | if (document instanceof require("./document")) { 6 | stringify = docStringify; 7 | } else { 8 | stringify = document.source.syntax.stringify; 9 | } 10 | return stringify.apply(this, arguments); 11 | } 12 | 13 | function docStringify (document, builder) { 14 | document.nodes.forEach((root, i) => { 15 | builder(root.raws.beforeStart, root, "beforeStart"); 16 | root.source.syntax && root.source.syntax.stringify(root, builder); 17 | }); 18 | builder(document.raws.afterEnd, document, "afterEnd"); 19 | } 20 | module.exports = stringify; 21 | -------------------------------------------------------------------------------- /syntax.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const stringify = require("./stringify"); 3 | const parseStyle = require("./parse-style"); 4 | const normalOpts = require("./normal-opts"); 5 | 6 | module.exports = (extract, lang) => { 7 | const defaultConfig = { 8 | postcss: "css", 9 | stylus: "css", 10 | babel: "jsx", 11 | xml: "html", 12 | }; 13 | function parse (source, opts) { 14 | source = source.toString(); 15 | opts = normalOpts(opts, this); 16 | const document = parseStyle(source, opts, extract(source, opts)); 17 | document.source.lang = lang; 18 | return document; 19 | } 20 | 21 | function initSyntax (syntax) { 22 | syntax.stringify = stringify.bind(syntax); 23 | syntax.parse = parse.bind(syntax); 24 | syntax.extract = extract.bind(syntax); 25 | return syntax; 26 | } 27 | 28 | function syntax (config) { 29 | return initSyntax({ 30 | config: Object.assign({}, defaultConfig, config), 31 | }); 32 | } 33 | 34 | initSyntax(syntax); 35 | syntax.config = defaultConfig; 36 | return syntax; 37 | }; 38 | -------------------------------------------------------------------------------- /test/custom.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const expect = require("chai").expect; 3 | const Module = require("module"); 4 | let _findPath; 5 | let syntax; 6 | 7 | describe("custom language", () => { 8 | before(() => { 9 | _findPath = Module._findPath; 10 | 11 | Module._findPath = (request, paths, isMain) => { 12 | if (request === "postcss-jsx") { 13 | return null; 14 | } 15 | return _findPath.apply(Module, [request, paths, isMain]); 16 | }; 17 | 18 | delete require.cache[require.resolve("../")]; 19 | syntax = require("../"); 20 | }); 21 | 22 | after(() => { 23 | Module._findPath = _findPath; 24 | }); 25 | 26 | it("custom.postcss", () => { 27 | const code = "a { display: block; }"; 28 | const document = syntax({ 29 | rules: [ 30 | { 31 | // custom language for file extension 32 | test: () => true, 33 | lang: "postcss", 34 | }, 35 | ], 36 | postcss: "css", 37 | }).parse(code, { 38 | from: "custom.postcss", 39 | }); 40 | expect(document.source).to.haveOwnProperty("lang", "postcss"); 41 | expect(document.toString()).to.equal(code); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/document.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const postcss = require("postcss"); 5 | const syntax = require("../syntax")(require("postcss-html/extract")); 6 | 7 | describe("document tests", () => { 8 | it("stringify for append node", () => { 9 | const html = [ 10 | "", 11 | "", 16 | "", 17 | ].join("\n"); 18 | const document = syntax.parse(html, { 19 | from: "append.html", 20 | }); 21 | document.append({ 22 | selector: "b", 23 | }); 24 | expect(document.toString()).to.equal([ 25 | "", 26 | "", 32 | "", 33 | ].join("\n")); 34 | }); 35 | 36 | it("stringify for prepend node", () => { 37 | const html = [ 38 | "", 39 | "", 44 | "", 45 | ].join("\n"); 46 | 47 | const document = syntax.parse(html, { 48 | from: "prepend.html", 49 | }); 50 | document.prepend({ 51 | selector: "b", 52 | }); 53 | expect(document.toString()).to.equal([ 54 | "", 55 | "", 61 | "", 62 | ].join("\n")); 63 | }); 64 | 65 | it("stringify for insertBefore node", () => { 66 | const html = [ 67 | "", 68 | "", 73 | "", 78 | "", 79 | ].join("\n"); 80 | const document = syntax.parse(html, { 81 | from: "insertBefore.html", 82 | }); 83 | document.insertBefore(document.last, { 84 | selector: "b", 85 | }); 86 | expect(document.toString()).to.equal([ 87 | "", 88 | "", 93 | "", 99 | "", 100 | ].join("\n")); 101 | }); 102 | 103 | it("stringify for insertAfter node", () => { 104 | const html = [ 105 | "", 106 | "", 111 | "", 116 | "", 117 | ].join("\n"); 118 | const document = syntax.parse(html, { 119 | from: "insertAfter.html", 120 | }); 121 | document.insertAfter(document.first, { 122 | selector: "b", 123 | }); 124 | expect(document.toString()).to.equal([ 125 | "", 126 | "", 132 | "", 137 | "", 138 | ].join("\n")); 139 | }); 140 | 141 | it("each()", () => { 142 | const html = [ 143 | "", 144 | "", 152 | "", 160 | "", 161 | ].join("\n"); 162 | const document = syntax.parse(html, { 163 | from: "insertAfter.html", 164 | }); 165 | let count = 0; 166 | const result = document.each(() => { 167 | count++; 168 | return false; 169 | }); 170 | expect(result).to.be.equal(false); 171 | expect(count).to.be.equal(2); 172 | }); 173 | 174 | it("async plugin", () => { 175 | const html = [ 176 | "", 177 | "", 182 | "", 187 | "", 188 | ].join("\n"); 189 | return postcss([ 190 | (root, result) => { 191 | result.warn("test", { 192 | node: root, 193 | }); 194 | return Promise.resolve().then(() => { 195 | root.nodes = []; 196 | }); 197 | }, 198 | ]).process(html, { 199 | syntax: syntax, 200 | from: "async_plugin.html", 201 | }).then(result => { 202 | expect(result.messages).to.have.lengthOf(2); 203 | result.messages.forEach((msg, i) => { 204 | expect(msg.text).to.equal("test"); 205 | expect(msg.node).to.equal(result.root.nodes[i]); 206 | }); 207 | expect(result.content).to.equal([ 208 | "", 209 | "", 212 | "", 215 | "", 216 | ].join("\n")); 217 | }); 218 | }); 219 | 220 | it("plugin error", () => { 221 | const html = [ 222 | "", 223 | "", 228 | "", 229 | ].join("\n"); 230 | const result = postcss([ 231 | root => { 232 | throw new Error("mock plugin error"); 233 | }, 234 | ]).process(html, { 235 | syntax: syntax, 236 | from: "plugin_error.html", 237 | }); 238 | 239 | expect(() => { 240 | result.sync(); 241 | }).to.throw(/^mock plugin error$/); 242 | }); 243 | 244 | it("empty nodes", () => { 245 | const html = [ 246 | "", 247 | "", 252 | "", 253 | ].join("\n"); 254 | const result = postcss([ 255 | root => { 256 | root.nodes = []; 257 | }, 258 | ]).process(html, { 259 | syntax: syntax, 260 | from: "empty_nodes.html", 261 | }); 262 | 263 | expect(result.content).to.equal([ 264 | "", 265 | "", 268 | "", 269 | ].join("\n")); 270 | }); 271 | }); 272 | -------------------------------------------------------------------------------- /test/fixtures/glamorous.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import glm from 'glamorous'; 3 | 4 | const minWidth = 700; 5 | const a = 1 6 | const Component1 = glm.a( 7 | /* start */ 8 | { 9 | // stylelint-disable-next-line 10 | "unknownProperty": '1.8em', // must not trigger any warnings 11 | unknownProperty: '1.8em', // must not trigger any warnings 12 | [`unknownPropertyaa${a}`]: '1.8em', // must not trigger any warnings 13 | [`unknownProperty` + 1 + "a"]: '1.8em', // must not trigger any warnings 14 | display: 'inline-block', 15 | [`@media (minWidth: ${minWidth}px)`]: { 16 | color: 'red', 17 | }, 18 | // unkown pseudo class selector 19 | ':focused': { 20 | backgroundColor: 'red', 21 | }, 22 | "@fontFace": { 23 | "fontFamily": 'diyfont', 24 | }, 25 | "@page:first": { 26 | margin: "300px" 27 | }, 28 | "@charset": "utf-8" 29 | }, 30 | // end 31 | ({ primary }) => ({ 32 | unknownProperty: '1.8em', // unknown prop 33 | ...minWidth.length, 34 | color: primary ? '#fff' : '#DA233C', 35 | }), 36 | ); 37 | 38 | const Component2 = glm(Component1, { 39 | displayName: 'Component2', 40 | forwardProps: ['shouldRender'], 41 | rootEl: 'div', 42 | })(props => ({ 43 | fontFamily: 'Arial, Arial, sans-serif', // duplicate font-family names 44 | fontSize: props.big ? 36 : 24, 45 | })); 46 | 47 | const Component3 = glm.div({ 48 | padding: '8px 12px', 49 | ...Component2 50 | }); 51 | 52 | export default () => ( 53 |
54 | 55 | 56 | 57 |
58 | ); 59 | -------------------------------------------------------------------------------- /test/fixtures/link.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const path = require("path"); 3 | const pkg = require("../../package.json"); 4 | const cwd = process.cwd(); 5 | const Module = require("module"); 6 | const _findPath = Module._findPath; 7 | Module._findPath = (request, paths, isMain) => { 8 | if (request.startsWith(pkg.name)) { 9 | request = path.join(cwd, request.slice(pkg.name.length)); 10 | } 11 | return _findPath.apply(Module, [request, paths, isMain]); 12 | }; 13 | -------------------------------------------------------------------------------- /test/glamorous.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const expect = require("chai").expect; 3 | const syntax = require("../syntax")(require("postcss-jsx/extract")); 4 | const fs = require("fs"); 5 | 6 | describe("javascript tests", () => { 7 | it("glamorous", () => { 8 | const filename = require.resolve("./fixtures/glamorous.jsx"); 9 | let code = fs.readFileSync(filename); 10 | 11 | const document = syntax.parse(code, { 12 | from: filename, 13 | }); 14 | 15 | code = code.toString(); 16 | 17 | expect(document.toString(syntax)).to.equal(code); 18 | document.nodes.forEach(root => { 19 | expect(root.source).to.haveOwnProperty("input"); 20 | 21 | expect(code).to.includes(root.source.input.css); 22 | expect(root.source.input.css.length).lessThan(code.length); 23 | expect(root.source).to.haveOwnProperty("start").to.haveOwnProperty("line").to.greaterThan(1); 24 | 25 | root.walk(node => { 26 | expect(node).to.haveOwnProperty("source"); 27 | 28 | expect(node.source).to.haveOwnProperty("input").to.haveOwnProperty("css").equal(root.source.input.css); 29 | 30 | expect(node.source).to.haveOwnProperty("start").to.haveOwnProperty("line"); 31 | expect(node.source).to.haveOwnProperty("end").to.haveOwnProperty("line"); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/html.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const postcss = require("postcss"); 5 | const syntax = require("../syntax")(require("postcss-html/extract")); 6 | 7 | describe("html tests", () => { 8 | it("Invalid HTML", () => { 9 | return postcss().process("<", { 10 | syntax: syntax, 11 | from: "invalid.html", 12 | }).then(result => { 13 | expect(result.content).to.equal("<"); 14 | }); 15 | }); 16 | 17 | it("less", () => { 18 | const html = [ 19 | "", 20 | "", 21 | "", 26 | "", 27 | "", 28 | "
", 29 | "
", 30 | "", 31 | "", 32 | ].join("\n"); 33 | const root = syntax.parse(html, { 34 | from: "less.html", 35 | }); 36 | expect(root.nodes).to.have.lengthOf(2); 37 | expect(root.toString()).equal(html); 38 | }); 39 | 40 | it("stringify for append node", () => { 41 | const html = [ 42 | "", 43 | "", 48 | "", 49 | ].join("\n"); 50 | return postcss([ 51 | function (root) { 52 | root.append({ 53 | selector: "b", 54 | }); 55 | }, 56 | ]).process(html, { 57 | syntax: syntax, 58 | from: "append.html", 59 | }).then(result => { 60 | expect(result.content).to.equal([ 61 | "", 62 | "", 68 | "", 69 | ].join("\n")); 70 | }); 71 | }); 72 | 73 | it("stringify for prepend node", () => { 74 | const html = [ 75 | "", 76 | "", 81 | "", 82 | ].join("\n"); 83 | return postcss([ 84 | function (root) { 85 | root.prepend({ 86 | selector: "b", 87 | }); 88 | }, 89 | ]).process(html, { 90 | syntax: syntax, 91 | from: "prepend.html", 92 | }).then(result => { 93 | expect(result.content).to.equal([ 94 | "", 95 | "", 101 | "", 102 | ].join("\n")); 103 | }); 104 | }); 105 | 106 | it("stringify for insertBefore node", () => { 107 | const html = [ 108 | "", 109 | "", 114 | "", 119 | "", 120 | ].join("\n"); 121 | return postcss([ 122 | function (root) { 123 | root.insertBefore(root.last, { 124 | selector: "b", 125 | }); 126 | }, 127 | ]).process(html, { 128 | syntax: syntax, 129 | from: "insertBefore.html", 130 | }).then(result => { 131 | expect(result.content).to.equal([ 132 | "", 133 | "", 139 | "", 145 | "", 146 | ].join("\n")); 147 | }); 148 | }); 149 | 150 | it("stringify for insertAfter node", () => { 151 | const html = [ 152 | "", 153 | "", 158 | "", 163 | "", 164 | ].join("\n"); 165 | return postcss([ 166 | function (root) { 167 | root.insertAfter(root.first, { 168 | selector: "b", 169 | }); 170 | }, 171 | ]).process(html, { 172 | syntax: syntax, 173 | from: "insertAfter.html", 174 | }).then(result => { 175 | expect(result.content).to.equal([ 176 | "", 177 | "", 183 | "", 189 | "", 190 | ].join("\n")); 191 | }); 192 | }); 193 | 194 | it("stringify for unshift node", () => { 195 | const html = [ 196 | "", 197 | "", 202 | "", 203 | ].join("\n"); 204 | return postcss([ 205 | function (root) { 206 | root.nodes.unshift(postcss.parse("b {}")); 207 | }, 208 | ]).process(html, { 209 | syntax: syntax, 210 | from: "unshift.html", 211 | }).then(result => { 212 | expect(result.content).to.equal([ 213 | "", 214 | "", 219 | "", 220 | ].join("\n")); 221 | }); 222 | }); 223 | 224 | it("stringify for push node", () => { 225 | const html = [ 226 | "", 227 | "", 232 | "", 233 | ].join("\n"); 234 | 235 | return postcss([ 236 | root => { 237 | root.nodes.push(postcss.parse("b {}")); 238 | }, 239 | ]).process(html, { 240 | syntax: syntax, 241 | from: "push.html", 242 | }).then(result => { 243 | expect(result.content).to.equal([ 244 | "", 245 | "", 250 | "", 251 | ].join("\n")); 252 | }); 253 | }); 254 | 255 | it("stringify for nodes array", () => { 256 | const html = [ 257 | "", 258 | "", 260 | "", 261 | ].join("\n"); 262 | return postcss([ 263 | root => { 264 | root.nodes = [postcss.parse("b {}")]; 265 | }, 266 | ]).process(html, { 267 | syntax: syntax, 268 | from: "push.html", 269 | }).then(result => { 270 | expect(result.content).to.equal([ 271 | "", 272 | "", 274 | "", 275 | ].join("\n")); 276 | }); 277 | }); 278 | 279 | it("", 282 | ].join("\n"); 283 | return postcss([ 284 | ]).process(html, { 285 | syntax: syntax, 286 | from: "push.html", 287 | }).then(result => { 288 | expect(result.root.nodes).to.be.lengthOf(1); 289 | expect(result.root.first.source.start.line).to.equal(2); 290 | expect(result.root.first.source.start.column).to.equal(8); 291 | }); 292 | }); 293 | 294 | it("react inline styles", () => { 295 | const html = ` 296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 | `; 305 | return postcss([ 306 | ]).process(html, { 307 | syntax: syntax, 308 | from: "react_inline_styles.html", 309 | }).then(result => { 310 | expect(result.root.nodes).to.be.lengthOf(0); 311 | }); 312 | }); 313 | }); 314 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const syntax = require("../"); 5 | describe("api", () => { 6 | it("default", () => { 7 | const root = syntax.parse("a{b:c}"); 8 | expect(root.nodes).to.have.lengthOf(1); 9 | expect(root.first).to.have.property("type", "rule"); 10 | expect(root.first).to.have.property("selector", "a"); 11 | expect(root.first.nodes).to.have.lengthOf(1); 12 | expect(root.first.first).to.have.property("type", "decl"); 13 | expect(root.first.first).to.have.property("prop", "b"); 14 | expect(root.first.first).to.have.property("value", "c"); 15 | }); 16 | 17 | it("parse error", () => { 18 | const syntax = require("../syntax")(require("postcss-html/extract"))({ 19 | css: { 20 | parse: () => { 21 | throw new Error("mock parse error"); 22 | }, 23 | }, 24 | }); 25 | expect(() => { 26 | syntax.parse(""); 27 | }).to.throw(/^mock parse error$/); 28 | }); 29 | 30 | it("loader return null", () => { 31 | const root = syntax({ 32 | rules: [ 33 | { 34 | test: /.*/, 35 | extract: "html", 36 | }, 37 | ], 38 | }).parse( 39 | "\n".repeat(3) 40 | ); 41 | expect(root.nodes).to.have.lengthOf(3); 42 | }); 43 | 44 | it("loader opts", () => { 45 | let result; 46 | syntax({ 47 | rules: [ 48 | { 49 | test: /.*/, 50 | opts: { 51 | loaderOpts: "mock", 52 | }, 53 | extract: (source, opts) => { 54 | result = opts; 55 | }, 56 | }, 57 | ], 58 | }).parse( 59 | "\n".repeat(3), 60 | { 61 | from: "mock.html", 62 | } 63 | ); 64 | expect(result).to.have.property("from", "mock.html"); 65 | expect(result).to.have.property("loaderOpts", "mock"); 66 | }); 67 | 68 | describe("standalone with syntax set by extension", () => { 69 | const opts = { 70 | rules: [], 71 | }; 72 | [ 73 | "css", 74 | "less", 75 | "sass", 76 | "scss", 77 | "sugarss", 78 | "stylus", 79 | ].forEach(lang => { 80 | opts[lang] = { 81 | parse: () => ({ 82 | source: {}, 83 | lang, 84 | }), 85 | }; 86 | }); 87 | const mockSyntax = syntax(opts); 88 | function lang (extension) { 89 | return mockSyntax.parse("", { 90 | from: "*." + extension, 91 | }).lang; 92 | } 93 | 94 | it("css", () => { 95 | expect(lang("css")).to.be.equal("css"); 96 | }); 97 | it("pcss", () => { 98 | expect(lang("pcss")).to.be.equal("css"); 99 | }); 100 | it("postcss", () => { 101 | expect(lang("postcss")).to.be.equal("css"); 102 | }); 103 | it("acss", () => { 104 | expect(lang("acss")).to.be.equal("css"); 105 | }); 106 | it("wxss", () => { 107 | expect(lang("wxss")).to.be.equal("css"); 108 | }); 109 | it("sass", () => { 110 | expect(lang("sass")).to.be.equal("sass"); 111 | }); 112 | it("scss", () => { 113 | expect(lang("scss")).to.be.equal("scss"); 114 | }); 115 | it("less", () => { 116 | expect(lang("less")).to.be.equal("less"); 117 | }); 118 | it("sugarss", () => { 119 | expect(lang("sss")).to.be.equal("sugarss"); 120 | }); 121 | it("stylus", () => { 122 | expect(lang("styl")).to.be.equal("stylus"); 123 | }); 124 | }); 125 | 126 | describe("standalone with syntax set by rules", () => { 127 | const opts = { 128 | rules: [ 129 | { 130 | test: /.*/, 131 | lang: "mockLang", 132 | }, 133 | ], 134 | }; 135 | opts.mockLang = { 136 | parse: () => ({ 137 | source: {}, 138 | lang: "mock lang", 139 | }), 140 | }; 141 | const mockSyntax = syntax(opts); 142 | function lang (extension) { 143 | return mockSyntax.parse("", { 144 | from: "*." + extension, 145 | }).lang; 146 | } 147 | 148 | [ 149 | "css", 150 | "pcss", 151 | "postcss", 152 | "acss", 153 | "wxss", 154 | "sass", 155 | "scss", 156 | "less", 157 | "sugarss", 158 | "stylus", 159 | ].forEach(extension => { 160 | it(extension, () => { 161 | expect(lang(extension)).to.be.equal("mock lang"); 162 | }); 163 | }); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /test/languages.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const expect = require("chai").expect; 3 | const syntax = require("../"); 4 | 5 | // https://github.com/Microsoft/vscode/blob/master/extensions/css/package.json 6 | // https://github.com/Microsoft/vscode/blob/master/extensions/less/package.json 7 | 8 | function testcase (lang, extensions, source) { 9 | describe(lang, () => { 10 | const code = ""; 11 | extensions.forEach(ext => { 12 | const file = ext.replace(/^\.*/, "empty."); 13 | it(file, () => { 14 | const document = syntax.parse(code, { 15 | from: file, 16 | }); 17 | expect(document.source).to.haveOwnProperty("lang", lang); 18 | expect(document.toString()).to.equal(code); 19 | }); 20 | }); 21 | if (!source) { 22 | return; 23 | } 24 | source.forEach(code => { 25 | it(JSON.stringify(code), () => { 26 | const document = syntax.parse(code, { 27 | from: undefined, 28 | }); 29 | expect(document.source).to.haveOwnProperty("lang", lang); 30 | expect(document.toString()).to.equal(code); 31 | }); 32 | }); 33 | }); 34 | } 35 | 36 | describe("language tests", () => { 37 | testcase("markdown", [ 38 | // https://github.com/Microsoft/vscode/blob/master/extensions/markdown-basics/package.json 39 | ".md", 40 | ".mdown", 41 | ".markdown", 42 | ".markdn", 43 | // https://github.com/jshttp/mime-db/blob/master/db.json 44 | // text/x-markdown 45 | ".mkd", 46 | ], [ 47 | "# asdadsad", 48 | "# asdadsad\n", 49 | "asdadsad\n====", 50 | "asdadsad\n====\n", 51 | ]); 52 | 53 | testcase("html", [ 54 | // https://github.com/Microsoft/vscode/blob/master/extensions/html/package.json 55 | ".html", 56 | ".htm", 57 | ".shtml", 58 | ".xhtml", 59 | ".mdoc", 60 | ".jsp", 61 | ".asp", 62 | ".aspx", 63 | ".jshtm", 64 | ".volt", 65 | ".ejs", 66 | ".rhtml", 67 | // https://github.com/jshttp/mime-db/blob/master/db.json 68 | // application/xhtml+xml 69 | ".xht", 70 | // https://github.com/Microsoft/vscode/blob/master/extensions/xml/package.json 71 | ".xsl", 72 | ".xslt", 73 | // https://vue-loader.vuejs.org/spec.html 74 | // https://github.com/vuejs/vetur/blob/master/package.json 75 | ".vue", 76 | // https://doc.quickapp.cn/framework/source-file.html 77 | ".ux", 78 | // https://github.com/Tencent/wepy/blob/master/docs/md/doc.md#wpy文件说明 79 | ".wpy", 80 | // https://github.com/mblode/vscode-twig-language/blob/master/package.json 81 | ".twig", 82 | // https://github.com/GingerBear/vscode-liquid/blob/master/package.json 83 | ".liquid", 84 | // https://github.com/UnwrittenFun/svelte-vscode/blob/master/package.json 85 | ".svelte", 86 | // https://github.com/Microsoft/vscode/blob/master/extensions/php/package.json 87 | ".php", 88 | ".php4", 89 | ".php5", 90 | ".phtml", 91 | ".ctp", 92 | ], [ 93 | "", 94 | "", 95 | "", 96 | "", 97 | "", 98 | "", 99 | "", 100 | "
", 101 | "\n", 102 | "\n\n", 103 | "\n", 104 | "\n", 105 | "", 106 | "", 107 | "\n", 110 | ]); 111 | 112 | testcase("jsx", [ 113 | // https://github.com/Microsoft/vscode/blob/master/extensions/javascript/package.json 114 | // javascript 115 | ".js", 116 | ".es6", 117 | ".mjs", 118 | ".cjs", 119 | ".pac", 120 | // javascriptreact 121 | ".jsx", 122 | // https://github.com/Microsoft/vscode/blob/master/extensions/typescript-basics/package.json 123 | // typescript 124 | ".ts", 125 | // typescriptreact 126 | ".tsx", 127 | // https://github.com/michaelgmcd/vscode-language-babel/blob/master/package.json 128 | ".babel", 129 | ".flow", 130 | ], [ 131 | "#!/usr/bin/env node", 132 | "#!~/.nvm/versions/node/v8.9.1/bin/node", 133 | "#!/c/Program Files/nodejs/node.exe", 134 | 135 | "\"use strict\";", 136 | "'use strict';", 137 | "\"use strict\"", 138 | "'use strict'", 139 | "\"use strict\";\nalert(0)", 140 | 141 | "// @flow\n'use strict';", 142 | "/* @flow */\n'use strict';", 143 | "// @flow\n/* @flow */'use strict';", 144 | 145 | "// @flow\nrequire('flow');", 146 | "/* @flow */\nrequire('flow');", 147 | "// @flow\n/* @flow */require('flow');", 148 | 149 | "// @flow\nimport('flow');", 150 | "/* @flow */\nimport('flow');", 151 | "// @flow\n/* @flow */import('flow');", 152 | 153 | "import React from 'react'", 154 | "import defaultExport from \"module-name\";", 155 | "import * as name from \"module-name\";", 156 | "import { export } from \"module-name\";", 157 | "import { export as alias } from \"module-name\";", 158 | "import { export1 , export2 } from \"module-name\";", 159 | "import { export1 , export2 as alias2 , [...] } from \"module-name\";", 160 | "import defaultExport, { export [ , [...] ] } from \"module-name\";", 161 | "import defaultExport, * as name from \"module-name\";", 162 | "import \"module-name\";", 163 | "import styled from 'styled-components';export default styled.div`padding-left: 10px; padding: 20px`;", 164 | 165 | "const styled=require('styled-components');module.exports=styled.div`padding-left: 10px; padding: 20px`;", 166 | "require('a');", 167 | "var a = require('a');", 168 | "var a=require('a');", 169 | "import(\"a\");", 170 | "const a = import('a');", 171 | // https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export 172 | // https://github.com/tc39/proposal-export-default-from 173 | // https://www.npmjs.com/package/babel-plugin-transform-export-extensions 174 | "export var v;", 175 | "export default function f(){}", 176 | "export default function(){}", 177 | "export default class {}", 178 | "export default 42;", 179 | "export defaultExport from \"module-name\";", 180 | "export { named };", 181 | "export { named as alias };", 182 | "export { named } from \"module-name\";", 183 | "export { named as alias } from \"module-name\";", 184 | "export { named1, namedExport2 } from \"module-name\";", 185 | "export { named1, named2 as alias2, [...] } from \"module-name\";", 186 | "export * from \"module-name\"", 187 | "export * as defaultExport from \"module-name\"", 188 | "export defaultExport, { named1, named2 as alias2 } from \"module-name\";", 189 | ]); 190 | 191 | testcase("css", [ 192 | // WXSS(WeiXin Style Sheets) See: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html 193 | // acss(AntFinancial Style Sheet) See: https://docs.alipay.com/mini/framework/acss 194 | // `*.pcss`, `*.postcss` 195 | ".css", 196 | ".pcss", 197 | ".postcss", 198 | ".acss", 199 | ".wxss", 200 | ".Unknown", 201 | ], [ 202 | "", 203 | "a{b:c}", 204 | ]); 205 | 206 | testcase("xml", [ 207 | // https://github.com/Microsoft/vscode/blob/master/extensions/xml/package.json 208 | ".xml", 209 | ".xsd", 210 | ".ascx", 211 | ".atom", 212 | ".axml", 213 | ".bpmn", 214 | ".config", 215 | ".cpt", 216 | ".csl", 217 | ".csproj", 218 | ".csproj.user", 219 | ".dita", 220 | ".ditamap", 221 | ".dtd", 222 | ".dtml", 223 | ".fsproj", 224 | ".fxml", 225 | ".iml", 226 | ".isml", 227 | ".jmx", 228 | ".launch", 229 | ".menu", 230 | ".mxml", 231 | ".nuspec", 232 | ".opml", 233 | ".owl", 234 | ".proj", 235 | ".props", 236 | ".pt", 237 | ".publishsettings", 238 | ".pubxml", 239 | ".pubxml.user", 240 | ".rdf", 241 | ".rng", 242 | ".rss", 243 | ".shproj", 244 | ".storyboard", 245 | ".svg", 246 | ".targets", 247 | ".tld", 248 | ".tmx", 249 | ".vbproj", 250 | ".vbproj.user", 251 | ".vcxproj", 252 | ".vcxproj.filters", 253 | ".wsdl", 254 | ".wxi", 255 | ".wxl", 256 | ".wxs", 257 | ".xaml", 258 | ".xbl", 259 | ".xib", 260 | ".xlf", 261 | ".xliff", 262 | ".xpdl", 263 | ".xul", 264 | ".xoml", 265 | ], [ 266 | "", 267 | "", 268 | ]); 269 | 270 | testcase("stylus", [ 271 | // https://github.com/d4rkr00t/language-stylus/blob/master/package.json 272 | ".styl", 273 | ".stylus", 274 | ]); 275 | }); 276 | -------------------------------------------------------------------------------- /test/load-syntax.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const postcss = require("postcss"); 5 | const syntax = require("../syntax")(require("postcss-html/extract")); 6 | 7 | describe("load-syntax", () => { 8 | it("template value in quickapp", () => { 9 | return postcss().process([ 10 | "", 14 | ].join("\n"), { 15 | syntax, 16 | from: "http://somehost.com/??quickapp.ax?v=102234", 17 | }).then(result => { 18 | expect(result.root.nodes).to.have.lengthOf(2); 19 | }); 20 | }); 21 | it("template value in quickapp with `postcss-safe-parser`", () => { 22 | return postcss().process([ 23 | "", 27 | ].join("\n"), { 28 | syntax: syntax({ 29 | css: require.resolve("postcss-safe-parser"), 30 | }), 31 | from: "quickapp.ax", 32 | }).then(result => { 33 | expect(result.root.nodes).to.have.lengthOf(2); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/markdown.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const syntax = require("../")({ 5 | rules: [ 6 | { 7 | test: /\.md$/, 8 | extract: require("postcss-markdown/extract"), 9 | }, 10 | { 11 | test: /\.html$/, 12 | extract: require("postcss-html/extract"), 13 | }, 14 | ], 15 | }); 16 | 17 | describe("markdown tests", () => { 18 | it("CSS", () => { 19 | const md = [ 20 | "---", 21 | "title: Something Special", 22 | "---", 23 | "Here is some text.", 24 | "```css", 25 | ".foo {}", 26 | "```", 27 | "And some other text.", 28 | "```css", 29 | " .foo { color: pink; }", 30 | " .bar {}", 31 | "```", 32 | "", 37 | "```scss", 38 | "// Parser-breaking comment", 39 | "$foo: bar;", 40 | ".foo {}", 41 | "```", 42 | "```js", 43 | "", 46 | "```", 47 | "```html", 48 | "", 51 | "```", 52 | "And the end.", 53 | ].join("\n"); 54 | const root = syntax.parse(md, { 55 | from: "markdown.md", 56 | }); 57 | expect(root.nodes).to.have.lengthOf(5); 58 | expect(root.toString()).to.equal(md); 59 | }); 60 | 61 | it("empty code block", () => { 62 | const source = [ 63 | "hi", 64 | "", 65 | "```css", 66 | "", 67 | "```", 68 | "", 69 | ].join("\n"); 70 | const root = syntax.parse(source, { 71 | from: "empty_code_block.md", 72 | }); 73 | expect(root.nodes).to.have.lengthOf(1); 74 | const css = root.first.source; 75 | expect(css.lang).equal("css"); 76 | expect(css.input.css).equal("\n"); 77 | expect(css.start.line).equal(4); 78 | expect(css.start.column).equal(1); 79 | }); 80 | 81 | it("empty file", () => { 82 | const root = syntax.parse("", { 83 | from: "empty_file.md", 84 | }); 85 | expect(root.nodes).have.lengthOf(0); 86 | expect(root.toString()).to.equal(""); 87 | }); 88 | 89 | it("without code blocks", () => { 90 | const root = syntax.parse("# Hi\n", { 91 | from: "without_code_blocks.md", 92 | }); 93 | expect(root.nodes).to.have.lengthOf(0); 94 | expect(root.toString()).to.equal("# Hi\n"); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test/parse-style.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const parseStyle = require("../parse-style"); 3 | const postcss = require("postcss"); 4 | const expect = require("chai").expect; 5 | 6 | describe("parse-style", () => { 7 | it("style.ignoreErrors", () => { 8 | parseStyle("test", {}, [ 9 | { 10 | syntax: { 11 | }, 12 | ignoreErrors: true, 13 | }, 14 | ]); 15 | }); 16 | 17 | it("fix error.column", () => { 18 | let error; 19 | try { 20 | parseStyle("test", {}, [ 21 | { 22 | startIndex: 9, 23 | content: "a {", 24 | syntax: postcss, 25 | }, 26 | ]); 27 | } catch (ex) { 28 | error = ex; 29 | } 30 | expect(error.column).to.equal(10); 31 | }); 32 | 33 | it("skipConvert error.column", () => { 34 | let error; 35 | try { 36 | parseStyle("test", {}, [ 37 | { 38 | startIndex: 9, 39 | skipConvert: true, 40 | content: "a {", 41 | syntax: postcss, 42 | }, 43 | ]); 44 | } catch (ex) { 45 | error = ex; 46 | } 47 | expect(error.column).to.equal(1); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/safe.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const postcss = require("postcss"); 5 | const syntax = require("../")({ 6 | css: "postcss-safe-parser", 7 | }); 8 | 9 | describe("postcss-safe-parser", () => { 10 | it("a{", () => { 11 | return postcss([ 12 | root => { 13 | expect(root.nodes).to.have.lengthOf(1); 14 | }, 15 | ]).process("a{", { 16 | syntax, 17 | from: "postcss-safe-parser.css", 18 | }).then(result => { 19 | expect(result.content).to.equal("a{}"); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/sugarss.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const postcss = require("postcss"); 5 | const syntax = require("../"); 6 | const sugarss = require("sugarss"); 7 | 8 | describe("SugarSS tests", () => { 9 | let sss = [ 10 | ".one", 11 | "\tbackground: linear-gradient(rgba(0, 0, 0, 0), black)", 12 | "\t\tlinear-gradient(red, rgba(255, 0, 0, 0))", 13 | ]; 14 | const css = [ 15 | sss[0] + " {", 16 | sss[1], 17 | sss[2], 18 | "}", 19 | ].join("\n"); 20 | 21 | sss = sss.join("\n"); 22 | 23 | it("SugarSS to CSS, `syntax({ sugarss: { parse: sugarss.parse } })`", () => { 24 | return postcss([ 25 | root => { 26 | expect(root.nodes).to.have.lengthOf(1); 27 | }, 28 | ]).process(sss, { 29 | syntax: syntax({ 30 | sugarss: { 31 | parse: sugarss.parse, 32 | }, 33 | }), 34 | from: "SugarSS.sss", 35 | }).then(result => { 36 | expect(result.content).to.equal(css); 37 | }); 38 | }); 39 | 40 | it("SugarSS to CSS, `syntax({ sugarss: sugarss.parse })`", () => { 41 | return postcss([ 42 | root => { 43 | expect(root.nodes).to.have.lengthOf(1); 44 | }, 45 | ]).process(sss, { 46 | syntax: syntax({ 47 | sugarss: sugarss.parse, 48 | }), 49 | from: "SugarSS.sss", 50 | }).then(result => { 51 | expect(result.content).to.equal(css); 52 | }); 53 | }); 54 | 55 | it("SugarSS toString()", () => { 56 | const root = syntax({ 57 | sugarss: sugarss, 58 | }).parse(sss, { 59 | from: "SugarSS.sss", 60 | }); 61 | expect(root.source).to.haveOwnProperty("lang", "sugarss"); 62 | expect(root.toString()).to.be.equal(sss); 63 | expect(root.toString(root.source.syntax)).to.be.equal(sss); 64 | }); 65 | 66 | it("SugarSS in vue", () => { 67 | const vue = [ 68 | "", 71 | ].join("\n"); 72 | return postcss([ 73 | root => { 74 | expect(root.nodes).to.have.lengthOf(1); 75 | root.each(() => false); 76 | }, 77 | ]).process(vue, { 78 | syntax, 79 | from: "sugarss.vue", 80 | }).then(result => { 81 | expect(result.root.first.source).to.haveOwnProperty("lang", "sugarss"); 82 | expect(result.content).to.equal(vue); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/vue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const syntax = require("../syntax")(require("postcss-html/extract")); 5 | 6 | describe("vue tests", () => { 7 | it("vue with empty ", 10 | "", 12 | ].join("\n"); 13 | const root = syntax({ 14 | "css": "css", 15 | }).parse(vue, { 16 | from: "empty.vue", 17 | }); 18 | expect(root.first.source.lang).to.equal("css"); 19 | expect(root.last.source.lang).to.equal("stylus"); 20 | expect(root.nodes).to.have.lengthOf(2); 21 | root.nodes.forEach(root => { 22 | expect(root.nodes).to.have.lengthOf(0); 23 | }); 24 | expect(root.toString()).to.equal(vue); 25 | }); 26 | 27 | it("safe-parser", () => { 28 | const vue = [ 29 | "", 30 | "", 32 | ].join("\n"); 33 | const root = syntax({ 34 | css: "postcss-safe-parser", 35 | }).parse(vue, { 36 | from: "empty.vue", 37 | }); 38 | expect(root.first.source.lang).to.equal("css"); 39 | expect(root.last.source.lang).to.equal("stylus"); 40 | expect(root.nodes).to.have.lengthOf(2); 41 | root.nodes.forEach(root => { 42 | expect(root.nodes).to.have.lengthOf(0); 43 | }); 44 | expect(root.toString()).to.equal(vue); 45 | }); 46 | 47 | it("vue with lang(s)", () => { 48 | const vue = [ 49 | "", 60 | "", 76 | ].join("\n"); 77 | const root = syntax.parse(vue, { 78 | from: "lang.vue", 79 | }); 80 | expect(root.nodes).to.have.lengthOf(2); 81 | expect(root.first.source.lang).to.equal("scss"); 82 | expect(root.last.source.lang).to.equal("less"); 83 | expect(root.toString()).to.equal(vue); 84 | }); 85 | 86 | it("single line syntax error", () => { 87 | expect(() => { 88 | syntax.parse("", { 89 | from: "SyntaxError.vue", 90 | }); 91 | }).to.throw(/SyntaxError.vue:1:8: Unclosed block\b/); 92 | }); 93 | }); 94 | --------------------------------------------------------------------------------