Before: runtime ale_linters/javascript/flow.vim After: unlet! g:flow_output unlet! g:expected unlet! g:actual call ale#linter#Reset() Execute(The flow handler should throw away non-JSON lines): AssertEqual \ [], \ ale_linters#javascript#flow#Handle(bufnr(''), [ \ 'Already up-to-date.', \ '{"flowVersion":"0.50.0","errors":[],"passed":true}', \ ]) AssertEqual \ [], \ ale_linters#javascript#flow#Handle(bufnr(''), [ \ 'foo', \ 'bar', \ 'baz', \ '{"flowVersion":"0.50.0","errors":[],"passed":true}', \ ]) Execute(The flow handler should process errors correctly.): silent! noautocmd file /home/w0rp/Downloads/graphql-js/src/language/parser.js let g:flow_output = { \ "flowVersion": "0.39.0", \ "errors": [ \ { \ "kind": "infer", \ "level": "error", \ "message": [ \ { \ "context": " return 1", \ "descr": "number", \ "type": "Blame", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 417, \ "column": 10, \ "offset": 9503 \ }, \ "end": { \ "line": 417, \ "column": 10, \ "offset": 9504 \ } \ }, \ "path": expand('%:p'), \ "line": 417, \ "endline": 417, \ "start": 10, \ "end": 10 \ }, \ { \ "context": v:null, \ "descr": "This type is incompatible with the expected return type of", \ "type": "Comment", \ "path": "", \ "line": 0, \ "endline": 0, \ "start": 1, \ "end": 0 \ }, \ { \ "context": "function parseArguments(lexer: Lexer<*>): Array {", \ "descr": "array type", \ "type": "Blame", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 416, \ "column": 43, \ "offset": 9472 \ }, \ "end": { \ "line": 416, \ "column": 61, \ "offset": 9491 \ } \ }, \ "path": expand('%:p'), \ "line": 416, \ "endline": 416, \ "start": 43, \ "end": 61 \ } \ ] \ }, \ { \ "kind": "infer", \ "level": "warning", \ "message": [ \ { \ "context": " return peek(lexer, TokenKind.PAREN_L) ?", \ "descr": "unreachable code", \ "type": "Blame", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 419, \ "column": 3, \ "offset": 9508 \ }, \ "end": { \ "line": 421, \ "column": 7, \ "offset": 9626 \ } \ }, \ "path": expand('%:p'), \ "line": 419, \ "endline": 421, \ "start": 3, \ "end": 7 \ } \ ] \ } \ ], \ "passed": v:false \} let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)]) let g:expected = [ \ { \ 'lnum': 417, \ 'type': 'E', \ 'col': 10, \ 'text': 'number: This type is incompatible with the expected return type of array type', \ }, \ { \ 'lnum': 419, \ 'type': 'W', \ 'col': 3, \ 'text': 'unreachable code:', \ }, \] AssertEqual g:expected, g:actual Execute(The flow handler should fetch the correct location for the currently opened file, even when it's not in the first message.): silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js let g:flow_output = { \ "flowVersion": "0.44.0", \ "errors": [{ \ "operation": { \ "context": " , document.getElementById('foo')", \ "descr": "React element `Foo`", \ "type": "Blame", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 6, \ "column": 3, \ "offset": 92 \ }, \ "end": { \ "line": 6, \ "column": 18, \ "offset": 108 \ } \ }, \ "path": expand('%:p'), \ "line": 6, \ "endline": 6, \ "start": 3, \ "end": 18 \ }, \ "kind": "infer", \ "level": "error", \ "message": [{ \ "context": "module.exports = function(props: Props) {", \ "descr": "property `bar`", \ "type": "Blame", \ "loc": { \ "source": "/Users/rav/Projects/vim-ale-flow/foo.js", \ "type": "SourceFile", \ "start": { \ "line": 9, \ "column": 34, \ "offset": 121 \ }, \ "end": { \ "line": 9, \ "column": 38, \ "offset": 126 \ } \ }, \ "path": "/Users/rav/Projects/vim-ale-flow/foo.js", \ "line": 9, \ "endline": 9, \ "start": 34, \ "end": 38 \ }, { \ "context": v:null, \ "descr": "Property not found in", \ "type": "Comment", \ "path": "", \ "line": 0, \ "endline": 0, \ "start": 1, \ "end": 0 \ }, { \ "context": " , document.getElementById('foo')", \ "descr": "props of React element `Foo`", \ "type": "Blame", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 6, \ "column": 3, \ "offset": 92 \ }, \ "end": { \ "line": 6, \ "column": 18, \ "offset": 108 \ } \ }, \ "path": expand('%:p'), \ "line": 6, \ "endline": 6, \ "start": 3, \ "end": 18 \ }] \ }], \ "passed": v:false \} let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)]) let g:expected = [ \ { \ 'lnum': 6, \ 'col': 3, \ 'type': 'E', \ 'text': 'property `bar`: Property not found in props of React element `Foo` See also: React element `Foo`', \ } \] AssertEqual g:expected, g:actual Execute(The flow handler should handle relative paths): silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js let g:flow_output = { \ "flowVersion": "0.44.0", \ "errors": [{ \ "operation": { \ "context": " , document.getElementById('foo')", \ "descr": "React element `Foo`", \ "type": "Blame", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 6, \ "column": 3, \ "offset": 92 \ }, \ "end": { \ "line": 6, \ "column": 18, \ "offset": 108 \ } \ }, \ "path": expand('%:p'), \ "line": 6, \ "endline": 6, \ "start": 3, \ "end": 18 \ }, \ "kind": "infer", \ "level": "error", \ "message": [{ \ "context": "module.exports = function(props: Props) {", \ "descr": "property `bar`", \ "type": "Blame", \ "loc": { \ "source": "vim-ale-flow/foo.js", \ "type": "SourceFile", \ "start": { \ "line": 9, \ "column": 34, \ "offset": 121 \ }, \ "end": { \ "line": 9, \ "column": 38, \ "offset": 126 \ } \ }, \ "path": "vim-ale-flow/foo.js", \ "line": 9, \ "endline": 9, \ "start": 34, \ "end": 38 \ }, { \ "context": v:null, \ "descr": "Property not found in", \ "type": "Comment", \ "path": "", \ "line": 0, \ "endline": 0, \ "start": 1, \ "end": 0 \ }, { \ "context": " , document.getElementById('foo')", \ "descr": "props of React element `Foo`", \ "type": "Blame", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 6, \ "column": 3, \ "offset": 92 \ }, \ "end": { \ "line": 6, \ "column": 18, \ "offset": 108 \ } \ }, \ "path": expand('%:p'), \ "line": 6, \ "endline": 6, \ "start": 3, \ "end": 18 \ }] \ }], \ "passed": v:false \} let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)]) let g:expected = [ \ { \ 'lnum': 6, \ 'col': 3, \ 'type': 'E', \ 'text': 'property `bar`: Property not found in props of React element `Foo` See also: React element `Foo`', \ } \] AssertEqual g:expected, g:actual Execute(The flow handler should handle extra errors): silent! noautocmd file /Users/rav/Projects/vim-ale-flow/index.js let g:flow_output = { \ "flowVersion": "0.54.0", \ "errors": [{ \ "extra": [{ \ "message": [{ \ "context": v:null, \ "descr": "Property \`setVector\` is incompatible:", \ "type": "Blame ", \ "path": "", \ "line": 0, \ "endline": 0, \ "start": 1, \ "end": 0 \ }], \ "children": [{ \ "message": [{ \ "context": "setVector = \{2\}", \ "descr": "number ", \ "type": "Blame ", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile ", \ "start": { \ "line": 90, \ "column": 30, \ "offset": 2296 \ }, \ "end": { \ "line": 90, \ "column": 30, \ "offset": 2297 \ } \ }, \ "path": expand('%:p'), \ "line": 90, \ "endline": 90, \ "start": 30, \ "end": 30 \ }, { \ "context": v:null, \ "descr": "This type is incompatible with ", \ "type": "Comment ", \ "path": "", \ "line": 0, \ "endline": 0, \ "start": 1, \ "end": 0 \ }, { \ "context": "setVector: VectorType => void,", \ "descr": "function type ", \ "type": "Blame ", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 9, \ "column": 14, \ "offset": 252 \ }, \ "end": { \ "line": 9, \ "column": 31, \ "offset": 270 \ } \ }, \ "path": expand('%:p'), \ "line": 9, \ "endline": 9, \ "start": 14, \ "end": 31 \ }] \ }] \ }], \ "kind": "infer", \ "level": "error", \ "suppressions": [], \ "message": [{ \ "context": " < New ", \ "descr": "props of React element `New`", \ "type": "Blame", \ "loc": { \ "source": "vim-ale-flow/foo.js", \ "type": "SourceFile", \ "start": { \ "line": 89, \ "column": 17, \ "offset": 2262 \ }, \ "end": { \ "line": 94, \ "column": 18, \ "offset": 2488 \ } \ }, \ "path": "", \ "line": 89, \ "endline": 94, \ "start": 17, \ "end": 18 \ }, { \ "context": v:null, \ "descr": "This type is incompatible with", \ "type": "Comment", \ "path": "", \ "line": 0, \ "endline": 0, \ "start": 1, \ "end": 0 \ }, { \ "context": "class New extends React.Component < NewProps,NewState > {", \ "descr": "object type", \ "type": "Blame", \ "loc": { \ "source": expand('%:p'), \ "type": "SourceFile", \ "start": { \ "line": 20, \ "column": 35, \ "offset": 489 \ }, \ "end": { \ "line": 20, \ "column": 42, \ "offset": 497 \ } \ }, \ "path": expand('%:p'), \ "line": 20, \ "endline": 20, \ "start": 35, \ "end": 42 \ }] \ }], \ "passed": v:false \} let g:actual = ale_linters#javascript#flow#Handle(bufnr(''), [json_encode(g:flow_output)]) let g:expected = [ \ { \ 'lnum': 20, \ 'col': 35, \ 'type': 'E', \ 'text': 'props of React element `New`: This type is incompatible with object type', \ 'detail': 'Property `setVector` is incompatible: number This type is incompatible with function type ', \ } \] AssertEqual g:expected, g:actual