Typescript: Consider JSON5 for config (e.g. trailing commas)

Created on 14 Nov 2016  路  13Comments  路  Source: microsoft/TypeScript

TypeScript Version: 2.0.9

Hi there,

Great work on TypeScript so far! A lot of thought has clearly been put into it, particularly the developer experience, so thank you.

A nice example is that tsconfig.json allows comments. This is really valuable.

I'm curious, though: instead of stripping out comments manually, would you guys be open to considering JSON5 for achieving both that as well as other niceties?

Trailing commas would be the big one to me: when I'm experimenting with new compiler options, that keeps tripping me up. More flexible quoting would also be nice, but less of a big deal.

Big disclaimer: I'm the original author of JSON5, though others maintain it these days, and it's reasonably popular on its own. (E.g. used by Babel and React Native for their config files.)

Thanks for the consideration. Cheers.

(Edit: FYI I just made a similar request for tslint.json: https://github.com/palantir/tslint/issues/1721)

Fixed Suggestion

Most helpful comment

Then every tool will need to implement this as well in order to parse the config file.
If that's the route to go down it's worth considering a clean break from the format http://vpzomtrrfrt.github.io/DSON/

All 13 comments

We do have a JSON parser already :) , so no need to use external libraries.

Cool, so consider this a request for at least trailing commas then. =) Thank you!

Then every tool will need to implement this as well in order to parse the config file.
If that's the route to go down it's worth considering a clean break from the format http://vpzomtrrfrt.github.io/DSON/

The compiler will allow for better error recovery, and will report JSON errors as warnings. users could choose to ignore them, and then other tools will not work. but at least they can get useful error messages, specially in the IDE.

Eslint supports configuration files in JavaScript format. I think this is good solution, because:

  • It's simple. You don't need learn new format.
  • You can add comments, trailing commas or any other logic (e.g. dynamically build includes).
  • Don't need additional libraries for parsing.
  • Clear error messages for invalid js (unlike JSON.parse()).

Doesn't TypeScript already support JSON5 config?

It's actually confusing, for both humans and machines, to use the .json file extension while deviating from the spec. I'd prefer going with .json5 or even just a .js config file.

Ideally, TypeScript should allow multiple formats for the config file: tsconfig.{yml,yaml,js,json}

Dynamic configuration would be possible:

tsconfig.js:

const prod = process.env.NODE_ENV == 'production'
module.exports = {
  compilerOptions: {
    sourceMap: !prod,
  },
}

Or a more terse format could be read:

tsconfig.yaml:

compilerOptions:
  sourceMap: true

I think that ESLint Configuration handles this beautifully:

Configuration Files - use a JavaScript, JSON or YAML file to specify configuration information for an entire directory and all of its subdirectories. This can be in the form of an .eslintrc.* file or an eslintConfig field in a package.json file, both of which ESLint will look for and read automatically, or you can specify a configuration file on the command line.

@jrop I don't see the need for YAML support, that doesn't add any new capabilities. On the other hand JavaScript support would be very useful but opens up a huge can of worms.

@aluanhaddad You are correct that is does not add any new capabilities. I think what people are asking for is added user-experience, not added functional capabilities. YAML is designed for human readability and is adopted by many projects. ESLint has one of the best user-experiences in my mind, and I wish more projects would learn from it.

I am curious what you mean by saying that JavaScript support would open up a huge can of worms? The configuration loader would potentially just be (not thought-out, unelegant pseudo-code ahead):

function getTsConfig() {
  if (exists('./tsconfig.json'))
    return jsonParser(read('./tsconfig.json'))
  else if (exists('./tsconfig.js'))
    return require('./tsconfig.js')
}

I just fiddled around with this, and it was pretty easy to get "working". By working I mean it showed some level of promise, but the code is gag-worthy. Namely, I did not take time to deduce how 1) tsc.ts does imports so that I could import js-yaml, json5, and glob or 2) how to execute path.parse(...). As an investigative step, I used eval's 馃槧. Don't judge me.

diff --git a/package.json b/package.json
index c62fa46853..cf54a00e26 100644
--- a/package.json
+++ b/package.json
@@ -63,6 +63,8 @@
         "into-stream": "latest",
         "istanbul": "latest",
         "jake": "latest",
+        "js-yaml": "^3.8.4",
+        "json5": "^0.5.1",
         "merge2": "latest",
         "minimist": "latest",
         "mkdirp": "latest",
diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts
index 834c7326e2..f2882cb850 100644
--- a/src/compiler/tsc.ts
+++ b/src/compiler/tsc.ts
@@ -158,7 +158,8 @@ namespace ts {

             const fileOrDirectory = normalizePath(commandLine.options.project);
             if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) {
-                configFileName = combinePaths(fileOrDirectory, "tsconfig.json");
+                const glob = eval(`require('glob')`);
+                [configFileName] = glob.sync(combinePaths(fileOrDirectory, "tsconfig.*"))
                 if (!sys.fileExists(configFileName)) {
                     reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project), /* host */ undefined);
                     return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
@@ -205,6 +206,20 @@ namespace ts {
         performCompilation();

         function parseConfigFile(): ParsedCommandLine {
+            // eval(`console.log('!!! Parsing', configFileName)`)
+            const parts = configFileName.split(/\\|\//)
+            const baseName = parts[parts.length - 1]
+            const baseParts = baseName.split('.')
+            const ext = baseParts[baseParts.length - 1]
+            const JSON5 = eval(`require('json5')`)
+            const YAML = eval(`require('js-yaml')`)
+            const parsers = {
+                js:    () => eval(`require(configFileName)`),
+                json:  (s: string) => parseConfigFileTextToJson(configFileName, s, true),
+                json5: (s: string) => ({config: JSON5.parse(s)}),
+                yaml:  (s: string) => ({config: YAML.safeLoad(s)}),
+                yml:   (s: string) => ({config: YAML.safeLoad(s)}),
+            }
             if (!cachedConfigFileText) {
                 try {
                     cachedConfigFileText = sys.readFile(configFileName);
@@ -223,7 +238,7 @@ namespace ts {
                 return;
             }

-            const result = parseConfigFileTextToJson(configFileName, cachedConfigFileText);
+            const result = (parsers as any)[ext](cachedConfigFileText)
             const configObject = result.config;
             if (!configObject) {
                 reportDiagnostics([result.error], /* compilerHost */ undefined);

@jrop believe me, I would _love_ tsconfig.js (or even better tsconfig.ts). It would improve the quality of my life 馃樄. I was just trying to think of reasons not to do it and the most obvious one was trying to diagnose why someone's compile was failing or their IDE was locked up due to synthetic config containing arbitrary code. I would do a lot of conditional manipulations of paths and I would probably call fs.readDir a number of times.

we now our own json parser to parse the json file so this should be resolved as of now.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

remojansen picture remojansen  路  3Comments

dlaberge picture dlaberge  路  3Comments

bgrieder picture bgrieder  路  3Comments

manekinekko picture manekinekko  路  3Comments

weswigham picture weswigham  路  3Comments