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

Add synthetic TypeScriptSettings interface that exposes some compiler options to type system #58396

Closed
wants to merge 2 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
56 changes: 44 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,9 @@ import {
getNameFromIndexInfo,
getNameOfDeclaration,
getNameOfExpando,
getNameOfModuleKind,
getNameOfModuleResolutionKind,
getNameOfScriptTarget,
getNamespaceDeclarationNode,
getNewTargetContainer,
getNonAugmentationDeclaration,
Expand Down Expand Up @@ -1089,6 +1092,8 @@ import {
VariableLikeDeclaration,
VariableStatement,
VarianceFlags,
version,
versionMajorMinor,
visitEachChild,
visitNode,
visitNodes,
Expand Down Expand Up @@ -1473,6 +1478,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var compilerOptions = host.getCompilerOptions();
var languageVersion = getEmitScriptTarget(compilerOptions);
var moduleKind = getEmitModuleKind(compilerOptions);
var moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
var legacyDecorators = !!compilerOptions.experimentalDecorators;
var useDefineForClassFields = getUseDefineForClassFields(compilerOptions);
var emitStandardClassFields = getEmitStandardClassFields(compilerOptions);
Expand All @@ -1485,6 +1491,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis");
var useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables");
var exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes;
var noUncheckedIndexedAccess = compilerOptions.noUncheckedIndexedAccess;

var checkBinaryExpression = createCheckBinaryExpression();
var emitResolver = createResolver();
Expand Down Expand Up @@ -2330,6 +2337,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
[".jsx", ".jsx"],
[".json", ".json"],
];

var typeScriptSettingsSymbol = createSymbol(SymbolFlags.Interface, "TypeScriptSettings" as __String, CheckFlags.SyntheticInterface);
typeScriptSettingsSymbol.declarations = [];
typeScriptSettingsSymbol.members = createSymbolTable([
createProperty("version" as __String, getStringLiteralType(version), CheckFlags.Readonly),
createProperty("versionMajorMinor" as __String, getStringLiteralType(versionMajorMinor), CheckFlags.Readonly),
createProperty("locale" as __String, compilerOptions.locale ? getStringLiteralType(compilerOptions.locale) : undefinedType, CheckFlags.Readonly),
createProperty("target" as __String, getStringLiteralType(getNameOfScriptTarget(languageVersion) ?? ""), CheckFlags.Readonly),
createProperty("module" as __String, getStringLiteralType(getNameOfModuleKind(moduleKind) ?? ""), CheckFlags.Readonly),
createProperty("moduleResolution" as __String, getStringLiteralType(getNameOfModuleResolutionKind(moduleResolutionKind) ?? ""), CheckFlags.Readonly),
createProperty("customConditions" as __String, createTupleType(compilerOptions.customConditions?.map(cond => getStringLiteralType(cond)) ?? emptyArray, /*elementFlags*/ undefined, /*readonly*/ true), CheckFlags.Readonly),
createProperty("esModuleInterop" as __String, getESModuleInterop(compilerOptions) ? trueType : falseType, CheckFlags.Readonly),
createProperty("exactOptionalPropertyTypes" as __String, exactOptionalPropertyTypes ? trueType : falseType, CheckFlags.Readonly),
createProperty("noImplicitAny" as __String, noImplicitAny ? trueType : falseType, CheckFlags.Readonly),
createProperty("noUncheckedIndexedAccess" as __String, noUncheckedIndexedAccess ? trueType : falseType, CheckFlags.Readonly),
createProperty("strictBindCallApply" as __String, strictBindCallApply ? trueType : falseType, CheckFlags.Readonly),
createProperty("strictFunctionTypes" as __String, strictFunctionTypes ? trueType : falseType, CheckFlags.Readonly),
createProperty("strictNullChecks" as __String, strictNullChecks ? trueType : falseType, CheckFlags.Readonly),
createProperty("useDefineForClassFields" as __String, useDefineForClassFields ? trueType : falseType, CheckFlags.Readonly),
]);
globals.set(typeScriptSettingsSymbol.escapedName, typeScriptSettingsSymbol);

/* eslint-enable no-var */

initializeTypeChecker();
Expand Down Expand Up @@ -2542,8 +2571,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return symbol;
}

