Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96d9825588 | ||
|
|
0fba073934 |
@@ -8,7 +8,7 @@
|
||||
import {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {Logger, ProgramContext} from '.';
|
||||
import {Logger, ProgramContext, SingleLineSuppressionRange} from '.';
|
||||
import {
|
||||
HIRFunction,
|
||||
ReactiveFunction,
|
||||
@@ -121,6 +121,7 @@ function run(
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
suppressions: Array<SingleLineSuppressionRange>,
|
||||
): CodegenFunction {
|
||||
const contextIdentifiers = findContextIdentifiers(func);
|
||||
const env = new Environment(
|
||||
@@ -134,6 +135,7 @@ function run(
|
||||
filename,
|
||||
code,
|
||||
programContext,
|
||||
suppressions,
|
||||
);
|
||||
env.logger?.debugLogIRs?.({
|
||||
kind: 'debug',
|
||||
@@ -567,6 +569,7 @@ export function compileFn(
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
singleLineSuppressions: Array<SingleLineSuppressionRange>,
|
||||
): CodegenFunction {
|
||||
return run(
|
||||
func,
|
||||
@@ -577,5 +580,6 @@ export function compileFn(
|
||||
logger,
|
||||
filename,
|
||||
code,
|
||||
singleLineSuppressions,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ import {
|
||||
import {CompilerReactTarget, PluginOptions} from './Options';
|
||||
import {compileFn} from './Pipeline';
|
||||
import {
|
||||
filterSuppressionsThatAffectFunction,
|
||||
filterSuppressionsThatAffectNode,
|
||||
findProgramSuppressions,
|
||||
SingleLineSuppressionRange,
|
||||
suppressionsToCompilerError,
|
||||
} from './Suppression';
|
||||
import {GeneratedSource} from '../HIR';
|
||||
@@ -691,11 +692,17 @@ function tryCompileFunction(
|
||||
* Program node itself. We need to figure out whether an eslint suppression range
|
||||
* applies to this function first.
|
||||
*/
|
||||
const suppressionsInFunction = filterSuppressionsThatAffectFunction(
|
||||
const suppressionsInFunction = filterSuppressionsThatAffectNode(
|
||||
programContext.suppressions,
|
||||
fn,
|
||||
);
|
||||
if (suppressionsInFunction.length > 0) {
|
||||
const singleLineSuppressions = suppressionsInFunction.filter(
|
||||
s => s.kind === 'single-line',
|
||||
) as Array<SingleLineSuppressionRange>;
|
||||
const multiLineSuppressions = suppressionsInFunction.filter(
|
||||
s => s.kind === 'multi-line',
|
||||
);
|
||||
if (multiLineSuppressions.length > 0) {
|
||||
return {
|
||||
kind: 'error',
|
||||
error: suppressionsToCompilerError(suppressionsInFunction),
|
||||
@@ -714,6 +721,7 @@ function tryCompileFunction(
|
||||
programContext.opts.logger,
|
||||
programContext.filename,
|
||||
programContext.code,
|
||||
singleLineSuppressions,
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
@@ -752,6 +760,7 @@ function retryCompileFunction(
|
||||
programContext.opts.logger,
|
||||
programContext.filename,
|
||||
programContext.code,
|
||||
[], // ignore suppressions in the retry pipeline
|
||||
);
|
||||
|
||||
if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) {
|
||||
|
||||
@@ -25,11 +25,23 @@ import {GeneratedSource} from '../HIR';
|
||||
* The enable comment can be missing in the case where only a disable block is present, ie the rest
|
||||
* of the file has potential React violations.
|
||||
*/
|
||||
export type SuppressionRange = {
|
||||
disableComment: t.Comment;
|
||||
enableComment: t.Comment | null;
|
||||
source: SuppressionSource;
|
||||
};
|
||||
export type SuppressionRange =
|
||||
| {
|
||||
kind: 'single-line';
|
||||
source: SuppressionSource;
|
||||
comment: t.Comment;
|
||||
}
|
||||
| {
|
||||
kind: 'multi-line';
|
||||
source: SuppressionSource;
|
||||
disableComment: t.Comment;
|
||||
enableComment: t.Comment | null;
|
||||
};
|
||||
|
||||
export type SingleLineSuppressionRange = Extract<
|
||||
SuppressionRange,
|
||||
{kind: 'single-line'}
|
||||
>;
|
||||
|
||||
type SuppressionSource = 'Eslint' | 'Flow';
|
||||
|
||||
@@ -38,15 +50,23 @@ type SuppressionSource = 'Eslint' | 'Flow';
|
||||
* 1. The suppression is within the function's body; or
|
||||
* 2. The suppression wraps the function
|
||||
*/
|
||||
export function filterSuppressionsThatAffectFunction(
|
||||
suppressionRanges: Array<SuppressionRange>,
|
||||
fn: NodePath<t.Function>,
|
||||
): Array<SuppressionRange> {
|
||||
const suppressionsInScope: Array<SuppressionRange> = [];
|
||||
const fnNode = fn.node;
|
||||
export function filterSuppressionsThatAffectNode<T extends SuppressionRange>(
|
||||
suppressionRanges: Array<T>,
|
||||
node: NodePath,
|
||||
): Array<T> {
|
||||
const suppressionsInScope: Array<T> = [];
|
||||
const fnNode = node.node;
|
||||
for (const suppressionRange of suppressionRanges) {
|
||||
const enableComment =
|
||||
suppressionRange.kind === 'single-line'
|
||||
? suppressionRange.comment
|
||||
: suppressionRange.enableComment;
|
||||
const disableComment =
|
||||
suppressionRange.kind === 'single-line'
|
||||
? suppressionRange.comment
|
||||
: suppressionRange.disableComment;
|
||||
if (
|
||||
suppressionRange.disableComment.start == null ||
|
||||
disableComment.start == null ||
|
||||
fnNode.start == null ||
|
||||
fnNode.end == null
|
||||
) {
|
||||
@@ -54,22 +74,20 @@ export function filterSuppressionsThatAffectFunction(
|
||||
}
|
||||
// The suppression is within the function
|
||||
if (
|
||||
suppressionRange.disableComment.start > fnNode.start &&
|
||||
disableComment.start > fnNode.start &&
|
||||
// If there is no matching enable, the rest of the file has potential violations
|
||||
(suppressionRange.enableComment === null ||
|
||||
(suppressionRange.enableComment.end != null &&
|
||||
suppressionRange.enableComment.end < fnNode.end))
|
||||
(enableComment === null ||
|
||||
(enableComment.end != null && enableComment.end < fnNode.end))
|
||||
) {
|
||||
suppressionsInScope.push(suppressionRange);
|
||||
}
|
||||
|
||||
// The suppression wraps the function
|
||||
if (
|
||||
suppressionRange.disableComment.start < fnNode.start &&
|
||||
disableComment.start < fnNode.start &&
|
||||
// If there is no matching enable, the rest of the file has potential violations
|
||||
(suppressionRange.enableComment === null ||
|
||||
(suppressionRange.enableComment.end != null &&
|
||||
suppressionRange.enableComment.end > fnNode.end))
|
||||
(enableComment === null ||
|
||||
(enableComment.end != null && enableComment.end > fnNode.end))
|
||||
) {
|
||||
suppressionsInScope.push(suppressionRange);
|
||||
}
|
||||
@@ -83,9 +101,7 @@ export function findProgramSuppressions(
|
||||
flowSuppressions: boolean,
|
||||
): Array<SuppressionRange> {
|
||||
const suppressionRanges: Array<SuppressionRange> = [];
|
||||
let disableComment: t.Comment | null = null;
|
||||
let enableComment: t.Comment | null = null;
|
||||
let source: SuppressionSource | null = null;
|
||||
let suppression: SuppressionRange | null = null;
|
||||
|
||||
const rulePattern = `(${ruleNames.join('|')})`;
|
||||
const disableNextLinePattern = new RegExp(
|
||||
@@ -107,42 +123,49 @@ export function findProgramSuppressions(
|
||||
* If we're already within a CommentBlock, we should not restart the range prematurely for a
|
||||
* CommentLine within the block.
|
||||
*/
|
||||
disableComment == null &&
|
||||
suppression == null &&
|
||||
disableNextLinePattern.test(comment.value)
|
||||
) {
|
||||
disableComment = comment;
|
||||
enableComment = comment;
|
||||
source = 'Eslint';
|
||||
suppression = {
|
||||
kind: 'single-line',
|
||||
comment,
|
||||
source: 'Eslint',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
flowSuppressions &&
|
||||
disableComment == null &&
|
||||
suppression == null &&
|
||||
flowSuppressionPattern.test(comment.value)
|
||||
) {
|
||||
disableComment = comment;
|
||||
enableComment = comment;
|
||||
source = 'Flow';
|
||||
suppression = {
|
||||
kind: 'single-line',
|
||||
comment,
|
||||
source: 'Flow',
|
||||
};
|
||||
}
|
||||
|
||||
if (disablePattern.test(comment.value)) {
|
||||
disableComment = comment;
|
||||
source = 'Eslint';
|
||||
suppression = {
|
||||
kind: 'multi-line',
|
||||
disableComment: comment,
|
||||
enableComment: null,
|
||||
source: 'Eslint',
|
||||
};
|
||||
}
|
||||
|
||||
if (enablePattern.test(comment.value) && source === 'Eslint') {
|
||||
enableComment = comment;
|
||||
if (
|
||||
enablePattern.test(comment.value) &&
|
||||
suppression != null &&
|
||||
suppression.kind === 'multi-line' &&
|
||||
suppression.source === 'Eslint'
|
||||
) {
|
||||
suppression.enableComment = comment;
|
||||
}
|
||||
|
||||
if (disableComment != null && source != null) {
|
||||
suppressionRanges.push({
|
||||
disableComment: disableComment,
|
||||
enableComment: enableComment,
|
||||
source,
|
||||
});
|
||||
disableComment = null;
|
||||
enableComment = null;
|
||||
source = null;
|
||||
if (suppression != null) {
|
||||
suppressionRanges.push(suppression);
|
||||
suppression = null;
|
||||
}
|
||||
}
|
||||
return suppressionRanges;
|
||||
@@ -157,10 +180,11 @@ export function suppressionsToCompilerError(
|
||||
});
|
||||
const error = new CompilerError();
|
||||
for (const suppressionRange of suppressionRanges) {
|
||||
if (
|
||||
suppressionRange.disableComment.start == null ||
|
||||
suppressionRange.disableComment.end == null
|
||||
) {
|
||||
const disableComment =
|
||||
suppressionRange.kind === 'single-line'
|
||||
? suppressionRange.comment
|
||||
: suppressionRange.disableComment;
|
||||
if (disableComment.start == null || disableComment.end == null) {
|
||||
continue;
|
||||
}
|
||||
let reason, suggestion;
|
||||
@@ -185,22 +209,19 @@ export function suppressionsToCompilerError(
|
||||
error.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
reason: reason,
|
||||
description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``,
|
||||
description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${disableComment.value.trim()}\``,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
category: ErrorCategory.Suppression,
|
||||
suggestions: [
|
||||
{
|
||||
description: suggestion,
|
||||
range: [
|
||||
suppressionRange.disableComment.start,
|
||||
suppressionRange.disableComment.end,
|
||||
],
|
||||
range: [disableComment.start, disableComment.end],
|
||||
op: CompilerSuggestionOperation.Remove,
|
||||
},
|
||||
],
|
||||
}).withDetail({
|
||||
kind: 'error',
|
||||
loc: suppressionRange.disableComment.loc ?? null,
|
||||
loc: disableComment.loc ?? null,
|
||||
message: 'Found React rule suppression',
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -51,6 +51,11 @@ import {
|
||||
} from './HIR';
|
||||
import HIRBuilder, {Bindings, createTemporaryPlace} from './HIRBuilder';
|
||||
import {BuiltInArrayId} from './ObjectShape';
|
||||
import {
|
||||
filterSuppressionsThatAffectNode,
|
||||
SingleLineSuppressionRange,
|
||||
suppressionsToCompilerError,
|
||||
} from '../Entrypoint';
|
||||
|
||||
/*
|
||||
* *******************************************************************************************
|
||||
@@ -237,6 +242,42 @@ export function lower(
|
||||
null,
|
||||
);
|
||||
|
||||
if (bindings == null) {
|
||||
/**
|
||||
* Any single-line suppressions which didn't get captured by a call expression
|
||||
* are thrown as errors from the outermost function being compiled. This is to
|
||||
* allow suppressions within function expressions that are passed to useEffect,
|
||||
* eg
|
||||
*
|
||||
* ```
|
||||
* useEffect(() => {
|
||||
* console.log(foo);
|
||||
* // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
* }, []);
|
||||
* ```
|
||||
*
|
||||
* Where we can't throw an error when exiting the function expression, but rather
|
||||
* want that suppression to bubble up to the useEffect() call node.
|
||||
*
|
||||
* Whereas the following should error since it's at the top-level
|
||||
*
|
||||
* ```
|
||||
* function Component() {
|
||||
* const f = () => {
|
||||
* // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const suppressions = filterSuppressionsThatAffectNode(
|
||||
env.suppressions,
|
||||
func,
|
||||
);
|
||||
if (suppressions.length !== 0) {
|
||||
throw suppressionsToCompilerError(suppressions);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok({
|
||||
id,
|
||||
params,
|
||||
@@ -1766,21 +1807,27 @@ function lowerExpression(
|
||||
const memberExpr = lowerMemberExpression(builder, calleePath);
|
||||
const propertyPlace = lowerValueToTemporary(builder, memberExpr.value);
|
||||
const args = lowerArguments(builder, expr.get('arguments'));
|
||||
const suppressions = consumeSuppressionOnNode(builder, expr);
|
||||
|
||||
return {
|
||||
kind: 'MethodCall',
|
||||
receiver: memberExpr.object,
|
||||
property: {...propertyPlace},
|
||||
args,
|
||||
loc: exprLoc,
|
||||
suppressions,
|
||||
};
|
||||
} else {
|
||||
const callee = lowerExpressionToTemporary(builder, calleePath);
|
||||
const args = lowerArguments(builder, expr.get('arguments'));
|
||||
const suppressions = consumeSuppressionOnNode(builder, expr);
|
||||
|
||||
return {
|
||||
kind: 'CallExpression',
|
||||
callee,
|
||||
args,
|
||||
loc: exprLoc,
|
||||
suppressions,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2956,6 +3003,7 @@ function lowerOptionalCallExpression(
|
||||
builder.enterReserved(consequent, () => {
|
||||
const args = lowerArguments(builder, expr.get('arguments'));
|
||||
const temp = buildTemporaryPlace(builder, loc);
|
||||
const suppressions = consumeSuppressionOnNode(builder, expr);
|
||||
if (callee.kind === 'CallExpression') {
|
||||
builder.push({
|
||||
id: makeInstructionId(0),
|
||||
@@ -2965,6 +3013,7 @@ function lowerOptionalCallExpression(
|
||||
callee: {...callee.callee},
|
||||
args,
|
||||
loc,
|
||||
suppressions,
|
||||
},
|
||||
effects: null,
|
||||
loc,
|
||||
@@ -2979,6 +3028,7 @@ function lowerOptionalCallExpression(
|
||||
property: {...callee.property},
|
||||
args,
|
||||
loc,
|
||||
suppressions,
|
||||
},
|
||||
effects: null,
|
||||
loc,
|
||||
@@ -4477,3 +4527,23 @@ export function lowerType(node: t.FlowType | t.TSType): Type {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the (single-line) suppression comments from the environment that are scoped
|
||||
* to within the given `node`, removing them from the environment's suppressions list.
|
||||
*
|
||||
* By calling this function depth-first, we can associate suppressions with the innermost
|
||||
* call expression that they effect. Unconsumed suppressions are thrown at the parent
|
||||
* function boundary.
|
||||
*/
|
||||
function consumeSuppressionOnNode(
|
||||
builder: HIRBuilder,
|
||||
node: NodePath,
|
||||
): Array<SingleLineSuppressionRange> {
|
||||
const env = builder.environment;
|
||||
const suppressions = filterSuppressionsThatAffectNode(env.suppressions, node);
|
||||
env.suppressions = env.suppressions.filter(
|
||||
s => suppressions.indexOf(s) === -1,
|
||||
);
|
||||
return suppressions;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@ import * as t from '@babel/types';
|
||||
import {ZodError, z} from 'zod';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Logger, ProgramContext} from '../Entrypoint';
|
||||
import {
|
||||
Logger,
|
||||
ProgramContext,
|
||||
SingleLineSuppressionRange,
|
||||
} from '../Entrypoint';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {
|
||||
DEFAULT_GLOBALS,
|
||||
@@ -702,6 +706,7 @@ export class Environment {
|
||||
hasFireRewrite: boolean;
|
||||
hasInferredEffect: boolean;
|
||||
inferredEffectLocations: Set<SourceLocation> = new Set();
|
||||
suppressions: Array<SingleLineSuppressionRange>;
|
||||
|
||||
#contextIdentifiers: Set<t.Identifier>;
|
||||
#hoistedIdentifiers: Set<t.Identifier>;
|
||||
@@ -720,6 +725,7 @@ export class Environment {
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
programContext: ProgramContext,
|
||||
suppressions: Array<SingleLineSuppressionRange>,
|
||||
) {
|
||||
this.#scope = scope;
|
||||
this.fnType = fnType;
|
||||
@@ -733,6 +739,7 @@ export class Environment {
|
||||
this.#globals = new Map(DEFAULT_GLOBALS);
|
||||
this.hasFireRewrite = false;
|
||||
this.hasInferredEffect = false;
|
||||
this.suppressions = suppressions;
|
||||
|
||||
if (
|
||||
config.disableMemoizationForDebugging &&
|
||||
|
||||
@@ -15,6 +15,7 @@ import {Type, makeType} from './Types';
|
||||
import {z} from 'zod';
|
||||
import type {AliasingEffect} from '../Inference/AliasingEffects';
|
||||
import {isReservedWord} from '../Utils/Keyword';
|
||||
import {SingleLineSuppressionRange} from '../Entrypoint';
|
||||
|
||||
/*
|
||||
* *******************************************************************************************
|
||||
@@ -843,6 +844,7 @@ export type MethodCall = {
|
||||
property: Place;
|
||||
args: Array<Place | SpreadPattern>;
|
||||
loc: SourceLocation;
|
||||
suppressions?: Array<SingleLineSuppressionRange>;
|
||||
};
|
||||
|
||||
export type CallExpression = {
|
||||
@@ -851,6 +853,7 @@ export type CallExpression = {
|
||||
args: Array<Place | SpreadPattern>;
|
||||
loc: SourceLocation;
|
||||
typeArguments?: Array<t.FlowType>;
|
||||
suppressions?: Array<SingleLineSuppressionRange>;
|
||||
};
|
||||
|
||||
export type NewExpression = {
|
||||
|
||||
@@ -255,6 +255,7 @@ function getManualMemoizationReplacement(
|
||||
*/
|
||||
args: [],
|
||||
loc,
|
||||
suppressions: [],
|
||||
};
|
||||
} else {
|
||||
/*
|
||||
|
||||
@@ -2089,7 +2089,7 @@ function computeSignatureForInstruction(
|
||||
effects.push({
|
||||
kind: 'Freeze',
|
||||
value: operand,
|
||||
reason: ValueReason.Other,
|
||||
reason: ValueReason.HookCaptured,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +546,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
* memoization. Note: we may still prune primitive-producing scopes if
|
||||
* they don't ultimately escape at all.
|
||||
*/
|
||||
const level = MemoizationLevel.Memoized;
|
||||
const level = MemoizationLevel.Conditional;
|
||||
return {
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
rvalues: [...eachReactiveValueOperand(value)],
|
||||
@@ -701,9 +701,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
|
||||
}
|
||||
case 'ComputedLoad':
|
||||
case 'PropertyLoad': {
|
||||
const level = options.forceMemoizePrimitives
|
||||
? MemoizationLevel.Memoized
|
||||
: MemoizationLevel.Conditional;
|
||||
const level = MemoizationLevel.Conditional;
|
||||
return {
|
||||
// Indirection for the inner value, memoized if the value is
|
||||
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
|
||||
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
ErrorCategory,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {SuppressionRange, suppressionsToCompilerError} from '../Entrypoint';
|
||||
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
|
||||
import {Result} from '../Utils/Result';
|
||||
|
||||
export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
const errors = new CompilerError();
|
||||
const suppressions: Array<SuppressionRange> = [];
|
||||
const useMemos = new Set<IdentifierId>();
|
||||
const effects = new Set<IdentifierId>();
|
||||
const react = new Set<IdentifierId>();
|
||||
const functions = new Map<IdentifierId, FunctionExpression>();
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
@@ -25,6 +28,12 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
case 'LoadGlobal': {
|
||||
if (value.binding.name === 'useMemo') {
|
||||
useMemos.add(lvalue.identifier.id);
|
||||
} else if (
|
||||
value.binding.name === 'useEffect' ||
|
||||
value.binding.name === 'useLayoutEffect' ||
|
||||
value.binding.name === 'useInsertionEffect'
|
||||
) {
|
||||
effects.add(lvalue.identifier.id);
|
||||
} else if (value.binding.name === 'React') {
|
||||
react.add(lvalue.identifier.id);
|
||||
}
|
||||
@@ -34,6 +43,12 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
if (react.has(value.object.identifier.id)) {
|
||||
if (value.property === 'useMemo') {
|
||||
useMemos.add(lvalue.identifier.id);
|
||||
} else if (
|
||||
value.property === 'useEffect' ||
|
||||
value.property === 'useLayoutEffect' ||
|
||||
value.property === 'useInsertionEffect'
|
||||
) {
|
||||
effects.add(lvalue.identifier.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -47,9 +62,18 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
// Is the function being called useMemo, with at least 1 argument?
|
||||
const callee =
|
||||
value.kind === 'CallExpression'
|
||||
? value.callee.identifier.id
|
||||
: value.property.identifier.id;
|
||||
const isUseMemo = useMemos.has(callee);
|
||||
? value.callee.identifier
|
||||
: value.property.identifier;
|
||||
const isEffect = effects.has(callee.id);
|
||||
if (
|
||||
!isEffect &&
|
||||
value.suppressions != null &&
|
||||
value.suppressions.length !== 0
|
||||
) {
|
||||
suppressions.push(...value.suppressions);
|
||||
}
|
||||
|
||||
const isUseMemo = useMemos.has(callee.id);
|
||||
if (!isUseMemo || value.args.length === 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -112,5 +136,9 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (suppressions.length !== 0) {
|
||||
const suppressionError = suppressionsToCompilerError(suppressions);
|
||||
errors.merge(suppressionError);
|
||||
}
|
||||
return errors.asResult();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
useEffect(
|
||||
() => {
|
||||
console.log(props.value);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
return <div />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] !== props.value) {
|
||||
t0 = () => {
|
||||
console.log(props.value);
|
||||
};
|
||||
$[0] = props.value;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [];
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
let t2;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <div />;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,10 @@
|
||||
function Component(props) {
|
||||
useEffect(
|
||||
() => {
|
||||
console.log(props.value);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
return <div />;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
useEffect(() => {
|
||||
console.log(props.value);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
return <div />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] !== props.value) {
|
||||
t0 = () => {
|
||||
console.log(props.value);
|
||||
};
|
||||
$[0] = props.value;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = [];
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
let t2;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <div />;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,7 @@
|
||||
function Component(props) {
|
||||
useEffect(() => {
|
||||
console.log(props.value);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
return <div />;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer"
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
@@ -30,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer"
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
function Component() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
function Component() {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
import {fbt} from 'fbt';
|
||||
|
||||
function Component() {
|
||||
const buttonLabel = () => {
|
||||
if (!someCondition) {
|
||||
return <fbt desc="My label">{'Purchase as a gift'}</fbt>;
|
||||
} else if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return (
|
||||
<fbt desc="Gift button's label">
|
||||
{'Gift | '}
|
||||
<fbt:param name="price">
|
||||
{item?.current_gift_offer?.price?.formatted}
|
||||
</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
} else if (!iconOnly && !showPrice) {
|
||||
return <fbt desc="Gift button's label">{'Gift'}</fbt>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
|
||||
import { fbt } from "fbt";
|
||||
|
||||
function Component() {
|
||||
const $ = _c(1);
|
||||
const buttonLabel = _temp;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
function _temp() {
|
||||
if (!someCondition) {
|
||||
return fbt._("Purchase as a gift", null, { hk: "1gHj4g" });
|
||||
} else {
|
||||
if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return fbt._(
|
||||
"Gift | {price}",
|
||||
[fbt._param("price", item?.current_gift_offer?.price?.formatted)],
|
||||
{ hk: "3GTnGE" },
|
||||
);
|
||||
} else {
|
||||
if (!iconOnly && !showPrice) {
|
||||
return fbt._("Gift", null, { hk: "3fqfrk" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,31 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
import {fbt} from 'fbt';
|
||||
|
||||
function Component() {
|
||||
const buttonLabel = () => {
|
||||
if (!someCondition) {
|
||||
return <fbt desc="My label">{'Purchase as a gift'}</fbt>;
|
||||
} else if (
|
||||
!iconOnly &&
|
||||
showPrice &&
|
||||
item?.current_gift_offer?.price?.formatted != null
|
||||
) {
|
||||
return (
|
||||
<fbt desc="Gift button's label">
|
||||
{'Gift | '}
|
||||
<fbt:param name="price">
|
||||
{item?.current_gift_offer?.price?.formatted}
|
||||
</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
} else if (!iconOnly && !showPrice) {
|
||||
return <fbt desc="Gift button's label">{'Gift'}</fbt>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Button text={buttonLabel()} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableForest
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
function Component({base, start, increment, test}) {
|
||||
let value = base;
|
||||
for (let i = start; i < test; i += increment) {
|
||||
@@ -27,25 +27,23 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableForest
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const $ = _c(2);
|
||||
const { base, start, increment, test } = t0;
|
||||
let value;
|
||||
if ($[0] !== base || $[1] !== increment || $[2] !== start || $[3] !== test) {
|
||||
value = base;
|
||||
for (let i = start; i < test; i = i + increment, i) {
|
||||
value = value + i;
|
||||
}
|
||||
$[0] = base;
|
||||
$[1] = increment;
|
||||
$[2] = start;
|
||||
$[3] = test;
|
||||
$[4] = value;
|
||||
} else {
|
||||
value = $[4];
|
||||
let value = base;
|
||||
for (let i = start; i < test; i = i + increment, i) {
|
||||
value = value + i;
|
||||
}
|
||||
return <div>{value}</div>;
|
||||
let t1;
|
||||
if ($[0] !== value) {
|
||||
t1 = <div>{value}</div>;
|
||||
$[0] = value;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enableForest
|
||||
// @enablePreserveExistingMemoizationGuarantees
|
||||
function Component({base, start, increment, test}) {
|
||||
let value = base;
|
||||
for (let i = start; i < test; i += increment) {
|
||||
|
||||
Reference in New Issue
Block a user