diff --git a/404.html b/404.html new file mode 100644 index 00000000000..bcab07bfc88 --- /dev/null +++ b/404.html @@ -0,0 +1,26 @@ + + + + + +Page Not Found | Flow + + + + + + + + + + + + + + +
+
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

+ + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 00000000000..23e7ab96957 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +flow.org diff --git a/_src/cli/annotate-exports.md b/_src/cli/annotate-exports.md new file mode 100644 index 00000000000..95c90642d96 --- /dev/null +++ b/_src/cli/annotate-exports.md @@ -0,0 +1,80 @@ +--- +title: Flow Annotate-Exports +slug: /cli/annotate-exports +--- + +Upgrading to [Types-First](../../lang/types-first) mode may require a substantial +number of type annotations at module boundaries. To help with the process of +upgrading large codebases, we are providing a codemod command, whose goal is to +fill in these missing annotations. This command is included in the Flow binary +in versions `>= 0.125`. + +> Note: As of version 0.134, types-first is the default mode. If you are using a +version `>=0.134`, make sure you set `types_first=false` in your .flowconfig while +running this codemod. + +This command uses types that Flow infers, to fill in positions that would otherwise +raise *signature-verification* failures. It will include the necessary type import +statements, as long as the respective types are exported from their defining modules. + +It is designed for use on multiple files at once, rather than one file at a time. +For this reason it doesn't connect to an existing Flow server, but rather starts +a checking process of its own. + +As is typical with such mechanized approaches, it comes with a few caveats: + +1. It won’t be able to fill in every required type annotation. Some cases will +require manual effort. +2. Inserted annotations may cause new flow errors, since it’s not always possible +to match inferred type with types that can be written as annotations. +3. File formatting may be affected. If a code formatter (e.g. prettier) is used, +it is recommended that you run it after the codemod has finished running. + + +### How to apply the codemod {#toc-how-to-apply-the-codemod} + +A typical way to invoke this command is + +``` +flow codemod annotate-exports \ + --write \ + --repeat \ + --log-level info \ + /path/to/folder \ + 2> out.log +``` + +This command will transform files under `/path/to/folder`. This does not need to +be the root directory (the one containing `.flowconfig`). + +It uses the following flags: +* `--write` will update files that require annotations under `/path/to/folder` +in-place. Without this flag the resulting files will be printed on the command line. + +* `--repeat` ensures that the transformation will be applied until no more files +change. This mode is necessary here, because each new type the codemod adds may +require new locations to be annotated. + +* `--log-level info` outputs useful debugging information in the standard error stream. +This option might lead to verbose output, so we're redirecting the error output +to a log file `out.log`. + +Another convenient way to provide the input is by passing the flag +``` +--input-file file.txt +``` +where `file.txt` contains a specific list of files to be transformed. + +### Codemod output {#toc-codemod-output} + +After each iteration of the codemod, a summary will be printed on the CLI. This +summary includes statistical information about the number of annotations that were +added, and how many locations were skipped. It also prints counts for various kinds +of errors that were encountered. These can be matched to the errors printed in the +logs. + +A common error case is when a type `A`, defined in a file `a.js`, but not exported, +is inferred in file `b.js`. The codemod will skip adding this annotation and report +an error in the logs. The fix this case, you can export `A` in `a.js`. Note that +it is not necessary to manually import `A` in `b.js`. The codemod will do this +automatically. diff --git a/_src/cli/coverage.md b/_src/cli/coverage.md new file mode 100644 index 00000000000..5a95b85e2e6 --- /dev/null +++ b/_src/cli/coverage.md @@ -0,0 +1,128 @@ +--- +title: Flow Coverage +slug: /cli/coverage +--- +The coverage command provides a metric of the amount of checking that Flow has +performed on each part of your code. A program with high Flow coverage should +increase your confidence that Flow has detected any potential runtime errors. + +The determining factor for this is the presence of [`any`](../../types/any/) in the +inferred type of each expression. An expression whose inferred type is `any` is +considered *uncovered*, otherwise it is considered *covered*. + +To see why this metric was chosen for determining Flow's effectiveness, consider +the example + +```js flow-check +const one: any = 1; +one(); +``` + +This code leads to a runtime type error, since we are attempting to perform a call +on a number. Flow, however, does not flag an error here, because we have annotated +variable `one` as `any`. Flow's checking is effectively turned off whenever `any` +is involved, so it will silently allow the call. The use of this *unsafe* type has +rendered the type checker ineffective, and the coverage metric is here to surface +this, by reporting all instances of `one` as uncovered. + +## Design Space {#toc-design-space} + +**Which types should be "covered"?** + +What was described above is a rather coarse grained way to determine coverage. One +could imagine a criterion that flags expressions as uncovered if *any* part of their +type includes `any`, for example `Array`. While there is value in a metric like +this, the "uncovered" part of the type will typically be uncovered through various +operations on values of this type. For example, in the code + +```js flow-check +declare const arr: Array; +arr.forEach(x => {}); +``` + +the parameter `x` will be flagged as uncovered. Also, in practice, a strict criterion +like this would be too noisy and rather expensive to compute on the fly. + +**Union types** + +An exception to this principle are union types: the type `number | any` is considered +*uncovered*, even though technically `any` is not the top-level constructor. +Unions merely encode an option among *a set of* other types. In that sense we are +conservatively viewing an expression as uncovered, when at least one possible type +of that expression causes limited checking. For example, in the code + +```js flow-check +let x: number | any = 1; +x = "a"; +``` + +Flow will let you assign anything to `x`, which reduces confidence in the use +of `x` as a number. Thus `x` is considered uncovered. + +**The empty type** + +An interesting type from a coverage perspective is the [`empty`](../../types/empty) type. +This type roughly corresponds to *dead code*. As such checking around expressions with +type `empty` is more relaxed, but for a good reason: this code will not be executed at +runtime. Since it is a common practice to clean up such code, Flow coverage will +also report code whose type is inferred to be `empty`, but distinguishes it from +the case of `any`. + + +## Command Line Use {#toc-command-line-use} + +To find out the coverage of a file foo.js with the following contents + +```js flow-check +function add(one: any, two: any): number { + return one + two; +} + +add(1, 2); +``` + +you can issue the following command + +``` +$ flow coverage file.js +Covered: 50.00% (5 of 10 expressions) +``` +This output means that 5 out of the 10 nodes of this program were inferred to have type +`any`. To see exactly which parts are uncovered you can also pass one of the following +flags: +* `--color`: This will print foo.js on the terminal with the uncovered locations in +red color. E.g. `flow coverage --color file.js` +* `--json`: This will list out all location spans that are uncovered under +the tag `"uncovered_locs"`. E.g. `flow coverage --json file.js` + +Finally, as an example of dead code, consider the code: + +```js flow-check +function f(x: 'a' | 'b') { + if (x === 'a') { + // ... + } else if (x === 'b') { + // ... + } else { + x; + } +} +``` + +The final `else` clause should never be reached, as we've already checked for both members of the union. +Because of this, `x` is inferred to have the type `empty` in that branch. + +In the colored version of this command, these parts appear in blue color, +and in the JSON version they are under the field `"empty_locs"`. + +**Use on multiple files** + +If you want to check coverage of multiple files at once, Flow offers the +`batch-coverage` command: +``` +$ flow batch-coverage dir/ +``` +will report coverage statistics for each file under `dir/`, as well as aggregate +results. + +Note that `batch-coverage` requires a non-lazy Flow server. diff --git a/_src/cli/index.md b/_src/cli/index.md new file mode 100644 index 00000000000..4bb855b5108 --- /dev/null +++ b/_src/cli/index.md @@ -0,0 +1,96 @@ +--- +title: Flow CLI +slug: /cli +description: How to use Flow from the command line. Including how to manage the Flow background process. +--- + +The flow command line tool is made to be easy-to-use for simple cases. + +Using the command `flow` will type-check your current directory if the +`.flowconfig` file is present. A flow server will automatically be started if +needed. + +The CLI tool also provides several other options and commands that allow you to +control the server and build tools that integrate with Flow. For example, this +is how the [Nuclide](https://nuclide.io/) editor integrates with Flow to +provide autocompletion, type errors, etc. in its UI. + +To find out more about the CLI just type: + +```sh +flow --help +``` + +This will give you information about everything that flow can do. Running this +command should print something like this: + +``` +Usage: flow [COMMAND] [PROJECT_ROOT] + +Valid values for COMMAND: + ast Print the AST + autocomplete Queries autocompletion information + batch-coverage Shows aggregate coverage information for a group of files or directories + check Does a full Flow check and prints the results + check-contents Run typechecker on contents from stdin + config Read or write the .flowconfig file + coverage Shows coverage information for a given file + cycle Output .dot file for cycle containing the given file + find-module Resolves a module reference to a file + find-refs Gets the reference locations of a variable or property + force-recheck Forces the server to recheck a given list of files + get-def Gets the definition location of a variable or property + graph Outputs dependency graphs of flow repositories + init Initializes a directory to be used as a flow root directory + ls Lists files visible to Flow + lsp Acts as a server for the Language Server Protocol over stdin/stdout [experimental] + print-signature Prints the type signature of a file as extracted in types-first mode + server Runs a Flow server in the foreground + start Starts a Flow server + status (default) Shows current Flow errors by asking the Flow server + stop Stops a Flow server + type-at-pos Shows the type at a given file and position + version Print version information + +Default values if unspecified: + COMMAND status + PROJECT_ROOT current folder + +Status command options: + --color Display terminal output in color. never, always, auto (default: auto) + --from Specify client (for use by editor plugins) + --help This list of options + --json Output results in JSON format + --no-auto-start If the server is not running, do not start it; just exit + --old-output-format Use old output format (absolute file names, line and column numbers) + --one-line Escapes newlines so that each error prints on one line + --quiet Suppresses the server-status information that would have been printed to stderr + --retries Set the number of retries. (default: 3) + --show-all-errors Print all errors (the default is to truncate after 50 errors) + --strip-root Print paths without the root + --temp-dir Directory in which to store temp files (default: /tmp/flow/) + --timeout Maximum time to wait, in seconds + --version Print version number and exit +``` + +Example with custom project root: +```sh +mydir +├── frontend +│ ├── .flowconfig +│ └── app.js +└── backend +``` + +```sh +flow check frontend +``` + +You can then, further dig into particular COMMANDs by adding the `--help` flag. + +So, for example, if you want to know more about how the autocomplete works, you +can use this command: + +```sh +flow autocomplete --help +``` diff --git a/_src/config/declarations.md b/_src/config/declarations.md new file mode 100644 index 00000000000..695caf4a65a --- /dev/null +++ b/_src/config/declarations.md @@ -0,0 +1,59 @@ +--- +title: .flowconfig [declarations] +slug: /config/declarations +--- + +Often third-party libraries have broken type definitions or have type +definitions only compatible with a certain version of Flow. In those cases it +may be useful to use type information from the third-party libraries without +typechecking their contents. + +The `[declarations]` section in a `.flowconfig` file tells Flow to parse files +matching the specified regular expressions in _declaration mode_. In declaration +mode the code is not typechecked. However, the signatures of functions, classes, +etc are extracted and used by the typechecker when checking other code. + +Conceptually one can think of declaration mode as if Flow still typechecks the +files but acts as if there is a `$FlowFixMe` comment on every line. + +See also [`[untyped]`](../untyped) for not typechecking files, and instead using `any` for all contents. + +Things to keep in mind: + +1. Declaration mode should only be used for existing third-party code. You + should never use this for code under your control. +2. These are [OCaml regular expressions](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html#TYPEregexp). +3. These regular expressions match against absolute paths. They probably should + start with `.*` + +An example `[declarations]` section might look like: + +``` +[declarations] +.*/third_party/.* +.*/src/\(foo\|bar\)/.* +.*\.decl\.js +``` + +This `[declarations]` section will parse in declaration mode: + +1. Any file or directory under a directory named `third_party` +2. Any file or directory under `.*/src/foo` or under `.*/src/bar` +3. Any file that ends with the extension `.decl.js` + +You may use the `` placeholder in your regular expressions. +At runtime, Flow will treat the placeholder as if it were the absolute +path to the project's root directory. This is useful for writing regular +expressions that are relative rather than absolute. + +For example, you can write: + +``` +[declarations] +/third_party/.* +``` + +Which would parse in declaration mode any file or directory under the directory +named `third_party/` within the project root. However, unlike the previous +example's `.*/third_party/.*`, it would NOT parse files or directories under +directories named `third_party/`, like `src/third_party/`. diff --git a/_src/config/ignore.md b/_src/config/ignore.md new file mode 100644 index 00000000000..5ca1f347f3b --- /dev/null +++ b/_src/config/ignore.md @@ -0,0 +1,58 @@ +--- +title: .flowconfig [ignore] +slug: /config/ignore +--- + +The `[ignore]` section in a `.flowconfig` file tells Flow to ignore files +matching the specified regular expressions when type checking your code. By +default, nothing is ignored. + +Things to keep in mind: + +1. These are [OCaml regular expressions](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html#TYPEregexp). +2. These regular expressions match against absolute paths. They probably should + start with `.*` +3. Ignores are processed AFTER includes. If you both include and ignore a file + it will be ignored. + +An example `[ignore]` section might look like: + +``` +[ignore] +.*/__tests__/.* +.*/src/\(foo\|bar\)/.* +.*\.ignore\.js +``` + +This `[ignore]` section will ignore: + +1. Any file or directory under a directory named `__tests__` +2. Any file or directory under `.*/src/foo` or under `.*/src/bar` +3. Any file that ends with the extension `.ignore.js` + +You may use the `` placeholder in your regular expressions. +At runtime, Flow will treat the placeholder as if it were the absolute +path to the project's root directory. This is useful for writing regular +expressions that are relative rather than absolute. + +For example, you can write: + +``` +[ignore] +/__tests__/.* +``` + +Which would ignore any file or directory under the directory named `__tests__/` +within the project root. However, unlike the previous example's +`.*/__tests__/.*`, it would NOT ignore files or directories under other +directories named `__tests__/`, like `src/__tests__/`. + +### Exclusions {#toc-ignore-exclusions} +Sometimes you may want to ignore all files inside a directory with the exception of a few. An optional prefix "!" which negates the pattern may help. With this, any matching file excluded by a previous pattern will become included again. + +``` +[ignore] +/node_modules/.* +!/node_modules/not-ignored-package-A/.* +!/node_modules/not-ignored-package-B/.* +``` diff --git a/_src/config/include.md b/_src/config/include.md new file mode 100644 index 00000000000..33e6f5be7d7 --- /dev/null +++ b/_src/config/include.md @@ -0,0 +1,33 @@ +--- +title: .flowconfig [include] +slug: /config/include +--- + +The `[include]` section in a `.flowconfig` file tells Flow to include the +specified files or directories. Including a directory recursively includes all +the files under that directory. Symlinks are followed as long as they lead to a +file or directory that is also included. Each line in the include section is a +path to include. These paths can be relative to the root directory or absolute, +and support both single and double star wildcards. + +The project root directory (where your `.flowconfig` lives) is automatically +included. + +For example, if `/path/to/root/.flowconfig` contains the following `[include]` +section: + +``` +[include] +../externalFile.js +../externalDir/ +../otherProject/*.js +../otherProject/**/coolStuff/ +``` + +Then when Flow checks the project in `/path/to/root`, it will read and watch + +1. `/path/to/root/` (automatically included) +2. `/path/to/externalFile.js` +3. `/path/to/externalDir/` +4. Any file in `/path/to/otherProject/` that ends in `.js` +5. Any directory under `/path/to/otherProject` named `coolStuff/` diff --git a/_src/config/index.md b/_src/config/index.md new file mode 100644 index 00000000000..179c53e0ab8 --- /dev/null +++ b/_src/config/index.md @@ -0,0 +1,91 @@ +--- +title: .flowconfig +slug: /config +description: Flow tries to work out of the box as much as possible, but can be configured to work with any codebase. +--- + +Every Flow project contains a `.flowconfig` file. You can configure Flow by +modifying `.flowconfig`. New projects or projects that are starting to use Flow +can generate a default `.flowconfig` by running `flow init`. + +### `.flowconfig` format {#toc-flowconfig-format} + +The `.flowconfig` uses a custom format that vaguely resembles INI files. + +The `.flowconfig` consists of different sections: + +* [`[version]`](./version) +* [`[options]`](./options) +* [`[include]`](./include) +* [`[ignore]`](./ignore) +* [`[untyped]`](./untyped) +* [`[declarations]`](./declarations) +* [`[libs]`](./libs) +* [`[lints]`](./lints) +* [`[strict]`](../strict/#toc-enabling-flow-strict-in-a-flowconfig) + +### Comments {#toc-comments} + +Lines beginning with zero or more spaces followed by an `#` or `;` or `💩` are +ignored. For example: + +``` +# This is a comment + # This is a comment +; This is a comment + ; This is a comment +💩 This is a comment + 💩 This is a comment +``` + +### Where to put the `.flowconfig` {#toc-where-to-put-the-flowconfig} + +The location of the `.flowconfig` is significant. Flow treats the directory that +contains the `.flowconfig` as the _project root_. By default Flow includes all +the source code under the project root. The paths in the +[[include] section](./include) are relative to the project root. Some other +configuration also lets you reference the project root via the macro +``. + +Most people put the `.flowconfig` in the root of their project (i.e. next to the +`package.json`). Some people put all their code in a `src/` directory and +therefore put the `.flowconfig` at `src/.flowconfig`. + +### Example {#toc-example} + +Say you have the following directory structure, with your `.flowconfig` in +`mydir`: + +```text +otherdir +└── src + ├── othercode.js +mydir +├── .flowconfig +├── build +│ ├── first.js +│ └── shim.js +├── lib +│ └── flow +├── node_modules +│ └── es6-shim +└── src + ├── first.js + └── shim.js +``` + +Here is an example of how you could use the `.flowconfig` directives. + +```text +[include] +../otherdir/src + +[ignore] +.*/build/.* + +[libs] +./lib +``` + +Now `flow` will include a directory outside the `.flowconfig` path in its +check, ignore the `build` directory and use the declarations in `lib`. diff --git a/_src/config/libs.md b/_src/config/libs.md new file mode 100644 index 00000000000..5cb33e10826 --- /dev/null +++ b/_src/config/libs.md @@ -0,0 +1,17 @@ +--- +title: .flowconfig [libs] +slug: /config/libs +--- + +The `[libs]` section in a `.flowconfig` file tells Flow to include the +specified [library definitions](../../libdefs/) when type +checking your code. Multiple libraries can be specified. By default, the +`flow-typed` folder in your project root directory is included as a library +directory. This default allows you to use +[`flow-typed`](https://github.com/flowtype/flow-typed) to install library +definitions without additional configuration. + +Each line in the `[libs]` section is a path to the library file or directory +which you would like to include. These paths can be relative to the project +root directory or absolute. Including a directory recursively includes all the +files under that directory as library files. diff --git a/_src/config/lints.md b/_src/config/lints.md new file mode 100644 index 00000000000..dcaed41dc3e --- /dev/null +++ b/_src/config/lints.md @@ -0,0 +1,15 @@ +--- +title: .flowconfig [lints] +slug: /config/lints +--- + +The `[lints]` section in a `.flowconfig` file can contain several key-value +pairs of the form: + +``` +[lints] +ruleA=severityA +ruleB=severityB +``` + +Check out the [linting docs](../../linting) for more information. diff --git a/_src/config/options.md b/_src/config/options.md new file mode 100644 index 00000000000..1c2c1eace00 --- /dev/null +++ b/_src/config/options.md @@ -0,0 +1,630 @@ +--- +title: .flowconfig [options] +slug: /config/options +--- + +import {SinceVersion, UntilVersion} from '../../components/VersionTags'; + +The `[options]` section in a `.flowconfig` file can contain several key-value +pairs of the form: + +``` +[options] +keyA=valueA +keyB=valueB +``` + +Any options that are omitted will use their default values. Some options +can be overridden with command line flags. + +## Available options {#toc-available-options} + +### all {#toc-all} + +Type: `boolean` + +Set this to `true` to check all files, not just those with `@flow`. + +The default value for `all` is `false`. + +### autoimports {#toc-autoimports} + +Type: `boolean` + +When enabled, IDE autocomplete suggests the exports of other files, and the necessary `import` statements are automatically inserted. A "quick fix" code action is also provided on undefined variables that suggests matching imports. + +The default value for `autoimports` is `true` as of Flow v0.155.0. + +### babel_loose_array_spread {#toc-babel-loose-array-spread} + +Type: `boolean` + +Set this to `true` to check that array spread syntax is only used with arrays, not arbitrary iterables (such as `Map` or `Set`). This is useful if you transform your code with Babel in [loose mode](https://babeljs.io/docs/en/babel-plugin-transform-spread#loose) which makes this non-spec-compliant assumption at runtime. + +For example: + +```js +const set = new Set(); +const values = [...set]; // Valid ES2015, but Set is not compatible with $ReadOnlyArray in Babel loose mode +``` + +The default value for `babel_loose_array_spread` is `false`. + +### emoji {#toc-emoji} + +Type: `boolean` + +Set this to `true` to add emoji to the status messages that Flow +outputs when it's busy checking your project. + +The default value for `emoji` is `false`. + +### enums {#toc-enums} + +Type: `boolean` + +Set this to `true` to enable [Flow Enums](../../enums). +[Additional steps](../../enums/enabling-enums/) are required beyond just enabling the `.flowconfig` option. + +The default value for `enums` is `false`. + +### exact_by_default {#toc-exact-by-default} + +Type: `boolean` + +When set to `true` (the default as of version 0.202), Flow interprets object types as exact by default: + +```js flow-check +type O1 = {foo: number} // exact +type O2 = {| foo: number |} // exact +type O3 = {foo: number, ...} // inexact +``` + +When this flag is `false`, Flow has the following behavior: + +```js flow-check +type O1 = {foo: number} // inexact +type O2 = {| foo: number |} // exact +type O3 = {foo: number, ...} // inexact +``` + +- From inception to Flow version 0.199, the default value of the flag was `false`. +- In versions 0.200 and 0.201, the flag was required to be explicitly set to either `true` or `false`. +- From version 0.202, the default value is `true`. + +You can read more about this change in our blog post about making [exact by object types by default, by default](https://medium.com/flow-type/exact-object-types-by-default-by-default-cc559af6f69). + +### experimental.const_params {#toc-experimental-const-params} + +Type: `boolean` + +Setting this to `true` makes Flow treat all function parameters as const +bindings. Reassigning a param is an error which lets Flow be less conservative +with refinements. + +The default value is `false`. + +### include_warnings {#toc-include-warnings} + +Type: `boolean` + +Setting this to `true` makes Flow commands include warnings in the error output. +Warnings are hidden by default in the CLI to avoid console spew. (An IDE is a +much better interface to show warnings.) + +The default value is `false`. + +### lazy_mode {#toc-lazy-mode} + +Type: `boolean` + +For more on lazy modes, see the [lazy modes docs](../../lang/lazy-modes/). + +Setting `lazy_mode` in the `.flowconfig` will cause new Flow servers for that +root to use lazy mode (or no lazy mode if set to `false`). This option can +be overridden from the CLI using the `--lazy-mode` flag. + +The default value is `false`. + +### max_header_tokens {#toc-max-header-tokens} + +Type: `integer` + +Flow tries to avoid parsing non-flow files. This means Flow needs to +start lexing a file to see if it has `@flow` or `@noflow` in it. This option +lets you configure how much of the file Flow lexes before it decides there is +no relevant docblock. + +- Neither `@flow` nor `@noflow` - Parse this file with Flow syntax disallowed and do not typecheck it. +- `@flow` - Parse this file with Flow syntax allowed and typecheck it. +- `@noflow` - Parse this file with Flow syntax allowed and do not typecheck it. This is meant as an escape hatch to suppress Flow in a file without having to delete all the Flow-specific syntax. + +The default value of `max_header_tokens` is 10. + +### module.file_ext {#toc-module-file-ext} + +By default, Flow will look for files with the extensions `.js`, `.jsx`, `.mjs`, +`.cjs` and `.json`. You can override this behavior with this option. + +For example, if you do: + +``` +[options] +module.file_ext=.foo +module.file_ext=.bar +``` + +Then Flow will instead look for the file extensions `.foo` and `.bar`. + +> **Note:** you can specify `module.file_ext` multiple times + +### module.ignore_non_literal_requires {#toc-module-ignore-non-literal-requires} + +Type: `boolean` + +Set this to `true` and Flow will no longer complain when you use `require()` +with something other than a string literal. + +The default value is `false`. + +### module.name_mapper {#toc-module-name-mapper} + +Type: `regex -> string` + +Specify a regular expression to match against module names, and a replacement +pattern, separated by a `->`. + +For example: + +``` +module.name_mapper='^image![a-zA-Z0-9$_]+$' -> 'ImageStub' +``` + +This makes Flow treat `require('image!foo.jpg')` as if it were +`require('ImageStub')`. + +These are [OCaml regular expressions](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html#TYPEregexp). +Use `\(` and `\)` (slashes required!) to create a capturing group, which you +can refer to in the replacement pattern as `\1` (up to `\9`). + +> **Note:** you can specify `module.name_mapper` multiple times + +### module.name_mapper.extension {#toc-module-name-mapper-extension} + +Type: `string -> string` + +Specify a file extension to match, and a replacement module name, separated by +a `->`. + +> **Note:** This is just shorthand for +> `module.name_mapper='^\(.*\)\.EXTENSION$' -> 'TEMPLATE'`) + +For example: + +``` +module.name_mapper.extension='css' -> '/CSSFlowStub.js.flow' +``` + +Makes Flow treat `require('foo.css')` as if it were +`require(PROJECT_ROOT + '/CSSFlowStub')`. + +> **Note:** You can specify `module.name_mapper.extension` multiple times for +> different extensions. + +### module.system {#toc-module-system} + +Type: `node | haste` + +The module system to use to resolve `import` and `require`. +Haste mode is used by Meta. + +The default is `node`. + +### module.system.node.main_field {#toc-module-system-node-main-field} + +Type: `string` + +Flow reads `package.json` files for the `"name"` and `"main"` fields to figure +out the name of the module and which file should be used to provide that +module. + +So if Flow sees this in the `.flowconfig`: + +``` +[options] +module.system.node.main_field=foo +module.system.node.main_field=bar +module.system.node.main_field=baz +``` + +and then it comes across a `package.json` with + +``` +{ + "name": "kittens", + "main": "main.js", + "bar": "bar.js", + "baz": "baz.js" +} +``` + +Flow will use `bar.js` to provide the `"kittens"` module. + +If this option is unspecified, Flow will always use the `"main"` field. + +See [this GitHub issue for the original motivation](https://github.com/facebook/flow/issues/5725) + +### module.system.node.resolve_dirname {#toc-module-system-node-resolve-dirname} + +Type: `string` + +By default, Flow will look in directories named `node_modules` for node +modules. You can configure this behavior with this option. + +For example, if you do: + +``` +[options] +module.system.node.resolve_dirname=node_modules +module.system.node.resolve_dirname=custom_node_modules +``` + +Then Flow will look in directories named `node_modules` or +`custom_node_modules`. + +> **Note:** you can specify `module.system.node.resolve_dirname` multiple times + +### module.use_strict {#toc-module-use-strict} + +Type: `boolean` + +Set this to `true` if you use a transpiler that adds `"use strict";` to the top +of every module. + +The default value is `false`. + +### munge_underscores {#toc-munge-underscores} + +Type: `boolean` + +Set this to `true` to have Flow treat underscore-prefixed class properties and +methods as private. This should be used in conjunction with [`jstransform`'s +ES6 class transform](https://github.com/facebook/jstransform/blob/master/visitors/es6-class-visitors.js), +which enforces the same privacy at runtime. + +The default value is `false`. + +### no_flowlib {#toc-no-flowlib} + +Type: `boolean` + +Flow has builtin library definitions. Setting this to `true` will tell Flow to +ignore the builtin library definitions. + +The default value is `false`. + +### react.runtime {#toc-react-runtime} + +Type: `automatic | classic` + +Set this to `automatic` if you are using React's automatic runtime in `@babel/plugin-transform-react-jsx`. +Otherwise, use `classic`. [See the babel documentation](https://babeljs.io/docs/en/babel-plugin-transform-react-jsx) +for details about the transform. + +The default value is `classic`. + +### server.max_workers {#toc-server-max-workers} + +Type: `integer` + +The maximum number of workers the Flow server can start. By default, the server +will use all available cores. + +### sharedmemory.hash_table_pow {#toc-sharedmemory-hash-table-pow} + +Type: `unsigned integer` + +The 3 largest parts of the shared memory are a dependency table, a hash table, +and a heap. While the heap grows and shrinks, the two tables are allocated in +full. This option lets you change the size of the hash table. + +Setting this option to X means the table will support up to 2^X elements, +which is 16*2^X bytes. + +By default, this is set to 19 (Table size is 2^19, which is 8 megabytes) + +### sharedmemory.heap_size {#toc-sharedmemory-heap-size} + +Type: `unsigned integer` + +This option configures the maximum possible size for the shared heap. You should +most likely not need to configure this, as it doesn't really affect how much +RSS Flow uses. However, if you are working on a massive codebase you might see +the following error after init: "Heap init size is too close to max heap size; +GC will never get triggered!" In this case, you may need to increase the size +of the heap. + +By default, this is set to 26843545600 (25 * 2^30 bytes, which is 25GiB) + +### suppress_type {#toc-suppress-type} + +Type: `string` + +This option lets you alias `any` with a given string. This is useful for +explaining why you're using `any`. For example, let's say you sometimes want +to sometimes use `any` to suppress an error and sometimes to mark a TODO. +Your code might look like + +``` +const myString: any = 1 + 1; +const myBoolean: any = 1 + 1; +``` + +If you add the following to your configuration: + +``` +[options] +suppress_type=$FlowFixMe +suppress_type=$FlowTODO +``` + +You can update your code to the more readable: + +``` +const myString: $FlowFixMe = 1 + 1; +const myBoolean: $FlowTODO = 1 + 1; +``` + +> **Note:** You can specify `suppress_type` multiple times. + +### traces {#toc-traces} + +Type: `integer` + +Enables traces on all error output (showing additional details about the flow +of types through the system), to the depth specified. This can be very +expensive, so is disabled by default. + +### use_mixed_in_catch_variables {#toc-use-mixed-in-catch-variables} + +Type: `boolean` + +Changes the default type of `catch` variables from [`any`](../../types/any) to [`mixed`](../../types/mixed). E.g. + +```js flow-check +try { +} catch (e) { +} +``` + +in the above example, if the option is `true`, `catch` will be typed as `mixed` as it lacks an explicit type annotation. + +## Deprecated options + +The following options no longer exist in the latest version of Flow: + +### esproposal.class_instance_fields {#toc-esproposal-class-instance-fields} + +Type: `enable | ignore | warn` + +Set this to `warn` to indicate that Flow should give a warning on use of +instance [class fields](https://github.com/tc39/proposal-class-public-fields) +per the pending spec. + +You may also set this to `ignore` to indicate that Flow should simply ignore +the syntax (i.e. Flow will not use this syntax to indicate the presence of a +property on instances of the class). + +The default value of this option is `enable`, which allows use of this proposed +syntax. + +### esproposal.class_static_fields {#toc-esproposal-class-static-fields} + +Type: `enable | ignore | warn` + +Set this to `warn` to indicate that Flow should give a warning on use of static +[class fields](https://github.com/tc39/proposal-class-public-fields) +per the pending spec. + +You may also set this to `ignore` to indicate that Flow should simply ignore +the syntax (i.e. Flow will not use this syntax to indicate the presence of a +static property on the class). + +The default value of this option is `enable`, which allows use of this proposed +syntax. + +### esproposal.decorators {#toc-esproposal-decorators} + +Type: `ignore | warn` + +Set this to `ignore` to indicate that Flow should ignore decorators. + +The default value of this option is `warn`, which gives a warning on use since +this proposal is still very early-stage. + +### esproposal.export_star_as {#toc-esproposal-export-star-as} + +Type: `enable | ignore | warn` + +Set this to `enable` to indicate that Flow should support the `export * as` +syntax from [leebyron's proposal](https://github.com/leebyron/ecmascript-more-export-from). + +You may also set this to `ignore` to indicate that Flow should simply ignore +the syntax. The default value of this option is `warn`, which gives a warning +on use since this proposal is still very early-stage. + +### esproposal.optional_chaining {#toc-esproposal-optional-chaining} + +Type: `enable | ignore | warn` + +Set this to `enable` to indicate that Flow should support the use of +[optional chaining](https://github.com/tc39/proposal-optional-chaining) +per the pending spec. + +You may also set this to `ignore` to indicate that Flow should simply ignore +the syntax. + +The default value of this option is `warn`, which gives a warning on +use since this proposal is still very early-stage. + +### esproposal.nullish_coalescing {#toc-esproposal-nullish-coalescing} + +Type: `enable | ignore | warn` + +Set this to `enable` to indicate that Flow should support the use of +[nullish coalescing](https://github.com/tc39/proposal-nullish-coalescing) +per the pending spec. + +You may also set this to `ignore` to indicate that Flow should simply ignore +the syntax. + +The default value of this option is `warn`, which gives a warning on +use since this proposal is still very early-stage. + +### inference_mode {#toc-inference-mode} + +Type: `classic | constrain-writes` + +Setting this to `constrain-writes` will enable the constrained-writes inference mode. + +For more info, see the [variable declaration docs](../../lang/variables). + +The default value is `classic` + +### log.file {#toc-log-file} + +Type: `string` + +The path to the log file (defaults to `/tmp/flow/.log`). + +### sharedmemory.dirs {#toc-sharedmemory-dirs} + +Type: `string` + +This affects Linux only. + +Flow's shared memory lives in a memory mapped file. On more modern versions of +Linux (3.17+), there is a system call `memfd_create` which allows Flow to create +the file anonymously and only in memory. However, in older kernels, Flow needs +to create a file on the file system. Ideally this file lives on a memory-backed +tmpfs. This option lets you decide where that file is created. + +By default this option is set to `/dev/shm` and `/tmp` + +> **Note:** You can specify `sharedmemory.dirs` multiple times. + +### sharedmemory.minimum_available {#toc-sharedmemory-minimum-available} + +Type: `unsigned integer` + +This affects Linux only. + +As explained in the [`sharedmemory.dirs`](#toc-sharedmemory-dirs-string) option's description, Flow needs to +create a file on a filesystem for older kernels. `sharedmemory.dirs` specifies +a list of locations where the shared memory file can be created. For each +location, Flow will check to make sure the filesystem has enough space for the +shared memory file. If Flow will likely run out of space, it skips that location +and tries the next. This option lets you configure the minimum amount of space +needed on a filesystem for shared memory. + +By default it is 536870912 (2^29 bytes, which is half a gigabyte). + +### strip_root {#toc-strip-root} + +Type: `boolean` + +Obsolete. Set this to `true` to always strip the root directory from file paths +in error messages when using `--json`, `--from emacs`, and `--from vim`. +Do not use this option. Instead, pass the command line flag `--strip-root`. + +By default this is `false`. + +### suppress_comment {#toc-suppress-comment} + +Type: `regex` + +Defines a magical comment that suppresses any Flow errors on the following +line. For example: + +``` +suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe +``` + +will match a comment like this: + +``` +// $FlowFixMe: suppressing this error until we can refactor +var x : string = 123; +``` + +and suppress the error. If there is no error on the next line (the suppression +is unnecessary), an "Unused suppression" warning will be shown instead. + +If no suppression comments are specified in your config, Flow will apply one +default: `// $FlowFixMe`. + +> **Note:** You can specify `suppress_comment` multiple times. If you do define +> any `suppress_comment`s, the built-in `$FlowFixMe` suppression will be erased +> in favor of the regexps you specify. If you wish to use `$FlowFixMe` with +> some additional custom suppression comments, you must manually specify +> `\\(.\\|\n\\)*\\$FlowFixMe` in your custom list of suppressions. + +> **Note:** In version v0.127.0, the option to specify the suppression comment +> syntax was removed. `$FlowFixMe`, `$FlowIssue`, `$FlowExpectedError`, +> and `$FlowIgnore` became the only standard suppressions. + +### temp_dir {#toc-temp-dir} + +Type: `string` + +Tell Flow which directory to use as a temp directory. Can be overridden with the +command line flag `--temp-dir`. + +The default value is `/tmp/flow`. + +### types_first {#toc-types-first} + +Type: `boolean` + +For more on types-first mode, see the [types-first docs](../../lang/types-first/). + +Flow builds intermediate artifacts to represent signatures of modules as they are +checked. If this option is set to `false`, then these artifacts are built using +inferred type information. If this option is set to `true`, then they are built +using type annotations at module boundaries. + +The default value for `types_first` is `true` (as of version 0.134). + +### well_formed_exports {#toc-well-formed-exports} + +Type: `boolean` + +Enforce the following restrictions on file exports: +* Statements manipulating `module.exports` and the `exports` alias may only appear + as top-level statements. +* Parts of the source that are visible from a file's exports need to be annotated + unless their type can be trivially inferred (e.g. the exported expression is a + numeric literal). This is a requirement for types-first mode to function properly. + Failure to properly annotate exports raise `signature-verification-failure`s. + +This option is set to `true` by default, since it is implied by [`types_first`](#toc-types-first-boolean), +but the option is useful on its own when upgrading a project from classic mode to +types-first mode. + +### well_formed_exports.includes {#toc-well-formed-exports-includes} + +Type: `string` + +Limit the scope of the `well_formed_exports` requirement to a specific directory +of this project. For example +``` +well_formed_exports=true +well_formed_exports.includes=/dirA +well_formed_exports.includes=/dirB +``` +will only report export related errors in files under `dirA` and `dirB`. This option +requires `well_formed_exports` to be set to `true`. + +The purpose of this option is to help prepare a codebase for Flow types-first mode. + +Between versions v0.125.0 and v0.127.0, this option was named `well_formed_exports.whitelist`. diff --git a/_src/config/untyped.md b/_src/config/untyped.md new file mode 100644 index 00000000000..59b8ebe5da8 --- /dev/null +++ b/_src/config/untyped.md @@ -0,0 +1,58 @@ +--- +title: .flowconfig [untyped] +slug: /config/untyped +--- + +The `[untyped]` section in a `.flowconfig` file tells Flow to not typecheck files +matching the specified regular expressions and instead throw away types and treat modules as [`any`](../../types/any). + +This is different from the [`[ignore]`](../ignore) config section that causes matching files to be ignored by the module resolver, +which inherently makes them un-typechecked, and also unresolvable by `import` or `require`. +When ignored, [`[libs]`](../libs) must then be specified for each `import` using `flow-typed`, which may not always be desired. + +It is also different from the [`[declarations]`](../declarations) section. +This also does not typecheck the file contents, but `[declarations]` does extract and use the signatures of functions, classes, etc, when checking other code. + +`[untyped]` instead causes a file to be ignored by the typechecker as if it had `@noflow` in it, +resolve modules as `any` type, but allow them to NOT be ignored by the module resolver. +Any matching file is skipped by Flow (not even parsed, like other `@noflow` files!), but can still be `require()`'d. + +Things to keep in mind: + +1. These are [OCaml regular expressions](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html#TYPEregexp). +2. These regular expressions match against absolute paths. They probably should + start with `.*` + +An example `[untyped]` section might look like: + +``` +[untyped] +.*/third_party/.* +.*/src/\(foo\|bar\)/.* +.*\.untyped\.js +``` + +This `[untyped]` section will parse: + +1. Any file or directory under a directory named `third_party` +2. Any file or directory under `.*/src/foo` or under `.*/src/bar` +3. Any file that ends with the extension `.untyped.js` + +You may use the `` placeholder in your regular expressions. +At runtime, Flow will treat the placeholder as if it were the absolute path +to the project's root directory. This is useful for writing regular +expressions that are relative rather than absolute. + +For example, you can write: + +``` +[untyped] +/third_party/.* +``` + +Which would parse in declaration mode any file or directory under the directory +named `third_party/` within the project root. However, unlike the previous +example's `.*/third_party/.*`, it would NOT parse files or directories under +directories named `third_party/`, like `src/third_party/`. + + diff --git a/_src/config/version.md b/_src/config/version.md new file mode 100644 index 00000000000..71488dc2848 --- /dev/null +++ b/_src/config/version.md @@ -0,0 +1,31 @@ +--- +title: .flowconfig [version] +slug: /config/version +--- + +You can specify in the `.flowconfig` which version of Flow you expect to use. +You do this with the `[version]` section. If this section is omitted or left +blank, then any version is allowed. If a version is specified and not matched, +then Flow will immediately error and exit. + +So if you have the following in your `.flowconfig`: + +``` +[version] +0.22.0 +``` + +and you try to use Flow v0.21.0, then Flow will immediately error with the +message + +`"Wrong version of Flow. The config specifies version 0.22.0 but this is version +0.21.0"` + +So far, we support the following ways to specify supported versions + +- Explicit versions, (e.g. `0.22.0`, which only matches `0.22.0`). +- Intersection ranges, which are ANDed together, (e.g. `>=0.13.0 <0.14.0`, + which matches `0.13.0` and `0.13.5` but not `0.14.0`). +- Caret ranges, which allow changes that do not modify the left-most non-zero + digit (e.g. `^0.13.0` expands into `>=0.13.0 <0.14.0`, and `^0.13.1` expands + into `>=0.13.1 <0.14.0`, whereas `^1.2.3` expands into `>=1.2.3 <2.0.0`). diff --git a/_src/declarations/index.md b/_src/declarations/index.md new file mode 100644 index 00000000000..be66dc47806 --- /dev/null +++ b/_src/declarations/index.md @@ -0,0 +1,87 @@ +--- +title: Declaration Files +slug: /declarations +description: Learn how to write types in .flow files. +--- + +## What's a Declaration File? {#toc-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_ `.flow`, that is located in the same directory as the corresponding _implementation +file_ ``. The declaration file completely shadows the colocated +implementation. In other words, Flow will completely ignore `` and just +read `.flow` instead. + +In the second case, the implementation file is missing entirely. `.flow` +is treated as if it is named ``. + +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`: + +```js +import { isLeapYear } from "./Misc"; +if (isLeapYear("2020")) console.log("Yay!"); +``` + +and suppose that `src/Misc.js` has an incompatible implementation of `isLeapYear`: + +```js flow-check +export function isLeapYear(year: number): boolean { + return year % 4 == 0; // yeah, this is approximate +} +``` + +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](../libdefs/creation). + +```js flow-check +declare export function isLeapYear(year: string): boolean; +``` + +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 {#toc-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`: + +```js flow-check +declare class List { + map(f: (x: T) => U): List; +} +declare function foo(n: number): string; + +function fooList(ns: List): List { + return ns.map(foo); +} +``` + +Just don't forget to replace the declarations with proper implementations. diff --git a/_src/editors/emacs.md b/_src/editors/emacs.md new file mode 100644 index 00000000000..b5d019457b3 --- /dev/null +++ b/_src/editors/emacs.md @@ -0,0 +1,22 @@ +--- +title: Emacs +slug: /editors/emacs +--- + +### flow-for-emacs {#toc-flow-for-emacs} + +You can add support for Flow in Emacs by using [flow-for-emacs](https://github.com/flowtype/flow-for-emacs) + +### Requirements {#toc-emacs-requirements} + +* Requires Flow to be installed and available on your path. +* Requires projects containing JavaScript files to be initialised with flow init. +* Requires JavaScript files to be marked with /* @flow */ at the top. + +### Installation {#toc-emacs-installation} + +```sh +cd ~/.emacs.d/ +git clone https://github.com/flowtype/flow-for-emacs.git +echo -e "\n(load-file \"~/.emacs.d/flow-for-emacs/flow.el\")" >> ~/.emacs +``` diff --git a/_src/editors/index.md b/_src/editors/index.md new file mode 100644 index 00000000000..0fa527cde78 --- /dev/null +++ b/_src/editors/index.md @@ -0,0 +1,9 @@ +--- +title: Editors +description: "Detailed guides, tips, and resources on how to integrate Flow with different code editors." +slug: /editors +--- + +You can benefit from having Flow run as you develop by integrating into your editor. + +Editor plugins are provided and maintained by the community. If you have trouble configuring or using a specific plugin for your IDE, please visit the project's repo or search for a community provided answer. diff --git a/_src/editors/sublime-text.md b/_src/editors/sublime-text.md new file mode 100644 index 00000000000..d9eec66894b --- /dev/null +++ b/_src/editors/sublime-text.md @@ -0,0 +1,18 @@ +--- +title: Sublime Text +slug: /editors/sublime-text +--- + +### Flow For Sublime Text 2 and 3 {#toc-flow-for-sublime-text-2-and-3} + +[Sublime Text](https://www.sublimetext.com/) can be integrated with Flow by using [Package Control](https://packagecontrol.io) + +* Install the Package Control plugin if you don't have it +* Press Ctrl+Shift+P to bring up the Command Palette (or use Tools > Command Palette menu) +* Select Package Control: Install Package +* Type 'Flow' to find 'Flow for Sublime Text 2 and 3' +* Select 'Flow for Sublime Text 2 and 3' to install + +### SublimeLinter-flow {#toc-sublimelinter-flow} + +You may also wish to install a popular SublimeLinter plugin for Flow like [SublimeLinter-flow](https://packagecontrol.io/packages/SublimeLinter-flow). diff --git a/_src/editors/vim.md b/_src/editors/vim.md new file mode 100644 index 00000000000..f35781e7286 --- /dev/null +++ b/_src/editors/vim.md @@ -0,0 +1,156 @@ +--- +title: Vim +slug: /editors/vim +--- + +Flow's editor integration is primarily via the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/). There are [many vim LSP clients](https://microsoft.github.io/language-server-protocol/implementors/tools/) to choose from, such as [ALE](#toc-ale). + +## ALE {#toc-ale} + +The Asynchronous Lint Engine (ALE) plugin for Vim 8+ and NeoVim, [vim-ale](https://github.com/w0rp/ale), is a generalized linting engine with support for Flow and many other tools. + +### Installation {#toc-installation} + +Follow the [instructions](https://github.com/w0rp/ale#3-installation) in the ALE README. + +Configure ALE to use the `flow-language-server` linter for JavaScript files: + +```vim +" In ~/.vim/ftplugin/javascript.vim, or somewhere similar. + +" Enables only Flow for JavaScript. See :ALEInfo for a list of other available +" linters. NOTE: the `flow` linter uses an old API; prefer `flow-language-server`. +let b:ale_linters = ['flow-language-server'] + +" Or in ~/.vim/vimrc: +let g:ale_linters = { +\ 'javascript': ['flow-language-server'], +\} +``` + +## coc.nvim-neovim {#toc-coc-nvim-neovim} + +[Coc](https://github.com/neoclide/coc.nvim) is an intellisense engine for vim8 & neovim. + +### Setup {#toc-setup} + +```vim +set nocompatible +filetype off + +" install coc.nvim using Plug or preffered plugin manager +call plug#begin('~/.vim/plugged') +Plug 'neoclide/coc.nvim', {'branch': 'release'} +call plug#end() + +filetype plugin indent on + +" ======= coc settings +set updatetime=300 +set shortmess+=c + +" Use leader T to show documentation in preview window +nnoremap t :call show_documentation() + + +function! s:show_documentation() + if (index(['vim','help'], &filetype) >= 0) + execute 'h '.expand('<cword>') + else + call CocAction('doHover') + endif +endfunction + +" instead of having ~/.vim/coc-settings.json +let s:LSP_CONFIG = { +\ 'flow': { +\ 'command': exepath('flow'), +\ 'args': ['lsp'], +\ 'filetypes': ['javascript', 'javascriptreact'], +\ 'initializationOptions': {}, +\ 'requireRootPattern': 1, +\ 'settings': {}, +\ 'rootPatterns': ['.flowconfig'] +\ } +\} + +let s:languageservers = {} +for [lsp, config] in items(s:LSP_CONFIG) + let s:not_empty_cmd = !empty(get(config, 'command')) + if s:not_empty_cmd | let s:languageservers[lsp] = config | endif +endfor + +if !empty(s:languageservers) + call coc#config('languageserver', s:languageservers) + endif +``` + +## LanguageClient-neovim {#toc-languageclient-neovim} + +Another way to add support for Flow in Vim is to use [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim). + +* Suports vim 8 and neovim +* Adds completions to omnifunc +* Checks JavaScript files for type errors on save +* Look up types under cursor + +### Requirements {#toc-requirements} + +* Requires Flow to be installed and available on your path. +* Requires projects containing JavaScript files to be initialised with flow init. +* Requires JavaScript files to be marked with /* @flow */ at the top. + +### Pathogen {#toc-pathogen} + +```sh +cd ~/.vim/bundle +git clone git://github.com/autozimu/LanguageClient-neovim.git +``` + +### NeoBundle {#toc-neobundle} + +Add this to your ~/.vimrc + +```vim + NeoBundleLazy 'autozimu/LanguageClient-neovim', { + \ 'autoload': { + \ 'filetypes': 'javascript' + \ }} +``` + +With Flow build step, using flow-bin + +```vim + NeoBundleLazy 'autozimu/LanguageClient-neovim', { + \ 'autoload': { + \ 'filetypes': 'javascript' + \ }, + \ 'build': { + \ 'mac': 'npm install -g flow-bin', + \ 'unix': 'npm install -g flow-bin' + \ }} +``` + +### VimPlug {#toc-vimplug} + +```vim + Plug 'autozimu/LanguageClient-neovim', { + \ 'branch': 'next', + \ 'do': 'bash install.sh && npm install -g flow-bin', + \ } +``` + +### Setup {#toc-setup} +```vim +let g:LanguageClient_rootMarkers = { +\ 'javascript': ['.flowconfig', 'package.json'] +\ } +let g:LanguageClient_serverCommands={ +\ 'javascript': ['flow', 'lsp'], +\ 'javascript.jsx': ['flow', 'lsp'] +\} + +" check the type under cursor w/ leader T +nnoremap t :call LanguageClient_textDocument_hover() +nnoremap y :call LanguageClient_textDocument_definition() +``` diff --git a/_src/editors/vscode.md b/_src/editors/vscode.md new file mode 100644 index 00000000000..b1fce3373c9 --- /dev/null +++ b/_src/editors/vscode.md @@ -0,0 +1,20 @@ +--- +title: Visual Studio Code +slug: /editors/vscode +--- + +![Screenshot of Flow Language Support](/img/flow_for_vscode.gif) + +Support for Flow in [Visual Studio Code](https://code.visualstudio.com/) is provided by +the [Flow Language Support](https://marketplace.visualstudio.com/items?itemName=flowtype.flow-for-vscode) +extension. It provides all the functionality you would expect: + +- IntelliSense (Autocomplete) +- Go to Definition / Peek Definition +- Diagnostics (Errors, Warnings) +- Hover type information +- Toggleable code coverage reports + +## Installation + +Search for "Flow Language Support" in the VS Code extensions panel or install through the [Marketplace](https://marketplace.visualstudio.com/items?itemName=flowtype.flow-for-vscode). diff --git a/_src/editors/webstorm.md b/_src/editors/webstorm.md new file mode 100644 index 00000000000..bccca7b354a --- /dev/null +++ b/_src/editors/webstorm.md @@ -0,0 +1,8 @@ +--- +title: WebStorm +slug: /editors/webstorm +--- + +Webstorm installation instructions can be found here: +- [WebStorm 2016.3](https://www.jetbrains.com/help/webstorm/2016.3/using-the-flow-type-checker.html) +- [WebStorm 2017.1](https://www.jetbrains.com/help/webstorm/2017.1/flow-type-checker.html) diff --git a/_src/enums/defining-enums.md b/_src/enums/defining-enums.md new file mode 100644 index 00000000000..6167bda9700 --- /dev/null +++ b/_src/enums/defining-enums.md @@ -0,0 +1,253 @@ +--- +title: Defining enums +slug: /enums/defining-enums +--- + +import {SinceVersion} from '../../components/VersionTags'; + +Learn how to define a Flow Enum. Looking for a quick overview? Check out the [Quickstart Guide](../#toc-quickstart). + +An enum declaration is a statement. Its name defines both a value (from which to [access its members](../using-enums/#toc-accessing-enum-members), +and call its [methods](../using-enums/#toc-methods)), and a type (which can be [used as an annotation](../using-enums/#toc-using-as-a-type-annotation) for the type of its members). + +Enum members must all be of the same type, and those members can be one of four types: +[string](#toc-string-enums), [number](#toc-number-enums), [boolean](#toc-boolean-enums), and [symbol](#toc-symbol-enums). + +Every enum has some common properties: + +#### Consistent member type {#toc-consistent-member-type} +The type of the enum members must be consistent. For example, you can’t mix `string` and `number` members in one enum. +They must all be strings, numbers, or booleans (you do not provide values for `symbol` based enums). + +#### Member name starting character {#toc-member-name-starting-character} +Member names must be valid identifiers (e.g. not start with numbers), and must not start with lowercase `a` through `z`. +Names starting with those letters are reserved for enum [methods](../using-enums/#toc-methods) (e.g. `Status.cast(...)`). + +This is not allowed: + +```js flow-check +enum Status { + active, // Error: names can't start with lowercase 'a' through 'z' +} +``` + +#### Unique member names {#toc-unique-member-names} +Member names must be unique. This is not allowed: + +```js flow-check +enum Status { + Active, + Active, // Error: the name 'Active` was already used above +} +``` + +#### Literal member values {#toc-literal-member-values} +If you specify a value for an enum member, it must be a literal (string, number, or boolean), not a computed value. This is not allowed: + +```js flow-check +enum Status { + Active = 1 + 2, // Error: the value must be a literal +} +``` + +#### Unique member values {#toc-unique-member-values} +Member values must be unique. This is not allowed: + +```js flow-check +enum Status { + Active = 1, + Paused = 1, // Error: the value has already been used above +} +``` + +#### Fixed at declaration {#toc-fixed-at-declaration} +An enum is not extendable, so you can’t add new members after the fact while your code is running. +At runtime, enum member values can’t change and the members can’t be deleted. In this way they act like a frozen object. + + +## String enums {#toc-string-enums} +String enums are the default. If you don’t specify an `of` clause (e.g. `enum Status of number {}`, `enum Status of symbol {}`, etc.), +and do not specify any values (e.g. `enum Status {Active = 1}`) then the definition will default to be a string enum. + +Unlike the other types of enums (e.g. number enums), you can either specify values for the enum members, or not specify values and allow them to be defaulted. + +If you don’t specify values for your enum members, they default to strings which are the same as the name of your members. + +```js flow-check +enum Status { + Active, + Paused, + Off, +} +``` +Is the same as: + +```js flow-check +enum Status { + Active = 'Active', + Paused = 'Paused', + Off = 'Off', +} +``` +You must consistently either specify the value for all members, or none of the members. This is not allowed: + +```js flow-check +enum Status { + Active = 'active', + Paused = 'paused', + Off, // Error: you must specify a value for all members (or none of the members) +} +``` +Optionally, you can use an `of` clause: + +```js flow-check +enum Status of string { + Active, + Paused, + Off, +} +``` + +We infer the type of the enum based on its values if there is no `of` clause. +Using an `of` clause will ensure that if you use incorrect values, the error message will always interpret it as an enum of that type. + + +## Number enums {#toc-number-enums} +Number enums must have their values specified. + +You can specify a number enum like this: + +```js flow-check +enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +``` +Optionally, you can use an `of` clause. +This does not affect the type-checking behavior of a valid Flow Enum, +it just ensures that all enum members are `number`s as the definition site. + +```js flow-check +enum Status of number { + Active = 1, + Paused = 2, + Off = 3, +} +``` + +We do not allow defaulting of number enums (unlike some other languages), because if a member from the middle of such an enum is added or removed, +all subsequent member values would be changed. This can be unsafe (e.g. push safety, serialization, logging). +Requiring the user to be explicit about the renumbering makes them think about the consequences of doing so. + +The value provided must be a number literal. Negative numbers are not literals in JS, but since Flow version 0.234 we allow negative number initializers as well. + +Example : + +```js flow-check +enum Status { + Active = 1, + Paused = 0, + Off = -1, +} +``` + +## Boolean enums {#toc-boolean-enums} +Boolean enums must have their values specified. Boolean enums can only have two members. + +You can specify a boolean enum like this: + +```js flow-check +enum Status { + Active = true, + Off = false, +} +``` +Optionally, you can use an `of` clause. +This does not affect the type-checking behavior of a valid Flow Enum, +it just ensures that all enum members are `boolean`s as the definition site. + +```js flow-check +enum Status of boolean { + Active = true, + Off = false, +} +``` + + +## Symbol enums {#toc-symbol-enums} +Symbol enums can’t have their values specified. Each member is a new symbol, with the symbol description set to the name of the member. +You must use the `of` clause with symbol enums, to distinguish them from string enums, which are the default when omitting values. + +You can specify a symbol enum like this: + +```js flow-check +enum Status of symbol { + Active, + Paused, + Off, +} +``` + + +## Flow Enums with Unknown Members {#toc-flow-enums-with-unknown-members} +You can specify that your enum contains "unknown members" by adding a `...` to the end of the declaration: + +```js flow-check +enum Status { + Active, + Paused, + Off, + ... +} +const status: Status = Status.Active; + +switch (status) { + case Status.Active: break; + case Status.Paused: break; + case Status.Off: break; +} +``` + +When this is used, Flow will always require a `default` when [switching over the enum](../using-enums/#toc-exhaustive-checking-with-unknown-members), +even if all known enum members are checked. The `default` checks for "unknown" members you haven't explicitly listed. + +This feature is useful when an enum value crosses some boundary and the enum declaration on each side may have different memebers. +For example, an enum definition which is used on both the client and the server: an enum member could be added, which would be immediately seen by the server, +but could be sent to an outdated client which isn't yet aware of the new member. + +One use case for this would be the JS output of [GraphQL Enums](https://graphql.org/learn/schema/#enumeration-types): +Flow Enums with unknown members could be used instead of the added `'%future added value'` member. + + +## Enums at runtime {#toc-enums-at-runtime} +Enums exist as values at runtime. We use a [Babel transform](https://www.npmjs.com/package/babel-plugin-transform-flow-enums) to transform +Flow Enum declarations into calls to the [enums runtime](https://www.npmjs.com/package/flow-enums-runtime) (read more in the [enabling enums documentation](../enabling-enums/)). +We use a runtime so all enums can share an implementation of the enum [methods](../using-enums/#toc-methods). + +We use `Object.create(null)` for enums' prototype (which has the enum methods), so properties in `Object.prototype` will not pollute enums. +The only own properties of the enum object are the enum members. The members are non-enumerable (use the [`.members()` method](../using-enums/#toc-members) for that). +The entire enum object is frozen, so it cannot be modified. + + +## Style guide {#toc-style-guide} + +#### Naming enums {#toc-naming-enums} +We encourage you to define enum names in `PascalCase`, following the naming conventions of other types. All caps names (e.g. `STATUS`) are harder to read and discouraged. + +We encourage you to name enums in the singular. E.g. `Status`, not `Statuses`. Just like the type of `true` and `false` is `boolean`, not `booleans`. + +Don't append `Enum` to the name (e.g. don't name your enum `StatusEnum`). This is unnecessary, just like we don't append `Class` to every class name, and `Type` to every type alias. + +#### Naming enum members {#toc-naming-enum-members} +We encourage you to define enum member names in `PascalCase`. All caps names (e.g. `ACTIVE`) are harder to read and discouraged. +Additionally, since Flow enforces that these are constants, you don't need to use the name to signal that intent to the programmer. + +See also: the rule about [enum member name starting characters](#toc-member-name-starting-character). + +#### Don't create a separate type {#toc-don-t-create-a-separate-type} +A Flow Enum, like a class, is both a type and a value. You don't need to create a separate type alias, you can use the enum name. + +#### Use dot access for accessing members {#toc-use-dot-access-for-accessing-members} +Prefer `Status.Active` vs. `const {Active} = Status;`. This makes it easier find uses of the enum with text search, and makes it clearer to the reader what enum is involved. + Additionally, this is required for [switch statements involving enums](../using-enums/#toc-exhaustively-checking-enums-with-a-switch). diff --git a/_src/enums/enabling-enums.md b/_src/enums/enabling-enums.md new file mode 100644 index 00000000000..2c6616ec3ad --- /dev/null +++ b/_src/enums/enabling-enums.md @@ -0,0 +1,57 @@ +--- +title: Enabling enums in your project +slug: /enums/enabling-enums +--- + +## Upgrade tooling {#toc-upgrade-tooling} +To enable Flow Enums in your repo, you must first update the following packages: + +- Upgrade to at least Flow 0.159 + - Flow needs to have some configuration set to enable enums - see below. +- Upgrade [Prettier](https://prettier.io/) to at least version 2.2 + - As of that version, Prettier can handle parsing and pretty printing Flow Enums out of the box. + - You must use the `flow` [parser option](https://prettier.io/docs/en/options.html#parser) for JavaScript files to be able to format Flow Enums. +- Upgrade [Babel](https://babeljs.io/) to at least version 7.13.0 + - As of that version, Babel can parse Flow Enums. However, to enable this parsing some configuration needs to be supplied, + and additionally it does not include the transform required - see below. +- Upgrade [jscodeshift](https://github.com/facebook/jscodeshift) to at least version 0.11.0 +- Upgrade [hermes-parser](https://www.npmjs.com/package/hermes-parser) to at least version 0.4.8 +- For ESLint, either: + - Use [hermes-eslint](https://www.npmjs.com/package/hermes-eslint) as your ESLint parser, at least version 0.4.8 + - Or upgrade [babel-eslint](https://github.com/babel/babel-eslint) to version 10.1.0 + - As of that version, `babel-eslint` can handle Flow Enums out of the box. + - Do not upgrade to 11.x, this branch does not support Flow Enums. + - Or use another solution using Babel 7.13.0 or later, with Flow enabled - this may also work + +If you have any other tool which examines your code, you need to update it as well. If it uses [flow-parser](https://www.npmjs.com/package/flow-parser), +[hermes-parser](https://www.npmjs.com/package/hermes-parser) or `@babel/parser`, upgrade those as per the instructions above. +If it uses some other parser, you will need to implement parsing Flow Enums in that parser. You can look at the existing code in Babel, Flow, and Hermes parsers to guide your work. + + +## Enable enums {#toc-enable-enums} +- In your `.flowconfig`, under the `[options]` heading, add `enums=true` +- Add the Flow Enums Babel transform. It turns enum declaration AST nodes into calls to the runtime: + [babel-plugin-transform-flow-enums](https://www.npmjs.com/package/babel-plugin-transform-flow-enums). + Add it to your development dependencies and adjust your Babel configuration to use the transform. + The transform by default requires the runtime package directly (below), but you can configure this. +- Add the Flow Enum runtime package to your production dependencies. + This will be required and used at runtime to create Flow Enums: [flow-enums-runtime](https://www.npmjs.com/package/flow-enums-runtime) + + +## Enable suggested ESLint rules {#toc-enable-suggested-eslint-rules} +Enums can be exhaustively checked in `switch` statements, so may increase the use of `switch` statements compared to before. +To prevent common issues with `switch` statements, we suggest you enable these ESLint rules (at least as warnings): + +- [no-fallthrough](https://eslint.org/docs/rules/no-fallthrough): + This prevents the user from accidentally forgetting a `break` statement at the end of their switch case, while supporting common use-cases. +- [no-case-declarations](https://eslint.org/docs/rules/no-case-declarations): + This prevents lexicaly scoped declarations (`let`, `const`) from being introduced in a switch case, without wrapping that case in a new block. + Otherwise, declarations in different cases could conflict. + +We also have some Flow Enums specific rules as part of [eslint-plugin-fb-flow](https://www.npmjs.com/package/eslint-plugin-fb-flow): +- [use-flow-enums](https://www.npmjs.com/package/eslint-plugin-fb-flow#use-flow-enums): + Suggests that enum-like `Object.freeze` and `keyMirror` usage be turned into Flow Enums instead. +- [flow-enums-default-if-possible](https://www.npmjs.com/package/eslint-plugin-fb-flow#flow-enums-default-if-possible): + Auto-fixes string enums with specified values identical to the member names to defaulted enums. +- [no-flow-enums-object-mapping](https://www.npmjs.com/package/eslint-plugin-fb-flow#no-flow-enums-object-mapping): + Suggests using a function with a switch to map enum values to other values, instead of an object literal. diff --git a/_src/enums/index.md b/_src/enums/index.md new file mode 100644 index 00000000000..d6e5cad9459 --- /dev/null +++ b/_src/enums/index.md @@ -0,0 +1,145 @@ +--- +title: Flow Enums +description: "Define a fixed set of constants which create their own type. Exhaustively checked in switch statements." +slug: /enums +--- + +Flow Enums define a fixed set of constants which create their own type. + +Unlike other features of Flow, Flow Enums exist as values at runtime, as well as existing as types. + +[Read how to enable Flow Enums in your project](./enabling-enums/). + + +## Benefits {#toc-benefits} +Enums provide several benefits over existing patterns: + +* Reduce repetition: Enum declarations provide both the type and the value of the enum. +* Improve Flow performance: Enums are guaranteed to have good type-checking performance, + unlike unions which may be expensive to type-check in certain situations. +* Enable new functionality: Enums come with a `cast` [method](./using-enums/#toc-methods), which converts from a primitive type to an enum type safely. +* Enhance safety: Enums define their own type which does not implicitly coerce to and from other types (e.g. from `string`s), + and are required to be [exhaustively checked in switch statements](./using-enums/#toc-exhaustively-checking-enums-with-a-switch). These properties can help prevent logic bugs. + + +## Quickstart {#toc-quickstart} + +#### [Defining enums](./defining-enums) {#toc-defining-enums-defining-enums} +An enum named `Status` with three members: `Active`, `Paused`, and `Off`. + +```js flow-check +enum Status { + Active, + Paused, + Off, +} +``` +By default, enums define members with string values which mirror their names. You can also explicitly set values: +```js flow-check +enum Status { + Active = 'active', + Paused = 'paused', + Off = 'off', +} +``` +You can use numbers as well: +```js flow-check +enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +``` +Values must be unique, literals, and all of the same type. Check out the [full docs on defining enums](./defining-enums/) to learn more. + + +#### [Using enums](./using-enums/) {#toc-using-enums-using-enums} +To access an enum member, use dot access: + +```js +Status.Active +``` +To use the enum type as an annotation, use the enum name: + +```js +const status: Status = Status.Active; +``` +Cast from the representation type (in this case, a `string`) to the enum type: + +```js +const status: Status | void = Status.cast(someString); +``` +You can easily provide a default value with the `??` operator: + +```js +const status: Status = Status.cast(someString) ?? Status.Off; +``` +Read more about the [other methods enums provide](./using-enums/#toc-methods), including `isValid`, `members`, and `getName`. + +Cast an enum type to its representation type (must be done explicitly): + +```js +status as string +``` +Checks of enums in `switch` statements are exhaustive - we ensure you check all members: +```js flow-check +enum Status { + Active, + Paused, + Off, +} +const status: Status = Status.Active; + +// ERROR: Incomplete exhaustive check +switch (status) { + case Status.Active: break; + case Status.Paused: break; + // We forgot to add `case: Status.Off:` here, resulting in error above. + // Using `default:` would also work to check all remaining members. +} +``` +Read more about [exhaustively checking enums](./using-enums/#toc-exhaustively-checking-enums-with-a-switch). + +Check out the [the full docs on using enums](./using-enums/) to learn more. + + +## When to use Flow Enums {#toc-when-to-use-flow-enums} +If you previously defined a union type of literals, you can use an enum to define that type instead. Instead of + +```js flow-check +type Status = + | 'Active' + | 'Paused' + | 'Off'; + +const x: Status = 'Active'; +``` + +or +```js flow-check +const Status = Object.freeze({ + Active: 'Active', + Paused: 'Paused', + Off: 'Off', +}); +type StatusType = $Keys; +const x: StatusType = Status.Active; +``` + +you can use: +```js flow-check +enum Status { + Active, + Paused, + Off, +} +const x: Status = Status.Active; +``` + +See [migrating from legacy patterns](./migrating-legacy-patterns) to learn more about migrating legacy JavaScript enum patterns to Flow Enums. + + +## When to not use Flow Enums {#toc-when-to-not-use-flow-enums} +Enums are designed to cover many use cases and exhibit certain benefits. The design makes a variety of trade-offs to make this happen, and in certain situations, +these trade-offs might not be right for you. In those cases, you can continue to use existing patterns to satisfy your use cases. +[Read more about those situations](./using-enums/#toc-when-to-not-use-enums). diff --git a/_src/enums/migrating-legacy-patterns.md b/_src/enums/migrating-legacy-patterns.md new file mode 100644 index 00000000000..2b2687e5bf6 --- /dev/null +++ b/_src/enums/migrating-legacy-patterns.md @@ -0,0 +1,224 @@ +--- +title: Migrating from legacy patterns +slug: /enums/migrating-legacy-patterns +--- + +Learn how to migrate to Flow Enums from legacy JavaScript enum patterns like `Object.freeze`. + +First, learn how to [update the enum definition site](#toc-updating-definitions), and next learn how to [update files that import and use the enum](#toc-updating-usage). + +## Updating definitions {#toc-updating-definitions} + +#### Object.freeze {#toc-object-freeze} +If you are using `Object.freeze`, you can migrate to an enum if the values of the object are: + +* All the same primitive type, and that type is `boolean`, `string`, `number`, or `symbol`. +* All literals. +* Contain no duplicate values. + +Replace + +```js flow-check +const Status = Object.freeze({ + Active: 1, + Paused: 2, + Off: 3, +}); + +export type StatusType = $Values; + +export default Status; +``` + +with +```js flow-check +export default enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +``` + +- Check to ensure that the key names do not start with lowercase ‘a’-‘z’ (disallowed in enums). If they do, you’ll need to rename the member names. +- Remove any usage of `$Keys<...>` or `$Values<...>` on the enum type, these are no longer needed as a Flow Enum defines a type itself (its name). +- Delete any type exports based on the enum, as you just need to export the Flow Enum. A Flow Enum acts as both a type and a value (like a class). + +Then, take a look at [how to update files that import and use the enum](#toc-updating-usage). + + +#### keyMirror {#toc-keymirror} +The `keyMirror` utility creates an object whose values are mirrors of its key names. You can replace `keyMirror` usage with a string based enum. + +Replace + +```js +import keyMirror from 'keyMirror'; + +const Status = keyMirror({ + Active: null, + Paused: null, + Off: null, +}); + +export type StatusType = $Keys; + +export default Status; +``` + +with + +```js flow-check +export default enum Status { + Active, + Paused, + Off, +} +``` + +- Check to ensure that the key names do not start with lowercase ‘a’-‘z’ (disallowed in enums). If they do, you’ll need to rename the member names. +- Remove any usage of `$Keys<...>` on the enum type, it's no longer needed as a Flow Enum defines a type itself (its name). +- Delete any type exports based on the enum, you just need to export the Flow Enum. A Flow Enum acts as both a type and a value (like a class). + +Then, take a look at [how to update files that import and use the enum](#toc-updating-usage). + + +## Updating usage {#toc-updating-usage} + +#### Fix type imports {#toc-fix-type-imports} +Previous patterns required you to export (and then import) a type separate from the enum itself. +Flow Enums are both types and values (like a class), so you just need to export the Flow Enum itself. Since there is now one export, you only need one import. +Read more about [exporting enums](../using-enums/#toc-exporting-enums) and [importing enums](../using-enums/#toc-importing-enums). + +If you previously had: +```js flow-check +const Status = Object.freeze({ + Active: 1, + Paused: 2, + Off: 3, +}); +export type StatusType = $Values; +export default Status; +``` + +And you've replaced it with: +```js flow-check +export default enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +``` + +Then you need to fix the imports as well: + +##### If both type and value were imported {#toc-if-both-type-and-value-were-imported} +For a user of the enum, if you previously imported both the type and the value, you can delete the type import and update annotations used. + +Change +```js +import type {StatusType} from 'status'; + +import Status from 'status'; + +const myStatus: StatusType = Status.Active; +``` +to +```js +// Type import is deleted + +import Status from 'status'; + +const myStatus: Status = Status.Active; // Changed type annotation to just `Status` +``` + +##### If only the type was imported {#toc-if-only-the-type-was-imported} +For a user of the enum, if you previously imported just the type, change the type import to a default import rather than a named import. + +Change +```js +import type {StatusType} from 'status'; + +function isActive(status: StatusType) { ... } +``` +to + +```js +// Remove the braces `{` `}` and changed the name - this is a default import now +import type Status from 'status'; + +function isActive(status: Status) { ... } // Changed type annotation to just `Status` +``` + + +#### Mapping enums to other values {#toc-mapping-enums-to-other-values} +Sometimes you want to map from an enum value to some other value. Previously, we sometimes used object literals for this. +With Flow Enums, use a function with a `switch` instead. The switch is [exhaustively checked](../using-enums/#toc-exhaustively-checking-enums-with-a-switch), +so Flow will ensure you update your mapping when you add or remove Flow Enum members. + +Replace this pattern + +```js +const STATUS_ICON: {[Status]: string} = { + [Status.Active]: 'green-checkmark', + [Status.Paused]: 'grey-pause', + [Status.Off]: 'red-x', +}; +const icon = STATUS_ICON[status]; +``` + +with + +```js +function statusIcon(status: Status): string { + switch (status) { + case Status.Active: + return 'green-checkmark'; + case Status.Paused: + return 'grey-pause'; + case Status.Off: + return 'red-x'; + } +} +const icon = statusIcon(status); +``` +Read more about [mapping enums to other values](../using-enums/#toc-mapping-enums-to-other-values). + + +#### Usage as the representation type (e.g. a string) {#toc-usage-as-the-representation-type-e-g-a-string} +You can't use a Flow Enum directly as its representation type (e.g. a `string`). +If you get Flow errors about using an enum as its representation type, first try to refactor your code so that it expects the enum type instead of the representation type +(e.g. change annotations from `string` to `Status`). If you really want to use the enum as its representation type, you can add in explicit casts. +See [casting to represetation type](../using-enums/#toc-casting-to-representation-type). + + +#### Casting to the enum type {#toc-casting-to-the-enum-type} +If before you cast from an enum's representation type (e.g. `string`) to the enum type with something like this: + +```js +function castToStatus(input: number): StatusType | void { + switch(input) { + case 1: return Status.Active; + case 2: return Status.Paused; + case 3: return Status.Off; + default: return undefined; + } +} + +castToStatus(x); +``` + +You can now just use the [cast](../using-enums/#toc-cast) method: + +```js +Status.cast(x); +``` + + +#### Update switch statements {#toc-update-switch-statements} +Flow Enums are exhaustively checked in `switch` statements. You might need to update your code when you are switching over an enum value. +Read more at [exhaustively checking enums in switch statements](../using-enums/#toc-exhaustively-checking-enums-with-a-switch). + + +#### Operations over enum members {#toc-operations-over-enum-members} +If previously you used functionality like `Object.values`, `Object.keys`, or `for-in` loops to get and operate on the enum members, +you can use the [members method](../using-enums/#toc-members) instead. diff --git a/_src/enums/using-enums.md b/_src/enums/using-enums.md new file mode 100644 index 00000000000..9d806732251 --- /dev/null +++ b/_src/enums/using-enums.md @@ -0,0 +1,711 @@ +--- +title: Using enums +slug: /enums/using-enums +--- + +import {SinceVersion} from '../../components/VersionTags'; + +Flow Enums are not a syntax for [union types](../../types/unions/). They are their own type, and each member of a Flow Enum has the same type. +Large union types can cause performance issues, as Flow has to consider each member as a separate type. With Flow Enums, no matter how large your enum is, +Flow will always exhibit good performance as it only has one type to keep track of. + +We use the following enum in the examples below: +```js +enum Status { + Active, + Paused, + Off, +} +``` + +### Accessing enum members {#toc-accessing-enum-members} +Access members with the dot syntax: + +```js +const status = Status.Active; +``` +You can’t use computed access: + +```js flow-check +enum Status { + Active, + Paused, + Off, +} +const x = "Active"; +Status[x]; // Error: computed access on enums is not allowed +``` + +### Using as a type annotation {#toc-using-as-a-type-annotation} +The enum declaration defines both a value (from which you can access the enum members and methods) and a type of the same name, which is the type of the enum members. + +```js +function calculateStatus(): Status { + ... +} + +const status: Status = calculateStatus(); +``` + +### Casting to representation type {#toc-casting-to-representation-type} +Enums do not implicitly coerce to their representation type or vice-versa. +If you want to convert from the enum type to the representation type, you can use an explicit cast `x as string`: + +```js flow-check +enum Status {Active, Paused, Off} + +const s: string = Status.Active; // Error: 'Status' is not compatible with 'string' + +declare const status: Status; +const statusString: string = status as string; +``` + +You can also call `.valueOf()` on the enum value . +This is particularly useful if you are dealing with [abstract enums](#toc-abstract-enums) and don't know the representation type. + +```js flow-check +enum Status {Active, Paused, Off} + +declare const status: Status; +const statusString: string = status.valueOf(); +``` + +To convert from a nullable enum type to nullable representation type, you can do: + +```js flow-check +enum Status {Active, Paused, Off} +declare const maybeStatus: ?Status; + +// Using `as` cast and `&&`: +const maybeStatusString1: ?string = maybeStatus && (maybeStatus as string); + +// Using `.valueOf()` and optional chaining: +const maybeStatusString2: ?string = maybeStatus?.valueOf(); +``` + +If you want to convert from the representation type (e.g. `string`) to an enum type (if valid), check out the [cast method](#toc-cast). + +### Methods {#toc-methods} +Enum declarations also define some helpful methods. + +Below, `TEnum` is the type of the enum (e.g. `Status`), and `TRepresentationType` is the type of the representation type for that enum (e.g. `string`). + +#### .cast {#toc-cast} +Type: `cast(input: ?TRepresentationType): TEnum | void` + +The `cast` method allows you to safely convert a primitive value, like a `string`, to the enum type (if it is a valid value of the enum), and `undefined` otherwise. + +```js +const data: string = getData(); +const maybeStatus: Status | void = Status.cast(data); +if (maybeStatus != null) { + const status: Status = maybeStatus; + // do stuff with status +} +``` + +Set a default value in one line with the `??` operator: +```js +const status: Status = Status.cast(data) ?? Status.Off; +``` + +The type of the argument of `cast` depends on the type of enum. If it is a [string enum](../defining-enums/#toc-string-enums), the type of the argument will be `string`. +If it is a [number enum](../defining-enums/#toc-number-enums), the type of the argument will be `number`, and so on. +If you wish to cast a `mixed` value, first use a `typeof` refinement: +```js +const data: mixed = ...; +if (typeof data === 'string') { + const maybeStatus: Status | void = Status.cast(data); +} +``` + +`cast` uses `this` (representing the object of enum members), so if you want to pass the function itself as a value, you should use an arrow function. For example: +```js +const strings: Array = ...; +// WRONG: const statuses: Array = strings.map(Status.cast); +const statuses: Array = strings.map((input) => Status.cast(input)); // Correct +``` + +Runtime cost: For [mirrored string enums](../defining-enums/#toc-string-enums) (e.g `enum E {A, B}`), as the member names are the same as the values, the runtime cost is constant - +equivalent to calling `.hasOwnProperty`. For other enums, a `Map` is created on the first call, and subsequent calls simply call `.has` on the cached map. +Thus the cost is amoritzed constant. + +#### .isValid {#toc-isvalid} +Type: `isValid(input: ?TRepresentationType): boolean` + +The `isValid` method is like `cast`, but simply returns a boolean: `true` if the input supplied is a valid enum value, and `false` if it is not. + +```js +const data: string = getData(); +const isStatus: boolean = Status.isValid(data); +``` + +`isValid` uses `this` (representing the object of enum members), so if you want to pass the function itself as a value, you should use an arrow function. For example: + +```js +const strings: Array = ...; +// WRONG: const statusStrings = strings.filter(Status.isValid); +const statusStrings = strings.filter((input) => Status.isValid(input)); // Correct +``` + +Runtime cost: The same as described under `.cast` above. + +#### .members {#toc-members} +Type: `members(): Iterator` + +The `members` method returns an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#iterators) (that is iterable) of all the enum members. + +```js +const buttons = []; +function getButtonForStatus(status: Status) { ... } + +for (const status of Status.members()) { + buttons.push(getButtonForStatus(status)); +} +``` +The iteration order is guaranteed to be the same as the order of the members in the declaration. + +The enum is not enumerable or iterable itself (e.g. a for-in/for-of loop over the enum will not iterate over its members), you have to use the `.members()` method for that purpose. + +You can convert the iterable into an `Array` using: `Array.from(Status.members())`. +You can make use of [`Array.from`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from)'s second argument to map over the values at +the same time you construct the array: e.g. + +```js +const buttonArray = Array.from( + Status.members(), + status => getButtonForStatus(status), +); +``` + +#### .getName {#toc-getname} +Type: `getName(value: TEnum): string` + +The `getName` method maps enum values to the string name of that value's enum member. When using `number`/`boolean`/`symbol` enums, +this can be useful for debugging and for generating internal CRUD UIs. For example: +```js +enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +const status: Status = ...; + +console.log(Status.getName(status)); +// Will print a string, either "Active", "Paused", or "Off" depending on the value. +``` +Runtime cost: The same as described under `.cast` above. A single cached reverse map from enum value to enum name is used for `.cast`, `.isValid`, and `.getName`. +The first call of any of those methods will create this cached map. + + +### Exhaustively checking enums with a `switch` {#toc-exhaustively-checking-enums-with-a-switch} +When checking an enum value in a `switch` statement, we enforce that you check against all possible enum members, and don’t include redundant cases. +This helps ensure you consider all possibilities when writing code that uses enums. It especially helps with refactoring when adding or removing members, +by pointing out the different places you need to update. + +```js +const status: Status = ...; + +switch (status) { // Good, all members checked + case Status.Active: + break; + case Status.Paused: + break; + case Status.Off: + break; +} +``` + +You can use `default` to match all members not checked so far: +```js +switch (status) { + case Status.Active: + break; + default: // When `Status.Paused` or `Status.Off` + break; +} +``` + +You can check multiple enum members in one switch case: +```js +switch (status) { + case Status.Active: + case Status.Paused: + break; + case Status.Off: + break; +} +``` + +You must match against all of the members of the enum (or supply a `default` case): +```js flow-check +enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +const status: Status = Status.Active; + +// Error: you haven't checked 'Status.Off' in the switch +switch (status) { + case Status.Active: + break; + case Status.Paused: + break; +} +``` + +You can’t repeat cases (as this would be dead code!): +```js flow-check +enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +const status: Status = Status.Active; + +switch (status) { + case Status.Active: + break; + case Status.Paused: + break; + case Status.Off: + break; + case Status.Paused: // Error: you already checked for 'Status.Paused' + break; +} +``` + +A `default` case is redundant if you’ve already matched all cases: +```js flow-check +enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +const status: Status = Status.Active; + +switch (status) { + case Status.Active: + break; + case Status.Paused: + break; + case Status.Off: + break; + default: // Error: you've already checked all cases, the 'default' is redundant + break; +} +// The following is OK because the `default` covers the `Status.Off` case: +switch (status) { + case Status.Active: + break; + case Status.Paused: + break; + default: + break; +} +``` +Except if you are switching over an enum with [unknown members](../defining-enums/#toc-flow-enums-with-unknown-members). + +If you nest exhaustively checked switches inside exhaustively checked switches, and are returning from each branch, you must add a `break;` after the nested switch: +```js +switch (status) { + case Status.Active: + return 1; + case Status.Paused: + return 2; + case Status.Off: + switch (otherStatus) { + case Status.Active: + return 1; + case Status.Paused: + return 2; + case Status.Off: + return 3; + } + break; +} +``` + +Remember, you can add blocks to your switch cases. They are useful if you want to use local variables: + +```js +switch (status) { + case Status.Active: { + const x = f(); + ... + break; + } + case Status.Paused: { + const x = g(); + ... + break; + } + case Status.Off: { + const y = ...; + ... + break; + } +} +``` +If you didn't add blocks in this example, the two declarations of `const x` would conflict and result in an error. + +Enums are not checked exhaustively in `if` statements or other contexts other than `switch` statements. + + +### Exhaustive checking with unknown members {#toc-exhaustive-checking-with-unknown-members} +If your enum has [unknown members](../defining-enums/#toc-flow-enums-with-unknown-members) (specified with the `...`), e.g. + +```js +enum Status { + Active, + Paused, + Off, + ... +} +``` + +Then a `default` is always required when switching over the enum. The `default` checks for "unknown" members you haven't explicitly listed. + +```js +switch (status) { + case Status.Active: + break; + case Status.Paused: + break; + case Status.Off: + break; + default: + // Checks for members not explicitly listed +} +``` + +You can use the `require-explicit-enum-switch-cases` [Flow Lint](../../linting/flowlint-comments/) to require that all known members are explicitly listed as cases. For example: + +```js flow-check +enum Status { + Active = 1, + Paused = 2, + Off = 3, +} +const status: Status = Status.Active; + +// flowlint-next-line require-explicit-enum-switch-cases:error +switch (status) { + case Status.Active: + break; + case Status.Paused: + break; + default: + break; +} +``` + +You can fix if by doing: +```js +// flowlint-next-line require-explicit-enum-switch-cases:error +switch (status) { + case Status.Active: + break; + case Status.Paused: + break; + case Status.Off: // Added the missing `Status.Off` case + break; + default: + break; +} +``` +The `require-explicit-enum-switch-cases` lint is not one to enable globally, but rather on a per-`switch` basis when you want the behavior. +With normal enums, for each `switch` statement on it, you can either provide a `default` or not, and thus decide if you want to require each case explicitly listed or not. +Similarly for Flow Enums with unknown members, you can also enable this lint on a per-switch basis. + +The lint works for switches of regular Flow Enum types as well. +It in effect bans the usage of `default` in that `switch` statement, by requiring the explicit listing of all enum members as cases. + + +### Mapping enums to other values {#toc-mapping-enums-to-other-values} +There are a variety of reasons you may want to map an enum value to another value, e.g. a label, icon, element, and so on. + +With previous patterns, it was common to use object literals for this purpose, however with Flow Enums we prefer functions which contain a switch, which we can exhaustively check. + +Instead of: +```js +const STATUS_ICON: {[Status]: string} = { + [Status.Active]: 'green-checkmark', + [Status.Paused]: 'grey-pause', + [Status.Off]: 'red-x', +}; +const icon = STATUS_ICON[status]; +``` + +Which doesn't actually guarantee that we are mapping each `Status` to some value, use: +```js +function getStatusIcon(status: Status): string { + switch (status) { + case Status.Active: + return 'green-checkmark'; + case Status.Paused: + return 'grey-pause'; + case Status.Off: + return 'red-x'; + } +} +const icon = getStatusIcon(status); +``` + +In the future if you add or remove an enum member, Flow will tell you to update the switch as well so it's always accurate. + +If you actually want a dictionary which is not exhaustive, you can use a [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map): +```js +const counts = new Map([ + [Status.Active, 2], + [Status.Off, 5], +]); +const activeCount: Status | void = counts.get(Status.Active); +``` + +Flow Enums cannot be used as keys in object literals, as [explained later on this page](#toc-distinct-object-keys). + + +### Enums in a union {#toc-enums-in-a-union} +If your enum value is in a union (e.g. `?Status`), first refine to only the enum type: + +```js +const status: ?Status = ...; + +if (status != null) { + status as Status; // 'status' is refined to 'Status' at this point + switch (status) { + case Status.Active: break; + case Status.Paused: break; + case Status.Off: break; + } +} +``` +If you want to refine *to* the enum value, you can use `typeof` with the representation type, for example: + +```js +const val: Status | number = ...; + +// 'Status' is a string enum +if (typeof val === 'string') { + val as Status; // 'val' is refined to 'Status' at this point + switch (val) { + case Status.Active: break; + case Status.Paused: break; + case Status.Off: break; + } +} +``` + + +### Exporting enums {#toc-exporting-enums} +An enum is a type and a value (like a class is). To export both the type and the value, export it like a you would a value: + +```js flow-check +export enum Status {} +``` +Or, as the default export (note: you must always specify an enum name, `export default enum {}` is not allowed): + +```js flow-check +export default enum Status {} +``` + +Using CommonJS: +```js flow-check +enum Status {} +module.exports = Status; +``` + +To export **only** its type, but not the value, you can do: + +```js flow-check +enum Status {} +export type {Status}; +``` +Other functions within the file will still have access to the enum implementation. + + +### Importing enums {#toc-importing-enums} +If you have exported an enum like this: + +```js flow-check +// status.js +export default enum Status { + Active, + Paused, + Off, +} +``` + +You can import it as both a value and a type like this: +```js +import Status from 'status'; + +const x: Status /* used as type */ = Status.Active /* used as value */; +``` + +If you only need to use the type, you can import it as a type: +```js +import type Status from 'status'; + +function printStatus(status: Status) { + ... +} +``` + +Using CommonJS: +```js +const Status = require('status'); +``` + + +### Abstract enums {#toc-abstract-enums} + +You can write code that operates on Flow Enums in a generic way using two types: `Enum<>` and `EnumValue<>`, which accept any Flow Enum and its values. + +#### EnumValue<> +`EnumValue<>` accepts any Flow Enum value. Want to narrow it down to enum values with a specific representation type? Just add a type argument: + +```js flow-check +enum Status {Active, Paused, Off} + +Status.Active as EnumValue<>; // OK +Status.Active as EnumValue; // OK - its a string enum +Status.Active as EnumValue; // ERROR - not a number enum +``` + +The new `EnumRepresentationTypes` type represents all the valid representation types: `string`, `number`, etc. + +Flow Enum values don’t implicitly coerce to their representation type, so we allow [explicit conversion](#toc-casting-to-representation-type) +with casts like `Status.Active as string`. With `EnumValue<>` you might not know the specific representation type, +so we now allow casts using `.valueOf()` directly on the enum value. For example: + +```js flow-check +function f(e: EnumValue<>) { + const x: EnumRepresentationTypes = e.valueOf(); // OK +} +``` + +#### Enum<> +`Enum<>` accepts any Flow Enum - the enum itself, rather than its value. You can optionally supply a type argument to +restrict it to enums with a certain enum value, which you can pair with the `EnumValue` type above: + +```js flow-check +enum Status {Active, Paused, Off} + +Status as Enum<>; // OK +Status as Enum; // OK +Status as Enum>; // OK +``` + +With the `Enum<>` type you can use all the Flow Enum [methods](#toc-methods) like `.cast` and `.members`. +This lets you craft code that generically handles Flow Enums, for example creating a full React selector component from only a Flow Enum: + +##### Usage + +```js +enum Status { + Active, + Paused, + Off, +} + +``` + +##### Definition + +```js flow-check +import * as React from 'react'; +import {useCallback, useMemo, useState} from 'react'; + +component Selector< + TEnumValue: EnumValue, +>( + items: Enum, + callback: (TEnumValue) => void, +) { + // Typing the `useState` selector with the enum value generic + const [value, setValue] = useState(null); + + const handleChange = useCallback((e: SyntheticInputEvent<>) => { + // Using the `.cast` method + const value = items.cast(e.target.value); + if (value == null) { throw new Error("Invalid value"); } + setValue(value); + callback(value); + }, [items, callback]); + + return
    + {Array.from(items.members(), item => // Using the `.members()` method +
  • + +
  • + )} +
