mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Lint rule to forbid access of cross-fork fields (#19679)
* Lint rule to forbid access of cross-fork fields
We use a shared Fiber type for both reconciler forks (old and new). It
is a superset of all the fields used by both forks. However, there are
some fields that should only be used in the new fork, and others that
should only be used in the old fork.
Ideally we would enforce this with separate Flow types for each fork.
The problem is that the Fiber type is accessed by some packages outside
the reconciler (like React DOM), and get passed into the reconciler as
arguments. So there's no way to fork the Fiber type without also forking
the packages where they are used. FiberRoot has the same issue.
Instead, I've added a lint rule that forbids cross-fork access of
fork-specific fields. Fields that end in `_old` or `_new` are forbidden
from being used inside the new or old fork respectively. Or you can
specific custom fields using the ESLint plugin options.
I used this plugin to find and remove references to the effect list
in d2e914a.
* Mark effect list fields as old
And `subtreeTag` as new.
I didn't mark `lastEffect` because that name is also used by the
Hook type. Not super important; could rename to `lastEffect_old` but
idk if it's worth the effort.
This commit is contained in:
parent
848bb2426e
commit
af219cc6e6
12
.eslintrc.js
12
.eslintrc.js
|
|
@ -99,6 +99,18 @@ module.exports = {
|
|||
'react-internal/warning-args': ERROR,
|
||||
'react-internal/no-production-logging': ERROR,
|
||||
'react-internal/no-cross-fork-imports': ERROR,
|
||||
'react-internal/no-cross-fork-types': [
|
||||
ERROR,
|
||||
{
|
||||
old: [
|
||||
'firstEffect',
|
||||
'nextEffect',
|
||||
// Disabled because it's also used by the Hook type.
|
||||
// 'lastEffect',
|
||||
],
|
||||
new: ['subtreeTag'],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
overrides: [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const rule = require('../no-cross-fork-types');
|
||||
const RuleTester = require('eslint').RuleTester;
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
sourceType: 'module',
|
||||
},
|
||||
});
|
||||
|
||||
const newAccessWarning =
|
||||
'Field cannot be accessed inside the old reconciler fork, only the ' +
|
||||
'new fork.';
|
||||
|
||||
const oldAccessWarning =
|
||||
'Field cannot be accessed inside the new reconciler fork, only the ' +
|
||||
'old fork.';
|
||||
|
||||
ruleTester.run('eslint-rules/no-cross-fork-types', rule, {
|
||||
valid: [
|
||||
{
|
||||
code: `
|
||||
const a = obj.key_old;
|
||||
const b = obj.key_new;
|
||||
const {key_old, key_new} = obj;
|
||||
`,
|
||||
filename: 'ReactFiberWorkLoop.js',
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const a = obj.key_old;
|
||||
const {key_old} = obj;
|
||||
`,
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
},
|
||||
{
|
||||
code: `
|
||||
const a = obj.key_new;
|
||||
const {key_new} = obj;
|
||||
`,
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: 'const a = obj.key_new;',
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
errors: [{message: newAccessWarning}],
|
||||
},
|
||||
{
|
||||
code: 'const a = obj.key_old;',
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
errors: [{message: oldAccessWarning}],
|
||||
},
|
||||
|
||||
{
|
||||
code: 'const {key_new} = obj;',
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
errors: [{message: newAccessWarning}],
|
||||
},
|
||||
{
|
||||
code: 'const {key_old} = obj;',
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
errors: [{message: oldAccessWarning}],
|
||||
},
|
||||
{
|
||||
code: 'const subtreeTag = obj.subtreeTag;',
|
||||
filename: 'ReactFiberWorkLoop.old.js',
|
||||
options: [{new: ['subtreeTag']}],
|
||||
errors: [{message: newAccessWarning}],
|
||||
},
|
||||
{
|
||||
code: 'const firstEffect = obj.firstEffect;',
|
||||
filename: 'ReactFiberWorkLoop.new.js',
|
||||
options: [{old: ['firstEffect']}],
|
||||
errors: [{message: oldAccessWarning}],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -8,5 +8,6 @@ module.exports = {
|
|||
'invariant-args': require('./invariant-args'),
|
||||
'no-production-logging': require('./no-production-logging'),
|
||||
'no-cross-fork-imports': require('./no-cross-fork-imports'),
|
||||
'no-cross-fork-types': require('./no-cross-fork-types'),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
126
scripts/eslint-rules/no-cross-fork-types.js
Normal file
126
scripts/eslint-rules/no-cross-fork-types.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
/* eslint-disable no-for-of-loops/no-for-of-loops */
|
||||
|
||||
'use strict';
|
||||
|
||||
function isOldFork(filename) {
|
||||
return filename.endsWith('.old.js') || filename.endsWith('.old');
|
||||
}
|
||||
|
||||
function isNewFork(filename) {
|
||||
return filename.endsWith('.new.js') || filename.endsWith('.new');
|
||||
}
|
||||
|
||||
function warnIfNewField(context, newFields, identifier) {
|
||||
const name = identifier.name;
|
||||
if (name.endsWith('_new') || (newFields !== null && newFields.has(name))) {
|
||||
context.report({
|
||||
node: identifier,
|
||||
message:
|
||||
'Field cannot be accessed inside the old reconciler fork, only the ' +
|
||||
'new fork.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function warnIfOldField(context, oldFields, identifier) {
|
||||
const name = identifier.name;
|
||||
if (name.endsWith('_old') || (oldFields !== null && oldFields.has(name))) {
|
||||
context.report({
|
||||
node: identifier,
|
||||
message:
|
||||
'Field cannot be accessed inside the new reconciler fork, only the ' +
|
||||
'old fork.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
fixable: 'code',
|
||||
},
|
||||
create(context) {
|
||||
const sourceFilename = context.getFilename();
|
||||
|
||||
if (isOldFork(sourceFilename)) {
|
||||
const options = context.options;
|
||||
let newFields = null;
|
||||
if (options !== null) {
|
||||
for (const option of options) {
|
||||
if (option.new !== undefined) {
|
||||
if (newFields === null) {
|
||||
newFields = new Set(option.new);
|
||||
} else {
|
||||
for (const field of option.new) {
|
||||
newFields.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
MemberExpression(node) {
|
||||
const property = node.property;
|
||||
if (property.type === 'Identifier') {
|
||||
warnIfNewField(context, newFields, property);
|
||||
}
|
||||
},
|
||||
|
||||
ObjectPattern(node) {
|
||||
for (const property of node.properties) {
|
||||
const key = property.key;
|
||||
if (key.type === 'Identifier') {
|
||||
warnIfNewField(context, newFields, key);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (isNewFork(sourceFilename)) {
|
||||
const options = context.options;
|
||||
let oldFields = null;
|
||||
if (options !== null) {
|
||||
for (const option of options) {
|
||||
if (option.old !== undefined) {
|
||||
if (oldFields === null) {
|
||||
oldFields = new Set(option.old);
|
||||
} else {
|
||||
for (const field of option.new) {
|
||||
oldFields.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
MemberExpression(node) {
|
||||
const property = node.property;
|
||||
if (property.type === 'Identifier') {
|
||||
warnIfOldField(context, oldFields, property);
|
||||
}
|
||||
},
|
||||
|
||||
ObjectPattern(node) {
|
||||
for (const property of node.properties) {
|
||||
const key = property.key;
|
||||
if (key.type === 'Identifier') {
|
||||
warnIfOldField(context, oldFields, key);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user