Skip to content

Commit

Permalink
Add a deprecatedPropType module to show deprecation warnings
Browse files Browse the repository at this point in the history
Summary:
To allow smoother API changes for users we often deprecate props and keep them around for a while before removing them. Right now it is all done manually, this adds a consistent way to show a warning when using a deprecated prop.

This also adds a deprecation warning of the website generated from the deprecatedPropType.

<img width="643" alt="screen shot 2016-01-26 at 7 43 08 pm" src="https://cloud.githubusercontent.com/assets/2677334/12600172/7af28fb0-c465-11e5-85e5-3786852bf522.png">

It also changes places where we added the warnings manually to use deprecatedPropType instead.
Closes #5566

Reviewed By: svcscm

Differential Revision: D2874629

Pulled By: nicklockwood

fb-gh-sync-id: c3c63bae7bbec26cc146029abd9aa5efbe73f795
  • Loading branch information
janicduplessis authored and facebook-github-bot-3 committed Jan 29, 2016
1 parent 8de86a0 commit 1c6e837
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 37 deletions.
41 changes: 21 additions & 20 deletions Libraries/Components/MapView/MapView.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const React = require('React');
const StyleSheet = require('StyleSheet');
const View = require('View');

const deprecatedPropType = require('deprecatedPropType');
const processColor = require('processColor');
const resolveAssetSource = require('resolveAssetSource');
const requireNativeComponent = require('requireNativeComponent');
Expand Down Expand Up @@ -64,7 +65,7 @@ const MapView = React.createClass({
* @platform ios
*/
followUserLocation: React.PropTypes.bool,

/**
* If `false` points of interest won't be displayed on the map.
* Default value is `true`.
Expand Down Expand Up @@ -160,15 +161,15 @@ const MapView = React.createClass({
* Whether the pin drop should be animated or not
*/
animateDrop: React.PropTypes.bool,

/**
* Whether the pin should be draggable or not
*/
draggable: React.PropTypes.bool,

/**
* Event that fires when the annotation drag state changes.
*/
*/
onDragStateChange: React.PropTypes.func,

/**
Expand Down Expand Up @@ -213,11 +214,22 @@ const MapView = React.createClass({
/**
* Deprecated. Use the left/right/detailsCalloutView props instead.
*/
hasLeftCallout: React.PropTypes.bool,
hasRightCallout: React.PropTypes.bool,
onLeftCalloutPress: React.PropTypes.func,
onRightCalloutPress: React.PropTypes.func,

hasLeftCallout: deprecatedPropType(
React.PropTypes.bool,
'Use `leftCalloutView` instead.'
),
hasRightCallout: deprecatedPropType(
React.PropTypes.bool,
'Use `rightCalloutView` instead.'
),
onLeftCalloutPress: deprecatedPropType(
React.PropTypes.func,
'Use `leftCalloutView` instead.'
),
onRightCalloutPress: deprecatedPropType(
React.PropTypes.func,
'Use `rightCalloutView` instead.'
),
})),

/**
Expand Down Expand Up @@ -335,18 +347,7 @@ const MapView = React.createClass({
style: [styles.calloutView, detailCalloutView.props.style || {}]
}));
}
if (__DEV__) {
['hasLeftCallout', 'onLeftCalloutPress'].forEach(key => {
if (annotation[key]) {
console.warn('`' + key + '` is deprecated. Use leftCalloutView instead.');
}
});
['hasRightCallout', 'onRightCalloutPress'].forEach(key => {
if (annotation[key]) {
console.warn('`' + key + '` is deprecated. Use rightCalloutView instead.');
}
});
}

let result = {
...annotation,
tintColor: tintColor && processColor(tintColor),
Expand Down
8 changes: 5 additions & 3 deletions Libraries/Components/ScrollView/ScrollView.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var View = require('View');
var ViewStylePropTypes = require('ViewStylePropTypes');

var deepDiffer = require('deepDiffer');
var deprecatedPropType = require('deprecatedPropType');
var dismissKeyboard = require('dismissKeyboard');
var flattenStyle = require('flattenStyle');
var insetsDiffer = require('insetsDiffer');
Expand Down Expand Up @@ -321,10 +322,12 @@ var ScrollView = React.createClass({
refreshControl: PropTypes.element,

/**
* Deprecated - use `refreshControl` property instead.
* @platform ios
*/
onRefreshStart: PropTypes.func,
onRefreshStart: deprecatedPropType(
PropTypes.func,
'Use the `refreshControl` prop instead.'
),
},

