Skip to content

Commit 2a841ff

Browse files
committed
Add new resolver for exported ES6 class definitions
This resolver finds the exported ES6 class definitions representing a React component. To be considered a React component, a class must either have a `render()` method or extend `React.Component`. Note that the resolver cannot be used yet. The handlers have to be updated first to consume class definitions, which will happen in a separate commit. The resolver is able to handle ```js class Foo extends React.Component { } Foo.propTypes = { ... } ``` by normalizing the class definition to ``` class Foo extends React.Component { // ... static propTypes = { ... }; } ``` However, parsing components with class properties will only be possible after switching to Babel (esprima-fb doesn't support it).
1 parent 67109b9 commit 2a841ff

11 files changed

+1010
-0
lines changed
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
/*global jest, describe, beforeEach, it, expect*/
12+
13+
jest.autoMockOff();
14+
15+
describe('findExportedClassDefinition', () => {
16+
var findExportedClassDefinition;
17+
var recast;
18+
19+
function parse(source) {
20+
return findExportedClassDefinition(
21+
recast.parse(source).program,
22+
recast
23+
);
24+
}
25+
26+
beforeEach(() => {
27+
findExportedClassDefinition =
28+
require('../findExportedClassDefinition');
29+
recast = require('recast');
30+
});
31+
32+
describe('CommonJS module exports', () => {
33+
it('finds class declarations', () => {
34+
var source = `
35+
var React = require("React");
36+
class Component extends React.Component {}
37+
module.exports = Component;
38+
`;
39+
40+
var result = parse(source);
41+
expect(result).toBeDefined();
42+
expect(result.node.type).toBe('ClassDeclaration');
43+
});
44+
45+
it('finds class expression', () => {
46+
var source = `
47+
var React = require("React");
48+
var Component = class extends React.Component {}
49+
module.exports = Component;
50+
`;
51+
52+
var result = parse(source);
53+
expect(result).toBeDefined();
54+
expect(result.node.type).toBe('ClassExpression');
55+
});
56+
57+
it('finds class definition, independent of the var name', () => {
58+
var source = `
59+
var R = require("React");
60+
class Component extends R.Component {}
61+
module.exports = Component;
62+
`;
63+
64+
var result = parse(source);
65+
expect(result).toBeDefined();
66+
expect(result.node.type).toBe('ClassDeclaration');
67+
});
68+
69+
describe('module.exports = <C>; / exports.foo = <C>;', () => {
70+
71+
it('finds assignments to exports', () => {
72+
var source = `
73+
var R = require("React");
74+
class Component extends R.Component {}
75+
exports.foo = 42;
76+
exports.Component = Component;
77+
`;
78+
79+
var result = parse(source);
80+
expect(result).toBeDefined();
81+
expect(result.node.type).toBe('ClassDeclaration');
82+
});
83+
84+
it('errors if multiple components are exported', () => {
85+
var source = `
86+
var R = require("React");
87+
class ComponentA extends R.Component {}
88+
class ComponentB extends R.Component {}
89+
exports.ComponentA = ComponentA;
90+
exports.ComponentB = ComponentB;
91+
`;
92+
93+
expect(() => parse(source)).toThrow();
94+
});
95+
96+
it('accepts multiple definitions if only one is exported', () => {
97+
var source = `
98+
var R = require("React");
99+
class ComponentA extends R.Component {}
100+
class ComponentB extends R.Component {}
101+
exports.ComponentB = ComponentB;
102+
`;
103+
104+
var result = parse(source);
105+
expect(result).toBeDefined();
106+
expect(result.node.type).toBe('ClassDeclaration');
107+
108+
source = `
109+
var R = require("React");
110+
class ComponentA extends R.Component {}
111+
class ComponentB extends R.Component {}
112+
module.exports = ComponentB;
113+
`;
114+
115+
result = parse(source);
116+
expect(result).toBeDefined();
117+
expect(result.node.type).toBe('ClassDeclaration');
118+
});
119+
120+
});
121+
});
122+
123+
describe('ES6 export declarations', () => {
124+
125+
describe('export default <component>;', () => {
126+
127+
it('finds default export', () => {
128+
var source = `
129+
import React from 'React';
130+
class Component extends React.Component {}
131+
export default Component;
132+
`;
133+
134+
var result = parse(source);
135+
expect(result).toBeDefined();
136+
expect(result.node.type).toBe('ClassDeclaration');
137+
138+
source = `
139+
import React from 'React';
140+
export default class Component extends React.Component {};
141+
`;
142+
143+
result = parse(source);
144+
expect(result).toBeDefined();
145+
expect(result.node.type).toBe('ClassDeclaration');
146+
});
147+
148+
it('errors if multiple components are exported', () => {
149+
var source = `
150+
import React from 'React';
151+
export var Component = class extends React.Component {};
152+
export default class ComponentB extends React.Component{};
153+
`;
154+
expect(() => parse(source)).toThrow();
155+
156+
var source = `
157+
import React from 'React';
158+
var Component = class extends React.Component {};
159+
export {Component};
160+
export default class ComponentB extends React.Component{};
161+
`;
162+
expect(() => parse(source)).toThrow();
163+
});
164+
165+
it('accepts multiple definitions if only one is exported', () => {
166+
var source = `
167+
import React from 'React';
168+
var Component = class extends React.Component {};
169+
export default class ComponentB extends React.Component{};
170+
`;
171+
172+
var result = parse(source);
173+
expect(result).toBeDefined();
174+
expect(result.node.type).toBe('ClassDeclaration');
175+
});
176+
177+
});
178+
179+
describe('export var foo = <C>, ...;', () => {
180+
181+
it('finds named exports', () => {
182+
var source = `
183+
import React from 'React';
184+
export var somethingElse = 42,
185+
Component = class extends React.Component {};
186+
`;
187+
var result = parse(source);
188+
expect(result).toBeDefined();
189+
expect(result.node.type).toBe('ClassExpression');
190+
191+
source = `
192+
import React from 'React';
193+
export let Component = class extends React.Component {},
194+
somethingElse = 42;
195+
`;
196+
result = parse(source);
197+
expect(result).toBeDefined();
198+
expect(result.node.type).toBe('ClassExpression');
199+
200+
source = `
201+
import React from 'React';
202+
export const something = 21,
203+
Component = class extends React.Component {},
204+
somethingElse = 42;
205+
`;
206+
result = parse(source);
207+
expect(result).toBeDefined();
208+
expect(result.node.type).toBe('ClassExpression');
209+
210+
source = `
211+
import React from 'React';
212+
export var somethingElse = function() {};
213+
export let Component = class extends React.Component {};
214+
`;
215+
result = parse(source);
216+
expect(result).toBeDefined();
217+
expect(result.node.type).toBe('ClassExpression');
218+
});
219+
220+
it('errors if multiple components are exported', () => {
221+
var source = `
222+
import React from 'React';
223+
export var ComponentA = class extends React.Component {};
224+
export var ComponentB = class extends React.Component {};
225+
`;
226+
expect(() => parse(source)).toThrow();
227+
228+
var source = `
229+
import React from 'React';
230+
export var ComponentA = class extends React.Component {};
231+
var ComponentB = class extends React.Component {};
232+
export {ComponentB};
233+
`;
234+
expect(() => parse(source)).toThrow();
235+
});
236+
237+
it('accepts multiple definitions if only one is exported', () => {
238+
var source = `
239+
import React from 'React';
240+
var ComponentA = class extends React.Component {}
241+
export var ComponentB = class extends React.Component {};
242+
`;
243+
var result = parse(source);
244+
expect(result).toBeDefined();
245+
expect(result.node.type).toBe('ClassExpression');
246+
});
247+
248+
});
249+
250+
describe('export {<C>};', () => {
251+
252+
it('finds exported specifiers', () => {
253+
var source = `
254+
import React from 'React';
255+
var foo = 42;
256+
var Component = class extends React.Component {};
257+
export {foo, Component};
258+
`;
259+
var result = parse(source);
260+
expect(result).toBeDefined();
261+
expect(result.node.type).toBe('ClassExpression');
262+
263+
source = `
264+
import React from 'React';
265+
var foo = 42;
266+
var Component = class extends React.Component {};
267+
export {Component, foo};
268+
`;
269+
result = parse(source);
270+
expect(result).toBeDefined();
271+
expect(result.node.type).toBe('ClassExpression');
272+
273+
source = `
274+
import React from 'React';
275+
var foo = 42;
276+
var baz = 21;
277+
var Component = class extends React.Component {};
278+
export {foo, Component as bar, baz};
279+
`;
280+
result = parse(source);
281+
expect(result).toBeDefined();
282+
expect(result.node.type).toBe('ClassExpression');
283+
});
284+
285+
it('errors if multiple components are exported', () => {
286+
var source = `
287+
import React from 'React';
288+
var ComponentA = class extends React.Component {};
289+
var ComponentB = class extends React.Component {};
290+
export {ComponentA as foo, ComponentB};
291+
`;
292+
293+
expect(() => parse(source)).toThrow();
294+
});
295+
296+
it('accepts multiple definitions if only one is exported', () => {
297+
var source = `
298+
import React from 'React';
299+
var ComponentA = class extends React.Component {};
300+
var ComponentB = class extends React.Component {};
301+
export {ComponentA};
302+
`;
303+
var result = parse(source);
304+
expect(result).toBeDefined();
305+
expect(result.node.type).toBe('ClassExpression');
306+
});
307+
308+
});
309+
310+
describe('export <C>;', () => {
311+
312+
it('finds named exports', () => {
313+
var source = `
314+
import React from 'React';
315+
export var foo = 42;
316+
export class Component extends React.Component {};
317+
`;
318+
var result = parse(source);
319+
expect(result).toBeDefined();
320+
expect(result.node.type).toBe('ClassDeclaration');
321+
});
322+
323+
it('errors if multiple components are exported', () => {
324+
var source = `
325+
import React from 'React';
326+
export class ComponentA extends React.Component {};
327+
export class ComponentB extends React.Component {};
328+
`;
329+
330+
expect(() => parse(source)).toThrow();
331+
});
332+
333+
it('accepts multiple definitions if only one is exported', () => {
334+
var source = `
335+
import React from 'React';
336+
class ComponentA extends React.Component {};
337+
export class ComponentB extends React.Component {};
338+
`;
339+
var result = parse(source);
340+
expect(result).toBeDefined();
341+
expect(result.node.type).toBe('ClassDeclaration');
342+
});
343+
344+
});
345+
});
346+
});

0 commit comments

Comments
 (0)