; +} +``` + +### When to not use enums {#toc-when-to-not-use-enums} +Enums are designed to cover many use cases and exhibit certain benefits. The design makes a variety of trade-offs to make this happen, and in certain situations, +these trade-offs might not be right for you. In these cases, you can continue to use existing patterns to satisfy your use cases. + + +#### Distinct object keys {#toc-distinct-object-keys} +You can’t use enum members as distinct object keys. + +The following pattern works because the types of `LegacyStatus.Active` and `LegacyStatus.Off` are different. One has the type `'Active'` and one has the type `'Off'`. + +```js flow-check +const LegacyStatus = Object.freeze({ + Active: 'Active', + Paused: 'Paused', + Off: 'Off', +}); +const o = { + [LegacyStatus.Active]: "hi", + [LegacyStatus.Off]: 1, +}; +const x: string = o[LegacyStatus.Active]; // OK +const y: number = o[LegacyStatus.Off]; // OK +const z: boolean = o[LegacyStatus.Active]; // Error - as expected +``` +We can’t use the same pattern with enums. All enum members have the same type, the enum type, so Flow can’t track the relationship between keys and values. + +If you wish to map from an enum value to another value, you should use a [function with an exhaustively-checked switch instead](#toc-mapping-enums-to-other-values). + + +#### Disjoint object unions {#toc-disjoint-object-unions} +A defining feature of enums is that unlike unions, each enum member does not form its own separate type. Every member has the same type, the enum type. +This allows enum usage to be analyzed by Flow in a consistently fast way, however it means that in certain situations which require separate types, we can’t use enums. +Consider the following union, following the [disjoint object union](../../types/unions/#toc-disjoint-object-unions) pattern: + +```js flow-check +type Action = + | {type: 'Upload', data: string} + | {type: 'Delete', id: number}; +``` +Each object type in the union has a single common field (`type`) which is used to distinguish which object type we are dealing with. + +We can’t use enum types for this field, because for this mechanism to work, the type of that field must be different in each member of the union, +but enum members all have the same type. + +In the future, we might add the ability for enums to encapsulate additional data besides a key and a primitive value - this would allow us to replace disjoint object unions. + + +#### Guaranteed inlining {#toc-guaranteed-inlining} +Flow Enums are designed to allow for inlining (e.g. [member values must be literals](../defining-enums/#toc-literal-member-values), +[enums are frozen](../defining-enums/#toc-fixed-at-declaration)), however the inlining itself needs to be part of the build system (whatever you use) rather than Flow itself. + +While enum member access (e.g. `Status.Active`) can be inlined (other than [symbol enums](../defining-enums/#toc-symbol-enums) which cannot be inlined due to the nature of symbols), +usage of its methods (e.g. `Status.cast(x)`) cannot be inlined. diff --git a/_src/errors/index.md b/_src/errors/index.md new file mode 100644 index 00000000000..e0e2aa95747 --- /dev/null +++ b/_src/errors/index.md @@ -0,0 +1,79 @@ +--- +title: Error Suppressions +slug: /errors +description: "Learn how to suppress Flow's type errors." +--- + +Flow reports many different kinds of errors for many common programming mistakes, but not every JavaScript pattern can be understood by Flow. +If you are confident your code is correct, and that Flow is +erroring too conservatively, you can suppress the error so that +Flow does not report it. + +### What is a Suppression? {#toc-what-is-a-suppression} + +A suppression is a special kind of comment that you can place on the line before a type +error. It tells Flow not to report that error when checking your code. Suppression +comments look like the following: + +``` +// [] extra text +``` + +A suppressor can be one of the following: +- `$FlowFixMe`: for type errors that you intend to fix later +- `$FlowIssue`: for a type error that you suspect is an issue with Flow +- `$FlowExpectedError`: for a location where you expect Flow to produce a type error (for instance, when performing an invalid type cast). +- `$FlowIgnore`: for locations where you want Flow to ignore your code + +Note that all of the suppressors behave identically; we simply recommend using them as described here for your own ease of reference. + +The `` portion of a suppression is optional, but when included specifies which [error code](#toc-making-suppressions-more-granular-with-error-codes) the suppression affects. + +Some examples of suppression comments: + +``` +// $FlowFixMe + +// $FlowIssue[incompatible-type] + +/* $FlowIgnore[prop-missing] some other text here */ + +/* $FlowFixMe[incompatible-cast] this + is a multi-line + comment */ + +{ /* $FlowIssue this is how you suppress errors inside JSX */ } +``` + +In order to be a valid suppression comment, there are also some conditions that must be true: +- No text can precede the suppressor, or come between the suppressor and the code. For example: `// some text then $FlowFixMe` is not a valid suppression, nor is `// $FlowIssue some text [incompatible-type]` or ` //$FlowFixMe [prop-missing]` (note the space here!). +- Suppressions must be on the line immediately before the error they suppress, otherwise they will not apply. + +### Making Suppressions More Granular with Error Codes {#toc-making-suppressions-more-granular-with-error-codes} + +Suppressible Flow errors will also have an error code associated with them (after version 0.127). This code concisely describes the type of issue the error is reporting, and is different between different kinds of errors. + +In order to prevent suppressions from suppressing different kinds of type errors on the same line (by default suppressions without codes suppress every error on the following line), you can add an error code to your suppression. For example: `// $FlowFixMe[incompatible-cast]` would only suppress errors with the `incompatible-cast` code. So: + +```js flow-check +// $FlowFixMe[incompatible-cast] +3 as string; +``` +would report no errors, but +```js flow-check +// $FlowFixMe[prop-missing] +3 as string; +``` +would still report a type incompatibility. + +To suppress multiple error codes on the same line, you can stack suppression comments one after another, and they will all apply to the first non-comment line like so: + +```js flow-check +let y: number | {x : number} = 1; + +// $FlowFixMe[incompatible-cast] +// $FlowFixMe[prop-missing] +y.x as string; +``` + +This will suppress both of the two errors on this line. diff --git a/_src/faq.md b/_src/faq.md new file mode 100644 index 00000000000..59a52670d43 --- /dev/null +++ b/_src/faq.md @@ -0,0 +1,278 @@ +--- +title: FAQ +description: Have a question about using Flow? Check here first! +slug: /faq +--- + +## I checked that `foo.bar` is not `null`, but Flow still thinks it is. Why does this happen and how can I fix it? + +Flow does not keep track of side effects, so any function call may potentially nullify your check. +This is called [refinement invalidation](../lang/refinements/#toc-refinement-invalidations). Example: + +```js flow-check +type Param = { + bar: ?string, +} +function myFunc(foo: Param): string { + if (foo.bar) { + console.log("checked!"); + return foo.bar; // Flow errors. If you remove the console.log, it works + } + + return "default string"; +} +``` + +You can get around this by storing your checked values in local variables: + +```js flow-check +type Param = { + bar: ?string, +} +function myFunc(foo: Param): string { + if (foo.bar) { + const bar = foo.bar; + console.log("checked!"); + return bar; // Ok! + } + + return "default string"; +} +``` + +## I checked that my value is of type A, so why does Flow still believe it's A | B? + +Refinement invalidation can also occur variables are updated: + +```js flow-check +type T = string | number; + +let x: T = 'hi'; + +function f() { + x = 1; +} + +if (typeof x === 'string') { + f(); + x as string; +} +``` + +A work around would be to make the variable `const` and refactor your code to avoid the reassignment: + +```js flow-check +type T = string | number; + +const x: T = 'hi'; + +function f(x: T): number { + return 1; +} + +if (typeof x === 'string') { + const xUpdated = f(x); + xUpdated as number; + x as string; +} +``` + +## I'm in a closure and Flow ignores the if check that asserts that `foo.bar` is defined. Why? + +In the previous section we showed how refinements are lost after a function call. The exact same thing happens within closures, since +Flow does not track how your value might change before the closure is called: + +```js flow-check +const people = [{age: 12}, {age: 18}, {age: 24}]; +const oldPerson: {age: ?number} = {age: 70}; +if (oldPerson.age) { + people.forEach(person => { + console.log(`The person is ${person.age} and the old one is ${oldPerson.age}`); + }) +} +``` + +The solution here is to move the if check in the `forEach`, or to assign the `age` to an intermediate variable: + +```js flow-check +const people = [{age: 12}, {age: 18}, {age: 24}]; +const oldPerson: {age: ?number} = {age: 70}; +if (oldPerson.age) { + const age = oldPerson.age; + people.forEach(person => { + console.log(`The person is ${person.age} and the old one is ${age}`); + }) +} +``` + +## But Flow should understand that this function cannot invalidate this refinement, right? + +Flow is not [complete](../lang/types-and-expressions/#toc-soundness-and-completeness), so it cannot check all code perfectly. Instead, +Flow will make conservative assumptions to try to be sound. + +## Why can't I use a function in my if-clause to check the type of a property? + +Flow doesn't track [refinements](.././lang/refinements/) made in separate function calls: + +```js flow-check +const add = (first: number, second: number) => first + second; +const val: string | number = 1; +const isNumber = (x: mixed): boolean => typeof x === 'number'; +if (isNumber(val)) { + add(val, 2); +} +``` + +However, you can annotate your function with a [type guard](../types/type-guards/) to get this behavior: + +```js flow-check +const add = (first: number, second: number) => first + second; +const val: string | number = 1; +// Return annotation updated: +const isNumber = (x: mixed): x is number => typeof x === 'number'; +if (isNumber(val)) { + add(val, 2); +} +``` + +## Why can't I pass an `Array` to a function that takes an `Array` + +The function's argument allows `string` values in its array, but in this case Flow prevents the original array from receiving a `number`. +Inside the function, you would be able to push a `number` to the argument array, causing the type of the original array to no longer be accurate. + +You can fix this error by changing the type of the argument to `$ReadOnlyArray`, making it [covariant](../lang/variance/#toc-covariance). +This prevents the function body from pushing anything to the array, allowing it to accept narrower types. + +As an example, this would not work: + +```js flow-check +const fn = (arr: Array) => { + arr.push(123); // Oops! Array was passed in - now inaccurate + return arr; +}; + +const arr: Array = ['abc']; + +fn(arr); // Error! +``` + +but with `$ReadOnlyArray` you can achieve what you were looking for: + +```js flow-check +const fn = (arr: $ReadOnlyArray) => { + // arr.push(321); NOTE! Since you are using $ReadOnlyArray<...> you cannot push anything to it + return arr; +}; + +const arr: Array = ['abc']; + +fn(arr); +``` + +## Why can't I pass `{a: string}` to a function that takes `{a: string | number}` + +The function argument allows `string` values in its field, but in this case Flow prevents the original object from having a `number` written to it. +Within the body of the function you would be able to mutate the object so that the property `a` would receive a `number`, causing the type of the original object to no longer be accurate. + +You can fix this error by making the property [covariant](../lang/variance/#toc-covariance) (read-only): `{+a: string | number}`. +This prevents the function body from writing to the property, making it safe to pass more restricted types to the function. + +As an example, this would not work: + +```js flow-check +const fn = (obj: {a: string | number}) => { + obj.a = 123; // Oops! {a: string} was passed in - now inaccurate + return obj; +}; + +const object: {a: string} = {a: 'str' }; + +fn(object); // Error! +``` + +but with a covariant property you can achieve what you were looking for: + +```js flow-check +const fn = (obj: {+a: string | number}) => { + // obj.a = 123 NOTE! Since you are using covariant {+a: string | number}, you can't mutate it + return obj; +}; + +const object: {a: string} = { a: 'str' }; + +fn(object); +``` + +## Why can't I refine a union of objects? + +There are two potential reasons: +1. You are using inexact objects. +2. You are destructuring the object. When destructuring, Flow loses track of object properties. + +Broken example: + +```js flow-check +type Action = + | {type: 'A', payload: string} + | {type: 'B', payload: number}; + +// Not OK +const fn = ({type, payload}: Action) => { + switch (type) { + case 'A': return payload.length; // Error! + case 'B': return payload + 10; + } +} +``` + +Fixed example: + +```js flow-check +type Action = + | {type: 'A', payload: string} + | {type: 'B', payload: number}; + +// OK +const fn = (action: Action) => { + switch (action.type) { + case 'A': return action.payload.length; + case 'B': return action.payload + 10; + } +} +``` + +## I got a "Missing type annotation" error. Where does it come from? + +Flow requires type annotations at module boundaries to make sure it can scale. To read more about that, check out our [blog post](https://medium.com/flow-type/asking-for-required-annotations-64d4f9c1edf8) about that. + +The most common case you'll encounter is when exporting a function or React component. Flow requires you to annotate inputs. For instance in this example, Flow will complain: + +```js flow-check +export const add = a => a + 1; +``` + +The fix here is to add types to the parameters of `add`: + +```js flow-check +export const add = (a: number): number => a + 1; +``` + +To see how you can annotate exported React components, check out our docs on [HOCs](../react/hoc/#toc-exporting-wrapped-components). + +There are other cases where this happens, and they might be harder to understand. You'll get an error like `Missing type annotation for U` For instance, you wrote this code: + +```js flow-check +const array = ['a', 'b'] +export const genericArray = array.map(a => a) +``` + +Here, Flow will complain on the `export`, asking for a type annotation. Flow wants you to annotate exports returned by a generic function. The type of `Array.prototype.map` is `map(callbackfn: (value: T, index: number, array: Array) => U, thisArg?: any): Array`. The `` corresponds to what is called a [generic](../types/generics/), to express the fact that the type of the function passed to map is linked to the type of the array. + +Understanding the logic behind generics might be useful, but what you really need to know to make your typings valid is that you need to help Flow to understand the type of `genericArray`. + +You can do that by annotating the exported constant: + +```js flow-check +const array = ['a', 'b'] +export const genericArray: Array = array.map(a => a) +``` diff --git a/_src/getting-started.md b/_src/getting-started.md new file mode 100644 index 00000000000..981862a45fc --- /dev/null +++ b/_src/getting-started.md @@ -0,0 +1,23 @@ +--- +title: Getting Started +slug: /getting-started +description: Never used a type system before or just new to Flow? Let's get you up and running in a few minutes. +--- + +Flow is a static type checker for your JavaScript code. It does a lot of work +to make you more productive. Making you code faster, smarter, more confidently, +and to a bigger scale. + +Flow checks your code for errors through **static type annotations**. These +_types_ allow you to tell Flow how you want your code to work, and Flow will +make sure it does work that way. + +```js flow-check +function square(n: number): number { + return n * n; +} + +square("2"); // Error! +``` + +First step: [install Flow](../install). diff --git a/_src/index.md b/_src/index.md new file mode 100644 index 00000000000..01ae475b7ad --- /dev/null +++ b/_src/index.md @@ -0,0 +1,13 @@ +--- +title: Documentation +description: Guides and references for all you need to know about Flow +displayed_sidebar: docsSidebar +spug: / +--- + +Guides and references for all you need to know about Flow. + +import DocCardList from '@theme/DocCardList'; +import docsCategories from '../src/js/docs-categories'; + + diff --git a/_src/install.md b/_src/install.md new file mode 100644 index 00000000000..5f33c151513 --- /dev/null +++ b/_src/install.md @@ -0,0 +1,165 @@ +--- +title: Installation +slug: /install +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Setup Compiler + +First you'll need to setup a compiler to strip away Flow types. You can +choose between [Babel](http://babeljs.io/) and +[flow-remove-types](https://github.com/facebook/flow/tree/master/packages/flow-remove-types). + + + + +[Babel](http://babeljs.io/) is a compiler for JavaScript code that has +support for Flow. Babel will take your Flow code and strip out any type +annotations. If you already use Babel in your project, see [using Babel](../tools/babel). + +First install `@babel/core`, `@babel/cli`, `@babel/preset-flow`, and `babel-plugin-syntax-hermes-parser` with +either [Yarn](https://yarnpkg.com/) or [npm](https://www.npmjs.com/). + +```bash npm2yarn +npm install --save-dev @babel/core @babel/cli @babel/preset-flow babel-plugin-syntax-hermes-parser +``` + +Next you need to create a `.babelrc` file at the root of your project with the `@babel/preset-flow` preset and `babel-plugin-syntax-hermes-parser` plugin. + +```json +{ + "presets": ["@babel/preset-flow"], + "plugins": ["babel-plugin-syntax-hermes-parser"], +} +``` + +If you then put all your source files in a `src` directory you can compile them +to another directory by running: + +```bash +./node_modules/.bin/babel src/ -d lib/ +``` + +You can add this to your `package.json` scripts easily, alongside your `"devDependencies"` on Babel. + +```json +{ + "name": "my-project", + "main": "lib/index.js", + "scripts": { + "build": "babel src/ -d lib/", + "prepublish": "yarn run build" + } +} +``` + +> **Note:** You'll probably want to add a `prepublish` script that runs this +> transform as well, so that it runs before you publish your code to the npm +> registry. + + + + +[flow-remove-types](https://github.com/facebook/flow/tree/master/packages/flow-remove-types) is a small +CLI tool for stripping Flow type annotations from files. It's a lighter-weight +alternative to Babel for projects that don't need everything Babel provides. + +First install `flow-remove-types` with either +[Yarn](https://yarnpkg.com/) or [npm](https://www.npmjs.com/). + +```bash npm2yarn +npm install --save-dev flow-remove-types +``` + +If you then put all your source files in a `src` directory you can compile them +to another directory by running: + +```sh +./node_modules/.bin/flow-remove-types src/ -d lib/ +``` + +You can add this to your `package.json` scripts easily, alongside your `"devDependencies"` on `flow-remove-types`. + +```json +{ + "name": "my-project", + "main": "lib/index.js", + "scripts": { + "build": "flow-remove-types src/ -d lib/", + "prepublish": "yarn run build" + } +} +``` + +> **Note:** You'll probably want to add a `prepublish` script that runs this +> transform as well, so that it runs before you publish your code to the npm +> registry. + + + + +## Setup Flow + +Flow works best when installed per-project with explicit versioning rather than +globally. + +Luckily, if you're already familiar with `npm` or `yarn`, this process should +be pretty familiar! + +### Add a `devDependency` on the `flow-bin` npm package + +```bash npm2yarn +npm install --save-dev flow-bin +``` + +### Add a `"flow"` script to your `package.json` + +```json +{ + "name": "my-flow-project", + "version": "1.0.0", + "devDependencies": { + "flow-bin": "^0.249.0" + }, + "scripts": { + "flow": "flow" + } +} +``` + +### Run Flow + +The first time, run: + +```bash npm2yarn +npm run flow init +``` + +``` +> my-flow-project@1.0.0 flow /Users/Projects/my-flow-project +> flow "init" +``` + +After running `flow` with `init` the first time, run: + +```bash npm2yarn +npm run flow +``` + +``` +> my-flow-project@1.0.0 flow /Users/Projects/my-flow-project +> flow + +No errors! +``` + +## Setup ESLint + +If you use ESLint, you can read [our page on ESLint](../tools/eslint) to set it up to support Flow. diff --git a/_src/lang/annotation-requirement.md b/_src/lang/annotation-requirement.md new file mode 100644 index 00000000000..4a7804565a6 --- /dev/null +++ b/_src/lang/annotation-requirement.md @@ -0,0 +1,311 @@ +--- +title: Annotation Requirement +slug: /lang/annotation-requirement +--- + +> **Note:** As of version 0.199 Flow uses [Local Type Inference](https://medium.com/flow-type/local-type-inference-for-flow-aaa65d071347) as its inference algorithm. +The rules in this section reflect the main design features in this inference scheme. + +Flow tries to avoid requiring type annotation for parts of programs where types can easily +be inferred from the immediate context of an expression, variable, parameter, etc. + +## Variable declarations + +Take for example the following variable definition +```js +const len = "abc".length; +``` +All information necessary to infer the type of `len` is included in the initializer +`"abc".length`. Flow will first determine that `"abc"` is a string, and then that the +`length` property of a string is a number. + +The same logic can be applied for all `const`-like initializations. Where things +get a little more complicated is when variable initialization spans across multiple statements, +for example in +```js flow-check +declare const maybeString: ?string; + +let len; +if (typeof maybeString === "string") { + len = maybeString.length; +} else { + len = 0; +} +``` +Flow can still determine that `len` is a `number`, but in order to do so it looks +ahead to multiple initializer statements. See section on [variable declarations](../variables) +for details on how various initializer patterns determine the type of a variable, +and when an annotation on a variable declaration is necessary. + +## Function Parameters + +Unlike variable declarations, this kind of "lookahead" reasoning cannot be used to determine +the type of function parameters. Consider the function +```js +function getLength(x) { + return x.length; +} +``` +There are many kinds of `x` on which we could access and return a `length` property: +an object with a `length` property, or a string, just to name a few. If later on in +the program we had the following calls to `getLength` +```js +getLength("abc"); +getLength({length: 1}); +``` +one possible inference would be that `x` is a `string | { length: number }`. What this implies, +however, is that the type of `getLength` is determined by any part of the current +program. This kind of global reasoning can lead to surprising action-at-a-distance +behavior, and so is avoided. Instead, Flow requires that function parameters are annotated. Failure to +provide such a type annotation manifests as a `[missing-local-annot]` error on the parameter `x`, +and the body of the function is checked with `x: any`: +```js flow-check +function getLength(x) { + return x.length; +} + +const n = getLength(1); // no error since getLength's parameter type is 'any' +``` + +To fix this error, one can simply annotate `x` as +```js flow-check +function getLength(x: string) { + return x.length; +} +``` +The same requirement holds for class methods +```js flow-check +class WrappedString { + data: string; + setStringNoAnnotation(x) { + this.data = x; + } + setString(x: string) { + this.data = x; + } +} +``` + +## Contextual Typing {#toc-contextual-typing} + +Function parameters do not always need to be explicitly annotated. In the case of a +callback function to a function call, the parameter type can easily +be inferred from the immediate context. Consider for example the following code +```js +const arr = [0, 1, 2]; +const arrPlusOne = arr.find(x => x % 2 === 1); +``` +Flow infers that the type of `arr` is `Array`. Combining this with the builtin +information for `Array.find`, Flow can determine that the type of `x => x % 2 + 1` +needs to be `number => mixed`. This type acts as a *hint* for Flow and provides enough +information to determine the type of `x` as `number`. + +Any attendant annotation can potentially act as a hint to a function parameter, for example +```js flow-check +const fn1: (x: number) => number = x => x + 1; +``` +However, it is also possible that an annotation cannot be used as a function +parameter hint: +```js flow-check +const fn2: mixed = x => x + 1; +``` +In this example the `mixed` type simply does not include enough information to +extract a candidate type for `x`. + +Flow can infer the types for unannotated parameters even when they are nested within +other expressions like objects. For example in +in +```js flow-check +const fn3: {f: (number) => void} = {f: (x) => {x as string}}; +``` +Flow will infer `number` as the type of `x`, and so the cast fails. + + +## Function Return Types {#toc-function-return-types} + +Unlike function parameters, a function's return type does not need to be annotated in general. +So the above definition of `getLength` won't raise any Flow errors. + +There are, however, a couple of notable exceptions to this rule. The first one is +class methods. If we included to the `WrappedString` class a `getString` method +that returns the internal `data` property: +```js flow-check +class WrappedString { + data: string; + getString(x: string) { + return this.data; + } +} +``` +Flow would complain that `getString` is missing an annotation on the return. + +The second exception is recursive definitions. A trivial example of this would be +```js flow-check +function foo() { + return bar(); +} + +function bar() { + return foo(); +} +``` +The above code raises a `[definition-cycle]` error, which points to the two locations +that form a dependency cycle, the two missing return annotations. Adding +a return annotation to either function would resolve the issue. + +Effectively, the requirement on an annotation for method returns is a special-case +of the recursive definition restriction. The recursion is possible through access on +`this`. + +## Generic Calls {#toc-generic-calls} + +In calls to [generic functions](../../types/generics) the type of the result may +depend on the types of the values passed in as arguments. +This section discusses how this result is computed, when type arguments are not +explicitly provided. + +Consider for example the definition +```js +declare function map( + f: (T) => U, + array: $ReadOnlyArray, +): Array; +``` +and a potential call with arguments `x => x + 1` and `[1, 2, 3]`: +```js +map(x => x + 1, [1, 2, 3]); +``` +Here Flow infers that the type of `x` is `number`. + +Some other common examples of generic calls are calling the constructor of the generic +[`Set` class](https://github.com/facebook/flow/blob/82f88520f2bfe0fa13748b5ead711432941f4cb9/lib/core.js#L1799-L1801) +or calling `useState` from the React library: +```js flow-check +const set = new Set([1, 2, 3]); + +import {useState} from 'react'; +const [num, setNum] = useState(42); +``` +Flow here infers that the type of `set` is `Set`, and that `num` and `setNum` +are `number` and `(number) => void`, respectively. + +### Computing a Solution + +Computing the result of a generic call amounts to: +1. coming up with a solution for `T` and `U` that does not contain generic parts, +2. replacing `T` and `U` with the solution in the signature of `map`, and +3. performing a call to this new signature of `map`. + +This process is designed with two goals in mind: +* *Soundness*. The results need to lead to a correct call when we reach step (3). +* *Completeness*. The types Flow produces need to be as precise and informative as possible, +to ensure that other parts of the program will be successfully checked. + +Let's see how these two goals come into play in the `map` example from above. + +Flow detects that `$ReadOnlyArray` needs to be compatible with the type of `[1, 2, 3]`. +It can therefore infer that `T` is `number`. + +With the knowledge of `T` it can now successfully check `x => x + 1`. The parameter `x` +is contextually typed as `number`, and thus the result `x + 1` is also a number. +This final constraint allows us to compute `U` as a `number` too. + +The new signature of `map` after replacing the generic parts with the above solution +is +```js +(f: (number) => number, array: $ReadOnlyArray) => Array +``` +It is easy to see that the call would be successfully checked. + +### Errors during Polymorphic Calls + +If the above process goes on smoothly, you should not be seeing any errors associated with the call. +What happens though when this process fails? + +There are two reasons why this process could fail: + +#### Under-constrained Type Parameters + +There are cases where Flow might not have enough information to decide the type of a type parameter. +Let's examine again a call to the builtin generic +[`Set` class](https://github.com/facebook/flow/blob/82f88520f2bfe0fa13748b5ead711432941f4cb9/lib/core.js#L1799-L1801) +constructor, this time without passing any arguments: +```js flow-check +const set = new Set(); +set.add("abc"); +``` +During the call to `new Set`, we are not providing enough information for Flow to +determine the type for `T`, even though the subsequent call to `set.add` clearly +implies that `T` will be a string. Remember that inference of type arguments is +local to the call, so Flow will not attempt to look ahead in later statements +to determine this. + +In the absence of information, Flow would be at liberty to infer *any* type +as `T`: `any`, `mixed`, `empty`, etc. +This kind of decision is undesirable, as it can lead to surprising results. +For example, if we silently decided on `Set` then the call to `set.add("abc")` would +fail with an incompatibility between `string` and `empty`, without a clear indication +of where the `empty` came from. + +So instead, in situations like this, you'll get an `[underconstrained-implicit-instantiation]` error. +The way to fix this error is by adding a type annotation. There a few potential ways to do this: + +- Add an annotation at the call-site in one of two ways: + * an explicit type argument + ```js + const set = new Set(); + ``` + * an annotation on the initialization variable: + ```js + const set: Set = new Set(); + ``` + +- Add a default type on the type parameter `T` at the definition of the class: + ```js + declare class SetWithDefault extends $ReadOnlySet { + constructor(iterable?: ?Iterable): void; + // more methods ... + } + ``` + In the absence of any type information at the call-site, Flow will use the default + type of `T` as the inferred type argument: + ```js + const defaultSet = new SetWithDefault(); // defaultSet is SetWithDefault + ``` + +#### Incompatibility Errors + +Even when Flow manages to infer non-generic types for the type parameters in a generic +call, these types might still lead to incompatibilities either in the current call or in +code later on. + +For example, if we had the following call to `map`: +```js flow-check +declare function map(f: (T) => U, array: $ReadOnlyArray): Array; +map(x => x + 1, [{}]); +``` +Flow will infer `T` as `{}`, and therefore type `x` as `{}`. This will cause an error when checking the arrow function +since the `+` operation is not allowed on objects. + +Finally, a common source of errors is the case where the inferred type in a generic +call is correct for the call itself, but not indicative of the expected use later in the code. +For example, consider +```js flow-check +import {useState} from 'react'; +const [str, setStr] = useState(""); + +declare const maybeString: ?string; +setStr(maybeString); +``` +Passing the string `""` to the call to `useState` makes Flow infer `string` as the type +of the state. So `setStr` will also expect a `string` as input when called later on, +and therefore passing a `?string` will be an error. + +Again, to fix this error it suffices to annotate the expected "wider" type of state +when calling `useState`: +```js +const [str, setStr] = useState(""); +``` + +## Empty Array Literals {#toc-empty-array-literals} +Empty array literals (`[]`) are handled specially in Flow. You can read about their [behavior and requirements](../../types/arrays/#toc-empty-array-literals). diff --git a/_src/lang/depth-subtyping.md b/_src/lang/depth-subtyping.md new file mode 100644 index 00000000000..d77206e51a3 --- /dev/null +++ b/_src/lang/depth-subtyping.md @@ -0,0 +1,68 @@ +--- +title: Depth Subtyping +slug: /lang/depth-subtyping +--- + +Assume we have two [classes](../../types/classes), which have a subtype relationship using `extends`: + +```js flow-check +class Person { + name: string; +} +class Employee extends Person { + department: string; +} +``` + +It's valid to use an `Employee` instance where a `Person` instance is expected. + +```js flow-check +class Person { name: string } +class Employee extends Person { department: string } + +const employee: Employee = new Employee(); +const person: Person = employee; // OK +``` + +However, it is not valid to use an object containing an `Employee` instance +where an object containing a `Person` instance is expected. + +```js flow-check +class Person { name: string } +class Employee extends Person { department: string } + +const employee: {who: Employee} = {who: new Employee()}; +const person: {who: Person} = employee; // Error +``` + +This is an error because objects are mutable. The value referenced by the +`employee` variable is the same as the value referenced by the `person` +variable. + +```js +person.who = new Person(); +``` + +If we write into the `who` property of the `person` object, we've also changed +the value of `employee.who`, which is explicitly annotated to be an `Employee` +instance. + +If we prevented any code from ever writing a new value to the object through +the `person` variable, it would be safe to use the `employee` variable. Flow +provides a syntax for this: + +```js flow-check +class Person { name: string } +class Employee extends Person { department: string } + +const employee: {who: Employee} = {who: new Employee()}; +const person: {+who: Person} = employee; // OK +person.who = new Person(); // Error! +``` + +The plus sign `+` indicates that the `who` property is [covariant](../variance/#toc-covariance). +Using a covariant property allows us to use objects which have subtype-compatible +values for that property. By default, object properties are invariant, which allow +both reads and writes, but are more restrictive in the values they accept. + +Read more about [property variance](../variance/). diff --git a/_src/lang/lazy-modes.md b/_src/lang/lazy-modes.md new file mode 100644 index 00000000000..2d10c3807dc --- /dev/null +++ b/_src/lang/lazy-modes.md @@ -0,0 +1,67 @@ +--- +title: Lazy Mode +slug: /lang/lazy-modes +--- + +By default, the Flow server will typecheck all your code. This way it can answer +questions like "are there any Flow errors anywhere in my code". This is very +useful for tooling, like a continuous integration hook which prevents code +changes which introduce Flow errors. + +However, sometimes a Flow user might not care about all the code. If they are +editing a file `foo.js`, they might only want Flow to typecheck the subset of +the repository needed to answer questions about `foo.js`. Since Flow would only +check a smaller number of files, this would be faster. This is the motivation +behind Flow's lazy mode. + +## Classifying Files {#toc-classifying-files} + +Lazy mode classifes your code into four categories: + +1. **Focused files**. These are the files which the user cares about. +2. **Dependent files**. These are the files which depend on the focused files. +Changes to the focused files might cause type errors in the dependent files. +3. **Dependency files**. These are the files which are needed in order to +typecheck the focused or dependent files. +4. **Unchecked files**. All other files. + +Lazy mode will still find all the JavaScript files and parse them. But it won't +typecheck the unchecked files. + +## Choosing Focused Files {#toc-choosing-focused-files} + +Flow will focus files when they change on disk, using Flow's built-in file watcher +("dfind") or Watchman. + +So, all files that change while Flow is running will be focused. But what about +files that change when Flow is not running? If you're using Git or Mercurial, +Flow will ask it for all of the files that have changed since the mergebase +with "master" (the common ancestor of the current commit and the master branch). + +If you're not using "master" (e.g. "main" instead), you can change this with +the `file_watcher.mergebase_with` config. If you're working from a clone, you +might want to set this to "origin/master" (for Git), which will focus all files +that have changed locally, even if you commit to your local "master" branch. + +The net result is that Flow will find the same errors in lazy mode as in a full +check, so long as there are no errors upstream. For example, if your CI ensures +that there are no errors in "master," then it's redundant for Flow to check all +of the unchanged files for errors that can't exist. + +## Using Lazy Mode {#toc-using-lazy-mode} + +To enable lazy mode, set `lazy_mode=true` in the `.flowconfig`. + +To start Flow in lazy mode manually, run + +```bash +flow start --lazy-mode true +``` + +## Forcing Flow to Treat a File as Focused {#toc-forcing-flow-to-treat-a-file-as-focused} + +You can force Flow to treat one or more files as focused from the CLI. + +```bash +flow force-recheck --focus path/to/A.js path/to/B.js +``` diff --git a/_src/lang/nominal-structural.md b/_src/lang/nominal-structural.md new file mode 100644 index 00000000000..d28d4bd1fea --- /dev/null +++ b/_src/lang/nominal-structural.md @@ -0,0 +1,141 @@ +--- +title: Nominal & Structural Typing +slug: /lang/nominal-structural +--- + +A static type checker can use either the name (nominal typing) or the structure (structural typing) +of types when comparing them against other types (like when checking if one is a [subtype](../subtypes) of another). + +## Nominal typing {#toc-nominal-typing} + +Languages like C++, Java, and Swift have primarily nominal type systems. + +```js +// Pseudo code: nominal system +class Foo { method(input: string) { /* ... */ } } +class Bar { method(input: string) { /* ... */ } } + +let foo: Foo = new Bar(); // Error! +``` + +In this pseudo-code example, the nominal type system errors even though both classes have a method of the same name and type. +This is because the name (and declaration location) of the classes is different. + +## Structural typing {#toc-structural-typing} + +Languages like Go and Elm have primarily structural type systems. + +```js +// Pseudo code: structural system +class Foo { method(input: string) { /* ... */ } } +class Bar { method(input: string) { /* ... */ } } + +let foo: Foo = new Bar(); // Works! +``` + +In this pseudo-code example, the structural type system allows a `Bar` to be used as a `Foo`, +since both classes have methods and fields of the same name and type. + +If the shape of the classes differ however, then a structural system would produce an error: + +```js +// Pseudo code +class Foo { method(input: string) { /* ... */ } } +class Bar { method(input: number) { /* ... */ } } + +let foo: Foo = new Bar(); // Error! +``` + +We've demonstrated both nominal and structural typing of classes, but there are +also other complex types like objects and functions which can also be either +nominally or structurally compared. +Additionally, a type system may have aspects of both structural and nominal systems. + +## In Flow + +Flow uses structural typing for objects and functions, but nominal typing for classes. + +### Functions are structurally typed {#toc-functions-are-structurally-typed} + +When comparing a [function type](../../types/functions) with a function it must have the same structure +in order to be considered valid. + +```js flow-check +type FuncType = (input: string) => void; +function func(input: string) { /* ... */ } +let test: FuncType = func; // Works! +``` + +### Objects are structurally typed {#toc-objects-are-structurally-typed} + +When comparing an [object type](../../types/objects) with an object it must have the same structure +in order to be considered valid. + +```js flow-check +type ObjType = {property: string}; +let obj = {property: "value"}; +let test: ObjType = obj; // Works +``` + +### Classes are nominally typed {#toc-classes-are-nominally-typed} + +When you have two [classes](../../types/classes) with the same structure, they still are not +considered equivalent because Flow uses nominal typing for classes. + +```js flow-check +class Foo { method(input: string) { /* ... */ } } +class Bar { method(input: string) { /* ... */ } } +let test: Foo = new Bar(); // Error! +``` + +If you wanted to use a class structurally you could do that using an [interface](../../types/interfaces): + +```js flow-check +interface Interface { + method(value: string): void; +}; + +class Foo { method(input: string) { /* ... */ } } +class Bar { method(input: string) { /* ... */ } } + +let test1: Interface = new Foo(); // Works +let test2: Interface = new Bar(); // Works +``` + +### Opaque types +You can use [opaque types](../../types/opaque-types) to turn a previously structurally typed alias into a nominal one (outside of the file that it is defined). + +```js flow-check +// A.js +export type MyTypeAlias = string; +export opaque type MyOpaqueType = string; + +const x: MyTypeAlias = "hi"; // Works +const y: MyOpaqueType = "hi"; // Works +``` + +In a different file: + +```js +// B.js +import type {MyTypeAlias, MyOpaqueType} from "A.js"; + +const x: MyTypeAlias = "hi"; // Works +const y: MyOpaqueType = "hi"; // Error! `MyOpaqueType` is not interchangable with `string` +// ^^^^ Cannot assign "hi" to y because string is incompatible with MyOpaqueType +``` + +### Flow Enums + +[Flow Enums](../../enums) do not allow enum members with the same value, but which belong to different enums, to be used interchangeably. + +```js flow-check +enum A { + X = "x", +} +enum B { + X = "x", +} + +const a: A = B.X; // Error! +``` diff --git a/_src/lang/refinements.md b/_src/lang/refinements.md new file mode 100644 index 00000000000..1c198209a9f --- /dev/null +++ b/_src/lang/refinements.md @@ -0,0 +1,284 @@ +--- +title: Type Refinements +slug: /lang/refinements +--- + +Refinements allow us to narrow the type of a value based on conditional tests. + +For example, in the function below `value` is a [union](../../types/unions) of `"A"` or `"B"`. + +```js flow-check +function func(value: "A" | "B") { + if (value === "A") { + value as "A"; + } +} +``` + +Inside of the `if` block we know that value must be `"A"` because that's the only +time the if-statement will be true. + +The ability for a static type checker to be able to tell that the value inside +the if statement must be `"A"` is known as a refinement. + +Next we'll add an `else` block to our if statement. + +```js flow-check +function func(value: "A" | "B") { + if (value === "A") { + value as "A"; + } else { + value as "B"; + } +} +``` + +Inside of the `else` block we know that value must be `"B"` because it can only +be `"A"` or `"B"` and we've removed `"A"` from the possibilities. + +## Ways to refine in Flow + +### `typeof` checks +You can use a `typeof value === ""` check to refine a value to one of the categories supported by the [`typeof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) operator. + +The `typeof` operator can output `"undefined"`,`"boolean"`, `"number"`, `"bigint"`, `"string"`, `"symbol"`, `"function"`, or `"object"`. + +Keep in mind that the `typeof` operator will return `"object"` for objects, but also `null` and arrays as well. + +```js flow-check +function func(value: mixed) { + if (typeof value === "string") { + value as string; + } else if (typeof value === "boolean") { + value as boolean; + } else if (typeof value === "object") { + // `value` could be null, an array, or an object + value as null | interface {} | $ReadOnlyArray; + } +} +``` + +To check for `null`, use a `value === null` [equality](#equality-checks) check. + +```js flow-check +function func(value: mixed) { + if (value === null) { + value as null; // `value` is null + } +} +``` + +To check for [arrays](../../types/arrays), use `Array.isArray`: + +```js flow-check +function func(value: mixed) { + if (Array.isArray(value)) { + value as $ReadOnlyArray; // `value` is an array + } +} +``` + +### Equality checks + +As shown in the introductory example, you can use an equality check to narrow a value to a specific type. +This also applies to equality checks made in `switch` statements. + +```js flow-check +function func(value: "A" | "B" | "C") { + if (value === "A") { + value as "A"; + } else { + value as "B" | "C"; + } + + switch (value) { + case "A": + value as "A"; + break; + case "B": + value as "B"; + break; + case "C": + value as "C"; + break; + } +} +``` + +While in general it is not recommended to use `==` in JavaScript, due to the coercions it performs, +doing `value == null` (or `value != null`) checks `value` exactly for `null` and `void`. +This works well with Flow's [maybe](../../types/maybe) types, which create a union with `null` and `void`. + +```js flow-check +function func(value: ?string) { + if (value != null) { + value as string; + } else { + value as null | void; + } +} +``` + +You can refine a union of object types based on a common tag, which we call [disjoint object unions](../../types/unions/#toc-disjoint-object-unions): + +```js flow-check +type A = {type: "A", s: string}; +type B = {type: "B", n: number}; + +function func(value: A | B) { + if (value.type === "A") { + // `value` is A + value.s as string; // Works + } else { + // `value` is B + value.n as number; // Works + } +} +``` + +### Truthiness checks + +You can use non-booleans in JavaScript conditionals. +`0`, `NaN`, `""`, `null`, and `undefined` will all coerce to `false` (and so are considered "falsey"). +Other values will coerce to `true` (and so are considered "truthy"). + +```js flow-check +function func(value: ?string) { + if (value) { + value as string; // Works + } else { + value as null | void; // Error! Could still be the empty string "" + } +} +``` + +You can see in the above example why doing a truthy check when your value can be a string or number is not suggested: +it is possible to unintentionally check against the `""` or `0`. +We created a [Flow lint](../../linting) called [sketchy-null](../../linting/rule-reference/#toc-sketchy-null) to guard against this scenario: + +```js flow-check +// flowlint sketchy-null:error +function func(value: ?string) { + if (value) { // Error! + } +} +``` + +### `instanceof` checks + +You can use the [instanceof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof) operator to narrow a value as well. +It checks if the supplied constructor's prototype is anywhere in a value's prototype chain. + +```js flow-check +class A { + amaze(): void {} +} +class B extends A { + build(): void {} +} + +function func(value: mixed) { + if (value instanceof B) { + value.amaze(); // Works + value.build(); // Works + } + + if (value instanceof A) { + value.amaze(); // Works + value.build(); // Error + } + + if (value instanceof Object) { + value.toString(); // Works + } +} +``` + +### Assignments + +Flow follows your control flow and narrows the type of a variable after you have assigned to it. + +```js flow-check +declare const b: boolean; + +let x: ?string = b ? "str" : null; + +x as ?string; + +x = "hi"; + +// We know `x` must now be a string after the assignment +x as string; // Works +``` + +### Type Guards + +You can create a reusable refinement by defining a function which is a [type guard](../../types/type-guards/). + +```js flow-check +function nonMaybe(x: ?T): x is T { + return x != null; +} + +function func(value: ?string) { + if (nonMaybe(value)) { + value as string; // Works! + } +} +``` + +## Refinement Invalidations {#toc-refinement-invalidations} + +It is also possible to invalidate refinements, for example: + +```js flow-check +function otherFunc() { /* ... */ } + +function func(value: {prop?: string}) { + if (value.prop) { + otherFunc(); + value.prop.charAt(0); // Error! + } +} +``` + +The reason for this is that we don't know that `otherFunc()` hasn't done +something to our value. Imagine the following scenario: + +```js flow-check +const obj: {prop?: string} = {prop: "test"}; + +function otherFunc() { + if (Math.random() > 0.5) { + delete obj.prop; + } +} + +function func(value: {prop?: string}) { + if (value.prop) { + otherFunc(); + value.prop.charAt(0); // Error! + } +} + +func(obj); +``` + +Inside of `otherFunc()` we sometimes remove `prop`. Flow doesn't know if the +`if (value.prop)` check is still true, so it invalidates the refinement. + +There's a straightforward way to get around this. Store the value before +calling another function and use the stored value instead. This way you can +prevent the refinement from invalidating. + +```js flow-check +function otherFunc() { /* ... */ } + +function func(value: {prop?: string}) { + if (value.prop) { + const prop = value.prop; + otherFunc(); + prop.charAt(0); + } +} +``` diff --git a/_src/lang/subtypes.md b/_src/lang/subtypes.md new file mode 100644 index 00000000000..92f3af77ce8 --- /dev/null +++ b/_src/lang/subtypes.md @@ -0,0 +1,148 @@ +--- +title: Subsets & Subtypes +slug: /lang/subtypes +--- + +## What is a subtype? {#toc-what-is-a-subtype} + +A type like `number`, `boolean`, or `string` describes a set of possible +values. A `number` describes every possible number, so a single number +(such as `42`) would be a *subtype* of the `number` type. Conversely, `number` +would be a *supertype* of the type `42`. + +If we want to know whether one type is the subtype of another, we need to look at +all the possible values for both types and figure out if the other has a +_subset_ of the values. + +For example, if we had a `TypeA` which described the numbers 1 through 3 +(a [union](../../types/unions) of [literal types](../../types/literals)), and +a `TypeB` which described the numbers 1 through 5: `TypeA` would be considered +a _subtype_ of `TypeB`, because `TypeA` is a subset of `TypeB`. + +```js flow-check +type TypeA = 1 | 2 | 3; +type TypeB = 1 | 2 | 3 | 4 | 5; +``` + +Consider a `TypeLetters` which described the strings: "A", "B", "C", and a +`TypeNumbers` which described the numbers: 1, 2, 3. Neither of them would +be a subtype of the other, as they each contain a completely different set of +values. + +```js flow-check +type TypeLetters = "A" | "B" | "C"; +type TypeNumbers = 1 | 2 | 3; +``` + +Finally, if we had a `TypeA` which described the numbers 1 through 3, and a +`TypeB` which described the numbers 3 through 5. Neither of them would be a +subtype of the other. Even though they both have 3 and describe numbers, they +each have some unique items. + +```js flow-check +type TypeA = 1 | 2 | 3; +type TypeB = 3 | 4 | 5; +``` + +## When are subtypes used? {#toc-when-are-subtypes-used} + +Most of the work that Flow does is comparing types against one another. + +For example, in order to know if you are calling a function correctly, Flow +needs to compare the arguments you are passing with the parameters the +function expects. + +This often means figuring out if the value you are passing in is a subtype of +the value you are expecting. + +So if you write a function that expects the numbers 1 through 5, any subtype of +that set will be acceptable. + +```js flow-check +function f(param: 1 | 2 | 3 | 4 | 5) { + // ... +} + +declare const oneOrTwo: 1 | 2; // Subset of the input parameters type. +declare const fiveOrSix: 5 | 6; // Not a subset of the input parameters type. + +f(oneOrTwo); // Works! +f(fiveOrSix); // Error! +``` + +## Subtypes of complex types {#toc-subtypes-of-complex-types} + +Flow needs to compare more than just sets of primitive values, it also needs to +be able to compare objects, functions, and every other type that appears in the +language. + +### Subtypes of objects {#toc-subtypes-of-objects} + +You can start to compare two objects by their keys. If one object contains all +the keys of another object, then it may be a subtype. + +For example, if we had an `ObjectA` which contained the key `foo`, and an +`ObjectB` which contained the keys `foo` and `bar`. Then it's possible that +`ObjectB` is a subtype of `ObjectA`, if `ObjectA` is inexact. + +```js flow-check +type ObjectA = {foo: string, ...}; +type ObjectB = {foo: string, bar: number}; + +let objectB: ObjectB = {foo: 'test', bar: 42}; +let objectA: ObjectA = objectB; // Works! +``` + +But we also need to compare the types of the values. If both objects had a key +`foo` but one was a `number` and the other was a `string`, then one would not +be the subtype of the other. + +```js flow-check +type ObjectA = {foo: string, ...}; +type ObjectB = {foo: number, bar: number}; + +let objectB: ObjectB = { foo: 1, bar: 2 }; +let objectA: ObjectA = objectB; // Error! +``` + +If these values on the object happen to be other objects, we would have to +compare those against one another. We need to compare every value +recursively until we can decide if we have a subtype or not. + +### Subtypes of functions {#toc-subtypes-of-functions} + +Subtyping rules for functions are more complicated. So far, we've seen that `A` +is a subtype of `B` if `B` contains all possible values for `A`. For functions, +it's not clear how this relationship would apply. To simplify things, you can think +of a function type `A` as being a subtype of a function type `B` if functions of type +`A` can be used wherever a function of type `B` is expected. + +Let's say we have a function type and a few functions. Which of the functions can +be used safely in code that expects the given function type? + +```js flow-check +type FuncType = (1 | 2) => "A" | "B"; + +declare function f1(1 | 2): "A" | "B" | "C"; +declare function f2(1 | null): "A" | "B"; +declare function f3(1 | 2 | 3): "A"; + +f1 as FuncType; // Error +f2 as FuncType; // Error +f3 as FuncType; // Works! +``` + +- `f1` can return a value that `FuncType` never does, so code that relies on `FuncType` +might not be safe if `f1` is used. Its type is not a subtype of `FuncType`. +- `f2` can't handle all the argument values that `FuncType` does, so code that relies on +`FuncType` can't safely use `f2`. Its type is also not a subtype of `FuncType`. +- `f3` can accept all the argument values that `FuncType` does, and only returns +values that `FuncType` does, so its type is a subtype of `FuncType`. + +In general, the function subtyping rule is this: a function type `B` is a subtype +of a function type `A` if and only if `B`'s inputs are a superset of `A`'s, and `B`'s outputs +are a subset of `A`'s. The subtype must accept _at least_ the same inputs as its parent, +and must return _at most_ the same outputs. + +The decision of which direction to apply the subtyping rule on inputs and outputs is +governed by [variance](../variance), which is the topic of the next section. diff --git a/_src/lang/type-hierarchy.md b/_src/lang/type-hierarchy.md new file mode 100644 index 00000000000..c24f3b6cdfd --- /dev/null +++ b/_src/lang/type-hierarchy.md @@ -0,0 +1,126 @@ +--- +title: Type Hierarchy +slug: /lang/type-hierarchy +--- + +Types in Flow form a hierarchy based on [subtyping](../subtypes): + +```mermaid +graph BT + +mixed -.- any + +symbol --> mixed +null --> mixed +maybe["Maybe: + ?string"] +maybe --> mixed +null --> maybe +void --> maybe +void --> mixed +string --> maybe +string --> mixed + +union["Union: + number | bigint"] +number --> union +number --> mixed +union --> mixed +bigint --> mixed +bigint --> union + +boolean --> mixed +true --> boolean +false --> boolean + +empty-interface["interface {}"] --> mixed +some-interface["interface {prop: string}"] --> empty-interface +someclass["class A {prop: string}"] --> some-interface +inexact-empty-obj["Inexact empty object: + {...}"] +inexact-empty-obj --> empty-interface +inexact-some-obj["Inexact object: + {prop: string, ...}"] --> inexact-empty-obj +inexact-some-obj --> some-interface +exact-some-obj["Exact object: + {prop: string}"] --> inexact-some-obj +exact-empty-obj["Exact empty object: + {}"] +exact-empty-obj --> inexact-empty-obj +roarray-mixed["$ReadOnlyArray<mixed>"] --> empty-interface +inexact-empty-tuple["Inexact empty tuple: + [...]"] +some-tuple["Tuple: + [string, number]"] +inexact-empty-tuple --> roarray-mixed +some-tuple --> inexact-empty-tuple +some-array["Array<string>"] --> roarray-mixed + +any-func["Function: + (...$ReadOnlyArray<empty>) => mixed"] +any-func --> empty-interface +some-func["(number) => boolean"] --> any-func +some-func2["(string) => string"] --> any-func + +inter["Intersection: + (number => boolean) & (string => string)"] +inter --> some-func +inter --> some-func2 + +empty --> inter +empty --> null +empty --> void +empty --> true +empty --> false +empty --> exact-some-obj +empty --> exact-empty-obj +empty --> some-tuple +empty --> some-array +empty --> string +empty --> number +empty --> bigint +empty --> someclass +empty --> symbol +any-bottom["any"] -.- empty + +click mixed "../../types/mixed" +click any "../../types/any" +click any-bottom "../../types/any" +click empty "../../types/empty" +click boolean "../../types/primitives/#toc-booleans" +click number "../../types/primitives/#toc-numbers" +click string "../../types/primitives/#toc-strings" +click symbol "../../types/primitives/#toc-symbols" +click bigint "../../types/primitives/#toc-bigints" +click null "../../types/primitives/#toc-null-and-void" +click void "../../types/primitives/#toc-null-and-void" +click true "../../types/literals" +click false "../../types/literals" +click union "../../types/unions" +click inter "../../types/intersections" +click maybe "../../types/maybe" +click some-array "../../types/arrays" +click roarray-mixed "../../types/arrays/#toc-readonlyarray" +click inexact-empty-tuple "../../types/tuples/#inexact-tuples" +click some-tuple "../../types/tuples" +click someclass "../../types/classes" +click empty-interface "../../types/interfaces" +click some-interface "../../types/interfaces" +click exact-some-obj "../../types/objects" +click exact-empty-obj "../../types/objects" +click inexact-some-obj "../../types/objects/#exact-and-inexact-object-types" +click inexact-empty-obj "../../types/objects/#exact-and-inexact-object-types" +click any-func "../../types/functions" +click some-func "../../types/functions" +click some-func2 "../../types/functions" + +classDef default fill:#eee, stroke:#000, stroke-width:1px +``` + +Click on a node to go to the documentation for that type. + +Types appearing higher in this graph are more general, while those appearing lower are more specific. +An arrow pointing from type `A` to type `B` means that `A` is a subtype of `B`. +For example, the type `string` is a subtype of `?string`. + +How can `any` be at both the top and the bottom? Because [it is unsafe](../../types/any/). This is denoted with a dotted line. diff --git a/_src/lang/types-and-expressions.md b/_src/lang/types-and-expressions.md new file mode 100644 index 00000000000..4a69aea5980 --- /dev/null +++ b/_src/lang/types-and-expressions.md @@ -0,0 +1,101 @@ +--- +title: Types & Expressions +slug: /lang/types-and-expressions +--- + +In JavaScript there are many types of values: numbers, strings, booleans, +functions, objects, and more. + +```js flow-check +1234 as number; +"hi" as string; +true as boolean; +[1, 2] as Array; +({prop: "value"}) as {prop: string}; +(function func(s: string) {}) as string => void; +``` + +These values can be used in many different ways: + +```js flow-check +1 + 2; +"foo" + "bar"; +!true; +[1, 2].push(3); +const obj = {prop: "s"}; +let value = obj.prop; +obj.prop = "value"; +function func(s: string) {} +func("value"); +``` + +All of these different expressions create a new type which is a result of the +types of values and the operations run on them. + +```js flow-check +let num: number = 1 + 2; +let str: string = "foo" + "bar"; +``` + +In Flow every value and expression has a type. + +## Figuring out types statically {#toc-figuring-out-types-statically} + +Flow needs a way to be able to figure out the type of every expression. But it +can't just run your code to figure it out, if it did it would be affected by +any issues that your code has. For example, if you created an infinite loop +Flow would wait for it to finish forever. + +Instead, Flow needs to be able to figure out the type of a value by analyzing +it without running it (static analysis). It works its way through every known +type and starts to figure out what all the expressions around them result in. + +For example, to figure out the result of the following expression, Flow needs to +figure out what its values are first. + +```js +val1 + val2; +``` + +If the values are numbers, then the expression results in a number. If the +values are strings, then the expression results in a string. There are a number +of different possibilities here, so Flow must look up what the values are. + +If Flow is unable to figure out what the exact type is for each value, Flow +must figure out what every possible value is and check to make sure that the +code around it will still work with all of the possible types. + +## Soundness and Completeness {#toc-soundness-and-completeness} + +When you run your code, a single expression will only be run with a limited set +of values. But still Flow checks _every_ possible value. In this way Flow is +checking too many things or _over-approximating_ what will be valid code. + +By checking every possible value, Flow might catch errors that will not +actually occur when the code is run. Flow does this in order to be _"sound"_. + +In type systems, ***soundness*** is the ability for a type checker to catch +every single error that _might_ happen at runtime. This comes at the cost of +sometimes catching errors that will not actually happen at runtime. + +On the flip-side, ***completeness*** is the ability for a type checker to only +ever catch errors that _would_ happen at runtime. This comes at the cost of +sometimes missing errors that will happen at runtime. + +In an ideal world, every type checker would be both sound _and_ complete so +that it catches _every_ error that _will_ happen at runtime. + +Flow tries to be as sound and complete as possible. But because JavaScript was +not designed around a type system, Flow sometimes has to make a tradeoff. When +this happens Flow tends to favor soundness over completeness, ensuring that +code doesn't have any bugs. + +Soundness is fine as long as Flow isn't being too noisy and preventing you from +being productive. Sometimes when soundness would get in your way too much, Flow +will favor completeness instead. There's only a handful of cases where Flow +does this. + +Other type systems will favor completeness instead, only reporting real errors +in favor of possibly missing errors. Unit/Integration testing is an extreme +form of this approach. Often this comes at the cost of missing the errors that +are the most complicated to find, leaving that part up to the developer. diff --git a/_src/lang/types-first.md b/_src/lang/types-first.md new file mode 100644 index 00000000000..a36addb8f6d --- /dev/null +++ b/_src/lang/types-first.md @@ -0,0 +1,254 @@ +--- +title: File Signatures (Types-First) +slug: /lang/types-first +--- + +Flow checks codebases by processing each file separately in dependency +order. For every file containing important typing information for the checking +process, a signature needs to be extracted and stored in +main memory, to be used for files that depend on it. Flow relies on annotations +available at the boundaries of files to build these signatures. We call this +requirement of Flow's architecture *Types-First*. + +The benefit of this architecture is dual: + +1. It dramatically improves *performance*, in particular when it comes to +rechecks. Suppose we want Flow to check a file `foo.js`, for which it hasn't +checked its dependencies yet. Flow extracts the dependency signatures just by +looking at the annotations around the exports. This process is mostly +syntactic, and therefore much faster than full type inference that legacy versions +of Flow (prior to v0.125) used to perform in order to generate signatures. + +2. It improves error *reliability*. Inferred types often become complicated, and may +lead to errors being reported in downstream files, far away from their actual source. +Type annotations at file boundaries of files can help localize such errors, and +address them in the file that introduced them. + +The trade-off for this performance benefit is that exported parts of the code need to be +annotated with types, or to be expressions whose type can be trivially inferred +(for example numbers and strings). + +More information on the Types-First architecture can be found in [this post](https://medium.com/flow-type/types-first-a-scalable-new-architecture-for-flow-3d8c7ba1d4eb). + +## How to upgrade your codebase to Types-First {#toc-how-to-upgrade-your-codebase-to-types-first} + +> Note: Types-first has been the default mode since v0.134 and the only available +mode since v0.143. No `.flowconfig` options are necessary to enable it since then. +In case you're upgrading your codebase from a much earlier version here are some +useful tools. + +### Upgrade Flow version {#toc-upgrade-flow-version} + +Types-first mode was officially released with version 0.125, but has been available in +*experimental* status as of version 0.102. If you are currently on an older +Flow version, you’d have to first upgrade Flow. Using the latest Flow version +is the best way to benefit from the performance benefits outlined above. + +### Prepare your codebase for Types-First {#toc-prepare-your-codebase-for-types-first} + +Types-first requires annotations at module boundaries in order to build type +signature for files. If these annotations are missing, then a `signature-verification-failure` +is raised, and the exported type for the respective part of the code will be `any`. + +To see what types are missing to make your codebase types-first ready, add the +following line to the `[options]` section of the `.flowconfig` file: + +``` +well_formed_exports=true +``` + +Consider for example a file `foo.js` that exports a function call to `foo` + +```js +declare function foo(x: T): T; +module.exports = foo(1); +``` + +The return type of function calls is currently not trivially inferable (due to +features like polymorphism, overloading etc.). Their result needs to be annotated +and so you’d see the following error: + +``` +Cannot build a typed interface for this module. You should annotate the exports +of this module with types. Cannot determine the type of this call expression. Please +provide an annotation, e.g., by adding a type cast around this expression. +(`signature-verification-failure`) + + 4│ module.exports = foo(1); + ^^^^^^ +``` + +To resolve this, you can add an annotation like the following: + +```js +declare function foo(x: T): T; +module.exports = foo(1) as number; +``` + +> Note: As of version 0.134, types-first is the default mode. This mode automatically +enables `well_formed_exports`, so you would see these errors without explicitly +setting this flag. It is advisable to set `types_first=false` during this part of +the upgrade. + +#### Seal your intermediate results {#toc-seal-your-intermediate-results} + +As you make progress adding types to your codebase, you can include directories +so that they don’t regress as new code gets committed, and until the entire project +has well-formed exports. You can do this by adding lines like the following to your +.flowconfig: + +``` +well_formed_exports.includes=/path/to/directory +``` + +> Warning: That this is a *substring* check, not a regular expression (for performance +reasons). + + +#### A codemod for large codebases {#toc-a-codemod-for-large-codebases} + + +Adding the necessary annotations to large codebases can be quite tedious. To ease +this burden, we are providing a codemod based on Flow's inference, that can be +used to annotate multiple files in bulk. See [this tutorial](../../cli/annotate-exports/) for more. + + +### Enable the types-first flag {#toc-enable-the-types-first-flag} + +Once you have eliminated signature verification errors, you can turn on the types-first +mode, by adding the following line to the `[options]` section of the `.flowconfig` file: + +``` +types_first=true +``` + +You can also pass `--types-first` to the `flow check` or `flow start` commands. + +The `well_formed_exports` flag from before is implied by `types_first`. Once +this process is completed and types-first has been enabled, you can remove +`well_formed_exports`. + +Unfortunately, it is not possible to enable types-first mode for part of your repo; this switch +affects all files managed by the current `.flowconfig`. + +> Note: The above flags are available in versions of Flow `>=0.102` with the `experimental.` +prefix (and prior to v0.128, it used `whitelist` in place of `includes`): +``` +experimental.well_formed_exports=true +experimental.well_formed_exports.whitelist=/path/to/directory +experimental.types_first=true +``` + +> Note: If you are using a version where types-first is enabled by default (ie. `>=0.134`), +make sure you set `types_first=false` in your .flowconfig while running the codemods. + + +### Deal with newly introduced errors {#toc-deal-with-newly-introduced-errors} + +Switching between classic and types-first mode may cause some new Flow errors, +besides signature-verification failures that we mentioned earlier. These errors +are due differences in the way types based on annotations are interpreted, compared +to their respective inferred types. + +Below are some common error patterns and how to overcome them. + + +#### Array tuples treated as regular arrays in exports {#toc-array-tuples-treated-as-regular-arrays-in-exports} + + +In types-first, an array literal in an *export position* + +```js +module.exports = [e1, e2]; +``` + +is treated as having type `Array`, where `e1` and `e2` have types `t1` +and `t2`, instead of the tuple type `[t1, t2]`. + +In classic mode, the inferred type encompassed both types at the same time. This +might cause errors in importing files that expect for example to find type `t1` +in the first position of the import. + +**Fix:** If a tuple type is expected, then the annotation `[t1, t2]` needs to be +explicitly added on the export side. + +#### Indirect object assignments in exports {#toc-indirect-object-assignments-in-exports} + + +Flow allows the code + +```js +function foo(): void {} +foo.x = () => {}; +foo.x.y = 2; +module.exports = foo; +``` + +but in types-first the exported type will be + +```plaintext +{ + (): void; + x: () => void; +} +``` + +In other words it won’t take into account the update on `y`. + +**Fix:** To include the update on `y` in the exported type, the export will need +to be annotated with the type + +```plaintext +{ + (): void; + x: { (): void; y: number; }; +}; +``` + +The same holds for more complex assignment patterns like + +```js +function foo(): void {} +Object.assign(foo, { x: 1}); +module.exports = foo; +``` + +where you’ll need to manually annotate the export with `{ (): void; x: number }`, +or assignments preceding the function definition + +```js +foo.x = 1; +function foo(): void {} +module.exports = foo; +``` + +Note that in the last example, Flow types-first will pick up the static update if +it was after the definition: + +```js +function foo(): void {} +foo.x = 1; +module.exports = foo; +``` + +### Exported variables with updates {#toc-exported-variables-with-updates} + +The types-first signature extractor will not pick up subsequent update of an exported +let-bound variables. Consider the example + +```js +let foo: number | string = 1; +foo = "blah"; +module.exports = foo; +``` + +In classic mode the exported type would be `string`. In types-first it will be +`number | string`, so if downstream typing depends on the more precise type, then +you might get some errors. + +**Fix:** Introduce a new variable on the update and export that one. For example +```js +const foo1: number | string = 1; +const foo2 = "blah"; +module.exports = foo2; +``` diff --git a/_src/lang/variables.md b/_src/lang/variables.md new file mode 100644 index 00000000000..477b3be40d2 --- /dev/null +++ b/_src/lang/variables.md @@ -0,0 +1,209 @@ +--- +title: Variable Declarations +slug: /lang/variables +--- + +import {SinceVersion} from '../../components/VersionTags'; + +When you are declaring a new variable, you may optionally declare its type. + +JavaScript has three ways of declaring local variables: + +- `var` - declares a variable, optionally assigning a value. + ([MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var)) +- `let` - declares a block-scoped variable, optionally assigning a value. + ([MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let)) +- `const` - declares a block-scoped variable, assigning a value that cannot be re-assigned. + ([MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const)) + +In Flow these fall into two groups: + +- `let` and `var` - variables that **can** be reassigned. +- `const` - variables that **cannot** be reassigned. + +```js flow-check +var varVariable = 1; +let letVariable = 1; +const constVariable = 1; + +varVariable = 2; // Works! +letVariable = 2; // Works! +constVariable = 2; // Error! +``` + +## `const` {#toc-const} + +Since a `const` variable cannot be re-assigned at a later time it is fairly +simple. + +Flow can either infer the type from the value you are assigning to it or you +can provide it with a type. + +```js flow-check +const foo /* : number */ = 1; +const bar: number = 2; +``` + +## `var` and `let` {#toc-var-and-let} +Since `var` and `let` can be re-assigned, there's a few more rules you'll need +to know about. + +When you provide a type, you will be able to re-assign the value, but it must +always be of a compatible type. + +```js flow-check +let foo: number = 1; +foo = 2; // Works! +foo = "3"; // Error! +``` + +When the variable has no annotation, Flow infers a precise type based on + their initializer or initial assignment. All subsequent assignments +to that variable will be constrained by this type. This section shows some examples +of how Flow determines what type an unannotated variable is inferred to have. + + +**If you want a variable to have a different type than what Flow infers for it, +you can always add a type annotation to the variable’s declaration. That will +override everything discussed in this page!** + +### Variables initialized at their declarations {#toc-variables-initialized-at-their-declarations} + +The common case for unannotated variables is very straightforward: when a +variable is declared with an initializer that is not the literal `null`, that +variable will from then on have the type of the initializer, and future writes +to the variable will be constrained by that type. + +```js flow-check +import * as React from 'react'; + +type Props = $ReadOnly<{ prop: string }>; + +declare var x: number; +declare var y: number; +declare var props: Props; + +let product = Math.sqrt(x) + y; +// `product` has type `number` + +let Component = ({prop}: Props): React.Node => { return
} +// `Component` has type`React.ComponentType` + +let element = +// `element` has type `React.Element>` + +/* Let's define a new component */ + +type OtherProps = $ReadOnly<{ ...Props, extra_prop: number }>; +declare var OtherComponent: (OtherProps) => React.Node; +declare var other_props: OtherProps + +/* Any subsequent assignments to `product`, `Component`, or `element` will be + * checked against the types that Flow infers for the initializers, and if + * conflicting types are assigned, Flow will signal an error. */ + +product = "Our new product is..."; +Component = ({prop}: OtherProps): React.Node => { return
}; +element = ; +``` + +If you want these examples to typecheck, and for Flow to realize that different +kinds of values can be written to these variables, you must add a type +annotation reflecting this more general type to their declarations: +```js +let product: number | string = ... +let Component: mixed = ... // No good type to represent this! Consider restructuring +let element: React.Node = ... +``` +### Variables declared without initializers {#toc-variables-declared-without-initializers} + +Often variables are declared without initializers. In such cases, Flow will try +to choose the "first" assignment or assignments to the variable to define its +type. "First" here means both top-to-bottom and nearer-scope to +deeper-scope—we’ll try to choose an assignment that happens in the same function +scope as the variable’s declaration, and only look inside nested functions if we +don’t find any assignments locally: +```js flow-check +let topLevelAssigned; +function helper() { + topLevelAssigned = 42; // Error: `topLevelAssigned` has type `string` +} +topLevelAssigned = "Hello world"; // This write determines the var's type +topLevelAssigned = true; // Error: `topLevelAssigned` has type `string` +``` +If there are two or more possible "first assignments," due to an `if`- or +`switch`-statement, they’ll both count—this is one of the few ways that Flow +will still infer unions for variable types: +```js flow-check +let myNumberOrString; +declare var condition: boolean; +if (condition) { + myNumberOrString = 42; // Determines type +} else { + myNumberOrString = "Hello world"; // Determines type +} +myNumberOrString = 21; // fine, compatible with type +myNumberOrString = "Goodbye"; // fine, compatible with type +myNumberOrString = false; // Error: `myNumberOrString` has type `number | string` +``` +This only applies when the variable is written to in both branches, however. If +only one branch contains a write, that write becomes the type of the variable +afterwards (though Flow will still check to make sure that the variable is +definitely initialized): + +```js flow-check +let oneBranchAssigned; +declare var condition: boolean; +if (condition) { + oneBranchAssigned = "Hello world!"; +} +oneBranchAssigned.toUpperCase(); // Error: `oneBranchAssigned` may be uninitialized +oneBranchAssigned = 42; // Error: `oneBranchAssigned` has type `string` +``` +### Variables initialized to `null` {#toc-variables-initialized-to-null} + +Finally, the one exception to the general principle that variable’s types are +determined by their first assignment(s) is when a variable is initialized as (or +whose first assignment is) the literal value `null`. In such cases, the *next* +non-null assignment (using the same rules as above) determines the rest of the +variable’s type, and the overall type of the variable becomes a union of `null` +and the type of the subsequent assignment. This supports the common pattern +where a variable starts off as `null` before getting assigned by a value of some +other type: +```js flow-check +function findIDValue(dict: {[key: string]: T}): T { + let idVal = null; // initialized as `null` + for (const key in dict) { + if (key === 'ID') { + idVal = dict[key]; // Infer that `idVal` has type `null | T` + } + } + if (idVal === null) { + throw new Error("No entry for ID!"); + } + return idVal; +} +``` + +## Catch variables +If a `catch` variable does not have an annotation, its default type is [`any`](../../types/any). + +You can optionally annotate it with exactly [`mixed`](../../types/mixed) or `any`. E.g. + +```js flow-check +try { +} catch (e: mixed) { + if (e instanceof TypeError) { + e as TypeError; // OK + } else if (e instanceof Error) { + e as Error; // OK + } else { + throw e; + } +} +``` + +By using `mixed`, you can improve your safety and Flow [coverage](../../cli/coverage/), +at the trade-off of increased runtime checks. + +You can change the default type of `catch` variables when there is no annotation by setting the [`use_mixed_in_catch_variables`](../../config/options/#toc-use-mixed-in-catch-variables) option to true. diff --git a/_src/lang/variance.md b/_src/lang/variance.md new file mode 100644 index 00000000000..be16a6a8701 --- /dev/null +++ b/_src/lang/variance.md @@ -0,0 +1,144 @@ +--- +title: Type Variance +slug: /lang/variance +--- + +Variance is a topic that comes up fairly often in type systems. It is used to determine +how type parameters behave with respect to subtyping. + +First we'll setup a couple of classes that extend one another. + +```js +class Noun {} +class City extends Noun {} +class SanFrancisco extends City {} +``` + +We saw in the section on [generic types](../../types/generics/#toc-variance-sigils) +that it is possible to +use variance sigils to describe when a type parameter is used in an output position, +when it is used in an input position, and when it is used in either one. + +Here we'll dive deeper into each one of these cases. + +## Covariance {#toc-covariance} + +Consider for example the type +```js +type CovariantOf = { + +prop: X; + getter(): X; +} +``` +Here, `X` appears strictly in *output* positions: it is used to read out information +from objects `o` of type `CovariantOf`, either through property accesses `o.prop`, +or through calls to `o.getter()`. + +Notably, there is no way to input data through the reference to the object `o`, +given that `prop` is a readonly property. + +When these conditions hold, we can use the sigil `+` to annotate `X` in the definition +of `CovariantOf`: +```js +type CovariantOf<+X> = { + +prop: X; + getter(): X; +} +``` + +These conditions have important implications on the way that we can treat an object +of type `CovariantOf` with respect to subtyping. As a reminder, subtyping rules +help us answer the question: "given some context that expects values of type +`T`, is it safe to pass in values of type `S`?" If this is the case, then `S` is a +subtype of `T`. + +Using our `CovariantOf` definition, and given that `City` is a subtype of `Noun`, it is +also the case that `CovariantOf` is a subtype of `CovariantOf`. Indeed +* it is safe to *read* a property `prop` of type `City` when a property +of type `Noun` is expected, and +* it is safe to *return* values of type `City` when calling `getter()`, when +values of type `Noun` are expected. + +Combining these two, it will always be safe to use `CovariantOf` whenever a +`CovariantOf` is expected. + +A commonly used example where covariance is used is [`$ReadOnlyArray`](../../types/arrays/#toc-readonlyarray). +Just like with the `prop` property, one cannot use a `$ReadOnlyArray` reference to write data +to an array. This allows more flexible subtyping rules: Flow only needs to prove that +`S` is a subtype of `T` to determine that `$ReadOnlyArray` is also a subtype +of `$ReadOnlyArray`. + + +## Invariance {#toc-invariance} + +Let's see what happens if we try to relax the restrictions on the use of `X` and make, +for example, `prop` be a read-write property. We arrive at the type definition +```js +type NonCovariantOf = { + prop: X; + getter(): X; +}; +``` +Let's also declare a variable `nonCovariantCity` of type `NonCovariantOf` +```js +declare const nonCovariantCity: NonCovariantOf; +``` +Now, it is not safe to consider `nonCovariantCity` as an object of type `NonCovariantOf`. +Were we allowed to do this, we could have the following declaration: +```js +const nonCovariantNoun: NonCovariantOf = nonCovariantCity; +``` +This type permits the following assignment: +```js +nonCovariantNoun.prop = new Noun; +``` +which would invalidate the original type for `nonCovariantCity` as it would now be storing +a `Noun` in its `prop` field. + + +What distinguishes `NonCovariantOf` from the `CovariantOf` definition is that type parameter `X` is used both +in input and output positions, as it is being used to both read and write to +property `prop`. Such a type parameter is called *invariant* and is the default case +of variance, thus requiring no prepending sigil: +```js +type InvariantOf = { + prop: X; + getter(): X; + setter(X): void; +}; +``` +Assuming a variable +```js +declare const invariantCity: InvariantOf; +``` +it is *not* safe to use `invariantCity` in a context where: +- an `InvariantOf` is needed, because we should not be able to write a `Noun` to property +`prop`. +- an `InvariantOf` is needed, because reading `prop` could return a `City` which +may not be `SanFrancisco`. + +In orther words, `InvariantOf` is neither a subtype of `InvariantOf` nor +a subtype of `InvariantOf`. + + +## Contravariance {#toc-contravariance} + +When a type parameter is only used in *input* positions, we say that it is used in +a *contravariant* way. This means that it only appears in positions through which +we write data to the structure. We use the sigil `-` to describe this kind of type +parameters: + +```js +type ContravariantOf<-X> = { + -prop: X; + setter(X): void; +}; +``` +Common contravariant positions are write-only properties and "setter" functions. + +An object of type `ContravariantOf` can be used whenever an object of type +`ContravariantOf` is expected, but not when a `ContravariantOf` is. +In other words, `ContravariantOf` is a subtype of `ContravariantOf`, but not +`ContravariantOf`. +This is because it is fine to write `SanFrancisco` into a property that can have any `City` written +to, but it is not safe to write just any `Noun`. diff --git a/_src/lang/width-subtyping.md b/_src/lang/width-subtyping.md new file mode 100644 index 00000000000..efaf18eb6d8 --- /dev/null +++ b/_src/lang/width-subtyping.md @@ -0,0 +1,65 @@ +--- +title: Width Subtyping +slug: /lang/width-subtyping +--- + +It's safe to use an object with "extra" properties in a position that is +annotated with a specific set of properties, if that object type is [inexact](../../types/objects/#exact-and-inexact-object-types). + +```js flow-check +function func(obj: {foo: string, ...}) { + // ... +} + +func({ + foo: "test", // Works! + bar: 42 // Works! +}); +``` + +Within `func`, we know that `obj` has at least a property `foo` and the +property access expression `obj.foo` will have type `string`. + +This is a kind of subtyping commonly referred to as "width subtyping" because +a type that is "wider" (i.e., has more properties) is a subtype of a +narrower type. + +So in the following example, `obj2` is a _subtype_ of `obj1`. + +```js flow-check +let obj1: {foo: string, ...} = {foo: 'test'}; +let obj2 = {foo: 'test', bar: 42}; +obj2 as {foo: string, ...}; +``` + +However, it's often useful to know that a property is definitely absent. + +```js flow-check +function func(obj: {foo: string, ...} | {bar: number, ...}) { + if (obj.foo) { + obj.foo as string; // Error! + } +} +``` + +The above code has a type error because Flow would also allow the call +expression `func({foo: 1, bar: 2})`, because `{foo: number, bar: number}` +is a subtype of `{bar: number, ...}`, one of the members of the parameter's union +type. + +For cases like this where it's useful to assert the absence of a property, +You can use [exact object types](../../types/objects/#exact-and-inexact-object-types). + +```js flow-check +function func(obj: {foo: string} | {bar: number}) { + if (obj.foo) { + obj.foo as string; // Works! + } +} +``` + +[Exact object types](../../types/objects/#exact-and-inexact-object-types) disable width +subtyping, and do not allow additional properties to exist. + +Using exact object types lets Flow know that no extra properties will exist at +runtime, which allows [refinements](../refinements/) to get more specific. diff --git a/_src/libdefs/creation.md b/_src/libdefs/creation.md new file mode 100644 index 00000000000..a8bf3d7e43a --- /dev/null +++ b/_src/libdefs/creation.md @@ -0,0 +1,239 @@ +--- +title: Creating Library Definitions +slug: /libdefs/creation +--- + +Before spending the time to write your own libdef, we recommend that you look to +see if there is already a libdef for the third-party code that you're addressing. +`flow-typed` is a [tool and repository](https://github.com/flowtype/flow-typed/) +for sharing common libdefs within the Flow community -- so it's a good way to +knock out a good chunk of any public libdefs you might need for your project. + +However sometimes there isn't a pre-existing libdef or you have third-party +code that isn't public and/or you really just need to write a libdef yourself. +To do this you'll start by creating a `.js` file for each libdef you're going to +write and put them in the `/flow-typed` directory at the root of your project. +In these libdef file(s) you'll use a special set of Flow syntax (explained +below) to describe the interfaces of the relevant third-party code. + +## Declaring A Global Function {#toc-declaring-a-global-function} + +To declare a global function that should be accessible throughout your project, +use the `declare function` syntax in a libdef file: + +**flow-typed/myLibDef.js** +```js flow-check +declare function foo(a: number): string; +``` + +This tells Flow that any code within the project can reference the +`foo` global function, and that the function takes one argument (a `number`) and +it returns a `string`. + +## Declaring A Global Class {#toc-declaring-a-global-class} + +To declare a global class that should be accessible throughout your project, +use the `declare class` syntax in a libdef file: + +**flow-typed/myLibDef.js** +```js flow-check +declare class URL { + constructor(urlStr: string): URL; + toString(): string; + + static compare(url1: URL, url2: URL): boolean; +} +``` + +This tells Flow that any code within the project can reference the `URL` global +class. Note that this class definition does not have any implementation details +-- it exclusively defines the interface of the class. + +## Declaring A Global Variable {#toc-declaring-a-global-variable} + +To declare a global variable that should be accessible throughout your project, +use the `declare var`, `declare let`, or `declare const` syntax in a libdef file: + +**flow-typed/myLibDef.js** +```js flow-check +declare const PI: number; +``` + +This tells Flow that any code within the project can reference the `PI` global +variable -- which, in this case, is a `number`. + +## Declaring A Global Type {#toc-declaring-a-global-type} + +To declare a global type that should be accessible throughout your project, +use the `declare type` syntax in a libdef file: + +**flow-typed/myLibDef.js** +```js flow-check +declare type UserID = number; +``` + +This tells Flow that any code within the project can reference the `UserID` +global type -- which, in this case, is just an alias for `number`. + +## Declaring A Global Namespace {#toc-declaring-a-global-namespace} + +A namespace defines a collection of values and types: + +```js flow-check +declare namespace Foo { + declare const bar: string; + type Baz = number; +} + +Foo.bar as Foo.Baz; // error +``` + +To declare a global namespace that should be accessible throughout your project, +use the `declare namespace` syntax in a libdef file: + +**flow-typed/myLibDef.js** +```js flow-check +declare namespace Foo { + declare const bar: string; + type Baz = number; +} +``` + +This tells Flow that any code within the project can reference the `Foo` global +namespace. + +If a declared namespace only contains type declarations, then the namespace itself +can only be used in a type context. + +**flow-typed/myTypeOnlyNamespace.js** +```js flow-check +declare namespace TypeOnlyFoo { + type Baz = number; +} + +TypeOnlyFoo; // error +type T = TypeOnlyFoo.Baz; // ok +``` + +## Declaring A Module {#toc-declaring-a-module} + +Often, third-party code is organized in terms of modules rather than globals. To +write a libdef that declares the presence of a module you'll want to use the +`declare module` syntax: + +```js +declare module "some-third-party-library" { + // This is where we'll list the module's exported interface(s) +} +``` + +The name specified in quotes after `declare module` can be any string, but it +should correspond to the same string you'd use to `require` or `import` the +third-party module into your project. For defining modules that are accessed via +a relative `require`/`import` path, please see the docs on the [`.flow` files](../../declarations) + +Within the body of a `declare module` block, you can specify the set of exports +for that module. However, before we start talking about exports we have to talk +about the two kinds of modules that Flow supports: CommonJS and ES modules. + +Flow can handle both CommonJS and ES modules, but there are some relevant +differences between the two that need to be considered when using +`declare module`. + +#### Declaring An ES Module {#toc-declaring-an-es-module} + +[ES modules](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export) +have two kinds of exports: A **named** export and a **default** export. Flow supports the ability +to declare either or both of these kinds of exports within a `declare module` body as follows: + +###### Named Exports {#toc-named-exports} + +**flow-typed/some-es-module.js** +```js +declare module "some-es-module" { + // Declares a named "concatPath" export + declare export function concatPath(dirA: string, dirB: string): string; +} +``` + +Note that you can also declare other things inside the body of the +`declare module`, and those things will be scoped to the body of the +`declare module` -- **but they will not be exported from the module**: + +**flow-typed/some-es-module.js** +```js +declare module "some-es-module" { + // Defines the type of a Path class within this `declare module` body, but + // does not export it. It can only be referenced by other things inside the + // body of this `declare module` + declare class Path { + toString(): string; + } + + // Declares a named "concatPath" export which returns an instance of the + // `Path` class (defined above) + declare export function concatPath(dirA: string, dirB: string): Path; +} +``` + +###### Default Exports {#toc-default-exports} + +**flow-typed/some-es-module.js** +```js +declare module "some-es-module" { + declare class URL { + constructor(urlStr: string): URL; + toString(): string; + + static compare(url1: URL, url2: URL): boolean; + } + + // Declares a default export whose type is `typeof URL` + declare export default typeof URL; +} +``` + +It is also possible to declare both **named** and **default** exports in the +same `declare module` body. + +#### Declaring A CommonJS Module {#toc-declaring-a-commonjs-module} + +CommonJS modules have a single value that is exported (the `module.exports` +value). To describe the type of this single value within a `declare module` +body, you'll use the `declare module.exports` syntax: + +**flow-typed/some-commonjs-module.js** +```js +declare module "some-commonjs-module" { + // The export of this module is an object with a "concatPath" method + declare module.exports: { + concatPath(dirA: string, dirB: string): string; + }; +} +``` + +Note that you can also declare other things inside the body of the +`declare module`, and those things will be scoped to the body of the +`declare module`, **but they will not be exported from the module**: + +**flow-typed/some-commonjs-module.js** +```js +declare module "some-commonjs-module" { + // Defines the type of a Path class within this `declare module` body, but + // does not export it. It can only be referenced by other things inside the + // body of this `declare module` + declare class Path { + toString(): string; + } + + // The "concatPath" function now returns an instance of the `Path` class + // (defined above). + declare module.exports: { + concatPath(dirA: string, dirB: string): Path + }; +} +``` + +NOTE: Because a given module cannot be both an ES module and a CommonJS module, + it is an error to mix `declare export [...]` with + `declare module.exports: ...` in the same `declare module` body. diff --git a/_src/libdefs/index.md b/_src/libdefs/index.md new file mode 100644 index 00000000000..c4be1c5b6d0 --- /dev/null +++ b/_src/libdefs/index.md @@ -0,0 +1,46 @@ +--- +title: Library Definitions +slug: /libdefs +description: Learn how to create and use library definitions for the third-party code your code depends on. +--- + +## What's a "Library Definition"? {#toc-what-s-a-library-definition} + +Most real JavaScript programs depend on third-party code and not just code +immediately under the control of the project. That means a project using Flow +may need to reference outside code that either doesn't have type information or +doesn't have accurate and/or precise type information. In order to handle this, +Flow supports the concept of a "library definition" (a.k.a. "libdef"). + +A libdef is a special file that informs Flow about the type signature of some +specific third-party module or package of modules that your application uses. +If you're familiar with languages that have header files (like `C++`), you can +think of libdefs as a similar concept. + +These special files use the same `.js` extension as normal JS code, but they are +placed in a directory called `flow-typed` in the root directory of your project. +Placement in this directory tells Flow to interpret them as libdefs rather than +normal JS files. + +> NOTE: Using the `/flow-typed` directory for libdefs is a convention that +> enables Flow to JustWork™ out of the box and encourages consistency +> across projects that use Flow, but it is also possible to explicitly +> configure Flow to look elsewhere for libdefs using the [`[libs]` section +> of your `.flowconfig`](../config/libs). + +You can also learn about [declaration files](../declarations). + +## General Best Practices {#toc-general-best-practices} + +**Try to provide a libdef for each third-party library your project uses.** + +If a third-party library that has no type information is used by your project, +Flow will treat it like any other untyped dependency and mark all of its exports +as `any`. + +Because of this behavior, it is a best practice to find or write libdefs for as +many of the third-party libraries that you use as you can. We recommend checking +out the `flow-typed` +[tool and repository](https://github.com/flow-typed/flow-typed/) +, which helps you quickly find and install pre-existing libdefs for your +third-party dependencies. diff --git a/_src/linting/flowlint-comments.md b/_src/linting/flowlint-comments.md new file mode 100644 index 00000000000..ff8eee30a2e --- /dev/null +++ b/_src/linting/flowlint-comments.md @@ -0,0 +1,82 @@ +--- +title: Flowlint Comments +slug: /linting/flowlint-comments +--- + +You can use `flowlint` comments to specify more granular lint settings within a file. +These comments come in three froms: +* [flowlint](#toc-flowlint) +* [flowlint-line](#toc-flowlint-line) +* [flowlint-next-line](#toc-flowlint-next-line) + +In all forms, whitespace and asterisks between words are ignored, allowing for flexible formatting. + +### flowlint {#toc-flowlint} +The basic `flowlint` comment takes a comma-delimited list of `rule:severity` pairs and +applies those settings for the rest of the source file until overridden. This has +three primary purposes: applying settings over a block, applying settings over a file, +and applying settings over part of a line. + +**settings over a block of code:** +A pair of `flowlint` comments can be used to apply a certain setting over a block of code. +For example, to disable the untyped-type-import lint over a block of type imports would look like this: +```js +import type { + // flowlint untyped-type-import:off + Foo, + Bar, + Baz, + // flowlint untyped-type-import:error +} from './untyped.js'; +``` + +**settings over a file:** +A `flowlint` comment doesn't have to have a matching comment to form a block. +An unmatched comment simply applies its settings to the rest of the file. You +could use this, for example, to suppress all sketchy-null-check lints in a particular file: +```js +// flowlint sketchy-null:off +... +``` + +**settings over part of a line:** +The settings applied by `flowlint` start and end right at the comment itself. This +means that you can do things like +```js +function foo(a: ?boolean, b: ?boolean) { + if (/* flowlint sketchy-null-bool:off */a/* flowlint sketchy-null-bool:warn */ && b) { + ... + } else { + ... + } +} +``` +if you want control at an even finer level than you get from the line-based comments. + +### flowlint-line {#toc-flowlint-line} +A `flowlint-line` comment works similarly to a `flowlint` comment, except it only +applies its settings to the current line instead of applying them for the rest of the file. +The primary use for `flowlint-line` comments is to suppress a lint on a particular line: +```js +function foo(x: ?boolean) { + if (x) { // flowlint-line sketchy-null-bool:off + ... + } else { + ... + } +} +``` + + +### flowlint-next-line {#toc-flowlint-next-line} +`flowlint-next-line` works the same as `flowlint-line`, except it applies its settings to the next line instead of the current line: +```js +function foo(x: ?boolean) { + // flowlint-next-line sketchy-null-bool:off + if (x) { + ... + } else { + ... + } +} +``` diff --git a/_src/linting/index.md b/_src/linting/index.md new file mode 100644 index 00000000000..d1f0df52c20 --- /dev/null +++ b/_src/linting/index.md @@ -0,0 +1,80 @@ +--- +title: Linting Overview +slug: /linting +description: Learn how to configure Flow's linter to find potentially harmful code. +--- + +Flow contains a linting framework that can tell you about more than just type errors. This framework is highly configurable in order to show you the information you want and hide the information you don't. + +### Configuring Lints in the `.flowconfig` {#toc-configuring-lints-in-the-flowconfig} + +Lint settings can be specified in the `[lints]` section of the `.flowconfig` as a list of `rule=severity` pairs. These settings apply globally to the entire project. + +``` +[lints] +all=warn +untyped-type-import=error +sketchy-null-bool=off +``` + +### Configuring Lints from the CLI {#toc-configuring-lints-from-the-cli} + +Lint settings can be specified using the `--lints` flag of a Flow server command as a comma-delimited list of `rule=severity` pairs. These settings apply globally to the entire project. + +``` +flow start --lints "all=warn, untyped-type-import=error, sketchy-null-bool=off" +``` + +### Configuring Lints with Comments {#toc-configuring-lints-with-comments} + +Lint settings can be specified inside a file using `flowlint` comments. These +settings apply to a region of a file, or a single line, or part of a line. For +more details see [Flowlint Comments](./flowlint-comments). + +```js flow-check +// flowlint sketchy-null:error +const x: ?number = 0; + +if (x) {} // Error + +// flowlint-next-line sketchy-null:off +if (x) {} // No Error + +if (x) {} /* flowlint-line sketchy-null:off */ // No Error + +// flowlint sketchy-null:off +if (x) {} // No Error +if (x) {} // No Error +``` + +### Lint Settings Precedence {#toc-lint-settings-precedence} + +Lint settings in `flowlint` comments have the highest priority, followed by lint rules in the `--lints` flag, followed by the `.flowconfig`. +This order allows you to use `flowlint` comments for fine-grained linting control, the `--lints` flag for trying out new lint settings, and the `.flowconfig` for stable project-wide settings. + +Within the `--lints` flag and the `.flowconfig`, rules lower down override rules higher up, allowing you to write things like +``` +[lints] +# warn on all sketchy-null checks +sketchy-null=warn +# ... except for booleans +sketchy-null-bool=off +``` + +The lint settings parser is fairly intelligent and will stop you if you write a redundant rule, a rule that gets completely overwritten, or an unused flowlint suppression. This should prevent most accidental misconfigurations of lint rules. + +### Severity Levels and Meanings {#toc-severity-levels-and-meanings} + +**off:** +The lint is ignored. Setting a lint to `off` is similar to suppressing a type error with a suppression comment, except with much more granularity. + +**warn:** +Warnings are a new severity level introduced by the linting framework. They are treated differently than errors in a couple of ways: +* Warnings don't affect the exit code of Flow. If Flow finds warnings but no errors, it still returns 0. +* Warnings aren't shown on the CLI by default, to avoid spew. CLI warnings can be + enabled by passing the `--include-warnings` flag to the Flow server or the + Flow client, or by setting `include_warnings=true` in the `.flowconfig`. + This is good for smaller projects that want to see all project warnings at once. + +**error:** +Lints with severity `error` are treated exactly the same as any other Flow error. diff --git a/_src/linting/rule-reference.md b/_src/linting/rule-reference.md new file mode 100644 index 00000000000..fb58f6c7446 --- /dev/null +++ b/_src/linting/rule-reference.md @@ -0,0 +1,232 @@ +--- +title: Lint Rule Reference +slug: /linting/rule-reference +--- + +### `all` {#toc-all} +While `all` isn't technically a lint rule, it's worth mentioning here. `all` sets the default +level for lint rules that don't have a level set explicitly. `all` can only +occur as the first entry in a `.flowconfig` or as the first rule in a `--lints` +flag. It's not allowed in comments at all because it would have different +semantics than would be expected. + +### `ambiguous-object-type` {#toc-ambiguous-object-type} +Triggers when you use object type syntax without explicitly specifying exactness or inexactness. + +This lint setting is ignored when [`exact_by_default`](../../config/options/#toc-exact-by-default) is set to `false`. + +```js flow-check +// flowlint ambiguous-object-type:error + +type A = {x: number}; // Error +type B = {x: number, ...} // Ok +type C = {| x: number |} // Ok +``` + +### `deprecated-type` {#toc-deprecated-type} +Triggered on the `bool` type, which is just an alias for `boolean`. Just use `boolean` instead. + +```js flow-check +// flowlint deprecated-type:error + +type A = Array; // Error +``` + +### `implicit-inexact-object` {#toc-implicit-inexact-object} +Like [`ambiguous-object-type`](#toc-ambiguous-object-type), except triggers even when the `exact_by_default` option is set to `false`. + +### `nonstrict-import` {#toc-nonstrict-import} +Used in conjuction with [Flow Strict](../../strict/). Triggers when importing a non `@flow strict` module. When enabled, dependencies of a `@flow strict` module must also be `@flow strict`. + +### `sketchy-null` {#toc-sketchy-null} +Triggers when you do an existence check on a value that can be either null/undefined or falsey. + +For example: +```js flow-check +// flowlint sketchy-null:error + +const x: ?number = 5; +if (x) {} // sketchy because x could be either null or 0. + +const y: number = 5; +if (y) {} // not sketchy because y can't be null, only 0. + +const z: ?{foo: number} = {foo: 5}; +if (z) {} // not sketchy, because z can't be falsey, only null/undefined. +``` + +Setting `sketchy-null` sets the level for all sketchy null checks, but there are more granular rules for particular types. These are: +* `sketchy-null-bool` +* `sketchy-null-number` +* `sketchy-null-string` +* `sketchy-null-mixed` +* `sketchy-null-bigint` + +The type-specific variants are useful for specifying that some types of sketchy null checks are acceptable while others should be errors/warnings. For example, if you want to allow boolean sketchy null checks (for the pattern of treating undefined optional booleans as false) but forbid other types of sketchy null checks, you can do so with this `.flowconfig` `[lints]` section: +``` +[lints] +sketchy-null=warn +sketchy-null-bool=off +``` +and now +```js +function foo (bar: ?bool): void { + if (bar) { + ... + } else { + ... + } +} +``` +doesn't report a warning. + +Suppressing one type of sketchy null check only suppresses that type, so, for example +```js flow-check +// flowlint sketchy-null:error, sketchy-null-bool:off +const x: ?(number | bool) = 0; +if (x) {} +``` +would still have a `sketchy-null-number` error on line 3. + +### `sketchy-number` {#toc-sketchy-number} +Triggers when a `number` is used in a manner which may lead to unexpected results if the value is falsy. +Currently, this lint triggers if a `number` appears in: +* the left-hand side of an `&&` expression. + +As a motivating example, consider this common idiom in React: + +```js +{showFoo && } +``` + +Here, `showFoo` is a boolean which controls whether or not to display the `` element. If `showFoo` is true, then this evaluates to `{}`. If `showFoo` is false, then this evaluates to `{false}`, which doesn't display anything. + +Now suppose that instead of a boolean, we have a numerical value representing, say, the number of comments on a post. We want to display a count of the comments, unless there are no comments. We might naively try to do something similar to the boolean case: + +```js +{count && <>[{count} comments]} +``` + +If `count` is, say, `5`, then this displays "[5 comments]". However, if `count` is `0`, then this displays "0" instead of displaying nothing. (This problem is unique to `number` because `0` and `NaN` are the only falsy values which React renders with a visible result.) This could be subtly dangerous: if this immediately follows another numerical value, it might appear to the user that we have multiplied that value by 10! Instead, we should do a proper conditional check: + +```js +{count ? <>[{count} comments] : null} +``` + +### `unclear-type` {#toc-unclear-type} +Triggers when you use `any`, `Object`, or `Function` as type annotations. These +types are unsafe. + +```js flow-check +// flowlint unclear-type:error + +declare const a: any; // Error +declare const c: Object; // Error +declare const d: Function; // Error +``` + +### `unnecessary-invariant` {#toc-unnecessary-invariant} +Triggers when you use `invariant` to check a condition which we know must be truthy based on the available type information. This is quite conservative: for example, if all we know about the condition is that it is a `boolean`, then the lint will not fire even if the condition must be `true` at runtime. + +Note that this lint does not trigger when we know a condition is always `false`. It is a common idiom to use `invariant()` or `invariant(false, ...)` to throw in code that should be unreachable. + +```js flow-check +// flowlint unnecessary-invariant:error +declare function invariant(boolean): void; + +declare const x: Array; // Array is truthy +invariant(x); +``` + +### `unnecessary-optional-chain` {#toc-unnecessary-optional-chain} + +Triggers when you use `?.` where it isn't needed. This comes in two main flavors. The first is when the left-hand-side cannot be nullish: + +```js flow-check +// flowlint unnecessary-optional-chain:error +type Foo = { + bar: number +} + +declare const foo: Foo; +foo?.bar; // Error +``` + +The second is when the left-hand-side could be nullish, but the short-circuiting behavior of `?.` is sufficient to handle it anyway: + +```js flow-check +// flowlint unnecessary-optional-chain:error +type Foo = { + bar: { + baz: number + } +} + +declare const foo: ?Foo; +foo?.bar?.baz; // Error +``` + +In the second example, the first use of `?.` is valid, since `foo` is potentially nullish, but the second use of `?.` is unnecessary. The left-hand-side of the second `?.` (`foo?.bar`) can only be nullish as a result of `foo` being nullish, and when `foo` is nullish, short-circuiting lets us avoid the second `?.` altogether! + +```js +foo?.bar.baz; +``` + +This makes it clear to the reader that `bar` is not a potentially nullish property. + +### `unsafe-getters-setters` {#toc-unsafe-getters-setters} +Triggers when you use getters or setters. Getters and setters can have side +effects and are unsafe. + +For example: + +```js flow-check +// flowlint unsafe-getters-setters:error +let a = 1; +const o = { + get a() { return a; }, // Error: unsafe-getters-setters + set b(x: number) { a = x; }, // Error: unsafe-getters-setters + c: 10, +}; +``` + +### `untyped-import` {#toc-untyped-import} +Triggers when you import from an untyped file. Importing from an untyped file +results in those imports being typed as `any`, which is unsafe. + +### `untyped-type-import` {#toc-untyped-type-import} +Triggers when you import a type from an untyped file. Importing a type from an +untyped file results in an `any` alias, which is typically not the intended behavior. +Enabling this lint brings extra attention to this case and can help improve Flow +coverage of typed files by limiting the spread of implicit `any` types. + +### `unused-promise` {#toc-unused-promise} +Triggers when a `Promise` is unused. This can be dangerous, because errors are potentially unhandled, and the code may not execute in the desired order. + +A promise can be "used" by... +* `await`ing it +* Calling `.then` with a rejection handler (i.e., with two arguments) +* Calling `.catch` +* Calling `.finally` +* Storing it in a variable, passing it to a function, etc. + +For example: + +```js flow-check +// flowlint unused-promise:error +declare function foo(): Promise; + +async function bar() { + await foo(); // ok + foo(); // error, we forgot to await! +} + +function baz() { + foo().catch(err => {console.log(err)}); // ok + foo(); // error +} +``` + +You can explicitly ignore the promise with the `void` operator (e.g., `void foo();`). + +Note: As of v0.201.0, this rule subsumed the `unused-promise-in-async-scope` and `unused-promise-in-sync-scope` rules. diff --git a/_src/react/component-syntax.md b/_src/react/component-syntax.md new file mode 100644 index 00000000000..cb9dd10b7db --- /dev/null +++ b/_src/react/component-syntax.md @@ -0,0 +1,204 @@ +--- +title: Component Syntax +slug: /react/component-syntax +--- + +[Components](https://react.dev/learn/your-first-component) are the foundation for building UIs in React. While components are typically expressed using JavaScript functions, Component Syntax provides component primitive values that provide several advantages over function components, like: + +1. More elegant syntax with significantly less verbosity and boilerplate than functions +2. Type system support tailored specifically for writing React +3. Better support for [React refs](https://react.dev/learn/manipulating-the-dom-with-refs) + +## Basic Usage +You can declare a component with Component Syntax similar to how you'd declare a function: + +```js flow-check +import * as React from 'react'; + +component Introduction(name: string, age: number) { + return

