Use parser to parse tsconfig json instead of using Json.parse#12336
Use parser to parse tsconfig json instead of using Json.parse#12336sheetalkamat merged 34 commits intomasterfrom
Conversation
This fixes build of typings installer
Also handle the JsonNode when converting to parsedCommandLine
68cc8fa to
0dd944a
Compare
src/compiler/diagnosticMessages.json
Outdated
| "category": "Error", | ||
| "code": 1320 | ||
| }, | ||
| "String, number, object, array, true, false or null expected.": { |
There was a problem hiding this comment.
how about: A property value can only string literal, numeric literal, 'true', 'false', 'null', object literal or array literal.
src/compiler/diagnosticMessages.json
Outdated
| "category": "Error", | ||
| "code": 1319 | ||
| }, | ||
| "String literal with double quotes expected.": { |
There was a problem hiding this comment.
how about: A property name must be wrapped in double quotes.
There was a problem hiding this comment.
This is not just for the property name though. It could be value that is string literal. Json file can only accept string literals that are double quoted
| sourceFile.endOfFileToken = <EndOfFileToken>parseTokenNode(); | ||
| } | ||
| else if (token() === SyntaxKind.OpenBraceToken || | ||
| lookAhead(() => token() === SyntaxKind.StringLiteral)) { |
There was a problem hiding this comment.
does this happen often? i am assuming this is to handle [ "compilerOptions" : ..., but do we need to ?
There was a problem hiding this comment.
This is to handle one of the failing test case which was used to sanitize the tsconfig.json
There was a problem hiding this comment.
Could you give a comment why we need a look ahead to be string literal? also would the the property is allow to be identifier since we use parseObjectLiteralExpression to parse the object?
There was a problem hiding this comment.
According to ECMA-404, the key for the property of a JSON object must be a string literal.
| * @param jsonText The text of the config file | ||
| */ | ||
| export function parseConfigFileTextToJson(fileName: string, jsonText: string, stripComments = true): { config?: any; error?: Diagnostic } { | ||
| export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { |
There was a problem hiding this comment.
this is a public method, so i would leave the optional parameter to avoid breaking users who pass it along.
There was a problem hiding this comment.
What do we do if strip comments is false?
src/compiler/commandLineParser.ts
Outdated
| * Read tsconfig.json file | ||
| * @param fileName The path to the config file | ||
| */ | ||
| export function readConfigFileToJsonSourceFile(fileName: string, readFile: (path: string) => string): JsonSourceFile { |
There was a problem hiding this comment.
how about readJsonConfigFile.
| export interface TsConfigOnlyOption extends CommandLineOptionBase { | ||
| type: "object"; | ||
| optionDeclarations?: CommandLineOption[]; | ||
| extraKeyDiagnosticMessage?: DiagnosticMessage; |
There was a problem hiding this comment.
just report one message unknown option '{0}' for all of the top level nodes.
There was a problem hiding this comment.
Hmm.. currently we report unknown compiler option | unknown type acquisition option. I tried to keep the same behavior. Is it ok to change the behavior?
src/compiler/commandLineParser.ts
Outdated
| { | ||
| name: "compilerOptions", | ||
| type: "object", | ||
| optionDeclarations: optionDeclarations, |
There was a problem hiding this comment.
Ok, here is an idea, just merge them all in one big object, and make type accept an array of CommandlineOptions along with string, number, and list. then for the maps, we can switch them to have a new type called "map", and an optional mapElements member, similar to how we handle list today.
There was a problem hiding this comment.
you can also just keep the optionsDeclarations, but can we just combine all the options in one object, instead of two separate hierarchies?
There was a problem hiding this comment.
But those are two different hierarchies isn't it? The options specified here can only be present at root of json object and other command line options are possible only inside object called compiler Options?
src/compiler/commandLineParser.ts
Outdated
| * @param jsonNode | ||
| * @param errors | ||
| */ | ||
| export function convertToJson(sourceFile: JsonSourceFile, errors: Diagnostic[]): any { |
There was a problem hiding this comment.
nit. i would say these functions should be convert*ToObject and not to json.
src/compiler/commandLineParser.ts
Outdated
| compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); | ||
| } | ||
| else { | ||
| options = getDefaultCompilerOptions(configFileName); |
There was a problem hiding this comment.
Combined with my other suggestion of having one options definition, can we just parse the whole file, and generate diagnostics for all known options, then extract them here afterwards?
| } | ||
|
|
||
| function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string) { | ||
| createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2); |
There was a problem hiding this comment.
it would be better to call the same createDiagnosticForOption twice, once for each option, instead of passing the second key through out the whole stack.
There was a problem hiding this comment.
This avoid iterating in compiler options object literal multiple times to find those two options. We have most of the error messages reported on 2 options and there are very select messages that are reported on single option so it seems ok to pass that undefined for those few scenarios?
…ctual json object, allowing to continue compilation even if there are errors in the tsconfig files
|
The build is failing because of incorrect version of tslint. |
| sourceFiles: SourceFile[]; | ||
| } | ||
|
|
||
| export interface JsonSourceFile extends SourceFile { |
There was a problem hiding this comment.
This shouldn't extend from SourceFile. There's an existing SourceFileLike interface, could you use that? Or just create a SourceFileBase with common properties.
There was a problem hiding this comment.
- I don't really understand why some functions need to be moved, but if it works, 👍
- Be sure to test that we give an error for trailing commas.
- We should also have a test for trying to use single quotes or unquoted properties, or a spread property.
- The absolute nittiest of nits: We should allow \u2028 in json files.
| /** | ||
| * Convert the json syntax tree into the json value | ||
| */ | ||
| export function convertToObject(sourceFile: JsonSourceFile, errors: Diagnostic[]): any { |
There was a problem hiding this comment.
Should this be @internal?
Also, I'd recommend using errors: Push<Diagnostic>, since it's really an output, not an input.
It would be a good idea to separate parse tree -> object conversion and options validation. This function does both at once.
| errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnosticMessage, keyText)); | ||
| } | ||
| const value = convertPropertyValueToJson(element.initializer, option); | ||
| if (typeof keyText !== undefined && typeof value !== undefined) { |
There was a problem hiding this comment.
This was merged like that??
Looks like an overlooked bug.
Surely it was meant to be typeof keyText !== 'undefined' and same for value.
There was a problem hiding this comment.
@sheetalkamat @Andy-MS can you please have a look? This looks like a bug.
Thanks!
| let extendedConfigPath: Path; | ||
|
|
||
| const optionsIterator: JsonConversionNotifier = { | ||
| onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { |
There was a problem hiding this comment.
Type annotations on parameters are not necessary, those should be inherited from JsonConversionNotifier.
| if (!isDoubleQuotedString(valueExpression)) { | ||
| errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); | ||
| } | ||
| reportInvalidOptionValue(option && (typeof option.type === "string" && option.type !== "string")); |
There was a problem hiding this comment.
Nit: refactor to check option && typeof option.type === "string" only once.
| // vs what we set in the json | ||
| // If need arises, we can modify this interface and callbacks as needed | ||
| if (option) { | ||
| const { elementOptions, extraKeyDiagnosticMessage, name: optionName } = <TsConfigOnlyOption>option; |
There was a problem hiding this comment.
This cast isn't valid. Someone might have passed an object literal to an option that shouldn't have an object value. We will report an error, but still continue on to this point.
| return result; | ||
| } | ||
|
|
||
| hasConfigFile(configFilePath: NormalizedPath) { |
There was a problem hiding this comment.
I would prefer isConfigFile -- it doesn't return whether the project has a config file, it returns whether the parameter is the path to one of the project's config files.
| // if current structure version is the same - return info without any changes | ||
| if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) { | ||
| return { info, projectErrors: this.projectErrors }; | ||
| return { info, projectErrors: this.getGlobalProjectErrors() }; |
There was a problem hiding this comment.
Can you explain the change here? It's gone from erporting every error to returning only errors with no associated file. (What errors would those be?)
| } | ||
|
|
||
| private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: Diagnostic[]) { | ||
| return diagnostics.map(d => <protocol.DiagnosticWithLinePosition>{ |
| return false; | ||
| } | ||
|
|
||
| export function cloneCompilerOptions(options: CompilerOptions): CompilerOptions { |
There was a problem hiding this comment.
Maybe we should just make sure clone correctly handles enumerability?
| { | ||
| const actual = ts.parseJsonConfigFileContent(json, host, basePath, existingOptions, configFileName, resolutionStack); | ||
| expected.errors = map(expected.errors, error => { | ||
| return <Diagnostic>{ |
| @@ -0,0 +1,180 @@ | |||
| /* @internal */ | |||
There was a problem hiding this comment.
I like that these function are pulled from factory.ts but would get @rbuckton opinion as well.
There was a problem hiding this comment.
I'm curious as to why they needed to be moved.
| sourceFile.endOfFileToken = <EndOfFileToken>parseTokenNode(); | ||
| } | ||
| else if (token() === SyntaxKind.OpenBraceToken || | ||
| lookAhead(() => token() === SyntaxKind.StringLiteral)) { |
There was a problem hiding this comment.
Could you give a comment why we need a look ahead to be string literal? also would the the property is allow to be identifier since we use parseObjectLiteralExpression to parse the object?
| } | ||
|
|
||
| function getLanguageVariant(scriptKind: ScriptKind) { | ||
| // .tsx and .jsx files are treated as jsx language variant. |
There was a problem hiding this comment.
this comment probably should be removed
| function getLanguageVariant(scriptKind: ScriptKind) { | ||
| // .tsx and .jsx files are treated as jsx language variant. | ||
| return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS ? LanguageVariant.JSX : LanguageVariant.Standard; | ||
| return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard; |
There was a problem hiding this comment.
Should we LanguageVariant.JSX be renamed ? since it is more than just TSX and JSX
|
|
||
| export interface JsonSourceFile extends SourceFile { | ||
| jsonObject?: ObjectLiteralExpression; | ||
| extendedSourceFiles?: string[]; |
There was a problem hiding this comment.
Is this another tsconfig. this tsconfig extends from??
|
@sheetalkamat let's get this ready to be merged |
| } | ||
|
|
||
| export interface JsonSourceFile extends SourceFile { | ||
| jsonObject?: ObjectLiteralExpression; |
There was a problem hiding this comment.
While it makes sense for parsing tsconfig.json, JSON isn't necessarily always an object literal. In the lib reference PR, I started using ts.parseConfigFileTextToJson to parse a single libs.json file shared between the gulpfile and jakefile. I'd have to make some changes since libs.json has an array as its root.
| @@ -0,0 +1,180 @@ | |||
| /* @internal */ | |||
There was a problem hiding this comment.
I'm curious as to why they needed to be moved.
| /** | ||
| * Gets flags that control emit behavior of a node. | ||
| */ | ||
| export function getEmitFlags(node: Node): EmitFlags | undefined { |
There was a problem hiding this comment.
Why was this moved here? All of the other emitNode related functions are still in factory, including the corresponding setEmitFlags function.
| return node.kind === SyntaxKind.ParenthesizedExpression; | ||
| } | ||
|
|
||
| export function skipPartiallyEmittedExpressions(node: Expression): Expression; |
There was a problem hiding this comment.
Why was this moved? All of the other related skip functions are still in factory.ts.
| sourceFile.endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, /*reportAtCurrentPosition*/ false, Diagnostics.Unexpected_token); | ||
| } | ||
| else { | ||
| parseExpected(SyntaxKind.OpenBraceToken); |
There was a problem hiding this comment.
If we are only planning on using this to parse tsconfig.json, then this is fine. If we are planning on using this to parse other JSON files, there are other legal root objects for a JSON file (e.g. Array Literals, string literals, numeric literals, true, false, and null).
|
Is there a written up rationale for this change anywhere? I can think of several reasons to do custom-parse JSON (and a few reasons not to as well!) — so it would be very helpful to clarify the intention here. Or add a link to the detailed explanation, if it's already mentioned. Many thanks! |
|
It is part of the Roadmap for 2.4 in order to provide better error reporting for |
|
This will probably be going into the 2.4.2 which is slated for next month. |
parseJsonTextand returnJsonSourceFile- syntax treeconvertToJsonthat convertsJsonSourceFiletojsonparseJsonSourceFileConfigFileContentthat takes thisJsonSourceFileto convert to actualParsedCommandLineand convert theJsonSourceFiletojsonin one shot so there are error locations for the invalid json options in thetsconfig.jsonJsonSourceFileis stored asconfigFileincompilerOptionsso the options related diagnostics can be reported in the actual tsconfig file