diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b4458e1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[test/fixtures/*] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..88e6b03 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,3 @@ +[ignore] + +[include] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..049e38d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules/ +temp/ +coverage +.idea diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..a9ccf70 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,21 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 4, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "double", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "white": true +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..140ec0c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - '0.11' + - '0.10' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2bf8e61 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright 2014 Charlie Dowler + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..94f3594 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +## gulp-flowtype [![Build Status][travis-image]][travis-url] + +> Run [Facebook's Flow](http://flowtype.org/) in your gulp pipeline + +![gulp-flowtype example](screenshot.png) + +## Installation +```shell +$ npm install --save gulp-flowtype +``` + +### Usage + +```js +var flow = require('gulp-flowtype'); +gulp.task('typecheck', function() { + return gulp.src('./*.js') + .pipe(flow()); +}); +``` + + +[travis-url]: http://travis-ci.org/charliedowler/gulp-flowtype +[travis-image]: https://secure.travis-ci.org/charliedowler/gulp-flowtype.png?branch=master diff --git a/flow-to-jshint.js b/flow-to-jshint.js new file mode 100644 index 0000000..fa335a2 --- /dev/null +++ b/flow-to-jshint.js @@ -0,0 +1,22 @@ +var stylish = require('jshint-stylish/stylish').reporter; +var path = require('path'); + +module.exports = function(result) { + var errors = result.errors; + errors.forEach(function(error) { + error.message.forEach(function(message) { + message.error = { + reason: message.descr, + code: message.code ? 'W' : 'E', + character: message.start, + line: message.line + }; + for (var i in message) { + error[i] = message[i]; + } + error.file = path.basename(message.path); + }); + delete error.message; + }); + stylish(errors) +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..aa51ab9 --- /dev/null +++ b/index.js @@ -0,0 +1,68 @@ +var through = require("through2"), + gutil = require("gulp-util"), + exec = require('child_process').exec, + path = require('path'), + flowBin = require('flow-bin'), + flowToJshint = require('./flow-to-jshint'); +var fs = require('fs'); + +function executeFlow(PATH, callback) { + exec([flowBin, 'check', '/' + path.relative('/', PATH), '--json'].join(' '), function (err, stdout, stderr) { + if (callback) callback(); + var result = JSON.parse(stdout); + if (result.passed) { + console.log('Passed Flow'); + return; + } + flowToJshint(result); + }); +} + +module.exports = function (param) { + "use strict"; + + // see "Writing a plugin" + // https://github.com/gulpjs/gulp/blob/master/docs/writing-a-plugin/README.md + function flow(file, enc, callback) { + /*jshint validthis:true*/ + + // Do nothing if no contents + if (file.isNull()) { + this.push(file); + return callback(); + } + + if (file.isStream()) { + + // http://nodejs.org/api/stream.html + // http://nodejs.org/api/child_process.html + // https://github.com/dominictarr/event-stream + + // accepting streams is optional + this.emit("error", + new gutil.PluginError("gulp-flow", "Stream content is not supported")); + return callback(); + } + + if (file.isBuffer()) { + var PATH = path.dirname(file.path); + var configPath = path.join(PATH, '.flowconfig'); + if (fs.existsSync(configPath)) { + executeFlow(PATH); + } + else { + fs.writeFile(configPath, '[ignore]\n[include]', function() { + executeFlow(PATH, function() { + fs.unlink(configPath); + }); + }); + } + + this.push(file); + + } + return callback(); + } + + return through.obj(flow); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..68d8c4d --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ + +{ + "name": "gulp-flowtype", + "version": "0.1.0", + "description": "A plugin for Gulp", + "keywords": [ + "gulpplugin" + ], + "author": { + "name": "Charlie Dowler", + "email": "", + "url": "https://github.com/charliedowler" + }, + "repository": "charliedowler/gulp-flowtype", + "scripts": { + "test": "istanbul test _mocha --report html -- test/*.js --reporter spec", + "coveralls": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" + }, + "dependencies": { + "through2": "*", + "gulp-util": "~2.2.0", + "jshint-stylish": "~1.0.0", + "flow-bin": "~0.1.1" + }, + "devDependencies": { + "mocha": "*", + "coveralls": "*", + "mocha-lcov-reporter": "*", + "istanbul": "*", + "event-stream": "*", + "should": "~2.1.0" + }, + "engines": { + "node": ">=0.8.0", + "npm": ">=1.2.10" + }, + "license": "MIT" +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..7068642 Binary files /dev/null and b/screenshot.png differ diff --git a/test/expected/hello.txt b/test/expected/hello.txt new file mode 100644 index 0000000..ed81d07 --- /dev/null +++ b/test/expected/hello.txt @@ -0,0 +1,2 @@ +Hello +World \ No newline at end of file diff --git a/test/fixtures/hello.js b/test/fixtures/hello.js new file mode 100644 index 0000000..de7f723 --- /dev/null +++ b/test/fixtures/hello.js @@ -0,0 +1,3 @@ +/* @flow */ + +var test: string = 0; \ No newline at end of file diff --git a/test/main.js b/test/main.js new file mode 100644 index 0000000..73be2c6 --- /dev/null +++ b/test/main.js @@ -0,0 +1,107 @@ +/*global describe, it*/ +"use strict"; + +var fs = require("fs"), + es = require("event-stream"), + should = require("should"); + +require("mocha"); + +delete require.cache[require.resolve("../")]; + +var gutil = require("gulp-util"), + flow = require("../"); + +describe("gulp-flow", function () { + + it("should produce expected file via buffer", function (done) { + + var srcFile = new gutil.File({ + path: "test/fixtures/hello.js", + cwd: "test/", + base: "test/fixtures", + contents: fs.readFileSync("test/fixtures/hello.js") + }); + + var stream = flow("World"); + + stream.on("error", function(err) { + should.exist(err); + done(err); + }); + + stream.on("data", function (newFile) { + + should.exist(newFile); + should.exist(newFile.contents); + }); + + stream.on('end', function() { + setTimeout(function() { + done(); + }, 1500); + }); + stream.write(srcFile); + stream.end(); + }); + + it("should error on stream", function (done) { + + var srcFile = new gutil.File({ + path: "test/fixtures/hello.js", + cwd: "test/", + base: "test/fixtures", + contents: fs.createReadStream("test/fixtures/hello.js") + }); + + var stream = flow("World"); + + stream.on("error", function(err) { + should.exist(err); + done(); + }); + + stream.on("data", function (newFile) { + newFile.contents.pipe(es.wait(function(err, data) { + done(err); + })); + }); + + stream.write(srcFile); + stream.end(); + }); + + /* + it("should produce expected file via stream", function (done) { + + var srcFile = new gutil.File({ + path: "test/fixtures/hello.txt", + cwd: "test/", + base: "test/fixtures", + contents: fs.createReadStream("test/fixtures/hello.txt") + }); + + var stream = flow("World"); + + stream.on("error", function(err) { + should.exist(err); + done(); + }); + + stream.on("data", function (newFile) { + + should.exist(newFile); + should.exist(newFile.contents); + + newFile.contents.pipe(es.wait(function(err, data) { + should.not.exist(err); + data.should.equal(String(expectedFile.contents)); + done(); + })); + }); + + stream.write(srcFile); + stream.end(); + }); + */ +});