├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .npmrc ├── .gitignore ├── index.js ├── package.json ├── LICENSE ├── README.md └── test └── fixture.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: LitoMore 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | tag-version-prefix='' 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn.lock 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: 24 14 | - run: npm install 15 | - run: npm test 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import process from 'node:process'; 4 | import concat from 'concat-stream'; 5 | import stripAnsi from 'strip-ansi'; 6 | 7 | const errors = {}; 8 | 9 | if (process.stdin.isTTY) { 10 | console.log('\nTo use this module, pipe output from `xo` into it:\n\nxo | xo-summary'); 11 | process.exit(1); 12 | } 13 | 14 | const isNameOnlyMode = process.argv.includes('--name-only'); 15 | 16 | process.stdin.pipe(concat(buffer => { 17 | for (let line of buffer 18 | .toString() 19 | .split('\n')) { 20 | line = stripAnsi(line); 21 | if (!line || !/\s+\d+:\d+\s+/.test(line)) { 22 | continue; 23 | } 24 | 25 | const error = line.split(/\s+\d+:\d+\s+/)[1].replace(isNameOnlyMode ? /^.+\s{2,}/ : /\s+\S+$/, ''); 26 | const count = errors[error] || 0; 27 | errors[error] = count + 1; 28 | } 29 | 30 | for (const error of Object.keys(errors) 31 | .sort((a, b) => errors[b] - errors[a])) { 32 | const count = errors[error]; 33 | console.log(String(count).padEnd(6, ' '), error); 34 | } 35 | })); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xo-summary", 3 | "version": "2.1.1", 4 | "description": "Display output from XO as a list of style errors, ordered by count", 5 | "type": "module", 6 | "bin": "index.js", 7 | "preferGlobal": true, 8 | "engines": { 9 | "node": ">=18" 10 | }, 11 | "files": [ 12 | "index.js" 13 | ], 14 | "scripts": { 15 | "test": "xo && npm run test:default && npm run test:name-only", 16 | "test:default": "cat test/fixture.txt | node index.js", 17 | "test:name-only": "cat test/fixture.txt | node index.js --name-only" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/LitoMore/xo-summary.git" 22 | }, 23 | "author": "LitoMore", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/LitoMore/xo-summary/issues" 27 | }, 28 | "homepage": "https://github.com/LitoMore/xo-summary#readme", 29 | "dependencies": { 30 | "concat-stream": "^2.0.0", 31 | "strip-ansi": "^7.1.0" 32 | }, 33 | "devDependencies": { 34 | "xo": "^1.2.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) LitoMore 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 | # xo-summary 2 | 3 | [![](https://img.shields.io/npm/v/xo-summary.svg)](https://www.npmjs.com/package/xo-summary) 4 | [![](https://img.shields.io/npm/l/xo-summary.svg)](https://github.com/LitoMore/xo-summary/blob/main/LICENSE) 5 | [![](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) 6 | 7 | Display output from `xo` as a list of style errors, ordered by count. 8 | 9 | _This package is inspired from [standard-summary](https://github.com/zeke/standard-summary)._ 10 | 11 | ## Install 12 | 13 | ```bash 14 | $ npm install -g xo xo-summary 15 | ``` 16 | 17 | ## Usage 18 | 19 | `cd` into your favorite javascript project, then pipe the output of xo into xo-summary: 20 | 21 | ``` 22 | $ cd my/project 23 | $ xo | xo-summary 24 | 25 | 17 Missing semicolon. 26 | 8 Expected indentation of 1 tab but found 2 spaces. 27 | 4 Expected blank line between class members. 28 | 3 Expected an assignment or function call and instead saw an expression. 29 | 1 Expected blank line before this statement. 30 | 1 Callbacks must be listed after all other props 31 | 1 Unable to resolve path to module pages/login. 32 | 1 Unable to resolve path to module pages/messages. 33 | 1 Unable to resolve path to module pages/settings. 34 | 1 Shorthand props must be listed before all other props 35 | ``` 36 | 37 | You can also use the `--name-only` option to display only the rule names: 38 | 39 | ``` 40 | $ cd my/project 41 | $ xo | xo-summary --name-only 42 | 43 | 17 semi 44 | 8 indent 45 | 4 lines-between-class-members 46 | 3 no-unused-expressions 47 | 3 import/no-unresolved 48 | 2 react/jsx-sort-props 49 | 1 padding-line-between-statements 50 | ``` 51 | 52 | ## Related 53 | 54 | - [XO](https://github.com/xojs/xo) - JavaScript happiness style linter 55 | 56 | ## License 57 | 58 | MIT © [LitoMore](https://github.com/LitoMore) 59 | -------------------------------------------------------------------------------- /test/fixture.txt: -------------------------------------------------------------------------------- 1 | test.js:1:13 2 | ✖ 1:13 Missing semicolon. semi 3 | ✖ 3:28 Missing semicolon. semi 4 | ✖ 4:31 Missing semicolon. semi 5 | ✖ 11:16 Missing semicolon. semi 6 | ✖ 13:106 Missing semicolon. semi 7 | ✖ 16:3 Expected indentation of 1 tab but found 2 spaces. indent 8 | ✖ 16:92 Missing semicolon. semi 9 | ✖ 17:3 Expected indentation of 1 tab but found 2 spaces. indent 10 | ✖ 17:19 Missing semicolon. semi 11 | ✖ 18:3 Missing semicolon. semi 12 | ✖ 21:3 Expected indentation of 1 tab but found 2 spaces. indent 13 | ✖ 21:94 Missing semicolon. semi 14 | ✖ 22:3 Expected an assignment or function call and instead saw an expression. no-unused-expressions 15 | ✖ 22:3 Expected indentation of 1 tab but found 2 spaces. indent 16 | ✖ 22:65 Missing semicolon. semi 17 | ✖ 23:3 Missing semicolon. semi 18 | ✖ 26:3 Expected indentation of 1 tab but found 2 spaces. indent 19 | ✖ 26:78 Missing semicolon. semi 20 | ✖ 27:3 Expected an assignment or function call and instead saw an expression. no-unused-expressions 21 | ✖ 27:3 Expected indentation of 1 tab but found 2 spaces. indent 22 | ✖ 27:65 Missing semicolon. semi 23 | ✖ 28:3 Missing semicolon. semi 24 | ✖ 31:3 Expected indentation of 1 tab but found 2 spaces. indent 25 | ✖ 31:61 Missing semicolon. semi 26 | ✖ 32:3 Expected an assignment or function call and instead saw an expression. no-unused-expressions 27 | ✖ 32:3 Expected indentation of 1 tab but found 2 spaces. indent 28 | ✖ 32:65 Missing semicolon. semi 29 | ✖ 33:3 Missing semicolon. semi 30 | 31 | src/pages/login/index.js:10:2 32 | ✖ 10:2 Expected blank line between class members. lines-between-class-members 33 | ✖ 13:2 Expected blank line between class members. lines-between-class-members 34 | ✖ 20:2 Expected blank line between class members. lines-between-class-members 35 | ✖ 31:3 Expected blank line before this statement. padding-line-between-statements 36 | ✖ 33:2 Expected blank line between class members. lines-between-class-members 37 | ✖ 41:7 Callbacks must be listed after all other props react/jsx-sort-props 38 | 39 | src/app.js:5:19 40 | ✖ 5:19 Unable to resolve path to module pages/login. import/no-unresolved 41 | ✖ 6:22 Unable to resolve path to module pages/messages. import/no-unresolved 42 | ✖ 7:22 Unable to resolve path to module pages/settings. import/no-unresolved 43 | 44 | src/pages/settings/index.js:61:53 45 | ✖ 61:53 Shorthand props must be listed before all other props react/jsx-sort-props 46 | 47 | 38 errors 48 | --------------------------------------------------------------------------------