My name is {name} and I am {age} years old

+} +``` + +You can use a component directly in JSX: ``. + +There are a few important details to notice here: + +1. the prop parameter names declared in the Introduction component are the same as the prop names passed to Introduction in JSX +2. the order of the parameters in the declaration does not need to match the order that they are provided in JSX + +## Parameters + +### String Parameters/Renaming Parameters + +Components also allow you to rename parameters, which is useful when your parameter name is not a valid JavaScript identifier: + +```js flow-check +import * as React from 'react'; + +component RenamedParameter( + 'required-renamed' as foo: number, + 'optional-renamed' as bar?: number, + 'optional-with-default-renamed' as baz?: number = 3, +) { + (foo: number); // OK + (bar: number | void); // OK + (baz: number); // OK + + return
; +} +``` + +### Rest Parameters + +Sometimes you do not want to list out every prop explicitly because you do not intend to reference them individually in your component. This is common when you are writing a component that wraps another and need to pass props from your component to the inner one: + +```jsx +import * as React from 'react'; + +import type {Props as StarProps} from './Star'; +import Star from './Star'; + +component BlueStar(...props: StarProps) { + return ; +} +``` + +Rest parameters use an object type as an annotation, which means you can use existing type utilities like object spreads and Pick to annotate more complex prop patterns: + +```js flow-check +import * as React from 'react'; + +component OtherComponent(foo: string, bar: number) { + return foo + bar; +} + +component FancyProps( + ...props: { + ...React.PropsOf, + additionalProp: string, + } +) { + return ; +} +``` + +### Optional Parameters and Defaults + +Components allow you to declare optional parameters and specify defaults: + +```js flow-check +import * as React from 'react'; + +component OptionalAndDefaults( + color: string = "blue", + extraMessage?: string, +) { + let message = `My favorite color is ${color}.`; + if (extraMessage != null) { + message += `\n${extraMessage}`; + } + return

{message}

+} + + // No error, all of the parameters are optional! +``` + +### Destructuring Parameters + +The `as` operator also allows you to destructure your parameters: + +```js flow-check +import * as React from 'react'; + +component Destructuring( + config as {color, height}: $ReadOnly<{color: number, height: number}>, +) { return
} +``` + +Rest parameters can be destructured without using as: + +```js flow-check +import * as React from 'react'; + +type Props = $ReadOnly<{ color: string, height: number }>; + +component DestructuredRest(...{color, height}: Props) { return
} +``` + +### Ref Parameters + +To access refs in components you just need to add a ref parameter. + +```js flow-check +import * as React from 'react'; + +component ComponentWithARef(ref: React.RefSetter) { + return
; +} +``` + +Behind the scenes Component Syntax will wrap the component in the required [React.forwardRef call](https://react.dev/reference/react/forwardRef) to ensure the component works as expected at runtime. The one restriction for refs is they must be defined as an inline parameter, refs within rest params are not supported. This is due to the need to compile in the `forwardRef` call, for this to work correctly we need to be able to statically determine the ref from the component definition. + +## Elements of Components + +For components declared in Component Syntax, you can use `ComponentName` to reference the type of an element of that component. +For components not using Component Syntax you have to write `React.Element`. + +```js flow-check +import * as React from 'react'; + +declare component Foo(); +declare component Bar(); + +const foo: Foo = ; +const bar: Bar = ; // ERROR +``` + +The syntax also works for generic components: + +```js flow-check +import * as React from 'react'; + +declare component Foo(prop: T); + +const foo1: Foo = ; +const foo2: Foo = ; // Invalid generic type argument +const foo3: Foo = ; // Missing generic type argument +``` + +We do not recommend requiring very specific type of elements. It will make your parent and children components more coupled. Instead, this feature is designed to make expressing [render types](../render-types) easier. + +## Rules for Components + +Component Syntax enforces a few restrictions in components to help ensure correctness: + +1. The return values must be a subtype of `React.Node`, otherwise React may crash while rendering your component. +2. All branches of a component must end in an explicit return. Even though `undefined` is a valid return value, we've seen many instances where an explicit return would have prevented bugs in production. +3. You cannot use `this` in a component. + +So these components are invalid: + +```js flow-check +import * as React from 'react'; + +component InvalidReturnValue() { + return new Object(); // ERROR: Value does not match `React.Node` type +} + +component ImplicitReturn(someCond: boolean) { + if (someCond) { + return

