Created
April 8, 2019 17:20
-
-
Save dralletje/babb5f81edf3b3a7e1dcd15b3234d158 to your computer and use it in GitHub Desktop.
Better flow CLI, most likely outdated
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
let { uniq, flatten } = require('lodash'); | |
let chalk = require('chalk'); | |
let dedent = require('dedent'); | |
let { isEqual, padStart, padEnd, trimEnd } = require('lodash'); | |
let fs = require('fs'); | |
// NOTE CLASSIC | |
const indent = indentLevel => (...args) => { | |
const spaces = ``.repeat(indentLevel); | |
const result = dedent(...args); | |
return result.split('\n').map(x => `${spaces}${x}`).join('\n'); | |
}; | |
/* :: | |
type TFlowPoint = { | |
line: number, | |
column: number, | |
offset: number, | |
} | |
type TFlowLocation = { | |
source: string, | |
type: 'JsonFile' | 'SourceFile' | 'LibFile', | |
start: TFlowPoint, | |
end: TFlowPoint, | |
} | |
type TFlowMessage = { | |
context: string, | |
descr: string, | |
type: 'Blame' | 'Comment', | |
loc: TFlowLocation, | |
path: string, | |
line: number, | |
endline: number, | |
start: number, | |
end: number, | |
} | |
type TFlowError = { | |
kind: string, | |
level: string, | |
message: Array<TFlowMessage>, | |
} | |
type TFlowReport = { | |
flowVersion: number, | |
errors: Array<TFlowError>, | |
passed: boolean, | |
} | |
*/ | |
// NOTE CLASSIC | |
const suck = stream => { | |
return new Promise(function(yell, cry) { | |
const content = []; | |
process.stdin.on('readable', () => { | |
const data: ?Buffer = (process.stdin.read(): any); | |
if (data) content.push(data); | |
else yell(Buffer.concat(content).toString()); | |
}); | |
}); | |
}; | |
const suckText = async (...args) => (await suck(...args)).toString(); | |
const does_not_contain_dot_directory = message => | |
message.path.split('/').every(name => !name.startsWith('.')); | |
const does_not_contain_node_modules = message => | |
message.path.split('/').every(name => name !== 'node_modules'); | |
// NOTE CLASSIC | |
const not = fn => (...args) => !fn(...args); | |
// NOTE CLASSIC | |
const divide = (xs, pred) => [xs.filter(pred), xs.filter(not(pred))]; | |
const workdir = '/Users/michieldral/Projects/uButler Portal/'; | |
// NOTE CLASSIC | |
const without_prefix = (string: string, prefix: string) => { | |
if (!string.startsWith(prefix)) return string; | |
else return string.slice(prefix.length); | |
}; | |
// const pretty_print = (obj) => JSON.stringify(obj, null, 2) | |
// const FLOW_START = { line: 0, column: 0, offset: 0 }; | |
// const FLOW_END = { line: Infinity, column: Infinity, offset: Infinity }; | |
// const string_range = (string, start: TFlowPoint, end: TFlowPoint) => { | |
// return string.slice(start.column, end.column); | |
// } | |
const decorate_string_range = (range: TFlowLocation, string, mapFn) => { | |
return [ | |
string.slice(0, range.start.column - 1), | |
mapFn(string.slice(range.start.column - 1, range.end.column)), | |
string.slice(range.end.column, Infinity), | |
].join(''); | |
}; | |
const count_regex = (str, regex) => { | |
let m = str.match(regex); | |
return m ? m.length : 0; | |
}; | |
const count_control_char = str => { | |
return count_regex(str, /[\x00-\x1F\x7F-\x9F]/g) * 5; | |
}; | |
const mark_characters = str => { | |
return str.replace( | |
/([ ]*)(.*[^ ])([ ]*)/, | |
'$1' + chalk.bgRed.black('$2') + '$3', | |
); | |
}; | |
const prefix_lines = (str, prefix) => { | |
return str.split('\n').map(line => `${prefix}${line}`).join('\n'); | |
}; | |
const print_code = async message => { | |
if (!message.loc) throw new Error(); | |
const loc = message.loc; | |
const all_lines = fs.readFileSync(loc.source).toString().split('\n'); | |
let indented; | |
if (loc.start.line === loc.end.line) { | |
const before = all_lines.slice(loc.start.line - 3, loc.start.line - 1); | |
const the_line = all_lines[loc.start.line - 1]; | |
const after = all_lines.slice(loc.end.line, loc.end.line + 2); | |
const the_line_before = the_line.slice(0, loc.start.column - 1); | |
const the_line_marked = the_line.slice( | |
loc.start.column - 1, | |
loc.end.column, | |
); | |
const the_line_after = the_line.slice(loc.end.column); | |
indented = | |
before.join('\n') + | |
'\n' + | |
the_line_before + | |
mark_characters(the_line_marked) + | |
the_line_after + | |
'\n' + | |
after.join('\n'); | |
} else { | |
const before = all_lines.slice(loc.start.line - 3, loc.start.line - 1); | |
const partial_first = all_lines[loc.start.line - 1]; | |
const middle = all_lines.slice(loc.start.line, loc.end.line - 1); | |
const partial_last = all_lines[loc.end.line - 1]; | |
const after = all_lines.slice(loc.end.line, loc.end.line + 2); | |
const middle_with_marks = middle.map(line => mark_characters(line)); | |
const partial_first_unmarked = partial_first.slice(0, loc.start.column - 1); | |
const partial_first_marked = partial_first.slice(loc.start.column - 1); | |
const partial_last_marked = partial_last.slice(0, loc.end.column); | |
const partial_last_unmarked = partial_last.slice(loc.end.column); | |
indented = | |
before.join('\n') + | |
'\n' + | |
partial_first_unmarked + | |
mark_characters(partial_first_marked) + | |
'\n' + | |
middle_with_marks.join('\n') + | |
'\n' + | |
mark_characters(partial_last_marked) + | |
partial_last_unmarked + | |
'\n' + | |
after.join('\n'); | |
} | |
let dedented = dedent( | |
indented.split('\n').map(x => ` ${x}`).join('\n'), | |
).split('\n'); | |
let box_width = Math.max( | |
dedented | |
.slice() | |
// Remove control characters (colors) | |
.map(x => x.replace(/[\x00-\x1F\x7F-\x9F]/g, '')) | |
.sort((a, b) => b.length - a.length)[0].length, | |
50, | |
); | |
return dedented | |
.map((line, i) => ` ${padEnd(loc.start.line - 2 + i + ':', 6)}${line}`) | |
.map(line => padEnd(line, box_width + count_control_char(line) + 8)) | |
.map(line => chalk.bgWhite.black(line)) | |
.join('\n'); | |
}; | |
const main = async ({ show_missing_annotations = false }) => { | |
const json = await suckText(process.stdin); | |
const result = JSON.parse(json.toString()); | |
const allKinds = uniq(result.errors.map(x => x.kind)); | |
const allLevels = uniq(result.errors.map(x => x.level)); | |
const allMessages = flatten(result.errors.map(x => x.message)); | |
const allTypes = uniq(allMessages.map(x => x.type)); | |
const allLocTypes = uniq(allMessages.map(x => x.loc && x.loc.type)); | |
console.log('Object.keys(result):', Object.keys(result)); | |
console.log('allKinds:', allKinds); | |
console.log('allLevels:', allLevels); | |
console.log('allLocTypes:', allLocTypes); | |
console.log('allTypes:', allTypes); | |
const errors = result.errors | |
.filter(err => err.message.every(does_not_contain_dot_directory)) | |
.filter(err => err.message.every(does_not_contain_node_modules)); | |
const [parse_errors, infer_errors] = divide( | |
errors, | |
err => err.kind === 'parse', | |
); | |
const parse_messages = parse_errors.map( | |
error => dedent` | |
${chalk.red(`===`)} Parse error in file | |
${without_prefix(error.message[0].path, workdir)} | |
`, | |
); | |
console.log('\n\n', parse_messages.join('\n')); | |
const infer_messages = await Promise.all( | |
infer_errors.map(async (error: TFlowError) => { | |
const structure = error.message.map(m => m.type); | |
if (isEqual(structure, ['Blame', 'Comment'])) { | |
const [blame, comment] = error.message; | |
if (!blame.loc) throw new Error(); | |
if (!show_missing_annotations && comment.descr === 'Missing annotation') | |
return ''; | |
const loc = blame.loc; | |
let code = await print_code(blame); | |
return indent(0)` | |
${chalk.blue(`[File]`)} In ${chalk.blue( | |
without_prefix(blame.path, workdir), | |
)} | |
${prefix_lines(code, chalk.blue(`[Code] `))} | |
${chalk.blue(`[Line]`)} | |
${chalk.blue(`[Line]`)} > ${chalk.yellow(blame.descr)} | |
${chalk.blue(`[Line]`)} ${comment.descr} | |
`; | |
} else if (isEqual(structure, ['Blame', 'Comment', 'Blame'])) { | |
const [blame1, comment, blame2] = error.message; | |
if (!blame1.loc || !blame2.loc) throw new Error(); | |
if (!show_missing_annotations && comment.descr === 'Missing annotation') | |
return ''; | |
let code1 = await print_code(blame1); | |
let code2 = await print_code(blame2); | |
return indent(0)` | |
${chalk.blue(`[File]`)} In ${chalk.blue( | |
without_prefix(blame1.path, workdir), | |
)} | |
${prefix_lines(code1, chalk.blue(`[Code] `))} | |
${chalk.blue(`[Line]`)} | |
${chalk.blue(`[Line]`)} > ${chalk.yellow(blame1.descr)} | |
${chalk.blue(`[Line]`)} ${comment.descr} | |
${chalk.blue(`[Line]`)} > ${chalk.yellow(blame2.descr)} | |
${chalk.blue(`[Line]`)} | |
${chalk.blue(`[File]`)} In ${chalk.blue( | |
without_prefix(blame2.path, workdir), | |
)} | |
${prefix_lines(code2, chalk.blue(`[Code] `))} | |
`; | |
} else { | |
return chalk.green(structure.join(', ')); | |
} | |
}), | |
); | |
console.log('\n'); | |
console.log(infer_messages.filter(Boolean).join('\n\n\n')); | |
}; | |
main({}).catch(err => { | |
console.log('err:', err); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment