Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-add I18n components #1466

Merged
merged 2 commits into from
Jan 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)

No public interface changes since `6.6.0`.
- Re-added EuiI18n, EuiI18nNumber, and EuiContext for localization ([#1466](https://github.com/elastic/eui/pull/1466))

## [`6.6.0`](https://github.com/elastic/eui/tree/v6.6.0)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"cssnano": "^4.0.5",
"dts-generator": "^2.1.0",
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.2",
"enzyme-adapter-react-16.3": "^1.4.1",
"enzyme-to-json": "^3.3.0",
"eslint": "^4.9.0",
"eslint-config-prettier": "^2.9.0",
Expand Down
165 changes: 136 additions & 29 deletions scripts/babel/proptypes-from-ts-props/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ function resolveIdentifierToPropTypes(node, state) {
);

// PropTypes.node
case 'ReactChild':
case 'ReactNode':
return types.memberExpression(
types.identifier('PropTypes'),
Expand All @@ -139,14 +140,56 @@ function resolveIdentifierToPropTypes(node, state) {
if (identifier.name === 'ExclusiveUnion') {
// We use ExclusiveUnion at the top level to exclusively discriminate between types
// propTypes itself must be an object so merge the union sets together as an intersection
return getPropTypesForNode(

// Any types that are optional or non-existant on one side must be optional after the union
const aPropType = getPropTypesForNode(node.typeParameters.params[0], true, state);
const bPropType = getPropTypesForNode(node.typeParameters.params[1], true, state);

const propsOnA = types.isCallExpression(aPropType) ? aPropType.arguments[0].properties : [];
const propsOnB = types.isCallExpression(bPropType) ? bPropType.arguments[0].properties : [];

// optional props is any prop that is optional or non-existant on one side
const optionalProps = new Set();
for (let i = 0; i < propsOnA.length; i++) {
const property = propsOnA[i];
const propertyName = property.key.name;
const isOptional = !isPropTypeRequired(types, property.value);
const existsOnB = propsOnB.find(property => property.key.name === propertyName) != null;
if (isOptional || !existsOnB) {
optionalProps.add(propertyName);
}
}
for (let i = 0; i < propsOnB.length; i++) {
const property = propsOnB[i];
const propertyName = property.key.name;
const isOptional = !isPropTypeRequired(types, property.value);
const existsOnA = propsOnA.find(property => property.key.name === propertyName) != null;
if (isOptional || !existsOnA) {
optionalProps.add(propertyName);
}
}

const propTypes = getPropTypesForNode(
{
type: 'TSIntersectionType',
types: node.typeParameters.params,
},
true,
state
);

if (types.isCallExpression(propTypes)) {
// apply the optionals
const properties = propTypes.arguments[0].properties;
for (let i = 0; i < properties.length; i++) {
const property = properties[i];
if (optionalProps.has(property.key.name)) {
property.value = makePropTypeOptional(types, property.value);
}
}
}

return propTypes;
}

// Lookup this identifier from types/interfaces defined in code
Expand All @@ -172,6 +215,41 @@ function buildPropTypePrimitiveExpression(types, typeName) {
);
}

function isPropTypeRequired(types, propType) {
return types.isMemberExpression(propType) &&
types.isIdentifier(propType.property) &&
propType.property.name === 'isRequired';
}

function makePropTypeRequired(types, propType) {
return types.memberExpression(
propType,
types.identifier('isRequired')
);
}

function makePropTypeOptional(types, propType) {
if (isPropTypeRequired(types, propType)) {
// strip the .isRequired member expression
return propType.object;
}
return propType;
}

function areExpressionsIdentical(a, b) {
const aCode = babelCore.transformFromAst(babelCore.types.program([
babelCore.types.expressionStatement(
babelCore.types.removeComments(babelCore.types.cloneDeep(a))
)
])).code;
const bCode = babelCore.transformFromAst(babelCore.types.program([
babelCore.types.expressionStatement(
babelCore.types.removeComments(babelCore.types.cloneDeep(b))
)
])).code;
return aCode === bCode;
}

/**
* Heavy lifter to generate the proptype AST for a node. Initially called by `processComponentDeclaration`,
* its return value is set as the component's `propTypes` value. This function calls itself recursively to translate
Expand Down Expand Up @@ -202,31 +280,35 @@ function getPropTypesForNode(node, optional, state) {

// translates intersections (Foo & Bar & Baz) to a shape with the types' members (Foo, Bar, Baz) merged together
case 'TSIntersectionType':
const usableNodes = node.types.filter(node => {
const nodePropTypes = getPropTypesForNode(node, true, state);

if (
types.isMemberExpression(nodePropTypes) &&
nodePropTypes.object.name === 'PropTypes' &&
nodePropTypes.property.name === 'any'
) {
return false;
}

// validate that this resulted in a shape, otherwise we don't know how to extract/merge the values
if (
!types.isCallExpression(nodePropTypes) ||
!types.isMemberExpression(nodePropTypes.callee) ||
nodePropTypes.callee.object.name !== 'PropTypes' ||
nodePropTypes.callee.property.name !== 'shape'
) {
return false;
}

return true;
});

// merge the resolved proptypes for each intersection member into one object, mergedProperties
const mergedProperties = node.types.reduce(
const mergedProperties = usableNodes.reduce(
(mergedProperties, node) => {
const nodePropTypes = getPropTypesForNode(node, true, state);

// if this propType is PropTypes.any there is nothing to do here
if (
types.isMemberExpression(nodePropTypes) &&
nodePropTypes.object.name === 'PropTypes' &&
nodePropTypes.property.name === 'any'
) {
return mergedProperties;
}

// validate that this resulted in a shape, otherwise we don't know how to extract/merge the values
if (
!types.isCallExpression(nodePropTypes) ||
!types.isMemberExpression(nodePropTypes.callee) ||
nodePropTypes.callee.object.name !== 'PropTypes' ||
nodePropTypes.callee.property.name !== 'shape'
) {
return mergedProperties;
// throw new Error('Cannot process an encountered type intersection');
}

// iterate over this type's members, adding them (and their comments) to `mergedProperties`
const typeProperties = nodePropTypes.arguments[0].properties; // properties on the ObjectExpression passed to PropTypes.shape()
for (let i = 0; i < typeProperties.length; i++) {
Expand All @@ -237,7 +319,32 @@ function getPropTypesForNode(node, optional, state) {
...(typeProperty.leadingComments || []),
...((mergedProperties[typeProperty.key.name] ? mergedProperties[typeProperty.key.name].leadingComments : null) || []),
];
mergedProperties[typeProperty.key.name] = typeProperty.value;

// if this property has already been found, the only action is to potentially change it to optional
if (mergedProperties.hasOwnProperty(typeProperty.key.name)) {
const existing = mergedProperties[typeProperty.key.name];
if (!areExpressionsIdentical(existing, typeProperty.value)) {
mergedProperties[typeProperty.key.name] = types.callExpression(
types.memberExpression(
types.identifier('PropTypes'),
types.identifier('oneOfType'),
),
[
types.arrayExpression(
[existing, typeProperty.value]
)
]
);

if (isPropTypeRequired(types, existing) && isPropTypeRequired(types, typeProperty.value)) {
mergedProperties[typeProperty.key.name] = makePropTypeRequired(types, mergedProperties[typeProperty.key.name]);
}
}
} else {
// property hasn't been seen yet, add it
mergedProperties[typeProperty.key.name] = typeProperty.value;
}

mergedProperties[typeProperty.key.name].leadingComments = leadingComments;
}

Expand Down Expand Up @@ -346,6 +453,9 @@ function getPropTypesForNode(node, optional, state) {
[
types.objectExpression(
node.members.map(property => {
// skip TS index signatures
if (types.isTSIndexSignature(property)) return null;

const objectProperty = types.objectProperty(
types.identifier(property.key.name || `"${property.key.value}"`),
getPropTypesForNode(property.typeAnnotation, property.optional, state)
Expand All @@ -354,7 +464,7 @@ function getPropTypesForNode(node, optional, state) {
objectProperty.leadingComments = property.leadingComments.map(({ type, value }) => ({ type, value }));
}
return objectProperty;
})
}).filter(x => x != null)
)
]
);
Expand Down Expand Up @@ -548,10 +658,7 @@ function getPropTypesForNode(node, optional, state) {
if (optional) {
return propType;
} else {
return types.memberExpression(
propType,
types.identifier('isRequired')
);
return makePropTypeRequired(types, propType);
}
}

Expand Down Expand Up @@ -967,7 +1074,7 @@ module.exports = function propTypesFromTypeScript({ types }) {
processComponentDeclaration(idTypeAnnotation.typeAnnotation.typeParameters.params[0], nodePath, state);
fileCodeNeedsUpdating = true;
} else {
throw new Error(`Cannot process annotation id React.${right.name}`);
// throw new Error(`Cannot process annotation id React.${right.name}`);
}
}
} else if (idTypeAnnotation.typeAnnotation.typeName.type === 'Identifier') {
Expand Down
6 changes: 3 additions & 3 deletions scripts/babel/proptypes-from-ts-props/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ import React from 'react';
export type ExclusiveUnion<T, U> = any;
interface BaseProps { asdf: boolean }
interface IFooProps extends BaseProps {d: number, foo?: string}
interface IBarProps extends BaseProps {d: string, bar?: string}
interface IBarProps extends BaseProps {d: string, foo: string, bar: string}
const FooComponent: React.SFC<ExclusiveUnion<IFooProps, IBarProps>> = () => {
return (<div>Hello World</div>);
}`,
Expand All @@ -999,8 +999,8 @@ const FooComponent = () => {
};

FooComponent.propTypes = {
d: PropTypes.string.isRequired,
foo: PropTypes.string,
d: PropTypes.oneOfType([PropTypes.number.isRequired, PropTypes.string.isRequired]).isRequired,
foo: PropTypes.oneOfType([PropTypes.string, PropTypes.string.isRequired]),
asdf: PropTypes.bool.isRequired,
bar: PropTypes.string
};`);
Expand Down
2 changes: 1 addition & 1 deletion scripts/jest/setup/enzyme.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Adapter from 'enzyme-adapter-react-16.3';

configure({ adapter: new Adapter() });
8 changes: 8 additions & 0 deletions src-docs/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ import { ColorPickerExample }
import { ComboBoxExample }
from './views/combo_box/combo_box_example';

import { ContextExample }
from './views/context/context_example';

import { ContextMenuExample }
from './views/context_menu/context_menu_example';

Expand Down Expand Up @@ -147,6 +150,9 @@ import { HighlightExample }
import { HorizontalRuleExample }
from './views/horizontal_rule/horizontal_rule_example';

import { I18nExample }
from './views/i18n/i18n_example';

import { IconExample }
from './views/icon/icon_example';

Expand Down Expand Up @@ -413,11 +419,13 @@ const navigation = [{
items: [
AccessibilityExample,
ColorPaletteExample,
ContextExample,
CopyExample,
UtilityClassesExample,
DelayHideExample,
ErrorBoundaryExample,
HighlightExample,
I18nExample,
IsColorDarkExample,
MutationObserverExample,
OutsideClickDetectorExample,
Expand Down
2 changes: 1 addition & 1 deletion src-docs/src/services/string/render_to_html.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
configure
} from 'enzyme';

import EnzymeAdapter from 'enzyme-adapter-react-16';
import EnzymeAdapter from 'enzyme-adapter-react-16.3';

import html from 'html';

Expand Down
Loading