Compare commits

..

2 Commits

Author SHA1 Message Date
Eli White
0e78755102 Prettier 2020-01-17 16:21:28 -08:00
Eli White
598739c2f4 [Native] Migrate focus/blur to call TextInputState with the host component 2020-01-17 16:04:28 -08:00
377 changed files with 16522 additions and 19188 deletions

View File

@@ -5,8 +5,6 @@ const {
esNextPaths,
} = require('./scripts/shared/pathsByLanguageVersion');
const restrictedGlobals = require('confusing-browser-globals');
const OFF = 0;
const ERROR = 2;
@@ -47,7 +45,6 @@ module.exports = {
'no-bitwise': OFF,
'no-inner-declarations': [ERROR, 'functions'],
'no-multi-spaces': ERROR,
'no-restricted-globals': [ERROR].concat(restrictedGlobals),
'no-restricted-syntax': [ERROR, 'WithStatement'],
'no-shadow': ERROR,
'no-unused-expressions': ERROR,

View File

@@ -1,7 +0,0 @@
contact_links:
- name: 📃 Documentation Issue
url: https://github.com/reactjs/reactjs.org/issues/new
about: This issue tracker is not for documentation issues. Please file documentation issues here.
- name: 🤔 Questions and Help
url: https://reactjs.org/community/support.html
about: This issue tracker is not for support questions. Please refer to the React community's help and discussion forums.

13
.github/ISSUE_TEMPLATE/documentation.md vendored Normal file
View File

@@ -0,0 +1,13 @@
---
name: "📃 Documentation Issue"
about: This issue tracker is not for documentation issues. Please file documentation issues at https://github.com/reactjs/reactjs.org.
title: 'Docs: '
labels: 'Resolution: Invalid'
---
🚨 This issue tracker is not for documentation issues. 🚨
The React website is hosted on a separate repository. You may let the
team know about any issues with the documentation by opening an issue there:
- https://github.com/reactjs/reactjs.org/issues/new

29
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,29 @@
---
name: "🤔 Questions and Help"
about: This issue tracker is not for questions. Please ask questions at https://stackoverflow.com/questions/tagged/react.
title: 'Question: '
labels: 'Resolution: Invalid, Type: Question'
---
🚨 This issue tracker is not for questions. 🚨
As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek. Please take a look at the following resources.
## Coding Questions
If you have a coding question related to React and React DOM, it might be better suited for Stack Overflow. It's a great place to browse through frequent questions about using React, as well as ask for help with specific questions.
https://stackoverflow.com/questions/tagged/react
## Talk to other React developers
There are many online forums which are a great place for discussion about best practices and application architecture as well as the future of React.
https://reactjs.org/community/support.html#popular-discussion-forums
## Proposals
If you'd like to discuss topics related to the future of React, or would like to propose a new feature or change before sending a pull request, please check out the discussions and proposals repository.
https://github.com/reactjs/rfcs

View File

@@ -1,3 +1,11 @@
## [Unreleased]
<details>
<summary>
Changes that have landed in master but are not yet released.
Click to see more.
</summary>
</details>
## 16.12.0 (November 14, 2019)
### React DOM

View File

@@ -171,7 +171,7 @@ function git(args) {
for (let i = 0; i < baseArtifactsInfo.length; i++) {
const info = baseArtifactsInfo[i];
if (info.path.endsWith('bundle-sizes.json')) {
if (info.path === 'home/circleci/project/build/bundle-sizes.json') {
const resultsResponse = await fetch(info.url);
previousBuildResults = await resultsResponse.json();
break;

View File

@@ -11,7 +11,7 @@
"classnames": "^2.2.5",
"codemirror": "^5.40.0",
"core-js": "^2.4.1",
"jest-diff": "^25.1.0",
"jest-diff": "^24.8.0",
"prop-types": "^15.6.0",
"query-string": "^4.2.3",
"react": "^15.4.1",

View File

@@ -8,9 +8,8 @@
*/
let React;
let DOMAct;
let TestUtils;
let TestRenderer;
let TestAct;
global.__DEV__ = process.env.NODE_ENV !== 'production';
@@ -20,9 +19,8 @@ describe('unmocked scheduler', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
DOMAct = require('react-dom/test-utils').act;
TestUtils = require('react-dom/test-utils');
TestRenderer = require('react-test-renderer');
TestAct = TestRenderer.act;
});
it('flushes work only outside the outermost act() corresponding to its own renderer', () => {
@@ -34,8 +32,8 @@ describe('unmocked scheduler', () => {
return null;
}
// in legacy mode, this tests whether an act only flushes its own effects
TestAct(() => {
DOMAct(() => {
TestRenderer.act(() => {
TestUtils.act(() => {
TestRenderer.create(<Effecty />);
});
expect(log).toEqual([]);
@@ -44,8 +42,8 @@ describe('unmocked scheduler', () => {
log = [];
// for doublechecking, we flip it inside out, and assert on the outermost
DOMAct(() => {
TestAct(() => {
TestUtils.act(() => {
TestRenderer.act(() => {
TestRenderer.create(<Effecty />);
});
expect(log).toEqual(['called']);
@@ -61,9 +59,8 @@ describe('mocked scheduler', () => {
require.requireActual('scheduler/unstable_mock')
);
React = require('react');
DOMAct = require('react-dom/test-utils').act;
TestUtils = require('react-dom/test-utils');
TestRenderer = require('react-test-renderer');
TestAct = TestRenderer.act;
});
afterEach(() => {
@@ -79,8 +76,8 @@ describe('mocked scheduler', () => {
return null;
}
// with a mocked scheduler, this tests whether it flushes all work only on the outermost act
TestAct(() => {
DOMAct(() => {
TestRenderer.act(() => {
TestUtils.act(() => {
TestRenderer.create(<Effecty />);
});
expect(log).toEqual([]);
@@ -89,8 +86,8 @@ describe('mocked scheduler', () => {
log = [];
// for doublechecking, we flip it inside out, and assert on the outermost
DOMAct(() => {
TestAct(() => {
TestUtils.act(() => {
TestRenderer.act(() => {
TestRenderer.create(<Effecty />);
});
expect(log).toEqual([]);

View File

@@ -10,9 +10,9 @@
let React;
let ReactDOM;
let ReactART;
let TestUtils;
let ARTSVGMode;
let ARTCurrentMode;
let TestUtils;
let TestRenderer;
let ARTTest;
@@ -29,10 +29,10 @@ beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
TestUtils = require('react-dom/test-utils');
ReactART = require('react-art');
ARTSVGMode = require('art/modes/svg');
ARTCurrentMode = require('art/modes/current');
TestUtils = require('react-dom/test-utils');
TestRenderer = require('react-test-renderer');
ARTCurrentMode.setCurrent(ARTSVGMode);
@@ -71,7 +71,7 @@ beforeEach(() => {
it("doesn't warn when you use the right act + renderer: dom", () => {
TestUtils.act(() => {
ReactDOM.render(<App />, document.createElement('div'));
TestUtils.renderIntoDocument(<App />);
});
});
@@ -99,7 +99,7 @@ it('resets correctly across renderers', () => {
it('warns when using the wrong act version - test + dom: render', () => {
expect(() => {
TestRenderer.act(() => {
ReactDOM.render(<App />, document.createElement('div'));
TestUtils.renderIntoDocument(<App />);
});
}).toWarnDev(["It looks like you're using the wrong act()"], {
withoutStack: true,
@@ -113,7 +113,7 @@ it('warns when using the wrong act version - test + dom: updates', () => {
setCtr = _setCtr;
return ctr;
}
ReactDOM.render(<Counter />, document.createElement('div'));
TestUtils.renderIntoDocument(<Counter />);
expect(() => {
TestRenderer.act(() => {
setCtr(1);
@@ -159,7 +159,7 @@ it('warns when using the wrong act version - dom + test: updates', () => {
it('does not warn when nesting react-act inside react-dom', () => {
TestUtils.act(() => {
ReactDOM.render(<ARTTest />, document.createElement('div'));
TestUtils.renderIntoDocument(<ARTTest />);
});
});

View File

@@ -86,7 +86,6 @@ class Header extends React.Component {
<option value="/mouse-events">Mouse Events</option>
<option value="/selection-events">Selection Events</option>
<option value="/suspense">Suspense</option>
<option value="/form-state">Form State</option>
</select>
</label>
<label htmlFor="global_version">

View File

@@ -1,60 +0,0 @@
import Fixture from '../../Fixture';
const React = window.React;
class ControlledFormFixture extends React.Component {
constructor(props) {
super(props);
this.state = {name: '', email: ''};
this.handleEmailChange = this.handleEmailChange.bind(this);
this.handleNameChange = this.handleNameChange.bind(this);
}
handleEmailChange(event) {
this.setState({email: event.target.value});
}
handleNameChange(event) {
this.setState({name: event.target.value});
}
render() {
return (
<Fixture>
<form>
<label>
Name:
<input
type="text"
value={this.state.name}
onChange={this.handleNameChange}
name="name"
x-autocompletetype="name"
/>
</label>
<br />
<label>
Email:
<input
type="text"
value={this.state.email}
onChange={this.handleEmailChange}
name="email"
x-autocompletetype="email"
/>
</label>
</form>
<br />
<div>
<span>States</span>
<br />
<span>Name: {this.state.name}</span>
<br />
<span>Email: {this.state.email}</span>
</div>
</Fixture>
);
}
}
export default ControlledFormFixture;

View File

@@ -1,60 +0,0 @@
import FixtureSet from '../../FixtureSet';
import TestCase from '../../TestCase';
import ControlledFormFixture from './ControlledFormFixture';
const React = window.React;
export default class FormStateCases extends React.Component {
render() {
return (
<FixtureSet title="Form State">
<TestCase
title="Form state autofills from browser"
description="Form start should autofill/autocomplete if user has autocomplete/autofill information saved. The user may need to set-up autofill or autocomplete with their specific browser.">
<TestCase.Steps>
<li>
Set up autofill/autocomplete for your browser.
<br />
Instructions:
<ul>
<li>
<SafeLink
href="https://support.google.com/chrome/answer/142893?co=GENIE.Platform%3DDesktop&hl=en"
text="Google Chrome"
/>
</li>
<li>
<SafeLink
href="https://support.mozilla.org/en-US/kb/autofill-logins-firefox"
text="Mozilla FireFox"
/>
</li>
<li>
<SafeLink
href="https://support.microsoft.com/en-us/help/4027718/microsoft-edge-automatically-fill-info"
text="Microsoft Edge"
/>
</li>
</ul>
</li>
<li>Click into any input.</li>
<li>Select any autofill option.</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
Autofill options should appear when clicking into fields. Selected
autofill options should change state (shown underneath, under
"States").
</TestCase.ExpectedResult>
<ControlledFormFixture />
</TestCase>
</FixtureSet>
);
}
}
const SafeLink = ({text, href}) => {
return (
<a target="_blank" rel="noreferrer" href={href}>
{text}
</a>
);
};

View File

@@ -1,7 +1,7 @@
// copied from scripts/jest/matchers/toWarnDev.js
'use strict';
const jestDiff = require('jest-diff').default;
const jestDiff = require('jest-diff');
const util = require('util');
function shouldIgnoreConsoleError(format, args) {

View File

@@ -6,20 +6,14 @@
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.0.0.tgz#856446641620c1c5f0ca775621d478324ebd1f52"
"@jest/types@^25.1.0":
version "25.1.0"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.1.0.tgz#b26831916f0d7c381e11dbb5e103a72aed1b4395"
integrity sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA==
"@jest/types@^24.8.0":
version "24.8.0"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad"
integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/yargs" "^12.0.9"
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
@@ -41,17 +35,10 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
"@types/yargs@^15.0.0":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.1.tgz#9266a9d7be68cfcc982568211085a49a277f7c96"
integrity sha512-sYlwNU7zYi6eZbMzFvG6eHD7VsEvFdoDtlD7eI1JTg7YNnuguzmiGsc6MPSq5l8n+h21AsNof0je+9sgOe4+dg==
dependencies:
"@types/yargs-parser" "*"
"@types/yargs@^12.0.9":
version "12.0.12"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
abab@^1.0.3:
version "1.0.3"
@@ -187,10 +174,10 @@ ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-regex@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-styles@^2.2.1:
version "2.2.1"
@@ -202,13 +189,12 @@ ansi-styles@^3.0.0, ansi-styles@^3.1.0:
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
color-convert "^1.9.0"
anymatch@^1.3.0:
version "1.3.0"
@@ -1521,13 +1507,14 @@ chalk@^2.0.0, chalk@^2.1.0:
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
chalk@^2.0.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chokidar@^1.6.0, chokidar@^1.7.0:
version "1.7.0"
@@ -1639,22 +1626,10 @@ color-convert@^1.9.0:
dependencies:
color-name "^1.1.1"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@^1.0.0, color-name@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
@@ -2138,10 +2113,10 @@ detect-port-alt@1.1.3:
address "^1.0.1"
debug "^2.6.0"
diff-sequences@^25.1.0:
version "25.1.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32"
integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw==
diff-sequences@^24.3.0:
version "24.3.0"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==
diff@^3.2.0:
version "3.3.0"
@@ -3205,10 +3180,10 @@ has-flag@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-unicode@^2.0.0:
version "2.0.1"
@@ -3861,15 +3836,15 @@ jest-diff@^20.0.3:
jest-matcher-utils "^20.0.3"
pretty-format "^20.0.3"
jest-diff@^25.1.0:
version "25.1.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad"
integrity sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw==
jest-diff@^24.8.0:
version "24.8.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172"
integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==
dependencies:
chalk "^3.0.0"
diff-sequences "^25.1.0"
jest-get-type "^25.1.0"
pretty-format "^25.1.0"
chalk "^2.0.1"
diff-sequences "^24.3.0"
jest-get-type "^24.8.0"
pretty-format "^24.8.0"
jest-docblock@^20.0.3:
version "20.0.3"
@@ -3890,10 +3865,10 @@ jest-environment-node@^20.0.3:
jest-mock "^20.0.3"
jest-util "^20.0.3"
jest-get-type@^25.1.0:
version "25.1.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.1.0.tgz#1cfe5fc34f148dc3a8a3b7275f6b9ce9e2e8a876"
integrity sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw==
jest-get-type@^24.8.0:
version "24.8.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc"
integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==
jest-haste-map@^20.0.4:
version "20.0.5"
@@ -5360,15 +5335,15 @@ pretty-format@^20.0.3:
ansi-regex "^2.1.1"
ansi-styles "^3.0.0"
pretty-format@^25.1.0:
version "25.1.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8"
integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ==
pretty-format@^24.8.0:
version "24.8.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==
dependencies:
"@jest/types" "^25.1.0"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^16.12.0"
"@jest/types" "^24.8.0"
ansi-regex "^4.0.0"
ansi-styles "^3.2.0"
react-is "^16.8.4"
private@^0.1.6:
version "0.1.6"
@@ -5556,10 +5531,10 @@ react-error-overlay@^1.0.10:
settle-promise "1.0.0"
source-map "0.5.6"
react-is@^16.12.0:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
react-is@^16.8.4:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
react-scripts@^1.0.11:
version "1.0.11"
@@ -6379,12 +6354,12 @@ supports-color@^4.0.0, supports-color@^4.2.1:
dependencies:
has-flag "^2.0.0"
supports-color@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^4.0.0"
has-flag "^3.0.0"
svgo@^0.7.0:
version "0.7.1"

View File

@@ -4,49 +4,48 @@
"packages/*"
],
"devDependencies": {
"@babel/cli": "^7.8.0",
"@babel/code-frame": "^7.8.0",
"@babel/core": "^7.8.0",
"@babel/helper-module-imports": "^7.8.0",
"@babel/parser": "^7.8.0",
"@babel/plugin-external-helpers": "^7.8.0",
"@babel/plugin-proposal-class-properties": "^7.8.0",
"@babel/plugin-proposal-object-rest-spread": "^7.8.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.0",
"@babel/plugin-syntax-jsx": "^7.8.0",
"@babel/plugin-transform-arrow-functions": "^7.8.0",
"@babel/plugin-transform-async-to-generator": "^7.8.0",
"@babel/plugin-transform-block-scoped-functions": "^7.8.0",
"@babel/plugin-transform-block-scoping": "^7.8.0",
"@babel/plugin-transform-classes": "^7.8.0",
"@babel/plugin-transform-computed-properties": "^7.8.0",
"@babel/plugin-transform-destructuring": "^7.8.0",
"@babel/plugin-transform-for-of": "^7.8.0",
"@babel/plugin-transform-literals": "^7.8.0",
"@babel/plugin-transform-modules-commonjs": "^7.8.0",
"@babel/plugin-transform-object-super": "^7.8.0",
"@babel/plugin-transform-parameters": "^7.8.0",
"@babel/plugin-transform-react-jsx-source": "^7.8.0",
"@babel/plugin-transform-shorthand-properties": "^7.8.0",
"@babel/plugin-transform-spread": "^7.8.0",
"@babel/plugin-transform-template-literals": "^7.8.0",
"@babel/preset-flow": "^7.8.0",
"@babel/preset-react": "^7.8.0",
"@babel/traverse": "^7.8.0",
"@mattiasbuelens/web-streams-polyfill": "^0.3.2",
"art": "0.10.1",
"@babel/cli": "^7.0.0",
"@babel/code-frame": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/helper-module-imports": "^7.0.0",
"@babel/parser": "^7.0.0",
"@babel/plugin-external-helpers": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-jsx": "^7.2.0",
"@babel/plugin-transform-arrow-functions": "^7.0.0",
"@babel/plugin-transform-async-to-generator": "^7.0.0",
"@babel/plugin-transform-block-scoped-functions": "^7.0.0",
"@babel/plugin-transform-block-scoping": "^7.0.0",
"@babel/plugin-transform-classes": "^7.0.0",
"@babel/plugin-transform-computed-properties": "^7.0.0",
"@babel/plugin-transform-destructuring": "^7.0.0",
"@babel/plugin-transform-for-of": "^7.0.0",
"@babel/plugin-transform-literals": "^7.0.0",
"@babel/plugin-transform-modules-commonjs": "^7.0.0",
"@babel/plugin-transform-object-super": "^7.0.0",
"@babel/plugin-transform-parameters": "^7.0.0",
"@babel/plugin-transform-react-jsx-source": "^7.0.0",
"@babel/plugin-transform-shorthand-properties": "^7.0.0",
"@babel/plugin-transform-spread": "^7.0.0",
"@babel/plugin-transform-template-literals": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/traverse": "^7.0.0",
"@mattiasbuelens/web-streams-polyfill": "0.1.0",
"art": "^0.10.1",
"babel-eslint": "^10.0.3",
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
"chalk": "^3.0.0",
"chalk": "^1.1.3",
"cli-table": "^0.3.1",
"coffee-script": "^1.12.7",
"confusing-browser-globals": "^1.0.9",
"core-js": "^3.6.4",
"coveralls": "^3.0.9",
"coffee-script": "^1.8.0",
"core-js": "^2.2.1",
"coveralls": "^2.11.6",
"create-react-class": "^15.6.3",
"cross-env": "^6.0.3",
"danger": "^9.2.10",
"error-stack-parser": "^2.0.6",
"cross-env": "^5.1.1",
"danger": "^9.1.8",
"error-stack-parser": "^2.0.2",
"eslint": "^6.8.0",
"eslint-config-fbjs": "^1.1.1",
"eslint-config-prettier": "^6.9.0",
@@ -56,40 +55,40 @@
"eslint-plugin-no-for-of-loops": "^1.0.0",
"eslint-plugin-react": "^6.7.1",
"eslint-plugin-react-internal": "link:./scripts/eslint-rules",
"fbjs-scripts": "0.8.3",
"filesize": "^6.0.1",
"flow-bin": "0.97",
"glob": "^7.1.6",
"fbjs-scripts": "^0.8.3",
"filesize": "^3.5.6",
"flow-bin": "^0.84.0",
"glob": "^6.0.4",
"glob-stream": "^6.1.0",
"google-closure-compiler": "^20200112.0.0",
"gzip-size": "^5.1.1",
"google-closure-compiler": "20190301.0.0",
"gzip-size": "^3.0.0",
"jasmine-check": "^1.0.0-rc.0",
"jest": "^25.1.0",
"jest-diff": "^25.1.0",
"jest": "^24.9.0",
"jest-diff": "^24.9.0",
"jest-snapshot-serializer-raw": "^1.1.0",
"minimatch": "^3.0.4",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"ncp": "^2.0.0",
"object-assign": "^4.1.1",
"pacote": "^10.3.0",
"pacote": "^9.5.6",
"prettier": "1.19.1",
"prop-types": "^15.6.2",
"random-seed": "^0.3.0",
"react-lifecycles-compat": "^3.0.4",
"rimraf": "^3.0.0",
"rollup": "^1.19.4",
"react-lifecycles-compat": "^3.0.2",
"rimraf": "^2.6.1",
"rollup": "^0.52.1",
"rollup-plugin-babel": "^4.0.1",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-commonjs": "^8.2.6",
"rollup-plugin-node-resolve": "^2.1.1",
"rollup-plugin-prettier": "^0.6.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-prettier": "^0.3.0",
"rollup-plugin-replace": "^2.0.0",
"rollup-plugin-strip-banner": "^0.2.0",
"semver": "^7.1.1",
"semver": "^5.5.0",
"targz": "^1.0.1",
"through2": "^3.0.1",
"tmp": "^0.1.0",
"typescript": "^3.7.5",
"through2": "^2.0.0",
"tmp": "~0.0.28",
"typescript": "^3.7.4",
"webpack": "^4.41.2"
},
"devEngines": {

View File

@@ -0,0 +1,392 @@
/**
* 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.
*/
/* eslint-disable quotes */
'use strict';
const babel = require('@babel/core');
const codeFrame = require('@babel/code-frame');
const {wrap} = require('jest-snapshot-serializer-raw');
function transform(input, options) {
return wrap(
babel.transform(input, {
configFile: false,
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-arrow-functions',
...(options && options.development
? [
'@babel/plugin-transform-react-jsx-source',
'@babel/plugin-transform-react-jsx-self',
]
: []),
[
'./packages/babel-plugin-react-jsx',
{
development: __DEV__,
useBuiltIns: true,
useCreateElement: true,
...options,
},
],
],
}).code
);
}
describe('transform react to jsx', () => {
it('fragment with no children', () => {
expect(transform(`var x = <></>`)).toMatchSnapshot();
});
it('React.Fragment to set keys and source', () => {
expect(
transform(`var x = <React.Fragment key='foo'><div /></React.Fragment>`, {
development: true,
})
).toMatchSnapshot();
});
it('normal fragments not to set key and source', () => {
expect(
transform(`var x = <><div /></>`, {
development: true,
})
).toMatchSnapshot();
});
it('should properly handle comments adjacent to children', () => {
expect(
transform(`
var x = (
<div>
{/* A comment at the beginning */}
{/* A second comment at the beginning */}
<span>
{/* A nested comment */}
</span>
{/* A sandwiched comment */}
<br />
{/* A comment at the end */}
{/* A second comment at the end */}
</div>
);
`)
).toMatchSnapshot();
});
it('adds appropriate new lines when using spread attribute', () => {
expect(transform(`<Component {...props} sound="moo" />`)).toMatchSnapshot();
});
it('arrow functions', () => {
expect(
transform(`
var foo = function () {
return () => <this />;
};
var bar = function () {
return () => <this.foo />;
};
`)
).toMatchSnapshot();
});
it('assignment', () => {
expect(
transform(`var div = <Component {...props} foo="bar" />`)
).toMatchSnapshot();
});
it('concatenates adjacent string literals', () => {
expect(
transform(`
var x =
<div>
foo
{"bar"}
baz
<div>
buz
bang
</div>
qux
{null}
quack
</div>
`)
).toMatchSnapshot();
});
it('should allow constructor as prop', () => {
expect(transform(`<Component constructor="foo" />;`)).toMatchSnapshot();
});
it('should allow deeper js namespacing', () => {
expect(
transform(`<Namespace.DeepNamespace.Component />;`)
).toMatchSnapshot();
});
it('should allow elements as attributes', () => {
expect(transform(`<div attr=<div /> />`)).toMatchSnapshot();
});
it('should allow js namespacing', () => {
expect(transform(`<Namespace.Component />;`)).toMatchSnapshot();
});
it('should allow nested fragments', () => {
expect(
transform(`
<div>
< >
<>
<span>Hello</span>
<span>world</span>
</>
<>
<span>Goodbye</span>
<span>world</span>
</>
</>
</div>
`)
).toMatchSnapshot();
});
it('should avoid wrapping in extra parens if not needed', () => {
expect(
transform(`
var x = <div>
<Component />
</div>;
var x = <div>
{props.children}
</div>;
var x = <Composite>
{props.children}
</Composite>;
var x = <Composite>
<Composite2 />
</Composite>;
`)
).toMatchSnapshot();
});
it('should convert simple tags', () => {
expect(transform(`var x = <div></div>;`)).toMatchSnapshot();
});
it('should convert simple text', () => {
expect(transform(`var x = <div>text</div>;`)).toMatchSnapshot();
});
it('should disallow spread children', () => {
let _error;
const code = `<div>{...children}</div>;`;
try {
transform(code);
} catch (error) {
_error = error;
}
expect(_error).toEqual(
new SyntaxError(
'undefined: Spread children are not supported in React.' +
'\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 6}},
{highlightCode: true}
)
)
);
});
it('should escape xhtml jsxattribute', () => {
expect(
transform(`
<div id="wôw" />;
<div id="\w" />;
<div id="w &lt; w" />;
`)
).toMatchSnapshot();
});
it('should escape xhtml jsxtext', () => {
/* eslint-disable no-irregular-whitespace */
expect(
transform(`
<div>wow</div>;
<div>wôw</div>;
<div>w & w</div>;
<div>w &amp; w</div>;
<div>w &nbsp; w</div>;
<div>this should not parse as unicode: \u00a0</div>;
<div>this should parse as nbsp:   </div>;
<div>this should parse as unicode: {'\u00a0 '}</div>;
<div>w &lt; w</div>;
`)
).toMatchSnapshot();
/*eslint-enable */
});
it('should handle attributed elements', () => {
expect(
transform(`
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.render(<HelloMessage name={
<span>
Sebastian
</span>
} />, mountNode);
`)
).toMatchSnapshot();
});
it('should handle has own property correctly', () => {
expect(
transform(`<hasOwnProperty>testing</hasOwnProperty>;`)
).toMatchSnapshot();
});
it('should have correct comma in nested children', () => {
expect(
transform(`
var x = <div>
<div><br /></div>
<Component>{foo}<br />{bar}</Component>
<br />
</div>;
`)
).toMatchSnapshot();
});
it('should insert commas after expressions before whitespace', () => {
expect(
transform(`
var x =
<div
attr1={
"foo" + "bar"
}
attr2={
"foo" + "bar" +
"baz" + "bug"
}
attr3={
"foo" + "bar" +
"baz" + "bug"
}
attr4="baz">
</div>
`)
).toMatchSnapshot();
});
it('should not add quotes to identifier names', () => {
expect(
transform(`var e = <F aaa new const var default foo-bar/>;`)
).toMatchSnapshot();
});
it('should not strip nbsp even couple with other whitespace', () => {
expect(transform(`<div>&nbsp; </div>;`)).toMatchSnapshot();
});
it('should not strip tags with a single child of nbsp', () => {
expect(transform(`<div>&nbsp;</div>;`)).toMatchSnapshot();
});
it('should properly handle comments between props', () => {
expect(
transform(`
var x = (
<div
/* a multi-line
comment */
attr1="foo">
<span // a double-slash comment
attr2="bar"
/>
</div>
);
`)
).toMatchSnapshot();
});
it('should quote jsx attributes', () => {
expect(
transform(`<button data-value='a value'>Button</button>`)
).toMatchSnapshot();
});
it('should support xml namespaces if flag', () => {
expect(
transform('<f:image n:attr />', {throwIfNamespace: false})
).toMatchSnapshot();
});
it('should throw error namespaces if not flag', () => {
let _error;
const code = `<f:image />`;
try {
transform(code);
} catch (error) {
_error = error;
}
expect(_error).toEqual(
new SyntaxError(
"undefined: Namespace tags are not supported by default. React's " +
"JSX doesn't support namespace tags. You can turn on the " +
"'throwIfNamespace' flag to bypass this warning." +
'\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 2}},
{highlightCode: true}
)
)
);
});
it('should transform known hyphenated tags', () => {
expect(transform(`<font-face />`)).toMatchSnapshot();
});
it('wraps props in react spread for first spread attributes', () => {
expect(transform(`<Component {...x} y={2} z />`)).toMatchSnapshot();
});
it('wraps props in react spread for last spread attributes', () => {
expect(transform(`<Component y={2} z { ... x } />`)).toMatchSnapshot();
});
it('wraps props in react spread for middle spread attributes', () => {
expect(transform(`<Component y={2} { ... x } z />`)).toMatchSnapshot();
});
it('useBuiltIns false uses extend instead of Object.assign', () => {
expect(
transform(`<Component y={2} {...x} />`, {useBuiltIns: false})
).toMatchSnapshot();
});
});

View File

@@ -11,15 +11,14 @@ const babel = require('@babel/core');
const codeFrame = require('@babel/code-frame');
const {wrap} = require('jest-snapshot-serializer-raw');
function transform(input, pluginOpts, babelOpts) {
function transform(input, options) {
return wrap(
babel.transform(input, {
configFile: false,
sourceType: 'module',
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-arrow-functions',
...(pluginOpts && pluginOpts.development
...(options && options.development
? [
'@babel/plugin-transform-react-jsx-source',
'@babel/plugin-transform-react-jsx-self',
@@ -30,380 +29,15 @@ function transform(input, pluginOpts, babelOpts) {
{
useBuiltIns: true,
useCreateElement: false,
...pluginOpts,
...options,
},
],
],
...babelOpts,
}).code
);
}
describe('transform react to jsx', () => {
it('auto import pragma overrides regular pragma', () => {
expect(
transform(
`/** @jsxAutoImport defaultExport */
var x = <div><span /></div>
`,
{
autoImport: 'namespace',
importSource: 'foobar',
}
)
).toMatchSnapshot();
});
it('import source pragma overrides regular pragma', () => {
expect(
transform(
`/** @jsxImportSource baz */
var x = <div><span /></div>
`,
{
autoImport: 'namespace',
importSource: 'foobar',
}
)
).toMatchSnapshot();
});
it('multiple pragmas work', () => {
expect(
transform(
`/** Some comment here
* @jsxImportSource baz
* @jsxAutoImport defaultExport
*/
var x = <div><span /></div>
`,
{
autoImport: 'namespace',
importSource: 'foobar',
}
)
).toMatchSnapshot();
});
it('throws error when sourceType is module and autoImport is require', () => {
const code = `var x = <div><span /></div>`;
expect(() => {
transform(code, {
autoImport: 'require',
});
}).toThrow(
'Babel `sourceType` must be set to `script` for autoImport ' +
'to use `require` syntax. See Babel `sourceType` for details.\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 1}, end: {line: 1, column: 28}},
{highlightCode: true}
)
);
});
it('throws error when sourceType is script and autoImport is not require', () => {
const code = `var x = <div><span /></div>`;
expect(() => {
transform(
code,
{
autoImport: 'namespace',
},
{sourceType: 'script'}
);
}).toThrow(
'Babel `sourceType` must be set to `module` for autoImport ' +
'to use `namespace` syntax. See Babel `sourceType` for details.\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 1}, end: {line: 1, column: 28}},
{highlightCode: true}
)
);
});
it("auto import that doesn't exist should throw error", () => {
const code = `var x = <div><span /></div>`;
expect(() => {
transform(code, {
autoImport: 'foo',
});
}).toThrow(
'autoImport must be one of the following: none, require, namespace, defaultExport, namedExports\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 1}, end: {line: 1, column: 28}},
{highlightCode: true}
)
);
});
it('auto import can specify source', () => {
expect(
transform(`var x = <div><span /></div>`, {
autoImport: 'namespace',
importSource: 'foobar',
})
).toMatchSnapshot();
});
it('auto import require', () => {
expect(
transform(
`var x = (
<>
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
</>
);`,
{
autoImport: 'require',
},
{
sourceType: 'script',
}
)
).toMatchSnapshot();
});
it('auto import namespace', () => {
expect(
transform(
`var x = (
<>
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
</>
);`,
{
autoImport: 'namespace',
}
)
).toMatchSnapshot();
});
it('auto import default', () => {
expect(
transform(
`var x = (
<>
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
</>
);`,
{
autoImport: 'defaultExport',
}
)
).toMatchSnapshot();
});
it('auto import named exports', () => {
expect(
transform(
`var x = (
<>
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
</>
);`,
{
autoImport: 'namedExports',
}
)
).toMatchSnapshot();
});
it('auto import with no JSX', () => {
expect(
transform(
`var foo = "<div></div>"`,
{
autoImport: 'require',
},
{
sourceType: 'script',
}
)
).toMatchSnapshot();
});
it('complicated scope require', () => {
expect(
transform(
`
const Bar = () => {
const Foo = () => {
const Component = ({thing, ..._react}) => {
if (!thing) {
var _react2 = "something useless";
var b = _react3();
var c = _react5();
var jsx = 1;
var _jsx = 2;
return <div />;
};
return <span />;
};
}
}
`,
{
autoImport: 'require',
},
{
sourceType: 'script',
}
)
).toMatchSnapshot();
});
it('complicated scope named exports', () => {
expect(
transform(
`
const Bar = () => {
const Foo = () => {
const Component = ({thing, ..._react}) => {
if (!thing) {
var _react2 = "something useless";
var b = _react3();
var jsx = 1;
var _jsx = 2;
return <div />;
};
return <span />;
};
}
}
`,
{
autoImport: 'namedExports',
}
)
).toMatchSnapshot();
});
it('auto import in dev', () => {
expect(
transform(
`var x = (
<>
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
</>
);`,
{
autoImport: 'namedExports',
development: true,
}
)
).toMatchSnapshot();
});
it('auto import none', () => {
expect(
transform(
`var x = (
<>
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
</>
);`,
{
autoImport: 'none',
}
)
).toMatchSnapshot();
});
it('auto import undefined', () => {
expect(
transform(
`var x = (
<>
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
</>
);`
)
).toMatchSnapshot();
});
it('auto import with namespaces already defined', () => {
expect(
transform(
`
import * as _react from "foo";
const react = _react(1);
const _react1 = react;
const _react2 = react;
var x = (
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
);`,
{
autoImport: 'namespace',
}
)
).toMatchSnapshot();
});
it('auto import with react already defined', () => {
expect(
transform(
`
import * as react from "react";
var y = react.createElement("div", {foo: 1});
var x = (
<div>
<div key="1" />
<div key="2" meow="wolf" />
<div key="3" />
<div {...props} key="4" />
</div>
);`,
{
autoImport: 'namespace',
}
)
).toMatchSnapshot();
});
it('fragment with no children', () => {
expect(transform(`var x = <></>`)).toMatchSnapshot();
});
@@ -658,11 +292,11 @@ describe('transform react to jsx', () => {
}
expect(_error).toEqual(
new SyntaxError(
'unknown: Spread children are not supported in React.' +
'undefined: Spread children are not supported in React.' +
'\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 6}, end: {line: 1, column: 19}},
{start: {line: 1, column: 6}},
{highlightCode: true}
)
)
@@ -812,13 +446,13 @@ describe('transform react to jsx', () => {
}
expect(_error).toEqual(
new SyntaxError(
"unknown: Namespace tags are not supported by default. React's " +
"undefined: Namespace tags are not supported by default. React's " +
"JSX doesn't support namespace tags. You can turn on the " +
"'throwIfNamespace' flag to bypass this warning." +
'\n' +
codeFrame.codeFrameColumns(
code,
{start: {line: 1, column: 2}, end: {line: 1, column: 9}},
{start: {line: 1, column: 2}},
{highlightCode: true}
)
)

View File

@@ -0,0 +1,213 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`transform react to jsx React.Fragment to set keys and source 1`] = `
var _jsxFileName = "";
var x = React.createElement(React.Fragment, {
key: "foo",
__source: {
fileName: _jsxFileName,
lineNumber: 1
},
__self: this
}, React.createElement("div", {
__source: {
fileName: _jsxFileName,
lineNumber: 1
},
__self: this
}));
`;
exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
React.createElement(Component, Object.assign({}, props, {
sound: "moo"
}));
`;
exports[`transform react to jsx arrow functions 1`] = `
var foo = function () {
var _this = this;
return function () {
return React.createElement(_this, null);
};
};
var bar = function () {
var _this2 = this;
return function () {
return React.createElement(_this2.foo, null);
};
};
`;
exports[`transform react to jsx assignment 1`] = `
var div = React.createElement(Component, Object.assign({}, props, {
foo: "bar"
}));
`;
exports[`transform react to jsx concatenates adjacent string literals 1`] = `var x = React.createElement("div", null, "foo", "bar", "baz", React.createElement("div", null, "buz bang"), "qux", null, "quack");`;
exports[`transform react to jsx fragment with no children 1`] = `var x = React.createElement(React.Fragment, null);`;
exports[`transform react to jsx normal fragments not to set key and source 1`] = `
var _jsxFileName = "";
var x = React.createElement(React.Fragment, null, React.createElement("div", {
__source: {
fileName: _jsxFileName,
lineNumber: 1
},
__self: this
}));
`;
exports[`transform react to jsx should allow constructor as prop 1`] = `
React.createElement(Component, {
constructor: "foo"
});
`;
exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.createElement(Namespace.DeepNamespace.Component, null);`;
exports[`transform react to jsx should allow elements as attributes 1`] = `
React.createElement("div", {
attr: React.createElement("div", null)
});
`;
exports[`transform react to jsx should allow js namespacing 1`] = `React.createElement(Namespace.Component, null);`;
exports[`transform react to jsx should allow nested fragments 1`] = `React.createElement("div", null, React.createElement(React.Fragment, null, React.createElement(React.Fragment, null, React.createElement("span", null, "Hello"), React.createElement("span", null, "world")), React.createElement(React.Fragment, null, React.createElement("span", null, "Goodbye"), React.createElement("span", null, "world"))));`;
exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
var x = React.createElement("div", null, React.createElement(Component, null));
var x = React.createElement("div", null, props.children);
var x = React.createElement(Composite, null, props.children);
var x = React.createElement(Composite, null, React.createElement(Composite2, null));
`;
exports[`transform react to jsx should convert simple tags 1`] = `var x = React.createElement("div", null);`;
exports[`transform react to jsx should convert simple text 1`] = `var x = React.createElement("div", null, "text");`;
exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
React.createElement("div", {
id: "w\\xF4w"
});
React.createElement("div", {
id: "w"
});
React.createElement("div", {
id: "w < w"
});
`;
exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
React.createElement("div", null, "wow");
React.createElement("div", null, "w\\xF4w");
React.createElement("div", null, "w & w");
React.createElement("div", null, "w & w");
React.createElement("div", null, "w \\xA0 w");
React.createElement("div", null, "this should not parse as unicode: \\xA0");
React.createElement("div", null, "this should parse as nbsp: \\xA0 ");
React.createElement("div", null, "this should parse as unicode: ", '  ');
React.createElement("div", null, "w < w");
`;
exports[`transform react to jsx should handle attributed elements 1`] = `
var HelloMessage = React.createClass({
render: function () {
return React.createElement("div", null, "Hello ", this.props.name);
}
});
React.render(React.createElement(HelloMessage, {
name: React.createElement("span", null, "Sebastian")
}), mountNode);
`;
exports[`transform react to jsx should handle has own property correctly 1`] = `React.createElement("hasOwnProperty", null, "testing");`;
exports[`transform react to jsx should have correct comma in nested children 1`] = `var x = React.createElement("div", null, React.createElement("div", null, React.createElement("br", null)), React.createElement(Component, null, foo, React.createElement("br", null), bar), React.createElement("br", null));`;
exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
var x = React.createElement("div", {
attr1: "foo" + "bar",
attr2: "foo" + "bar" + "baz" + "bug",
attr3: "foo" + "bar" + "baz" + "bug",
attr4: "baz"
});
`;
exports[`transform react to jsx should not add quotes to identifier names 1`] = `
var e = React.createElement(F, {
aaa: true,
new: true,
const: true,
var: true,
default: true,
"foo-bar": true
});
`;
exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `React.createElement("div", null, "\\xA0 ");`;
exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `React.createElement("div", null, "\\xA0");`;
exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `var x = React.createElement("div", null, React.createElement("span", null), React.createElement("br", null));`;
exports[`transform react to jsx should properly handle comments between props 1`] = `
var x = React.createElement("div", {
/* a multi-line
comment */
attr1: "foo"
}, React.createElement("span", {
// a double-slash comment
attr2: "bar"
}));
`;
exports[`transform react to jsx should quote jsx attributes 1`] = `
React.createElement("button", {
"data-value": "a value"
}, "Button");
`;
exports[`transform react to jsx should support xml namespaces if flag 1`] = `
React.createElement("f:image", {
"n:attr": true
});
`;
exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.createElement("font-face", null);`;
exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
React.createElement(Component, _extends({
y: 2
}, x));
`;
exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
React.createElement(Component, Object.assign({}, x, {
y: 2,
z: true
}));
`;
exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
React.createElement(Component, Object.assign({
y: 2,
z: true
}, x));
`;
exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
React.createElement(Component, Object.assign({
y: 2
}, x, {
z: true
}));
`;

View File

@@ -38,230 +38,6 @@ var div = React.jsx(Component, Object.assign({}, props, {
}));
`;
exports[`transform react to jsx auto import can specify source 1`] = `
import * as _foobar from "foobar";
var x = _foobar.jsx("div", {
children: _foobar.jsx("span", {})
});
`;
exports[`transform react to jsx auto import default 1`] = `
import _default from "react";
var x = _default.jsx(_default.Fragment, {
children: _default.jsxs("div", {
children: [_default.jsx("div", {}, "1"), _default.jsx("div", {
meow: "wolf"
}, "2"), _default.jsx("div", {}, "3"), _default.createElement("div", Object.assign({}, props, {
key: "4"
}))]
})
});
`;
exports[`transform react to jsx auto import in dev 1`] = `
import { createElement as _createElement } from "react";
import { jsxDEV as _jsxDEV } from "react";
import { Fragment as _Fragment } from "react";
var _jsxFileName = "";
var x = _jsxDEV(_Fragment, {
children: _jsxDEV("div", {
children: [_jsxDEV("div", {}, "1", false, {
fileName: _jsxFileName,
lineNumber: 4
}, this), _jsxDEV("div", {
meow: "wolf"
}, "2", false, {
fileName: _jsxFileName,
lineNumber: 5
}, this), _jsxDEV("div", {}, "3", false, {
fileName: _jsxFileName,
lineNumber: 6
}, this), _createElement("div", Object.assign({}, props, {
key: "4",
__source: {
fileName: _jsxFileName,
lineNumber: 7
},
__self: this
}))]
}, undefined, true, {
fileName: _jsxFileName,
lineNumber: 3
}, this)
}, undefined, false);
`;
exports[`transform react to jsx auto import named exports 1`] = `
import { createElement as _createElement } from "react";
import { jsx as _jsx } from "react";
import { jsxs as _jsxs } from "react";
import { Fragment as _Fragment } from "react";
var x = _jsx(_Fragment, {
children: _jsxs("div", {
children: [_jsx("div", {}, "1"), _jsx("div", {
meow: "wolf"
}, "2"), _jsx("div", {}, "3"), _createElement("div", Object.assign({}, props, {
key: "4"
}))]
})
});
`;
exports[`transform react to jsx auto import namespace 1`] = `
import * as _react from "react";
var x = _react.jsx(_react.Fragment, {
children: _react.jsxs("div", {
children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
meow: "wolf"
}, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
key: "4"
}))]
})
});
`;
exports[`transform react to jsx auto import none 1`] = `
var x = React.jsx(React.Fragment, {
children: React.jsxs("div", {
children: [React.jsx("div", {}, "1"), React.jsx("div", {
meow: "wolf"
}, "2"), React.jsx("div", {}, "3"), React.createElement("div", Object.assign({}, props, {
key: "4"
}))]
})
});
`;
exports[`transform react to jsx auto import pragma overrides regular pragma 1`] = `
import _default from "foobar";
/** @jsxAutoImport defaultExport */
var x = _default.jsx("div", {
children: _default.jsx("span", {})
});
`;
exports[`transform react to jsx auto import require 1`] = `
var _react = require("react");
var x = _react.jsx(_react.Fragment, {
children: _react.jsxs("div", {
children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
meow: "wolf"
}, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
key: "4"
}))]
})
});
`;
exports[`transform react to jsx auto import undefined 1`] = `
var x = React.jsx(React.Fragment, {
children: React.jsxs("div", {
children: [React.jsx("div", {}, "1"), React.jsx("div", {
meow: "wolf"
}, "2"), React.jsx("div", {}, "3"), React.createElement("div", Object.assign({}, props, {
key: "4"
}))]
})
});
`;
exports[`transform react to jsx auto import with namespaces already defined 1`] = `
import * as _react3 from "react";
import * as _react from "foo";
const react = _react(1);
const _react1 = react;
const _react2 = react;
var x = _react3.jsxs("div", {
children: [_react3.jsx("div", {}, "1"), _react3.jsx("div", {
meow: "wolf"
}, "2"), _react3.jsx("div", {}, "3"), _react3.createElement("div", Object.assign({}, props, {
key: "4"
}))]
});
`;
exports[`transform react to jsx auto import with no JSX 1`] = `var foo = "<div></div>";`;
exports[`transform react to jsx auto import with react already defined 1`] = `
import * as _react from "react";
import * as react from "react";
var y = react.createElement("div", {
foo: 1
});
var x = _react.jsxs("div", {
children: [_react.jsx("div", {}, "1"), _react.jsx("div", {
meow: "wolf"
}, "2"), _react.jsx("div", {}, "3"), _react.createElement("div", Object.assign({}, props, {
key: "4"
}))]
});
`;
exports[`transform react to jsx complicated scope named exports 1`] = `
import { jsx as _jsx2 } from "react";
const Bar = function () {
const Foo = function () {
const Component = function ({
thing,
..._react
}) {
if (!thing) {
var _react2 = "something useless";
var b = _react3();
var jsx = 1;
var _jsx = 2;
return _jsx2("div", {});
}
;
return _jsx2("span", {});
};
};
};
`;
exports[`transform react to jsx complicated scope require 1`] = `
var _react4 = require("react");
const Bar = function () {
const Foo = function () {
const Component = function ({
thing,
..._react
}) {
if (!thing) {
var _react2 = "something useless";
var b = _react3();
var c = _react5();
var jsx = 1;
var _jsx = 2;
return _react4.jsx("div", {});
}
;
return _react4.jsx("span", {});
};
};
};
`;
exports[`transform react to jsx concatenates adjacent string literals 1`] = `
var x = React.jsxs("div", {
children: ["foo", "bar", "baz", React.jsx("div", {
@@ -316,27 +92,6 @@ var x = React.jsxDEV(React.Fragment, {
exports[`transform react to jsx fragments to set keys 1`] = `var x = React.jsx(React.Fragment, {}, "foo");`;
exports[`transform react to jsx import source pragma overrides regular pragma 1`] = `
import * as _baz from "baz";
/** @jsxImportSource baz */
var x = _baz.jsx("div", {
children: _baz.jsx("span", {})
});
`;
exports[`transform react to jsx multiple pragmas work 1`] = `
import _default from "baz";
/** Some comment here
* @jsxImportSource baz
* @jsxAutoImport defaultExport
*/
var x = _default.jsx("div", {
children: _default.jsx("span", {})
});
`;
exports[`transform react to jsx nonStatic children 1`] = `
var _jsxFileName = "";
var x = React.jsxDEV("div", {

View File

@@ -5,8 +5,8 @@
"description": "@babel/plugin-transform-react-jsx",
"main": "index.js",
"dependencies": {
"@babel/helper-module-imports": "^7.0.0",
"esutils": "^2.0.0"
},
"files": [
"README.md",

View File

@@ -24,50 +24,6 @@
'use strict';
const esutils = require('esutils');
const {
isModule,
addNamespace,
addNamed,
addDefault,
} = require('@babel/helper-module-imports');
// These are all the valid auto import types (under the config autoImport)
// that a user can specific
const IMPORT_TYPES = {
none: 'none', // default option. Will not import anything
require: 'require', // var _react = require("react");
namespace: 'namespace', // import * as _react from "react";
defaultExport: 'defaultExport', // import _default from "react";
namedExports: 'namedExports', // import { jsx } from "react";
};
const JSX_AUTO_IMPORT_ANNOTATION_REGEX = /\*?\s*@jsxAutoImport\s+([^\s]+)/;
const JSX_IMPORT_SOURCE_ANNOTATION_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/;
// We want to use React.createElement, even in the case of
// jsx, for <div {...props} key={key} /> to distinguish it
// from <div key={key} {...props} />. This is an intermediary
// step while we deprecate key spread from props. Afterwards,
// we will remove createElement entirely
function shouldUseCreateElement(path, types) {
const openingPath = path.get('openingElement');
const attributes = openingPath.node.attributes;
let seenPropsSpread = false;
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
if (
seenPropsSpread &&
types.isJSXAttribute(attr) &&
attr.name.name === 'key'
) {
return true;
} else if (types.isJSXSpreadAttribute(attr)) {
seenPropsSpread = true;
}
}
return false;
}
function helper(babel, opts) {
const {types: t} = babel;
@@ -96,7 +52,7 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
visitor.JSXElement = {
exit(path, file) {
let callExpr;
if (shouldUseCreateElement(path, t)) {
if (file.opts.useCreateElement || shouldUseCreateElement(path)) {
callExpr = buildCreateElementCall(path, file);
} else {
callExpr = buildJSXElementCall(path, file);
@@ -115,7 +71,12 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
'Fragment tags are only supported in React 16 and up.',
);
}
let callExpr = buildJSXFragmentCall(path, file);
let callExpr;
if (file.opts.useCreateElement) {
callExpr = buildCreateElementFragmentCall(path, file);
} else {
callExpr = buildJSXFragmentCall(path, file);
}
if (callExpr) {
path.replaceWith(t.inherits(callExpr, path.node));
@@ -186,6 +147,31 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
return t.inherits(t.objectProperty(node.name, value), node);
}
// We want to use React.createElement, even in the case of
// jsx, for <div {...props} key={key} /> to distinguish it
// from <div key={key} {...props} />. This is an intermediary
// step while we deprecate key spread from props. Afterwards,
// we will remove createElement entirely
function shouldUseCreateElement(path) {
const openingPath = path.get('openingElement');
const attributes = openingPath.node.attributes;
let seenPropsSpread = false;
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
if (
seenPropsSpread &&
t.isJSXAttribute(attr) &&
attr.name.name === 'key'
) {
return true;
} else if (t.isJSXSpreadAttribute(attr)) {
seenPropsSpread = true;
}
}
return false;
}
// Builds JSX into:
// Production: React.jsx(type, arguments, key)
// Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
@@ -276,7 +262,6 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
if (opts.post) {
opts.post(state, file);
}
return (
state.call ||
t.callExpression(
@@ -576,6 +561,38 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
return attribs;
}
function buildCreateElementFragmentCall(path, file) {
if (opts.filter && !opts.filter(path.node, file)) {
return;
}
const openingPath = path.get('openingElement');
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
const args = [];
const tagName = null;
const tagExpr = file.get('jsxFragIdentifier')();
const state = {
tagExpr: tagExpr,
tagName: tagName,
args: args,
};
if (opts.pre) {
opts.pre(state, file);
}
// no attributes are allowed with <> syntax
args.push(t.nullLiteral(), ...path.node.children);
if (opts.post) {
opts.post(state, file);
}
return state.call || t.callExpression(state.oldCallee, args);
}
}
module.exports = function(babel) {
@@ -606,183 +623,25 @@ module.exports = function(babel) {
},
});
const createIdentifierName = (path, autoImport, name, importName) => {
if (autoImport === IMPORT_TYPES.none) {
return `React.${name}`;
} else if (autoImport === IMPORT_TYPES.namedExports) {
if (importName) {
const identifierName = `${importName[name]}`;
return identifierName;
}
} else {
return `${importName}.${name}`;
}
};
function getImportNames(parentPath, state) {
const imports = {};
parentPath.traverse({
JSXElement(path) {
if (shouldUseCreateElement(path, t)) {
imports.createElement = true;
} else if (path.node.children.length > 1) {
const importName = state.development ? 'jsxDEV' : 'jsxs';
imports[importName] = true;
} else {
const importName = state.development ? 'jsxDEV' : 'jsx';
imports[importName] = true;
}
},
JSXFragment(path) {
imports.Fragment = true;
},
});
return imports;
}
function hasJSX(parentPath) {
let fileHasJSX = false;
parentPath.traverse({
JSXElement(path) {
fileHasJSX = true;
path.stop();
},
JSXFragment(path) {
fileHasJSX = true;
path.stop();
},
});
return fileHasJSX;
}
function addAutoImports(path, state) {
if (state.autoImport === IMPORT_TYPES.none) {
return;
}
if (IMPORT_TYPES[state.autoImport] === undefined) {
throw path.buildCodeFrameError(
'autoImport must be one of the following: ' +
Object.keys(IMPORT_TYPES).join(', '),
);
}
if (state.autoImport === IMPORT_TYPES.require && isModule(path)) {
throw path.buildCodeFrameError(
'Babel `sourceType` must be set to `script` for autoImport ' +
'to use `require` syntax. See Babel `sourceType` for details.',
);
}
if (state.autoImport !== IMPORT_TYPES.require && !isModule(path)) {
throw path.buildCodeFrameError(
'Babel `sourceType` must be set to `module` for autoImport to use `' +
state.autoImport +
'` syntax. See Babel `sourceType` for details.',
);
}
// import {jsx} from "react";
// import {createElement} from "react";
if (state.autoImport === IMPORT_TYPES.namedExports) {
const imports = getImportNames(path, state);
const importMap = {};
Object.keys(imports).forEach(importName => {
importMap[importName] = addNamed(path, importName, state.source).name;
});
return importMap;
}
// add import to file and get the import name
let name;
if (state.autoImport === IMPORT_TYPES.require) {
// var _react = require("react");
name = addNamespace(path, state.source, {
importedInterop: 'uncompiled',
}).name;
} else if (state.autoImport === IMPORT_TYPES.namespace) {
// import * as _react from "react";
name = addNamespace(path, state.source).name;
} else if (state.autoImport === IMPORT_TYPES.defaultExport) {
// import _default from "react";
name = addDefault(path, state.source).name;
}
return name;
}
visitor.Program = {
enter(path, state) {
if (hasJSX(path)) {
let autoImport = state.opts.autoImport || IMPORT_TYPES.none;
let source = state.opts.importSource || 'react';
const {file} = state;
if (file.ast.comments) {
for (let i = 0; i < file.ast.comments.length; i++) {
const comment = file.ast.comments[i];
const jsxAutoImportMatches = JSX_AUTO_IMPORT_ANNOTATION_REGEX.exec(
comment.value,
);
if (jsxAutoImportMatches) {
autoImport = jsxAutoImportMatches[1];
}
const jsxImportSourceMatches = JSX_IMPORT_SOURCE_ANNOTATION_REGEX.exec(
comment.value,
);
if (jsxImportSourceMatches) {
source = jsxImportSourceMatches[1];
}
}
}
const importName = addAutoImports(path, {
...state.opts,
autoImport,
source,
});
state.set(
'oldJSXIdentifier',
createIdentifierParser(
createIdentifierName(path, autoImport, 'createElement', importName),
),
);
state.set(
'jsxIdentifier',
createIdentifierParser(
createIdentifierName(
path,
autoImport,
state.opts.development ? 'jsxDEV' : 'jsx',
importName,
),
),
);
state.set(
'jsxStaticIdentifier',
createIdentifierParser(
createIdentifierName(
path,
autoImport,
state.opts.development ? 'jsxDEV' : 'jsxs',
importName,
),
),
);
state.set(
'jsxFragIdentifier',
createIdentifierParser(
createIdentifierName(path, autoImport, 'Fragment', importName),
),
);
}
state.set(
'oldJSXIdentifier',
createIdentifierParser('React.createElement'),
);
state.set(
'jsxIdentifier',
createIdentifierParser(
state.opts.development ? 'React.jsxDEV' : 'React.jsx',
),
);
state.set(
'jsxStaticIdentifier',
createIdentifierParser(
state.opts.development ? 'React.jsxDEV' : 'React.jsxs',
),
);
state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment'));
},
};

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import invariant from 'shared/invariant';
type Unsubscribe = () => void;

View File

@@ -405,18 +405,6 @@ const tests = {
useHook();
}
`,
`
// Valid because the neither the condition nor the loop affect the hook call.
function App(props) {
const someObject = {propA: true};
for (const propName in someObject) {
if (propName === true) {
} else {
}
}
const [myState, setMyState] = useState(null);
}
`,
],
invalid: [
{
@@ -652,7 +640,14 @@ const tests = {
}
}
`,
errors: [loopError('useHook1'), loopError('useHook2', true)],
errors: [
loopError('useHook1'),
// NOTE: Small imprecision in error reporting due to caching means we
// have a conditional error here instead of a loop error. However,
// we will always get an error so this is acceptable.
conditionalError('useHook2', true),
],
},
{
code: `

View File

@@ -1,7 +1,7 @@
{
"name": "eslint-plugin-react-hooks",
"description": "ESLint rules for React Hooks",
"version": "2.4.0",
"version": "2.3.0",
"repository": {
"type": "git",
"url": "https://github.com/facebook/react.git",

View File

@@ -11,6 +11,7 @@
export default {
meta: {
fixable: 'code',
schema: [
{
type: 'object',
@@ -93,7 +94,7 @@ export default {
reactiveHookName === 'useMemo' ||
reactiveHookName === 'useCallback'
) {
// TODO: Can this have a suggestion?
// TODO: Can this have an autofix?
context.report({
node: node.parent.callee,
message:
@@ -557,19 +558,12 @@ export default {
`To fix this, pass [` +
suggestedDependencies.join(', ') +
`] as a second argument to the ${reactiveHookName} Hook.`,
suggest: [
{
desc: `Add dependencies array: [${suggestedDependencies.join(
', ',
)}]`,
fix(fixer) {
return fixer.insertTextAfter(
node,
`, [${suggestedDependencies.join(', ')}]`,
);
},
},
],
fix(fixer) {
return fixer.insertTextAfter(
node,
`, [${suggestedDependencies.join(', ')}]`,
);
},
});
}
return;
@@ -708,35 +702,27 @@ export default {
` Move it inside the ${reactiveHookName} callback. ` +
`Alternatively, wrap the '${fn.name.name}' definition into its own useCallback() Hook.`;
}
let suggest;
// Only handle the simple case: arrow functions.
// Wrapping function declarations can mess up hoisting.
if (suggestUseCallback && fn.type === 'Variable') {
suggest = [
{
desc: `Wrap the '${fn.name.name}' definition into its own useCallback() Hook.`,
fix(fixer) {
return [
// TODO: also add an import?
fixer.insertTextBefore(fn.node.init, 'useCallback('),
// TODO: ideally we'd gather deps here but it would require
// restructuring the rule code. This will cause a new lint
// error to appear immediately for useCallback. Note we're
// not adding [] because would that changes semantics.
fixer.insertTextAfter(fn.node.init, ')'),
];
},
},
];
}
// TODO: What if the function needs to change on every render anyway?
// Should we suggest removing effect deps as an appropriate fix too?
context.report({
// TODO: Why not report this at the dependency site?
node: fn.node,
message,
suggest,
fix(fixer) {
// Only handle the simple case: arrow functions.
// Wrapping function declarations can mess up hoisting.
if (suggestUseCallback && fn.type === 'Variable') {
return [
// TODO: also add an import?
fixer.insertTextBefore(fn.node.init, 'useCallback('),
// TODO: ideally we'd gather deps here but it would require
// restructuring the rule code. This will cause a new lint
// error to appear immediately for useCallback. Note we're
// not adding [] because would that changes semantics.
fixer.insertTextAfter(fn.node.init, ')'),
];
}
},
});
});
return;
@@ -1022,20 +1008,13 @@ export default {
'omit',
)) +
extraWarning,
suggest: [
{
desc: `Update the dependencies array to be: [${suggestedDependencies.join(
', ',
)}]`,
fix(fixer) {
// TODO: consider preserving the comments or formatting?
return fixer.replaceText(
declaredDependenciesNode,
`[${suggestedDependencies.join(', ')}]`,
);
},
},
],
fix(fixer) {
// TODO: consider preserving the comments or formatting?
return fixer.replaceText(
declaredDependenciesNode,
`[${suggestedDependencies.join(', ')}]`,
);
},
});
}
},

View File

@@ -152,33 +152,31 @@ export default {
* Populates `cyclic` with cyclic segments.
*/
function countPathsFromStart(segment, pathHistory) {
function countPathsFromStart(segment) {
const {cache} = countPathsFromStart;
let paths = cache.get(segment.id);
const pathList = new Set(pathHistory);
// If `pathList` includes the current segment then we've found a cycle!
// We need to fill `cyclic` with all segments inside cycle
if (pathList.has(segment.id)) {
const pathArray = [...pathList];
const cyclicSegments = pathArray.slice(
pathArray.indexOf(segment.id) + 1,
);
for (const cyclicSegment of cyclicSegments) {
cyclic.add(cyclicSegment);
// If `paths` is null then we've found a cycle! Add it to `cyclic` and
// any other segments which are a part of this cycle.
if (paths === null) {
if (cyclic.has(segment.id)) {
return 0;
} else {
cyclic.add(segment.id);
for (const prevSegment of segment.prevSegments) {
countPathsFromStart(prevSegment);
}
return 0;
}
return 0;
}
// add the current segment to pathList
pathList.add(segment.id);
// We have a cached `paths`. Return it.
if (paths !== undefined) {
return paths;
}
// Compute `paths` and cache it. Guarding against cycles.
cache.set(segment.id, null);
if (codePath.thrownSegments.includes(segment)) {
paths = 0;
} else if (segment.prevSegments.length === 0) {
@@ -186,7 +184,7 @@ export default {
} else {
paths = 0;
for (const prevSegment of segment.prevSegments) {
paths += countPathsFromStart(prevSegment, pathList);
paths += countPathsFromStart(prevSegment);
}
}
@@ -223,33 +221,31 @@ export default {
* Populates `cyclic` with cyclic segments.
*/
function countPathsToEnd(segment, pathHistory) {
function countPathsToEnd(segment) {
const {cache} = countPathsToEnd;
let paths = cache.get(segment.id);
let pathList = new Set(pathHistory);
// If `pathList` includes the current segment then we've found a cycle!
// We need to fill `cyclic` with all segments inside cycle
if (pathList.has(segment.id)) {
const pathArray = Array.from(pathList);
const cyclicSegments = pathArray.slice(
pathArray.indexOf(segment.id) + 1,
);
for (const cyclicSegment of cyclicSegments) {
cyclic.add(cyclicSegment);
// If `paths` is null then we've found a cycle! Add it to `cyclic` and
// any other segments which are a part of this cycle.
if (paths === null) {
if (cyclic.has(segment.id)) {
return 0;
} else {
cyclic.add(segment.id);
for (const nextSegment of segment.nextSegments) {
countPathsToEnd(nextSegment);
}
return 0;
}
return 0;
}
// add the current segment to pathList
pathList.add(segment.id);
// We have a cached `paths`. Return it.
if (paths !== undefined) {
return paths;
}
// Compute `paths` and cache it. Guarding against cycles.
cache.set(segment.id, null);
if (codePath.thrownSegments.includes(segment)) {
paths = 0;
} else if (segment.nextSegments.length === 0) {
@@ -257,11 +253,11 @@ export default {
} else {
paths = 0;
for (const nextSegment of segment.nextSegments) {
paths += countPathsToEnd(nextSegment, pathList);
paths += countPathsToEnd(nextSegment);
}
}
cache.set(segment.id, paths);
return paths;
}

View File

@@ -19,7 +19,7 @@
},
"homepage": "https://reactjs.org/",
"peerDependencies": {
"jest": "^23.0.1 || ^24.0.0 || ^25.1.0",
"jest": "^23.0.1 || ^24.0.0",
"scheduler": "^0.15.0"
},
"files": [

View File

@@ -19,7 +19,7 @@
},
"homepage": "https://reactjs.org/",
"peerDependencies": {
"jest": "^23.0.1 || ^24.0.0 || ^25.1.0",
"jest": "^23.0.1 || ^24.0.0",
"react": "^16.0.0",
"react-test-renderer": "^16.0.0"
},

View File

@@ -0,0 +1,175 @@
/**
* 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.
* @flow
*/
import invariant from 'shared/invariant';
import {
injectEventPluginOrder,
injectEventPluginsByName,
plugins,
} from './EventPluginRegistry';
import {getFiberCurrentPropsFromNode} from './EventPluginUtils';
import accumulateInto from './accumulateInto';
import {runEventsInBatch} from './EventBatching';
import type {PluginModule} from './PluginModuleType';
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {AnyNativeEvent} from './PluginModuleType';
import type {TopLevelType} from './TopLevelEventTypes';
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
function isInteractive(tag) {
return (
tag === 'button' ||
tag === 'input' ||
tag === 'select' ||
tag === 'textarea'
);
}
function shouldPreventMouseEvent(name, type, props) {
switch (name) {
case 'onClick':
case 'onClickCapture':
case 'onDoubleClick':
case 'onDoubleClickCapture':
case 'onMouseDown':
case 'onMouseDownCapture':
case 'onMouseMove':
case 'onMouseMoveCapture':
case 'onMouseUp':
case 'onMouseUpCapture':
return !!(props.disabled && isInteractive(type));
default:
return false;
}
}
/**
* This is a unified interface for event plugins to be installed and configured.
*
* Event plugins can implement the following properties:
*
* `extractEvents` {function(string, DOMEventTarget, string, object): *}
* Required. When a top-level event is fired, this method is expected to
* extract synthetic events that will in turn be queued and dispatched.
*
* `eventTypes` {object}
* Optional, plugins that fire events must publish a mapping of registration
* names that are used to register listeners. Values of this mapping must
* be objects that contain `registrationName` or `phasedRegistrationNames`.
*
* `executeDispatch` {function(object, function, string)}
* Optional, allows plugins to override how an event gets dispatched. By
* default, the listener is simply invoked.
*
* Each plugin that is injected into `EventsPluginHub` is immediately operable.
*
* @public
*/
/**
* Methods for injecting dependencies.
*/
export const injection = {
/**
* @param {array} InjectedEventPluginOrder
* @public
*/
injectEventPluginOrder,
/**
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
*/
injectEventPluginsByName,
};
/**
* @param {object} inst The instance, which is the source of events.
* @param {string} registrationName Name of listener (e.g. `onClick`).
* @return {?function} The stored callback.
*/
export function getListener(inst: Fiber, registrationName: string) {
let listener;
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
// live here; needs to be moved to a better place soon
const stateNode = inst.stateNode;
if (!stateNode) {
// Work in progress (ex: onload events in incremental mode).
return null;
}
const props = getFiberCurrentPropsFromNode(stateNode);
if (!props) {
// Work in progress.
return null;
}
listener = props[registrationName];
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
return null;
}
invariant(
!listener || typeof listener === 'function',
'Expected `%s` listener to be a function, instead got a value of `%s` type.',
registrationName,
typeof listener,
);
return listener;
}
/**
* Allows registered plugins an opportunity to extract events from top-level
* native browser events.
*
* @return {*} An accumulation of synthetic events.
* @internal
*/
function extractPluginEvents(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
let events = null;
for (let i = 0; i < plugins.length; i++) {
// Not every plugin in the ordering may be loaded at runtime.
const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
if (possiblePlugin) {
const extractedEvents = possiblePlugin.extractEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
return events;
}
export function runExtractedPluginEventsInBatch(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
) {
const events = extractPluginEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
);
runEventsInBatch(events);
}

View File

@@ -89,7 +89,7 @@ function publishEventForPlugin(
): boolean {
invariant(
!eventNameDispatchConfigs.hasOwnProperty(eventName),
'EventPluginRegistry: More than one plugin attempted to publish the same ' +
'EventPluginHub: More than one plugin attempted to publish the same ' +
'event name, `%s`.',
eventName,
);
@@ -133,7 +133,7 @@ function publishRegistrationName(
): void {
invariant(
!registrationNameModules[registrationName],
'EventPluginRegistry: More than one plugin attempted to publish the same ' +
'EventPluginHub: More than one plugin attempted to publish the same ' +
'registration name, `%s`.',
registrationName,
);
@@ -153,6 +153,8 @@ function publishRegistrationName(
/**
* Registers plugins so that they can extract and dispatch events.
*
* @see {EventPluginHub}
*/
/**
@@ -191,6 +193,7 @@ export const possibleRegistrationNames = __DEV__ ? {} : (null: any);
*
* @param {array} InjectedEventPluginOrder
* @internal
* @see {EventPluginHub.injection.injectEventPluginOrder}
*/
export function injectEventPluginOrder(
injectedEventPluginOrder: EventPluginOrder,
@@ -206,13 +209,14 @@ export function injectEventPluginOrder(
}
/**
* Injects plugins to be used by plugin event system. The plugin names must be
* Injects plugins to be used by `EventPluginHub`. The plugin names must be
* in the ordering injected by `injectEventPluginOrder`.
*
* Plugins can be injected as part of page initialization or on-the-fly.
*
* @param {object} injectedNamesToPlugins Map from names to plugin modules.
* @internal
* @see {EventPluginHub.injection.injectEventPluginsByName}
*/
export function injectEventPluginsByName(
injectedNamesToPlugins: NamesToPlugins,

View File

@@ -11,7 +11,7 @@ import {
traverseEnterLeave,
} from 'shared/ReactTreeTraversal';
import getListener from 'legacy-events/getListener';
import {getListener} from './EventPluginHub';
import accumulateInto from './accumulateInto';
import forEachAccumulated from './forEachAccumulated';

View File

@@ -29,7 +29,7 @@ export type PluginModule<NativeEvent> = {
nativeTarget: NativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
container?: Document | Element | Node,
) => ?ReactSyntheticEvent,
tapMoveThreshold?: number,
...
};

View File

@@ -23,8 +23,8 @@ import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
let batchedUpdatesImpl = function(fn, bookkeeping) {
return fn(bookkeeping);
};
let discreteUpdatesImpl = function(fn, a, b, c, d) {
return fn(a, b, c, d);
let discreteUpdatesImpl = function(fn, a, b, c) {
return fn(a, b, c);
};
let flushDiscreteUpdatesImpl = function() {};
let batchedEventUpdatesImpl = batchedUpdatesImpl;
@@ -89,11 +89,11 @@ export function executeUserEventHandler(fn: any => void, value: any): void {
}
}
export function discreteUpdates(fn, a, b, c, d) {
export function discreteUpdates(fn, a, b, c) {
const prevIsInsideEventHandler = isInsideEventHandler;
isInsideEventHandler = true;
try {
return discreteUpdatesImpl(fn, a, b, c, d);
return discreteUpdatesImpl(fn, a, b, c);
} finally {
isInsideEventHandler = prevIsInsideEventHandler;
if (!isInsideEventHandler) {

View File

@@ -31,4 +31,4 @@ export type ReactSyntheticEvent = {|
nativeEventTarget: EventTarget,
) => ReactSyntheticEvent,
isPersistent: () => boolean,
|};
|} & SyntheticEvent<>;

View File

@@ -282,7 +282,7 @@ to return true:wantsResponderID| |
+ + */
/**
* A note about event ordering in the `EventPluginRegistry`.
* A note about event ordering in the `EventPluginHub`.
*
* Suppose plugins are injected in the following order:
*
@@ -301,7 +301,7 @@ to return true:wantsResponderID| |
* - When returned from `extractEvents`, deferred-dispatched events contain an
* "accumulation" of deferred dispatches.
* - These deferred dispatches are accumulated/collected before they are
* returned, but processed at a later time by the `EventPluginRegistry` (hence the
* returned, but processed at a later time by the `EventPluginHub` (hence the
* name deferred).
*
* In the process of returning their deferred-dispatched events, event plugins
@@ -325,9 +325,9 @@ to return true:wantsResponderID| |
* - `R`s on-demand events (if any) (dispatched by `R` on-demand)
* - `S`s on-demand events (if any) (dispatched by `S` on-demand)
* - `C`s on-demand events (if any) (dispatched by `C` on-demand)
* - `R`s extracted events (if any) (dispatched by `EventPluginRegistry`)
* - `S`s extracted events (if any) (dispatched by `EventPluginRegistry`)
* - `C`s extracted events (if any) (dispatched by `EventPluginRegistry`)
* - `R`s extracted events (if any) (dispatched by `EventPluginHub`)
* - `S`s extracted events (if any) (dispatched by `EventPluginHub`)
* - `C`s extracted events (if any) (dispatched by `EventPluginHub`)
*
* In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
* on-demand dispatch returns `true` (and some other details are satisfied) the
@@ -336,9 +336,9 @@ to return true:wantsResponderID| |
* will appear as follows:
*
* - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
* - `touchStartCapture` (`EventPluginRegistry` dispatches as usual)
* - `touchStart` (`EventPluginRegistry` dispatches as usual)
* - `responderGrant/Reject` (`EventPluginRegistry` dispatches as usual)
* - `touchStartCapture` (`EventPluginHub` dispatches as usual)
* - `touchStart` (`EventPluginHub` dispatches as usual)
* - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
*/
function setResponderAndExtractTransfer(

View File

@@ -211,7 +211,7 @@ describe('EventPluginRegistry', () => {
expect(function() {
EventPluginRegistry.injectEventPluginOrder(['one', 'two']);
}).toThrowError(
'EventPluginRegistry: More than one plugin attempted to publish the same ' +
'EventPluginHub: More than one plugin attempted to publish the same ' +
'registration name, `onPhotoCapture`.',
);
});

View File

@@ -1,74 +0,0 @@
/**
* 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.
* @flow
*/
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import invariant from 'shared/invariant';
import {getFiberCurrentPropsFromNode} from './EventPluginUtils';
function isInteractive(tag) {
return (
tag === 'button' ||
tag === 'input' ||
tag === 'select' ||
tag === 'textarea'
);
}
function shouldPreventMouseEvent(name, type, props) {
switch (name) {
case 'onClick':
case 'onClickCapture':
case 'onDoubleClick':
case 'onDoubleClickCapture':
case 'onMouseDown':
case 'onMouseDownCapture':
case 'onMouseMove':
case 'onMouseMoveCapture':
case 'onMouseUp':
case 'onMouseUpCapture':
case 'onMouseEnter':
return !!(props.disabled && isInteractive(type));
default:
return false;
}
}
/**
* @param {object} inst The instance, which is the source of events.
* @param {string} registrationName Name of listener (e.g. `onClick`).
* @return {?function} The stored callback.
*/
export default function getListener(inst: Fiber, registrationName: string) {
let listener;
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
// live here; needs to be moved to a better place soon
const stateNode = inst.stateNode;
if (!stateNode) {
// Work in progress (ex: onload events in incremental mode).
return null;
}
const props = getFiberCurrentPropsFromNode(stateNode);
if (!props) {
// Work in progress.
return null;
}
listener = props[registrationName];
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
return null;
}
invariant(
!listener || typeof listener === 'function',
'Expected `%s` listener to be a function, instead got a value of `%s` type.',
registrationName,
typeof listener,
);
return listener;
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import * as React from 'react';
import React from 'react';
import ReactVersion from 'shared/ReactVersion';
import {LegacyRoot} from 'shared/ReactRootTags';
import {

View File

@@ -77,7 +77,7 @@ export function createLRU<T>(limit: number) {
}
}
function add(value: Object, onDelete: () => mixed): Entry<Object> {
function add(value: T, onDelete: () => mixed): Entry<T> {
const entry = {
value,
onDelete,

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import {createLRU} from './LRU';

View File

@@ -25,7 +25,7 @@ import {
SimpleMemoComponent,
ContextProvider,
ForwardRef,
Block,
Chunk,
} from 'shared/ReactWorkTags';
type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
@@ -117,8 +117,7 @@ function useState<S>(
hook !== null
? hook.memoizedState
: typeof initialState === 'function'
? // $FlowFixMe: Flow doesn't like mixed types
initialState()
? initialState()
: initialState;
hookLog.push({primitive: 'State', stackError: new Error(), value: state});
return [state, (action: BasicStateAction<S>) => {}];
@@ -628,7 +627,7 @@ export function inspectHooksOfFiber(
fiber.tag !== FunctionComponent &&
fiber.tag !== SimpleMemoComponent &&
fiber.tag !== ForwardRef &&
fiber.tag !== Block
fiber.tag !== Chunk
) {
throw new Error(
'Unknown Fiber. Needs to be a function component to inspect hooks.',

View File

@@ -22,11 +22,6 @@ describe('ReactHooksInspection', () => {
ReactDebugTools = require('react-debug-tools');
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
it('should inspect a simple useResponder hook', () => {
const TestResponder = React.DEPRECATED_createResponder('TestResponder', {});

View File

@@ -32,7 +32,7 @@ type ConnectOptions = {
installHook(window);
const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
const hook: DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
let savedComponentFilters: Array<ComponentFilter> = getDefaultComponentFilters();
@@ -48,10 +48,6 @@ function debug(methodName: string, ...args) {
}
export function connectToDevTools(options: ?ConnectOptions) {
if (hook == null) {
// DevTools didn't get injected into this page (maybe b'c of the contentType).
return;
}
const {
host = 'localhost',
nativeStyleEditorValidAttributes,

View File

@@ -1,8 +0,0 @@
<ol>
<li><a href="ReactDevTools.zip">download extension</a></li>
<li>Double-click to extract</li>
<li>Navigate to <code>edge://extensions/</code></li>
<li>Enable "Developer mode"</li>
<li>Click "LOAD UNPACKED"</li>
<li>Select extracted extension folder (<code>ReactDevTools</code>)</li>
</ol>

View File

@@ -1,12 +0,0 @@
# The Microsoft Edge extension
The source code for this extension has moved to `shells/webextension`.
Modify the source code there and then rebuild this extension by running `node build` from this directory or `yarn run build:extension:edge` from the root directory.
## Testing in Microsoft Edge
You can test a local build of the web extension like so:
1. Build the extension: `node build`
1. Follow the on-screen instructions.

View File

@@ -1,46 +0,0 @@
#!/usr/bin/env node
'use strict';
const chalk = require('chalk');
const {execSync} = require('child_process');
const {join} = require('path');
const {argv} = require('yargs');
const build = require('../build');
const main = async () => {
const {crx} = argv;
await build('edge');
const cwd = join(__dirname, 'build');
if (crx) {
const crxPath = join(
__dirname,
'..',
'..',
'..',
'node_modules',
'.bin',
'crx'
);
execSync(`${crxPath} pack ./unpacked -o ReactDevTools.crx`, {
cwd,
});
}
console.log(chalk.green('\nThe Microsoft Edge extension has been built!'));
console.log(chalk.green('\nTo load this extension:'));
console.log(chalk.yellow('Navigate to edge://extensions/'));
console.log(chalk.yellow('Enable "Developer mode"'));
console.log(chalk.yellow('Click "LOAD UNPACKED"'));
console.log(chalk.yellow('Select extension folder - ' + cwd + '\\unpacked'));
console.log(chalk.green('\nYou can test this build by running:'));
console.log(chalk.gray('\n# From the react-devtools root directory:'));
console.log('yarn run test:edge\n');
};
main();

View File

@@ -1,9 +0,0 @@
#!/usr/bin/env node
'use strict';
const deploy = require('../deploy');
const main = async () => await deploy('edge');
main();

View File

@@ -1,52 +0,0 @@
{
"manifest_version": 2,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Microsoft Edge Developer Tools.",
"version": "4.4.0",
"version_name": "4.4.0",
"minimum_chrome_version": "49",
"icons": {
"16": "icons/16-production.png",
"32": "icons/32-production.png",
"48": "icons/48-production.png",
"128": "icons/128-production.png"
},
"browser_action": {
"default_icon": {
"16": "icons/16-disabled.png",
"32": "icons/32-disabled.png",
"48": "icons/48-disabled.png",
"128": "icons/128-disabled.png"
},
"default_popup": "popups/disabled.html"
},
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"web_accessible_resources": [
"main.html",
"panel.html",
"build/react_devtools_backend.js",
"build/renderer.js"
],
"background": {
"scripts": ["build/background.js"],
"persistent": false
},
"permissions": ["file:///*", "http://*/*", "https://*/*"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["build/injectGlobalHook.js"],
"run_at": "document_start"
}
]
}

View File

@@ -1,5 +0,0 @@
{
"name": "react-devtools-experimental-edge",
"alias": ["react-devtools-experimental-edge"],
"files": ["index.html", "ReactDevTools.zip"]
}

View File

@@ -1,29 +0,0 @@
#!/usr/bin/env node
'use strict';
const open = require('open');
const os = require('os');
const osName = require('os-name');
const START_URL = 'https://facebook.github.io/react/';
const {resolve} = require('path');
const EXTENSION_PATH = resolve('./edge/build/unpacked');
const extargs = `--load-extension=${EXTENSION_PATH}`;
const osname = osName(os.platform());
let appname;
if (osname && osname.toLocaleLowerCase().startsWith('windows')) {
appname = 'msedge';
} else if (osname && osname.toLocaleLowerCase().startsWith('mac')) {
appname = 'Microsoft Edge';
} else if (osname && osname.toLocaleLowerCase().startsWith('linux')) {
//Coming soon
}
if (appname) {
(async () => {
await open(START_URL, {app: [appname, extargs]});
})();
}

View File

@@ -3,19 +3,15 @@
"version": "0.0.0",
"private": true,
"scripts": {
"build": "cross-env NODE_ENV=production yarn run build:chrome && yarn run build:firefox && yarn run build:edge",
"build:dev": "cross-env NODE_ENV=development yarn run build:chrome && yarn run build:firefox && yarn run build:edge",
"build": "cross-env NODE_ENV=production yarn run build:chrome && yarn run build:firefox",
"build:dev": "cross-env NODE_ENV=development yarn run build:chrome && yarn run build:firefox",
"build:chrome": "cross-env NODE_ENV=production node ./chrome/build",
"build:chrome:crx": "cross-env NODE_ENV=production node ./chrome/build --crx",
"build:chrome:dev": "cross-env NODE_ENV=development node ./chrome/build",
"build:firefox": "cross-env NODE_ENV=production node ./firefox/build",
"build:firefox:dev": "cross-env NODE_ENV=development node ./firefox/build",
"build:edge": "cross-env NODE_ENV=production node ./edge/build",
"build:edge:crx": "cross-env NODE_ENV=production node ./edge/build --crx",
"build:edge:dev": "cross-env NODE_ENV=development node ./edge/build",
"test:chrome": "node ./chrome/test",
"test:firefox": "node ./firefox/test",
"test:edge": "node ./edge/test"
"test:firefox": "node ./firefox/test"
},
"devDependencies": {
"@babel/core": "^7.1.6",
@@ -36,8 +32,6 @@
"firefox-profile": "^1.0.2",
"node-libs-browser": "0.5.3",
"nullthrows": "^1.0.0",
"open": "^7.0.2",
"os-name": "^3.1.0",
"raw-loader": "^3.1.0",
"style-loader": "^0.23.1",
"web-ext": "^3.0.0",

View File

@@ -22,10 +22,6 @@ function welcome(event) {
window.addEventListener('message', welcome);
function setup(hook) {
if (hook == null) {
// DevTools didn't get injected into this page (maybe b'c of the contentType).
return;
}
const Agent = require('react-devtools-shared/src/backend/agent').default;
const Bridge = require('react-devtools-shared/src/bridge').default;
const {initBackend} = require('react-devtools-shared/src/backend');

View File

@@ -88,7 +88,7 @@ if (sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
// Inject a __REACT_DEVTOOLS_GLOBAL_HOOK__ global for React to interact with.
// Only do this for HTML documents though, to avoid e.g. breaking syntax highlighting for XML docs.
if ('text/html' === document.contentType) {
if (document.contentType === 'text/html') {
injectCode(
';(' +
installHook.toString() +

View File

@@ -271,7 +271,7 @@ function createPanelIfReactLoaded() {
// When the user chooses a different node in the browser Elements tab,
// copy it over to the hook object so that we can sync the selection.
chrome.devtools.inspectedWindow.eval(
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' +
'false',
(didSelectionChange, evalError) => {
@@ -355,9 +355,11 @@ function createPanelIfReactLoaded() {
// It's easiest to recreate the DevTools panel (to clean up potential stale state).
// We can revisit this in the future as a small optimization.
flushSync(() => root.unmount());
initBridgeAndStore();
flushSync(() => {
root.unmount(() => {
initBridgeAndStore();
});
});
});
},
);

View File

@@ -73,18 +73,17 @@ function finishActivation(contentWindow: window) {
const agent = new Agent(bridge);
const hook = contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__;
if (hook) {
initBackend(hook, agent, contentWindow);
// Setup React Native style editor if a renderer like react-native-web has injected it.
if (hook.resolveRNStyle) {
setupNativeStyleEditor(
bridge,
agent,
hook.resolveRNStyle,
hook.nativeStyleEditorValidAttributes,
);
}
initBackend(hook, agent, contentWindow);
// Setup React Native style editor if a renderer like react-native-web has injected it.
if (hook.resolveRNStyle) {
setupNativeStyleEditor(
bridge,
agent,
hook.resolveRNStyle,
hook.nativeStyleEditorValidAttributes,
);
}
}

View File

@@ -1,7 +1,6 @@
/** @flow */
import * as React from 'react';
import {forwardRef} from 'react';
import React, {forwardRef} from 'react';
import Bridge from 'react-devtools-shared/src/bridge';
import Store from 'react-devtools-shared/src/devtools/store';
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';

View File

@@ -15,7 +15,6 @@
"local-storage-fallback": "^4.1.1",
"lodash.throttle": "^4.1.1",
"memoize-one": "^3.1.1",
"react-virtualized-auto-sizer": "^1.0.2",
"semver": "^6.3.0"
"react-virtualized-auto-sizer": "^1.0.2"
}
}

View File

@@ -1472,103 +1472,4 @@ describe('InspectedElementContext', () => {
done();
});
it('should enable complex values to be copied to the clipboard', async done => {
const Immutable = require('immutable');
const Example = () => null;
const set = new Set(['abc', 123]);
const map = new Map([
['name', 'Brian'],
['food', 'sushi'],
]);
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
const mapOfMaps = new Map([
['first', map],
['second', map],
]);
const typedArray = Int8Array.from([100, -100, 0]);
const arrayBuffer = typedArray.buffer;
const dataView = new DataView(arrayBuffer);
const immutable = Immutable.fromJS({
a: [{hello: 'there'}, 'fixed', true],
b: 123,
c: {
'1': 'xyz',
xyz: 1,
},
});
// $FlowFixMe
const bigInt = BigInt(123); // eslint-disable-line no-undef
await utils.actAsync(() =>
ReactDOM.render(
<Example
arrayBuffer={arrayBuffer}
dataView={dataView}
map={map}
set={set}
mapOfMaps={mapOfMaps}
setOfSets={setOfSets}
typedArray={typedArray}
immutable={immutable}
bigInt={bigInt}
/>,
document.createElement('div'),
),
);
const id = ((store.getElementIDAtIndex(0): any): number);
let copyPath: CopyInspectedElementPath = ((null: any): CopyInspectedElementPath);
function Suspender({target}) {
const context = React.useContext(InspectedElementContext);
copyPath = context.copyInspectedElementPath;
return null;
}
await utils.actAsync(
() =>
TestRenderer.create(
<Contexts
defaultSelectedElementID={id}
defaultSelectedElementIndex={0}>
<React.Suspense fallback={null}>
<Suspender target={id} />
</React.Suspense>
</Contexts>,
),
false,
);
expect(copyPath).not.toBeNull();
// Should copy the whole value (not just the hydrated parts)
copyPath(id, ['props']);
jest.runOnlyPendingTimers();
// Should not error despite lots of unserialized values.
global.mockClipboardCopy.mockReset();
// Should copy the nested property specified (not just the outer value)
copyPath(id, ['props', 'bigInt']);
jest.runOnlyPendingTimers();
expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
expect(global.mockClipboardCopy).toHaveBeenCalledWith(
JSON.stringify('123n'),
);
global.mockClipboardCopy.mockReset();
// Should copy the nested property specified (not just the outer value)
copyPath(id, ['props', 'typedArray']);
jest.runOnlyPendingTimers();
expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
expect(global.mockClipboardCopy).toHaveBeenCalledWith(
JSON.stringify({0: 100, 1: -100, 2: 0}),
);
done();
});
});

View File

@@ -589,91 +589,4 @@ describe('InspectedElementContext', () => {
JSON.stringify(nestedObject.a.b),
);
});
it('should enable complex values to be copied to the clipboard', () => {
const Immutable = require('immutable');
const Example = () => null;
const set = new Set(['abc', 123]);
const map = new Map([
['name', 'Brian'],
['food', 'sushi'],
]);
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
const mapOfMaps = new Map([
['first', map],
['second', map],
]);
const typedArray = Int8Array.from([100, -100, 0]);
const arrayBuffer = typedArray.buffer;
const dataView = new DataView(arrayBuffer);
const immutable = Immutable.fromJS({
a: [{hello: 'there'}, 'fixed', true],
b: 123,
c: {
'1': 'xyz',
xyz: 1,
},
});
// $FlowFixMe
const bigInt = BigInt(123); // eslint-disable-line no-undef
act(() =>
ReactDOM.render(
<Example
arrayBuffer={arrayBuffer}
dataView={dataView}
map={map}
set={set}
mapOfMaps={mapOfMaps}
setOfSets={setOfSets}
typedArray={typedArray}
immutable={immutable}
bigInt={bigInt}
/>,
document.createElement('div'),
),
);
const id = ((store.getElementIDAtIndex(0): any): number);
const rendererID = ((store.getRendererIDForElement(id): any): number);
// Should copy the whole value (not just the hydrated parts)
bridge.send('copyElementPath', {
id,
path: ['props'],
rendererID,
});
jest.runOnlyPendingTimers();
// Should not error despite lots of unserialized values.
global.mockClipboardCopy.mockReset();
// Should copy the nested property specified (not just the outer value)
bridge.send('copyElementPath', {
id,
path: ['props', 'bigInt'],
rendererID,
});
jest.runOnlyPendingTimers();
expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
expect(global.mockClipboardCopy).toHaveBeenCalledWith(
JSON.stringify('123n'),
);
global.mockClipboardCopy.mockReset();
// Should copy the nested property specified (not just the outer value)
bridge.send('copyElementPath', {
id,
path: ['props', 'typedArray'],
rendererID,
});
jest.runOnlyPendingTimers();
expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
expect(global.mockClipboardCopy).toHaveBeenCalledWith(
JSON.stringify({0: 100, 1: -100, 2: 0}),
);
});
});

View File

@@ -19,10 +19,6 @@ export function initBackend(
agent: Agent,
global: Object,
): () => void {
if (hook == null) {
// DevTools didn't get injected into this page (maybe b'c of the contentType).
return () => {};
}
const subs = [
hook.sub(
'renderer-attached',

View File

@@ -81,10 +81,6 @@ export function serializeToString(data: any): string {
}
cache.add(value);
}
// $FlowFixMe
if (typeof value === 'bigint') {
return value.toString() + 'n';
}
return value;
});
}

View File

@@ -7,8 +7,13 @@
* @flow
*/
import * as React from 'react';
import {useContext, useEffect, useLayoutEffect, useRef, useState} from 'react';
import React, {
useContext,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import {createPortal} from 'react-dom';
import {RegistryContext} from './Contexts';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {useContext} from 'react';
import React, {useContext} from 'react';
import {RegistryContext} from './Contexts';
import styles from './ContextMenuItem.css';

View File

@@ -19,22 +19,18 @@ export default function useContextMenu({
}: {|
data: Object,
id: string,
ref: {current: ElementRef<'div'> | null},
ref: ElementRef<HTMLElement>,
|}) {
const {showMenu} = useContext(RegistryContext);
useEffect(() => {
if (ref.current !== null) {
const handleContextMenu = (event: MouseEvent | TouchEvent) => {
const handleContextMenu = event => {
event.preventDefault();
event.stopPropagation();
const pageX =
event.pageX ||
(event.touches && ((event: any): TouchEvent).touches[0].pageX);
const pageY =
event.pageY ||
(event.touches && ((event: any): TouchEvent).touches[0].pageY);
const pageX = event.pageX || (event.touches && event.touches[0].pageX);
const pageY = event.pageY || (event.touches && event.touches[0].pageY);
showMenu({data, id, pageX, pageY});
};

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {createContext} from 'react';
import React, {createContext} from 'react';
// Cache implementation was forked from the React repo:
// https://github.com/facebook/react/blob/master/packages/react-cache/src/ReactCache.js

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import Tooltip from '@reach/tooltip';
import styles from './Button.css';

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import styles from './ButtonIcon.css';
export type IconType =

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {Fragment} from 'react';
import React, {Fragment} from 'react';
import {
ElementTypeMemo,
ElementTypeForwardRef,

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {Suspense} from 'react';
import React, {Suspense} from 'react';
import Tree from './Tree';
import SelectedElement from './SelectedElement';
import {InspectedElementContextController} from './InspectedElementContext';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {useCallback, useState} from 'react';
import React, {useCallback, useState} from 'react';
import AutoSizeInput from './NativeStyleEditor/AutoSizeInput';
import styles from './EditableName.css';

View File

@@ -7,8 +7,9 @@
* @flow
*/
import * as React from 'react';
import {Fragment, useRef} from 'react';
import React, {Fragment, useRef} from 'react';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import styles from './EditableValue.css';
import {useEditableValue} from '../hooks';
@@ -50,7 +51,9 @@ export default function EditableValue({
switch (event.key) {
case 'Enter':
applyChanges();
if (isValid && hasPendingChanges) {
overrideValueFn(path, parsedValue);
}
break;
case 'Escape':
reset();
@@ -60,12 +63,6 @@ export default function EditableValue({
}
};
const applyChanges = () => {
if (isValid && hasPendingChanges) {
overrideValueFn(path, parsedValue);
}
};
let placeholder = '';
if (editableValue === undefined) {
placeholder = '(undefined)';
@@ -78,7 +75,6 @@ export default function EditableValue({
<input
autoComplete="new-password"
className={`${isValid ? styles.Input : styles.Invalid} ${className}`}
onBlur={applyChanges}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
@@ -86,6 +82,14 @@ export default function EditableValue({
type="text"
value={editableValue}
/>
{hasPendingChanges && (
<Button
className={styles.ResetButton}
onClick={reset}
title="Reset value">
<ButtonIcon type="undo" />
</Button>
)}
</Fragment>
);
}

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {Fragment, useContext, useMemo, useState} from 'react';
import React, {Fragment, useContext, useMemo, useState} from 'react';
import Store from 'react-devtools-shared/src/devtools/store';
import Badge from './Badge';
import ButtonIcon from '../ButtonIcon';

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import {
ElementTypeForwardRef,
ElementTypeMemo,

View File

@@ -8,8 +8,7 @@
*/
import {copy} from 'clipboard-js';
import * as React from 'react';
import {useCallback, useContext, useRef, useState} from 'react';
import React, {useCallback, useContext, useRef, useState} from 'react';
import {BridgeContext, StoreContext} from '../context';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {useCallback, useContext, useEffect, useState} from 'react';
import React, {useCallback, useContext, useEffect, useState} from 'react';
import {BridgeContext} from '../context';
import Toggle from '../Toggle';
import ButtonIcon from '../ButtonIcon';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {
import React, {
createContext,
useCallback,
useContext,

View File

@@ -8,8 +8,7 @@
*/
import {copy} from 'clipboard-js';
import * as React from 'react';
import {useCallback, useState} from 'react';
import React, {useCallback, useState} from 'react';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import KeyValue from './KeyValue';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {useEffect, useRef, useState} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import EditableValue from './EditableValue';
import ExpandCollapseToggle from './ExpandCollapseToggle';
import {alphaSortEntries, getMetaValueLabel} from '../utils';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {Fragment, useLayoutEffect, useRef} from 'react';
import React, {Fragment, useLayoutEffect, useRef} from 'react';
import styles from './AutoSizeInput.css';
type Props = {

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import styles from './LayoutViewer.css';
import type {Layout} from './types';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {useContext, useMemo, useRef, useState} from 'react';
import React, {useContext, useMemo, useRef, useState} from 'react';
import {unstable_batchedUpdates as batchedUpdates} from 'react-dom';
import {copy} from 'clipboard-js';
import {

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {
import React, {
createContext,
useCallback,
useContext,

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {Fragment, useContext, useMemo} from 'react';
import React, {Fragment, useContext, useMemo} from 'react';
import {StoreContext} from 'react-devtools-shared/src/devtools/views/context';
import {useSubscription} from 'react-devtools-shared/src/devtools/views/hooks';
import {NativeStyleContext} from './context';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {createContext, useCallback, useContext, useEffect} from 'react';
import React, {createContext, useCallback, useContext, useEffect} from 'react';
import {createResource} from '../../cache';
import {BridgeContext, StoreContext} from '../context';
import {TreeStateContext} from './TreeContext';

View File

@@ -6,8 +6,7 @@
*
* @flow
*/
import * as React from 'react';
import {
import React, {
Fragment,
useCallback,
useContext,

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {useCallback, useContext, useEffect, useRef} from 'react';
import React, {useCallback, useContext, useEffect, useRef} from 'react';
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';

View File

@@ -8,8 +8,7 @@
*/
import {copy} from 'clipboard-js';
import * as React from 'react';
import {Fragment, useCallback, useContext} from 'react';
import React, {Fragment, useCallback, useContext} from 'react';
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
import {BridgeContext, ContextMenuContext, StoreContext} from '../context';
import ContextMenu from '../../ContextMenu/ContextMenu';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {useContext, useMemo} from 'react';
import React, {useContext, useMemo} from 'react';
import {TreeStateContext} from './TreeContext';
import {SettingsContext} from '../Settings/SettingsContext';
import TreeFocusedContext from './TreeFocusedContext';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {
import React, {
Fragment,
Suspense,
useCallback,

View File

@@ -24,8 +24,7 @@
// For this reason, changes to the tree context are processed in sequence: tree -> search -> owners
// This enables each section to potentially override (or mask) previous values.
import * as React from 'react';
import {
import React, {
createContext,
useCallback,
useContext,

View File

@@ -12,8 +12,7 @@
import '@reach/menu-button/styles.css';
import '@reach/tooltip/styles.css';
import * as React from 'react';
import {useEffect, useMemo, useState} from 'react';
import React, {useEffect, useMemo, useState} from 'react';
import Store from '../store';
import {BridgeContext, ContextMenuContext, StoreContext} from './context';
import Components from './Components/Components';

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {Component} from 'react';
import React, {Component} from 'react';
import styles from './ErrorBoundary.css';
type Props = {|

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import styles from './Icon.css';
export type IconType =

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {
import React, {
createContext,
useCallback,
useContext,

View File

@@ -7,7 +7,7 @@
* @flow
*/
import * as React from 'react';
import React from 'react';
import styles from './ChartNode.css';
@@ -18,8 +18,6 @@ type Props = {|
label: string,
onClick: (event: SyntheticMouseEvent<*>) => mixed,
onDoubleClick?: (event: SyntheticMouseEvent<*>) => mixed,
onMouseEnter: (event: SyntheticMouseEvent<*>) => mixed,
onMouseLeave: (event: SyntheticMouseEvent<*>) => mixed,
placeLabelAboveNode?: boolean,
textStyle?: Object,
width: number,
@@ -35,8 +33,6 @@ export default function ChartNode({
isDimmed = false,
label,
onClick,
onMouseEnter,
onMouseLeave,
onDoubleClick,
textStyle,
width,
@@ -45,13 +41,12 @@ export default function ChartNode({
}: Props) {
return (
<g className={styles.Group} transform={`translate(${x},${y})`}>
<title>{label}</title>
<rect
width={width}
height={height}
fill={color}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onDoubleClick={onDoubleClick}
className={styles.Rect}
style={{

View File

@@ -7,8 +7,7 @@
* @flow
*/
import * as React from 'react';
import {useCallback, useContext} from 'react';
import React, {useCallback, useContext} from 'react';
import {ProfilerContext} from './ProfilerContext';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';

View File

@@ -7,28 +7,23 @@
* @flow
*/
import * as React from 'react';
import {forwardRef, useCallback, useContext, useMemo, useState} from 'react';
import React, {forwardRef, useCallback, useContext, useMemo} from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import {FixedSizeList} from 'react-window';
import {ProfilerContext} from './ProfilerContext';
import NoCommitData from './NoCommitData';
import CommitFlamegraphListItem from './CommitFlamegraphListItem';
import HoveredFiberInfo from './HoveredFiberInfo';
import {scale} from './utils';
import {StoreContext} from '../context';
import {SettingsContext} from '../Settings/SettingsContext';
import Tooltip from './Tooltip';
import styles from './CommitFlamegraph.css';
import type {TooltipFiberData} from './HoveredFiberInfo';
import type {ChartData, ChartNode} from './FlamegraphChartBuilder';
import type {CommitTree} from './types';
export type ItemData = {|
chartData: ChartData,
hoverFiber: (fiberData: TooltipFiberData | null) => void,
scaleX: (value: number, fallbackValue: number) => number,
selectedChartNode: ChartNode | null,
selectedChartNodeIndex: number,
@@ -96,7 +91,6 @@ type Props = {|
|};
function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
const [hoveredFiberData, hoverFiber] = useState<number | null>(null);
const {lineHeight} = useContext(SettingsContext);
const {selectFiber, selectedFiberID} = useContext(ProfilerContext);
@@ -124,7 +118,6 @@ function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
const itemData = useMemo<ItemData>(
() => ({
chartData,
hoverFiber,
scaleX: scale(
0,
selectedChartNode !== null
@@ -138,37 +131,19 @@ function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
selectFiber,
width,
}),
[
chartData,
hoverFiber,
selectedChartNode,
selectedChartNodeIndex,
selectFiber,
width,
],
);
// Tooltip used to show summary of fiber info on hover
const tooltipLabel = useMemo(
() =>
hoveredFiberData !== null ? (
<HoveredFiberInfo fiberData={hoveredFiberData} />
) : null,
[hoveredFiberData],
[chartData, selectedChartNode, selectedChartNodeIndex, selectFiber, width],
);
return (
<Tooltip label={tooltipLabel}>
<FixedSizeList
height={height}
innerElementType={InnerElementType}
itemCount={chartData.depth}
itemData={itemData}
itemSize={lineHeight}
width={width}>
{CommitFlamegraphListItem}
</FixedSizeList>
</Tooltip>
<FixedSizeList
height={height}
innerElementType={InnerElementType}
itemCount={chartData.depth}
itemData={itemData}
itemSize={lineHeight}
width={width}>
{CommitFlamegraphListItem}
</FixedSizeList>
);
}

View File

@@ -7,15 +7,13 @@
* @flow
*/
import * as React from 'react';
import {Fragment, memo, useCallback, useContext} from 'react';
import React, {Fragment, memo, useCallback, useContext} from 'react';
import {areEqual} from 'react-window';
import {barWidthThreshold} from './constants';
import {getGradientColor} from './utils';
import ChartNode from './ChartNode';
import {SettingsContext} from '../Settings/SettingsContext';
import type {ChartNode as ChartNodeType} from './FlamegraphChartBuilder';
import type {ItemData} from './CommitFlamegraph';
type Props = {
@@ -28,7 +26,6 @@ type Props = {
function CommitFlamegraphListItem({data, index, style}: Props) {
const {
chartData,
hoverFiber,
scaleX,
selectedChartNode,
selectedChartNodeIndex,
@@ -38,7 +35,6 @@ function CommitFlamegraphListItem({data, index, style}: Props) {
const {renderPathNodes, maxSelfDuration, rows} = chartData;
const {lineHeight} = useContext(SettingsContext);
const handleClick = useCallback(
(event: SyntheticMouseEvent<*>, id: number, name: string) => {
event.stopPropagation();
@@ -47,15 +43,6 @@ function CommitFlamegraphListItem({data, index, style}: Props) {
[selectFiber],
);
const handleMouseEnter = (nodeData: ChartNodeType) => {
const {id, name} = nodeData;
hoverFiber({id, name});
};
const handleMouseLeave = () => {
hoverFiber(null);
};
// List items are absolutely positioned using the CSS "top" attribute.
// The "left" value will always be 0.
// Since height is fixed, and width is based on the node's duration,
@@ -117,8 +104,6 @@ function CommitFlamegraphListItem({data, index, style}: Props) {
key={id}
label={label}
onClick={event => handleClick(event, id, name)}
onMouseEnter={() => handleMouseEnter(chartNode)}
onMouseLeave={handleMouseLeave}
textStyle={{color: textColor}}
width={nodeWidth}
x={nodeOffset - selectedNodeOffset}

Some files were not shown because too many files have changed in this diff Show More