├── .node-version ├── .npmrc ├── index.js ├── example.png ├── src ├── bin │ └── stylelint-find-rules.js └── lib │ ├── is-deprecated.js │ └── cli.js ├── .eslintrc ├── tests └── configs │ ├── extends-recommended.config.js │ ├── empty-extends.config.js │ ├── invalid-rules.config.js │ └── mixed.config.js ├── jsconfig.json ├── jest.config.js ├── .editorconfig ├── .gitignore ├── prettier.config.js ├── .gitattributes ├── LICENSE ├── CONTRIBUTING.md ├── .circleci └── config.yml ├── package.json ├── CODE_OF_CONDUCT.md ├── README.md └── stylelint.config.js /.node-version: -------------------------------------------------------------------------------- 1 | 10.16.1 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact = true 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./src/bin/stylelint-find-rules.js'); 4 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexilyaev/stylelint-find-rules/HEAD/example.png -------------------------------------------------------------------------------- /src/bin/stylelint-find-rules.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | require('../lib/cli'); 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "extends": ["ai", "ai/jest", "ai/lodash", "ai/promise"], 5 | 6 | "rules": { 7 | // --- 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/configs/extends-recommended.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-recommended'], 5 | 6 | rules: {}, 7 | }; 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs" 5 | }, 6 | "exclude": ["node_modules", "**/node_modules/*"] 7 | } 8 | -------------------------------------------------------------------------------- /tests/configs/empty-extends.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['./extends-recommended.config.js', './invalid-rules.config.js'], 5 | 6 | rules: {}, 7 | }; 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | testEnvironment: 'node', 5 | verbose: true, 6 | restoreMocks: true, 7 | resetMocks: true, 8 | resetModules: true, 9 | }; 10 | -------------------------------------------------------------------------------- /tests/configs/invalid-rules.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | rules: { 5 | // Simulate set rules that are not available at all 6 | 'some-bogus-rule': true, 7 | 'some-other-bogus-rule': true, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rules Reference 2 | # http://philhosoft.github.io/Software/Git-gitignore-rules/ 3 | 4 | # Temp files 5 | tmp/** 6 | .tmp/** 7 | 8 | # Sass 9 | .sass-cache 10 | 11 | # Logs 12 | logs 13 | *.log 14 | 15 | # Compiled binary addons (http://nodejs.org/api/addons.html) 16 | build/Release 17 | 18 | # Dependency directory 19 | /node_modules 20 | 21 | # OS generated files 22 | .DS_STORE 23 | 24 | # Thumbnails 25 | ._* 26 | Thumbs.db 27 | 28 | # Editors stuff 29 | .idea 30 | .project 31 | .vscode 32 | 33 | # Build stuff 34 | /dist 35 | /app/dist 36 | 37 | # Testing 38 | /coverage 39 | /.test 40 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable line-comment-position, no-inline-comments */ 2 | 'use strict'; 3 | 4 | /** 5 | * Ref: 6 | * https://prettier.io/docs/en/options.html 7 | */ 8 | 9 | module.exports = { 10 | arrowParens: 'avoid', 11 | bracketSpacing: true, 12 | endOfLine: 'auto', 13 | htmlWhitespaceSensitivity: 'css', 14 | jsxBracketSameLine: false, 15 | jsxSingleQuote: false, 16 | printWidth: 80, // default: 80 17 | proseWrap: 'preserve', 18 | semi: true, 19 | singleQuote: true, // default: false 20 | tabWidth: 2, 21 | trailingComma: 'es5', // default: none 22 | useTabs: false, 23 | }; 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Based on: 2 | # https://github.com/h5bp/html5-boilerplate/blob/master/.gitattributes 3 | # http://adaptivepatchwork.com/2012/03/01/mind-the-end-of-your-line/ 4 | 5 | # Auto detect text files and perform LF normalization 6 | * text=auto 7 | 8 | # For the following file types, normalize line endings to LF on 9 | # checkin and prevent conversion to CRLF when they are checked out 10 | # (this is required in order to prevent newline related issues like, 11 | # for example, after the build script is run) 12 | .* text eol=lf 13 | *.css text eol=lf 14 | *.html text eol=lf 15 | *.js text eol=lf 16 | *.json text eol=lf 17 | *.md text eol=lf 18 | *.sh text eol=lf 19 | *.txt text eol=lf 20 | *.xml text eol=lf 21 | *.map text eol=lf 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alex Ilyaev 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 | -------------------------------------------------------------------------------- /src/lib/is-deprecated.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const Promise = require('bluebird'); 5 | 6 | function handleError(err) { 7 | throw err; 8 | } 9 | 10 | /** 11 | * Check if the given rule is deprecated 12 | * This is a hacky solution that looks for "deprecated" in the rule docs 13 | * stylelint has no meta data, see discussion: 14 | * https://github.com/stylelint/stylelint/issues/2622 15 | * 16 | * @param {string} ruleName 17 | * @returns {Promise} 18 | */ 19 | module.exports = function isDDeprecated(ruleName) { 20 | const target = `stylelint/lib/rules/${ruleName}/README.md`; 21 | const filePath = require.resolve(target); 22 | 23 | // Limit the chunk size to get only the beginning of the file 24 | const readStream = fs.createReadStream(filePath, { 25 | highWaterMark: 512, 26 | }); 27 | 28 | readStream.on('error', err => { 29 | readStream.destroy(); 30 | 31 | handleError(err); 32 | }); 33 | 34 | return new Promise(resolve => { 35 | readStream.on('data', chunk => { 36 | const text = chunk.toString(); 37 | 38 | resolve(/deprecated/i.test(text)); 39 | 40 | readStream.destroy(); 41 | }); 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | **Working on your first Pull Request?** You can learn how from this _free_ course: 4 | [EggHead.io - How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) 5 | 6 | Feel free to open an issue to discuss feature requests, or if you're savvy, just PR. 7 | 8 | ## Style Guide 9 | 10 | This project happily uses [Prettier](https://github.com/prettier/prettier) (reformat's code based on common practices) and [ESLint](https://github.com/eslint/eslint) (static analysis of code patterns, can also fix stuff). 11 | The result is a consistent code style and happy unicorns. 12 | 13 | There's a Git `precommit` hook in this repo (using [husky](https://github.com/typicode/husky)), 14 | so when you commit, all files will be formatted with Prettier and ESLint and the changes will be added 15 | to the commit. 16 | 17 | You can also setup [editor-integration](https://github.com/prettier/prettier#editor-integration) 18 | for automatically reformatting your code with Prettier on Save or using a hotkey. 19 | 20 | ## Running In Development 21 | 22 | `npm start` will run the same file that users run in projects. 23 | There are test configs under `tests/configs/`, so we can provide a specific config using: 24 | 25 | ``` 26 | npm start -- --config tests/configs/mixed.config.js 27 | ``` 28 | 29 | ## Commit Message Format 30 | 31 | ``` 32 | Tag: Short description (fixes #1234) 33 | 34 | Longer description if necessary 35 | ``` 36 | 37 | Based on [ESLint commit message conventions](https://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) 38 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | 6 | version: 2 7 | 8 | # YAML Anchors and Aliases 9 | # https://circleci.com/docs/2.0/writing-yaml/ 10 | # https://circleci.com/blog/circleci-hacks-reuse-yaml-in-your-circleci-config-with-yaml/ 11 | 12 | # Download and cache dependencies 13 | # https://circleci.com/docs/2.0/yarn/ 14 | # https://circleci.com/docs/2.0/caching/ 15 | _restore_yarn_cache: &restore_yarn_cache 16 | keys: 17 | - yarn-packages-{{ checksum "yarn.lock" }} 18 | 19 | _save_yarn_cache: &save_yarn_cache 20 | key: yarn-packages-{{ checksum "yarn.lock" }} 21 | paths: 22 | - ~/.cache/yarn 23 | 24 | _defaults: &defaults 25 | steps: 26 | - run: node -v 27 | - checkout 28 | - restore_cache: *restore_yarn_cache 29 | - run: yarn install --frozen-lockfile 30 | - save_cache: *save_yarn_cache 31 | - run: npm run lint 32 | 33 | # Setup 34 | 35 | jobs: 36 | node-latest: 37 | <<: *defaults 38 | 39 | docker: 40 | - image: circleci/node:latest 41 | 42 | node-lts: 43 | <<: *defaults 44 | 45 | docker: 46 | - image: circleci/node:lts 47 | 48 | node-v8: 49 | <<: *defaults 50 | 51 | docker: 52 | # Some tools depends on `execa` and it has specific node versions dependency: 53 | # error execa@2.0.3: The engine "node" is incompatible with this module. 54 | # Expected version "^8.12.0 || >=9.7.0". 55 | - image: circleci/node:8.12 56 | 57 | workflows: 58 | version: 2 59 | node-multi-build: 60 | jobs: 61 | - node-latest 62 | - node-lts 63 | - node-v8 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stylelint-find-rules", 3 | "version": "2.2.0", 4 | "description": "Find stylelint rules that you don't have in your config", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "start-mixed": "node index.js --config tests/configs/mixed.config.js --test-deprecated", 9 | "base-prettier": "prettier --ignore-path .gitignore", 10 | "base-eslint": "eslint --ignore-path .gitignore --rule 'no-console: 1'", 11 | "format": "npm run base-eslint -- --fix \"**/*.js\" && npm run base-prettier -- --write \"**/*.js\"", 12 | "lint-prettier": "npm run base-prettier -- -l \"**/*.js\"", 13 | "lint-eslint": "npm run base-eslint -- --max-warnings 0 \"**/*.js\"", 14 | "lint": "npm run lint-prettier && npm run lint-eslint", 15 | "test": "jest ./src", 16 | "test-watch": "npm run test -- --watch --notify", 17 | "test-coverage": "npm run test -- --coverage", 18 | "test-ci": "npm run test-coverage" 19 | }, 20 | "lint-staged": { 21 | "*.js": [ 22 | "npm run base-eslint -- --fix --max-warnings 0", 23 | "npm run base-prettier -- --write", 24 | "git add" 25 | ] 26 | }, 27 | "husky": { 28 | "hooks": { 29 | "pre-commit": "lint-staged" 30 | } 31 | }, 32 | "bin": { 33 | "stylelint-find-rules": "src/bin/stylelint-find-rules.js" 34 | }, 35 | "dependencies": { 36 | "bluebird": "3.7.0", 37 | "chalk": "2.4.2", 38 | "columnify": "1.5.4", 39 | "cosmiconfig": "5.2.1", 40 | "lodash": "4.17.21", 41 | "yargs": "14.0.0" 42 | }, 43 | "devDependencies": { 44 | "babel-eslint": "10.0.3", 45 | "eslint": "6.5.1", 46 | "eslint-config-ai": "1.7.0", 47 | "eslint-config-prettier": "6.4.0", 48 | "eslint-find-rules": "3.4.0", 49 | "eslint-plugin-jest": "22.17.0", 50 | "eslint-plugin-lodash": "6.0.0", 51 | "eslint-plugin-promise": "4.2.1", 52 | "husky": "3.0.8", 53 | "jest": "24.9.0", 54 | "lint-staged": "9.4.1", 55 | "prettier": "1.18.2", 56 | "stylelint": "9.9.0", 57 | "stylelint-config-recommended": "2.1.0", 58 | "stylelint-config-standard": "18.2.0" 59 | }, 60 | "peerDependencies": { 61 | "stylelint": ">=8.3.0" 62 | }, 63 | "engines": { 64 | "node": "^8.12.0 || >=9.7.0" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "git+https://github.com/alexilyaev/stylelint-find-rules.git" 69 | }, 70 | "keywords": [], 71 | "author": "Alex Ilyaev", 72 | "license": "MIT", 73 | "bugs": { 74 | "url": "https://github.com/alexilyaev/stylelint-find-rules/issues" 75 | }, 76 | "homepage": "https://github.com/alexilyaev/stylelint-find-rules#readme" 77 | } 78 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [Contact Address](https://github.com/alexilyaev). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stylelint-find-rules 2 | 3 | [![Build Status](https://circleci.com/gh/alexilyaev/stylelint-find-rules.svg?&style=shield&circle-token=7fa54818dd54da41bad761661259992a74e50c7a)](https://circleci.com/gh/alexilyaev/stylelint-find-rules) 4 | [![version](https://img.shields.io/npm/v/stylelint-find-rules.svg?style=flat-square)](http://npm.im/stylelint-find-rules) 5 | [![MIT License](https://img.shields.io/npm/l/stylelint-find-rules.svg?style=flat-square)](http://opensource.org/licenses/MIT) 6 | [![codebeat badge](https://codebeat.co/badges/10dc4306-cbef-4a6e-aa23-b197ee8ad3eb)](https://codebeat.co/projects/github-com-alexilyaev-stylelint-find-rules-master) 7 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 8 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 9 | 10 | Find [stylelint](https://github.com/stylelint/stylelint) rules that are not configured in your stylelint config. 11 | 12 | > Inspired by [eslint-find-rules](https://github.com/sarbbottam/eslint-find-rules) 13 | 14 | ## Example Output 15 | 16 | ![Example](example.png) 17 | 18 | ## Installation 19 | 20 | Install as a dev dependency of your project: 21 | 22 | ```shell 23 | yarn add -D stylelint-find-rules 24 | ``` 25 | 26 | Or with `npm` 27 | 28 | ```shell 29 | npm i -D stylelint-find-rules 30 | ``` 31 | 32 | ## Usage 33 | 34 | This package requires `stylelint` to be already installed in the project, as it will search for 35 | available rules from that package. 36 | 37 | ### npm script 38 | 39 | ```js 40 | { 41 | ... 42 | "scripts": { 43 | "stylelint-find-unused-rules": "stylelint-find-rules" 44 | } 45 | ... 46 | } 47 | ``` 48 | 49 | ### Command line 50 | 51 | Using `npx`: 52 | 53 | ```shell 54 | npx stylelint-find-rules 55 | ``` 56 | 57 | Or the old way: 58 | 59 | ```shell 60 | ./node_modules/.bin/stylelint-find-rules 61 | ``` 62 | 63 | ### Options 64 | 65 | ```txt 66 | stylelint-find-rules [options] 67 | 68 | General: 69 | -h, --help Show help [boolean] 70 | --config Optional, path to a custom config file (passed to cosmiconfig) 71 | 72 | Options: 73 | --version Show version number [boolean] 74 | -u, --unused Find available rules that are not configured 75 | To disable, set to false or use --no-u [boolean] [default: true] 76 | -d, --deprecated Find deprecated configured rules 77 | To disable, set to false or use --no-d [boolean] [default: true] 78 | -i, --invalid Find configured rules that are no longer available 79 | To disable, set to false or use --no-i [boolean] [default: true] 80 | -c, --current Find all currently configured rules [boolean] 81 | -a, --available Find all available stylelint rules [boolean] 82 | 83 | Examples: 84 | stylelint-find-rules 85 | stylelint-find-rules --no-d --no-i 86 | stylelint-find-rules --config path/to/custom.config.js 87 | ``` 88 | 89 | ## Supported configs 90 | 91 | Just like stylelint, this package uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) 92 | to find your config data, so if stylelint works for you, this should too. 93 | 94 | ### Custom config file 95 | 96 | ```shell 97 | ./node_modules/.bin/stylelint-find-rules --config my-custom-config.js 98 | ``` 99 | 100 | ### How does it handle `extends`? 101 | 102 | The rules of `extends` are added to the list of configured rules, thus, if a rule is covered in an 103 | extend config, it will not show up in the `unused` results. 104 | 105 | ## Contributing 106 | 107 | See the [CONTRIBUTING](CONTRIBUTING.md) document. 108 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | rules: { 5 | 'at-rule-blacklist': null, 6 | 'at-rule-empty-line-before': [ 7 | 'always', 8 | { 9 | except: [ 10 | 'after-same-name', 11 | 'inside-block', 12 | 'blockless-after-same-name-blockless', 13 | 'first-nested', 14 | ], 15 | ignore: ['after-comment'], 16 | }, 17 | ], 18 | 'at-rule-name-case': 'lower', 19 | 'at-rule-name-newline-after': null, 20 | 'at-rule-name-space-after': 'always', 21 | 'at-rule-no-unknown': true, 22 | 'at-rule-no-vendor-prefix': true, 23 | 'at-rule-semicolon-newline-after': 'always', 24 | 'at-rule-semicolon-space-before': 'never', 25 | 'at-rule-whitelist': null, 26 | 'color-hex-case': 'lower', 27 | 'color-hex-length': 'short', 28 | 'color-named': null, 29 | 'color-no-hex': null, 30 | 'color-no-invalid-hex': true, 31 | 'comment-empty-line-before': [ 32 | 'always', 33 | { 34 | except: ['first-nested'], 35 | ignore: ['after-comment', 'stylelint-commands'], 36 | }, 37 | ], 38 | 'comment-no-empty': true, 39 | 'comment-whitespace-inside': 'always', 40 | 'comment-word-blacklist': ['todo', 'TODO'], 41 | 'custom-media-pattern': null, 42 | 'custom-property-empty-line-before': null, 43 | 'custom-property-pattern': null, 44 | 'font-family-name-quotes': 'always-where-recommended', 45 | 'font-family-no-duplicate-names': true, 46 | 'font-family-no-missing-generic-family-keyword': true, 47 | 'font-weight-notation': null, 48 | 'function-blacklist': null, 49 | 'function-calc-no-unspaced-operator': true, 50 | 'function-comma-newline-after': null, 51 | 'function-comma-newline-before': 'never-multi-line', 52 | 'function-comma-space-after': 'always-single-line', 53 | 'function-comma-space-before': 'never', 54 | 'function-linear-gradient-no-nonstandard-direction': true, 55 | 'function-max-empty-lines': 0, 56 | 'function-name-case': 'lower', 57 | 'function-parentheses-newline-inside': 'always-multi-line', 58 | 'function-parentheses-space-inside': 'never-single-line', 59 | 'function-url-no-scheme-relative': null, 60 | 'function-url-quotes': [ 61 | 'always', 62 | { 63 | except: ['empty'], 64 | }, 65 | ], 66 | 'function-url-scheme-blacklist': null, 67 | 'function-url-scheme-whitelist': null, 68 | 'function-whitespace-after': 'always', 69 | 'function-whitelist': null, 70 | indentation: [ 71 | 2, 72 | { 73 | indentInsideParens: 'once-at-root-twice-in-block', 74 | indentClosingBrace: false, 75 | ignore: ['inside-parens'], 76 | }, 77 | ], 78 | 'number-leading-zero': 'always', 79 | 'number-max-precision': null, 80 | 'number-no-trailing-zeros': true, 81 | 'string-no-newline': true, 82 | 'string-quotes': 'single', 83 | 'length-zero-no-unit': true, 84 | 'time-min-milliseconds': null, 85 | 'unit-blacklist': null, 86 | 'unit-case': 'lower', 87 | 'unit-no-unknown': true, 88 | 'unit-whitelist': null, 89 | 'value-keyword-case': [ 90 | 'lower', 91 | { 92 | ignoreKeywords: '/\\$.+$/', 93 | }, 94 | ], 95 | 'value-list-comma-newline-after': 'always-multi-line', 96 | 'value-list-comma-space-after': 'always-single-line', 97 | 'value-list-comma-space-before': 'never-single-line', 98 | 'value-list-comma-newline-before': 'never-multi-line', 99 | 'value-list-max-empty-lines': 0, 100 | 'shorthand-property-no-redundant-values': true, 101 | 'property-blacklist': null, 102 | 'property-case': 'lower', 103 | 'property-no-unknown': true, 104 | 'property-no-vendor-prefix': true, 105 | 'property-whitelist': null, 106 | 'value-no-vendor-prefix': true, 107 | 'declaration-bang-space-after': 'never', 108 | 'declaration-bang-space-before': 'always', 109 | 'declaration-colon-space-after': 'always-single-line', 110 | 'declaration-colon-space-before': 'never', 111 | 'declaration-empty-line-before': [ 112 | 'always', 113 | { 114 | except: ['after-comment', 'after-declaration', 'first-nested'], 115 | ignore: ['inside-single-line-block'], 116 | }, 117 | ], 118 | 'declaration-block-no-duplicate-properties': [ 119 | true, 120 | { 121 | ignore: ['consecutive-duplicates-with-different-values'], 122 | }, 123 | ], 124 | 'declaration-block-no-redundant-longhand-properties': true, 125 | 'declaration-block-no-shorthand-property-overrides': true, 126 | 'declaration-block-semicolon-newline-after': 'always', 127 | 'declaration-block-semicolon-newline-before': 'never-multi-line', 128 | 'declaration-block-semicolon-space-after': 'always-single-line', 129 | 'declaration-block-semicolon-space-before': 'never', 130 | 'declaration-block-single-line-max-declarations': 1, 131 | 'declaration-block-trailing-semicolon': 'always', 132 | 'declaration-colon-newline-after': null, 133 | 'declaration-no-important': null, 134 | 'declaration-property-unit-blacklist': null, 135 | 'declaration-property-unit-whitelist': null, 136 | 'declaration-property-value-blacklist': null, 137 | 'declaration-property-value-whitelist': null, 138 | 'block-closing-brace-empty-line-before': 'never', 139 | 'block-closing-brace-newline-after': 'always', 140 | 'block-closing-brace-newline-before': 'always', 141 | 'block-closing-brace-space-after': 'always-single-line', 142 | 'block-closing-brace-space-before': 'always-single-line', 143 | 'block-no-empty': true, 144 | 'block-opening-brace-newline-after': 'always-multi-line', 145 | 'block-opening-brace-newline-before': 'never-single-line', 146 | 'block-opening-brace-space-after': 'always-single-line', 147 | 'block-opening-brace-space-before': 'always', 148 | 'selector-attribute-brackets-space-inside': 'never', 149 | 'selector-attribute-operator-space-after': 'never', 150 | 'selector-attribute-operator-space-before': 'never', 151 | 'selector-attribute-quotes': 'always', 152 | 'selector-class-pattern': [ 153 | '^[a-z0-9\\-]+$', 154 | { 155 | message: 'Class names should be dash-cased. Pattern: "^[a-z0-9\\-]+$"', 156 | }, 157 | ], 158 | 'selector-combinator-blacklist': null, 159 | 'selector-combinator-whitelist': null, 160 | 'selector-attribute-operator-blacklist': null, 161 | 'selector-attribute-operator-whitelist': null, 162 | 'selector-combinator-space-after': 'always', 163 | 'selector-combinator-space-before': 'always', 164 | 'selector-descendant-combinator-no-non-space': true, 165 | 'selector-id-pattern': null, 166 | 'selector-list-comma-newline-after': 'always', 167 | 'selector-list-comma-newline-before': 'never-multi-line', 168 | 'selector-list-comma-space-after': 'always-single-line', 169 | 'selector-list-comma-space-before': 'never-single-line', 170 | 'selector-max-attribute': null, 171 | 'selector-max-class': null, 172 | 'selector-max-combinators': null, 173 | 'selector-max-compound-selectors': null, 174 | 'selector-max-empty-lines': 0, 175 | 'selector-max-id': null, 176 | 'selector-max-specificity': null, 177 | 'selector-max-type': null, 178 | 'selector-max-universal': null, 179 | 'selector-nested-pattern': null, 180 | 'selector-no-qualifying-type': true, 181 | 'selector-no-vendor-prefix': true, 182 | 'selector-pseudo-class-case': 'lower', 183 | 'selector-pseudo-class-no-unknown': true, 184 | 'selector-pseudo-class-parentheses-space-inside': 'never', 185 | 'selector-pseudo-class-blacklist': null, 186 | 'selector-pseudo-class-whitelist': null, 187 | 'selector-pseudo-element-blacklist': null, 188 | 'selector-pseudo-element-whitelist': null, 189 | 'selector-pseudo-element-case': 'lower', 190 | 'selector-pseudo-element-colon-notation': 'double', 191 | 'selector-pseudo-element-no-unknown': true, 192 | 'selector-type-case': 'lower', 193 | 'selector-type-no-unknown': null, 194 | 'rule-empty-line-before': [ 195 | 'always', 196 | { 197 | except: ['first-nested'], 198 | ignore: ['after-comment'], 199 | }, 200 | ], 201 | 'media-feature-colon-space-after': 'always', 202 | 'media-feature-colon-space-before': 'never', 203 | 'media-feature-name-blacklist': null, 204 | 'media-feature-name-case': 'lower', 205 | 'media-feature-name-no-unknown': true, 206 | 'media-feature-name-no-vendor-prefix': true, 207 | 'media-feature-name-whitelist': null, 208 | 'media-feature-parentheses-space-inside': 'never', 209 | 'media-feature-range-operator-space-after': 'always', 210 | 'media-feature-range-operator-space-before': 'always', 211 | 'media-query-list-comma-newline-after': 'always-multi-line', 212 | 'media-query-list-comma-newline-before': 'never-multi-line', 213 | 'media-query-list-comma-space-after': 'always-single-line', 214 | 'media-query-list-comma-space-before': 'never-single-line', 215 | 'max-empty-lines': 1, 216 | 'max-line-length': 100, 217 | 'max-nesting-depth': null, 218 | 'no-descending-specificity': true, 219 | 'no-duplicate-at-import-rules': true, 220 | 'no-duplicate-selectors': true, 221 | 'no-empty-source': true, 222 | 'no-eol-whitespace': true, 223 | 'no-extra-semicolons': true, 224 | 'no-invalid-double-slash-comments': true, 225 | 'no-missing-end-of-source-newline': true, 226 | 'no-unknown-animations': true, 227 | 'keyframe-declaration-no-important': true, 228 | }, 229 | }; 230 | -------------------------------------------------------------------------------- /tests/configs/mixed.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: [ 5 | 'stylelint-config-standard', 6 | './extends-recommended.config.js', 7 | './invalid-rules.config.js', 8 | ], 9 | 10 | rules: { 11 | // Simulate unset available rules 12 | // 'at-rule-blacklist': null, 13 | // 'at-rule-name-newline-after': null, 14 | 15 | // Simulate unset rules covered by extends (comment out the extends to check) 16 | // 'at-rule-name-case': 'lower', 17 | // 'at-rule-name-space-after': 'always', 18 | // 'at-rule-no-unknown': true, 19 | 20 | // Simulate set rules that are deprecated (hard coded in `findDeprecatedStylelintRules`) 21 | // Requires `--test-deprecated` flag to work, since there are no actual deprecated rules at the moment 22 | 'color-hex-case': 'lower', 23 | 'color-hex-length': 'short', 24 | 25 | 'at-rule-empty-line-before': [ 26 | 'always', 27 | { 28 | except: [ 29 | 'after-same-name', 30 | 'inside-block', 31 | 'blockless-after-same-name-blockless', 32 | 'first-nested', 33 | ], 34 | ignore: ['after-comment'], 35 | }, 36 | ], 37 | 38 | 'at-rule-no-vendor-prefix': true, 39 | 'at-rule-semicolon-newline-after': 'always', 40 | 'at-rule-semicolon-space-before': 'never', 41 | 'at-rule-whitelist': null, 42 | 'color-named': null, 43 | 'color-no-hex': null, 44 | 'color-no-invalid-hex': true, 45 | 'comment-empty-line-before': [ 46 | 'always', 47 | { 48 | except: ['first-nested'], 49 | ignore: ['after-comment', 'stylelint-commands'], 50 | }, 51 | ], 52 | 'comment-no-empty': true, 53 | 'comment-whitespace-inside': 'always', 54 | 'comment-word-blacklist': ['todo', 'TODO'], 55 | 'custom-media-pattern': null, 56 | 'custom-property-empty-line-before': null, 57 | 'custom-property-pattern': null, 58 | 'font-family-name-quotes': 'always-where-recommended', 59 | 'font-family-no-duplicate-names': true, 60 | 'font-family-no-missing-generic-family-keyword': true, 61 | 'font-weight-notation': null, 62 | 'function-blacklist': null, 63 | 'function-calc-no-unspaced-operator': true, 64 | 'function-comma-newline-after': null, 65 | 'function-comma-newline-before': 'never-multi-line', 66 | 'function-comma-space-after': 'always-single-line', 67 | 'function-comma-space-before': 'never', 68 | 'function-linear-gradient-no-nonstandard-direction': true, 69 | 'function-max-empty-lines': 0, 70 | 'function-name-case': 'lower', 71 | 'function-parentheses-newline-inside': 'always-multi-line', 72 | 'function-parentheses-space-inside': 'never-single-line', 73 | 'function-url-no-scheme-relative': null, 74 | 'function-url-quotes': [ 75 | 'always', 76 | { 77 | except: ['empty'], 78 | }, 79 | ], 80 | 'function-url-scheme-blacklist': null, 81 | 'function-url-scheme-whitelist': null, 82 | 'function-whitespace-after': 'always', 83 | 'function-whitelist': null, 84 | indentation: [ 85 | 2, 86 | { 87 | indentInsideParens: 'once-at-root-twice-in-block', 88 | indentClosingBrace: false, 89 | ignore: ['inside-parens'], 90 | }, 91 | ], 92 | 'number-leading-zero': 'always', 93 | 'number-max-precision': null, 94 | 'number-no-trailing-zeros': true, 95 | 'string-no-newline': true, 96 | 'string-quotes': 'single', 97 | 'length-zero-no-unit': true, 98 | 'time-min-milliseconds': null, 99 | 'unit-blacklist': null, 100 | 'unit-case': 'lower', 101 | 'unit-no-unknown': true, 102 | 'unit-whitelist': null, 103 | 'value-keyword-case': [ 104 | 'lower', 105 | { 106 | ignoreKeywords: '/\\$.+$/', 107 | }, 108 | ], 109 | 'value-list-comma-newline-after': 'always-multi-line', 110 | 'value-list-comma-space-after': 'always-single-line', 111 | 'value-list-comma-space-before': 'never-single-line', 112 | 'value-list-comma-newline-before': 'never-multi-line', 113 | 'value-list-max-empty-lines': 0, 114 | 'shorthand-property-no-redundant-values': true, 115 | 'property-blacklist': null, 116 | 'property-case': 'lower', 117 | 'property-no-unknown': true, 118 | 'property-no-vendor-prefix': true, 119 | 'property-whitelist': null, 120 | 'value-no-vendor-prefix': true, 121 | 'declaration-bang-space-after': 'never', 122 | 'declaration-bang-space-before': 'always', 123 | 'declaration-colon-space-after': 'always-single-line', 124 | 'declaration-colon-space-before': 'never', 125 | 'declaration-empty-line-before': [ 126 | 'always', 127 | { 128 | except: ['after-comment', 'after-declaration', 'first-nested'], 129 | ignore: ['inside-single-line-block'], 130 | }, 131 | ], 132 | 'declaration-block-no-duplicate-properties': [ 133 | true, 134 | { 135 | ignore: ['consecutive-duplicates-with-different-values'], 136 | }, 137 | ], 138 | 'declaration-block-no-redundant-longhand-properties': true, 139 | 'declaration-block-no-shorthand-property-overrides': true, 140 | 'declaration-block-semicolon-newline-after': 'always', 141 | 'declaration-block-semicolon-newline-before': 'never-multi-line', 142 | 'declaration-block-semicolon-space-after': 'always-single-line', 143 | 'declaration-block-semicolon-space-before': 'never', 144 | 'declaration-block-single-line-max-declarations': 1, 145 | 'declaration-block-trailing-semicolon': 'always', 146 | 'declaration-colon-newline-after': null, 147 | 'declaration-no-important': null, 148 | 'declaration-property-unit-blacklist': null, 149 | 'declaration-property-unit-whitelist': null, 150 | 'declaration-property-value-blacklist': null, 151 | 'declaration-property-value-whitelist': null, 152 | 'block-closing-brace-empty-line-before': 'never', 153 | 'block-closing-brace-newline-after': 'always', 154 | 'block-closing-brace-newline-before': 'always', 155 | 'block-closing-brace-space-after': 'always-single-line', 156 | 'block-closing-brace-space-before': 'always-single-line', 157 | 'block-no-empty': true, 158 | 'block-opening-brace-newline-after': 'always-multi-line', 159 | 'block-opening-brace-newline-before': 'never-single-line', 160 | 'block-opening-brace-space-after': 'always-single-line', 161 | 'block-opening-brace-space-before': 'always', 162 | 'selector-attribute-brackets-space-inside': 'never', 163 | 'selector-attribute-operator-space-after': 'never', 164 | 'selector-attribute-operator-space-before': 'never', 165 | 'selector-attribute-quotes': 'always', 166 | 'selector-class-pattern': [ 167 | '^[a-z0-9\\-]+$', 168 | { 169 | message: 'Class names should be dash-cased. Pattern: "^[a-z0-9\\-]+$"', 170 | }, 171 | ], 172 | 'selector-combinator-blacklist': null, 173 | 'selector-combinator-whitelist': null, 174 | 'selector-attribute-operator-blacklist': null, 175 | 'selector-attribute-operator-whitelist': null, 176 | 'selector-combinator-space-after': 'always', 177 | 'selector-combinator-space-before': 'always', 178 | 'selector-descendant-combinator-no-non-space': true, 179 | 'selector-id-pattern': null, 180 | 'selector-list-comma-newline-after': 'always', 181 | 'selector-list-comma-newline-before': 'never-multi-line', 182 | 'selector-list-comma-space-after': 'always-single-line', 183 | 'selector-list-comma-space-before': 'never-single-line', 184 | 'selector-max-attribute': null, 185 | 'selector-max-class': null, 186 | 'selector-max-combinators': null, 187 | 'selector-max-compound-selectors': null, 188 | 'selector-max-empty-lines': 0, 189 | 'selector-max-id': null, 190 | 'selector-max-specificity': null, 191 | 'selector-max-type': null, 192 | 'selector-max-universal': null, 193 | 'selector-nested-pattern': null, 194 | 'selector-no-qualifying-type': true, 195 | 'selector-no-vendor-prefix': true, 196 | 'selector-pseudo-class-case': 'lower', 197 | 'selector-pseudo-class-no-unknown': true, 198 | 'selector-pseudo-class-parentheses-space-inside': 'never', 199 | 'selector-pseudo-class-blacklist': null, 200 | 'selector-pseudo-class-whitelist': null, 201 | 'selector-pseudo-element-blacklist': null, 202 | 'selector-pseudo-element-whitelist': null, 203 | 'selector-pseudo-element-case': 'lower', 204 | 'selector-pseudo-element-colon-notation': 'double', 205 | 'selector-pseudo-element-no-unknown': true, 206 | 'selector-type-case': 'lower', 207 | 'selector-type-no-unknown': null, 208 | 'rule-empty-line-before': [ 209 | 'always', 210 | { 211 | except: ['first-nested'], 212 | ignore: ['after-comment'], 213 | }, 214 | ], 215 | 'media-feature-colon-space-after': 'always', 216 | 'media-feature-colon-space-before': 'never', 217 | 'media-feature-name-blacklist': null, 218 | 'media-feature-name-case': 'lower', 219 | 'media-feature-name-no-unknown': true, 220 | 'media-feature-name-no-vendor-prefix': true, 221 | 'media-feature-name-whitelist': null, 222 | 'media-feature-parentheses-space-inside': 'never', 223 | 'media-feature-range-operator-space-after': 'always', 224 | 'media-feature-range-operator-space-before': 'always', 225 | 'media-query-list-comma-newline-after': 'always-multi-line', 226 | 'media-query-list-comma-newline-before': 'never-multi-line', 227 | 'media-query-list-comma-space-after': 'always-single-line', 228 | 'media-query-list-comma-space-before': 'never-single-line', 229 | 'max-empty-lines': 1, 230 | 'max-line-length': 100, 231 | 'max-nesting-depth': null, 232 | 'no-descending-specificity': true, 233 | 'no-duplicate-at-import-rules': true, 234 | 'no-duplicate-selectors': true, 235 | 'no-empty-source': true, 236 | 'no-eol-whitespace': true, 237 | 'no-extra-semicolons': true, 238 | 'no-invalid-double-slash-comments': true, 239 | 'no-missing-end-of-source-newline': true, 240 | 'no-unknown-animations': true, 241 | 'keyframe-declaration-no-important': true, 242 | }, 243 | }; 244 | -------------------------------------------------------------------------------- /src/lib/cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const cosmiconfig = require('cosmiconfig'); 5 | const columnify = require('columnify'); 6 | const stylelint = require('stylelint'); 7 | const chalk = require('chalk'); 8 | const yargs = require('yargs'); 9 | const Promise = require('bluebird'); 10 | const EOL = require('os').EOL; 11 | 12 | const pkg = require('../../package.json'); 13 | const isDDeprecated = require('./is-deprecated'); 14 | 15 | const rules = { 16 | stylelintAll: _.keys(stylelint.rules), 17 | stylelintDeprecated: [], 18 | stylelintNoDeprecated: [], 19 | userRulesNames: [], 20 | }; 21 | 22 | /** 23 | * Define command line arguments 24 | */ 25 | const argv = yargs 26 | .usage('stylelint-find-rules [options]') 27 | .example('stylelint-find-rules', '') 28 | .example('stylelint-find-rules --no-d --no-i', '') 29 | .example('stylelint-find-rules --config path/to/custom.config.js', '') 30 | .option('u', { 31 | type: 'boolean', 32 | alias: 'unused', 33 | describe: `Find available rules that are not configured 34 | To disable, set to ${chalk.blue('false')} or use ${chalk.blue('--no-u')}`, 35 | default: true, 36 | }) 37 | .option('d', { 38 | type: 'boolean', 39 | alias: 'deprecated', 40 | describe: `Find deprecated configured rules 41 | To disable, set to ${chalk.blue('false')} or use ${chalk.blue('--no-d')}`, 42 | default: true, 43 | }) 44 | .option('i', { 45 | type: 'boolean', 46 | alias: 'invalid', 47 | describe: `Find configured rules that are no longer available 48 | To disable, set to ${chalk.blue('false')} or use ${chalk.blue('--no-i')}`, 49 | default: true, 50 | }) 51 | .option('c', { 52 | type: 'boolean', 53 | alias: 'current', 54 | describe: 'Find all currently configured rules', 55 | }) 56 | .option('a', { 57 | type: 'boolean', 58 | alias: 'available', 59 | describe: 'Find all available stylelint rules', 60 | }) 61 | .option('config', { 62 | describe: 'Optional, path to a custom config file (passed to cosmiconfig)', 63 | }) 64 | .help('h') 65 | .alias('h', 'help') 66 | .group(['help', 'config'], 'General:') 67 | .wrap(100).argv; 68 | 69 | /** 70 | * High resolution timing API 71 | * 72 | * @returns {number} High resolution timestamp 73 | */ 74 | function time() { 75 | const time = process.hrtime(); 76 | 77 | return time[0] * 1e3 + time[1] / 1e6; 78 | } 79 | 80 | /** 81 | * Handle promise rejections and errors 82 | */ 83 | function handleError(err) { 84 | let errMsg = err; 85 | 86 | if (err instanceof Object) { 87 | errMsg = err.message || err.error || JSON.stringify(err); 88 | } 89 | 90 | printColumns(chalk.red('Error: ' + errMsg)); 91 | printColumns( 92 | chalk.white( 93 | "If you can't settle this, please open an issue at:" + 94 | EOL + 95 | chalk.cyan(pkg.bugs.url) 96 | ) 97 | ); 98 | process.exit(1); 99 | } 100 | 101 | /** 102 | * Print to stdout 103 | * 104 | * @param {string} heading 105 | * @param {Array?} data 106 | */ 107 | function printColumns(heading, data) { 108 | const columns = columnify(data, {}); 109 | const spacer = EOL + EOL; 110 | 111 | process.stdout.write(heading); 112 | process.stdout.write(spacer); 113 | 114 | if (columns) { 115 | process.stdout.write(columns); 116 | process.stdout.write(spacer); 117 | } 118 | } 119 | 120 | /** 121 | * Check we got everything we need: 122 | * - stylelint config (by cosmiconfig) 123 | * - CLI arguments 124 | */ 125 | function validate(cosmiconfig) { 126 | if (!cosmiconfig) { 127 | printColumns( 128 | chalk.red( 129 | `Oops, no stylelint config found, we support cosmiconfig...${EOL}` + 130 | chalk.cyan('https://github.com/davidtheclark/cosmiconfig') 131 | ) 132 | ); 133 | 134 | return process.exit(1); 135 | } 136 | 137 | if ( 138 | !argv.unused && 139 | !argv.deprecated && 140 | !argv.invalid && 141 | !argv.current && 142 | !argv.available 143 | ) { 144 | printColumns( 145 | chalk.red(`Oops, one of the command line Options must be set...${EOL}`) 146 | ); 147 | yargs.showHelp(); 148 | 149 | return process.exit(1); 150 | } 151 | 152 | return cosmiconfig; 153 | } 154 | 155 | /** 156 | * Get user rules 157 | * Gather rules from `extends` as well 158 | */ 159 | function getUserRules(cosmiconfig) { 160 | const userConfig = cosmiconfig.config; 161 | const configPath = cosmiconfig.filepath; 162 | 163 | return ( 164 | Promise.resolve() 165 | // Handle `extends` 166 | .then(() => { 167 | // If `extends` is defined, use stylelint's extends resolver 168 | if (!_.isEmpty(userConfig.extends)) { 169 | // We need this to fetch and merge all `extends` from the provided config 170 | // This will also merge the `rules` on top of the `extends` rules 171 | const linter = stylelint.createLinter(); 172 | 173 | // stylelint used cosmiconfig v4 at version 9, bumped to v5 somewhere 9.x 174 | // So we pass both arguments to `load`, works in both versions 175 | return linter._extendExplorer.load(configPath, configPath); 176 | } 177 | }) 178 | .then(extendedConfig => { 179 | const finalConfig = extendedConfig ? extendedConfig.config : userConfig; 180 | 181 | rules.userRulesNames = _.sortedUniq(_.keys(finalConfig.rules)); 182 | }) 183 | ); 184 | } 185 | 186 | /** 187 | * Find all deprecated rules from the list of stylelint rules 188 | * 189 | * @returns {Promise} 190 | */ 191 | function findDeprecatedStylelintRules() { 192 | if (!argv.deprecated && !argv.unused) { 193 | return Promise.resolve(); 194 | } 195 | 196 | const isDeprecatedPromises = _.map(rules.stylelintAll, isDDeprecated); 197 | 198 | return Promise.all(isDeprecatedPromises).then(rulesIsDeprecated => { 199 | rules.stylelintDeprecated = _.filter( 200 | rules.stylelintAll, 201 | (rule, index) => rulesIsDeprecated[index] 202 | ); 203 | 204 | // Don't remove, just for testing deprecated rules matching 205 | if (argv.testDeprecated) { 206 | rules.stylelintDeprecated.push('color-hex-case', 'color-hex-length'); 207 | } 208 | 209 | if (argv.unused) { 210 | rules.stylelintNoDeprecated = _.difference( 211 | rules.stylelintAll, 212 | rules.stylelintDeprecated 213 | ); 214 | } 215 | 216 | return rules.stylelintDeprecated; 217 | }); 218 | } 219 | 220 | /** 221 | * Print a nice header 222 | */ 223 | function printBegin() { 224 | printColumns(chalk.whiteBright.bold(`stylelint-find-rules v${pkg.version}`)); 225 | } 226 | 227 | /** 228 | * Print currently configured rules 229 | */ 230 | function printUserCurrent() { 231 | if (!argv.current) { 232 | return; 233 | } 234 | 235 | const heading = chalk.blue.underline( 236 | 'CURRENT: Currently configured user rules:' 237 | ); 238 | const rulesToPrint = _.map(rules.userRulesNames, rule => { 239 | return { 240 | rule, 241 | url: chalk.cyan(`https://stylelint.io/user-guide/rules/${rule}/`), 242 | }; 243 | }); 244 | 245 | printColumns(heading, rulesToPrint); 246 | } 247 | 248 | /** 249 | * Print all available stylelint rules 250 | */ 251 | function printAllAvailable() { 252 | if (!argv.available) { 253 | return; 254 | } 255 | 256 | const heading = chalk.blue.underline( 257 | 'AVAILABLE: All available stylelint rules:' 258 | ); 259 | const rulesToPrint = _.map(rules.stylelintAll, rule => { 260 | return { 261 | rule, 262 | url: chalk.cyan(`https://stylelint.io/user-guide/rules/${rule}/`), 263 | }; 264 | }); 265 | 266 | printColumns(heading, rulesToPrint); 267 | } 268 | 269 | /** 270 | * Print configured rules that are no longer available 271 | */ 272 | function printConfiguredUnavailable() { 273 | if (!argv.invalid) { 274 | return; 275 | } 276 | 277 | const configuredUnavailable = _.difference( 278 | rules.userRulesNames, 279 | rules.stylelintAll 280 | ); 281 | 282 | if (!configuredUnavailable.length) { 283 | return; 284 | } 285 | 286 | const heading = chalk.red.underline( 287 | 'INVALID: Configured rules that are no longer available:' 288 | ); 289 | const rulesToPrint = _.map(configuredUnavailable, rule => { 290 | return { 291 | rule: chalk.redBright(rule), 292 | }; 293 | }); 294 | 295 | printColumns(heading, rulesToPrint); 296 | } 297 | 298 | /** 299 | * Print user configured rules that are deprecated 300 | */ 301 | function printUserDeprecated() { 302 | if (!argv.deprecated) { 303 | return; 304 | } 305 | 306 | const userDeprecated = _.intersection( 307 | rules.stylelintDeprecated, 308 | rules.userRulesNames 309 | ); 310 | 311 | if (!userDeprecated.length) { 312 | return; 313 | } 314 | 315 | const heading = chalk.red.underline( 316 | 'DEPRECATED: Configured rules that are deprecated:' 317 | ); 318 | const rulesToPrint = _.map(userDeprecated, rule => { 319 | return { 320 | rule: chalk.redBright(rule), 321 | url: chalk.cyan(`https://stylelint.io/user-guide/rules/${rule}/`), 322 | }; 323 | }); 324 | 325 | printColumns(heading, rulesToPrint); 326 | } 327 | 328 | /** 329 | * Print available stylelint rules that the user hasn't configured yet 330 | */ 331 | function printUserUnused() { 332 | if (!argv.unused) { 333 | return; 334 | } 335 | 336 | const userUnconfigured = _.difference( 337 | rules.stylelintNoDeprecated, 338 | rules.userRulesNames 339 | ); 340 | let heading; 341 | 342 | if (!userUnconfigured.length) { 343 | heading = chalk.green('All rules are up-to-date!'); 344 | printColumns(heading); 345 | 346 | return; 347 | } 348 | 349 | const rulesToPrint = _.map(userUnconfigured, rule => { 350 | return { 351 | rule, 352 | url: chalk.cyan(`https://stylelint.io/user-guide/rules/${rule}/`), 353 | }; 354 | }); 355 | 356 | heading = chalk.blue.underline( 357 | 'UNUSED: Available rules that are not configured:' 358 | ); 359 | 360 | printColumns(heading, rulesToPrint); 361 | } 362 | 363 | /** 364 | * Print how long it took the tool to execute 365 | */ 366 | function printTimingAndExit(startTime) { 367 | const execTime = time() - startTime; 368 | 369 | printColumns(chalk.green(`Finished in: ${execTime.toFixed()}ms`)); 370 | process.exit(0); 371 | } 372 | 373 | /** 374 | * Hit it 375 | */ 376 | function init() { 377 | const startTime = time(); 378 | 379 | process.on('unhandledRejection', handleError); 380 | 381 | const explorer = cosmiconfig('stylelint'); 382 | let configPromise; 383 | 384 | if (argv.config) { 385 | // Ref: https://github.com/davidtheclark/cosmiconfig#explorerload 386 | configPromise = explorer.load(argv.config); 387 | } else { 388 | configPromise = explorer.search(); 389 | } 390 | 391 | configPromise 392 | .then(validate) 393 | .then(getUserRules) 394 | .then(findDeprecatedStylelintRules) 395 | .then(printBegin) 396 | .then(printUserCurrent) 397 | .then(printAllAvailable) 398 | .then(printUserUnused) 399 | .then(printUserDeprecated) 400 | .then(printConfiguredUnavailable) 401 | .then(printTimingAndExit.bind(null, startTime)) 402 | .catch(handleError); 403 | } 404 | 405 | init(); 406 | --------------------------------------------------------------------------------