├── .gitattributes ├── .npmrc ├── .prettierignore ├── .gitignore ├── TODO.md ├── tests ├── expected │ └── empty.md ├── fixtures │ ├── coffee.coffee │ ├── line.styl │ ├── block.scss │ ├── jsdoc.js │ ├── comments.jade │ ├── typescript.ts │ ├── block.styl │ ├── block.sass │ └── handlebars.hbs └── stream-spec.js ├── .prettierrc.yml ├── .travis.yml ├── .editorconfig ├── .jshintrc ├── LICENSE ├── package.json ├── lib └── reporter.js ├── gulpfile.js ├── index.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | tests/fixtures/ 2 | tests/expected/ 3 | .idea/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .idea/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ### TODOs 2 | | Filename | line # | TODO 3 | |:------|:------:|:------ -------------------------------------------------------------------------------- /tests/expected/empty.md: -------------------------------------------------------------------------------- 1 | ### TODOs 2 | | Filename | line # | TODO 3 | |:------|:------:|:------ 4 | -------------------------------------------------------------------------------- /tests/fixtures/coffee.coffee: -------------------------------------------------------------------------------- 1 | # TODO: Do something 2 | callFunction 'hello', true 3 | # FIXME:Fix something 4 | -------------------------------------------------------------------------------- /tests/fixtures/line.styl: -------------------------------------------------------------------------------- 1 | body 2 | // This is a comment without a todo 3 | color white 4 | // FIXME: use fixmes as well 5 | form 6 | color yellow 7 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | arrowParens: avoid 2 | bracketSpacing: true 3 | printWidth: 120 4 | semi: true 5 | singleQuote: true 6 | tabWidth: 4 7 | trailingComma: es5 8 | useTabs: false 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 6 5 | - 8 6 | - 9 7 | - 10 8 | - 11 9 | cache: 10 | directories: 11 | - node_modules 12 | notifications: 13 | email: false 14 | -------------------------------------------------------------------------------- /tests/fixtures/block.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | color: #000; 3 | display: block; 4 | // TODO: add another class 5 | } 6 | 7 | .test { 8 | .foo, 9 | .bar { 10 | margin: 10px; 11 | padding: 10px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/jsdoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fun = function() { 3 | return null; 4 | }; 5 | /** 6 | * Controller 7 | * 8 | * 9 | * @description: Yay 10 | * 11 | * 12 | * @author: SC 13 | ***/ 14 | // TODO Show my TODO please 15 | var a = true; 16 | 17 | if (a) { 18 | fun(); 19 | } 20 | -------------------------------------------------------------------------------- /tests/fixtures/comments.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | head 3 | 4 | body 5 | //this is a a comment 6 | .cluster class 7 | //- this is also a comment 8 | #oh-no yes 9 | //TODO: this is a todo 10 | //TODOTHERE this isnt a todo 11 | //FIXME also should be caught 12 | - var string = "//this isnt a todo as well" 13 | -------------------------------------------------------------------------------- /tests/fixtures/typescript.ts: -------------------------------------------------------------------------------- 1 | // TODO: change to public 2 | class Greeter { 3 | constructor(public greeting: string) { } 4 | greet() { 5 | return "

" + this.greeting + "

"; 6 | } 7 | }; 8 | var greeter = new Greeter("Hello, world!"); 9 | var str = greeter.greet(); 10 | /* 11 | * FIXME: use jquery 12 | */ 13 | document.body.innerHTML = str; 14 | -------------------------------------------------------------------------------- /tests/fixtures/block.styl: -------------------------------------------------------------------------------- 1 | body 2 | // This is a comment without a todo 3 | color white 4 | /* single line comment without todo */ 5 | /* TODO: single line comment with a todo */ 6 | /*! FIXME: single line comment with a todo */ 7 | /* 8 | multiple line comment without todo 9 | yes another line 10 | */ 11 | form 12 | color yellow 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | 8 | indent_style = space 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.{js,json,html,jade,md}] 15 | indent_size = 4 16 | 17 | [*.js] 18 | jslint_happy = true 19 | -------------------------------------------------------------------------------- /tests/fixtures/block.sass: -------------------------------------------------------------------------------- 1 | /* This comment is 2 | * TODO: it will appear in the CSS output. 3 | * FIXME: this is a block comment too 4 | * several lines long. 5 | * since it uses the CSS comment syntax, 6 | * */ 7 | body { color: black; } 8 | 9 | // These comments are only one line long each. 10 | // FIXME: They won't appear in the CSS output, 11 | // since they use the single-line comment syntax. 12 | a { color: green; } 13 | 14 | //TODO: improve this syntax 15 | -------------------------------------------------------------------------------- /tests/fixtures/handlebars.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{!-- TODO: only output this author names if an author exists --}} 3 | {{#if author}} 4 |