Hello World!

; + } + // ERROR: No return in this branch +} + +component UsesThis() { + this.foo = 3; // ERROR: Accessing `this` + return null; +} +``` + +## Enable Component Syntax {#toc-enable-component-syntax} + +In your `.flowconfig`, under the `[options]` heading, add `component_syntax=true`. diff --git a/_src/react/component-types.md b/_src/react/component-types.md new file mode 100644 index 00000000000..57a37530f75 --- /dev/null +++ b/_src/react/component-types.md @@ -0,0 +1,102 @@ +--- +title: Component Types +slug: /react/component-types +--- +:::info +Component Types are only available in Flow v0.243.0+. If you are on an older version, please use [React.AbstractComponent](../types#toc-react-abstractcomponent) +::: + +Component Types have syntax similar to our runtime [Component Syntax](../component-syntax) to make it easy to describe +the type of a component. Component Types are most useful for writing library definitions. + +## Specifying Props +To declare a Component Type you can use the `component` keyword and list out the props your component expects. + +```js flow-check +import * as React from 'react'; + +type ComponentType = component(numberProp: number, optionalProp?: string); + +declare const Component: ComponentType; + +; // OK! optionalProp is optional +``` + +Like [Component Syntax](../component-syntax/#rest-parameters), Component Types also accept a rest parameter: +```js +import * as React from 'react'; + +import type {Props as StarProps} from './Star'; +import Star from './Star'; + +type BlueStarType = component(specificProp: string, ...StarProps); +``` + +Like Component Syntax, you can also declare an inline ref prop (but not in your rest parameter): +```js flow-check +import * as React from 'react'; +import {useRef} from 'react'; + +type ComponentWithRef = component(someProp: number, ref: React.RefSetter); + +declare const Component: ComponentWithRef; + +component Example() { + const ref = useRef(null); + return ; +} +``` +## Specifying Render Types + +You can also specify the [Render Type](../render-types) for your component just like you can with +[Component Syntax](../render-types/#basic-behavior) + +```js flow-check +import * as React from 'react'; + +component Foo() { return null } +type ComponentWithRenders = component() renders Foo; + +declare const Component: ComponentWithRenders; + as renders Foo; // OK! +``` + +## Polymorphic Component Types + +You can also write polymorphic Component Types, which is helpful for declaring "transparent" components: + +```js flow-check +import * as React from 'react'; + +declare const TransparentComponent: component(children: T) renders T; + +component Example() { return null } + +const element: renders Example = ( + + + +); // OK! +``` + +## Annotating Components with Component Types + +Here's how you can describe the type of a Component Syntax component using a Component Type: +```js flow-check +import * as React from 'react'; + +component Foo() { return null } + +component Example(someProp: number, ref: React.RefSetter) renders Foo { + return ; +} + +Example as component(someProp: number, ref: React.RefSetter) renders Foo; // OK! + + +component PolymorphicExample(children: T) renders T { + return children; +} + +PolymorphicExample as component(children: T) renders T; // OK! +``` diff --git a/_src/react/events.md b/_src/react/events.md new file mode 100644 index 00000000000..7b8bdb0048f --- /dev/null +++ b/_src/react/events.md @@ -0,0 +1,68 @@ +--- +title: Event Handling +slug: /react/events +--- + +The [React docs for handling events](https://react.dev/learn/responding-to-events) show how an event handler can be attached to +a React element. To type these event handlers you may use the `SyntheticEvent` +types like this: + +```js flow-check +import {useState} from 'react'; +import * as React from 'react'; + +function MyComponent(): React.Node { + const [state, setState] = useState({count: 0}); + + const handleClick = (event: SyntheticEvent) => { + // To access your button instance use `event.currentTarget`. + event.currentTarget as HTMLButtonElement; + + setState(prevState => ({ + count: prevState.count + 1, + })); + }; + + return ( +
+

