diff --git a/package.json b/package.json
index 8565c902e74..5a89b8f943c 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"commander": "^2.19.0",
"doctrine": "^3.0.0",
"node-dir": "^0.1.10",
+ "resolve": "^1.10.1",
"strip-indent": "^3.0.0"
},
"devDependencies": {
@@ -79,5 +80,8 @@
"src"
],
"testRegex": "/__tests__/.*-test\\.js$"
+ },
+ "browser": {
+ "./dist/utils/resolveImportedValue.js": "./dist/utils/resolveImportedValue.browser.js"
}
}
diff --git a/src/__tests__/__snapshots__/main-test.js.snap b/src/__tests__/__snapshots__/main-test.js.snap
index 66bb69a47b6..2eb6471286a 100644
--- a/src/__tests__/__snapshots__/main-test.js.snap
+++ b/src/__tests__/__snapshots__/main-test.js.snap
@@ -1446,3 +1446,213 @@ Object {
},
}
`;
+
+exports[`main fixtures processes component "component_29.tsx" without errors 1`] = `
+Object {
+ "description": "This is a typescript component with imported prop types",
+ "displayName": "ImportedExtendedComponent",
+ "methods": Array [],
+}
+`;
+
+exports[`main fixtures processes component "component_30.js" without errors 1`] = `
+Object {
+ "description": "",
+ "displayName": "CustomButton",
+ "methods": Array [],
+ "props": Object {
+ "children": Object {
+ "description": "",
+ "required": true,
+ "type": Object {
+ "name": "string",
+ },
+ },
+ "color": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "string",
+ },
+ },
+ "onClick": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "func",
+ },
+ },
+ "style": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "object",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_31.js" without errors 1`] = `
+Object {
+ "description": "",
+ "displayName": "SuperCustomButton",
+ "methods": Array [],
+ "props": Object {
+ "children": Object {
+ "description": "",
+ "required": true,
+ "type": Object {
+ "name": "string",
+ },
+ },
+ "onClick": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "func",
+ },
+ },
+ "style": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "object",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_32.js" without errors 1`] = `
+Object {
+ "description": "",
+ "displayName": "SuperDuperCustomButton",
+ "methods": Array [],
+ "props": Object {
+ "children": Object {
+ "description": "",
+ "required": true,
+ "type": Object {
+ "name": "string",
+ },
+ },
+ "onClick": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "func",
+ },
+ },
+ "style": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "object",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_33.js" without errors 1`] = `
+Object {
+ "description": "",
+ "displayName": "SuperDuperCustomButton",
+ "methods": Array [],
+ "props": Object {
+ "children": Object {
+ "description": "",
+ "required": true,
+ "type": Object {
+ "name": "string",
+ },
+ },
+ "onClick": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "func",
+ },
+ },
+ "style": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "object",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_34.js" without errors 1`] = `
+Object {
+ "description": "",
+ "displayName": "SuperDuperCustomButton",
+ "methods": Array [],
+ "props": Object {
+ "children": Object {
+ "description": "",
+ "required": true,
+ "type": Object {
+ "name": "string",
+ },
+ },
+ "onClick": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "func",
+ },
+ },
+ "style": Object {
+ "description": "",
+ "required": false,
+ "type": Object {
+ "name": "object",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_35.tsx" without errors 1`] = `
+Object {
+ "description": "This is a typescript component with imported prop types",
+ "displayName": "ImportedComponent",
+ "methods": Array [],
+ "props": Object {
+ "foo": Object {
+ "description": "",
+ "required": true,
+ "tsType": Object {
+ "name": "string",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_36.js" without errors 1`] = `
+Object {
+ "description": "",
+ "displayName": "SuperDuperCustomButton",
+ "methods": Array [],
+ "props": Object {
+ "size": Object {
+ "defaultValue": Object {
+ "computed": true,
+ "value": "Sizes.EXTRA_LARGE",
+ },
+ "description": "",
+ "required": false,
+ "type": Object {
+ "computed": true,
+ "name": "enum",
+ "value": "Object.values(Sizes)",
+ },
+ },
+ },
+}
+`;
diff --git a/src/__tests__/fixtures/component_27.tsx b/src/__tests__/fixtures/component_27.tsx
index 9cd7c4187ae..b7c84d7dfb5 100644
--- a/src/__tests__/fixtures/component_27.tsx
+++ b/src/__tests__/fixtures/component_27.tsx
@@ -8,7 +8,7 @@
import React, { Component } from 'react';
-interface Props {
+export interface Props {
foo: string
}
diff --git a/src/__tests__/fixtures/component_29.tsx b/src/__tests__/fixtures/component_29.tsx
new file mode 100644
index 00000000000..b0c7b95432a
--- /dev/null
+++ b/src/__tests__/fixtures/component_29.tsx
@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import React, { Component } from 'react';
+import ExtendedProps from './component_28';
+
+/**
+ * This is a typescript component with imported prop types
+ */
+export function ImportedExtendedComponent(props: ExtendedProps) {
+ return
Hello world
;
+}
diff --git a/src/__tests__/fixtures/component_30.js b/src/__tests__/fixtures/component_30.js
new file mode 100644
index 00000000000..4b227a5bc62
--- /dev/null
+++ b/src/__tests__/fixtures/component_30.js
@@ -0,0 +1,13 @@
+import Button from './component_6';
+import PropTypes from 'prop-types';
+
+export function CustomButton({color, ...otherProps}) {
+ return ;
+}
+
+CustomButton.propTypes = {
+ ...Button.propTypes,
+ color: PropTypes.string
+};
+
+export const sharedProps = Button.propTypes;
diff --git a/src/__tests__/fixtures/component_31.js b/src/__tests__/fixtures/component_31.js
new file mode 100644
index 00000000000..781626edd91
--- /dev/null
+++ b/src/__tests__/fixtures/component_31.js
@@ -0,0 +1,10 @@
+import {CustomButton, sharedProps} from './component_30';
+import PropTypes from 'prop-types';
+
+export function SuperCustomButton({color, ...otherProps}) {
+ return ;
+}
+
+SuperCustomButton.propTypes = sharedProps;
+export {sharedProps};
+export * from './component_32';
diff --git a/src/__tests__/fixtures/component_32.js b/src/__tests__/fixtures/component_32.js
new file mode 100644
index 00000000000..fa473b92ddd
--- /dev/null
+++ b/src/__tests__/fixtures/component_32.js
@@ -0,0 +1,9 @@
+import {SuperCustomButton, sharedProps} from './component_31';
+import PropTypes from 'prop-types';
+
+export function SuperDuperCustomButton({color, ...otherProps}) {
+ return ;
+}
+
+SuperDuperCustomButton.propTypes = sharedProps;
+export * from './component_31';
diff --git a/src/__tests__/fixtures/component_33.js b/src/__tests__/fixtures/component_33.js
new file mode 100644
index 00000000000..4bdef5ec20c
--- /dev/null
+++ b/src/__tests__/fixtures/component_33.js
@@ -0,0 +1,9 @@
+import * as C31 from './component_32';
+import PropTypes from 'prop-types';
+
+export function SuperDuperCustomButton({color, ...otherProps}) {
+ return ;
+}
+
+SuperDuperCustomButton.propTypes = C31.sharedProps;
+export {SuperCustomButton} from './component_32';
diff --git a/src/__tests__/fixtures/component_34.js b/src/__tests__/fixtures/component_34.js
new file mode 100644
index 00000000000..849783bd06c
--- /dev/null
+++ b/src/__tests__/fixtures/component_34.js
@@ -0,0 +1,8 @@
+import {SuperCustomButton} from './component_33';
+import PropTypes from 'prop-types';
+
+export function SuperDuperCustomButton({color, ...otherProps}) {
+ return ;
+}
+
+SuperDuperCustomButton.propTypes = SuperCustomButton.propTypes;
diff --git a/src/__tests__/fixtures/component_35.tsx b/src/__tests__/fixtures/component_35.tsx
new file mode 100644
index 00000000000..69265650558
--- /dev/null
+++ b/src/__tests__/fixtures/component_35.tsx
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import React, { Component } from 'react';
+import { Props as ImportedProps } from './component_27';
+
+export default interface ExtendedProps extends ImportedProps {
+ bar: number
+}
+
+/**
+ * This is a typescript component with imported prop types
+ */
+export function ImportedComponent(props: ImportedProps) {
+ return Hello world
;
+}
diff --git a/src/__tests__/fixtures/component_36.js b/src/__tests__/fixtures/component_36.js
new file mode 100644
index 00000000000..f9d7480923d
--- /dev/null
+++ b/src/__tests__/fixtures/component_36.js
@@ -0,0 +1,16 @@
+import { Sizes } from '../common';
+import T from 'prop-types';
+
+function SuperDuperCustomButton() {
+ return ;
+}
+
+SuperDuperCustomButton.defaultProps = {
+ size: Sizes.EXTRA_LARGE,
+};
+
+SuperDuperCustomButton.propTypes = {
+ size: T.oneOf(Object.values(Sizes)),
+};
+
+export default SuperDuperCustomButton;
diff --git a/src/babelParser.js b/src/babelParser.js
index a8e386c6150..d72164f6584 100644
--- a/src/babelParser.js
+++ b/src/babelParser.js
@@ -100,13 +100,17 @@ function buildOptions(
export default function buildParse(options?: Options = {}): Parser {
const { parserOptions, ...babelOptions } = options;
const parserOpts = buildOptions(parserOptions, babelOptions);
+ const opts = {
+ parserOpts,
+ ...babelOptions,
+ };
return {
parse(src: string): ASTNode {
- return babel.parseSync(src, {
- parserOpts,
- ...babelOptions,
- });
+ const ast = babel.parseSync(src, opts);
+ // Attach options to the Program node, for use when processing imports.
+ ast.program.options = options;
+ return ast;
},
};
}
diff --git a/src/handlers/__tests__/propDocblockHandler-test.js b/src/handlers/__tests__/propDocblockHandler-test.js
index 47e48995313..3eee7732c71 100644
--- a/src/handlers/__tests__/propDocblockHandler-test.js
+++ b/src/handlers/__tests__/propDocblockHandler-test.js
@@ -128,7 +128,7 @@ describe('propDocBlockHandler', () => {
const definition = parse(
getSrc(
`{
- ...Foo.propTypes,
+ ...Bar.propTypes,
/**
* Foo comment
*/
diff --git a/src/utils/getMemberValuePath.js b/src/utils/getMemberValuePath.js
index 0583834fd51..801f97d4536 100644
--- a/src/utils/getMemberValuePath.js
+++ b/src/utils/getMemberValuePath.js
@@ -37,7 +37,7 @@ const LOOKUP_METHOD = {
[t.ClassExpression.name]: getClassMemberValuePath,
};
-function isSupportedDefinitionType({ node }) {
+export function isSupportedDefinitionType({ node }: NodePath) {
return (
t.ObjectExpression.check(node) ||
t.ClassDeclaration.check(node) ||
diff --git a/src/utils/isReactComponentClass.js b/src/utils/isReactComponentClass.js
index 79c7ae059d8..ea1ce76ab6f 100644
--- a/src/utils/isReactComponentClass.js
+++ b/src/utils/isReactComponentClass.js
@@ -42,7 +42,7 @@ export default function isReactComponentClass(path: NodePath): boolean {
}
// React.Component or React.PureComponent
- const superClass = resolveToValue(path.get('superClass'));
+ const superClass = resolveToValue(path.get('superClass'), false);
if (
match(superClass.node, { property: { name: 'Component' } }) ||
match(superClass.node, { property: { name: 'PureComponent' } })
diff --git a/src/utils/resolveImportedValue.browser.js b/src/utils/resolveImportedValue.browser.js
new file mode 100644
index 00000000000..f9e3a07e0e3
--- /dev/null
+++ b/src/utils/resolveImportedValue.browser.js
@@ -0,0 +1,4 @@
+export default function resolveImportedValue() {
+ // The browser build does not support importing due to the lack of fs access.
+ return null;
+}
diff --git a/src/utils/resolveImportedValue.js b/src/utils/resolveImportedValue.js
new file mode 100644
index 00000000000..f9c66e946bf
--- /dev/null
+++ b/src/utils/resolveImportedValue.js
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import types from 'ast-types';
+import { traverseShallow } from './traverse';
+import resolve from 'resolve';
+import { dirname } from 'path';
+import buildParser, { type Options } from '../babelParser';
+import fs from 'fs';
+
+const { namedTypes: t, NodePath } = types;
+
+export default function resolveImportedValue(
+ path: NodePath,
+ name: string,
+ seen: Set = new Set(),
+) {
+ // Bail if no filename was provided for the current source file.
+ // Also never traverse into react itself.
+ const source = path.node.source.value;
+ const options = getOptions(path);
+ if (!options || !options.filename || source === 'react') {
+ return null;
+ }
+
+ // Resolve the imported module using the Node resolver
+ const basedir = dirname(options.filename);
+ let resolvedSource;
+
+ try {
+ resolvedSource = resolve.sync(source, {
+ basedir,
+ extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx'],
+ });
+ } catch (err) {
+ return null;
+ }
+
+ // Prevent recursive imports
+ if (seen.has(resolvedSource)) {
+ return null;
+ }
+
+ seen.add(resolvedSource);
+
+ // Read and parse the code
+ // TODO: cache and reuse
+ const code = fs.readFileSync(resolvedSource, 'utf8');
+ const parseOptions: Options = {
+ ...options,
+ parserOptions: {},
+ filename: resolvedSource,
+ };
+
+ const parser = buildParser(parseOptions);
+ const ast = parser.parse(code);
+ return findExportedValue(ast.program, name, seen);
+}
+
+// Find the root Program node, which we attached our options too in babelParser.js
+function getOptions(path: NodePath): Options {
+ while (!t.Program.check(path.node)) {
+ path = path.parentPath;
+ }
+
+ return path.node.options || {};
+}
+
+// Traverses the program looking for an export that matches the requested name
+function findExportedValue(ast, name, seen) {
+ let resultPath: ?NodePath = null;
+
+ traverseShallow(ast, {
+ visitExportNamedDeclaration(path: NodePath) {
+ const { declaration, specifiers, source } = path.node;
+ if (declaration && declaration.id && declaration.id.name === name) {
+ resultPath = path.get('declaration');
+ } else if (declaration && declaration.declarations) {
+ path.get('declaration', 'declarations').each((declPath: NodePath) => {
+ const decl = declPath.node;
+ // TODO: ArrayPattern and ObjectPattern
+ if (
+ t.Identifier.check(decl.id) &&
+ decl.id.name === name &&
+ decl.init
+ ) {
+ resultPath = declPath.get('init');
+ }
+ });
+ } else if (specifiers) {
+ path.get('specifiers').each((specifierPath: NodePath) => {
+ if (specifierPath.node.exported.name === name) {
+ if (source) {
+ const local = specifierPath.node.local.name;
+ resultPath = resolveImportedValue(path, local, seen);
+ } else {
+ resultPath = specifierPath.get('local');
+ }
+ }
+ });
+ }
+
+ return false;
+ },
+ visitExportDefaultDeclaration(path: NodePath) {
+ if (name === 'default') {
+ resultPath = path.get('declaration');
+ }
+
+ return false;
+ },
+ visitExportAllDeclaration(path: NodePath) {
+ const resolvedPath = resolveImportedValue(path, name, seen);
+ if (resolvedPath) {
+ resultPath = resolvedPath;
+ }
+
+ return false;
+ },
+ });
+
+ return resultPath;
+}
diff --git a/src/utils/resolveToModule.js b/src/utils/resolveToModule.js
index 9d2ea3855f7..30b1e4e4a93 100644
--- a/src/utils/resolveToModule.js
+++ b/src/utils/resolveToModule.js
@@ -33,7 +33,7 @@ export default function resolveToModule(path: NodePath): ?string {
return resolveToModule(path.get('callee'));
case t.Identifier.name:
case t.JSXIdentifier.name: {
- const valuePath = resolveToValue(path);
+ const valuePath = resolveToValue(path, false);
if (valuePath !== path) {
return resolveToModule(valuePath);
}
diff --git a/src/utils/resolveToValue.js b/src/utils/resolveToValue.js
index 73b7e436745..aab7b911510 100644
--- a/src/utils/resolveToValue.js
+++ b/src/utils/resolveToValue.js
@@ -12,6 +12,10 @@ import getMemberExpressionRoot from './getMemberExpressionRoot';
import getPropertyValuePath from './getPropertyValuePath';
import { Array as toArray } from './expressionTo';
import { traverseShallow } from './traverse';
+import resolveImportedValue from './resolveImportedValue';
+import getMemberValuePath, {
+ isSupportedDefinitionType,
+} from './getMemberValuePath';
const { namedTypes: t, NodePath, builders } = types;
@@ -37,13 +41,40 @@ function buildMemberExpressionFromPattern(path: NodePath): ?NodePath {
return null;
}
-function findScopePath(paths: Array, path: NodePath): ?NodePath {
+function findScopePath(
+ paths: Array,
+ path: NodePath,
+ resolveImports: boolean,
+): ?NodePath {
if (paths.length < 1) {
return null;
}
let resultPath = paths[0];
const parentPath = resultPath.parent;
+ // Namespace imports are handled separately, at the site of a member expression access
+ if (
+ resolveImports &&
+ (t.ImportDefaultSpecifier.check(parentPath.node) ||
+ t.ImportSpecifier.check(parentPath.node))
+ ) {
+ let exportName;
+ if (t.ImportDefaultSpecifier.check(parentPath.node)) {
+ exportName = 'default';
+ } else {
+ exportName = parentPath.node.imported.name;
+ }
+
+ const resolvedPath = resolveImportedValue(
+ parentPath.parentPath,
+ exportName,
+ );
+
+ if (resolvedPath) {
+ return resolveToValue(resolvedPath, resolveImports);
+ }
+ }
+
if (
t.ImportDefaultSpecifier.check(parentPath.node) ||
t.ImportSpecifier.check(parentPath.node) ||
@@ -64,7 +95,7 @@ function findScopePath(paths: Array, path: NodePath): ?NodePath {
}
if (resultPath.node !== path.node) {
- return resolveToValue(resultPath);
+ return resolveToValue(resultPath, resolveImports);
}
return null;
@@ -74,7 +105,7 @@ function findScopePath(paths: Array, path: NodePath): ?NodePath {
* Tries to find the last value assigned to `name` in the scope created by
* `scope`. We are not descending into any statements (blocks).
*/
-function findLastAssignedValue(scope, name) {
+function findLastAssignedValue(scope, name, resolveImports) {
const results = [];
traverseShallow(scope.path.node, {
@@ -97,7 +128,7 @@ function findLastAssignedValue(scope, name) {
if (results.length === 0) {
return null;
}
- return resolveToValue(results.pop());
+ return resolveToValue(results.pop(), resolveImports);
}
/**
@@ -107,14 +138,18 @@ function findLastAssignedValue(scope, name) {
*
* Else the path itself is returned.
*/
-export default function resolveToValue(path: NodePath): NodePath {
+export default function resolveToValue(
+ path: NodePath,
+ resolveImports: boolean = true,
+): NodePath {
const node = path.node;
if (t.VariableDeclarator.check(node)) {
if (node.init) {
- return resolveToValue(path.get('init'));
+ return resolveToValue(path.get('init'), resolveImports);
}
} else if (t.MemberExpression.check(node)) {
- const resolved = resolveToValue(getMemberExpressionRoot(path));
+ const root = getMemberExpressionRoot(path);
+ const resolved = resolveToValue(root, resolveImports);
if (t.ObjectExpression.check(resolved.node)) {
let propertyPath = resolved;
for (const propertyName of toArray(path).slice(1)) {
@@ -124,9 +159,32 @@ export default function resolveToValue(path: NodePath): NodePath {
if (!propertyPath) {
return path;
}
- propertyPath = resolveToValue(propertyPath);
+ propertyPath = resolveToValue(propertyPath, resolveImports);
}
return propertyPath;
+ } else if (isSupportedDefinitionType(resolved)) {
+ const memberPath = getMemberValuePath(resolved, path.node.property.name);
+ if (memberPath) {
+ return resolveToValue(memberPath, resolveImports);
+ }
+ } else if (t.ImportDeclaration.check(resolved.node)) {
+ // Handle references to namespace imports, e.g. import * as foo from 'bar'.
+ // Try to find a specifier that matches the root of the member expression, and
+ // find the export that matches the property name.
+ for (const specifier of resolved.node.specifiers) {
+ if (
+ t.ImportNamespaceSpecifier.check(specifier) &&
+ specifier.local.name === root.node.name
+ ) {
+ const resolvedPath = resolveImportedValue(
+ resolved,
+ root.parentPath.node.property.name,
+ );
+ if (resolvedPath) {
+ return resolveToValue(resolvedPath, resolveImports);
+ }
+ }
+ }
}
} else if (
t.ImportDefaultSpecifier.check(node) ||
@@ -137,10 +195,10 @@ export default function resolveToValue(path: NodePath): NodePath {
return path.parentPath.parentPath;
} else if (t.AssignmentExpression.check(node)) {
if (node.operator === '=') {
- return resolveToValue(path.get('right'));
+ return resolveToValue(path.get('right'), resolveImports);
}
} else if (t.TypeCastExpression.check(node)) {
- return resolveToValue(path.get('expression'));
+ return resolveToValue(path.get('expression'), resolveImports);
} else if (t.Identifier.check(node)) {
if (
(t.ClassDeclaration.check(path.parentPath.node) ||
@@ -157,16 +215,16 @@ export default function resolveToValue(path: NodePath): NodePath {
// The variable may be assigned a different value after initialization.
// We are first trying to find all assignments to the variable in the
// block where it is defined (i.e. we are not traversing into statements)
- resolvedPath = findLastAssignedValue(scope, node.name);
+ resolvedPath = findLastAssignedValue(scope, node.name, resolveImports);
if (!resolvedPath) {
const bindings = scope.getBindings()[node.name];
- resolvedPath = findScopePath(bindings, path);
+ resolvedPath = findScopePath(bindings, path, resolveImports);
}
} else {
scope = path.scope.lookupType(node.name);
if (scope) {
const typesInScope = scope.getTypes()[node.name];
- resolvedPath = findScopePath(typesInScope, path);
+ resolvedPath = findScopePath(typesInScope, path, resolveImports);
}
}
return resolvedPath || path;
diff --git a/yarn.lock b/yarn.lock
index 5a2768b9b4b..51e26d5e906 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4043,6 +4043,13 @@ resolve@^1.10.0, resolve@^1.3.2, resolve@^1.8.1:
dependencies:
path-parse "^1.0.6"
+resolve@^1.10.1:
+ version "1.10.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18"
+ integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==
+ dependencies:
+ path-parse "^1.0.6"
+
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"