From 2ecd4e8ca2bcb4618b4544af82bffd838b236efa Mon Sep 17 00:00:00 2001 From: Kitson Kelly Date: Tue, 30 Oct 2018 16:40:11 +1100 Subject: [PATCH 1/2] [WIP] TS Compiler Options --- js/compiler.ts | 59 ++++++++++++++-- js/main.ts | 5 +- src/flags.rs | 57 ++++++++++----- src/msg.fbs | 7 +- src/ops.rs | 7 +- tests/flags/README.md | 19 +++++ tests/flags/strict/error_maybe_undefined.ts | 5 ++ .../flags/strict/error_maybe_undefined.ts.out | 5 ++ tools/flag_output_tests.py | 70 +++++++++++++++++++ tools/test.py | 3 + 10 files changed, 206 insertions(+), 31 deletions(-) create mode 100644 tests/flags/README.md create mode 100644 tests/flags/strict/error_maybe_undefined.ts create mode 100644 tests/flags/strict/error_maybe_undefined.ts.out create mode 100755 tools/flag_output_tests.py diff --git a/js/compiler.ts b/js/compiler.ts index 20ddbae9fc3bab..047e3486c28429 100644 --- a/js/compiler.ts +++ b/js/compiler.ts @@ -129,6 +129,37 @@ function getExtension( } } +export interface DenoCompilerOptions { + /** Do not allow JavaScript modules to be imported into the programme. */ + disallowJs?: boolean; + /** Do not type check JavaScript files. + * + * Requires `disallowJs` to be false. + */ + noCheckJs?: boolean; + /** Do not allow JSON files to be imported as modules. */ + noResolveJsonModule?: boolean; + /** Code will be evaluated in TypeScript's strict mode. */ + strict?: boolean; +} + +/** A static map of options supported by the Deno compiler. */ +const compilerOptionMap = new Map< + keyof DenoCompilerOptions, + { + tsCompilerOption: keyof ts.CompilerOptions; + reverse: boolean; + } +>([ + ["disallowJs", { tsCompilerOption: "allowJs", reverse: true }], + ["noCheckJs", { tsCompilerOption: "checkJs", reverse: true }], + [ + "noResolveJsonModule", + { tsCompilerOption: "resolveJsonModule", reverse: true } + ], + ["strict", { tsCompilerOption: "strict", reverse: false }] +]); + /** A singleton class that combines the TypeScript Language Service host API * with Deno specific APIs to provide an interface for compiling and running * TypeScript and JavaScript modules. @@ -153,7 +184,7 @@ export class DenoCompiler >(); // TODO ideally this are not static and can be influenced by command line // arguments - private readonly _options: Readonly = { + private readonly _options: ts.CompilerOptions = { allowJs: true, checkJs: true, module: ts.ModuleKind.AMD, @@ -358,6 +389,20 @@ export class DenoCompiler innerMap.set(moduleSpecifier, fileName); } + /** Update the options for the TypeScript compiler. */ + private _setOptions(options: DenoCompilerOptions) { + for (const [option, value] of Object.entries(options) as Array< + [keyof DenoCompilerOptions, boolean] + >) { + this._log("compiler._setOptions", options); + const optionInfo = compilerOptionMap.get(option); + if (optionInfo) { + const { tsCompilerOption, reverse } = optionInfo; + this._options[tsCompilerOption] = reverse ? !value : value; + } + } + } + /** Setup being able to map back source references back to their source * * TODO is this the best place for this? It is tightly coupled to how the @@ -596,7 +641,6 @@ export class DenoCompiler } getCompilationSettings(): ts.CompilerOptions { - this._log("getCompilationSettings()"); return this._options; } @@ -720,9 +764,12 @@ export class DenoCompiler private static _instance: DenoCompiler | undefined; /** Returns the instance of `DenoCompiler` or creates a new instance. */ - static instance(): DenoCompiler { - return ( - DenoCompiler._instance || (DenoCompiler._instance = new DenoCompiler()) - ); + static instance(options?: DenoCompilerOptions): DenoCompiler { + const compiler = + DenoCompiler._instance || (DenoCompiler._instance = new DenoCompiler()); + if (options) { + compiler._setOptions(options); + } + return compiler; } } diff --git a/js/main.ts b/js/main.ts index 50de2c26c31ed0..8e5101394ca8be 100644 --- a/js/main.ts +++ b/js/main.ts @@ -43,7 +43,6 @@ export default function denoMain() { libdeno.setGlobalErrorHandler(onGlobalError); libdeno.setPromiseRejectHandler(promiseRejectHandler); libdeno.setPromiseErrorExaminer(promiseErrorExaminer); - const compiler = DenoCompiler.instance(); // First we send an empty "Start" message to let the privileged side know we // are ready. The response should be a "StartRes" message containing the CLI @@ -52,6 +51,10 @@ export default function denoMain() { setLogDebug(startResMsg.debugFlag()); + const compiler = DenoCompiler.instance({ + strict: startResMsg.strictFlag() + }); + // handle `--types` if (startResMsg.typesFlag()) { const defaultLibFileName = compiler.getDefaultLibFileName(); diff --git a/src/flags.rs b/src/flags.rs index cd5284352b29f9..6f34fb1f7dd585 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -17,15 +17,16 @@ macro_rules! svec { #[derive(Debug, PartialEq, Default)] pub struct DenoFlags { + pub allow_env: bool, + pub allow_net: bool, + pub allow_write: bool, pub help: bool, pub log_debug: bool, - pub version: bool, - pub reload: bool, pub recompile: bool, - pub allow_write: bool, - pub allow_net: bool, - pub allow_env: bool, + pub reload: bool, + pub strict: bool, pub types_flag: bool, + pub version: bool, } pub fn process(flags: &DenoFlags, usage_string: String) { @@ -70,6 +71,7 @@ pub fn set_flags( opts.optflag("r", "reload", "Reload cached remote resources."); opts.optflag("", "v8-options", "Print V8 command line options."); opts.optflag("", "types", "Print runtime TypeScript declarations."); + opts.optflag("s", "strict", "Evaluate TypeScript in strict mode."); let mut flags = DenoFlags::default(); @@ -80,33 +82,39 @@ pub fn set_flags( } }; + if matches.opt_present("allow-env") { + flags.allow_env = true; + } + if matches.opt_present("allow-net") { + flags.allow_net = true; + } + if matches.opt_present("allow-write") { + flags.allow_write = true; + } if matches.opt_present("help") { flags.help = true; } if matches.opt_present("log-debug") { flags.log_debug = true; } - if matches.opt_present("version") { - flags.version = true; - } - if matches.opt_present("reload") { - flags.reload = true; - } if matches.opt_present("recompile") { flags.recompile = true; } - if matches.opt_present("allow-write") { - flags.allow_write = true; - } - if matches.opt_present("allow-net") { - flags.allow_net = true; + if matches.opt_present("reload") { + flags.reload = true; } - if matches.opt_present("allow-env") { - flags.allow_env = true; + if matches.opt_present("strict") { + flags.strict = true; } if matches.opt_present("types") { flags.types_flag = true; } + if matches.opt_present("strict") { + flags.strict = true; + } + if matches.opt_present("version") { + flags.version = true; + } let rest: Vec<_> = matches.free.iter().map(|s| s.clone()).collect(); return Ok((flags, rest, get_usage(&opts))); @@ -184,6 +192,19 @@ fn test_set_flags_5() { ) } +#[test] +fn test_set_flags_6() { + let (flags, rest, _) = set_flags(svec!["deno", "--strict"]).unwrap(); + assert_eq!(rest, svec!["deno"]); + assert_eq!( + flags, + DenoFlags { + strict: true, + ..DenoFlags::default() + } + ) +} + #[test] fn test_set_bad_flags_1() { let err = set_flags(svec!["deno", "--unknown-flag"]).unwrap_err(); diff --git a/src/msg.fbs b/src/msg.fbs index e7ebe66842f659..9b22420bb10678 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -123,15 +123,16 @@ table Start { } table StartRes { - cwd: string; argv: [string]; + cwd: string; debug_flag: bool; + deno_version: string; deps_flag: bool; recompile_flag: bool; + strict_flag: bool; types_flag: bool; - version_flag: bool; - deno_version: string; v8_version: string; + version_flag: bool; } table CodeFetch { diff --git a/src/ops.rs b/src/ops.rs index 95522fb6b8305a..532d9e0e621fd4 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -187,14 +187,15 @@ fn op_start( let inner = msg::StartRes::create( &mut builder, &msg::StartResArgs { - cwd: Some(cwd_off), argv: Some(argv_off), + cwd: Some(cwd_off), debug_flag: state.flags.log_debug, + deno_version: Some(deno_version_off), recompile_flag: state.flags.recompile, + strict_flag: state.flags.strict, types_flag: state.flags.types_flag, - version_flag: state.flags.version, v8_version: Some(v8_version_off), - deno_version: Some(deno_version_off), + version_flag: state.flags.version, ..Default::default() }, ); diff --git a/tests/flags/README.md b/tests/flags/README.md new file mode 100644 index 00000000000000..72d7e64f1bc45f --- /dev/null +++ b/tests/flags/README.md @@ -0,0 +1,19 @@ +# Flag Tests + +Tests located in sub-directories of this one will be executed by passing the +specified flags to Deno on the command line. Multiple flags are denoted by an +`_` in the directory name. + +For example if a test was named `tests/flags/foo/test.ts` with a corresponding +`tests/flags/foo/test.ts.out` the command line to Deno would be: + +``` +$ deno --foo tests/flags/foo/test.ts +``` + +If the test was named `tests/flags/foo_bar/test.ts` with a corresponding +`tests/flags/foo_bar/test.ts.out` the command line to Deno would be: + +``` +$ deno --foo --bar tests/flags/foo_bar/test.ts +``` diff --git a/tests/flags/strict/error_maybe_undefined.ts b/tests/flags/strict/error_maybe_undefined.ts new file mode 100644 index 00000000000000..53f00248413862 --- /dev/null +++ b/tests/flags/strict/error_maybe_undefined.ts @@ -0,0 +1,5 @@ +const map = new Map(); + +if (map.get("foo").bar) { + console.log("maybe undefined"); +} diff --git a/tests/flags/strict/error_maybe_undefined.ts.out b/tests/flags/strict/error_maybe_undefined.ts.out new file mode 100644 index 00000000000000..f698e8dfe38e9b --- /dev/null +++ b/tests/flags/strict/error_maybe_undefined.ts.out @@ -0,0 +1,5 @@ +[WILDCARD]/tests/flags/strict/error_maybe_undefined.ts:3:5 - error TS2532: Object is possibly 'undefined'. + +3 if (map.get("foo").bar) { +   ~~~~~~~~~~~~~~ + diff --git a/tools/flag_output_tests.py b/tools/flag_output_tests.py new file mode 100755 index 00000000000000..a2c55e9bcd00ad --- /dev/null +++ b/tools/flag_output_tests.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# Copyright 2018 the Deno authors. All rights reserved. MIT license. +# Given a deno executable, this script executes several integration tests also +# passing command line flags to the deno executable based on the path of the +# test case. +# +# Usage: flag_output_tests.py [path to deno executable] +import os +import sys +import subprocess +from util import pattern_match, parse_exit_code + +root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +flags_path = os.path.join(root_path, "tests", "flags") + + +def flag_output_tests(deno_executable): + assert os.path.isfile(deno_executable) + switch_dirs = sorted([ + filename for filename in os.listdir(flags_path) + if os.path.isdir(os.path.join(flags_path, filename)) + ]) + for switch_dir in switch_dirs: + tests_path = os.path.join(flags_path, switch_dir) + outs = sorted([ + filename for filename in os.listdir(tests_path) + if filename.endswith(".out") + ]) + assert len(outs) > 0 + tests = [(os.path.splitext(filename)[0], filename) for filename in outs] + for (script, out_filename) in tests: + script_abs = os.path.join(tests_path, script) + out_abs = os.path.join(tests_path, out_filename) + with open(out_abs, 'r') as f: + expected_out = f.read() + flags = ["--" + flag for flag in switch_dir.split("_")] + cmd = [deno_executable, script_abs, "--reload"] + flags + expected_code = parse_exit_code(script) + print " ".join(cmd) + actual_code = 0 + try: + actual_out = subprocess.check_output(cmd, universal_newlines=True) + except subprocess.CalledProcessError as e: + actual_code = e.returncode + actual_out = e.output + if expected_code == 0: + print "Expected success but got error. Output:" + print actual_out + sys.exit(1) + + if expected_code != actual_code: + print "Expected exit code %d but got %d" % (expected_code, + actual_code) + print "Output:" + print actual_out + sys.exit(1) + + if pattern_match(expected_out, actual_out) != True: + print "Expected output does not match actual." + print "Expected: " + expected_out + print "Actual: " + actual_out + sys.exit(1) + + +def main (argv): + flag_output_tests(argv[1]) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/test.py b/tools/test.py index e2d07724533ae5..5c70b31a73d373 100755 --- a/tools/test.py +++ b/tools/test.py @@ -5,6 +5,7 @@ import os import sys from check_output_test import check_output_test +from flag_output_tests import flag_output_tests from deno_dir_test import deno_dir_test from setup_test import setup_test from util import build_path, enable_ansi_colors, executable_suffix, run, rmtree @@ -60,6 +61,8 @@ def main(argv): check_output_test(deno_exe) + flag_output_tests(deno_exe) + # TODO We currently skip testing the prompt in Windows completely. # Windows does not support the pty module used for testing the permission # prompt. From cab9ed162e3c44c9cbf50ae33bc1fe00d4a08d5b Mon Sep 17 00:00:00 2001 From: Kitson Kelly Date: Tue, 30 Oct 2018 16:45:54 +1100 Subject: [PATCH 2/2] Remember to format stuff --- tools/flag_output_tests.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/flag_output_tests.py b/tools/flag_output_tests.py index a2c55e9bcd00ad..5f228c99b55b27 100755 --- a/tools/flag_output_tests.py +++ b/tools/flag_output_tests.py @@ -27,7 +27,8 @@ def flag_output_tests(deno_executable): if filename.endswith(".out") ]) assert len(outs) > 0 - tests = [(os.path.splitext(filename)[0], filename) for filename in outs] + tests = [(os.path.splitext(filename)[0], filename) + for filename in outs] for (script, out_filename) in tests: script_abs = os.path.join(tests_path, script) out_abs = os.path.join(tests_path, out_filename) @@ -39,7 +40,8 @@ def flag_output_tests(deno_executable): print " ".join(cmd) actual_code = 0 try: - actual_out = subprocess.check_output(cmd, universal_newlines=True) + actual_out = subprocess.check_output( + cmd, universal_newlines=True) except subprocess.CalledProcessError as e: actual_code = e.returncode actual_out = e.output @@ -62,7 +64,7 @@ def flag_output_tests(deno_executable): sys.exit(1) -def main (argv): +def main(argv): flag_output_tests(argv[1])