microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.17k stars 12.38k forks source link

Potentially wrong 'showConfig' results for default values, as it's used in the checker differently. #59442

Open chwoerz opened 1 month ago

chwoerz commented 1 month ago

πŸ”Ž Search Terms

"showConfig result moduleResolution wrong"

πŸ•— Version & Regression Information

⏯ Playground Link

No response

πŸ’» Code

tsconfig:

{
  "compilerOptions": {
  }
}

cli call: tsc --showConfig

πŸ™ Actual behavior

{    
  "compilerOptions": {}
}

πŸ™‚ Expected behavior

Wanted Output from tsc --showConfig

{
  "compilerOptions": {
    "moduleResolution": "Node10",
    // And All other fields which have default values
  }
}

Explanation

When we check utilities.ts: 8859 computedOptions we can see, that this moduleResolution has the following computeValue functionality and that its not the default value because it depends on the computed module value which is, if not set, commonjs.

{
dependencies: ["module", "target"],
  computeValue: (compilerOptions): ModuleResolutionKind => {
  let moduleResolution = compilerOptions.moduleResolution;
  if (moduleResolution === undefined) {
    switch (computedOptions.module.computeValue(compilerOptions)) {
      case ModuleKind.CommonJS:
        moduleResolution = ModuleResolutionKind.Node10;
        break;
      case ModuleKind.Node16:
        moduleResolution = ModuleResolutionKind.Node16;
        break;
      case ModuleKind.NodeNext:
        moduleResolution = ModuleResolutionKind.NodeNext;
        break;
      case ModuleKind.Preserve:
        moduleResolution = ModuleResolutionKind.Bundler;
        break;
      default:
        moduleResolution = ModuleResolutionKind.Classic;
        break;
    }
  }
  return moduleResolution;
},
    }

And in the compiler, it's used like this:

export const getEmitModuleResolutionKind = computedOptions.moduleResolution.computeValue;
// Usage:
getEmitModuleResolutionKind(compilerOptions)

So there is no checking for if the dependencies are used. So this means that the default value will be used even if there is no dependency specified in the tsconfig.

But in the showConfig code (commandLineParser.ts: 2565) we can see, that it checks if some of it's dependencies are there:

if (!providedKeys.has(option) && some(computedOptions[option as keyof typeof computedOptions].dependencies, dep => providedKeys.has(dep))) {

Suggestion

So I think the showConfig does not show the "real" values which are used during the execution of the code and I would suggest to change that to show all the properties if they have a value which is other than undefined.

RyanCavanaugh commented 1 month ago

There are a few different stages of computation here:

  1. Read the primary tsconfig
  2. Read all the extended tsconfigs
  3. Merge their config options
  4. Apply default values
  5. Apply options in the final user-specified config
  6. Compute dependent options

--showConfig shows you the computation at step 3, not step 4 or step 6. The intended use is figuring out what the "effective" tsconfig file you have is specifically in the case of figuring out what your extended tsconfigs are doing

chwoerz commented 1 month ago

Thanks for your response. But it does more. If you specify for example strict, then it will compute values because of this. So it should also show the moduleResolution if it's not the default even when no dependencies are set because dependencies could also be automatically computed.

RyanCavanaugh commented 1 month ago

I think noImplicitAny etc showing up is the bug here, not the other way around. If showConfig showed every default option it'd be way too big to comfortably read.

jakebailey commented 1 month ago

https://github.com/microsoft/TypeScript/pull/56701 is what changed showConfig to start showing some implied options; the code shown in the issue above is from that PR, so the fact that it's not working sounds like the bug to me, not that we're showing implied values.

RyanCavanaugh commented 1 month ago

@andrewbranch gets to drive this one to resolution then πŸ˜…

chwoerz commented 1 month ago

I don't know if it's a bug but I would want to see the whole config when I use showConfig and not only the properties which will be generated from my inputs but also the ones from the result.

So for example when I only set the target to nodenext I would want to see module to be set to nodenext too and moduleResolution to nodenext because of the computed module value.

andrewbranch commented 1 month ago

This is working as I intended it to work... Implied and computed options are shown only if they’re computed to be something other than the default. So "strict": true expands out into everything else it implies, but an empty config doesn’t show you all the strict flags set to false. Showing every default would make the output of every --showConfig 102 lines.

So for example when I only set the target to nodenext I would want to see module to be set to nodenext too and moduleResolution to nodenext because of the computed module value.

nodenext is not a value for target, but I feel like it’s already doing what you want?

~/Developer/microsoft/eg/ts                                                                           1m 28s
❯ cat tsconfig.json
{
  "compilerOptions": {
    "module": "NodeNext",
  }
}

~/Developer/microsoft/eg/ts
❯ tsc --version
Version 5.4.5

~/Developer/microsoft/eg/ts
❯ tsc --showConfig -p tsconfig.json
{
    "compilerOptions": {
        "module": "nodenext",
        "target": "esnext",
        "moduleResolution": "nodenext",
        "moduleDetection": "force",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "useDefineForClassFields": true
    },
    "files": []
}
chwoerz commented 1 month ago

You are right. Somehow I misread the code. I will create a small cli for myself to show me all the value which are at default.

chwoerz commented 1 month ago

I still have found an issue. When i only set

{
  "compilerOptions": {
    "module": "NodeNext"
  }
}

Then I would think that it should create:

{
 "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "moduleDetection": "force",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "resolvePackageJsonExports": true,
    "resolvePackageJsonImports": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  }
}

So resolveXy should also be shown as true.

But it only shows:


{ 
  "compilerOptions": {
        "module": "nodenext",
        "target": "esnext",
        "moduleResolution": "nodenext",
        "moduleDetection": "force",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "useDefineForClassFields": true
    }
}

But moduleResolution is `nodenext`. But this does not happen. So maybe we need to add the "module" dependency too.