summaryrefslogtreecommitdiff
path: root/cli/tsc_config.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tsc_config.rs')
-rw-r--r--cli/tsc_config.rs236
1 files changed, 236 insertions, 0 deletions
diff --git a/cli/tsc_config.rs b/cli/tsc_config.rs
new file mode 100644
index 000000000..e5f7bcdc4
--- /dev/null
+++ b/cli/tsc_config.rs
@@ -0,0 +1,236 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::ErrBox;
+use jsonc_parser::JsonValue;
+use serde::Deserialize;
+use serde_json::Value;
+use std::collections::HashMap;
+use std::fmt;
+use std::str::FromStr;
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct IgnoredCompilerOptions(pub Vec<String>);
+
+impl fmt::Display for IgnoredCompilerOptions {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut codes = self.0.clone();
+ codes.sort();
+ write!(f, "{}", codes.join(", "))?;
+
+ Ok(())
+ }
+}
+
+/// A static slice of all the compiler options that should be ignored that
+/// either have no effect on the compilation or would cause the emit to not work
+/// in Deno.
+const IGNORED_COMPILER_OPTIONS: [&str; 61] = [
+ "allowSyntheticDefaultImports",
+ "allowUmdGlobalAccess",
+ "assumeChangesOnlyAffectDirectDependencies",
+ "baseUrl",
+ "build",
+ "composite",
+ "declaration",
+ "declarationDir",
+ "declarationMap",
+ "diagnostics",
+ "downlevelIteration",
+ "emitBOM",
+ "emitDeclarationOnly",
+ "esModuleInterop",
+ "extendedDiagnostics",
+ "forceConsistentCasingInFileNames",
+ "generateCpuProfile",
+ "help",
+ "importHelpers",
+ "incremental",
+ "inlineSourceMap",
+ "inlineSources",
+ "init",
+ "listEmittedFiles",
+ "listFiles",
+ "mapRoot",
+ "maxNodeModuleJsDepth",
+ "module",
+ "moduleResolution",
+ "newLine",
+ "noEmit",
+ "noEmitHelpers",
+ "noEmitOnError",
+ "noLib",
+ "noResolve",
+ "out",
+ "outDir",
+ "outFile",
+ "paths",
+ "preserveConstEnums",
+ "preserveSymlinks",
+ "preserveWatchOutput",
+ "pretty",
+ "reactNamespace",
+ "resolveJsonModule",
+ "rootDir",
+ "rootDirs",
+ "showConfig",
+ "skipDefaultLibCheck",
+ "skipLibCheck",
+ "sourceMap",
+ "sourceRoot",
+ "stripInternal",
+ "target",
+ "traceResolution",
+ "tsBuildInfoFile",
+ "types",
+ "typeRoots",
+ "useDefineForClassFields",
+ "version",
+ "watch",
+];
+
+/// A function that works like JavaScript's `Object.assign()`.
+pub fn json_merge(a: &mut Value, b: &Value) {
+ match (a, b) {
+ (&mut Value::Object(ref mut a), &Value::Object(ref b)) => {
+ for (k, v) in b {
+ json_merge(a.entry(k.clone()).or_insert(Value::Null), v);
+ }
+ }
+ (a, b) => {
+ *a = b.clone();
+ }
+ }
+}
+
+/// Convert a jsonc libraries `JsonValue` to a serde `Value`.
+fn jsonc_to_serde(j: JsonValue) -> Value {
+ match j {
+ JsonValue::Array(arr) => {
+ let vec = arr.into_iter().map(jsonc_to_serde).collect();
+ Value::Array(vec)
+ }
+ JsonValue::Boolean(bool) => Value::Bool(bool),
+ JsonValue::Null => Value::Null,
+ JsonValue::Number(num) => {
+ let number =
+ serde_json::Number::from_str(&num).expect("could not parse number");
+ Value::Number(number)
+ }
+ JsonValue::Object(obj) => {
+ let mut map = serde_json::map::Map::new();
+ for (key, json_value) in obj.into_iter() {
+ map.insert(key, jsonc_to_serde(json_value));
+ }
+ Value::Object(map)
+ }
+ JsonValue::String(str) => Value::String(str),
+ }
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct TSConfigJson {
+ compiler_options: Option<HashMap<String, Value>>,
+ exclude: Option<Vec<String>>,
+ extends: Option<String>,
+ files: Option<Vec<String>>,
+ include: Option<Vec<String>>,
+ references: Option<Value>,
+ type_acquisition: Option<Value>,
+}
+
+pub fn parse_raw_config(config_text: &str) -> Result<Value, ErrBox> {
+ assert!(!config_text.is_empty());
+ let jsonc = jsonc_parser::parse_to_value(config_text)?.unwrap();
+ Ok(jsonc_to_serde(jsonc))
+}
+
+/// Take a string of JSONC, parse it and return a serde `Value` of the text.
+/// The result also contains any options that were ignored.
+pub fn parse_config(
+ config_text: &str,
+) -> Result<(Value, Option<IgnoredCompilerOptions>), ErrBox> {
+ assert!(!config_text.is_empty());
+ let jsonc = jsonc_parser::parse_to_value(config_text)?.unwrap();
+ let config: TSConfigJson = serde_json::from_value(jsonc_to_serde(jsonc))?;
+ let mut compiler_options: HashMap<String, Value> = HashMap::new();
+ let mut items: Vec<String> = Vec::new();
+
+ if let Some(in_compiler_options) = config.compiler_options {
+ for (key, value) in in_compiler_options.iter() {
+ if IGNORED_COMPILER_OPTIONS.contains(&key.as_str()) {
+ items.push(key.to_owned());
+ } else {
+ compiler_options.insert(key.to_owned(), value.to_owned());
+ }
+ }
+ }
+ let options_value = serde_json::to_value(compiler_options)?;
+ let ignored_options = if !items.is_empty() {
+ Some(IgnoredCompilerOptions(items))
+ } else {
+ None
+ };
+
+ Ok((options_value, ignored_options))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_json::json;
+
+ #[test]
+ fn test_json_merge() {
+ let mut value_a = json!({
+ "a": true,
+ "b": "c"
+ });
+ let value_b = json!({
+ "b": "d",
+ "e": false,
+ });
+ json_merge(&mut value_a, &value_b);
+ assert_eq!(
+ value_a,
+ json!({
+ "a": true,
+ "b": "d",
+ "e": false,
+ })
+ );
+ }
+
+ #[test]
+ fn test_parse_config() {
+ let config_text = r#"{
+ "compilerOptions": {
+ "build": true,
+ // comments are allowed
+ "strict": true
+ }
+ }"#;
+ let (options_value, ignored) =
+ parse_config(config_text).expect("error parsing");
+ assert!(options_value.is_object());
+ let options = options_value.as_object().unwrap();
+ assert!(options.contains_key("strict"));
+ assert_eq!(options.len(), 1);
+ assert_eq!(
+ ignored,
+ Some(IgnoredCompilerOptions(vec!["build".to_string()])),
+ );
+ }
+
+ #[test]
+ fn test_parse_raw_config() {
+ let invalid_config_text = r#"{
+ "compilerOptions": {
+ // comments are allowed
+ }"#;
+ let errbox = parse_raw_config(invalid_config_text).unwrap_err();
+ assert!(errbox
+ .to_string()
+ .starts_with("Unterminated object on line 1"));
+ }
+}