mirror of
https://github.com/zebrajr/react.git
synced 2025-12-07 00:20:28 +01:00
322 lines
10 KiB
JavaScript
322 lines
10 KiB
JavaScript
/**
|
|
* 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';
|
|
|
|
var CORE_PROPERTIES = [
|
|
'Children',
|
|
'Component',
|
|
'createElement',
|
|
'cloneElement',
|
|
'isValidElement',
|
|
'PropTypes',
|
|
'createClass',
|
|
'createFactory',
|
|
'createMixin',
|
|
'DOM',
|
|
'__spread',
|
|
];
|
|
|
|
var DOM_PROPERTIES = [
|
|
'findDOMNode',
|
|
'render',
|
|
'unmountComponentAtNode',
|
|
'unstable_batchedUpdates',
|
|
'unstable_renderSubtreeIntoContainer',
|
|
];
|
|
|
|
var DOM_SERVER_PROPERTIES = [
|
|
'renderToString',
|
|
'renderToStaticMarkup',
|
|
];
|
|
|
|
function reportError(node, error) {
|
|
throw new Error(
|
|
`At ${node.loc.start.line}:${node.loc.start.column}: ${error}`
|
|
);
|
|
}
|
|
|
|
function isRequire(path, moduleName) {
|
|
return (
|
|
path.value.type === 'CallExpression' &&
|
|
path.value.callee.type === 'Identifier' &&
|
|
path.value.callee.name === 'require' &&
|
|
path.value.arguments.length === 1 &&
|
|
path.value.arguments[0].type === 'Literal' &&
|
|
path.value.arguments[0].value === moduleName
|
|
);
|
|
}
|
|
|
|
module.exports = function(file, api) {
|
|
var j = api.jscodeshift;
|
|
var root = j(file.source);
|
|
|
|
[
|
|
['React', 'ReactDOM', 'ReactDOMServer'],
|
|
['react', 'react-dom', 'react-dom/server'],
|
|
].forEach(function(pair) {
|
|
var coreModuleName = pair[0];
|
|
var domModuleName = pair[1];
|
|
var domServerModuleName = pair[2];
|
|
|
|
var domAlreadyDeclared = false;
|
|
var domServerAlreadyDeclared = false;
|
|
|
|
var coreRequireDeclarator;
|
|
root
|
|
.find(j.CallExpression)
|
|
.filter(p => isRequire(p, coreModuleName))
|
|
.forEach(p => {
|
|
var name, scope;
|
|
if (p.parent.value.type === 'VariableDeclarator') {
|
|
if (p.parent.value.id.type === 'ObjectPattern') {
|
|
var pattern = p.parent.value.id;
|
|
var all = pattern.properties.every(function(prop) {
|
|
if (prop.key.type === 'Identifier') {
|
|
name = prop.key.name;
|
|
return CORE_PROPERTIES.indexOf(name) !== -1;
|
|
}
|
|
return false;
|
|
});
|
|
if (all) {
|
|
// var {PropTypes} = require('React'); so leave alone
|
|
return;
|
|
}
|
|
}
|
|
if (coreRequireDeclarator) {
|
|
reportError(
|
|
p.value,
|
|
'Multiple declarations of React'
|
|
);
|
|
}
|
|
if (p.parent.value.id.type !== 'Identifier') {
|
|
reportError(
|
|
p.value,
|
|
'Unexpected destructuring in require of ' + coreModuleName
|
|
);
|
|
}
|
|
name = p.parent.value.id.name;
|
|
scope = p.scope.lookup(name);
|
|
if (scope.declares('ReactDOM')) {
|
|
console.log('Using existing ReactDOM var in ' + file.path);
|
|
domAlreadyDeclared = true;
|
|
}
|
|
if (scope.declares('ReactDOMServer')) {
|
|
console.log('Using existing ReactDOMServer var in ' + file.path);
|
|
domServerAlreadyDeclared = true;
|
|
}
|
|
coreRequireDeclarator = p.parent;
|
|
} else if (p.parent.value.type === 'AssignmentExpression') {
|
|
if (p.parent.value.left.type !== 'Identifier') {
|
|
reportError(
|
|
p.value,
|
|
'Unexpected destructuring in require of ' + coreModuleName
|
|
);
|
|
}
|
|
name = p.parent.value.left.name;
|
|
scope = p.scope.lookup(name);
|
|
var reactBindings = scope.getBindings()[name];
|
|
if (reactBindings.length !== 1) {
|
|
throw new Error(
|
|
'Unexpected number of bindings: ' + reactBindings.length
|
|
);
|
|
}
|
|
coreRequireDeclarator = reactBindings[0].parent;
|
|
if (coreRequireDeclarator.value.init &&
|
|
!isRequire(coreRequireDeclarator.get('init'), coreModuleName)) {
|
|
reportError(
|
|
coreRequireDeclarator.value,
|
|
'Unexpected initialization of ' + coreModuleName
|
|
);
|
|
}
|
|
if (scope.declares('ReactDOM')) {
|
|
console.log('Using existing ReactDOM var in ' + file.path);
|
|
domAlreadyDeclared = true;
|
|
}
|
|
if (scope.declares('ReactDOMServer')) {
|
|
console.log('Using existing ReactDOMServer var in ' + file.path);
|
|
domServerAlreadyDeclared = true;
|
|
}
|
|
}
|
|
});
|
|
if (!coreRequireDeclarator) {
|
|
return;
|
|
}
|
|
|
|
if (!domAlreadyDeclared &&
|
|
root.find(j.Identifier, {name: 'ReactDOM'}).size() > 0) {
|
|
throw new Error(
|
|
'ReactDOM is already defined in a different scope than React'
|
|
);
|
|
}
|
|
if (!domServerAlreadyDeclared &&
|
|
root.find(j.Identifier, {name: 'ReactDOMServer'}).size() > 0) {
|
|
throw new Error(
|
|
'ReactDOMServer is already defined in a different scope than React'
|
|
);
|
|
}
|
|
|
|
var coreName = coreRequireDeclarator.value.id.name;
|
|
|
|
var processed = new Set();
|
|
var requireAssignments = [];
|
|
var coreUses = 0;
|
|
var domUses = 0;
|
|
var domServerUses = 0;
|
|
|
|
root
|
|
.find(j.Identifier, {name: coreName})
|
|
.forEach(p => {
|
|
if (processed.has(p.value)) {
|
|
// https://github.com/facebook/jscodeshift/issues/36
|
|
return;
|
|
}
|
|
processed.add(p.value);
|
|
if (p.parent.value.type === 'MemberExpression' ||
|
|
p.parent.value.type === 'QualifiedTypeIdentifier') {
|
|
var left;
|
|
var right;
|
|
if (p.parent.value.type === 'MemberExpression') {
|
|
left = p.parent.value.object;
|
|
right = p.parent.value.property;
|
|
} else {
|
|
left = p.parent.value.qualification;
|
|
right = p.parent.value.id;
|
|
}
|
|
if (left === p.value) {
|
|
// React.foo (or React[foo])
|
|
if (right.type === 'Identifier') {
|
|
var name = right.name;
|
|
if (CORE_PROPERTIES.indexOf(name) !== -1) {
|
|
coreUses++;
|
|
} else if (DOM_PROPERTIES.indexOf(name) !== -1) {
|
|
domUses++;
|
|
j(p).replaceWith(j.identifier('ReactDOM'));
|
|
} else if (DOM_SERVER_PROPERTIES.indexOf(name) !== -1) {
|
|
domServerUses++;
|
|
j(p).replaceWith(j.identifier('ReactDOMServer'));
|
|
} else {
|
|
throw new Error('Unknown property React.' + name);
|
|
}
|
|
}
|
|
} else if (right === p.value) {
|
|
// foo.React, no need to transform
|
|
} else {
|
|
throw new Error('unimplemented');
|
|
}
|
|
} else if (p.parent.value.type === 'VariableDeclarator') {
|
|
if (p.parent.value.id === p.value) {
|
|
// var React = ...;
|
|
} else if (p.parent.value.init === p.value) {
|
|
// var ... = React;
|
|
var pattern = p.parent.value.id;
|
|
if (pattern.type === 'ObjectPattern') {
|
|
// var {PropTypes} = React;
|
|
// Most of these cases will just be looking at {PropTypes} so this
|
|
// is usually a no-op.
|
|
var coreProperties = [];
|
|
var domProperties = [];
|
|
pattern.properties.forEach(function(prop) {
|
|
if (prop.key.type === 'Identifier') {
|
|
var key = prop.key.name;
|
|
if (CORE_PROPERTIES.indexOf(key) !== -1) {
|
|
coreProperties.push(prop);
|
|
} else if (DOM_PROPERTIES.indexOf(key) !== -1) {
|
|
domProperties.push(prop);
|
|
} else {
|
|
throw new Error(
|
|
'Unknown property React.' + key + ' while destructuring'
|
|
);
|
|
}
|
|
} else {
|
|
throw new Error('unimplemented');
|
|
}
|
|
});
|
|
var domDeclarator = j.variableDeclarator(
|
|
j.objectPattern(domProperties),
|
|
j.identifier('ReactDOM')
|
|
);
|
|
if (coreProperties.length && !domProperties.length) {
|
|
// nothing to do
|
|
coreUses++;
|
|
} else if (domProperties.length && !coreProperties.length) {
|
|
domUses++;
|
|
j(p.parent).replaceWith(domDeclarator);
|
|
} else {
|
|
coreUses++;
|
|
domUses++;
|
|
var decl = j(p).closest(j.VariableDeclaration);
|
|
decl.insertAfter(j.variableDeclaration(
|
|
decl.get().value.kind,
|
|
[domDeclarator]
|
|
));
|
|
}
|
|
} else {
|
|
throw new Error('unimplemented');
|
|
}
|
|
} else {
|
|
throw new Error('unimplemented');
|
|
}
|
|
} else if (p.parent.value.type === 'AssignmentExpression') {
|
|
if (p.parent.value.left === p.value) {
|
|
if (isRequire(p.parent.get('right'), coreModuleName)) {
|
|
requireAssignments.push(p.parent);
|
|
} else {
|
|
reportError(
|
|
p.parent.value,
|
|
'Unexpected assignment to ' + coreModuleName
|
|
);
|
|
}
|
|
} else {
|
|
throw new Error('unimplemented');
|
|
}
|
|
} else {
|
|
reportError(p.value, 'unimplemented ' + p.parent.value.type);
|
|
}
|
|
});
|
|
|
|
coreUses += root.find(j.JSXElement).size();
|
|
|
|
function insertRequire(name, path) {
|
|
var req = j.callExpression(
|
|
j.identifier('require'),
|
|
[j.literal(path)]
|
|
);
|
|
requireAssignments.forEach(function(requireAssignment) {
|
|
requireAssignment.parent.insertAfter(
|
|
j.expressionStatement(
|
|
j.assignmentExpression('=', j.identifier(name), req)
|
|
)
|
|
);
|
|
});
|
|
coreRequireDeclarator.parent.insertAfter(j.variableDeclaration(
|
|
coreRequireDeclarator.parent.value.kind,
|
|
[j.variableDeclarator(
|
|
j.identifier(name),
|
|
coreRequireDeclarator.value.init ? req : null
|
|
)]
|
|
));
|
|
}
|
|
|
|
if (domServerUses > 0 && !domServerAlreadyDeclared) {
|
|
insertRequire('ReactDOMServer', domServerModuleName);
|
|
}
|
|
if (domUses > 0 && !domAlreadyDeclared) {
|
|
insertRequire('ReactDOM', domModuleName);
|
|
}
|
|
if ((domUses > 0 || domServerUses > 0) && coreUses === 0) {
|
|
j(coreRequireDeclarator).remove();
|
|
requireAssignments.forEach(r => j(r).remove());
|
|
}
|
|
});
|
|
|
|
return root.toSource({quote: 'single'});
|
|
};
|