Resolve the modue names with "modulename.json" to json files when doing node module resolution#22167
Resolve the modue names with "modulename.json" to json files when doing node module resolution#22167sheetalkamat merged 25 commits intomasterfrom
Conversation
|
This is great! Any chance it will make it into 2.8? |
256ef2a to
2071466
Compare
|
@mhegazy can you please take a look so we can get this in. Thank you. |
sandersn
left a comment
There was a problem hiding this comment.
The binder and checker code look good. I'll have to look at the rest later.
One big question: why doesn't the flag default to true, at least for Javascript files? In fact, why is there a flag at all?
If there is a difference between JS and TS, then there should be a JS-specific test.
sandersn
left a comment
There was a problem hiding this comment.
Some comments and questions, plus I think this should default to true for JS. (Actually, it could probably default to true for TS if our main concern about this feature is about taking too long to compile very large json files.)
| parentOption: string | undefined | ||
| ): any { | ||
| const result: any = {}; | ||
| const result: any = returnValue ? {} : undefined; |
There was a problem hiding this comment.
is this returnValue check just for efficiency, to avoid building up a value that's not needed.
Actually, why isn't it needed, when existing callers needed it?
There was a problem hiding this comment.
We dont need value when we are importing modules: We just want the error reporting on valid json syntax. So creating (potentially large) json object isnt needed hence the flag. In case of config file, we do need json object as well as checks which can be done simultaneously.
There was a problem hiding this comment.
Could you add a comment on the function header?
src/compiler/parser.ts
Outdated
| // Set source file so that errors will be reported with this file name | ||
| sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false); | ||
| const result = <JsonSourceFile>sourceFile; | ||
| const result = sourceFile as JsonSourceFile; |
There was a problem hiding this comment.
The new code modifies sourceFile directly. I don't think result is really used anymore, so I'd just return sourceFile at the end of this function and get rid of result..
| else { | ||
| const statement = createNode(SyntaxKind.ExpressionStatement) as JsonObjectExpressionStatement; | ||
| switch (token()) { | ||
| case SyntaxKind.OpenBracketToken: |
There was a problem hiding this comment.
I take it that we didn't parse top-level arrays, etc, in json files until this change?
There was a problem hiding this comment.
Would it be easier to parseExpression and then have an error for expression kinds that are not legal? I don't think we're checking the contents of, say, parseObjectLiteralExpression either, so there's not actually much point in being strict here.
There was a problem hiding this comment.
The issue in that is we dont do better job with incorrect object literal expression .Eg. There is already test (https://github.com/Microsoft/TypeScript/blob/master/src/harness/unittests/projectErrors.ts#L116) for "files": something} where in the first bracket is missing. We cant default to object literal which would be very likely the contents in json
There was a problem hiding this comment.
Oh, ok. I didn't realise that this approach gives better error recovery.
| @@ -1983,7 +1983,7 @@ namespace ts { | |||
| } | |||
There was a problem hiding this comment.
interesting to see permissions changing from 755 to 644. Any idea why they were 755 before?
There was a problem hiding this comment.
in the title for program.ts, it says that program.ts has 14 lines changed and also changed permissions from 100755 to 100644. I think that is safer since I don't think we can run program.ts from the command line, but I wasn't sure what prompted the change.
|
|
||
| export interface JsonSourceFile extends SourceFile { | ||
| jsonObject?: ObjectLiteralExpression; | ||
| statements: NodeArray<JsonObjectExpressionStatement>; |
There was a problem hiding this comment.
I don't get the reason for this -- ObjectLiteralExpression can contain methods, so JsonObjectExpressionStatement is not actually that restrictive. Why not just use ExpressionStatement here?
Because we dont want to pull in big json files. Its almost too late most of the time when the json could result into big memory. It could be too many options or too long a string. it was discussed in design note to put this under the flag instead. |
sandersn
left a comment
There was a problem hiding this comment.
I think it's ready to go, although I'd still like to know whether JsonSourceFile.statements could have type NodeArray<ExpressionStatement>.
After our in-person discussion, I think it's ok to start with a flag required for both JS and TS files, and perhaps revisit this if there's enough demand for better types in config-free scenarios.
| else { | ||
| const statement = createNode(SyntaxKind.ExpressionStatement) as JsonObjectExpressionStatement; | ||
| switch (token()) { | ||
| case SyntaxKind.OpenBracketToken: |
There was a problem hiding this comment.
Oh, ok. I didn't realise that this approach gives better error recovery.
| @@ -1983,7 +1983,7 @@ namespace ts { | |||
| } | |||
There was a problem hiding this comment.
in the title for program.ts, it says that program.ts has 14 lines changed and also changed permissions from 100755 to 100644. I think that is safer since I don't think we can run program.ts from the command line, but I wasn't sure what prompted the change.
| function emitExpressionStatement(node: ExpressionStatement) { | ||
| emitExpression(node.expression); | ||
| writeSemicolon(); | ||
| if (!isJsonSourceFile(currentSourceFile)) { |
There was a problem hiding this comment.
why would this be called in the first place?
There was a problem hiding this comment.
https://github.com/Microsoft/TypeScript/pull/22167/files#diff-9e36f50e46928676c6142f634cb7cd1bR18 when the output is copied into outDir just like any other js file ?
src/compiler/checker.ts
Outdated
| return links.type = anyType; | ||
| } | ||
| // Handle export default expressions | ||
| if (declaration.kind === SyntaxKind.SourceFile) { |
There was a problem hiding this comment.
isSourceFile(declaration) and save the cast few lines in
| JSDoc = 1 << 21, // If node was parsed inside jsdoc | ||
| /* @internal */ Ambient = 1 << 22, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. | ||
| /* @internal */ InWithStatement = 1 << 23, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) | ||
| JsonFile = 1 << 24, // If node was parsed in a Json |
There was a problem hiding this comment.
https://github.com/Microsoft/TypeScript/pull/22167/files#diff-4b8bd1eea29904f1be39cd864e1a45c0R489 has this as JavaScriptFile isnt it more consitent to use JsonFile ?
|
@Andy-MS can you please review this change. |
|
@sheetalkamat, a couple of comments:
|
|
chatted with @mhegazy offline and the making |
src/compiler/checker.ts
Outdated
| // Handle export default expressions | ||
| if (isSourceFile(declaration)) { | ||
| Debug.assert(isJsonSourceFile(declaration)); | ||
| const jsonSourceFile = <JsonSourceFile>declaration; |
There was a problem hiding this comment.
cast(declaration, isJsonSourceFile) helps avoid the type assertion
src/compiler/diagnosticMessages.json
Outdated
| "code": 6196, | ||
| "reportsUnnecessary": true | ||
| }, | ||
| "Resolve module name imported with '.json' extension to the json source file.": { |
There was a problem hiding this comment.
I think this message could be improved -- not obvious what the alternative is. "Type-check imported '.json' files" might be a better explanation. The same for the error message -- it's not the module resolution that users will care about, but the fact that we typecheck the contents of the file.
There was a problem hiding this comment.
But that's not what it means though.. We wont even look for .json files if the flag is on. So its not just typechecking but picking up the modules?
There was a problem hiding this comment.
How about "Include '.json' files in the program"?
The reason is that module resolution seems like more of an internal issue that most users won't care about. But they will care if their '.json' files are getting compiled or not.
There was a problem hiding this comment.
somehow module needs to be part of the msg since aren't just including any json file?
Include modules imported with '.json' extension ?
There was a problem hiding this comment.
Assuming that .json imports would have failed before, that's a better description.
| parentOption: string | undefined | ||
| ): any { | ||
| const result: any = {}; | ||
| const result: any = returnValue ? {} : undefined; |
There was a problem hiding this comment.
Could you add a comment on the function header?
| * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. | ||
| */ | ||
| function loadModuleFromFile(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { | ||
| if (extensions === Extensions.Json) { |
There was a problem hiding this comment.
Seems like this could be handled along with the bottom case if (hasJavaScriptFileExtension)?
There was a problem hiding this comment.
That just makes too many checks of extension !== Extensions.Json since we dont want to resolve "module" to module.json but only "module.json"
| case Extension.Ts: | ||
| case Extension.Dts: | ||
| case Extension.Json: | ||
| // These are always allowed. |
There was a problem hiding this comment.
Assuming we would only ever get a .json module if the option was set? Maybe could put that in the comment.
src/compiler/utilities.ts
Outdated
| } | ||
| } | ||
|
|
||
| export function getTsConfigPropArrayElementValue(tsConfigSourceFile: TsConfigSourceFile, propKey: string, elementValue: string): StringLiteral { |
src/compiler/utilities.ts
Outdated
|
|
||
| export function getTsConfigPropArrayElementValue(tsConfigSourceFile: TsConfigSourceFile, propKey: string, elementValue: string): StringLiteral { | ||
| const jsonObjectLiteral = getTsConfigObjectLiteralExpression(tsConfigSourceFile); | ||
| if (jsonObjectLiteral) { |
There was a problem hiding this comment.
return jsonObjectLiteral && firstDefined(getPropertyAssignment(jsonObjectLiteral, propKey), property =>
isArrayLiteralExpression(property.initializer)
? find(property.initializer.elements, (element): element is StringLiteral => isStringLiteral(element) && element.text === elementValue)
: undefined);|
|
||
| const useCaseSensitiveFileNames = options.useCaseSensitiveFileNames !== undefined ? options.useCaseSensitiveFileNames : true; | ||
| const programFileNames = inputFiles.map(file => file.unitName); | ||
| const programFileNames = inputFiles.map(file => file.unitName).filter(fileName => !ts.fileExtensionIs(fileName, ts.Extension.Json)); |
There was a problem hiding this comment.
Because json files arent provided as fileNames but only picked during module resolution
| if (!cfg) return; | ||
| const oldFile = cfg.jsonObject && getFilesEntry(cfg.jsonObject, oldFilePath); | ||
| const configFile = program.getCompilerOptions().configFile; | ||
| const oldFile = getTsConfigPropArrayElementValue(configFile, "files", oldFilePath); |
There was a problem hiding this comment.
Nit: That function isn't declared to accept undefined inputs
| } | ||
|
|
||
| if (options.resolveJsonModule) { | ||
| if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs) { |
There was a problem hiding this comment.
what about --module? do not think we should allow this for --module ES2015 for example..
There was a problem hiding this comment.
Clarified offline. At present --moduleResolution specified takes preferences for resolution irrespective of --module and since there is no change in there, no error needs to be reported specially.
mhegazy
left a comment
There was a problem hiding this comment.
One comment about --module and --resolveJsonModule relationship.
Fixes #7071