From 18291cbb3abfd03c77684a42b6fe961bfa68eed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Sat, 18 Jun 2022 17:20:45 -0400 Subject: [PATCH] Improve plugins typings (Part 3) (#14642) * external-helpers * bugfix * transform-parameters * object-rest-spread * destructuring-private * transform-destructuring * proposal-decorators * optional-chaining * helper-wrap-function * explode-assignable-expression * helper-compilation-targets * helper-plugin-utils * helpers * helper-validator-option * fix: allow "+" and "-" in MappedType .readonly/.optional * fixture-test-runner * remove charcodes from dependencies the charcodes transform will inline charCodes.* so we can remove it from dependencies --- .../src/debug.ts | 2 +- .../src/index.ts | 107 ++++++------ .../src/pretty.ts | 8 +- .../src/targets.ts | 11 +- .../src/types.ts | 8 +- .../src/utils.ts | 25 ++- .../src/index.ts | 2 +- packages/babel-helper-fixtures/src/index.ts | 2 +- .../babel-helper-plugin-utils/src/index.ts | 31 +++- .../src/index.ts | 12 +- .../src/find-suggestion.ts | 2 +- .../babel-helper-wrap-function/src/index.ts | 6 +- .../babel-helpers/scripts/generate-helpers.js | 2 +- .../babel-helpers/src/helpers-generated.ts | 2 +- .../src/util.ts | 2 +- .../src/index.ts | 2 +- .../package.json | 5 +- .../src/index.ts | 8 +- .../src/transformer-2021-12.ts | 10 +- .../src/transformer-legacy.ts | 129 ++++++++++---- .../src/index.ts | 2 +- .../src/util.ts | 22 +-- .../src/index.ts | 33 ++-- .../src/shouldStoreRHSInTemporaryVariable.ts | 4 +- .../src/transform.ts | 35 ++-- .../src/index.ts | 7 +- .../src/util.ts | 60 ++++--- .../src/params.ts | 55 ++++-- .../src/rest.ts | 158 +++++++++--------- .../src/ast-types/generated/index.ts | 4 +- .../babel-types/src/definitions/typescript.ts | 4 +- yarn.lock | 8 + 32 files changed, 478 insertions(+), 290 deletions(-) diff --git a/packages/babel-helper-compilation-targets/src/debug.ts b/packages/babel-helper-compilation-targets/src/debug.ts index aef9fc12a249..d1ee514cd980 100644 --- a/packages/babel-helper-compilation-targets/src/debug.ts +++ b/packages/babel-helper-compilation-targets/src/debug.ts @@ -34,5 +34,5 @@ export function getInclusionReasons( } return result; - }, {}); + }, {} as Partial>); } diff --git a/packages/babel-helper-compilation-targets/src/index.ts b/packages/babel-helper-compilation-targets/src/index.ts index 31b9c40d0e27..b1bfbd17fe2f 100644 --- a/packages/babel-helper-compilation-targets/src/index.ts +++ b/packages/babel-helper-compilation-targets/src/index.ts @@ -12,7 +12,14 @@ import { import { OptionValidator } from "@babel/helper-validator-option"; import { browserNameMap } from "./targets"; import { TargetNames } from "./options"; -import type { Targets, InputTargets, Browsers, TargetsTuple } from "./types"; +import type { + Target, + Targets, + InputTargets, + Browsers, + BrowserslistBrowserName, + TargetsTuple, +} from "./types"; export type { Targets, InputTargets }; @@ -58,50 +65,49 @@ function validateBrowsers(browsers: Browsers | undefined) { } function getLowestVersions(browsers: Array): Targets { - return browsers.reduce((all: any, browser: string): any => { - const [browserName, browserVersion] = browser.split(" "); - const normalizedBrowserName = browserNameMap[browserName]; - - if (!normalizedBrowserName) { + return browsers.reduce((all, browser) => { + const [browserName, browserVersion] = browser.split(" ") as [ + BrowserslistBrowserName, + string, + ]; + const target: Target = browserNameMap[browserName]; + + if (!target) { return all; } try { // Browser version can return as "10.0-10.2" const splitVersion = browserVersion.split("-")[0].toLowerCase(); - const isSplitUnreleased = isUnreleasedVersion(splitVersion, browserName); + const isSplitUnreleased = isUnreleasedVersion(splitVersion, target); - if (!all[normalizedBrowserName]) { - all[normalizedBrowserName] = isSplitUnreleased + if (!all[target]) { + all[target] = isSplitUnreleased ? splitVersion : semverify(splitVersion); return all; } - const version = all[normalizedBrowserName]; - const isUnreleased = isUnreleasedVersion(version, browserName); + const version = all[target]; + const isUnreleased = isUnreleasedVersion(version, target); if (isUnreleased && isSplitUnreleased) { - all[normalizedBrowserName] = getLowestUnreleased( - version, - splitVersion, - browserName, - ); + all[target] = getLowestUnreleased(version, splitVersion, target); } else if (isUnreleased) { - all[normalizedBrowserName] = semverify(splitVersion); + all[target] = semverify(splitVersion); } else if (!isUnreleased && !isSplitUnreleased) { const parsedBrowserVersion = semverify(splitVersion); - all[normalizedBrowserName] = semverMin(version, parsedBrowserVersion); + all[target] = semverMin(version, parsedBrowserVersion); } } catch (e) {} return all; - }, {}); + }, {} as Record); } function outputDecimalWarning( - decimalTargets: Array<{ target: string; value: string }>, + decimalTargets: Array<{ target: string; value: number }>, ): void { if (!decimalTargets.length) { return; @@ -117,7 +123,7 @@ getting parsed as 6.1, which can lead to unexpected behavior. `); } -function semverifyTarget(target, value) { +function semverifyTarget(target: keyof Targets, value: string) { try { return semverify(value); } catch (error) { @@ -129,23 +135,24 @@ function semverifyTarget(target, value) { } } -const targetParserMap = { - __default(target, value) { - const version = isUnreleasedVersion(value, target) - ? value.toLowerCase() - : semverifyTarget(target, value); - return [target, version]; - }, - - // Parse `node: true` and `node: "current"` to version - node(target, value) { - const parsed = - value === true || value === "current" - ? process.versions.node - : semverifyTarget(target, value); - return [target, parsed]; - }, -}; +// Parse `node: true` and `node: "current"` to version +function nodeTargetParser(value: true | string) { + const parsed = + value === true || value === "current" + ? process.versions.node + : semverifyTarget("node", value); + return ["node" as const, parsed] as const; +} + +function defaultTargetParser( + target: Exclude, + value: string, +): readonly [Exclude, string] { + const version = isUnreleasedVersion(value, target) + ? value.toLowerCase() + : semverifyTarget(target, value); + return [target, version] as const; +} function generateTargets(inputTargets: InputTargets): Targets { const input = { ...inputTargets }; @@ -214,7 +221,10 @@ export default function getTargets( // These values OVERRIDE the `browsers` field. if (esmodules && (esmodules !== "intersect" || !browsers?.length)) { browsers = Object.keys(ESM_SUPPORT) - .map(browser => `${browser} >= ${ESM_SUPPORT[browser]}`) + .map( + (browser: keyof typeof ESM_SUPPORT) => + `${browser} >= ${ESM_SUPPORT[browser]}`, + ) .join(", "); esmodules = false; } @@ -226,13 +236,16 @@ export default function getTargets( const queryBrowsers = resolveTargets(browsers, options.browserslistEnv); if (esmodules === "intersect") { - for (const browser of Object.keys(queryBrowsers)) { + for (const browser of Object.keys(queryBrowsers) as Target[]) { const version = queryBrowsers[browser]; + const esmSupportVersion = + // @ts-ignore ie is not in ESM_SUPPORT + ESM_SUPPORT[browser]; - if (ESM_SUPPORT[browser]) { + if (esmSupportVersion) { queryBrowsers[browser] = getHighestUnreleased( version, - semverify(ESM_SUPPORT[browser]), + semverify(esmSupportVersion), browser, ); } else { @@ -247,7 +260,7 @@ export default function getTargets( // Parse remaining targets const result: Targets = {} as Targets; const decimalWarnings = []; - for (const target of Object.keys(targets).sort()) { + for (const target of Object.keys(targets).sort() as Target[]) { const value = targets[target]; // Warn when specifying minor/patch as a decimal @@ -255,10 +268,10 @@ export default function getTargets( decimalWarnings.push({ target, value }); } - // Check if we have a target parser? - // $FlowIgnore - Flow doesn't like that some targetParserMap[target] might be missing - const parser = targetParserMap[target] ?? targetParserMap.__default; - const [parsedTarget, parsedValue] = parser(target, value); + const [parsedTarget, parsedValue] = + target === "node" + ? nodeTargetParser(value) + : defaultTargetParser(target, value as string); if (parsedValue) { // Merge (lowest wins) diff --git a/packages/babel-helper-compilation-targets/src/pretty.ts b/packages/babel-helper-compilation-targets/src/pretty.ts index 10afed440b75..34f5257b15ae 100644 --- a/packages/babel-helper-compilation-targets/src/pretty.ts +++ b/packages/babel-helper-compilation-targets/src/pretty.ts @@ -1,6 +1,6 @@ import semver from "semver"; import { unreleasedLabels } from "./targets"; -import type { Targets } from "./types"; +import type { Targets, Target } from "./types"; export function prettifyVersion(version: string) { if (typeof version !== "string") { @@ -23,10 +23,12 @@ export function prettifyVersion(version: string) { } export function prettifyTargets(targets: Targets): Targets { - return Object.keys(targets).reduce((results, target) => { + return Object.keys(targets).reduce((results, target: Target) => { let value = targets[target]; - const unreleasedLabel = unreleasedLabels[target]; + const unreleasedLabel = + // @ts-expect-error undefined is strictly compared with string later + unreleasedLabels[target]; if (typeof value === "string" && unreleasedLabel !== value) { value = prettifyVersion(value); } diff --git a/packages/babel-helper-compilation-targets/src/targets.ts b/packages/babel-helper-compilation-targets/src/targets.ts index 401d7904a995..ff08703a6492 100644 --- a/packages/babel-helper-compilation-targets/src/targets.ts +++ b/packages/babel-helper-compilation-targets/src/targets.ts @@ -1,8 +1,11 @@ export const unreleasedLabels = { safari: "tp", -}; +} as const; -export const browserNameMap = { +import type { Target } from "./types"; + +// Map from browserslist|@mdn/browser-compat-data browser names to @kangax/compat-table browser names +export const browserNameMap: Record = { and_chr: "chrome", and_ff: "firefox", android: "android", @@ -17,4 +20,6 @@ export const browserNameMap = { opera: "opera", safari: "safari", samsung: "samsung", -}; +} as const; + +export type BrowserslistBrowserName = keyof typeof browserNameMap; diff --git a/packages/babel-helper-compilation-targets/src/types.ts b/packages/babel-helper-compilation-targets/src/types.ts index 00035a1f0e14..d17f15299333 100644 --- a/packages/babel-helper-compilation-targets/src/types.ts +++ b/packages/babel-helper-compilation-targets/src/types.ts @@ -1,4 +1,4 @@ -// Targets +// Targets, engine names defined in compat-tables export type Target = | "node" | "chrome" @@ -17,7 +17,9 @@ export type Targets = { }; export type TargetsTuple = { - [target in Target]: string; + [target in Exclude]: string; +} & { + node: string | true; }; export type Browsers = string | ReadonlyArray; @@ -31,3 +33,5 @@ export type InputTargets = { // remove `intersect`. esmodules?: boolean | "intersect"; } & Targets; + +export type { BrowserslistBrowserName } from "./targets"; diff --git a/packages/babel-helper-compilation-targets/src/utils.ts b/packages/babel-helper-compilation-targets/src/utils.ts index d527247e9fc0..2beea6b0404e 100644 --- a/packages/babel-helper-compilation-targets/src/utils.ts +++ b/packages/babel-helper-compilation-targets/src/utils.ts @@ -38,20 +38,27 @@ export function semverify(version: number | string): string { export function isUnreleasedVersion( version: string | number, - env: string, + env: Target, ): boolean { - const unreleasedLabel = unreleasedLabels[env]; + const unreleasedLabel = + // @ts-expect-error unreleasedLabel will be guarded later + unreleasedLabels[env]; return ( !!unreleasedLabel && unreleasedLabel === version.toString().toLowerCase() ); } -export function getLowestUnreleased(a: string, b: string, env: string): string { - const unreleasedLabel = unreleasedLabels[env]; - const hasUnreleased = [a, b].some(item => item === unreleasedLabel); - if (hasUnreleased) { - // @ts-expect-error todo(flow->ts): probably a bug - types of a hasUnreleased to not overlap - return a === hasUnreleased ? b : a || b; +export function getLowestUnreleased(a: string, b: string, env: Target): string { + const unreleasedLabel: + | typeof unreleasedLabels[keyof typeof unreleasedLabels] + | undefined = + // @ts-ignore unreleasedLabel is undefined when env is not safari + unreleasedLabels[env]; + if (a === unreleasedLabel) { + return b; + } + if (b === unreleasedLabel) { + return a; } return semverMin(a, b); } @@ -59,7 +66,7 @@ export function getLowestUnreleased(a: string, b: string, env: string): string { export function getHighestUnreleased( a: string, b: string, - env: string, + env: Target, ): string { return getLowestUnreleased(a, b, env) === a ? b : a; } diff --git a/packages/babel-helper-explode-assignable-expression/src/index.ts b/packages/babel-helper-explode-assignable-expression/src/index.ts index deaebcc99b60..01762222cdaa 100644 --- a/packages/babel-helper-explode-assignable-expression/src/index.ts +++ b/packages/babel-helper-explode-assignable-expression/src/index.ts @@ -78,7 +78,7 @@ export default function ( scope: Scope, allowedSingleIdent?: boolean, ): { - uid: t.Identifier | t.MemberExpression; + uid: t.Identifier | t.MemberExpression | t.Super; ref: t.Identifier | t.MemberExpression; } { let obj; diff --git a/packages/babel-helper-fixtures/src/index.ts b/packages/babel-helper-fixtures/src/index.ts index b30a2cf73e4d..c1119cb2bbed 100644 --- a/packages/babel-helper-fixtures/src/index.ts +++ b/packages/babel-helper-fixtures/src/index.ts @@ -45,7 +45,7 @@ export interface Test { validateLogs: boolean; } -interface TaskOptions extends InputOptions { +export interface TaskOptions extends InputOptions { BABEL_8_BREAKING?: boolean; DO_NOT_SET_SOURCE_TYPE?: boolean; externalHelpers?: boolean; diff --git a/packages/babel-helper-plugin-utils/src/index.ts b/packages/babel-helper-plugin-utils/src/index.ts index 96c4296b4a24..d1327b6bf0ce 100644 --- a/packages/babel-helper-plugin-utils/src/index.ts +++ b/packages/babel-helper-plugin-utils/src/index.ts @@ -19,13 +19,16 @@ export function declare( ) => PluginObject { // @ts-ignore return (api, options: Option, dirname: string) => { - let clonedApi; + let clonedApi: PluginAPI; - for (const name of Object.keys(apiPolyfills)) { + for (const name of Object.keys( + apiPolyfills, + ) as (keyof typeof apiPolyfills)[]) { if (api[name]) continue; // TODO: Use ??= when flow lets us to do so clonedApi = clonedApi ?? copyApiObject(api); + // @ts-expect-error The shape of API polyfill is guaranteed by APIPolyfillFactory clonedApi[name] = apiPolyfills[name](clonedApi); } @@ -38,11 +41,21 @@ export const declarePreset = declare as