From 3f6f3164d6b8226953490bb43fd1ae5a7aa99fa4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 23 Jul 2024 18:17:07 -0700 Subject: [PATCH] Remove stableSort, rename sort to toSorted (#55728) --- src/compiler/core.ts | 19 ++++--------------- src/compiler/debug.ts | 4 ++-- src/compiler/emitter.ts | 4 ++-- src/compiler/executeCommandLine.ts | 4 ++-- src/compiler/moduleNameResolver.ts | 4 ++-- src/compiler/program.ts | 4 ++-- src/compiler/utilities.ts | 6 +++--- src/harness/fourslashImpl.ts | 2 +- src/harness/fourslashInterfaceImpl.ts | 2 +- src/harness/harnessIO.ts | 2 +- src/server/project.ts | 6 +++--- src/services/codefixes/importFixes.ts | 9 ++++----- src/services/completions.ts | 6 +++--- src/services/organizeImports.ts | 15 +++++++-------- src/services/textChanges.ts | 4 ++-- src/services/utilities.ts | 4 ++-- 16 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e40d8826ca6..e2238766ee5 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -806,7 +806,7 @@ export function sortAndDeduplicate(array: readonly string[]): SortedReadonlyArra export function sortAndDeduplicate(array: readonly T[], comparer: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray; /** @internal */ export function sortAndDeduplicate(array: readonly T[], comparer?: Comparer, equalityComparer?: EqualityComparer): SortedReadonlyArray { - return deduplicateSorted(sort(array, comparer), equalityComparer ?? comparer ?? compareStringsCaseSensitive as any as Comparer); + return deduplicateSorted(toSorted(array, comparer), equalityComparer ?? comparer ?? compareStringsCaseSensitive as any as Comparer); } /** @internal */ @@ -1035,12 +1035,12 @@ function stableSortIndices(array: readonly T[], indices: number[], comparer: } /** - * Returns a new sorted array. + * Returns a new sorted array. This sort is stable, meaning elements equal to each other maintain their relative position in the array. * * @internal */ -export function sort(array: readonly T[], comparer?: Comparer): SortedReadonlyArray { - return (array.length === 0 ? array : array.slice().sort(comparer)) as SortedReadonlyArray; +export function toSorted(array: readonly T[], comparer?: Comparer): SortedReadonlyArray { + return (array.length === 0 ? emptyArray : array.slice().sort(comparer)) as readonly T[] as SortedReadonlyArray; } /** @internal */ @@ -1050,17 +1050,6 @@ export function* arrayReverseIterator(array: readonly T[]) { } } -/** - * Stable sort of an array. Elements equal to each other maintain their relative position in the array. - * - * @internal - */ -export function stableSort(array: readonly T[], comparer: Comparer): SortedReadonlyArray { - const indices = indicesOf(array); - stableSortIndices(array, indices, comparer); - return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; -} - /** @internal */ export function rangeEquals(array1: readonly T[], array2: readonly T[], pos: number, end: number) { while (pos < end) { diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 70c7bf17998..298c628907e 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -79,11 +79,11 @@ import { SignatureFlags, SnippetKind, SortedReadonlyArray, - stableSort, Symbol, SymbolFlags, symbolName, SyntaxKind, + toSorted, TransformFlags, Type, TypeFacts, @@ -436,7 +436,7 @@ export namespace Debug { } } - const sorted = stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); + const sorted = toSorted<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); enumMemberCache.set(enumObject, sorted); return sorted; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1a524647738..6f68329ba0a 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -374,7 +374,6 @@ import { SourceMapSource, SpreadAssignment, SpreadElement, - stableSort, Statement, StringLiteral, supportedJSExtensionsFlat, @@ -394,6 +393,7 @@ import { ThrowStatement, TokenFlags, tokenToString, + toSorted, tracing, TransformationResult, transformNodes, @@ -2072,7 +2072,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri function getSortedEmitHelpers(node: Node) { const helpers = getEmitHelpers(node); - return helpers && stableSort(helpers, compareEmitHelpers); + return helpers && toSorted(helpers, compareEmitHelpers); } // diff --git a/src/compiler/executeCommandLine.ts b/src/compiler/executeCommandLine.ts index f9b1400b4da..0f8fdd25ccc 100644 --- a/src/compiler/executeCommandLine.ts +++ b/src/compiler/executeCommandLine.ts @@ -71,7 +71,6 @@ import { ReportEmitErrorSummary, SolutionBuilder, SolutionBuilderHostBase, - sort, SourceFile, startsWith, startTracing, @@ -80,6 +79,7 @@ import { sys, System, toPath, + toSorted, tracing, validateLocaleAndSetLanguage, version, @@ -170,7 +170,7 @@ function shouldBePretty(sys: System, options: CompilerOptions | BuildOptions) { function getOptionsForHelp(commandLine: ParsedCommandLine) { // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") return !!commandLine.options.all ? - sort(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) : + toSorted(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) : filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView); } diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 62756b7309a..850a8a7f381 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -94,12 +94,12 @@ import { ResolvedTypeReferenceDirective, ResolvedTypeReferenceDirectiveWithFailedLookupLocations, some, - sort, startsWith, supportedDeclarationExtensions, supportedJSExtensionsFlat, supportedTSImplementationExtensions, toPath, + toSorted, tryExtractTSExtension, tryGetExtensionFromPath, tryParsePatterns, @@ -2695,7 +2695,7 @@ function loadModuleFromImportsOrExports(extensions: Extensions, state: ModuleRes const target = (lookupTable as { [idx: string]: unknown; })[moduleName]; return loadModuleFromTargetImportOrExport(target, /*subpath*/ "", /*pattern*/ false, moduleName); } - const expandingKeys = sort(filter(getOwnKeys(lookupTable as MapLike), k => hasOneAsterisk(k) || endsWith(k, "/")), comparePatternKeys); + const expandingKeys = toSorted(filter(getOwnKeys(lookupTable as MapLike), k => hasOneAsterisk(k) || endsWith(k, "/")), comparePatternKeys); for (const potentialTarget of expandingKeys) { if (state.features & NodeResolutionFeatures.ExportsPatternTrailers && matchesPatternWithTrailer(potentialTarget, moduleName)) { const target = (lookupTable as { [idx: string]: unknown; })[potentialTarget]; diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0fc914a51f2..6e8e8c4f492 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -302,7 +302,6 @@ import { sourceFileAffectingCompilerOptions, sourceFileMayBeEmitted, SourceOfProjectReferenceRedirect, - stableSort, startsWith, Statement, StringLiteral, @@ -316,6 +315,7 @@ import { toFileNameLowerCase, tokenToString, toPath as ts_toPath, + toSorted, trace, tracing, tryCast, @@ -1887,7 +1887,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg } } - files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); + files = toSorted(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles); processingDefaultLibFiles = undefined; processingOtherFiles = undefined; filesWithReferencesProcessed = undefined; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e1df7bd5320..a1ef5bb08d8 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -510,7 +510,6 @@ import { skipTrivia, SnippetKind, some, - sort, SortedArray, SourceFile, SourceFileLike, @@ -545,6 +544,7 @@ import { TokenFlags, tokenToString, toPath, + toSorted, tracing, TransformFlags, TransientSymbol, @@ -9596,7 +9596,7 @@ export function matchFiles(path: string, extensions: readonly string[] | undefin visited.set(canonicalPath, true); const { files, directories } = getFileSystemEntries(path); - for (const current of sort(files, compareStringsCaseSensitive)) { + for (const current of toSorted(files, compareStringsCaseSensitive)) { const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); if (extensions && !fileExtensionIsOneOf(name, extensions)) continue; @@ -9619,7 +9619,7 @@ export function matchFiles(path: string, extensions: readonly string[] | undefin } } - for (const current of sort(directories, compareStringsCaseSensitive)) { + for (const current of toSorted(directories, compareStringsCaseSensitive)) { const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); if ( diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 1ac4c439683..4d3d6cea32e 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -1621,7 +1621,7 @@ export class TestState { } } let pos = 0; - const sortedDetails = ts.stableSort(details, (a, b) => ts.compareValues(a.location, b.location)); + const sortedDetails = ts.toSorted(details, (a, b) => ts.compareValues(a.location, b.location)); if (!canDetermineContextIdInline) { // Assign contextIds sortedDetails.forEach(({ span, type }) => { diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 2315d214e2b..eaa96991589 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -1121,7 +1121,7 @@ export namespace Completion { ].map(keywordEntry); export function sorted(entries: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { - return ts.stableSort(entries, compareExpectedCompletionEntries); + return ts.toSorted(entries, compareExpectedCompletionEntries); } // If you want to use a function like `globalsPlus`, that function needs to sort diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index 0082536d8aa..d73092a297a 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -547,7 +547,7 @@ export namespace Compiler { export const diagnosticSummaryMarker = "__diagnosticSummary"; export const globalErrorsMarker = "__globalErrors"; export function* iterateErrorBaseline(inputFiles: readonly TestFile[], diagnostics: readonly ts.Diagnostic[], options?: { pretty?: boolean; caseSensitive?: boolean; currentDirectory?: string; }): IterableIterator<[string, string, number]> { - diagnostics = ts.sort(diagnostics, ts.compareDiagnostics); + diagnostics = ts.toSorted(diagnostics, ts.compareDiagnostics); let outputLines = ""; // Count up all errors that were found in files other than lib.d.ts so we don't miss any let totalErrorsReportedInNonLibraryNonTsconfigFiles = 0; diff --git a/src/server/project.ts b/src/server/project.ts index 3bb22536214..8cf623a844f 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -112,7 +112,6 @@ import { returnTrue, ScriptKind, some, - sort, sortAndDeduplicate, SortedReadonlyArray, SourceFile, @@ -125,6 +124,7 @@ import { ThrottledCancellationToken, timestamp, toPath, + toSorted, tracing, TypeAcquisition, updateErrorForNoInputFiles, @@ -1085,7 +1085,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo } getExternalFiles(updateLevel?: ProgramUpdateLevel): SortedReadonlyArray { - return sort(flatMap(this.plugins, plugin => { + return toSorted(flatMap(this.plugins, plugin => { if (typeof plugin.module.getExternalFiles !== "function") return; try { return plugin.module.getExternalFiles(this, updateLevel || ProgramUpdateLevel.Update); @@ -1508,7 +1508,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo typeAcquisition, unresolvedImports, }; - const typingFiles = !typeAcquisition || !typeAcquisition.enable ? emptyArray : sort(newTypings); + const typingFiles = !typeAcquisition || !typeAcquisition.enable ? emptyArray : toSorted(newTypings); if (enumerateInsertsAndDeletes(typingFiles, this.typingFiles, getStringComparer(!this.useCaseSensitiveFileNames()), /*inserted*/ noop, removed => this.detachScriptInfoFromProject(removed))) { // If typing files changed, then only schedule project update this.typingFiles = typingFiles; diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index f622a0d55f3..aa5efcee158 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -137,9 +137,7 @@ import { single, skipAlias, some, - sort, SourceFile, - stableSort, startsWith, StringLiteral, stripQuotes, @@ -150,6 +148,7 @@ import { SyntaxKind, textChanges, toPath, + toSorted, tryCast, tryGetModuleSpecifierFromDeclaration, TypeChecker, @@ -1290,7 +1289,7 @@ function getFixInfos(context: CodeFixContextBase, errorCode: number, pos: number function sortFixInfo(fixes: readonly (FixInfo & { fix: ImportFixWithModuleSpecifier; })[], sourceFile: SourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost, preferences: UserPreferences): readonly (FixInfo & { fix: ImportFixWithModuleSpecifier; })[] { const _toPath = (fileName: string) => toPath(fileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)); - return sort(fixes, (a, b) => + return toSorted(fixes, (a, b) => compareBooleans(!!a.isJsxNamespaceFix, !!b.isJsxNamespaceFix) || compareValues(a.fix.kind, b.fix.kind) || compareModuleSpecifiers(a.fix, b.fix, sourceFile, program, preferences, packageJsonImportFilter.allowsImportingSpecifier, _toPath)); @@ -1822,7 +1821,7 @@ function doAddExistingFix( if (namedImports.length) { const { specifierComparer, isSorted } = OrganizeImports.getNamedImportSpecifierComparerWithDetection(clause.parent, preferences, sourceFile); - const newSpecifiers = stableSort( + const newSpecifiers = toSorted( namedImports.map(namedImport => factory.createImportSpecifier( (!clause.isTypeOnly || promoteFromTypeOnly) && shouldUseTypeOnly(namedImport, preferences), @@ -1842,7 +1841,7 @@ function doAddExistingFix( clause.namedBindings!, factory.updateNamedImports( clause.namedBindings as NamedImports, - stableSort([...existingSpecifiers!.filter(s => !removeExistingImportSpecifiers.has(s)), ...newSpecifiers], specifierComparer), + toSorted([...existingSpecifiers!.filter(s => !removeExistingImportSpecifiers.has(s)), ...newSpecifiers], specifierComparer), ), ); } diff --git a/src/services/completions.ts b/src/services/completions.ts index cd27ef3ba1e..1300005c8b5 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -352,7 +352,6 @@ import { SortedArray, SourceFile, SpreadAssignment, - stableSort, startsWith, stringToToken, stripQuotes, @@ -374,6 +373,7 @@ import { Token, TokenSyntaxKind, tokenToString, + toSorted, tryCast, tryGetImportFromModuleSpecifier, tryGetTextOfPropertyName, @@ -2459,7 +2459,7 @@ function createSnippetPrinter( }); const allChanges = escapes - ? stableSort(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span)) + ? toSorted(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span)) : changes; return textChanges.applyChanges(syntheticFile.text, allChanges); } @@ -2506,7 +2506,7 @@ function createSnippetPrinter( ); const allChanges = escapes - ? stableSort(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span)) + ? toSorted(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span)) : changes; return textChanges.applyChanges(syntheticFile.text, allChanges); } diff --git a/src/services/organizeImports.ts b/src/services/organizeImports.ts index 8841ee3f2cb..e7cc236947c 100644 --- a/src/services/organizeImports.ts +++ b/src/services/organizeImports.ts @@ -56,11 +56,10 @@ import { Scanner, setEmitFlags, some, - sort, SourceFile, - stableSort, SyntaxKind, textChanges, + toSorted, TransformFlags, tryCast, UserPreferences, @@ -159,7 +158,7 @@ export function organizeImports( ? group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier)!) : [oldImportDecls]; const sortedImportGroups = shouldSort - ? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiersWorker(group1[0].moduleSpecifier, group2[0].moduleSpecifier, comparer.moduleSpecifierComparer ?? defaultComparer)) + ? toSorted(oldImportGroups, (group1, group2) => compareModuleSpecifiersWorker(group1[0].moduleSpecifier, group2[0].moduleSpecifier, comparer.moduleSpecifierComparer ?? defaultComparer)) : oldImportGroups; const newImportDecls = flatMap(sortedImportGroups, importGroup => getExternalModuleName(importGroup[0].moduleSpecifier) || importGroup[0].moduleSpecifier === undefined @@ -199,7 +198,7 @@ export function organizeImports( const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => { if (shouldRemove) importGroup = removeUnusedImports(importGroup, sourceFile, program); if (shouldCombine) importGroup = coalesceImportsWorker(importGroup, detectedModuleCaseComparer, specifierComparer, sourceFile); - if (shouldSort) importGroup = stableSort(importGroup, (s1, s2) => compareImportsOrRequireStatements(s1, s2, detectedModuleCaseComparer)); + if (shouldSort) importGroup = toSorted(importGroup, (s1, s2) => compareImportsOrRequireStatements(s1, s2, detectedModuleCaseComparer)); return importGroup; }; @@ -428,7 +427,7 @@ function coalesceImportsWorker(importGroup: readonly ImportDeclaration[], compar const importGroupsByAttributes = groupBy(importGroup, decl => { if (decl.attributes) { let attrs = decl.attributes.token + " "; - for (const x of sort(decl.attributes.elements, (x, y) => compareStringsCaseSensitive(x.name.text, y.name.text))) { + for (const x of toSorted(decl.attributes.elements, (x, y) => compareStringsCaseSensitive(x.name.text, y.name.text))) { attrs += x.name.text + ":"; attrs += isStringLiteralLike(x.value) ? `"${x.value.text}"` : x.value.getText() + " "; } @@ -461,7 +460,7 @@ function coalesceImportsWorker(importGroup: readonly ImportDeclaration[], compar continue; } - const sortedNamespaceImports = stableSort(namespaceImports, (i1, i2) => comparer(i1.importClause.namedBindings.name.text, i2.importClause.namedBindings.name.text)); + const sortedNamespaceImports = toSorted(namespaceImports, (i1, i2) => comparer(i1.importClause.namedBindings.name.text, i2.importClause.namedBindings.name.text)); for (const namespaceImport of sortedNamespaceImports) { // Drop the name, if any @@ -493,7 +492,7 @@ function coalesceImportsWorker(importGroup: readonly ImportDeclaration[], compar newImportSpecifiers.push(...getNewImportSpecifiers(namedImports)); const sortedImportSpecifiers = factory.createNodeArray( - stableSort(newImportSpecifiers, specifierComparer), + toSorted(newImportSpecifiers, specifierComparer), firstNamedImport?.importClause.namedBindings.elements.hasTrailingComma, ); @@ -579,7 +578,7 @@ function coalesceExportsWorker(exportGroup: readonly ExportDeclaration[], specif const newExportSpecifiers: ExportSpecifier[] = []; newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray)); - const sortedExportSpecifiers = stableSort(newExportSpecifiers, specifierComparer); + const sortedExportSpecifiers = toSorted(newExportSpecifiers, specifierComparer); const exportDecl = exportGroup[0]; coalescedExports.push( diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 1fa19379afc..b02caba811b 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -154,7 +154,6 @@ import { skipTrivia, SourceFile, SourceFileLike, - stableSort, Statement, stringContainsAt, Symbol, @@ -164,6 +163,7 @@ import { textSpanEnd, Token, tokenToString, + toSorted, TransformationContext, TypeLiteralNode, TypeNode, @@ -1264,7 +1264,7 @@ namespace changesToText { const sourceFile = changesInFile[0].sourceFile; // order changes by start position // If the start position is the same, put the shorter range first, since an empty range (x, x) may precede (x, y) but not vice-versa. - const normalized = stableSort(changesInFile, (a, b) => (a.range.pos - b.range.pos) || (a.range.end - b.range.end)); + const normalized = toSorted(changesInFile, (a, b) => (a.range.pos - b.range.pos) || (a.range.end - b.range.end)); // verify that change intervals do not overlap, except possibly at end points. for (let i = 0; i < normalized.length - 1; i++) { Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos, "Changes overlap", () => `${JSON.stringify(normalized[i].range)} and ${JSON.stringify(normalized[i + 1].range)}`); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9c954d43189..55ab684196e 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -342,7 +342,6 @@ import { SourceFileLike, SourceMapper, SpreadElement, - stableSort, startsWith, StringLiteral, StringLiteralLike, @@ -371,6 +370,7 @@ import { Token, tokenToString, toPath, + toSorted, tryCast, tryParseJson, Type, @@ -2625,7 +2625,7 @@ export function insertImports(changes: textChanges.ChangeTracker, sourceFile: So const importKindPredicate: (node: Node) => node is AnyImportOrRequireStatement = decl.kind === SyntaxKind.VariableStatement ? isRequireVariableStatement : isAnyImportSyntax; const existingImportStatements = filter(sourceFile.statements, importKindPredicate); const { comparer, isSorted } = OrganizeImports.getOrganizeImportsStringComparerWithDetection(existingImportStatements, preferences); - const sortedNewImports = isArray(imports) ? stableSort(imports, (a, b) => OrganizeImports.compareImportsOrRequireStatements(a, b, comparer)) : [imports]; + const sortedNewImports = isArray(imports) ? toSorted(imports, (a, b) => OrganizeImports.compareImportsOrRequireStatements(a, b, comparer)) : [imports]; if (!existingImportStatements?.length) { if (isFullSourceFile(sourceFile)) { changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween);