(Comp);
+ `;
+ expect(await transform(src)).toBe(expected);
+ });
+
+ it("should not remove when annotation is not for a function call", async () => {
+ const src = dedent`
+ export default function withFoo(f: Foo): (S: AbstractComponent
) => AbstractComponent
{
+ return function withFoo
(Subject: AbstractComponent
): AbstractComponent
{
+ return () => null;
+ };
+ }
+ `;
+ expect(await transform(src)).toBe(src);
+ });
+ });
+
+ describe("React.forwardRef", () => {
+ it("should handle anonymous component", async () => {
+ const src = dedent`
+ // @flow
+ const C: AbstractComponent = forwardRef((props, ref) => null);
+ export default (forwardRef((props, ref) => null): AbstractComponent);
+ `;
+ const expected = dedent`
+ const C = forwardRef[((props, ref) => null);
+ export default forwardRef][((props, ref) => null);
+ `;
+ expect(await transform(src)).toBe(expected);
+ });
+
+ it("should handle named component", async () => {
+ const src = dedent`
+ // @flow
+ const C: AbstractComponent = forwardRef(Comp);
+ export default (forwardRef(Comp): AbstractComponent);
+ `;
+ const expected = dedent`
+ const C = forwardRef][(Comp);
+ export default forwardRef][(Comp);
+ `;
+ expect(await transform(src)).toBe(expected);
+ });
+ });
+
describe.each(JEST_MOCK_METHODS)("jest.%s paths", (mockMethod) => {
it("should do nothing if there is no extension already", async () => {
const src = dedent`jest.${mockMethod}('foo');`;
diff --git a/src/convert/expressions.ts b/src/convert/expressions.ts
index 0e3b3cc..1c7e618 100644
--- a/src/convert/expressions.ts
+++ b/src/convert/expressions.ts
@@ -168,6 +168,15 @@ export function transformExpressions({
);
}
},
+ TSAsExpression(path) {
+ if (
+ path.node.typeAnnotation.type === "TSTypeReference" &&
+ path.node.typeAnnotation.typeName.type === "Identifier" &&
+ path.node.typeAnnotation.typeName.name === "AbstractComponent"
+ ) {
+ path.replaceWith(path.node.expression);
+ }
+ },
ArrowFunctionExpression(path) {
// Arrow functions with a generic type parameter (() => {}) often don't typecheck in tsx files
// since they can be parsed as a JSX tag. To solve this the type parameters usually extend unknown,
@@ -188,6 +197,19 @@ export function transformExpressions({
CallExpression(path) {
migrateArgumentsToParameters(path, reporter, state);
+ // forwardRef<> in TS has the type parameters in reverse order
+ // `forwardRef` → `forwardRef][`
+ if (
+ path.node.type === "CallExpression" &&
+ path.node.callee.type === "Identifier" &&
+ path.node.callee.name === "forwardRef" &&
+ path.node.typeParameters?.type === "TSTypeParameterInstantiation"
+ ) {
+ path.node.typeParameters.params = path.node.typeParameters.params
+ .concat()
+ .reverse();
+ }
+
if (
t.isMemberExpression(path.node.callee) &&
t.isIdentifier(path.node.callee.property) &&
diff --git a/src/convert/jsx-spread/jsx-spread.test.ts b/src/convert/jsx-spread/jsx-spread.test.ts
index 599b2ed..821fd4b 100644
--- a/src/convert/jsx-spread/jsx-spread.test.ts
+++ b/src/convert/jsx-spread/jsx-spread.test.ts
@@ -22,8 +22,8 @@ describe("transform spread JSX attributes", () => {
const expected = `
import {Flow} from 'flow-to-typescript-codemod';
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
function Foobar(x: Props & Omit, 'it'>, keyof Props>) {
@@ -51,8 +51,8 @@ describe("transform spread JSX attributes", () => {
const expected = `
import {Flow} from 'flow-to-typescript-codemod';
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
const Foobar = (x: Props & Omit, 'it'>, keyof Props>) => {
@@ -84,8 +84,8 @@ describe("transform spread JSX attributes", () => {
const expected = `
import {Flow} from 'flow-to-typescript-codemod';
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
function Foobar(x: Props & Omit, 'it'>, keyof Props>) {
@@ -121,8 +121,8 @@ describe("transform spread JSX attributes", () => {
const expected = `
import {Flow} from 'flow-to-typescript-codemod';
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
class MyComponent extends React.Component, 'it'>, keyof Props>> {
@@ -157,8 +157,8 @@ describe("transform spread JSX attributes", () => {
const expected = `
import {Flow} from 'flow-to-typescript-codemod';
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
class MyComponent extends React.Component, 'it'>, keyof Props>> {
@@ -192,8 +192,8 @@ describe("transform spread JSX attributes", () => {
const expected = `
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
class MyComponent extends React.Component {
@@ -228,11 +228,11 @@ describe("transform spread JSX attributes", () => {
const expected = `
import {Flow} from 'flow-to-typescript-codemod';
type State = {
- thing: boolean
+ thing: boolean;
};
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
class MyComponent extends React.Component, 'it'>, keyof Props>, State> {
@@ -263,8 +263,8 @@ describe("transform spread JSX attributes", () => {
const expected = `
import {Flow} from 'flow-to-typescript-codemod';
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
function Foobar(x: Props & Omit, keyof Props>) {
@@ -292,8 +292,8 @@ describe("transform spread JSX attributes", () => {
}`;
const expected = `
type Props = {
- it: string,
- foo: number
+ it: string;
+ foo: number;
};
function Foobar(x: Props) {
diff --git a/src/convert/migrate/type.ts b/src/convert/migrate/type.ts
index 0d1e325..8f4593f 100644
--- a/src/convert/migrate/type.ts
+++ b/src/convert/migrate/type.ts
@@ -4,7 +4,6 @@ import {
inheritLocAndComments,
GlobalTypes,
LiteralTypes,
- hasNullReturn,
} from "../utils/common";
import {
migrateTypeParameterDeclaration,
@@ -18,10 +17,12 @@ import {
MomentTypes,
} from "../utils/type-mappings";
import { State } from "../../runner/state";
-import { matchesFullyQualifiedName } from "../utils/matchers";
+import { matchesReact } from "../utils/matchers";
import { migrateFunctionParameters } from "./function-parameter";
import { MetaData } from "./metadata";
+export const REMOVE_ME_ID = "__RemoveMe__";
+
export function migrateType(
reporter: MigrationReporter,
state: State,
@@ -144,6 +145,26 @@ function actuallyMigrateType(
return t.tsTypeReference(t.identifier("ReadonlyArray"), params);
}
+ // `$ReadOnlySet` → `ReadonlySet`
+ if (
+ id.type === "Identifier" &&
+ id.name === "$ReadOnlySet" &&
+ params &&
+ params.params.length === 1
+ ) {
+ return t.tsTypeReference(t.identifier("ReadonlySet"), params);
+ }
+
+ // `$ReadOnlyMap` → `ReadonlyMap`
+ if (
+ id.type === "Identifier" &&
+ id.name === "$ReadOnlyMap" &&
+ params &&
+ params.params.length === 2
+ ) {
+ return t.tsTypeReference(t.identifier("ReadonlyMap"), params);
+ }
+
// `$ReadOnly` → `Readonly`
if (
id.type === "Identifier" &&
@@ -178,10 +199,10 @@ function actuallyMigrateType(
return t.tsIndexedAccessType(params.params[0], typeOperator);
}
- // `$Shape` → `Partial`
+ // `$Shape|$Partial` → `Partial`
if (
id.type === "Identifier" &&
- id.name === "$Shape" &&
+ (id.name === "$Shape" || id.name === "$Partial") &&
params &&
params.params.length === 1
) {
@@ -536,29 +557,24 @@ function actuallyMigrateType(
}
}
if (
- ((matchesFullyQualifiedName("React", "Node")(id) &&
+ (((matchesReact("Node")(id) || matchesReact("ReactNode")(id)) &&
isRenderMethodOrNonClassMethodReturnType) ||
- matchesFullyQualifiedName("React", "MixedElement")(id)) &&
+ matchesReact("MixedElement")(id)) &&
!params
) {
- const parentNode = metaData?.path?.parentPath?.node;
- let hasNull = false;
- if (parentNode && "body" in parentNode) {
- const parentPath = metaData?.path?.parentPath;
- const scope = metaData?.path?.scope;
- hasNull = hasNullReturn(
- parentNode.body as t.BlockStatement,
- scope,
- parentPath
- );
- }
- const reactElement = t.tsTypeReference(
- t.tsQualifiedName(t.identifier("React"), t.identifier("ReactElement"))
- );
- if (hasNull) {
- return t.tsUnionType([reactElement, t.tsNullKeyword()]);
+ const parentType = metaData?.path?.parentPath?.type;
+ const parentReturnType =
+ // @ts-expect-error returnType not found
+ metaData?.path?.parentPath?.node?.returnType?.typeAnnotation?.id
+ ?.name;
+ if (
+ (parentType === "FunctionDeclaration" ||
+ parentType === "ArrowFunctionExpression") &&
+ parentReturnType !== "$ReadOnlyArray"
+ ) {
+ return t.tsTypeReference(t.identifier(REMOVE_ME_ID));
} else {
- return reactElement;
+ return t.tsTypeReference(t.identifier("ReactNode"));
}
}
@@ -567,7 +583,7 @@ function actuallyMigrateType(
// convert this flow type to something that TypeScript understands so we can get the less strict children
// prop checking.
if (
- matchesFullyQualifiedName("React", "ChildrenArray")(id) &&
+ matchesReact("ChildrenArray")(id) &&
params &&
params.params.length === 1
) {
@@ -597,33 +613,17 @@ function actuallyMigrateType(
);
}
- // React.ElementConfig -> JSX.LibraryManagedAttributes>
+ // ElementConfig -> ComponentProps
if (
- id.type === "TSQualifiedName" &&
- id.left.type === "Identifier" &&
- id.left.name === "React" &&
- id.right.type === "Identifier" &&
- id.right.name === "ElementConfig" &&
+ matchesReact("ElementConfig")(id) &&
params &&
params.params.length === 1
) {
const parameter = params.params[0];
return t.tsTypeReference(
- t.tsQualifiedName(
- t.identifier("JSX"),
- t.identifier("LibraryManagedAttributes")
- ),
- t.tsTypeParameterInstantiation([
- parameter,
- t.tsTypeReference(
- t.tsQualifiedName(
- t.identifier("React"),
- t.identifier("ComponentProps")
- ),
- t.tsTypeParameterInstantiation([parameter])
- ),
- ])
+ t.identifier("ComponentProps"),
+ t.tsTypeParameterInstantiation([parameter])
);
}
@@ -694,11 +694,25 @@ function actuallyMigrateType(
}
}
+ // `Portal/ElementProps/Etc` → `ReactPortal/ComponentProps/Etc`
+ // NOTE: skipping Node, Child, Children, Text, Fragment, FragmentType because the names are too
+ // generic and we don't want to accidentally convert the wrong thing. For example, we don't want
+ // to covert Node to ReactNode because Node is also a global builtin type type for DOM Nodes.
+ if (
+ id.type === "Identifier" &&
+ ["Portal", "ElementProps", "StatelessFunctionalComponent"].includes(
+ id.name
+ )
+ ) {
+ return t.tsTypeReference(
+ t.identifier(ReactTypes[id.name as keyof typeof ReactTypes]),
+ params
+ );
+ }
+
// `React.Portal/Children/Etc` → `React.ReactPortal/ReactChildren/Etc`
if (
id.type === "TSQualifiedName" &&
- id.left.type === "Identifier" &&
- id.left.name === "React" &&
id.right.type === "Identifier" &&
id.right.name in ReactTypes
) {
@@ -736,9 +750,27 @@ function actuallyMigrateType(
);
}
+ if (id.type === "Identifier" && id.name === "Match") {
+ return t.tsTypeReference(
+ t.identifier("match"),
+ t.tsTypeParameterInstantiation([
+ t.tsTypeReference(
+ t.identifier("{ [key: string]: string | undefined }")
+ ),
+ ])
+ );
+ }
+
return t.tsTypeReference(id, params);
}
+ // `T[K]` → `T[K]`
+ case "IndexedAccessType":
+ return t.tsIndexedAccessType(
+ migrateType(reporter, state, flowType.objectType),
+ migrateType(reporter, state, flowType.indexType)
+ );
+
case "InterfaceTypeAnnotation":
throw new Error(`Unsupported AST node: ${JSON.stringify(flowType.type)}`);
@@ -762,8 +794,11 @@ function actuallyMigrateType(
return t.tsNeverKeyword();
case "NullableTypeAnnotation": {
+ const type = migrateType(reporter, state, flowType.typeAnnotation);
return t.tsUnionType([
- migrateType(reporter, state, flowType.typeAnnotation),
+ flowType.typeAnnotation.type === "FunctionTypeAnnotation"
+ ? t.tsParenthesizedType(type)
+ : type,
t.tsNullKeyword(),
t.tsUndefinedKeyword(),
]);
@@ -880,10 +915,27 @@ function actuallyMigrateType(
if (types.length === 1) {
return types[0];
} else {
- return t.tsIntersectionType(types);
+ return t.tsParenthesizedType(t.tsIntersectionType(types));
}
}
+ // `T?.[K]` → `NonNullable[K] | null | undefined`
+ case "OptionalIndexedAccessType": {
+ return t.tsUnionType([
+ t.tsIndexedAccessType(
+ t.tsTypeReference(
+ t.identifier("NonNullable"),
+ t.tsTypeParameterInstantiation([
+ migrateType(reporter, state, flowType.objectType),
+ ])
+ ),
+ migrateType(reporter, state, flowType.indexType)
+ ),
+ t.tsNullKeyword(),
+ t.tsUndefinedKeyword(),
+ ]);
+ }
+
case "StringLiteralTypeAnnotation":
return t.tsLiteralType(t.stringLiteral(flowType.value));
@@ -961,6 +1013,7 @@ function actuallyMigrateType(
const never: { type: string } = flowType;
reporter.unhandledFlowInputNode(
state.config.filePath,
+ // @ts-expect-error loc not found
flowType.loc as t.SourceLocation,
(flowType as unknown as { name: string }).name ?? "undefined",
JSON.stringify(never.type)
diff --git a/src/convert/move-imports.test.ts b/src/convert/move-imports.test.ts
new file mode 100644
index 0000000..f2664a7
--- /dev/null
+++ b/src/convert/move-imports.test.ts
@@ -0,0 +1,59 @@
+import dedent from "dedent";
+import { transform } from "./utils/testing";
+
+jest.mock("../runner/migration-reporter/migration-reporter.ts");
+jest.mock("./flow/type-at-pos.ts");
+
+describe("imports", () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("move react-router-dom imports", () => {
+ it("react-router-dom stays and history stays", async () => {
+ const src = dedent(`
+ import {type RouterHistory as H, useLocation} from 'react-router-dom';
+ import {createMemoryHistory} from 'history';
+ `);
+ const expected = dedent(`
+ import {useLocation} from 'react-router-dom';
+ import {History as H, createMemoryHistory} from 'history';
+ `);
+ expect(await transform(src)).toBe(expected);
+ });
+
+ it("react-router-dom stays and history is added", async () => {
+ const src = dedent(`
+ import {type RouterHistory as H, useLocation} from 'react-router-dom';
+ `);
+ const expected = dedent(`
+ import {History as H} from 'history';
+ import {useLocation} from 'react-router-dom';
+ `);
+ expect(await transform(src)).toBe(expected);
+ });
+
+ it("react-router-dom is removed and history stays", async () => {
+ const src = dedent(`
+ import {type RouterHistory as H} from 'react-router-dom';
+ import {createMemoryHistory} from 'history';
+ `);
+ const expected = dedent(`
+ import {History as H, createMemoryHistory} from 'history';
+ `);
+ expect(await transform(src)).toBe(expected);
+ });
+
+ it("react-router-dom is removed and history is added", async () => {
+ const src = dedent(`
+ import {type RouterHistory as H} from 'react-router-dom';
+ const h: H | null = null;
+ `);
+ const expected = dedent(`
+ import {History as H} from 'history';
+ const h: H | null = null;
+ `);
+ expect(await transform(src)).toBe(expected);
+ });
+ });
+});
diff --git a/src/convert/move-imports.ts b/src/convert/move-imports.ts
new file mode 100644
index 0000000..1b7d581
--- /dev/null
+++ b/src/convert/move-imports.ts
@@ -0,0 +1,118 @@
+import * as t from "@babel/types";
+import traverse from "@babel/traverse";
+import { TransformerInput } from "./transformer";
+
+/**
+ * Add or remove imports
+ * @param state
+ * @param file
+ */
+export function moveImports({ state, file }: TransformerInput) {
+ traverse(file, {
+ Program: {
+ exit(path) {
+ const removeImportDeclaration = (
+ importDeclaration: t.ImportDeclaration
+ ) => {
+ path.node.body = path.node.body.filter(
+ (node) =>
+ !(
+ t.isImportDeclaration(node) &&
+ node.source.value === importDeclaration.source.value
+ )
+ );
+ };
+
+ const insertImportDeclaration = (
+ importDeclaration: t.ImportDeclaration
+ ) => {
+ path.node.body = [importDeclaration, ...path.node.body];
+ };
+
+ if (state.usedUtils) {
+ const importDeclaration = t.importDeclaration(
+ [t.importSpecifier(t.identifier("Flow"), t.identifier("Flow"))],
+ t.stringLiteral("flow-to-typescript-codemod")
+ );
+ insertImportDeclaration(importDeclaration);
+ }
+
+ const importDeclarations = path.node.body.reduce<
+ Array
+ >(
+ (agg, node) =>
+ t.isImportDeclaration(node) ? agg.concat([node]) : agg,
+ []
+ );
+ const reactRouterDomImportDeclaration: t.ImportDeclaration | undefined =
+ importDeclarations.find(
+ (node) => node.source.value === "react-router-dom"
+ );
+
+ if (reactRouterDomImportDeclaration) {
+ const locationLocalName: string | undefined =
+ reactRouterDomImportDeclaration.specifiers.find(
+ (s) =>
+ s.type === "ImportSpecifier" &&
+ s.imported.type === "Identifier" &&
+ s.imported.name === "Location"
+ )?.local.name;
+ const historyLocalName: string | undefined =
+ reactRouterDomImportDeclaration.specifiers.find(
+ (s) =>
+ s.type === "ImportSpecifier" &&
+ s.imported.type === "Identifier" &&
+ s.imported.name === "RouterHistory"
+ )?.local.name;
+
+ const newImportSpecifiers = [
+ !!historyLocalName &&
+ t.importSpecifier(
+ t.identifier(historyLocalName),
+ t.identifier("History")
+ ),
+ !!locationLocalName &&
+ t.importSpecifier(
+ t.identifier(locationLocalName),
+ t.identifier("Location")
+ ),
+ ].filter(Boolean) as t.ImportSpecifier[];
+
+ if (newImportSpecifiers.length > 0) {
+ reactRouterDomImportDeclaration.specifiers =
+ reactRouterDomImportDeclaration.specifiers.filter(
+ (s) =>
+ !(
+ s.type === "ImportSpecifier" &&
+ s.imported.type === "Identifier" &&
+ (s.imported.name === "Location" ||
+ s.imported.name === "RouterHistory")
+ )
+ );
+ if (reactRouterDomImportDeclaration.specifiers.length === 0) {
+ removeImportDeclaration(reactRouterDomImportDeclaration);
+ }
+
+ const historyImportDeclaration = importDeclarations.find(
+ (node) => node.source.value === "history"
+ );
+
+ if (historyImportDeclaration) {
+ historyImportDeclaration.specifiers = [
+ ...newImportSpecifiers,
+ ...historyImportDeclaration.specifiers,
+ ];
+ } else {
+ insertImportDeclaration(
+ t.importDeclaration(
+ newImportSpecifiers,
+ t.stringLiteral("history")
+ )
+ );
+ }
+ }
+ }
+ },
+ },
+ });
+}
diff --git a/src/convert/private-types.test.ts b/src/convert/private-types.test.ts
index 197719f..a73914a 100644
--- a/src/convert/private-types.test.ts
+++ b/src/convert/private-types.test.ts
@@ -17,7 +17,7 @@ describe("transform type annotations", () => {
it("converts React$Element", async () => {
const src = `const Component = (props: Props): React$Element => {return };`;
- const expected = `const Component = (props: Props): React.Element => {return };`;
+ const expected = `const Component = (props: Props): React.ReactElement => {return };`;
expect(await transform(src)).toBe(expected);
});
diff --git a/src/convert/private-types.ts b/src/convert/private-types.ts
index ff45e41..2bad923 100644
--- a/src/convert/private-types.ts
+++ b/src/convert/private-types.ts
@@ -3,6 +3,11 @@ import traverse from "@babel/traverse";
import { replaceWith } from "./utils/common";
import { TransformerInput } from "./transformer";
+const isRelayEmittedType = (name: string): boolean =>
+ ["fragmentType", "data", "key", "variables"].some((relayTypeSuffix) =>
+ name.endsWith(relayTypeSuffix)
+ );
+
/**
* Flow commonly uses `$` to denote private type members like React$Node.
* This syntax is hard to account for everywhere, so we convert it to `.` at the start.
@@ -16,7 +21,10 @@ export function transformPrivateTypes({
Identifier(path) {
const id = path.node;
const hasPrivateType =
- /\w\$\w/.test(id.name) && !state.config.keepPrivateTypes;
+ /\w\$\w/.test(id.name) &&
+ !isRelayEmittedType(id.name) &&
+ !id.name.startsWith("$IMPORTED_TYPE$") &&
+ !state.config.keepPrivateTypes;
const privateReactType = id.name.startsWith("React$");
const privateFlowType = id.name.startsWith("$FlowFixMe");
const isTypeAnnotation = path.parentPath.type === "GenericTypeAnnotation";
diff --git a/src/convert/remove-flow-comments.test.ts b/src/convert/remove-flow-comments.test.ts
index 8a6ae76..8cc031b 100644
--- a/src/convert/remove-flow-comments.test.ts
+++ b/src/convert/remove-flow-comments.test.ts
@@ -25,6 +25,22 @@ describe("remove-flow-pragmas", () => {
expect(await transform(src)).toEqual(expected);
});
+ it("should retain the rest of the comment", async () => {
+ const src = dedent`
+ /**
+ * @flow
+ * @jest-environment jsdom
+ */
+ ${standardProgram}
+ `;
+
+ expect(await transform(src)).toEqual(dedent`
+ /**
+ * @jest-environment jsdom
+ */
+ ${expected}`);
+ });
+
it("should remove the comment even if it has extra comment marks", async () => {
const src = dedent`
// ///// / / / / / /// // // /// ////////// /// // /////// @flow
diff --git a/src/convert/remove-flow-comments.ts b/src/convert/remove-flow-comments.ts
index 2c3769b..9017044 100644
--- a/src/convert/remove-flow-comments.ts
+++ b/src/convert/remove-flow-comments.ts
@@ -3,20 +3,49 @@ import * as t from "@babel/types";
import { types } from "recast";
import { TransformerInput } from "./transformer";
+const ESLINT_FLOW_RULE_PREFIX = "flowtype/";
+
const flowComments = [
"@flow",
"$FlowFixMe",
"$FlowIssue",
"$FlowExpectedError",
"$FlowIgnore",
+ ESLINT_FLOW_RULE_PREFIX, // ESLint flowtype rules
];
+/***
+ * Remove flowtype/ rules from eslint-disable-line or eslint-disable-next-line comments,
+ * unless flowtype/ rules are the only rules.
+ *
+ * If flowtype/ rules are the only rules, the entire comment is removed.
+ */
+const stripOutFlowESLintRuleFromNonFlowRules = (comment: string): string => {
+ const matches = comment.match(
+ /^(.*)(eslint-disable-line|eslint-disable-next-line)\s+(.*)+/
+ );
+ if (!matches) {
+ return comment;
+ }
+ const [, prefix, eslintDirective, rulesString] = matches;
+ if (
+ rulesString.includes(ESLINT_FLOW_RULE_PREFIX) &&
+ rulesString.includes(",")
+ ) {
+ const rules = rulesString.split(",").map((rule) => rule.trim());
+ if (rules.some((rule) => !rule.startsWith(ESLINT_FLOW_RULE_PREFIX))) {
+ return `${prefix}${eslintDirective} ${rules
+ .filter((rule) => !rule.startsWith(ESLINT_FLOW_RULE_PREFIX))
+ .join(",")}`;
+ }
+ }
+ return comment;
+};
+
/**
* Scan through top level programs, or code blocks and remove Flow-specific comments
*/
-const removeComments = (
- path: NodePath | NodePath
-) => {
+const removeTopLevelComments = (path: NodePath) => {
if (path.node.body.length === 0) {
return;
}
@@ -28,10 +57,7 @@ const removeComments = (
rootNode.comments =
comments
- ?.filter(
- (comment) => !flowComments.some((c) => comment.value.includes(c))
- )
- .map((comment) => {
+ ?.map((comment) => {
if (comment.value.includes("@noflow")) {
return {
...comment,
@@ -39,8 +65,23 @@ const removeComments = (
};
}
- return comment;
- }) || rootNode.comments;
+ return {
+ ...comment,
+ value: comment.value
+ .split("\n")
+ .map((line) => stripOutFlowESLintRuleFromNonFlowRules(line))
+ .filter((line) => !flowComments.some((c) => line.includes(c)))
+ .join("\n"),
+ };
+ })
+ ?.map((comment) => ({
+ ...comment,
+ value: stripOutFlowESLintRuleFromNonFlowRules(comment.value),
+ }))
+ ?.filter(
+ (comment) => !flowComments.some((c) => comment.value.includes(c))
+ )
+ ?.filter((comment) => comment.value.trim()) || rootNode.comments;
}
};
@@ -49,11 +90,24 @@ const removeComments = (
*/
export function removeFlowComments({ file }: TransformerInput) {
traverse(file, {
- Program(path) {
- removeComments(path);
+ enter({ node }) {
+ // @ts-expect-error comments doesn't exist
+ if (node.comments) {
+ // @ts-expect-error comments doesn't exist
+ node.comments = node.comments
+ // @ts-expect-error comments doesn't exist
+ ?.map((comment) => ({
+ ...comment,
+ value: stripOutFlowESLintRuleFromNonFlowRules(comment.value),
+ }))
+ ?.filter(
+ // @ts-expect-error comments doesn't exist
+ (comment) => !flowComments.some((c) => comment.value.includes(c))
+ );
+ }
},
- BlockStatement(path) {
- removeComments(path);
+ Program(path) {
+ removeTopLevelComments(path);
},
});
}
diff --git a/src/convert/transform-runners.ts b/src/convert/transform-runners.ts
index 979809d..8d098c0 100644
--- a/src/convert/transform-runners.ts
+++ b/src/convert/transform-runners.ts
@@ -1,5 +1,5 @@
import { hasJSX } from "./utils/common";
-import { addImports } from "./add-imports";
+import { moveImports } from "./move-imports";
import { addWatermark } from "./add-watermark";
import { transformJSX } from "./jsx";
import { transformDeclarations } from "./declarations";
@@ -46,7 +46,7 @@ export const patternTransformRunner: Transformer =
standardTransformRunnerFactory(transformPatterns);
export const importTransformRunner: Transformer =
- standardTransformRunnerFactory(addImports);
+ standardTransformRunnerFactory(moveImports);
export const watermarkTransformRunner: Transformer =
standardTransformRunnerFactory(addWatermark);
diff --git a/src/convert/type-annotations.test.ts b/src/convert/type-annotations.test.ts
index c99daab..c2df3d9 100644
--- a/src/convert/type-annotations.test.ts
+++ b/src/convert/type-annotations.test.ts
@@ -72,6 +72,14 @@ describe("transform type annotations", () => {
expect(await transform(src)).toBe(expected);
});
+ it("Converts maybe function", async () => {
+ const src = dedent`
+ const a: ?() => void = null;`;
+ const expected = dedent`
+ const a: (() => void) | null | undefined = null;`;
+ expect(await transform(src)).toBe(expected);
+ });
+
it("Keeps void promises as void", async () => {
const src = `function f(): Promise {};`;
const expected = `function f(): Promise {};`;
@@ -158,7 +166,7 @@ describe("transform type annotations", () => {
it("Does not convert string Object key types to records", async () => {
const src = `const MyObj: {[key: string]: ValueType} = {};`;
const expected = dedent`const MyObj: {
- [key: string]: ValueType
+ [key: string]: ValueType;
} = {};`;
expect(await transform(src)).toBe(expected);
});
@@ -166,7 +174,7 @@ describe("transform type annotations", () => {
it("Does not convert number Object key types to records", async () => {
const src = `const MyObj: {[key: number]: ValueType} = {};`;
const expected = dedent`const MyObj: {
- [key: number]: ValueType
+ [key: number]: ValueType;
} = {};`;
expect(await transform(src)).toBe(expected);
});
@@ -323,7 +331,7 @@ describe("transform type annotations", () => {
`;
const expected = dedent`
type MockApiOptions = {
- errors: jest.MockedFunction
+ errors: jest.MockedFunction;
};
`;
expect(await transform(src)).toBe(expected);
@@ -342,7 +350,7 @@ describe("transform type annotations", () => {
startDate: moment
};`;
const expected = dedent`type Test = {
- startDate: moment.Moment
+ startDate: moment.Moment;
};`;
expect(await transform(src)).toBe(expected);
});
@@ -367,9 +375,9 @@ describe("transform type annotations", () => {
// React
- it("Converts React.Node to React.ReactElement in function return", async () => {
+ it("Converts React.Node to ReactNode in function return", async () => {
const src = `const Component = (props: Props): React.Node => {return };`;
- const expected = `const Component = (props: Props): React.ReactElement => {return };`;
+ const expected = `const Component = (props: Props) => {return };`;
expect(await transform(src)).toBe(expected);
});
@@ -441,9 +449,9 @@ describe("transform type annotations", () => {
expect(await transform(src)).toBe(expected);
});
- it("Converts React.MixedElement", async () => {
+ it("Strips React.MixedElement", async () => {
const src = `function f(): React.MixedElement {};`;
- const expected = `function f(): React.ReactElement {};`;
+ const expected = `function f() {};`;
expect(await transform(src)).toBe(expected);
});
@@ -453,18 +461,18 @@ describe("transform type annotations", () => {
expect(await transform(src)).toBe(expected);
});
- it("Converts React.Node to React.ReactElement in arrow function", async () => {
+ it("Converts React.Node to ReactNode in arrow function", async () => {
const src = `const Component = (props: Props): React.Node => {return };`;
- const expected = `const Component = (props: Props): React.ReactElement => {return };`;
+ const expected = `const Component = (props: Props) => {return };`;
expect(await transform(src)).toBe(expected);
});
- it("Converts React.Node to React.ReactElement or null in arrow function return", async () => {
+ it("Converts React.Node to ReactNode in arrow function return", async () => {
const src = dedent`const Component = (props: Props): React.Node => {
if (foo) return ();
return null;
};`;
- const expected = dedent`const Component = (props: Props): React.ReactElement | null => {
+ const expected = dedent`const Component = (props: Props) => {
if (foo) return ();
return null;
};`;
@@ -473,16 +481,16 @@ describe("transform type annotations", () => {
it("Converts React.Node to React.ReactElement in normal function", async () => {
const src = `function Component(props: Props): React.Node {return };`;
- const expected = `function Component(props: Props): React.ReactElement {return };`;
+ const expected = `function Component(props: Props) {return };`;
expect(await transform(src)).toBe(expected);
});
- it("Converts React.Node to React.ReactElement or null in normal function return", async () => {
+ it("Strips React.Node in normal function return", async () => {
const src = dedent`function Component(props: Props): React.Node {
if (foo) return ();
return null;
};`;
- const expected = dedent`function Component(props: Props): React.ReactElement | null {
+ const expected = dedent`function Component(props: Props) {
if (foo) return ();
return null;
};`;
@@ -507,21 +515,21 @@ describe("transform type annotations", () => {
expect(await transform(src)).toBe(expected);
});
- it("Converts React.ElementConfig to JSX.LibraryManagedAttributes", async () => {
+ it("Converts React.ElementConfig to ComponentProps", async () => {
const src = `type Test = React.ElementConfig;`;
- const expected = `type Test = JSX.LibraryManagedAttributes>;`;
+ const expected = `type Test = ComponentProps;`;
expect(await transform(src)).toBe(expected);
});
it("Converts React.ElementConfig and keeps typeof", async () => {
const src = `type Test = React.ElementConfig;`;
- const expected = `type Test = JSX.LibraryManagedAttributes>;`;
+ const expected = `type Test = ComponentProps;`;
expect(await transform(src)).toBe(expected);
});
it("Converts React.ElementConfig with indexing", async () => {
const src = `type Test = $PropertyType, 'foo'>;`;
- const expected = `type Test = JSX.LibraryManagedAttributes>['foo'];`;
+ const expected = `type Test = ComponentProps['foo'];`;
expect(await transform(src)).toBe(expected);
});
@@ -532,7 +540,7 @@ describe("transform type annotations", () => {
};`;
const expected = dedent`
type Props = {
- children: Array | MenuChildren
+ children: Array | MenuChildren;
};`;
expect(await transform(src)).toBe(expected);
});
@@ -595,14 +603,14 @@ describe("transform type annotations", () => {
it("Does not convert {} to Record if an object has any properties", async () => {
// dedent messes up the indentation of the string
const src = `function f(): {
- prop: boolean
+ prop: boolean;
} {return {}}
let af: () => {
- prop: boolean
+ prop: boolean;
}
class C {
m(): {
- prop: boolean
+ prop: boolean;
} {return {}}
}`;
@@ -764,9 +772,9 @@ class C {
const expected = dedent`
export type Test = {
- (arg1: undefined | Example | Example[], arg2?: () => T | null | undefined): T,
- (arg1: undefined | Example | Example[]): Attributes,
- foo: number
+ (arg1: undefined | Example | Example[], arg2?: (() => T) | null | undefined): T;
+ (arg1: undefined | Example | Example[]): Attributes;
+ foo: number;
};
`;
expect(await transform(src)).toBe(expected);
@@ -783,9 +791,9 @@ class C {
const expected = dedent`
type Test = {
- (arg1: undefined | Example | Example[], arg2?: () => T | null | undefined): T,
- (arg1: undefined | Example | Example[]): Attributes,
- foo: number
+ (arg1: undefined | Example | Example[], arg2?: (() => T) | null | undefined): T;
+ (arg1: undefined | Example | Example[]): Attributes;
+ foo: number;
};
`;
expect(await transform(src)).toBe(expected);
diff --git a/src/convert/type-annotations.ts b/src/convert/type-annotations.ts
index 66377df..e925580 100644
--- a/src/convert/type-annotations.ts
+++ b/src/convert/type-annotations.ts
@@ -1,7 +1,7 @@
import * as t from "@babel/types";
import traverse from "@babel/traverse";
-import { replaceWith, inheritLocAndComments } from "./utils/common";
-import { migrateType } from "./migrate/type";
+import { remove, replaceWith, inheritLocAndComments } from "./utils/common";
+import { migrateType, REMOVE_ME_ID } from "./migrate/type";
import { migrateTypeParameterDeclaration } from "./migrate/type-parameter";
import { TransformerInput } from "./transformer";
import { MetaData } from "./migrate/metadata";
@@ -60,14 +60,28 @@ export function transformTypeAnnotations({
metaData.returnType = true;
}
- replaceWith(
- path,
- t.tsTypeAnnotation(
- migrateType(reporter, state, path.node.typeAnnotation, metaData)
- ),
- state.config.filePath,
- reporter
+ const tsType = migrateType(
+ reporter,
+ state,
+ path.node.typeAnnotation,
+ metaData
);
+
+ if (
+ metaData.returnType &&
+ tsType.type === "TSTypeReference" &&
+ tsType.typeName.type === "Identifier" &&
+ tsType.typeName.name === REMOVE_ME_ID
+ ) {
+ remove(path, state.config.filePath, reporter);
+ } else {
+ replaceWith(
+ path,
+ t.tsTypeAnnotation(tsType),
+ state.config.filePath,
+ reporter
+ );
+ }
},
TypeParameterDeclaration(path) {
diff --git a/src/convert/utils/common.ts b/src/convert/utils/common.ts
index c8710d0..4f217b6 100644
--- a/src/convert/utils/common.ts
+++ b/src/convert/utils/common.ts
@@ -5,6 +5,8 @@ import { TransformerInput } from "../transformer";
import MigrationReporter from "../../runner/migration-reporter";
import { logger } from "../../runner/logger";
+const FORCE_JSX_COMMENT = "@force-jsx";
+
/**
* Determine whether the file contains any JSX
* @param file : File source to check
@@ -12,6 +14,18 @@ import { logger } from "../../runner/logger";
export function hasJSX({ file }: TransformerInput): boolean {
let found = false;
traverse(file, {
+ Program(path: NodePath) {
+ if (
+ path.node.body?.some((node) =>
+ // @ts-expect-error comments doesn't exist
+ node.comments?.some((comment) =>
+ comment.value.includes(FORCE_JSX_COMMENT)
+ )
+ )
+ ) {
+ found = true;
+ }
+ },
JSXElement() {
found = true;
},
@@ -191,6 +205,19 @@ export function addEmptyLineInProgramPath(path: NodePath) {
path.unshiftContainer("body", t.noop());
}
+export function remove(
+ path: NodePath,
+ filePath: string,
+ reporter: MigrationReporter
+) {
+ try {
+ path.remove();
+ } catch (e) {
+ // Catch the error so conversion of the file can continue.
+ reporter.error(filePath, e);
+ }
+}
+
/**
* Recast uses a different format for comments. We need to manually copy them over to the new node.
* We also attach the old location so that Recast prints it at the same place.
diff --git a/src/convert/utils/matchers.ts b/src/convert/utils/matchers.ts
index ba316fc..6175644 100644
--- a/src/convert/utils/matchers.ts
+++ b/src/convert/utils/matchers.ts
@@ -9,7 +9,7 @@ export function isIdentifierNamed(name: string) {
* @param leftName - Left side of the type, e.g. React
* @param rightName - Right side of the type
*/
-export function matchesFullyQualifiedName(leftName: string, rightName: string) {
+function matchesFullyQualifiedName(leftName: string, rightName: string) {
const leftMatcher = isIdentifierNamed(leftName);
const rightMatcher = isIdentifierNamed(rightName);
return (node: t.Identifier | t.TSQualifiedName) =>
@@ -17,3 +17,12 @@ export function matchesFullyQualifiedName(leftName: string, rightName: string) {
leftMatcher(node.left) &&
rightMatcher(node.right);
}
+
+/**
+ * Utility for checking React.* (e.g. matches both React.Node and Node)
+ */
+export function matchesReact(specifierName: string) {
+ return (node: t.Identifier | t.TSQualifiedName) =>
+ isIdentifierNamed(specifierName)(node) ||
+ matchesFullyQualifiedName("React", specifierName)(node);
+}
diff --git a/src/convert/utils/type-mappings.ts b/src/convert/utils/type-mappings.ts
index 13ab56d..4b1af5b 100644
--- a/src/convert/utils/type-mappings.ts
+++ b/src/convert/utils/type-mappings.ts
@@ -3,6 +3,8 @@ export const ReactTypes = {
Child: "ReactChild",
Children: "ReactChildren",
Text: "ReactText",
+ Element: "ReactElement",
+ ElementConfig: "ComponentProps",
Fragment: "ReactFragment",
FragmentType: "ComponentType",
Portal: "ReactPortal",
diff --git a/src/test/regression/__snapshots__/regression.test.ts.snap b/src/test/regression/__snapshots__/regression.test.ts.snap
index 815cf6f..580b7ba 100644
--- a/src/test/regression/__snapshots__/regression.test.ts.snap
+++ b/src/test/regression/__snapshots__/regression.test.ts.snap
@@ -6,7 +6,7 @@ exports[`Regression tests flow_typescript_differences 1`] = `
// bounded polymorphism
function fooGood(obj: T): T {
console.log(Math.abs(obj.x));
return obj;
@@ -23,12 +23,12 @@ function fnOptional(x?: number) {}
// Exact/partial types
type ExactUser = {
- name: string,
- age: number
+ name: string;
+ age: number;
};
type User = {
- name: string,
- age: number
+ name: string;
+ age: number;
};
type OptionalUser = Partial;
@@ -77,7 +77,7 @@ const onePlusArrow = [1, 2, 3].map((a) => a + 1);
// Lookup types
type A = {
- thing: string
+ thing: string;
};
// when the property is a string constant use $PropertyType (i.e. you know it when typing)
@@ -110,15 +110,15 @@ declare var e: E; // E is number
// Mapped types
type InputType = {
- hello: string
+ hello: string;
};
type MappedType = Flow.ObjMap number>;
type FormFieldDef = {
- label: string,
- description?: string,
- default: T | (() => T)
+ label: string;
+ description?: string;
+ default: T | (() => T);
};
type ExtractShape = (arg1: T) => FormFieldDef;
@@ -137,34 +137,34 @@ declare class Adder {
// Readonly
type B = {
- readonly b: string
+ readonly b: string;
};
let b: B = {b: 'something'};
// Difference
type C = Flow.Diff<{
- a: string,
- b: number
+ a: string;
+ b: number;
}, {
- a: string
+ a: string;
}>;
// Rest
type Props = {
- name: string,
- age: number
+ name: string;
+ age: number;
};
const propsRest: Props = {name: 'Jon', age: 42};
const {age, ...otherProps} = propsRest;
(otherProps as Partial>);
// Same
type F = {
- (): string
+ (): string;
};
const f: F = () => 'hello';
const hello: string = f();
diff --git a/src/test/test-files/force-jsx.js b/src/test/test-files/force-jsx.js
new file mode 100644
index 0000000..a38bea2
--- /dev/null
+++ b/src/test/test-files/force-jsx.js
@@ -0,0 +1,6 @@
+// @force-jsx
+
+function helloWorld() {
+ print('hello_world');
+}
+helloWorld();
diff --git a/yarn.lock b/yarn.lock
index 4b5f0e8..d79c16f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1116,15 +1116,26 @@ array.prototype.flat@^1.2.5:
define-properties "^1.1.3"
es-abstract "^1.19.0"
+assert@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd"
+ integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==
+ dependencies:
+ call-bind "^1.0.2"
+ is-nan "^1.3.2"
+ object-is "^1.1.5"
+ object.assign "^4.1.4"
+ util "^0.12.5"
+
assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==
-ast-types@0.14.2:
- version "0.14.2"
- resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd"
- integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==
+ast-types@^0.16.1:
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.16.1.tgz#7a9da1617c9081bc121faafe91711b4c8bb81da2"
+ integrity sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==
dependencies:
tslib "^2.0.1"
@@ -1143,6 +1154,13 @@ atob@^2.1.2:
resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+available-typed-arrays@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
+ integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==
+ dependencies:
+ possible-typed-array-names "^1.0.0"
+
babel-jest@^26.6.3:
version "26.6.3"
resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
@@ -1311,6 +1329,17 @@ call-bind@^1.0.0, call-bind@^1.0.2:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
+call-bind@^1.0.5, call-bind@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
+ integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ set-function-length "^1.2.1"
+
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -1591,6 +1620,15 @@ deepmerge@^4.2.2:
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+define-data-property@^1.0.1, define-data-property@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
+ integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ gopd "^1.0.1"
+
define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -1598,6 +1636,15 @@ define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
+define-properties@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
+ integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
+ dependencies:
+ define-data-property "^1.0.1"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
define-property@^0.2.5:
version "0.2.5"
resolved "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@@ -1730,6 +1777,18 @@ es-abstract@^1.19.0, es-abstract@^1.19.1:
string.prototype.trimstart "^1.0.4"
unbox-primitive "^1.0.1"
+es-define-property@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
+ integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
+ dependencies:
+ get-intrinsic "^1.2.4"
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@@ -2291,6 +2350,13 @@ flow@^0.2.3:
resolved "https://registry.npmjs.org/flow/-/flow-0.2.3.tgz#f8da65efa249127ec99376a28896572a9795d1af"
integrity sha512-CFNnRuX6nsOmEjGvE5RP0zwGoebdU9k+IJJy97HHkc0Xdlyh5EFAmdOznTKhEQkWKWXTJp63bW0TWQZ+sywWRw==
+for-each@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
+ integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
+ dependencies:
+ is-callable "^1.1.3"
+
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -2345,6 +2411,11 @@ function-bind@^1.1.1:
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@@ -2369,6 +2440,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
+get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
+ integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+ hasown "^2.0.0"
+
get-package-type@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
@@ -2444,6 +2526,13 @@ globby@^11.0.4:
merge2 "^1.3.0"
slash "^3.0.0"
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ dependencies:
+ get-intrinsic "^1.1.3"
+
graceful-fs@^4.1.11, graceful-fs@^4.1.2:
version "4.2.8"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
@@ -2474,11 +2563,28 @@ has-flag@^4.0.0:
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
+ integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+ dependencies:
+ es-define-property "^1.0.0"
+
+has-proto@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
+ integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
+
has-symbols@^1.0.1, has-symbols@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
has-tostringtag@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
@@ -2486,6 +2592,13 @@ has-tostringtag@^1.0.0:
dependencies:
has-symbols "^1.0.2"
+has-tostringtag@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
+ integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
+ dependencies:
+ has-symbols "^1.0.3"
+
has-value@^0.3.1:
version "0.3.1"
resolved "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -2524,6 +2637,13 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
+hasown@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -2614,7 +2734,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2:
+inherits@2, inherits@^2.0.3:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -2642,6 +2762,14 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
+is-arguments@^1.0.4:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
+ integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
+ dependencies:
+ call-bind "^1.0.2"
+ has-tostringtag "^1.0.0"
+
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -2667,6 +2795,11 @@ is-buffer@^1.1.5:
resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+is-callable@^1.1.3:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
+ integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
+
is-callable@^1.1.4, is-callable@^1.2.4:
version "1.2.4"
resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
@@ -2764,6 +2897,13 @@ is-generator-fn@^2.0.0:
resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
+is-generator-function@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
+ integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@@ -2771,6 +2911,14 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
dependencies:
is-extglob "^2.1.1"
+is-nan@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
+ integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
+ dependencies:
+ call-bind "^1.0.0"
+ define-properties "^1.1.3"
+
is-negative-zero@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
@@ -2844,6 +2992,13 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
dependencies:
has-symbols "^1.0.2"
+is-typed-array@^1.1.3:
+ version "1.1.13"
+ resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229"
+ integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==
+ dependencies:
+ which-typed-array "^1.1.14"
+
is-typedarray@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -3774,6 +3929,14 @@ object-inspect@^1.11.0, object-inspect@^1.9.0:
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
+object-is@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07"
+ integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+
object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@@ -3796,6 +3959,16 @@ object.assign@^4.1.2:
has-symbols "^1.0.1"
object-keys "^1.1.1"
+object.assign@^4.1.4:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0"
+ integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==
+ dependencies:
+ call-bind "^1.0.5"
+ define-properties "^1.2.1"
+ has-symbols "^1.0.3"
+ object-keys "^1.1.1"
+
object.pick@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
@@ -4049,6 +4222,11 @@ posix-character-classes@^0.1.0:
resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==
+possible-typed-array-names@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
+ integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==
+
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -4151,12 +4329,13 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
-recast@^0.20.4:
- version "0.20.4"
- resolved "https://registry.npmjs.org/recast/-/recast-0.20.4.tgz#db55983eac70c46b3fff96c8e467d65ffb4a7abc"
- integrity sha512-6qLIBGGRcwjrTZGIiBpJVC/NeuXpogXNyRQpqU1zWPUigCphvApoCs9KIwDYh1eDuJ6dAFlQoi/QUyE5KQ6RBQ==
+recast@0.23.4:
+ version "0.23.4"
+ resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.4.tgz#ca1bac7bfd3011ea5a28dfecb5df678559fb1ddf"
+ integrity sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw==
dependencies:
- ast-types "0.14.2"
+ assert "^2.0.0"
+ ast-types "^0.16.1"
esprima "~4.0.0"
source-map "~0.6.1"
tslib "^2.0.1"
@@ -4331,6 +4510,18 @@ set-blocking@^2.0.0:
resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
+set-function-length@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
+ integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.2"
+
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
@@ -4910,6 +5101,17 @@ use@^3.1.0:
resolved "https://registry.npmjs.org/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
+util@^0.12.5:
+ version "0.12.5"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc"
+ integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
+ dependencies:
+ inherits "^2.0.3"
+ is-arguments "^1.0.4"
+ is-generator-function "^1.0.7"
+ is-typed-array "^1.1.3"
+ which-typed-array "^1.1.2"
+
uuid@^8.3.0, uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
@@ -5005,6 +5207,17 @@ which-module@^2.0.0:
resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==
+which-typed-array@^1.1.14, which-typed-array@^1.1.2:
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d"
+ integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.7"
+ for-each "^0.3.3"
+ gopd "^1.0.1"
+ has-tostringtag "^1.0.2"
+
which@^1.2.9:
version "1.3.1"
resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
]