-
-
Save TimMensch/45df32352b9d32353ab8b5912362632b to your computer and use it in GitHub Desktop.
/* | |
Copyright 2017, Tim Mensch. | |
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. | |
*/ | |
"use strict"; | |
const gulp = require('gulp'); | |
const mapStream = require('map-stream'); | |
const newer = require('gulp-newer'); | |
const transform = require('vinyl-transform'); | |
const rename = require('gulp-rename'); | |
const strip = require('gulp-strip-comments'); | |
const fs = require('fs'); | |
const promisify = require('es6-promisify'); | |
const readFile = promisify(fs.readFile); | |
const cache = require('gulp-cached'); | |
const _ = require("lodash"); | |
let TJS; | |
let tv4; | |
/** | |
* Temporary (?) workaround for the fact that, sometimes, when there's a | |
* type|otherType given, it will give *both* a type and a oneOf object. | |
* The type in this case is completely wrong. This tree traversal deletes | |
* any "type" object members that exist alongside a "oneOf" member. | |
* | |
* @param {any} schema | |
*/ | |
function cleanSchema(schema) { | |
for (let key in schema) { | |
if (schema.hasOwnProperty(key)) { | |
let member = schema[ key ]; | |
if (typeof member === "object") { | |
if (member.type && member.oneOf) { | |
delete member.type; | |
} | |
cleanSchema(member); | |
} | |
} | |
} | |
} | |
function makeGenerator(typeToValidate, cb) { | |
return transform((filename) => { | |
if (!TJS) | |
TJS = require("typescript-json-schema"); | |
return mapStream((chunk, next) => { | |
// Thrown errors from within a mapStream callback | |
// aren't propagated, so I'm wrapping this in a | |
// try/catch. | |
try { | |
const schema = TJS.generateSchema( | |
TJS.getProgramFromFiles([ "./src/declarations/es6.d.ts", filename ]), | |
typeToValidate | |
); | |
if (!schema) { | |
return cb("Schema generation failed."); | |
} | |
cleanSchema(schema); | |
return next(null, JSON.stringify(schema)); | |
} catch (e) { | |
return cb(e); | |
} | |
}); | |
}); | |
} | |
function generateSchema(type, cb) { | |
const generate = makeGenerator(type, cb); | |
gulp.src("./src/declarations/scenario.d.ts") | |
.pipe(newer({ dest: `./int/schema/${type}`, extra: [ "./src/declarations/trait.d.ts" ] })) | |
.pipe(generate) | |
.pipe(rename(type)) | |
.pipe(gulp.dest("./int/schema")) | |
.on('end', cb); | |
} | |
let schemas = {}; | |
function validateSchema(type, files) { | |
const promise = new Promise((resolve, reject) => { | |
const schemaSource = readFile(`./int/schema/${type}`); | |
const validate = transform((filename) => { | |
return mapStream((chunk, next) => { | |
schemaSource.then((schemaJson) => { | |
try { | |
const json = JSON.parse(chunk); | |
if (schemaJson.length === 0) { | |
return reject("Bad schema!"); | |
} | |
if (!schemas[ type ]) { | |
schemas[ type ] = JSON.parse(schemaJson.toString()); | |
} | |
const schema = schemas[ type ]; | |
if (!tv4) { | |
tv4 = require("tv4"); | |
} | |
const result = tv4.validateResult(json, schema, false, true); | |
if (!result.valid) { | |
const suberrors = _.map( result.error.subErrors, (suberror) => { | |
return `${suberror.message}: ${suberror.dataPath}`; | |
}).join("\n"); | |
return reject(`Validation error in ${filename}: ${result.error.message}. | |
${suberrors} | |
At data path: ${result.error.dataPath}`); | |
} | |
return next(null, chunk); | |
} catch (e) { | |
console.error(`Error in parsing ${filename}: ${e}.`); | |
return reject(e); | |
} | |
}); | |
}); | |
}); | |
gulp.src(files) | |
.pipe(cache(`${type}-linting`)) | |
.pipe(strip()) | |
.pipe(validate) | |
.on("end", () => { | |
resolve(); | |
}); | |
}); | |
return promise; | |
} | |
function generateAndValidate(task, type, paths) { | |
var generateTask = task + "-generate"; | |
gulp.task(generateTask, function (cb) { | |
generateSchema(type, cb); | |
}); | |
gulp.task(task, [ generateTask ], function () { | |
return validateSchema(type, paths); | |
}); | |
} | |
module.exports = generateAndValidate; |
Here's my feedback
EE.prototype.once
instead ofEE.prototype.on
in general- Get rid of double quotes - " , single quotes are one of the greatest inventions ever lol
- Why not just put this at the top of the file:
if (!TJS)
TJS = require("typescript-json-schema");
Also, recommend naming js files like so:
validate-schema.js
not like so:
validateSchema.js
that's my preference
why? because *nix vs macos differ in case sensititivy, so I keep all filenames lowercase because i am badass like that, but last time I checked Mensch was on Windows so he's in the clear on this one :)
My JavaScript style is heavily influenced by my TypeScript style:
-
Double quotes are the standard I adhere to. JSON requires double quotes, in fact, and I like consistency. Single quotes are for ex-JavaScript-only devs who don't also use C, C++, C#, Go, Java, and Swift on a regular basis. If you note, all TypeScript samples use double quotes as well.
-
TypeScript itself will enforce consistent case in file includes. I also build most everything on Linux. It's an aesthetic difference, and I prefer to use camel case.
-
TJS
is loaded lazily, if it's needed. Most of the time the schemas don't change, soTJS
isn't needed. AndTJS
is big, counting its dependencies. I like my gulpfile to load faster when it can. -
Not sure what you mean about the EE.prototype.once. If you mean the
.on("end")
calls, literally every example is seen for gulp pipes useson
. Is there an advantage toonce
, given thatend
can only happen once?
ballerific