Count: {state.count}

+ +
+ ); +} +``` + +There are also more specific synthetic event types like +`SyntheticKeyboardEvent`, `SyntheticMouseEvent`, or +`SyntheticTouchEvent`. The `SyntheticEvent` types all take a single type +argument: the type of the HTML element the event handler was placed on. + +If you don't want to add the type of your element instance you can also use +`SyntheticEvent` with *no* type arguments like so: `SyntheticEvent<>`. + +> **Note:** To get the element instance, like `HTMLButtonElement` in the example +> above, it is a common mistake to use `event.target` instead of +> `event.currentTarget`. The reason you want to use `event.currentTarget` is +> that `event.target` may be the wrong element due to [event propagation][]. + +[event propagation]: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Examples#example_5_event_propagation + +> **Note:** React uses its own event system so it is important to use the +> `SyntheticEvent` types instead of the DOM types such as `Event`, +> `KeyboardEvent`, and `MouseEvent`. + +The `SyntheticEvent` types that React provides and the DOM events they are +related to are: + +- `SyntheticEvent` for [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) +- `SyntheticAnimationEvent` for [AnimationEvent](https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent) +- `SyntheticCompositionEvent` for [CompositionEvent](https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent) +- `SyntheticInputEvent` for [InputEvent](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent) +- `SyntheticUIEvent` for [UIEvent](https://developer.mozilla.org/en-US/docs/Web/API/UIEvent) +- `SyntheticFocusEvent` for [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent) +- `SyntheticKeyboardEvent` for [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) +- `SyntheticMouseEvent` for [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) +- `SyntheticDragEvent` for [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent) +- `SyntheticWheelEvent` for [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent) +- `SyntheticTouchEvent` for [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent) +- `SyntheticTransitionEvent` for [TransitionEvent](https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent) diff --git a/_src/react/function-and-class-components.md b/_src/react/function-and-class-components.md new file mode 100644 index 00000000000..13392b802ad --- /dev/null +++ b/_src/react/function-and-class-components.md @@ -0,0 +1,185 @@ +--- +title: Function/Class Components +slug: /react/function-and-class-components +--- + +Adding Flow types to your [React components](https://react.dev/learn/your-first-component) is incredibly powerful. After typing +your component, Flow will statically ensure that you are using the component in +the way it was designed to be used. + +## Functional Components {#toc-functional-components} + +Adding Flow types to a functional component is the same as [adding types to a standard function](../../types/functions/). +Just create an object type for the props and Flow will ensure that the props passed to the component match up with what is expected. + +```js flow-check +import React from 'react'; + +type Props = { + foo: number, + bar?: string, +}; + +function MyComponent(props: Props) { + props.doesNotExist; // Error! You did not define a `doesNotExist` prop. + + return
{props.bar}
; +} + + +``` + + +### Adding Default Props to Functional Components {#toc-adding-default-props-to-functional-components} + +A nice pattern to add default props to functional components is to use +[destructuring with default values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment/#default_value). +By destructuring the props in the function parameter, you can assign a value to any props that are not passed +to the component (or passed with the value `undefined`). + +```js flow-check +import React from 'react'; + +type Props = { + foo?: number, // foo is optional to pass in. + bar: string, // bar is required. +}; + +function MyComponent({foo = 42, bar}: Props) { + // Flow knows that foo is not null or undefined + const baz = foo + 1; +} + +// And we don't need to include foo. +; +``` + +## Class Components {#toc-class-components} + +To Flowify a [class component](https://react.dev/reference/react/Component#defining-a-class-component), the type of the props can be passed as the first +argument to the `React.Component` type. This will have the same effect as adding types +to the `props` parameter of a function component. + +```js flow-check +import React from 'react'; + +type Props = { + foo: number, + bar?: string, +}; + +class MyComponent extends React.Component { + render(): React.Node { + this.props.doesNotExist; // Error! You did not define a `doesNotExist` prop. + + return
{this.props.bar}
; + } +} + +; +``` + +Now wherever we use `this.props` in our React component Flow will treat it as +the `Props` type we defined. + +> **Note:** If you don't need to use the `Props` type again you could also +> define it inline: `extends React.Component<{ foo: number, bar?: string }>`. + +`React.Component` is a [generic type](../../types/generics) that takes two type +arguments: props and state. The second type argument, `State`, is optional. By +default it is `undefined` so you can see in the example above we did not include +`State`. We will learn more about state in the next section... + +### Adding State {#toc-adding-state} + +To add a type for state to your React class component: create a new object +type, in the example below we name it `State`, and pass it as the second type +argument to `React.Component`. + +```js flow-check +import React from 'react'; + +type Props = { /* ... */ }; + +type State = { + count: number, +}; + +class MyComponent extends React.Component { + state: State = { + count: 0, + }; + + componentDidMount() { + setInterval(() => { + this.setState(prevState => ({ + count: prevState.count + 1, + })); + }, 1000); + } + + render(): React.Node { + return
Count: {this.state.count}
; + } +} + +; +``` + +In the example above we are using a [React `setState()` updater function](https://react.dev/reference/react/Component#setstate) +but you could also pass a partial state object to `setState()`. + +> **Note:** If you don't need to use the `State` type again you could also +> define it inline: `extends React.Component<{}, { count: number }>`. + +### Using Default Props for Class Components {#toc-using-default-props-for-class-components} + +React supports the notion of `defaultProps` which you can think of as default +function arguments. When you create an element and do not include a prop +which has a default then React will substitute that prop with its corresponding +value from `defaultProps`. Flow supports this notion as well. To type default +props add a `static defaultProps` property to your class. + +```js flow-check +import React from 'react'; + +type Props = { + foo: number, // foo is required. + bar: string, // bar is required. +}; + +class MyComponent extends React.Component { + static defaultProps: {foo: number} = { + foo: 42, // ...but we have a default prop for foo. + }; +} + +// So we don't need to include foo. + +``` + +> **Note:** You don't need to make `foo` nullable in your `Props` type. Flow +> will make sure that `foo` is optional if you have a default prop for `foo`. + +If you add a type annotation to `defaultProps` you can define the type as +```js flow-check +type DefaultProps = { + foo: number, +}; +``` +and spread that into the `Props` type: +```js +type Props = { + ...DefaultProps, + bar: string, +}; +``` +This way you avoid duplicating the properties that happen to have a default value. + +> **Note:** You can also apply this format of default props to functional components +> by adding a `defaultProps` property to a the component function. However, it is generally +> simpler to use the destructuring pattern described above. +> ```js flow-check +> function MyComponent(props: {foo: number}) {} +> MyComponent.defaultProps = {foo: 42}; +> ``` diff --git a/_src/react/hoc.md b/_src/react/hoc.md new file mode 100644 index 00000000000..682bf8ef012 --- /dev/null +++ b/_src/react/hoc.md @@ -0,0 +1,184 @@ +--- +title: Higher-order Components +slug: /react/hoc +--- + +:::danger +Higher-order components are discouraged in modern React code and will not be updated for [Component Syntax](../component-syntax). +Consider using a hook to accomplish your task instead. +::: + +A popular pattern in React is the [higher-order component pattern][], so it's +important that we can provide effective types for higher-order components in +Flow. If you don't already know what a higher-order component is then make sure +to read the [React documentation on higher-order components][] before +continuing. + +[higher-order component pattern]: https://facebook.github.io/react/docs/higher-order-components.html +[React documentation on higher-order components]: https://facebook.github.io/react/docs/higher-order-components.html + +You can make use of the [`React.AbstractComponent`](../types/#toc-react-abstractcomponent) type to annotate your higher order components. + +### The Trivial HOC {#toc-the-trivial-hoc} + +Let's start with the simplest HOC: + +```js flow-check +import * as React from 'react'; + +function trivialHOC( + Component: React.AbstractComponent +): React.AbstractComponent { + return Component; +} +``` + +This is a basic template for what your HOCs might look like. At runtime, this HOC doesn't +do anything at all. Let's take a look at some more complex examples. + +### Injecting Props {#toc-injecting-props} + +A common use case for higher-order components is to inject a prop. +The HOC automatically sets a prop and returns a component which no longer requires +that prop. For example, consider a navigation prop. How would one type this? + +To remove a prop from the config, we can take a component that includes the +prop and return a component that does not. It's best to construct these +types using object type spread. + +```js flow-check +import * as React from 'react'; + +type InjectedProps = {foo: number} + +function injectProp( + Component: React.AbstractComponent<{...Config, ...InjectedProps}> +): React.AbstractComponent { + return function WrapperComponent( + props: Config, + ) { + return ; + }; +} + +function MyComponent(props: { + a: number, + b: number, + ...InjectedProps, +}): React.Node {} + +const MyEnhancedComponent = injectProp(MyComponent); + +// We don't need to pass in `foo` even though `MyComponent` requires it: +; // OK + +// We still require `a` and `b`: +; // ERROR +``` + +### Preserving the Instance Type of a Component {#toc-preserving-the-instance-type-of-a-component} + +Recall that the instance type of a function component is `void`. Our example +above wraps a component in a function, so the returned component has the instance +type `void`. + +```js flow-check +import * as React from 'react'; + +type InjectedProps = {foo: number} + +function injectProp( + Component: React.AbstractComponent<{...Config, ...InjectedProps}> +): React.AbstractComponent { + return function WrapperComponent( + props: Config, + ) { + return ; + }; +} + +// A class component in this example +class MyComponent extends React.Component<{ + a: number, + b: number, + ...InjectedProps, +}> {} + +const MyEnhancedComponent = injectProp(MyComponent); + +// If we create a ref object for the component, it will never be assigned +// an instance of MyComponent! +const ref = React.createRef(); + +// Error, mixed is incompatible with MyComponent. +; +``` + +We get this error message because `React.AbstractComponent` doesn't set the `Instance` type +parameter, so it is automatically set to `mixed`. If we wanted to preserve the instance type +of the component, we can use [`React.forwardRef`](https://reactjs.org/docs/forwarding-refs.html): + +```js flow-check +import * as React from 'react'; + +type InjectedProps = {foo: number} + +function injectAndPreserveInstance( + Component: React.AbstractComponent<{...Config, ...InjectedProps}, Instance> +): React.AbstractComponent { + return React.forwardRef((props, ref) => + + ); +} + +class MyComponent extends React.Component<{ + a: number, + b: number, + ...InjectedProps, +}> {} + +const MyEnhancedComponent = injectAndPreserveInstance(MyComponent); + +const ref = React.createRef(); + +// All good! The ref is forwarded. +; +``` + +### Exporting Wrapped Components {#toc-exporting-wrapped-components} + +If you try to export a wrapped component, chances are that you'll run into a missing annotation error: + +```js flow-check +import * as React from 'react'; + +function trivialHOC( + Component: React.AbstractComponent, +): React.AbstractComponent { + return Component; +} + +type Props = $ReadOnly<{bar: number, foo?: number}>; + +function MyComponent({bar, foo = 3}: Props): React.Node {} + +export const MyEnhancedComponent = trivialHOC(MyComponent); // ERROR +``` + +You can add an annotation to your exported component using `React.AbstractComponent`: + +```js flow-check +import * as React from 'react'; + +function trivialHOC( + Component: React.AbstractComponent, +): React.AbstractComponent { + return Component; +} + +type Props = $ReadOnly<{bar: number, foo?: number}>; + +function MyComponent({bar, foo = 3}: Props): React.Node {} + +export const MyEnhancedComponent: React.AbstractComponent = trivialHOC(MyComponent); // OK +``` diff --git a/_src/react/hook-syntax.md b/_src/react/hook-syntax.md new file mode 100644 index 00000000000..ea3ff9e9506 --- /dev/null +++ b/_src/react/hook-syntax.md @@ -0,0 +1,162 @@ +--- +title: Hook Syntax +slug: /react/hook-syntax +--- + +Hook Syntax is first-class syntax and typechecking support for React hooks, bringing hooks into +the React language as their own entities that are syntactically and semantically distinct from +regular functions, and using Flow to enforce that the [Rules of React](https://react.dev/reference/rules) aren’t violated. + +## Basic Usage + +The primary difference between writing a function and a hook is the `hook` keyword: +```js flow-check +import {useState, useEffect} from 'react'; + +hook useOnlineStatus(initial: boolean): boolean { + const [isOnline, setIsOnline] = useState(initial); + useEffect(() => { + // ... + }, []); + return isOnline; +} +``` + +Hooks can be called just like regular functions: +```js flow-check +import * as React from 'react'; + +hook useOnlineStatus(): boolean { + return true; +} + +component StatusBar() { + const isOnline = useOnlineStatus(); + return

{isOnline ? '✅ Online' : '❌ Disconnected'}

; +} +``` + +Hooks can be exported just like normal functions: + +```js flow-check +export hook useNamedExportedHook(): boolean { + return true; +} + +export default hook useDefaultExportedHook(): boolean { + return true; +} +``` + +## Hook Type Annotations +There are a few cases where you might wish to define a value as having the type of a +hook. Because function types and hook types aren’t compatible (more on this below!), +we also introduce a new syntax for hook type annotations, which is simply the +existing function type annotation but preceded by hook. + +```js +export const useGKOnlineStatus: hook (boolean) => boolean = + experiment('show_online_status') + ? useOnlineStatus + : useAlwaysOnlineStatus +``` + +## Enforcing the Rules of React with Hook Syntax +With hook syntax, we can now unambiguously distinguish syntactically between hooks and +non-hooks. Flow will use this information to enforce a number of the rules of hooks and +[Rules of React](https://react.dev/reference/rules) generally. + +### Preventing Unsafe Mutation +According to the [Rules of React](https://react.dev/reference/rules), refs aren’t allowed +to be read from or written to while a component is rendering, and the return value of +other hooks (especially `useState``) cannot be safely mutated directly at all. By making +Flow aware of hooks as a first-class concept, we can now detect these issues in many cases +and raise errors early, rather than depending on testing to uncover them. + +```js flow-check +import {useState, useEffect, useRef} from 'react'; +import * as React from 'react'; + +component MyComponent() { + const ref = useRef(null); + const [state, setState] = useState<{ val: number }>({val: 0}); + + state.val = 42; // Flow error: cannot mutate return value of hook + + return ( +
+ {ref.current /* Flow error: cannot read ref during rendering */} +
+ ); +} +``` +Flow currently prevents component props from being modified within the component. +Hook syntax allows us to extend this checking to hooks, and will let us detect and +raise errors when illegal mutations occur within hook declarations. + +```js flow-check +hook useIllegalMutation(values: Array) { + values[0] = 42; // Flow error: mutating argument to hook + // ... +} +``` + +### Preventing Conditional Hook Calls +[The Rules of Hooks](https://react.dev/reference/rules#rules-of-hooks) prohibit hooks +from being called conditionally. This is covered by [React's ESLint plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks), +but now Flow will check for these violations too. + +```js flow-check +hook useOnlineStatus(): boolean { + return true; +} + +component StatusBar(shouldShowOnlineStatus: boolean) { + if (shouldShowOnlineStatus) { + const onlineStatus = useOnlineStatus(); + } + + return null; +} +``` + +### Preventing Conflation of Hooks and Functions +The distinction between hooks and regular functions is reflected in the Flow type system. +Because of the different properties that hooks and functions must obey, it’s Flow error +to pass a value defined as a hook into a position that expects a function type, and +an error to pass a regular JavaScript function into a position that expects a hook. + +```js flow-check +import {useState, useEffect} from 'react'; + +hook useMultiplier(x: number): number { + const [y, setY] = useState(1); + useEffect(() => { setY(0) }) + return x * y; +} + +component Mapper(args: Array) { + const multArgs = args.map(useMultiplier); + + return multArgs; +} +``` + +In addition, Flow enforces that callees with hook-like names inside hooks and components +are indeed hooks. We also ensure that callees inside of regular function definitions +are never hooks. + +```js flow-check +hook useHook() { return null } + +function regularJavascript() { + const x = useHook(); // Flow error: cannot call a hook outside of a component or hook +} + +component Component() { + const renamedHook = useHook; + renamedHook(); // Flow error: cannot call a hook whose name does not begin with `use` + + return null; +} +``` diff --git a/_src/react/index.md b/_src/react/index.md new file mode 100644 index 00000000000..944f54521c9 --- /dev/null +++ b/_src/react/index.md @@ -0,0 +1,39 @@ +--- +title: Getting Started +slug: /react +description: Learn how to use Flow to effectively type common and advanced React patterns. +--- + +Developers will often use Flow and React together, so it is important that Flow +can effectively type both common and advanced React patterns. This guide will +teach you how to use Flow to create safer React applications. + +In this guide we will assume you know [the React basics](https://react.dev/learn) and focus on adding +types for patterns you are already familiar with. We will be using examples +based on `react-dom`, but all of these patterns work in other environments +like `react-native` as well. + +## Setup Flow with React {#toc-setup-flow-with-react} + +Flow and Babel work well together, so it doesn't take much to adopt Flow as a +React user who already uses Babel. If you need to setup Babel with Flow, you can +follow [this guide](../tools/babel/). + +## Check Out Component Syntax +Flow supports a dedicated syntax for writing React components and hooks that we recommend instead of using regular +function/class components. Ensure you are set up using our [most up-to-date instructions to +configure your toolchain](../install) and then take a look at the [Component Syntax](./component-syntax) +and [Hook Syntax](./hook-syntax) docs. + +## React Runtimes + +Flow supports the `@babel/plugin-transform-react-jsx` runtime options required +to use JSX without explicitly importing the React namespace. + +If you are using the new automatic runtime, use this configuration in your `.flowconfig` so +that Flow knows to auto-import `jsx`: + +```ini +[options] +react.runtime=automatic +``` diff --git a/_src/react/multiplatform.md b/_src/react/multiplatform.md new file mode 100644 index 00000000000..c07e94a8f73 --- /dev/null +++ b/_src/react/multiplatform.md @@ -0,0 +1,143 @@ +--- +title: Multi-platform Support for React Native +slug: /react/multiplatform +description: "Flow's support for multiple platforms inside a single React Native codebase" +--- + +:::caution +The feature is still experimental. Behaviors might change in the future. +::: + +## Benefits {#toc-benefits} + +React Native supports conditional bundling of files with [platform specific extensions](https://reactnative.dev/docs/platform-specific-code#platform-specific-extensions). For example, if you have different implementations of an Image component for iOS and Android, you can have an `Image.ios.js` file and `Image.android.js` file, and an import of Image can be resolved to either file based on the platform you are targeting. + +These platform specific files live under the same repository, but it would normally require two flowconfigs to check them like the following setup: + +```toml title=.flowconfig +; for ios +[ignore] +.*\.android\.js$ +[options] +module.file_ext=.js +module.file_ext=.ios.js +``` + +```toml title=.flowconfig.android +; for android +[ignore] +; Ignore other platform suffixes +.*\.ios\.js$ +[options] +module.file_ext=.js +module.file_ext=.android.js +``` + +Flow's optional React Native multi-platform support allows you to check your entire project with mixed platforms under a single Flow root, so that during the development of a module with both .ios and .android files, you no longer have to run both Flow servers and constantly switch between different servers to see type errors on different platforms. + +## Quick Start {#toc-quick-start} + +You can start by deleting the flowconfig for all other platforms, deleting all the platform specific configs in the only remaining flowconfig, and add the following new lines to the `options` section: + +``` +experimental.multi_platform=true +experimental.multi_platform.extensions=.ios +experimental.multi_platform.extensions=.android +``` + +For example, these are the required changes for the `.flowconfig` example above: + +```diff title=.flowconfig +[ignore] +- .*\.android\.js$ +[options] +module.file_ext=.js +- module.file_ext=.ios.js ++ experimental.multi_platform=true ++ experimental.multi_platform.extensions=.ios ++ experimental.multi_platform.extensions=.android +``` + +After enabling the new configurations, there will likely be new errors. The sections below explain the additional rules that Flow imposes to check a multiplatform React Native project. + +## Common Interface Files {#toc-common-interface-file} + +Suppose you have a file that imports the `Image` module, but `Image` module has different iOS and Android implementations as follows: + +```jsx title=MyReactNativeApp.js +import * as React from 'react'; +import Image from './Image'; + +; +; +``` + +```jsx title=Image.ios.js +import * as React from 'react'; + +type Props = { src: string, lazyLoading?: boolean }; + +export default function Image(props: Props): React.Node { /* ... */ } +``` + +```jsx title=Image.android.js +import * as React from 'react'; + +type Props = { src: string, lazyLoading: boolean }; + +export default class Image extends React.Components { + static defaultProps: { lazyLoading: boolean } = { lazyLoading: false }; + render(): React.Node { /* ... */ } +} +``` + +When you enabled multiplatform support, you will likely see that error that the `./Image` module cannot be resolved. To fix the error, you need to create a common interface file under the same directory: + +### Common Interface File in `.js.flow` {#toc-common-interface-file-in-js-flow} + +One option is to write a common interface file in `.js.flow`: + +With [Component Types](../component-types) +```jsx title=Image.js.flow +import * as React from 'react'; + +type Props = { src: string, lazyLoading?: boolean }; + +declare const Image: component(...Props); +export default Image; +``` + +With [React.AbstractComponent](../types#toc-react-abstractcomponent) +```jsx title=Image.js.flow +import * as React from 'react'; + +type Props = { src: string, lazyLoading?: boolean }; + +declare const Image: React.AbstractComponent; +export default Image; +``` + +Flow will ensure that the module types of both `Image.ios.js` and `./Image.android.js` are subtype of the module type of `./Image.js.flow`. Flow will also ensure that there exists an implementation for each platform you declared in your `.flowconfig`. + +### Common Interface File in `.js` {#toc-common-interface-file-in-js} + +Sometimes you might target desktop platforms in addition to iOS and Android, and you only have a special implementation for one platform, and all the other platforms will use the fallback implementation in a `.js` file. For example: + +```jsx title=Image.js +import * as React from 'react'; +import DefaultImage from 'react-native/Libraries/Image'; + +export default DefaultImage; +``` + +```jsx title=Image.ios.js +import * as React from 'react'; + +type Props = { src: string, lazyLoading: boolean }; + +export default function Image(props: Props): React.Node { + // Custom implementation to take advantage of some unique iOS capabilities +} +``` + +In this case, Flow will use the `.js` file as the common interface file, and check all other platform-specific implementation files' against the `.js` file. Since the `.js` file is already a fallback implementation, Flow will no longer require that platform-specific implementation files exist for all platforms. diff --git a/_src/react/refs.md b/_src/react/refs.md new file mode 100644 index 00000000000..900117750b7 --- /dev/null +++ b/_src/react/refs.md @@ -0,0 +1,54 @@ +--- +title: Ref Functions +slug: /react/refs +--- + +React allows you to grab the instance of an element or component with [refs](https://react.dev/learn/manipulating-the-dom-with-refs). + +## Refs in Functional Components {#toc-refs-in-functional-components} + +Inside a functional component, refs are accessed with the `useRef` hook: + +```js flow-check +import {useRef} from 'react'; +import * as React from 'react'; + +function MyComponent() { + const buttonRef = useRef(null); + buttonRef as {current: null | HTMLButtonElement}; // useRef wraps the ref value in an object + return ; +} +``` + +Note that `useRef` wraps the ref value in an object with a `current` property. This must be +reflected in the type of anything accepting the ref value. + +## Refs in Class Components {#toc-refs-in-class-components} + +Refs in class components are similar to function components. To create one, add a +property to your class and assign the result of `React.createRef` to it. + +```js flow-check +import * as React from 'react'; + +class MyComponent extends React.Component<{}> { + // The `null` here is important because you may not always have the instance. + buttonRef: {current: null | HTMLButtonElement}; + + constructor() { + super(); + this.buttonRef = React.createRef(); + } + + render(): React.Node { + return ; + } +} +``` + +One notable difference between `useRef` and `createRef` is that `createRef` does not accept +a default value. It will initialize the ref with the value `null`. This is because +DOM elements will not exist until the first render of `MyComponent` and so a `null` value +must be used. + +Again, note that the ref value is wrapped in an object with a `current` property. diff --git a/_src/react/render-types.md b/_src/react/render-types.md new file mode 100644 index 00000000000..092054da7f9 --- /dev/null +++ b/_src/react/render-types.md @@ -0,0 +1,301 @@ +--- +title: Render Types +slug: /react/render-types +--- +Some component libraries or design systems may want to restrict how components may be composed. +For example, a Menu should only ever render MenuItems as children. Render types are a built-in way to support +these constraints while still affording users rich flexibility in how they use those components. + +## Basic Behavior + +A component can declare what it renders using the renders keyword: + +```js flow-check +import * as React from 'react'; + +component Header(size: string, color: string) { return
} + +component LargeHeader(color: string) renders Header { + return
; // Ok! +} +``` + +When you declare that your component renders some specific element, you can return any component that eventually renders that component in its renders chain: + +```js flow-check +import * as React from 'react'; + +component Header(size: string, color: string) { return
} + +component LargeHeader(color: string) renders Header { + return
; +} + +component LargeBlueHeader() renders Header { + // You could also use `renders LargeHeader` above + return ; +} +``` + +Components can specify props that render specific elements: + +```js flow-check +import * as React from 'react'; + +component Header(size: string, color: string, message: string) { + return

{message}

; +} + +component Layout(header: renders Header) { + return ( +
+ {header} +
Hi
+
+ ); +} +``` + +And you can pass an element of either Header, or an element of a component that renders `Header`, to that prop: + +```js +} />; +``` + +You cannot pass a component that does not render a header to a render type expecting a header: + +```js flow-check +import * as React from 'react'; + +component Footer() { + return