Skip to content

Commit 4575801

Browse files
authored
Merge pull request #146 from lencioni/variable-assignment
Remove newly unreferenced VariableDeclarators
2 parents b2f03bb + f4b25a3 commit 4575801

File tree

14 files changed

+464
-12
lines changed

14 files changed

+464
-12
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@babel/generator": "7.0.0-beta.42",
3737
"@babel/plugin-external-helpers": "7.0.0-beta.42",
3838
"@babel/plugin-proposal-class-properties": "7.0.0-beta.42",
39+
"@babel/plugin-proposal-object-rest-spread": "^7.0.0-rc.1",
3940
"@babel/plugin-transform-flow-strip-types": "7.0.0-beta.42",
4041
"@babel/preset-env": "7.0.0-beta.42",
4142
"@babel/preset-flow": "7.0.0-beta.42",

src/index.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,43 @@ function isReactClass(superClass, scope, globalOptions) {
5353
return answer
5454
}
5555

56+
function areSetsEqual(set1, set2) {
57+
if (set1 === set2) {
58+
return true
59+
}
60+
61+
if (set1.size !== set2.size) {
62+
return false
63+
}
64+
65+
return !Array.from(set1).some(item => !set2.has(item))
66+
}
67+
5668
export default function(api) {
5769
const { template, types, traverse } = api
5870

71+
const nestedIdentifiers = new Set()
72+
const removedPaths = new Set()
73+
const collectNestedIdentifiers = {
74+
Identifier(path) {
75+
if (path.parent.type === 'MemberExpression') {
76+
// foo.bar
77+
return
78+
}
79+
80+
if (
81+
path.parent.type === 'ObjectProperty' &&
82+
(path.parent.key === path.node || path.parent.shorthand)
83+
) {
84+
// { foo: 'bar' }
85+
// { foo }
86+
return
87+
}
88+
89+
nestedIdentifiers.add(path.node.name)
90+
},
91+
}
92+
5993
return {
6094
visitor: {
6195
Program(programPath, state) {
@@ -146,6 +180,8 @@ export default function(api) {
146180
})
147181

148182
if (parent) {
183+
path.traverse(collectNestedIdentifiers)
184+
removedPaths.add(path)
149185
remove(path, globalOptions, {
150186
type: 'createClass',
151187
})
@@ -160,6 +196,8 @@ export default function(api) {
160196
const pathClassDeclaration = scope.path
161197

162198
if (isReactClass(pathClassDeclaration.get('superClass'), scope, globalOptions)) {
199+
path.traverse(collectNestedIdentifiers)
200+
removedPaths.add(path)
163201
remove(path, globalOptions, {
164202
type: 'class static',
165203
pathClassDeclaration,
@@ -181,6 +219,8 @@ export default function(api) {
181219
const forceRemoval = isAnnotatedForRemoval(path.node.left)
182220

183221
if (forceRemoval) {
222+
path.traverse(collectNestedIdentifiers)
223+
removedPaths.add(path)
184224
remove(path, globalOptions, { type: 'assign' })
185225
return
186226
}
@@ -196,14 +236,61 @@ export default function(api) {
196236
const superClass = binding.path.get('superClass')
197237

198238
if (isReactClass(superClass, scope, globalOptions)) {
239+
path.traverse(collectNestedIdentifiers)
240+
removedPaths.add(path)
199241
remove(path, globalOptions, { type: 'assign' })
200242
}
201243
} else if (isStatelessComponent(binding.path)) {
244+
path.traverse(collectNestedIdentifiers)
245+
removedPaths.add(path)
202246
remove(path, globalOptions, { type: 'assign' })
203247
}
204248
},
205249
})
206250

251+
let skippedIdentifiers = 0
252+
const removeNewlyUnusedIdentifiers = {
253+
VariableDeclarator(path) {
254+
if (!nestedIdentifiers.has(path.node.id.name)) {
255+
return
256+
}
257+
258+
const { referencePaths } = path.scope.getBinding(path.node.id.name)
259+
260+
// Count the number of referencePaths that are not in the
261+
// removedPaths Set. We need to do this in order to support the wrap
262+
// option, which doesn't actually remove the references.
263+
const hasRemainingReferencePaths = referencePaths.some(referencePath => {
264+
const found = referencePath.find(p => removedPaths.has(p))
265+
return !found
266+
})
267+
268+
if (hasRemainingReferencePaths) {
269+
// There are still references to this identifier, so we need to
270+
// skip over it for now.
271+
skippedIdentifiers += 1
272+
return
273+
}
274+
275+
removedPaths.add(path)
276+
nestedIdentifiers.delete(path.node.id.name)
277+
path.get('init').traverse(collectNestedIdentifiers)
278+
remove(path, globalOptions, { type: 'declarator' })
279+
},
280+
}
281+
282+
let lastNestedIdentifiers = new Set()
283+
while (
284+
!areSetsEqual(nestedIdentifiers, lastNestedIdentifiers) &&
285+
nestedIdentifiers.size > 0 &&
286+
skippedIdentifiers < nestedIdentifiers.size
287+
) {
288+
lastNestedIdentifiers = new Set(nestedIdentifiers)
289+
skippedIdentifiers = 0
290+
programPath.scope.crawl()
291+
programPath.traverse(removeNewlyUnusedIdentifiers)
292+
}
293+
207294
if (globalOptions.removeImport) {
208295
if (globalOptions.mode === 'remove') {
209296
programPath.scope.crawl()

src/remove.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,24 @@ export default function remove(path, globalOptions, options) {
101101
path.node[visitedKey] = true
102102
break
103103

104+
case 'declarator':
105+
if (mode === 'unsafe-wrap') {
106+
path.replaceWith(
107+
unsafeWrapTemplate({
108+
NODE: path.node,
109+
})
110+
)
111+
} else {
112+
path.replaceWith(
113+
wrapTemplate({
114+
LEFT: path.node.id,
115+
RIGHT: path.node.init,
116+
})
117+
)
118+
}
119+
path.node[visitedKey] = true
120+
break
121+
104122
default:
105123
break
106124
}

test/fixtures.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { transformFileSync } from '@babel/core'
99
import babelPluginSyntaxJsx from '@babel/plugin-syntax-jsx'
1010
import babelPluginExternalHelpers from '@babel/plugin-external-helpers'
1111
import babelPluginTransformClassProperties from '@babel/plugin-proposal-class-properties'
12+
import babelPluginProposalObjectRestSpread from '@babel/plugin-proposal-object-rest-spread'
1213
import babelPluginTransformReactRemovePropTypes from '../src/index'
1314
import { trim } from './utils'
1415

@@ -55,6 +56,7 @@ describe('fixtures', () => {
5556
babelConfig = {
5657
plugins: [
5758
babelPluginExternalHelpers,
59+
babelPluginProposalObjectRestSpread,
5860
[
5961
babelPluginTransformReactRemovePropTypes,
6062
{
@@ -70,6 +72,7 @@ describe('fixtures', () => {
7072
babelConfig = {
7173
plugins: [
7274
babelPluginExternalHelpers,
75+
babelPluginProposalObjectRestSpread,
7376
[
7477
babelPluginTransformReactRemovePropTypes,
7578
{
@@ -88,6 +91,7 @@ describe('fixtures', () => {
8891
babelPluginExternalHelpers,
8992
babelPluginSyntaxJsx,
9093
babelPluginTransformClassProperties,
94+
babelPluginProposalObjectRestSpread,
9195
[
9296
babelPluginTransformReactRemovePropTypes,
9397
{
@@ -106,6 +110,7 @@ describe('fixtures', () => {
106110
babelPluginExternalHelpers,
107111
babelPluginSyntaxJsx,
108112
babelPluginTransformClassProperties,
113+
babelPluginProposalObjectRestSpread,
109114
[
110115
babelPluginTransformReactRemovePropTypes,
111116
{
@@ -121,6 +126,7 @@ describe('fixtures', () => {
121126
babelConfig = {
122127
plugins: [
123128
babelPluginExternalHelpers,
129+
babelPluginProposalObjectRestSpread,
124130
[babelPluginTransformReactRemovePropTypes, options],
125131
],
126132
}

test/fixtures/es-class-assign-property-variable/actual.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,13 @@ class Foo extends React.Component {
77
}
88

99
Foo.propTypes = propTypes;
10+
11+
class Getter extends React.Component {
12+
get foo() {
13+
return { foo: PropTypes.string };
14+
}
15+
16+
render() {}
17+
}
18+
19+
Getter.propTypes = Getter.foo;

test/fixtures/es-class-assign-property-variable/expected-remove-es5.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
"use strict";
22

3-
var propTypes = {
4-
foo: PropTypes.string
5-
};
6-
73
var Foo =
84
/*#__PURE__*/
95
function (_React$Component) {
@@ -20,3 +16,27 @@ function (_React$Component) {
2016
}]);
2117
return Foo;
2218
}(React.Component);
19+
20+
var Getter =
21+
/*#__PURE__*/
22+
function (_React$Component2) {
23+
babelHelpers.inherits(Getter, _React$Component2);
24+
25+
function Getter() {
26+
babelHelpers.classCallCheck(this, Getter);
27+
return babelHelpers.possibleConstructorReturn(this, (Getter.__proto__ || Object.getPrototypeOf(Getter)).apply(this, arguments));
28+
}
29+
30+
babelHelpers.createClass(Getter, [{
31+
key: "render",
32+
value: function render() {}
33+
}, {
34+
key: "foo",
35+
get: function get() {
36+
return {
37+
foo: PropTypes.string
38+
};
39+
}
40+
}]);
41+
return Getter;
42+
}(React.Component);
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
const propTypes = {
2-
foo: PropTypes.string
3-
};
4-
51
class Foo extends React.Component {
62
render() {}
73

84
}
5+
6+
class Getter extends React.Component {
7+
get foo() {
8+
return {
9+
foo: PropTypes.string
10+
};
11+
}
12+
13+
render() {}
14+
15+
}

test/fixtures/es-class-assign-property-variable/expected-wrap-es5.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use strict";
22

3-
var propTypes = {
3+
var propTypes = process.env.NODE_ENV !== "production" ? {
44
foo: PropTypes.string
5-
};
5+
} : {};;
66

77
var Foo =
88
/*#__PURE__*/
@@ -22,3 +22,29 @@ function (_React$Component) {
2222
}(React.Component);
2323

2424
Foo.propTypes = process.env.NODE_ENV !== "production" ? propTypes : {};
25+
26+
var Getter =
27+
/*#__PURE__*/
28+
function (_React$Component2) {
29+
babelHelpers.inherits(Getter, _React$Component2);
30+
31+
function Getter() {
32+
babelHelpers.classCallCheck(this, Getter);
33+
return babelHelpers.possibleConstructorReturn(this, (Getter.__proto__ || Object.getPrototypeOf(Getter)).apply(this, arguments));
34+
}
35+
36+
babelHelpers.createClass(Getter, [{
37+
key: "render",
38+
value: function render() {}
39+
}, {
40+
key: "foo",
41+
get: function get() {
42+
return {
43+
foo: PropTypes.string
44+
};
45+
}
46+
}]);
47+
return Getter;
48+
}(React.Component);
49+
50+
Getter.propTypes = process.env.NODE_ENV !== "production" ? Getter.foo : {};
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1-
const propTypes = {
1+
const propTypes = process.env.NODE_ENV !== "production" ? {
22
foo: PropTypes.string
3-
};
3+
} : {};;
44

55
class Foo extends React.Component {
66
render() {}
77

88
}
99

1010
Foo.propTypes = process.env.NODE_ENV !== "production" ? propTypes : {};
11+
12+
class Getter extends React.Component {
13+
get foo() {
14+
return {
15+
foo: PropTypes.string
16+
};
17+
}
18+
19+
render() {}
20+
21+
}
22+
23+
Getter.propTypes = process.env.NODE_ENV !== "production" ? Getter.foo : {};

0 commit comments

Comments
 (0)