Skip to content

Commit 5133c70

Browse files
authored
Merge pull request microsoft#22167 from Microsoft/requireJson
Resolve the modue names with "modulename.json" to json files when doing node module resolution
2 parents ec19733 + c4143ae commit 5133c70

File tree

87 files changed

+2489
-139
lines changed

Some content is hidden

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

87 files changed

+2489
-139
lines changed

Diff for: src/compiler/binder.ts

+11
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ namespace ts {
281281
return InternalSymbolName.Index;
282282
case SyntaxKind.ExportDeclaration:
283283
return InternalSymbolName.ExportStar;
284+
case SyntaxKind.SourceFile:
285+
// json file should behave as
286+
// module.exports = ...
287+
return InternalSymbolName.ExportEquals;
284288
case SyntaxKind.BinaryExpression:
285289
if (getSpecialPropertyAssignmentKind(node as BinaryExpression) === SpecialPropertyAssignmentKind.ModuleExports) {
286290
// module.exports = ...
@@ -2234,6 +2238,13 @@ namespace ts {
22342238
if (isExternalModule(file)) {
22352239
bindSourceFileAsExternalModule();
22362240
}
2241+
else if (isJsonSourceFile(file)) {
2242+
bindSourceFileAsExternalModule();
2243+
// Create symbol equivalent for the module.exports = {}
2244+
const originalSymbol = file.symbol;
2245+
declareSymbol(file.symbol.exports, file.symbol, file, SymbolFlags.Property, SymbolFlags.All);
2246+
file.symbol = originalSymbol;
2247+
}
22372248
}
22382249

22392250
function bindSourceFileAsExternalModule() {

Diff for: src/compiler/checker.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -2207,7 +2207,7 @@ namespace ts {
22072207
}
22082208

22092209
// May be an untyped module. If so, ignore resolutionDiagnostic.
2210-
if (resolvedModule && !extensionIsTypeScript(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) {
2210+
if (resolvedModule && !resolutionExtensionIsTypeScriptOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) {
22112211
if (isForAugmentation) {
22122212
const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented;
22132213
error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName);
@@ -4718,6 +4718,10 @@ namespace ts {
47184718
return links.type = anyType;
47194719
}
47204720
// Handle export default expressions
4721+
if (isSourceFile(declaration)) {
4722+
const jsonSourceFile = cast(declaration, isJsonSourceFile);
4723+
return links.type = jsonSourceFile.statements.length ? checkExpression(jsonSourceFile.statements[0].expression) : emptyObjectType;
4724+
}
47214725
if (declaration.kind === SyntaxKind.ExportAssignment) {
47224726
return links.type = checkExpression((<ExportAssignment>declaration).expression);
47234727
}
@@ -15597,7 +15601,7 @@ namespace ts {
1559715601
const contextualType = getApparentTypeOfContextualType(node);
1559815602
const contextualTypeHasPattern = contextualType && contextualType.pattern &&
1559915603
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
15600-
const isInJSFile = isInJavaScriptFile(node);
15604+
const isInJSFile = isInJavaScriptFile(node) && !isInJsonFile(node);
1560115605
const isJSObjectLiteral = !contextualType && isInJSFile;
1560215606
let typeFlags: TypeFlags = 0;
1560315607
let patternWithComputedProperties = false;

Diff for: src/compiler/commandLineParser.ts

+95-84
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,12 @@ namespace ts {
501501
category: Diagnostics.Advanced_Options,
502502
description: Diagnostics.Enable_tracing_of_the_name_resolution_process
503503
},
504+
{
505+
name: "resolveJsonModule",
506+
type: "boolean",
507+
category: Diagnostics.Advanced_Options,
508+
description: Diagnostics.Include_modules_imported_with_json_extension
509+
},
504510
{
505511
name: "listFiles",
506512
type: "boolean",
@@ -953,9 +959,9 @@ namespace ts {
953959
* Read tsconfig.json file
954960
* @param fileName The path to the config file
955961
*/
956-
export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile {
962+
export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): TsConfigSourceFile {
957963
const textOrDiagnostic = tryReadFile(fileName, readFile);
958-
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : <JsonSourceFile>{ parseDiagnostics: [textOrDiagnostic] };
964+
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : <TsConfigSourceFile>{ parseDiagnostics: [textOrDiagnostic] };
959965
}
960966

961967
function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic {
@@ -973,58 +979,62 @@ namespace ts {
973979
return arrayToMap(options, option => option.name);
974980
}
975981

976-
let _tsconfigRootOptions: Map<CommandLineOption>;
982+
let _tsconfigRootOptions: TsConfigOnlyOption;
977983
function getTsconfigRootOptionsMap() {
978984
if (_tsconfigRootOptions === undefined) {
979-
_tsconfigRootOptions = commandLineOptionsToMap([
980-
{
981-
name: "compilerOptions",
982-
type: "object",
983-
elementOptions: commandLineOptionsToMap(optionDeclarations),
984-
extraKeyDiagnosticMessage: Diagnostics.Unknown_compiler_option_0
985-
},
986-
{
987-
name: "typingOptions",
988-
type: "object",
989-
elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations),
990-
extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0
991-
},
992-
{
993-
name: "typeAcquisition",
994-
type: "object",
995-
elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations),
996-
extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0
997-
},
998-
{
999-
name: "extends",
1000-
type: "string"
1001-
},
1002-
{
1003-
name: "files",
1004-
type: "list",
1005-
element: {
1006-
name: "files",
985+
_tsconfigRootOptions = {
986+
name: undefined, // should never be needed since this is root
987+
type: "object",
988+
elementOptions: commandLineOptionsToMap([
989+
{
990+
name: "compilerOptions",
991+
type: "object",
992+
elementOptions: commandLineOptionsToMap(optionDeclarations),
993+
extraKeyDiagnosticMessage: Diagnostics.Unknown_compiler_option_0
994+
},
995+
{
996+
name: "typingOptions",
997+
type: "object",
998+
elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations),
999+
extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0
1000+
},
1001+
{
1002+
name: "typeAcquisition",
1003+
type: "object",
1004+
elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations),
1005+
extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0
1006+
},
1007+
{
1008+
name: "extends",
10071009
type: "string"
1008-
}
1009-
},
1010-
{
1011-
name: "include",
1012-
type: "list",
1013-
element: {
1010+
},
1011+
{
1012+
name: "files",
1013+
type: "list",
1014+
element: {
1015+
name: "files",
1016+
type: "string"
1017+
}
1018+
},
1019+
{
10141020
name: "include",
1015-
type: "string"
1016-
}
1017-
},
1018-
{
1019-
name: "exclude",
1020-
type: "list",
1021-
element: {
1021+
type: "list",
1022+
element: {
1023+
name: "include",
1024+
type: "string"
1025+
}
1026+
},
1027+
{
10221028
name: "exclude",
1023-
type: "string"
1024-
}
1025-
},
1026-
compileOnSaveCommandLineOption
1027-
]);
1029+
type: "list",
1030+
element: {
1031+
name: "exclude",
1032+
type: "string"
1033+
}
1034+
},
1035+
compileOnSaveCommandLineOption
1036+
])
1037+
};
10281038
}
10291039
return _tsconfigRootOptions;
10301040
}
@@ -1061,31 +1071,38 @@ namespace ts {
10611071
* Convert the json syntax tree into the json value
10621072
*/
10631073
export function convertToObject(sourceFile: JsonSourceFile, errors: Push<Diagnostic>): any {
1064-
return convertToObjectWorker(sourceFile, errors, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined);
1074+
return convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined);
10651075
}
10661076

10671077
/**
1068-
* Convert the json syntax tree into the json value
1078+
* Convert the json syntax tree into the json value and report errors
1079+
* This returns the json value (apart from checking errors) only if returnValue provided is true.
1080+
* Otherwise it just checks the errors and returns undefined
10691081
*/
1070-
function convertToObjectWorker(
1082+
/*@internal*/
1083+
export function convertToObjectWorker(
10711084
sourceFile: JsonSourceFile,
10721085
errors: Push<Diagnostic>,
1073-
knownRootOptions: Map<CommandLineOption> | undefined,
1086+
returnValue: boolean,
1087+
knownRootOptions: CommandLineOption | undefined,
10741088
jsonConversionNotifier: JsonConversionNotifier | undefined): any {
1075-
if (!sourceFile.jsonObject) {
1076-
return {};
1089+
if (!sourceFile.statements.length) {
1090+
return returnValue ? {} : undefined;
10771091
}
10781092

1079-
return convertObjectLiteralExpressionToJson(sourceFile.jsonObject, knownRootOptions,
1080-
/*extraKeyDiagnosticMessage*/ undefined, /*parentOption*/ undefined);
1093+
return convertPropertyValueToJson(sourceFile.statements[0].expression, knownRootOptions);
1094+
1095+
function isRootOptionMap(knownOptions: Map<CommandLineOption> | undefined) {
1096+
return knownRootOptions && (knownRootOptions as TsConfigOnlyOption).elementOptions === knownOptions;
1097+
}
10811098

10821099
function convertObjectLiteralExpressionToJson(
10831100
node: ObjectLiteralExpression,
10841101
knownOptions: Map<CommandLineOption> | undefined,
10851102
extraKeyDiagnosticMessage: DiagnosticMessage | undefined,
10861103
parentOption: string | undefined
10871104
): any {
1088-
const result: any = {};
1105+
const result: any = returnValue ? {} : undefined;
10891106
for (const element of node.properties) {
10901107
if (element.kind !== SyntaxKind.PropertyAssignment) {
10911108
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected));
@@ -1106,19 +1123,21 @@ namespace ts {
11061123
}
11071124
const value = convertPropertyValueToJson(element.initializer, option);
11081125
if (typeof keyText !== "undefined") {
1109-
result[keyText] = value;
1126+
if (returnValue) {
1127+
result[keyText] = value;
1128+
}
11101129
// Notify key value set, if user asked for it
11111130
if (jsonConversionNotifier &&
11121131
// Current callbacks are only on known parent option or if we are setting values in the root
1113-
(parentOption || knownOptions === knownRootOptions)) {
1132+
(parentOption || isRootOptionMap(knownOptions))) {
11141133
const isValidOptionValue = isCompilerOptionsValue(option, value);
11151134
if (parentOption) {
11161135
if (isValidOptionValue) {
11171136
// Notify option set in the parent if its a valid option value
11181137
jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option, value);
11191138
}
11201139
}
1121-
else if (knownOptions === knownRootOptions) {
1140+
else if (isRootOptionMap(knownOptions)) {
11221141
if (isValidOptionValue) {
11231142
// Notify about the valid root key value being set
11241143
jsonConversionNotifier.onSetValidOptionKeyValueInRoot(keyText, element.name, value, element.initializer);
@@ -1137,8 +1156,8 @@ namespace ts {
11371156
function convertArrayLiteralExpressionToJson(
11381157
elements: NodeArray<Expression>,
11391158
elementOption: CommandLineOption | undefined
1140-
): any[] {
1141-
return elements.map(element => convertPropertyValueToJson(element, elementOption));
1159+
): any[] | void {
1160+
return (returnValue ? elements.map : elements.forEach).call(elements, (element: Expression) => convertPropertyValueToJson(element, elementOption));
11421161
}
11431162

11441163
function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption): any {
@@ -1433,12 +1452,12 @@ namespace ts {
14331452
* @param basePath A root directory to resolve relative path entries in the config
14341453
* file to. e.g. outDir
14351454
*/
1436-
export function parseJsonSourceFileConfigFileContent(sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray<FileExtensionInfo>): ParsedCommandLine {
1455+
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray<FileExtensionInfo>): ParsedCommandLine {
14371456
return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions);
14381457
}
14391458

14401459
/*@internal*/
1441-
export function setConfigFileInOptions(options: CompilerOptions, configFile: JsonSourceFile) {
1460+
export function setConfigFileInOptions(options: CompilerOptions, configFile: TsConfigSourceFile) {
14421461
if (configFile) {
14431462
Object.defineProperty(options, "configFile", { enumerable: false, writable: false, value: configFile });
14441463
}
@@ -1466,7 +1485,7 @@ namespace ts {
14661485
*/
14671486
function parseJsonConfigFileContentWorker(
14681487
json: any,
1469-
sourceFile: JsonSourceFile,
1488+
sourceFile: TsConfigSourceFile,
14701489
host: ParseConfigHost,
14711490
basePath: string,
14721491
existingOptions: CompilerOptions = {},
@@ -1587,7 +1606,7 @@ namespace ts {
15871606
*/
15881607
function parseConfig(
15891608
json: any,
1590-
sourceFile: JsonSourceFile,
1609+
sourceFile: TsConfigSourceFile,
15911610
host: ParseConfigHost,
15921611
basePath: string,
15931612
configFileName: string,
@@ -1664,7 +1683,7 @@ namespace ts {
16641683
}
16651684

16661685
function parseOwnConfigOfJsonSourceFile(
1667-
sourceFile: JsonSourceFile,
1686+
sourceFile: TsConfigSourceFile,
16681687
host: ParseConfigHost,
16691688
basePath: string,
16701689
configFileName: string | undefined,
@@ -1711,7 +1730,7 @@ namespace ts {
17111730
}
17121731
}
17131732
};
1714-
const json = convertToObjectWorker(sourceFile, errors, getTsconfigRootOptionsMap(), optionsIterator);
1733+
const json = convertToObjectWorker(sourceFile, errors, /*returnValue*/ true, getTsconfigRootOptionsMap(), optionsIterator);
17151734
if (!typeAcquisition) {
17161735
if (typingOptionstypeAcquisition) {
17171736
typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ?
@@ -1754,7 +1773,7 @@ namespace ts {
17541773
}
17551774

17561775
function getExtendedConfig(
1757-
sourceFile: JsonSourceFile,
1776+
sourceFile: TsConfigSourceFile,
17581777
extendedConfigPath: string,
17591778
host: ParseConfigHost,
17601779
basePath: string,
@@ -2006,7 +2025,7 @@ namespace ts {
20062025
host: ParseConfigHost,
20072026
errors: Push<Diagnostic>,
20082027
extraFileExtensions: ReadonlyArray<FileExtensionInfo>,
2009-
jsonSourceFile: JsonSourceFile
2028+
jsonSourceFile: TsConfigSourceFile
20102029
): ExpandResult {
20112030
basePath = normalizePath(basePath);
20122031
let validatedIncludeSpecs: ReadonlyArray<string>, validatedExcludeSpecs: ReadonlyArray<string>;
@@ -2107,7 +2126,7 @@ namespace ts {
21072126
};
21082127
}
21092128

2110-
function validateSpecs(specs: ReadonlyArray<string>, errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: JsonSourceFile, specKey: string): ReadonlyArray<string> {
2129+
function validateSpecs(specs: ReadonlyArray<string>, errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile, specKey: string): ReadonlyArray<string> {
21112130
return specs.filter(spec => {
21122131
const diag = specToDiagnostic(spec, allowTrailingRecursion);
21132132
if (diag !== undefined) {
@@ -2117,18 +2136,10 @@ namespace ts {
21172136
});
21182137

21192138
function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic {
2120-
if (jsonSourceFile && jsonSourceFile.jsonObject) {
2121-
for (const property of getPropertyAssignment(jsonSourceFile.jsonObject, specKey)) {
2122-
if (isArrayLiteralExpression(property.initializer)) {
2123-
for (const element of property.initializer.elements) {
2124-
if (isStringLiteral(element) && element.text === spec) {
2125-
return createDiagnosticForNodeInSourceFile(jsonSourceFile, element, message, spec);
2126-
}
2127-
}
2128-
}
2129-
}
2130-
}
2131-
return createCompilerDiagnostic(message, spec);
2139+
const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec);
2140+
return element ?
2141+
createDiagnosticForNodeInSourceFile(jsonSourceFile, element, message, spec) :
2142+
createCompilerDiagnostic(message, spec);
21322143
}
21332144
}
21342145

Diff for: src/compiler/core.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -2966,7 +2966,7 @@ namespace ts {
29662966
}
29672967
}
29682968

2969-
const extensionsToRemove = [Extension.Dts, Extension.Ts, Extension.Js, Extension.Tsx, Extension.Jsx];
2969+
const extensionsToRemove = [Extension.Dts, Extension.Ts, Extension.Js, Extension.Tsx, Extension.Jsx, Extension.Json];
29702970
export function removeFileExtension(path: string): string {
29712971
for (const ext of extensionsToRemove) {
29722972
const extensionless = tryRemoveExtension(path, ext);
@@ -3307,6 +3307,10 @@ namespace ts {
33073307
return ext === Extension.Ts || ext === Extension.Tsx || ext === Extension.Dts;
33083308
}
33093309

3310+
export function resolutionExtensionIsTypeScriptOrJson(ext: Extension) {
3311+
return extensionIsTypeScript(ext) || ext === Extension.Json;
3312+
}
3313+
33103314
/**
33113315
* Gets the extension from a path.
33123316
* Path must have a valid extension.

0 commit comments

Comments
 (0)