Skip to content

Instantly share code, notes, and snippets.

@dralletje
Created April 8, 2019 17:20
Show Gist options
  • Save dralletje/babb5f81edf3b3a7e1dcd15b3234d158 to your computer and use it in GitHub Desktop.
Save dralletje/babb5f81edf3b3a7e1dcd15b3234d158 to your computer and use it in GitHub Desktop.
Better flow CLI, most likely outdated
// @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