diff --git a/cli/BUILD.gn b/cli/BUILD.gn index 7bf34dec34a518..dc11f3b0b8407a 100644 --- a/cli/BUILD.gn +++ b/cli/BUILD.gn @@ -52,9 +52,10 @@ ts_sources = [ "../js/buffer.ts", "../js/build.ts", "../js/chmod.ts", - "../js/console_table.ts", + "../js/colors.ts", "../js/compiler.ts", "../js/console.ts", + "../js/console_table.ts", "../js/copy_file.ts", "../js/core.ts", "../js/custom_event.ts", diff --git a/cli/compiler.rs b/cli/compiler.rs index d327835d33fbdb..522002b0b02363 100644 --- a/cli/compiler.rs +++ b/cli/compiler.rs @@ -158,6 +158,23 @@ fn req(specifier: &str, referrer: &str, cmd_id: u32) -> Buf { .into_boxed_bytes() } +/// Returns an optional tuple which represents the state of the compiler +/// configuration where the first is canonical name for the configuration file +/// and a vector of the bytes of the contents of the configuration file. +pub fn get_compiler_config( + parent_state: &ThreadSafeState, + _compiler_type: &str, +) -> Option<(String, Vec)> { + // The compiler type is being passed to make it easier to implement custom + // compilers in the future. + match (&parent_state.config_path, &parent_state.config) { + (Some(config_path), Some(config)) => { + Some((config_path.to_string(), config.to_vec())) + } + _ => None, + } +} + pub fn compile_async( parent_state: ThreadSafeState, specifier: &str, @@ -306,4 +323,12 @@ mod tests { assert_eq!(parse_cmd_id(res_json), cmd_id); } + + #[test] + fn test_get_compiler_config_no_flag() { + let compiler_type = "typescript"; + let state = ThreadSafeState::mock(); + let out = get_compiler_config(&state, compiler_type); + assert_eq!(out, None); + } } diff --git a/cli/deno_dir.rs b/cli/deno_dir.rs index 5dc9afed3663f5..e38d454e1b4820 100644 --- a/cli/deno_dir.rs +++ b/cli/deno_dir.rs @@ -51,12 +51,18 @@ pub struct DenoDir { // This splits to http and https deps pub deps_http: PathBuf, pub deps_https: PathBuf, + /// The active configuration file contents (or empty array) which applies to + /// source code cached by `DenoDir`. + pub config: Vec, } impl DenoDir { // Must be called before using any function from this module. // https://github.com/denoland/deno/blob/golang/deno_dir.go#L99-L111 - pub fn new(custom_root: Option) -> std::io::Result { + pub fn new( + custom_root: Option, + state_config: &Option>, + ) -> std::io::Result { // Only setup once. let home_dir = dirs::home_dir().expect("Could not get home directory."); let fallback = home_dir.join(".deno"); @@ -73,12 +79,22 @@ impl DenoDir { let deps_http = deps.join("http"); let deps_https = deps.join("https"); + // Internally within DenoDir, we use the config as part of the hash to + // determine if a file has been transpiled with the same configuration, but + // we have borrowed the `State` configuration, which we want to either clone + // or create an empty `Vec` which we will use in our hash function. + let config = match state_config { + Some(config) => config.clone(), + _ => b"".to_vec(), + }; + let deno_dir = Self { root, gen, deps, deps_http, deps_https, + config, }; // TODO Lazily create these directories. @@ -102,7 +118,8 @@ impl DenoDir { filename: &str, source_code: &[u8], ) -> (PathBuf, PathBuf) { - let cache_key = source_code_hash(filename, source_code, version::DENO); + let cache_key = + source_code_hash(filename, source_code, version::DENO, &self.config); ( self.gen.join(cache_key.to_string() + ".js"), self.gen.join(cache_key.to_string() + ".js.map"), @@ -156,6 +173,11 @@ impl DenoDir { let gen = self.gen.clone(); + // If we don't clone the config, we then end up creating an implied lifetime + // which gets returned in the future, so we clone here so as to not leak the + // move below when the future is resolving. + let config = self.config.clone(); + Either::B( get_source_code_async( self, @@ -191,8 +213,12 @@ impl DenoDir { return Ok(out); } - let cache_key = - source_code_hash(&out.filename, &out.source_code, version::DENO); + let cache_key = source_code_hash( + &out.filename, + &out.source_code, + version::DENO, + &config, + ); let (output_code_filename, output_source_map_filename) = ( gen.join(cache_key.to_string() + ".js"), gen.join(cache_key.to_string() + ".js.map"), @@ -468,15 +494,19 @@ fn load_cache2( Ok((read_output_code, read_source_map)) } +/// Generate an SHA1 hash for source code, to be used to determine if a cached +/// version of the code is valid or invalid. fn source_code_hash( filename: &str, source_code: &[u8], version: &str, + config: &[u8], ) -> String { let mut ctx = ring::digest::Context::new(&ring::digest::SHA1); ctx.update(version.as_bytes()); ctx.update(filename.as_bytes()); ctx.update(source_code); + ctx.update(config); let digest = ctx.finish(); let mut out = String::new(); // TODO There must be a better way to do this... @@ -860,8 +890,9 @@ mod tests { fn test_setup() -> (TempDir, DenoDir) { let temp_dir = TempDir::new().expect("tempdir fail"); - let deno_dir = - DenoDir::new(Some(temp_dir.path().to_path_buf())).expect("setup fail"); + let config = Some(b"{}".to_vec()); + let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf()), &config) + .expect("setup fail"); (temp_dir, deno_dir) } @@ -904,7 +935,8 @@ mod tests { let (temp_dir, deno_dir) = test_setup(); let filename = "hello.js"; let source_code = b"1+2"; - let hash = source_code_hash(filename, source_code, version::DENO); + let config = b"{}"; + let hash = source_code_hash(filename, source_code, version::DENO, config); assert_eq!( ( temp_dir.path().join(format!("gen/{}.js", hash)), @@ -914,6 +946,24 @@ mod tests { ); } + #[test] + fn test_cache_path_config() { + // We are changing the compiler config from the "mock" and so we expect the + // resolved files coming back to not match the calculated hash. + let (temp_dir, deno_dir) = test_setup(); + let filename = "hello.js"; + let source_code = b"1+2"; + let config = b"{\"compilerOptions\":{}}"; + let hash = source_code_hash(filename, source_code, version::DENO, config); + assert_ne!( + ( + temp_dir.path().join(format!("gen/{}.js", hash)), + temp_dir.path().join(format!("gen/{}.js.map", hash)) + ), + deno_dir.cache_path(filename, source_code) + ); + } + #[test] fn test_code_cache() { let (_temp_dir, deno_dir) = test_setup(); @@ -922,7 +972,8 @@ mod tests { let source_code = b"1+2"; let output_code = b"1+2 // output code"; let source_map = b"{}"; - let hash = source_code_hash(filename, source_code, version::DENO); + let config = b"{}"; + let hash = source_code_hash(filename, source_code, version::DENO, config); let (cache_path, source_map_path) = deno_dir.cache_path(filename, source_code); assert!(cache_path.ends_with(format!("gen/{}.js", hash))); @@ -949,23 +1000,23 @@ mod tests { #[test] fn test_source_code_hash() { assert_eq!( - "7e44de2ed9e0065da09d835b76b8d70be503d276", - source_code_hash("hello.ts", b"1+2", "0.2.11") + "830c8b63ba3194cf2108a3054c176b2bf53aee45", + source_code_hash("hello.ts", b"1+2", "0.2.11", b"{}") ); // Different source_code should result in different hash. assert_eq!( - "57033366cf9db1ef93deca258cdbcd9ef5f4bde1", - source_code_hash("hello.ts", b"1", "0.2.11") + "fb06127e9b2e169bea9c697fa73386ae7c901e8b", + source_code_hash("hello.ts", b"1", "0.2.11", b"{}") ); // Different filename should result in different hash. assert_eq!( - "19657f90b5b0540f87679e2fb362e7bd62b644b0", - source_code_hash("hi.ts", b"1+2", "0.2.11") + "3a17b6a493ff744b6a455071935f4bdcd2b72ec7", + source_code_hash("hi.ts", b"1+2", "0.2.11", b"{}") ); // Different version should result in different hash. assert_eq!( - "e2b4b7162975a02bf2770f16836eb21d5bcb8be1", - source_code_hash("hi.ts", b"1+2", "0.2.0") + "d6b2cfdc39dae9bd3ad5b493ee1544eb22e7475f", + source_code_hash("hi.ts", b"1+2", "0.2.0", b"{}") ); } diff --git a/cli/flags.rs b/cli/flags.rs index 2b0b37b9a8155d..dbd185efbc2707 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -13,6 +13,9 @@ pub struct DenoFlags { pub log_debug: bool, pub version: bool, pub reload: bool, + /// When the `--config`/`-c` flag is used to pass the name, this will be set + /// the path passed on the command line, otherwise `None`. + pub config_path: Option, pub allow_read: bool, pub allow_write: bool, pub allow_net: bool, @@ -79,6 +82,13 @@ pub fn create_cli_app<'a, 'b>() -> App<'a, 'b> { .short("r") .long("reload") .help("Reload source code cache (recompile TypeScript)"), + ).arg( + Arg::with_name("config") + .short("c") + .long("config") + .value_name("FILE") + .help("Load compiler configuration file") + .takes_value(true), ).arg( Arg::with_name("v8-options") .long("v8-options") @@ -146,6 +156,7 @@ pub fn parse_flags(matches: ArgMatches) -> DenoFlags { if matches.is_present("reload") { flags.reload = true; } + flags.config_path = matches.value_of("config").map(ToOwned::to_owned); if matches.is_present("allow-read") { flags.allow_read = true; } @@ -353,4 +364,17 @@ mod tests { } ) } + + #[test] + fn test_set_flags_11() { + let flags = + flags_from_vec(svec!["deno", "-c", "tsconfig.json", "script.ts"]); + assert_eq!( + flags, + DenoFlags { + config_path: Some("tsconfig.json".to_owned()), + ..DenoFlags::default() + } + ) + } } diff --git a/cli/msg.fbs b/cli/msg.fbs index d217fc7ba70f09..ff5454a9169acb 100644 --- a/cli/msg.fbs +++ b/cli/msg.fbs @@ -3,6 +3,8 @@ union Any { Chdir, Chmod, Close, + CompilerConfig, + CompilerConfigRes, CopyFile, Cwd, CwdRes, @@ -174,6 +176,15 @@ table StartRes { no_color: bool; } +table CompilerConfig { + compiler_type: string; +} + +table CompilerConfigRes { + path: string; + data: [ubyte]; +} + table FormatError { error: string; } diff --git a/cli/ops.rs b/cli/ops.rs index bc06a2fb71a74c..4ebcf5fdb59a6d 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -1,6 +1,7 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use atty; use crate::ansi; +use crate::compiler::get_compiler_config; use crate::errors; use crate::errors::{DenoError, DenoResult, ErrorKind}; use crate::fs as deno_fs; @@ -146,6 +147,8 @@ pub fn dispatch_all( pub fn op_selector_compiler(inner_type: msg::Any) -> Option { match inner_type { + msg::Any::CompilerConfig => Some(op_compiler_config), + msg::Any::Cwd => Some(op_cwd), msg::Any::FetchModuleMetaData => Some(op_fetch_module_meta_data), msg::Any::WorkerGetMessage => Some(op_worker_get_message), msg::Any::WorkerPostMessage => Some(op_worker_post_message), @@ -443,6 +446,41 @@ fn op_fetch_module_meta_data( }())) } +/// Retrieve any relevant compiler configuration. +fn op_compiler_config( + state: &ThreadSafeState, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_compiler_config().unwrap(); + let cmd_id = base.cmd_id(); + let compiler_type = inner.compiler_type().unwrap(); + + Box::new(futures::future::result(|| -> OpResult { + let builder = &mut FlatBufferBuilder::new(); + let (path, out) = match get_compiler_config(state, compiler_type) { + Some(val) => val, + _ => ("".to_owned(), "".as_bytes().to_owned()), + }; + let data_off = builder.create_vector(&out); + let msg_args = msg::CompilerConfigResArgs { + path: Some(builder.create_string(&path)), + data: Some(data_off), + }; + let inner = msg::CompilerConfigRes::create(builder, &msg_args); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::CompilerConfigRes, + ..Default::default() + }, + )) + }())) +} + fn op_chdir( _state: &ThreadSafeState, base: &msg::Base<'_>, diff --git a/cli/state.rs b/cli/state.rs index f10f3b7e0b1d56..5aefe7d908640d 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -15,6 +15,7 @@ use futures::future::Shared; use std; use std::collections::HashMap; use std::env; +use std::fs; use std::ops::Deref; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -51,6 +52,12 @@ pub struct State { pub argv: Vec, pub permissions: DenoPermissions, pub flags: flags::DenoFlags, + /// When flags contains a `.config_path` option, the content of the + /// configuration file will be resolved and set. + pub config: Option>, + /// When flags contains a `.config_path` option, the fully qualified path + /// name of the passed path will be resolved and set. + pub config_path: Option, pub metrics: Metrics, pub worker_channels: Mutex, pub global_timer: Mutex, @@ -97,11 +104,52 @@ impl ThreadSafeState { let external_channels = (worker_in_tx, worker_out_rx); let resource = resources::add_worker(external_channels); + // take the passed flag and resolve the file name relative to the cwd + let config_file = match &flags.config_path { + Some(config_file_name) => { + debug!("Compiler config file: {}", config_file_name); + let cwd = std::env::current_dir().unwrap(); + Some(cwd.join(config_file_name)) + } + _ => None, + }; + + // Convert the PathBuf to a canonicalized string. This is needed by the + // compiler to properly deal with the configuration. + let config_path = match &config_file { + Some(config_file) => Some( + config_file + .canonicalize() + .unwrap() + .to_str() + .unwrap() + .to_owned(), + ), + _ => None, + }; + + // Load the contents of the configuration file + let config = match &config_file { + Some(config_file) => { + debug!("Attempt to load config: {}", config_file.to_str().unwrap()); + match fs::read(&config_file) { + Ok(config_data) => Some(config_data.to_owned()), + _ => panic!( + "Error retrieving compiler config file at \"{}\"", + config_file.to_str().unwrap() + ), + } + } + _ => None, + }; + ThreadSafeState(Arc::new(State { - dir: deno_dir::DenoDir::new(custom_root).unwrap(), + dir: deno_dir::DenoDir::new(custom_root, &config).unwrap(), argv: argv_rest, permissions: DenoPermissions::from_flags(&flags), flags, + config, + config_path, metrics: Metrics::default(), worker_channels: Mutex::new(internal_channels), global_timer: Mutex::new(GlobalTimer::new()), diff --git a/js/colors.ts b/js/colors.ts new file mode 100644 index 00000000000000..ab89af81cd417e --- /dev/null +++ b/js/colors.ts @@ -0,0 +1,40 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +// TODO(kitsonk) Replace with `deno_std/colors/mod.ts` when we can load modules +// which end in `.ts`. + +import { noColor } from "./os"; + +interface Code { + open: string; + close: string; + regexp: RegExp; +} + +let enabled = !noColor; + +function code(open: number, close: number): Code { + return { + open: `\x1b[${open}m`, + close: `\x1b[${close}m`, + regexp: new RegExp(`\\x1b\\[${close}m`, "g") + }; +} + +function run(str: string, code: Code): string { + return enabled + ? `${code.open}${str.replace(code.regexp, code.open)}${code.close}` + : str; +} + +export function bold(str: string): string { + return run(str, code(1, 22)); +} + +export function yellow(str: string): string { + return run(str, code(33, 39)); +} + +export function cyan(str: string): string { + return run(str, code(36, 39)); +} diff --git a/js/compiler.ts b/js/compiler.ts index fffb9e852d5189..5711e103a94f7f 100644 --- a/js/compiler.ts +++ b/js/compiler.ts @@ -3,8 +3,12 @@ import * as ts from "typescript"; import * as msg from "gen/cli/msg_generated"; import { window } from "./window"; import { assetSourceCode } from "./assets"; +import { bold, cyan, yellow } from "./colors"; import { Console } from "./console"; import { core } from "./core"; +import { cwd } from "./dir"; +import { sendSync } from "./dispatch"; +import * as flatbuffers from "./flatbuffers"; import * as os from "./os"; import { TextDecoder, TextEncoder } from "./text_encoding"; import { clearTimer, setTimeout } from "./timers"; @@ -55,17 +59,81 @@ interface CompilerLookup { interface Os { fetchModuleMetaData: typeof os.fetchModuleMetaData; exit: typeof os.exit; + noColor: typeof os.noColor; } /** Abstraction of the APIs required from the `typescript` module so they can * be easily mocked. */ interface Ts { + convertCompilerOptionsFromJson: typeof ts.convertCompilerOptionsFromJson; createLanguageService: typeof ts.createLanguageService; formatDiagnosticsWithColorAndContext: typeof ts.formatDiagnosticsWithColorAndContext; formatDiagnostics: typeof ts.formatDiagnostics; + parseConfigFileTextToJson: typeof ts.parseConfigFileTextToJson; } +/** Options that either do nothing in Deno, or would cause undesired behavior + * if modified. */ +const ignoredCompilerOptions: ReadonlyArray = [ + "allowSyntheticDefaultImports", + "baseUrl", + "build", + "composite", + "declaration", + "declarationDir", + "declarationMap", + "diagnostics", + "downlevelIteration", + "emitBOM", + "emitDeclarationOnly", + "esModuleInterop", + "extendedDiagnostics", + "forceConsistentCasingInFileNames", + "help", + "importHelpers", + "incremental", + "inlineSourceMap", + "inlineSources", + "init", + "isolatedModules", + "lib", + "listEmittedFiles", + "listFiles", + "mapRoot", + "maxNodeModuleJsDepth", + "module", + "moduleResolution", + "newLine", + "noEmit", + "noEmitHelpers", + "noEmitOnError", + "noLib", + "noResolve", + "out", + "outDir", + "outFile", + "paths", + "preserveSymlinks", + "preserveWatchOutput", + "pretty", + "rootDir", + "rootDirs", + "showConfig", + "skipDefaultLibCheck", + "skipLibCheck", + "sourceMap", + "sourceRoot", + "stripInternal", + "target", + "traceResolution", + "tsBuildInfoFile", + "types", + "typeRoots", + "version", + "watch" +]; + /** A simple object structure for caching resolved modules and their contents. * * Named `ModuleMetaData` to clarify it is just a representation of meta data of @@ -201,6 +269,18 @@ class Compiler implements ts.LanguageServiceHost, ts.FormatDiagnosticsHost { ); } + /** Log TypeScript diagnostics to the console and exit */ + private _logDiagnostics(diagnostics: ts.Diagnostic[]): never { + const errMsg = this._os.noColor + ? this._ts.formatDiagnostics(diagnostics, this) + : this._ts.formatDiagnosticsWithColorAndContext(diagnostics, this); + + console.log(errMsg); + // TODO The compiler isolate shouldn't exit. Errors should be forwarded to + // to the caller and the caller exit. + return this._os.exit(1); + } + /** Given a `moduleSpecifier` and `containingFile` retrieve the cached * `fileName` for a given module. If the module has yet to be resolved * this will return `undefined`. @@ -354,13 +434,7 @@ class Compiler implements ts.LanguageServiceHost, ts.FormatDiagnosticsHost { ...service.getSemanticDiagnostics(fileName) ]; if (diagnostics.length > 0) { - const errMsg = os.noColor - ? this._ts.formatDiagnostics(diagnostics, this) - : this._ts.formatDiagnosticsWithColorAndContext(diagnostics, this); - - console.log(errMsg); - // All TypeScript errors are terminal for deno - this._os.exit(1); + this._logDiagnostics(diagnostics); } assert( @@ -392,6 +466,40 @@ class Compiler implements ts.LanguageServiceHost, ts.FormatDiagnosticsHost { return { outputCode, sourceMap }; } + /** Take a configuration string, parse it, and use it to merge with the + * compiler's configuration options. The method returns an array of compiler + * options which were ignored, or `undefined`. + */ + configure(path: string, configurationText: string): string[] | undefined { + this._log("compile.configure", path); + const { config, error } = this._ts.parseConfigFileTextToJson( + path, + configurationText + ); + if (error) { + this._logDiagnostics([error]); + } + const { options, errors } = this._ts.convertCompilerOptionsFromJson( + config.compilerOptions, + cwd() + ); + if (errors.length) { + this._logDiagnostics(errors); + } + const ignoredOptions: string[] = []; + for (const key of Object.keys(options)) { + if ( + ignoredCompilerOptions.includes(key) && + (!(key in this._options) || options[key] !== this._options[key]) + ) { + ignoredOptions.push(key); + delete options[key]; + } + } + Object.assign(this._options, options); + return ignoredOptions.length ? ignoredOptions : undefined; + } + // TypeScript Language Service and Format Diagnostic Host API getCanonicalFileName(fileName: string): string { @@ -541,6 +649,46 @@ window.compilerMain = function compilerMain(): void { }; }; +const decoder = new TextDecoder(); + +// Perform the op to retrieve the compiler configuration if there was any +// provided on startup. +function getCompilerConfig( + compilerType: string +): { path: string; data: string } { + const builder = flatbuffers.createBuilder(); + const compilerType_ = builder.createString(compilerType); + msg.CompilerConfig.startCompilerConfig(builder); + msg.CompilerConfig.addCompilerType(builder, compilerType_); + const inner = msg.CompilerConfig.endCompilerConfig(builder); + const baseRes = sendSync(builder, msg.Any.CompilerConfig, inner); + assert(baseRes != null); + assert(msg.Any.CompilerConfigRes === baseRes!.innerType()); + const res = new msg.CompilerConfigRes(); + assert(baseRes!.inner(res) != null); + + // the privileged side does not normalize path separators in windows, so we + // will normalize them here + const path = res.path()!.replace(/\\/g, "/"); + assert(path != null); + const dataArray = res.dataArray()!; + assert(dataArray != null); + const data = decoder.decode(dataArray); + return { path, data }; +} + export default function denoMain(): void { os.start("TS"); + + const { path, data } = getCompilerConfig("typescript"); + if (data.length) { + const ignoredOptions = compiler.configure(path, data); + if (ignoredOptions) { + console.warn( + yellow(`Unsupported compiler options in "${path}"\n`) + + cyan(` The following options were ignored:\n`) + + ` ${ignoredOptions.map((value): string => bold(value)).join(", ")}` + ); + } + } } diff --git a/rollup.config.js b/rollup.config.js index 0907ba737184d6..31ee1dc23fd040 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -229,9 +229,11 @@ export default function makeConfig(commandOptions) { // rollup requires them to be explicitly defined to make them available in the // bundle [typescriptPath]: [ + "convertCompilerOptionsFromJson", "createLanguageService", "formatDiagnostics", "formatDiagnosticsWithColorAndContext", + "parseConfigFileTextToJson", "version", "Extension", "ModuleKind", diff --git a/tests/config.test b/tests/config.test new file mode 100644 index 00000000000000..ee811ab8b1cc13 --- /dev/null +++ b/tests/config.test @@ -0,0 +1,4 @@ +args: --reload --config tests/config.tsconfig.json tests/config.ts +check_stderr: true +exit_code: 1 +output: tests/config.ts.out diff --git a/tests/config.ts b/tests/config.ts new file mode 100644 index 00000000000000..e08061e774d6bc --- /dev/null +++ b/tests/config.ts @@ -0,0 +1,5 @@ +const map = new Map(); + +if (map.get("bar").foo) { + console.log("here"); +} diff --git a/tests/config.ts.out b/tests/config.ts.out new file mode 100644 index 00000000000000..e3ceb52bc8d17a --- /dev/null +++ b/tests/config.ts.out @@ -0,0 +1,9 @@ +Unsupported compiler options in "[WILDCARD]tests/config.tsconfig.json" + The following options were ignored: + module, target +Compiling file://[WILDCARD]tests/config.ts +[WILDCARD]tests/config.ts:3:5 - error TS2532: Object is possibly 'undefined'. + +3 if (map.get("bar").foo) { + ~~~~~~~~~~~~~~ + diff --git a/tests/config.tsconfig.json b/tests/config.tsconfig.json new file mode 100644 index 00000000000000..074d7ac0bc1929 --- /dev/null +++ b/tests/config.tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "amd", + "strict": true, + "target": "es5" + } +}