-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Use parser to parse tsconfig json instead of using Json.parse #12336
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about: A property value can only string literal, numeric literal, 'true', 'false', 'null', object literal or array literal.
src/compiler/diagnosticMessages.json
Outdated
@@ -859,6 +859,14 @@ | |||
"category": "Error", | |||
"code": 1319 | |||
}, | |||
"String literal with double quotes expected.": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about: A property name must be wrapped in double quotes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this happen often? i am assuming this is to handle [ "compilerOptions" : ...
, but do we need to ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to ECMA-404, the key for the property of a JSON object must be a string literal.
src/compiler/commandLineParser.ts
Outdated
@@ -710,13 +714,259 @@ namespace ts { | |||
* @param fileName The path to the config file | |||
* @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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about readJsonConfigFile
.
src/compiler/types.ts
Outdated
@@ -3389,6 +3395,8 @@ | |||
/* @internal */ | |||
export interface TsConfigOnlyOption extends CommandLineOptionBase { | |||
type: "object"; | |||
optionDeclarations?: CommandLineOption[]; | |||
extraKeyDiagnosticMessage?: DiagnosticMessage; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just report one message unknown option '{0}'
for all of the top level nodes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
@@ -2365,6 +2365,11 @@ namespace ts { | |||
sourceFiles: SourceFile[]; | |||
} | |||
|
|||
export interface JsonSourceFile extends SourceFile { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean keyText !== undefined
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was merged like that??
Looks like an overlooked bug.
https://github.com/Microsoft/TypeScript/blob/master/src/compiler/commandLineParser.ts#L1056
Surely it was meant to be typeof keyText !== 'undefined'
and same for value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -657,7 +694,7 @@ namespace ts.server { | |||
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) { | |||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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>{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Casted object literal
@@ -1011,6 +1011,12 @@ namespace ts { | |||
return false; | |||
} | |||
|
|||
export function cloneCompilerOptions(options: CompilerOptions): CompilerOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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>{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Casted object literal
@@ -0,0 +1,180 @@ | |||
/* @internal */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that these function are pulled from factory.ts
but would get @rbuckton opinion as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
clearState(); | ||
return result; | ||
} | ||
|
||
function getLanguageVariant(scriptKind: ScriptKind) { | ||
// .tsx and .jsx files are treated as jsx language variant. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we LanguageVariant.JSX
be renamed ? since it is more than just TSX and JSX
@@ -2365,6 +2365,11 @@ namespace ts { | |||
sourceFiles: SourceFile[]; | |||
} | |||
|
|||
export interface JsonSourceFile extends SourceFile { | |||
jsonObject?: ObjectLiteralExpression; | |||
extendedSourceFiles?: string[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this another tsconfig. this tsconfig extends from??
@sheetalkamat let's get this ready to be merged |
@@ -2365,6 +2365,11 @@ namespace ts { | |||
sourceFiles: SourceFile[]; | |||
} | |||
|
|||
export interface JsonSourceFile extends SourceFile { | |||
jsonObject?: ObjectLiteralExpression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this moved here? All of the other emitNode
related functions are still in factory, including the corresponding setEmitFlags
function.
@@ -4215,6 +4158,16 @@ namespace ts { | |||
return node.kind === SyntaxKind.ParenthesizedExpression; | |||
} | |||
|
|||
export function skipPartiallyEmittedExpressions(node: Expression): Expression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
parseJsonText
and returnJsonSourceFile
- syntax treeconvertToJson
that convertsJsonSourceFile
tojson
parseJsonSourceFileConfigFileContent
that takes thisJsonSourceFile
to convert to actualParsedCommandLine
and convert theJsonSourceFile
tojson
in one shot so there are error locations for the invalid json options in thetsconfig.json
JsonSourceFile
is stored asconfigFile
incompilerOptions
so the options related diagnostics can be reported in the actual tsconfig file