Skip to content

Commit 60e15f3

Browse files
committed
erge branch 'main' into wtf-bug
2 parents 22cfe3a + 2082ef2 commit 60e15f3

File tree

135 files changed

+4468
-1490
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+4468
-1490
lines changed

package-lock.json

Lines changed: 80 additions & 80 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/compiler/checker.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@ import {
604604
isNewExpression,
605605
isNightly,
606606
isNodeDescendantOf,
607+
isNonNullAccess,
607608
isNullishCoalesce,
608609
isNumericLiteral,
609610
isNumericLiteralName,
@@ -4738,7 +4739,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
47384739
const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat;
47394740
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
47404741
const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode);
4741-
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule);
4742+
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule, currentSourceFile);
47424743
const sourceFile = resolvedModule
47434744
&& (!resolutionDiagnostic || resolutionDiagnostic === Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set)
47444745
&& host.getSourceFile(resolvedModule.resolvedFileName);
@@ -26372,12 +26373,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2637226373
if (propName === undefined) {
2637326374
return type;
2637426375
}
26375-
const removeNullable = strictNullChecks && isOptionalChain(access) && maybeTypeOfKind(type, TypeFlags.Nullable);
26376+
const optionalChain = isOptionalChain(access);
26377+
const removeNullable = strictNullChecks && (optionalChain || isNonNullAccess(access)) && maybeTypeOfKind(type, TypeFlags.Nullable);
2637626378
let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName);
2637726379
if (!propType) {
2637826380
return type;
2637926381
}
26380-
propType = removeNullable ? getOptionalType(propType) : propType;
26382+
propType = removeNullable && optionalChain ? getOptionalType(propType) : propType;
2638126383
const narrowedPropType = narrowType(propType);
2638226384
return filterType(type, t => {
2638326385
const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName);
@@ -35810,7 +35812,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3581035812
return getRegularTypeOfObjectLiteral(rightType);
3581135813
}
3581235814
case SyntaxKind.CommaToken:
35813-
if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) {
35815+
if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isIndirectCall(left.parent as BinaryExpression)) {
3581435816
const sf = getSourceFileOfNode(left);
3581535817
const sourceText = sf.text;
3581635818
const start = skipTrivia(sourceText, left.pos);
@@ -35846,8 +35848,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3584635848
}
3584735849
}
3584835850

35849-
function isEvalNode(node: Expression) {
35850-
return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval";
35851+
// Return true for "indirect calls", (i.e. `(0, x.f)(...)` or `(0, eval)(...)`), which prevents passing `this`.
35852+
function isIndirectCall(node: BinaryExpression): boolean {
35853+
return node.parent.kind === SyntaxKind.ParenthesizedExpression &&
35854+
isNumericLiteral(node.left) &&
35855+
node.left.text === "0" &&
35856+
(isCallExpression(node.parent.parent) && node.parent.parent.expression === node.parent || node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) &&
35857+
// special-case for "eval" because it's the only non-access case where an indirect call actually affects behavior.
35858+
(isAccessExpression(node.right) || isIdentifier(node.right) && node.right.escapedText === "eval");
3585135859
}
3585235860

3585335861
// Return true if there was no error, false if there was an error.

src/compiler/commandLineParser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,14 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
12061206
description: Diagnostics.Enable_importing_json_files,
12071207
defaultValueDescription: false,
12081208
},
1209+
{
1210+
name: "allowArbitraryExtensions",
1211+
type: "boolean",
1212+
affectsModuleResolution: true,
1213+
category: Diagnostics.Modules,
1214+
description: Diagnostics.Enable_importing_files_with_any_extension_provided_a_declaration_file_is_present,
1215+
defaultValueDescription: false,
1216+
},
12091217

12101218
{
12111219
name: "out",

src/compiler/diagnosticMessages.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5190,6 +5190,18 @@
51905190
"category": "Message",
51915191
"code": 6261
51925192
},
5193+
"File name '{0}' has a '{1}' extension - looking up '{2}' instead.": {
5194+
"category": "Message",
5195+
"code": 6262
5196+
},
5197+
"Module '{0}' was resolved to '{1}', but '--allowArbitraryExtensions' is not set.": {
5198+
"category": "Error",
5199+
"code": 6263
5200+
},
5201+
"Enable importing files with any extension, provided a declaration file is present.": {
5202+
"category": "Message",
5203+
"code": 6264
5204+
},
51935205

