Skip to content

Fix --showConfig to show transitively implied options that vary from the default config #60240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
addToSeen,
AlternateModeDiagnostics,
append,
arrayFrom,
Expand Down Expand Up @@ -2629,7 +2630,7 @@ export function convertToTSConfig(configParseResult: ParsedCommandLine, configFi
const providedKeys = new Set(optionMap.keys());
const impliedCompilerOptions: Record<string, CompilerOptionsValue> = {};
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) {
Expand All @@ -2641,6 +2642,18 @@ export function convertToTSConfig(configParseResult: ParsedCommandLine, configFi
return config;
}

function optionDependsOn(option: string, dependsOn: Set<string>): boolean {
const seen = new Set<string>();
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<string, CompilerOptionsValue>): object {
return Object.fromEntries(optionMap);
Expand Down
8 changes: 2 additions & 6 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8193,15 +8193,11 @@ export function getLastChild(node: Node): Node | undefined {
*
* @internal
*/
export function addToSeen<K>(seen: Map<K, true>, key: K): boolean;
/** @internal */
export function addToSeen<K, T>(seen: Map<K, T>, key: K, value: T): boolean;
/** @internal */
export function addToSeen<K, T>(seen: Map<K, T>, key: K, value: T = true as any): boolean {
export function addToSeen<K>(seen: Set<K>, key: K): boolean {
if (seen.has(key)) {
return false;
}
seen.set(key, value);
seen.add(key);
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,7 @@ export class ProjectService {
*/
private readonly filenameToScriptInfoVersion = new Map<Path, number>();
// Set of all '.js' files ever opened.
private readonly allJsFilesForOpenFileTelemetry = new Map<string, true>();
private readonly allJsFilesForOpenFileTelemetry = new Set<string>();

/**
* Map to the real path of the infos
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/convertConstToLet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ registerCodeFix({
},
getAllCodeActions: context => {
const { program } = context;
const seen = new Map<number, true>();
const seen = new Set<number>();

return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
eachDiagnostic(context, errorCodes, diag => {
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/convertToTypeOnlyExport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ registerCodeFix({
},
fixIds: [fixId],
getAllCodeActions: function getAllCodeActionsToConvertToTypeOnlyExport(context) {
const fixedExportDeclarations = new Map<number, true>();
const fixedExportDeclarations = new Set<number>();
return codeFixAll(context, errorCodes, (changes, diag) => {
const exportSpecifier = getExportSpecifierForDiagnosticSpan(diag, context.sourceFile);
if (exportSpecifier && addToSeen(fixedExportDeclarations, getNodeId(exportSpecifier.parent.parent))) {
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/fixAddMissingConstraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ registerCodeFix({
fixIds: [fixId],
getAllCodeActions: context => {
const { program, preferences, host } = context;
const seen = new Map<number, true>();
const seen = new Set<number>();

return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
eachDiagnostic(context, errorCodes, diag => {
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/fixAddMissingMember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ registerCodeFix({
getAllCodeActions: context => {
const { program, fixId } = context;
const checker = program.getTypeChecker();
const seen = new Map<string, true>();
const seen = new Set<string>();
const typeDeclToMembers = new Map<ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode, TypeLikeDeclarationInfo[]>();

return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/fixAwaitInSyncFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ registerCodeFix({
},
fixIds: [fixId],
getAllCodeActions: function getAllCodeActionsToFixAwaitInSyncFunction(context) {
const seen = new Map<number, true>();
const seen = new Set<number>();
return codeFixAll(context, errorCodes, (changes, diag) => {
const nodes = getNodes(diag.file, diag.start);
if (!nodes || !addToSeen(seen, getNodeId(nodes.insertBefore))) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ registerCodeFix({
},
fixIds: [fixId],
getAllCodeActions: function getAllCodeActionsToFixClassDoesntImplementInheritedAbstractMember(context) {
const seenClassDeclarations = new Map<number, true>();
const seenClassDeclarations = new Set<number>();
return codeFixAll(context, errorCodes, (changes, diag) => {
const classDeclaration = getClass(diag.file, diag.start);
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ registerCodeFix({
},
fixIds: [fixId],
getAllCodeActions(context) {
const seenClassDeclarations = new Map<number, true>();
const seenClassDeclarations = new Set<number>();
return codeFixAll(context, errorCodes, (changes, diag) => {
const classDeclaration = getClass(diag.file, diag.start);
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ registerCodeFix({
fixIds: [fixId],
getAllCodeActions(context) {
const { sourceFile } = context;
const seenClasses = new Map<number, true>(); // Ensure we only do this once per class.
const seenClasses = new Set<number>(); // Ensure we only do this once per class.
return codeFixAll(context, errorCodes, (changes, diag) => {
const nodes = getNodes(diag.file, diag.start);
if (!nodes) return;
Expand Down
6 changes: 3 additions & 3 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3585,7 +3585,7 @@ function getCompletionData(
let importSpecifierResolver: codefix.ImportSpecifierResolver | undefined;
const symbolToOriginInfoMap: SymbolOriginInfoMap = [];
const symbolToSortTextMap: SymbolSortTextMap = [];
const seenPropertySymbols = new Map<SymbolId, true>();
const seenPropertySymbols = new Set<SymbolId>();
const isTypeOnlyLocation = isTypeOnlyCompletion();
const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => {
return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host);
Expand Down Expand Up @@ -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<SymbolId, true>()): boolean {
function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Set<Symbol>()): 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));
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/services/exportInfoMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -634,7 +634,7 @@ function getNamesForExportedSymbol(defaultExport: Symbol, checker: TypeChecker,
export function forEachNameOfDefaultExport<T>(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<Symbol, true>();
const seen = new Set<Symbol>();

while (current) {
// The predecessor to this function also looked for a name on the `localSymbol`
Expand Down
7 changes: 3 additions & 4 deletions src/services/findAllReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ import {
SymbolDisplayPart,
SymbolDisplayPartKind,
SymbolFlags,
SymbolId,
symbolName,
SyntaxKind,
textPart,
Expand Down Expand Up @@ -544,7 +543,7 @@ export function getImplementationsAtPosition(program: Program, cancellationToken
}
else if (entries) {
const queue = createQueue(entries);
const seenNodes = new Map<number, true>();
const seenNodes = new Set<number>();
while (!queue.isEmpty()) {
const entry = queue.dequeue() as NodeEntry;
if (!addToSeen(seenNodes, getNodeId(entry.node))) {
Expand Down Expand Up @@ -2666,15 +2665,15 @@ export namespace Core {
* The value of previousIterationSymbol is undefined when the function is first called.
*/
function getPropertySymbolsFromBaseTypes<T>(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined {
const seen = new Map<SymbolId, true>();
const seen = new Set<Symbol>();
return recur(symbol);

function recur(symbol: Symbol): T | undefined {
// Use `addToSeen` to ensure we don't infinitely recurse in this situation:
// 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 => {
Expand Down
2 changes: 1 addition & 1 deletion src/services/refactors/extractType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ function flattenTypeLiteralNodeReference(checker: TypeChecker, selection: TypeNo
}
if (isIntersectionTypeNode(selection)) {
const result: TypeElement[] = [];
const seen = new Map<string, true>();
const seen = new Set<string>();
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))) {
Expand Down
4 changes: 2 additions & 2 deletions src/services/stringCompletions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, true>();
const uniques = new Set<string>();
const editingArgument = isJsxOpeningLikeElement(call) ? Debug.checkDefined(findAncestor(arg.parent, isJsxAttribute)) : arg;
const candidates = checker.getCandidateSignaturesForStringLiteralCompletions(call, editingArgument);
const types = flatMap(candidates, candidate => {
Expand Down Expand Up @@ -600,7 +600,7 @@ function stringLiteralCompletionsForObjectLiteral(checker: TypeChecker, objectLi
};
}

function getStringLiteralTypes(type: Type | undefined, uniques = new Map<string, true>()): readonly StringLiteralType[] {
function getStringLiteralTypes(type: Type | undefined, uniques = new Set<string>()): readonly StringLiteralType[] {
if (!type) return emptyArray;
type = skipConstraint(type);
return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) :
Expand Down
10 changes: 6 additions & 4 deletions src/services/textChanges.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
addToSeen,
ArrowFunction,
BindingElement,
CharacterCodes,
Expand Down Expand Up @@ -493,7 +492,7 @@ export function isThisTypeAnnotatable(containingFunction: SignatureDeclaration):
export class ChangeTracker {
private readonly changes: Change[] = [];
private newFileChanges?: MultiMap<string, NewFileInsertion>;
private readonly classesWithNodesInsertedAtStart = new Map<number, { readonly node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression; readonly sourceFile: SourceFile; }>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly classesWithNodesInsertedAtStart = new Map<number, { readonly node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression | TypeLiteralNode | EnumDeclaration; readonly sourceFile: SourceFile; }>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly deletedNodes: { readonly sourceFile: SourceFile; readonly node: Node | NodeArray<TypeParameterDeclaration>; }[] = [];

public static fromContext(context: TextChangesContext): ChangeTracker {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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];
Expand Down
2 changes: 2 additions & 0 deletions src/testRunner/unittests/config/showConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "nodenext",
"target": "esnext",
"moduleResolution": "nodenext",
"moduleDetection": "force",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"useDefineForClassFields": true
}
}