Commit Graph
1175 Commits
Author SHA1 Message Date
Sathya Gunasekaran da61d47e87 Add flag 2023-08-31 11:15:41 +01:00
Sathya Gunasekaran c456662318 [tsconfig] Specify module resolution to be nodenext 2023-08-31 11:15:37 +01:00
Joe Savona 41c23e87c5 Disable compilation of "Components" inside class methods
Minimal repro extracted from our internal codebase. Our inference mode sees that 
this arrow function is component-like and attempts to compile it, which then 
fails because the function accesses `this`  which we bailout on.
2023-08-31 23:16:27 +01:00
Mofei Zhang e36c8cb395 [patch] tsconfig: unbreak vercel build on main 2023-08-30 10:36:46 -07:00
Mofei Zhang 7930a213d2 [sprout][QoL] remove isComponent annotation
--- 

quality of life improvement as this seemed to be confusing (oops, and thanks for 
the feedback!) 

We don't *really* need static annotations for whether a function returns jsx 
(e.g. should be rendered as a React element) or not (e.g. should be wrapped in a 
wrapper component. This PR adds check for returned jsx objects at runtime 

--- 

Tested by running diffing the output of `yarn sprout --verbose` between this PR 
and base.
2023-08-29 17:00:55 -07:00
Mofei Zhang c7b5f2fde0 [yarn] Update typescript node16 -> node18 2023-08-29 16:51:32 -07:00
Joe Savona 8790324562 Workaround serious Babel bug
I ran into the same issue that @poteto and @gsathya (and probably @mofeiZ) have 
run into: "Duplicate declaration of '$'" caused by Babel visiting a function 
twice despite our calling `skip()`. This PR keeps a set of nodes that we have 
already visited to avoid visiting them again, as a workaround for skip not 
working. 

# Test Plan 

Synced to www and confirmed that the previous bug no longer reproduces, and the 
compiled output looks sane.
2023-08-30 13:21:51 +01:00
Joe Savona 4f55a66d5c Compile args to forwardRef/memo
Completes a todo (ie fixes a silly mistake) from a PR earlier in the stack, so 
we now correctly recognize and compile arguments to `React.forwardRef()` and 
`React.memo()`.
2023-08-29 22:09:42 +01:00
Joe Savona ddc9f8030e Enable compilation of FunctionExpressions
Thanks to the previous diff we can trivially support components or hooks defined 
as a FunctionExpression.
2023-08-29 22:09:41 +01:00
Joe Savona cee08ba750 [rfc] Dont convert ArrowFunction to FunctionDecl
This PR changes the way we compile ArrowFunctionExpression to allow compiling 
more cases, such as within `React.forwardRef()`. We no longer convert arrow 
functions to function declarations. Instead, CodeGenerator emits a generic 
`CodegenFunction` type, and `Program.ts` is responsible for converting that to 
the appropriate type. The rule is basically: 

* Retain the original node type by default. Function declaration in, function 
declaration out. Arrow function in, arrow function out. 

* When gating is enabled, we emit a ConditionalExpression instead of creating a 
temporary variable. If the original (and hence compiled) functions are function 
declarations, we force them into FunctionExpressions only here, since we need an 
expression for each branch of the conditional. Then the rules are: 

* If this is a `export function Foo` ie a named export, replace it with a 
variable declaration with the conditional expression as the initializer, and the 
function name as the variable name. 

* Else, just replace the original function node with the conditional. This works 
for all other cases. 

I'm open to feedback but this seems like a pretty robust approach and will allow 
us to support a lot of real-world cases that we didn't yet, so i think we need 
_something_ in this direction.
2023-08-29 22:09:41 +01:00
Joe Savona 7d11445d8e Revamp compilation modes
We currently have multiple flags for targeting which functions to compile, but 
they are actually mutually exclusive. This PR consolidates to a single 
`compilationMode: 'annotation' | 'infer' | 'all'` flag: 

* Annotation compiles only functions that explicitly opt-in with "use forget" 

* Infer compiles explicitly opted-in functions (via "use forget") as well as any 
known/inferred components or hooks: 

* Component declarations 

* Component or hook-like functions (same rules as the ESLint plugin but with an 
extra check for whether it uses JSX or calls a hook) 

* All compiles all top-level functions. We should get rid of this in a follow-up 
and make tests use infer mode by default, and add explicit opt-ins where 
necessary. 

In all modes, "use no forget" always takes precedence and can be used to 
opt-out. The default mode is now "infer".
2023-08-29 22:09:40 +01:00
Joe Savona 3653ae2de3 Option to infer React functions to compile
Adds a new option to infer which functions to compile, based on React's ESLint 
rule. The main difference is that in addition to checking the function name we 
also check that it creates JSX or calls a hook. This should cover a significant 
majority of components and reduce the chance of accidentally targeting 
non-components, but it will leave some false negatives. 

Note that some cases that the ESLint plugin infers as React functions don't work 
yet: we don't compile FunctionExpressions, only ArrowFunctionExpressions, and 
the way we handle ArrowFunctionExpression doesn't work with things like 
forwardRef or variable declarations. We'll need more updates to fully handle all 
these cases, which I'll do later in the stack.
2023-08-29 22:09:40 +01:00
Mofei Zhang 6dbc48075b [tests] bug repros 2023-08-26 14:00:43 -07:00
Mofei Zhang bcbcd37eb0 [sprout] Allow forwardRef and memo components 2023-08-25 16:54:21 -07:00
Sathya Gunasekaran 56b0f971d0 [sprout] Port alias-capture-in-method-receiver-and-mutate test 2023-08-23 17:44:26 +01:00
Sathya Gunasekaran aceacce5e2 [sprout] Add a mutate utility function 2023-08-23 17:37:54 +01:00
Lauren Tan ce82a56fa6 [ci] Fix output of parser benchmark
yarn adds some other output, so just run the script directly
2023-08-24 10:14:28 -04:00
Lauren Tan 80efd75b8e [ci] Add benchmark comparison
Updates the parser-benchmark script to use a benchmarking framework, and 

also brings in yargs to make it easier to handle parsing arguments. 

Non-CI usage: 

``` 

$ cd bench/parser-benchmark 

$ yarn bench --help 

Options: 

--help        Show help                                              [boolean] 

--ci                                                [boolean] [default: false] 

--sampleSize                                             [number] [default: 1] 

$ yarn bench --sampleSize=3 

[ISOBENCH ENDED] Parser Benchmark (3 times) 

OXC           - 32 op/s. 3 samples in 9518 ms. 5.288x (BEST) 

SWC           - 20 op/s. 3 samples in 9487 ms. 3.256x 

HermesParser  - 6 op/s. 3 samples in 9124 ms. 1.000x (WORST) 

Forget (Rust) - 15 op/s. 3 samples in 9749 ms. 2.499x 

``` 

In CI this outputs JSON instead of logging to stdout. The results are 

stored in github's action cache and used as a point of comparison when 

new commits are pushed to main. The comparison should be shown on 

workflow [summary 
pages](https://github.com/facebook/react-forget/actions/runs/5956421081), but 
nothing will be populated until we merge this PR. 

We intentionally only run this workflow on main as any run would 

override the last cached result, so a PR with multiple pushes would then 

start having comparisons with itself.
2023-08-23 17:58:23 -04:00
Lauren Tan aeae3ba0a7 [ci] Add rust-cache 2023-08-23 17:16:40 -04:00
Lauren Tan fc506b5fed [ci] Run parser benchmark on ci
Example: 

![Screenshot 2023-08-23 at 11 22 55 
AM](https://github.com/facebook/react-forget/assets/1390709/158fa9f1-32f6-444e-8ea8-75f4f1475ee0)
2023-08-23 17:16:40 -04:00
Lauren Tan 3a9be52f51 Update test262 2023-08-22 15:07:46 -04:00
lauren a0dc166991 [rfc] Remove top level forget directory
Sorry about the thrash in advance! This removes the top level `forget` directory 
which adds unnecessary nesting to our repo 

Hopefully everything still works
2023-08-22 15:04:54 -04:00
Joe Savona a13df48d4e [rust] Serialize range as array
ESTree expects the `range` value to be an array of `[start, end]`, we support 
deserializing from that format but serialized as an object of `{start,end}`, now 
we serialize to the array form.
2023-08-21 21:55:08 -07:00
Joe Savona 9c13e4413c [rust] Expose parse function to napi
Updates the `forget_napi` crate to call the parser and semantic analyzer and 
convert their values to JS.
2023-08-21 16:04:03 -07:00
Joe Savona 7fc7488ac8 [rust] Scaffolding for napi-backed npm package
Scaffolds out a `forget_napi` crate following the guide for https://napi.rs/. 
This crate will be the public JS api for interacting with Forget.
2023-08-21 14:51:10 -07:00
Mofei Zhang 1c56843518 [tests] Allow .ts, .tsx fixture files 2023-08-22 12:54:01 -04:00
Lauren Tan 6be03b681e [ci] Split out babel-plugin-react-forget jest to its own job 2023-08-18 17:06:52 -04:00
Lauren Tan e426cb553d [ci] Split out babel-plugin-react-forget prettier/eslint to its own job 2023-08-18 15:22:09 -04:00
Joe Savona bcd692fca4 Promote enableOptimizeFunctionExpressions to default/only option
We’ve had this feature turned on internally for a while with only a couple minor 
bugs and no fundamental issues. It’s a necessary change for simplifying function 
expression dependencies and context variables, so let’s remove the feature flag 
and fix forward for any issues.
2023-08-18 12:06:16 -07:00
Lauren Tan c4d000ce49 [ci] Rename TS ci to be consistent with other workflows 2023-08-18 14:16:21 -04:00
Lauren Tan 9e332ee05a [ci] Run yarn workspace tests in parallel 2023-08-18 14:10:41 -04:00
Joe Savona a905a02901 [rust] Add SAFETY comments for unsafe blocks
Adds SAFETY comments for the two instances of unsafe blocks that are *not* just 
FFI. It seems like overkill to annotate all the FFI calls so i'm skipping that, 
let me know if anyone has strong preferences otherwise.
2023-08-17 15:23:46 -07:00
Joe Savona d5a8e80157 [rust][ci] Run cargo check/fmt
Formats the codebase, fixes some clippy lints, and updates CI to check that code 
is formatted.
2023-08-17 12:59:29 -07:00
Joe Savona 5b6370234c Update readme to mention Rust CI config
This is mostly to test that the new configuration skips the Rust CI step if 
there are no rust changes - yay, that worked! But also worth mentioning in the 
readme so lets land.
2023-08-17 10:15:59 -07:00
Joe Savona 514916d9ce [rust][ci] Only run Rust actions when relevant files change 2023-08-17 10:10:44 -07:00
Joe Savona 3ce4116930 [rust][sema] Only create one scope per function/class
Fixes up function and class handling to not create nested scopes in some cases. 
Doesn't affect name resolution otherwise.
2023-08-17 10:03:00 -07:00
Joe Savona 543d27c2f0 [rust][sema] Support classes
Adds name resolution support for class declarations and expressions. Mostly this 
involves _not_ visiting some `Identifier` nodes that don't actually represent 
variables. For example, method names don't introduce new variables (but if they 
are computed, they may _refer_ to variables).
2023-08-17 10:02:56 -07:00
Joe Savona dafa468765 [rust][sema] Allow defining/resolving known globals
Adds an option to pass a list of known globals into the semantic analyzer so 
that references to globals can be checked. As a follow-up we'll need to 
distinguish between different types of semantic analysis errors, so that callers 
which don't want to validate globals can ignore "unknown variable" reference 
errors while still handling definite errors such as duplicate declarations.
2023-08-16 16:44:40 -07:00
Joe Savona bfb7d2bfbd [rust][sema] Handle var/let/const redeclaration
JavaScript has ~sane~ fun rules around where variables can be redeclared or not, 
and which kinds of variables this applies to. Actually the rules are pretty 
straightforward: 

* `var` can be redeclared any number of times. 

* In strict mode, other declarations cannot be redeclared within the same scope. 
This implies that a `var` declaration cannot conflict with these other forms, 
which must take into account hoisting. So you can't have a `var a; let a` in the 
same scope, but you also can't have a `let a` at a scope and then a `var a` 
which will hoist to that same scope.
2023-08-16 12:26:04 -07:00
Joe Savona 796080f4d1 [rust][sema] Statically detect some TDZ violations
The [temporal dead 
zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz), 
often abbreviated TDZ, is the period between the start of its declaring block 
and the line that contains the let/const/class declaration. Not all instances of 
TDZ can be detected statically, because whether or not a TDZ error will occur at 
runtime is a property of which control flow path is taken and whether some other 
code has initialized the value. However, a subset of cases can be detected, and 
that's what we implement here. 

When we encounter a variable reference we record the next declaration id at that 
point in time. Then when resolving references after visiting the program, we can 
check: did the reference end up referring to a let/const/class binding whose id 
is equal or greater to that "next declaration"? If so, it means the reference 
refers to a variable that is provably declared later and is a known TDZ 
violation. The catch is that when resolving references, we reset the "next 
declaration" limit value when we bubble up out of a function scope. That's 
because references to let/const within a function may occur after the 
declaration, and we can't statically validate them.
2023-08-15 17:20:34 -07:00
Joe Savona 2edf66ecdf [rust][sema] Resolve all references after traversing program
Previously we attempted to resolve each reference at the close of its defining 
scope, and if it couldn't be resolved yet we bubbled the unresolved reference up 
to the parent scope. That approach isn't ideal for two reasons: 

* First, it's inefficient since we may have to make multiple attempts to resolve 
the same reference. 

* Second, it's incorrect. There can be cases where we think we can resolve a 
reference to a value defined in an outer scope, but there is a hoisted 
declaration from an intermediate scope that we haven't seen yet. 

The safest and most optimal thing is to just queue all references and resolve 
them at the end.
2023-08-15 12:33:45 -07:00
Joe Savona 34914796b2 [rust][sema] Set root scope kind based on program source type
Rather than always create a Global scope and then immediately add a Module child 
node, we now set the kind of the root scope to global (for scripts) or module 
(for modules) based on the program node.
2023-08-15 12:24:38 -07:00
Joe Savona 875d7175ac [rust] cargo fix 2023-08-15 09:31:49 -07:00
Joe Savona b133b6c0b4 [rust][sema] Visit imports as declarations
Teaches the semantic analyzer about import statements. We now treat imports as 
declarations, so that subsequent references to the imported value can be 
resolved.
2023-08-15 09:30:43 -07:00
Joe Savona 15e0ecaa8f [rust] Convert Hermes source locations to ESTree SourceRange
Teaches the hermes->estree conversion to convert source ranges. This means our 
diagnostics now point to the actual source of the error: 

<img width="903" alt="Screenshot 2023-08-14 at 5 39 50 PM" 
src="https://github.com/facebook/react-forget/assets/6425824/2960d114-0cc6-4531-9e7d-00ff3bfb1eb3"> 

Of course I still need to teach our semantic analysis about imports, but that's 
a separate issue!
2023-08-14 17:36:58 -07:00
Joe Savona f2a1a6a6e4 [rust][sema] Test for function hoisting 2023-08-14 17:36:54 -07:00
Mofei Zhang 1ad0460693 [sprout] Add shared runtime, improve error reporting
--- 

I added ~20 more tests to Sprout to get more of a feel for what the test 
framework would need to support all fixtures. I'm relatively confident that the 
approach outlined in [the original workplace 
post](https://fburl.com/workplace/ftu8woch) works to migrate almost all existing 
fixture tests to sprout (TLDR: use shared functions when possible, otherwise 
write helper functions in-file). 

Add `shared-runtime` file to reduce the amount of overhead needed to set up a 
fixture test. 

- All generalizable functions and values should be added here (e.g. 
`shallowCopy`, `deepCopy`, `sum`). 

- Editor integration is set up through a custom tsconfig, so importing from 
`shared-runtime` should just work (with hover annotations, click-to-definition, 
etc). 

<img width="682" alt="Screenshot 2023-08-17 at 11 09 47 AM" 
src="https://github.com/facebook/react-forget/assets/34200447/78c3dff9-ba10-4057-b3f6-2fa842d19b1d"> 

--- 

Tested new test fixtures added to sprout by adding them to `testfilter.txt` and 
running with `--verbose --filter`. 

Note that there are no unexpected exceptions, and all logs + returned values 
match. 

``` 

feifei0@feifei0-mbp babel-plugin-react-forget % yarn sprout:build && yarn sprout 
--verbose --filter 

yarn run v1.22.19 

$ yarn workspace sprout run build 

$ rimraf dist && tsc --build 

  Done in 2.07s. 

yarn run v1.22.19 

$ node ../sprout/dist/main.js --verbose --filter 

PASS  console-readonly 

ok {"a":1,"b":2} [ 

"{ a: 1, b: 2 }", 

"{ a: 1, b: 2 }", 

"{ a: 1, b: 2 }", 

"{ a: 1, b: 2 }", 

"{ a: 1, b: 2 }" 

] 

PASS  constant-propagate-global-phis-constant 

ok <div>global string 0</div> 

PASS  constant-propagate-global-phis 

ok <div>global string 1</div> 

PASS  dce-loop 

ok 10 

PASS  destructure-capture-global 

ok {"a":"value 1","someGlobal":{}} 

PASS  destructuring-mixed-scope-and-local-variables-with-default 

ok {"media":null,"allUrls":["url1","url2","url3"],"onClick":"[[ function 
params=1 ]]"} 

PASS  holey-array-expr 

ok [null,"global string 0",{"a":1,"b":2}] 

PASS  infer-global-object 

ok {"primitiveVal1":2,"primitiveVal2":null,"primitiveVal3":null} 

PASS  infer-phi-primitive 

ok 1 

PASS  infer-types-through-type-cast.flow 

ok 4 

PASS  issue933-disjoint-set-infinite-loop 

ok [2] 

PASS  jsx-tag-evaluation-order-non-global 

ok <div>StaticText1<div>StaticText2</div></div> 

PASS  jsx-tag-evaluation-order 

ok <div>StaticText1string value 1<div>StaticText2</div></div> 

PASS  method-call 

ok 4 

PASS  mutable-lifetime-loops 

ok {"a":{"value":6},"b":{"value":5},"c":{"value":4},"d":{"value":6}} 

PASS  mutable-lifetime-with-aliasing 

ok {"b":[{}],"value":[{"c":{},"value":"[[ cyclic ref *0 ]]"},null]} 

PASS  update-expression-in-sequence 

ok [4,2,3,4] 

PASS  update-expression-on-function-parameter 

ok [4,1,4,2,4,3,1,4,4] 

PASS  update-expression 

ok {"x":1,"y":1,"z":2} 

PASS  useMemo-multiple-if-else 

ok 2 

20 Tests, 20 Passed, 0 Failed 

  Done in 4.11s. 

```
2023-08-17 13:28:08 -04:00
Mofei Zhang 2b4a1d86ff [pipeline] Recognize "use no forget" directive
--- 

Add "use no forget", not gated behind a feature flag for simplicity ("use no 
forget" should always tell Forget to not compile a component, right?)
2023-08-17 11:03:20 -04:00
Mofei Zhang 28688ade9f [sprout] Add copyright headers etc 2023-08-17 11:03:18 -04:00
Lauren Tan 74aec8aa35 [ez] Add concurrently labels to yarn dev 2023-08-17 12:01:42 -04:00