51945206
"Directory '{0}' has no containing package.json scope. Imports will not resolve.": {
51955207
"category": "Message",

src/compiler/moduleNameResolver.ts

Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ import {
5252
getRelativePathFromDirectory,
5353
getResolveJsonModule,
5454
getRootLength,
55-
hasJSFileExtension,
5655
hasProperty,
5756
hasTrailingDirectorySeparator,
5857
hostGetCanonicalFileName,
@@ -99,7 +98,6 @@ import {
9998
startsWith,
10099
stringContains,
101100
supportedDeclarationExtensions,
102-
supportedTSExtensionsFlat,
103101
supportedTSImplementationExtensions,
104102
toPath,
105103
tryExtractTSExtension,
@@ -151,7 +149,7 @@ function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | und
151149
/** Result of trying to resolve a module. */
152150
interface Resolved {
153151
path: string;
154-
extension: Extension;
152+
extension: string;
155153
packageId: PackageId | undefined;
156154
/**
157155
* When the resolved is not created from cache, the value is
@@ -170,7 +168,7 @@ interface Resolved {
170168
interface PathAndExtension {
171169
path: string;
172170
// (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.)
173-
ext: Extension;
171+
ext: string;
174172
resolvedUsingTsExtension: boolean | undefined;
175173
}
176174

@@ -1856,21 +1854,21 @@ function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecor
18561854
}
18571855

18581856
function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
1859-
// If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one;
1860-
// e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts"
1861-
if (hasJSFileExtension(candidate) ||
1862-
extensions & Extensions.Json && fileExtensionIs(candidate, Extension.Json) ||
1863-
extensions & (Extensions.TypeScript | Extensions.Declaration)
1864-
&& moduleResolutionSupportsResolvingTsExtensions(state.compilerOptions)
1865-
&& fileExtensionIsOneOf(candidate, supportedTSExtensionsFlat)
1866-
) {
1867-
const extensionless = removeFileExtension(candidate);
1868-
const extension = candidate.substring(extensionless.length);
1869-
if (state.traceEnabled) {
1870-
trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension);
1871-
}
1872-
return tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures, state);
1857+
const filename = getBaseFileName(candidate);
1858+
if (filename.indexOf(".") === -1) {
1859+
return undefined; // extensionless import, no lookups performed, since we don't support extensionless files
1860+
}
1861+
let extensionless = removeFileExtension(candidate);
1862+
if (extensionless === candidate) {
1863+
// Once TS native extensions are handled, handle arbitrary extensions for declaration file mapping
1864+
extensionless = candidate.substring(0, candidate.lastIndexOf("."));
1865+
}
1866+
1867+
const extension = candidate.substring(extensionless.length);
1868+
if (state.traceEnabled) {
1869+
trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension);
18731870
}
1871+
return tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures, state);
18741872
}
18751873

18761874
/**
@@ -1909,47 +1907,46 @@ function tryAddingExtensions(candidate: string, extensions: Extensions, original
19091907
case Extension.Mjs:
19101908
case Extension.Mts:
19111909
case Extension.Dmts:
1912-
return extensions & Extensions.TypeScript && tryExtension(Extension.Mts)
1913-
|| extensions & Extensions.Declaration && tryExtension(Extension.Dmts)
1910+
return extensions & Extensions.TypeScript && tryExtension(Extension.Mts, originalExtension === Extension.Mts || originalExtension === Extension.Dmts)
1911+
|| extensions & Extensions.Declaration && tryExtension(Extension.Dmts, originalExtension === Extension.Mts || originalExtension === Extension.Dmts)
19141912
|| extensions & Extensions.JavaScript && tryExtension(Extension.Mjs)
19151913
|| undefined;
19161914
case Extension.Cjs:
19171915
case Extension.Cts:
19181916
case Extension.Dcts:
1919-
return extensions & Extensions.TypeScript && tryExtension(Extension.Cts)
1920-
|| extensions & Extensions.Declaration && tryExtension(Extension.Dcts)
1917+
return extensions & Extensions.TypeScript && tryExtension(Extension.Cts, originalExtension === Extension.Cts || originalExtension === Extension.Dcts)
1918+
|| extensions & Extensions.Declaration && tryExtension(Extension.Dcts, originalExtension === Extension.Cts || originalExtension === Extension.Dcts)
19211919
|| extensions & Extensions.JavaScript && tryExtension(Extension.Cjs)
19221920
|| undefined;
19231921
case Extension.Json:
1924-
const originalCandidate = candidate;
1925-
if (extensions & Extensions.Declaration) {
1926-
candidate += Extension.Json;
1927-
const result = tryExtension(Extension.Dts);
1928-
if (result) return result;
1929-
}
1930-
if (extensions & Extensions.Json) {
1931-
candidate = originalCandidate;
1932-
const result = tryExtension(Extension.Json);
1933-
if (result) return result;
1934-
}
1935-
return undefined;
1936-
case Extension.Ts:
1922+
return extensions & Extensions.Declaration && tryExtension(".d.json.ts")
1923+
|| extensions & Extensions.Json && tryExtension(Extension.Json)
1924+
|| undefined;
19371925
case Extension.Tsx:
1926+
case Extension.Jsx:
1927+
// basically idendical to the ts/js case below, but prefers matching tsx and jsx files exactly before falling back to the ts or js file path
1928+
// (historically, we disallow having both a a.ts and a.tsx file in the same compilation, since their outputs clash)
1929+
// TODO: We should probably error if `"./a.tsx"` resolved to `"./a.ts"`, right?
1930+
return extensions & Extensions.TypeScript && (tryExtension(Extension.Tsx, originalExtension === Extension.Tsx) || tryExtension(Extension.Ts, originalExtension === Extension.Tsx))
1931+
|| extensions & Extensions.Declaration && tryExtension(Extension.Dts, originalExtension === Extension.Tsx)
1932+
|| extensions & Extensions.JavaScript && (tryExtension(Extension.Jsx) || tryExtension(Extension.Js))
1933+
|| undefined;
1934+
case Extension.Ts:
19381935
case Extension.Dts:
1939-
if (moduleResolutionSupportsResolvingTsExtensions(state.compilerOptions) && extensionIsOk(extensions, originalExtension)) {
1940-
return tryExtension(originalExtension, /*resolvedUsingTsExtension*/ true);
1941-
}
1942-
// falls through
1943-
default:
1944-
return extensions & Extensions.TypeScript && (tryExtension(Extension.Ts) || tryExtension(Extension.Tsx))
1945-
|| extensions & Extensions.Declaration && tryExtension(Extension.Dts)
1936+
case Extension.Js:
1937+
case "":
1938+
return extensions & Extensions.TypeScript && (tryExtension(Extension.Ts, originalExtension === Extension.Ts || originalExtension === Extension.Dts) || tryExtension(Extension.Tsx, originalExtension === Extension.Ts || originalExtension === Extension.Dts))
1939+
|| extensions & Extensions.Declaration && tryExtension(Extension.Dts, originalExtension === Extension.Ts || originalExtension === Extension.Dts)
19461940
|| extensions & Extensions.JavaScript && (tryExtension(Extension.Js) || tryExtension(Extension.Jsx))
19471941
|| state.isConfigLookup && tryExtension(Extension.Json)
19481942
|| undefined;
1943+
default:
1944+
return extensions & Extensions.Declaration && !isDeclarationFileName(candidate + originalExtension) && tryExtension(`.d${originalExtension}.ts`)
1945+
|| undefined;
19491946

19501947
}
19511948

1952-
function tryExtension(ext: Extension, resolvedUsingTsExtension?: boolean): PathAndExtension | undefined {
1949+
function tryExtension(ext: string, resolvedUsingTsExtension?: boolean): PathAndExtension | undefined {
19531950
const path = tryFile(candidate + ext, onlyRecordFailures, state);
19541951
return path === undefined ? undefined : { path, ext, resolvedUsingTsExtension };
19551952
}
@@ -2984,14 +2981,10 @@ export function classicNameResolver(moduleName: string, containingFile: string,
29842981
}
29852982
}
29862983

2987-
export function moduleResolutionSupportsResolvingTsExtensions(compilerOptions: CompilerOptions) {
2988-
return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Bundler;
2989-
}
2990-
29912984
// Program errors validate that `noEmit` or `emitDeclarationOnly` is also set,
29922985
// so this function doesn't check them to avoid propagating errors.
29932986
export function shouldAllowImportingTsExtension(compilerOptions: CompilerOptions, fromFileName?: string) {
2994-
return moduleResolutionSupportsResolvingTsExtensions(compilerOptions) && (
2987+
return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Bundler && (
29952988
!!compilerOptions.allowImportingTsExtensions ||
29962989
fromFileName && isDeclarationFileName(fromFileName));
29972990
}

src/compiler/moduleSpecifiers.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
flatten,
3232
forEach,
3333
forEachAncestorDirectory,
34+
getBaseFileName,
3435
GetCanonicalFileName,
3536
getDirectoryPath,
3637
getEmitModuleResolutionKind,
@@ -85,6 +86,7 @@ import {
8586
pathIsBareSpecifier,
8687
pathIsRelative,
8788
PropertyAccessExpression,
89+
removeExtension,
8890
removeFileExtension,
8991
removeSuffix,
9092
ResolutionMode,
@@ -1036,6 +1038,10 @@ function processEnding(fileName: string, allowedEndings: readonly ModuleSpecifie
10361038
if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) {
10371039
return noExtension + getJSExtensionForFile(fileName, options);
10381040
}
1041+
else if (!fileExtensionIsOneOf(fileName, [Extension.Dts]) && fileExtensionIsOneOf(fileName, [Extension.Ts]) && stringContains(fileName, ".d.")) {
1042+
// `foo.d.json.ts` and the like - remap back to `foo.json`
1043+
return tryGetRealFileNameForNonJsDeclarationFileName(fileName)!;
1044+
}
10391045

10401046
switch (allowedEndings[0]) {
10411047
case ModuleSpecifierEnding.Minimal:
@@ -1066,6 +1072,15 @@ function processEnding(fileName: string, allowedEndings: readonly ModuleSpecifie
10661072
}
10671073
}
10681074

1075+
/** @internal */
1076+
export function tryGetRealFileNameForNonJsDeclarationFileName(fileName: string) {
1077+
const baseName = getBaseFileName(fileName);
1078+
if (!endsWith(fileName, Extension.Ts) || !stringContains(baseName, ".d.") || fileExtensionIsOneOf(baseName, [Extension.Dts])) return undefined;
1079+
const noExtension = removeExtension(fileName, Extension.Ts);
1080+
const ext = noExtension.substring(noExtension.lastIndexOf("."));
1081+
return noExtension.substring(0, noExtension.indexOf(".d.")) + ext;
1082+
}
1083+
10691084
function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension {
10701085
return tryGetJSExtensionForFile(fileName, options) ?? Debug.fail(`Extension ${extensionFromPath(fileName)} is unsupported:: FileName:: ${fileName}`);
10711086
}

src/compiler/parser.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ import {
8484
Expression,
8585
ExpressionStatement,
8686
ExpressionWithTypeArguments,
87+
Extension,
8788
ExternalModuleReference,
89+
fileExtensionIs,
8890
fileExtensionIsOneOf,
8991
findIndex,
9092
forEach,
@@ -98,6 +100,7 @@ import {
98100
FunctionOrConstructorTypeNode,
99101
FunctionTypeNode,
100102
GetAccessorDeclaration,
103+
getBaseFileName,
101104
getBinaryOperatorPrecedence,
102105
getFullWidth,
103106
getJSDocCommentRanges,
@@ -328,6 +331,7 @@ import {
328331
SpreadElement,
329332
startsWith,
330333
Statement,
334+
stringContains,
331335
StringLiteral,
332336
supportedDeclarationExtensions,
333337
SwitchStatement,
@@ -10114,7 +10118,7 @@ namespace IncrementalParser {
1011410118

1011510119
/** @internal */
1011610120
export function isDeclarationFileName(fileName: string): boolean {
10117-
return fileExtensionIsOneOf(fileName, supportedDeclarationExtensions);
10121+
return fileExtensionIsOneOf(fileName, supportedDeclarationExtensions) || (fileExtensionIs(fileName, Extension.Ts) && stringContains(getBaseFileName(fileName), ".d."));
1011810122
}
1011910123

1012010124
function parseResolutionMode(mode: string | undefined, pos: number, end: number, reportDiagnostic: PragmaDiagnosticReporter): ResolutionMode {

0 commit comments

Comments
 (0)