Description
Hey folks! I was taking the new JSX fragments syntax for a spin and noticed that there seems to be a glitch when it's used in combination with the --noUnusedLocals
flag. Looks like TypeScript doesn't recognize declared variables as "read" when they're used within a JSX fragment.
One particularly insidious consequence of this bug is that the compiler will strip out imports that it believes are unused---in the sample below, the import for MyComponent
is removed from the emitted code when it appears within a fragment, but not when it appears between (e.g.) <div />
s.
TypeScript Version: 2.7.0-dev.20171110
Code
~ ❯❯❯ tsc --jsx react --noUnusedLocals test.tsx
// test.tsx
import * as React from 'react'
import MyComponent from './my-component'
const MY_STRING: string = 'Ceci n\'est pas une string.'
const MY_CLASSNAME: string = 'jeclass'
class RenderString extends React.PureComponent {
render() {
return (
<>
<MyComponent />
<span>{ MY_STRING }</span>
<span className={ MY_CLASSNAME } />
</>
)
}
}
export default RenderString
Expected behavior:
TypeScript should recognize that MyComponent
, MY_STRING
, and MY_CLASSNAME
are used within the JSX fragment, and not return errors as a consequence of the --noUnusedLocals
compiler flag. Additionally, TypeScript should emit a require
call for ./my-component
.
Actual behavior:
The compiler flags MyComponent
, MY_STRING
, and MY_CLASSNAME
as unused...
~ ❯❯❯ tsc --jsx react --noUnusedLocals test.tsx
test.tsx(2,8): error TS6133: 'MyComponent' is declared but its value is never read.
test.tsx(4,7): error TS6133: 'MY_STRING' is declared but its value is never read.
test.tsx(5,7): error TS6133: 'MY_CLASSNAME' is declared but its value is never read.
...and strips the require('./my-component')
line from the emitted JS:
// with the TSX source file above...
var React = require("react");
// 🕵️♀️ Whither my require?
var MY_STRING = 'Ceci n\'est pas une string.';
var MY_CLASSNAME = 'jeclass';
var RenderString = /** @class */ (function (_super) {
__extends(RenderString, _super);
function RenderString() {
return _super !== null && _super.apply(this, arguments) || this;
}
RenderString.prototype.render = function () {
return (React.createElement(React.Fragment, null,
React.createElement(my_component_1["default"], null), // 🚫 I don't exist.
React.createElement("span", null, MY_STRING),
React.createElement("span", { className: MY_CLASSNAME })));
};
return RenderString;
}(React.PureComponent));
// ...compared to using <div />s in place of the fragments.
var React = require("react");
var my_component_1 = require("./my-component"); // 🎉Whew.
var MY_STRING = 'Ceci n\'est pas une string.';
var MY_CLASSNAME = 'jeclass';
var RenderString = /** @class */ (function (_super) {
__extends(RenderString, _super);
function RenderString() {
return _super !== null && _super.apply(this, arguments) || this;
}
RenderString.prototype.render = function () {
return (React.createElement("div", null,
React.createElement(my_component_1["default"], null), // 💃 I'm defined!
React.createElement("span", null, MY_STRING),
React.createElement("span", { className: MY_CLASSNAME })));
};
return RenderString;
}(React.PureComponent));