{{firstName}} {{lastName}}

5 | {{/if}} 6 |
7 |
8 | {{! FIXME: This comment will not be in the output }} 9 | 10 |
11 | 12 |
13 | {{! TODO: Multiple line comment}} {{! TODO: and again}} 14 |
15 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "dojo": true 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Gilad Peleg (https://www.giladpeleg.com) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-todo", 3 | "version": "7.1.1", 4 | "description": "Generate a TODO.md file from comments of files in stream", 5 | "repository": "pgilad/gulp-todo", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Gilad Peleg", 9 | "email": "giladp007@gmail.com", 10 | "url": "http://giladpeleg.com" 11 | }, 12 | "main": "index.js", 13 | "files": [ 14 | "index.js", 15 | "lib" 16 | ], 17 | "engines": { 18 | "node": ">=6.0.0" 19 | }, 20 | "scripts": { 21 | "test": "mocha -R spec tests/*.js", 22 | "prettier": "prettier --write '**/*.js'", 23 | "watchTest": "mocha --watch -R spec tests/*.js" 24 | }, 25 | "keywords": [ 26 | "gulpplugin", 27 | "gulp", 28 | "js", 29 | "fixme", 30 | "comments", 31 | "todo", 32 | "list", 33 | "parse", 34 | "generator", 35 | "ci" 36 | ], 37 | "dependencies": { 38 | "ansi-colors": "^3.2.3", 39 | "fancy-log": "^1.3.3", 40 | "leasot": "^7.3.1", 41 | "lodash.defaults": "^4.2.0", 42 | "plugin-error": "^1.0.1", 43 | "through2": "^3.0.0", 44 | "vinyl": "^2.2.0" 45 | }, 46 | "devDependencies": { 47 | "gulp": "^4.0.0", 48 | "gulp-header": "^2.0.7", 49 | "gulp-wrap": "^0.14.0", 50 | "mocha": "^5.2.0", 51 | "prettier": "^1.16.4", 52 | "should": "^13.2.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/reporter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const leasot = require('leasot'); 4 | const path = require('path'); 5 | const PluginError = require('plugin-error'); 6 | const through = require('through2'); 7 | 8 | const pluginName = 'gulp-todo'; 9 | 10 | /** 11 | * 12 | * @param {string|function} [reporter] The reporter to use. See https://pgilad.github.io/leasot/enums/builtinreporters.html 13 | * @param {Object} [reportOptions={}] Passed directly to `leasot.report` - See https://pgilad.github.io/leasot/index.html#report 14 | * @param {string} [fileName] Pass along options to the reporter, and also if you pass a `fileName` - it will rename the filename in stream 15 | * @returns {*} 16 | */ 17 | module.exports = function(reporter, { reportOptions = {}, fileName } = {}) { 18 | if (!reporter) { 19 | throw new PluginError('Reporter is required'); 20 | } 21 | return through.obj(function(file, enc, cb) { 22 | if (file.isNull()) { 23 | return cb(null, file); 24 | } 25 | 26 | if (file.isStream()) { 27 | return cb(new PluginError(pluginName, 'Streaming not supported')); 28 | } 29 | 30 | // replace contents with requested reporter contents 31 | if (file.todos && file.todos.length) { 32 | const newContents = leasot.report(file.todos, reporter, reportOptions); 33 | 34 | if (fileName) { 35 | file.path = path.join(file.base, fileName); 36 | } 37 | file.contents = Buffer.from(newContents); 38 | } 39 | 40 | cb(null, file); 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var gulp = require('gulp'); 3 | var todo = require('./index'); 4 | var wrap = require('gulp-wrap'); 5 | var header = require('gulp-header'); 6 | 7 | gulp.task('xml', function() { 8 | gulp.src('./**/*.js', { 9 | base: './', 10 | }) 11 | .pipe( 12 | todo({ 13 | fileName: 'todo.xml', 14 | reporter: 'custom', 15 | transformComment: function(file, line, text, kind) { 16 | return [ 17 | '', 18 | text, 19 | '', 20 | ]; 21 | }, 22 | transformHeader: function() { 23 | return ''; 24 | }, 25 | }) 26 | ) 27 | .pipe(wrap(' <%= contents %>')) 28 | .pipe(header('')) 29 | .pipe(gulp.dest('./')); 30 | }); 31 | 32 | gulp.task('json', function() { 33 | gulp.src('./**/*.js', { 34 | base: './', 35 | }) 36 | .pipe( 37 | todo({ 38 | fileName: 'todo.json', 39 | reporter: 'json', 40 | absolute: true, 41 | }) 42 | ) 43 | .pipe(gulp.dest('./')); 44 | }); 45 | 46 | gulp.task('multiple', function() { 47 | gulp.src('./**/*.js', { 48 | base: './', 49 | }) 50 | .pipe(todo()) 51 | .pipe(gulp.dest('./')) 52 | .pipe( 53 | todo.reporter('xml', { 54 | fileName: 'todo.xml', 55 | }) 56 | ) 57 | .pipe(gulp.dest('./')); 58 | }); 59 | 60 | gulp.task('default', function() { 61 | gulp.src(['./index.js', './lib/**.*.js'], { 62 | base: './', 63 | }) 64 | .pipe(todo()) 65 | .pipe(gulp.dest('./')); 66 | }); 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const colors = require('ansi-colors'); 4 | const fancyLog = require('fancy-log'); 5 | const leasot = require('leasot'); 6 | const path = require('path'); 7 | const PluginError = require('plugin-error'); 8 | const through = require('through2'); 9 | const Vinyl = require('vinyl'); 10 | 11 | const pluginName = 'gulp-todo'; 12 | 13 | function logCommentsToConsole(comments) { 14 | comments.forEach(function(comment) { 15 | const isTodo = /todo/i.test(comment.tag); 16 | const commentType = isTodo ? colors.cyan(comment.tag) : colors.magenta(comment.tag); 17 | const commentLocation = '@' + colors.gray(comment.file + ':' + comment.line); 18 | fancyLog(commentType, comment.text, commentLocation); 19 | }); 20 | } 21 | 22 | /** 23 | * 24 | * @param {boolean} [absolute=false] Output absolute paths of files (as available via `file.path`) 25 | * @param {string} [fileName=TODO.md] Specify the output filename 26 | * @param {Object} [parseOptions={}] Passed directly to `leasot.parse` - See [ParseConfig](https://pgilad.github.io/leasot/interfaces/parseconfig.html) 27 | * @param {string|function} [reporter=markdown] The reporter to use. See https://pgilad.github.io/leasot/enums/builtinreporters.html 28 | * @param {Object} [reportOptions={}] Passed directly to `leasot.report` - See https://pgilad.github.io/leasot/index.html#report 29 | * @param {boolean} [skipUnsupported=false] Whether to skip unsupported files or not 30 | * @param {boolean} [verbose=false] Output comments to console as well 31 | * @returns {*} 32 | */ 33 | module.exports = function({ 34 | absolute = false, 35 | fileName = 'TODO.md', 36 | parseOptions = {}, 37 | reporter = 'markdown', 38 | reportOptions = {}, 39 | skipUnsupported = false, 40 | verbose = false, 41 | } = {}) { 42 | let firstFile; 43 | const comments = []; 44 | 45 | return through.obj( 46 | function collectTodos(file, enc, cb) { 47 | if (file.isNull()) { 48 | cb(null, file); 49 | return; 50 | } 51 | 52 | if (file.isStream()) { 53 | cb(new PluginError(pluginName, 'Streaming not supported')); 54 | return; 55 | } 56 | firstFile = firstFile || file; 57 | 58 | //get extension - assume .js as default 59 | const ext = path.extname(file.path) || '.js'; 60 | 61 | //check if parser for filetype exists 62 | if (!leasot.isExtensionSupported(ext)) { 63 | if (!skipUnsupported) { 64 | const msg = `File: ${file.path} with extension ${colors.red(ext)} is not supported`; 65 | return cb(new PluginError(pluginName, msg)); 66 | } 67 | if (verbose) { 68 | const msg = `Skipping file ${file.path} with extension ${colors.red(ext)} as it is unsupported`; 69 | fancyLog(msg); 70 | } 71 | return cb(); 72 | } 73 | const filePath = absolute ? file.path : (file.path && file.relative) || file.path; 74 | 75 | const parsedComments = leasot.parse(file.contents.toString(), { 76 | associateParser: parseOptions.associateParser, 77 | customParsers: parseOptions.customParsers, 78 | customTags: parseOptions.customTags, 79 | extension: ext, 80 | filename: filePath, 81 | withInlineFiles: parseOptions.withInlineFiles, 82 | }); 83 | if (verbose) { 84 | logCommentsToConsole(parsedComments); 85 | } 86 | comments.push(...parsedComments); 87 | cb(); 88 | }, 89 | function reportTodos(cb) { 90 | if (!firstFile) { 91 | return cb(); 92 | } 93 | const reporterContents = leasot.report(comments, reporter, reportOptions); 94 | 95 | const todoFile = new Vinyl({ 96 | base: firstFile.base, 97 | contents: Buffer.from(reporterContents), 98 | cwd: firstFile.cwd, 99 | path: path.join(firstFile.base, fileName), 100 | }); 101 | 102 | // also pass along comments object for future reporters 103 | todoFile.todos = comments; 104 | cb(null, todoFile); 105 | } 106 | ); 107 | }; 108 | 109 | const reporter = require('./lib/reporter'); 110 | module.exports.reporter = reporter; 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [gulp](https://github.com/wearefractal/gulp)-todo 2 | > Parse and output TODOs and FIXMEs from comments in your file in a stream 3 | 4 | [![NPM Version](http://img.shields.io/npm/v/gulp-todo.svg?style=flat)](https://npmjs.org/package/gulp-todo) 5 | [![NPM Downloads](http://img.shields.io/npm/dm/gulp-todo.svg?style=flat)](https://npmjs.org/package/gulp-todo) 6 | [![Build Status](http://img.shields.io/travis/pgilad/gulp-todo.svg?style=flat)](https://travis-ci.org/pgilad/gulp-todo) 7 | 8 | Parse your files in a gulp-stream, extracting todos/fixmes from comments and reporting them 9 | in a reporter to your choosing using [leasot](https://github.com/pgilad/leasot). 10 | 11 | Issues with the output should be reported on the [leasot issue tracker](https://github.com/pgilad/leasot/issues) 12 | 13 | **Supports latest `leasot` version `7.0.0`.** 14 | 15 | **Please upgrade carefully to version `7.0.0`, there were breaking changes in the `gulp-todo` API** 16 | 17 | ## Install 18 | 19 | Install with [npm](https://npmjs.org/package/gulp-todo) 20 | 21 | ```sh 22 | $ npm install --save-dev gulp-todo 23 | ``` 24 | 25 | ## Usage 26 | 27 | ```js 28 | const gulp = require('gulp'); 29 | const todo = require('gulp-todo'); 30 | 31 | // generate a todo.md from your javascript files 32 | gulp.task('todo', function() { 33 | gulp.src('js/**/*.js') 34 | .pipe(todo()) 35 | .pipe(gulp.dest('./')); 36 | // -> Will output a TODO.md with your todos 37 | }); 38 | 39 | // generate todo from your jade files 40 | gulp.task('todo-jade', function() { 41 | gulp.src('partials/**/*.jade') 42 | .pipe(todo({ fileName: 'jade-todo.md' })) 43 | .pipe(gulp.dest('./')); 44 | // -> Will output a jade-todo.md with your todos 45 | }); 46 | 47 | // get filenames relative to project root (where your gulpfile is) 48 | gulp.task('todo-absolute', function() { 49 | gulp.src('js/**/*.js') 50 | .pipe(todo({ 51 | absolute: true 52 | })) 53 | .pipe(gulp.dest('./')); 54 | }); 55 | 56 | // get relative path filenames 57 | gulp.task('todo-absolute', function() { 58 | gulp.src('js/**/*.js', { base: '/' }) 59 | .pipe(todo()) 60 | .pipe(gulp.dest('./')); 61 | }); 62 | 63 | // create a json output of the comments (useful for CI such as jenkins) 64 | gulp.task('todo-json', function () { 65 | gulp.src('./**/*.js', { 66 | base: './' 67 | }) 68 | .pipe(todo({ 69 | fileName: 'todo.json', 70 | reporter: 'json' 71 | })) 72 | .pipe(gulp.dest('./')); 73 | }); 74 | 75 | // output once in markdown and then output a json file as well 76 | gulp.task('todo-reporters', function() { 77 | gulp.src('js/**/*.js') 78 | .pipe(todo()) 79 | .pipe(gulp.dest('./')) //output todo.md as markdown 80 | .pipe(todo.reporter('json', {fileName: 'todo.json'})) 81 | .pipe(gulp.dest('./')) //output todo.json as json 82 | }); 83 | 84 | 85 | // Delete the todo.md file if no todos were found 86 | const gulpIf = require('gulp-if'); 87 | const del = require('del'); 88 | const vinylPaths = require('vinyl-paths'); 89 | 90 | gulp.task('todo-delete', function() { 91 | gulp.src('js/**/*.js') 92 | .pipe(todo()) 93 | .pipe(gulpIf(function (file) { 94 | return file.todos && Boolean(file.todos.length); 95 | }, gulp.dest('./'), vinylPaths(del))); 96 | }); 97 | ``` 98 | 99 | #### Injecting the todo generated file into another template 100 | 101 | If you want to inject the generated todo stream into another file (say a `readme.md.template`) 102 | you can do the following: 103 | 104 | - Create `readme.md.template` file that contains the following marker, marking where you want to inject the generated todo file: 105 | 106 | ```md 107 | ### some previous content 108 | <%= marker %> 109 | ``` 110 | 111 | - Use the following code to inject into that markdown, creating a markdown file with the generated todo: 112 | 113 | ```js 114 | const fs = require('fs'); 115 | const path = require('path'); 116 | const gulp = require('gulp'); 117 | const todo = require('gulp-todo'); 118 | const template = require('lodash.template'); 119 | const through = require('through2'); 120 | 121 | gulp.task('default', function () { 122 | gulp.src('./js/**/*.js') 123 | .pipe(todo()) 124 | .pipe(through.obj(function (file, enc, cb) { 125 | //read and interpolate template 126 | const tmpl = fs.readFileSync('./readme.md.template', 'utf8'); 127 | const compiledTpl = template(tmpl); 128 | const newContents = compiledTpl({ 129 | 'marker': file.contents.toString() 130 | }); 131 | //change file name 132 | file.path = path.join(file.base, 'readme-new.md'); 133 | //replace old contents 134 | file.contents = Buffer.from(newContents); 135 | //push new file 136 | this.push(file); 137 | cb(); 138 | })) 139 | .pipe(gulp.dest('./')); 140 | }); 141 | ``` 142 | 143 | ## Supported Filetypes 144 | 145 | See https://github.com/pgilad/leasot#supported-languages 146 | 147 | ## API 148 | 149 | ### todo(options) 150 | 151 | `options` is an optional configuration object, see https://github.com/pgilad/gulp-todo/blob/master/index.js#L22-L32 152 | 153 | ### todo.reporter(reporter, options) 154 | 155 | `options` is an optional configuration object, see https://github.com/pgilad/gulp-todo/blob/master/lib/reporter.js#L10-L16 156 | 157 | Use another reporter in stream, will replace the contents of the output file. 158 | Must be used after `todo()`, since it uses the `file.todos` that are passed along. 159 | 160 | See the example in the [usage](#usage) 161 | 162 | ## License 163 | 164 | MIT © [Gilad Peleg](https://www.giladpeleg.com) 165 | -------------------------------------------------------------------------------- /tests/stream-spec.js: -------------------------------------------------------------------------------- 1 | /* global describe,it */ 2 | 'use strict'; 3 | 4 | const assert = require('assert'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const Vinyl = require('vinyl'); 8 | const todo = require('../index'); 9 | 10 | const streamFile = function(filename, stream) { 11 | const file = fs.readFileSync(filename); 12 | stream.write( 13 | new Vinyl({ 14 | path: filename, 15 | contents: Buffer.from(file.toString()), 16 | }) 17 | ); 18 | 19 | stream.end(); 20 | }; 21 | 22 | describe('gulp-todo streaming', function() { 23 | it('should output empty file when getting file with no comments', function(cb) { 24 | const stream = todo(); 25 | const files = []; 26 | 27 | const expected = fs.readFileSync('./tests/expected/empty.md', 'utf8').trim(); 28 | stream 29 | .on('data', function(file) { 30 | assert.equal(file.contents.toString(), expected); 31 | files.push(file); 32 | }) 33 | .on('end', function() { 34 | assert.equal(files.length, 1, 'Make sure only 1 file was outputted'); 35 | cb(); 36 | }); 37 | 38 | streamFile('./tests/stream-spec.js', stream); 39 | }); 40 | 41 | it('should parse a file with comments correctly', function(cb) { 42 | const stream = todo(); 43 | 44 | stream 45 | .on('data', function(file) { 46 | const _filename = path.basename(file.path); 47 | assert.equal(_filename, 'TODO.md'); 48 | assert.ok(/Do something/.test(file._contents.toString())); 49 | assert.ok(/coffee.coffee/.test(file._contents.toString())); 50 | }) 51 | .on('end', cb); 52 | 53 | streamFile('./tests/fixtures/coffee.coffee', stream); 54 | }); 55 | 56 | it('should output to the correct filename', function(cb) { 57 | const name = 'magic.md'; 58 | const stream = todo({ 59 | fileName: name, 60 | }); 61 | 62 | stream 63 | .on('data', function(file) { 64 | assert.equal(path.basename(file.path), name); 65 | }) 66 | .on('end', cb); 67 | 68 | streamFile('./index.js', stream); 69 | }); 70 | 71 | it('should work with verbose output', function(cb) { 72 | const stream = todo({ 73 | verbose: true, 74 | }); 75 | const output = []; 76 | 77 | const write = process.stdout.write; 78 | process.stdout.write = (function(stub) { 79 | return function(string) { 80 | stub.apply(process.stdout, arguments); 81 | output.push(string); 82 | }; 83 | })(process.stdout.write); 84 | 85 | stream 86 | .on('data', function(file) { 87 | const _filename = path.basename(file.path); 88 | assert.equal(_filename, 'TODO.md'); 89 | assert.ok(/Do something/.test(file._contents.toString())); 90 | assert.ok(/coffee.coffee/.test(file._contents.toString())); 91 | }) 92 | .on('end', function() { 93 | //restore write 94 | process.stdout.write = write; 95 | let result = output.join('\n'); 96 | assert(/TODO/.test(result)); 97 | assert(/coffee.coffee/.test(result)); 98 | cb(); 99 | }); 100 | 101 | streamFile('./tests/fixtures/coffee.coffee', stream); 102 | }); 103 | 104 | it('should use custom transformation for header', function(cb) { 105 | const stream = todo({ 106 | reportOptions: { 107 | transformHeader: function(kind) { 108 | return ['### //' + kind]; 109 | }, 110 | }, 111 | }); 112 | 113 | stream 114 | .on('data', function(file) { 115 | const contents = file._contents.toString(); 116 | assert(/### \/\/TODO/.test(contents)); 117 | }) 118 | .on('end', cb); 119 | 120 | streamFile('./index.js', stream); 121 | }); 122 | 123 | it('should use custom transformation for comment', function(cb) { 124 | const stream = todo({ 125 | reportOptions: { 126 | transformComment: function(file, line, text) { 127 | return ['* ' + text + ' (at ' + file + ':' + line + ')']; 128 | }, 129 | }, 130 | }); 131 | 132 | stream 133 | .on('data', function(file) { 134 | const contents = file._contents.toString(); 135 | assert(/\*\s*(\w+\s*)+\s*\(at.*coffee.coffee:[0-9]+\)/.test(contents)); 136 | }) 137 | .on('end', cb); 138 | 139 | streamFile('./tests/fixtures/coffee.coffee', stream); 140 | }); 141 | 142 | it('should throw if got an unsupported file extension', function(cb) { 143 | const stream = todo(); 144 | 145 | stream 146 | .on('error', function(err) { 147 | assert(err); 148 | assert(/is not supported/.test(err.message)); 149 | cb(); 150 | }) 151 | .on('end', cb); 152 | 153 | const file = './index.js'; 154 | const testFile = fs.readFileSync(file); 155 | 156 | stream.write( 157 | new Vinyl({ 158 | path: './index.unsupported', 159 | contents: Buffer.from(testFile.toString()), 160 | }) 161 | ); 162 | 163 | stream.end(); 164 | }); 165 | 166 | it('should skip on unsupported files when skip is true', function(cb) { 167 | const stream = todo({ 168 | skipUnsupported: true, 169 | }); 170 | 171 | const files = []; 172 | 173 | const expected = fs.readFileSync('./tests/expected/empty.md', 'utf8').trim(); 174 | stream 175 | .on('data', function(file) { 176 | assert.equal(file.contents.toString(), expected); 177 | files.push(file); 178 | }) 179 | .on('end', function() { 180 | assert.equal(files.length, 1, 'Make sure only 1 file was outputted'); 181 | cb(); 182 | }); 183 | 184 | const file = './index.js'; 185 | const testFile = fs.readFileSync(file); 186 | 187 | stream.write( 188 | new Vinyl({ 189 | path: './index.unsupported', 190 | contents: Buffer.from(testFile.toString()), 191 | }) 192 | ); 193 | 194 | stream.end(); 195 | }); 196 | 197 | it('should show a message about skipping unsupported files if verbose and skip unsupported is true', function(cb) { 198 | const stream = todo({ 199 | skipUnsupported: true, 200 | verbose: true, 201 | }); 202 | 203 | const files = []; 204 | 205 | const expected = fs.readFileSync('./tests/expected/empty.md', 'utf8').trim(); 206 | stream 207 | .on('data', function(file) { 208 | assert.equal(file.contents.toString(), expected); 209 | files.push(file); 210 | }) 211 | .on('end', function() { 212 | assert.equal(files.length, 1, 'Make sure only 1 file was outputted'); 213 | cb(); 214 | }); 215 | 216 | const file = './index.js'; 217 | const testFile = fs.readFileSync(file); 218 | 219 | stream.write( 220 | new Vinyl({ 221 | path: './index.unsupported', 222 | contents: Buffer.from(testFile.toString()), 223 | }) 224 | ); 225 | 226 | stream.end(); 227 | }); 228 | 229 | it('should parse a jade file', function(cb) { 230 | const stream = todo(); 231 | 232 | stream 233 | .on('data', function(file) { 234 | const _filename = path.basename(file.path); 235 | assert.equal(_filename, 'TODO.md'); 236 | const contents = file._contents.toString(); 237 | assert.ok(/this is a todo/.test(contents)); 238 | assert.ok(!/THERE this isnt a todo/.test(contents)); 239 | assert.ok(/also should be caught/.test(contents)); 240 | }) 241 | .on('end', cb); 242 | 243 | streamFile('./tests/fixtures/comments.jade', stream); 244 | }); 245 | }); 246 | --------------------------------------------------------------------------------