From b8e4ed8aeb0b228f544c5736908c31f136a9f7e3 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 16 Oct 2024 11:14:44 -0700 Subject: [PATCH] Fix `--showConfig` to show transitively implied options that vary from the default config (#60240) --- src/compiler/commandLineParser.ts | 15 ++++++++++++++- src/compiler/utilities.ts | 8 ++------ src/server/editorServices.ts | 2 +- src/services/codefixes/convertConstToLet.ts | 2 +- src/services/codefixes/convertToTypeOnlyExport.ts | 2 +- src/services/codefixes/fixAddMissingConstraint.ts | 2 +- src/services/codefixes/fixAddMissingMember.ts | 2 +- src/services/codefixes/fixAwaitInSyncFunction.ts | 2 +- ...ClassDoesntImplementInheritedAbstractMember.ts | 2 +- .../fixClassIncorrectlyImplementsInterface.ts | 2 +- .../fixClassSuperMustPrecedeThisAccess.ts | 2 +- src/services/completions.ts | 6 +++--- src/services/exportInfoMap.ts | 4 ++-- src/services/findAllReferences.ts | 7 +++---- src/services/refactors/extractType.ts | 2 +- src/services/stringCompletions.ts | 4 ++-- src/services/textChanges.ts | 10 ++++++---- src/testRunner/unittests/config/showConfig.ts | 2 ++ .../tsconfig.json | 13 +++++++++++++ 19 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 tests/baselines/reference/config/showConfig/Show TSConfig with transitively implied options/tsconfig.json diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 6bb5e981e04..b3c10e3c849 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1,4 +1,5 @@ import { + addToSeen, AlternateModeDiagnostics, append, arrayFrom, @@ -2629,7 +2630,7 @@ export function convertToTSConfig(configParseResult: ParsedCommandLine, configFi const providedKeys = new Set(optionMap.keys()); const impliedCompilerOptions: Record = {}; for (const option in computedOptions) { - if (!providedKeys.has(option) && some(computedOptions[option].dependencies, dep => providedKeys.has(dep))) { + if (!providedKeys.has(option) && optionDependsOn(option, providedKeys)) { const implied = computedOptions[option].computeValue(configParseResult.options); const defaultValue = computedOptions[option].computeValue({}); if (implied !== defaultValue) { @@ -2641,6 +2642,18 @@ export function convertToTSConfig(configParseResult: ParsedCommandLine, configFi return config; } +function optionDependsOn(option: string, dependsOn: Set): boolean { + const seen = new Set(); + return optionDependsOnRecursive(option); + + function optionDependsOnRecursive(option: string): boolean { + if (addToSeen(seen, option)) { + return some(computedOptions[option]?.dependencies, dep => dependsOn.has(dep) || optionDependsOnRecursive(dep)); + } + return false; + } +} + /** @internal */ export function optionMapToObject(optionMap: Map): object { return Object.fromEntries(optionMap); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fc55fb14117..64be5c4f7eb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -8193,15 +8193,11 @@ export function getLastChild(node: Node): Node | undefined { * * @internal */ -export function addToSeen(seen: Map, key: K): boolean; -/** @internal */ -export function addToSeen(seen: Map, key: K, value: T): boolean; -/** @internal */ -export function addToSeen(seen: Map, key: K, value: T = true as any): boolean { +export function addToSeen(seen: Set, key: K): boolean { if (seen.has(key)) { return false; } - seen.set(key, value); + seen.add(key); return true; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index ae25654acaf..23f8d844c98 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1297,7 +1297,7 @@ export class ProjectService { */ private readonly filenameToScriptInfoVersion = new Map(); // Set of all '.js' files ever opened. - private readonly allJsFilesForOpenFileTelemetry = new Map(); + private readonly allJsFilesForOpenFileTelemetry = new Set(); /** * Map to the real path of the infos diff --git a/src/services/codefixes/convertConstToLet.ts b/src/services/codefixes/convertConstToLet.ts index 8497cb2ee80..9337c0819e2 100644 --- a/src/services/codefixes/convertConstToLet.ts +++ b/src/services/codefixes/convertConstToLet.ts @@ -36,7 +36,7 @@ registerCodeFix({ }, getAllCodeActions: context => { const { program } = context; - const seen = new Map(); + const seen = new Set(); return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { eachDiagnostic(context, errorCodes, diag => { diff --git a/src/services/codefixes/convertToTypeOnlyExport.ts b/src/services/codefixes/convertToTypeOnlyExport.ts index 5f2e80f4c84..c5f0d6b3d92 100644 --- a/src/services/codefixes/convertToTypeOnlyExport.ts +++ b/src/services/codefixes/convertToTypeOnlyExport.ts @@ -36,7 +36,7 @@ registerCodeFix({ }, fixIds: [fixId], getAllCodeActions: function getAllCodeActionsToConvertToTypeOnlyExport(context) { - const fixedExportDeclarations = new Map(); + const fixedExportDeclarations = new Set(); return codeFixAll(context, errorCodes, (changes, diag) => { const exportSpecifier = getExportSpecifierForDiagnosticSpan(diag, context.sourceFile); if (exportSpecifier && addToSeen(fixedExportDeclarations, getNodeId(exportSpecifier.parent.parent))) { diff --git a/src/services/codefixes/fixAddMissingConstraint.ts b/src/services/codefixes/fixAddMissingConstraint.ts index ba1e7aa0279..34c0bf9ab2b 100644 --- a/src/services/codefixes/fixAddMissingConstraint.ts +++ b/src/services/codefixes/fixAddMissingConstraint.ts @@ -63,7 +63,7 @@ registerCodeFix({ fixIds: [fixId], getAllCodeActions: context => { const { program, preferences, host } = context; - const seen = new Map(); + const seen = new Set(); return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { eachDiagnostic(context, errorCodes, diag => { diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 87f931a62b1..f448482e4ed 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -182,7 +182,7 @@ registerCodeFix({ getAllCodeActions: context => { const { program, fixId } = context; const checker = program.getTypeChecker(); - const seen = new Map(); + const seen = new Set(); const typeDeclToMembers = new Map(); return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { diff --git a/src/services/codefixes/fixAwaitInSyncFunction.ts b/src/services/codefixes/fixAwaitInSyncFunction.ts index d6b5d766caf..ffcbeaed776 100644 --- a/src/services/codefixes/fixAwaitInSyncFunction.ts +++ b/src/services/codefixes/fixAwaitInSyncFunction.ts @@ -44,7 +44,7 @@ registerCodeFix({ }, fixIds: [fixId], getAllCodeActions: function getAllCodeActionsToFixAwaitInSyncFunction(context) { - const seen = new Map(); + const seen = new Set(); return codeFixAll(context, errorCodes, (changes, diag) => { const nodes = getNodes(diag.file, diag.start); if (!nodes || !addToSeen(seen, getNodeId(nodes.insertBefore))) return; diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index 8f77976267f..5ebecd78f1c 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -44,7 +44,7 @@ registerCodeFix({ }, fixIds: [fixId], getAllCodeActions: function getAllCodeActionsToFixClassDoesntImplementInheritedAbstractMember(context) { - const seenClassDeclarations = new Map(); + const seenClassDeclarations = new Set(); return codeFixAll(context, errorCodes, (changes, diag) => { const classDeclaration = getClass(diag.file, diag.start); if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) { diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index a0dd5aa5e58..49298f90580 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -55,7 +55,7 @@ registerCodeFix({ }, fixIds: [fixId], getAllCodeActions(context) { - const seenClassDeclarations = new Map(); + const seenClassDeclarations = new Set(); return codeFixAll(context, errorCodes, (changes, diag) => { const classDeclaration = getClass(diag.file, diag.start); if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) { diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index a3585ace24d..cf54c1c3d47 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -38,7 +38,7 @@ registerCodeFix({ fixIds: [fixId], getAllCodeActions(context) { const { sourceFile } = context; - const seenClasses = new Map(); // Ensure we only do this once per class. + const seenClasses = new Set(); // Ensure we only do this once per class. return codeFixAll(context, errorCodes, (changes, diag) => { const nodes = getNodes(diag.file, diag.start); if (!nodes) return; diff --git a/src/services/completions.ts b/src/services/completions.ts index 3291902986f..30e9ad40bf3 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -3585,7 +3585,7 @@ function getCompletionData( let importSpecifierResolver: codefix.ImportSpecifierResolver | undefined; const symbolToOriginInfoMap: SymbolOriginInfoMap = []; const symbolToSortTextMap: SymbolSortTextMap = []; - const seenPropertySymbols = new Map(); + const seenPropertySymbols = new Set(); const isTypeOnlyLocation = isTypeOnlyCompletion(); const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host); @@ -6051,14 +6051,14 @@ function isArrowFunctionBody(node: Node) { } /** True if symbol is a type or a module containing at least one type. */ -function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Map()): boolean { +function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Set()): boolean { // Since an alias can be merged with a local declaration, we need to test both the alias and its target. // This code used to just test the result of `skipAlias`, but that would ignore any locally introduced meanings. return nonAliasCanBeReferencedAtTypeLocation(symbol) || nonAliasCanBeReferencedAtTypeLocation(skipAlias(symbol.exportSymbol || symbol, checker)); function nonAliasCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { return !!(symbol.flags & SymbolFlags.Type) || checker.isUnknownSymbol(symbol) || - !!(symbol.flags & SymbolFlags.Module) && addToSeen(seenModules, getSymbolId(symbol)) && + !!(symbol.flags & SymbolFlags.Module) && addToSeen(seenModules, symbol) && checker.getExportsOfModule(symbol).some(e => symbolCanBeReferencedAtTypeLocation(e, checker, seenModules)); } } diff --git a/src/services/exportInfoMap.ts b/src/services/exportInfoMap.ts index acfb3765c7c..f1f88c6ad42 100644 --- a/src/services/exportInfoMap.ts +++ b/src/services/exportInfoMap.ts @@ -555,7 +555,7 @@ export function getExportInfoMap(importingFile: SourceFile | FutureSourceFile, h try { forEachExternalModuleToImportFrom(program, host, preferences, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => { if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested(); - const seenExports = new Map<__String, true>(); + const seenExports = new Set<__String>(); const checker = program.getTypeChecker(); const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker); // Note: I think we shouldn't actually see resolved module symbols here, but weird merges @@ -634,7 +634,7 @@ function getNamesForExportedSymbol(defaultExport: Symbol, checker: TypeChecker, export function forEachNameOfDefaultExport(defaultExport: Symbol, checker: TypeChecker, scriptTarget: ScriptTarget | undefined, cb: (name: string, capitalizedName?: string) => T | undefined): T | undefined { let chain: Symbol[] | undefined; let current: Symbol | undefined = defaultExport; - const seen = new Map(); + const seen = new Set(); while (current) { // The predecessor to this function also looked for a name on the `localSymbol` diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 74aeb9c5be0..710a01b1c2d 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -251,7 +251,6 @@ import { SymbolDisplayPart, SymbolDisplayPartKind, SymbolFlags, - SymbolId, symbolName, SyntaxKind, textPart, @@ -544,7 +543,7 @@ export function getImplementationsAtPosition(program: Program, cancellationToken } else if (entries) { const queue = createQueue(entries); - const seenNodes = new Map(); + const seenNodes = new Set(); while (!queue.isEmpty()) { const entry = queue.dequeue() as NodeEntry; if (!addToSeen(seenNodes, getNodeId(entry.node))) { @@ -2666,7 +2665,7 @@ export namespace Core { * The value of previousIterationSymbol is undefined when the function is first called. */ function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined { - const seen = new Map(); + const seen = new Set(); return recur(symbol); function recur(symbol: Symbol): T | undefined { @@ -2674,7 +2673,7 @@ export namespace Core { // interface C extends C { // /*findRef*/propName: string; // } - if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, getSymbolId(symbol))) return; + if (!(symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) || !addToSeen(seen, symbol)) return; return firstDefined(symbol.declarations, declaration => firstDefined(getAllSuperTypeNodes(declaration), typeReference => { diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index b6f4c97b55a..5201313f972 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -247,7 +247,7 @@ function flattenTypeLiteralNodeReference(checker: TypeChecker, selection: TypeNo } if (isIntersectionTypeNode(selection)) { const result: TypeElement[] = []; - const seen = new Map(); + const seen = new Set(); for (const type of selection.types) { const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type); if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) { diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 7c41dd5e103..b5cf950c337 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -555,7 +555,7 @@ function getAlreadyUsedTypesInStringLiteralUnion(union: UnionTypeNode, current: function getStringLiteralCompletionsFromSignature(call: CallLikeExpression, arg: StringLiteralLike, argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes | undefined { let isNewIdentifier = false; - const uniques = new Map(); + const uniques = new Set(); const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg; const candidates = checker.getCandidateSignaturesForStringLiteralCompletions(call, editingArgument); const types = flatMap(candidates, candidate => { @@ -600,7 +600,7 @@ function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLi }; } -function getStringLiteralTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralType[] { +function getStringLiteralTypes(type: Type | undefined, uniques = new Set()): readonly StringLiteralType[] { if (!type) return emptyArray; type = skipConstraint(type); return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index e73da8490b6..196f5389555 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -1,5 +1,4 @@ import { - addToSeen, ArrowFunction, BindingElement, CharacterCodes, @@ -493,7 +492,7 @@ export function isThisTypeAnnotatable(containingFunction: SignatureDeclaration): export class ChangeTracker { private readonly changes: Change[] = []; private newFileChanges?: MultiMap; - private readonly classesWithNodesInsertedAtStart = new Map(); // Set implemented as Map + private readonly classesWithNodesInsertedAtStart = new Map(); // Set implemented as Map private readonly deletedNodes: { readonly sourceFile: SourceFile; readonly node: Node | NodeArray; }[] = []; public static fromContext(context: TextChangesContext): ChangeTracker { @@ -903,7 +902,10 @@ export class ChangeTracker { const members = getMembersOrProperties(node); const isEmpty = members.length === 0; - const isFirstInsertion = addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(node), { node, sourceFile }); + const isFirstInsertion = !this.classesWithNodesInsertedAtStart.has(getNodeId(node)); + if (isFirstInsertion) { + this.classesWithNodesInsertedAtStart.set(getNodeId(node), { node, sourceFile }); + } const insertTrailingComma = isObjectLiteralExpression(node) && (!isJsonSourceFile(sourceFile) || !isEmpty); const insertLeadingComma = isObjectLiteralExpression(node) && isJsonSourceFile(sourceFile) && isEmpty && !isFirstInsertion; return { @@ -1246,7 +1248,7 @@ function endPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node, prevN return end; } -function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number | undefined, number | undefined] { +function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode | EnumDeclaration, sourceFile: SourceFile): [number | undefined, number | undefined] { const open = findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile); const close = findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile); return [open?.end, close?.end]; diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index 350b18c06ed..9000ce035cb 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -57,6 +57,8 @@ describe("unittests:: config:: showConfig", () => { showTSConfigCorrectly("Show TSConfig with advanced options", ["--showConfig", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"]); + showTSConfigCorrectly("Show TSConfig with transitively implied options", ["--showConfig", "--module", "nodenext"]); + showTSConfigCorrectly("Show TSConfig with compileOnSave and more", ["-p", "tsconfig.json"], { compilerOptions: { esModuleInterop: true, diff --git a/tests/baselines/reference/config/showConfig/Show TSConfig with transitively implied options/tsconfig.json b/tests/baselines/reference/config/showConfig/Show TSConfig with transitively implied options/tsconfig.json new file mode 100644 index 00000000000..3afdb1cd627 --- /dev/null +++ b/tests/baselines/reference/config/showConfig/Show TSConfig with transitively implied options/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "nodenext", + "target": "esnext", + "moduleResolution": "nodenext", + "moduleDetection": "force", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolvePackageJsonExports": true, + "resolvePackageJsonImports": true, + "useDefineForClassFields": true + } +}