Skip to content
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

Proposal: Types as Configuration #58025

Closed
wants to merge 6 commits into from
Closed
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
173 changes: 130 additions & 43 deletions src/compiler/commandLineParser.ts

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
EqualityComparer,
MapLike,
Queue,
RawTSConfig,
SortedArray,
SortedReadonlyArray,
TextSpan,
Expand Down Expand Up @@ -2716,3 +2717,8 @@ export function isNodeLikeSystem(): boolean {
&& !(process as any).browser
&& typeof require !== "undefined";
}

export function config<const T extends RawTSConfig>(config: T): T {
Debug.fail("This function does nothing at runtime.");
return config;
}
2 changes: 1 addition & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ export function getLibraryNameFromLibFileName(libFileName: string) {

function getLibFileNameFromLibReference(libReference: FileReference) {
const libName = toFileNameLowerCase(libReference.fileName);
const libFileName = libMap.get(libName);
const libFileName = libMap.get(libName as Parameters<(typeof libMap)["get"]>[0]);
return { libName, libFileName };
}

Expand Down
3 changes: 2 additions & 1 deletion src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
closeFileWatcher,
closeFileWatcherOf,
combinePaths,
CommandLineOption,
commonOptionsWithBuild,
CompilerHost,
CompilerOptions,
Expand Down Expand Up @@ -324,7 +325,7 @@ export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = Em

function getCompilerOptionsOfBuildOptions(buildOptions: BuildOptions): CompilerOptions {
const result = {} as CompilerOptions;
commonOptionsWithBuild.forEach(option => {
commonOptionsWithBuild.forEach((option: CommandLineOption) => {
if (hasProperty(buildOptions, option.name)) result[option.name] = buildOptions[option.name];
});
return result;
Expand Down
35 changes: 35 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ModuleResolutionCache,
MultiMap,
NodeFactoryFlags,
optionDeclarations,
OptionsNameMap,
PackageJsonInfo,
PackageJsonInfoCache,
Expand Down Expand Up @@ -9913,6 +9914,40 @@ export interface PragmaMap extends Map<string, PragmaPseudoMap[keyof PragmaPseud
forEach(action: <TKey extends keyof PragmaPseudoMap>(value: PragmaPseudoMap[TKey] | PragmaPseudoMap[TKey][], key: TKey, map: PragmaMap) => void): void;
}

type IntoCompilerOptionsValue<T extends CommandLineOption> = T["type"] extends "string" ? string
: T["type"] extends "number" ? number
: T["type"] extends "boolean" ? boolean
: T["type"] extends "object" ? Record<string, any>
: T["type"] extends "list" ? IntoCompilerOptionsValue<Extract<T, CommandLineOptionOfListType>["element"]>[]
: T["type"] extends "listOrElement" ? IntoCompilerOptionsValue<Extract<T, CommandLineOptionOfListType>["element"]>[] | IntoCompilerOptionsValue<Extract<T, CommandLineOptionOfListType>["element"]>
: T["type"] extends Map<infer InputKeyTypes, infer _EnumType> ? InputKeyTypes
: never;

type IntoCompilerOptionsNameValuePair<T extends CommandLineOption> = {
[K in T["name"]]?: IntoCompilerOptionsValue<T>;
};

type IntoCompilerOptionsNameValuePairs<T extends CommandLineOption> = T extends T ? IntoCompilerOptionsNameValuePair<T> : never;

type IntoCompilerOptionsDefinitionWorker<T extends CommandLineOption[]> = UnionToIntersection<IntoCompilerOptionsNameValuePairs<T[number]>>;

type IntoCompilerOptionsDefinition<T extends CommandLineOption[]> = IntoCompilerOptionsDefinitionWorker<T> extends infer U ? { [K in keyof U]: U[K]; } : never;

export const _optionsType = [undefined! as IntoCompilerOptionsDefinition<typeof optionDeclarations> | undefined][0];

/**
* An unprocessed TSConfig object, suitable to read as JSON and transform into command line options
*/
export interface RawTSConfig {
extends?: string | string[];
compilerOptions?: NonNullable<typeof _optionsType>;
references?: { path: string; }[];
files?: string[];
include?: string[];
exclude?: string[];
compileOnSave?: boolean;
}

/** @internal */
export interface CommentDirectivesMap {
getUnusedExpectations(): CommentDirective[];
Expand Down
2 changes: 1 addition & 1 deletion src/executeCommandLine/executeCommandLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ 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)) :
filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView);
filter(optionDeclarations.slice(), (v: CommandLineOption) => !!v.showInSimplifiedHelpView);
}

function printVersion(sys: System) {
Expand Down
24 changes: 17 additions & 7 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import {
convertCompilerOptionsForTelemetry,
convertJsonOption,
createCachedDirectoryStructureHost,
createDetachedDiagnostic,
createDocumentRegistryInternal,
createGetCanonicalFileName,
createMultiMap,
Debug,
Diagnostic,
Diagnostics,
directorySeparator,
DirectoryStructureHost,
DirectoryWatcherCallback,
Expand Down Expand Up @@ -59,6 +61,7 @@ import {
getSnapshotText,
getWatchFactory,
hasExtension,
hasJSFileExtension,
hasProperty,
hasTSFileExtension,
HostCancellationToken,
Expand All @@ -76,6 +79,7 @@ import {
JSDocParsingMode,
LanguageServiceMode,
length,
loadConfigFromDefaultType,
map,
mapDefinedEntries,
mapDefinedIterator,
Expand Down Expand Up @@ -2243,13 +2247,13 @@ export class ProjectService {
do {
if (searchInDirectory) {
const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName);
const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json"));
let result = action(combinePaths(canonicalSearchPath, "tsconfig.json") as NormalizedPath, tsconfigFileName);
if (result) return tsconfigFileName;

const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json"));
result = action(combinePaths(canonicalSearchPath, "jsconfig.json") as NormalizedPath, jsconfigFileName);
if (result) return jsconfigFileName;
const defaultConfigFileNames = ["tsconfig.d.ts", "tsconfig.ts", "tsconfig.js", "tsconfig.json", "jsconfig.json"];
for (const fileName of defaultConfigFileNames) {
const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, fileName));
const result = action(combinePaths(canonicalSearchPath, fileName) as NormalizedPath, tsconfigFileName);
if (result) return tsconfigFileName;
}

// If we started within node_modules, don't look outside node_modules.
// Otherwise, we might pick up a very large project and pull in the world,
Expand Down Expand Up @@ -2576,8 +2580,14 @@ export class ProjectService {
const cachedDirectoryStructureHost = configFileExistenceInfo.config?.cachedDirectoryStructureHost ||
createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames)!;

let compilerHost: true | undefined;
if (hasTSFileExtension(configFilename) || hasJSFileExtension(configFilename)) {
compilerHost = true;
}

// Read updated contents from disk
const configFileContent = tryReadFile(configFilename, fileName => this.host.readFile(fileName));
const processedResult = compilerHost && loadConfigFromDefaultType(configFilename);
const configFileContent = compilerHost ? length(processedResult?.errors) ? processedResult!.errors[0] : processedResult ? processedResult.configText : createDetachedDiagnostic(configFilename, "", 0, 1, Diagnostics._0_expected, "typescript") : tryReadFile(configFilename, fileName => this.host.readFile(fileName));
const configFile = parseJsonText(configFilename, isString(configFileContent) ? configFileContent : "") as TsConfigSourceFile;
const configFileErrors = configFile.parseDiagnostics as Diagnostic[];
if (!isString(configFileContent)) configFileErrors.push(configFileContent);
Expand Down
3 changes: 2 additions & 1 deletion src/services/transpile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
addRange,
cloneCompilerOptions,
CommandLineOption,
CommandLineOptionOfCustomType,
CompilerHost,
CompilerOptions,
Expand Down Expand Up @@ -169,7 +170,7 @@ let commandLineOptionsStringToEnum: CommandLineOptionOfCustomType[];
export function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions {
// Lazily create this value to fix module loading errors.
commandLineOptionsStringToEnum = commandLineOptionsStringToEnum ||
filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")) as CommandLineOptionOfCustomType[];
filter(optionDeclarations, (o: CommandLineOption) => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")) as CommandLineOptionOfCustomType[];

options = cloneCompilerOptions(options);

Expand Down
4 changes: 2 additions & 2 deletions src/testRunner/projectsRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,13 +472,13 @@ function createCompilerOptions(testCase: ProjectRunnerTestCase & ts.CompilerOpti
const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name);
for (const name in testCase) {
if (name !== "mapRoot" && name !== "sourceRoot") {
const option = optionNameMap.get(name);
const option = optionNameMap.get(name as Parameters<(typeof optionNameMap)["get"]>[0]);
if (option) {
const optType = option.type;
let value = testCase[name] as any;
if (!ts.isString(optType)) {
const key = value.toLowerCase();
const optTypeValue = optType.get(key);
const optTypeValue = optType.get(key as never);
if (optTypeValue) {
value = optTypeValue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/testRunner/unittests/config/commandLineParsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,9 @@ describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => {
describe("unittests:: config:: commandLineParsing:: optionDeclarations", () => {
it("should have affectsBuildInfo true for every option with affectsSemanticDiagnostics", () => {
for (const option of ts.optionDeclarations) {
if (option.affectsSemanticDiagnostics) {
if ((option as ts.CommandLineOption).affectsSemanticDiagnostics) {
// semantic diagnostics affect the build info, so ensure they're included
assert(option.affectsBuildInfo ?? false, option.name);
assert((option as ts.CommandLineOption).affectsBuildInfo ?? false, option.name);
}
}
});
Expand Down
Loading