Advertisement
Guest User

Untitled

a guest
Apr 27th, 2017
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.61 KB | None | 0 0
  1. /**
  2. * Copyright 2013-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. 'use strict';
  12.  
  13. const { basename, extname, dirname } = require('path');
  14.  
  15. module.exports = (file, api, options) => {
  16. const j = api.jscodeshift;
  17.  
  18. require('./utils/array-polyfills');
  19. const ReactUtils = require('./utils/ReactUtils')(j);
  20.  
  21. const printOptions =
  22. options.printOptions || {
  23. quote: 'single',
  24. trailingComma: true,
  25. flowObjectCommas: true,
  26. arrowParensAlways: true,
  27. arrayBracketSpacing: false,
  28. objectCurlySpacing: false,
  29. };
  30.  
  31. const root = j(file.source);
  32.  
  33. // retain top comments
  34. const { comments: topComments } = root.find(j.Program).get('body', 0).node;
  35.  
  36. const AUTOBIND_IGNORE_KEYS = {
  37. componentDidMount: true,
  38. componentDidUpdate: true,
  39. componentWillReceiveProps: true,
  40. componentWillMount: true,
  41. componentWillUpdate: true,
  42. componentWillUnmount: true,
  43. getChildContext: true,
  44. getDefaultProps: true,
  45. getInitialState: true,
  46. render: true,
  47. shouldComponentUpdate: true,
  48. };
  49.  
  50. const DEFAULT_PROPS_FIELD = 'getDefaultProps';
  51. const DEFAULT_PROPS_KEY = 'defaultProps';
  52. const GET_INITIAL_STATE_FIELD = 'getInitialState';
  53.  
  54. const DEPRECATED_APIS = [
  55. 'getDOMNode',
  56. 'isMounted',
  57. 'replaceProps',
  58. 'replaceState',
  59. 'setProps',
  60. ];
  61.  
  62. const PURE_MIXIN_MODULE_NAME = options['mixin-module-name'] ||
  63. 'react-addons-pure-render-mixin';
  64.  
  65. const CREATE_CLASS_MODULE_NAME = options['create-class-module-name'] ||
  66. 'create-react-class';
  67.  
  68. const CREATE_CLASS_VARIABLE_NAME = options['create-class-variable-name'] ||
  69. 'createReactClass';
  70.  
  71. const STATIC_KEY = 'statics';
  72.  
  73. const STATIC_KEYS = {
  74. childContextTypes: true,
  75. contextTypes: true,
  76. displayName: true,
  77. propTypes: true,
  78. };
  79.  
  80. const MIXIN_KEY = 'mixins';
  81.  
  82. const NO_CONVERSION = options.conversion === false;
  83.  
  84. const NO_DISPLAY_NAME = options['display-name'] === false;
  85.  
  86. let shouldTransformFlow = false;
  87.  
  88. if (options['flow']) {
  89. const programBodyNode = root.find(j.Program).get('body', 0).node;
  90. if (programBodyNode && programBodyNode.comments) {
  91. programBodyNode.comments.forEach(node => {
  92. if (node.value.indexOf('@flow') !== -1) {
  93. shouldTransformFlow = true;
  94. }
  95. });
  96. }
  97. }
  98.  
  99. // ---------------------------------------------------------------------------
  100. // Helpers
  101. const createFindPropFn = prop => property => (
  102. property.key &&
  103. property.key.type === 'Identifier' &&
  104. property.key.name === prop
  105. );
  106.  
  107. const filterDefaultPropsField = node =>
  108. createFindPropFn(DEFAULT_PROPS_FIELD)(node);
  109.  
  110. const filterGetInitialStateField = node =>
  111. createFindPropFn(GET_INITIAL_STATE_FIELD)(node);
  112.  
  113. const findGetInitialState = specPath =>
  114. specPath.properties.find(createFindPropFn(GET_INITIAL_STATE_FIELD));
  115.  
  116. const withComments = (to, from) => {
  117. to.comments = from.comments;
  118. return to;
  119. };
  120.  
  121. const isPrimExpression = node => (
  122. node.type === 'Literal' || ( // NOTE this might change in babylon v6
  123. node.type === 'Identifier' &&
  124. node.name === 'undefined'
  125. ));
  126.  
  127. const isFunctionExpression = node => (
  128. node.key &&
  129. node.key.type === 'Identifier' &&
  130. node.value &&
  131. node.value.type === 'FunctionExpression'
  132. );
  133.  
  134. const isPrimProperty = prop => (
  135. prop.key &&
  136. prop.key.type === 'Identifier' &&
  137. prop.value &&
  138. isPrimExpression(prop.value)
  139. );
  140.  
  141. const isPrimPropertyWithTypeAnnotation = prop => (
  142. prop.key &&
  143. prop.key.type === 'Identifier' &&
  144. prop.value &&
  145. prop.value.type === 'TypeCastExpression' &&
  146. isPrimExpression(prop.value.expression)
  147. );
  148.  
  149. const hasSingleReturnStatement = value => (
  150. value.type === 'FunctionExpression' &&
  151. value.body &&
  152. value.body.type === 'BlockStatement' &&
  153. value.body.body &&
  154. value.body.body.length === 1 &&
  155. value.body.body[0].type === 'ReturnStatement' &&
  156. value.body.body[0].argument
  157. );
  158.  
  159. const isInitialStateLiftable = getInitialState => {
  160. if (!getInitialState || !(getInitialState.value)) {
  161. return true;
  162. }
  163.  
  164. return hasSingleReturnStatement(getInitialState.value);
  165. };
  166.  
  167. // ---------------------------------------------------------------------------
  168. // Checks if the module uses mixins or accesses deprecated APIs.
  169. const checkDeprecatedAPICalls = classPath =>
  170. DEPRECATED_APIS.reduce(
  171. (acc, name) =>
  172. acc + j(classPath)
  173. .find(j.Identifier, {name})
  174. .size(),
  175. 0
  176. ) > 0;
  177.  
  178. const hasNoCallsToDeprecatedAPIs = classPath => {
  179. if (checkDeprecatedAPICalls(classPath)) {
  180. console.warn(
  181. file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' +
  182. 'was skipped because of deprecated API calls. Remove calls to ' +
  183. DEPRECATED_APIS.join(', ') + ' in your React component and re-run ' +
  184. 'this script.'
  185. );
  186. return false;
  187. }
  188. return true;
  189. };
  190.  
  191. const hasNoRefsToAPIsThatWillBeRemoved = classPath => {
  192. const hasInvalidCalls = (
  193. j(classPath).find(j.MemberExpression, {
  194. object: {type: 'ThisExpression'},
  195. property: {name: DEFAULT_PROPS_FIELD},
  196. }).size() > 0 ||
  197. j(classPath).find(j.MemberExpression, {
  198. object: {type: 'ThisExpression'},
  199. property: {name: GET_INITIAL_STATE_FIELD},
  200. }).size() > 0
  201. );
  202.  
  203. if (hasInvalidCalls) {
  204. console.warn(
  205. file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' +
  206. 'was skipped because of API calls that will be removed. Remove calls to `' +
  207. DEFAULT_PROPS_FIELD + '` and/or `' + GET_INITIAL_STATE_FIELD +
  208. '` in your React component and re-run this script.'
  209. );
  210. return false;
  211. }
  212. return true;
  213. };
  214.  
  215. const doesNotUseArguments = classPath => {
  216. const hasArguments = (
  217. j(classPath).find(j.Identifier, {name: 'arguments'}).size() > 0
  218. );
  219. if (hasArguments) {
  220. console.warn(
  221. file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' +
  222. 'was skipped because `arguments` was found in your functions. ' +
  223. 'Arrow functions do not expose an `arguments` object; ' +
  224. 'consider changing to use ES6 spread operator and re-run this script.'
  225. );
  226. return false;
  227. }
  228. return true;
  229. };
  230.  
  231. const isGetInitialStateConstructorSafe = getInitialState => {
  232. if (!getInitialState) {
  233. return true;
  234. }
  235.  
  236. const collection = j(getInitialState);
  237. let result = true;
  238.  
  239. const propsVarDeclarationCount = collection.find(j.VariableDeclarator, {
  240. id: {name: 'props'},
  241. }).size();
  242.  
  243. const contextVarDeclarationCount = collection.find(j.VariableDeclarator, {
  244. id: {name: 'context'},
  245. }).size();
  246.  
  247. if (
  248. propsVarDeclarationCount &&
  249. propsVarDeclarationCount !== collection.find(j.VariableDeclarator, {
  250. id: {name: 'props'},
  251. init: {
  252. type: 'MemberExpression',
  253. object: {type: 'ThisExpression'},
  254. property: {name: 'props'},
  255. }
  256. }).size()
  257. ) {
  258. result = false;
  259. }
  260.  
  261. if (
  262. contextVarDeclarationCount &&
  263. contextVarDeclarationCount !== collection.find(j.VariableDeclarator, {
  264. id: {name: 'context'},
  265. init: {
  266. type: 'MemberExpression',
  267. object: {type: 'ThisExpression'},
  268. property: {name: 'context'},
  269. }
  270. }).size()
  271. ) {
  272. result = false;
  273. }
  274.  
  275. return result;
  276. };
  277.  
  278. const isInitialStateConvertible = classPath => {
  279. const specPath = ReactUtils.directlyGetCreateClassSpec(classPath);
  280. if (!specPath) {
  281. return false;
  282. }
  283. const result = isGetInitialStateConstructorSafe(findGetInitialState(specPath));
  284. if (!result) {
  285. console.warn(
  286. file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' +
  287. 'was skipped because of potential shadowing issues were found in ' +
  288. 'the React component. Rename variable declarations of `props` and/or `context` ' +
  289. 'in your `getInitialState` and re-run this script.'
  290. );
  291. }
  292. return result;
  293. };
  294.  
  295. const canConvertToClass = classPath => {
  296. const specPath = ReactUtils.directlyGetCreateClassSpec(classPath);
  297. if (!specPath) {
  298. return false;
  299. }
  300. const invalidProperties = specPath.properties.filter(prop => (
  301. !prop.key.name || (
  302. !STATIC_KEYS.hasOwnProperty(prop.key.name) &&
  303. STATIC_KEY != prop.key.name &&
  304. !filterDefaultPropsField(prop) &&
  305. !filterGetInitialStateField(prop) &&
  306. !isFunctionExpression(prop) &&
  307. !isPrimProperty(prop) &&
  308. !isPrimPropertyWithTypeAnnotation(prop) &&
  309. MIXIN_KEY != prop.key.name
  310. )
  311. ));
  312.  
  313. if (invalidProperties.length) {
  314. const invalidText = invalidProperties
  315. .map(prop => prop.key.name ? prop.key.name : prop.key)
  316. .join(', ');
  317. console.warn(
  318. file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' +
  319. 'was skipped because of invalid field(s) `' + invalidText + '` on ' +
  320. 'the React component. Remove any right-hand-side expressions that ' +
  321. 'are not simple, like: `componentWillUpdate: createWillUpdate()` or ' +
  322. '`render: foo ? renderA : renderB`.'
  323. );
  324. }
  325. return !invalidProperties.length;
  326. };
  327.  
  328. const areMixinsConvertible = (mixinIdentifierNames, classPath) => {
  329. if (
  330. ReactUtils.directlyHasMixinsField(classPath) &&
  331. !ReactUtils.directlyHasSpecificMixins(classPath, mixinIdentifierNames)
  332. ) {
  333. return false;
  334. }
  335. return true;
  336. };
  337.  
  338. // ---------------------------------------------------------------------------
  339. // Collectors
  340. const pickReturnValueOrCreateIIFE = value => {
  341. if (hasSingleReturnStatement(value)) {
  342. return value.body.body[0].argument;
  343. } else {
  344. return j.callExpression(
  345. value,
  346. []
  347. );
  348. }
  349. };
  350.  
  351. const createDefaultProps = prop =>
  352. withComments(
  353. j.property(
  354. 'init',
  355. j.identifier(DEFAULT_PROPS_KEY),
  356. pickReturnValueOrCreateIIFE(prop.value)
  357. ),
  358. prop
  359. );
  360.  
  361. // Collects `childContextTypes`, `contextTypes`, `displayName`, and `propTypes`;
  362. // simplifies `getDefaultProps` or converts it to an IIFE;
  363. // and collects everything else in the `statics` property object.
  364. const collectStatics = specPath => {
  365. const result = [];
  366.  
  367. for (let i = 0; i < specPath.properties.length; i++) {
  368. const property = specPath.properties[i];
  369. if (createFindPropFn('statics')(property) && property.value && property.value.properties) {
  370. result.push(...property.value.properties);
  371. } else if (createFindPropFn(DEFAULT_PROPS_FIELD)(property)) {
  372. result.push(createDefaultProps(property));
  373. } else if (property.key && STATIC_KEYS.hasOwnProperty(property.key.name)) {
  374. result.push(property);
  375. }
  376. }
  377.  
  378. return result;
  379. };
  380.  
  381. const collectNonStaticProperties = specPath => specPath.properties
  382. .filter(prop =>
  383. !(filterDefaultPropsField(prop) || filterGetInitialStateField(prop))
  384. )
  385. .filter(prop => (!STATIC_KEYS.hasOwnProperty(prop.key.name)) && prop.key.name !== STATIC_KEY)
  386. .filter(prop =>
  387. isFunctionExpression(prop) ||
  388. isPrimPropertyWithTypeAnnotation(prop) ||
  389. isPrimProperty(prop)
  390. );
  391.  
  392. const findRequirePathAndBinding = (moduleName) => {
  393. let result = null;
  394. const requireCall = root.find(j.VariableDeclarator, {
  395. id: {type: 'Identifier'},
  396. init: {
  397. callee: {name: 'require'},
  398. arguments: [{value: moduleName}],
  399. },
  400. });
  401.  
  402. const importStatement = root.find(j.ImportDeclaration, {
  403. source: {
  404. value: moduleName,
  405. },
  406. });
  407.  
  408. if (importStatement.size()) {
  409. importStatement.forEach(path => {
  410. result = {
  411. path,
  412. binding: path.value.specifiers[0].local.name,
  413. type: 'import',
  414. };
  415. });
  416. } else if (requireCall.size()) {
  417. requireCall.forEach(path => {
  418. result = {
  419. path,
  420. binding: path.value.id.name,
  421. type: 'require',
  422. };
  423. });
  424. }
  425.  
  426. return result;
  427. };
  428.  
  429. const pureRenderMixinPathAndBinding = findRequirePathAndBinding(PURE_MIXIN_MODULE_NAME);
  430.  
  431. // ---------------------------------------------------------------------------
  432. // Boom!
  433. const createMethodDefinition = fn =>
  434. withComments(j.methodDefinition(
  435. 'method',
  436. fn.key,
  437. fn.value
  438. ), fn);
  439.  
  440. const updatePropsAndContextAccess = getInitialState => {
  441. const collection = j(getInitialState);
  442.  
  443. collection.find(j.MemberExpression, {
  444. object: {
  445. type: 'ThisExpression',
  446. },
  447. property: {
  448. type: 'Identifier',
  449. name: 'props',
  450. },
  451. }).forEach(path => j(path).replaceWith(j.identifier('props')));
  452.  
  453. collection.find(j.MemberExpression, {
  454. object: {
  455. type: 'ThisExpression',
  456. },
  457. property: {
  458. type: 'Identifier',
  459. name: 'context',
  460. },
  461. }).forEach(path => j(path).replaceWith(j.identifier('context')));
  462. };
  463.  
  464.  
  465. const inlineGetInitialState = getInitialState => {
  466. const functionExpressionCollection = j(getInitialState.value);
  467.  
  468. // at this point if there exists bindings like `const props = ...`, we
  469. // already know the RHS must be `this.props` (see `isGetInitialStateConstructorSafe`)
  470. // so it's safe to just remove them
  471. functionExpressionCollection.find(j.VariableDeclarator, {id: {name: 'props'}})
  472. .forEach(path => j(path).remove());
  473.  
  474. functionExpressionCollection.find(j.VariableDeclarator, {id: {name: 'context'}})
  475. .forEach(path => j(path).remove());
  476.  
  477. return functionExpressionCollection
  478. .find(j.ReturnStatement)
  479. .filter(path => {
  480. // filter out inner function declarations here (helper functions, promises, etc.).
  481. const mainBodyCollection = j(getInitialState.value.body);
  482. return (
  483. mainBodyCollection
  484. .find(j.ArrowFunctionExpression)
  485. .find(j.ReturnStatement, path.value)
  486. .size() === 0 &&
  487. mainBodyCollection
  488. .find(j.FunctionDeclaration)
  489. .find(j.ReturnStatement, path.value)
  490. .size() === 0 &&
  491. mainBodyCollection
  492. .find(j.FunctionExpression)
  493. .find(j.ReturnStatement, path.value)
  494. .size() === 0
  495. );
  496. })
  497. .forEach(path => {
  498. let shouldInsertReturnAfterAssignment = false;
  499.  
  500. // if the return statement is not a direct child of getInitialState's body
  501. if (getInitialState.value.body.body.indexOf(path.value) === -1) {
  502. shouldInsertReturnAfterAssignment = true;
  503. }
  504.  
  505. j(path).replaceWith(j.expressionStatement(
  506. j.assignmentExpression(
  507. '=',
  508. j.memberExpression(
  509. j.thisExpression(),
  510. j.identifier('state'),
  511. false
  512. ),
  513. path.value.argument
  514. )
  515. ));
  516.  
  517. if (shouldInsertReturnAfterAssignment) {
  518. j(path).insertAfter(j.returnStatement(null));
  519. }
  520. }).getAST()[0].value.body.body;
  521. };
  522.  
  523. const convertInitialStateToClassProperty = getInitialState =>
  524. withComments(j.classProperty(
  525. j.identifier('state'),
  526. pickReturnValueOrCreateIIFE(getInitialState.value),
  527. getInitialState.value.returnType,
  528. false
  529. ), getInitialState);
  530.  
  531. const createConstructorArgs = (hasContextAccess) => {
  532. if (hasContextAccess) {
  533. return [j.identifier('props'), j.identifier('context')];
  534. }
  535.  
  536. return [j.identifier('props')];
  537. };
  538.  
  539. const createConstructor = (getInitialState) => {
  540. const initialStateAST = j(getInitialState);
  541. let hasContextAccess = false;
  542.  
  543. if (
  544. initialStateAST.find(j.MemberExpression, { // has `this.context` access
  545. object: {type: 'ThisExpression'},
  546. property: {type: 'Identifier', name: 'context'},
  547. }).size() ||
  548. initialStateAST.find(j.CallExpression, { // a direct method call `this.x()`
  549. callee: {
  550. type: 'MemberExpression',
  551. object: {type: 'ThisExpression'},
  552. },
  553. }).size() ||
  554. initialStateAST.find(j.MemberExpression, { // `this` is referenced alone
  555. object: {type: 'ThisExpression'},
  556. }).size() !== initialStateAST.find(j.ThisExpression).size()
  557. ) {
  558. hasContextAccess = true;
  559. }
  560.  
  561. updatePropsAndContextAccess(getInitialState);
  562. const constructorArgs = createConstructorArgs(hasContextAccess);
  563.  
  564. return [
  565. createMethodDefinition({
  566. key: j.identifier('constructor'),
  567. value: j.functionExpression(
  568. null,
  569. constructorArgs,
  570. j.blockStatement(
  571. [].concat(
  572. [
  573. j.expressionStatement(
  574. j.callExpression(
  575. j.identifier('super'),
  576. constructorArgs
  577. )
  578. ),
  579. ],
  580. inlineGetInitialState(getInitialState)
  581. )
  582. )
  583. ),
  584. }),
  585. ];
  586. };
  587.  
  588. const createArrowFunctionExpression = fn => {
  589. const arrowFunc = j.arrowFunctionExpression(
  590. fn.params,
  591. fn.body,
  592. false
  593. );
  594.  
  595. arrowFunc.returnType = fn.returnType;
  596. arrowFunc.defaults = fn.defaults;
  597. arrowFunc.rest = fn.rest;
  598. arrowFunc.async = fn.async;
  599. arrowFunc.generator = fn.generator;
  600.  
  601. return arrowFunc;
  602. };
  603.  
  604. const createArrowProperty = prop =>
  605. withComments(j.classProperty(
  606. j.identifier(prop.key.name),
  607. createArrowFunctionExpression(prop.value),
  608. null,
  609. false
  610. ), prop);
  611.  
  612. const createClassProperty = prop =>
  613. withComments(j.classProperty(
  614. j.identifier(prop.key.name),
  615. prop.value,
  616. null,
  617. false
  618. ), prop);
  619.  
  620. const createClassPropertyWithType = prop =>
  621. withComments(j.classProperty(
  622. j.identifier(prop.key.name),
  623. prop.value.expression,
  624. prop.value.typeAnnotation,
  625. false
  626. ), prop);
  627.  
  628. // ---------------------------------------------------------------------------
  629. // Flow!
  630.  
  631. const flowAnyType = j.anyTypeAnnotation();
  632. const flowFixMeType = j.genericTypeAnnotation(
  633. j.identifier('$FlowFixMe'),
  634. null
  635. );
  636.  
  637. const literalToFlowType = node => {
  638. if (node.type === 'Identifier' && node.name === 'undefined') {
  639. return j.voidTypeAnnotation();
  640. }
  641.  
  642. switch (typeof node.value) {
  643. case 'string':
  644. return j.stringLiteralTypeAnnotation(node.value, node.raw);
  645. case 'number':
  646. return j.numberLiteralTypeAnnotation(node.value, node.raw);
  647. case 'boolean':
  648. return j.booleanLiteralTypeAnnotation(node.value, node.raw);
  649. case 'object': // we already know it's a NullLiteral here
  650. return j.nullLiteralTypeAnnotation();
  651. default: // this should never happen
  652. return flowFixMeType;
  653. }
  654. };
  655.  
  656. const propTypeToFlowMapping = {
  657. // prim types
  658. any: flowAnyType,
  659. array: j.genericTypeAnnotation(
  660. j.identifier('Array'),
  661. j.typeParameterInstantiation([flowFixMeType])
  662. ),
  663. bool: j.booleanTypeAnnotation(),
  664. element: flowFixMeType, // flow does the same for `element` type in `propTypes`
  665. func: j.genericTypeAnnotation(
  666. j.identifier('Function'),
  667. null
  668. ),
  669. node: flowFixMeType, // flow does the same for `node` type in `propTypes`
  670. number: j.numberTypeAnnotation(),
  671. object: j.genericTypeAnnotation(
  672. j.identifier('Object'),
  673. null
  674. ),
  675. string: j.stringTypeAnnotation(),
  676.  
  677. // type classes
  678. arrayOf: (type) => j.genericTypeAnnotation(
  679. j.identifier('Array'),
  680. j.typeParameterInstantiation([type])
  681. ),
  682. instanceOf: (type) => j.genericTypeAnnotation(
  683. type,
  684. null
  685. ),
  686. objectOf: (type) => j.objectTypeAnnotation([], [
  687. j.objectTypeIndexer(
  688. j.identifier('key'),
  689. j.stringTypeAnnotation(),
  690. type
  691. )
  692. ]),
  693. oneOf: (typeList) => j.unionTypeAnnotation(typeList),
  694. oneOfType: (typeList) => j.unionTypeAnnotation(typeList),
  695. shape: (propList) => j.objectTypeAnnotation(propList),
  696. };
  697.  
  698. const propTypeToFlowAnnotation = val => {
  699. let cursor = val;
  700. let isOptional = true;
  701. let typeResult = flowFixMeType;
  702.  
  703. if ( // check `.isRequired` first
  704. cursor.type === 'MemberExpression' &&
  705. cursor.property.type === 'Identifier' &&
  706. cursor.property.name === 'isRequired'
  707. ) {
  708. isOptional = false;
  709. cursor = cursor.object;
  710. }
  711.  
  712. switch (cursor.type) {
  713. case 'CallExpression': { // type class
  714. const calleeName = cursor.callee.type === 'MemberExpression' ?
  715. cursor.callee.property.name :
  716. cursor.callee.name;
  717.  
  718. const constructor = propTypeToFlowMapping[calleeName];
  719. if (!constructor) { // unknown type class
  720. // it's not necessary since `typeResult` defaults to `flowFixMeType`,
  721. // but it's more explicit this way
  722. typeResult = flowFixMeType;
  723. break;
  724. }
  725.  
  726. switch (cursor.callee.property.name) {
  727. case 'arrayOf': {
  728. const arg = cursor.arguments[0];
  729. typeResult = constructor(
  730. propTypeToFlowAnnotation(arg)[0]
  731. );
  732. break;
  733. }
  734. case 'instanceOf': {
  735. const arg = cursor.arguments[0];
  736. if (arg.type !== 'Identifier') {
  737. typeResult = flowFixMeType;
  738. break;
  739. }
  740.  
  741. typeResult = constructor(arg);
  742. break;
  743. }
  744. case 'objectOf': {
  745. const arg = cursor.arguments[0];
  746. typeResult = constructor(
  747. propTypeToFlowAnnotation(arg)[0]
  748. );
  749. break;
  750. }
  751. case 'oneOf': {
  752. const argList = cursor.arguments[0].elements;
  753. if (
  754. !argList ||
  755. !argList.every(node =>
  756. (node.type === 'Literal') ||
  757. (node.type === 'Identifier' && node.name === 'undefined')
  758. )
  759. ) {
  760. typeResult = flowFixMeType;
  761. } else {
  762. typeResult = constructor(
  763. argList.map(literalToFlowType)
  764. );
  765. }
  766. break;
  767. }
  768. case 'oneOfType': {
  769. const argList = cursor.arguments[0].elements;
  770. if (!argList) {
  771. typeResult = flowFixMeType;
  772. } else {
  773. typeResult = constructor(
  774. argList.map(arg => propTypeToFlowAnnotation(arg)[0])
  775. );
  776. }
  777. break;
  778. }
  779. case 'shape': {
  780. const rawPropList = cursor.arguments[0].properties;
  781. if (!rawPropList) {
  782. typeResult = flowFixMeType;
  783. break;
  784. }
  785. const flowPropList = [];
  786. rawPropList.forEach(typeProp => {
  787. const keyIsLiteral = typeProp.key.type === 'Literal';
  788. const name = keyIsLiteral ? typeProp.key.value : typeProp.key.name;
  789.  
  790. const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value);
  791. flowPropList.push(j.objectTypeProperty(
  792. keyIsLiteral ? j.literal(name) : j.identifier(name),
  793. valueType,
  794. isOptional
  795. ));
  796. });
  797.  
  798. typeResult = constructor(flowPropList);
  799. break;
  800. }
  801. default: {
  802. break;
  803. }
  804. }
  805. break;
  806. }
  807. case 'MemberExpression': { // prim type
  808. if (cursor.property.type !== 'Identifier') { // unrecognizable
  809. typeResult = flowFixMeType;
  810. break;
  811. }
  812.  
  813. const maybeType = propTypeToFlowMapping[cursor.property.name];
  814. if (maybeType) {
  815. typeResult = propTypeToFlowMapping[cursor.property.name];
  816. } else { // type not found
  817. typeResult = flowFixMeType;
  818. }
  819.  
  820. break;
  821. }
  822. default: { // unrecognizable
  823. break;
  824. }
  825. }
  826.  
  827. return [typeResult, isOptional];
  828. };
  829.  
  830. const createFlowAnnotationsFromPropTypesProperties = (prop) => {
  831. const typePropertyList = [];
  832.  
  833. if (!prop || prop.value.type !== 'ObjectExpression') {
  834. return typePropertyList;
  835. }
  836.  
  837. prop.value.properties.forEach(typeProp => {
  838. if (!typeProp.key) { // stuff like SpreadProperty
  839. return;
  840. }
  841.  
  842. const keyIsLiteral = typeProp.key.type === 'Literal';
  843. const name = keyIsLiteral ? typeProp.key.value : typeProp.key.name;
  844.  
  845. const [valueType, isOptional] = propTypeToFlowAnnotation(typeProp.value);
  846. typePropertyList.push(j.objectTypeProperty(
  847. keyIsLiteral ? j.literal(name) : j.identifier(name),
  848. valueType,
  849. isOptional
  850. ));
  851. });
  852.  
  853. return j.classProperty(
  854. j.identifier('props'),
  855. null,
  856. j.typeAnnotation(j.objectTypeAnnotation(typePropertyList)),
  857. false
  858. );
  859. };
  860.  
  861. // to ensure that our property initializers' evaluation order is safe
  862. const repositionStateProperty = (initialStateProperty, propertiesAndMethods) => {
  863. const initialStateCollection = j(initialStateProperty);
  864. const thisCount = initialStateCollection.find(j.ThisExpression).size();
  865. const safeThisMemberCount = initialStateCollection.find(j.MemberExpression, {
  866. object: {
  867. type: 'ThisExpression',
  868. },
  869. property: {
  870. type: 'Identifier',
  871. name: 'props',
  872. },
  873. }).size() + initialStateCollection.find(j.MemberExpression, {
  874. object: {
  875. type: 'ThisExpression',
  876. },
  877. property: {
  878. type: 'Identifier',
  879. name: 'context',
  880. },
  881. }).size();
  882.  
  883. if (thisCount === safeThisMemberCount) {
  884. return initialStateProperty.concat(propertiesAndMethods);
  885. }
  886.  
  887. const result = [].concat(propertiesAndMethods);
  888. let lastPropPosition = result.length - 1;
  889.  
  890. while (lastPropPosition >= 0 && result[lastPropPosition].kind === 'method') {
  891. lastPropPosition--;
  892. }
  893.  
  894. result.splice(lastPropPosition + 1, 0, initialStateProperty[0]);
  895. return result;
  896. };
  897.  
  898. // if there's no `getInitialState` or the `getInitialState` function is simple
  899. // (i.e., it's just a return statement) then we don't need a constructor.
  900. // we can simply lift `state = {...}` as a property initializer.
  901. // otherwise, create a constructor and inline `this.state = ...`.
  902. //
  903. // when we need to create a constructor, we only put `context` as the
  904. // second parameter when the following things happen in `getInitialState()`:
  905. // 1. there's a `this.context` access, or
  906. // 2. there's a direct method call `this.x()`, or
  907. // 3. `this` is referenced alone
  908. const createESClass = (
  909. name,
  910. baseClassName,
  911. staticProperties,
  912. getInitialState,
  913. rawProperties,
  914. comments
  915. ) => {
  916. const initialStateProperty = [];
  917. let maybeConstructor = [];
  918. let maybeFlowStateAnnotation = []; // we only need this when we do `this.state = ...`
  919.  
  920. if (isInitialStateLiftable(getInitialState)) {
  921. if (getInitialState) {
  922. initialStateProperty.push(convertInitialStateToClassProperty(getInitialState));
  923. }
  924. } else {
  925. maybeConstructor = createConstructor(getInitialState);
  926. if (shouldTransformFlow) {
  927. let stateType = j.typeAnnotation(
  928. j.existsTypeAnnotation()
  929. );
  930.  
  931. if (getInitialState.value.returnType) {
  932. stateType = getInitialState.value.returnType;
  933. }
  934.  
  935. maybeFlowStateAnnotation.push(j.classProperty(
  936. j.identifier('state'),
  937. null,
  938. stateType,
  939. false
  940. ));
  941. }
  942. }
  943.  
  944. const propertiesAndMethods = rawProperties.map(prop => {
  945. if (isPrimPropertyWithTypeAnnotation(prop)) {
  946. return createClassPropertyWithType(prop);
  947. } else if (isPrimProperty(prop)) {
  948. return createClassProperty(prop);
  949. } else if (AUTOBIND_IGNORE_KEYS.hasOwnProperty(prop.key.name)) {
  950. return createMethodDefinition(prop);
  951. }
  952.  
  953. return createArrowProperty(prop);
  954. });
  955.  
  956. const flowPropsAnnotation = shouldTransformFlow ?
  957. createFlowAnnotationsFromPropTypesProperties(
  958. staticProperties.find((path) => path.key.name === 'propTypes')
  959. ) :
  960. [];
  961.  
  962. let finalStaticProperties = staticProperties;
  963.  
  964. if (shouldTransformFlow && options['remove-runtime-proptypes']) {
  965. finalStaticProperties = staticProperties.filter((prop) => prop.key.name !== 'propTypes');
  966. }
  967.  
  968. return withComments(j.classDeclaration(
  969. name ? j.identifier(name) : null,
  970. j.classBody(
  971. [].concat(
  972. flowPropsAnnotation,
  973. maybeFlowStateAnnotation,
  974. finalStaticProperties,
  975. maybeConstructor,
  976. repositionStateProperty(initialStateProperty, propertiesAndMethods)
  977. )
  978. ),
  979. j.memberExpression(
  980. j.identifier('React'),
  981. j.identifier(baseClassName),
  982. false
  983. )
  984. ), {comments});
  985. };
  986.  
  987. const createStaticClassProperty = staticProperty => {
  988. if (staticProperty.value.type === 'FunctionExpression') {
  989. return withComments(j.methodDefinition(
  990. 'method',
  991. j.identifier(staticProperty.key.name),
  992. staticProperty.value,
  993. true
  994. ), staticProperty);
  995. }
  996.  
  997. if (staticProperty.value.type === 'TypeCastExpression') {
  998. return withComments(j.classProperty(
  999. j.identifier(staticProperty.key.name),
  1000. staticProperty.value.expression,
  1001. staticProperty.value.typeAnnotation,
  1002. true
  1003. ), staticProperty);
  1004. }
  1005.  
  1006. return withComments(j.classProperty(
  1007. j.identifier(staticProperty.key.name),
  1008. staticProperty.value,
  1009. null,
  1010. true
  1011. ), staticProperty);
  1012. };
  1013.  
  1014. const createStaticClassProperties = statics =>
  1015. statics.map(createStaticClassProperty);
  1016.  
  1017. const getComments = classPath => {
  1018. if (classPath.value.comments) {
  1019. return classPath.value.comments;
  1020. }
  1021. const declaration = j(classPath).closest(j.VariableDeclaration);
  1022. if (declaration.size()) {
  1023. return declaration.get().value.comments;
  1024. }
  1025. return null;
  1026. };
  1027.  
  1028. const findUnusedVariables = (path, varName) => j(path)
  1029. .closestScope()
  1030. .find(j.Identifier, {name: varName})
  1031. // Ignore require vars
  1032. .filter(identifierPath => identifierPath.value !== path.value.id)
  1033. // Ignore import bindings
  1034. .filter(identifierPath => !(
  1035. path.value.type === 'ImportDeclaration' &&
  1036. path.value.specifiers.some(specifier => specifier.local === identifierPath.value)
  1037. ))
  1038. // Ignore properties in MemberExpressions
  1039. .filter(identifierPath => {
  1040. const parent = identifierPath.parent.value;
  1041. return !(
  1042. j.MemberExpression.check(parent) &&
  1043. parent.property === identifierPath.value
  1044. );
  1045. });
  1046.  
  1047. const updateToClass = (classPath) => {
  1048. const specPath = ReactUtils.directlyGetCreateClassSpec(classPath);
  1049. const name = ReactUtils.directlyGetComponentName(classPath);
  1050. const statics = collectStatics(specPath);
  1051. const properties = collectNonStaticProperties(specPath);
  1052. const comments = getComments(classPath);
  1053.  
  1054. const getInitialState = findGetInitialState(specPath);
  1055.  
  1056. var path = classPath;
  1057.  
  1058. if (
  1059. classPath.parentPath &&
  1060. classPath.parentPath.value &&
  1061. classPath.parentPath.value.type === 'VariableDeclarator'
  1062. ) {
  1063. // the reason that we need to do this awkward dance here is that
  1064. // for things like `var Foo = React.createClass({...})`, we need to
  1065. // replace the _entire_ VariableDeclaration with
  1066. // `class Foo extends React.Component {...}`.
  1067. // it looks scary but since we already know it's a VariableDeclarator
  1068. // it's actually safe.
  1069. // (VariableDeclaration > declarations > VariableDeclarator > CallExpression)
  1070. path = classPath.parentPath.parentPath.parentPath;
  1071. }
  1072.  
  1073. const staticProperties = createStaticClassProperties(statics);
  1074. const baseClassName =
  1075. pureRenderMixinPathAndBinding &&
  1076. ReactUtils.directlyHasSpecificMixins(classPath, [pureRenderMixinPathAndBinding.binding]) ?
  1077. 'PureComponent' :
  1078. 'Component';
  1079.  
  1080. j(path).replaceWith(
  1081. createESClass(
  1082. name,
  1083. baseClassName,
  1084. staticProperties,
  1085. getInitialState,
  1086. properties,
  1087. comments
  1088. )
  1089. );
  1090. };
  1091.  
  1092. const addDisplayName = (displayName, specPath) => {
  1093. const props = specPath.properties;
  1094. let safe = true;
  1095.  
  1096. for (let i = 0; i < props.length; i++) {
  1097. const prop = props[i];
  1098. if (prop.key.name === 'displayName') {
  1099. safe = false;
  1100. break;
  1101. }
  1102. }
  1103.  
  1104. if (safe) {
  1105. props.unshift(j.objectProperty(j.identifier('displayName'), j.stringLiteral(displayName)));
  1106. }
  1107. };
  1108.  
  1109. const fallbackToCreateClassModule = (classPath) => {
  1110. const comments = getComments(classPath);
  1111. const specPath = ReactUtils.directlyGetCreateClassSpec(classPath);
  1112.  
  1113. if (!NO_DISPLAY_NAME) {
  1114. if (specPath) {
  1115. // Add a displayName property to the spec object
  1116. let path = classPath;
  1117. let displayName;
  1118. while (path && displayName === undefined) {
  1119. switch (path.node.type) {
  1120. case 'ExportDefaultDeclaration':
  1121. displayName = basename(file.path, extname(file.path));
  1122. if (displayName === 'index') {
  1123. // ./{module name}/index.js
  1124. displayName = basename(dirname(file.path));
  1125. }
  1126. break;
  1127. case 'VariableDeclarator':
  1128. displayName = path.node.id.name;
  1129. break;
  1130. case 'AssignmentExpression':
  1131. displayName = path.node.left.name;
  1132. break;
  1133. case 'Property':
  1134. displayName = path.node.key.name;
  1135. break;
  1136. case 'Statement':
  1137. displayName = null;
  1138. break;
  1139. }
  1140. path = path.parent;
  1141. }
  1142. if (displayName) {
  1143. addDisplayName(displayName, specPath);
  1144. }
  1145. }
  1146. }
  1147.  
  1148. withComments(
  1149. j(classPath).replaceWith(
  1150. specPath
  1151. ? j.callExpression(j.identifier(CREATE_CLASS_VARIABLE_NAME), [specPath])
  1152. : j.callExpression(j.identifier(CREATE_CLASS_VARIABLE_NAME), classPath.value.arguments)
  1153. ),
  1154. {comments},
  1155. );
  1156. };
  1157.  
  1158. if (
  1159. options['explicit-require'] === false || ReactUtils.hasReact(root)
  1160. ) {
  1161. // no mixins found on the classPath -> true
  1162. // pure mixin identifier not found -> (has mixins) -> false
  1163. // found pure mixin identifier ->
  1164. // class mixins is an array and only contains the identifier -> true
  1165. // otherwise -> false
  1166. const mixinsFilter = (classPath) => {
  1167. if (!ReactUtils.directlyHasMixinsField(classPath)) {
  1168. return true;
  1169. } else if (options['pure-component'] && pureRenderMixinPathAndBinding) {
  1170. const {binding} = pureRenderMixinPathAndBinding;
  1171. if (areMixinsConvertible([binding], classPath)) {
  1172. return true;
  1173. }
  1174. }
  1175. console.warn(
  1176. file.path + ': `' + ReactUtils.directlyGetComponentName(classPath) + '` ' +
  1177. 'was skipped because of inconvertible mixins.'
  1178. );
  1179.  
  1180. return false;
  1181. };
  1182.  
  1183. const reinsertTopComments = () => {
  1184. root.get().node.comments = topComments;
  1185. };
  1186.  
  1187. let didTransform = false;
  1188.  
  1189. const path = ReactUtils.findAllReactCreateClassCalls(root);
  1190. if (NO_CONVERSION) {
  1191. throw new Error("Unsupported NO_CONVERSION");
  1192. } else {
  1193. // the only time that we can't simply replace the createClass call path
  1194. // with a new class is when the parent of that is a variable declaration.
  1195. // let's delay it and figure it out later (by looking at `path.parentPath`)
  1196. // in `updateToClass`.
  1197. path.forEach(childPath => {
  1198. if (
  1199. mixinsFilter(childPath) &&
  1200. hasNoCallsToDeprecatedAPIs(childPath) &&
  1201. hasNoRefsToAPIsThatWillBeRemoved(childPath) &&
  1202. doesNotUseArguments(childPath) &&
  1203. isInitialStateConvertible(childPath) &&
  1204. canConvertToClass(childPath)
  1205. ) {
  1206. didTransform = true;
  1207. updateToClass(childPath);
  1208. }
  1209. });
  1210. }
  1211.  
  1212. if (didTransform) {
  1213. // prune removed requires
  1214. if (pureRenderMixinPathAndBinding) {
  1215. const {binding, path, type} = pureRenderMixinPathAndBinding;
  1216. let shouldReinsertComment = false;
  1217. if (findUnusedVariables(path, binding).size() === 0) {
  1218. var removePath = null;
  1219. if (type === 'require') {
  1220. const bodyNode = path.parentPath.parentPath.parentPath.value;
  1221. const variableDeclarationNode = path.parentPath.parentPath.value;
  1222.  
  1223. removePath = path.parentPath.parentPath;
  1224. shouldReinsertComment = bodyNode.indexOf(variableDeclarationNode) === 0;
  1225. } else {
  1226. const importDeclarationNode = path.value;
  1227. const bodyNode = path.parentPath.value;
  1228.  
  1229. removePath = path;
  1230. shouldReinsertComment = bodyNode.indexOf(importDeclarationNode) === 0;
  1231. }
  1232.  
  1233. j(removePath).remove();
  1234. if (shouldReinsertComment) {
  1235. reinsertTopComments();
  1236. }
  1237. }
  1238. }
  1239. }
  1240. }
  1241. return root.toSource(printOptions);
  1242. };
  1243.  
  1244. module.exports.parser = 'flow';
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement