Merge pull request #3777 from zpao/deprecate-react-tools

Deprecate react-tools
This commit is contained in:
Paul O’Shannessy
2015-05-05 11:06:15 -07:00
13 changed files with 52 additions and 1587 deletions
+23 -80
View File
@@ -8,88 +8,31 @@
*/
'use strict';
/*eslint-disable no-undef*/
var visitors = require('./vendor/fbtransform/visitors');
var transform = require('jstransform').transform;
var typesSyntax = require('jstransform/visitors/type-syntax');
var inlineSourceMap = require('./vendor/inline-source-map');
var jstransform = require('jstransform/simple');
function transformWithDetails(code, opts) {
opts = opts || {};
// Copy out the values we need and make sure they are compatible with
// jstransform options. Always set react:true.
var options = {
react: true,
harmony: opts.harmony,
stripTypes: opts.stripTypes,
sourceMapInline: opts.sourceMap,
sourceFilename: opts.sourceFilename,
es6module: opts.es6module,
nonStrictEs6module: opts.nonStrictEs6module,
target: opts.target
};
return jstransform.transform(code, options);
}
module.exports = {
transform: function(input, options) {
options = processOptions(options);
var output = innerTransform(input, options);
var result = output.code;
if (options.sourceMap) {
var map = inlineSourceMap(
output.sourceMap,
input,
options.filename
);
result += '\n' + map;
}
return result;
return transformWithDetails(input, options).code;
},
transformWithDetails: function(input, options) {
options = processOptions(options);
var output = innerTransform(input, options);
var result = {};
result.code = output.code;
if (options.sourceMap) {
result.sourceMap = output.sourceMap.toJSON();
}
if (options.filename) {
result.sourceMap.sources = [options.filename];
}
return result;
}
transformWithDetails: transformWithDetails
};
/**
* Only copy the values that we need. We'll do some preprocessing to account for
* converting command line flags to options that jstransform can actually use.
*/
function processOptions(opts) {
opts = opts || {};
var options = {};
options.harmony = opts.harmony;
options.stripTypes = opts.stripTypes;
options.sourceMap = opts.sourceMap;
options.filename = opts.sourceFilename;
if (opts.es6module) {
options.sourceType = 'module';
}
if (opts.nonStrictEs6module) {
options.sourceType = 'nonStrictModule';
}
// Instead of doing any fancy validation, only look for 'es3'. If we have
// that, then use it. Otherwise use 'es5'.
options.es3 = opts.target === 'es3';
options.es5 = !options.es3;
return options;
}
function innerTransform(input, options) {
var visitorSets = ['react'];
if (options.harmony) {
visitorSets.push('harmony');
}
if (options.es3) {
visitorSets.push('es3');
}
if (options.stripTypes) {
// Stripping types needs to happen before the other transforms
// unfortunately, due to bad interactions. For example,
// es6-rest-param-visitors conflict with stripping rest param type
// annotation
input = transform(typesSyntax.visitorList, input, options).code;
}
var visitorList = visitors.getVisitorsBySet(visitorSets);
return transform(visitorList, input, options);
}
+3 -4
View File
@@ -14,9 +14,7 @@
"files": [
"main.js",
"bin/jsx",
"src/",
"vendor/fbtransform/",
"vendor/inline-source-map.js"
"src/"
],
"main": "main.js",
"bin": {
@@ -28,7 +26,8 @@
},
"dependencies": {
"commoner": "^0.10.0",
"jstransform": "^10.1.0"
"esprima-fb": "^15001.1.0-dev-harmony-fb",
"jstransform": "^11.0.0"
},
"devDependencies": {
"babel-core": "^5.2.7",
+26 -31
View File
@@ -13,8 +13,7 @@
'use strict';
var ReactTools = require('../main');
var inlineSourceMap = require('./inline-source-map');
var jstransform = require('jstransform/simple');
var headEl;
var dummyAnchor;
@@ -36,6 +35,9 @@ var supportsAccessors = Object.prototype.hasOwnProperty('__defineGetter__');
function transformReact(source, options) {
options = options || {};
// Always enable the React transforms.
options.react = true;
// Force the sourcemaps option manually. We don't want to use it if it will
// break (see above note about supportsAccessors). We'll only override the
// value here if sourceMap was specified and is truthy. This guarantees that
@@ -45,7 +47,7 @@ function transformReact(source, options) {
}
// Otherwise just pass all options straight through to react-tools.
return ReactTools.transformWithDetails(source, options);
return jstransform.transform(source, options);
}
/**
@@ -119,7 +121,7 @@ function createSourceCodeErrorMessage(code, e) {
*/
function transformCode(code, url, options) {
try {
var transformed = transformReact(code, options);
return transformReact(code, options).code;
} catch(e) {
e.message += '\n at ';
if (url) {
@@ -136,32 +138,6 @@ function transformCode(code, url, options) {
e.message += createSourceCodeErrorMessage(code, e);
throw e;
}
if (!transformed.sourceMap) {
return transformed.code;
}
var source;
if (url == null) {
source = 'Inline JSX script';
inlineScriptCount++;
if (inlineScriptCount > 1) {
source += ' (' + inlineScriptCount + ')';
}
} else if (dummyAnchor) {
// Firefox has problems when the sourcemap source is a proper URL with a
// protocol and hostname, so use the pathname. We could use just the
// filename, but hopefully using the full path will prevent potential
// issues where the same filename exists in multiple directories.
dummyAnchor.href = url;
source = dummyAnchor.pathname.substr(1);
}
return (
transformed.code +
'\n' +
inlineSourceMap(transformed.sourceMap, code, source)
);
}
@@ -239,8 +215,27 @@ function loadScripts(scripts) {
}
scripts.forEach(function(script, i) {
// Determine the filename to use for the sourcemap.
var sourceFilename;
if (script.src == null) {
sourceFilename = 'Inline JSX script';
inlineScriptCount++;
if (inlineScriptCount > 1) {
sourceFilename += ' (' + inlineScriptCount + ')';
}
} else if (dummyAnchor) {
// Firefox has problems when the sourcemap sourceFilename is a proper URL
// with a protocol and hostname, so use the pathname. We could use just
// the filename, but hopefully using the full path will prevent potential
// issues where the same filename exists in multiple directories.
dummyAnchor.href = script.src;
sourceFilename = dummyAnchor.pathname.substr(1);
}
console.log(sourceFilename);
var options = {
sourceMap: true
sourceMapInline: true,
sourceFilename: sourceFilename
};
if (/;harmony=true(;|$)/.test(script.type)) {
options.harmony = true;
-9
View File
@@ -1,9 +0,0 @@
---
globals:
describe: true
expect: true
it: true
jest: true
rules:
no-process-exit: 0
-94
View File
@@ -1,94 +0,0 @@
/*global process:false*/
/*global module:true*/
/*global exports:true*/
"use strict";
var transform = require('jstransform').transform;
var typesSyntax = require('jstransform/visitors/type-syntax');
var visitors = require('./visitors');
/**
* @typechecks
* @param {string} source
* @param {object?} options
* @param {array?} excludes
* @return {string}
*/
function transformAll(source, options, excludes) {
excludes = excludes || [];
// Stripping types needs to happen before the other transforms
// unfortunately, due to bad interactions. For example,
// es6-rest-param-visitors conflict with stripping rest param type
// annotation
source = transform(typesSyntax.visitorList, source, options).code;
// The typechecker transform must run in a second pass in order to operate on
// the entire source code -- so exclude it from the first pass
var visitorsList = visitors.getAllVisitors(excludes.concat('typechecker'));
source = transform(visitorsList, source, options);
if (excludes.indexOf('typechecks') == -1 && /@typechecks/.test(source.code)) {
source = transform(
visitors.transformVisitors.typechecker,
source.code,
options
);
}
return source;
}
function runCli(argv) {
var options = {};
for (var optName in argv) {
if (optName === '_' || optName === '$0') {
continue;
}
options[optName] = optimist.argv[optName];
}
if (options.help) {
optimist.showHelp();
process.exit(0);
}
var excludes = options.excludes;
delete options.excludes;
var source = '';
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk) {
source += chunk;
});
process.stdin.on('end', function() {
try {
source = transformAll(source, options, excludes);
} catch (e) {
console.error(e.stack);
process.exit(1);
}
process.stdout.write(source.code);
});
}
if (require.main === module) {
var optimist = require('optimist');
optimist = optimist
.usage('Usage: $0 [options]')
.default('exclude', [])
.boolean('help').alias('h', 'help')
.boolean('minify')
.describe(
'minify',
'Best-effort minification of the output source (when possible)'
)
.describe(
'exclude',
'A list of transformNames to exclude'
);
runCli(optimist.argv);
} else {
exports.transformAll = transformAll;
}
-142
View File
@@ -1,142 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails jeffmo@fb.com
*/
'use strict';
require('mock-modules').autoMockOff();
describe('jsx', function() {
var jsx = require('../jsx');
var utils = require('jstransform/src/utils');
var esprima = require('esprima-fb');
function runWithLiteral(text, isLast) {
var source = '<div>' + text + '</div>';
var ast = esprima.parse(source, { range: true });
var state = utils.createState(source);
var literal = ast.body[0].expression.children[0];
state.g.position = literal.range[0];
jsx.renderJSXLiteral(literal, isLast, state);
return state;
}
function runWithAttribute(inlineAttribute, isLast) {
var source = '<div ' + inlineAttribute + ' />';
var ast = esprima.parse(source, { range: true });
var state = utils.createState(source);
var attribute = ast.body[0].expression.openingElement.attributes[0];
state.g.position = attribute.value.range[0];
jsx.renderJSXExpressionContainer(
function() {},
attribute.value,
isLast,
[],
state
);
return state;
}
it('should render simple literal', function() {
var state = runWithLiteral('a', true);
expect(state.g.buffer).toEqual('"a"');
});
it('should render simple literal with single space before', function() {
var state = runWithLiteral(' a', true);
expect(state.g.buffer).toEqual('" a"');
});
it('should render simple literal with single space before' +
' from multiple spaces', function() {
var state = runWithLiteral(' \t a', true);
expect(state.g.buffer).toEqual('" a"');
});
it('should render simple literal with single space after', function() {
var state = runWithLiteral('a ', true);
expect(state.g.buffer).toEqual('"a "');
});
it('should render simple literal with single space after' +
' from multiple spaces', function() {
var state = runWithLiteral('a \t \t ', true);
expect(state.g.buffer).toEqual('"a "');
});
it('should render multiline literal as last', function() {
var state = runWithLiteral(
' sdfsdfsdf\n' +
' sdlkfjsdfljs\n' +
' ',
true);
expect(state.g.buffer).toEqual(
'" sdfsdfsdf" + \' \' +\n' +
' "sdlkfjsdfljs"\n' +
' ');
});
it('should render multiline literal as not last', function() {
var state = runWithLiteral(
' sdfsdfsdf\n' +
' sdlkfjsdfljs\n' +
' ',
false);
expect(state.g.buffer).toEqual(
'" sdfsdfsdf" + \' \' +\n' +
' "sdlkfjsdfljs", \n' +
' ');
});
it('should render attribute expressions', function() {
var state = runWithAttribute('attr={"foo"}', true);
expect(state.g.buffer).toEqual('"foo"');
});
it('should render attribute expressions as not last', function() {
var state = runWithAttribute('attr={"foo"}', false);
expect(state.g.buffer).toEqual('"foo", ');
});
it('should render attribute expressions with spaces', function() {
var state = runWithAttribute('attr={ "foo"\n }', true);
expect(state.g.buffer).toEqual(' "foo"\n ');
});
it('should render attribute expressions with commas before trailing ' +
'whitespace', function() {
var state = runWithAttribute('attr={\n"foo"\n }', false);
expect(state.g.buffer).toEqual('\n"foo", \n ');
});
it('should render empty child expressions with comments', function() {
var source = '<div>{/*comment*/}</div>';
var ast = esprima.parse(source, {range: true});
var child = ast.body[0].expression.children[0];
var state = utils.createState(source, child);
state.g.position = child.range[0];
jsx.renderJSXExpressionContainer(function() {}, child, true, [], state);
expect(state.g.buffer).toBe('/*comment*/');
});
it('should not render commas after empty child expressions even if they\'re' +
'not last', function() {
var source = '<div>{/*comment*/}</div>';
var ast = esprima.parse(source, {range: true});
var child = ast.body[0].expression.children[0];
var state = utils.createState(source, child);
state.g.position = child.range[0];
jsx.renderJSXExpressionContainer(function() {}, child, false, [], state);
expect(state.g.buffer).toBe('/*comment*/');
});
});
@@ -1,174 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
require('mock-modules').autoMockOff();
var transformAll = require('../../syntax.js').transformAll;
function transform(source) {
return transformAll(source, {}, ['sourcemeta']);
}
describe('react displayName jsx', function() {
it('should only inject displayName if missing', function() {
var code = [
'"use strict";',
'var Whateva = React.createClass({',
' displayName: "Whateva",',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
var result = [
'"use strict";',
'var Whateva = React.createClass({',
' displayName: "Whateva",',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should inject displayName in simple assignment', () => {
var code = [
'var Component = React.createClass({',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
var result = [
'var Component = React.createClass({displayName: "Component",',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should inject displayName in simple assignment without var', () => {
var code = [
'var Component;',
'Component = React.createClass({',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
var result = [
'var Component;',
'Component = React.createClass({displayName: "Component",',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should inject displayName in property assignment', () => {
var code = [
'ns.Component = React.createClass({',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
var result = [
'ns.Component = React.createClass({displayName: "ns.Component",',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should inject displayName in chained property assignment', () => {
var code = [
'ns.ns1.Component = React.createClass({',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
var result = [
'ns.ns1.Component = React.createClass({displayName: "ns.ns1.Component",',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should inject displayName in exports property assignment', () => {
var code = [
'exports.Component = React.createClass({',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
var result = [
'exports.Component = React.createClass({displayName: "Component",',
' render: function() {',
' return null;',
' }',
'});'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should inject displayName in object declaration', () => {
var code = [
'exports = {',
' Component: React.createClass({',
' render: function() {',
' return null;',
' }',
' })',
'};'
].join('\n');
var result = [
'exports = {',
' Component: React.createClass({displayName: "Component",',
' render: function() {',
' return null;',
' }',
' })',
'};'
].join('\n');
expect(transform(code).code).toEqual(result);
});
});
-396
View File
@@ -1,396 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
/*jshint evil:true, unused:false*/
'use strict';
require('mock-modules').autoMockOff();
describe('react jsx', function() {
var transformAll = require('../../syntax.js').transformAll;
var transform = function(code, options, excludes) {
return transformAll(
code,
options,
(excludes || []).concat(['sourcemeta'])
);
};
// These are placeholder variables in scope that we can use to assert that a
// specific variable reference was passed, rather than an object clone of it.
var x = 123456;
var y = 789012;
var z = 345678;
var expectObjectAssign = function(code) {
/*eslint-disable no-unused-vars, no-eval*/
var Component = jest.genMockFunction();
var Child = jest.genMockFunction();
var objectAssignMock = jest.genMockFunction();
React.__spread = objectAssignMock;
eval(transform(code).code);
return expect(objectAssignMock);
/*eslint-enable*/
};
var React = {
createElement: jest.genMockFunction()
};
it('should convert simple tags', function() {
var code = 'var x = <div></div>;';
var result = 'var x = React.createElement("div", null);';
expect(transform(code).code).toEqual(result);
});
it('should convert simple text', function() {
var code = 'var x = <div>text</div>;';
var result = 'var x = React.createElement("div", null, "text");';
expect(transform(code).code).toEqual(result);
});
it('should have correct comma in nested children', function() {
var code = [
'var x = <div>',
' <div><br /></div>',
' <Component>{foo}<br />{bar}</Component>',
' <br />',
'</div>;'
].join('\n');
var result = [
'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)',
');'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should avoid wrapping in extra parens if not needed', function() {
// Try with a single composite child, wrapped in a div.
var code = [
'var x = <div>',
' <Component />',
'</div>;'
].join('\n');
var result = [
'var x = React.createElement("div", null, ',
' React.createElement(Component, null)',
');'
].join('\n');
expect(transform(code).code).toEqual(result);
// Try with a single interpolated child, wrapped in a div.
code = [
'var x = <div>',
' {this.props.children}',
'</div>;'
].join('\n');
result = [
'var x = React.createElement("div", null, ',
' this.props.children',
');'
].join('\n');
expect(transform(code).code).toEqual(result);
// Try with a single interpolated child, wrapped in a composite.
code = [
'var x = <Composite>',
' {this.props.children}',
'</Composite>;'
].join('\n');
result = [
'var x = React.createElement(Composite, null, ',
' this.props.children',
');'
].join('\n');
expect(transform(code).code).toEqual(result);
// Try with a single composite child, wrapped in a composite.
code = [
'var x = <Composite>',
' <Composite2 />',
'</Composite>;'
].join('\n');
result = [
'var x = React.createElement(Composite, null, ',
' React.createElement(Composite2, null)',
');'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should insert commas after expressions before whitespace', function() {
var code = [
'var x =',
' <div',
' attr1={',
' "foo" + "bar"',
' }',
' attr2={',
' "foo" + "bar" +',
' ',
' "baz" + "bug"',
' }',
' attr3={',
' "foo" + "bar" +',
' "baz" + "bug"',
' // Extra line here.',
' }',
' attr4="baz">',
' </div>;'
].join('\n');
var result = [
'var x =',
' React.createElement("div", {',
' attr1: ',
' "foo" + "bar", ',
' ',
' attr2: ',
' "foo" + "bar" +',
' ',
' "baz" + "bug", ',
' ',
' attr3: ',
' "foo" + "bar" +',
' "baz" + "bug", ',
' // Extra line here.',
' ',
' attr4: "baz"}',
' );'
].join('\n');
expect(transform(code).code).toEqual(result);
});
it('should properly handle comments adjacent to children', function() {
var code = [
'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>',
');'
].join('\n');
var result = [
'var x = (',
' React.createElement("div", null, ',
' /* A comment at the beginning */',
' /* A second comment at the beginning */',
' React.createElement("span", null',
' /* A nested comment */',
' ), ',
' /* A sandwiched comment */',
' React.createElement("br", null)',
' /* A comment at the end */',
' /* A second comment at the end */',
' )',
');'
].join('\n');
expect(transform(code).code).toBe(result);
});
it('should properly handle comments between props', function() {
var code = [
'var x = (',
' <div',
' /* a multi-line',
' comment */',
' attr1="foo">',
' <span // a double-slash comment',
' attr2="bar"',
' />',
' </div>',
');'
].join('\n');
var result = [
'var x = (',
' React.createElement("div", {',
' /* a multi-line',
' comment */',
' attr1: "foo"}, ',
' React.createElement("span", {// a double-slash comment',
' attr2: "bar"}',
' )',
' )',
');'
].join('\n');
expect(transform(code).code).toBe(result);
});
it('should not strip tags with a single child of &nbsp;', function() {
var code = [
'<div>&nbsp;</div>;'
].join('\n');
var result = [
'React.createElement("div", null, "\u00A0");'
].join('\n');
expect(transform(code).code).toBe(result);
});
it('should not strip &nbsp; even coupled with other whitespace', function() {
var code = [
'<div>&nbsp; </div>;'
].join('\n');
var result = [
'React.createElement("div", null, "\u00A0 ");'
].join('\n');
expect(transform(code).code).toBe(result);
});
it('should handle hasOwnProperty correctly', function() {
var code = '<hasOwnProperty>testing</hasOwnProperty>;';
var result = 'React.createElement("hasOwnProperty", null, "testing");';
expect(transform(code).code).toBe(result);
});
it('should allow constructor as prop', function() {
var code = '<Component constructor="foo" />;';
var result = 'React.createElement(Component, {constructor: "foo"});';
expect(transform(code).code).toBe(result);
});
it('should allow JS namespacing', function() {
var code = '<Namespace.Component />;';
var result = 'React.createElement(Namespace.Component, null);';
expect(transform(code).code).toBe(result);
});
it('should allow deeper JS namespacing', function() {
var code = '<Namespace.DeepNamespace.Component />;';
var result =
'React.createElement(Namespace.DeepNamespace.Component, null);';
expect(transform(code).code).toBe(result);
});
it('should disallow XML namespacing', function() {
var code = '<Namespace:Component />;';
expect(() => transform(code)).toThrow();
});
it('wraps props in React.__spread for spread attributes', function() {
var code =
'<Component { ... x } y\n' +
'={2 } z />';
var result =
'React.createElement(Component, React.__spread({}, x , {y: \n' +
'2, z: true}))';
expect(transform(code).code).toBe(result);
});
it('adds appropriate newlines when using spread attribute', function() {
var code =
'<Component\n' +
' {...this.props}\n' +
' sound="moo" />';
var result =
'React.createElement(Component, React.__spread({}, \n' +
' this.props, \n' +
' {sound: "moo"}))';
expect(transform(code).code).toBe(result);
});
it('handles overparenthesized JS', function() {
var code =
'<foo a={(b)} c={(d)}>Foo {(e+f //A line comment\n' +
'/* A multiline comment */)\n' +
'} bar\n' +
'</foo>';
var result = 'React.createElement("foo", {a: (b), c: (d)}, "Foo ", (e+f //A line comment\n' +
'/* A multiline comment */), \n' +
'" bar"\n' +
')';
expect(transform(code).code).toBe(result);
});
it('should transform known hyphenated tags', function() {
var code = '<font-face />;';
var result = 'React.createElement("font-face", null);';
expect(transform(code).code).toBe(result);
});
it('does not call React.__spread when there are no spreads', function() {
expectObjectAssign(
'<Component x={y} />'
).not.toBeCalled();
});
it('should not throw for unknown hyphenated tags', function() {
var code = '<x-component />;';
expect(function() {
transform(code);
}).not.toThrow();
});
it('calls assign with a new target object for spreads', function() {
expectObjectAssign(
'<Component {...x} />'
).toBeCalledWith({}, x);
});
it('calls assign with an empty object when the spread is first', function() {
expectObjectAssign(
'<Component { ...x } y={2} />'
).toBeCalledWith({}, x, {y: 2});
});
it('coalesces consecutive properties into a single object', function() {
expectObjectAssign(
'<Component { ... x } y={2} z />'
).toBeCalledWith({}, x, {y: 2, z: true});
});
it('avoids an unnecessary empty object when spread is not first', function() {
expectObjectAssign(
'<Component x={1} {...y} />'
).toBeCalledWith({x: 1}, y);
});
it('passes the same value multiple times to React.__spread', function() {
expectObjectAssign(
'<Component x={1} y="2" {...z} {...z}><Child /></Component>'
).toBeCalledWith({x: 1, y: '2'}, z, z);
});
it('evaluates sequences before passing them to React.__spread', function() {
expectObjectAssign(
'<Component x="1" {...(z = { y: 2 }, z)} z={3}>Text</Component>'
).toBeCalledWith({x: '1'}, {y: 2}, {z: 3});
});
});
-138
View File
@@ -1,138 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/*global exports:true*/
'use strict';
var Syntax = require('jstransform').Syntax;
var utils = require('jstransform/src/utils');
function commaAfterLastParen(value) {
var state = 'normal';
var commaPos = 0;
for (var i = 0; i < value.length; ++i) {
if (state === 'normal') {
if (value.substr(i, 2) === '//') {
state = 'singleline';
i += 1;
} else if (value.substr(i, 2) === '/*') {
state = 'multiline';
i += 1;
} else if (value.charAt(i).trim() !== '') {
commaPos = i + 1;
}
} else if (state === 'singleline' && value.charAt(i) === '\n') {
state = 'normal';
} else if (state === 'multiline' &&
value.charAt(i) === '*' &&
i + 1 < value.length &&
value.charAt(i + 1) === '/') {
i += 1;
state = 'normal';
}
}
return value.substring(0, commaPos) + ', ' + trimLeft(value.substring(commaPos));
}
function renderJSXLiteral(object, isLast, state, start, end) {
var lines = object.value.split(/\r\n|\n|\r/);
if (start) {
utils.append(start, state);
}
var lastNonEmptyLine = 0;
lines.forEach(function(line, index) {
if (line.match(/[^ \t]/)) {
lastNonEmptyLine = index;
}
});
lines.forEach(function(line, index) {
var isFirstLine = index === 0;
var isLastLine = index === lines.length - 1;
var isLastNonEmptyLine = index === lastNonEmptyLine;
// replace rendered whitespace tabs with spaces
var trimmedLine = line.replace(/\t/g, ' ');
// trim whitespace touching a newline
if (!isFirstLine) {
trimmedLine = trimmedLine.replace(/^[ ]+/, '');
}
if (!isLastLine) {
trimmedLine = trimmedLine.replace(/[ ]+$/, '');
}
if (!isFirstLine) {
utils.append(line.match(/^[ \t]*/)[0], state);
}
if (trimmedLine || isLastNonEmptyLine) {
utils.append(
JSON.stringify(trimmedLine) +
(!isLastNonEmptyLine ? ' + \' \' +' : ''),
state);
if (isLastNonEmptyLine) {
if (end) {
utils.append(end, state);
}
if (!isLast) {
utils.append(', ', state);
}
}
// only restore tail whitespace if line had literals
if (trimmedLine && !isLastLine) {
utils.append(line.match(/[ \t]*$/)[0], state);
}
}
if (!isLastLine) {
utils.append('\n', state);
}
});
utils.move(object.range[1], state);
}
function renderJSXExpressionContainer(traverse, object, isLast, path, state) {
// Plus 1 to skip `{`.
utils.move(object.range[0] + 1, state);
utils.catchup(object.expression.range[0], state);
traverse(object.expression, path, state);
if (!isLast && object.expression.type !== Syntax.JSXEmptyExpression) {
// If we need to append a comma, make sure to do so after the expression.
utils.catchup(object.expression.range[1], state, trimLeft);
utils.catchup(object.range[1] - 1, state, commaAfterLastParen);
} else {
// Minus 1 to skip `}`.
utils.catchup(object.range[1] - 1, state, trimLeft);
}
utils.move(object.range[1], state);
return false;
}
function quoteAttrName(attr) {
// Quote invalid JS identifiers.
if (!/^[a-z_$][a-z\d_$]*$/i.test(attr)) {
return '"' + attr + '"';
}
return attr;
}
function trimLeft(value) {
return value.replace(/^[ ]+/, '');
}
exports.renderJSXExpressionContainer = renderJSXExpressionContainer;
exports.renderJSXLiteral = renderJSXLiteral;
exports.quoteAttrName = quoteAttrName;
exports.trimLeft = trimLeft;
-241
View File
@@ -1,241 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
/*global exports:true*/
var Syntax = require('jstransform').Syntax;
var utils = require('jstransform/src/utils');
var renderJSXExpressionContainer = require('./jsx').renderJSXExpressionContainer;
var renderJSXLiteral = require('./jsx').renderJSXLiteral;
var quoteAttrName = require('./jsx').quoteAttrName;
var trimLeft = require('./jsx').trimLeft;
/**
* Customized desugar processor for React JSX. Currently:
*
* <X> </X> => React.createElement(X, null)
* <X prop="1" /> => React.createElement(X, {prop: '1'}, null)
* <X prop="2"><Y /></X> => React.createElement(X, {prop:'2'},
* React.createElement(Y, null)
* )
* <div /> => React.createElement("div", null)
*/
/**
* Removes all non-whitespace/parenthesis characters
*/
var reNonWhiteParen = /([^\s\(\)])/g;
function stripNonWhiteParen(value) {
return value.replace(reNonWhiteParen, '');
}
var tagConvention = /^[a-z]|\-/;
function isTagName(name) {
return tagConvention.test(name);
}
function visitReactTag(traverse, object, path, state) {
var openingElement = object.openingElement;
var nameObject = openingElement.name;
var attributesObject = openingElement.attributes;
utils.catchup(openingElement.range[0], state, trimLeft);
if (nameObject.type === Syntax.JSXNamespacedName && nameObject.namespace) {
throw new Error('Namespace tags are not supported. ReactJSX is not XML.');
}
// We assume that the React runtime is already in scope
utils.append('React.createElement(', state);
if (nameObject.type === Syntax.JSXIdentifier && isTagName(nameObject.name)) {
utils.append('"' + nameObject.name + '"', state);
utils.move(nameObject.range[1], state);
} else {
// Use utils.catchup in this case so we can easily handle
// JSXMemberExpressions which look like Foo.Bar.Baz. This also handles
// JSXIdentifiers that aren't fallback tags.
utils.move(nameObject.range[0], state);
utils.catchup(nameObject.range[1], state);
}
utils.append(', ', state);
var hasAttributes = attributesObject.length;
var hasAtLeastOneSpreadProperty = attributesObject.some(function(attr) {
return attr.type === Syntax.JSXSpreadAttribute;
});
// if we don't have any attributes, pass in null
if (hasAtLeastOneSpreadProperty) {
utils.append('React.__spread({', state);
} else if (hasAttributes) {
utils.append('{', state);
} else {
utils.append('null', state);
}
// keep track of if the previous attribute was a spread attribute
var previousWasSpread = false;
// write attributes
attributesObject.forEach(function(attr, index) {
var isLast = index === attributesObject.length - 1;
if (attr.type === Syntax.JSXSpreadAttribute) {
// Close the previous object or initial object
if (!previousWasSpread) {
utils.append('}, ', state);
}
// Move to the expression start, ignoring everything except parenthesis
// and whitespace.
utils.catchup(attr.range[0], state, stripNonWhiteParen);
// Plus 1 to skip `{`.
utils.move(attr.range[0] + 1, state);
utils.catchup(attr.argument.range[0], state, stripNonWhiteParen);
traverse(attr.argument, path, state);
utils.catchup(attr.argument.range[1], state);
// Move to the end, ignoring parenthesis and the closing `}`
utils.catchup(attr.range[1] - 1, state, stripNonWhiteParen);
if (!isLast) {
utils.append(', ', state);
}
utils.move(attr.range[1], state);
previousWasSpread = true;
return;
}
// If the next attribute is a spread, we're effective last in this object
if (!isLast) {
isLast = attributesObject[index + 1].type === Syntax.JSXSpreadAttribute;
}
if (attr.name.namespace) {
throw new Error(
'Namespace attributes are not supported. ReactJSX is not XML.');
}
var name = attr.name.name;
utils.catchup(attr.range[0], state, trimLeft);
if (previousWasSpread) {
utils.append('{', state);
}
utils.append(quoteAttrName(name), state);
utils.append(': ', state);
if (!attr.value) {
state.g.buffer += 'true';
state.g.position = attr.name.range[1];
if (!isLast) {
utils.append(', ', state);
}
} else {
utils.move(attr.name.range[1], state);
// Use catchupNewlines to skip over the '=' in the attribute
utils.catchupNewlines(attr.value.range[0], state);
if (attr.value.type === Syntax.Literal) {
renderJSXLiteral(attr.value, isLast, state);
} else {
renderJSXExpressionContainer(traverse, attr.value, isLast, path, state);
}
}
utils.catchup(attr.range[1], state, trimLeft);
previousWasSpread = false;
});
if (!openingElement.selfClosing) {
utils.catchup(openingElement.range[1] - 1, state, trimLeft);
utils.move(openingElement.range[1], state);
}
if (hasAttributes && !previousWasSpread) {
utils.append('}', state);
}
if (hasAtLeastOneSpreadProperty) {
utils.append(')', state);
}
// filter out whitespace
var childrenToRender = object.children.filter(function(child) {
return !(child.type === Syntax.Literal
&& typeof child.value === 'string'
&& child.value.match(/^[ \t]*[\r\n][ \t\r\n]*$/));
});
if (childrenToRender.length > 0) {
var lastRenderableIndex;
childrenToRender.forEach(function(child, index) {
if (child.type !== Syntax.JSXExpressionContainer ||
child.expression.type !== Syntax.JSXEmptyExpression) {
lastRenderableIndex = index;
}
});
if (lastRenderableIndex !== undefined) {
utils.append(', ', state);
}
childrenToRender.forEach(function(child, index) {
utils.catchup(child.range[0], state, trimLeft);
var isLast = index >= lastRenderableIndex;
if (child.type === Syntax.Literal) {
renderJSXLiteral(child, isLast, state);
} else if (child.type === Syntax.JSXExpressionContainer) {
renderJSXExpressionContainer(traverse, child, isLast, path, state);
} else {
traverse(child, path, state);
if (!isLast) {
utils.append(', ', state);
}
}
utils.catchup(child.range[1], state, trimLeft);
});
}
if (openingElement.selfClosing) {
// everything up to />
utils.catchup(openingElement.range[1] - 2, state, trimLeft);
utils.move(openingElement.range[1], state);
} else {
// everything up to </ sdflksjfd>
utils.catchup(object.closingElement.range[0], state, trimLeft);
utils.move(object.closingElement.range[1], state);
}
utils.append(')', state);
return false;
}
visitReactTag.test = function(object, path, state) {
return object.type === Syntax.JSXElement;
};
exports.visitorList = [
visitReactTag
];
-126
View File
@@ -1,126 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/*global exports:true*/
'use strict';
var Syntax = require('jstransform').Syntax;
var utils = require('jstransform/src/utils');
function shouldAddDisplayName(object) {
if (object &&
object.type === Syntax.CallExpression &&
object.callee.type === Syntax.MemberExpression &&
object.callee.object.type === Syntax.Identifier &&
object.callee.object.name === 'React' &&
object.callee.property.type === Syntax.Identifier &&
object.callee.property.name === 'createClass' &&
object.arguments.length === 1 &&
object.arguments[0].type === Syntax.ObjectExpression) {
// Verify that the displayName property isn't already set
var properties = object.arguments[0].properties;
var safe = properties.every(function(property) {
var value = property.key.type === Syntax.Identifier ?
property.key.name :
property.key.value;
return value !== 'displayName';
});
return safe;
}
return false;
}
/**
* If `expr` is an Identifier or MemberExpression node made of identifiers and
* dot accesses, return a list of the identifier parts. Other nodes return null.
*
* Examples:
*
* MyComponent -> ['MyComponent']
* namespace.MyComponent -> ['namespace', 'MyComponent']
* namespace['foo'] -> null
* namespace['foo'].bar -> ['bar']
*/
function flattenIdentifierOrMemberExpression(expr) {
if (expr.type === Syntax.Identifier) {
return [expr.name];
} else if (expr.type === Syntax.MemberExpression) {
if (!expr.computed && expr.property.type === Syntax.Identifier) {
var flattenedObject = flattenIdentifierOrMemberExpression(expr.object);
if (flattenedObject) {
flattenedObject.push(expr.property.name);
return flattenedObject;
} else {
return [expr.property.name];
}
}
}
return null;
}
/**
* Transforms the following:
*
* var MyComponent = React.createClass({
* render: ...
* });
*
* into:
*
* var MyComponent = React.createClass({
* displayName: 'MyComponent',
* render: ...
* });
*
* Also catches:
*
* MyComponent = React.createClass(...);
* namespace.MyComponent = React.createClass(...);
* exports.MyComponent = React.createClass(...);
* module.exports = {MyComponent: React.createClass(...)};
*/
function visitReactDisplayName(traverse, object, path, state) {
var left, right;
if (object.type === Syntax.AssignmentExpression) {
left = object.left;
right = object.right;
} else if (object.type === Syntax.Property) {
left = object.key;
right = object.value;
} else if (object.type === Syntax.VariableDeclarator) {
left = object.id;
right = object.init;
}
if (right && shouldAddDisplayName(right)) {
var displayNamePath = flattenIdentifierOrMemberExpression(left);
if (displayNamePath) {
if (displayNamePath.length > 1 && displayNamePath[0] === 'exports') {
displayNamePath.shift();
}
var displayName = displayNamePath.join('.');
utils.catchup(right.arguments[0].range[0] + 1, state);
utils.append('displayName: "' + displayName + '",', state);
}
}
}
visitReactDisplayName.test = function(object, path, state) {
return (
object.type === Syntax.AssignmentExpression ||
object.type === Syntax.Property ||
object.type === Syntax.VariableDeclarator
);
};
exports.visitorList = [
visitReactDisplayName
];
-125
View File
@@ -1,125 +0,0 @@
/*global exports:true*/
'use strict';
var es6ArrowFunctions =
require('jstransform/visitors/es6-arrow-function-visitors');
var es6Classes = require('jstransform/visitors/es6-class-visitors');
var es6Destructuring =
require('jstransform/visitors/es6-destructuring-visitors');
var es6ObjectConciseMethod =
require('jstransform/visitors/es6-object-concise-method-visitors');
var es6ObjectShortNotation =
require('jstransform/visitors/es6-object-short-notation-visitors');
var es6RestParameters = require('jstransform/visitors/es6-rest-param-visitors');
var es6Templates = require('jstransform/visitors/es6-template-visitors');
var es6CallSpread =
require('jstransform/visitors/es6-call-spread-visitors');
var es7SpreadProperty =
require('jstransform/visitors/es7-spread-property-visitors');
var react = require('./transforms/react');
var reactDisplayName = require('./transforms/reactDisplayName');
var reservedWords = require('jstransform/visitors/reserved-words-visitors');
/**
* Map from transformName => orderedListOfVisitors.
*/
var transformVisitors = {
'es6-arrow-functions': es6ArrowFunctions.visitorList,
'es6-classes': es6Classes.visitorList,
'es6-destructuring': es6Destructuring.visitorList,
'es6-object-concise-method': es6ObjectConciseMethod.visitorList,
'es6-object-short-notation': es6ObjectShortNotation.visitorList,
'es6-rest-params': es6RestParameters.visitorList,
'es6-templates': es6Templates.visitorList,
'es6-call-spread': es6CallSpread.visitorList,
'es7-spread-property': es7SpreadProperty.visitorList,
'react': react.visitorList.concat(reactDisplayName.visitorList),
'reserved-words': reservedWords.visitorList
};
var transformSets = {
'harmony': [
'es6-arrow-functions',
'es6-object-concise-method',
'es6-object-short-notation',
'es6-classes',
'es6-rest-params',
'es6-templates',
'es6-destructuring',
'es6-call-spread',
'es7-spread-property'
],
'es3': [
'reserved-words'
],
'react': [
'react'
]
};
/**
* Specifies the order in which each transform should run.
*/
var transformRunOrder = [
'reserved-words',
'es6-arrow-functions',
'es6-object-concise-method',
'es6-object-short-notation',
'es6-classes',
'es6-rest-params',
'es6-templates',
'es6-destructuring',
'es6-call-spread',
'es7-spread-property',
'react'
];
/**
* Given a list of transform names, return the ordered list of visitors to be
* passed to the transform() function.
*
* @param {array?} excludes
* @return {array}
*/
function getAllVisitors(excludes) {
var ret = [];
for (var i = 0, il = transformRunOrder.length; i < il; i++) {
if (!excludes || excludes.indexOf(transformRunOrder[i]) === -1) {
ret = ret.concat(transformVisitors[transformRunOrder[i]]);
}
}
return ret;
}
/**
* Given a list of visitor set names, return the ordered list of visitors to be
* passed to jstransform.
*
* @param {array}
* @return {array}
*/
function getVisitorsBySet(sets) {
var visitorsToInclude = sets.reduce(function(visitors, set) {
if (!transformSets.hasOwnProperty(set)) {
throw new Error('Unknown visitor set: ' + set);
}
transformSets[set].forEach(function(visitor) {
visitors[visitor] = true;
});
return visitors;
}, {});
var visitorList = [];
for (var i = 0; i < transformRunOrder.length; i++) {
if (visitorsToInclude.hasOwnProperty(transformRunOrder[i])) {
visitorList = visitorList.concat(transformVisitors[transformRunOrder[i]]);
}
}
return visitorList;
}
exports.getVisitorsBySet = getVisitorsBySet;
exports.getAllVisitors = getAllVisitors;
exports.transformVisitors = transformVisitors;
-27
View File
@@ -1,27 +0,0 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
/*eslint-disable no-undef*/
var Buffer = require('buffer').Buffer;
function inlineSourceMap(sourceMap, sourceCode, sourceFilename) {
// This can be used with a sourcemap that has already has toJSON called on it.
// Check first.
var json = sourceMap;
if (typeof sourceMap.toJSON === 'function') {
json = sourceMap.toJSON();
}
json.sources = [sourceFilename];
json.sourcesContent = [sourceCode];
var base64 = Buffer(JSON.stringify(json)).toString('base64');
return '//# sourceMappingURL=data:application/json;base64,' + base64;
}
module.exports = inlineSourceMap;