diff --git a/Herebyfile.mjs b/Herebyfile.mjs index 402f7917cf3..ab938470184 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -175,6 +175,7 @@ async function runDtsBundler(entrypoint, output) { * @property {boolean} [exportIsTsObject] * @property {boolean} [treeShaking] * @property {boolean} [usePublicAPI] + * @property {boolean} [tsgoCompat] * @property {() => void} [onWatchRebuild] */ function createBundler(entrypoint, outfile, taskOptions = {}) { @@ -196,6 +197,9 @@ function createBundler(entrypoint, outfile, taskOptions = {}) { treeShaking: taskOptions.treeShaking, packages: "external", logLevel: "warning", + define: { + __TSGO_COMPAT__: taskOptions.tsgoCompat ? "true" : "false", + }, // legalComments: "none", // If we add copyright headers to the source files, uncomment. }; @@ -232,7 +236,8 @@ function createBundler(entrypoint, outfile, taskOptions = {}) { const require = "require"; const fakeName = "Q".repeat(require.length); const fakeNameRegExp = new RegExp(fakeName, "g"); - options.define = { [require]: fakeName }; + options.define ||= {}; + options.define[require] = fakeName; // For historical reasons, TypeScript does not set __esModule. Hack esbuild's __toCommonJS to be a noop. // We reference `__copyProps` to ensure the final bundle doesn't have any unreferenced code. @@ -541,6 +546,7 @@ const { main: tests, watch: watchTests } = entrypointBuildTask({ output: testRunner, mainDeps: [generateLibs], bundlerOptions: { + tsgoCompat: true, // Ensure we never drop any dead code, which might be helpful while debugging. treeShaking: false, onWatchRebuild() { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 50036c47945..d1dc7107215 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -84,6 +84,7 @@ import { compareComparableValues, compareDiagnostics, comparePaths, + compareValues, Comparison, CompilerOptions, ComputedPropertyName, @@ -5394,7 +5395,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function createTypeofType() { - return getUnionType(map([...typeofNEFacts.keys()].sort(), getStringLiteralType)); + return getUnionType(map(__TSGO_COMPAT__ ? [...typeofNEFacts.keys()].sort() : arrayFrom(typeofNEFacts.keys()), getStringLiteralType)); } function createTypeParameter(symbol?: Symbol) { @@ -5420,7 +5421,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { (result || (result = [])).push(symbol); } }); - result?.sort(compareSymbols); + sortSymbolsIfTSGoCompat(result); return result || emptyArray; } @@ -17653,11 +17654,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function containsType(types: readonly Type[], type: Type): boolean { - return binarySearch(types, type, identity, compareTypes) >= 0; + return __TSGO_COMPAT__ ? binarySearch(types, type, identity, compareTypes) >= 0 : binarySearch(types, type, getTypeId, compareValues) >= 0; } function insertType(types: Type[], type: Type): boolean { - const index = binarySearch(types, type, identity, compareTypes); + const index = __TSGO_COMPAT__ ? binarySearch(types, type, identity, compareTypes) : binarySearch(types, type, getTypeId, compareValues); if (index < 0) { types.splice(~index, 0, type); return true; @@ -17678,7 +17679,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType; } else { - const index = binarySearch(typeSet, type, identity, compareTypes); + const len = typeSet.length; + const index = __TSGO_COMPAT__ ? binarySearch(typeSet, type, identity, compareTypes) : (len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues)); if (index < 0) { typeSet.splice(~index, 0, type); } @@ -34717,7 +34719,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // So the table *contains* `x` but `x` isn't actually in scope. // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. if (symbol) return symbol; - let candidates = arrayFrom(symbols.values()).sort(compareSymbols); + let candidates = arrayFrom(symbols.values()); if (symbols === globals) { const primitives = mapDefined( ["string", "number", "boolean", "object", "bigint", "symbol"], @@ -34725,8 +34727,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ? createSymbol(SymbolFlags.TypeAlias, s as __String) as Symbol : undefined, ); - candidates = concatenate(candidates, primitives); + candidates = concatenate(primitives, candidates); } + sortSymbolsIfTSGoCompat(candidates); return getSpellingSuggestionForName(unescapeLeadingUnderscores(name), candidates, meaning); } function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { @@ -34736,7 +34739,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { - return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule).sort(compareSymbols), SymbolFlags.ModuleMember); // eslint-disable-line local/no-array-mutating-method-expressions + return targetModule.exports && getSpellingSuggestionForName(idText(name), sortSymbolsIfTSGoCompat(getExportsOfModuleAsArray(targetModule)), SymbolFlags.ModuleMember); } function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined { @@ -52850,6 +52853,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return specifier; } + function sortSymbolsIfTSGoCompat(array: Symbol[]): Symbol[]; + function sortSymbolsIfTSGoCompat(array: Symbol[] | undefined): Symbol[] | undefined; + function sortSymbolsIfTSGoCompat(array: Symbol[] | undefined): Symbol[] | undefined { + if (__TSGO_COMPAT__ && array) { + return array.sort(compareSymbols); + } + return array; + } + function compareSymbols(s1: Symbol | undefined, s2: Symbol | undefined): number { if (s1 === s2) return 0; if (s1 === undefined) return 1; diff --git a/src/compiler/corePublic.ts b/src/compiler/corePublic.ts index 2693eb0e1eb..038fbde8e4f 100644 --- a/src/compiler/corePublic.ts +++ b/src/compiler/corePublic.ts @@ -5,6 +5,12 @@ export const versionMajorMinor = "5.7"; /** The version of the TypeScript compiler release */ export const version: string = `${versionMajorMinor}.0-dev`; +declare global { + var __TSGO_COMPAT__: boolean | undefined; +} + +globalThis.__TSGO_COMPAT__ ??= false; + /** * Type of objects whose values are all of the same type. * The `in` and `for-in` operators can *not* be safely used,