What’s a Declaration File?
Let’s look at a more general, and sometimes more convenient way to
declare types for modules: .flow
files.
There are two possible use cases, depending on whether an implementation file exists
or not.
In the first case, the exported types of a module are declared in a declaration
file <FILENAME>.flow
, that is located in the same directory as the corresponding implementation
file <FILENAME>
. The declaration file completely shadows the colocated
implementation. In other words, Flow will completely ignore <FILENAME>
and just
read <FILENAME>.flow
instead.
In the second case, the implementation file is missing entirely. <FILENAME>.flow
is treated as if it is named <FILENAME>
.
Note that the .flow
extension applies both to .js
files as well as .json
ones. The corresponding declaration files have extensions .js.flow
and .json.flow
,
respectively.
Now let’s see an example of the first case documented above. Suppose we have the
following code in a file src/LookBeforeYouLeap.js
:
1
2
3
|
import { isLeapYear } from "./Misc";
if (isLeapYear("2020")) console.log("Yay!");
|
Cannot resolve module `./Misc`. [cannot-resolve-module]
{"value":"// @flow\nimport { isLeapYear } from \"./Misc\";\nif (isLeapYear(\"2020\")) console.log(\"Yay!\");\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_IMPORT","context":"normal","value":"import","line":2,"start":9,"end":15},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":16,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"isLeapYear","line":2,"start":18,"end":28},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":29,"end":30},{"type":"T_IDENTIFIER","context":"normal","value":"from","line":2,"start":31,"end":35},{"type":"T_STRING","context":"normal","value":"\"./Misc\"","line":2,"start":36,"end":44},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":44,"end":45},{"type":"T_IF","context":"normal","value":"if","line":3,"start":46,"end":48},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"normal","value":"isLeapYear","line":3,"start":50,"end":60},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":60,"end":61},{"type":"T_STRING","context":"normal","value":"\"2020\"","line":3,"start":61,"end":67},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":67,"end":68},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":68,"end":69},{"type":"T_IDENTIFIER","context":"normal","value":"console","line":3,"start":70,"end":77},{"type":"T_PERIOD","context":"normal","value":".","line":3,"start":77,"end":78},{"type":"T_IDENTIFIER","context":"normal","value":"log","line":3,"start":78,"end":81},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":81,"end":82},{"type":"T_STRING","context":"normal","value":"\"Yay!\"","line":3,"start":82,"end":88},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":88,"end":89},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":89,"end":90}],"errors":[{"id":"E1","messages":[{"id":"E1M1","description":"Cannot resolve module `./Misc`. [cannot-resolve-module]","context":"import { isLeapYear } from \"./Misc\";","source":"-","start":{"line":2,"column":28,"offset":36},"end":{"line":2,"column":35,"offset":44}}],"operation":null}]}
and suppose that src/Misc.js
has an incompatible implementation of isLeapYear
:
1
2
3
4
|
export function isLeapYear(year: number): boolean {
return year % 4 == 0;
}
|
{"value":"// @flow\nexport function isLeapYear(year: number): boolean {\n return year % 4 == 0; // yeah, this is approximate\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_EXPORT","context":"normal","value":"export","line":2,"start":9,"end":15},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":16,"end":24},{"type":"T_IDENTIFIER","context":"normal","value":"isLeapYear","line":2,"start":25,"end":35},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":35,"end":36},{"type":"T_IDENTIFIER","context":"normal","value":"year","line":2,"start":36,"end":40},{"type":"T_COLON","context":"type","value":":","line":2,"start":40,"end":41},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":2,"start":42,"end":48},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":48,"end":49},{"type":"T_COLON","context":"type","value":":","line":2,"start":49,"end":50},{"type":"T_BOOLEAN_TYPE","context":"type","value":"boolean","line":2,"start":51,"end":58},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":59,"end":60},{"type":"T_RETURN","context":"normal","value":"return","line":3,"start":63,"end":69},{"type":"T_IDENTIFIER","context":"normal","value":"year","line":3,"start":70,"end":74},{"type":"T_MOD","context":"normal","value":"%","line":3,"start":75,"end":76},{"type":"T_NUMBER","context":"normal","value":"4","line":3,"start":77,"end":78},{"type":"T_EQUAL","context":"normal","value":"==","line":3,"start":79,"end":81},{"type":"T_NUMBER","context":"normal","value":"0","line":3,"start":82,"end":83},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":83,"end":84},{"type":"Line","context":"comment","value":"// yeah, this is approximate","line":3,"start":85,"end":113},{"type":"T_RCURLY","context":"normal","value":"}","line":4,"start":114,"end":115}],"errors":[]}
If we now create a declaration file src/Misc.js.flow
, the declarations in it
will be used instead of the code in src/Misc.js
. Let’s say we have the
following declarations in src/Misc.js.flow
.
NOTE: The syntax for declarations in a declaration file is the same as we’ve seen in
Creating Library Definitions section.
1
2
|
declare export function isLeapYear(year: string): boolean;
|
{"value":"// @flow\ndeclare export function isLeapYear(year: string): boolean;\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_DECLARE","context":"normal","value":"declare","line":2,"start":9,"end":16},{"type":"T_EXPORT","context":"normal","value":"export","line":2,"start":17,"end":23},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":24,"end":32},{"type":"T_IDENTIFIER","context":"normal","value":"isLeapYear","line":2,"start":33,"end":43},{"type":"T_LPAREN","context":"type","value":"(","line":2,"start":43,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"year","line":2,"start":44,"end":48},{"type":"T_COLON","context":"type","value":":","line":2,"start":48,"end":49},{"type":"T_STRING_TYPE","context":"type","value":"string","line":2,"start":50,"end":56},{"type":"T_RPAREN","context":"type","value":")","line":2,"start":56,"end":57},{"type":"T_COLON","context":"normal","value":":","line":2,"start":57,"end":58},{"type":"T_BOOLEAN_TYPE","context":"type","value":"boolean","line":2,"start":59,"end":66},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":66,"end":67}],"errors":[]}
What do you think will happen?
Right, the isLeapYear
call in src/LookBeforeYouLeap.js
will typecheck, since
the year
parameter expects a string
in the declaration file.
As this example shows, declaration files must be written with care: it is up
to the programmer to ensure they are correct, otherwise they may hide type
errors.
Inlining declarations in regular code
Sometimes it is useful to make declarations inline, as part of the source of
an implementation file.
In the following example, say you want to finish writing
the function fooList
without bothering to mock up its dependencies first: a
function foo
that takes a number
and returns a string
, and a class
List
that has a map
method. You can do this by including declarations for
List
and foo
:
1
2
3
4
5
6
7
8
|
declare class List<T> {
map<U>(f: (x: T) => U): List<U>;
}
declare function foo(n: number): string;
function fooList(ns: List<number>): List<string> {
return ns.map(foo);
}
|
{"value":"declare class List<T> {\n map<U>(f: (x: T) => U): List<U>;\n}\ndeclare function foo(n: number): string;\n\nfunction fooList(ns: List<number>): List<string> {\n return ns.map(foo);\n}\n","tokens":[{"type":"T_DECLARE","context":"normal","value":"declare","line":1,"start":0,"end":7},{"type":"T_CLASS","context":"normal","value":"class","line":1,"start":8,"end":13},{"type":"T_IDENTIFIER","context":"normal","value":"List","line":1,"start":14,"end":18},{"type":"T_LESS_THAN","context":"type","value":"<","line":1,"start":18,"end":19},{"type":"T_IDENTIFIER","context":"type","value":"T","line":1,"start":19,"end":20},{"type":"T_GREATER_THAN","context":"type","value":">","line":1,"start":20,"end":21},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":22,"end":23},{"type":"T_IDENTIFIER","context":"normal","value":"map","line":2,"start":26,"end":29},{"type":"T_LESS_THAN","context":"type","value":"<","line":2,"start":29,"end":30},{"type":"T_IDENTIFIER","context":"type","value":"U","line":2,"start":30,"end":31},{"type":"T_GREATER_THAN","context":"type","value":">","line":2,"start":31,"end":32},{"type":"T_LPAREN","context":"type","value":"(","line":2,"start":32,"end":33},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":2,"start":33,"end":34},{"type":"T_COLON","context":"type","value":":","line":2,"start":34,"end":35},{"type":"T_LPAREN","context":"type","value":"(","line":2,"start":36,"end":37},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":2,"start":37,"end":38},{"type":"T_COLON","context":"type","value":":","line":2,"start":38,"end":39},{"type":"T_IDENTIFIER","context":"type","value":"T","line":2,"start":40,"end":41},{"type":"T_RPAREN","context":"type","value":")","line":2,"start":41,"end":42},{"type":"T_ARROW","context":"type","value":"=>","line":2,"start":43,"end":45},{"type":"T_IDENTIFIER","context":"type","value":"U","line":2,"start":46,"end":47},{"type":"T_RPAREN","context":"type","value":")","line":2,"start":47,"end":48},{"type":"T_COLON","context":"type","value":":","line":2,"start":48,"end":49},{"type":"T_IDENTIFIER","context":"type","value":"List","line":2,"start":50,"end":54},{"type":"T_LESS_THAN","context":"type","value":"<","line":2,"start":54,"end":55},{"type":"T_IDENTIFIER","context":"type","value":"U","line":2,"start":55,"end":56},{"type":"T_GREATER_THAN","context":"type","value":">","line":2,"start":56,"end":57},{"type":"T_SEMICOLON","context":"type","value":";","line":2,"start":57,"end":58},{"type":"T_RCURLY","context":"type","value":"}","line":3,"start":59,"end":60},{"type":"T_DECLARE","context":"normal","value":"declare","line":4,"start":61,"end":68},{"type":"T_FUNCTION","context":"normal","value":"function","line":4,"start":69,"end":77},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":4,"start":78,"end":81},{"type":"T_LPAREN","context":"type","value":"(","line":4,"start":81,"end":82},{"type":"T_IDENTIFIER","context":"normal","value":"n","line":4,"start":82,"end":83},{"type":"T_COLON","context":"type","value":":","line":4,"start":83,"end":84},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":4,"start":85,"end":91},{"type":"T_RPAREN","context":"type","value":")","line":4,"start":91,"end":92},{"type":"T_COLON","context":"normal","value":":","line":4,"start":92,"end":93},{"type":"T_STRING_TYPE","context":"type","value":"string","line":4,"start":94,"end":100},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":100,"end":101},{"type":"T_FUNCTION","context":"normal","value":"function","line":6,"start":103,"end":111},{"type":"T_IDENTIFIER","context":"normal","value":"fooList","line":6,"start":112,"end":119},{"type":"T_LPAREN","context":"normal","value":"(","line":6,"start":119,"end":120},{"type":"T_IDENTIFIER","context":"normal","value":"ns","line":6,"start":120,"end":122},{"type":"T_COLON","context":"type","value":":","line":6,"start":122,"end":123},{"type":"T_IDENTIFIER","context":"type","value":"List","line":6,"start":124,"end":128},{"type":"T_LESS_THAN","context":"type","value":"<","line":6,"start":128,"end":129},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":6,"start":129,"end":135},{"type":"T_GREATER_THAN","context":"type","value":">","line":6,"start":135,"end":136},{"type":"T_RPAREN","context":"normal","value":")","line":6,"start":136,"end":137},{"type":"T_COLON","context":"type","value":":","line":6,"start":137,"end":138},{"type":"T_IDENTIFIER","context":"type","value":"List","line":6,"start":139,"end":143},{"type":"T_LESS_THAN","context":"type","value":"<","line":6,"start":143,"end":144},{"type":"T_STRING_TYPE","context":"type","value":"string","line":6,"start":144,"end":150},{"type":"T_GREATER_THAN","context":"type","value":">","line":6,"start":150,"end":151},{"type":"T_LCURLY","context":"normal","value":"{","line":6,"start":152,"end":153},{"type":"T_RETURN","context":"normal","value":"return","line":7,"start":156,"end":162},{"type":"T_IDENTIFIER","context":"normal","value":"ns","line":7,"start":163,"end":165},{"type":"T_PERIOD","context":"normal","value":".","line":7,"start":165,"end":166},{"type":"T_IDENTIFIER","context":"normal","value":"map","line":7,"start":166,"end":169},{"type":"T_LPAREN","context":"normal","value":"(","line":7,"start":169,"end":170},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":7,"start":170,"end":173},{"type":"T_RPAREN","context":"normal","value":")","line":7,"start":173,"end":174},{"type":"T_SEMICOLON","context":"normal","value":";","line":7,"start":174,"end":175},{"type":"T_RCURLY","context":"normal","value":"}","line":8,"start":176,"end":177}],"errors":[]}
Just don’t forget to replace the declarations with proper implementations.