From 3f008f7133a1febb72812b81d9b3af5add807830 Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 10 Oct 2024 20:15:25 +0200 Subject: [PATCH 01/49] already added dependencies to flak and copied files over from js. --- flake.nix | 3 +- tested/languages/typescript/config.py | 228 +++++++++++++ tested/languages/typescript/generators.py | 309 ++++++++++++++++++ .../languages/typescript/templates/values.ts | 144 ++++++++ 4 files changed, 683 insertions(+), 1 deletion(-) create mode 100644 tested/languages/typescript/config.py create mode 100644 tested/languages/typescript/generators.py create mode 100644 tested/languages/typescript/templates/values.ts diff --git a/flake.nix b/flake.nix index 5ad3fe51..43e976d8 100644 --- a/flake.nix +++ b/flake.nix @@ -75,6 +75,7 @@ (pkgs.haskell.packages.ghc96.ghcWithPackages (p: [ p.aeson ])) pkgs.hlint ]; + ts-deps = [ nodejs_base pkgs.typescript pkgs.nodePackages.ts-node ]; node-deps = [ nodejs_base pkgs.nodePackages.eslint ast ]; bash-deps = [ pkgs.shellcheck ]; c-deps = [ pkgs.cppcheck pkgs.gcc13 ]; @@ -82,7 +83,7 @@ kotlin-deps = [ pkgs.kotlin pkgs.ktlint ]; csharp-deps = [ pkgs.dotnetCorePackages.sdk_8_0 ]; - all-other-dependencies = haskell-deps ++ node-deps ++ bash-deps + all-other-dependencies = ts-deps ++ haskell-deps ++ node-deps ++ bash-deps ++ c-deps ++ java-deps ++ kotlin-deps ++ csharp-deps ++ [ pkgs.coreutils ]; diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py new file mode 100644 index 00000000..3cba365f --- /dev/null +++ b/tested/languages/typescript/config.py @@ -0,0 +1,228 @@ +import logging +import re +from pathlib import Path +from typing import TYPE_CHECKING + +from tested.datatypes import ( + AdvancedObjectTypes, + AllTypes, + BasicStringTypes, + ExpressionTypes, +) +from tested.dodona import AnnotateCode, Message +from tested.features import Construct, TypeSupport +from tested.languages.conventionalize import ( + EXECUTION_PREFIX, + Conventionable, + NamingConventions, + submission_file, + submission_name, +) +from tested.languages.language import ( + CallbackResult, + Command, + Language, + TypeDeclarationMetadata, +) +from tested.languages.utils import cleanup_description +from tested.serialisation import Statement, Value + +if TYPE_CHECKING: + from tested.languages.generation import PreparedExecutionUnit + +logger = logging.getLogger(__name__) + +class Typescript(Language): + + def initial_dependencies(self) -> list[str]: + return ["values.ts"] + + def needs_selector(self) -> bool: + return False + + def file_extension(self) -> str: + return "ts" + + def naming_conventions(self) -> dict[Conventionable, NamingConventions]: + return { + "namespace": "camel_case", + "function": "camel_case", + "identifier": "camel_case", + "global_identifier": "macro_case", + "property": "camel_case", + "class": "pascal_case", + } + + def supported_constructs(self) -> set[Construct]: + return { + Construct.OBJECTS, + Construct.EXCEPTIONS, + Construct.FUNCTION_CALLS, + Construct.ASSIGNMENTS, + Construct.HETEROGENEOUS_COLLECTIONS, + Construct.HETEROGENEOUS_ARGUMENTS, + Construct.EVALUATION, + Construct.DEFAULT_PARAMETERS, + Construct.GLOBAL_VARIABLES, + Construct.NAMED_ARGUMENTS, + } + + def datatype_support(self) -> dict[AllTypes, TypeSupport]: + return { # type: ignore + "integer": "supported", + "real": "supported", + "char": "reduced", + "text": "supported", + "string": "supported", + "boolean": "supported", + "sequence": "supported", + "set": "supported", + "map": "supported", + "dictionary": "supported", + "object": "supported", + "nothing": "supported", + "undefined": "supported", + "null": "supported", + "int8": "reduced", + "uint8": "reduced", + "int16": "reduced", + "uint16": "reduced", + "int32": "reduced", + "uint32": "reduced", + "int64": "reduced", + "uint64": "reduced", + "bigint": "supported", + "single_precision": "reduced", + "double_precision": "reduced", + "double_extended": "reduced", + "array": "reduced", + "list": "reduced", + "tuple": "reduced", + } + + def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: + return {AdvancedObjectTypes.OBJECT: {BasicStringTypes.TEXT}} + + def compilation(self, files: list[str]) -> CallbackResult: + submission = submission_file(self) + main_file = list(filter(lambda x: x == submission, files)) + if main_file: + return ["tsc", "--noEmit", main_file[0]], files + else: + return [], files + + def execution(self, cwd: Path, file: str, arguments: list[str]) -> Command: + return ["ts-node", file, *arguments] + + def modify_solution(self, solution: Path): + # import local to prevent errors + from tested.judge.utils import run_command + + assert self.config + + parse_file = str(Path(__file__).parent / "parseAst.ts") + output = run_command( + solution.parent, + timeout=None, + command=["node", parse_file, str(solution.absolute())], + check=True, + ) + assert output, "Missing output from TypesScript's modify_solution" + namings = output.stdout.strip() + with open(solution, "a") as file: + print(f"\nmodule.exports = {{{namings}}};", file=file) + + # Add strict mode to the script. + with open(solution, "r") as file: + non_strict = file.read() + with open(solution, "w") as file: + file.write('"use strict";\n\n' + non_strict) + self.config.dodona.source_offset -= 2 + + def cleanup_stacktrace(self, stacktrace: str) -> str: + assert self.config + # What this does: + # 1a. While inside the submission code, replace all references to the location with + # 1b. Remove any "submission.SOMETHING" -> "SOMETHING" + # 2. Once we encounter a line with the execution location, skip all lines. + execution_submission_location_regex = f"{self.config.dodona.workdir}/{EXECUTION_PREFIX}[_0-9]+/{submission_file(self)}" + submission_location = ( + self.config.dodona.workdir / "common" / submission_file(self) + ) + compilation_submission_location = str(submission_location.resolve()) + execution_location_regex = f"{self.config.dodona.workdir}/{EXECUTION_PREFIX}[_0-9]+/{EXECUTION_PREFIX}[_0-9]+.ts" + submission_namespace = f"{submission_name(self)}." + + resulting_lines = "" + for line in stacktrace.splitlines(keepends=True): + # If we encounter an execution location, we are done. + if re.search(execution_location_regex, line): + break + + # Replace any reference to the submission. + line = re.sub(execution_submission_location_regex, "", line) + line = line.replace(compilation_submission_location, "") + # Remove any references of the form "submission.SOMETHING" + line = line.replace(submission_namespace, "") + + resulting_lines += line + + return resulting_lines + + def cleanup_description(self, statement: str) -> str: + statement = cleanup_description(self, statement) + await_regex = re.compile(r"await\s+") + return await_regex.sub("", statement) + + def generate_statement(self, statement: Statement) -> str: + from tested.languages.typescript import generators + + return generators.convert_statement(statement, full=True) + + def generate_execution_unit(self, execution_unit: "PreparedExecutionUnit") -> str: + from tested.languages.typescript import generators + + return generators.convert_execution_unit(execution_unit) + + def generate_encoder(self, values: list[Value]) -> str: + from tested.languages.typescript import generators + + return generators.convert_encoder(values) + + def get_declaration_metadata(self) -> TypeDeclarationMetadata: + return { + "names": { # type: ignore + "integer": "number", + "real": "number", + "char": "string", + "text": "string", + "string": "string", + "boolean": "boolean", + "sequence": "array", + "set": "set", + "map": "object", + "nothing": "null", + "undefined": "undefined", + "int8": "number", + "uint8": "number", + "int16": "number", + "uint16": "number", + "int32": "number", + "uint32": "number", + "int64": "number", + "uint64": "number", + "bigint": "number", + "single_precision": "number", + "double_precision": "number", + "double_extended": "number", + "fixed_precision": "number", + "array": "array", + "list": "array", + "tuple": "array", + "any": "object", + }, + "nested": ("<", ">"), + "exception": "Error", + } + + diff --git a/tested/languages/typescript/generators.py b/tested/languages/typescript/generators.py new file mode 100644 index 00000000..f0d89e82 --- /dev/null +++ b/tested/languages/typescript/generators.py @@ -0,0 +1,309 @@ +import json + +from tested.datatypes import ( + AdvancedNothingTypes, + AdvancedNumericTypes, + BasicBooleanTypes, + BasicNothingTypes, + BasicNumericTypes, + BasicObjectTypes, + BasicSequenceTypes, + BasicStringTypes, +) +from tested.datatypes.advanced import AdvancedObjectTypes +from tested.languages.conventionalize import submission_file +from tested.languages.preparation import ( + PreparedContext, + PreparedExecutionUnit, + PreparedTestcase, + PreparedTestcaseStatement, +) +from tested.languages.utils import convert_unknown_type +from tested.serialisation import ( + Expression, + FunctionCall, + FunctionType, + Identifier, + NamedArgument, + ObjectType, + PropertyAssignment, + SequenceType, + SpecialNumbers, + Statement, + StringType, + Value, + VariableAssignment, + as_basic_type, +) +from tested.testsuite import MainInput + + +def convert_arguments(arguments: list[NamedArgument | Expression]) -> str: + results = [] + for arg in arguments: + if isinstance(arg, NamedArgument): + results.append(f"{arg.name}={convert_statement(arg.value, True)}") + else: + results.append(convert_statement(arg, True)) + return ", ".join(results) + + +def convert_value(value: Value) -> str: + # Handle some advanced types. + if value.type == AdvancedNothingTypes.UNDEFINED: + return "undefined" + elif value.type == AdvancedNumericTypes.DOUBLE_EXTENDED: + raise AssertionError("Double extended values are not supported in ts.") + elif value.type == AdvancedNumericTypes.FIXED_PRECISION: + raise AssertionError("Fixed precision values are not supported in ts.") + elif value.type == AdvancedObjectTypes.OBJECT: + assert isinstance(value, ObjectType) + result = "{" + for i, pair in enumerate(value.data): + result += convert_statement(pair.key, True) + result += ": " + result += convert_statement(pair.value, True) + if i != len(value.data) - 1: + result += ", " + result += "}" + return result + elif value.type in ( + AdvancedNumericTypes.INT_64, + AdvancedNumericTypes.U_INT_64, + AdvancedNumericTypes.BIG_INT, + ): + return f'BigInt("{value.data}")' + # Handle basic types + value = as_basic_type(value) + if value.type in (BasicNumericTypes.INTEGER, BasicNumericTypes.REAL): + if not isinstance(value.data, SpecialNumbers): + return str(value.data) + elif value.data == SpecialNumbers.NOT_A_NUMBER: + return "NaN" + elif value.data == SpecialNumbers.POS_INFINITY: + return "Infinity" + else: + return "(-Infinity)" + elif value.type == BasicStringTypes.TEXT: + return json.dumps(value.data, ensure_ascii=False) + elif value.type == BasicBooleanTypes.BOOLEAN: + return str(value.data).lower() + elif value.type == BasicNothingTypes.NOTHING: + return "null" + elif value.type == BasicSequenceTypes.SEQUENCE: + assert isinstance(value, SequenceType) + return f"[{convert_arguments(value.data)}]" # pyright: ignore + elif value.type == BasicSequenceTypes.SET: + assert isinstance(value, SequenceType) + return f"new Set([{convert_arguments(value.data)}])" # pyright: ignore + elif value.type == BasicObjectTypes.MAP: + assert isinstance(value, ObjectType) + result = "new Map([" + for i, pair in enumerate(value.data): + result += "[" + result += convert_statement(pair.key, True) + result += ", " + result += convert_statement(pair.value, True) + result += "]" + if i != len(value.data) - 1: + result += ", " + result += "])" + return result + elif value.type == BasicStringTypes.UNKNOWN: + assert isinstance(value, StringType) + return convert_unknown_type(value) + raise AssertionError(f"Invalid literal: {value!r}") + + +def convert_function_call(call: FunctionCall, internal=False) -> str: + result = "" + if not internal: + result += "await " + if call.type == FunctionType.CONSTRUCTOR: + result += "new " + if call.namespace: + result += convert_statement(call.namespace, True) + "." + result += call.name + if call.type != FunctionType.PROPERTY: + result += f"({convert_arguments(call.arguments)})" # pyright: ignore + return result + + +def convert_statement(statement: Statement, internal=False, full=False) -> str: + if isinstance(statement, Identifier): + return statement + elif isinstance(statement, FunctionCall): + return convert_function_call(statement, internal) + elif isinstance(statement, Value): + return convert_value(statement) + elif isinstance(statement, PropertyAssignment): + return ( + f"{convert_statement(statement.property, True)} = " + f"{convert_statement(statement.expression, True)}" + ) + elif isinstance(statement, VariableAssignment): + if full: + prefix = "let " + else: + prefix = "" + return ( + f"{prefix}{statement.variable} = " + f"{convert_statement(statement.expression, True)}" + ) + raise AssertionError(f"Unknown statement: {statement!r}") + + +def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) -> str: + result = ctx.before + "\n" + + # Import the submission if there is no main call. + if not ctx.context.has_main_testcase(): + result += f""" + writeSeparator(); + delete require.cache[require.resolve("./{submission_file(pu.language)}")]; + const {pu.submission_name} = require("./{submission_file(pu.language)}"); + """ + + # Generate code for each testcase + tc: PreparedTestcase + for i, tc in enumerate(ctx.testcases): + # Prepare command arguments if needed. + if tc.testcase.is_main_testcase(): + assert isinstance(tc.input, MainInput) + wrapped = [json.dumps(a) for a in tc.input.arguments] + result += f""" + writeSeparator(); + let new_args = [process.argv[0]]; + new_args = new_args.concat([{", ".join(wrapped)}]); + process.argv = new_args; + """ + elif i != 0: + result += "writeSeparator();\n" + + # We need special code to make variables available outside of the try-catch block. + if ( + not tc.testcase.is_main_testcase() + and isinstance(tc.input, PreparedTestcaseStatement) + and isinstance(tc.input.statement, VariableAssignment) + ): + result += f"let {tc.input.statement.variable}\n" + + result += "try {\n" + if tc.testcase.is_main_testcase(): + assert isinstance(tc.input, MainInput) + result += f""" + delete require.cache[require.resolve("./{pu.submission_name}.js")]; + const {pu.submission_name} = require("./{pu.submission_name}.js"); + """ + else: + assert isinstance(tc.input, PreparedTestcaseStatement) + result += " " * 4 + convert_statement(tc.input.input_statement()) + ";\n" + + result += f""" + {convert_statement(tc.exception_statement())}; + }} catch(e) {{ + {convert_statement(tc.exception_statement("e"))}; + }} + """ + + result += ctx.after + + return result + + +def convert_execution_unit(pu: PreparedExecutionUnit) -> str: + result = """ + const fs = require('fs'); + const values = require("./values.js"); + """ + + # Import the language specific functions we will need. + for name in pu.evaluator_names: + result += f'const {name} = require("./{name}.js");\n' + + # We now open files for results and define some functions. + result += f""" + const valueFile = fs.openSync("{pu.value_file}", "w"); + const exceptionFile = fs.openSync("{pu.exception_file}", "w"); + + function writeSeparator() {{ + fs.writeSync(valueFile, "--{pu.testcase_separator_secret}-- SEP"); + fs.writeSync(exceptionFile, "--{pu.testcase_separator_secret}-- SEP"); + fs.writeSync(process.stdout.fd, "--{pu.testcase_separator_secret}-- SEP"); + fs.writeSync(process.stderr.fd, "--{pu.testcase_separator_secret}-- SEP"); + }} + + function writeContextSeparator() {{ + fs.writeSync(valueFile, "--{pu.context_separator_secret}-- SEP"); + fs.writeSync(exceptionFile, "--{pu.context_separator_secret}-- SEP"); + fs.writeSync(process.stdout.fd, "--{pu.context_separator_secret}-- SEP"); + fs.writeSync(process.stderr.fd, "--{pu.context_separator_secret}-- SEP"); + }} + + async function sendValue(value) {{ + values.sendValue(valueFile, await value); + }} + + async function sendException(exception) {{ + values.sendException(exceptionFile, await exception); + }} + + async function sendSpecificValue(value) {{ + values.sendEvaluated(valueFile, await value); + }} + + async function sendSpecificException(exception) {{ + values.sendEvaluated(exceptionFile, await exception); + }} + """ + + # Generate code for each context. + ctx: PreparedContext + for i, ctx in enumerate(pu.contexts): + result += f""" + async function context{i}() {{ + {_generate_internal_context(ctx, pu)} + }} + """ + + # Functions to write separators + result += f"(async () => {{\n" + + for i, ctx in enumerate(pu.contexts): + result += f""" + writeContextSeparator(); + await context{i}(); + """ + + result += """ + fs.closeSync(valueFile); + fs.closeSync(exceptionFile); + })(); + """ + + return result + + +def convert_check_function(evaluator: str, function: FunctionCall) -> str: + return f""" + (async () => {{ + const {evaluator} = require('./{evaluator}.js'); + const values = require('./values.js'); + + const result = {convert_function_call(function)}; + values.sendEvaluated(process.stdout.fd, result); + }})(); + """ + + +def convert_encoder(values: list[Value]) -> str: + result = """ + const values = require('./values.js'); + const fs = require("fs"); + """ + + for value in values: + result += f"values.sendValue(process.stdout.fd, {convert_value(value)});\n" + result += 'fs.writeSync(process.stdout.fd, "␞");\n' + + return result diff --git a/tested/languages/typescript/templates/values.ts b/tested/languages/typescript/templates/values.ts new file mode 100644 index 00000000..2ca5b24c --- /dev/null +++ b/tested/languages/typescript/templates/values.ts @@ -0,0 +1,144 @@ +import * as fs from "fs"; + +function isVanillaObject(value) : boolean { + try { + return Reflect.getPrototypeOf(value) === null; + } catch { + return false; + } +} + +function encode(value) { + let diagnostic = null; + let type = null; + + if ( typeof value === "undefined") { + type = "undefined"; + } else if (typeof value === "boolean") { + type = "boolean"; + } else if (typeof value === "number") { + if (Number.isInteger(value)) { + type = "integer"; + } else { + type = "real"; + if (Number.isNaN(value)) { + value = "nan"; + } else if (!Number.isFinite(value)) { + if (value < 0) { + value = "-inf"; + } else { + value = "inf"; + } + } + } + } else if (typeof value === "string") { + type = "text"; + } else if (typeof value === "bigint") { + type = "bigint"; + value = value.toString(); + } else if (typeof value === "symbol") { + type = "unknown"; + value = value.toString(); + } else if (typeof value === "object") { + if (value === null) { + type = "null"; + } else if (Array.isArray(value)) { + type = "list"; + // Handle holes in arrays... + const unholed = []; + for (let i = 0; i < value.length; i++) { + if (!value.hasOwnProperty(i)) { + unholed.push(``) + } else { + unholed.push(value[i]); + } + } + value = unholed.map(encode); + } else if (value instanceof Set) { + type = "set"; + value = Array.from(value).map(encode); + } else if (value instanceof Map) { + type = "dictionary"; + value = Array + .from(value) + .map(([key, value]) => { + return { + key: encode(key), + value: encode(value) + }; + } + ); + } else if (value?.constructor === Object || isVanillaObject(value)) { + // Plain objects + type = "object"; + // Process the elements of the object. + + value = Object.keys(value).map(key => { + return { + key: encode(key), + value: encode(value[key]) + }; + }); + } else { + type = "unknown"; + diagnostic = value?.constructor?.name; + value = JSON.stringify(value); + } + } else { + type = "unknown"; + diagnostic = value?.constructor?.name; + value = Object.prototype.toString.call(value); + } + + return { + type: type, + data: value, + diagnostic: diagnostic + }; + +} + +// Send a value to the given stream. +export function sendValue(stream, value) { + fs.writeSync(stream, JSON.stringify(encode(value))); +} + +// Send an exception to the given stream. +export function sendException(stream, exception) { + if (!exception) { + return; + } + if (exception instanceof Error) { + // We have a proper error... + fs.writeSync(stream, JSON.stringify({ + "message": exception.message, + "stacktrace": exception.stack ?? "", + "type": exception.constructor.name + })); + } else { + // Comes out of the values.js: + // Temporarily allow objects with "message" and "name". + // TODO: remove this once the semester is over + // noinspection PointlessBooleanExpressionJS + if (typeof exception === 'object') { + fs.writeSync(stream, JSON.stringify({ + "message": exception.message ?? "", + "stacktrace": "", + "type": exception.name ?? "" + })); + } else { + // We have something else, so we cannot rely on stuff being present. + fs.writeSync(stream, JSON.stringify({ + "message": JSON.stringify(exception), + "stacktrace": "", + "type": exception.constructor.name ?? (Object.prototype.toString.call(exception)), + "additional_message_keys": ["languages.javascript.runtime.invalid.exception"] + })); + } + } +} + +// Send an evaluation result to the given stream. +export function sendEvaluated(stream, result) { + fs.writeSync(stream, JSON.stringify(result)); +} From 208f5897698e1ee154192c71faeab2b1ec44c631 Mon Sep 17 00:00:00 2001 From: breblanc Date: Sat, 12 Oct 2024 17:11:05 +0200 Subject: [PATCH 02/49] Have an implementation that might work, but test seem to do something weird. --- tested/judge/compilation.py | 1 + tested/judge/core.py | 2 +- tested/judge/utils.py | 1 + tested/languages/__init__.py | 2 + tested/languages/typescript/config.py | 8 +- tested/languages/typescript/generators.py | 18 ++-- tested/languages/typescript/parseAst.ts | 43 +++++++++ .../languages/typescript/templates/values.ts | 36 ++++---- tested/testsuite.py | 1 + tests/exercises/counter/solution/solution.ts | 15 ++++ .../solution/correct.ts | 5 ++ .../workdir/echo.ts | 5 ++ .../solution/correct.ts | 5 ++ .../solution/correct.ts | 5 ++ tests/exercises/global/solution/correct.ts | 1 + .../one-with-crashing-assignment-java.tson | 4 +- ...e-with-crashing-assignment-typescript.tson | 88 +++++++++++++++++++ tests/exercises/isbn/solution/solution.ts | 59 +++++++++++++ tests/exercises/lotto/solution/correct.ts | 7 ++ tests/exercises/lotto/solution/wrong.ts | 7 ++ tests/exercises/objects/solution/correct.ts | 16 ++++ tests/exercises/sum/solution/correct.ts | 14 +++ tests/language_markers.py | 1 + tests/test_functionality.py | 2 +- 24 files changed, 314 insertions(+), 32 deletions(-) create mode 100644 tested/languages/typescript/parseAst.ts create mode 100644 tests/exercises/counter/solution/solution.ts create mode 100644 tests/exercises/echo-function-additional-source-files/solution/correct.ts create mode 100644 tests/exercises/echo-function-additional-source-files/workdir/echo.ts create mode 100644 tests/exercises/echo-function-file-input/solution/correct.ts create mode 100644 tests/exercises/echo-function-file-output/solution/correct.ts create mode 100644 tests/exercises/global/solution/correct.ts create mode 100644 tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson create mode 100644 tests/exercises/isbn/solution/solution.ts create mode 100644 tests/exercises/lotto/solution/correct.ts create mode 100644 tests/exercises/lotto/solution/wrong.ts create mode 100644 tests/exercises/objects/solution/correct.ts create mode 100644 tests/exercises/sum/solution/correct.ts diff --git a/tested/judge/compilation.py b/tested/judge/compilation.py index b079285e..38cd36ca 100644 --- a/tested/judge/compilation.py +++ b/tested/judge/compilation.py @@ -63,6 +63,7 @@ def run_compilation( _logger.debug( "Generating files with command %s in directory %s", command, directory ) + breakpoint() result = run_command(directory, remaining, command) _logger.debug(f"Compilation dependencies are: {files}") return result, files diff --git a/tested/judge/core.py b/tested/judge/core.py index 24bdd3b3..033a7695 100644 --- a/tested/judge/core.py +++ b/tested/judge/core.py @@ -127,7 +127,7 @@ def judge(bundle: Bundle): return planned_units = plan_test_suite(bundle, strategy=PlanStrategy.OPTIMAL) - + breakpoint() # Attempt to precompile everything. common_dir, dependencies, selector = _generate_files(bundle, planned_units) diff --git a/tested/judge/utils.py b/tested/judge/utils.py index c50a00b2..02e01625 100644 --- a/tested/judge/utils.py +++ b/tested/judge/utils.py @@ -52,6 +52,7 @@ def run_command( try: timeout = int(timeout) if timeout is not None else None + breakpoint() process = subprocess.run( command, cwd=directory, diff --git a/tested/languages/__init__.py b/tested/languages/__init__.py index a045fb26..974ad31b 100644 --- a/tested/languages/__init__.py +++ b/tested/languages/__init__.py @@ -19,6 +19,7 @@ from tested.languages.language import Language from tested.languages.python.config import Python from tested.languages.runhaskell.config import RunHaskell +from tested.languages.typescript.config import TypeScript if TYPE_CHECKING: from tested.configs import GlobalConfig @@ -30,6 +31,7 @@ "haskell": Haskell, "java": Java, "javascript": JavaScript, + "typescript": TypeScript, "kotlin": Kotlin, "python": Python, "runhaskell": RunHaskell, diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 3cba365f..3e6db69b 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -32,7 +32,7 @@ logger = logging.getLogger(__name__) -class Typescript(Language): +class TypeScript(Language): def initial_dependencies(self) -> list[str]: return ["values.ts"] @@ -112,7 +112,8 @@ def compilation(self, files: list[str]) -> CallbackResult: return [], files def execution(self, cwd: Path, file: str, arguments: list[str]) -> Command: - return ["ts-node", file, *arguments] + # Used es2022 because of the top-level await feature (most up-to data). + return ["ts-node", "-O", '{"module": "es2022"}', file, *arguments] def modify_solution(self, solution: Path): # import local to prevent errors @@ -129,8 +130,9 @@ def modify_solution(self, solution: Path): ) assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() + breakpoint() with open(solution, "a") as file: - print(f"\nmodule.exports = {{{namings}}};", file=file) + print(f"\nexports.{namings} = {{{namings}}};", file=file) # Add strict mode to the script. with open(solution, "r") as file: diff --git a/tested/languages/typescript/generators.py b/tested/languages/typescript/generators.py index f0d89e82..ae10cb85 100644 --- a/tested/languages/typescript/generators.py +++ b/tested/languages/typescript/generators.py @@ -214,12 +214,12 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) def convert_execution_unit(pu: PreparedExecutionUnit) -> str: result = """ const fs = require('fs'); - const values = require("./values.js"); + const values = require("./values.ts"); """ # Import the language specific functions we will need. for name in pu.evaluator_names: - result += f'const {name} = require("./{name}.js");\n' + result += f'const {name} = require("./{name}.ts");\n' # We now open files for results and define some functions. result += f""" @@ -240,19 +240,19 @@ def convert_execution_unit(pu: PreparedExecutionUnit) -> str: fs.writeSync(process.stderr.fd, "--{pu.context_separator_secret}-- SEP"); }} - async function sendValue(value) {{ + async function sendValue(value: unknown) {{ values.sendValue(valueFile, await value); }} - async function sendException(exception) {{ + async function sendException(exception: unknown) {{ values.sendException(exceptionFile, await exception); }} - async function sendSpecificValue(value) {{ + async function sendSpecificValue(value: unknown) {{ values.sendEvaluated(valueFile, await value); }} - async function sendSpecificException(exception) {{ + async function sendSpecificException(exception: unknown) {{ values.sendEvaluated(exceptionFile, await exception); }} """ @@ -287,8 +287,8 @@ def convert_execution_unit(pu: PreparedExecutionUnit) -> str: def convert_check_function(evaluator: str, function: FunctionCall) -> str: return f""" (async () => {{ - const {evaluator} = require('./{evaluator}.js'); - const values = require('./values.js'); + const {evaluator} = require('./{evaluator}.ts'); + const values = require('./values.ts'); const result = {convert_function_call(function)}; values.sendEvaluated(process.stdout.fd, result); @@ -298,7 +298,7 @@ def convert_check_function(evaluator: str, function: FunctionCall) -> str: def convert_encoder(values: list[Value]) -> str: result = """ - const values = require('./values.js'); + const values = require('./values.ts'); const fs = require("fs"); """ diff --git a/tested/languages/typescript/parseAst.ts b/tested/languages/typescript/parseAst.ts new file mode 100644 index 00000000..097113fc --- /dev/null +++ b/tested/languages/typescript/parseAst.ts @@ -0,0 +1,43 @@ +const { parse } = require('abstract-syntax-tree'); +const fs = require('fs'); +const source = fs.readFileSync(process.argv[2], 'utf-8'); + +function mapSubTreeToIds(subtree) { + const type = subtree.type; + if (type === 'VariableDeclaration') { + return subtree.declarations.map(row => row.id); + } else if (type === 'FunctionDeclaration' || type === 'ClassDeclaration') { + return [subtree.id]; + } else if (type === 'ExpressionStatement' && + subtree.expression.type === 'AssignmentExpression') { + return [subtree.expression.left]; + } else { + return []; + } +} + +function mapIdToName(id) { + const type = id.type; + if (type === 'Identifier') { + return id.name; + } else if (type === 'ArrayPattern') { + return id.elements.flatMap(mapIdToName); + } else if (type === 'ObjectPattern') { + return id.properties.map(d => d.key).flatMap(mapIdToName); + } else { + return []; + } +} + +let ast; +try { + // Add next option to support more TypeScript features. + const ast = parse(source, {next: true}).body; + // Use Set to remove duplicates + const array = Array.from(new Set(ast.flatMap(mapSubTreeToIds).flatMap(mapIdToName))); + console.log(array.join(', ')); +} catch (e) { + // Assume this is invalid TypeScript at this point. + console.error(e); + process.exit(0); +} diff --git a/tested/languages/typescript/templates/values.ts b/tested/languages/typescript/templates/values.ts index 2ca5b24c..2a2c64ca 100644 --- a/tested/languages/typescript/templates/values.ts +++ b/tested/languages/typescript/templates/values.ts @@ -1,6 +1,6 @@ -import * as fs from "fs"; +const fileSystem = require('fs'); -function isVanillaObject(value) : boolean { +function isVanillaObject(value: Object) : boolean { try { return Reflect.getPrototypeOf(value) === null; } catch { @@ -8,7 +8,7 @@ function isVanillaObject(value) : boolean { } } -function encode(value) { +function encode(value: Object): { data: Object; diagnostic: any; type: string } { let diagnostic = null; let type = null; @@ -76,7 +76,7 @@ function encode(value) { value = Object.keys(value).map(key => { return { key: encode(key), - value: encode(value[key]) + value: encode((value as Record)[key]) }; }); } else { @@ -86,7 +86,7 @@ function encode(value) { } } else { type = "unknown"; - diagnostic = value?.constructor?.name; + diagnostic = (value as Object)?.constructor?.name; value = Object.prototype.toString.call(value); } @@ -99,18 +99,18 @@ function encode(value) { } // Send a value to the given stream. -export function sendValue(stream, value) { - fs.writeSync(stream, JSON.stringify(encode(value))); +function sendValue(stream: number, value: Object) { + fileSystem.writeSync(stream, JSON.stringify(encode(value))); } // Send an exception to the given stream. -export function sendException(stream, exception) { +function sendException(stream: number, exception: Error | Object | {constructor: {name: any}}): void { if (!exception) { return; } if (exception instanceof Error) { // We have a proper error... - fs.writeSync(stream, JSON.stringify({ + fileSystem.writeSync(stream, JSON.stringify({ "message": exception.message, "stacktrace": exception.stack ?? "", "type": exception.constructor.name @@ -121,17 +121,17 @@ export function sendException(stream, exception) { // TODO: remove this once the semester is over // noinspection PointlessBooleanExpressionJS if (typeof exception === 'object') { - fs.writeSync(stream, JSON.stringify({ - "message": exception.message ?? "", + fileSystem.writeSync(stream, JSON.stringify({ + "message": (exception as Error).message ?? "", "stacktrace": "", - "type": exception.name ?? "" + "type": (exception as Error).name ?? "" })); } else { // We have something else, so we cannot rely on stuff being present. - fs.writeSync(stream, JSON.stringify({ + fileSystem.writeSync(stream, JSON.stringify({ "message": JSON.stringify(exception), "stacktrace": "", - "type": exception.constructor.name ?? (Object.prototype.toString.call(exception)), + "type": (exception as Object).constructor.name ?? (Object.prototype.toString.call(exception)), "additional_message_keys": ["languages.javascript.runtime.invalid.exception"] })); } @@ -139,6 +139,10 @@ export function sendException(stream, exception) { } // Send an evaluation result to the given stream. -export function sendEvaluated(stream, result) { - fs.writeSync(stream, JSON.stringify(result)); +function sendEvaluated(stream: number, result: Object) { + fileSystem.writeSync(stream, JSON.stringify(result)); } + +exports.sendValue = sendValue; +exports.sendException = sendException; +exports.sendEvaluated = sendEvaluated; diff --git a/tested/testsuite.py b/tested/testsuite.py index 9d858f9f..a617fcb9 100644 --- a/tested/testsuite.py +++ b/tested/testsuite.py @@ -72,6 +72,7 @@ class SupportedLanguage(StrEnum): HASKELL = auto() JAVA = auto() JAVASCRIPT = auto() + TYPESCRIPT = auto() KOTLIN = auto() PYTHON = auto() RUNHASKELL = auto() diff --git a/tests/exercises/counter/solution/solution.ts b/tests/exercises/counter/solution/solution.ts new file mode 100644 index 00000000..8ffae64b --- /dev/null +++ b/tests/exercises/counter/solution/solution.ts @@ -0,0 +1,15 @@ +class Counter { + private count: number; + + constructor() { + this.count = 0; + } + + add() { + this.count++; + } + + get() { + this.count--; + } +} diff --git a/tests/exercises/echo-function-additional-source-files/solution/correct.ts b/tests/exercises/echo-function-additional-source-files/solution/correct.ts new file mode 100644 index 00000000..40bad740 --- /dev/null +++ b/tests/exercises/echo-function-additional-source-files/solution/correct.ts @@ -0,0 +1,5 @@ +const e = require("./echo.ts"); + +function echo(content) { + return e.echo(content); +} diff --git a/tests/exercises/echo-function-additional-source-files/workdir/echo.ts b/tests/exercises/echo-function-additional-source-files/workdir/echo.ts new file mode 100644 index 00000000..3bb749dc --- /dev/null +++ b/tests/exercises/echo-function-additional-source-files/workdir/echo.ts @@ -0,0 +1,5 @@ +function echo(content) { + return content; +} + +module.exports = { echo }; diff --git a/tests/exercises/echo-function-file-input/solution/correct.ts b/tests/exercises/echo-function-file-input/solution/correct.ts new file mode 100644 index 00000000..87e768a3 --- /dev/null +++ b/tests/exercises/echo-function-file-input/solution/correct.ts @@ -0,0 +1,5 @@ +const fs = require('fs'); + +function echoFile(content) { + return fs.readFileSync(content, {encoding:'utf8', flag:'r'}).trim(); +} diff --git a/tests/exercises/echo-function-file-output/solution/correct.ts b/tests/exercises/echo-function-file-output/solution/correct.ts new file mode 100644 index 00000000..da5cfb3d --- /dev/null +++ b/tests/exercises/echo-function-file-output/solution/correct.ts @@ -0,0 +1,5 @@ +const fs = require('fs'); + +function echoFunction(filename, stringToWrite) { + fs.writeFileSync(filename, stringToWrite + '\n', { flag: 'w' }); +} diff --git a/tests/exercises/global/solution/correct.ts b/tests/exercises/global/solution/correct.ts new file mode 100644 index 00000000..ba25496b --- /dev/null +++ b/tests/exercises/global/solution/correct.ts @@ -0,0 +1 @@ +const GLOBAL_VAR = "GLOBAL"; diff --git a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-java.tson b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-java.tson index 09f8546d..35c7d006 100644 --- a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-java.tson +++ b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-java.tson @@ -7,8 +7,8 @@ "contexts": [ { "before": { - "java": { - "data": "Supplier> ex = () -> {throw new AssertionError();};" + "typescript": { + "data": "const ex: () => Array = () => {throw new Error("AssertionError");};" } }, "testcases": [ diff --git a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson new file mode 100644 index 00000000..09f8546d --- /dev/null +++ b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson @@ -0,0 +1,88 @@ +{ + "tabs": [ + { + "name": "are_isbn", + "runs": [ + { + "contexts": [ + { + "before": { + "java": { + "data": "Supplier> ex = () -> {throw new AssertionError();};" + } + }, + "testcases": [ + { + "input": { + "type": "sequence", + "variable": "codes01", + "expression": { + "type": "function", + "namespace": "ex", + "name": "get", + "arguments": [] + } + } + }, + { + "input": { + "type": "function", + "name": "are_isbn", + "arguments": [ + "codes01" + ] + }, + "output": { + "result": { + "value": { + "data": [ + { + "data": false, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + } + ], + "type": "sequence" + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/tests/exercises/isbn/solution/solution.ts b/tests/exercises/isbn/solution/solution.ts new file mode 100644 index 00000000..7e007422 --- /dev/null +++ b/tests/exercises/isbn/solution/solution.ts @@ -0,0 +1,59 @@ +function isIsbn10(code): boolean { + + function checkDigit(code: string) { + let check: number = 0; + for (let i = 0; i < code.length - 1; i++) { + check += parseInt(code[i]) * (i + 1); + } + check %= 11; + return check === 10 ? 'X' : check.toString(); + } + + if (typeof code !== 'string') { + return false; + } + + if (code.length !== 10) { + return false; + } + + if (! isNaN(Number(code.substring(0, 9)))) { + return false; + } + + return code[9] === checkDigit(code); +} + + +function isIsbn13(code): boolean { + + function checkDigit(code: string) { + let check: number = 0; + for (let i = 0; i < code.length - 1; i++) { + check += parseInt(code[i]) * (i % 2 === 0 ? 1 : 3); + } + return ((10 - check) % 10).toString(); + } + + if (typeof code !== 'string') { + return false; + } + + if (code.length !== 13) { + return false; + } + + if (! isNaN(Number(code.substring(0, 12)))) { + return false; + } + + return code[12] === checkDigit(code); +} + +function isIsbn(code, isbn13: boolean=true): boolean { + return isbn13 ? isIsbn13(code) : isIsbn10(code); +} + +function areIsbn(codes: Array, isbn13: boolean=true): Array { + return codes.map((code) => isIsbn(code, isbn13)); +} diff --git a/tests/exercises/lotto/solution/correct.ts b/tests/exercises/lotto/solution/correct.ts new file mode 100644 index 00000000..a14f6082 --- /dev/null +++ b/tests/exercises/lotto/solution/correct.ts @@ -0,0 +1,7 @@ +function loterij(aantal=6, maximum = 42) { + const getallen = new Set(); + while (getallen.size < aantal) { + getallen.add(Math.floor(Math.random() * maximum) + 1); + } + return Array.from(getallen).sort((x: number, y: number) => x - y).join(" - "); +} diff --git a/tests/exercises/lotto/solution/wrong.ts b/tests/exercises/lotto/solution/wrong.ts new file mode 100644 index 00000000..4a7b33fd --- /dev/null +++ b/tests/exercises/lotto/solution/wrong.ts @@ -0,0 +1,7 @@ +function loterij(aantal=6, maximum = 42) { + const getallen = new Set(); + while (getallen.size < aantal) { + getallen.add(Math.floor(Math.random() * maximum) + 1); + } + return Array.from(getallen).sort((x: number, y: number) => y - x).join(" - "); +} diff --git a/tests/exercises/objects/solution/correct.ts b/tests/exercises/objects/solution/correct.ts new file mode 100644 index 00000000..ce09a081 --- /dev/null +++ b/tests/exercises/objects/solution/correct.ts @@ -0,0 +1,16 @@ +class EqualChecker { + + private number: number; + + constructor(number) { + this.number = number; + } + + check(other) { + return other === this.number; + } +} + +function setTest() { + return new Set([[1, 2], [2, 3]]); +} diff --git a/tests/exercises/sum/solution/correct.ts b/tests/exercises/sum/solution/correct.ts new file mode 100644 index 00000000..4781d7f3 --- /dev/null +++ b/tests/exercises/sum/solution/correct.ts @@ -0,0 +1,14 @@ +const getallen = process.argv.slice(1) +let som = 0 + +for (const getal of getallen) { + const r = parseInt(getal) + if (isNaN(r)) { + console.error("som: ongeldige argumenten") + process.exit(1); + } else { + som += r + } +} + +console.log(som); diff --git a/tests/language_markers.py b/tests/language_markers.py index 3a2acb76..ca96cd37 100644 --- a/tests/language_markers.py +++ b/tests/language_markers.py @@ -10,6 +10,7 @@ ] ALL_SPECIFIC_LANGUAGES = COMPILE_LANGUAGES + [ "javascript", + "typescript", "runhaskell", ] ALL_LANGUAGES = ALL_SPECIFIC_LANGUAGES + ["bash"] diff --git a/tests/test_functionality.py b/tests/test_functionality.py index d1dcb24e..262b7378 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -94,7 +94,7 @@ def test_generic_exception_wrong_error( assert updates.find_status_enum() == ["wrong"] -@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp"]) +@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp", "typescript"]) def test_assignment_and_use_in_expression( lang: str, tmp_path: Path, pytestconfig: pytest.Config ): From d047237942f705a7e1c30646a48a7c7b4dff2dd4 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 18 Oct 2024 10:26:01 +0200 Subject: [PATCH 03/49] Made an implementation that mostly works for typescript --- package.json | 3 + tested/dsl/schema-strict.json | 1 + tested/dsl/schema.json | 1 + tested/judge/compilation.py | 1 - tested/judge/core.py | 1 - tested/judge/utils.py | 1 - tested/languages/typescript/config.py | 14 +++-- tested/languages/typescript/generators.py | 44 ++++++------- tested/languages/typescript/parseAst.ts | 62 +++++++++++-------- .../languages/typescript/templates/values.ts | 15 +++-- .../echo-function/evaluation/evaluator.ts | 22 +++++++ .../evaluation/one-language-literals.yaml | 1 + .../evaluation/one-specific-argument.tson | 7 +++ .../evaluation/one-specific-argument.yaml | 5 ++ .../evaluation/two-specific.tson | 6 ++ .../echo-function/solution/correct.ts | 11 ++++ tests/exercises/echo/solution/comp-error.ts | 1 + tests/exercises/echo/solution/correct.ts | 3 + tests/exercises/echo/solution/run-error.ts | 1 + tests/exercises/echo/solution/wrong.ts | 1 + tests/exercises/isbn/solution/solution.ts | 31 ++++------ tsconfig.json | 11 ++++ 22 files changed, 165 insertions(+), 78 deletions(-) create mode 100644 package.json create mode 100644 tests/exercises/echo-function/evaluation/evaluator.ts create mode 100644 tests/exercises/echo-function/solution/correct.ts create mode 100644 tests/exercises/echo/solution/comp-error.ts create mode 100644 tests/exercises/echo/solution/correct.ts create mode 100644 tests/exercises/echo/solution/run-error.ts create mode 100644 tests/exercises/echo/solution/wrong.ts create mode 100644 tsconfig.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..4efd1077 --- /dev/null +++ b/package.json @@ -0,0 +1,3 @@ +{ + "type" : "module" +} diff --git a/tested/dsl/schema-strict.json b/tested/dsl/schema-strict.json index f9bd7a34..8ec453be 100644 --- a/tested/dsl/schema-strict.json +++ b/tested/dsl/schema-strict.json @@ -774,6 +774,7 @@ "haskell", "java", "javascript", + "typescript", "kotlin", "python", "runhaskell", diff --git a/tested/dsl/schema.json b/tested/dsl/schema.json index 2f714949..92478b6a 100644 --- a/tested/dsl/schema.json +++ b/tested/dsl/schema.json @@ -774,6 +774,7 @@ "haskell", "java", "javascript", + "typescript", "kotlin", "python", "runhaskell", diff --git a/tested/judge/compilation.py b/tested/judge/compilation.py index 38cd36ca..b079285e 100644 --- a/tested/judge/compilation.py +++ b/tested/judge/compilation.py @@ -63,7 +63,6 @@ def run_compilation( _logger.debug( "Generating files with command %s in directory %s", command, directory ) - breakpoint() result = run_command(directory, remaining, command) _logger.debug(f"Compilation dependencies are: {files}") return result, files diff --git a/tested/judge/core.py b/tested/judge/core.py index 033a7695..9d4ad65a 100644 --- a/tested/judge/core.py +++ b/tested/judge/core.py @@ -127,7 +127,6 @@ def judge(bundle: Bundle): return planned_units = plan_test_suite(bundle, strategy=PlanStrategy.OPTIMAL) - breakpoint() # Attempt to precompile everything. common_dir, dependencies, selector = _generate_files(bundle, planned_units) diff --git a/tested/judge/utils.py b/tested/judge/utils.py index 02e01625..c50a00b2 100644 --- a/tested/judge/utils.py +++ b/tested/judge/utils.py @@ -52,7 +52,6 @@ def run_command( try: timeout = int(timeout) if timeout is not None else None - breakpoint() process = subprocess.run( command, cwd=directory, diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 3e6db69b..5c608aa7 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -107,13 +107,15 @@ def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) if main_file: - return ["tsc", "--noEmit", main_file[0]], files + return (["tsc", "--module", "nodenext", "--moduleResolution", + "nodenext", "--noEmit", main_file[0]], + files) else: return [], files def execution(self, cwd: Path, file: str, arguments: list[str]) -> Command: # Used es2022 because of the top-level await feature (most up-to data). - return ["ts-node", "-O", '{"module": "es2022"}', file, *arguments] + return ["ts-node", "-O", '{"module": "commonjs"}', file, *arguments] def modify_solution(self, solution: Path): # import local to prevent errors @@ -125,19 +127,21 @@ def modify_solution(self, solution: Path): output = run_command( solution.parent, timeout=None, - command=["node", parse_file, str(solution.absolute())], + command=["ts-node", "-O", '{"module": "commonjs"}', parse_file, str(solution.absolute())], check=True, ) assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() - breakpoint() with open(solution, "a") as file: - print(f"\nexports.{namings} = {{{namings}}};", file=file) + print(f"\ndeclare var module: any;", file=file) + print(f"\nmodule.exports = {{{namings}}};", file=file) # Add strict mode to the script. with open(solution, "r") as file: non_strict = file.read() with open(solution, "w") as file: + # This rule is added because Typescript might + # complain about "require('fs')" being redeclared. file.write('"use strict";\n\n' + non_strict) self.config.dodona.source_offset -= 2 diff --git a/tested/languages/typescript/generators.py b/tested/languages/typescript/generators.py index ae10cb85..df9364ce 100644 --- a/tested/languages/typescript/generators.py +++ b/tested/languages/typescript/generators.py @@ -192,8 +192,8 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) if tc.testcase.is_main_testcase(): assert isinstance(tc.input, MainInput) result += f""" - delete require.cache[require.resolve("./{pu.submission_name}.js")]; - const {pu.submission_name} = require("./{pu.submission_name}.js"); + delete require.cache[require.resolve("./{pu.submission_name}.ts")]; + const {pu.submission_name} = require("./{pu.submission_name}.ts"); """ else: assert isinstance(tc.input, PreparedTestcaseStatement) @@ -212,8 +212,10 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) def convert_execution_unit(pu: PreparedExecutionUnit) -> str: - result = """ - const fs = require('fs'); + result = f""" + const {pu.testcase_separator_secret}Namespace = {{ + fs: require('fs') + }} const values = require("./values.ts"); """ @@ -223,21 +225,21 @@ def convert_execution_unit(pu: PreparedExecutionUnit) -> str: # We now open files for results and define some functions. result += f""" - const valueFile = fs.openSync("{pu.value_file}", "w"); - const exceptionFile = fs.openSync("{pu.exception_file}", "w"); + const valueFile = {pu.testcase_separator_secret}Namespace.fs.openSync("{pu.value_file}", "w"); + const exceptionFile = {pu.testcase_separator_secret}Namespace.fs.openSync("{pu.exception_file}", "w"); function writeSeparator() {{ - fs.writeSync(valueFile, "--{pu.testcase_separator_secret}-- SEP"); - fs.writeSync(exceptionFile, "--{pu.testcase_separator_secret}-- SEP"); - fs.writeSync(process.stdout.fd, "--{pu.testcase_separator_secret}-- SEP"); - fs.writeSync(process.stderr.fd, "--{pu.testcase_separator_secret}-- SEP"); + {pu.testcase_separator_secret}Namespace.fs.writeSync(valueFile, "--{pu.testcase_separator_secret}-- SEP"); + {pu.testcase_separator_secret}Namespace.fs.writeSync(exceptionFile, "--{pu.testcase_separator_secret}-- SEP"); + {pu.testcase_separator_secret}Namespace.fs.writeSync(process.stdout.fd, "--{pu.testcase_separator_secret}-- SEP"); + {pu.testcase_separator_secret}Namespace.fs.writeSync(process.stderr.fd, "--{pu.testcase_separator_secret}-- SEP"); }} function writeContextSeparator() {{ - fs.writeSync(valueFile, "--{pu.context_separator_secret}-- SEP"); - fs.writeSync(exceptionFile, "--{pu.context_separator_secret}-- SEP"); - fs.writeSync(process.stdout.fd, "--{pu.context_separator_secret}-- SEP"); - fs.writeSync(process.stderr.fd, "--{pu.context_separator_secret}-- SEP"); + {pu.testcase_separator_secret}Namespace.fs.writeSync(valueFile, "--{pu.context_separator_secret}-- SEP"); + {pu.testcase_separator_secret}Namespace.fs.writeSync(exceptionFile, "--{pu.context_separator_secret}-- SEP"); + {pu.testcase_separator_secret}Namespace.fs.writeSync(process.stdout.fd, "--{pu.context_separator_secret}-- SEP"); + {pu.testcase_separator_secret}Namespace.fs.writeSync(process.stderr.fd, "--{pu.context_separator_secret}-- SEP"); }} async function sendValue(value: unknown) {{ @@ -275,10 +277,10 @@ def convert_execution_unit(pu: PreparedExecutionUnit) -> str: await context{i}(); """ - result += """ - fs.closeSync(valueFile); - fs.closeSync(exceptionFile); - })(); + result += f""" + {pu.testcase_separator_secret}Namespace.fs.closeSync(valueFile); + {pu.testcase_separator_secret}Namespace.fs.closeSync(exceptionFile); + }})(); """ return result @@ -297,13 +299,13 @@ def convert_check_function(evaluator: str, function: FunctionCall) -> str: def convert_encoder(values: list[Value]) -> str: - result = """ + result = f""" const values = require('./values.ts'); - const fs = require("fs"); + const fileSystem = require("fs"); """ for value in values: result += f"values.sendValue(process.stdout.fd, {convert_value(value)});\n" - result += 'fs.writeSync(process.stdout.fd, "␞");\n' + result += f'fileSystem.writeSync(process.stdout.fd, "␞");\n' return result diff --git a/tested/languages/typescript/parseAst.ts b/tested/languages/typescript/parseAst.ts index 097113fc..ced6e1aa 100644 --- a/tested/languages/typescript/parseAst.ts +++ b/tested/languages/typescript/parseAst.ts @@ -1,40 +1,52 @@ -const { parse } = require('abstract-syntax-tree'); -const fs = require('fs'); +import * as ts from 'typescript'; +import * as fs from 'fs'; const source = fs.readFileSync(process.argv[2], 'utf-8'); -function mapSubTreeToIds(subtree) { - const type = subtree.type; - if (type === 'VariableDeclaration') { - return subtree.declarations.map(row => row.id); - } else if (type === 'FunctionDeclaration' || type === 'ClassDeclaration') { - return [subtree.id]; - } else if (type === 'ExpressionStatement' && - subtree.expression.type === 'AssignmentExpression') { - return [subtree.expression.left]; +const ast = ts.createSourceFile( + process.argv[2], // File name + source, // Source code + ts.ScriptTarget.ESNext, // Target language version + true // SetParentNodes option to preserve parent-child relationships +); + +// Helper function to extract relevant identifiers from AST nodes +function mapSubTreeToIds(node: ts.Node): Array { + if (ts.isVariableDeclaration(node)) { + return [node.name]; + } else if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) { + return [node.name]; + } else if (ts.isExpressionStatement(node) && + ts.isBinaryExpression(node.expression) && + node.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken) { + return [node.expression.left]; } else { - return []; + const ids: Array = []; + ts.forEachChild(node, (child: ts.Node) => { + ids.push(...mapSubTreeToIds(child)); + }); + return ids; } } -function mapIdToName(id) { - const type = id.type; - if (type === 'Identifier') { - return id.name; - } else if (type === 'ArrayPattern') { - return id.elements.flatMap(mapIdToName); - } else if (type === 'ObjectPattern') { - return id.properties.map(d => d.key).flatMap(mapIdToName); +// Convert node to identifier names, handling patterns (Array/Object destructuring) +function mapIdToName(node: ts.Node|undefined): Array { + if (!node) { + return []; + } + + if (ts.isIdentifier(node)) { + return [node.text]; + } else if (ts.isArrayBindingPattern(node)) { + return node.elements.flatMap(element => ts.isBindingElement(element) ? mapIdToName(element.name) : []); + } else if (ts.isObjectBindingPattern(node)) { + return node.elements.flatMap(prop => mapIdToName(prop.name)); } else { return []; } } -let ast; try { - // Add next option to support more TypeScript features. - const ast = parse(source, {next: true}).body; - // Use Set to remove duplicates - const array = Array.from(new Set(ast.flatMap(mapSubTreeToIds).flatMap(mapIdToName))); + const array = Array.from(new Set(mapSubTreeToIds(ast).flatMap(mapIdToName))); console.log(array.join(', ')); } catch (e) { // Assume this is invalid TypeScript at this point. diff --git a/tested/languages/typescript/templates/values.ts b/tested/languages/typescript/templates/values.ts index 2a2c64ca..6b24fffb 100644 --- a/tested/languages/typescript/templates/values.ts +++ b/tested/languages/typescript/templates/values.ts @@ -1,4 +1,7 @@ -const fileSystem = require('fs'); +const valueNamespace = { + fs: require('fs') +} +//const fs = require('fs'); function isVanillaObject(value: Object) : boolean { try { @@ -100,7 +103,7 @@ function encode(value: Object): { data: Object; diagnostic: any; type: string } // Send a value to the given stream. function sendValue(stream: number, value: Object) { - fileSystem.writeSync(stream, JSON.stringify(encode(value))); + valueNamespace.fs.writeSync(stream, JSON.stringify(encode(value))); } // Send an exception to the given stream. @@ -110,7 +113,7 @@ function sendException(stream: number, exception: Error | Object | {constructor: } if (exception instanceof Error) { // We have a proper error... - fileSystem.writeSync(stream, JSON.stringify({ + valueNamespace.fs.writeSync(stream, JSON.stringify({ "message": exception.message, "stacktrace": exception.stack ?? "", "type": exception.constructor.name @@ -121,14 +124,14 @@ function sendException(stream: number, exception: Error | Object | {constructor: // TODO: remove this once the semester is over // noinspection PointlessBooleanExpressionJS if (typeof exception === 'object') { - fileSystem.writeSync(stream, JSON.stringify({ + valueNamespace.fs.writeSync(stream, JSON.stringify({ "message": (exception as Error).message ?? "", "stacktrace": "", "type": (exception as Error).name ?? "" })); } else { // We have something else, so we cannot rely on stuff being present. - fileSystem.writeSync(stream, JSON.stringify({ + valueNamespace.fs.writeSync(stream, JSON.stringify({ "message": JSON.stringify(exception), "stacktrace": "", "type": (exception as Object).constructor.name ?? (Object.prototype.toString.call(exception)), @@ -140,7 +143,7 @@ function sendException(stream: number, exception: Error | Object | {constructor: // Send an evaluation result to the given stream. function sendEvaluated(stream: number, result: Object) { - fileSystem.writeSync(stream, JSON.stringify(result)); + valueNamespace.fs.writeSync(stream, JSON.stringify(result)); } exports.sendValue = sendValue; diff --git a/tests/exercises/echo-function/evaluation/evaluator.ts b/tests/exercises/echo-function/evaluation/evaluator.ts new file mode 100644 index 00000000..4cb3a9bf --- /dev/null +++ b/tests/exercises/echo-function/evaluation/evaluator.ts @@ -0,0 +1,22 @@ +function evaluate(actual: string) { + const correct = actual === "correct"; + return { + "result": correct, + "readable_expected": "correct", + "readable_actual": actual.toString(), + "messages": [{"description": "Hallo", "format": "text"}] + } +} + +function evaluateSum(actual: { toString: () => any; }, sum: number) { + const correct = sum == 10; + return { + "result": correct, + "readable_expected": "correct", + "readable_actual": actual.toString(), + "messages": [{"description": "Hallo", "format": "text"}] + } +} + +exports.evaluate = evaluate; +exports.evaluateSum = evaluateSum; diff --git a/tests/exercises/echo-function/evaluation/one-language-literals.yaml b/tests/exercises/echo-function/evaluation/one-language-literals.yaml index 20718509..667b8f38 100644 --- a/tests/exercises/echo-function/evaluation/one-language-literals.yaml +++ b/tests/exercises/echo-function/evaluation/one-language-literals.yaml @@ -6,6 +6,7 @@ runhaskell: "Submission.toString (1+1)" java: "Submission.toString(1+1)" javascript: "submission.toString(1+1)" + typescript: "submission.toString(1+1)" kotlin: "toString(1+1)" python: "submission.to_string(1+1)" csharp: "Submission.toString(1+1)" diff --git a/tests/exercises/echo-function/evaluation/one-specific-argument.tson b/tests/exercises/echo-function/evaluation/one-specific-argument.tson index 12bbb4c1..1f0dc54f 100644 --- a/tests/exercises/echo-function/evaluation/one-specific-argument.tson +++ b/tests/exercises/echo-function/evaluation/one-specific-argument.tson @@ -51,6 +51,10 @@ "file" : "evaluator.js", "name" : "evaluate_sum" }, + "typescript" : { + "file" : "evaluator.ts", + "name" : "evaluate_sum" + }, "csharp" : { "file" : "Evaluator.cs", "name" : "evaluate_sum" @@ -78,6 +82,9 @@ "javascript" : [ "5 + 5" ], + "typescript" : [ + "5 + 5" + ], "csharp" : [ "5 + 5" ] diff --git a/tests/exercises/echo-function/evaluation/one-specific-argument.yaml b/tests/exercises/echo-function/evaluation/one-specific-argument.yaml index c098a1ab..a3b116f9 100644 --- a/tests/exercises/echo-function/evaluation/one-specific-argument.yaml +++ b/tests/exercises/echo-function/evaluation/one-specific-argument.yaml @@ -25,6 +25,9 @@ javascript: file: evaluator.js name: evaluate_sum + typescript: + file: evaluator.ts + name: evaluate_sum csharp: file: Evaluator.cs name: evaluate_sum @@ -43,6 +46,8 @@ - 5 + 5 javascript: - 5 + 5 + typescript: + - 5 + 5 csharp: - 5 + 5 diff --git a/tests/exercises/echo-function/evaluation/two-specific.tson b/tests/exercises/echo-function/evaluation/two-specific.tson index 3cdec8b9..5710b879 100644 --- a/tests/exercises/echo-function/evaluation/two-specific.tson +++ b/tests/exercises/echo-function/evaluation/two-specific.tson @@ -44,6 +44,9 @@ "javascript": { "file": "evaluator.js" }, + "typescript": { + "file": "evaluator.ts" + }, "csharp": { "file": "Evaluator.cs" } @@ -89,6 +92,9 @@ "javascript": { "file": "evaluator.js" }, + "typescript": { + "file": "evaluator.ts" + }, "csharp": { "file": "Evaluator.cs" } diff --git a/tests/exercises/echo-function/solution/correct.ts b/tests/exercises/echo-function/solution/correct.ts new file mode 100644 index 00000000..c381c461 --- /dev/null +++ b/tests/exercises/echo-function/solution/correct.ts @@ -0,0 +1,11 @@ +function echo(content: Object) { + return content; +} + +function noEcho(content: Object) { + // Do nothing. +} + +function toString(number: Object): string { + return number.toString(); +} diff --git a/tests/exercises/echo/solution/comp-error.ts b/tests/exercises/echo/solution/comp-error.ts new file mode 100644 index 00000000..a34a07a4 --- /dev/null +++ b/tests/exercises/echo/solution/comp-error.ts @@ -0,0 +1 @@ +mfzej àryhg çyh aiogharuio ghqgh diff --git a/tests/exercises/echo/solution/correct.ts b/tests/exercises/echo/solution/correct.ts new file mode 100644 index 00000000..37e0cbfb --- /dev/null +++ b/tests/exercises/echo/solution/correct.ts @@ -0,0 +1,3 @@ +import * as fs from 'fs'; +const stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0 +console.log(stdinBuffer.toString().trimEnd()); diff --git a/tests/exercises/echo/solution/run-error.ts b/tests/exercises/echo/solution/run-error.ts new file mode 100644 index 00000000..7a12bbf8 --- /dev/null +++ b/tests/exercises/echo/solution/run-error.ts @@ -0,0 +1 @@ +does_not_exist() diff --git a/tests/exercises/echo/solution/wrong.ts b/tests/exercises/echo/solution/wrong.ts new file mode 100644 index 00000000..2e1bb218 --- /dev/null +++ b/tests/exercises/echo/solution/wrong.ts @@ -0,0 +1 @@ +console.log("WRONG"); diff --git a/tests/exercises/isbn/solution/solution.ts b/tests/exercises/isbn/solution/solution.ts index 7e007422..263bdc91 100644 --- a/tests/exercises/isbn/solution/solution.ts +++ b/tests/exercises/isbn/solution/solution.ts @@ -1,23 +1,19 @@ -function isIsbn10(code): boolean { +function isIsbn10(code: string): boolean { function checkDigit(code: string) { let check: number = 0; - for (let i = 0; i < code.length - 1; i++) { + for (let i = 0; i < 9; i++) { check += parseInt(code[i]) * (i + 1); } check %= 11; return check === 10 ? 'X' : check.toString(); } - if (typeof code !== 'string') { - return false; - } - if (code.length !== 10) { return false; } - if (! isNaN(Number(code.substring(0, 9)))) { + if (isNaN(Number(code.substring(0, 9)))) { return false; } @@ -25,35 +21,34 @@ function isIsbn10(code): boolean { } -function isIsbn13(code): boolean { +function isIsbn13(code: string): boolean { function checkDigit(code: string) { let check: number = 0; - for (let i = 0; i < code.length - 1; i++) { + for (let i = 0; i < 12; i++) { check += parseInt(code[i]) * (i % 2 === 0 ? 1 : 3); } - return ((10 - check) % 10).toString(); - } - - if (typeof code !== 'string') { - return false; + return ((((10 - check) % 10) + 10) % 10).toString(); } if (code.length !== 13) { return false; } - if (! isNaN(Number(code.substring(0, 12)))) { + if (isNaN(Number(code.substring(0, 12)))) { return false; } return code[12] === checkDigit(code); } -function isIsbn(code, isbn13: boolean=true): boolean { +function isIsbn(code: string, isbn13: boolean=true): boolean { return isbn13 ? isIsbn13(code) : isIsbn10(code); } -function areIsbn(codes: Array, isbn13: boolean=true): Array { - return codes.map((code) => isIsbn(code, isbn13)); +function areIsbn(codes: Array, isbn13: boolean | undefined=undefined): Array { + if (isbn13 === undefined) { + return codes.map((code:unknown) => typeof code === 'string' ? isIsbn(code, code.length === 13) : false); + } + return codes.map((code:unknown) => typeof code === 'string' ? isIsbn(code, isbn13) : false); } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..0a7708b6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions" : { + "esModuleInterop" : true, + "module" : "es2022", + "moduleResolution" : "nodenext", + }, + "ts-node" : { + "esm" : true, + "experimentalSpecifierResolution" : "node" + } +} From 53886dba28cd157353b71b790029ee3baf950303 Mon Sep 17 00:00:00 2001 From: breblanc Date: Wed, 23 Oct 2024 12:45:22 +0200 Subject: [PATCH 04/49] More consistent use of ESM. Things still broken when running tests --- tested/languages/typescript/config.py | 16 +++--- tested/languages/typescript/generators.py | 50 +++++++++---------- .../typescript/{parseAst.ts => parseAst.mts} | 0 .../templates/{values.ts => values.mts} | 18 +++---- .../solution/{solution.ts => solution.mts} | 0 .../solution/{correct.ts => correct.mts} | 0 .../workdir/{echo.ts => echo.mts} | 0 .../solution/{correct.ts => correct.mts} | 0 .../solution/{correct.ts => correct.mts} | 0 .../{evaluator.ts => evaluator.mts} | 0 .../solution/{correct.ts => correct.mts} | 0 .../{comp-error.ts => comp-error.mts} | 0 .../echo/solution/{correct.ts => correct.mts} | 0 .../solution/{run-error.ts => run-error.mts} | 0 .../echo/solution/{wrong.ts => wrong.mts} | 0 .../solution/{correct.ts => correct.mts} | 0 .../solution/{solution.ts => solution.mts} | 0 .../solution/{correct.ts => correct.mts} | 0 .../solution/{correct.ts => correct.mts} | 0 .../sum/solution/{correct.ts => correct.mts} | 0 tsconfig.json | 8 ++- 21 files changed, 45 insertions(+), 47 deletions(-) rename tested/languages/typescript/{parseAst.ts => parseAst.mts} (100%) rename tested/languages/typescript/templates/{values.ts => values.mts} (90%) rename tests/exercises/counter/solution/{solution.ts => solution.mts} (100%) rename tests/exercises/echo-function-additional-source-files/solution/{correct.ts => correct.mts} (100%) rename tests/exercises/echo-function-additional-source-files/workdir/{echo.ts => echo.mts} (100%) rename tests/exercises/echo-function-file-input/solution/{correct.ts => correct.mts} (100%) rename tests/exercises/echo-function-file-output/solution/{correct.ts => correct.mts} (100%) rename tests/exercises/echo-function/evaluation/{evaluator.ts => evaluator.mts} (100%) rename tests/exercises/echo-function/solution/{correct.ts => correct.mts} (100%) rename tests/exercises/echo/solution/{comp-error.ts => comp-error.mts} (100%) rename tests/exercises/echo/solution/{correct.ts => correct.mts} (100%) rename tests/exercises/echo/solution/{run-error.ts => run-error.mts} (100%) rename tests/exercises/echo/solution/{wrong.ts => wrong.mts} (100%) rename tests/exercises/global/solution/{correct.ts => correct.mts} (100%) rename tests/exercises/isbn/solution/{solution.ts => solution.mts} (100%) rename tests/exercises/lotto/solution/{correct.ts => correct.mts} (100%) rename tests/exercises/objects/solution/{correct.ts => correct.mts} (100%) rename tests/exercises/sum/solution/{correct.ts => correct.mts} (100%) diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 5c608aa7..c1e3110e 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -35,13 +35,13 @@ class TypeScript(Language): def initial_dependencies(self) -> list[str]: - return ["values.ts"] + return ["values.mts"] def needs_selector(self) -> bool: return False def file_extension(self) -> str: - return "ts" + return "mts" def naming_conventions(self) -> dict[Conventionable, NamingConventions]: return { @@ -115,7 +115,7 @@ def compilation(self, files: list[str]) -> CallbackResult: def execution(self, cwd: Path, file: str, arguments: list[str]) -> Command: # Used es2022 because of the top-level await feature (most up-to data). - return ["ts-node", "-O", '{"module": "commonjs"}', file, *arguments] + return ["node", "--no-warnings", "--loader", "ts-node/esm", file, *arguments] def modify_solution(self, solution: Path): # import local to prevent errors @@ -123,18 +123,18 @@ def modify_solution(self, solution: Path): assert self.config - parse_file = str(Path(__file__).parent / "parseAst.ts") + parse_file = str(Path(__file__).parent / "parseAst.mts") output = run_command( solution.parent, timeout=None, - command=["ts-node", "-O", '{"module": "commonjs"}', parse_file, str(solution.absolute())], - check=True, + command=["node", "--no-warnings", "--loader", "ts-node/esm", parse_file, str(solution.absolute())], + check=False, ) assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() with open(solution, "a") as file: - print(f"\ndeclare var module: any;", file=file) - print(f"\nmodule.exports = {{{namings}}};", file=file) + # print(f"\ndeclare var module: any;", file=file) + print(f"\nexport {{{namings}}};", file=file) # Add strict mode to the script. with open(solution, "r") as file: diff --git a/tested/languages/typescript/generators.py b/tested/languages/typescript/generators.py index df9364ce..1560ed0c 100644 --- a/tested/languages/typescript/generators.py +++ b/tested/languages/typescript/generators.py @@ -160,8 +160,8 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) if not ctx.context.has_main_testcase(): result += f""" writeSeparator(); - delete require.cache[require.resolve("./{submission_file(pu.language)}")]; - const {pu.submission_name} = require("./{submission_file(pu.language)}"); + //delete require.cache[require.resolve("./{submission_file(pu.language)}")]; + let {pu.submission_name} = await import('./{submission_file(pu.language)}'); """ # Generate code for each testcase @@ -192,8 +192,8 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) if tc.testcase.is_main_testcase(): assert isinstance(tc.input, MainInput) result += f""" - delete require.cache[require.resolve("./{pu.submission_name}.ts")]; - const {pu.submission_name} = require("./{pu.submission_name}.ts"); + //delete require.cache[require.resolve("./{pu.submission_name}.mts")]; + let {pu.submission_name} = await import('./{pu.submission_name}.mts'); """ else: assert isinstance(tc.input, PreparedTestcaseStatement) @@ -213,33 +213,31 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) def convert_execution_unit(pu: PreparedExecutionUnit) -> str: result = f""" - const {pu.testcase_separator_secret}Namespace = {{ - fs: require('fs') - }} - const values = require("./values.ts"); + import * as fs from 'fs'; + import * as values from './values.mts'; """ # Import the language specific functions we will need. for name in pu.evaluator_names: - result += f'const {name} = require("./{name}.ts");\n' + result += f"import * as {name} from './{name}.mts';\n" # We now open files for results and define some functions. result += f""" - const valueFile = {pu.testcase_separator_secret}Namespace.fs.openSync("{pu.value_file}", "w"); - const exceptionFile = {pu.testcase_separator_secret}Namespace.fs.openSync("{pu.exception_file}", "w"); + const valueFile = fs.openSync("{pu.value_file}", "w"); + const exceptionFile = fs.openSync("{pu.exception_file}", "w"); function writeSeparator() {{ - {pu.testcase_separator_secret}Namespace.fs.writeSync(valueFile, "--{pu.testcase_separator_secret}-- SEP"); - {pu.testcase_separator_secret}Namespace.fs.writeSync(exceptionFile, "--{pu.testcase_separator_secret}-- SEP"); - {pu.testcase_separator_secret}Namespace.fs.writeSync(process.stdout.fd, "--{pu.testcase_separator_secret}-- SEP"); - {pu.testcase_separator_secret}Namespace.fs.writeSync(process.stderr.fd, "--{pu.testcase_separator_secret}-- SEP"); + fs.writeSync(valueFile, "--{pu.testcase_separator_secret}-- SEP"); + fs.writeSync(exceptionFile, "--{pu.testcase_separator_secret}-- SEP"); + fs.writeSync(process.stdout.fd, "--{pu.testcase_separator_secret}-- SEP"); + fs.writeSync(process.stderr.fd, "--{pu.testcase_separator_secret}-- SEP"); }} function writeContextSeparator() {{ - {pu.testcase_separator_secret}Namespace.fs.writeSync(valueFile, "--{pu.context_separator_secret}-- SEP"); - {pu.testcase_separator_secret}Namespace.fs.writeSync(exceptionFile, "--{pu.context_separator_secret}-- SEP"); - {pu.testcase_separator_secret}Namespace.fs.writeSync(process.stdout.fd, "--{pu.context_separator_secret}-- SEP"); - {pu.testcase_separator_secret}Namespace.fs.writeSync(process.stderr.fd, "--{pu.context_separator_secret}-- SEP"); + fs.writeSync(valueFile, "--{pu.context_separator_secret}-- SEP"); + fs.writeSync(exceptionFile, "--{pu.context_separator_secret}-- SEP"); + fs.writeSync(process.stdout.fd, "--{pu.context_separator_secret}-- SEP"); + fs.writeSync(process.stderr.fd, "--{pu.context_separator_secret}-- SEP"); }} async function sendValue(value: unknown) {{ @@ -278,8 +276,8 @@ def convert_execution_unit(pu: PreparedExecutionUnit) -> str: """ result += f""" - {pu.testcase_separator_secret}Namespace.fs.closeSync(valueFile); - {pu.testcase_separator_secret}Namespace.fs.closeSync(exceptionFile); + fs.closeSync(valueFile); + fs.closeSync(exceptionFile); }})(); """ @@ -289,8 +287,8 @@ def convert_execution_unit(pu: PreparedExecutionUnit) -> str: def convert_check_function(evaluator: str, function: FunctionCall) -> str: return f""" (async () => {{ - const {evaluator} = require('./{evaluator}.ts'); - const values = require('./values.ts'); + import * as {evaluator} from './{evaluator}.mts'; + import * as values from './values.mts'; const result = {convert_function_call(function)}; values.sendEvaluated(process.stdout.fd, result); @@ -300,12 +298,12 @@ def convert_check_function(evaluator: str, function: FunctionCall) -> str: def convert_encoder(values: list[Value]) -> str: result = f""" - const values = require('./values.ts'); - const fileSystem = require("fs"); + import * as values from './values.mts'; + import * as fs from 'fs'; """ for value in values: result += f"values.sendValue(process.stdout.fd, {convert_value(value)});\n" - result += f'fileSystem.writeSync(process.stdout.fd, "␞");\n' + result += f'fs.writeSync(process.stdout.fd, "␞");\n' return result diff --git a/tested/languages/typescript/parseAst.ts b/tested/languages/typescript/parseAst.mts similarity index 100% rename from tested/languages/typescript/parseAst.ts rename to tested/languages/typescript/parseAst.mts diff --git a/tested/languages/typescript/templates/values.ts b/tested/languages/typescript/templates/values.mts similarity index 90% rename from tested/languages/typescript/templates/values.ts rename to tested/languages/typescript/templates/values.mts index 6b24fffb..d7a07737 100644 --- a/tested/languages/typescript/templates/values.ts +++ b/tested/languages/typescript/templates/values.mts @@ -1,6 +1,4 @@ -const valueNamespace = { - fs: require('fs') -} +import * as fs from 'fs'; //const fs = require('fs'); function isVanillaObject(value: Object) : boolean { @@ -103,7 +101,7 @@ function encode(value: Object): { data: Object; diagnostic: any; type: string } // Send a value to the given stream. function sendValue(stream: number, value: Object) { - valueNamespace.fs.writeSync(stream, JSON.stringify(encode(value))); + fs.writeSync(stream, JSON.stringify(encode(value))); } // Send an exception to the given stream. @@ -113,7 +111,7 @@ function sendException(stream: number, exception: Error | Object | {constructor: } if (exception instanceof Error) { // We have a proper error... - valueNamespace.fs.writeSync(stream, JSON.stringify({ + fs.writeSync(stream, JSON.stringify({ "message": exception.message, "stacktrace": exception.stack ?? "", "type": exception.constructor.name @@ -124,14 +122,14 @@ function sendException(stream: number, exception: Error | Object | {constructor: // TODO: remove this once the semester is over // noinspection PointlessBooleanExpressionJS if (typeof exception === 'object') { - valueNamespace.fs.writeSync(stream, JSON.stringify({ + fs.writeSync(stream, JSON.stringify({ "message": (exception as Error).message ?? "", "stacktrace": "", "type": (exception as Error).name ?? "" })); } else { // We have something else, so we cannot rely on stuff being present. - valueNamespace.fs.writeSync(stream, JSON.stringify({ + fs.writeSync(stream, JSON.stringify({ "message": JSON.stringify(exception), "stacktrace": "", "type": (exception as Object).constructor.name ?? (Object.prototype.toString.call(exception)), @@ -143,9 +141,7 @@ function sendException(stream: number, exception: Error | Object | {constructor: // Send an evaluation result to the given stream. function sendEvaluated(stream: number, result: Object) { - valueNamespace.fs.writeSync(stream, JSON.stringify(result)); + fs.writeSync(stream, JSON.stringify(result)); } -exports.sendValue = sendValue; -exports.sendException = sendException; -exports.sendEvaluated = sendEvaluated; +export { sendValue, sendException, sendEvaluated }; diff --git a/tests/exercises/counter/solution/solution.ts b/tests/exercises/counter/solution/solution.mts similarity index 100% rename from tests/exercises/counter/solution/solution.ts rename to tests/exercises/counter/solution/solution.mts diff --git a/tests/exercises/echo-function-additional-source-files/solution/correct.ts b/tests/exercises/echo-function-additional-source-files/solution/correct.mts similarity index 100% rename from tests/exercises/echo-function-additional-source-files/solution/correct.ts rename to tests/exercises/echo-function-additional-source-files/solution/correct.mts diff --git a/tests/exercises/echo-function-additional-source-files/workdir/echo.ts b/tests/exercises/echo-function-additional-source-files/workdir/echo.mts similarity index 100% rename from tests/exercises/echo-function-additional-source-files/workdir/echo.ts rename to tests/exercises/echo-function-additional-source-files/workdir/echo.mts diff --git a/tests/exercises/echo-function-file-input/solution/correct.ts b/tests/exercises/echo-function-file-input/solution/correct.mts similarity index 100% rename from tests/exercises/echo-function-file-input/solution/correct.ts rename to tests/exercises/echo-function-file-input/solution/correct.mts diff --git a/tests/exercises/echo-function-file-output/solution/correct.ts b/tests/exercises/echo-function-file-output/solution/correct.mts similarity index 100% rename from tests/exercises/echo-function-file-output/solution/correct.ts rename to tests/exercises/echo-function-file-output/solution/correct.mts diff --git a/tests/exercises/echo-function/evaluation/evaluator.ts b/tests/exercises/echo-function/evaluation/evaluator.mts similarity index 100% rename from tests/exercises/echo-function/evaluation/evaluator.ts rename to tests/exercises/echo-function/evaluation/evaluator.mts diff --git a/tests/exercises/echo-function/solution/correct.ts b/tests/exercises/echo-function/solution/correct.mts similarity index 100% rename from tests/exercises/echo-function/solution/correct.ts rename to tests/exercises/echo-function/solution/correct.mts diff --git a/tests/exercises/echo/solution/comp-error.ts b/tests/exercises/echo/solution/comp-error.mts similarity index 100% rename from tests/exercises/echo/solution/comp-error.ts rename to tests/exercises/echo/solution/comp-error.mts diff --git a/tests/exercises/echo/solution/correct.ts b/tests/exercises/echo/solution/correct.mts similarity index 100% rename from tests/exercises/echo/solution/correct.ts rename to tests/exercises/echo/solution/correct.mts diff --git a/tests/exercises/echo/solution/run-error.ts b/tests/exercises/echo/solution/run-error.mts similarity index 100% rename from tests/exercises/echo/solution/run-error.ts rename to tests/exercises/echo/solution/run-error.mts diff --git a/tests/exercises/echo/solution/wrong.ts b/tests/exercises/echo/solution/wrong.mts similarity index 100% rename from tests/exercises/echo/solution/wrong.ts rename to tests/exercises/echo/solution/wrong.mts diff --git a/tests/exercises/global/solution/correct.ts b/tests/exercises/global/solution/correct.mts similarity index 100% rename from tests/exercises/global/solution/correct.ts rename to tests/exercises/global/solution/correct.mts diff --git a/tests/exercises/isbn/solution/solution.ts b/tests/exercises/isbn/solution/solution.mts similarity index 100% rename from tests/exercises/isbn/solution/solution.ts rename to tests/exercises/isbn/solution/solution.mts diff --git a/tests/exercises/lotto/solution/correct.ts b/tests/exercises/lotto/solution/correct.mts similarity index 100% rename from tests/exercises/lotto/solution/correct.ts rename to tests/exercises/lotto/solution/correct.mts diff --git a/tests/exercises/objects/solution/correct.ts b/tests/exercises/objects/solution/correct.mts similarity index 100% rename from tests/exercises/objects/solution/correct.ts rename to tests/exercises/objects/solution/correct.mts diff --git a/tests/exercises/sum/solution/correct.ts b/tests/exercises/sum/solution/correct.mts similarity index 100% rename from tests/exercises/sum/solution/correct.ts rename to tests/exercises/sum/solution/correct.mts diff --git a/tsconfig.json b/tsconfig.json index 0a7708b6..ed93465f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,15 @@ { + "extends": "ts-node/node16/tsconfig.json", "compilerOptions" : { "esModuleInterop" : true, - "module" : "es2022", - "moduleResolution" : "nodenext", + "module" : "ESNext", + "moduleResolution" : "node", + "allowImportingTsExtensions" : true, + "types" : ["node"], }, "ts-node" : { "esm" : true, + "transpileOnly": true, "experimentalSpecifierResolution" : "node" } } From 3d6c7c9d282bf2078e6a4b7c88aa40edc7e620ba Mon Sep 17 00:00:00 2001 From: breblanc Date: Wed, 23 Oct 2024 17:22:44 +0200 Subject: [PATCH 05/49] Tried fixing the error regarding ts-node being unknown. Still no solution yet --- flake.nix | 12 +++++++++--- package.json | 3 --- 2 files changed, 9 insertions(+), 6 deletions(-) delete mode 100644 package.json diff --git a/flake.nix b/flake.nix index 43e976d8..7b0a57b2 100644 --- a/flake.nix +++ b/flake.nix @@ -111,7 +111,7 @@ doCheck = false; postInstall = '' wrapProgram "$out/bin/tested" \ - --prefix NODE_PATH : ${ast}/lib/node_modules + --prefix NODE_PATH : ${ast}/lib/node_modules:/nix/store/1vkwbqyd02jfmgyaxq3bly78rlx5mqx9-TESTed-dir/bin/ts-node ''; }; devShell = self.outputs.devShells.${system}.default; @@ -123,8 +123,10 @@ checkPhase = '' DOTNET_CLI_HOME="$(mktemp -d)" export DOTNET_CLI_HOME - NODE_PATH=${ast}/lib/node_modules + TS_NODE_PROJECT=./tsconfig.json + NODE_PATH=${ast}/lib/node_modules:/nix/store/1vkwbqyd02jfmgyaxq3bly78rlx5mqx9-TESTed-dir/bin/ts-node export NODE_PATH + export TS_NODE_PROJECT poetry run pytest -n auto --cov=tested --cov-report=xml tests/ ''; installPhase = '' @@ -154,7 +156,11 @@ } { name = "NODE_PATH"; - prefix = "${ast}/lib/node_modules"; + prefix = "${ast}/lib/node_modules:/nix/store/1vkwbqyd02jfmgyaxq3bly78rlx5mqx9-TESTed-dir/bin/ts-node"; + } + { + name = "TS_NODE_PROJECT"; + prefix = "./tsconfig.json"; } ]; commands = [ diff --git a/package.json b/package.json deleted file mode 100644 index 4efd1077..00000000 --- a/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type" : "module" -} From 896b00018294e19e122f0fddf92c8761892a5f76 Mon Sep 17 00:00:00 2001 From: Niko Strijbol Date: Wed, 23 Oct 2024 23:53:09 +0200 Subject: [PATCH 06/49] Add tsx in flake --- flake.lock | 84 ++++++++++++++++-------------------------------------- flake.nix | 53 ++++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 65 deletions(-) diff --git a/flake.lock b/flake.lock index 37e3aa59..642374cb 100644 --- a/flake.lock +++ b/flake.lock @@ -2,17 +2,16 @@ "nodes": { "devshell": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1713532798, - "narHash": "sha256-wtBhsdMJA3Wa32Wtm1eeo84GejtI43pMrFrmwLXrsEc=", + "lastModified": 1728330715, + "narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=", "owner": "numtide", "repo": "devshell", - "rev": "12e914740a25ea1891ec619bb53cf5e6ca922e40", + "rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef", "type": "github" }, "original": { @@ -26,11 +25,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -44,29 +43,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_3": { - "inputs": { - "systems": "systems_3" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -83,11 +64,11 @@ ] }, "locked": { - "lastModified": 1703863825, - "narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=", + "lastModified": 1720066371, + "narHash": "sha256-uPlLYH2S0ACj0IcgaK9Lsf4spmJoGejR9DotXiXSBZQ=", "owner": "nix-community", "repo": "nix-github-actions", - "rev": "5163432afc817cf8bd1f031418d1869e4c9d5547", + "rev": "622f829f5fe69310a866c8a6cd07e747c44ef820", "type": "github" }, "original": { @@ -98,11 +79,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1716293225, - "narHash": "sha256-pU9ViBVE3XYb70xZx+jK6SEVphvt7xMTbm6yDIF4xPs=", + "lastModified": 1729413321, + "narHash": "sha256-I4tuhRpZFa6Fu6dcH9Dlo5LlH17peT79vx1y1SpeKt0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916", + "rev": "1997e4aa514312c1af7e2bda7fad1644e778ff26", "type": "github" }, "original": { @@ -114,25 +95,25 @@ }, "poetry2nix": { "inputs": { - "flake-utils": "flake-utils_3", + "flake-utils": "flake-utils_2", "nix-github-actions": "nix-github-actions", "nixpkgs": [ "nixpkgs" ], - "systems": "systems_4", + "systems": "systems_3", "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1715251496, - "narHash": "sha256-vRBfJCKvJtu5sYev56XStirA3lAOPv0EkoEV2Nfc+tc=", + "lastModified": 1729621708, + "narHash": "sha256-NEagK4dENd9Riu8Gc1JHOnrKL/3TmiAINDCAvXqWkL4=", "owner": "nix-community", "repo": "poetry2nix", - "rev": "291a863e866972f356967d0a270b259f46bf987f", + "rev": "f7f9446455084a6333c428371afe23229379cf62", "type": "github" }, "original": { "owner": "nix-community", - "ref": "refs/tags/2024.5.939250", + "ref": "refs/tags/2024.10.2267247", "repo": "poetry2nix", "type": "github" } @@ -140,7 +121,7 @@ "root": { "inputs": { "devshell": "devshell", - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", "poetry2nix": "poetry2nix" } @@ -176,21 +157,6 @@ } }, "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_4": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -212,11 +178,11 @@ ] }, "locked": { - "lastModified": 1714058656, - "narHash": "sha256-Qv4RBm4LKuO4fNOfx9wl40W2rBbv5u5m+whxRYUMiaA=", + "lastModified": 1727984844, + "narHash": "sha256-xpRqITAoD8rHlXQafYZOLvUXCF6cnZkPfoq67ThN0Hc=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "c6aaf729f34a36c445618580a9f95a48f5e4e03f", + "rev": "4446c7a6fc0775df028c5a3f6727945ba8400e64", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 7b0a57b2..e41a7086 100644 --- a/flake.nix +++ b/flake.nix @@ -9,7 +9,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; poetry2nix = { - url = "github:nix-community/poetry2nix?ref=refs/tags/2024.5.939250"; + url = "github:nix-community/poetry2nix?ref=refs/tags/2024.10.2267247"; inputs.nixpkgs.follows = "nixpkgs"; }; }; @@ -45,6 +45,47 @@ nodejs_base = pkgs.nodejs_22; + tsx = pkgs.stdenvNoCC.mkDerivation rec { + pname = "tsx"; + version = "4.19.1"; + + src = pkgs.fetchFromGitHub { + owner = "privatenumber"; + repo = pname; + rev = "v${version}"; + hash = "sha256-3fEH1KiQqsw3NOtyvbx0DCWwWz+n8YFHUJAw8/8mMR8="; + }; + + nativeBuildInputs = [ + nodejs_base + pkgs.pnpm.configHook + pkgs.makeWrapper + ]; + + pnpmDeps = pkgs.pnpm.fetchDeps { + inherit pname version src; + hash = "sha256-wOw7kHP6ajFwOefvl4vphxSmm1jhJXTQ+2knqSjyuek="; + }; + + buildPhase = '' + runHook preBuild + + pnpm build + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p "$out/bin" "$out/lib/tsx" + cp -r dist node_modules "$out/lib/tsx" + makeWrapper "${pkgs.lib.getExe nodejs_base}" "$out/bin/tsx" --add-flags "$out/lib/tsx/dist/cli.mjs" + + runHook postInstall + ''; + }; + # This one isn't in Nix, so do it manually. ast = pkgs.buildNpmPackage rec { pname = "abstract-syntax-tree"; @@ -75,7 +116,7 @@ (pkgs.haskell.packages.ghc96.ghcWithPackages (p: [ p.aeson ])) pkgs.hlint ]; - ts-deps = [ nodejs_base pkgs.typescript pkgs.nodePackages.ts-node ]; + ts-deps = [ nodejs_base pkgs.typescript tsx ]; node-deps = [ nodejs_base pkgs.nodePackages.eslint ast ]; bash-deps = [ pkgs.shellcheck ]; c-deps = [ pkgs.cppcheck pkgs.gcc13 ]; @@ -111,7 +152,7 @@ doCheck = false; postInstall = '' wrapProgram "$out/bin/tested" \ - --prefix NODE_PATH : ${ast}/lib/node_modules:/nix/store/1vkwbqyd02jfmgyaxq3bly78rlx5mqx9-TESTed-dir/bin/ts-node + --prefix NODE_PATH : ${ast}/lib/node_modules ''; }; devShell = self.outputs.devShells.${system}.default; @@ -124,7 +165,7 @@ DOTNET_CLI_HOME="$(mktemp -d)" export DOTNET_CLI_HOME TS_NODE_PROJECT=./tsconfig.json - NODE_PATH=${ast}/lib/node_modules:/nix/store/1vkwbqyd02jfmgyaxq3bly78rlx5mqx9-TESTed-dir/bin/ts-node + NODE_PATH=${ast}/lib/node_modules export NODE_PATH export TS_NODE_PROJECT poetry run pytest -n auto --cov=tested --cov-report=xml tests/ @@ -141,7 +182,7 @@ tested = pkgs.devshell.mkShell { name = "TESTed"; - packages = [ python-dev-env pkgs.nodePackages.pyright poetry-package ] + packages = [ python-dev-env pkgs.pyright poetry-package ] ++ all-other-dependencies; devshell.startup.link.text = '' @@ -156,7 +197,7 @@ } { name = "NODE_PATH"; - prefix = "${ast}/lib/node_modules:/nix/store/1vkwbqyd02jfmgyaxq3bly78rlx5mqx9-TESTed-dir/bin/ts-node"; + prefix = "${ast}/lib/node_modules"; } { name = "TS_NODE_PROJECT"; From 20da6b025abce89d1d5cfa77089c1e55244b65b5 Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 24 Oct 2024 16:17:47 +0200 Subject: [PATCH 07/49] Tsx works for the most part. The same error with "fs" still persists somehow. --- tested/languages/typescript/config.py | 10 +++++----- tested/languages/typescript/generators.py | 14 +++++++------- .../typescript/{parseAst.mts => parseAst.ts} | 0 .../typescript/templates/{values.mts => values.ts} | 3 +-- .../counter/solution/{solution.mts => solution.ts} | 0 .../solution/{correct.mts => correct.ts} | 0 .../workdir/{echo.mts => echo.ts} | 0 .../solution/{correct.mts => correct.ts} | 0 .../solution/{correct.mts => correct.ts} | 0 .../evaluation/{evaluator.mts => evaluator.ts} | 0 .../solution/{correct.mts => correct.ts} | 0 .../solution/{comp-error.mts => comp-error.ts} | 0 .../echo/solution/{correct.mts => correct.ts} | 0 .../echo/solution/{run-error.mts => run-error.ts} | 0 .../echo/solution/{wrong.mts => wrong.ts} | 0 .../global/solution/{correct.mts => correct.ts} | 0 .../isbn/solution/{solution.mts => solution.ts} | 0 .../lotto/solution/{correct.mts => correct.ts} | 0 .../objects/solution/{correct.mts => correct.ts} | 0 .../sum/solution/{correct.mts => correct.ts} | 0 20 files changed, 13 insertions(+), 14 deletions(-) rename tested/languages/typescript/{parseAst.mts => parseAst.ts} (100%) rename tested/languages/typescript/templates/{values.mts => values.ts} (98%) rename tests/exercises/counter/solution/{solution.mts => solution.ts} (100%) rename tests/exercises/echo-function-additional-source-files/solution/{correct.mts => correct.ts} (100%) rename tests/exercises/echo-function-additional-source-files/workdir/{echo.mts => echo.ts} (100%) rename tests/exercises/echo-function-file-input/solution/{correct.mts => correct.ts} (100%) rename tests/exercises/echo-function-file-output/solution/{correct.mts => correct.ts} (100%) rename tests/exercises/echo-function/evaluation/{evaluator.mts => evaluator.ts} (100%) rename tests/exercises/echo-function/solution/{correct.mts => correct.ts} (100%) rename tests/exercises/echo/solution/{comp-error.mts => comp-error.ts} (100%) rename tests/exercises/echo/solution/{correct.mts => correct.ts} (100%) rename tests/exercises/echo/solution/{run-error.mts => run-error.ts} (100%) rename tests/exercises/echo/solution/{wrong.mts => wrong.ts} (100%) rename tests/exercises/global/solution/{correct.mts => correct.ts} (100%) rename tests/exercises/isbn/solution/{solution.mts => solution.ts} (100%) rename tests/exercises/lotto/solution/{correct.mts => correct.ts} (100%) rename tests/exercises/objects/solution/{correct.mts => correct.ts} (100%) rename tests/exercises/sum/solution/{correct.mts => correct.ts} (100%) diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index c1e3110e..d5e9831f 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -35,13 +35,13 @@ class TypeScript(Language): def initial_dependencies(self) -> list[str]: - return ["values.mts"] + return ["values.ts"] def needs_selector(self) -> bool: return False def file_extension(self) -> str: - return "mts" + return "ts" def naming_conventions(self) -> dict[Conventionable, NamingConventions]: return { @@ -115,7 +115,7 @@ def compilation(self, files: list[str]) -> CallbackResult: def execution(self, cwd: Path, file: str, arguments: list[str]) -> Command: # Used es2022 because of the top-level await feature (most up-to data). - return ["node", "--no-warnings", "--loader", "ts-node/esm", file, *arguments] + return ["tsx", file, *arguments] def modify_solution(self, solution: Path): # import local to prevent errors @@ -123,11 +123,11 @@ def modify_solution(self, solution: Path): assert self.config - parse_file = str(Path(__file__).parent / "parseAst.mts") + parse_file = str(Path(__file__).parent / "parseAst.ts") output = run_command( solution.parent, timeout=None, - command=["node", "--no-warnings", "--loader", "ts-node/esm", parse_file, str(solution.absolute())], + command=["tsx", parse_file, str(solution.absolute())], check=False, ) assert output, "Missing output from TypesScript's modify_solution" diff --git a/tested/languages/typescript/generators.py b/tested/languages/typescript/generators.py index 1560ed0c..3ca690b0 100644 --- a/tested/languages/typescript/generators.py +++ b/tested/languages/typescript/generators.py @@ -192,8 +192,8 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) if tc.testcase.is_main_testcase(): assert isinstance(tc.input, MainInput) result += f""" - //delete require.cache[require.resolve("./{pu.submission_name}.mts")]; - let {pu.submission_name} = await import('./{pu.submission_name}.mts'); + //delete require.cache[require.resolve("./{pu.submission_name}.ts")]; + let {pu.submission_name} = await import('./{pu.submission_name}.ts'); """ else: assert isinstance(tc.input, PreparedTestcaseStatement) @@ -214,12 +214,12 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) def convert_execution_unit(pu: PreparedExecutionUnit) -> str: result = f""" import * as fs from 'fs'; - import * as values from './values.mts'; + import * as values from './values.ts'; """ # Import the language specific functions we will need. for name in pu.evaluator_names: - result += f"import * as {name} from './{name}.mts';\n" + result += f"import * as {name} from './{name}.ts';\n" # We now open files for results and define some functions. result += f""" @@ -287,8 +287,8 @@ def convert_execution_unit(pu: PreparedExecutionUnit) -> str: def convert_check_function(evaluator: str, function: FunctionCall) -> str: return f""" (async () => {{ - import * as {evaluator} from './{evaluator}.mts'; - import * as values from './values.mts'; + import * as {evaluator} from './{evaluator}.ts'; + import * as values from './values.ts'; const result = {convert_function_call(function)}; values.sendEvaluated(process.stdout.fd, result); @@ -298,7 +298,7 @@ def convert_check_function(evaluator: str, function: FunctionCall) -> str: def convert_encoder(values: list[Value]) -> str: result = f""" - import * as values from './values.mts'; + import * as values from './values.ts'; import * as fs from 'fs'; """ diff --git a/tested/languages/typescript/parseAst.mts b/tested/languages/typescript/parseAst.ts similarity index 100% rename from tested/languages/typescript/parseAst.mts rename to tested/languages/typescript/parseAst.ts diff --git a/tested/languages/typescript/templates/values.mts b/tested/languages/typescript/templates/values.ts similarity index 98% rename from tested/languages/typescript/templates/values.mts rename to tested/languages/typescript/templates/values.ts index d7a07737..0a3add92 100644 --- a/tested/languages/typescript/templates/values.mts +++ b/tested/languages/typescript/templates/values.ts @@ -1,5 +1,4 @@ import * as fs from 'fs'; -//const fs = require('fs'); function isVanillaObject(value: Object) : boolean { try { @@ -133,7 +132,7 @@ function sendException(stream: number, exception: Error | Object | {constructor: "message": JSON.stringify(exception), "stacktrace": "", "type": (exception as Object).constructor.name ?? (Object.prototype.toString.call(exception)), - "additional_message_keys": ["languages.javascript.runtime.invalid.exception"] + "additional_message_keys": ["languages.typescript.runtime.invalid.exception"] })); } } diff --git a/tests/exercises/counter/solution/solution.mts b/tests/exercises/counter/solution/solution.ts similarity index 100% rename from tests/exercises/counter/solution/solution.mts rename to tests/exercises/counter/solution/solution.ts diff --git a/tests/exercises/echo-function-additional-source-files/solution/correct.mts b/tests/exercises/echo-function-additional-source-files/solution/correct.ts similarity index 100% rename from tests/exercises/echo-function-additional-source-files/solution/correct.mts rename to tests/exercises/echo-function-additional-source-files/solution/correct.ts diff --git a/tests/exercises/echo-function-additional-source-files/workdir/echo.mts b/tests/exercises/echo-function-additional-source-files/workdir/echo.ts similarity index 100% rename from tests/exercises/echo-function-additional-source-files/workdir/echo.mts rename to tests/exercises/echo-function-additional-source-files/workdir/echo.ts diff --git a/tests/exercises/echo-function-file-input/solution/correct.mts b/tests/exercises/echo-function-file-input/solution/correct.ts similarity index 100% rename from tests/exercises/echo-function-file-input/solution/correct.mts rename to tests/exercises/echo-function-file-input/solution/correct.ts diff --git a/tests/exercises/echo-function-file-output/solution/correct.mts b/tests/exercises/echo-function-file-output/solution/correct.ts similarity index 100% rename from tests/exercises/echo-function-file-output/solution/correct.mts rename to tests/exercises/echo-function-file-output/solution/correct.ts diff --git a/tests/exercises/echo-function/evaluation/evaluator.mts b/tests/exercises/echo-function/evaluation/evaluator.ts similarity index 100% rename from tests/exercises/echo-function/evaluation/evaluator.mts rename to tests/exercises/echo-function/evaluation/evaluator.ts diff --git a/tests/exercises/echo-function/solution/correct.mts b/tests/exercises/echo-function/solution/correct.ts similarity index 100% rename from tests/exercises/echo-function/solution/correct.mts rename to tests/exercises/echo-function/solution/correct.ts diff --git a/tests/exercises/echo/solution/comp-error.mts b/tests/exercises/echo/solution/comp-error.ts similarity index 100% rename from tests/exercises/echo/solution/comp-error.mts rename to tests/exercises/echo/solution/comp-error.ts diff --git a/tests/exercises/echo/solution/correct.mts b/tests/exercises/echo/solution/correct.ts similarity index 100% rename from tests/exercises/echo/solution/correct.mts rename to tests/exercises/echo/solution/correct.ts diff --git a/tests/exercises/echo/solution/run-error.mts b/tests/exercises/echo/solution/run-error.ts similarity index 100% rename from tests/exercises/echo/solution/run-error.mts rename to tests/exercises/echo/solution/run-error.ts diff --git a/tests/exercises/echo/solution/wrong.mts b/tests/exercises/echo/solution/wrong.ts similarity index 100% rename from tests/exercises/echo/solution/wrong.mts rename to tests/exercises/echo/solution/wrong.ts diff --git a/tests/exercises/global/solution/correct.mts b/tests/exercises/global/solution/correct.ts similarity index 100% rename from tests/exercises/global/solution/correct.mts rename to tests/exercises/global/solution/correct.ts diff --git a/tests/exercises/isbn/solution/solution.mts b/tests/exercises/isbn/solution/solution.ts similarity index 100% rename from tests/exercises/isbn/solution/solution.mts rename to tests/exercises/isbn/solution/solution.ts diff --git a/tests/exercises/lotto/solution/correct.mts b/tests/exercises/lotto/solution/correct.ts similarity index 100% rename from tests/exercises/lotto/solution/correct.mts rename to tests/exercises/lotto/solution/correct.ts diff --git a/tests/exercises/objects/solution/correct.mts b/tests/exercises/objects/solution/correct.ts similarity index 100% rename from tests/exercises/objects/solution/correct.mts rename to tests/exercises/objects/solution/correct.ts diff --git a/tests/exercises/sum/solution/correct.mts b/tests/exercises/sum/solution/correct.ts similarity index 100% rename from tests/exercises/sum/solution/correct.mts rename to tests/exercises/sum/solution/correct.ts From f0402489bb2e5a9b36f14860623bc17459d0325f Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 24 Oct 2024 16:23:18 +0200 Subject: [PATCH 08/49] Fixed accidental change to java test --- .../isbn/evaluation/one-with-crashing-assignment-java.tson | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-java.tson b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-java.tson index 35c7d006..09f8546d 100644 --- a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-java.tson +++ b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-java.tson @@ -7,8 +7,8 @@ "contexts": [ { "before": { - "typescript": { - "data": "const ex: () => Array = () => {throw new Error("AssertionError");};" + "java": { + "data": "Supplier> ex = () -> {throw new AssertionError();};" } }, "testcases": [ From 0d1c9c66a74bb208809c018be785b491b5ad88fc Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 24 Oct 2024 17:38:57 +0200 Subject: [PATCH 09/49] rewrote some tests --- tested/languages/typescript/config.py | 2 +- .../solution/correct.ts | 2 +- .../workdir/echo.ts | 2 +- .../echo-function-file-input/solution/correct.ts | 2 +- .../echo-function-file-output/solution/correct.ts | 2 +- .../one-with-crashing-assignment-typescript.tson | 4 ++-- tests/test_linters.py | 1 + tsconfig.json | 15 --------------- 8 files changed, 8 insertions(+), 22 deletions(-) delete mode 100644 tsconfig.json diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index d5e9831f..ee9ad819 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -128,7 +128,7 @@ def modify_solution(self, solution: Path): solution.parent, timeout=None, command=["tsx", parse_file, str(solution.absolute())], - check=False, + check=True, ) assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() diff --git a/tests/exercises/echo-function-additional-source-files/solution/correct.ts b/tests/exercises/echo-function-additional-source-files/solution/correct.ts index 40bad740..cd443043 100644 --- a/tests/exercises/echo-function-additional-source-files/solution/correct.ts +++ b/tests/exercises/echo-function-additional-source-files/solution/correct.ts @@ -1,4 +1,4 @@ -const e = require("./echo.ts"); +import * as e from "./echo.ts"; function echo(content) { return e.echo(content); diff --git a/tests/exercises/echo-function-additional-source-files/workdir/echo.ts b/tests/exercises/echo-function-additional-source-files/workdir/echo.ts index 3bb749dc..f8f72077 100644 --- a/tests/exercises/echo-function-additional-source-files/workdir/echo.ts +++ b/tests/exercises/echo-function-additional-source-files/workdir/echo.ts @@ -2,4 +2,4 @@ function echo(content) { return content; } -module.exports = { echo }; +export { echo }; diff --git a/tests/exercises/echo-function-file-input/solution/correct.ts b/tests/exercises/echo-function-file-input/solution/correct.ts index 87e768a3..b9added1 100644 --- a/tests/exercises/echo-function-file-input/solution/correct.ts +++ b/tests/exercises/echo-function-file-input/solution/correct.ts @@ -1,4 +1,4 @@ -const fs = require('fs'); +import * as fs from 'fs'; function echoFile(content) { return fs.readFileSync(content, {encoding:'utf8', flag:'r'}).trim(); diff --git a/tests/exercises/echo-function-file-output/solution/correct.ts b/tests/exercises/echo-function-file-output/solution/correct.ts index da5cfb3d..e7bacf3d 100644 --- a/tests/exercises/echo-function-file-output/solution/correct.ts +++ b/tests/exercises/echo-function-file-output/solution/correct.ts @@ -1,4 +1,4 @@ -const fs = require('fs'); +import * as fs from 'fs'; function echoFunction(filename, stringToWrite) { fs.writeFileSync(filename, stringToWrite + '\n', { flag: 'w' }); diff --git a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson index 09f8546d..35c7d006 100644 --- a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson +++ b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson @@ -7,8 +7,8 @@ "contexts": [ { "before": { - "java": { - "data": "Supplier> ex = () -> {throw new AssertionError();};" + "typescript": { + "data": "const ex: () => Array = () => {throw new Error("AssertionError");};" } }, "testcases": [ diff --git a/tests/test_linters.py b/tests/test_linters.py index 00db95ec..3497d84b 100644 --- a/tests/test_linters.py +++ b/tests/test_linters.py @@ -56,6 +56,7 @@ def test_eslint(tmp_path: Path, config: dict, pytestconfig: pytest.Config): config, ) result = execute_config(conf) + print(conf) updates = assert_valid_output(result, pytestconfig) assert len(updates.find_all("annotate-code")) > 0 diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index ed93465f..00000000 --- a/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "ts-node/node16/tsconfig.json", - "compilerOptions" : { - "esModuleInterop" : true, - "module" : "ESNext", - "moduleResolution" : "node", - "allowImportingTsExtensions" : true, - "types" : ["node"], - }, - "ts-node" : { - "esm" : true, - "transpileOnly": true, - "experimentalSpecifierResolution" : "node" - } -} From 55bbfa18d5a4642e4f0d28274aaffaf5f2925c11 Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 24 Oct 2024 17:53:31 +0200 Subject: [PATCH 10/49] reformatted code --- flake.nix | 4 -- tested/languages/typescript/config.py | 76 +++++++++++++---------- tested/languages/typescript/generators.py | 12 ++-- tests/test_linters.py | 1 - 4 files changed, 48 insertions(+), 45 deletions(-) diff --git a/flake.nix b/flake.nix index e41a7086..b3412cdb 100644 --- a/flake.nix +++ b/flake.nix @@ -199,10 +199,6 @@ name = "NODE_PATH"; prefix = "${ast}/lib/node_modules"; } - { - name = "TS_NODE_PROJECT"; - prefix = "./tsconfig.json"; - } ]; commands = [ { diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index ee9ad819..abf8fb1d 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -32,6 +32,7 @@ logger = logging.getLogger(__name__) + class TypeScript(Language): def initial_dependencies(self) -> list[str]: @@ -107,9 +108,18 @@ def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) if main_file: - return (["tsc", "--module", "nodenext", "--moduleResolution", - "nodenext", "--noEmit", main_file[0]], - files) + return ( + [ + "tsc", + "--module", + "nodenext", + "--moduleResolution", + "nodenext", + "--noEmit", + main_file[0], + ], + files, + ) else: return [], files @@ -133,7 +143,7 @@ def modify_solution(self, solution: Path): assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() with open(solution, "a") as file: - # print(f"\ndeclare var module: any;", file=file) + # print(f"\ndeclare var module: any;", file=file) print(f"\nexport {{{namings}}};", file=file) # Add strict mode to the script. @@ -197,38 +207,36 @@ def generate_encoder(self, values: list[Value]) -> str: def get_declaration_metadata(self) -> TypeDeclarationMetadata: return { - "names": { # type: ignore - "integer": "number", - "real": "number", - "char": "string", - "text": "string", - "string": "string", - "boolean": "boolean", - "sequence": "array", - "set": "set", - "map": "object", - "nothing": "null", - "undefined": "undefined", - "int8": "number", - "uint8": "number", - "int16": "number", - "uint16": "number", - "int32": "number", - "uint32": "number", - "int64": "number", - "uint64": "number", - "bigint": "number", + "names": { # type: ignore + "integer": "number", + "real": "number", + "char": "string", + "text": "string", + "string": "string", + "boolean": "boolean", + "sequence": "array", + "set": "set", + "map": "object", + "nothing": "null", + "undefined": "undefined", + "int8": "number", + "uint8": "number", + "int16": "number", + "uint16": "number", + "int32": "number", + "uint32": "number", + "int64": "number", + "uint64": "number", + "bigint": "number", "single_precision": "number", "double_precision": "number", - "double_extended": "number", - "fixed_precision": "number", - "array": "array", - "list": "array", - "tuple": "array", - "any": "object", + "double_extended": "number", + "fixed_precision": "number", + "array": "array", + "list": "array", + "tuple": "array", + "any": "object", }, - "nested": ("<", ">"), + "nested": ("<", ">"), "exception": "Error", } - - diff --git a/tested/languages/typescript/generators.py b/tested/languages/typescript/generators.py index 3ca690b0..c93a8923 100644 --- a/tested/languages/typescript/generators.py +++ b/tested/languages/typescript/generators.py @@ -68,9 +68,9 @@ def convert_value(value: Value) -> str: result += "}" return result elif value.type in ( - AdvancedNumericTypes.INT_64, - AdvancedNumericTypes.U_INT_64, - AdvancedNumericTypes.BIG_INT, + AdvancedNumericTypes.INT_64, + AdvancedNumericTypes.U_INT_64, + AdvancedNumericTypes.BIG_INT, ): return f'BigInt("{value.data}")' # Handle basic types @@ -182,9 +182,9 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) # We need special code to make variables available outside of the try-catch block. if ( - not tc.testcase.is_main_testcase() - and isinstance(tc.input, PreparedTestcaseStatement) - and isinstance(tc.input.statement, VariableAssignment) + not tc.testcase.is_main_testcase() + and isinstance(tc.input, PreparedTestcaseStatement) + and isinstance(tc.input.statement, VariableAssignment) ): result += f"let {tc.input.statement.variable}\n" diff --git a/tests/test_linters.py b/tests/test_linters.py index 3497d84b..00db95ec 100644 --- a/tests/test_linters.py +++ b/tests/test_linters.py @@ -56,7 +56,6 @@ def test_eslint(tmp_path: Path, config: dict, pytestconfig: pytest.Config): config, ) result = execute_config(conf) - print(conf) updates = assert_valid_output(result, pytestconfig) assert len(updates.find_all("annotate-code")) > 0 From 01ad38a64871bd342c769a304969af42504f2f95 Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 24 Oct 2024 18:47:13 +0200 Subject: [PATCH 11/49] cleaned up code some more and turned off throwing of errors for now. --- tested/languages/typescript/config.py | 3 +-- tested/languages/typescript/generators.py | 4 ++-- .../echo-function-additional-source-files/solution/correct.ts | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index abf8fb1d..ee746073 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -138,12 +138,11 @@ def modify_solution(self, solution: Path): solution.parent, timeout=None, command=["tsx", parse_file, str(solution.absolute())], - check=True, + check=False, ) assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() with open(solution, "a") as file: - # print(f"\ndeclare var module: any;", file=file) print(f"\nexport {{{namings}}};", file=file) # Add strict mode to the script. diff --git a/tested/languages/typescript/generators.py b/tested/languages/typescript/generators.py index c93a8923..20a462b9 100644 --- a/tested/languages/typescript/generators.py +++ b/tested/languages/typescript/generators.py @@ -160,7 +160,7 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) if not ctx.context.has_main_testcase(): result += f""" writeSeparator(); - //delete require.cache[require.resolve("./{submission_file(pu.language)}")]; + delete require.cache[require.resolve("./{submission_file(pu.language)}")]; let {pu.submission_name} = await import('./{submission_file(pu.language)}'); """ @@ -192,7 +192,7 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit) if tc.testcase.is_main_testcase(): assert isinstance(tc.input, MainInput) result += f""" - //delete require.cache[require.resolve("./{pu.submission_name}.ts")]; + delete require.cache[require.resolve("./{pu.submission_name}.ts")]; let {pu.submission_name} = await import('./{pu.submission_name}.ts'); """ else: diff --git a/tests/exercises/echo-function-additional-source-files/solution/correct.ts b/tests/exercises/echo-function-additional-source-files/solution/correct.ts index cd443043..7d4a970d 100644 --- a/tests/exercises/echo-function-additional-source-files/solution/correct.ts +++ b/tests/exercises/echo-function-additional-source-files/solution/correct.ts @@ -1,3 +1,4 @@ +// @ts-ignore import * as e from "./echo.ts"; function echo(content) { From 3e1b568527679db01a14359d3e36179653794306 Mon Sep 17 00:00:00 2001 From: breblanc Date: Sat, 26 Oct 2024 17:14:00 +0200 Subject: [PATCH 12/49] Fixed typescript error in terminal. When running tested errors still persist. --- flake.nix | 9 +++++---- tested/languages/typescript/config.py | 6 ++++-- tested/languages/typescript/parseAst.ts | 1 + tested/languages/typescript/tsconfig.json | 18 ++++++++++++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 tested/languages/typescript/tsconfig.json diff --git a/flake.nix b/flake.nix index b3412cdb..d750c69f 100644 --- a/flake.nix +++ b/flake.nix @@ -80,7 +80,10 @@ mkdir -p "$out/bin" "$out/lib/tsx" cp -r dist node_modules "$out/lib/tsx" - makeWrapper "${pkgs.lib.getExe nodejs_base}" "$out/bin/tsx" --add-flags "$out/lib/tsx/dist/cli.mjs" + cp -r ${pkgs.typescript}/lib/node_modules/typescript/* "$out/lib/tsx/node_modules/typescript" + makeWrapper "${pkgs.lib.getExe nodejs_base}" "$out/bin/tsx" \ + --add-flags "$out/lib/tsx/dist/cli.mjs" \ + --set NODE_PATH "$out/lib/tsx/node_modules" runHook postInstall ''; @@ -124,7 +127,7 @@ kotlin-deps = [ pkgs.kotlin pkgs.ktlint ]; csharp-deps = [ pkgs.dotnetCorePackages.sdk_8_0 ]; - all-other-dependencies = ts-deps ++ haskell-deps ++ node-deps ++ bash-deps + all-other-dependencies = haskell-deps ++ ts-deps ++ node-deps ++ bash-deps ++ c-deps ++ java-deps ++ kotlin-deps ++ csharp-deps ++ [ pkgs.coreutils ]; @@ -164,10 +167,8 @@ checkPhase = '' DOTNET_CLI_HOME="$(mktemp -d)" export DOTNET_CLI_HOME - TS_NODE_PROJECT=./tsconfig.json NODE_PATH=${ast}/lib/node_modules export NODE_PATH - export TS_NODE_PROJECT poetry run pytest -n auto --cov=tested --cov-report=xml tests/ ''; installPhase = '' diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index ee746073..24a6c5fb 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -1,4 +1,5 @@ import logging +import os import re from pathlib import Path from typing import TYPE_CHECKING @@ -125,7 +126,7 @@ def compilation(self, files: list[str]) -> CallbackResult: def execution(self, cwd: Path, file: str, arguments: list[str]) -> Command: # Used es2022 because of the top-level await feature (most up-to data). - return ["tsx", file, *arguments] + return ["tsx", "--tsconfig", str(Path(__file__).parent / "tsconfig.json"), file, *arguments] def modify_solution(self, solution: Path): # import local to prevent errors @@ -137,9 +138,10 @@ def modify_solution(self, solution: Path): output = run_command( solution.parent, timeout=None, - command=["tsx", parse_file, str(solution.absolute())], + command=["tsx", "--tsconfig", str(Path(__file__).parent / "tsconfig.json"), parse_file, str(solution.absolute())], check=False, ) + print(output.stderr) assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() with open(solution, "a") as file: diff --git a/tested/languages/typescript/parseAst.ts b/tested/languages/typescript/parseAst.ts index ced6e1aa..5dbf02df 100644 --- a/tested/languages/typescript/parseAst.ts +++ b/tested/languages/typescript/parseAst.ts @@ -1,3 +1,4 @@ +import * as values from "./templates/values.ts"; import * as ts from 'typescript'; import * as fs from 'fs'; const source = fs.readFileSync(process.argv[2], 'utf-8'); diff --git a/tested/languages/typescript/tsconfig.json b/tested/languages/typescript/tsconfig.json new file mode 100644 index 00000000..be08f0df --- /dev/null +++ b/tested/languages/typescript/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "esnext", + "strict": true, + "target": "es6", + "moduleDetection": "force", + "resolveJsonModule": true, + "allowJs": true, + "esModuleInterop": true, + "skipLibCheck": true, + "isolatedModules": true, + "verbatimModuleSyntax" : true, + "types" : ["node"], + "moduleResolution" : "node", + "allowImportingTsExtensions" : true, + }, + +} From 9480ac33bbd6d0ac49875c0c4ed8223eff6da69e Mon Sep 17 00:00:00 2001 From: breblanc Date: Sat, 26 Oct 2024 17:59:08 +0200 Subject: [PATCH 13/49] fixed all tests --- tested/judge/utils.py | 4 ++++ tested/languages/typescript/config.py | 18 +++++++----------- .../typescript/tsconfig-compilation.json | 19 +++++++++++++++++++ tested/languages/typescript/tsconfig.json | 2 +- 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 tested/languages/typescript/tsconfig-compilation.json diff --git a/tested/judge/utils.py b/tested/judge/utils.py index c50a00b2..f2fa53da 100644 --- a/tested/judge/utils.py +++ b/tested/judge/utils.py @@ -47,6 +47,10 @@ def run_command( :return: The result of the execution if the command was not None. """ + print(f"Running command {command} in {directory}") + print(f"Timeout: {timeout}") + print(f"Check: {check}") + print(f"Stdin: {stdin}") if not command: return None diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 24a6c5fb..7c2fc7a6 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -111,12 +111,9 @@ def compilation(self, files: list[str]) -> CallbackResult: if main_file: return ( [ - "tsc", - "--module", - "nodenext", - "--moduleResolution", - "nodenext", - "--noEmit", + "tsx", + "--tsconfig", + str(Path(__file__).parent / "tsconfig-compilation.json"), main_file[0], ], files, @@ -125,8 +122,7 @@ def compilation(self, files: list[str]) -> CallbackResult: return [], files def execution(self, cwd: Path, file: str, arguments: list[str]) -> Command: - # Used es2022 because of the top-level await feature (most up-to data). - return ["tsx", "--tsconfig", str(Path(__file__).parent / "tsconfig.json"), file, *arguments] + return ["tsx", file, *arguments] def modify_solution(self, solution: Path): # import local to prevent errors @@ -138,10 +134,10 @@ def modify_solution(self, solution: Path): output = run_command( solution.parent, timeout=None, - command=["tsx", "--tsconfig", str(Path(__file__).parent / "tsconfig.json"), parse_file, str(solution.absolute())], - check=False, + command=["tsx", parse_file, str(solution.absolute())], + check=True, ) - print(output.stderr) + print(output) assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() with open(solution, "a") as file: diff --git a/tested/languages/typescript/tsconfig-compilation.json b/tested/languages/typescript/tsconfig-compilation.json new file mode 100644 index 00000000..9d1c2b86 --- /dev/null +++ b/tested/languages/typescript/tsconfig-compilation.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "esnext", + "strict": true, + "target": "es6", + "moduleDetection": "force", + "resolveJsonModule": true, + "allowJs": true, + "esModuleInterop": true, + "skipLibCheck": true, + "isolatedModules": true, + "verbatimModuleSyntax" : true, + "types" : ["node"], + "moduleResolution" : "node", + "allowImportingTsExtensions" : true, + "noEmit" : true + }, + +} diff --git a/tested/languages/typescript/tsconfig.json b/tested/languages/typescript/tsconfig.json index be08f0df..24eb8bdd 100644 --- a/tested/languages/typescript/tsconfig.json +++ b/tested/languages/typescript/tsconfig.json @@ -6,7 +6,7 @@ "moduleDetection": "force", "resolveJsonModule": true, "allowJs": true, - "esModuleInterop": true, + "esModuleInterop": true, "skipLibCheck": true, "isolatedModules": true, "verbatimModuleSyntax" : true, From 56dcc3c4ecd11b99fd367ef31b2cdf21b942f789 Mon Sep 17 00:00:00 2001 From: breblanc Date: Sat, 26 Oct 2024 18:28:41 +0200 Subject: [PATCH 14/49] Changed test such that tsconfig file isn't parsed --- tests/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index a72ba04b..00d2f087 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -241,7 +241,7 @@ def test_valid_yaml_and_json(): def recursive_iter_dir(directory: Path) -> list[Path]: yaml_and_json_files = [] for file in directory.iterdir(): - if file.is_file() and ( + if file.is_file() and not file.name.startswith("tsconfig") and ( file.name.endswith(".yml") or file.name.endswith(".yaml") or file.name.endswith(".json") From 373b0c30f237197e377faf5b17f252b485e13645 Mon Sep 17 00:00:00 2001 From: breblanc Date: Sun, 27 Oct 2024 16:00:54 +0100 Subject: [PATCH 15/49] Changing compilation command to tsc instead of tsx because of noEmit --- tested/judge/utils.py | 4 ---- tested/languages/typescript/config.py | 10 +++------- .../languages/typescript/tsconfig-compilation.json | 13 ++----------- tests/exercises/echo/solution/run-error.ts | 7 ++++++- 4 files changed, 11 insertions(+), 23 deletions(-) diff --git a/tested/judge/utils.py b/tested/judge/utils.py index f2fa53da..c50a00b2 100644 --- a/tested/judge/utils.py +++ b/tested/judge/utils.py @@ -47,10 +47,6 @@ def run_command( :return: The result of the execution if the command was not None. """ - print(f"Running command {command} in {directory}") - print(f"Timeout: {timeout}") - print(f"Check: {check}") - print(f"Stdin: {stdin}") if not command: return None diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 7c2fc7a6..59ed9e7e 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -111,9 +111,8 @@ def compilation(self, files: list[str]) -> CallbackResult: if main_file: return ( [ - "tsx", - "--tsconfig", - str(Path(__file__).parent / "tsconfig-compilation.json"), + "tsc", + "--noEmit", main_file[0], ], files, @@ -137,7 +136,6 @@ def modify_solution(self, solution: Path): command=["tsx", parse_file, str(solution.absolute())], check=True, ) - print(output) assert output, "Missing output from TypesScript's modify_solution" namings = output.stdout.strip() with open(solution, "a") as file: @@ -147,9 +145,7 @@ def modify_solution(self, solution: Path): with open(solution, "r") as file: non_strict = file.read() with open(solution, "w") as file: - # This rule is added because Typescript might - # complain about "require('fs')" being redeclared. - file.write('"use strict";\n\n' + non_strict) + file.write('"use strict";\n' + non_strict) self.config.dodona.source_offset -= 2 def cleanup_stacktrace(self, stacktrace: str) -> str: diff --git a/tested/languages/typescript/tsconfig-compilation.json b/tested/languages/typescript/tsconfig-compilation.json index 9d1c2b86..2b37fa6f 100644 --- a/tested/languages/typescript/tsconfig-compilation.json +++ b/tested/languages/typescript/tsconfig-compilation.json @@ -1,19 +1,10 @@ { "compilerOptions": { "module": "esnext", - "strict": true, "target": "es6", - "moduleDetection": "force", - "resolveJsonModule": true, "allowJs": true, - "esModuleInterop": true, "skipLibCheck": true, - "isolatedModules": true, - "verbatimModuleSyntax" : true, - "types" : ["node"], - "moduleResolution" : "node", - "allowImportingTsExtensions" : true, - "noEmit" : true + "strict": false, + "noEmit": true }, - } diff --git a/tests/exercises/echo/solution/run-error.ts b/tests/exercises/echo/solution/run-error.ts index 7a12bbf8..0bc2bdd4 100644 --- a/tests/exercises/echo/solution/run-error.ts +++ b/tests/exercises/echo/solution/run-error.ts @@ -1 +1,6 @@ -does_not_exist() +function throw_error() { + const obj: any = null; + console.log(obj.does_not_exist()); +} + +throw_error() From 1b80f287a0710c5b09fe5cdaa53a5b1fe04ae58e Mon Sep 17 00:00:00 2001 From: breblanc Date: Sun, 27 Oct 2024 16:48:45 +0100 Subject: [PATCH 16/49] fixed linting issue --- tests/test_utils.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 00d2f087..ea4a00f5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -241,10 +241,14 @@ def test_valid_yaml_and_json(): def recursive_iter_dir(directory: Path) -> list[Path]: yaml_and_json_files = [] for file in directory.iterdir(): - if file.is_file() and not file.name.startswith("tsconfig") and ( - file.name.endswith(".yml") - or file.name.endswith(".yaml") - or file.name.endswith(".json") + if ( + file.is_file() + and not file.name.startswith("tsconfig") + and ( + file.name.endswith(".yml") + or file.name.endswith(".yaml") + or file.name.endswith(".json") + ) ): yaml_and_json_files.append(file) elif file.is_dir(): From c22eeb6f472698c4a5b6038960612db8d8f7bb69 Mon Sep 17 00:00:00 2001 From: breblanc Date: Tue, 29 Oct 2024 13:35:08 +0100 Subject: [PATCH 17/49] cleanup --- tested/languages/typescript/parseAst.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tested/languages/typescript/parseAst.ts b/tested/languages/typescript/parseAst.ts index 5dbf02df..ced6e1aa 100644 --- a/tested/languages/typescript/parseAst.ts +++ b/tested/languages/typescript/parseAst.ts @@ -1,4 +1,3 @@ -import * as values from "./templates/values.ts"; import * as ts from 'typescript'; import * as fs from 'fs'; const source = fs.readFileSync(process.argv[2], 'utf-8'); From 3b70092e239f099ed531cf06a0810984bf533f5a Mon Sep 17 00:00:00 2001 From: breblanc Date: Wed, 13 Nov 2024 12:40:39 +0100 Subject: [PATCH 18/49] Added changes to devcontainer.json --- .devcontainer/dodona-tested.dockerfile | 5 +++++ .../typescript/tsconfig-compilation.json | 10 ---------- tested/languages/typescript/tsconfig.json | 18 ------------------ 3 files changed, 5 insertions(+), 28 deletions(-) delete mode 100644 tested/languages/typescript/tsconfig-compilation.json delete mode 100644 tested/languages/typescript/tsconfig.json diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index 230e5d58..29230352 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -83,6 +83,11 @@ RUN < Date: Wed, 13 Nov 2024 17:06:38 +0100 Subject: [PATCH 19/49] Fixed problem with tsc --- tested/judge/compilation.py | 2 +- tested/languages/bash/config.py | 2 +- tested/languages/c/config.py | 2 +- tested/languages/csharp/config.py | 2 +- tested/languages/haskell/config.py | 2 +- tested/languages/java/config.py | 2 +- tested/languages/javascript/config.py | 2 +- tested/languages/kotlin/config.py | 2 +- tested/languages/language.py | 4 +++- tested/languages/python/config.py | 2 +- tested/languages/runhaskell/config.py | 2 +- tested/languages/typescript/config.py | 18 +++++++++++++++--- 12 files changed, 28 insertions(+), 14 deletions(-) diff --git a/tested/judge/compilation.py b/tested/judge/compilation.py index b079285e..86918322 100644 --- a/tested/judge/compilation.py +++ b/tested/judge/compilation.py @@ -59,7 +59,7 @@ def run_compilation( decide to fallback to individual mode if the compilation result is not positive. """ - command, files = bundle.language.compilation(dependencies) + command, files = bundle.language.compilation(dependencies, directory) _logger.debug( "Generating files with command %s in directory %s", command, directory ) diff --git a/tested/languages/bash/config.py b/tested/languages/bash/config.py index 787ea82e..1b4ed4fa 100644 --- a/tested/languages/bash/config.py +++ b/tested/languages/bash/config.py @@ -58,7 +58,7 @@ def is_source_file(self, file: Path) -> bool: def submission_file(self) -> str: return submission_name(self) - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) if main_file: diff --git a/tested/languages/c/config.py b/tested/languages/c/config.py index cb7c8e6a..114eddbd 100644 --- a/tested/languages/c/config.py +++ b/tested/languages/c/config.py @@ -72,7 +72,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "double_extended": "supported", } - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: main_file = files[-1] exec_file = Path(main_file).stem result = executable_name(exec_file) diff --git a/tested/languages/csharp/config.py b/tested/languages/csharp/config.py index 1f97335c..e80978d4 100644 --- a/tested/languages/csharp/config.py +++ b/tested/languages/csharp/config.py @@ -97,7 +97,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "tuple": "supported", } - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: # In C#, all output files are located in a subdirectory, so we just # want to copy over the subdirectory. def file_filter(file: Path) -> bool: diff --git a/tested/languages/haskell/config.py b/tested/languages/haskell/config.py index 7b45168d..7c2e4a91 100644 --- a/tested/languages/haskell/config.py +++ b/tested/languages/haskell/config.py @@ -81,7 +81,7 @@ def supported_constructs(self) -> set[Construct]: Construct.GLOBAL_VARIABLES, } - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: main_ = files[-1] exec_ = main_.rstrip(".hs") assert self.config diff --git a/tested/languages/java/config.py b/tested/languages/java/config.py index 72c08538..688cba04 100644 --- a/tested/languages/java/config.py +++ b/tested/languages/java/config.py @@ -126,7 +126,7 @@ def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: BasicSequenceTypes.SET: restrictions, } - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: def file_filter(file: Path) -> bool: return file.suffix == ".class" diff --git a/tested/languages/javascript/config.py b/tested/languages/javascript/config.py index c9eb9a3b..14e2e9d1 100644 --- a/tested/languages/javascript/config.py +++ b/tested/languages/javascript/config.py @@ -103,7 +103,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: return {AdvancedObjectTypes.OBJECT: {BasicStringTypes.TEXT}} - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) if main_file: diff --git a/tested/languages/kotlin/config.py b/tested/languages/kotlin/config.py index 4610fd3e..3ca46297 100644 --- a/tested/languages/kotlin/config.py +++ b/tested/languages/kotlin/config.py @@ -136,7 +136,7 @@ def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: BasicSequenceTypes.SET: restrictions, # type: ignore } - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: def file_filter(file: Path) -> bool: return file.suffix == ".class" diff --git a/tested/languages/language.py b/tested/languages/language.py index f78eb4ba..549f2548 100644 --- a/tested/languages/language.py +++ b/tested/languages/language.py @@ -74,7 +74,7 @@ def __init__(self, config: Optional["GlobalConfig"]): """ self.config = config - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: """ Callback for generating the compilation command. @@ -139,6 +139,8 @@ def compilation(self, files: list[str]) -> CallbackResult: be useful to compile. By convention, the last file in the list is the file containing the "main" function. + :param directory: The directory in which these files can be found. + :return: The compilation command and either the resulting files or a filter for the resulting files. """ diff --git a/tested/languages/python/config.py b/tested/languages/python/config.py index cb495054..0ae659db 100644 --- a/tested/languages/python/config.py +++ b/tested/languages/python/config.py @@ -119,7 +119,7 @@ def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: BasicSequenceTypes.SET: restrictions, # type: ignore } - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: result = [x.replace(".py", ".pyc") for x in files] return [ _executable(), diff --git a/tested/languages/runhaskell/config.py b/tested/languages/runhaskell/config.py index 69d9b174..b5b8124b 100644 --- a/tested/languages/runhaskell/config.py +++ b/tested/languages/runhaskell/config.py @@ -6,7 +6,7 @@ class RunHaskell(Haskell): - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) if main_file: diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 59ed9e7e..486aafe7 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -1,3 +1,4 @@ +import json import logging import os import re @@ -105,15 +106,26 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: return {AdvancedObjectTypes.OBJECT: {BasicStringTypes.TEXT}} - def compilation(self, files: list[str]) -> CallbackResult: + def compilation(self, files: list[str], directory: Path) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) + + # Create a config file to just that extends tsconfig. + # This way it will only run tsc on the current file. + config_file = { + "extends": str(Path(__file__).parent / "tsconfig.json"), + "include": [f"{main_file[0]}"] + } + with open(str(directory / "tsconfig-sub.json"), "w") as file: + file.write(json.dumps(config_file, indent=4)) + + if main_file: return ( [ "tsc", - "--noEmit", - main_file[0], + "--project", + "tsconfig-sub.json", ], files, ) From 7bd5b432253b0b5d4e329b16a9725eb99a1ff159 Mon Sep 17 00:00:00 2001 From: breblanc Date: Wed, 13 Nov 2024 17:32:19 +0100 Subject: [PATCH 20/49] Fix linting issue and add actual tsconfig to commit --- tested/languages/typescript/config.py | 3 +-- tested/languages/typescript/tsconfig.json | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 tested/languages/typescript/tsconfig.json diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 486aafe7..f146ed35 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -114,12 +114,11 @@ def compilation(self, files: list[str], directory: Path) -> CallbackResult: # This way it will only run tsc on the current file. config_file = { "extends": str(Path(__file__).parent / "tsconfig.json"), - "include": [f"{main_file[0]}"] + "include": [f"{main_file[0]}"], } with open(str(directory / "tsconfig-sub.json"), "w") as file: file.write(json.dumps(config_file, indent=4)) - if main_file: return ( [ diff --git a/tested/languages/typescript/tsconfig.json b/tested/languages/typescript/tsconfig.json new file mode 100644 index 00000000..3881d16c --- /dev/null +++ b/tested/languages/typescript/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "es6", + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "allowImportingTsExtensions": true, + "typeRoots": [ + "/usr/lib/node_modules/@types"], + "noResolve": false, + "esModuleInterop": true, + "moduleResolution": "node" + }, + "include" : [] +} From f685b7cdc5bcd42a098b192791767c8f645ba009 Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 14 Nov 2024 09:37:32 +0100 Subject: [PATCH 21/49] Added ESlint for typescript --- .devcontainer/dodona-tested.dockerfile | 5 +- tested/internationalization/nl.yaml | 8 ++ tested/languages/typescript/config.py | 9 ++ tested/languages/typescript/eslintrc.yml | 19 ++++ tested/languages/typescript/linter.py | 103 ++++++++++++++++++ .../counter/solution/solution-eslint.ts | 15 +++ tests/test_functionality.py | 3 +- tests/test_linters.py | 16 +++ 8 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 tested/languages/typescript/eslintrc.yml create mode 100644 tested/languages/typescript/linter.py create mode 100644 tests/exercises/counter/solution/solution-eslint.ts diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index 29230352..f538dcdb 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -84,9 +84,8 @@ RUN < tuple[list[Message], list[AnnotateCode]]: + # Import locally to prevent errors. + from tested.languages.typescript import linter + + assert self.config + return linter.run_eslint(self.config.dodona, remaining) + def cleanup_stacktrace(self, stacktrace: str) -> str: assert self.config # What this does: diff --git a/tested/languages/typescript/eslintrc.yml b/tested/languages/typescript/eslintrc.yml new file mode 100644 index 00000000..bea959cc --- /dev/null +++ b/tested/languages/typescript/eslintrc.yml @@ -0,0 +1,19 @@ +parser: "@typescript-eslint/parser" +parserOptions: + ecmaVersion: "latest" + sourceType: "module" + ecmaFeatures: {} +plugins: + - "@typescript-eslint" +extends: "plugin:@typescript-eslint/recommended" +env: + node: yes + es2020: yes +rules: + no-var: "warn" + semi: "warn" + # Disable unused vars, otherwise written classes, functions and variables will be detected as unused, + # even when the student is expected to write these classes, functions and variables + # TODO: Add module.export before linting, to allow detecting unused vars in classes and functions + no-unused-vars: ["off", { "vars": "local", "args": "after-used", "ignoreRestSiblings": false }] + diff --git a/tested/languages/typescript/linter.py b/tested/languages/typescript/linter.py new file mode 100644 index 00000000..b25de60a --- /dev/null +++ b/tested/languages/typescript/linter.py @@ -0,0 +1,103 @@ +import json +import logging +from pathlib import Path + +from tested.configs import DodonaConfig +from tested.dodona import AnnotateCode, ExtendedMessage, Message, Permission, Severity +from tested.internationalization import get_i18n_string +from tested.judge.utils import run_command + +logger = logging.getLogger(__name__) +severity = [Severity.INFO, Severity.WARNING, Severity.ERROR] + + +def run_eslint( + config: DodonaConfig, remaining: float +) -> tuple[list[Message], list[AnnotateCode]]: + print("ping") + """ + Calls eslint to annotate submitted source code and adds resulting score and + annotations to tab. + """ + submission = config.source + language_options = config.config_for() + if path := language_options.get("eslint_config", None): + assert isinstance(path, str) + config_path = config.resources / path + else: + # Use the default file. + config_path = config.judge / "tested/languages/typescript/eslintrc.yml" + config_path = str(config_path.absolute()) + + execution_results = run_command( + directory=submission.parent, + timeout=remaining, + command=[ + "eslint", + "-f", + "json", + "--no-inline-config", + "-c", + config_path, + str(submission.absolute()), + ], + ) + print(execution_results) + + if execution_results is None: + return [], [] + + if execution_results.timeout or execution_results.memory: + return [ + ( + get_i18n_string("languages.typescript.linter.timeout") + if execution_results.timeout + else get_i18n_string("languages.typescript.linter.memory") + ) + ], [] + + try: + eslint_objects = json.loads(execution_results.stdout) + except Exception as e: + logger.warning("ESLint produced bad output", exc_info=e) + return [ + get_i18n_string("languages.typescript.linter.output"), + ExtendedMessage( + description=str(e), format="code", permission=Permission.STAFF + ), + ], [] + annotations = [] + + for eslint_object in eslint_objects: + if Path(eslint_object.get("filePath", submission)).name != submission.name: + continue + for message in eslint_object.get("messages", []): + text = message.get("message", None) + if not text: + continue + rule_id = message.get("ruleId") + external = None + if rule_id: + external = f"https://eslint.org/docs/rules/{rule_id}" + + start_row = message.get("line", 1) + end_row = message.get("endLine") + rows = end_row - start_row if end_row and end_row > start_row else None + start_col = message.get("column", 1) + end_col = message.get("endColumn") + cols = end_col - start_col if end_col and end_col > start_col else None + annotations.append( + AnnotateCode( + row=start_row - 1 + config.source_offset, + rows=rows, + text=text, + externalUrl=external, + column=start_col - 1, + columns=cols, + type=severity[int(message.get("severity", 1))], + ) + ) + + # sort linting messages on line, column and code + annotations.sort(key=lambda a: (a.row, a.column, a.text)) + return [], annotations diff --git a/tests/exercises/counter/solution/solution-eslint.ts b/tests/exercises/counter/solution/solution-eslint.ts new file mode 100644 index 00000000..d240ad10 --- /dev/null +++ b/tests/exercises/counter/solution/solution-eslint.ts @@ -0,0 +1,15 @@ +class Counter { + private count: number + + constructor() { + this.count = 0 + } + + add() { + this.count++; + } + + get() { + this.count--; + } +} diff --git a/tests/test_functionality.py b/tests/test_functionality.py index 812115c2..56ee3ca3 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -129,6 +129,7 @@ def test_assignment_and_use_in_expression( "java", "kotlin", "csharp", + "typescript", pytest.param("haskell", marks=pytest.mark.haskell), pytest.param("runhaskell", marks=pytest.mark.haskell), ], @@ -154,7 +155,7 @@ def test_assignment_and_use_in_expression_list( assert len(updates.find_all("start-test")) == 1 -@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp"]) +@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp", "typescript"]) def test_crashing_assignment_with_before( lang: str, tmp_path: Path, pytestconfig: pytest.Config ): diff --git a/tests/test_linters.py b/tests/test_linters.py index 00db95ec..332e3cfb 100644 --- a/tests/test_linters.py +++ b/tests/test_linters.py @@ -59,6 +59,22 @@ def test_eslint(tmp_path: Path, config: dict, pytestconfig: pytest.Config): updates = assert_valid_output(result, pytestconfig) assert len(updates.find_all("annotate-code")) > 0 +@pytest.mark.parametrize("config", _get_config_options("typescript")) +def test_eslint_typescript(tmp_path: Path, config: dict, pytestconfig: pytest.Config): + conf = configuration( + pytestconfig, + "counter", + "typescript", + tmp_path, + "plan.yaml", + "solution-eslint", + config, + ) + result = execute_config(conf) + updates = assert_valid_output(result, pytestconfig) + print(updates) + assert len(updates.find_all("annotate-code")) > 0 + @pytest.mark.parametrize( ("language", "config"), From 0c5fb1856a69aaf167588b9d8c527ff74eabc971 Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 14 Nov 2024 16:53:26 +0100 Subject: [PATCH 22/49] add typescript to more functionality tests --- tested/languages/typescript/linter.py | 2 +- tested/languages/typescript/parseAst.ts | 6 ++ tests/exercises/counter/solution/solution.ts | 5 +- ...e-with-crashing-assignment-typescript.tson | 2 +- tests/test_functionality.py | 56 +++++++++++++++---- 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/tested/languages/typescript/linter.py b/tested/languages/typescript/linter.py index b25de60a..590c6559 100644 --- a/tested/languages/typescript/linter.py +++ b/tested/languages/typescript/linter.py @@ -14,7 +14,7 @@ def run_eslint( config: DodonaConfig, remaining: float ) -> tuple[list[Message], list[AnnotateCode]]: - print("ping") + """ Calls eslint to annotate submitted source code and adds resulting score and annotations to tab. diff --git a/tested/languages/typescript/parseAst.ts b/tested/languages/typescript/parseAst.ts index ced6e1aa..fa043ac2 100644 --- a/tested/languages/typescript/parseAst.ts +++ b/tested/languages/typescript/parseAst.ts @@ -11,6 +11,12 @@ const ast = ts.createSourceFile( // Helper function to extract relevant identifiers from AST nodes function mapSubTreeToIds(node: ts.Node): Array { + // Make sure that no definitions inside a block of some kind are accounted with. + if (ts.isForOfStatement(node) || ts.isForInStatement(node) || ts.isIfStatement(node) || + ts.isForStatement(node) || ts.isWhileStatement(node) || ts.isBlock(node)) { + return [] + } + if (ts.isVariableDeclaration(node)) { return [node.name]; } else if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) { diff --git a/tests/exercises/counter/solution/solution.ts b/tests/exercises/counter/solution/solution.ts index 8ffae64b..4594d2fb 100644 --- a/tests/exercises/counter/solution/solution.ts +++ b/tests/exercises/counter/solution/solution.ts @@ -6,10 +6,11 @@ class Counter { } add() { - this.count++; + this.count++; + return this; } get() { - this.count--; + return this.count; } } diff --git a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson index 35c7d006..30d8566d 100644 --- a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson +++ b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-typescript.tson @@ -8,7 +8,7 @@ { "before": { "typescript": { - "data": "const ex: () => Array = () => {throw new Error("AssertionError");};" + "data": "const ex: () => Array = () => {throw new Error('AssertionError');};" } }, "testcases": [ diff --git a/tests/test_functionality.py b/tests/test_functionality.py index 56ee3ca3..88e74cc5 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -105,7 +105,7 @@ def test_generic_exception_wrong_error( assert updates.find_status_enum() == ["wrong"] -@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp", "typescript"]) +@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp", "typescript", "javascript"]) def test_assignment_and_use_in_expression( lang: str, tmp_path: Path, pytestconfig: pytest.Config ): @@ -130,6 +130,7 @@ def test_assignment_and_use_in_expression( "kotlin", "csharp", "typescript", + "javascript", pytest.param("haskell", marks=pytest.mark.haskell), pytest.param("runhaskell", marks=pytest.mark.haskell), ], @@ -155,7 +156,7 @@ def test_assignment_and_use_in_expression_list( assert len(updates.find_all("start-test")) == 1 -@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp", "typescript"]) +@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp", "typescript", "javascript"]) def test_crashing_assignment_with_before( lang: str, tmp_path: Path, pytestconfig: pytest.Config ): @@ -236,6 +237,37 @@ def test_missing_key_types_detected_js_dictionary( assert len(updates.find_all("start-testcase")) == 1 assert updates.find_status_enum() == ["correct"] +def test_missing_key_types_detected_ts_object( + tmp_path: Path, pytestconfig: pytest.Config +): + conf = configuration( + pytestconfig, + "objects", + "typescript", + tmp_path, + "missing_key_types_js_object.yaml", + "correct", + ) + result = execute_config(conf) + updates = assert_valid_output(result, pytestconfig) + assert len(updates.find_all("start-testcase")) == 0 + assert updates.find_status_enum() == ["internal error"] + + +@pytest.mark.parametrize( + "suite", ["missing_key_types_js_dictionary", "missing_key_types"] +) +def test_missing_key_types_detected_ts_dictionary( + suite: str, tmp_path: Path, pytestconfig: pytest.Config +): + conf = configuration( + pytestconfig, "objects", "typescript", tmp_path, f"{suite}.yaml", "correct" + ) + result = execute_config(conf) + updates = assert_valid_output(result, pytestconfig) + assert len(updates.find_all("start-testcase")) == 1 + assert updates.find_status_enum() == ["correct"] + @pytest.mark.parametrize("lang", ["java"]) def test_advanced_types_are_allowed( @@ -324,7 +356,7 @@ def test_batch_compilation_no_fallback_runtime( @pytest.mark.parametrize( - "lang", ["python", "java", "c", "javascript", "kotlin", "bash", "csharp"] + "lang", ["python", "java", "c", "javascript", "typescript", "kotlin", "bash", "csharp"] ) def test_program_params(lang: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration(pytestconfig, "sum", lang, tmp_path, "short.tson", "correct") @@ -336,7 +368,7 @@ def test_program_params(lang: str, tmp_path: Path, pytestconfig: pytest.Config): @pytest.mark.parametrize( - "language", ["python", "java", "kotlin", "javascript", "csharp"] + "language", ["python", "java", "kotlin", "javascript", "typescript", "csharp"] ) def test_objects(language: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( @@ -349,7 +381,7 @@ def test_objects(language: str, tmp_path: Path, pytestconfig: pytest.Config): @pytest.mark.parametrize( - "language", ["python", "java", "kotlin", "javascript", "csharp"] + "language", ["python", "java", "kotlin", "javascript", "typescript", "csharp"] ) def test_objects_chained(language: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( @@ -362,7 +394,7 @@ def test_objects_chained(language: str, tmp_path: Path, pytestconfig: pytest.Con @pytest.mark.parametrize( - "language", ["python", "java", "kotlin", "javascript", "csharp"] + "language", ["python", "java", "kotlin", "javascript", "typescript", "csharp"] ) def test_property_assignment( language: str, tmp_path: Path, pytestconfig: pytest.Config @@ -382,7 +414,7 @@ def test_property_assignment( @pytest.mark.parametrize( - "language", ["python", "java", "kotlin", "javascript", "csharp"] + "language", ["python", "java", "kotlin", "javascript", "typescript", "csharp"] ) def test_counter(language: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( @@ -395,7 +427,7 @@ def test_counter(language: str, tmp_path: Path, pytestconfig: pytest.Config): @pytest.mark.parametrize( - "language", ["python", "java", "kotlin", "javascript", "csharp"] + "language", ["python", "java", "kotlin", "javascript", "typescript", "csharp"] ) def test_counter_chained(language: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( @@ -408,7 +440,7 @@ def test_counter_chained(language: str, tmp_path: Path, pytestconfig: pytest.Con @pytest.mark.parametrize( - "language", ["python", "java", "kotlin", "javascript", "csharp"] + "language", ["python", "java", "kotlin", "javascript", "typescript", "csharp"] ) def test_objects_yaml(language: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( @@ -445,6 +477,7 @@ def test_objects_error(language: str, tmp_path: Path, pytestconfig: pytest.Confi ("java", ["internal error"]), ("c", ["internal error"]), ("javascript", ["correct"]), + ("typescript", ["correct"]), ("haskell", ["internal error"]), ("runhaskell", ["internal error"]), ], @@ -490,6 +523,7 @@ def test_timeouts_propagate_to_contexts(): ("csharp", '(Coords) {"X":5.5,"Y":7.5}'), ("java", "Coord[x=5, y=7]"), ("javascript", '(Coord) {"x":5,"y":7}'), + ("typescript", '(Coord) {"x":5,"y":7}'), ("kotlin", "Coord(x=5, y=6)"), ("python", "() Coord(x=5, y=6)"), ], @@ -622,7 +656,7 @@ def test_language_literals_work( # Check that the test suite is valid with a correct submission. # This test suite is used for the test below "test_output_in_script_is_caught". -@pytest.mark.parametrize("language", ["python", "javascript", "bash"]) +@pytest.mark.parametrize("language", ["python", "javascript", "typescript", "bash"]) def test_two_suite_is_valid(language: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, @@ -637,7 +671,7 @@ def test_two_suite_is_valid(language: str, tmp_path: Path, pytestconfig: pytest. assert updates.find_status_enum() == ["correct"] * 2 -@pytest.mark.parametrize("language", ["python", "javascript", "bash"]) +@pytest.mark.parametrize("language", ["python", "javascript", "typescript", "bash"]) def test_output_in_script_is_caught( language: str, tmp_path: Path, pytestconfig: pytest.Config ): From 07c07092a93bff7a4245c2dae91489b212b932d4 Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 14 Nov 2024 17:26:18 +0100 Subject: [PATCH 23/49] add test files --- .../exercises/isbn-list/solution/solution.js | 54 ++++++++++++ .../exercises/isbn-list/solution/solution.ts | 54 ++++++++++++ ...e-with-crashing-assignment-javascript.tson | 88 +++++++++++++++++++ tests/exercises/isbn/solution/solution.js | 54 ++++++++++++ 4 files changed, 250 insertions(+) create mode 100644 tests/exercises/isbn-list/solution/solution.js create mode 100644 tests/exercises/isbn-list/solution/solution.ts create mode 100644 tests/exercises/isbn/evaluation/one-with-crashing-assignment-javascript.tson create mode 100644 tests/exercises/isbn/solution/solution.js diff --git a/tests/exercises/isbn-list/solution/solution.js b/tests/exercises/isbn-list/solution/solution.js new file mode 100644 index 00000000..fad85c78 --- /dev/null +++ b/tests/exercises/isbn-list/solution/solution.js @@ -0,0 +1,54 @@ +function isIsbn10(code) { + + function checkDigit(code) { + let check = 0; + for (let i = 0; i < 9; i++) { + check += parseInt(code[i]) * (i + 1); + } + check %= 11; + return check === 10 ? 'X' : check.toString(); + } + + if (code.length !== 10) { + return false; + } + + if (isNaN(Number(code.substring(0, 9)))) { + return false; + } + + return code[9] === checkDigit(code); +} + + +function isIsbn13(code) { + + function checkDigit(code) { + let check = 0; + for (let i = 0; i < 12; i++) { + check += parseInt(code[i]) * (i % 2 === 0 ? 1 : 3); + } + return ((((10 - check) % 10) + 10) % 10).toString(); + } + + if (code.length !== 13) { + return false; + } + + if (isNaN(Number(code.substring(0, 12)))) { + return false; + } + + return code[12] === checkDigit(code); +} + +function isIsbn(code, isbn13=true) { + return isbn13 ? isIsbn13(code) : isIsbn10(code); +} + +function areIsbn(codes, isbn13=undefined) { + if (isbn13 === undefined) { + return codes.map((code) => typeof code === 'string' ? isIsbn(code, code.length === 13) : false); + } + return codes.map((code) => typeof code === 'string' ? isIsbn(code, isbn13) : false); +} diff --git a/tests/exercises/isbn-list/solution/solution.ts b/tests/exercises/isbn-list/solution/solution.ts new file mode 100644 index 00000000..263bdc91 --- /dev/null +++ b/tests/exercises/isbn-list/solution/solution.ts @@ -0,0 +1,54 @@ +function isIsbn10(code: string): boolean { + + function checkDigit(code: string) { + let check: number = 0; + for (let i = 0; i < 9; i++) { + check += parseInt(code[i]) * (i + 1); + } + check %= 11; + return check === 10 ? 'X' : check.toString(); + } + + if (code.length !== 10) { + return false; + } + + if (isNaN(Number(code.substring(0, 9)))) { + return false; + } + + return code[9] === checkDigit(code); +} + + +function isIsbn13(code: string): boolean { + + function checkDigit(code: string) { + let check: number = 0; + for (let i = 0; i < 12; i++) { + check += parseInt(code[i]) * (i % 2 === 0 ? 1 : 3); + } + return ((((10 - check) % 10) + 10) % 10).toString(); + } + + if (code.length !== 13) { + return false; + } + + if (isNaN(Number(code.substring(0, 12)))) { + return false; + } + + return code[12] === checkDigit(code); +} + +function isIsbn(code: string, isbn13: boolean=true): boolean { + return isbn13 ? isIsbn13(code) : isIsbn10(code); +} + +function areIsbn(codes: Array, isbn13: boolean | undefined=undefined): Array { + if (isbn13 === undefined) { + return codes.map((code:unknown) => typeof code === 'string' ? isIsbn(code, code.length === 13) : false); + } + return codes.map((code:unknown) => typeof code === 'string' ? isIsbn(code, isbn13) : false); +} diff --git a/tests/exercises/isbn/evaluation/one-with-crashing-assignment-javascript.tson b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-javascript.tson new file mode 100644 index 00000000..afc3b9b0 --- /dev/null +++ b/tests/exercises/isbn/evaluation/one-with-crashing-assignment-javascript.tson @@ -0,0 +1,88 @@ +{ + "tabs": [ + { + "name": "are_isbn", + "runs": [ + { + "contexts": [ + { + "before": { + "typescript": { + "data": "const ex = () => {throw new Error('AssertionError');};" + } + }, + "testcases": [ + { + "input": { + "type": "sequence", + "variable": "codes01", + "expression": { + "type": "function", + "namespace": "ex", + "name": "get", + "arguments": [] + } + } + }, + { + "input": { + "type": "function", + "name": "are_isbn", + "arguments": [ + "codes01" + ] + }, + "output": { + "result": { + "value": { + "data": [ + { + "data": false, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + }, + { + "data": true, + "type": "boolean" + }, + { + "data": false, + "type": "boolean" + } + ], + "type": "sequence" + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/tests/exercises/isbn/solution/solution.js b/tests/exercises/isbn/solution/solution.js new file mode 100644 index 00000000..fad85c78 --- /dev/null +++ b/tests/exercises/isbn/solution/solution.js @@ -0,0 +1,54 @@ +function isIsbn10(code) { + + function checkDigit(code) { + let check = 0; + for (let i = 0; i < 9; i++) { + check += parseInt(code[i]) * (i + 1); + } + check %= 11; + return check === 10 ? 'X' : check.toString(); + } + + if (code.length !== 10) { + return false; + } + + if (isNaN(Number(code.substring(0, 9)))) { + return false; + } + + return code[9] === checkDigit(code); +} + + +function isIsbn13(code) { + + function checkDigit(code) { + let check = 0; + for (let i = 0; i < 12; i++) { + check += parseInt(code[i]) * (i % 2 === 0 ? 1 : 3); + } + return ((((10 - check) % 10) + 10) % 10).toString(); + } + + if (code.length !== 13) { + return false; + } + + if (isNaN(Number(code.substring(0, 12)))) { + return false; + } + + return code[12] === checkDigit(code); +} + +function isIsbn(code, isbn13=true) { + return isbn13 ? isIsbn13(code) : isIsbn10(code); +} + +function areIsbn(codes, isbn13=undefined) { + if (isbn13 === undefined) { + return codes.map((code) => typeof code === 'string' ? isIsbn(code, code.length === 13) : false); + } + return codes.map((code) => typeof code === 'string' ? isIsbn(code, isbn13) : false); +} From 3602bb886693c1caef6cc822c9930f8814f1c53e Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 14 Nov 2024 17:26:26 +0100 Subject: [PATCH 24/49] add test files --- .../echo-function/solution/top-level-output.ts | 13 +++++++++++++ .../echo-function/solution/unknown-return-type.ts | 15 +++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tests/exercises/echo-function/solution/top-level-output.ts create mode 100644 tests/exercises/echo-function/solution/unknown-return-type.ts diff --git a/tests/exercises/echo-function/solution/top-level-output.ts b/tests/exercises/echo-function/solution/top-level-output.ts new file mode 100644 index 00000000..4325cd2c --- /dev/null +++ b/tests/exercises/echo-function/solution/top-level-output.ts @@ -0,0 +1,13 @@ +function echo(content: Object) { + return content; +} + +function noEcho(content: Object) { + // Do nothing. +} + +function toString(number: Object): string { + return number.toString(); +} + +console.log("This is top-level output"); diff --git a/tests/exercises/echo-function/solution/unknown-return-type.ts b/tests/exercises/echo-function/solution/unknown-return-type.ts new file mode 100644 index 00000000..85b5d277 --- /dev/null +++ b/tests/exercises/echo-function/solution/unknown-return-type.ts @@ -0,0 +1,15 @@ +class Coord { + + public x: number; + public y: number; + + constructor(x: number, y: number ) { + this.x = x; + this.y = y; + } + +} + +function echo(content: unknown) { + return new Coord(5, 7); +} From e28fdd1e68105c2f043e07966703b1ec47f4b2e3 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 15 Nov 2024 12:07:10 +0100 Subject: [PATCH 25/49] updated language quircks --- .../solution/correct-async.ts | 13 ++++ .../evaluation/typescript-object.yaml | 5 ++ .../echo-function/solution/correct-async.ts | 3 + .../solution/typescript-object.ts | 3 + .../evaluation/plan.yaml | 0 .../solution/correct-temp.js | 0 .../js-ts-exceptions/solution/correct-temp.ts | 4 ++ .../solution/correct.js | 0 .../js-ts-exceptions/solution/correct.ts | 2 + .../solution/wrong-message.js | 0 .../solution/wrong-message.ts | 4 ++ .../solution/wrong-null.js | 0 .../js-ts-exceptions/solution/wrong-null.ts | 1 + .../solution/wrong.js | 0 .../js-ts-exceptions/solution/wrong.ts | 1 + tests/test_language_quircks.py | 61 ++++++++++++++----- 16 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 tests/exercises/echo-function-file-input/solution/correct-async.ts create mode 100644 tests/exercises/echo-function/evaluation/typescript-object.yaml create mode 100644 tests/exercises/echo-function/solution/correct-async.ts create mode 100644 tests/exercises/echo-function/solution/typescript-object.ts rename tests/exercises/{js-exceptions => js-ts-exceptions}/evaluation/plan.yaml (100%) rename tests/exercises/{js-exceptions => js-ts-exceptions}/solution/correct-temp.js (100%) create mode 100644 tests/exercises/js-ts-exceptions/solution/correct-temp.ts rename tests/exercises/{js-exceptions => js-ts-exceptions}/solution/correct.js (100%) create mode 100644 tests/exercises/js-ts-exceptions/solution/correct.ts rename tests/exercises/{js-exceptions => js-ts-exceptions}/solution/wrong-message.js (100%) create mode 100644 tests/exercises/js-ts-exceptions/solution/wrong-message.ts rename tests/exercises/{js-exceptions => js-ts-exceptions}/solution/wrong-null.js (100%) create mode 100644 tests/exercises/js-ts-exceptions/solution/wrong-null.ts rename tests/exercises/{js-exceptions => js-ts-exceptions}/solution/wrong.js (100%) create mode 100644 tests/exercises/js-ts-exceptions/solution/wrong.ts diff --git a/tests/exercises/echo-function-file-input/solution/correct-async.ts b/tests/exercises/echo-function-file-input/solution/correct-async.ts new file mode 100644 index 00000000..a2a1a907 --- /dev/null +++ b/tests/exercises/echo-function-file-input/solution/correct-async.ts @@ -0,0 +1,13 @@ +import * as fs from "fs"; + +function echoFile(content: any) { + return new Promise((resolve: (value: unknown) => void, reject: (reason?: any) => void) => { + fs.readFile(content, {encoding:'utf8', flag:'r'}, (err: any, data: unknown) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }).then((c: string) => c.trim()); +} diff --git a/tests/exercises/echo-function/evaluation/typescript-object.yaml b/tests/exercises/echo-function/evaluation/typescript-object.yaml new file mode 100644 index 00000000..fb6eb476 --- /dev/null +++ b/tests/exercises/echo-function/evaluation/typescript-object.yaml @@ -0,0 +1,5 @@ +- tab: "My tab" + contexts: + - testcases: + - expression: 'echo("input-1")' + return: !object {} diff --git a/tests/exercises/echo-function/solution/correct-async.ts b/tests/exercises/echo-function/solution/correct-async.ts new file mode 100644 index 00000000..b95165c1 --- /dev/null +++ b/tests/exercises/echo-function/solution/correct-async.ts @@ -0,0 +1,3 @@ +async function echo(content: any) { + return content; +} diff --git a/tests/exercises/echo-function/solution/typescript-object.ts b/tests/exercises/echo-function/solution/typescript-object.ts new file mode 100644 index 00000000..2f83d9b6 --- /dev/null +++ b/tests/exercises/echo-function/solution/typescript-object.ts @@ -0,0 +1,3 @@ +function echo(_ignored: any) { + return Object.create(null); +} diff --git a/tests/exercises/js-exceptions/evaluation/plan.yaml b/tests/exercises/js-ts-exceptions/evaluation/plan.yaml similarity index 100% rename from tests/exercises/js-exceptions/evaluation/plan.yaml rename to tests/exercises/js-ts-exceptions/evaluation/plan.yaml diff --git a/tests/exercises/js-exceptions/solution/correct-temp.js b/tests/exercises/js-ts-exceptions/solution/correct-temp.js similarity index 100% rename from tests/exercises/js-exceptions/solution/correct-temp.js rename to tests/exercises/js-ts-exceptions/solution/correct-temp.js diff --git a/tests/exercises/js-ts-exceptions/solution/correct-temp.ts b/tests/exercises/js-ts-exceptions/solution/correct-temp.ts new file mode 100644 index 00000000..55cfdfa1 --- /dev/null +++ b/tests/exercises/js-ts-exceptions/solution/correct-temp.ts @@ -0,0 +1,4 @@ +throw { + name: "AssertionError", + message: "Valid exceptions" +} diff --git a/tests/exercises/js-exceptions/solution/correct.js b/tests/exercises/js-ts-exceptions/solution/correct.js similarity index 100% rename from tests/exercises/js-exceptions/solution/correct.js rename to tests/exercises/js-ts-exceptions/solution/correct.js diff --git a/tests/exercises/js-ts-exceptions/solution/correct.ts b/tests/exercises/js-ts-exceptions/solution/correct.ts new file mode 100644 index 00000000..4ccbb484 --- /dev/null +++ b/tests/exercises/js-ts-exceptions/solution/correct.ts @@ -0,0 +1,2 @@ + +throw new Error("Valid exceptions"); diff --git a/tests/exercises/js-exceptions/solution/wrong-message.js b/tests/exercises/js-ts-exceptions/solution/wrong-message.js similarity index 100% rename from tests/exercises/js-exceptions/solution/wrong-message.js rename to tests/exercises/js-ts-exceptions/solution/wrong-message.js diff --git a/tests/exercises/js-ts-exceptions/solution/wrong-message.ts b/tests/exercises/js-ts-exceptions/solution/wrong-message.ts new file mode 100644 index 00000000..17810d49 --- /dev/null +++ b/tests/exercises/js-ts-exceptions/solution/wrong-message.ts @@ -0,0 +1,4 @@ +throw { + "name": "AssertionError", + "boodschap": "Valid exceptions" +}; diff --git a/tests/exercises/js-exceptions/solution/wrong-null.js b/tests/exercises/js-ts-exceptions/solution/wrong-null.js similarity index 100% rename from tests/exercises/js-exceptions/solution/wrong-null.js rename to tests/exercises/js-ts-exceptions/solution/wrong-null.js diff --git a/tests/exercises/js-ts-exceptions/solution/wrong-null.ts b/tests/exercises/js-ts-exceptions/solution/wrong-null.ts new file mode 100644 index 00000000..37d3d14b --- /dev/null +++ b/tests/exercises/js-ts-exceptions/solution/wrong-null.ts @@ -0,0 +1 @@ +throw null; diff --git a/tests/exercises/js-exceptions/solution/wrong.js b/tests/exercises/js-ts-exceptions/solution/wrong.js similarity index 100% rename from tests/exercises/js-exceptions/solution/wrong.js rename to tests/exercises/js-ts-exceptions/solution/wrong.js diff --git a/tests/exercises/js-ts-exceptions/solution/wrong.ts b/tests/exercises/js-ts-exceptions/solution/wrong.ts new file mode 100644 index 00000000..1acaa8b3 --- /dev/null +++ b/tests/exercises/js-ts-exceptions/solution/wrong.ts @@ -0,0 +1 @@ +throw "Valid exceptions"; diff --git a/tests/test_language_quircks.py b/tests/test_language_quircks.py index 9af65f88..61c4ed88 100644 --- a/tests/test_language_quircks.py +++ b/tests/test_language_quircks.py @@ -36,6 +36,18 @@ def test_javascript_vanilla_object(tmp_path: Path, pytestconfig: pytest.Config): updates = assert_valid_output(result, pytestconfig) assert updates.find_status_enum() == ["correct"] +def test_typescript_vanilla_object(tmp_path: Path, pytestconfig: pytest.Config): + conf = configuration( + pytestconfig, + "echo-function", + "typescript", + tmp_path, + "typescript-object.yaml", + "typescript-object", + ) + result = execute_config(conf) + updates = assert_valid_output(result, pytestconfig) + assert updates.find_status_enum() == ["correct"] def test_python_input_prompt_is_ignored(tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( @@ -76,11 +88,12 @@ def test_haskell_function_arguments_without_brackets( ) -def test_javascript_exception_correct(tmp_path: Path, pytestconfig: pytest.Config): +@pytest.mark.parametrize("lang", ["javascript", "typescript"]) +def test_js_ts_exception_correct(lang:str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, - "js-exceptions", - "javascript", + "js-ts-exceptions", + lang, tmp_path, "plan.yaml", "correct", @@ -91,11 +104,12 @@ def test_javascript_exception_correct(tmp_path: Path, pytestconfig: pytest.Confi assert len(updates.find_all("append-message")) == 0 -def test_javascript_exception_correct_temp(tmp_path: Path, pytestconfig: pytest.Config): +@pytest.mark.parametrize("lang", ["javascript", "typescript"]) +def test_js_ts_exception_correct_temp(lang:str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, - "js-exceptions", - "javascript", + "js-ts-exceptions", + lang, tmp_path, "plan.yaml", "correct-temp", @@ -106,11 +120,12 @@ def test_javascript_exception_correct_temp(tmp_path: Path, pytestconfig: pytest. assert len(updates.find_all("append-message")) == 0 -def test_javascript_exception_wrong(tmp_path: Path, pytestconfig: pytest.Config): +@pytest.mark.parametrize("lang", ["javascript", "typescript"]) +def test_js_ts_exception_wrong(lang:str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, - "js-exceptions", - "javascript", + "js-ts-exceptions", + lang, tmp_path, "plan.yaml", "wrong", @@ -121,11 +136,12 @@ def test_javascript_exception_wrong(tmp_path: Path, pytestconfig: pytest.Config) assert len(updates.find_all("append-message")) == 1 -def test_javascript_exception_wrong_null(tmp_path: Path, pytestconfig: pytest.Config): +@pytest.mark.parametrize("lang", ["javascript", "typescript"]) +def test_js_ts_exception_wrong_null(lang:str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, - "js-exceptions", - "javascript", + "js-ts-exceptions", + lang, tmp_path, "plan.yaml", "wrong-null", @@ -136,13 +152,14 @@ def test_javascript_exception_wrong_null(tmp_path: Path, pytestconfig: pytest.Co assert len(updates.find_all("append-message")) == 0 -def test_javascript_exception_missing_message( - tmp_path: Path, pytestconfig: pytest.Config +@pytest.mark.parametrize("lang", ["javascript", "typescript"]) +def test_js_ts_exception_missing_message( + lang:str, tmp_path: Path, pytestconfig: pytest.Config ): conf = configuration( pytestconfig, - "js-exceptions", - "javascript", + "js-ts-exceptions", + lang, tmp_path, "plan.yaml", "wrong-message", @@ -163,3 +180,15 @@ def test_javascript_async(exercise: str, tmp_path: Path, pytestconfig: pytest.Co result = execute_config(conf) updates = assert_valid_output(result, pytestconfig) assert updates.find_status_enum() == ["correct"] + +@pytest.mark.parametrize("exercise", ["echo-function-file-input", "echo-function"]) +def test_typescript_async(exercise: str, tmp_path: Path, pytestconfig: pytest.Config): + conf = configuration( + pytestconfig, exercise, "typescript", tmp_path, "one.tson", "correct-async" + ) + workdir = Path(conf.resources).parent / "workdir" + if workdir.exists(): + shutil.copytree(workdir, tmp_path, dirs_exist_ok=True) + result = execute_config(conf) + updates = assert_valid_output(result, pytestconfig) + assert updates.find_status_enum() == ["correct"] From 82ca93e7e6ecf4d384c970ebf37c7b10fb056969 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 15 Nov 2024 12:32:25 +0100 Subject: [PATCH 26/49] updated oracle and problem statements tests --- tested/languages/typescript/linter.py | 1 - tests/test_functionality.py | 12 +++++++++--- tests/test_language_quircks.py | 19 ++++++++++++++----- tests/test_linters.py | 1 + tests/test_oracles_programmed.py | 8 ++++++-- tests/test_problem_statements.py | 15 +++++++++++++++ 6 files changed, 45 insertions(+), 11 deletions(-) diff --git a/tested/languages/typescript/linter.py b/tested/languages/typescript/linter.py index 590c6559..e3d719b2 100644 --- a/tested/languages/typescript/linter.py +++ b/tested/languages/typescript/linter.py @@ -14,7 +14,6 @@ def run_eslint( config: DodonaConfig, remaining: float ) -> tuple[list[Message], list[AnnotateCode]]: - """ Calls eslint to annotate submitted source code and adds resulting score and annotations to tab. diff --git a/tests/test_functionality.py b/tests/test_functionality.py index 88e74cc5..29b0beb5 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -105,7 +105,9 @@ def test_generic_exception_wrong_error( assert updates.find_status_enum() == ["wrong"] -@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp", "typescript", "javascript"]) +@pytest.mark.parametrize( + "lang", ["python", "java", "kotlin", "csharp", "typescript", "javascript"] +) def test_assignment_and_use_in_expression( lang: str, tmp_path: Path, pytestconfig: pytest.Config ): @@ -156,7 +158,9 @@ def test_assignment_and_use_in_expression_list( assert len(updates.find_all("start-test")) == 1 -@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "csharp", "typescript", "javascript"]) +@pytest.mark.parametrize( + "lang", ["python", "java", "kotlin", "csharp", "typescript", "javascript"] +) def test_crashing_assignment_with_before( lang: str, tmp_path: Path, pytestconfig: pytest.Config ): @@ -237,6 +241,7 @@ def test_missing_key_types_detected_js_dictionary( assert len(updates.find_all("start-testcase")) == 1 assert updates.find_status_enum() == ["correct"] + def test_missing_key_types_detected_ts_object( tmp_path: Path, pytestconfig: pytest.Config ): @@ -356,7 +361,8 @@ def test_batch_compilation_no_fallback_runtime( @pytest.mark.parametrize( - "lang", ["python", "java", "c", "javascript", "typescript", "kotlin", "bash", "csharp"] + "lang", + ["python", "java", "c", "javascript", "typescript", "kotlin", "bash", "csharp"], ) def test_program_params(lang: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration(pytestconfig, "sum", lang, tmp_path, "short.tson", "correct") diff --git a/tests/test_language_quircks.py b/tests/test_language_quircks.py index 61c4ed88..7ee0e5d2 100644 --- a/tests/test_language_quircks.py +++ b/tests/test_language_quircks.py @@ -36,6 +36,7 @@ def test_javascript_vanilla_object(tmp_path: Path, pytestconfig: pytest.Config): updates = assert_valid_output(result, pytestconfig) assert updates.find_status_enum() == ["correct"] + def test_typescript_vanilla_object(tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, @@ -49,6 +50,7 @@ def test_typescript_vanilla_object(tmp_path: Path, pytestconfig: pytest.Config): updates = assert_valid_output(result, pytestconfig) assert updates.find_status_enum() == ["correct"] + def test_python_input_prompt_is_ignored(tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, @@ -89,7 +91,9 @@ def test_haskell_function_arguments_without_brackets( @pytest.mark.parametrize("lang", ["javascript", "typescript"]) -def test_js_ts_exception_correct(lang:str, tmp_path: Path, pytestconfig: pytest.Config): +def test_js_ts_exception_correct( + lang: str, tmp_path: Path, pytestconfig: pytest.Config +): conf = configuration( pytestconfig, "js-ts-exceptions", @@ -105,7 +109,9 @@ def test_js_ts_exception_correct(lang:str, tmp_path: Path, pytestconfig: pytest. @pytest.mark.parametrize("lang", ["javascript", "typescript"]) -def test_js_ts_exception_correct_temp(lang:str, tmp_path: Path, pytestconfig: pytest.Config): +def test_js_ts_exception_correct_temp( + lang: str, tmp_path: Path, pytestconfig: pytest.Config +): conf = configuration( pytestconfig, "js-ts-exceptions", @@ -121,7 +127,7 @@ def test_js_ts_exception_correct_temp(lang:str, tmp_path: Path, pytestconfig: py @pytest.mark.parametrize("lang", ["javascript", "typescript"]) -def test_js_ts_exception_wrong(lang:str, tmp_path: Path, pytestconfig: pytest.Config): +def test_js_ts_exception_wrong(lang: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, "js-ts-exceptions", @@ -137,7 +143,9 @@ def test_js_ts_exception_wrong(lang:str, tmp_path: Path, pytestconfig: pytest.Co @pytest.mark.parametrize("lang", ["javascript", "typescript"]) -def test_js_ts_exception_wrong_null(lang:str, tmp_path: Path, pytestconfig: pytest.Config): +def test_js_ts_exception_wrong_null( + lang: str, tmp_path: Path, pytestconfig: pytest.Config +): conf = configuration( pytestconfig, "js-ts-exceptions", @@ -154,7 +162,7 @@ def test_js_ts_exception_wrong_null(lang:str, tmp_path: Path, pytestconfig: pyte @pytest.mark.parametrize("lang", ["javascript", "typescript"]) def test_js_ts_exception_missing_message( - lang:str, tmp_path: Path, pytestconfig: pytest.Config + lang: str, tmp_path: Path, pytestconfig: pytest.Config ): conf = configuration( pytestconfig, @@ -181,6 +189,7 @@ def test_javascript_async(exercise: str, tmp_path: Path, pytestconfig: pytest.Co updates = assert_valid_output(result, pytestconfig) assert updates.find_status_enum() == ["correct"] + @pytest.mark.parametrize("exercise", ["echo-function-file-input", "echo-function"]) def test_typescript_async(exercise: str, tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( diff --git a/tests/test_linters.py b/tests/test_linters.py index 332e3cfb..ff3037df 100644 --- a/tests/test_linters.py +++ b/tests/test_linters.py @@ -59,6 +59,7 @@ def test_eslint(tmp_path: Path, config: dict, pytestconfig: pytest.Config): updates = assert_valid_output(result, pytestconfig) assert len(updates.find_all("annotate-code")) > 0 + @pytest.mark.parametrize("config", _get_config_options("typescript")) def test_eslint_typescript(tmp_path: Path, config: dict, pytestconfig: pytest.Config): conf = configuration( diff --git a/tests/test_oracles_programmed.py b/tests/test_oracles_programmed.py index 166a721c..f3f491dd 100644 --- a/tests/test_oracles_programmed.py +++ b/tests/test_oracles_programmed.py @@ -105,7 +105,9 @@ def test_missing_custom_check_function(tmp_path: Path, pytestconfig: pytest.Conf assert len(updates.find_all("append-message")) == 4 -@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "javascript", "csharp"]) +@pytest.mark.parametrize( + "lang", ["python", "java", "kotlin", "javascript", "typescript", "csharp"] +) def test_custom_check_function_lotto_correct( lang: str, tmp_path: Path, pytestconfig: pytest.Config ): @@ -118,7 +120,9 @@ def test_custom_check_function_lotto_correct( assert updates.find_status_enum() == ["correct"] -@pytest.mark.parametrize("lang", ["python", "java", "kotlin", "javascript", "csharp"]) +@pytest.mark.parametrize( + "lang", ["python", "java", "kotlin", "javascript", "typescript", "csharp"] +) def test_custom_check_function_lotto_wrong( lang: str, tmp_path: Path, pytestconfig: pytest.Config ): diff --git a/tests/test_problem_statements.py b/tests/test_problem_statements.py index f4a095b3..fb161aa1 100644 --- a/tests/test_problem_statements.py +++ b/tests/test_problem_statements.py @@ -30,6 +30,7 @@ def test_small_descriptions(language: str): ("c", "this_is_a_function_name"), ("kotlin", "thisIsAFunctionName"), ("javascript", "thisIsAFunctionName"), + ("typescript", "thisIsAFunctionName"), ("haskell", "thisIsAFunctionName"), ("runhaskell", "thisIsAFunctionName"), ], @@ -71,6 +72,11 @@ def test_template_function_name(lang: str, expected: str): ("javascript", "'text'", "string"), ("javascript", '"sequence", "integer"', "array"), ("javascript", '"array", ("set", ("integer", ))', "array>"), + ("typescript", "'integer'", "number"), + ("typescript", "'real'", "number"), + ("typescript", "'text'", "string"), + ("typescript", '"sequence", "integer"', "array"), + ("typescript", '"array", ("set", ("integer", ))', "array>"), ("haskell", "'integer'", "Int"), ("haskell", "'real'", "Double"), ("haskell", "'text'", "String"), @@ -99,6 +105,8 @@ def test_template_type_name(lang: str, tested_type: Any, expected: str): ("kotlin", "'map'", "map"), ("javascript", "'sequence'", "sequence"), ("javascript", "'map'", "map"), + ("typescript", "'sequence'", "sequence"), + ("typescript", "'map'", "map"), ("haskell", "'sequence'", "sequence"), ("haskell", "'list'", "list"), ], @@ -120,6 +128,8 @@ def test_template_natural_type_name(lang: str, tested_type: Any, expected: str): ("kotlin", "'map'", "afbeelding"), ("javascript", "'sequence'", "sequentie"), ("javascript", "'map'", "afbeelding"), + ("typescript", "'sequence'", "sequentie"), + ("typescript", "'map'", "afbeelding"), ("haskell", "'sequence'", "sequentie"), ("haskell", "'list'", "lijst"), ], @@ -149,6 +159,10 @@ def test_template_natural_type_name_nl(lang: str, tested_type: Any, expected: st "javascript", "let random = new Random()\nrandom.newSequence(10, 10)\n[10, 5, 2, 8, 7, 1, 3, 4, 9, 6]", ), + ( + "typescript", + "let random = new Random()\nrandom.newSequence(10, 10)\n[10, 5, 2, 8, 7, 1, 3, 4, 9, 6]", + ), ], ) def test_template_statement_expression(lang: str, expected: str): @@ -167,6 +181,7 @@ def test_template_statement_expression(lang: str, expected: str): "c", "kotlin", "javascript", + "typescript", "haskell", ], ) From 0b78aba8b7224412b180b0bac371c9f61ab9f742 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 15 Nov 2024 13:04:18 +0100 Subject: [PATCH 27/49] updated stacktrace cleaner and ast tests --- tested/languages/typescript/config.py | 14 +++++---- tests/testTypeScriptAstParserFile.ts | 43 +++++++++++++++++++++++++++ tests/test_serialisation.py | 1 + tests/test_stacktrace_cleaners.py | 38 +++++++++++++++++++++++ tests/test_utils.py | 31 +++++++++++++++++++ 5 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 tests/testTypeScriptAstParserFile.ts diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 4e310bbd..71783984 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -112,14 +112,16 @@ def compilation(self, files: list[str], directory: Path) -> CallbackResult: # Create a config file to just that extends tsconfig. # This way it will only run tsc on the current file. - config_file = { - "extends": str(Path(__file__).parent / "tsconfig.json"), - "include": [f"{main_file[0]}"], - } - with open(str(directory / "tsconfig-sub.json"), "w") as file: - file.write(json.dumps(config_file, indent=4)) if main_file: + + config_file = { + "extends": str(Path(__file__).parent / "tsconfig.json"), + "include": [f"{main_file[0]}"], + } + with open(str(directory / "tsconfig-sub.json"), "w") as file: + file.write(json.dumps(config_file, indent=4)) + return ( [ "tsc", diff --git a/tests/testTypeScriptAstParserFile.ts b/tests/testTypeScriptAstParserFile.ts new file mode 100644 index 00000000..bae1efab --- /dev/null +++ b/tests/testTypeScriptAstParserFile.ts @@ -0,0 +1,43 @@ +// Test ObjectPattern +const {c, d} = {c: 5, d: 7}; +// Test Array Pattern +let [a, b] = ["alpha", "beta"]; +// Test normal variables +var x = 5, y = 6; +// Test first reassignment +x = y; + +// Test function +function demoFunction() { +} + +// Test simple class +class SimpleClass { + constructor() { + } +} + +// Test class with static variables +class StaticClass extends SimpleClass { + data = ["Static data"]; + constants; +} + +// Test try-catch +function tryCatch() { + try { + let demo = 5; + } catch { + // Do nothing + } +} + +// Test async function +async function asyncFunction() { + return 5; +} + +// Test second Reassignment +x = 5; +// Assignment to not defined var +z = x + y; diff --git a/tests/test_serialisation.py b/tests/test_serialisation.py index bf86a12c..8feec03b 100644 --- a/tests/test_serialisation.py +++ b/tests/test_serialisation.py @@ -62,6 +62,7 @@ "java", "c", "javascript", + "typescript", "kotlin", pytest.param("runhaskell", marks=pytest.mark.haskell), "bash", diff --git a/tests/test_stacktrace_cleaners.py b/tests/test_stacktrace_cleaners.py index 8b284c76..6c1cd844 100644 --- a/tests/test_stacktrace_cleaners.py +++ b/tests/test_stacktrace_cleaners.py @@ -55,6 +55,26 @@ def test_javascript_assertion_error(): actual_cleaned = language_config.cleanup_stacktrace(original) assert actual_cleaned == expected_cleaned +def test_typescript_assertion_error(): + workdir = "/home/bliep/bloep/universal-judge/workdir" + language_config = get_language(workdir, "typescript") + original = f"""AssertionError [ERR_ASSERTION]: ongeldig bericht + at bigram2letter ({workdir}/execution00/submission.ts:86:13) + at {workdir}/execution00/submission.ts:98:32 + at Array.map () + at Codeersleutel.decodeer ({workdir}/execution00/submission.ts:98:18) + at context0 ({workdir}/execution00/execution00.ts:78:31) + at async {workdir}/execution00/execution00.ts:1515:13 +""" + expected_cleaned = f"""AssertionError [ERR_ASSERTION]: ongeldig bericht + at bigram2letter (:86:13) + at :98:32 + at Array.map () + at Codeersleutel.decodeer (:98:18) +""" + actual_cleaned = language_config.cleanup_stacktrace(original) + assert actual_cleaned == expected_cleaned + def test_javascript_type_error(): workdir = "/home/bliep/bloep/universal-judge/workdir" @@ -74,6 +94,24 @@ def test_javascript_type_error(): actual_cleaned = language_config.cleanup_stacktrace(original) assert actual_cleaned == expected_cleaned +def test_typescript_type_error(): + workdir = "/home/bliep/bloep/universal-judge/workdir" + language_config = get_language(workdir, "typescript") + original = f"""TypeError: submission.Codeersleutel is not a constructor + at context0 ({workdir}/execution00/execution00.ts:46:17) + at {workdir}/execution00.ts:1515:19 + at Object. ({workdir}/execution00/execution00.ts:1573:7) + at Module._compile (node:internal/modules/cjs/loader:1254:14) + at Module._extensions..ts (node:internal/modules/cjs/loader:1308:10) + at Module.load (node:internal/modules/cjs/loader:1117:32) + at Module._load (node:internal/modules/cjs/loader:958:12) + at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) + at node:internal/main/run_main_module:23:47 + """ + expected_cleaned = f"TypeError: Codeersleutel is not a constructor\n" + actual_cleaned = language_config.cleanup_stacktrace(original) + assert actual_cleaned == expected_cleaned + @pytest.mark.parametrize("language", ALL_LANGUAGES) def test_empty_stacktrace(language): diff --git a/tests/test_utils.py b/tests/test_utils.py index ea4a00f5..a21fda91 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -44,6 +44,37 @@ def test_javascript_ast_parse(): namings = frozenset(output.stdout.strip().split(", ")) assert namings == expected +def test_typescript_ast_parse(): + expected = frozenset( + [ + "c", + "d", + "a", + "b", + "x", + "y", + "demoFunction", + "SimpleClass", + "StaticClass", + "tryCatch", + "z", + "asyncFunction", + ] + ) + from tested.judge.utils import run_command + + test_dir = Path(__file__).parent + parse_file = test_dir.parent / "tested" / "languages" / "typescript" / "parseAst.js" + demo_file = test_dir / "testTypeScriptAstParserFile.ts" + output = run_command( + demo_file.parent, + timeout=None, + command=["tsx", str(parse_file), str(demo_file.absolute())], + ) + assert output + namings = frozenset(output.stdout.strip().split(", ")) + assert namings == expected + def test_run_doctests_tested_utils(): import doctest From a792223eb1915eaf91a30a878081039b6a735027 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 15 Nov 2024 13:07:27 +0100 Subject: [PATCH 28/49] fixed linting issues --- tests/test_stacktrace_cleaners.py | 2 ++ tests/test_utils.py | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/test_stacktrace_cleaners.py b/tests/test_stacktrace_cleaners.py index 6c1cd844..ae75c416 100644 --- a/tests/test_stacktrace_cleaners.py +++ b/tests/test_stacktrace_cleaners.py @@ -55,6 +55,7 @@ def test_javascript_assertion_error(): actual_cleaned = language_config.cleanup_stacktrace(original) assert actual_cleaned == expected_cleaned + def test_typescript_assertion_error(): workdir = "/home/bliep/bloep/universal-judge/workdir" language_config = get_language(workdir, "typescript") @@ -94,6 +95,7 @@ def test_javascript_type_error(): actual_cleaned = language_config.cleanup_stacktrace(original) assert actual_cleaned == expected_cleaned + def test_typescript_type_error(): workdir = "/home/bliep/bloep/universal-judge/workdir" language_config = get_language(workdir, "typescript") diff --git a/tests/test_utils.py b/tests/test_utils.py index a21fda91..d97dff96 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -44,6 +44,7 @@ def test_javascript_ast_parse(): namings = frozenset(output.stdout.strip().split(", ")) assert namings == expected + def test_typescript_ast_parse(): expected = frozenset( [ From f083c6039b297b1af017f49dd18929798445b208 Mon Sep 17 00:00:00 2001 From: breblanc Date: Wed, 20 Nov 2024 16:43:31 +0100 Subject: [PATCH 29/49] Tried using command-line arguments --- tested/languages/typescript/config.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 71783984..334164e2 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -122,11 +122,21 @@ def compilation(self, files: list[str], directory: Path) -> CallbackResult: with open(str(directory / "tsconfig-sub.json"), "w") as file: file.write(json.dumps(config_file, indent=4)) + path_to_modules = os.environ['NODE_PATH'] return ( [ "tsc", - "--project", - "tsconfig-sub.json", + "--target", + "esnext", + "--module", + "nodenext", + "--allowJs", + "--allowImportingTsExtensions", + "--noEmit", + "--esModuleInterop", + "--typeRoots", + f"{path_to_modules}/@types", + main_file[0], ], files, ) From 6f4ee94354a5b7751211cc17465b2ea291ae823b Mon Sep 17 00:00:00 2001 From: breblanc Date: Wed, 20 Nov 2024 17:36:26 +0100 Subject: [PATCH 30/49] Cleaned up code. --- tested/languages/typescript/config.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 334164e2..76225c69 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -115,13 +115,6 @@ def compilation(self, files: list[str], directory: Path) -> CallbackResult: if main_file: - config_file = { - "extends": str(Path(__file__).parent / "tsconfig.json"), - "include": [f"{main_file[0]}"], - } - with open(str(directory / "tsconfig-sub.json"), "w") as file: - file.write(json.dumps(config_file, indent=4)) - path_to_modules = os.environ['NODE_PATH'] return ( [ From 59a3f1100599abf661188c2f7115affbfbe8e069 Mon Sep 17 00:00:00 2001 From: breblanc Date: Wed, 20 Nov 2024 17:45:01 +0100 Subject: [PATCH 31/49] Revert "Cleaned up code." This reverts commit 6f4ee94354a5b7751211cc17465b2ea291ae823b. --- tested/languages/typescript/config.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 76225c69..334164e2 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -115,6 +115,13 @@ def compilation(self, files: list[str], directory: Path) -> CallbackResult: if main_file: + config_file = { + "extends": str(Path(__file__).parent / "tsconfig.json"), + "include": [f"{main_file[0]}"], + } + with open(str(directory / "tsconfig-sub.json"), "w") as file: + file.write(json.dumps(config_file, indent=4)) + path_to_modules = os.environ['NODE_PATH'] return ( [ From cca868b28aaf6086716a15cb390fb602c2e772be Mon Sep 17 00:00:00 2001 From: breblanc Date: Wed, 20 Nov 2024 17:46:13 +0100 Subject: [PATCH 32/49] Revert "Tried using command-line arguments" This reverts commit f083c6039b297b1af017f49dd18929798445b208. --- tested/languages/typescript/config.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 334164e2..71783984 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -122,21 +122,11 @@ def compilation(self, files: list[str], directory: Path) -> CallbackResult: with open(str(directory / "tsconfig-sub.json"), "w") as file: file.write(json.dumps(config_file, indent=4)) - path_to_modules = os.environ['NODE_PATH'] return ( [ "tsc", - "--target", - "esnext", - "--module", - "nodenext", - "--allowJs", - "--allowImportingTsExtensions", - "--noEmit", - "--esModuleInterop", - "--typeRoots", - f"{path_to_modules}/@types", - main_file[0], + "--project", + "tsconfig-sub.json", ], files, ) From dd50af7ed12ab314a5c1d62c8f757c749343994f Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 21 Nov 2024 10:01:27 +0100 Subject: [PATCH 33/49] switched content position in docker --- .devcontainer/dodona-tested.dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index f538dcdb..8b0b8a43 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -78,15 +78,15 @@ RUN < Date: Thu, 21 Nov 2024 10:46:53 +0100 Subject: [PATCH 34/49] tried adding path --- .devcontainer/dodona-tested.dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index 8b0b8a43..67e2be96 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -93,6 +93,7 @@ RUN < Date: Thu, 21 Nov 2024 15:07:00 +0100 Subject: [PATCH 35/49] Changed usage of tsc. Keep csharp error for what it is for now --- .devcontainer/dodona-tested.dockerfile | 1 - tested/judge/compilation.py | 2 +- tested/judge/core.py | 1 + tested/languages/bash/config.py | 2 +- tested/languages/c/config.py | 2 +- tested/languages/csharp/config.py | 2 +- tested/languages/haskell/config.py | 2 +- tested/languages/java/config.py | 2 +- tested/languages/javascript/config.py | 2 +- tested/languages/kotlin/config.py | 2 +- tested/languages/language.py | 2 +- tested/languages/python/config.py | 2 +- tested/languages/runhaskell/config.py | 2 +- tested/languages/typescript/config.py | 27 +++++++++++------------ tested/languages/typescript/tsconfig.json | 17 -------------- 15 files changed, 25 insertions(+), 43 deletions(-) delete mode 100644 tested/languages/typescript/tsconfig.json diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index 67e2be96..8b0b8a43 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -93,7 +93,6 @@ RUN < bool: def submission_file(self) -> str: return submission_name(self) - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) if main_file: diff --git a/tested/languages/c/config.py b/tested/languages/c/config.py index 114eddbd..cb7c8e6a 100644 --- a/tested/languages/c/config.py +++ b/tested/languages/c/config.py @@ -72,7 +72,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "double_extended": "supported", } - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: main_file = files[-1] exec_file = Path(main_file).stem result = executable_name(exec_file) diff --git a/tested/languages/csharp/config.py b/tested/languages/csharp/config.py index e80978d4..1f97335c 100644 --- a/tested/languages/csharp/config.py +++ b/tested/languages/csharp/config.py @@ -97,7 +97,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: "tuple": "supported", } - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: # In C#, all output files are located in a subdirectory, so we just # want to copy over the subdirectory. def file_filter(file: Path) -> bool: diff --git a/tested/languages/haskell/config.py b/tested/languages/haskell/config.py index 7c2e4a91..7b45168d 100644 --- a/tested/languages/haskell/config.py +++ b/tested/languages/haskell/config.py @@ -81,7 +81,7 @@ def supported_constructs(self) -> set[Construct]: Construct.GLOBAL_VARIABLES, } - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: main_ = files[-1] exec_ = main_.rstrip(".hs") assert self.config diff --git a/tested/languages/java/config.py b/tested/languages/java/config.py index 688cba04..72c08538 100644 --- a/tested/languages/java/config.py +++ b/tested/languages/java/config.py @@ -126,7 +126,7 @@ def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: BasicSequenceTypes.SET: restrictions, } - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: def file_filter(file: Path) -> bool: return file.suffix == ".class" diff --git a/tested/languages/javascript/config.py b/tested/languages/javascript/config.py index 14e2e9d1..c9eb9a3b 100644 --- a/tested/languages/javascript/config.py +++ b/tested/languages/javascript/config.py @@ -103,7 +103,7 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: return {AdvancedObjectTypes.OBJECT: {BasicStringTypes.TEXT}} - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) if main_file: diff --git a/tested/languages/kotlin/config.py b/tested/languages/kotlin/config.py index 3ca46297..4610fd3e 100644 --- a/tested/languages/kotlin/config.py +++ b/tested/languages/kotlin/config.py @@ -136,7 +136,7 @@ def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: BasicSequenceTypes.SET: restrictions, # type: ignore } - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: def file_filter(file: Path) -> bool: return file.suffix == ".class" diff --git a/tested/languages/language.py b/tested/languages/language.py index 549f2548..1b9f5f57 100644 --- a/tested/languages/language.py +++ b/tested/languages/language.py @@ -74,7 +74,7 @@ def __init__(self, config: Optional["GlobalConfig"]): """ self.config = config - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: """ Callback for generating the compilation command. diff --git a/tested/languages/python/config.py b/tested/languages/python/config.py index 0ae659db..cb495054 100644 --- a/tested/languages/python/config.py +++ b/tested/languages/python/config.py @@ -119,7 +119,7 @@ def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: BasicSequenceTypes.SET: restrictions, # type: ignore } - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: result = [x.replace(".py", ".pyc") for x in files] return [ _executable(), diff --git a/tested/languages/runhaskell/config.py b/tested/languages/runhaskell/config.py index b5b8124b..69d9b174 100644 --- a/tested/languages/runhaskell/config.py +++ b/tested/languages/runhaskell/config.py @@ -6,7 +6,7 @@ class RunHaskell(Haskell): - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) if main_file: diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 71783984..55fa0aff 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -106,27 +106,26 @@ def datatype_support(self) -> dict[AllTypes, TypeSupport]: def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: return {AdvancedObjectTypes.OBJECT: {BasicStringTypes.TEXT}} - def compilation(self, files: list[str], directory: Path) -> CallbackResult: + def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) main_file = list(filter(lambda x: x == submission, files)) - # Create a config file to just that extends tsconfig. - # This way it will only run tsc on the current file. - if main_file: - - config_file = { - "extends": str(Path(__file__).parent / "tsconfig.json"), - "include": [f"{main_file[0]}"], - } - with open(str(directory / "tsconfig-sub.json"), "w") as file: - file.write(json.dumps(config_file, indent=4)) - + path_to_modules = os.environ['NODE_PATH'] return ( [ "tsc", - "--project", - "tsconfig-sub.json", + "--target", + "esnext", + "--module", + "nodenext", + "--allowJs", + "--allowImportingTsExtensions", + "--noEmit", + "--esModuleInterop", + "--typeRoots", + f"{path_to_modules}/@types", + main_file[0], ], files, ) diff --git a/tested/languages/typescript/tsconfig.json b/tested/languages/typescript/tsconfig.json deleted file mode 100644 index 3881d16c..00000000 --- a/tested/languages/typescript/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "target": "es6", - "allowJs": true, - "skipLibCheck": true, - "strict": false, - "noEmit": true, - "allowImportingTsExtensions": true, - "typeRoots": [ - "/usr/lib/node_modules/@types"], - "noResolve": false, - "esModuleInterop": true, - "moduleResolution": "node" - }, - "include" : [] -} From 8cd5a86cfb0b52dde3be282db37eb5b3be9c69bf Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 21 Nov 2024 15:46:27 +0100 Subject: [PATCH 36/49] Cleaned up my code some more --- .../languages/csharp/templates/dotnet.csproj | 28 +++++++++---------- tested/languages/typescript/config.py | 8 +++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tested/languages/csharp/templates/dotnet.csproj b/tested/languages/csharp/templates/dotnet.csproj index e6451ca0..57bbf6e8 100644 --- a/tested/languages/csharp/templates/dotnet.csproj +++ b/tested/languages/csharp/templates/dotnet.csproj @@ -1,14 +1,14 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 55fa0aff..1f4540b8 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -11,7 +11,7 @@ BasicStringTypes, ExpressionTypes, ) -from tested.dodona import AnnotateCode, Message +from tested.dodona import AnnotateCode, Message, Status from tested.features import Construct, TypeSupport from tested.languages.conventionalize import ( EXECUTION_PREFIX, @@ -108,9 +108,9 @@ def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) - main_file = list(filter(lambda x: x == submission, files)) + main_file = self.find_main_file(list(map(lambda name: Path(name), files)), submission) - if main_file: + if main_file != Status.COMPILATION_ERROR: path_to_modules = os.environ['NODE_PATH'] return ( [ @@ -125,7 +125,7 @@ def compilation(self, files: list[str]) -> CallbackResult: "--esModuleInterop", "--typeRoots", f"{path_to_modules}/@types", - main_file[0], + main_file, ], files, ) From cdd32814f11dfac67a1afdc874b7806d3398b86d Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 21 Nov 2024 16:27:51 +0100 Subject: [PATCH 37/49] fixed typing, linting and removed old print --- tested/languages/typescript/config.py | 8 +++++--- tested/languages/typescript/linter.py | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tested/languages/typescript/config.py b/tested/languages/typescript/config.py index 1f4540b8..18ebca24 100644 --- a/tested/languages/typescript/config.py +++ b/tested/languages/typescript/config.py @@ -108,10 +108,12 @@ def collection_restrictions(self) -> dict[AllTypes, set[ExpressionTypes]]: def compilation(self, files: list[str]) -> CallbackResult: submission = submission_file(self) - main_file = self.find_main_file(list(map(lambda name: Path(name), files)), submission) + main_file = self.find_main_file( + list(map(lambda name: Path(name), files)), submission + ) if main_file != Status.COMPILATION_ERROR: - path_to_modules = os.environ['NODE_PATH'] + path_to_modules = os.environ["NODE_PATH"] return ( [ "tsc", @@ -125,7 +127,7 @@ def compilation(self, files: list[str]) -> CallbackResult: "--esModuleInterop", "--typeRoots", f"{path_to_modules}/@types", - main_file, + str(main_file.name), ], files, ) diff --git a/tested/languages/typescript/linter.py b/tested/languages/typescript/linter.py index e3d719b2..f15ffec7 100644 --- a/tested/languages/typescript/linter.py +++ b/tested/languages/typescript/linter.py @@ -41,7 +41,6 @@ def run_eslint( str(submission.absolute()), ], ) - print(execution_results) if execution_results is None: return [], [] From aaaa29ab68a7da546dbf94b139ba496082e4269f Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 21 Nov 2024 20:10:30 +0100 Subject: [PATCH 38/49] Made some changes to the code as suggested by copilot --- tested/languages/typescript/templates/values.ts | 2 +- .../echo-function-additional-source-files/solution/correct.ts | 3 +-- .../echo-function-additional-source-files/workdir/echo.ts | 2 +- .../echo-function-file-input/solution/correct-async.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tested/languages/typescript/templates/values.ts b/tested/languages/typescript/templates/values.ts index 0a3add92..97a3b2ad 100644 --- a/tested/languages/typescript/templates/values.ts +++ b/tested/languages/typescript/templates/values.ts @@ -47,7 +47,7 @@ function encode(value: Object): { data: Object; diagnostic: any; type: string } // Handle holes in arrays... const unholed = []; for (let i = 0; i < value.length; i++) { - if (!value.hasOwnProperty(i)) { + if (!(i in value)) { unholed.push(``) } else { unholed.push(value[i]); diff --git a/tests/exercises/echo-function-additional-source-files/solution/correct.ts b/tests/exercises/echo-function-additional-source-files/solution/correct.ts index 7d4a970d..ee38b7dd 100644 --- a/tests/exercises/echo-function-additional-source-files/solution/correct.ts +++ b/tests/exercises/echo-function-additional-source-files/solution/correct.ts @@ -1,6 +1,5 @@ -// @ts-ignore import * as e from "./echo.ts"; -function echo(content) { +function echo(content: Object): Object { return e.echo(content); } diff --git a/tests/exercises/echo-function-additional-source-files/workdir/echo.ts b/tests/exercises/echo-function-additional-source-files/workdir/echo.ts index f8f72077..177b2c36 100644 --- a/tests/exercises/echo-function-additional-source-files/workdir/echo.ts +++ b/tests/exercises/echo-function-additional-source-files/workdir/echo.ts @@ -1,4 +1,4 @@ -function echo(content) { +function echo(content: Object): Object { return content; } diff --git a/tests/exercises/echo-function-file-input/solution/correct-async.ts b/tests/exercises/echo-function-file-input/solution/correct-async.ts index a2a1a907..dca36c6c 100644 --- a/tests/exercises/echo-function-file-input/solution/correct-async.ts +++ b/tests/exercises/echo-function-file-input/solution/correct-async.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -function echoFile(content: any) { +function echoFile(content: Object) { return new Promise((resolve: (value: unknown) => void, reject: (reason?: any) => void) => { fs.readFile(content, {encoding:'utf8', flag:'r'}, (err: any, data: unknown) => { if (err) { From a3897fd90ca938ce87921e3b46873e56c1a1d236 Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 21 Nov 2024 20:29:21 +0100 Subject: [PATCH 39/49] testing dotnet availability. --- .devcontainer/dodona-tested.dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index 8b0b8a43..abfc3f14 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -118,5 +118,6 @@ EOF USER runner WORKDIR /home/runner/workdir +RUN dotnet --version COPY main.sh /main.sh From 487f9ae08b3e0c0f3253c4087e529ed06d4ba25e Mon Sep 17 00:00:00 2001 From: breblanc Date: Thu, 21 Nov 2024 20:40:54 +0100 Subject: [PATCH 40/49] Potential fix? --- .devcontainer/dodona-tested.dockerfile | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index abfc3f14..a38844ac 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -65,6 +65,17 @@ RUN < Date: Thu, 21 Nov 2024 21:31:37 +0100 Subject: [PATCH 41/49] fixed test --- .devcontainer/dodona-tested.dockerfile | 2 -- .../echo-function-file-input/solution/correct-async.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index a38844ac..f71fcbb4 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -65,8 +65,6 @@ RUN < void, reject: (reason?: any) => void) => { fs.readFile(content, {encoding:'utf8', flag:'r'}, (err: any, data: unknown) => { if (err) { From 319ed02d93a73b855bd5820e37a51901f7241453 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 22 Nov 2024 09:58:19 +0100 Subject: [PATCH 42/49] Check what's wrong with linter on github --- tested/languages/typescript/linter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tested/languages/typescript/linter.py b/tested/languages/typescript/linter.py index f15ffec7..7d5f9d8a 100644 --- a/tested/languages/typescript/linter.py +++ b/tested/languages/typescript/linter.py @@ -41,6 +41,7 @@ def run_eslint( str(submission.absolute()), ], ) + logger.warning(f"ESLint produced {execution_results}") if execution_results is None: return [], [] From 0bf135ee46f6f751a78dbab31e6894cb79fc0756 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 22 Nov 2024 10:38:39 +0100 Subject: [PATCH 43/49] potential fix for linter --- tested/languages/typescript/eslintrc.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tested/languages/typescript/eslintrc.yml b/tested/languages/typescript/eslintrc.yml index bea959cc..89c23c8f 100644 --- a/tested/languages/typescript/eslintrc.yml +++ b/tested/languages/typescript/eslintrc.yml @@ -12,8 +12,4 @@ env: rules: no-var: "warn" semi: "warn" - # Disable unused vars, otherwise written classes, functions and variables will be detected as unused, - # even when the student is expected to write these classes, functions and variables - # TODO: Add module.export before linting, to allow detecting unused vars in classes and functions - no-unused-vars: ["off", { "vars": "local", "args": "after-used", "ignoreRestSiblings": false }] From a7acf7d8e9f4fe7018f237f7ea233cc1b172502c Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 22 Nov 2024 12:22:07 +0100 Subject: [PATCH 44/49] potential fix for linter (again) --- tested/languages/typescript/eslintrc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/tested/languages/typescript/eslintrc.yml b/tested/languages/typescript/eslintrc.yml index 89c23c8f..ccb38a66 100644 --- a/tested/languages/typescript/eslintrc.yml +++ b/tested/languages/typescript/eslintrc.yml @@ -12,4 +12,5 @@ env: rules: no-var: "warn" semi: "warn" + '@typescript-eslint/no-unused-expressions': "off" From 60146bb06a7d65235112780b7c85e3e6583b8cb7 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 22 Nov 2024 20:03:45 +0100 Subject: [PATCH 45/49] Removed redundant code lines --- .../languages/javascript/templates/values.js | 25 ++++++------------- tested/languages/language.py | 3 --- tested/languages/typescript/linter.py | 1 - .../languages/typescript/templates/values.ts | 23 +++++------------ 4 files changed, 13 insertions(+), 39 deletions(-) diff --git a/tested/languages/javascript/templates/values.js b/tested/languages/javascript/templates/values.js index d6a1a781..99a7eb44 100644 --- a/tested/languages/javascript/templates/values.js +++ b/tested/languages/javascript/templates/values.js @@ -118,24 +118,13 @@ function sendException(stream, exception) { "type": exception.constructor.name })); } else { - // Temporarily allow objects with "message" and "name". - // TODO: remove this once the semester is over - // noinspection PointlessBooleanExpressionJS - if (typeof exception === 'object') { - fs.writeSync(stream, JSON.stringify({ - "message": exception.message ?? "", - "stacktrace": "", - "type": exception.name ?? "" - })); - } else { - // We have something else, so we cannot rely on stuff being present. - fs.writeSync(stream, JSON.stringify({ - "message": JSON.stringify(exception), - "stacktrace": "", - "type": exception.constructor.name ?? (Object.prototype.toString.call(exception)), - "additional_message_keys": ["languages.javascript.runtime.invalid.exception"] - })); - } + // We have something else, so we cannot rely on stuff being present. + fs.writeSync(stream, JSON.stringify({ + "message": JSON.stringify(exception), + "stacktrace": "", + "type": exception.constructor.name ?? (Object.prototype.toString.call(exception)), + "additional_message_keys": ["languages.javascript.runtime.invalid.exception"] + })); } } diff --git a/tested/languages/language.py b/tested/languages/language.py index 1b9f5f57..a6432f82 100644 --- a/tested/languages/language.py +++ b/tested/languages/language.py @@ -138,9 +138,6 @@ def compilation(self, files: list[str]) -> CallbackResult: :param files: A suggestion containing the dependencies TESTed thinks might be useful to compile. By convention, the last file in the list is the file containing the "main" function. - - :param directory: The directory in which these files can be found. - :return: The compilation command and either the resulting files or a filter for the resulting files. """ diff --git a/tested/languages/typescript/linter.py b/tested/languages/typescript/linter.py index 7d5f9d8a..f15ffec7 100644 --- a/tested/languages/typescript/linter.py +++ b/tested/languages/typescript/linter.py @@ -41,7 +41,6 @@ def run_eslint( str(submission.absolute()), ], ) - logger.warning(f"ESLint produced {execution_results}") if execution_results is None: return [], [] diff --git a/tested/languages/typescript/templates/values.ts b/tested/languages/typescript/templates/values.ts index 97a3b2ad..34c34bb9 100644 --- a/tested/languages/typescript/templates/values.ts +++ b/tested/languages/typescript/templates/values.ts @@ -117,24 +117,13 @@ function sendException(stream: number, exception: Error | Object | {constructor: })); } else { // Comes out of the values.js: - // Temporarily allow objects with "message" and "name". - // TODO: remove this once the semester is over - // noinspection PointlessBooleanExpressionJS - if (typeof exception === 'object') { - fs.writeSync(stream, JSON.stringify({ - "message": (exception as Error).message ?? "", - "stacktrace": "", - "type": (exception as Error).name ?? "" - })); - } else { // We have something else, so we cannot rely on stuff being present. - fs.writeSync(stream, JSON.stringify({ - "message": JSON.stringify(exception), - "stacktrace": "", - "type": (exception as Object).constructor.name ?? (Object.prototype.toString.call(exception)), - "additional_message_keys": ["languages.typescript.runtime.invalid.exception"] - })); - } + fs.writeSync(stream, JSON.stringify({ + "message": JSON.stringify(exception), + "stacktrace": "", + "type": (exception as Object).constructor.name ?? (Object.prototype.toString.call(exception)), + "additional_message_keys": ["languages.typescript.runtime.invalid.exception"] + })); } } From 23c3e4e934703b43a585224701dc941346039181 Mon Sep 17 00:00:00 2001 From: breblanc Date: Fri, 22 Nov 2024 20:32:47 +0100 Subject: [PATCH 46/49] reverting changes for js --- .../languages/javascript/templates/values.js | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tested/languages/javascript/templates/values.js b/tested/languages/javascript/templates/values.js index 99a7eb44..d6a1a781 100644 --- a/tested/languages/javascript/templates/values.js +++ b/tested/languages/javascript/templates/values.js @@ -118,13 +118,24 @@ function sendException(stream, exception) { "type": exception.constructor.name })); } else { - // We have something else, so we cannot rely on stuff being present. - fs.writeSync(stream, JSON.stringify({ - "message": JSON.stringify(exception), - "stacktrace": "", - "type": exception.constructor.name ?? (Object.prototype.toString.call(exception)), - "additional_message_keys": ["languages.javascript.runtime.invalid.exception"] - })); + // Temporarily allow objects with "message" and "name". + // TODO: remove this once the semester is over + // noinspection PointlessBooleanExpressionJS + if (typeof exception === 'object') { + fs.writeSync(stream, JSON.stringify({ + "message": exception.message ?? "", + "stacktrace": "", + "type": exception.name ?? "" + })); + } else { + // We have something else, so we cannot rely on stuff being present. + fs.writeSync(stream, JSON.stringify({ + "message": JSON.stringify(exception), + "stacktrace": "", + "type": exception.constructor.name ?? (Object.prototype.toString.call(exception)), + "additional_message_keys": ["languages.javascript.runtime.invalid.exception"] + })); + } } } From b1e604389e8eed520fee711db3bb26be70ecfbd2 Mon Sep 17 00:00:00 2001 From: breblanc Date: Sat, 23 Nov 2024 09:57:52 +0100 Subject: [PATCH 47/49] removed test for ts --- tests/test_language_quircks.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_language_quircks.py b/tests/test_language_quircks.py index 7ee0e5d2..9b5c1392 100644 --- a/tests/test_language_quircks.py +++ b/tests/test_language_quircks.py @@ -108,14 +108,11 @@ def test_js_ts_exception_correct( assert len(updates.find_all("append-message")) == 0 -@pytest.mark.parametrize("lang", ["javascript", "typescript"]) -def test_js_ts_exception_correct_temp( - lang: str, tmp_path: Path, pytestconfig: pytest.Config -): +def test_javascript_exception_correct_temp(tmp_path: Path, pytestconfig: pytest.Config): conf = configuration( pytestconfig, "js-ts-exceptions", - lang, + "javascript", tmp_path, "plan.yaml", "correct-temp", From ba671b9149b24afe59bfc3f864c1e0211da0c84c Mon Sep 17 00:00:00 2001 From: breblanc Date: Mon, 25 Nov 2024 20:23:26 +0100 Subject: [PATCH 48/49] removed old check in docker --- .devcontainer/dodona-tested.dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index f71fcbb4..a74bcc22 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -118,6 +118,5 @@ EOF USER runner WORKDIR /home/runner/workdir -RUN dotnet --version COPY main.sh /main.sh From f507efcd01855c7d109ff85f4ce80ed93629fc64 Mon Sep 17 00:00:00 2001 From: breblanc Date: Mon, 25 Nov 2024 20:55:51 +0100 Subject: [PATCH 49/49] removed save-dev --- .devcontainer/dodona-tested.dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/dodona-tested.dockerfile b/.devcontainer/dodona-tested.dockerfile index a74bcc22..d7172452 100644 --- a/.devcontainer/dodona-tested.dockerfile +++ b/.devcontainer/dodona-tested.dockerfile @@ -72,7 +72,7 @@ RUN <