function createProperty(name: __String, type: Type) {
const symbol = createSymbol(SymbolFlags.Property, name);
function createProperty(name: __String, type: Type, checkFlags?: CheckFlags) {
const symbol = createSymbol(SymbolFlags.Property, name, checkFlags);
symbol.links.type = type;
return symbol;
}
Expand Down Expand Up @@ -12079,6 +12108,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

// The outer type parameters are those defined by enclosing generic classes, methods, or functions.
function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined {
if (getCheckFlags(symbol) & CheckFlags.SyntheticInterface) {
return undefined;
}
const declaration = (symbol.flags & SymbolFlags.Class || symbol.flags & SymbolFlags.Function)
? symbol.valueDeclaration
: symbol.declarations?.find(decl => {
Expand Down Expand Up @@ -18483,7 +18515,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// In noUncheckedIndexedAccess mode, indexed access operations that occur in an expression in a read position and resolve to
// an index signature have 'undefined' included in their type.
if (compilerOptions.noUncheckedIndexedAccess && accessFlags & AccessFlags.ExpressionPosition) accessFlags |= AccessFlags.IncludeUndefined;
if (noUncheckedIndexedAccess && accessFlags & AccessFlags.ExpressionPosition) accessFlags |= AccessFlags.IncludeUndefined;
// If the index type is generic, or if the object type is generic and doesn't originate in an expression and
// the operation isn't exclusively indexing the fixed (non-variadic) portion of a tuple type, we are performing
// a higher-order index access where we cannot meaningfully access the properties of the object type. Note that
Expand Down Expand Up @@ -23923,7 +23955,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Invoke the callback for each underlying property symbol of the given symbol and return the first
// value that isn't undefined.
function forEachProperty<T>(prop: Symbol, callback: (p: Symbol) => T): T | undefined {
if (getCheckFlags(prop) & CheckFlags.Synthetic) {
if (getCheckFlags(prop) & CheckFlags.SyntheticMember) {
// NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.Synthetic
for (const t of (prop as TransientSymbol).links.containingType!.types) {
const p = getPropertyOfType(t, prop.escapedName);
Expand Down Expand Up @@ -24340,7 +24372,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return propType;
}
if (everyType(type, isTupleType)) {
return getTupleElementTypeOutOfStartCount(type, index, compilerOptions.noUncheckedIndexedAccess ? undefinedType : undefined);
return getTupleElementTypeOutOfStartCount(type, index, noUncheckedIndexedAccess ? undefinedType : undefined);
}
return undefined;
}
Expand Down Expand Up @@ -27007,7 +27039,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function includeUndefinedInIndexSignature(type: Type | undefined): Type | undefined {
if (!type) return type;
return compilerOptions.noUncheckedIndexedAccess ?
return noUncheckedIndexedAccess ?
getUnionType([type, missingType]) :
type;
}
Expand Down Expand Up @@ -33132,7 +33164,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

propType = indexInfo.type;
if (compilerOptions.noUncheckedIndexedAccess && getAssignmentTargetKind(node) !== AssignmentKind.Definite) {
if (noUncheckedIndexedAccess && getAssignmentTargetKind(node) !== AssignmentKind.Definite) {
propType = getUnionType([propType, missingType]);
}
if (compilerOptions.noPropertyAccessFromIndexSignature && isPropertyAccessExpression(node)) {
Expand Down Expand Up @@ -38321,7 +38353,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// present (aka the tuple element property). This call also checks that the parentType is in
// fact an iterable or array (depending on target language).
const possiblyOutOfBoundsType = checkIteratedTypeOrElementType(IterationUse.Destructuring | IterationUse.PossiblyOutOfBounds, sourceType, undefinedType, node) || errorType;
let inBoundsType: Type | undefined = compilerOptions.noUncheckedIndexedAccess ? undefined : possiblyOutOfBoundsType;
let inBoundsType: Type | undefined = noUncheckedIndexedAccess ? undefined : possiblyOutOfBoundsType;
for (let i = 0; i < elements.length; i++) {
let type = possiblyOutOfBoundsType;
if (node.elements[i].kind === SyntaxKind.SpreadElement) {
Expand Down Expand Up @@ -43513,7 +43545,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

const uplevelIteration = languageVersion >= ScriptTarget.ES2015;
const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration;
const possibleOutOfBounds = compilerOptions.noUncheckedIndexedAccess && !!(use & IterationUse.PossiblyOutOfBounds);
const possibleOutOfBounds = noUncheckedIndexedAccess && !!(use & IterationUse.PossiblyOutOfBounds);

// Get the iterated type of an `Iterable<T>` or `IterableIterator<T>` only in ES2015
// or higher, when inside of an async generator or for-await-if, or when
Expand Down Expand Up @@ -43590,7 +43622,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const arrayElementType = getIndexTypeOfType(arrayType, numberType);
if (hasStringConstituent && arrayElementType) {
// This is just an optimization for the case where arrayOrStringType is string | string[]
if (arrayElementType.flags & TypeFlags.StringLike && !compilerOptions.noUncheckedIndexedAccess) {
if (arrayElementType.flags & TypeFlags.StringLike && !noUncheckedIndexedAccess) {
return stringType;
}

Expand Down Expand Up @@ -45352,7 +45384,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (basePropertyFlags && derivedPropertyFlags) {
// property/accessor is overridden with property/accessor
if (
(getCheckFlags(base) & CheckFlags.Synthetic
(getCheckFlags(base) & CheckFlags.SyntheticMember
? base.declarations?.some(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags))
: base.declarations?.every(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags)))
|| getCheckFlags(base) & CheckFlags.Mapped
Expand Down Expand Up @@ -48101,7 +48133,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return roots ? flatMap(roots, getRootSymbols) : [symbol];
}
function getImmediateRootSymbols(symbol: Symbol): readonly Symbol[] | undefined {
if (getCheckFlags(symbol) & CheckFlags.Synthetic) {
if (getCheckFlags(symbol) & CheckFlags.SyntheticMember) {
return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName));
}
else if (symbol.flags & SymbolFlags.Transient) {
Expand Down
41 changes: 22 additions & 19 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,27 @@ export const moduleOptionDeclaration: CommandLineOptionOfCustomType = {
defaultValueDescription: undefined,
};

/** @internal */
export const moduleResolutionOptionDeclaration: CommandLineOptionOfCustomType = {
name: "moduleResolution",
type: new Map(Object.entries({
// N.B. The first entry specifies the value shown in `tsc --init`
node10: ModuleResolutionKind.Node10,
node: ModuleResolutionKind.Node10,
classic: ModuleResolutionKind.Classic,
node16: ModuleResolutionKind.Node16,
nodenext: ModuleResolutionKind.NodeNext,
bundler: ModuleResolutionKind.Bundler,
})),
deprecatedKeys: new Set(["node"]),
affectsSourceFile: true,
affectsModuleResolution: true,
paramType: Diagnostics.STRATEGY,
category: Diagnostics.Modules,
description: Diagnostics.Specify_how_TypeScript_looks_up_a_file_from_a_given_module_specifier,
defaultValueDescription: Diagnostics.module_AMD_or_UMD_or_System_or_ES6_then_Classic_Otherwise_Node,
};

const commandOptionsWithoutBuild: CommandLineOption[] = [
// CommandLine only options
{
Expand Down Expand Up @@ -1028,25 +1049,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
},

// Module Resolution
{
name: "moduleResolution",
type: new Map(Object.entries({
// N.B. The first entry specifies the value shown in `tsc --init`
node10: ModuleResolutionKind.Node10,
node: ModuleResolutionKind.Node10,
classic: ModuleResolutionKind.Classic,
node16: ModuleResolutionKind.Node16,
nodenext: ModuleResolutionKind.NodeNext,
bundler: ModuleResolutionKind.Bundler,
})),
deprecatedKeys: new Set(["node"]),
affectsSourceFile: true,
affectsModuleResolution: true,
paramType: Diagnostics.STRATEGY,
category: Diagnostics.Modules,
description: Diagnostics.Specify_how_TypeScript_looks_up_a_file_from_a_given_module_specifier,
defaultValueDescription: Diagnostics.module_AMD_or_UMD_or_System_or_ES6_then_Classic_Otherwise_Node,
},
moduleResolutionOptionDeclaration,
{
name: "baseUrl",
type: "string",
Expand Down
7 changes: 5 additions & 2 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4790,8 +4790,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
message = Diagnostics.File_is_library_specified_here;
break;
}
const target = getNameOfScriptTarget(getEmitScriptTarget(options));
configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined;
const target = getEmitScriptTarget(options);
const targetText = getNameOfScriptTarget(target);
configFileNode = target === ScriptTarget.ES2015 ? getOptionsSyntaxByValue("target", "es2015") ?? getOptionsSyntaxByValue("target", "es6") :
targetText ? getOptionsSyntaxByValue("target", targetText) :
undefined;
message = Diagnostics.File_is_default_library_for_target_specified_here;
break;
default:
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5981,7 +5981,8 @@ export const enum CheckFlags {
Mapped = 1 << 18, // Property of mapped type
StripOptional = 1 << 19, // Strip optionality in mapped property
Unresolved = 1 << 20, // Unresolved type alias symbol
Synthetic = SyntheticProperty | SyntheticMethod,
SyntheticInterface = 1 << 21, // Synthetic interface
SyntheticMember = SyntheticProperty | SyntheticMethod,
Discriminant = HasNonUniformType | HasLiteralType,
Partial = ReadPartial | WritePartial,
}
Expand Down
22 changes: 20 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,9 @@ import {
ModuleDeclaration,
ModuleDetectionKind,
ModuleKind,
moduleOptionDeclaration,
ModuleResolutionKind,
moduleResolutionOptionDeclaration,
moduleResolutionOptionDeclarations,
MultiMap,
NamedDeclaration,
Expand Down Expand Up @@ -7808,7 +7810,7 @@ export function getDeclarationModifierFlagsFromSymbol(s: Symbol, isWrite = false
const flags = getCombinedModifierFlags(declaration);
return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier;
}
if (getCheckFlags(s) & CheckFlags.Synthetic) {
if (getCheckFlags(s) & CheckFlags.SyntheticMember) {
// NOTE: potentially unchecked cast to TransientSymbol
const checkFlags = (s as TransientSymbol).links.checkFlags;
const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private :
Expand Down Expand Up @@ -9101,7 +9103,23 @@ export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: Str

/** @internal */
export function getNameOfScriptTarget(scriptTarget: ScriptTarget): string | undefined {
return forEachEntry(targetOptionDeclaration.type, (value, key) => value === scriptTarget ? key : undefined);
const entries = [...targetOptionDeclaration.type].reverse();
return forEach(entries, ([key, value]) => value === scriptTarget ? key : undefined);
}

/** @internal */
export function getNameOfModuleKind(moduleKind: ModuleKind): string | undefined {
const entries = [...moduleOptionDeclaration.type].reverse();
return forEach(entries, ([key, value]) => value === moduleKind ? key : undefined);
}

/** @internal */
export function getNameOfModuleResolutionKind(moduleResolution: ModuleResolutionKind): string | undefined {
if (moduleResolution === ModuleResolutionKind.Node10) {
return "node10";
}
const entries = [...moduleResolutionOptionDeclaration.type].reverse();
return forEach(entries, ([key, value]) => value === moduleResolution ? key : undefined);
}

/** @internal */
Expand Down
14 changes: 12 additions & 2 deletions src/harness/fourslashInterfaceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,8 @@ export namespace Completion {
return combineExpectedCompletionEntries("typeKeywordsPlus", typeKeywords, plus);
}

const typeScriptSettingsEntry = interfaceEntry("TypeScriptSettings");

const globalTypeDecls: readonly ExpectedCompletionEntryObject[] = [
interfaceEntry("Symbol"),
typeEntry("PropertyKey"),
Expand Down Expand Up @@ -1238,11 +1240,19 @@ export namespace Completion {
kind: "module",
sortText: SortText.GlobalsOrKeywords,
};

export const globalTypes = globalTypesPlus([]);
export function globalTypesPlus(plus: readonly ExpectedCompletionEntry[]) {
export const globalTypesNoLib = globalTypesPlus([], { noLib: true });

export function globalTypesPlus(plus: readonly ExpectedCompletionEntry[], options?: { noLib?: boolean; }) {
return combineExpectedCompletionEntries(
"globalTypesPlus",
[globalThisEntry, ...globalTypeDecls, ...typeKeywords],
[
globalThisEntry,
typeScriptSettingsEntry,
...typeKeywords,
...options?.noLib ? [] : globalTypeDecls,
],
plus,
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/findAllReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2706,7 +2706,7 @@ export namespace Core {
}
return search.includes(baseSymbol || rootSymbol || sym)
// For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol.
? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind }
? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.SyntheticMember) ? rootSymbol : sym, kind }
: undefined;
}, /*allowBaseTypes*/ rootSymbol => !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker))));
}
Expand Down
Loading