mixins: [ScrollResponder.Mixin],
Expand Down Expand Up @@ -464,7 +467,6 @@ var ScrollView = React.createClass({

var onRefreshStart = this.props.onRefreshStart;
if (onRefreshStart) {
console.warn('onRefreshStart is deprecated. Use the refreshControl prop instead.');
// this is necessary because if we set it on props, even when empty,
// it'll trigger the default pull-to-refresh behavior on native.
props.onRefreshStart =
Expand Down
17 changes: 9 additions & 8 deletions Libraries/CustomComponents/Navigator/Navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var TimerMixin = require('react-timer-mixin');
var View = require('View');

var clamp = require('clamp');
var deprecatedPropType = require('deprecatedPropType');
var flattenStyle = require('flattenStyle');
var invariant = require('invariant');
var rebound = require('rebound');
Expand Down Expand Up @@ -224,21 +225,21 @@ var Navigator = React.createClass({
initialRouteStack: PropTypes.arrayOf(PropTypes.object),

/**
* @deprecated
* Use `navigationContext.addListener('willfocus', callback)` instead.
*
* Will emit the target route upon mounting and before each nav transition
*/
onWillFocus: PropTypes.func,
onWillFocus: deprecatedPropType(
PropTypes.func,
"Use `navigationContext.addListener('willfocus', callback)` instead."
),

/**
* @deprecated
* Use `navigationContext.addListener('didfocus', callback)` instead.
*
* Will be called with the new route of each scene after the transition is
* complete or after the initial mounting
*/
onDidFocus: PropTypes.func,
onDidFocus: deprecatedPropType(
PropTypes.func,
"Use `navigationContext.addListener('didfocus', callback)` instead."
),

/**
* Optionally provide a navigation bar that persists across scene
Expand Down
11 changes: 6 additions & 5 deletions Libraries/StyleSheet/TransformPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'use strict';

var ReactPropTypes = require('ReactPropTypes');
var deprecatedPropType = require('deprecatedPropType');

var ArrayOfNumberPropType = ReactPropTypes.arrayOf(ReactPropTypes.number);

Expand Down Expand Up @@ -49,11 +50,11 @@ var TransformPropTypes = {
transformMatrix: TransformMatrixPropType,

/* Deprecated transform props used on Android only */
scaleX: ReactPropTypes.number,
scaleY: ReactPropTypes.number,
rotation: ReactPropTypes.number,
translateX: ReactPropTypes.number,
translateY: ReactPropTypes.number,
scaleX: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
scaleY: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
rotation: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
translateX: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
translateY: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
};

module.exports = TransformPropTypes;
33 changes: 33 additions & 0 deletions Libraries/Utilities/deprecatedPropType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2015-present, 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.
*
* @providesModule deprecatedPropType
* @flow
*/
'use strict';

const UIManager = require('UIManager');

/**
* Adds a deprecation warning when the prop is used.
*/
function deprecatedPropType(
propType: ReactPropsCheckType,
explanation: string
): ReactPropsCheckType {
return function validate(props, propName, componentName) {
// Don't warn for native components.
if (!UIManager[componentName] && props[propName] !== undefined) {
console.warn(`\`${propName}\` supplied to \`${componentName}\` has been deprecated. ${explanation}`);
}

return propType(props, propName, componentName);
};
}

module.exports = deprecatedPropType;
9 changes: 9 additions & 0 deletions website/layout/AutodocsLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ var ComponentDoc = React.createClass({
{renderType(prop.type)}
</span>}
</Header>
{prop.deprecationMessage && <div className="deprecated">
<div className="deprecatedTitle">
<img className="deprecatedIcon" src="/react-native/img/Warning.png" />
<span>Deprecated</span>
</div>
<div className="deprecatedMessage">
<Marked>{prop.deprecationMessage}</Marked>
</div>
</div>}
{prop.type && prop.type.name === 'stylesheet' &&
this.renderStylesheetProps(prop.type.value)}
{prop.description && <Marked>{prop.description}</Marked>}
Expand Down
39 changes: 39 additions & 0 deletions website/server/docgenHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function stylePropTypeHandler(documentation, path) {
if (!propTypesPath) {
return;
}

propTypesPath = docgen.utils.resolveToValue(propTypesPath);
if (!propTypesPath || propTypesPath.node.type !== 'ObjectExpression') {
return;
Expand Down Expand Up @@ -34,6 +35,43 @@ function stylePropTypeHandler(documentation, path) {
});
}

function deprecatedPropTypeHandler(documentation, path) {
var propTypesPath = docgen.utils.getMemberValuePath(path, 'propTypes');
if (!propTypesPath) {
return;
}

propTypesPath = docgen.utils.resolveToValue(propTypesPath);
if (!propTypesPath || propTypesPath.node.type !== 'ObjectExpression') {
return;
}

// Checks for deprecatedPropType function and add deprecation info.
propTypesPath.get('properties').each(function(propertyPath) {
var valuePath = docgen.utils.resolveToValue(propertyPath.get('value'));
// If it's a call to deprecatedPropType, do stuff
if (valuePath.node.type !== 'CallExpression' ||
valuePath.node.callee.name !== 'deprecatedPropType') {
return;
}
var propDescriptor = documentation.getPropDescriptor(
docgen.utils.getPropertyName(propertyPath)
);
// The 2nd argument of deprecatedPropType is the deprecation message.
// Used printValue to get the string otherwise there was issues with template
// strings.
propDescriptor.deprecationMessage = docgen.utils
.printValue(valuePath.get('arguments', 1))
// Remove the quotes printValue adds.
.slice(1, -1);

// Get the actual prop type.
propDescriptor.type = docgen.utils.getPropType(
valuePath.get('arguments', 0)
);
});
}

function findExportedOrFirst(node, recast) {
return docgen.resolver.findExportedComponentDefinition(node, recast) ||
docgen.resolver.findAllComponentDefinitions(node, recast)[0];
Expand Down Expand Up @@ -86,5 +124,6 @@ function findExportedObject(ast, recast) {
}

exports.stylePropTypeHandler = stylePropTypeHandler;
exports.deprecatedPropTypeHandler = deprecatedPropTypeHandler;
exports.findExportedOrFirst = findExportedOrFirst;
exports.findExportedObject = findExportedObject;
5 changes: 4 additions & 1 deletion website/server/extractDocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ function renderComponent(filepath) {
var json = docgen.parse(
fs.readFileSync(filepath),
docgenHelpers.findExportedOrFirst,
docgen.defaultHandlers.concat(docgenHelpers.stylePropTypeHandler)
docgen.defaultHandlers.concat([
docgenHelpers.stylePropTypeHandler,
docgenHelpers.deprecatedPropTypeHandler,
])
);

return componentsToMarkdown('component', json, filepath, n++, styleDocs);
Expand Down
22 changes: 22 additions & 0 deletions website/src/react-native/css/react-native.css
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,28 @@ div[data-twttr-id] iframe {
border: 1px solid rgba(0, 0, 0, 0.2);
}

.deprecated {
margin-bottom: 24px;
}

.deprecatedTitle {
margin-bottom: 6px;
line-height: 18px;
font-weight: bold;
color: #ffa500;
}

.deprecatedIcon {
width: 18px;
height: 18px;
margin-right: 8px;
vertical-align: top;
}

.deprecatedMessage {
margin-left: 26px;
}

.edit-github {
font-size: 15px;
font-weight: normal;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

1 comment on commit 1c6e837

@mkonicek
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.